| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | 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"], | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-23 03:49:17 -04:00
										 |  |  |     # 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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  |     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": | 
					
						
							| 
									
										
										
										
											2024-05-23 03:49:17 -04:00
										 |  |  |         # Only allow Subcon levels if painting skips are allowed | 
					
						
							|  |  |  |         if diff < Difficulty.MODERATE or world.options.NoPaintingSkips: | 
					
						
							|  |  |  |             return False | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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 |