from typing import Dict, Optional, TYPE_CHECKING from BaseClasses import Entrance, ItemClassification, Region from .datatypes import EntranceType, Room, RoomAndDoor from .items import LingoItem from .locations import LingoLocation from .options import SunwarpAccess from .rules import lingo_can_do_pilgrimage, lingo_can_use_entrance, make_location_lambda from .static_logic import ALL_ROOMS, PAINTINGS if TYPE_CHECKING: from . import LingoWorld def create_region(room: Room, world: "LingoWorld") -> Region: new_region = Region(room.name, world.player, world.multiworld) for location in world.player_logic.locations_by_room.get(room.name, {}): new_location = LingoLocation(world.player, location.name, location.code, new_region) new_location.access_rule = make_location_lambda(location, world) new_region.locations.append(new_location) if location.name in world.player_logic.event_loc_to_item: event_name = world.player_logic.event_loc_to_item[location.name] event_item = LingoItem(event_name, ItemClassification.progression, None, world.player) new_location.place_locked_item(event_item) return new_region def is_acceptable_pilgrimage_entrance(entrance_type: EntranceType, world: "LingoWorld") -> bool: allowed_entrance_types = EntranceType.NORMAL if world.options.pilgrimage_allows_paintings: allowed_entrance_types |= EntranceType.PAINTING if world.options.pilgrimage_allows_roof_access: allowed_entrance_types |= EntranceType.CROSSROADS_ROOF_ACCESS return bool(entrance_type & allowed_entrance_types) def connect_entrance(regions: Dict[str, Region], source_region: Region, target_region: Region, description: str, door: Optional[RoomAndDoor], entrance_type: EntranceType, pilgrimage: bool, world: "LingoWorld"): connection = Entrance(world.player, description, source_region) connection.access_rule = lambda state: lingo_can_use_entrance(state, target_region.name, door, world) source_region.exits.append(connection) connection.connect(target_region) if door is not None: effective_room = target_region.name if door.room is None else door.room if door.door not in world.player_logic.item_by_door.get(effective_room, {}): for region in world.player_logic.calculate_door_requirements(effective_room, door.door, world).rooms: world.multiworld.register_indirect_condition(regions[region], connection) if not pilgrimage and world.options.enable_pilgrimage and is_acceptable_pilgrimage_entrance(entrance_type, world)\ and source_region.name != "Menu": for part in range(1, 6): pilgrimage_descriptor = f" (Pilgrimage Part {part})" pilgrim_source_region = regions[f"{source_region.name}{pilgrimage_descriptor}"] pilgrim_target_region = regions[f"{target_region.name}{pilgrimage_descriptor}"] effective_door = door if effective_door is not None: effective_room = target_region.name if door.room is None else door.room effective_door = RoomAndDoor(effective_room, door.door) connect_entrance(regions, pilgrim_source_region, pilgrim_target_region, f"{description}{pilgrimage_descriptor}", effective_door, entrance_type, True, world) def connect_painting(regions: Dict[str, Region], warp_enter: str, warp_exit: str, world: "LingoWorld") -> None: source_painting = PAINTINGS[warp_enter] target_painting = PAINTINGS[warp_exit] target_region = regions[target_painting.room] source_region = regions[source_painting.room] entrance_name = f"{source_painting.room} to {target_painting.room} ({source_painting.id} Painting)" connect_entrance(regions, source_region, target_region, entrance_name, source_painting.required_door, EntranceType.PAINTING, False, world) def create_regions(world: "LingoWorld") -> None: regions = { "Menu": Region("Menu", world.player, world.multiworld) } painting_shuffle = world.options.shuffle_paintings early_color_hallways = world.options.early_color_hallways # Instantiate all rooms as regions with their locations first. for room in ALL_ROOMS: regions[room.name] = create_region(room, world) if world.options.enable_pilgrimage: for part in range(1, 6): pilgrimage_region_name = f"{room.name} (Pilgrimage Part {part})" regions[pilgrimage_region_name] = Region(pilgrimage_region_name, world.player, world.multiworld) # Connect all created regions now that they exist. allowed_entrance_types = EntranceType.NORMAL | EntranceType.WARP | EntranceType.CROSSROADS_ROOF_ACCESS if not painting_shuffle: # Don't use the vanilla painting connections if we are shuffling paintings. allowed_entrance_types |= EntranceType.PAINTING if world.options.sunwarp_access != SunwarpAccess.option_disabled and not world.options.shuffle_sunwarps: # Don't connect sunwarps if sunwarps are disabled or if we're shuffling sunwarps. allowed_entrance_types |= EntranceType.SUNWARP for room in ALL_ROOMS: for entrance in room.entrances: effective_entrance_type = entrance.type & allowed_entrance_types if not effective_entrance_type: continue entrance_name = f"{entrance.room} to {room.name}" if entrance.door is not None: if entrance.door.room is not None: entrance_name += f" (through {entrance.door.room} - {entrance.door.door})" else: entrance_name += f" (through {room.name} - {entrance.door.door})" effective_door = entrance.door if entrance.type == EntranceType.SUNWARP and world.options.sunwarp_access == SunwarpAccess.option_normal: effective_door = None connect_entrance(regions, regions[entrance.room], regions[room.name], entrance_name, effective_door, effective_entrance_type, False, world) if world.options.enable_pilgrimage: # Connect the start of the pilgrimage. We check for all sunwarp items here. pilgrim_start_from = regions[world.player_logic.sunwarp_entrances[0]] pilgrim_start_to = regions[f"{world.player_logic.sunwarp_exits[0]} (Pilgrimage Part 1)"] if world.options.sunwarp_access >= SunwarpAccess.option_unlock: pilgrim_start_from.connect(pilgrim_start_to, f"Pilgrimage Part 1", lambda state: lingo_can_do_pilgrimage(state, world)) else: pilgrim_start_from.connect(pilgrim_start_to, f"Pilgrimage Part 1") # Create connections between each segment of the pilgrimage. for i in range(1, 6): from_room = f"{world.player_logic.sunwarp_entrances[i]} (Pilgrimage Part {i})" to_room = f"{world.player_logic.sunwarp_exits[i]} (Pilgrimage Part {i+1})" if i == 5: to_room = "Pilgrim Antechamber" regions[from_room].connect(regions[to_room], f"Pilgrimage Part {i+1}") else: connect_entrance(regions, regions["Starting Room"], regions["Pilgrim Antechamber"], "Sun Painting", RoomAndDoor("Pilgrim Antechamber", "Sun Painting"), EntranceType.PAINTING, False, world) if early_color_hallways: connect_entrance(regions, regions["Starting Room"], regions["Outside The Undeterred"], "Early Color Hallways", None, EntranceType.PAINTING, False, world) if painting_shuffle: for warp_enter, warp_exit in world.player_logic.painting_mapping.items(): connect_painting(regions, warp_enter, warp_exit, world) if world.options.shuffle_sunwarps: for i in range(0, 6): if world.options.sunwarp_access == SunwarpAccess.option_normal: effective_door = None else: effective_door = RoomAndDoor("Sunwarps", f"{i + 1} Sunwarp") source_region = regions[world.player_logic.sunwarp_entrances[i]] target_region = regions[world.player_logic.sunwarp_exits[i]] entrance_name = f"{source_region.name} to {target_region.name} ({i + 1} Sunwarp)" connect_entrance(regions, source_region, target_region, entrance_name, effective_door, EntranceType.SUNWARP, False, world) world.multiworld.regions += regions.values()