From 45ccb9909ebb13c146bb12f2171065e586105450 Mon Sep 17 00:00:00 2001 From: CookieCat Date: Sun, 27 Aug 2023 18:46:20 -0400 Subject: [PATCH] duh --- worlds/ahit/Items.py | 190 ++++++++++ worlds/ahit/Locations.py | 682 +++++++++++++++++++++++++++++++++++ worlds/ahit/Options.py | 517 ++++++++++++++++++++++++++ worlds/ahit/Regions.py | 760 +++++++++++++++++++++++++++++++++++++++ worlds/ahit/Rules.py | 682 +++++++++++++++++++++++++++++++++++ worlds/ahit/Types.py | 28 ++ worlds/ahit/__init__.py | 276 ++++++++++++++ 7 files changed, 3135 insertions(+) create mode 100644 worlds/ahit/Items.py create mode 100644 worlds/ahit/Locations.py create mode 100644 worlds/ahit/Options.py create mode 100644 worlds/ahit/Regions.py create mode 100644 worlds/ahit/Rules.py create mode 100644 worlds/ahit/Types.py create mode 100644 worlds/ahit/__init__.py diff --git a/worlds/ahit/Items.py b/worlds/ahit/Items.py new file mode 100644 index 0000000000..7c9b4297d3 --- /dev/null +++ b/worlds/ahit/Items.py @@ -0,0 +1,190 @@ +from BaseClasses import Item, ItemClassification +from worlds.AutoWorld import World +from .Types import HatDLC +import typing + + +class ItemData(typing.NamedTuple): + code: typing.Optional[int] + classification: ItemClassification + dlc_flags: typing.Optional[HatDLC] = HatDLC.none + + +class HatInTimeItem(Item): + game: str = "A Hat in Time" + + +def item_dlc_enabled(world: World, name: str) -> bool: + data = item_table[name] + + if data.dlc_flags == HatDLC.none: + return True + elif data.dlc_flags == HatDLC.dlc1 and world.multiworld.EnableDLC1[world.player].value > 0: + return True + elif data.dlc_flags == HatDLC.dlc2 and world.multiworld.EnableDLC2[world.player].value > 0: + return True + elif data.dlc_flags == HatDLC.death_wish and world.multiworld.EnableDeathWish[world.player].value > 0: + return True + + return False + + +def get_total_time_pieces(world: World) -> int: + count: int = 40 + if world.multiworld.EnableDLC1[world.player].value > 0: + count += 6 + + return min(40+world.multiworld.MaxExtraTimePieces[world.player].value, count) + + +def create_item(world: World, name: str) -> Item: + data = item_table[name] + return HatInTimeItem(name, data.classification, data.code, world.player) + + +def create_multiple_items(world: World, name: str, count: int = 1) -> typing.List[Item]: + data = item_table[name] + itemlist: typing.List[Item] = [] + + for i in range(count): + itemlist += [HatInTimeItem(name, data.classification, data.code, world.player)] + + return itemlist + + +def create_junk_items(world: World, count: int) -> typing.List[Item]: + trap_chance = world.multiworld.TrapChance[world.player].value + junk_pool: typing.List[Item] = [] + junk_list: typing.Dict[str, int] = {} + trap_list: typing.Dict[str, int] = {} + ic: ItemClassification + + for name in item_table.keys(): + ic = item_table[name].classification + if ic == ItemClassification.filler: + junk_list[name] = junk_weights.get(name) + elif trap_chance > 0 and ic == ItemClassification.trap: + if name == "Baby Trap": + trap_list[name] = world.multiworld.BabyTrapWeight[world.player].value + elif name == "Laser Trap": + trap_list[name] = world.multiworld.LaserTrapWeight[world.player].value + elif name == "Parade Trap": + trap_list[name] = world.multiworld.ParadeTrapWeight[world.player].value + + for i in range(count): + if trap_chance > 0 and world.multiworld.random.randint(1, 100) <= trap_chance: + junk_pool += [world.create_item( + world.multiworld.random.choices(list(trap_list.keys()), weights=list(trap_list.values()), k=1)[0])] + else: + junk_pool += [world.create_item( + world.multiworld.random.choices(list(junk_list.keys()), weights=list(junk_list.values()), k=1)[0])] + + return junk_pool + + +ahit_items = { + "Yarn": ItemData(300001, ItemClassification.progression_skip_balancing), + "Time Piece": ItemData(300002, ItemClassification.progression_skip_balancing), + "Progressive Painting Unlock": ItemData(300003, ItemClassification.progression), + + # Relics + "Relic (Burger Patty)": ItemData(300006, ItemClassification.progression), + "Relic (Burger Cushion)": ItemData(300007, ItemClassification.progression), + "Relic (Mountain Set)": ItemData(300008, ItemClassification.progression), + "Relic (Train)": ItemData(300009, ItemClassification.progression), + "Relic (UFO)": ItemData(300010, ItemClassification.progression), + "Relic (Cow)": ItemData(300011, ItemClassification.progression), + "Relic (Cool Cow)": ItemData(300012, ItemClassification.progression), + "Relic (Tin-foil Hat Cow)": ItemData(300013, ItemClassification.progression), + "Relic (Crayon Box)": ItemData(300014, ItemClassification.progression), + "Relic (Red Crayon)": ItemData(300015, ItemClassification.progression), + "Relic (Blue Crayon)": ItemData(300016, ItemClassification.progression), + "Relic (Green Crayon)": ItemData(300017, ItemClassification.progression), + + # Badges + "Projectile Badge": ItemData(300024, ItemClassification.useful), + "Fast Hatter Badge": ItemData(300025, ItemClassification.useful), + "Hover Badge": ItemData(300026, ItemClassification.useful), + "Hookshot Badge": ItemData(300027, ItemClassification.progression), + "Item Magnet Badge": ItemData(300028, ItemClassification.useful), + "No Bonk Badge": ItemData(300029, ItemClassification.useful), + "Compass Badge": ItemData(300030, ItemClassification.useful), + "Scooter Badge": ItemData(300031, ItemClassification.progression), + "Badge Pin": ItemData(300043, ItemClassification.useful), + + # Other + # "Rift Token": ItemData(300032, ItemClassification.filler), + "Random Cosmetic": ItemData(300044, ItemClassification.filler), + "Umbrella": ItemData(300033, ItemClassification.progression), + + # Garbage items + "25 Pons": ItemData(300034, ItemClassification.filler), + "50 Pons": ItemData(300035, ItemClassification.filler), + "100 Pons": ItemData(300036, ItemClassification.filler), + "Health Pon": ItemData(300037, ItemClassification.filler), + + # Traps + "Baby Trap": ItemData(300039, ItemClassification.trap), + "Laser Trap": ItemData(300040, ItemClassification.trap), + "Parade Trap": ItemData(300041, ItemClassification.trap), + + # DLC1 items + "Relic (Cake Stand)": ItemData(300018, ItemClassification.progression, HatDLC.dlc1), + "Relic (Cake)": ItemData(300019, ItemClassification.progression, HatDLC.dlc1), + "Relic (Cake Slice)": ItemData(300020, ItemClassification.progression, HatDLC.dlc1), + "Relic (Shortcake)": ItemData(300021, ItemClassification.progression, HatDLC.dlc1), + + # DLC2 items + "Relic (Necklace Bust)": ItemData(300022, ItemClassification.progression, HatDLC.dlc2), + "Relic (Necklace)": ItemData(300023, ItemClassification.progression, HatDLC.dlc2), + "Metro Ticket - Yellow": ItemData(300045, ItemClassification.progression, HatDLC.dlc2), + "Metro Ticket - Green": ItemData(300046, ItemClassification.progression, HatDLC.dlc2), + "Metro Ticket - Blue": ItemData(300047, ItemClassification.progression, HatDLC.dlc2), + "Metro Ticket - Pink": ItemData(300048, ItemClassification.progression, HatDLC.dlc2), + + # Death Wish items + "One-Hit Hero Badge": ItemData(300038, ItemClassification.progression, HatDLC.death_wish), + "Camera Badge": ItemData(300042, ItemClassification.progression, HatDLC.death_wish), +} + +act_contracts = { + "Snatcher's Contract - The Subcon Well": ItemData(300200, ItemClassification.progression), + "Snatcher's Contract - Toilet of Doom": ItemData(300201, ItemClassification.progression), + "Snatcher's Contract - Queen Vanessa's Manor": ItemData(300202, ItemClassification.progression), + "Snatcher's Contract - Mail Delivery Service": ItemData(300203, ItemClassification.progression), +} + +alps_hooks = { + "Zipline Unlock - The Birdhouse Path": ItemData(300204, ItemClassification.progression), + "Zipline Unlock - The Lava Cake Path": ItemData(300205, ItemClassification.progression), + "Zipline Unlock - The Windmill Path": ItemData(300206, ItemClassification.progression), + "Zipline Unlock - The Twilight Bell Path": ItemData(300207, ItemClassification.progression), +} + +relic_groups = { + "Burger": {"Relic (Burger Patty)", "Relic (Burger Cushion)"}, + "Train": {"Relic (Mountain Set)", "Relic (Train)"}, + "UFO": {"Relic (UFO)", "Relic (Cow)", "Relic (Cool Cow)", "Relic (Tin-foil Hat Cow)"}, + "Crayon": {"Relic (Crayon Box)", "Relic (Red Crayon)", "Relic (Blue Crayon)", "Relic (Green Crayon)"}, + "Cake": {"Relic (Cake Stand)", "Relic (Cake)", "Relic (Cake Slice)", "Relic (Shortcake)"}, + "Necklace": {"Relic (Necklace Bust)", "Relic (Necklace)"}, +} + +item_frequencies = { + "Badge Pin": 2, + "Progressive Painting Unlock": 3, +} + +junk_weights = { + "25 Pons": 50, + "50 Pons": 10, + "Health Pon": 35, + "100 Pons": 5, + "Random Cosmetic": 25, +} + +item_table = { + **ahit_items, + **act_contracts, + **alps_hooks, +} diff --git a/worlds/ahit/Locations.py b/worlds/ahit/Locations.py new file mode 100644 index 0000000000..f48c3d0b4a --- /dev/null +++ b/worlds/ahit/Locations.py @@ -0,0 +1,682 @@ +from BaseClasses import Location +from worlds.AutoWorld import World +from .Types import HatDLC, HatType +from typing import Optional, NamedTuple, List, Dict +from .Options import TasksanityCheckCount + + +class LocData(NamedTuple): + id: int + region: str + required_hats: Optional[List[HatType]] = [HatType.NONE] + required_tps: Optional[int] = 0 + hookshot: Optional[bool] = False + dlc_flags: Optional[HatDLC] = HatDLC.none + paintings: Optional[int] = 0 # Progressive paintings required for Subcon painting shuffle + + # For UmbrellaLogic setting + umbrella: Optional[bool] = False # Umbrella required for this check + dweller_bell: Optional[int] = 0 # Dweller bell hit required, 1 means must hit bell, 2 means can bypass w/mask + + # Other + act_complete_event: Optional[bool] = True # Only used for event locations. Copy access rule from act completion + nyakuza_thug: Optional[str] = "" # Name of Nyakuza thug NPC (for metro shops) + + +class HatInTimeLocation(Location): + game: str = "A Hat in Time" + + +def get_total_locations(world: World) -> int: + total: int = 0 + + for (name) in location_table.keys(): + if is_location_valid(world, name): + total += 1 + + if world.multiworld.EnableDLC1[world.player].value > 0 and world.multiworld.Tasksanity[world.player].value > 0: + total += world.multiworld.TasksanityCheckCount[world.player].value + + return total + + +def location_dlc_enabled(world: World, location: str) -> bool: + data = location_table.get(location) or event_locs.get(location) + + if data.dlc_flags == HatDLC.none: + return True + elif data.dlc_flags == HatDLC.dlc1 and world.multiworld.EnableDLC1[world.player].value > 0: + return True + elif data.dlc_flags == HatDLC.dlc2 and world.multiworld.EnableDLC2[world.player].value > 0: + return True + elif data.dlc_flags == HatDLC.death_wish and world.multiworld.EnableDeathWish[world.player].value > 0: + return True + + return False + + +def is_location_valid(world: World, location: str) -> bool: + if not location_dlc_enabled(world, location): + return False + + if location in storybook_pages.keys() \ + and world.multiworld.ShuffleStorybookPages[world.player].value == 0: + return False + + if location in shop_locations and location not in world.shop_locs: + return False + + return True + + +def get_location_names() -> Dict[str, int]: + names = {name: data.id for name, data in location_table.items()} + id_start: int = 300204 + for i in range(TasksanityCheckCount.range_end): + names.setdefault(format("Tasksanity Check %i") % (i+1), id_start+i) + + return names + + +ahit_locations = { + "Spaceship - Rumbi Abuse": LocData(301000, "Spaceship", required_tps=4, dweller_bell=1), + # "Spaceship - Cooking Cat": LocData(301001, "Spaceship", required_tps=5), + + # 300000 range - Mafia Town/Batle of the Birds + "Welcome to Mafia Town - Umbrella": LocData(301002, "Welcome to Mafia Town"), + "Mafia Town - Old Man (Seaside Spaghetti)": LocData(303833, "Mafia Town Area"), + "Mafia Town - Old Man (Steel Beams)": LocData(303832, "Mafia Town Area"), + "Mafia Town - Blue Vault": LocData(302850, "Mafia Town Area"), + "Mafia Town - Green Vault": LocData(302851, "Mafia Town Area"), + "Mafia Town - Red Vault": LocData(302848, "Mafia Town Area"), + "Mafia Town - Blue Vault Brewing Crate": LocData(305572, "Mafia Town Area", required_hats=[HatType.BREWING]), + "Mafia Town - Plaza Under Boxes": LocData(304458, "Mafia Town Area"), + "Mafia Town - Small Boat": LocData(304460, "Mafia Town Area"), + "Mafia Town - Staircase Pon Cluster": LocData(304611, "Mafia Town Area"), + "Mafia Town - Palm Tree": LocData(304609, "Mafia Town Area"), + "Mafia Town - Port": LocData(305219, "Mafia Town Area"), + "Mafia Town - Docks Chest": LocData(303534, "Mafia Town Area"), + "Mafia Town - Ice Hat Cage": LocData(304831, "Mafia Town Area", required_hats=[HatType.ICE]), + "Mafia Town - Hidden Buttons Chest": LocData(303483, "Mafia Town Area"), + + # These can be accessed from HUMT, the above locations can't be + "Mafia Town - Dweller Boxes": LocData(304462, "Mafia Town Area (HUMT)"), + "Mafia Town - Ledge Chest": LocData(303530, "Mafia Town Area (HUMT)"), + "Mafia Town - Yellow Sphere Building Chest": LocData(303535, "Mafia Town Area (HUMT)"), + "Mafia Town - Beneath Scaffolding": LocData(304456, "Mafia Town Area (HUMT)"), + "Mafia Town - On Scaffolding": LocData(304457, "Mafia Town Area (HUMT)"), + "Mafia Town - Cargo Ship": LocData(304459, "Mafia Town Area (HUMT)"), + "Mafia Town - Beach Alcove": LocData(304463, "Mafia Town Area (HUMT)"), + "Mafia Town - Wood Cage": LocData(304606, "Mafia Town Area (HUMT)"), + "Mafia Town - Beach Patio": LocData(304610, "Mafia Town Area (HUMT)"), + "Mafia Town - Steel Beam Nest": LocData(304608, "Mafia Town Area (HUMT)"), + "Mafia Town - Top of Ruined Tower": LocData(304607, "Mafia Town Area (HUMT)", required_hats=[HatType.ICE]), + "Mafia Town - Hot Air Balloon": LocData(304829, "Mafia Town Area (HUMT)", required_hats=[HatType.ICE]), + "Mafia Town - Camera Badge 1": LocData(302003, "Mafia Town Area (HUMT)"), + "Mafia Town - Camera Badge 2": LocData(302004, "Mafia Town Area (HUMT)"), + "Mafia Town - Chest Beneath Aqueduct": LocData(303489, "Mafia Town Area (HUMT)"), + "Mafia Town - Secret Cave": LocData(305220, "Mafia Town Area (HUMT)", required_hats=[HatType.BREWING]), + "Mafia Town - Crow Chest": LocData(303532, "Mafia Town Area (HUMT)"), + "Mafia Town - Above Boats": LocData(305218, "Mafia Town Area (HUMT)", hookshot=True), + "Mafia Town - Slip Slide Chest": LocData(303529, "Mafia Town Area (HUMT)"), + "Mafia Town - Behind Faucet": LocData(304214, "Mafia Town Area (HUMT)"), + "Mafia Town - Clock Tower Chest": LocData(303481, "Mafia Town Area (HUMT)", hookshot=True), + "Mafia Town - Top of Lighthouse": LocData(304213, "Mafia Town Area (HUMT)", hookshot=True), + "Mafia Town - Mafia Geek Platform": LocData(304212, "Mafia Town Area (HUMT)"), + "Mafia Town - Behind HQ Chest": LocData(303486, "Mafia Town Area (HUMT)"), + + "Mafia HQ - Hallway Brewing Crate": LocData(305387, "Down with the Mafia!", required_hats=[HatType.BREWING]), + "Mafia HQ - Freezer Chest": LocData(303241, "Down with the Mafia!"), + "Mafia HQ - Secret Room": LocData(304979, "Down with the Mafia!", required_hats=[HatType.ICE]), + "Mafia HQ - Bathroom Stall Chest": LocData(303243, "Down with the Mafia!"), + + "Dead Bird Studio - Up the Ladder": LocData(304874, "Dead Bird Studio - Elevator Area"), + "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", umbrella=True), + "Dead Bird Studio - Tightrope Chest": LocData(303898, "Dead Bird Studio", umbrella=True), + "Dead Bird Studio - Tepee Chest": LocData(303899, "Dead Bird Studio", umbrella=True), + "Dead Bird Studio - Conductor Chest": LocData(303900, "Dead Bird Studio", umbrella=True), + + "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"), + "Murder on the Owl Express - Luggage Room Bottom": LocData(305091, "Murder on the Owl Express"), + + "Murder on the Owl Express - Raven Suite Room": LocData(305701, "Murder on the Owl Express", + required_hats=[HatType.BREWING]), + + "Murder on the Owl Express - Raven Suite Top": LocData(305312, "Murder on the Owl Express"), + "Murder on the Owl Express - Lounge Chest": LocData(303963, "Murder on the Owl Express"), + + "Picture Perfect - Behind Badge Seller": LocData(304307, "Picture Perfect"), + "Picture Perfect - Hats Buy Building": LocData(304530, "Picture Perfect"), + + "Dead Bird Studio Basement - Window Platform": LocData(305432, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Cardboard Conductor": LocData(305059, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Above Conductor Sign": LocData(305057, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Logo Wall": LocData(305207, "Dead Bird Studio Basement"), + "Dead Bird Studio Basement - Disco Room": LocData(305061, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Small Room": LocData(304813, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Vent Pipe": LocData(305430, "Dead Bird Studio Basement"), + "Dead Bird Studio Basement - Tightrope": LocData(305058, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Cameras": LocData(305431, "Dead Bird Studio Basement", hookshot=True), + "Dead Bird Studio Basement - Locked Room": LocData(305819, "Dead Bird Studio Basement", hookshot=True), + + # 320000 range - Subcon Forest + "Contractual Obligations - Cherry Bomb Bone Cage": LocData(324761, "Contractual Obligations"), + "Subcon Village - Tree Top Ice Cube": LocData(325078, "Subcon Forest Area"), + "Subcon Village - Graveyard Ice Cube": LocData(325077, "Subcon Forest Area"), + "Subcon Village - House Top": LocData(325471, "Subcon Forest Area"), + "Subcon Village - Ice Cube House": LocData(325469, "Subcon Forest Area"), + "Subcon Village - Snatcher Statue Chest": LocData(323730, "Subcon Forest Area", paintings=1), + "Subcon Village - Stump Platform Chest": LocData(323729, "Subcon Forest Area"), + "Subcon Forest - Giant Tree Climb": LocData(325470, "Subcon Forest Area"), + + "Subcon Forest - Swamp Gravestone": LocData(326296, "Subcon Forest Area", + required_hats=[HatType.BREWING], + paintings=1), + + "Subcon Forest - Swamp Near Well": LocData(324762, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Tree A": LocData(324763, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Tree B": LocData(324764, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Ice Wall": LocData(324706, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Treehouse": LocData(325468, "Subcon Forest Area", paintings=1), + "Subcon Forest - Swamp Tree Chest": LocData(323728, "Subcon Forest Area", paintings=1), + + "Subcon Forest - Dweller Stump": LocData(324767, "Subcon Forest Area", + required_hats=[HatType.DWELLER], + paintings=3), + + "Subcon Forest - Dweller Floating Rocks": LocData(324464, "Subcon Forest Area", + required_hats=[HatType.DWELLER], + paintings=3), + + "Subcon Forest - Dweller Platforming Tree A": LocData(324709, "Subcon Forest Area", paintings=3), + + "Subcon Forest - Dweller Platforming Tree B": LocData(324855, "Subcon Forest Area", + required_hats=[HatType.DWELLER], + paintings=3), + + "Subcon Forest - Giant Time Piece": LocData(325473, "Subcon Forest Area", paintings=1), + "Subcon Forest - Gallows": LocData(325472, "Subcon Forest Area", paintings=1), + + "Subcon Forest - Green and Purple Dweller Rocks": LocData(325082, "Subcon Forest Area", + required_hats=[HatType.DWELLER], + paintings=3), + + "Subcon Forest - Dweller Shack": LocData(324463, "Subcon Forest Area", + required_hats=[HatType.DWELLER], + paintings=1), + + "Subcon Forest - Tall Tree Hookshot Swing": LocData(324766, "Subcon Forest Area", + paintings=3, + required_hats=[HatType.DWELLER], + hookshot=True), + + "Subcon Forest - Burning House": LocData(324710, "Subcon Forest Area", paintings=2), + "Subcon Forest - Burning Tree Climb": LocData(325079, "Subcon Forest Area", paintings=2), + "Subcon Forest - Burning Stump Chest": LocData(323731, "Subcon Forest Area", paintings=2), + "Subcon Forest - Burning Forest Treehouse": LocData(325467, "Subcon Forest Area", paintings=2), + "Subcon Forest - Spider Bone Cage A": LocData(324462, "Subcon Forest Area", paintings=2), + "Subcon Forest - Spider Bone Cage B": LocData(325080, "Subcon Forest Area", paintings=2), + "Subcon Forest - Triple Spider Bounce": LocData(324765, "Subcon Forest Area", paintings=2), + "Subcon Forest - Noose Treehouse": LocData(324856, "Subcon Forest Area", hookshot=True, paintings=2), + "Subcon Forest - Ice Cube Shack": LocData(324465, "Subcon Forest Area", paintings=1), + + "Subcon Forest - Long Tree Climb Chest": LocData(323734, "Subcon Forest Area", + required_hats=[HatType.DWELLER], + paintings=2), + + "Subcon Forest - Boss Arena Chest": LocData(323735, "Subcon Forest Area"), + "Subcon Forest - Manor Rooftop": LocData(325466, "Subcon Forest Area", dweller_bell=2, paintings=1), + + "Subcon Forest - Infinite Yarn Bush": LocData(325478, "Subcon Forest Area", + required_hats=[HatType.BREWING], + paintings=2), + + "Subcon Forest - Magnet Badge Bush": LocData(325479, "Subcon Forest Area", + required_hats=[HatType.BREWING], + paintings=3), + + "Subcon Well - Hookshot Badge Chest": LocData(324114, "The Subcon Well", dweller_bell=1, paintings=1), + "Subcon Well - Above Chest": LocData(324612, "The Subcon Well", dweller_bell=1, paintings=1), + "Subcon Well - On Pipe": LocData(324311, "The Subcon Well", hookshot=True, dweller_bell=1, paintings=1), + "Subcon Well - Mushroom": LocData(325318, "The Subcon Well", dweller_bell=1, paintings=1), + + "Queen Vanessa's Manor - Cellar": LocData(324841, "Queen Vanessa's Manor", dweller_bell=2, paintings=1), + "Queen Vanessa's Manor - Bedroom Chest": LocData(323808, "Queen Vanessa's Manor", dweller_bell=2, paintings=1), + "Queen Vanessa's Manor - Hall Chest": LocData(323896, "Queen Vanessa's Manor", dweller_bell=2, paintings=1), + "Queen Vanessa's Manor - Chandelier": LocData(325546, "Queen Vanessa's Manor", dweller_bell=2, paintings=1), + + # 330000 range - 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 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 - 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"), + + "Alpine Skyline - The Birdhouse: Dweller Platforms Relic": LocData(336497, "The Birdhouse", + required_hats=[HatType.DWELLER]), + + "Alpine Skyline - The Birdhouse: Brewing Crate House": LocData(336496, "The Birdhouse"), + "Alpine Skyline - The Birdhouse: Hay Bale": LocData(335885, "The Birdhouse"), + "Alpine Skyline - The Birdhouse: Alpine Crow Mini-Gauntlet": LocData(335886, "The Birdhouse"), + "Alpine Skyline - The Birdhouse: Outer Edge": LocData(335492, "The Birdhouse"), + + "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 - 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"), + "Alpine Skyline - The Lava Cake: Top Cake": LocData(335418, "The Lava Cake"), + "Alpine Skyline - The Twilight Path": LocData(334434, "Alpine Skyline Area", required_hats=[HatType.DWELLER]), + "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 - 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"), + "Alpine Skyline - The Windmill: House Window": LocData(335389, "The Windmill"), + + "The Finale - Frozen Item": LocData(304108, "The Finale"), + + "Bon Voyage! - Lamp Post Top": LocData(305321, "Bon Voyage!", dlc_flags=HatDLC.dlc1), + "Bon Voyage! - Mafia Cargo Ship": LocData(304313, "Bon Voyage!", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Toilet": LocData(305109, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Bar": LocData(304251, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Dive Board Ledge": LocData(304254, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Top Balcony": LocData(304255, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Octopus Room": LocData(305253, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Octopus Room Top": LocData(304249, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Laundry Room": LocData(304250, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Ship Side": LocData(304247, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "The Arctic Cruise - Silver Ring": LocData(305252, "Cruise Ship", dlc_flags=HatDLC.dlc1), + "Rock the Boat - Reception Room - Suitcase": LocData(304045, "Rock the Boat", dlc_flags=HatDLC.dlc1), + "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), + + "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), + + "Nyakuza Metro - Yellow Overpass Station Crate": LocData(305413, "Yellow Overpass Station", + dlc_flags=HatDLC.dlc2, + required_hats=[HatType.BREWING]), + + "Nyakuza Metro - Bluefin Tunnel Cat Vacuum": LocData(305111, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2), + + "Nyakuza Metro - Pink Paw Station Cat Vacuum": LocData(305110, "Pink Paw Station", + dlc_flags=HatDLC.dlc2, + hookshot=True, + required_hats=[HatType.DWELLER]), + + "Nyakuza Metro - Pink Paw Station Behind Fan": LocData(304106, "Pink Paw Station", + dlc_flags=HatDLC.dlc2, + hookshot=True, + required_hats=[HatType.TIME_STOP, HatType.DWELLER]), +} + +act_completions = { + # 310000 range - Act Completions + "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 (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"), + "Act Completion (Time Rift - Sewers)": LocData(312484, "Time Rift - Sewers"), + "Act Completion (Time Rift - Mafia of Cooks)": LocData(311855, "Time Rift - Mafia of Cooks"), + + "Act Completion (Dead Bird Studio)": LocData(311383, "Dead Bird Studio", umbrella=True), + "Act Completion (Murder on the Owl Express)": LocData(311544, "Murder on the Owl Express"), + "Act Completion (Picture Perfect)": LocData(311587, "Picture Perfect"), + "Act Completion (Train Rush)": LocData(312481, "Train Rush", hookshot=True), + "Act Completion (The Big Parade)": LocData(311157, "The Big Parade", umbrella=True), + "Act Completion (Award Ceremony)": LocData(311488, "Award Ceremony"), + "Act Completion (Dead Bird Studio Basement)": LocData(312253, "Dead Bird Studio Basement", hookshot=True), + "Act Completion (Time Rift - The Owl Express)": LocData(312807, "Time Rift - The Owl Express"), + "Act Completion (Time Rift - The Moon)": LocData(312785, "Time Rift - The Moon"), + "Act Completion (Time Rift - Dead Bird Studio)": LocData(312577, "Time Rift - Dead Bird Studio"), + + "Act Completion (Contractual Obligations)": LocData(312317, "Contractual Obligations", paintings=1), + "Act Completion (The Subcon Well)": LocData(311160, "The Subcon Well", hookshot=True, umbrella=True, paintings=1), + "Act Completion (Toilet of Doom)": LocData(311984, "Toilet of Doom", hookshot=True, paintings=1), + "Act Completion (Queen Vanessa's Manor)": LocData(312017, "Queen Vanessa's Manor", umbrella=True, paintings=1), + "Act Completion (Mail Delivery Service)": LocData(312032, "Mail Delivery Service", required_hats=[HatType.SPRINT]), + "Act Completion (Your Contract has Expired)": LocData(311390, "Your Contract has Expired", umbrella=True), + "Act Completion (Time Rift - Pipe)": LocData(313069, "Time Rift - Pipe", hookshot=True), + "Act Completion (Time Rift - Village)": LocData(313056, "Time Rift - Village"), + "Act Completion (Time Rift - Sleepy Subcon)": LocData(312086, "Time Rift - Sleepy Subcon"), + + "Act Completion (The Birdhouse)": LocData(311428, "The Birdhouse"), + "Act Completion (The Lava Cake)": LocData(312509, "The Lava Cake"), + "Act Completion (The Twilight Bell)": LocData(311540, "The Twilight Bell"), + "Act Completion (The Windmill)": LocData(312263, "The Windmill"), + "Act Completion (The Illness has Spread)": LocData(312022, "The Illness has Spread", hookshot=True), + + "Act Completion (Time Rift - The Twilight Bell)": LocData(312399, "Time Rift - The Twilight Bell", + required_hats=[HatType.DWELLER]), + + "Act Completion (Time Rift - Curly Tail Trail)": LocData(313335, "Time Rift - Curly Tail Trail", + required_hats=[HatType.ICE]), + + "Act Completion (Time Rift - Alpine Skyline)": LocData(311777, "Time Rift - Alpine Skyline"), + + "Act Completion (The Finale)": LocData(311872, "The Finale", hookshot=True, required_hats=[HatType.DWELLER]), + "Act Completion (Time Rift - Tour)": LocData(311803, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + + "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 (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 (Nyakuza Metro Intro)": LocData(311138, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2), + + "Act Completion (Yellow Overpass Station)": LocData(311206, "Yellow Overpass Station", + dlc_flags=HatDLC.dlc2, + hookshot=True), + + "Act Completion (Yellow Overpass Manhole)": LocData(311387, "Yellow Overpass Manhole", + dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE]), + + "Act Completion (Green Clean Station)": LocData(311207, "Green Clean Station", dlc_flags=HatDLC.dlc2), + + "Act Completion (Green Clean Manhole)": LocData(311388, "Green Clean Manhole", + dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE, HatType.DWELLER]), + + "Act Completion (Bluefin Tunnel)": LocData(311208, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2), + + "Act Completion (Pink Paw Station)": LocData(311209, "Pink Paw Station", + dlc_flags=HatDLC.dlc2, + hookshot=True, + required_hats=[HatType.DWELLER]), + + "Act Completion (Pink Paw Manhole)": LocData(311389, "Pink Paw Manhole", + dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE]), + + "Act Completion (Rush Hour)": LocData(311210, "Rush Hour", + dlc_flags=HatDLC.dlc2, + hookshot=True, + required_hats=[HatType.ICE, HatType.BREWING]), + + "Act Completion (Rumbi Factory)": LocData(312736, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), +} + +storybook_pages = { + "Mafia of Cooks - Page: Fish Pile": LocData(345091, "Time Rift - Mafia of Cooks"), + "Mafia of Cooks - Page: Trash Mound": LocData(345090, "Time Rift - Mafia of Cooks"), + "Mafia of Cooks - Page: Beside Red Building": LocData(345092, "Time Rift - Mafia of Cooks"), + "Mafia of Cooks - Page: Behind Shipping Containers": LocData(345095, "Time Rift - Mafia of Cooks"), + "Mafia of Cooks - Page: Top of Boat": LocData(345093, "Time Rift - Mafia of Cooks"), + "Mafia of Cooks - Page: Below Dock": LocData(345094, "Time Rift - Mafia of Cooks"), + + "Dead Bird Studio (Rift) - Page: Behind Cardboard Planet": LocData(345449, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Near Time Rift Gate": LocData(345447, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Top of Metal Bar": LocData(345448, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Lava Lamp": LocData(345450, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Above Horse Picture": LocData(345451, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Green Screen": LocData(345452, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: In The Corner": LocData(345453, "Time Rift - Dead Bird Studio"), + "Dead Bird Studio (Rift) - Page: Above TV Room": LocData(345445, "Time Rift - Dead Bird Studio"), + + "Sleepy Subcon - Page: Behind Entrance Area": LocData(345373, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Near Wrecking Ball": LocData(345327, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Behind Crane": LocData(345371, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Wrecked Treehouse": LocData(345326, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Behind 2nd Rift Gate": LocData(345372, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Rotating Platform": LocData(345328, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Behind 3rd Rift Gate": LocData(345329, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Frozen Tree": LocData(345330, "Time Rift - Sleepy Subcon"), + "Sleepy Subcon - Page: Secret Library": LocData(345370, "Time Rift - Sleepy Subcon"), + + "Alpine Skyline (Rift) - Page: Entrance Area Hidden Ledge": LocData(345016, "Time Rift - Alpine Skyline"), + "Alpine Skyline (Rift) - Page: Windmill Island Ledge": LocData(345012, "Time Rift - Alpine Skyline"), + "Alpine Skyline (Rift) - Page: Waterfall Wooden Pillar": LocData(345015, "Time Rift - Alpine Skyline"), + "Alpine Skyline (Rift) - Page: Lonely Birdhouse Top": LocData(345014, "Time Rift - Alpine Skyline"), + "Alpine Skyline (Rift) - Page: Below Aqueduct": LocData(345013, "Time Rift - Alpine Skyline"), + + "Deep Sea - Page: Starfish": LocData(346454, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1), + "Deep Sea - Page: Mini Castle": LocData(346452, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1), + "Deep Sea - Page: Urchins": LocData(346449, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1), + "Deep Sea - Page: Big Castle": LocData(346450, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, hookshot=True), + "Deep Sea - Page: Castle Top Chest": LocData(304850, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, hookshot=True), + "Deep Sea - Page: Urchin Ledge": LocData(346451, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, hookshot=True), + "Deep Sea - Page: Hidden Castle Chest": LocData(304849, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, hookshot=True), + "Deep Sea - Page: Falling Platform": LocData(346456, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, hookshot=True), + "Deep Sea - Page: Lava Starfish": LocData(346453, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, hookshot=True), + + "Tour - Page: Mafia Town - Ledge": LocData(345038, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Mafia Town - Beach": LocData(345039, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Dead Bird Studio - C.A.W. Agents": LocData(345040, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Dead Bird Studio - Fragile Box": LocData(345041, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Subcon Forest - Giant Frozen Tree": LocData(345042, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Subcon Forest - Top of Pillar": LocData(345043, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Alpine Skyline - Birdhouse": LocData(345044, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: Alpine Skyline - Behind Lava Isle": LocData(345047, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + "Tour - Page: The Finale - Near Entrance": LocData(345087, "Time Rift - Tour", dlc_flags=HatDLC.dlc1), + + "Rumbi Factory - Page: Manhole": LocData(345891, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Shutter Doors": LocData(345888, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Toxic Waste Dispenser": LocData(345892, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: 3rd Area Ledge": LocData(345889, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Green Box Assembly Line": LocData(345884, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Broken Window": LocData(345885, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Money Vault": LocData(345890, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Warehouse Boxes": LocData(345887, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Glass Shelf": LocData(345886, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), + "Rumbi Factory - Page: Last Area": LocData(345883, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2), +} + +contract_locations = { + "Snatcher's Contract - The Subcon Well": LocData(300200, "Contractual Obligations"), + "Snatcher's Contract - Toilet of Doom": LocData(300201, "Subcon Forest Area"), + "Snatcher's Contract - Queen Vanessa's Manor": LocData(300202, "Subcon Forest Area"), + "Snatcher's Contract - Mail Delivery Service": LocData(300203, "Subcon Forest Area"), +} + +shop_locations = { + "Badge Seller - Item 1": LocData(301003, "Badge Seller"), + "Badge Seller - Item 2": LocData(301004, "Badge Seller"), + "Badge Seller - Item 3": LocData(301005, "Badge Seller"), + "Badge Seller - Item 4": LocData(301006, "Badge Seller"), + "Badge Seller - Item 5": LocData(301007, "Badge Seller"), + "Badge Seller - Item 6": LocData(301008, "Badge Seller"), + "Badge Seller - Item 7": LocData(301009, "Badge Seller"), + "Badge Seller - Item 8": LocData(301010, "Badge Seller"), + "Badge Seller - Item 9": LocData(301011, "Badge Seller"), + "Badge Seller - Item 10": LocData(301012, "Badge Seller"), + "Mafia Boss Shop Item": LocData(301013, "Spaceship", required_tps=12), + + "Yellow Overpass Station - Yellow Ticket Booth": LocData(301014, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2), + "Green Clean Station - Green Ticket Booth": LocData(301015, "Green Clean Station", dlc_flags=HatDLC.dlc2), + "Bluefin Tunnel - Blue Ticket Booth": LocData(301016, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2), + "Pink Paw Station - Pink Ticket Booth": LocData(301017, "Pink Paw Station", dlc_flags=HatDLC.dlc2), + + "Main Station Thug A - Item 1": LocData(301048, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_0"), + "Main Station Thug A - Item 2": LocData(301049, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_0"), + "Main Station Thug A - Item 3": LocData(301050, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_0"), + "Main Station Thug A - Item 4": LocData(301051, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_0"), + "Main Station Thug A - Item 5": LocData(301052, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_0"), + + "Main Station Thug B - Item 1": LocData(301053, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_1"), + "Main Station Thug B - Item 2": LocData(301054, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_1"), + "Main Station Thug B - Item 3": LocData(301055, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_1"), + "Main Station Thug B - Item 4": LocData(301056, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_1"), + "Main Station Thug B - Item 5": LocData(301057, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_1"), + + "Main Station Thug C - Item 1": LocData(301058, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_2"), + "Main Station Thug C - Item 2": LocData(301059, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_2"), + "Main Station Thug C - Item 3": LocData(301060, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_2"), + "Main Station Thug C - Item 4": LocData(301061, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_2"), + "Main Station Thug C - Item 5": LocData(301062, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_2"), + + "Yellow Overpass Thug A - Item 1": LocData(301018, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_13"), + "Yellow Overpass Thug A - Item 2": LocData(301019, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_13"), + "Yellow Overpass Thug A - Item 3": LocData(301020, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_13"), + "Yellow Overpass Thug A - Item 4": LocData(301021, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_13"), + "Yellow Overpass Thug A - Item 5": LocData(301022, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_13"), + + "Yellow Overpass Thug B - Item 1": LocData(301043, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_5"), + "Yellow Overpass Thug B - Item 2": LocData(301044, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_5"), + "Yellow Overpass Thug B - Item 3": LocData(301045, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_5"), + "Yellow Overpass Thug B - Item 4": LocData(301046, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_5"), + "Yellow Overpass Thug B - Item 5": LocData(301047, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_5"), + + "Yellow Overpass Thug C - Item 1": LocData(301063, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_14"), + "Yellow Overpass Thug C - Item 2": LocData(301064, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_14"), + "Yellow Overpass Thug C - Item 3": LocData(301065, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_14"), + "Yellow Overpass Thug C - Item 4": LocData(301066, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_14"), + "Yellow Overpass Thug C - Item 5": LocData(301067, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_14"), + + "Green Clean Station Thug A - Item 1": LocData(301033, "Green Clean Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_4"), + "Green Clean Station Thug A - Item 2": LocData(301034, "Green Clean Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_4"), + "Green Clean Station Thug A - Item 3": LocData(301035, "Green Clean Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_4"), + "Green Clean Station Thug A - Item 4": LocData(301036, "Green Clean Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_4"), + "Green Clean Station Thug A - Item 5": LocData(301037, "Green Clean Station", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_4"), + + # This guy requires either the yellow ticket or the Ice Hat + "Green Clean Station Thug B - Item 1": LocData(301028, "Green Clean Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE], nyakuza_thug="Hat_NPC_NyakuzaShop_6"), + "Green Clean Station Thug B - Item 2": LocData(301029, "Green Clean Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE], nyakuza_thug="Hat_NPC_NyakuzaShop_6"), + "Green Clean Station Thug B - Item 3": LocData(301030, "Green Clean Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE], nyakuza_thug="Hat_NPC_NyakuzaShop_6"), + "Green Clean Station Thug B - Item 4": LocData(301031, "Green Clean Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE], nyakuza_thug="Hat_NPC_NyakuzaShop_6"), + "Green Clean Station Thug B - Item 5": LocData(301032, "Green Clean Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.ICE], nyakuza_thug="Hat_NPC_NyakuzaShop_6"), + + "Bluefin Tunnel Thug - Item 1": LocData(301023, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_7"), + "Bluefin Tunnel Thug - Item 2": LocData(301024, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_7"), + "Bluefin Tunnel Thug - Item 3": LocData(301025, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_7"), + "Bluefin Tunnel Thug - Item 4": LocData(301026, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_7"), + "Bluefin Tunnel Thug - Item 5": LocData(301027, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2, + nyakuza_thug="Hat_NPC_NyakuzaShop_7"), + + "Pink Paw Station Thug - Item 1": LocData(301038, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.DWELLER], hookshot=True, + nyakuza_thug="Hat_NPC_NyakuzaShop_12"), + "Pink Paw Station Thug - Item 2": LocData(301039, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.DWELLER], hookshot=True, + nyakuza_thug="Hat_NPC_NyakuzaShop_12"), + "Pink Paw Station Thug - Item 3": LocData(301040, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.DWELLER], hookshot=True, + nyakuza_thug="Hat_NPC_NyakuzaShop_12"), + "Pink Paw Station Thug - Item 4": LocData(301041, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.DWELLER], hookshot=True, + nyakuza_thug="Hat_NPC_NyakuzaShop_12"), + "Pink Paw Station Thug - Item 5": LocData(301042, "Pink Paw Station", dlc_flags=HatDLC.dlc2, + required_hats=[HatType.DWELLER], hookshot=True, + nyakuza_thug="Hat_NPC_NyakuzaShop_12"), + +} + +# Don't put any of the locations from peaks here, the rules for their entrances are set already +zipline_unlocks = { + "Alpine Skyline - Bird Pass Fork": "Zipline Unlock - The Birdhouse Path", + "Alpine Skyline - Yellow Band Hills": "Zipline Unlock - The Birdhouse Path", + "Alpine Skyline - The Purrloined Village: Horned Stone": "Zipline Unlock - The Birdhouse Path", + "Alpine Skyline - The Purrloined Village: Chest Reward": "Zipline Unlock - The Birdhouse Path", + + "Alpine Skyline - Mystifying Time Mesa: Zipline": "Zipline Unlock - The Lava Cake Path", + "Alpine Skyline - Mystifying Time Mesa: Gate Puzzle": "Zipline Unlock - The Lava Cake Path", + "Alpine Skyline - Ember Summit": "Zipline Unlock - The Lava Cake Path", + + "Alpine Skyline - Goat Outpost Horn": "Zipline Unlock - The Windmill Path", + "Alpine Skyline - Windy Passage": "Zipline Unlock - The Windmill Path", + + "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 = { + "Birdhouse Cleared": LocData(0, "The Birdhouse"), + "Lava Cake Cleared": LocData(0, "The Lava Cake"), + "Windmill Cleared": LocData(0, "The Windmill"), + "Twilight Bell Cleared": LocData(0, "The Twilight Bell"), + "Time Piece Cluster": LocData(0, "The Finale"), + + # not really an act + "Nyakuza Intro Cleared": LocData(0, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2, act_complete_event=False), + + "Yellow Overpass Station Cleared": LocData(0, "Yellow Overpass Station", dlc_flags=HatDLC.dlc2), + "Green Clean Station Cleared": LocData(0, "Green Clean Station", dlc_flags=HatDLC.dlc2), + "Bluefin Tunnel Cleared": LocData(0, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2), + "Pink Paw Station Cleared": LocData(0, "Pink Paw Station", dlc_flags=HatDLC.dlc2), + "Yellow Overpass Manhole Cleared": LocData(0, "Yellow Overpass Manhole", dlc_flags=HatDLC.dlc2), + "Green Clean Manhole Cleared": LocData(0, "Green Clean Manhole", dlc_flags=HatDLC.dlc2), + "Pink Paw Manhole Cleared": LocData(0, "Pink Paw Manhole", dlc_flags=HatDLC.dlc2), + "Rush Hour Cleared": LocData(0, "Rush Hour", dlc_flags=HatDLC.dlc2), +} + +location_table = { + **ahit_locations, + **act_completions, + **storybook_pages, + **contract_locations, + **shop_locations, +} diff --git a/worlds/ahit/Options.py b/worlds/ahit/Options.py new file mode 100644 index 0000000000..772fa08aef --- /dev/null +++ b/worlds/ahit/Options.py @@ -0,0 +1,517 @@ +import typing +from worlds.AutoWorld import World +from Options import Option, Range, Toggle, DeathLink, Choice +from .Items import get_total_time_pieces + + +def adjust_options(world: World): + world.multiworld.HighestChapterCost[world.player].value = max( + world.multiworld.HighestChapterCost[world.player].value, + world.multiworld.LowestChapterCost[world.player].value) + + world.multiworld.LowestChapterCost[world.player].value = min( + world.multiworld.LowestChapterCost[world.player].value, + world.multiworld.HighestChapterCost[world.player].value) + + world.multiworld.FinalChapterMinCost[world.player].value = min( + world.multiworld.FinalChapterMinCost[world.player].value, + world.multiworld.FinalChapterMaxCost[world.player].value) + + world.multiworld.FinalChapterMaxCost[world.player].value = max( + world.multiworld.FinalChapterMaxCost[world.player].value, + world.multiworld.FinalChapterMinCost[world.player].value) + + world.multiworld.BadgeSellerMinItems[world.player].value = min( + world.multiworld.BadgeSellerMinItems[world.player].value, + world.multiworld.BadgeSellerMaxItems[world.player].value) + + world.multiworld.BadgeSellerMaxItems[world.player].value = max( + world.multiworld.BadgeSellerMinItems[world.player].value, + world.multiworld.BadgeSellerMaxItems[world.player].value) + + total_tps: int = get_total_time_pieces(world) + if world.multiworld.HighestChapterCost[world.player].value > total_tps-5: + world.multiworld.HighestChapterCost[world.player].value = min(45, total_tps-5) + + if world.multiworld.FinalChapterMaxCost[world.player].value > total_tps: + world.multiworld.FinalChapterMaxCost[world.player].value = min(50, total_tps) + + # Don't allow Rush Hour goal if DLC2 content is disabled + if world.multiworld.EndGoal[world.player].value == 2 and world.multiworld.EnableDLC2[world.player].value == 0: + world.multiworld.EndGoal[world.player].value = 1 + + +# General +class EndGoal(Choice): + """The end goal required to beat the game. + Finale: Reach Time's End and beat Mustache Girl. The Finale will be in its vanilla location. + + Rush Hour: Reach and complete Rush Hour. The level will be in its vanilla location and Chapter 7 + will be the final chapter. You also must find Nyakuza Metro itself and complete all of its levels. + Requires DLC2 content to be enabled.""" + display_name = "End Goal" + option_finale = 1 + option_rush_hour = 2 + default = 1 + + +class ActRandomizer(Choice): + """If enabled, shuffle the game's Acts between each other. + Light will cause Time Rifts to only be shuffled amongst each other, + and Blue Time Rifts and Purple Time Rifts are shuffled separately.""" + display_name = "Shuffle Acts" + option_false = 0 + option_light = 1 + option_insanity = 2 + default = 1 + + +class ShuffleAlpineZiplines(Toggle): + """If enabled, Alpine's zipline paths leading to the peaks will be locked behind items.""" + display_name = "Shuffle Alpine Ziplines" + default = 0 + + +class VanillaAlpine(Choice): + """If enabled, force Alpine (and optionally its finale) onto their vanilla locations in act shuffle.""" + display_name = "Vanilla Alpine Skyline" + option_false = 0 + option_true = 1 + option_finale = 2 + default = 0 + + +class LogicDifficulty(Choice): + """Choose the difficulty setting for logic. Note that Hard or above will force SDJ logic on.""" + display_name = "Logic Difficulty" + option_normal = 0 + option_hard = 1 + option_expert = 2 + default = 0 + + +class RandomizeHatOrder(Toggle): + """Randomize the order that hats are stitched in.""" + display_name = "Randomize Hat Order" + default = 1 + + +class UmbrellaLogic(Toggle): + """Makes Hat Kid's default punch attack do absolutely nothing, making the Umbrella much more relevant and useful""" + display_name = "Umbrella Logic" + default = 0 + + +class StartWithCompassBadge(Toggle): + """If enabled, start with the Compass Badge. In Archipelago, the Compass Badge will track all items in the world + (instead of just Relics). Recommended if you're not familiar with where item locations are.""" + display_name = "Start with Compass Badge" + default = 1 + + +class CompassBadgeMode(Choice): + """closest - Compass Badge points to the closest item regardless of classification + important_only - Compass Badge points to progression/useful items only + important_first - Compass Badge points to progression/useful items first, then it will point to junk items""" + display_name = "Compass Badge Mode" + option_closest = 1 + option_important_only = 2 + option_important_first = 3 + default = 1 + + +class ShuffleStorybookPages(Toggle): + """If enabled, each storybook page in the purple Time Rifts is an item check. + The Compass Badge can track these down for you.""" + display_name = "Shuffle Storybook Pages" + default = 1 + + +class ShuffleActContracts(Toggle): + """If enabled, shuffle Snatcher's act contracts into the pool as items""" + display_name = "Shuffle Contracts" + default = 1 + + +class ShuffleSubconPaintings(Toggle): + """If enabled, shuffle items into the pool that unlock Subcon Forest fire spirit paintings. + These items are progressive, with the order of Village-Swamp-Courtyard.""" + display_name = "Shuffle Subcon Paintings" + default = 0 + + +class StartingChapter(Choice): + """Determines which chapter you will be guaranteed to be able to enter at the beginning of the game.""" + display_name = "Starting Chapter" + option_1 = 1 + option_2 = 2 + option_3 = 3 + option_4 = 4 + default = 1 + + +class SDJLogic(Toggle): + """Allow the SDJ (Sprint Double Jump) technique to be considered in logic.""" + display_name = "SDJ Logic" + default = 0 + + +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 + + +# DLC +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!!!""" + display_name = "Shuffle Chapter 6" + default = 0 + + +class Tasksanity(Toggle): + """If enabled, Ship Shape tasks will become checks. Requires DLC1 content to be enabled.""" + display_name = "Tasksanity" + default = 0 + + +class TasksanityTaskStep(Range): + """How many tasks the player must complete in Tasksanity to send a check.""" + display_name = "Tasksanity Task Step" + range_start = 1 + range_end = 3 + default = 1 + + +class TasksanityCheckCount(Range): + """How many Tasksanity checks there will be in total.""" + display_name = "Tasksanity Check Count" + range_start = 5 + range_end = 30 + default = 18 + + +class EnableDLC2(Toggle): + """Shuffle content from Nyakuza Metro (Chapter 7) into the game. + DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE NYAKUZA METRO DLC INSTALLED!!!""" + display_name = "Shuffle Chapter 7" + default = 0 + + +class MetroMinPonCost(Range): + """The cheapest an item can be in any Nyakuza Metro shop. Includes ticket booths.""" + display_name = "Metro Shops Minimum Pon Cost" + range_start = 10 + range_end = 800 + default = 50 + + +class MetroMaxPonCost(Range): + """The most expensive an item can be in any Nyakuza Metro shop. Includes ticket booths.""" + display_name = "Metro Shops Minimum Pon Cost" + range_start = 10 + range_end = 800 + default = 200 + + +class NyakuzaThugMinShopItems(Range): + """The smallest amount of items that the thugs in Nyakuza Metro can have for sale.""" + display_name = "Nyakuza Thug Minimum Shop Items" + range_start = 0 + range_end = 5 + default = 2 + + +class NyakuzaThugMaxShopItems(Range): + """The largest amount of items that the thugs in Nyakuza Metro can have for sale.""" + display_name = "Nyakuza Thug Maximum Shop Items" + range_start = 0 + range_end = 5 + default = 4 + + +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.""" + display_name = "Baseball Bat" + default = 0 + + +class VanillaMetro(Choice): + """Force Nyakuza Metro (and optionally its finale) onto their vanilla locations in act shuffle.""" + display_name = "Vanilla Metro" + option_false = 0 + option_true = 1 + option_finale = 2 + + +class ChapterCostIncrement(Range): + """Lower values mean chapter costs increase slower. Higher values make the cost differences more steep.""" + display_name = "Chapter Cost Increment" + range_start = 1 + range_end = 8 + default = 4 + + +class ChapterCostMinDifference(Range): + """The minimum difference between chapter costs.""" + display_name = "Minimum Chapter Cost Difference" + range_start = 1 + range_end = 8 + default = 5 + + +class LowestChapterCost(Range): + """Value determining the lowest possible cost for a chapter. + Chapter costs will, progressively, be calculated based on this value (except for Chapter 5).""" + display_name = "Lowest Possible Chapter Cost" + range_start = 0 + range_end = 10 + default = 5 + + +class HighestChapterCost(Range): + """Value determining the highest possible cost for a chapter. + Chapter costs will, progressively, be calculated based on this value (except for Chapter 5).""" + display_name = "Highest Possible Chapter Cost" + range_start = 15 + range_end = 45 + default = 25 + + +class FinalChapterMinCost(Range): + """Minimum Time Pieces required to enter the final chapter. This is part of your goal.""" + display_name = "Final Chapter Minimum Time Piece Cost" + range_start = 0 + range_end = 50 + default = 30 + + +class FinalChapterMaxCost(Range): + """Maximum Time Pieces required to enter the final chapter. This is part of your goal.""" + display_name = "Final Chapter Maximum Time Piece Cost" + range_start = 0 + range_end = 50 + default = 35 + + +class MaxExtraTimePieces(Range): + """Maximum amount of extra Time Pieces from the DLCs. + Arctic Cruise will add up to 6. Nyakuza Metro will add up to 10. The absolute maximum is 56.""" + display_name = "Max Extra Time Pieces" + range_start = 0 + range_end = 16 + default = 16 + + +# Death Wish +class EnableDeathWish(Toggle): + """NOT IMPLEMENTED Shuffle Death Wish contracts into the game. + Each contract by default will have a single check granted upon completion. + DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE SEAL THE DEAL DLC INSTALLED!!!""" + display_name = "Enable Death Wish" + default = 0 + + +class DWEnableBonus(Toggle): + """NOT IMPLEMENTED In Death Wish, allow the full completion of contracts to reward items.""" + display_name = "Shuffle Death Wish Full Completions" + default = 0 + + +class DWExcludeAnnoyingContracts(Toggle): + """NOT IMPLEMENTED Exclude Death Wish contracts from the pool that are particularly tedious or take a long time to reach/clear.""" + display_name = "Exclude Annoying Death Wish Contracts" + default = 1 + + +class DWExcludeAnnoyingBonuses(Toggle): + """NOT IMPLEMENTED If Death Wish full completions are shuffled in, exclude particularly tedious Death Wish full completions + from the pool""" + display_name = "Exclude Annoying Death Wish Full Completions" + default = 1 + + +# Yarn +class YarnCostMin(Range): + """The minimum possible yarn needed to stitch each hat.""" + display_name = "Minimum Yarn Cost" + range_start = 1 + range_end = 12 + default = 4 + + +class YarnCostMax(Range): + """The maximum possible yarn needed to stitch each hat.""" + display_name = "Maximum Yarn Cost" + range_start = 1 + range_end = 12 + default = 8 + + +class YarnAvailable(Range): + """How much yarn is available to collect in the item pool.""" + display_name = "Yarn Available" + range_start = 30 + range_end = 75 + default = 45 + + +class MinPonCost(Range): + """The minimum amount of Pons that any shop item can cost.""" + display_name = "Minimum Shop Pon Cost" + range_start = 10 + range_end = 800 + default = 75 + + +class MaxPonCost(Range): + """The maximum amount of Pons that any shop item can cost.""" + display_name = "Maximum Shop Pon Cost" + range_start = 10 + range_end = 800 + default = 400 + + +class BadgeSellerMinItems(Range): + """The smallest amount of items that the Badge Seller can have for sale.""" + display_name = "Badge Seller Minimum Items" + range_start = 0 + range_end = 10 + default = 4 + + +class BadgeSellerMaxItems(Range): + """The largest amount of items that the Badge Seller can have for sale.""" + display_name = "Badge Seller Maximum Items" + range_start = 0 + range_end = 10 + default = 8 + + +# Traps +class TrapChance(Range): + """The chance for any junk item in the pool to be replaced by a trap.""" + display_name = "Trap Chance" + range_start = 0 + range_end = 100 + default = 0 + + +class BabyTrapWeight(Range): + """The weight of Baby Traps in the trap pool. + Baby Traps place a multitude of the Conductor's grandkids into Hat Kid's hands, causing her to lose her balance.""" + display_name = "Baby Trap Weight" + range_start = 0 + range_end = 100 + default = 40 + + +class LaserTrapWeight(Range): + """The weight of Laser Traps in the trap pool. + Laser Traps will spawn multiple giant lasers (from Snatcher's boss fight) at Hat Kid's location.""" + display_name = "Laser Trap Weight" + range_start = 0 + range_end = 100 + default = 40 + + +class ParadeTrapWeight(Range): + """The weight of Parade Traps in the trap pool. + Parade Traps will summon multiple Express Band owls with knives that chase Hat Kid by mimicking her movement.""" + display_name = "Parade Trap Weight" + range_start = 0 + range_end = 100 + default = 20 + + +ahit_options: typing.Dict[str, type(Option)] = { + + "EndGoal": EndGoal, + "ActRandomizer": ActRandomizer, + "ShuffleAlpineZiplines": ShuffleAlpineZiplines, + "VanillaAlpine": VanillaAlpine, + "LogicDifficulty": LogicDifficulty, + "RandomizeHatOrder": RandomizeHatOrder, + "UmbrellaLogic": UmbrellaLogic, + "StartWithCompassBadge": StartWithCompassBadge, + "CompassBadgeMode": CompassBadgeMode, + "ShuffleStorybookPages": ShuffleStorybookPages, + "ShuffleActContracts": ShuffleActContracts, + "ShuffleSubconPaintings": ShuffleSubconPaintings, + "StartingChapter": StartingChapter, + "SDJLogic": SDJLogic, + "CTRWithSprint": CTRWithSprint, + + "EnableDLC1": EnableDLC1, + "Tasksanity": Tasksanity, + "TasksanityTaskStep": TasksanityTaskStep, + "TasksanityCheckCount": TasksanityCheckCount, + + "EnableDeathWish": EnableDeathWish, + "EnableDLC2": EnableDLC2, + "BaseballBat": BaseballBat, + "VanillaMetro": VanillaMetro, + "MetroMinPonCost": MetroMinPonCost, + "MetroMaxPonCost": MetroMaxPonCost, + "NyakuzaThugMinShopItems": NyakuzaThugMinShopItems, + "NyakuzaThugMaxShopItems": NyakuzaThugMaxShopItems, + + "LowestChapterCost": LowestChapterCost, + "HighestChapterCost": HighestChapterCost, + "ChapterCostIncrement": ChapterCostIncrement, + "ChapterCostMinDifference": ChapterCostMinDifference, + "MaxExtraTimePieces": MaxExtraTimePieces, + + "FinalChapterMinCost": FinalChapterMinCost, + "FinalChapterMaxCost": FinalChapterMaxCost, + + "YarnCostMin": YarnCostMin, + "YarnCostMax": YarnCostMax, + "YarnAvailable": YarnAvailable, + + "MinPonCost": MinPonCost, + "MaxPonCost": MaxPonCost, + "BadgeSellerMinItems": BadgeSellerMinItems, + "BadgeSellerMaxItems": BadgeSellerMaxItems, + + "TrapChance": TrapChance, + "BabyTrapWeight": BabyTrapWeight, + "LaserTrapWeight": LaserTrapWeight, + "ParadeTrapWeight": ParadeTrapWeight, + + "death_link": DeathLink, +} + +slot_data_options: typing.Dict[str, type(Option)] = { + + "EndGoal": EndGoal, + "ActRandomizer": ActRandomizer, + "ShuffleAlpineZiplines": ShuffleAlpineZiplines, + "LogicDifficulty": LogicDifficulty, + "RandomizeHatOrder": RandomizeHatOrder, + "UmbrellaLogic": UmbrellaLogic, + "CompassBadgeMode": CompassBadgeMode, + "ShuffleStorybookPages": ShuffleStorybookPages, + "ShuffleActContracts": ShuffleActContracts, + "ShuffleSubconPaintings": ShuffleSubconPaintings, + "SDJLogic": SDJLogic, + + "EnableDLC1": EnableDLC1, + "Tasksanity": Tasksanity, + "TasksanityTaskStep": TasksanityTaskStep, + "TasksanityCheckCount": TasksanityCheckCount, + + "EnableDeathWish": EnableDeathWish, + + "EnableDLC2": EnableDLC2, + "MetroMinPonCost": MetroMinPonCost, + "MetroMaxPonCost": MetroMaxPonCost, + "BaseballBat": BaseballBat, + + "MinPonCost": MinPonCost, + "MaxPonCost": MaxPonCost, + + "death_link": DeathLink, +} diff --git a/worlds/ahit/Regions.py b/worlds/ahit/Regions.py new file mode 100644 index 0000000000..f7c74e4f94 --- /dev/null +++ b/worlds/ahit/Regions.py @@ -0,0 +1,760 @@ +from worlds.AutoWorld import World +from BaseClasses import Region, Entrance, ItemClassification, Location +from .Locations import HatInTimeLocation, location_table, storybook_pages, event_locs, is_location_valid, shop_locations +from .Items import HatInTimeItem +from .Types import ChapterIndex +import typing +from .Rules import set_rift_rules + + +# ChapterIndex: region +chapter_regions = { + ChapterIndex.SPACESHIP: "Spaceship", + ChapterIndex.MAFIA: "Mafia Town", + ChapterIndex.BIRDS: "Battle of the Birds", + ChapterIndex.SUBCON: "Subcon Forest", + ChapterIndex.ALPINE: "Alpine Skyline", + ChapterIndex.FINALE: "Time's End", + ChapterIndex.CRUISE: "The Arctic Cruise", + ChapterIndex.METRO: "Nyakuza Metro", +} + +# entrance: region +act_entrances = { + "Welcome to Mafia Town": "Mafia Town - Act 1", + "Barrel Battle": "Mafia Town - Act 2", + "She Came from Outer Space": "Mafia Town - Act 3", + "Down with the Mafia!": "Mafia Town - Act 4", + "Cheating the Race": "Mafia Town - Act 5", + "Heating Up Mafia Town": "Mafia Town - Act 6", + "The Golden Vault": "Mafia Town - Act 7", + + "Dead Bird Studio": "Battle of the Birds - Act 1", + "Murder on the Owl Express": "Battle of the Birds - Act 2", + "Picture Perfect": "Battle of the Birds - Act 3", + "Train Rush": "Battle of the Birds - Act 4", + "The Big Parade": "Battle of the Birds - Act 5", + "Award Ceremony": "Battle of the Birds - Finale A", + "Dead Bird Studio Basement": "Battle of the Birds - Finale B", + + "Contractual Obligations": "Subcon Forest - Act 1", + "The Subcon Well": "Subcon Forest - Act 2", + "Toilet of Doom": "Subcon Forest - Act 3", + "Queen Vanessa's Manor": "Subcon Forest - Act 4", + "Mail Delivery Service": "Subcon Forest - Act 5", + "Your Contract has Expired": "Subcon Forest - Finale", + + "Alpine Free Roam": "Alpine Skyline - Free Roam", + "The Illness has Spread": "Alpine Skyline - Finale", + + "The Finale": "Time's End - Act 1", + + "Bon Voyage!": "The Arctic Cruise - Act 1", + "Ship Shape": "The Arctic Cruise - Act 2", + "Rock the Boat": "The Arctic Cruise - Finale", + + "Nyakuza Free Roam": "Nyakuza Metro - Free Roam", + "Rush Hour": "Nyakuza Metro - Finale", +} + +act_chapters = { + "Time Rift - Gallery": "Spaceship", + "Time Rift - The Lab": "Spaceship", + + "Welcome to Mafia Town": "Mafia Town", + "Barrel Battle": "Mafia Town", + "She Came from Outer Space": "Mafia Town", + "Down with the Mafia!": "Mafia Town", + "Cheating the Race": "Mafia Town", + "Heating Up Mafia Town": "Mafia Town", + "The Golden Vault": "Mafia Town", + "Time Rift - Mafia of Cooks": "Mafia Town", + "Time Rift - Sewers": "Mafia Town", + "Time Rift - Bazaar": "Mafia Town", + + "Dead Bird Studio": "Battle of the Birds", + "Murder on the Owl Express": "Battle of the Birds", + "Picture Perfect": "Battle of the Birds", + "Train Rush": "Battle of the Birds", + "The Big Parade": "Battle of the Birds", + "Award Ceremony": "Battle of the Birds", + "Dead Bird Studio Basement": "Battle of the Birds", + "Time Rift - Dead Bird Studio": "Battle of the Birds", + "Time Rift - The Owl Express": "Battle of the Birds", + "Time Rift - The Moon": "Battle of the Birds", + + "Contractual Obligations": "Subcon Forest", + "The Subcon Well": "Subcon Forest", + "Toilet of Doom": "Subcon Forest", + "Queen Vanessa's Manor": "Subcon Forest", + "Mail Delivery Service": "Subcon Forest", + "Your Contract has Expired": "Subcon Forest", + "Time Rift - Sleepy Subcon": "Subcon Forest", + "Time Rift - Pipe": "Subcon Forest", + "Time Rift - Village": "Subcon Forest", + + "Alpine Free Roam": "Alpine Skyline", + "The Illness has Spread": "Alpine Skyline", + "Time Rift - Alpine Skyline": "Alpine Skyline", + "Time Rift - The Twilight Bell": "Alpine Skyline", + "Time Rift - Curly Tail Trail": "Alpine Skyline", + + "The Finale": "Time's End", + "Time Rift - Tour": "Time's End", + + "Bon Voyage!": "The Arctic Cruise", + "Ship Shape": "The Arctic Cruise", + "Rock the Boat": "The Arctic Cruise", + "Time Rift - Balcony": "The Arctic Cruise", + "Time Rift - Deep Sea": "The Arctic Cruise", + + "Nyakuza Free Roam": "Nyakuza Metro", + "Rush Hour": "Nyakuza Metro", + "Time Rift - Rumbi Factory": "Nyakuza Metro", +} + +# region: list[Region] +rift_access_regions = { + "Time Rift - Gallery": ["Spaceship"], + "Time Rift - The Lab": ["Spaceship"], + + "Time Rift - Sewers": ["Welcome to Mafia Town", "Barrel Battle", "She Came from Outer Space", + "Down with the Mafia!", "Cheating the Race", "Heating Up Mafia Town", + "The Golden Vault"], + + "Time Rift - Bazaar": ["Welcome to Mafia Town", "Barrel Battle", "She Came from Outer Space", + "Down with the Mafia!", "Cheating the Race", "Heating Up Mafia Town", + "The Golden Vault"], + + "Time Rift - Mafia of Cooks": ["Welcome to Mafia Town", "Barrel Battle", "She Came from Outer Space", + "Down with the Mafia!", "Cheating the Race", "The Golden Vault"], + + "Time Rift - The Owl Express": ["Murder on the Owl Express"], + "Time Rift - The Moon": ["Picture Perfect", "The Big Parade"], + "Time Rift - Dead Bird Studio": ["Dead Bird Studio", "Dead Bird Studio Basement"], + + "Time Rift - Pipe": ["Contractual Obligations", "The Subcon Well", + "Toilet of Doom", "Queen Vanessa's Manor", + "Mail Delivery Service"], + + "Time Rift - Village": ["Contractual Obligations", "The Subcon Well", + "Toilet of Doom", "Queen Vanessa's Manor", + "Mail Delivery Service"], + + "Time Rift - Sleepy Subcon": ["Contractual Obligations", "The Subcon Well", + "Toilet of Doom", "Queen Vanessa's Manor", + "Mail Delivery Service"], + + "Time Rift - The Twilight Bell": ["Alpine Free Roam"], + "Time Rift - Curly Tail Trail": ["Alpine Free Roam"], + "Time Rift - Alpine Skyline": ["Alpine Free Roam", "The Illness has Spread"], + + "Time Rift - Tour": ["Time's End"], + + "Time Rift - Balcony": ["Cruise Ship"], + "Time Rift - Deep Sea": ["Cruise Ship"], + + "Time Rift - Rumbi Factory": ["Nyakuza Free Roam"], +} + +# Hat_ChapterActInfo, from the game files to be used in act shuffle +chapter_act_info = { + "Time Rift - Gallery": "hatintime_chapterinfo.spaceship.Spaceship_WaterRift_Gallery", + "Time Rift - The Lab": "hatintime_chapterinfo.spaceship.Spaceship_WaterRift_MailRoom", + + "Welcome to Mafia Town": "hatintime_chapterinfo.MafiaTown.MafiaTown_Welcome", + "Barrel Battle": "hatintime_chapterinfo.MafiaTown.MafiaTown_BarrelBattle", + "She Came from Outer Space": "hatintime_chapterinfo.MafiaTown.MafiaTown_AlienChase", + "Down with the Mafia!": "hatintime_chapterinfo.MafiaTown.MafiaTown_MafiaBoss", + "Cheating the Race": "hatintime_chapterinfo.MafiaTown.MafiaTown_Race", + "Heating Up Mafia Town": "hatintime_chapterinfo.MafiaTown.MafiaTown_Lava", + "The Golden Vault": "hatintime_chapterinfo.MafiaTown.MafiaTown_GoldenVault", + "Time Rift - Mafia of Cooks": "hatintime_chapterinfo.MafiaTown.MafiaTown_CaveRift_Mafia", + "Time Rift - Sewers": "hatintime_chapterinfo.MafiaTown.MafiaTown_WaterRift_Easy", + "Time Rift - Bazaar": "hatintime_chapterinfo.MafiaTown.MafiaTown_WaterRift_Hard", + + "Dead Bird Studio": "hatintime_chapterinfo.BattleOfTheBirds.BattleOfTheBirds_DeadBirdStudio", + "Murder on the Owl Express": "hatintime_chapterinfo.BattleOfTheBirds.BattleOfTheBirds_Murder", + "Picture Perfect": "hatintime_chapterinfo.BattleOfTheBirds.BattleOfTheBirds_PicturePerfect", + "Train Rush": "hatintime_chapterinfo.BattleOfTheBirds.BattleOfTheBirds_TrainRush", + "The Big Parade": "hatintime_chapterinfo.BattleOfTheBirds.BattleOfTheBirds_Parade", + "Award Ceremony": "hatintime_chapterinfo.BattleOfTheBirds.BattleOfTheBirds_AwardCeremony", + "Dead Bird Studio Basement": "DeadBirdBasement", # Dead Bird Studio Basement has no ChapterActInfo + "Time Rift - Dead Bird Studio": "hatintime_chapterinfo.BattleOfTheBirds.BattleOfTheBirds_CaveRift_Basement", + "Time Rift - The Owl Express": "hatintime_chapterinfo.BattleOfTheBirds.BattleOfTheBirds_WaterRift_Panels", + "Time Rift - The Moon": "hatintime_chapterinfo.BattleOfTheBirds.BattleOfTheBirds_WaterRift_Parade", + + "Contractual Obligations": "hatintime_chapterinfo.subconforest.SubconForest_IceWall", + "The Subcon Well": "hatintime_chapterinfo.subconforest.SubconForest_Cave", + "Toilet of Doom": "hatintime_chapterinfo.subconforest.SubconForest_Toilet", + "Queen Vanessa's Manor": "hatintime_chapterinfo.subconforest.SubconForest_Manor", + "Mail Delivery Service": "hatintime_chapterinfo.subconforest.SubconForest_MailDelivery", + "Your Contract has Expired": "hatintime_chapterinfo.subconforest.SubconForest_SnatcherBoss", + "Time Rift - Sleepy Subcon": "hatintime_chapterinfo.subconforest.SubconForest_CaveRift_Raccoon", + "Time Rift - Pipe": "hatintime_chapterinfo.subconforest.SubconForest_WaterRift_Hookshot", + "Time Rift - Village": "hatintime_chapterinfo.subconforest.SubconForest_WaterRift_Dwellers", + + "Alpine Free Roam": "hatintime_chapterinfo.AlpineSkyline.AlpineSkyline_IntroMountain", + "The Illness has Spread": "hatintime_chapterinfo.AlpineSkyline.AlpineSkyline_Finale", + "Time Rift - Alpine Skyline": "hatintime_chapterinfo.AlpineSkyline.AlpineSkyline_CaveRift_Alpine", + "Time Rift - The Twilight Bell": "hatintime_chapterinfo.AlpineSkyline.AlpineSkyline_WaterRift_Goats", + "Time Rift - Curly Tail Trail": "hatintime_chapterinfo.AlpineSkyline.AlpineSkyline_WaterRift_Cats", + + "The Finale": "hatintime_chapterinfo.TheFinale.TheFinale_FinalBoss", + "Time Rift - Tour": "hatintime_chapterinfo_dlc1.spaceship.CaveRift_Tour", + + "Bon Voyage!": "hatintime_chapterinfo_dlc1.Cruise.Cruise_Boarding", + "Ship Shape": "hatintime_chapterinfo_dlc1.Cruise.Cruise_Working", + "Rock the Boat": "hatintime_chapterinfo_dlc1.Cruise.Cruise_Sinking", + "Time Rift - Balcony": "hatintime_chapterinfo_dlc1.Cruise.Cruise_WaterRift_Slide", + "Time Rift - Deep Sea": "hatintime_chapterinfo_dlc1.Cruise.Cruise_CaveRift", + + "Nyakuza Free Roam": "hatintime_chapterinfo_dlc2.metro.Metro_FreeRoam", + "Rush Hour": "hatintime_chapterinfo_dlc2.metro.Metro_Escape", + "Time Rift - Rumbi Factory": "hatintime_chapterinfo_dlc2.metro.Metro_RumbiFactory" +} + +# Guarantee that the first level a player can access is a location dense area beatable with no items +guaranteed_first_acts = [ + "Welcome to Mafia Town", + "Barrel Battle", + "She Came from Outer Space", + "Down with the Mafia!", + "Heating Up Mafia Town", # Removed in umbrella logic + "The Golden Vault", + + "Contractual Obligations", # Removed in painting logic + "Queen Vanessa's Manor", # Removed in umbrella/painting logic +] + +purple_time_rifts = [ + "Time Rift - Mafia of Cooks", + "Time Rift - Dead Bird Studio", + "Time Rift - Sleepy Subcon", + "Time Rift - Alpine Skyline", + "Time Rift - Deep Sea", + "Time Rift - Tour", + "Time Rift - Rumbi Factory", +] + +# Acts blacklisted in act shuffle +# entrance: region +blacklisted_acts = { + "Battle of the Birds - Finale A": "Award Ceremony", + "Time's End - Act 1": "The Finale", +} + + +def create_regions(world: World): + w = world + mw = world.multiworld + p = world.player + + # ------------------------------------------- HUB -------------------------------------------------- # + menu = create_region(w, "Menu") + spaceship = create_region_and_connect(w, "Spaceship", "Save File -> Spaceship", menu) + create_rift_connections(w, create_region(w, "Time Rift - Gallery")) + create_rift_connections(w, create_region(w, "Time Rift - The Lab")) + + # ------------------------------------------- MAFIA TOWN ------------------------------------------- # + mafia_town = create_region_and_connect(w, "Mafia Town", "Telescope -> Mafia Town", spaceship) + mt_act1 = create_region_and_connect(w, "Welcome to Mafia Town", "Mafia Town - Act 1", mafia_town) + mt_act2 = create_region_and_connect(w, "Barrel Battle", "Mafia Town - Act 2", mafia_town) + mt_act3 = create_region_and_connect(w, "She Came from Outer Space", "Mafia Town - Act 3", mafia_town) + mt_act4 = create_region_and_connect(w, "Down with the Mafia!", "Mafia Town - Act 4", mafia_town) + mt_act6 = create_region_and_connect(w, "Heating Up Mafia Town", "Mafia Town - Act 6", mafia_town) + mt_act5 = create_region_and_connect(w, "Cheating the Race", "Mafia Town - Act 5", mafia_town) + mt_act7 = create_region_and_connect(w, "The Golden Vault", "Mafia Town - Act 7", mafia_town) + + # ------------------------------------------- BOTB ------------------------------------------------- # + botb = create_region_and_connect(w, "Battle of the Birds", "Telescope -> Battle of the Birds", spaceship) + dbs = create_region_and_connect(w, "Dead Bird Studio", "Battle of the Birds - Act 1", botb) + create_region_and_connect(w, "Murder on the Owl Express", "Battle of the Birds - Act 2", botb) + create_region_and_connect(w, "Picture Perfect", "Battle of the Birds - Act 3", botb) + create_region_and_connect(w, "Train Rush", "Battle of the Birds - Act 4", botb) + create_region_and_connect(w, "The Big Parade", "Battle of the Birds - Act 5", botb) + create_region_and_connect(w, "Award Ceremony", "Battle of the Birds - Finale A", botb) + create_region_and_connect(w, "Dead Bird Studio Basement", "Battle of the Birds - Finale B", botb) + create_rift_connections(w, create_region(w, "Time Rift - Dead Bird Studio")) + 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 + 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) + + # ------------------------------------------- SUBCON FOREST --------------------------------------- # + subcon_forest = create_region_and_connect(w, "Subcon Forest", "Telescope -> Subcon Forest", spaceship) + sf_act1 = create_region_and_connect(w, "Contractual Obligations", "Subcon Forest - Act 1", subcon_forest) + sf_act2 = create_region_and_connect(w, "The Subcon Well", "Subcon Forest - Act 2", subcon_forest) + sf_act3 = create_region_and_connect(w, "Toilet of Doom", "Subcon Forest - Act 3", subcon_forest) + sf_act4 = create_region_and_connect(w, "Queen Vanessa's Manor", "Subcon Forest - Act 4", subcon_forest) + sf_act5 = create_region_and_connect(w, "Mail Delivery Service", "Subcon Forest - Act 5", subcon_forest) + create_region_and_connect(w, "Your Contract has Expired", "Subcon Forest - Finale", subcon_forest) + + # ------------------------------------------- ALPINE SKYLINE ------------------------------------------ # + 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) + + create_region_and_connect(w, "The Birdhouse", "-> The Birdhouse", alpine_area) + create_region_and_connect(w, "The Lava Cake", "-> The Lava Cake", alpine_area) + create_region_and_connect(w, "The Windmill", "-> The Windmill", alpine_area) + 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) + 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")) + + # ------------------------------------------- OTHER -------------------------------------------------- # + mt_area: Region = create_region(w, "Mafia Town Area") + mt_area_humt: Region = create_region(w, "Mafia Town Area (HUMT)") + connect_regions(mt_area, mt_area_humt, "MT Area -> MT Area (HUMT)", p) + connect_regions(mt_act1, mt_area, "Mafia Town Entrance WTMT", p) + connect_regions(mt_act2, mt_area, "Mafia Town Entrance BB", p) + connect_regions(mt_act3, mt_area, "Mafia Town Entrance SCFOS", p) + connect_regions(mt_act4, mt_area, "Mafia Town Entrance DWTM", p) + connect_regions(mt_act5, mt_area, "Mafia Town Entrance CTR", p) + connect_regions(mt_act6, mt_area_humt, "Mafia Town Entrance HUMT", p) + connect_regions(mt_act7, mt_area, "Mafia Town Entrance TGV", p) + + create_rift_connections(w, create_region(w, "Time Rift - Mafia of Cooks")) + create_rift_connections(w, create_region(w, "Time Rift - Sewers")) + create_rift_connections(w, create_region(w, "Time Rift - Bazaar")) + + sf_area: Region = create_region(w, "Subcon Forest Area") + connect_regions(sf_act1, sf_area, "Subcon Forest Entrance CO", p) + connect_regions(sf_act2, sf_area, "Subcon Forest Entrance SW", p) + connect_regions(sf_act3, sf_area, "Subcon Forest Entrance TOD", p) + connect_regions(sf_act4, sf_area, "Subcon Forest Entrance QVM", p) + connect_regions(sf_act5, sf_area, "Subcon Forest Entrance MDS", p) + + create_rift_connections(w, create_region(w, "Time Rift - Sleepy Subcon")) + create_rift_connections(w, create_region(w, "Time Rift - Pipe")) + create_rift_connections(w, create_region(w, "Time Rift - Village")) + + badge_seller = create_badge_seller(w) + connect_regions(mt_area, badge_seller, "MT Area -> Badge Seller", p) + connect_regions(mt_area_humt, badge_seller, "MT Area (HUMT) -> Badge Seller", p) + connect_regions(sf_area, badge_seller, "SF Area -> Badge Seller", p) + 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) + + 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) + + # ------------------------------------------- DLC1 ------------------------------------------------- # + if mw.EnableDLC1[p].value > 0: + arctic_cruise = create_region_and_connect(w, "The Arctic Cruise", "Telescope -> The Arctic Cruise", spaceship) + cruise_ship = create_region(w, "Cruise Ship") + + ac_act1 = create_region_and_connect(w, "Bon Voyage!", "The Arctic Cruise - Act 1", arctic_cruise) + ac_act2 = create_region_and_connect(w, "Ship Shape", "The Arctic Cruise - Act 2", arctic_cruise) + ac_act3 = create_region_and_connect(w, "Rock the Boat", "The Arctic Cruise - Finale", arctic_cruise) + + connect_regions(ac_act1, cruise_ship, "Cruise Ship Entrance BV", p) + connect_regions(ac_act2, cruise_ship, "Cruise Ship Entrance SS", p) + connect_regions(ac_act3, cruise_ship, "Cruise Ship Entrance RTB", p) + create_rift_connections(w, create_region(w, "Time Rift - Balcony")) + create_rift_connections(w, create_region(w, "Time Rift - Deep Sea")) + create_rift_connections(w, create_region(w, "Time Rift - Tour")) + + if mw.Tasksanity[p].value > 0: + create_tasksanity_locations(w) + + # force recache + mw.get_region("Time Rift - Deep Sea", p) + + connect_regions(mw.get_region("Cruise Ship", p), badge_seller, "CS -> Badge Seller", p) + + if mw.EnableDLC2[p].value > 0: + nyakuza_metro = create_region_and_connect(w, "Nyakuza Metro", "Telescope -> Nyakuza Metro", spaceship) + metro_freeroam = create_region_and_connect(w, "Nyakuza Free Roam", "Nyakuza Metro - Free Roam", nyakuza_metro) + create_region_and_connect(w, "Rush Hour", "Nyakuza Metro - Finale", nyakuza_metro) + + yellow = create_region_and_connect(w, "Yellow Overpass Station", "-> Yellow Overpass Station", metro_freeroam) + green = create_region_and_connect(w, "Green Clean Station", "-> Green Clean Station", metro_freeroam) + pink = create_region_and_connect(w, "Pink Paw Station", "-> Pink Paw Station", metro_freeroam) + create_region_and_connect(w, "Bluefin Tunnel", "-> Bluefin Tunnel", metro_freeroam) # No manhole + + create_region_and_connect(w, "Yellow Overpass Manhole", "-> Yellow Overpass Manhole", yellow) + create_region_and_connect(w, "Green Clean Manhole", "-> Green Clean Manhole", green) + create_region_and_connect(w, "Pink Paw Manhole", "-> Pink Paw Manhole", pink) + + create_rift_connections(w, create_region(w, "Time Rift - Rumbi Factory")) + create_thug_shops(w) + + # force recache + mw.get_region("Time Rift - Sewers", p) + + +def create_rift_connections(world: World, region: Region): + i = 1 + for name in rift_access_regions[region.name]: + act_region = world.multiworld.get_region(name, world.player) + entrance_name = "{name} Portal - Entrance {num}" + connect_regions(act_region, region, entrance_name.format(name=region.name, num=i), world.player) + i += 1 + + +def create_tasksanity_locations(world: World): + ship_shape: Region = world.multiworld.get_region("Ship Shape", world.player) + id_start: int = 300204 + for i in range(world.multiworld.TasksanityCheckCount[world.player].value): + location = HatInTimeLocation(world.player, format("Tasksanity Check %i" % (i+1)), id_start+i, ship_shape) + ship_shape.locations.append(location) + # world.location_name_to_id.setdefault(location.name, location.address) + + +def randomize_act_entrances(world: World): + region_list: typing.List[Region] = get_act_regions(world) + world.multiworld.random.shuffle(region_list) + + separate_rifts: bool = bool(world.multiworld.ActRandomizer[world.player].value == 1) + + for region in region_list.copy(): + if (act_chapters[region.name] == "Alpine Skyline" or act_chapters[region.name] == "Nyakuza Metro") \ + and "Time Rift" not in region.name: + region_list.remove(region) + region_list.append(region) + + for region in region_list.copy(): + if "Time Rift" in region.name: + region_list.remove(region) + region_list.append(region) + + # Reverse the list, so we can do what we want to do first + region_list.reverse() + + shuffled_list: typing.List[Region] = [] + mapped_list: typing.List[Region] = [] + rift_dict: typing.Dict[str, Region] = {} + first_chapter: Region = get_first_chapter_region(world) + has_guaranteed: bool = False + + i: int = 0 + while i < len(region_list): + region = region_list[i] + i += 1 + + # Get the first accessible act, so we can map that to something first + if not has_guaranteed: + if act_chapters[region.name] != first_chapter.name: + continue + + if region.name not in act_entrances.keys() or "Act 1" not in act_entrances[region.name] \ + and "Free Roam" not in act_entrances[region.name]: + continue + + i = 0 + + # Already mapped to something else + if region in mapped_list: + continue + + mapped_list.append(region) + + # Look for candidates to map this act to + candidate_list: typing.List[Region] = [] + for candidate in region_list: + + if world.multiworld.VanillaAlpine[world.player].value > 0 and region.name == "Alpine Free Roam" \ + or world.multiworld.VanillaAlpine[world.player].value == 2 and region.name == "The Illness has Spread": + candidate_list.append(region) + break + + if world.multiworld.VanillaMetro[world.player].value > 0 and region.name == "Nyakuza Free Roam": + candidate_list.append(region) + break + + if region.name == "Rush Hour": + if world.multiworld.EndGoal[world.player].value == 2 or \ + world.multiworld.VanillaMetro[world.player].value == 2: + candidate_list.append(region) + break + + # We're mapping something to the first act, make sure it is valid + if not has_guaranteed: + if candidate.name not in guaranteed_first_acts: + continue + + # Not completable without Umbrella + if world.multiworld.UmbrellaLogic[world.player].value > 0 \ + and (candidate.name == "Heating Up Mafia Town" or candidate.name == "Queen Vanessa's Manor"): + continue + + # Subcon sphere 1 is too small without painting unlocks, and no acts are completable either + if world.multiworld.ShuffleSubconPaintings[world.player].value > 0 \ + and "Subcon Forest" in act_entrances[candidate.name]: + continue + + candidate_list.append(candidate) + has_guaranteed = True + break + + # Already mapped onto something else + if candidate in shuffled_list: + continue + + if separate_rifts: + # Don't map Time Rifts to normal acts + if "Time Rift" in region.name and "Time Rift" not in candidate.name: + continue + + # Don't map normal acts to Time Rifts + if "Time Rift" not in region.name and "Time Rift" in candidate.name: + continue + + # Separate purple rifts + if region.name in purple_time_rifts and candidate.name not in purple_time_rifts \ + or region.name not in purple_time_rifts and candidate.name in purple_time_rifts: + continue + + # Don't map Alpine to its own finale + if region.name == "The Illness has Spread" and candidate.name == "Alpine Free Roam": + continue + + # Ditto for Metro + if region.name == "Rush Hour" and candidate.name == "Nyakuza Free Roam": + continue + + if region.name in rift_access_regions and candidate.name in rift_access_regions[region.name]: + continue + + candidate_list.append(candidate) + + candidate: Region = candidate_list[world.multiworld.random.randint(0, len(candidate_list)-1)] + shuffled_list.append(candidate) + + # Vanilla + if candidate.name == region.name: + if region.name in rift_access_regions.keys(): + rift_dict.setdefault(region.name, candidate) + + world.update_chapter_act_info(region, candidate) + continue + + if region.name in rift_access_regions.keys(): + connect_time_rift(world, region, candidate) + rift_dict.setdefault(region.name, candidate) + else: + if candidate.name in rift_access_regions.keys(): + for e in candidate.entrances.copy(): + e.parent_region.exits.remove(e) + e.connected_region.entrances.remove(e) + del e.parent_region + del e.connected_region + + entrance = world.multiworld.get_entrance(act_entrances[region.name], world.player) + reconnect_regions(entrance, world.multiworld.get_region(act_chapters[region.name], world.player), candidate) + + world.update_chapter_act_info(region, candidate) + + for name in blacklisted_acts.values(): + if not is_act_blacklisted(world, name): + continue + + region: Region = world.multiworld.get_region(name, world.player) + world.update_chapter_act_info(region, region) + + set_rift_rules(world, rift_dict) + + +def connect_time_rift(world: World, time_rift: Region, exit_region: Region): + count: int = len(rift_access_regions[time_rift.name]) + i: int = 1 + while i <= count: + name = format("%s Portal - Entrance %i" % (time_rift.name, i)) + entrance: Entrance = world.multiworld.get_entrance(name, world.player) + reconnect_regions(entrance, entrance.parent_region, exit_region) + i += 1 + + +def get_act_regions(world: World) -> typing.List[Region]: + act_list: typing.List[Region] = [] + for region in world.multiworld.get_regions(world.player): + if region.name in chapter_act_info.keys(): + if not is_act_blacklisted(world, region.name): + act_list.append(region) + + return act_list + + +def is_act_blacklisted(world: World, name: str) -> bool: + if name == "The Finale": + return world.multiworld.EndGoal[world.player].value == 1 + + return name in blacklisted_acts.values() + + +def create_region(world: World, name: str) -> Region: + reg = Region(name, world.player, world.multiworld) + + for (key, data) in location_table.items(): + if data.nyakuza_thug != "": + continue + + if data.region == name: + if key in storybook_pages.keys() \ + and world.multiworld.ShuffleStorybookPages[world.player].value == 0: + continue + + location = HatInTimeLocation(world.player, key, data.id, reg) + reg.locations.append(location) + if location.name in shop_locations: + world.shop_locs.append(location.name) + + world.multiworld.regions.append(reg) + return reg + + +def create_badge_seller(world: World) -> Region: + badge_seller = Region("Badge Seller", world.player, world.multiworld) + world.multiworld.regions.append(badge_seller) + count: int = 0 + max_items: int = 0 + + if world.multiworld.BadgeSellerMaxItems[world.player].value > 0: + max_items = world.multiworld.random.randint(world.multiworld.BadgeSellerMinItems[world.player].value, + world.multiworld.BadgeSellerMaxItems[world.player].value) + + if max_items <= 0: + world.badge_seller_count = 0 + return badge_seller + + for (key, data) in shop_locations.items(): + if "Badge Seller" not in key: + continue + + location = HatInTimeLocation(world.player, key, data.id, badge_seller) + badge_seller.locations.append(location) + world.shop_locs.append(location.name) + + count += 1 + if count >= max_items: + break + + world.badge_seller_count = max_items + return badge_seller + + +def connect_regions(start_region: Region, exit_region: Region, entrancename: str, player: int): + entrance = Entrance(player, entrancename, start_region) + start_region.exits.append(entrance) + entrance.connect(exit_region) + + +# Takes an entrance, removes its old connections, and reconnects it between the two regions specified. +def reconnect_regions(entrance: Entrance, start_region: Region, exit_region: Region): + if entrance in entrance.connected_region.entrances: + entrance.connected_region.entrances.remove(entrance) + + if entrance in entrance.parent_region.exits: + entrance.parent_region.exits.remove(entrance) + + if entrance in start_region.exits: + start_region.exits.remove(entrance) + + if entrance in exit_region.entrances: + exit_region.entrances.remove(entrance) + + entrance.parent_region = start_region + start_region.exits.append(entrance) + entrance.connect(exit_region) + + +def create_region_and_connect(world: World, + name: str, entrancename: str, connected_region: Region, is_exit: bool = True) -> Region: + + reg: Region = create_region(world, name) + entrance_region: Region + exit_region: Region + + if is_exit: + entrance_region = connected_region + exit_region = reg + else: + entrance_region = reg + exit_region = connected_region + + connect_regions(entrance_region, exit_region, entrancename, world.player) + return reg + + +def get_first_chapter_region(world: World) -> Region: + start_chapter: ChapterIndex = world.multiworld.StartingChapter[world.player] + return world.multiworld.get_region(chapter_regions.get(start_chapter), world.player) + + +def get_act_original_chapter(world: World, act_name: str) -> Region: + return world.multiworld.get_region(act_chapters[act_name], world.player) + + +def create_thug_shops(world: World): + min_items: int = world.multiworld.NyakuzaThugMinShopItems[world.player].value + max_items: int = world.multiworld.NyakuzaThugMaxShopItems[world.player].value + count: int = -1 + step: int = 0 + old_name: str = "" + + for key, data in shop_locations.items(): + if data.nyakuza_thug == "": + continue + + if old_name != "" and old_name == data.nyakuza_thug: + continue + + try: + if world.nyakuza_thug_items[data.nyakuza_thug] <= 0: + continue + except KeyError: + pass + + if count == -1: + count = world.multiworld.random.randint(min_items, max_items) + world.nyakuza_thug_items.setdefault(data.nyakuza_thug, count) + if count <= 0: + continue + + if count >= 1: + region = world.multiworld.get_region(data.region, world.player) + loc = HatInTimeLocation(world.player, key, data.id, region) + region.locations.append(loc) + world.shop_locs.append(loc.name) + + step += 1 + if step >= count: + old_name = data.nyakuza_thug + step = 0 + count = -1 + + +def create_events(world: World) -> int: + count: int = 0 + + for (name, data) in event_locs.items(): + if not is_location_valid(world, name): + continue + + event: Location = create_event(name, world.multiworld.get_region(data.region, world.player), world) + + if data.act_complete_event: + act_completion: str = format("Act Completion (%s)" % data.region) + event.access_rule = world.multiworld.get_location(act_completion, world.player).access_rule + + count += 1 + + return count + + +def create_event(name: str, region: Region, world: World) -> Location: + event = HatInTimeLocation(world.player, name, None, region) + region.locations.append(event) + event.place_locked_item(HatInTimeItem(name, ItemClassification.progression, None, world.player)) + return event diff --git a/worlds/ahit/Rules.py b/worlds/ahit/Rules.py new file mode 100644 index 0000000000..4ba0b45f1a --- /dev/null +++ b/worlds/ahit/Rules.py @@ -0,0 +1,682 @@ +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, \ + shop_locations +from .Types import HatType, ChapterIndex +from BaseClasses import Location, Entrance, Region +import typing + + +act_connections = { + "Mafia Town - Act 2": ["Mafia Town - Act 1"], + "Mafia Town - Act 3": ["Mafia Town - Act 1"], + "Mafia Town - Act 4": ["Mafia Town - Act 2", "Mafia Town - Act 3"], + "Mafia Town - Act 6": ["Mafia Town - Act 4"], + "Mafia Town - Act 7": ["Mafia Town - Act 4"], + "Mafia Town - Act 5": ["Mafia Town - Act 6", "Mafia Town - Act 7"], + + "Battle of the Birds - Act 2": ["Battle of the Birds - Act 1"], + "Battle of the Birds - Act 3": ["Battle of the Birds - Act 1"], + "Battle of the Birds - Act 4": ["Battle of the Birds - Act 2", "Battle of the Birds - Act 3"], + "Battle of the Birds - Act 5": ["Battle of the Birds - Act 2", "Battle of the Birds - Act 3"], + "Battle of the Birds - Finale A": ["Battle of the Birds - Act 4", "Battle of the Birds - Act 5"], + "Battle of the Birds - Finale B": ["Battle of the Birds - Finale A"], + + "Subcon Forest - Finale": ["Subcon Forest - Act 1", "Subcon Forest - Act 2", + "Subcon Forest - Act 3", "Subcon Forest - Act 4", + "Subcon Forest - Act 5"], + + "The Arctic Cruise - Act 2": ["The Arctic Cruise - Act 1"], + "The Arctic Cruise - Finale": ["The Arctic Cruise - Act 2"], +} + + +def can_use_hat(state: CollectionState, world: World, hat: HatType) -> bool: + return get_remaining_hat_cost(state, world, hat) <= 0 + + +def get_remaining_hat_cost(state: CollectionState, world: World, hat: HatType) -> int: + cost: int = 0 + for h in world.hat_craft_order: + cost += world.hat_yarn_costs.get(h) + if h == hat: + break + + return max(cost - state.count("Yarn", world.player), 0) + + +def can_sdj(state: CollectionState, world: World): + return can_use_hat(state, world, HatType.SPRINT) + + +def can_use_hookshot(state: CollectionState, world: World): + return state.has("Hookshot Badge", world.player) + + +def has_relic_combo(state: CollectionState, world: World, relic: str) -> bool: + return state.has_group(relic, world.player, len(world.item_name_groups[relic])) + + +def get_relic_count(state: CollectionState, world: World, relic: str) -> int: + return state.count_group(relic, world.player) + + +def can_hit_bells(state: CollectionState, world: World): + if world.multiworld.UmbrellaLogic[world.player].value == 0: + return True + + return state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING) + + +# Only use for rifts +def can_clear_act(state: CollectionState, world: World, act_entrance: str) -> bool: + entrance: Entrance = world.multiworld.get_entrance(act_entrance, world.player) + if not state.can_reach(entrance.connected_region, player=world.player): + return False + + if "Free Roam" in entrance.connected_region.name: + return True + + name: str = format("Act Completion (%s)" % entrance.connected_region.name) + return world.multiworld.get_location(name, world.player).access_rule(state) + + +def can_clear_alpine(state: CollectionState, world: World) -> bool: + return state.has("Birdhouse Cleared", world.player) and state.has("Lava Cake Cleared", world.player) \ + and state.has("Windmill Cleared", world.player) and state.has("Twilight Bell Cleared", world.player) + + +def can_clear_metro(state: CollectionState, world: World) -> bool: + return state.has("Nyakuza Intro Cleared", world.player) \ + and state.has("Yellow Overpass Station Cleared", world.player) \ + and state.has("Yellow Overpass Manhole Cleared", world.player) \ + and state.has("Green Clean Station Cleared", world.player) \ + and state.has("Green Clean Manhole Cleared", world.player) \ + and state.has("Bluefin Tunnel Cleared", world.player) \ + and state.has("Pink Paw Station Cleared", world.player) \ + and state.has("Pink Paw Manhole Cleared", world.player) + + +def set_rules(world: World): + w = world + mw = world.multiworld + p = world.player + + dlc1: bool = bool(mw.EnableDLC1[p].value > 0) + dlc2: bool = bool(mw.EnableDLC2[p].value > 0) + + # First, chapter access + starting_chapter = ChapterIndex(mw.StartingChapter[p].value) + w.set_chapter_cost(starting_chapter, 0) + + # Chapter costs increase progressively. Randomly decide the chapter order, except for Finale + chapter_list: typing.List[ChapterIndex] = [ChapterIndex.MAFIA, ChapterIndex.BIRDS, + ChapterIndex.SUBCON, ChapterIndex.ALPINE] + + final_chapter = ChapterIndex.FINALE + if mw.EndGoal[p].value == 2: + final_chapter = ChapterIndex.METRO + chapter_list.append(ChapterIndex.FINALE) + + if dlc1: + chapter_list.append(ChapterIndex.CRUISE) + + if dlc2 and final_chapter is not ChapterIndex.METRO: + chapter_list.append(ChapterIndex.METRO) + + chapter_list.remove(starting_chapter) + mw.random.shuffle(chapter_list) + + if starting_chapter is not ChapterIndex.ALPINE and dlc1 or dlc2: + index1: int = 69 + index2: int = 69 + lowest_index: int + chapter_list.remove(ChapterIndex.ALPINE) + + if dlc1: + index1 = chapter_list.index(ChapterIndex.CRUISE) + + if dlc2 and final_chapter is not ChapterIndex.METRO: + index2 = chapter_list.index(ChapterIndex.METRO) + + lowest_index = min(index1, index2) + if lowest_index == 0: + pos = 0 + else: + pos = mw.random.randint(0, lowest_index) + + chapter_list.insert(pos, ChapterIndex.ALPINE) + + lowest_cost: int = mw.LowestChapterCost[p].value + highest_cost: int = mw.HighestChapterCost[p].value + + cost_increment: int = mw.ChapterCostIncrement[p].value + min_difference: int = mw.ChapterCostMinDifference[p].value + last_cost: int = 0 + cost: int + loop_count: int = 0 + + for chapter in chapter_list: + min_range: int = lowest_cost + (cost_increment * loop_count) + if min_range >= highest_cost: + min_range = highest_cost-1 + + value: int = mw.random.randint(min_range, min(highest_cost, max(lowest_cost, last_cost + cost_increment))) + + cost = mw.random.randint(value, min(value + cost_increment, highest_cost)) + if loop_count >= 1: + if last_cost + min_difference > cost: + cost = last_cost + min_difference + + cost = min(cost, highest_cost) + w.set_chapter_cost(chapter, cost) + last_cost = cost + loop_count += 1 + + w.set_chapter_cost(final_chapter, mw.random.randint(mw.FinalChapterMinCost[p].value, + mw.FinalChapterMaxCost[p].value)) + + add_rule(mw.get_entrance("Telescope -> Mafia Town", p), + lambda state: state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.MAFIA))) + + add_rule(mw.get_entrance("Telescope -> Battle of the Birds", p), + lambda state: state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.BIRDS))) + + add_rule(mw.get_entrance("Telescope -> Subcon Forest", p), + lambda state: state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.SUBCON))) + + add_rule(mw.get_entrance("Telescope -> Alpine Skyline", p), + lambda state: state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.ALPINE))) + + add_rule(mw.get_entrance("Telescope -> Time's End", p), + lambda state: state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.FINALE)) + and can_use_hat(state, w, HatType.BREWING) and can_use_hat(state, w, HatType.DWELLER)) + + if dlc1: + add_rule(mw.get_entrance("Telescope -> The Arctic Cruise", p), + lambda state: state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.ALPINE)) + and state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.CRUISE))) + + if dlc2: + add_rule(mw.get_entrance("Telescope -> Nyakuza Metro", p), + lambda state: state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.ALPINE)) + and state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.METRO)) + and can_use_hat(state, w, HatType.DWELLER) and can_use_hat(state, w, HatType.ICE)) + + if mw.ActRandomizer[p].value == 0: + set_default_rift_rules(w) + + location: Location + for (key, data) in location_table.items(): + if not is_location_valid(w, key): + continue + + if key in contract_locations.keys(): + continue + + location = mw.get_location(key, p) + + # 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" and key not in tihs_locations: + add_rule(location, lambda state: state.can_reach("Alpine Free Roam", "Region", p), "and") + + 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", p), "and") + + for hat in data.required_hats: + if hat is not HatType.NONE: + add_rule(location, lambda state: can_use_hat(state, w, hat)) + + if data.required_tps > 0: + add_rule(location, lambda state: state.has("Time Piece", p, data.required_tps)) + + if data.hookshot: + add_rule(location, lambda state: can_use_hookshot(state, w)) + + if data.umbrella and mw.UmbrellaLogic[p].value > 0: + add_rule(location, lambda state: state.has("Umbrella", p)) + + if data.dweller_bell > 0: + if data.dweller_bell == 1: # Required to be hit regardless of Dweller Mask + add_rule(location, lambda state: can_hit_bells(state, w)) + else: # Can bypass with Dweller Mask + add_rule(location, lambda state: can_hit_bells(state, w) or can_use_hat(state, w, HatType.DWELLER)) + + if data.paintings > 0 and mw.ShuffleSubconPaintings[p].value > 0: + value: int = data.paintings + add_rule(location, lambda state: state.count("Progressive Painting Unlock", p) >= value) + + set_specific_rules(w) + + if mw.LogicDifficulty[p].value >= 1: + mw.SDJLogic[p].value = 1 + + if mw.SDJLogic[p].value > 0: + set_sdj_rules(world) + + if mw.ShuffleAlpineZiplines[p].value > 0: + set_alps_zipline_rules(w) + + for (key, acts) in act_connections.items(): + if "Arctic Cruise" in key and not dlc1: + continue + + i: int = 1 + entrance: Entrance = mw.get_entrance(key, p) + region: Region = entrance.connected_region + access_rules: typing.List[typing.Callable[[CollectionState], bool]] = [] + entrance.parent_region.exits.remove(entrance) + del entrance.parent_region + + # Entrances to this act that we have to set access_rules on + entrances: typing.List[Entrance] = [] + + for act in acts: + act_entrance: Entrance = mw.get_entrance(act, p) + + required_region = act_entrance.connected_region + name: str = format("%s: Connection %i" % (key, i)) + new_entrance: Entrance = connect_regions(required_region, region, name, p) + entrances.append(new_entrance) + + # Copy access rules from act completions + if "Free Roam" not in required_region.name: + rule: typing.Callable[[CollectionState], bool] + name = format("Act Completion (%s)" % required_region.name) + rule = mw.get_location(name, p).access_rule + access_rules.append(rule) + + i += 1 + + for e in entrances: + for rules in access_rules: + add_rule(e, rules) + + for entrance in mw.get_region("Alpine Free Roam", p).entrances: + add_rule(entrance, lambda state: can_use_hookshot(state, w)) + if mw.UmbrellaLogic[p].value > 0: + add_rule(entrance, lambda state: state.has("Umbrella", p)) + + if mw.EndGoal[p].value == 1: + mw.completion_condition[p] = lambda state: state.has("Time Piece Cluster", p) + elif mw.EndGoal[p].value == 2: + mw.completion_condition[p] = lambda state: state.has("Rush Hour Cleared", p) + + +def set_specific_rules(world: World): + mw = world.multiworld + w = world + p = world.player + dlc1: bool = bool(mw.EnableDLC1[p].value > 0) + + add_rule(mw.get_entrance("Alpine Skyline - Finale", p), + lambda state: can_clear_alpine(state, w)) + + # Normal logic + if mw.LogicDifficulty[p].value == 0: + add_rule(mw.get_entrance("-> The Birdhouse", p), + lambda state: can_use_hat(state, w, HatType.BREWING)) + + add_rule(mw.get_location("Alpine Skyline - Yellow Band Hills", p), + lambda state: can_use_hat(state, w, HatType.BREWING)) + + if dlc1: + add_rule(mw.get_location("Act Completion (Time Rift - Deep Sea)", p), + lambda state: can_use_hat(state, w, HatType.DWELLER)) + + add_rule(mw.get_location("Rock the Boat - Post Captain Rescue", p), + lambda state: can_use_hat(state, w, HatType.ICE)) + + add_rule(mw.get_location("Act Completion (Rock the Boat)", p), + lambda state: can_use_hat(state, w, HatType.ICE)) + + # Hard logic, includes SDJ stuff + if mw.LogicDifficulty[p].value >= 1: + add_rule(mw.get_location("Act Completion (Time Rift - The Twilight Bell)", p), + lambda state: can_use_hat(state, w, HatType.SPRINT) and state.has("Scooter Badge", p), "or") + + # Expert logic + if mw.LogicDifficulty[p].value >= 2: + set_rule(mw.get_location("Alpine Skyline - The Twilight Path", p), lambda state: True) + else: + add_rule(mw.get_entrance("-> The Twilight Bell", p), + lambda state: can_use_hat(state, w, HatType.DWELLER)) + + add_rule(mw.get_location("Mafia Town - Behind HQ Chest", p), + lambda state: state.can_reach("Act Completion (Heating Up Mafia Town)", "Location", p) + or state.can_reach("Down with the Mafia!", "Region", p) + or state.can_reach("Cheating the Race", "Region", p) + or state.can_reach("The Golden Vault", "Region", p)) + + # Old guys don't appear in SCFOS + add_rule(mw.get_location("Mafia Town - Old Man (Steel Beams)", p), + lambda state: state.can_reach("Welcome to Mafia Town", "Region", p) + or state.can_reach("Barrel Battle", "Region", p) + or state.can_reach("Cheating the Race", "Region", p) + or state.can_reach("The Golden Vault", "Region", p) + or state.can_reach("Down with the Mafia!", "Region", p)) + + add_rule(mw.get_location("Mafia Town - Old Man (Seaside Spaghetti)", p), + lambda state: state.can_reach("Welcome to Mafia Town", "Region", p) + or state.can_reach("Barrel Battle", "Region", p) + or state.can_reach("Cheating the Race", "Region", p) + or state.can_reach("The Golden Vault", "Region", p) + or state.can_reach("Down with the Mafia!", "Region", p)) + + # Only available outside She Came from Outer Space + add_rule(mw.get_location("Mafia Town - Mafia Geek Platform", p), + lambda state: state.can_reach("Welcome to Mafia Town", "Region", p) + or state.can_reach("Barrel Battle", "Region", p) + or state.can_reach("Down with the Mafia!", "Region", p) + or state.can_reach("Cheating the Race", "Region", p) + or state.can_reach("Heating Up Mafia Town", "Region", p) + or state.can_reach("The Golden Vault", "Region", p)) + + # Only available outside Down with the Mafia! (for some reason) + add_rule(mw.get_location("Mafia Town - On Scaffolding", p), + lambda state: state.can_reach("Welcome to Mafia Town", "Region", p) + or state.can_reach("Barrel Battle", "Region", p) + or state.can_reach("She Came from Outer Space", "Region", p) + or state.can_reach("Cheating the Race", "Region", p) + or state.can_reach("Heating Up Mafia Town", "Region", p) + or state.can_reach("The Golden Vault", "Region", p)) + + # For some reason, the brewing crate is removed in HUMT + set_rule(mw.get_location("Mafia Town - Secret Cave", p), + lambda state: state.can_reach("Heating Up Mafia Town", "Region", p) + or can_use_hat(state, w, HatType.BREWING)) + + # Can bounce across the lava to get this without Hookshot (need to die though :P) + set_rule(mw.get_location("Mafia Town - Above Boats", p), + lambda state: state.can_reach("Heating Up Mafia Town", "Region", p) + or can_use_hookshot(state, w)) + + set_rule(mw.get_location("Act Completion (Cheating the Race)", p), + lambda state: can_use_hat(state, w, HatType.TIME_STOP) + or mw.CTRWithSprint[p].value > 0 and can_use_hat(state, w, HatType.SPRINT)) + + set_rule(mw.get_location("Subcon Forest - Boss Arena Chest", p), + lambda state: state.can_reach("Toilet of Doom", "Region", p) + and (mw.ShuffleSubconPaintings[p].value == 0 or state.has("Progressive Painting Unlock", p, 1)) + or state.can_reach("Your Contract has Expired", "Region", p)) + + if mw.UmbrellaLogic[p].value > 0: + add_rule(mw.get_location("Act Completion (Toilet of Doom)", p), + lambda state: state.has("Umbrella", p) or can_use_hat(state, w, HatType.BREWING)) + + set_rule(mw.get_location("Act Completion (Time Rift - Village)", p), + lambda state: can_use_hat(state, w, HatType.BREWING) or state.has("Umbrella", p) + or can_use_hat(state, w, HatType.DWELLER)) + + add_rule(mw.get_entrance("Subcon Forest - Act 2", p), + lambda state: state.has("Snatcher's Contract - The Subcon Well", p)) + + add_rule(mw.get_entrance("Subcon Forest - Act 3", p), + lambda state: state.has("Snatcher's Contract - Toilet of Doom", p)) + + add_rule(mw.get_entrance("Subcon Forest - Act 4", p), + lambda state: state.has("Snatcher's Contract - Queen Vanessa's Manor", p)) + + add_rule(mw.get_entrance("Subcon Forest - Act 5", p), + lambda state: state.has("Snatcher's Contract - Mail Delivery Service", p)) + + if mw.ShuffleSubconPaintings[p].value > 0: + for key in contract_locations: + if key == "Snatcher's Contract - The Subcon Well": + continue + + add_rule(mw.get_location(key, p), + lambda state: state.has("Progressive Painting Unlock", p, 1)) + + add_rule(mw.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", p), + lambda state: can_use_hat(state, w, HatType.SPRINT) or can_use_hat(state, w, HatType.TIME_STOP)) + + if mw.EnableDLC1[p].value > 0: + add_rule(mw.get_entrance("Cruise Ship Entrance BV", p), lambda state: can_use_hookshot(state, w)) + + # This particular item isn't present in Act 3 for some reason, yes in vanilla too + add_rule(mw.get_location("The Arctic Cruise - Toilet", p), + lambda state: state.can_reach("Bon Voyage!", "Region", p) + or state.can_reach("Ship Shape", "Region", p)) + + if mw.EnableDLC2[p].value > 0: + add_rule(mw.get_entrance("-> Bluefin Tunnel", p), + lambda state: state.has("Metro Ticket - Green", p) or state.has("Metro Ticket - Blue", p)) + + add_rule(mw.get_entrance("-> Pink Paw Station", p), + lambda state: state.has("Metro Ticket - Pink", p) + or state.has("Metro Ticket - Yellow", p) and state.has("Metro Ticket - Blue", p)) + + add_rule(mw.get_entrance("Nyakuza Metro - Finale", p), + lambda state: can_clear_metro(state, w)) + + add_rule(mw.get_location("Act Completion (Rush Hour)", p), + lambda state: state.has("Metro Ticket - Yellow", p) and state.has("Metro Ticket - Blue", p) + and state.has("Metro Ticket - Pink", p)) + + for key in shop_locations.keys(): + if "Green Clean Station Thug B" in key and is_location_valid(w, key): + add_rule(mw.get_location(key, p), lambda state: state.has("Metro Ticket - Yellow", p), "or") + + +def set_sdj_rules(world: World): + add_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player), + lambda state: can_sdj(state, world), "or") + + add_rule(world.multiworld.get_location("Subcon Forest - Green and Purple Dweller Rocks", world.player), + lambda state: can_sdj(state, world), "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("Act Completion (Time Rift - Gallery)", world.player), + lambda state: 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") + + +def set_alps_zipline_rules(world: 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: state.has(zipline, world.player)) + + +def reg_act_connection(world: World, region: typing.Union[str, Region], unlocked_entrance: typing.Union[str, Entrance]): + reg: Region + entrance: Entrance + if isinstance(region, str): + reg = world.multiworld.get_region(region, world.player) + else: + reg = region + + if isinstance(unlocked_entrance, str): + entrance = world.multiworld.get_entrance(unlocked_entrance, world.player) + else: + entrance = unlocked_entrance + + world.multiworld.register_indirect_condition(reg, entrance) + + +# See randomize_act_entrances in Regions.py +# Called BEFORE set_rules! +def set_rift_rules(world: World, regions: typing.Dict[str, Region]): + w = world + mw = world.multiworld + p = world.player + + # This is accessing the regions in place of these time rifts, so we can set the rules on all the entrances. + for entrance in regions["Time Rift - Gallery"].entrances: + add_rule(entrance, lambda state: can_use_hat(state, w, HatType.BREWING) + and state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.BIRDS))) + + for entrance in regions["Time Rift - The Lab"].entrances: + add_rule(entrance, lambda state: can_use_hat(state, w, HatType.DWELLER) + and state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.ALPINE))) + + for entrance in regions["Time Rift - Sewers"].entrances: + add_rule(entrance, lambda state: can_clear_act(state, w, "Mafia Town - Act 4")) + reg_act_connection(w, mw.get_entrance("Mafia Town - Act 4", p).connected_region, entrance) + + for entrance in regions["Time Rift - Bazaar"].entrances: + add_rule(entrance, lambda state: can_clear_act(state, w, "Mafia Town - Act 6")) + reg_act_connection(w, mw.get_entrance("Mafia Town - Act 6", p).connected_region, entrance) + + for entrance in regions["Time Rift - Mafia of Cooks"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "Burger")) + + for entrance in regions["Time Rift - The Owl Express"].entrances: + add_rule(entrance, lambda state: can_clear_act(state, w, "Battle of the Birds - Act 2")) + add_rule(entrance, lambda state: can_clear_act(state, w, "Battle of the Birds - Act 3")) + reg_act_connection(w, mw.get_entrance("Battle of the Birds - Act 2", p).connected_region, entrance) + reg_act_connection(w, mw.get_entrance("Battle of the Birds - Act 3", p).connected_region, entrance) + + for entrance in regions["Time Rift - The Moon"].entrances: + add_rule(entrance, lambda state: can_clear_act(state, w, "Battle of the Birds - Act 4")) + add_rule(entrance, lambda state: can_clear_act(state, w, "Battle of the Birds - Act 5")) + reg_act_connection(w, mw.get_entrance("Battle of the Birds - Act 4", p).connected_region, entrance) + reg_act_connection(w, mw.get_entrance("Battle of the Birds - Act 5", p).connected_region, entrance) + + for entrance in regions["Time Rift - Dead Bird Studio"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "Train")) + + for entrance in regions["Time Rift - Pipe"].entrances: + add_rule(entrance, lambda state: can_clear_act(state, w, "Subcon Forest - Act 2")) + reg_act_connection(w, mw.get_entrance("Subcon Forest - Act 2", p).connected_region, entrance) + if mw.ShuffleSubconPaintings[p].value > 0: + add_rule(entrance, lambda state: state.has("Progressive Painting Unlock", p, 2)) + + for entrance in regions["Time Rift - Village"].entrances: + add_rule(entrance, lambda state: can_clear_act(state, w, "Subcon Forest - Act 4")) + reg_act_connection(w, mw.get_entrance("Subcon Forest - Act 4", p).connected_region, entrance) + if mw.ShuffleSubconPaintings[p].value > 0: + add_rule(entrance, lambda state: state.has("Progressive Painting Unlock", p, 2)) + + for entrance in regions["Time Rift - Sleepy Subcon"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "UFO")) + if mw.ShuffleSubconPaintings[p].value > 0: + add_rule(entrance, lambda state: state.has("Progressive Painting Unlock", p, 3)) + + for entrance in regions["Time Rift - Curly Tail Trail"].entrances: + add_rule(entrance, lambda state: state.has("Windmill Cleared", p)) + + for entrance in regions["Time Rift - The Twilight Bell"].entrances: + add_rule(entrance, lambda state: state.has("Twilight Bell Cleared", p)) + + for entrance in regions["Time Rift - Alpine Skyline"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "Crayon")) + + if mw.EnableDLC1[p].value > 0: + for entrance in regions["Time Rift - Balcony"].entrances: + add_rule(entrance, lambda state: can_clear_act(state, w, "The Arctic Cruise - Finale")) + + for entrance in regions["Time Rift - Deep Sea"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "Cake")) + + if mw.EnableDLC2[p].value > 0: + for entrance in regions["Time Rift - Rumbi Factory"].entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "Necklace")) + + +# Basically the same as above, but without the need of the dict since we are just setting defaults +# Called if Act Rando is disabled +def set_default_rift_rules(world: World): + w = world + mw = world.multiworld + p = world.player + + for entrance in mw.get_region("Time Rift - Gallery", p).entrances: + add_rule(entrance, lambda state: can_use_hat(state, w, HatType.BREWING) + and state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.BIRDS))) + + for entrance in mw.get_region("Time Rift - The Lab", p).entrances: + add_rule(entrance, lambda state: can_use_hat(state, w, HatType.DWELLER) + and state.has("Time Piece", p, w.get_chapter_cost(ChapterIndex.ALPINE))) + + for entrance in mw.get_region("Time Rift - Sewers", p).entrances: + add_rule(entrance, lambda state: can_clear_act(state, world, "Mafia Town - Act 4")) + reg_act_connection(w, "Down with the Mafia!", entrance.name) + + for entrance in mw.get_region("Time Rift - Bazaar", p).entrances: + add_rule(entrance, lambda state: can_clear_act(state, world, "Mafia Town - Act 6")) + reg_act_connection(w, "Heating Up Mafia Town", entrance.name) + + for entrance in mw.get_region("Time Rift - Mafia of Cooks", p).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "Burger")) + + for entrance in mw.get_region("Time Rift - The Owl Express", p).entrances: + add_rule(entrance, lambda state: can_clear_act(state, world, "Battle of the Birds - Act 2")) + add_rule(entrance, lambda state: can_clear_act(state, world, "Battle of the Birds - Act 3")) + reg_act_connection(w, "Murder on the Owl Express", entrance.name) + reg_act_connection(w, "Picture Perfect", entrance.name) + + for entrance in mw.get_region("Time Rift - The Moon", p).entrances: + add_rule(entrance, lambda state: can_clear_act(state, world, "Battle of the Birds - Act 4")) + add_rule(entrance, lambda state: can_clear_act(state, world, "Battle of the Birds - Act 5")) + reg_act_connection(w, "Train Rush", entrance.name) + reg_act_connection(w, "The Big Parade", entrance.name) + + for entrance in mw.get_region("Time Rift - Dead Bird Studio", p).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "Train")) + + for entrance in mw.get_region("Time Rift - Pipe", p).entrances: + add_rule(entrance, lambda state: can_clear_act(state, world, "Subcon Forest - Act 2")) + reg_act_connection(w, "The Subcon Well", entrance.name) + if mw.ShuffleSubconPaintings[p].value > 0: + add_rule(entrance, lambda state: state.has("Progressive Painting Unlock", p, 2)) + + for entrance in mw.get_region("Time Rift - Village", p).entrances: + add_rule(entrance, lambda state: can_clear_act(state, world, "Subcon Forest - Act 4")) + reg_act_connection(w, "Queen Vanessa's Manor", entrance.name) + if mw.ShuffleSubconPaintings[p].value > 0: + add_rule(entrance, lambda state: state.has("Progressive Painting Unlock", p, 2)) + + for entrance in mw.get_region("Time Rift - Sleepy Subcon", p).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "UFO")) + if mw.ShuffleSubconPaintings[p].value > 0: + add_rule(entrance, lambda state: state.has("Progressive Painting Unlock", p, 3)) + + for entrance in mw.get_region("Time Rift - Curly Tail Trail", p).entrances: + add_rule(entrance, lambda state: state.has("Windmill Cleared", p)) + + for entrance in mw.get_region("Time Rift - The Twilight Bell", p).entrances: + add_rule(entrance, lambda state: state.has("Twilight Bell Cleared", p)) + + for entrance in mw.get_region("Time Rift - Alpine Skyline", p).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "Crayon")) + + if mw.EnableDLC1[p].value > 0: + for entrance in mw.get_region("Time Rift - Balcony", p).entrances: + add_rule(entrance, lambda state: can_clear_act(state, w, "The Arctic Cruise - Finale")) + + for entrance in mw.get_region("Time Rift - Deep Sea", p).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "Cake")) + + if mw.EnableDLC2[p].value > 0: + for entrance in mw.get_region("Time Rift - Rumbi Factory", p).entrances: + add_rule(entrance, lambda state: has_relic_combo(state, w, "Necklace")) + + +def connect_regions(start_region: Region, exit_region: Region, entrancename: str, player: int) -> Entrance: + entrance = Entrance(player, entrancename, start_region) + start_region.exits.append(entrance) + entrance.connect(exit_region) + return entrance diff --git a/worlds/ahit/Types.py b/worlds/ahit/Types.py new file mode 100644 index 0000000000..45f5535b58 --- /dev/null +++ b/worlds/ahit/Types.py @@ -0,0 +1,28 @@ +from enum import IntEnum, IntFlag + + +class HatType(IntEnum): + NONE = -1 + SPRINT = 0 + BREWING = 1 + ICE = 2 + DWELLER = 3 + TIME_STOP = 4 + + +class HatDLC(IntFlag): + none = 0b000 + dlc1 = 0b001 + dlc2 = 0b010 + death_wish = 0b100 + + +class ChapterIndex(IntEnum): + SPACESHIP = 0 + MAFIA = 1 + BIRDS = 2 + SUBCON = 3 + ALPINE = 4 + FINALE = 5 + CRUISE = 6 + METRO = 7 diff --git a/worlds/ahit/__init__.py b/worlds/ahit/__init__.py new file mode 100644 index 0000000000..b3d78ad4b6 --- /dev/null +++ b/worlds/ahit/__init__.py @@ -0,0 +1,276 @@ +from BaseClasses import Item, ItemClassification, Region, LocationProgressType + +from .Items import HatInTimeItem, item_table, item_frequencies, item_dlc_enabled, junk_weights,\ + create_item, create_multiple_items, create_junk_items, relic_groups, act_contracts, alps_hooks, \ + get_total_time_pieces + +from .Regions import create_region, create_regions, connect_regions, randomize_act_entrances, chapter_act_info, \ + create_events, chapter_regions, act_chapters + +from .Locations import HatInTimeLocation, location_table, get_total_locations, contract_locations, is_location_valid, \ + get_location_names + +from .Types import HatDLC, HatType, ChapterIndex +from .Options import ahit_options, slot_data_options, adjust_options +from worlds.AutoWorld import World +from .Rules import set_rules +import typing + + +class HatInTimeWorld(World): + """ + A Hat in Time is a cute-as-heck 3D platformer featuring a little girl who stitches hats for wicked powers! + Freely explore giant worlds and recover Time Pieces to travel to new heights! + """ + + game = "A Hat in Time" + data_version = 1 + + item_name_to_id = {name: data.code for name, data in item_table.items()} + location_name_to_id = get_location_names() + + option_definitions = ahit_options + + hat_craft_order: typing.List[HatType] + hat_yarn_costs: typing.Dict[HatType, int] + chapter_timepiece_costs: typing.Dict[ChapterIndex, int] + act_connections: typing.Dict[str, str] = {} + nyakuza_thug_items: typing.Dict[str, int] = {} + shop_locs: typing.List[str] = [] + item_name_groups = relic_groups + badge_seller_count: int = 0 + + def generate_early(self): + adjust_options(self) + + # If our starting chapter is 4 and act rando isn't on, force hookshot into inventory + # If starting chapter is 3 and painting shuffle is enabled, and act rando isn't, give one free painting unlock + start_chapter: int = self.multiworld.StartingChapter[self.player].value + + if start_chapter == 4 or start_chapter == 3: + if self.multiworld.ActRandomizer[self.player].value == 0: + if start_chapter == 4: + self.multiworld.push_precollected(self.create_item("Hookshot Badge")) + + if start_chapter == 3 and self.multiworld.ShuffleSubconPaintings[self.player].value > 0: + self.multiworld.push_precollected(self.create_item("Progressive Painting Unlock")) + + if self.multiworld.StartWithCompassBadge[self.player].value > 0: + self.multiworld.push_precollected(self.create_item("Compass Badge")) + + def create_regions(self): + self.nyakuza_thug_items = {} + self.shop_locs = [] + create_regions(self) + + # place default contract locations if contract shuffle is off so logic can still utilize them + 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): + self.hat_yarn_costs = {HatType.SPRINT: -1, HatType.BREWING: -1, HatType.ICE: -1, + HatType.DWELLER: -1, HatType.TIME_STOP: -1} + + self.hat_craft_order = [HatType.SPRINT, HatType.BREWING, HatType.ICE, + HatType.DWELLER, HatType.TIME_STOP] + + self.topology_present = self.multiworld.ActRandomizer[self.player].value + + # Item Pool + itempool: typing.List[Item] = [] + self.calculate_yarn_costs() + yarn_pool: typing.List[Item] = create_multiple_items(self, "Yarn", self.multiworld.YarnAvailable[self.player].value) + + # 1/5 is progression balanced + for i in range(len(yarn_pool) // 5): + yarn_pool[i].classification = ItemClassification.progression + + itempool += yarn_pool + + if self.multiworld.RandomizeHatOrder[self.player].value > 0: + self.multiworld.random.shuffle(self.hat_craft_order) + + for name in item_table.keys(): + if name == "Yarn": + continue + + if not item_dlc_enabled(self, name): + continue + + item_type: ItemClassification = item_table.get(name).classification + if item_type is ItemClassification.filler or item_type is ItemClassification.trap: + continue + + if name in act_contracts.keys() and self.multiworld.ShuffleActContracts[self.player].value == 0: + continue + + if name in alps_hooks.keys() and self.multiworld.ShuffleAlpineZiplines[self.player].value == 0: + continue + + if name == "Progressive Painting Unlock" \ + and self.multiworld.ShuffleSubconPaintings[self.player].value == 0: + continue + + if name == "Time Piece": + tp_count: int = 40 + max_extra: int = 0 + if self.multiworld.EnableDLC1[self.player].value > 0: + max_extra += 6 + + if self.multiworld.EnableDLC2[self.player].value > 0: + max_extra += 10 + + tp_count += min(max_extra, self.multiworld.MaxExtraTimePieces[self.player].value) + tp_list: typing.List[Item] = create_multiple_items(self, name, tp_count) + + # 1/5 is progression balanced + for i in range(len(tp_list) // 5): + tp_list[i].classification = ItemClassification.progression + + itempool += tp_list + continue + + itempool += create_multiple_items(self, name, item_frequencies.get(name, 1)) + + create_events(self) + total_locations: int = get_total_locations(self) + itempool += create_junk_items(self, total_locations-len(itempool)) + self.multiworld.itempool += itempool + + def set_rules(self): + self.act_connections = {} + self.chapter_timepiece_costs = {ChapterIndex.MAFIA: -1, + ChapterIndex.BIRDS: -1, + ChapterIndex.SUBCON: -1, + ChapterIndex.ALPINE: -1, + ChapterIndex.FINALE: -1, + ChapterIndex.CRUISE: -1, + ChapterIndex.METRO: -1} + + if self.multiworld.ActRandomizer[self.player].value > 0: + randomize_act_entrances(self) + + set_rules(self) + + def create_item(self, name: str) -> Item: + return create_item(self, name) + + def fill_slot_data(self) -> dict: + slot_data: dict = {"SprintYarnCost": self.hat_yarn_costs[HatType.SPRINT], + "BrewingYarnCost": self.hat_yarn_costs[HatType.BREWING], + "IceYarnCost": self.hat_yarn_costs[HatType.ICE], + "DwellerYarnCost": self.hat_yarn_costs[HatType.DWELLER], + "TimeStopYarnCost": self.hat_yarn_costs[HatType.TIME_STOP], + "Chapter1Cost": self.chapter_timepiece_costs[ChapterIndex.MAFIA], + "Chapter2Cost": self.chapter_timepiece_costs[ChapterIndex.BIRDS], + "Chapter3Cost": self.chapter_timepiece_costs[ChapterIndex.SUBCON], + "Chapter4Cost": self.chapter_timepiece_costs[ChapterIndex.ALPINE], + "Chapter5Cost": self.chapter_timepiece_costs[ChapterIndex.FINALE], + "Chapter6Cost": self.chapter_timepiece_costs[ChapterIndex.CRUISE], + "Chapter7Cost": self.chapter_timepiece_costs[ChapterIndex.METRO], + "Hat1": int(self.hat_craft_order[0]), + "Hat2": int(self.hat_craft_order[1]), + "Hat3": int(self.hat_craft_order[2]), + "Hat4": int(self.hat_craft_order[3]), + "Hat5": int(self.hat_craft_order[4]), + "BadgeSellerItemCount": self.badge_seller_count, + "SeedNumber": self.multiworld.seed} # For shop prices + + if self.multiworld.ActRandomizer[self.player].value > 0: + for name in self.act_connections.keys(): + slot_data[name] = self.act_connections[name] + + if self.multiworld.EnableDLC2[self.player].value > 0: + for name in self.nyakuza_thug_items.keys(): + slot_data[name] = self.nyakuza_thug_items[name] + + for option_name in slot_data_options: + option = getattr(self.multiworld, option_name)[self.player] + slot_data[option_name] = option.value + + return slot_data + + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): + new_hint_data = {} + alpine_regions = ["The Birdhouse", "The Lava Cake", "The Windmill", "The Twilight Bell"] + metro_regions = ["Yellow Overpass Station", "Green Clean Station", "Bluefin Tunnel", "Pink Paw Station"] + + for key, data in location_table.items(): + if not is_location_valid(self, key): + continue + + location = self.multiworld.get_location(key, self.player) + region_name: str + + if data.region in alpine_regions: + region_name = "Alpine Free Roam" + elif data.region in metro_regions: + region_name = "Nyakuza Free Roam" + elif data.region in chapter_act_info.keys(): + region_name = location.parent_region.name + else: + continue + + new_hint_data[location.address] = self.get_shuffled_region(region_name) + + if self.multiworld.EnableDLC1[self.player].value > 0 and self.multiworld.Tasksanity[self.player].value > 0: + ship_shape_region = self.get_shuffled_region("Ship Shape") + id_start: int = 300204 + for i in range(self.multiworld.TasksanityCheckCount[self.player].value): + new_hint_data[id_start+i] = ship_shape_region + + hint_data[self.player] = new_hint_data + + def write_spoiler_header(self, spoiler_handle: typing.TextIO): + for i in self.chapter_timepiece_costs.keys(): + spoiler_handle.write("Chapter %i Cost: %i\n" % (i, self.chapter_timepiece_costs[ChapterIndex(i)])) + + for hat in self.hat_craft_order: + spoiler_handle.write("Hat Cost: %s: %i\n" % (hat, self.hat_yarn_costs[hat])) + + def calculate_yarn_costs(self): + mw = self.multiworld + p = self.player + min_yarn_cost = int(min(mw.YarnCostMin[p].value, mw.YarnCostMax[p].value)) + max_yarn_cost = int(max(mw.YarnCostMin[p].value, mw.YarnCostMax[p].value)) + + max_cost: int = 0 + for i in range(5): + cost = mw.random.randint(min(min_yarn_cost, max_yarn_cost), max(max_yarn_cost, min_yarn_cost)) + self.hat_yarn_costs[HatType(i)] = cost + max_cost += cost + + available_yarn = mw.YarnAvailable[p].value + if max_cost > available_yarn: + mw.YarnAvailable[p].value = max_cost + available_yarn = max_cost + + # make sure we always have at least 8 extra + if max_cost + 8 > available_yarn: + mw.YarnAvailable[p].value += (max_cost + 8) - available_yarn + + def set_chapter_cost(self, chapter: ChapterIndex, cost: int): + self.chapter_timepiece_costs[chapter] = cost + + def get_chapter_cost(self, chapter: ChapterIndex) -> int: + return self.chapter_timepiece_costs.get(chapter) + + # Sets an act entrance in slot data by specifying the Hat_ChapterActInfo, to be used in-game + def update_chapter_act_info(self, original_region: Region, new_region: Region): + original_act_info = chapter_act_info[original_region.name] + new_act_info = chapter_act_info[new_region.name] + self.act_connections[original_act_info] = new_act_info + + def get_shuffled_region(self, region: str) -> str: + ci: str = chapter_act_info[region] + for key, val in self.act_connections.items(): + if val == ci: + for name in chapter_act_info.keys(): + if chapter_act_info[name] == key: + return name