Lingo: The Pilgrim Update (#2884)
* An option was added to enable or disable the pilgrimage, and it defaults to disabled. When disabled, the client will prevent you from performing a pilgrimage (i.e. the yellow border will not appear when you enter the 1 sunwarp). The sun painting is added to the item pool when pilgrimage is disabled, as otherwise there is no way into the Pilgrim Antechamber. Inversely, the sun painting is no longer in the item pool when pilgrimage is enabled (even if door shuffle is on), requiring you to perform a pilgrimage to get to that room. * The canonical pilgrimage has been deprecated. Instead, there is logic for determining whether a pilgrimage is possible. * Two options were added that allow the player to decide whether paintings and/or Crossroads - Roof Access are permitted during the pilgrimage. Both default to disabled. These options apply both to logical expectations in the generator, and are also enforced by the game client. * An option was added to control how sunwarps are accessed. The default is for them to always be accessible, like in the base game. It is also possible to disable them entirely (which is not possible when pilgrimage is enabled), or lock them behind items similar to door shuffle. It can either be one item that unlocks all sunwarps at the same time, six progressive items that unlock the sunwarps from 1 to 6, or six individual items that unlock the sunwarps in any order. This option is independent from door shuffle. * An option was added that shuffles sunwarps. This acts similarly to painting shuffle. The 12 sunwarps are re-ordered and re-paired. Sunwarps that were previously entrances or exits do not need to stay entrances or exits. Performing a pilgrimage requires proceeding through the sunwarps in the new order, rather than the original one. * Pilgrimage was added as a win condition. It requires you to solve the blue PILGRIM panel in the Pilgrim Antechamber.
This commit is contained in:
parent
6b50c91ce2
commit
740b76ebd5
|
@ -132,7 +132,8 @@ class LingoWorld(World):
|
|||
def fill_slot_data(self):
|
||||
slot_options = [
|
||||
"death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels",
|
||||
"mastery_achievements", "level_2_requirement", "location_checks", "early_color_hallways"
|
||||
"enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks",
|
||||
"early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps"
|
||||
]
|
||||
|
||||
slot_data = {
|
||||
|
@ -143,6 +144,9 @@ class LingoWorld(World):
|
|||
if self.options.shuffle_paintings:
|
||||
slot_data["painting_entrance_to_exit"] = self.player_logic.painting_mapping
|
||||
|
||||
if self.options.shuffle_sunwarps:
|
||||
slot_data["sunwarp_permutation"] = self.player_logic.sunwarp_mapping
|
||||
|
||||
return slot_data
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -140,13 +140,9 @@ panels:
|
|||
PURPLE: 444502
|
||||
FIVE (1): 444503
|
||||
FIVE (2): 444504
|
||||
OUT: 444505
|
||||
HIDE: 444506
|
||||
DAZE: 444507
|
||||
WALL: 444508
|
||||
KEEP: 444509
|
||||
BAILEY: 444510
|
||||
TOWER: 444511
|
||||
Compass Room:
|
||||
NORTH: 444512
|
||||
DIAMONDS: 444513
|
||||
FIRE: 444514
|
||||
|
@ -689,6 +685,12 @@ panels:
|
|||
Arrow Garden:
|
||||
MASTERY: 444948
|
||||
SHARP: 444949
|
||||
Hallway Room (1):
|
||||
OUT: 444505
|
||||
WALL: 444508
|
||||
KEEP: 444509
|
||||
BAILEY: 444510
|
||||
TOWER: 444511
|
||||
Hallway Room (2):
|
||||
WISE: 444950
|
||||
CLOCK: 444951
|
||||
|
@ -995,6 +997,19 @@ doors:
|
|||
Traveled Entrance:
|
||||
item: 444433
|
||||
location: 444438
|
||||
Sunwarps:
|
||||
1 Sunwarp:
|
||||
item: 444581
|
||||
2 Sunwarp:
|
||||
item: 444588
|
||||
3 Sunwarp:
|
||||
item: 444586
|
||||
4 Sunwarp:
|
||||
item: 444585
|
||||
5 Sunwarp:
|
||||
item: 444587
|
||||
6 Sunwarp:
|
||||
item: 444584
|
||||
Pilgrim Antechamber:
|
||||
Sun Painting:
|
||||
item: 444436
|
||||
|
@ -1067,9 +1082,7 @@ doors:
|
|||
location: 444501
|
||||
Purple Barrier:
|
||||
item: 444457
|
||||
Hallway Door:
|
||||
item: 444459
|
||||
location: 445214
|
||||
Compass Room:
|
||||
Lookout Entrance:
|
||||
item: 444579
|
||||
location: 445271
|
||||
|
@ -1342,6 +1355,10 @@ doors:
|
|||
Exit:
|
||||
item: 444552
|
||||
location: 444947
|
||||
Hallway Room (1):
|
||||
Exit:
|
||||
item: 444459
|
||||
location: 445214
|
||||
Hallway Room (2):
|
||||
Exit:
|
||||
item: 444553
|
||||
|
@ -1452,9 +1469,11 @@ door_groups:
|
|||
Colorful Doors: 444498
|
||||
Directional Gallery Doors: 444531
|
||||
Artistic Doors: 444545
|
||||
Sunwarps: 444582
|
||||
progression:
|
||||
Progressive Hallway Room: 444461
|
||||
Progressive Fearless: 444470
|
||||
Progressive Orange Tower: 444482
|
||||
Progressive Art Gallery: 444563
|
||||
Progressive Colorful: 444580
|
||||
Progressive Pilgrimage: 444583
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from enum import Enum, Flag, auto
|
||||
from typing import List, NamedTuple, Optional
|
||||
|
||||
|
||||
|
@ -11,10 +12,18 @@ class RoomAndPanel(NamedTuple):
|
|||
panel: str
|
||||
|
||||
|
||||
class EntranceType(Flag):
|
||||
NORMAL = auto()
|
||||
PAINTING = auto()
|
||||
SUNWARP = auto()
|
||||
WARP = auto()
|
||||
CROSSROADS_ROOF_ACCESS = auto()
|
||||
|
||||
|
||||
class RoomEntrance(NamedTuple):
|
||||
room: str # source room
|
||||
door: Optional[RoomAndDoor]
|
||||
painting: bool
|
||||
type: EntranceType
|
||||
|
||||
|
||||
class Room(NamedTuple):
|
||||
|
@ -22,6 +31,12 @@ class Room(NamedTuple):
|
|||
entrances: List[RoomEntrance]
|
||||
|
||||
|
||||
class DoorType(Enum):
|
||||
NORMAL = 1
|
||||
SUNWARP = 2
|
||||
SUN_PAINTING = 3
|
||||
|
||||
|
||||
class Door(NamedTuple):
|
||||
name: str
|
||||
item_name: str
|
||||
|
@ -34,7 +49,7 @@ class Door(NamedTuple):
|
|||
event: bool
|
||||
door_group: Optional[str]
|
||||
include_reduce: bool
|
||||
junk_item: bool
|
||||
type: DoorType
|
||||
item_group: Optional[str]
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
from typing import Dict, List, NamedTuple, Optional, TYPE_CHECKING
|
||||
from enum import Enum
|
||||
from typing import Dict, List, NamedTuple, Set
|
||||
|
||||
from BaseClasses import Item, ItemClassification
|
||||
from .static_logic import DOORS_BY_ROOM, PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS, get_door_group_item_id, \
|
||||
get_door_item_id, get_progressive_item_id, get_special_item_id
|
||||
from .static_logic import DOORS_BY_ROOM, PROGRESSIVE_ITEMS, get_door_group_item_id, get_door_item_id, \
|
||||
get_progressive_item_id, get_special_item_id
|
||||
|
||||
|
||||
class ItemType(Enum):
|
||||
NORMAL = 1
|
||||
COLOR = 2
|
||||
|
||||
|
||||
class ItemData(NamedTuple):
|
||||
|
@ -11,7 +17,7 @@ class ItemData(NamedTuple):
|
|||
"""
|
||||
code: int
|
||||
classification: ItemClassification
|
||||
mode: Optional[str]
|
||||
type: ItemType
|
||||
has_doors: bool
|
||||
painting_ids: List[str]
|
||||
|
||||
|
@ -34,36 +40,29 @@ def load_item_data():
|
|||
|
||||
for color in ["Black", "Red", "Blue", "Yellow", "Green", "Orange", "Gray", "Brown", "Purple"]:
|
||||
ALL_ITEM_TABLE[color] = ItemData(get_special_item_id(color), ItemClassification.progression,
|
||||
"colors", [], [])
|
||||
ItemType.COLOR, False, [])
|
||||
ITEMS_BY_GROUP.setdefault("Colors", []).append(color)
|
||||
|
||||
door_groups: Dict[str, List[str]] = {}
|
||||
door_groups: Set[str] = set()
|
||||
for room_name, doors in DOORS_BY_ROOM.items():
|
||||
for door_name, door in doors.items():
|
||||
if door.skip_item is True or door.event is True:
|
||||
continue
|
||||
|
||||
if door.door_group is None:
|
||||
door_mode = "doors"
|
||||
else:
|
||||
door_mode = "complex door"
|
||||
door_groups.setdefault(door.door_group, [])
|
||||
|
||||
if room_name in PROGRESSION_BY_ROOM and door_name in PROGRESSION_BY_ROOM[room_name]:
|
||||
door_mode = "special"
|
||||
if door.door_group is not None:
|
||||
door_groups.add(door.door_group)
|
||||
|
||||
ALL_ITEM_TABLE[door.item_name] = \
|
||||
ItemData(get_door_item_id(room_name, door_name),
|
||||
ItemClassification.filler if door.junk_item else ItemClassification.progression, door_mode,
|
||||
ItemData(get_door_item_id(room_name, door_name), ItemClassification.progression, ItemType.NORMAL,
|
||||
door.has_doors, door.painting_ids)
|
||||
ITEMS_BY_GROUP.setdefault("Doors", []).append(door.item_name)
|
||||
|
||||
if door.item_group is not None:
|
||||
ITEMS_BY_GROUP.setdefault(door.item_group, []).append(door.item_name)
|
||||
|
||||
for group, group_door_ids in door_groups.items():
|
||||
for group in door_groups:
|
||||
ALL_ITEM_TABLE[group] = ItemData(get_door_group_item_id(group),
|
||||
ItemClassification.progression, "door group", True, [])
|
||||
ItemClassification.progression, ItemType.NORMAL, True, [])
|
||||
ITEMS_BY_GROUP.setdefault("Doors", []).append(group)
|
||||
|
||||
special_items: Dict[str, ItemClassification] = {
|
||||
|
@ -77,7 +76,7 @@ def load_item_data():
|
|||
|
||||
for item_name, classification in special_items.items():
|
||||
ALL_ITEM_TABLE[item_name] = ItemData(get_special_item_id(item_name), classification,
|
||||
"special", False, [])
|
||||
ItemType.NORMAL, False, [])
|
||||
|
||||
if classification == ItemClassification.filler:
|
||||
ITEMS_BY_GROUP.setdefault("Junk", []).append(item_name)
|
||||
|
@ -86,7 +85,7 @@ def load_item_data():
|
|||
|
||||
for item_name in PROGRESSIVE_ITEMS:
|
||||
ALL_ITEM_TABLE[item_name] = ItemData(get_progressive_item_id(item_name),
|
||||
ItemClassification.progression, "special", False, [])
|
||||
ItemClassification.progression, ItemType.NORMAL, False, [])
|
||||
|
||||
|
||||
# Initialize the item data at module scope.
|
||||
|
|
|
@ -56,7 +56,7 @@ def load_location_data():
|
|||
|
||||
for room_name, doors in DOORS_BY_ROOM.items():
|
||||
for door_name, door in doors.items():
|
||||
if door.skip_location or door.event or door.panels is None:
|
||||
if door.skip_location or door.event or not door.panels:
|
||||
continue
|
||||
|
||||
location_name = door.location_name
|
||||
|
|
|
@ -61,15 +61,55 @@ class ShufflePaintings(Toggle):
|
|||
display_name = "Shuffle Paintings"
|
||||
|
||||
|
||||
class EnablePilgrimage(Toggle):
|
||||
"""If on, you are required to complete a pilgrimage in order to access the Pilgrim Antechamber.
|
||||
If off, the pilgrimage will be deactivated, and the sun painting will be added to the pool, even if door shuffle is off."""
|
||||
display_name = "Enable Pilgrimage"
|
||||
|
||||
|
||||
class PilgrimageAllowsRoofAccess(DefaultOnToggle):
|
||||
"""If on, you may use the Crossroads roof access during a pilgrimage (and you may be expected to do so).
|
||||
Otherwise, pilgrimage will be deactivated when going up the stairs."""
|
||||
display_name = "Allow Roof Access for Pilgrimage"
|
||||
|
||||
|
||||
class PilgrimageAllowsPaintings(DefaultOnToggle):
|
||||
"""If on, you may use paintings during a pilgrimage (and you may be expected to do so).
|
||||
Otherwise, pilgrimage will be deactivated when going through a painting."""
|
||||
display_name = "Allow Paintings for Pilgrimage"
|
||||
|
||||
|
||||
class SunwarpAccess(Choice):
|
||||
"""Determines how access to sunwarps works.
|
||||
On "normal", all sunwarps are enabled from the start.
|
||||
On "disabled", all sunwarps are disabled. Pilgrimage must be disabled when this is used.
|
||||
On "unlock", sunwarps start off disabled, and all six activate once you receive an item.
|
||||
On "individual", sunwarps start off disabled, and each has a corresponding item that unlocks it.
|
||||
On "progressive", sunwarps start off disabled, and they unlock in order using a progressive item."""
|
||||
display_name = "Sunwarp Access"
|
||||
option_normal = 0
|
||||
option_disabled = 1
|
||||
option_unlock = 2
|
||||
option_individual = 3
|
||||
option_progressive = 4
|
||||
|
||||
|
||||
class ShuffleSunwarps(Toggle):
|
||||
"""If on, the pairing and ordering of the sunwarps in the game will be randomized."""
|
||||
display_name = "Shuffle Sunwarps"
|
||||
|
||||
|
||||
class VictoryCondition(Choice):
|
||||
"""Change the victory condition.
|
||||
On "the_end", the goal is to solve THE END at the top of the tower.
|
||||
On "the_master", the goal is to solve THE MASTER at the top of the tower, after getting the number of achievements specified in the Mastery Achievements option.
|
||||
On "level_2", the goal is to solve LEVEL 2 in the second room, after solving the number of panels specified in the Level 2 Requirement option."""
|
||||
On "level_2", the goal is to solve LEVEL 2 in the second room, after solving the number of panels specified in the Level 2 Requirement option.
|
||||
On "pilgrimage", the goal is to solve PILGRIM in the Pilgrim Antechamber, typically after performing a Pilgrimage."""
|
||||
display_name = "Victory Condition"
|
||||
option_the_end = 0
|
||||
option_the_master = 1
|
||||
option_level_2 = 2
|
||||
option_pilgrimage = 3
|
||||
|
||||
|
||||
class MasteryAchievements(Range):
|
||||
|
@ -140,6 +180,11 @@ class LingoOptions(PerGameCommonOptions):
|
|||
shuffle_colors: ShuffleColors
|
||||
shuffle_panels: ShufflePanels
|
||||
shuffle_paintings: ShufflePaintings
|
||||
enable_pilgrimage: EnablePilgrimage
|
||||
pilgrimage_allows_roof_access: PilgrimageAllowsRoofAccess
|
||||
pilgrimage_allows_paintings: PilgrimageAllowsPaintings
|
||||
sunwarp_access: SunwarpAccess
|
||||
shuffle_sunwarps: ShuffleSunwarps
|
||||
victory_condition: VictoryCondition
|
||||
mastery_achievements: MasteryAchievements
|
||||
level_2_requirement: Level2Requirement
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
from enum import Enum
|
||||
from typing import Dict, List, NamedTuple, Optional, Set, Tuple, TYPE_CHECKING
|
||||
|
||||
from .datatypes import Door, RoomAndDoor, RoomAndPanel
|
||||
from .items import ALL_ITEM_TABLE, ItemData
|
||||
from .datatypes import Door, DoorType, RoomAndDoor, RoomAndPanel
|
||||
from .items import ALL_ITEM_TABLE, ItemType
|
||||
from .locations import ALL_LOCATION_TABLE, LocationClassification
|
||||
from .options import LocationChecks, ShuffleDoors, VictoryCondition
|
||||
from .options import LocationChecks, ShuffleDoors, SunwarpAccess, VictoryCondition
|
||||
from .static_logic import DOORS_BY_ROOM, PAINTINGS, PAINTING_ENTRANCES, PAINTING_EXITS, \
|
||||
PANELS_BY_ROOM, PROGRESSION_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS
|
||||
PANELS_BY_ROOM, PROGRESSION_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, \
|
||||
SUNWARP_ENTRANCES, SUNWARP_EXITS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import LingoWorld
|
||||
|
@ -58,21 +59,6 @@ def should_split_progression(progression_name: str, world: "LingoWorld") -> Prog
|
|||
return ProgressiveItemBehavior.PROGRESSIVE
|
||||
|
||||
|
||||
def should_include_item(item: ItemData, world: "LingoWorld") -> bool:
|
||||
if item.mode == "colors":
|
||||
return world.options.shuffle_colors > 0
|
||||
elif item.mode == "doors":
|
||||
return world.options.shuffle_doors != ShuffleDoors.option_none
|
||||
elif item.mode == "complex door":
|
||||
return world.options.shuffle_doors == ShuffleDoors.option_complex
|
||||
elif item.mode == "door group":
|
||||
return world.options.shuffle_doors == ShuffleDoors.option_simple
|
||||
elif item.mode == "special":
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class LingoPlayerLogic:
|
||||
"""
|
||||
Defines logic after a player's options have been applied
|
||||
|
@ -99,6 +85,10 @@ class LingoPlayerLogic:
|
|||
mastery_reqs: List[AccessRequirements]
|
||||
counting_panel_reqs: Dict[str, List[Tuple[AccessRequirements, int]]]
|
||||
|
||||
sunwarp_mapping: List[int]
|
||||
sunwarp_entrances: List[str]
|
||||
sunwarp_exits: List[str]
|
||||
|
||||
def add_location(self, room: str, name: str, code: Optional[int], panels: List[RoomAndPanel], world: "LingoWorld"):
|
||||
"""
|
||||
Creates a location. This function determines the access requirements for the location by combining and
|
||||
|
@ -132,6 +122,7 @@ class LingoPlayerLogic:
|
|||
self.real_items.append(progressive_item_name)
|
||||
else:
|
||||
self.set_door_item(room_name, door_data.name, door_data.item_name)
|
||||
self.real_items.append(door_data.item_name)
|
||||
|
||||
def __init__(self, world: "LingoWorld"):
|
||||
self.item_by_door = {}
|
||||
|
@ -148,6 +139,7 @@ class LingoPlayerLogic:
|
|||
self.door_reqs = {}
|
||||
self.mastery_reqs = []
|
||||
self.counting_panel_reqs = {}
|
||||
self.sunwarp_mapping = []
|
||||
|
||||
door_shuffle = world.options.shuffle_doors
|
||||
color_shuffle = world.options.shuffle_colors
|
||||
|
@ -161,15 +153,37 @@ class LingoPlayerLogic:
|
|||
"be enough locations for all of the door items.")
|
||||
|
||||
# Create door items, where needed.
|
||||
if door_shuffle != ShuffleDoors.option_none:
|
||||
for room_name, room_data in DOORS_BY_ROOM.items():
|
||||
for door_name, door_data in room_data.items():
|
||||
if door_data.skip_item is False and door_data.event is False:
|
||||
door_groups: Set[str] = set()
|
||||
for room_name, room_data in DOORS_BY_ROOM.items():
|
||||
for door_name, door_data in room_data.items():
|
||||
if door_data.skip_item is False and door_data.event is False:
|
||||
if door_data.type == DoorType.NORMAL and door_shuffle != ShuffleDoors.option_none:
|
||||
if door_data.door_group is not None and door_shuffle == ShuffleDoors.option_simple:
|
||||
# Grouped doors are handled differently if shuffle doors is on simple.
|
||||
self.set_door_item(room_name, door_name, door_data.door_group)
|
||||
door_groups.add(door_data.door_group)
|
||||
else:
|
||||
self.handle_non_grouped_door(room_name, door_data, world)
|
||||
elif door_data.type == DoorType.SUNWARP:
|
||||
if world.options.sunwarp_access == SunwarpAccess.option_unlock:
|
||||
self.set_door_item(room_name, door_name, "Sunwarps")
|
||||
door_groups.add("Sunwarps")
|
||||
elif world.options.sunwarp_access == SunwarpAccess.option_individual:
|
||||
self.set_door_item(room_name, door_name, door_data.item_name)
|
||||
self.real_items.append(door_data.item_name)
|
||||
elif world.options.sunwarp_access == SunwarpAccess.option_progressive:
|
||||
self.set_door_item(room_name, door_name, "Progressive Pilgrimage")
|
||||
self.real_items.append("Progressive Pilgrimage")
|
||||
elif door_data.type == DoorType.SUN_PAINTING:
|
||||
if not world.options.enable_pilgrimage:
|
||||
self.set_door_item(room_name, door_name, door_data.item_name)
|
||||
self.real_items.append(door_data.item_name)
|
||||
|
||||
self.real_items += door_groups
|
||||
|
||||
# Create color items, if needed.
|
||||
if color_shuffle:
|
||||
self.real_items += [name for name, item in ALL_ITEM_TABLE.items() if item.type == ItemType.COLOR]
|
||||
|
||||
# Create events for each achievement panel, so that we can determine when THE MASTER is accessible.
|
||||
for room_name, room_data in PANELS_BY_ROOM.items():
|
||||
|
@ -206,6 +220,11 @@ class LingoPlayerLogic:
|
|||
|
||||
if world.options.level_2_requirement == 1:
|
||||
raise Exception("The Level 2 requirement must be at least 2 when LEVEL 2 is the victory condition.")
|
||||
elif victory_condition == VictoryCondition.option_pilgrimage:
|
||||
self.victory_condition = "Pilgrim Antechamber - PILGRIM"
|
||||
self.add_location("Pilgrim Antechamber", "PILGRIM (Solved)", None,
|
||||
[RoomAndPanel("Pilgrim Antechamber", "PILGRIM")], world)
|
||||
self.event_loc_to_item["PILGRIM (Solved)"] = "Victory"
|
||||
|
||||
# Create groups of counting panel access requirements for the LEVEL 2 check.
|
||||
self.create_panel_hunt_events(world)
|
||||
|
@ -225,28 +244,22 @@ class LingoPlayerLogic:
|
|||
self.add_location(location_data.room, location_name, location_data.code, location_data.panels, world)
|
||||
self.real_locations.append(location_name)
|
||||
|
||||
# Instantiate all real items.
|
||||
for name, item in ALL_ITEM_TABLE.items():
|
||||
if should_include_item(item, world):
|
||||
self.real_items.append(name)
|
||||
if world.options.enable_pilgrimage and world.options.sunwarp_access == SunwarpAccess.option_disabled:
|
||||
raise Exception("Sunwarps cannot be disabled when pilgrimage is enabled.")
|
||||
|
||||
# Calculate the requirements for the fake pilgrimage.
|
||||
fake_pilgrimage = [
|
||||
["Second Room", "Exit Door"], ["Crossroads", "Tower Entrance"],
|
||||
["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"],
|
||||
["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"],
|
||||
["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"],
|
||||
["Color Hunt", "Shortcut to The Steady"], ["The Bearer", "Entrance"], ["Art Gallery", "Exit"],
|
||||
["The Tenacious", "Shortcut to Hub Room"], ["Outside The Agreeable", "Tenacious Entrance"]
|
||||
]
|
||||
pilgrimage_reqs = AccessRequirements()
|
||||
for door in fake_pilgrimage:
|
||||
door_object = DOORS_BY_ROOM[door[0]][door[1]]
|
||||
if door_object.event or world.options.shuffle_doors == ShuffleDoors.option_none:
|
||||
pilgrimage_reqs.merge(self.calculate_door_requirements(door[0], door[1], world))
|
||||
else:
|
||||
pilgrimage_reqs.doors.add(RoomAndDoor(door[0], door[1]))
|
||||
self.door_reqs.setdefault("Pilgrim Antechamber", {})["Pilgrimage"] = pilgrimage_reqs
|
||||
if world.options.shuffle_sunwarps:
|
||||
if world.options.sunwarp_access == SunwarpAccess.option_disabled:
|
||||
raise Exception("Sunwarps cannot be shuffled if they are disabled.")
|
||||
|
||||
self.sunwarp_mapping = list(range(0, 12))
|
||||
world.random.shuffle(self.sunwarp_mapping)
|
||||
|
||||
sunwarp_rooms = SUNWARP_ENTRANCES + SUNWARP_EXITS
|
||||
self.sunwarp_entrances = [sunwarp_rooms[i] for i in self.sunwarp_mapping[0:6]]
|
||||
self.sunwarp_exits = [sunwarp_rooms[i] for i in self.sunwarp_mapping[6:12]]
|
||||
else:
|
||||
self.sunwarp_entrances = SUNWARP_ENTRANCES
|
||||
self.sunwarp_exits = SUNWARP_EXITS
|
||||
|
||||
# Create the paintings mapping, if painting shuffle is on.
|
||||
if painting_shuffle:
|
||||
|
@ -277,10 +290,11 @@ class LingoPlayerLogic:
|
|||
# Starting Room - Exit Door gives access to OPEN and TRACE.
|
||||
good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"]
|
||||
|
||||
if not color_shuffle:
|
||||
if not color_shuffle and not world.options.enable_pilgrimage:
|
||||
# HOT CRUST and THIS.
|
||||
good_item_options.append("Pilgrim Room - Sun Painting")
|
||||
|
||||
if not color_shuffle:
|
||||
if door_shuffle == ShuffleDoors.option_simple:
|
||||
# WELCOME BACK, CLOCKWISE, and DRAWL + RUNS.
|
||||
good_item_options.append("Welcome Back Doors")
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from typing import Dict, Optional, TYPE_CHECKING
|
||||
|
||||
from BaseClasses import Entrance, ItemClassification, Region
|
||||
from .datatypes import Room, RoomAndDoor
|
||||
from .datatypes import EntranceType, Room, RoomAndDoor
|
||||
from .items import LingoItem
|
||||
from .locations import LingoLocation
|
||||
from .rules import lingo_can_use_entrance, make_location_lambda
|
||||
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:
|
||||
|
@ -25,8 +26,20 @@ def create_region(room: Room, world: "LingoWorld") -> Region:
|
|||
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], world: "LingoWorld"):
|
||||
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)
|
||||
|
||||
|
@ -38,6 +51,21 @@ def connect_entrance(regions: Dict[str, Region], source_region: Region, target_r
|
|||
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:
|
||||
|
@ -48,7 +76,8 @@ def connect_painting(regions: Dict[str, Region], warp_enter: str, warp_exit: str
|
|||
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, world)
|
||||
connect_entrance(regions, source_region, target_region, entrance_name, source_painting.required_door,
|
||||
EntranceType.PAINTING, False, world)
|
||||
|
||||
|
||||
def create_regions(world: "LingoWorld") -> None:
|
||||
|
@ -63,11 +92,26 @@ def create_regions(world: "LingoWorld") -> None:
|
|||
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:
|
||||
# Don't use the vanilla painting connections if we are shuffling paintings.
|
||||
if entrance.painting and painting_shuffle:
|
||||
effective_entrance_type = entrance.type & allowed_entrance_types
|
||||
if not effective_entrance_type:
|
||||
continue
|
||||
|
||||
entrance_name = f"{entrance.room} to {room.name}"
|
||||
|
@ -77,17 +121,56 @@ def create_regions(world: "LingoWorld") -> None:
|
|||
else:
|
||||
entrance_name += f" (through {room.name} - {entrance.door.door})"
|
||||
|
||||
connect_entrance(regions, regions[entrance.room], regions[room.name], entrance_name, entrance.door, world)
|
||||
effective_door = entrance.door
|
||||
if entrance.type == EntranceType.SUNWARP and world.options.sunwarp_access == SunwarpAccess.option_normal:
|
||||
effective_door = None
|
||||
|
||||
# Add the fake pilgrimage.
|
||||
connect_entrance(regions, regions["Outside The Agreeable"], regions["Pilgrim Antechamber"], "Pilgrimage",
|
||||
RoomAndDoor("Pilgrim Antechamber", "Pilgrimage"), world)
|
||||
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:
|
||||
regions["Starting Room"].connect(regions["Outside The Undeterred"], "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()
|
||||
|
|
|
@ -17,6 +17,10 @@ def lingo_can_use_entrance(state: CollectionState, room: str, door: RoomAndDoor,
|
|||
return _lingo_can_open_door(state, effective_room, door.door, world)
|
||||
|
||||
|
||||
def lingo_can_do_pilgrimage(state: CollectionState, world: "LingoWorld"):
|
||||
return all(_lingo_can_open_door(state, "Sunwarps", f"{i} Sunwarp", world) for i in range(1, 7))
|
||||
|
||||
|
||||
def lingo_can_use_location(state: CollectionState, location: PlayerLocation, world: "LingoWorld"):
|
||||
return _lingo_can_satisfy_requirements(state, location.access, world)
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import os
|
||||
import pkgutil
|
||||
import pickle
|
||||
from io import BytesIO
|
||||
from typing import Dict, List, Set
|
||||
|
||||
import pickle
|
||||
|
||||
from .datatypes import Door, Painting, Panel, Progression, Room
|
||||
|
||||
ALL_ROOMS: List[Room] = []
|
||||
|
@ -21,6 +20,9 @@ PAINTING_EXITS: int = 0
|
|||
REQUIRED_PAINTING_ROOMS: List[str] = []
|
||||
REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS: List[str] = []
|
||||
|
||||
SUNWARP_ENTRANCES: List[str] = []
|
||||
SUNWARP_EXITS: List[str] = []
|
||||
|
||||
SPECIAL_ITEM_IDS: Dict[str, int] = {}
|
||||
PANEL_LOCATION_IDS: Dict[str, Dict[str, int]] = {}
|
||||
DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {}
|
||||
|
@ -99,6 +101,8 @@ def load_static_data_from_file():
|
|||
PAINTING_EXITS = pickdata["PAINTING_EXITS"]
|
||||
REQUIRED_PAINTING_ROOMS.extend(pickdata["REQUIRED_PAINTING_ROOMS"])
|
||||
REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS.extend(pickdata["REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS"])
|
||||
SUNWARP_ENTRANCES.extend(pickdata["SUNWARP_ENTRANCES"])
|
||||
SUNWARP_EXITS.extend(pickdata["SUNWARP_EXITS"])
|
||||
SPECIAL_ITEM_IDS.update(pickdata["SPECIAL_ITEM_IDS"])
|
||||
PANEL_LOCATION_IDS.update(pickdata["PANEL_LOCATION_IDS"])
|
||||
DOOR_LOCATION_IDS.update(pickdata["DOOR_LOCATION_IDS"])
|
||||
|
|
|
@ -29,3 +29,23 @@ class TestAllPanelHunt(LingoTestBase):
|
|||
"level_2_requirement": "800",
|
||||
"early_color_hallways": "true"
|
||||
}
|
||||
|
||||
|
||||
class TestShuffleSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "none",
|
||||
"shuffle_colors": "false",
|
||||
"victory_condition": "pilgrimage",
|
||||
"shuffle_sunwarps": "true",
|
||||
"sunwarp_access": "normal"
|
||||
}
|
||||
|
||||
|
||||
class TestShuffleSunwarpsAccess(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "none",
|
||||
"shuffle_colors": "false",
|
||||
"victory_condition": "pilgrimage",
|
||||
"shuffle_sunwarps": "true",
|
||||
"sunwarp_access": "individual"
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
from . import LingoTestBase
|
||||
|
||||
|
||||
class TestDisabledPilgrimage(LingoTestBase):
|
||||
options = {
|
||||
"enable_pilgrimage": "false",
|
||||
"shuffle_colors": "false"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
|
||||
self.collect_by_name("Pilgrim Room - Sun Painting")
|
||||
self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
|
||||
|
||||
class TestPilgrimageWithRoofAndPaintings(LingoTestBase):
|
||||
options = {
|
||||
"enable_pilgrimage": "true",
|
||||
"shuffle_colors": "false",
|
||||
"shuffle_doors": "complex",
|
||||
"pilgrimage_allows_roof_access": "true",
|
||||
"pilgrimage_allows_paintings": "true",
|
||||
"early_color_hallways": "false"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
doors = ["Second Room - Exit Door", "Crossroads - Roof Access", "Hub Room - Crossroads Entrance",
|
||||
"Outside The Undeterred - Green Painting"]
|
||||
|
||||
for door in doors:
|
||||
print(door)
|
||||
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
self.collect_by_name(door)
|
||||
|
||||
self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
|
||||
|
||||
class TestPilgrimageNoRoofYesPaintings(LingoTestBase):
|
||||
options = {
|
||||
"enable_pilgrimage": "true",
|
||||
"shuffle_colors": "false",
|
||||
"shuffle_doors": "complex",
|
||||
"pilgrimage_allows_roof_access": "false",
|
||||
"pilgrimage_allows_paintings": "true",
|
||||
"early_color_hallways": "false"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
doors = ["Second Room - Exit Door", "Crossroads - Roof Access", "Hub Room - Crossroads Entrance",
|
||||
"Outside The Undeterred - Green Painting", "Crossroads - Tower Entrance",
|
||||
"Orange Tower Fourth Floor - Hot Crusts Door", "Orange Tower First Floor - Shortcut to Hub Room",
|
||||
"Starting Room - Street Painting"]
|
||||
|
||||
for door in doors:
|
||||
print(door)
|
||||
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
self.collect_by_name(door)
|
||||
|
||||
self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
|
||||
|
||||
class TestPilgrimageNoRoofNoPaintings(LingoTestBase):
|
||||
options = {
|
||||
"enable_pilgrimage": "true",
|
||||
"shuffle_colors": "false",
|
||||
"shuffle_doors": "complex",
|
||||
"pilgrimage_allows_roof_access": "false",
|
||||
"pilgrimage_allows_paintings": "false",
|
||||
"early_color_hallways": "false"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
doors = ["Second Room - Exit Door", "Crossroads - Roof Access", "Hub Room - Crossroads Entrance",
|
||||
"Outside The Undeterred - Green Painting", "Orange Tower First Floor - Shortcut to Hub Room",
|
||||
"Starting Room - Street Painting", "Outside The Initiated - Shortcut to Hub Room",
|
||||
"Directional Gallery - Shortcut to The Undeterred", "Orange Tower First Floor - Salt Pepper Door",
|
||||
"Color Hunt - Shortcut to The Steady", "The Bearer - Entrance",
|
||||
"Orange Tower Fifth Floor - Quadruple Intersection", "The Tenacious - Shortcut to Hub Room",
|
||||
"Outside The Agreeable - Tenacious Entrance", "Crossroads - Tower Entrance",
|
||||
"Orange Tower Fourth Floor - Hot Crusts Door"]
|
||||
|
||||
for door in doors:
|
||||
print(door)
|
||||
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
self.collect_by_name(door)
|
||||
|
||||
self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
|
||||
|
||||
class TestPilgrimageYesRoofNoPaintings(LingoTestBase):
|
||||
options = {
|
||||
"enable_pilgrimage": "true",
|
||||
"shuffle_colors": "false",
|
||||
"shuffle_doors": "complex",
|
||||
"pilgrimage_allows_roof_access": "true",
|
||||
"pilgrimage_allows_paintings": "false",
|
||||
"early_color_hallways": "false"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
doors = ["Second Room - Exit Door", "Crossroads - Roof Access", "Hub Room - Crossroads Entrance",
|
||||
"Outside The Undeterred - Green Painting", "Orange Tower First Floor - Shortcut to Hub Room",
|
||||
"Starting Room - Street Painting", "Outside The Initiated - Shortcut to Hub Room",
|
||||
"Directional Gallery - Shortcut to The Undeterred", "Orange Tower First Floor - Salt Pepper Door",
|
||||
"Color Hunt - Shortcut to The Steady", "The Bearer - Entrance",
|
||||
"Orange Tower Fifth Floor - Quadruple Intersection"]
|
||||
|
||||
for door in doors:
|
||||
print(door)
|
||||
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
self.collect_by_name(door)
|
||||
|
||||
self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
|
@ -0,0 +1,213 @@
|
|||
from . import LingoTestBase
|
||||
|
||||
|
||||
class TestVanillaDoorsNormalSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "none",
|
||||
"shuffle_colors": "true",
|
||||
"sunwarp_access": "normal"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
|
||||
self.collect_by_name("Yellow")
|
||||
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
|
||||
class TestSimpleDoorsNormalSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "simple",
|
||||
"sunwarp_access": "normal"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name("Second Room - Exit Door")
|
||||
self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
|
||||
self.collect_by_name(["Crossroads - Tower Entrances", "Orange Tower Fourth Floor - Hot Crusts Door"])
|
||||
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
|
||||
class TestSimpleDoorsDisabledSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "simple",
|
||||
"sunwarp_access": "disabled"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name("Second Room - Exit Door")
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
|
||||
self.collect_by_name(["Hub Room - Crossroads Entrance", "Crossroads - Tower Entrancse",
|
||||
"Orange Tower Fourth Floor - Hot Crusts Door"])
|
||||
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
|
||||
class TestSimpleDoorsUnlockSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "simple",
|
||||
"sunwarp_access": "unlock"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name("Second Room - Exit Door")
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name(["Crossroads - Tower Entrances", "Orange Tower Fourth Floor - Hot Crusts Door"])
|
||||
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
self.collect_by_name("Sunwarps")
|
||||
self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
|
||||
class TestComplexDoorsNormalSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"sunwarp_access": "normal"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name("Second Room - Exit Door")
|
||||
self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
|
||||
self.collect_by_name(["Crossroads - Tower Entrance", "Orange Tower Fourth Floor - Hot Crusts Door"])
|
||||
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
|
||||
class TestComplexDoorsDisabledSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"sunwarp_access": "disabled"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name("Second Room - Exit Door")
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
|
||||
self.collect_by_name(["Hub Room - Crossroads Entrance", "Crossroads - Tower Entrance",
|
||||
"Orange Tower Fourth Floor - Hot Crusts Door"])
|
||||
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
|
||||
class TestComplexDoorsIndividualSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"sunwarp_access": "individual"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name("Second Room - Exit Door")
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name("1 Sunwarp")
|
||||
self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name(["Crossroads - Tower Entrance", "Orange Tower Fourth Floor - Hot Crusts Door"])
|
||||
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
self.collect_by_name("2 Sunwarp")
|
||||
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
self.collect_by_name("3 Sunwarp")
|
||||
self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
|
||||
class TestComplexDoorsProgressiveSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"sunwarp_access": "progressive"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name("Second Room - Exit Door")
|
||||
self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
progressive_pilgrimage = self.get_items_by_name("Progressive Pilgrimage")
|
||||
self.collect(progressive_pilgrimage[0])
|
||||
self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player))
|
||||
|
||||
self.collect_by_name(["Crossroads - Tower Entrance", "Orange Tower Fourth Floor - Hot Crusts Door"])
|
||||
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
self.collect(progressive_pilgrimage[1])
|
||||
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
|
||||
self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
self.collect(progressive_pilgrimage[2])
|
||||
self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player))
|
||||
|
||||
|
||||
class TestUnlockSunwarpPilgrimage(LingoTestBase):
|
||||
options = {
|
||||
"sunwarp_access": "unlock",
|
||||
"shuffle_colors": "false",
|
||||
"enable_pilgrimage": "true"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
|
||||
self.collect_by_name("Sunwarps")
|
||||
|
||||
self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
|
||||
|
||||
class TestIndividualSunwarpPilgrimage(LingoTestBase):
|
||||
options = {
|
||||
"sunwarp_access": "individual",
|
||||
"shuffle_colors": "false",
|
||||
"enable_pilgrimage": "true"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
for i in range(1, 7):
|
||||
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
self.collect_by_name(f"{i} Sunwarp")
|
||||
|
||||
self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
|
||||
|
||||
class TestProgressiveSunwarpPilgrimage(LingoTestBase):
|
||||
options = {
|
||||
"sunwarp_access": "progressive",
|
||||
"shuffle_colors": "false",
|
||||
"enable_pilgrimage": "true"
|
||||
}
|
||||
|
||||
def test_access(self):
|
||||
for item in self.get_items_by_name("Progressive Pilgrimage"):
|
||||
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
||||
self.collect(item)
|
||||
|
||||
self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Dict, List, Set
|
||||
from typing import Dict, List, Set, Optional
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -6,7 +6,8 @@ import sys
|
|||
sys.path.append(os.path.join("worlds", "lingo"))
|
||||
sys.path.append(".")
|
||||
sys.path.append("..")
|
||||
from datatypes import Door, Painting, Panel, Progression, Room, RoomAndDoor, RoomAndPanel, RoomEntrance
|
||||
from datatypes import Door, DoorType, EntranceType, Painting, Panel, Progression, Room, RoomAndDoor, RoomAndPanel,\
|
||||
RoomEntrance
|
||||
|
||||
import hashlib
|
||||
import pickle
|
||||
|
@ -28,6 +29,9 @@ PAINTING_EXITS: int = 0
|
|||
REQUIRED_PAINTING_ROOMS: List[str] = []
|
||||
REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS: List[str] = []
|
||||
|
||||
SUNWARP_ENTRANCES: List[str] = ["", "", "", "", "", ""]
|
||||
SUNWARP_EXITS: List[str] = ["", "", "", "", "", ""]
|
||||
|
||||
SPECIAL_ITEM_IDS: Dict[str, int] = {}
|
||||
PANEL_LOCATION_IDS: Dict[str, Dict[str, int]] = {}
|
||||
DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {}
|
||||
|
@ -96,41 +100,51 @@ def load_static_data(ll1_path, ids_path):
|
|||
PAINTING_EXITS = len(PAINTING_EXIT_ROOMS)
|
||||
|
||||
|
||||
def process_entrance(source_room, doors, room_obj):
|
||||
def process_single_entrance(source_room: str, room_name: str, door_obj) -> RoomEntrance:
|
||||
global PAINTING_ENTRANCES, PAINTING_EXIT_ROOMS
|
||||
|
||||
entrance_type = EntranceType.NORMAL
|
||||
if "painting" in door_obj and door_obj["painting"]:
|
||||
entrance_type = EntranceType.PAINTING
|
||||
elif "sunwarp" in door_obj and door_obj["sunwarp"]:
|
||||
entrance_type = EntranceType.SUNWARP
|
||||
elif "warp" in door_obj and door_obj["warp"]:
|
||||
entrance_type = EntranceType.WARP
|
||||
elif source_room == "Crossroads" and room_name == "Roof":
|
||||
entrance_type = EntranceType.CROSSROADS_ROOF_ACCESS
|
||||
|
||||
if "painting" in door_obj and door_obj["painting"]:
|
||||
PAINTING_EXIT_ROOMS.add(room_name)
|
||||
PAINTING_ENTRANCES += 1
|
||||
|
||||
if "door" in door_obj:
|
||||
return RoomEntrance(source_room, RoomAndDoor(
|
||||
door_obj["room"] if "room" in door_obj else None,
|
||||
door_obj["door"]
|
||||
), entrance_type)
|
||||
else:
|
||||
return RoomEntrance(source_room, None, entrance_type)
|
||||
|
||||
|
||||
def process_entrance(source_room, doors, room_obj):
|
||||
# If the value of an entrance is just True, that means that the entrance is always accessible.
|
||||
if doors is True:
|
||||
room_obj.entrances.append(RoomEntrance(source_room, None, False))
|
||||
room_obj.entrances.append(RoomEntrance(source_room, None, EntranceType.NORMAL))
|
||||
elif isinstance(doors, dict):
|
||||
# If the value of an entrance is a dictionary, that means the entrance requires a door to be accessible, is a
|
||||
# painting-based entrance, or both.
|
||||
if "painting" in doors and "door" not in doors:
|
||||
PAINTING_EXIT_ROOMS.add(room_obj.name)
|
||||
PAINTING_ENTRANCES += 1
|
||||
|
||||
room_obj.entrances.append(RoomEntrance(source_room, None, True))
|
||||
else:
|
||||
if "painting" in doors and doors["painting"]:
|
||||
PAINTING_EXIT_ROOMS.add(room_obj.name)
|
||||
PAINTING_ENTRANCES += 1
|
||||
|
||||
room_obj.entrances.append(RoomEntrance(source_room, RoomAndDoor(
|
||||
doors["room"] if "room" in doors else None,
|
||||
doors["door"]
|
||||
), doors["painting"] if "painting" in doors else False))
|
||||
room_obj.entrances.append(process_single_entrance(source_room, room_obj.name, doors))
|
||||
else:
|
||||
# If the value of an entrance is a list, then there are multiple possible doors that can give access to the
|
||||
# entrance.
|
||||
# entrance. If there are multiple connections with the same door (or lack of door) that differ only by entrance
|
||||
# type, coalesce them into one entrance.
|
||||
entrances: Dict[Optional[RoomAndDoor], EntranceType] = {}
|
||||
for door in doors:
|
||||
if "painting" in door and door["painting"]:
|
||||
PAINTING_EXIT_ROOMS.add(room_obj.name)
|
||||
PAINTING_ENTRANCES += 1
|
||||
entrance = process_single_entrance(source_room, room_obj.name, door)
|
||||
entrances[entrance.door] = entrances.get(entrance.door, EntranceType(0)) | entrance.type
|
||||
|
||||
room_obj.entrances.append(RoomEntrance(source_room, RoomAndDoor(
|
||||
door["room"] if "room" in door else None,
|
||||
door["door"]
|
||||
), door["painting"] if "painting" in door else False))
|
||||
for door, entrance_type in entrances.items():
|
||||
room_obj.entrances.append(RoomEntrance(source_room, door, entrance_type))
|
||||
|
||||
|
||||
def process_panel(room_name, panel_name, panel_data):
|
||||
|
@ -250,11 +264,6 @@ def process_door(room_name, door_name, door_data):
|
|||
else:
|
||||
include_reduce = False
|
||||
|
||||
if "junk_item" in door_data:
|
||||
junk_item = door_data["junk_item"]
|
||||
else:
|
||||
junk_item = False
|
||||
|
||||
if "door_group" in door_data:
|
||||
door_group = door_data["door_group"]
|
||||
else:
|
||||
|
@ -276,7 +285,7 @@ def process_door(room_name, door_name, door_data):
|
|||
panels.append(RoomAndPanel(None, panel))
|
||||
else:
|
||||
skip_location = True
|
||||
panels = None
|
||||
panels = []
|
||||
|
||||
# The location name associated with a door can be explicitly specified in the configuration. If it is not, then the
|
||||
# name is generated using a combination of all of the panels that would ordinarily open the door. This can get quite
|
||||
|
@ -312,8 +321,14 @@ def process_door(room_name, door_name, door_data):
|
|||
else:
|
||||
painting_ids = []
|
||||
|
||||
door_type = DoorType.NORMAL
|
||||
if door_name.endswith(" Sunwarp"):
|
||||
door_type = DoorType.SUNWARP
|
||||
elif room_name == "Pilgrim Antechamber" and door_name == "Sun Painting":
|
||||
door_type = DoorType.SUN_PAINTING
|
||||
|
||||
door_obj = Door(door_name, item_name, location_name, panels, skip_location, skip_item, has_doors,
|
||||
painting_ids, event, door_group, include_reduce, junk_item, item_group)
|
||||
painting_ids, event, door_group, include_reduce, door_type, item_group)
|
||||
|
||||
DOORS_BY_ROOM[room_name][door_name] = door_obj
|
||||
|
||||
|
@ -377,6 +392,15 @@ def process_painting(room_name, painting_data):
|
|||
PAINTINGS[painting_id] = painting_obj
|
||||
|
||||
|
||||
def process_sunwarp(room_name, sunwarp_data):
|
||||
global SUNWARP_ENTRANCES, SUNWARP_EXITS
|
||||
|
||||
if sunwarp_data["direction"] == "enter":
|
||||
SUNWARP_ENTRANCES[sunwarp_data["dots"] - 1] = room_name
|
||||
else:
|
||||
SUNWARP_EXITS[sunwarp_data["dots"] - 1] = room_name
|
||||
|
||||
|
||||
def process_progression(room_name, progression_name, progression_doors):
|
||||
global PROGRESSIVE_ITEMS, PROGRESSION_BY_ROOM
|
||||
|
||||
|
@ -422,6 +446,10 @@ def process_room(room_name, room_data):
|
|||
for painting_data in room_data["paintings"]:
|
||||
process_painting(room_name, painting_data)
|
||||
|
||||
if "sunwarps" in room_data:
|
||||
for sunwarp_data in room_data["sunwarps"]:
|
||||
process_sunwarp(room_name, sunwarp_data)
|
||||
|
||||
if "progression" in room_data:
|
||||
for progression_name, progression_doors in room_data["progression"].items():
|
||||
process_progression(room_name, progression_name, progression_doors)
|
||||
|
@ -468,6 +496,8 @@ if __name__ == '__main__':
|
|||
"PAINTING_EXITS": PAINTING_EXITS,
|
||||
"REQUIRED_PAINTING_ROOMS": REQUIRED_PAINTING_ROOMS,
|
||||
"REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS": REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS,
|
||||
"SUNWARP_ENTRANCES": SUNWARP_ENTRANCES,
|
||||
"SUNWARP_EXITS": SUNWARP_EXITS,
|
||||
"SPECIAL_ITEM_IDS": SPECIAL_ITEM_IDS,
|
||||
"PANEL_LOCATION_IDS": PANEL_LOCATION_IDS,
|
||||
"DOOR_LOCATION_IDS": DOOR_LOCATION_IDS,
|
||||
|
|
|
@ -37,12 +37,14 @@ configured_panels = Set[]
|
|||
mentioned_rooms = Set[]
|
||||
mentioned_doors = Set[]
|
||||
mentioned_panels = Set[]
|
||||
mentioned_sunwarp_entrances = Set[]
|
||||
mentioned_sunwarp_exits = Set[]
|
||||
|
||||
door_groups = {}
|
||||
|
||||
directives = Set["entrances", "panels", "doors", "paintings", "progression"]
|
||||
directives = Set["entrances", "panels", "doors", "paintings", "sunwarps", "progression"]
|
||||
panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting", "hunt"]
|
||||
door_directives = Set["id", "painting_id", "panels", "item_name", "item_group", "location_name", "skip_location", "skip_item", "door_group", "include_reduce", "junk_item", "event"]
|
||||
door_directives = Set["id", "painting_id", "panels", "item_name", "item_group", "location_name", "skip_location", "skip_item", "door_group", "include_reduce", "event", "warp_id"]
|
||||
painting_directives = Set["id", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move", "req_blocked", "req_blocked_when_no_doors"]
|
||||
|
||||
non_counting = 0
|
||||
|
@ -67,17 +69,17 @@ config.each do |room_name, room|
|
|||
|
||||
entrances = []
|
||||
if entrance.kind_of? Hash
|
||||
if entrance.keys() != ["painting"] then
|
||||
entrances = [entrance]
|
||||
end
|
||||
entrances = [entrance]
|
||||
elsif entrance.kind_of? Array
|
||||
entrances = entrance
|
||||
end
|
||||
|
||||
entrances.each do |e|
|
||||
entrance_room = e.include?("room") ? e["room"] : room_name
|
||||
mentioned_rooms.add(entrance_room)
|
||||
mentioned_doors.add(entrance_room + " - " + e["door"])
|
||||
if e.include?("door") then
|
||||
entrance_room = e.include?("room") ? e["room"] : room_name
|
||||
mentioned_rooms.add(entrance_room)
|
||||
mentioned_doors.add(entrance_room + " - " + e["door"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -204,8 +206,8 @@ config.each do |room_name, room|
|
|||
end
|
||||
end
|
||||
|
||||
if not door.include?("id") and not door.include?("painting_id") and not door["skip_item"] and not door["event"] then
|
||||
puts "#{room_name} - #{door_name} :::: Should be marked skip_item or event if there are no doors or paintings"
|
||||
if not door.include?("id") and not door.include?("painting_id") and not door.include?("warp_id") and not door["skip_item"] and not door["event"] then
|
||||
puts "#{room_name} - #{door_name} :::: Should be marked skip_item or event if there are no doors, paintings, or warps"
|
||||
end
|
||||
|
||||
if door.include?("panels")
|
||||
|
@ -292,6 +294,32 @@ config.each do |room_name, room|
|
|||
end
|
||||
end
|
||||
|
||||
(room["sunwarps"] || []).each do |sunwarp|
|
||||
if sunwarp.include? "dots" and sunwarp.include? "direction" then
|
||||
if sunwarp["dots"] < 1 or sunwarp["dots"] > 6 then
|
||||
puts "#{room_name} :::: Contains a sunwarp with an invalid dots value"
|
||||
end
|
||||
|
||||
if sunwarp["direction"] == "enter" then
|
||||
if mentioned_sunwarp_entrances.include? sunwarp["dots"] then
|
||||
puts "Multiple #{sunwarp["dots"]} sunwarp entrances were found"
|
||||
else
|
||||
mentioned_sunwarp_entrances.add(sunwarp["dots"])
|
||||
end
|
||||
elsif sunwarp["direction"] == "exit" then
|
||||
if mentioned_sunwarp_exits.include? sunwarp["dots"] then
|
||||
puts "Multiple #{sunwarp["dots"]} sunwarp exits were found"
|
||||
else
|
||||
mentioned_sunwarp_exits.add(sunwarp["dots"])
|
||||
end
|
||||
else
|
||||
puts "#{room_name} :::: Contains a sunwarp with an invalid direction value"
|
||||
end
|
||||
else
|
||||
puts "#{room_name} :::: Contains a sunwarp without a dots and direction"
|
||||
end
|
||||
end
|
||||
|
||||
(room["progression"] || {}).each do |progression_name, door_list|
|
||||
door_list.each do |door|
|
||||
if door.kind_of? Hash then
|
||||
|
|
Loading…
Reference in New Issue