1029 lines
		
	
	
		
			45 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1029 lines
		
	
	
		
			45 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from BaseClasses import Region, Entrance, ItemClassification, Location, LocationProgressType
 | |
| from .Types import ChapterIndex, Difficulty, HatInTimeLocation, HatInTimeItem
 | |
| from .Locations import location_table, storybook_pages, event_locs, is_location_valid, \
 | |
|     shop_locations, TASKSANITY_START_ID, snatcher_coins, zero_jumps, zero_jumps_expert, zero_jumps_hard
 | |
| from typing import TYPE_CHECKING, List, Dict, Optional
 | |
| from .Rules import set_rift_rules, get_difficulty
 | |
| from .Options import ActRandomizer, EndGoal
 | |
| 
 | |
| if TYPE_CHECKING:
 | |
|     from . import HatInTimeWorld
 | |
| 
 | |
| 
 | |
| MIN_FIRST_SPHERE_LOCATIONS = 30
 | |
| 
 | |
| 
 | |
| # 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"
 | |
| }
 | |
| 
 | |
| # Some of these may vary depending on options. See is_valid_first_act()
 | |
| guaranteed_first_acts = [
 | |
|     "Welcome to Mafia Town",
 | |
|     "Barrel Battle",
 | |
|     "She Came from Outer Space",
 | |
|     "Down with the Mafia!",
 | |
|     "Heating Up Mafia Town",
 | |
|     "The Golden Vault",
 | |
| 
 | |
|     "Dead Bird Studio",
 | |
|     "Murder on the Owl Express",
 | |
|     "Dead Bird Studio Basement",
 | |
| 
 | |
|     "Contractual Obligations",
 | |
|     "The Subcon Well",
 | |
|     "Queen Vanessa's Manor",
 | |
|     "Your Contract has Expired",
 | |
| 
 | |
|     "Rock the Boat",
 | |
| 
 | |
|     "Time Rift - Mafia of Cooks",
 | |
|     "Time Rift - Dead Bird Studio",
 | |
|     "Time Rift - Sleepy Subcon",
 | |
|     "Time Rift - Alpine Skyline"
 | |
|     "Time Rift - Tour",
 | |
|     "Time Rift - Rumbi Factory",
 | |
| ]
 | |
| 
 | |
| 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"],
 | |
| 
 | |
|     # Bon Voyage is here to prevent the cycle: Owl Express -> Bon Voyage -> Deep Sea -> MOTOE -> Owl Express
 | |
|     # which would make them all inaccessible since those rifts have no other entrances
 | |
|     "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"],
 | |
| 
 | |
|     # See above comment
 | |
|     "Time Rift - Deep Sea":             ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations",
 | |
|                                          "Murder on the Owl Express"],
 | |
| 
 | |
|     # was causing test failures
 | |
|     "Time Rift - Balcony":              ["Alpine Free Roam"],
 | |
| }
 | |
| 
 | |
| 
 | |
| def create_regions(world: "HatInTimeWorld"):
 | |
|     # ------------------------------------------- HUB -------------------------------------------------- #
 | |
|     menu = create_region(world, "Menu")
 | |
|     spaceship = create_region_and_connect(world, "Spaceship", "Save File -> Spaceship", menu)
 | |
| 
 | |
|     # we only need the menu and the spaceship regions
 | |
|     if world.is_dw_only():
 | |
|         return
 | |
| 
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - Gallery"))
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - The Lab"))
 | |
| 
 | |
|     # ------------------------------------------- MAFIA TOWN ------------------------------------------- #
 | |
|     mafia_town = create_region_and_connect(world, "Mafia Town", "Telescope -> Mafia Town", spaceship)
 | |
|     mt_act1 = create_region_and_connect(world, "Welcome to Mafia Town", "Mafia Town - Act 1", mafia_town)
 | |
|     mt_act2 = create_region_and_connect(world, "Barrel Battle", "Mafia Town - Act 2", mafia_town)
 | |
|     mt_act3 = create_region_and_connect(world, "She Came from Outer Space", "Mafia Town - Act 3", mafia_town)
 | |
|     mt_act4 = create_region_and_connect(world, "Down with the Mafia!", "Mafia Town - Act 4", mafia_town)
 | |
|     mt_act6 = create_region_and_connect(world, "Heating Up Mafia Town", "Mafia Town - Act 6", mafia_town)
 | |
|     mt_act5 = create_region_and_connect(world, "Cheating the Race", "Mafia Town - Act 5", mafia_town)
 | |
|     mt_act7 = create_region_and_connect(world, "The Golden Vault", "Mafia Town - Act 7", mafia_town)
 | |
| 
 | |
|     # ------------------------------------------- BOTB ------------------------------------------------- #
 | |
|     botb = create_region_and_connect(world, "Battle of the Birds", "Telescope -> Battle of the Birds", spaceship)
 | |
|     dbs = create_region_and_connect(world, "Dead Bird Studio", "Battle of the Birds - Act 1", botb)
 | |
|     create_region_and_connect(world, "Murder on the Owl Express", "Battle of the Birds - Act 2", botb)
 | |
|     pp = create_region_and_connect(world, "Picture Perfect", "Battle of the Birds - Act 3", botb)
 | |
|     tr = create_region_and_connect(world, "Train Rush", "Battle of the Birds - Act 4", botb)
 | |
|     create_region_and_connect(world, "The Big Parade", "Battle of the Birds - Act 5", botb)
 | |
|     create_region_and_connect(world, "Award Ceremony", "Battle of the Birds - Finale A", botb)
 | |
|     basement = create_region_and_connect(world, "Dead Bird Studio Basement", "Battle of the Birds - Finale B", botb)
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - Dead Bird Studio"))
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - The Owl Express"))
 | |
|     create_rift_connections(world, create_region(world, "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(world, "Dead Bird Studio - Elevator Area", "DBS -> Elevator Area", dbs)
 | |
|     post_ev = create_region_and_connect(world, "Dead Bird Studio - Post Elevator Area", "DBS -> Post Elevator Area", dbs)
 | |
|     basement.connect(ev_area, "DBS Basement -> Elevator Area")
 | |
|     if world.options.LogicDifficulty >= int(Difficulty.EXPERT):
 | |
|         basement.connect(post_ev, "DBS Basement -> Post Elevator Area")
 | |
| 
 | |
|     # ------------------------------------------- SUBCON FOREST --------------------------------------- #
 | |
|     subcon_forest = create_region_and_connect(world, "Subcon Forest", "Telescope -> Subcon Forest", spaceship)
 | |
|     sf_act1 = create_region_and_connect(world, "Contractual Obligations", "Subcon Forest - Act 1", subcon_forest)
 | |
|     sf_act2 = create_region_and_connect(world, "The Subcon Well", "Subcon Forest - Act 2", subcon_forest)
 | |
|     sf_act3 = create_region_and_connect(world, "Toilet of Doom", "Subcon Forest - Act 3", subcon_forest)
 | |
|     sf_act4 = create_region_and_connect(world, "Queen Vanessa's Manor", "Subcon Forest - Act 4", subcon_forest)
 | |
|     sf_act5 = create_region_and_connect(world, "Mail Delivery Service", "Subcon Forest - Act 5", subcon_forest)
 | |
|     create_region_and_connect(world, "Your Contract has Expired", "Subcon Forest - Finale", subcon_forest)
 | |
| 
 | |
|     # ------------------------------------------- ALPINE SKYLINE ------------------------------------------ #
 | |
|     alpine_skyline = create_region_and_connect(world, "Alpine Skyline",  "Telescope -> Alpine Skyline", spaceship)
 | |
|     alpine_freeroam = create_region_and_connect(world, "Alpine Free Roam", "Alpine Skyline - Free Roam", alpine_skyline)
 | |
|     alpine_area = create_region_and_connect(world, "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(world, "Alpine Skyline Area (TIHS)", "-> Alpine Skyline Area (TIHS)",
 | |
|                                                  alpine_area)
 | |
| 
 | |
|     create_region_and_connect(world, "The Birdhouse", "-> The Birdhouse", alpine_area)
 | |
|     create_region_and_connect(world, "The Lava Cake", "-> The Lava Cake", alpine_area)
 | |
|     create_region_and_connect(world, "The Windmill", "-> The Windmill", alpine_area)
 | |
|     create_region_and_connect(world, "The Twilight Bell", "-> The Twilight Bell", alpine_area)
 | |
| 
 | |
|     illness = create_region_and_connect(world, "The Illness has Spread", "Alpine Skyline - Finale", alpine_skyline)
 | |
|     illness.connect(alpine_area_tihs, "TIHS -> Alpine Skyline Area (TIHS)")
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - Alpine Skyline"))
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - The Twilight Bell"))
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - Curly Tail Trail"))
 | |
| 
 | |
|     # ------------------------------------------- OTHER -------------------------------------------------- #
 | |
|     mt_area: Region = create_region(world, "Mafia Town Area")
 | |
|     mt_area_humt: Region = create_region(world, "Mafia Town Area (HUMT)")
 | |
|     mt_area.connect(mt_area_humt, "MT Area -> MT Area (HUMT)")
 | |
|     mt_act1.connect(mt_area, "Mafia Town Entrance WTMT")
 | |
|     mt_act2.connect(mt_area, "Mafia Town Entrance BB")
 | |
|     mt_act3.connect(mt_area, "Mafia Town Entrance SCFOS")
 | |
|     mt_act4.connect(mt_area, "Mafia Town Entrance DWTM")
 | |
|     mt_act5.connect(mt_area, "Mafia Town Entrance CTR")
 | |
|     mt_act6.connect(mt_area_humt, "Mafia Town Entrance HUMT")
 | |
|     mt_act7.connect(mt_area, "Mafia Town Entrance TGV")
 | |
| 
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - Mafia of Cooks"))
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - Sewers"))
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - Bazaar"))
 | |
| 
 | |
|     sf_area: Region = create_region(world, "Subcon Forest Area")
 | |
|     sf_act1.connect(sf_area, "Subcon Forest Entrance CO")
 | |
|     sf_act2.connect(sf_area, "Subcon Forest Entrance SW")
 | |
|     sf_act3.connect(sf_area, "Subcon Forest Entrance TOD")
 | |
|     sf_act4.connect(sf_area, "Subcon Forest Entrance QVM")
 | |
|     sf_act5.connect(sf_area, "Subcon Forest Entrance MDS")
 | |
| 
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - Sleepy Subcon"))
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - Pipe"))
 | |
|     create_rift_connections(world, create_region(world, "Time Rift - Village"))
 | |
| 
 | |
|     badge_seller = create_badge_seller(world)
 | |
|     mt_area.connect(badge_seller, "MT Area -> Badge Seller")
 | |
|     mt_area_humt.connect(badge_seller, "MT Area (HUMT) -> Badge Seller")
 | |
|     sf_area.connect(badge_seller, "SF Area -> Badge Seller")
 | |
|     dbs.connect(badge_seller, "DBS -> Badge Seller")
 | |
|     pp.connect(badge_seller, "PP -> Badge Seller")
 | |
|     tr.connect(badge_seller, "TR -> Badge Seller")
 | |
|     alpine_area_tihs.connect(badge_seller, "ASA -> Badge Seller")
 | |
| 
 | |
|     times_end = create_region_and_connect(world, "Time's End", "Telescope -> Time's End", spaceship)
 | |
|     create_region_and_connect(world, "The Finale", "Time's End - Act 1", times_end)
 | |
| 
 | |
|     # ------------------------------------------- DLC1 ------------------------------------------------- #
 | |
|     if world.is_dlc1():
 | |
|         arctic_cruise = create_region_and_connect(world, "The Arctic Cruise", "Telescope -> Arctic Cruise", spaceship)
 | |
|         cruise_ship = create_region(world, "Cruise Ship")
 | |
| 
 | |
|         ac_act1 = create_region_and_connect(world, "Bon Voyage!", "The Arctic Cruise - Act 1", arctic_cruise)
 | |
|         ac_act2 = create_region_and_connect(world, "Ship Shape", "The Arctic Cruise - Act 2", arctic_cruise)
 | |
|         ac_act3 = create_region_and_connect(world, "Rock the Boat", "The Arctic Cruise - Finale", arctic_cruise)
 | |
| 
 | |
|         ac_act1.connect(cruise_ship, "Cruise Ship Entrance BV")
 | |
|         ac_act2.connect(cruise_ship, "Cruise Ship Entrance SS")
 | |
|         ac_act3.connect(cruise_ship, "Cruise Ship Entrance RTB")
 | |
|         create_rift_connections(world, create_region(world, "Time Rift - Balcony"))
 | |
|         create_rift_connections(world, create_region(world, "Time Rift - Deep Sea"))
 | |
| 
 | |
|         if not world.options.ExcludeTour:
 | |
|             create_rift_connections(world, create_region(world, "Time Rift - Tour"))
 | |
| 
 | |
|         if world.options.Tasksanity:
 | |
|             create_tasksanity_locations(world)
 | |
| 
 | |
|         cruise_ship.connect(badge_seller, "CS -> Badge Seller")
 | |
| 
 | |
|     if world.is_dlc2():
 | |
|         nyakuza = create_region_and_connect(world, "Nyakuza Metro", "Telescope -> Nyakuza Metro", spaceship)
 | |
|         metro_freeroam = create_region_and_connect(world, "Nyakuza Free Roam", "Nyakuza Metro - Free Roam", nyakuza)
 | |
|         create_region_and_connect(world, "Rush Hour", "Nyakuza Metro - Finale", nyakuza)
 | |
| 
 | |
|         yellow = create_region_and_connect(world, "Yellow Overpass Station", "-> Yellow Overpass Station", metro_freeroam)
 | |
|         green = create_region_and_connect(world, "Green Clean Station", "-> Green Clean Station", metro_freeroam)
 | |
|         pink = create_region_and_connect(world, "Pink Paw Station", "-> Pink Paw Station", metro_freeroam)
 | |
|         create_region_and_connect(world, "Bluefin Tunnel", "-> Bluefin Tunnel", metro_freeroam)  # No manhole
 | |
| 
 | |
|         create_region_and_connect(world, "Yellow Overpass Manhole", "-> Yellow Overpass Manhole", yellow)
 | |
|         create_region_and_connect(world, "Green Clean Manhole", "-> Green Clean Manhole", green)
 | |
|         create_region_and_connect(world, "Pink Paw Manhole", "-> Pink Paw Manhole", pink)
 | |
| 
 | |
|         create_rift_connections(world, create_region(world, "Time Rift - Rumbi Factory"))
 | |
|         create_thug_shops(world)
 | |
| 
 | |
| 
 | |
| def create_rift_connections(world: "HatInTimeWorld", region: Region):
 | |
|     for i, name in enumerate(rift_access_regions[region.name]):
 | |
|         act_region = world.multiworld.get_region(name, world.player)
 | |
|         entrance_name = f"{region.name} Portal - Entrance {i+1}"
 | |
|         act_region.connect(region, entrance_name)
 | |
| 
 | |
| 
 | |
| def create_tasksanity_locations(world: "HatInTimeWorld"):
 | |
|     ship_shape: Region = world.multiworld.get_region("Ship Shape", world.player)
 | |
|     id_start: int = TASKSANITY_START_ID
 | |
|     for i in range(world.options.TasksanityCheckCount):
 | |
|         location = HatInTimeLocation(world.player, f"Tasksanity Check {i+1}", id_start+i, ship_shape)
 | |
|         ship_shape.locations.append(location)
 | |
| 
 | |
| 
 | |
| def randomize_act_entrances(world: "HatInTimeWorld"):
 | |
|     region_list: List[Region] = get_shuffleable_act_regions(world)
 | |
|     world.random.shuffle(region_list)
 | |
|     region_list.sort(key=sort_acts)
 | |
|     candidate_list: List[Region] = region_list.copy()
 | |
|     rift_dict: Dict[str, Region] = {}
 | |
| 
 | |
|     # Check if Plando's are valid, if so, map them
 | |
|     if world.options.ActPlando:
 | |
|         player_name = world.multiworld.get_player_name(world.player)
 | |
|         for (name1, name2) in world.options.ActPlando.items():
 | |
|             region: Region
 | |
|             act: Region
 | |
|             try:
 | |
|                 region = world.multiworld.get_region(name1, world.player)
 | |
|             except KeyError:
 | |
|                 print(f"ActPlando ({player_name}) - "
 | |
|                       f"Act \"{name1}\" does not exist in the multiworld. "
 | |
|                       f"Possible reasons are typos, case-sensitivity, or DLC options.")
 | |
|                 continue
 | |
| 
 | |
|             try:
 | |
|                 act = world.multiworld.get_region(name2, world.player)
 | |
|             except KeyError:
 | |
|                 print(f"ActPlando ({player_name}) - "
 | |
|                       f"Act \"{name2}\" does not exist in the multiworld. "
 | |
|                       f"Possible reasons are typos, case-sensitivity, or DLC options.")
 | |
|                 continue
 | |
| 
 | |
|             if is_valid_plando(world, region.name, act.name):
 | |
|                 region_list.remove(region)
 | |
|                 candidate_list.remove(act)
 | |
|                 connect_acts(world, region, act, rift_dict)
 | |
|             else:
 | |
|                 print(f"ActPlando "
 | |
|                       f"({player_name}) - "
 | |
|                       f"\"{name1}: {name2}\" "
 | |
|                       f"is an invalid or disallowed act plando combination!")
 | |
| 
 | |
|     # Decide what should be on the first few levels before randomizing the rest
 | |
|     first_acts: List[Region] = []
 | |
|     first_chapter_name = chapter_regions[ChapterIndex(world.options.StartingChapter)]
 | |
|     first_acts.append(get_act_by_number(world, first_chapter_name, 1))
 | |
|     # Chapter 3 and 4 only have one level accessible at the start
 | |
|     if first_chapter_name == "Mafia Town" or first_chapter_name == "Battle of the Birds":
 | |
|         first_acts.append(get_act_by_number(world, first_chapter_name, 2))
 | |
|         first_acts.append(get_act_by_number(world, first_chapter_name, 3))
 | |
| 
 | |
|     valid_first_acts: List[Region] = []
 | |
|     for candidate in candidate_list:
 | |
|         if is_valid_first_act(world, candidate):
 | |
|             valid_first_acts.append(candidate)
 | |
| 
 | |
|     total_locations = 0
 | |
|     for level in first_acts:
 | |
|         if level not in region_list:  # make sure it hasn't been plando'd
 | |
|             continue
 | |
| 
 | |
|         candidate = valid_first_acts[world.random.randint(0, len(valid_first_acts)-1)]
 | |
|         region_list.remove(level)
 | |
|         candidate_list.remove(candidate)
 | |
|         valid_first_acts.remove(candidate)
 | |
|         connect_acts(world, level, candidate, rift_dict)
 | |
| 
 | |
|         # Only allow one purple rift
 | |
|         if candidate.name in purple_time_rifts:
 | |
|             for act in reversed(valid_first_acts):
 | |
|                 if act.name in purple_time_rifts:
 | |
|                     valid_first_acts.remove(act)
 | |
| 
 | |
|         total_locations += get_region_location_count(world, candidate.name)
 | |
|         if "Time Rift" not in candidate.name:
 | |
|             chapter = act_chapters.get(candidate.name)
 | |
|             if chapter == "Mafia Town":
 | |
|                 total_locations += get_region_location_count(world, "Mafia Town Area (HUMT)")
 | |
|                 if candidate.name != "Heating Up Mafia Town":
 | |
|                     total_locations += get_region_location_count(world, "Mafia Town Area")
 | |
|             elif chapter == "Subcon Forest":
 | |
|                 total_locations += get_region_location_count(world, "Subcon Forest Area")
 | |
|             elif chapter == "The Arctic Cruise":
 | |
|                 total_locations += get_region_location_count(world, "Cruise Ship")
 | |
| 
 | |
|         # If we have enough Sphere 1 locations, we can allow the rest to be randomized
 | |
|         if total_locations >= MIN_FIRST_SPHERE_LOCATIONS:
 | |
|             break
 | |
| 
 | |
|     ignore_certain_rules: bool = False
 | |
|     while len(region_list) > 0:
 | |
|         region = region_list[0]
 | |
|         candidate: Region
 | |
|         valid_candidates: List[Region] = []
 | |
| 
 | |
|         # Look for candidates to map this act to
 | |
|         for c in candidate_list:
 | |
|             if is_valid_act_combo(world, region, c, ignore_certain_rules):
 | |
|                 valid_candidates.append(c)
 | |
| 
 | |
|         if len(valid_candidates) > 0:
 | |
|             candidate = valid_candidates[world.random.randint(0, len(valid_candidates)-1)]
 | |
|         else:
 | |
|             # If we fail here, try again with less shuffle rules. If we still somehow fail, there's an issue for sure
 | |
|             if ignore_certain_rules:
 | |
|                 raise Exception(f"Failed to find act shuffle candidate for {region}"
 | |
|                                 f"\nRemaining acts to map to: {region_list}"
 | |
|                                 f"\nRemaining candidates: {candidate_list}")
 | |
| 
 | |
|             ignore_certain_rules = True
 | |
|             continue
 | |
| 
 | |
|         ignore_certain_rules = False
 | |
|         region_list.remove(region)
 | |
|         candidate_list.remove(candidate)
 | |
|         connect_acts(world, region, candidate, rift_dict)
 | |
| 
 | |
|     for name in blacklisted_acts.values():
 | |
|         region: Region = world.multiworld.get_region(name, world.player)
 | |
|         update_chapter_act_info(world, region, region)
 | |
| 
 | |
|     set_rift_rules(world, rift_dict)
 | |
| 
 | |
| 
 | |
| # Try to do levels that may have specific mapping rules first
 | |
| def sort_acts(act: Region) -> int:
 | |
|     if "Time Rift" in act.name:
 | |
|         return -5
 | |
| 
 | |
|     if act.name in chapter_finales:
 | |
|         return -4
 | |
| 
 | |
|     # Free Roam
 | |
|     if (act_chapters[act.name] == "Alpine Skyline" or act_chapters[act.name] == "Nyakuza Metro") \
 | |
|        and "Time Rift" not in act.name:
 | |
|         return -3
 | |
| 
 | |
|     if act.name == "Contractual Obligations" or act.name == "The Subcon Well":
 | |
|         return -2
 | |
| 
 | |
|     world = act.multiworld.worlds[act.player]
 | |
|     blacklist = world.options.ActBlacklist
 | |
|     if len(blacklist) > 0:
 | |
|         for name, act_list in blacklist.items():
 | |
|             if act.name == name or act.name in act_list:
 | |
|                 return -1
 | |
| 
 | |
|     return 0
 | |
| 
 | |
| 
 | |
| def connect_acts(world: "HatInTimeWorld", entrance_act: Region, exit_act: Region, rift_dict: Dict[str, Region]):
 | |
|     # Vanilla
 | |
|     if exit_act.name == entrance_act.name:
 | |
|         if entrance_act.name in rift_access_regions.keys():
 | |
|             rift_dict.setdefault(entrance_act.name, exit_act)
 | |
| 
 | |
|         update_chapter_act_info(world, entrance_act, exit_act)
 | |
|         return
 | |
| 
 | |
|     if entrance_act.name in rift_access_regions.keys():
 | |
|         connect_time_rift(world, entrance_act, exit_act)
 | |
|         rift_dict.setdefault(entrance_act.name, exit_act)
 | |
|     else:
 | |
|         if exit_act.name in rift_access_regions.keys():
 | |
|             for e in exit_act.entrances.copy():
 | |
|                 e.parent_region.exits.remove(e)
 | |
|                 e.connected_region.entrances.remove(e)
 | |
| 
 | |
|         entrance = world.multiworld.get_entrance(act_entrances[entrance_act.name], world.player)
 | |
|         chapter = world.multiworld.get_region(act_chapters[entrance_act.name], world.player)
 | |
|         reconnect_regions(entrance, chapter, exit_act)
 | |
| 
 | |
|     update_chapter_act_info(world, entrance_act, exit_act)
 | |
| 
 | |
| 
 | |
| def is_valid_act_combo(world: "HatInTimeWorld", entrance_act: Region,
 | |
|                        exit_act: Region, ignore_certain_rules: bool = False) -> bool:
 | |
| 
 | |
|     # Ignore certain rules that aren't to prevent impossible combos. This is needed for ActPlando.
 | |
|     if not ignore_certain_rules:
 | |
|         if world.options.ActRandomizer == ActRandomizer.option_light and not ignore_certain_rules:
 | |
|             # Don't map Time Rifts to normal acts
 | |
|             if "Time Rift" in entrance_act.name and "Time Rift" not in exit_act.name:
 | |
|                 return False
 | |
| 
 | |
|             # Don't map normal acts to Time Rifts
 | |
|             if "Time Rift" not in entrance_act.name and "Time Rift" in exit_act.name:
 | |
|                 return False
 | |
| 
 | |
|             # Separate purple rifts
 | |
|             if entrance_act.name in purple_time_rifts and exit_act.name not in purple_time_rifts \
 | |
|                     or entrance_act.name not in purple_time_rifts and exit_act.name in purple_time_rifts:
 | |
|                 return False
 | |
| 
 | |
|         if world.options.FinaleShuffle and entrance_act.name in chapter_finales:
 | |
|             if exit_act.name not in chapter_finales:
 | |
|                 return False
 | |
| 
 | |
|         if entrance_act.name in rift_access_regions and exit_act.name in rift_access_regions[entrance_act.name]:
 | |
|             return False
 | |
| 
 | |
|     # Blacklisted?
 | |
|     if entrance_act.name in blacklisted_combos.keys() and exit_act.name in blacklisted_combos[entrance_act.name]:
 | |
|         return False
 | |
| 
 | |
|     if world.options.ActBlacklist:
 | |
|         act_blacklist = world.options.ActBlacklist.get(entrance_act.name)
 | |
|         if act_blacklist is not None and exit_act.name in act_blacklist:
 | |
|             return False
 | |
| 
 | |
|     # Prevent Contractual Obligations from being inaccessible if contracts are not shuffled
 | |
|     if not world.options.ShuffleActContracts:
 | |
|         if (entrance_act.name == "Your Contract has Expired" or entrance_act.name == "The Subcon Well") \
 | |
|            and exit_act.name == "Contractual Obligations":
 | |
|             return False
 | |
| 
 | |
|     return True
 | |
| 
 | |
| 
 | |
| def is_valid_first_act(world: "HatInTimeWorld", act: Region) -> bool:
 | |
|     if act.name not in guaranteed_first_acts:
 | |
|         return False
 | |
| 
 | |
|     # If there's only a single level in the starting chapter, only allow Mafia Town or Subcon Forest levels
 | |
|     start_chapter = world.options.StartingChapter
 | |
|     if start_chapter is ChapterIndex.ALPINE or start_chapter is ChapterIndex.SUBCON:
 | |
|         if "Time Rift" in act.name:
 | |
|             return False
 | |
| 
 | |
|         if act_chapters[act.name] != "Mafia Town" and act_chapters[act.name] != "Subcon Forest":
 | |
|             return False
 | |
| 
 | |
|     if act.name in purple_time_rifts and not world.options.ShuffleStorybookPages:
 | |
|         return False
 | |
| 
 | |
|     diff = get_difficulty(world)
 | |
|     # Not completable without Umbrella?
 | |
|     if world.options.UmbrellaLogic:
 | |
|         # Needs to be at least moderate to cross the big dweller wall
 | |
|         if act.name == "Queen Vanessa's Manor" and diff < Difficulty.MODERATE:
 | |
|             return False
 | |
|         elif act.name == "Heating Up Mafia Town":  # Straight up impossible
 | |
|             return False
 | |
| 
 | |
|     # Need to be able to hover
 | |
|     if act.name == "Your Contract has Expired":
 | |
|         if diff < Difficulty.EXPERT or world.options.ShuffleSubconPaintings and world.options.NoPaintingSkips:
 | |
|             return False
 | |
| 
 | |
|     if act.name == "Dead Bird Studio":
 | |
|         # No umbrella logic = moderate, umbrella logic = expert.
 | |
|         if diff < Difficulty.MODERATE or world.options.UmbrellaLogic and diff < Difficulty.EXPERT:
 | |
|             return False
 | |
|     elif act.name == "Dead Bird Studio Basement" and (diff < Difficulty.EXPERT or world.options.FinaleShuffle):
 | |
|         return False
 | |
|     elif act.name == "Rock the Boat" and (diff < Difficulty.MODERATE or world.options.FinaleShuffle):
 | |
|         return False
 | |
|     elif act.name == "The Subcon Well" and diff < Difficulty.MODERATE:
 | |
|         return False
 | |
|     elif act.name == "Contractual Obligations" and world.options.ShuffleSubconPaintings:
 | |
|         return False
 | |
| 
 | |
|     if world.options.ShuffleSubconPaintings and act_chapters.get(act.name, "") == "Subcon Forest":
 | |
|         # Only allow Subcon levels if painting skips are allowed
 | |
|         if diff < Difficulty.MODERATE or world.options.NoPaintingSkips:
 | |
|             return False
 | |
| 
 | |
|     return True
 | |
| 
 | |
| 
 | |
| def connect_time_rift(world: "HatInTimeWorld", time_rift: Region, exit_region: Region):
 | |
|     i = 1
 | |
|     while i <= len(rift_access_regions[time_rift.name]):
 | |
|         name = f"{time_rift.name} Portal - Entrance {i}"
 | |
|         entrance: Entrance
 | |
|         try:
 | |
|             entrance = world.multiworld.get_entrance(name, world.player)
 | |
|             reconnect_regions(entrance, entrance.parent_region, exit_region)
 | |
|         except KeyError:
 | |
|             time_rift.connect(exit_region, name)
 | |
| 
 | |
|         i += 1
 | |
| 
 | |
| 
 | |
| def get_shuffleable_act_regions(world: "HatInTimeWorld") -> List[Region]:
 | |
|     act_list: 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: "HatInTimeWorld", name: str) -> bool:
 | |
|     act_plando = world.options.ActPlando
 | |
|     plando: bool = name in act_plando.keys() and is_valid_plando(world, name, act_plando[name])
 | |
|     if not plando and name in act_plando.values():
 | |
|         for key in act_plando.keys():
 | |
|             if act_plando[key] == name and is_valid_plando(world, key, name):
 | |
|                 plando = True
 | |
|                 break
 | |
| 
 | |
|     if name == "The Finale":
 | |
|         return not plando and world.options.EndGoal == EndGoal.option_finale
 | |
| 
 | |
|     if name == "Rush Hour":
 | |
|         return not plando and world.options.EndGoal == EndGoal.option_rush_hour
 | |
| 
 | |
|     if name == "Time Rift - Tour":
 | |
|         return bool(world.options.ExcludeTour)
 | |
| 
 | |
|     return name in blacklisted_acts.values()
 | |
| 
 | |
| 
 | |
| def is_valid_plando(world: "HatInTimeWorld", region: str, act: str) -> bool:
 | |
|     # Duplicated keys will throw an exception for us, but we still need to check for duplicated values
 | |
|     found_count = 0
 | |
|     for val in world.options.ActPlando.values():
 | |
|         if val == act:
 | |
|             found_count += 1
 | |
| 
 | |
|     if found_count > 1:
 | |
|         raise Exception(f"ActPlando ({world.multiworld.get_player_name(world.player)}) - "
 | |
|               f"Duplicated act plando mapping found for act: \"{act}\"")
 | |
| 
 | |
|     if region in blacklisted_acts.values() or (region not in act_entrances.keys() and "Time Rift" not in region):
 | |
|         return False
 | |
| 
 | |
|     if act in blacklisted_acts.values() or (act not in act_entrances.keys() and "Time Rift" not in act):
 | |
|         return False
 | |
| 
 | |
|     # Don't allow plando-ing things onto the first act that aren't permitted
 | |
|     entrance_name = act_entrances.get(region, "")
 | |
|     if entrance_name != "":
 | |
|         is_first_act: bool = act_chapters.get(region) == get_first_chapter_region(world).name \
 | |
|                              and ("Act 1" in entrance_name or "Free Roam" in entrance_name)
 | |
| 
 | |
|         if is_first_act and not is_valid_first_act(world, world.multiworld.get_region(act, world.player)):
 | |
|             return False
 | |
| 
 | |
|     # Don't allow straight up impossible mappings
 | |
|     if (region == "Time Rift - Curly Tail Trail"
 | |
|        or region == "Time Rift - The Twilight Bell"
 | |
|        or region == "The Illness has Spread") \
 | |
|        and act == "Alpine Free Roam":
 | |
|         return False
 | |
| 
 | |
|     if (region == "Rush Hour" or 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
 | |
| 
 | |
|     if region == "Time Rift - Deep Sea" and act == "Bon Voyage!":
 | |
|         return False
 | |
| 
 | |
|     return any(a.name == world.options.ActPlando.get(region) for a in world.multiworld.get_regions(world.player))
 | |
| 
 | |
| 
 | |
| def create_region(world: "HatInTimeWorld", 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 not world.options.ShuffleStorybookPages:
 | |
|                 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: "HatInTimeWorld") -> Region:
 | |
|     badge_seller = Region("Badge Seller", world.player, world.multiworld)
 | |
|     world.multiworld.regions.append(badge_seller)
 | |
|     count = 0
 | |
|     max_items = 0
 | |
| 
 | |
|     if world.options.BadgeSellerMaxItems > 0:
 | |
|         max_items = world.random.randint(world.options.BadgeSellerMinItems.value,
 | |
|                                          world.options.BadgeSellerMaxItems.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
 | |
| 
 | |
| 
 | |
| # 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: "HatInTimeWorld",
 | |
|                               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
 | |
| 
 | |
|     entrance_region.connect(exit_region, entrancename)
 | |
|     return reg
 | |
| 
 | |
| 
 | |
| def get_first_chapter_region(world: "HatInTimeWorld") -> Region:
 | |
|     start_chapter: ChapterIndex = ChapterIndex(world.options.StartingChapter)
 | |
|     return world.multiworld.get_region(chapter_regions.get(start_chapter), world.player)
 | |
| 
 | |
| 
 | |
| def get_act_original_chapter(world: "HatInTimeWorld", 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: "HatInTimeWorld", 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(world: "HatInTimeWorld", region: str) -> str:
 | |
|     ci: str = chapter_act_info[region]
 | |
|     for key, val in world.act_connections.items():
 | |
|         if val == ci:
 | |
|             for name in chapter_act_info.keys():
 | |
|                 if chapter_act_info[name] == key:
 | |
|                     return name
 | |
| 
 | |
| 
 | |
| def get_region_location_count(world: "HatInTimeWorld", region_name: str, included_only: bool = True) -> int:
 | |
|     count = 0
 | |
|     region = world.multiworld.get_region(region_name, world.player)
 | |
|     for loc in region.locations:
 | |
|         if loc.address is not None and (not included_only or loc.progress_type is not LocationProgressType.EXCLUDED):
 | |
|             count += 1
 | |
| 
 | |
|     return count
 | |
| 
 | |
| 
 | |
| def get_act_by_number(world: "HatInTimeWorld", chapter_name: str, num: int) -> Region:
 | |
|     chapter = world.multiworld.get_region(chapter_name, world.player)
 | |
|     act: Optional[Region] = None
 | |
|     for e in chapter.exits:
 | |
|         if f"Act {num}" in e.name or num == 1 and "Free Roam" in e.name:
 | |
|             act = e.connected_region
 | |
|             break
 | |
| 
 | |
|     return act
 | |
| 
 | |
| 
 | |
| def create_thug_shops(world: "HatInTimeWorld"):
 | |
|     min_items: int = world.options.NyakuzaThugMinShopItems.value
 | |
|     max_items: int = world.options.NyakuzaThugMaxShopItems.value
 | |
|     count = -1
 | |
|     step = 0
 | |
|     old_name = ""
 | |
| 
 | |
|     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.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: "HatInTimeWorld") -> int:
 | |
|     count = 0
 | |
| 
 | |
|     for (name, data) in event_locs.items():
 | |
|         if not is_location_valid(world, name):
 | |
|             continue
 | |
| 
 | |
|         item_name: str = name
 | |
|         if world.is_dw():
 | |
|             if name in snatcher_coins.keys():
 | |
|                 item_name = data.snatcher_coin
 | |
|             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, item_name, world.multiworld.get_region(data.region, world.player), world)
 | |
|         event.show_in_spoiler = False
 | |
|         count += 1
 | |
| 
 | |
|     return count
 | |
| 
 | |
| 
 | |
| def create_event(name: str, item_name: str, region: Region, world: "HatInTimeWorld") -> Location:
 | |
|     event = HatInTimeLocation(world.player, name, None, region)
 | |
|     region.locations.append(event)
 | |
|     event.place_locked_item(HatInTimeItem(item_name, ItemClassification.progression, None, world.player))
 | |
|     return event
 | 
