| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  | from copy import deepcopy | 
					
						
							| 
									
										
										
										
											2024-11-27 03:28:00 +01:00
										 |  |  | from typing import TYPE_CHECKING | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | from BaseClasses import CollectionState, PlandoOptions | 
					
						
							| 
									
										
										
										
											2024-06-01 06:34:41 -05:00
										 |  |  | from Options import PlandoConnection | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from . import MessengerWorld | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-27 03:28:00 +01:00
										 |  |  | PORTALS: list[str] = [ | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  |     "Autumn Hills", | 
					
						
							|  |  |  |     "Riviere Turquoise", | 
					
						
							|  |  |  |     "Howling Grotto", | 
					
						
							|  |  |  |     "Sunken Shrine", | 
					
						
							|  |  |  |     "Searing Crags", | 
					
						
							|  |  |  |     "Glacial Peak", | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-27 03:28:00 +01:00
										 |  |  | SHOP_POINTS: dict[str, list[str]] = { | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  |     "Autumn Hills": [ | 
					
						
							|  |  |  |         "Climbing Claws", | 
					
						
							|  |  |  |         "Hope Path", | 
					
						
							|  |  |  |         "Dimension Climb", | 
					
						
							|  |  |  |         "Leaf Golem", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Forlorn Temple": [ | 
					
						
							|  |  |  |         "Outside", | 
					
						
							|  |  |  |         "Entrance", | 
					
						
							|  |  |  |         "Climb", | 
					
						
							|  |  |  |         "Rocket Sunset", | 
					
						
							|  |  |  |         "Descent", | 
					
						
							|  |  |  |         "Saw Gauntlet", | 
					
						
							|  |  |  |         "Demon King", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Catacombs": [ | 
					
						
							|  |  |  |         "Triple Spike Crushers", | 
					
						
							|  |  |  |         "Ruxxtin", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Bamboo Creek": [ | 
					
						
							|  |  |  |         "Spike Crushers", | 
					
						
							|  |  |  |         "Abandoned", | 
					
						
							|  |  |  |         "Time Loop", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Howling Grotto": [ | 
					
						
							|  |  |  |         "Wingsuit", | 
					
						
							|  |  |  |         "Crushing Pits", | 
					
						
							|  |  |  |         "Emerald Golem", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Quillshroom Marsh": [ | 
					
						
							|  |  |  |         "Spikey Window", | 
					
						
							|  |  |  |         "Sand Trap", | 
					
						
							|  |  |  |         "Queen of Quills", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Searing Crags": [ | 
					
						
							|  |  |  |         "Rope Dart", | 
					
						
							|  |  |  |         "Falling Rocks", | 
					
						
							|  |  |  |         "Searing Mega Shard", | 
					
						
							|  |  |  |         "Before Final Climb", | 
					
						
							|  |  |  |         "Colossuses", | 
					
						
							|  |  |  |         "Key of Strength", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Glacial Peak": [ | 
					
						
							|  |  |  |         "Ice Climbers'", | 
					
						
							|  |  |  |         "Glacial Mega Shard", | 
					
						
							|  |  |  |         "Tower Entrance", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Tower of Time": [ | 
					
						
							|  |  |  |         "Final Chance", | 
					
						
							|  |  |  |         "Arcane Golem", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Cloud Ruins": [ | 
					
						
							|  |  |  |         "Cloud Entrance", | 
					
						
							|  |  |  |         "Pillar Glide", | 
					
						
							|  |  |  |         "Crushers' Descent", | 
					
						
							|  |  |  |         "Seeing Spikes", | 
					
						
							|  |  |  |         "Final Flight", | 
					
						
							|  |  |  |         "Manfred's", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Underworld": [ | 
					
						
							|  |  |  |         "Left", | 
					
						
							|  |  |  |         "Fireball Wave", | 
					
						
							|  |  |  |         "Long Climb", | 
					
						
							|  |  |  |         # "Barm'athaziel",  # not currently valid | 
					
						
							|  |  |  |         "Key of Chaos", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Riviere Turquoise": [ | 
					
						
							|  |  |  |         "Waterfall", | 
					
						
							|  |  |  |         "Launch of Faith", | 
					
						
							|  |  |  |         "Log Flume", | 
					
						
							|  |  |  |         "Log Climb", | 
					
						
							|  |  |  |         "Restock", | 
					
						
							|  |  |  |         "Butterfly Matriarch", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Elemental Skylands": [ | 
					
						
							|  |  |  |         "Air Intro", | 
					
						
							|  |  |  |         "Air Generator", | 
					
						
							|  |  |  |         "Earth Intro", | 
					
						
							|  |  |  |         "Earth Generator", | 
					
						
							|  |  |  |         "Fire Intro", | 
					
						
							|  |  |  |         "Fire Generator", | 
					
						
							|  |  |  |         "Water Intro", | 
					
						
							|  |  |  |         "Water Generator", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Sunken Shrine": [ | 
					
						
							|  |  |  |         "Above Portal", | 
					
						
							|  |  |  |         "Lifeguard", | 
					
						
							|  |  |  |         "Sun Path", | 
					
						
							|  |  |  |         "Tabi Gauntlet", | 
					
						
							|  |  |  |         "Moon Path", | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-27 03:28:00 +01:00
										 |  |  | CHECKPOINTS: dict[str, list[str]] = { | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  |     "Autumn Hills": [ | 
					
						
							|  |  |  |         "Hope Latch", | 
					
						
							|  |  |  |         "Key of Hope", | 
					
						
							|  |  |  |         "Lakeside", | 
					
						
							|  |  |  |         "Double Swing", | 
					
						
							|  |  |  |         "Spike Ball Swing", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Forlorn Temple": [ | 
					
						
							|  |  |  |         "Sunny Day", | 
					
						
							|  |  |  |         "Rocket Maze", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Catacombs": [ | 
					
						
							|  |  |  |         "Death Trap", | 
					
						
							|  |  |  |         "Crusher Gauntlet", | 
					
						
							|  |  |  |         "Dirty Pond", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Bamboo Creek": [ | 
					
						
							|  |  |  |         "Spike Ball Pits", | 
					
						
							|  |  |  |         "Spike Doors", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Howling Grotto": [ | 
					
						
							|  |  |  |         "Lost Woods", | 
					
						
							|  |  |  |         "Breezy Crushers", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Quillshroom Marsh": [ | 
					
						
							|  |  |  |         "Seashell", | 
					
						
							|  |  |  |         "Quicksand", | 
					
						
							|  |  |  |         "Spike Wave", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Searing Crags": [ | 
					
						
							|  |  |  |         "Triple Ball Spinner", | 
					
						
							|  |  |  |         "Raining Rocks", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Glacial Peak": [ | 
					
						
							|  |  |  |         "Projectile Spike Pit", | 
					
						
							|  |  |  |         "Air Swag", | 
					
						
							|  |  |  |         "Free Climbing", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Tower of Time": [ | 
					
						
							|  |  |  |         "First", | 
					
						
							|  |  |  |         "Second", | 
					
						
							|  |  |  |         "Third", | 
					
						
							|  |  |  |         "Fourth", | 
					
						
							|  |  |  |         "Fifth", | 
					
						
							|  |  |  |         "Sixth", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Cloud Ruins": [ | 
					
						
							|  |  |  |         "Spike Float", | 
					
						
							|  |  |  |         "Ghost Pit", | 
					
						
							|  |  |  |         "Toothbrush Alley", | 
					
						
							|  |  |  |         "Saw Pit", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Underworld": [ | 
					
						
							|  |  |  |         "Hot Dip", | 
					
						
							|  |  |  |         "Hot Tub", | 
					
						
							|  |  |  |         "Lava Run", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Riviere Turquoise": [ | 
					
						
							|  |  |  |         "Flower Flight", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Elemental Skylands": [ | 
					
						
							|  |  |  |         "Air Seal", | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     "Sunken Shrine": [ | 
					
						
							|  |  |  |         "Lightfoot Tabi", | 
					
						
							|  |  |  |         "Sun Crest", | 
					
						
							|  |  |  |         "Waterfall Paradise", | 
					
						
							|  |  |  |         "Moon Crest", | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-27 03:28:00 +01:00
										 |  |  | REGION_ORDER: list[str] = [ | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |     "Autumn Hills", | 
					
						
							|  |  |  |     "Forlorn Temple", | 
					
						
							|  |  |  |     "Catacombs", | 
					
						
							|  |  |  |     "Bamboo Creek", | 
					
						
							|  |  |  |     "Howling Grotto", | 
					
						
							|  |  |  |     "Quillshroom Marsh", | 
					
						
							|  |  |  |     "Searing Crags", | 
					
						
							|  |  |  |     "Glacial Peak", | 
					
						
							|  |  |  |     "Tower of Time", | 
					
						
							|  |  |  |     "Cloud Ruins", | 
					
						
							|  |  |  |     "Underworld", | 
					
						
							|  |  |  |     "Riviere Turquoise", | 
					
						
							|  |  |  |     "Elemental Skylands", | 
					
						
							|  |  |  |     "Sunken Shrine", | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  | def shuffle_portals(world: "MessengerWorld") -> None: | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |     """shuffles the output of the portals from the main hub""" | 
					
						
							| 
									
										
										
										
											2024-06-01 06:34:41 -05:00
										 |  |  |     from .options import ShufflePortals | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |     def create_mapping(in_portal: str, warp: str) -> str: | 
					
						
							|  |  |  |         """assigns the chosen output to the input""" | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  |         parent = out_to_parent[warp] | 
					
						
							|  |  |  |         exit_string = f"{parent.strip(' ')} - " | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if "Portal" in warp: | 
					
						
							|  |  |  |             exit_string += "Portal" | 
					
						
							| 
									
										
										
										
											2024-09-17 17:00:26 -05:00
										 |  |  |             world.portal_mapping.insert(PORTALS.index(in_portal), int(f"{REGION_ORDER.index(parent)}00")) | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |         elif warp in SHOP_POINTS[parent]: | 
					
						
							|  |  |  |             exit_string += f"{warp} Shop" | 
					
						
							| 
									
										
										
										
											2024-09-17 17:00:26 -05:00
										 |  |  |             world.portal_mapping.insert(PORTALS.index(in_portal), int(f"{REGION_ORDER.index(parent)}1{SHOP_POINTS[parent].index(warp)}")) | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |             exit_string += f"{warp} Checkpoint" | 
					
						
							| 
									
										
										
										
											2024-09-17 17:00:26 -05:00
										 |  |  |             world.portal_mapping.insert(PORTALS.index(in_portal), int(f"{REGION_ORDER.index(parent)}2{CHECKPOINTS[parent].index(warp)}")) | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         world.spoiler_portal_mapping[in_portal] = exit_string | 
					
						
							|  |  |  |         connect_portal(world, in_portal, exit_string) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |         return parent | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-27 03:28:00 +01:00
										 |  |  |     def handle_planned_portals(plando_connections: list[PlandoConnection]) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |         """checks the provided plando connections for portals and connects them""" | 
					
						
							| 
									
										
										
										
											2024-09-17 17:00:26 -05:00
										 |  |  |         nonlocal available_portals | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  |         for connection in plando_connections: | 
					
						
							|  |  |  |             # let it crash here if input is invalid | 
					
						
							| 
									
										
										
										
											2024-09-17 17:00:26 -05:00
										 |  |  |             available_portals.remove(connection.exit) | 
					
						
							|  |  |  |             parent = create_mapping(connection.entrance, connection.exit) | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  |             world.plando_portals.append(connection.entrance) | 
					
						
							| 
									
										
										
										
											2024-09-17 17:00:26 -05:00
										 |  |  |             if shuffle_type < ShufflePortals.option_anywhere: | 
					
						
							|  |  |  |                 available_portals = [port for port in available_portals if port not in shop_points[parent]] | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     shuffle_type = world.options.shuffle_portals | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |     shop_points = deepcopy(SHOP_POINTS) | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  |     for portal in PORTALS: | 
					
						
							|  |  |  |         shop_points[portal].append(f"{portal} Portal") | 
					
						
							|  |  |  |     if shuffle_type > ShufflePortals.option_shops: | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |         for area, points in CHECKPOINTS.items(): | 
					
						
							|  |  |  |             shop_points[area] += points | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  |     out_to_parent = {checkpoint: parent for parent, checkpoints in shop_points.items() for checkpoint in checkpoints} | 
					
						
							|  |  |  |     available_portals = [val for zone in shop_points.values() for val in zone] | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |     world.random.shuffle(available_portals) | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-01 06:34:41 -05:00
										 |  |  |     plando = world.options.portal_plando.value | 
					
						
							|  |  |  |     if not plando: | 
					
						
							|  |  |  |         plando = world.options.plando_connections.value | 
					
						
							| 
									
										
										
										
											2024-09-17 17:00:26 -05:00
										 |  |  |     if plando and world.multiworld.plando_options & PlandoOptions.connections and not world.plando_portals: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             handle_planned_portals(plando) | 
					
						
							|  |  |  |         # any failure i expect will trigger on available_portals.remove | 
					
						
							|  |  |  |         except ValueError: | 
					
						
							|  |  |  |             raise ValueError(f"Unable to complete portal plando for Player {world.player_name}. " | 
					
						
							|  |  |  |                              f"If you attempted to plando a checkpoint, checkpoints must be shuffled.") | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  |     for portal in PORTALS: | 
					
						
							| 
									
										
										
										
											2024-03-29 20:14:53 -05:00
										 |  |  |         if portal in world.plando_portals: | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         warp_point = available_portals.pop() | 
					
						
							|  |  |  |         parent = create_mapping(portal, warp_point) | 
					
						
							|  |  |  |         if shuffle_type < ShufflePortals.option_anywhere: | 
					
						
							|  |  |  |             available_portals = [port for port in available_portals if port not in shop_points[parent]] | 
					
						
							|  |  |  |             world.random.shuffle(available_portals) | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def connect_portal(world: "MessengerWorld", portal: str, out_region: str) -> None: | 
					
						
							|  |  |  |     entrance = world.multiworld.get_entrance(f"ToTHQ {portal} Portal", world.player) | 
					
						
							|  |  |  |     entrance.connect(world.multiworld.get_region(out_region, world.player)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def disconnect_portals(world: "MessengerWorld") -> None: | 
					
						
							|  |  |  |     for portal in [port for port in PORTALS if port not in world.plando_portals]: | 
					
						
							|  |  |  |         entrance = world.multiworld.get_entrance(f"ToTHQ {portal} Portal", world.player) | 
					
						
							|  |  |  |         entrance.connected_region.entrances.remove(entrance) | 
					
						
							|  |  |  |         entrance.connected_region = None | 
					
						
							|  |  |  |         if portal in world.spoiler_portal_mapping: | 
					
						
							|  |  |  |             del world.spoiler_portal_mapping[portal] | 
					
						
							| 
									
										
										
										
											2024-09-17 17:00:26 -05:00
										 |  |  |     if world.plando_portals: | 
					
						
							|  |  |  |         indexes = [PORTALS.index(portal) for portal in world.plando_portals] | 
					
						
							|  |  |  |         planned_portals = [] | 
					
						
							|  |  |  |         for index, portal_coord in enumerate(world.portal_mapping): | 
					
						
							|  |  |  |             if index in indexes: | 
					
						
							|  |  |  |                 planned_portals.append(portal_coord) | 
					
						
							|  |  |  |         world.portal_mapping = planned_portals | 
					
						
							| 
									
										
										
										
											2024-03-11 17:23:41 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def validate_portals(world: "MessengerWorld") -> bool: | 
					
						
							|  |  |  |     # if world.options.shuffle_transitions: | 
					
						
							|  |  |  |     #     return True | 
					
						
							|  |  |  |     new_state = CollectionState(world.multiworld) | 
					
						
							|  |  |  |     new_state.update_reachable_regions(world.player) | 
					
						
							|  |  |  |     reachable_locs = 0 | 
					
						
							|  |  |  |     for loc in world.multiworld.get_locations(world.player): | 
					
						
							|  |  |  |         reachable_locs += loc.can_reach(new_state) | 
					
						
							|  |  |  |         if reachable_locs > 5: | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |     return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def add_closed_portal_reqs(world: "MessengerWorld") -> None: | 
					
						
							|  |  |  |     closed_portals = [entrance for entrance in PORTALS if f"{entrance} Portal" not in world.starting_portals] | 
					
						
							|  |  |  |     for portal in closed_portals: | 
					
						
							|  |  |  |         tower_exit = world.multiworld.get_entrance(f"ToTHQ {portal} Portal", world.player) | 
					
						
							|  |  |  |         tower_exit.access_rule = lambda state: state.has(portal, world.player) |