from worlds.AutoWorld import World from BaseClasses import Region, Entrance, ItemClassification, Location from .Types import ChapterIndex, Difficulty, HatInTimeLocation, HatInTimeItem from .Locations import location_table, storybook_pages, event_locs, is_location_valid, \ shop_locations, get_tasksanity_start_id, snatcher_coins, zero_jumps, zero_jumps_expert, zero_jumps_hard import typing from .Rules import set_rift_rules, get_difficulty # 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": ["Bon Voyage!"], "Time Rift - Rumbi Factory": ["Nyakuza Free Roam"], } # Time piece identifiers to be used in act shuffle chapter_act_info = { "Time Rift - Gallery": "Spaceship_WaterRift_Gallery", "Time Rift - The Lab": "Spaceship_WaterRift_MailRoom", "Welcome to Mafia Town": "chapter1_tutorial", "Barrel Battle": "chapter1_barrelboss", "She Came from Outer Space": "chapter1_cannon_repair", "Down with the Mafia!": "chapter1_boss", "Cheating the Race": "harbor_impossible_race", "Heating Up Mafia Town": "mafiatown_lava", "The Golden Vault": "mafiatown_goldenvault", "Time Rift - Mafia of Cooks": "TimeRift_Cave_Mafia", "Time Rift - Sewers": "TimeRift_Water_Mafia_Easy", "Time Rift - Bazaar": "TimeRift_Water_Mafia_Hard", "Dead Bird Studio": "DeadBirdStudio", "Murder on the Owl Express": "chapter3_murder", "Picture Perfect": "moon_camerasnap", "Train Rush": "trainwreck_selfdestruct", "The Big Parade": "moon_parade", "Award Ceremony": "award_ceremony", "Dead Bird Studio Basement": "chapter3_secret_finale", "Time Rift - Dead Bird Studio": "TimeRift_Cave_BirdBasement", "Time Rift - The Owl Express": "TimeRift_Water_TWreck_Panels", "Time Rift - The Moon": "TimeRift_Water_TWreck_Parade", "Contractual Obligations": "subcon_village_icewall", "The Subcon Well": "subcon_cave", "Toilet of Doom": "chapter2_toiletboss", "Queen Vanessa's Manor": "vanessa_manor_attic", "Mail Delivery Service": "subcon_maildelivery", "Your Contract has Expired": "snatcher_boss", "Time Rift - Sleepy Subcon": "TimeRift_Cave_Raccoon", "Time Rift - Pipe": "TimeRift_Water_Subcon_Hookshot", "Time Rift - Village": "TimeRift_Water_Subcon_Dwellers", "Alpine Free Roam": "AlpineFreeRoam", # not an actual Time Piece "The Illness has Spread": "AlpineSkyline_Finale", "Time Rift - Alpine Skyline": "TimeRift_Cave_Alps", "Time Rift - The Twilight Bell": "TimeRift_Water_Alp_Goats", "Time Rift - Curly Tail Trail": "TimeRift_Water_AlpineSkyline_Cats", "The Finale": "TheFinale_FinalBoss", "Time Rift - Tour": "TimeRift_Cave_Tour", "Bon Voyage!": "Cruise_Boarding", "Ship Shape": "Cruise_Working", "Rock the Boat": "Cruise_Sinking", "Time Rift - Balcony": "Cruise_WaterRift_Slide", "Time Rift - Deep Sea": "Cruise_CaveRift_Aquarium", "Nyakuza Free Roam": "MetroFreeRoam", # not an actual Time Piece "Rush Hour": "Metro_Escape", "Time Rift - Rumbi Factory": "Metro_CaveRift_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", ] chapter_finales = [ "Dead Bird Studio Basement", "Your Contract has Expired", "The Illness has Spread", "Rock the Boat", "Rush Hour", ] # Acts blacklisted in act shuffle # entrance: region blacklisted_acts = { "Battle of the Birds - Finale A": "Award Ceremony", } # Blacklisted act shuffle combinations to help prevent impossible layouts. Mostly for free roam acts. blacklisted_combos = { "The Illness has Spread": ["Nyakuza Free Roam", "Alpine Free Roam", "Contractual Obligations"], "Rush Hour": ["Nyakuza Free Roam", "Alpine Free Roam", "Contractual Obligations"], "Time Rift - The Owl Express": ["Alpine Free Roam", "Nyakuza Free Roam", "Bon Voyage!", "Contractual Obligations"], "Time Rift - The Moon": ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations"], "Time Rift - Dead Bird Studio": ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations"], "Time Rift - Curly Tail Trail": ["Nyakuza Free Roam", "Contractual Obligations"], "Time Rift - The Twilight Bell": ["Nyakuza Free Roam", "Contractual Obligations"], "Time Rift - Alpine Skyline": ["Nyakuza Free Roam", "Contractual Obligations"], "Time Rift - Rumbi Factory": ["Alpine Free Roam", "Contractual Obligations"], "Time Rift - Deep Sea": ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations"], } 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) # we only need the menu and the spaceship regions if world.is_dw_only(): return 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) pp = create_region_and_connect(w, "Picture Perfect", "Battle of the Birds - Act 3", botb) tr = 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) basement = 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, and beyond in Expert ev_area = create_region_and_connect(w, "Dead Bird Studio - Elevator Area", "DBS -> Elevator Area", dbs) post_ev_area = create_region_and_connect(w, "Dead Bird Studio - Post Elevator Area", "DBS -> Post Elevator Area", dbs) connect_regions(basement, ev_area, "DBS Basement -> Elevator Area", p) if world.multiworld.LogicDifficulty[world.player].value >= int(Difficulty.EXPERT): connect_regions(basement, post_ev_area, "DBS Basement -> Post Elevator Area", p) # ------------------------------------------- SUBCON FOREST --------------------------------------- # subcon_forest = create_region_and_connect(w, "Subcon Forest", "Telescope -> Subcon Forest", spaceship) 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) # Needs to be separate because there are a lot of locations in Alpine that can't be accessed from Illness alpine_area_tihs = create_region_and_connect(w, "Alpine Skyline Area (TIHS)", "-> Alpine Skyline Area (TIHS)", alpine_area) create_region_and_connect(w, "The Birdhouse", "-> The Birdhouse", alpine_area) create_region_and_connect(w, "The Lava Cake", "-> The Lava Cake", alpine_area) 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, "TIHS -> Alpine Skyline Area (TIHS)", p) create_rift_connections(w, create_region(w, "Time Rift - Alpine Skyline")) create_rift_connections(w, create_region(w, "Time Rift - The Twilight Bell")) create_rift_connections(w, create_region(w, "Time Rift - Curly Tail Trail")) # ------------------------------------------- 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(dbs, badge_seller, "DBS -> Badge Seller", p) connect_regions(pp, badge_seller, "PP -> Badge Seller", p) connect_regions(tr, badge_seller, "TR -> Badge Seller", p) connect_regions(alpine_area_tihs, badge_seller, "ASA -> Badge Seller", p) times_end = create_region_and_connect(w, "Time's End", "Telescope -> Time's End", spaceship) create_region_and_connect(w, "The Finale", "Time's End - Act 1", times_end) # ------------------------------------------- DLC1 ------------------------------------------------- # if w.is_dlc1(): 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")) if mw.ExcludeTour[world.player].value == 0: create_rift_connections(w, create_region(w, "Time Rift - Tour")) if mw.Tasksanity[p].value > 0: create_tasksanity_locations(w) connect_regions(cruise_ship, badge_seller, "CS -> Badge Seller", p) if w.is_dlc2(): 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) 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 = f"{region.name} Portal - Entrance {i}" connect_regions(act_region, region, entrance_name, world.player) i += 1 # fix for some weird keyerror from tests if region.name == "Time Rift - Rumbi Factory": for entrance in region.entrances: world.multiworld.get_entrance(entrance.name, world.player) def create_tasksanity_locations(world: World): ship_shape: Region = world.multiworld.get_region("Ship Shape", world.player) id_start: int = get_tasksanity_start_id() for i in range(world.multiworld.TasksanityCheckCount[world.player].value): location = HatInTimeLocation(world.player, f"Tasksanity Check {i+1}", id_start+i, ship_shape) ship_shape.locations.append(location) def is_valid_plando(world: World, region: str) -> bool: if region in blacklisted_acts.values(): return False if region not in world.multiworld.ActPlando[world.player].keys(): return False act = world.multiworld.ActPlando[world.player].get(region) if act in blacklisted_acts.values(): return False # Don't allow plando-ing things onto the first act that aren't completable with nothing is_first_act: bool = act_chapters[region] == get_first_chapter_region(world).name \ and region in act_entrances.keys() and ("Act 1" in act_entrances[region] or "Free Roam" in act_entrances[region]) if is_first_act: if act_chapters[act] == "Subcon Forest" and world.multiworld.ShuffleSubconPaintings[world.player].value > 0: return False if world.multiworld.UmbrellaLogic[world.player].value > 0 \ and (act == "Heating Up Mafia Town" or act == "Queen Vanessa's Manor"): return False if act not in guaranteed_first_acts: return False # Don't allow straight up impossible mappings if region == "The Illness has Spread" and act == "Alpine Free Roam": return False if region == "Rush Hour" and act == "Nyakuza Free Roam": return False if region == "Time Rift - Rumbi Factory" and act == "Nyakuza Free Roam": return False if region == "Time Rift - The Owl Express" and act == "Murder on the Owl Express": return False return any(a.name == world.multiworld.ActPlando[world.player].get(region) for a in world.multiworld.get_regions(world.player)) def randomize_act_entrances(world: World): region_list: typing.List[Region] = get_act_regions(world) world.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) for region in region_list.copy(): if region.name in chapter_finales: region_list.remove(region) region_list.append(region) for region in region_list.copy(): if region.name in world.multiworld.ActPlando[world.player].keys(): if is_valid_plando(world, region.name): region_list.remove(region) region_list.append(region) else: print("Disallowing act plando for", world.multiworld.player_name[world.player], "-", region.name, ":", world.multiworld.ActPlando[world.player].get(region.name)) # 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 if region.name in world.multiworld.ActPlando[world.player].keys() and is_valid_plando(world, region.name): has_guaranteed = True 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: # 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 if candidate.name in world.multiworld.ActPlando[world.player].values(): 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 if region.name in world.multiworld.ActPlando[world.player].keys() and is_valid_plando(world, region.name): candidate_list.clear() candidate_list.append( world.multiworld.get_region(world.multiworld.ActPlando[world.player].get(region.name), world.player)) 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 if region.name in blacklisted_combos.keys() and candidate.name in blacklisted_combos[region.name]: continue # Prevent Contractual Obligations from being inaccessible if contracts are not shuffled if world.multiworld.ShuffleActContracts[world.player].value == 0: if (region.name == "Your Contract has Expired" or region.name == "The Subcon Well") \ and candidate.name == "Contractual Obligations": continue if world.multiworld.FinaleShuffle[world.player].value > 0 and region.name in chapter_finales: if candidate.name not in chapter_finales: continue if region.name in rift_access_regions and candidate.name in rift_access_regions[region.name]: continue candidate_list.append(candidate) candidate: Region if len(candidate_list) > 0: candidate = candidate_list[world.random.randint(0, len(candidate_list)-1)] else: # plando can still break certain rules, so acts may not always end up shuffled. for c in region_list: if c not in shuffled_list: candidate = c break shuffled_list.append(candidate) # print(region, candidate) # Vanilla if candidate.name == region.name: if region.name in rift_access_regions.keys(): rift_dict.setdefault(region.name, candidate) update_chapter_act_info(world, 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) 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) update_chapter_act_info(world, 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) update_chapter_act_info(world, 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 = f"{time_rift.name} Portal - Entrance {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: plando: bool = name in world.multiworld.ActPlando[world.player].keys() \ or name in world.multiworld.ActPlando[world.player].values() if name == "The Finale": return not plando and world.multiworld.EndGoal[world.player].value == 1 if name == "Rush Hour": return not plando and world.multiworld.EndGoal[world.player].value == 2 if name == "Time Rift - Tour": return world.multiworld.ExcludeTour[world.player].value > 0 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 world.is_dw_only(): break 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.random.randint(world.multiworld.BadgeSellerMinItems[world.player].value, world.multiworld.BadgeSellerMaxItems[world.player].value) if max_items <= 0: world.set_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.set_badge_seller_count(max_items) return badge_seller 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 # 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) # Sets an act entrance in slot data by specifying the Hat_ChapterActInfo, to be used in-game def update_chapter_act_info(world: World, original_region: Region, new_region: Region): original_act_info = chapter_act_info[original_region.name] new_act_info = chapter_act_info[new_region.name] world.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 def create_thug_shops(world: World): min_items: int = min(world.multiworld.NyakuzaThugMinShopItems[world.player].value, world.multiworld.NyakuzaThugMaxShopItems[world.player].value) max_items: int = max(world.multiworld.NyakuzaThugMaxShopItems[world.player].value, world.multiworld.NyakuzaThugMinShopItems[world.player].value) count: int = -1 step: int = 0 old_name: str = "" thug_items = world.get_nyakuza_thug_items() for key, data in shop_locations.items(): if data.nyakuza_thug == "": continue if old_name != "" and old_name == data.nyakuza_thug: continue try: if thug_items[data.nyakuza_thug] <= 0: continue except KeyError: pass if count == -1: count = world.random.randint(min_items, max_items) 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 world.set_nyakuza_thug_items(thug_items) def create_events(world: World) -> int: count: int = 0 for (name, data) in event_locs.items(): if not is_location_valid(world, name): continue if world.is_dw(): if name in snatcher_coins.keys(): name = f"{name} ({data.region})" elif name in zero_jumps: if get_difficulty(world) < Difficulty.HARD and name in zero_jumps_hard: continue if get_difficulty(world) < Difficulty.EXPERT and name in zero_jumps_expert: continue event: Location = create_event(name, world.multiworld.get_region(data.region, world.player), world) event.show_in_spoiler = False 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