Lingo: Add panels mode door shuffle (#3163)

* Created panels mode door shuffle

* Added some panel door item names

* Remove RUNT TURN panel door

Not really useful.

* Fix logic with First SIX related stuff

* Add group_doors to slot data

* Fix LEVEL 2 behavior with panels mode

* Fixed unit tests

* Fixed duplicate IDs from merge

* Just regenerated new IDs

* Fixed duplication of color and door group items

* Removed unnecessary unit test option

* Fix The Seeker being achievable without entrance door

* Fix The Observant being achievable without locked panels

* Added some more panel doors

* Added Progressive Suits Area

* Lingo: Fix Basement access with THE MASTER

* Added indirect conditions for MASTER-blocked entrances

* Fixed Incomparable achievement access

* Fix STAIRS panel logic

* Fix merge error with good items

* Is this clearer?

* DREAD and TURN LEARN

* Allow a weird edge case for reduced locations

Panels mode door shuffle + grouped doors + color shuffle + pilgrimage enabled is exactly the right number of items for reduced locations. Removing color shuffle also allows for disabling pilgrimage, adding sunwarp locking, or both, with a couple of locations left over.

* Prevent small sphere one on panels mode

* Added shuffle_doors aliases for old options

* Fixed a unit test

* Updated datafile

* Tweaked requirements for reduced locations

* Added player name to OptionError messages

* Update generated.dat
This commit is contained in:
Star Rauchenberger 2024-07-26 04:53:11 -04:00 committed by GitHub
parent d030a698a6
commit cc22161644
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1274 additions and 150 deletions

View File

@ -170,7 +170,8 @@ class LingoWorld(World):
slot_options = [ slot_options = [
"death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels", "death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels",
"enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks", "enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks",
"early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps" "early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps",
"group_doors"
] ]
slot_data = { slot_data = {

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1478,3 +1478,145 @@ progression:
Progressive Art Gallery: 444563 Progressive Art Gallery: 444563
Progressive Colorful: 444580 Progressive Colorful: 444580
Progressive Pilgrimage: 444583 Progressive Pilgrimage: 444583
Progressive Suits Area: 444602
Progressive Symmetry Room: 444608
Progressive Number Hunt: 444654
panel_doors:
Starting Room:
HIDDEN: 444589
Hidden Room:
OPEN: 444590
Hub Room:
ORDER: 444591
SLAUGHTER: 444592
TRACE: 444594
RAT: 444595
OPEN: 444596
Crossroads:
DECAY: 444597
NOPE: 444598
WE ROT: 444599
WORDS SWORD: 444600
BEND HI: 444601
Lost Area:
LOST: 444603
Amen Name Area:
AMEN NAME: 444604
The Tenacious:
Black Palindromes: 444605
Near Far Area:
NEAR FAR: 444606
Warts Straw Area:
WARTS STRAW: 444609
Leaf Feel Area:
LEAF FEEL: 444610
Outside The Agreeable:
MASSACRED: 444611
BLACK: 444612
CLOSE: 444613
RIGHT: 444614
Compass Room:
Lookout: 444615
Hedge Maze:
DOWN: 444617
The Perceptive:
GAZE: 444618
The Observant:
BACKSIDE: 444619
STAIRS: 444621
The Incomparable:
Giant Sevens: 444622
Orange Tower:
Access: 444623
Orange Tower First Floor:
SECRET: 444624
Orange Tower Fourth Floor:
HOT CRUSTS: 444625
Orange Tower Fifth Floor:
SIZE: 444626
First Second Third Fourth:
FIRST SECOND THIRD FOURTH: 444627
The Colorful (White):
BEGIN: 444628
The Colorful (Black):
FOUND: 444630
The Colorful (Red):
LOAF: 444631
The Colorful (Yellow):
CREAM: 444632
The Colorful (Blue):
SUN: 444633
The Colorful (Purple):
SPOON: 444634
The Colorful (Orange):
LETTERS: 444635
The Colorful (Green):
WALLS: 444636
The Colorful (Brown):
IRON: 444637
The Colorful (Gray):
OBSTACLE: 444638
Owl Hallway:
STRAYS: 444639
Outside The Initiated:
UNCOVER: 444640
OXEN: 444641
Outside The Bold:
UNOPEN: 444642
BEGIN: 444643
Outside The Undeterred:
ZERO: 444644
PEN: 444645
TWO: 444646
THREE: 444647
FOUR: 444648
Number Hunt:
FIVE: 444649
SIX: 444650
SEVEN: 444651
EIGHT: 444652
NINE: 444653
Color Hunt:
EXIT: 444655
RED: 444656
BLUE: 444658
YELLOW: 444659
ORANGE: 444660
PURPLE: 444661
GREEN: 444662
The Bearer:
FARTHER: 444663
MIDDLE: 444664
Knight Night (Final):
TRUSTED: 444665
Outside The Wondrous:
SHRINK: 444666
Hallway Room (1):
CASTLE: 444667
Hallway Room (2):
COUNTERCLOCKWISE: 444669
Hallway Room (3):
TRANSFORMATION: 444670
Hallway Room (4):
WHEELBARROW: 444671
Outside The Wanderer:
WANDERLUST: 444672
Art Gallery:
ORDER: 444673
Room Room:
STAIRS: 444674
Colors: 444676
Outside The Wise:
KITTEN CAT: 444677
Outside The Scientific:
OPEN: 444678
Directional Gallery:
TURN LEARN: 444679
panel_groups:
Tenacious Entrance Panels: 444593
Symmetry Room Panels: 444607
Backside Entrance Panels: 444620
Colorful Panels: 444629
Color Hunt Panels: 444657
Hallway Room Panels: 444668
Room Room Panels: 444675

View File

@ -12,6 +12,11 @@ class RoomAndPanel(NamedTuple):
panel: str panel: str
class RoomAndPanelDoor(NamedTuple):
room: Optional[str]
panel_door: str
class EntranceType(Flag): class EntranceType(Flag):
NORMAL = auto() NORMAL = auto()
PAINTING = auto() PAINTING = auto()
@ -63,9 +68,15 @@ class Panel(NamedTuple):
exclude_reduce: bool exclude_reduce: bool
achievement: bool achievement: bool
non_counting: bool non_counting: bool
panel_door: Optional[RoomAndPanelDoor] # This will always be fully specified.
location_name: Optional[str] location_name: Optional[str]
class PanelDoor(NamedTuple):
item_name: str
panel_group: Optional[str]
class Painting(NamedTuple): class Painting(NamedTuple):
id: str id: str
room: str room: str

View File

@ -3,7 +3,7 @@ from typing import Dict, List, NamedTuple, Set
from BaseClasses import Item, ItemClassification from BaseClasses import Item, ItemClassification
from .static_logic import DOORS_BY_ROOM, PROGRESSIVE_ITEMS, get_door_group_item_id, get_door_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 get_progressive_item_id, get_special_item_id, PANEL_DOORS_BY_ROOM, get_panel_door_item_id, get_panel_group_item_id
class ItemType(Enum): class ItemType(Enum):
@ -65,6 +65,21 @@ def load_item_data():
ItemClassification.progression, ItemType.NORMAL, True, []) ItemClassification.progression, ItemType.NORMAL, True, [])
ITEMS_BY_GROUP.setdefault("Doors", []).append(group) ITEMS_BY_GROUP.setdefault("Doors", []).append(group)
panel_groups: Set[str] = set()
for room_name, panel_doors in PANEL_DOORS_BY_ROOM.items():
for panel_door_name, panel_door in panel_doors.items():
if panel_door.panel_group is not None:
panel_groups.add(panel_door.panel_group)
ALL_ITEM_TABLE[panel_door.item_name] = ItemData(get_panel_door_item_id(room_name, panel_door_name),
ItemClassification.progression, ItemType.NORMAL, False, [])
ITEMS_BY_GROUP.setdefault("Panels", []).append(panel_door.item_name)
for group in panel_groups:
ALL_ITEM_TABLE[group] = ItemData(get_panel_group_item_id(group), ItemClassification.progression,
ItemType.NORMAL, False, [])
ITEMS_BY_GROUP.setdefault("Panels", []).append(group)
special_items: Dict[str, ItemClassification] = { special_items: Dict[str, ItemClassification] = {
":)": ItemClassification.filler, ":)": ItemClassification.filler,
"The Feeling of Being Lost": ItemClassification.filler, "The Feeling of Being Lost": ItemClassification.filler,

View File

@ -8,21 +8,31 @@ from .items import TRAP_ITEMS
class ShuffleDoors(Choice): class ShuffleDoors(Choice):
"""If on, opening doors will require their respective "keys". """This option specifies how doors open.
- **Simple:** Doors are sorted into logical groups, which are all opened by - **None:** Doors in the game will open the way they do in vanilla.
receiving an item. - **Panels:** Doors still open as in vanilla, but the panels that open the
- **Complex:** The items are much more granular, and will usually only open doors will be locked, and an item will be required to unlock the panels.
a single door each. - **Doors:** the doors themselves are locked behind items, and will open
automatically without needing to solve a panel once the key is obtained.
""" """
display_name = "Shuffle Doors" display_name = "Shuffle Doors"
option_none = 0 option_none = 0
option_simple = 1 option_panels = 1
option_complex = 2 option_doors = 2
alias_simple = 2
alias_complex = 2
class GroupDoors(Toggle):
"""By default, door shuffle in either panels or doors mode will create individual keys for every panel or door to be locked.
When group doors is on, some panels and doors are sorted into logical groups, which are opened together by receiving an item."""
display_name = "Group Doors"
class ProgressiveOrangeTower(DefaultOnToggle): class ProgressiveOrangeTower(DefaultOnToggle):
"""When "Shuffle Doors" is on, this setting governs the manner in which the Orange Tower floors open up. """When "Shuffle Doors" is on doors mode, this setting governs the manner in which the Orange Tower floors open up.
- **Off:** There is an item for each floor of the tower, and each floor's - **Off:** There is an item for each floor of the tower, and each floor's
item is the only one needed to access that floor. item is the only one needed to access that floor.
@ -33,7 +43,7 @@ class ProgressiveOrangeTower(DefaultOnToggle):
class ProgressiveColorful(DefaultOnToggle): class ProgressiveColorful(DefaultOnToggle):
"""When "Shuffle Doors" is on "complex", this setting governs the manner in which The Colorful opens up. """When "Shuffle Doors" is on either panels or doors mode and "Group Doors" is off, this setting governs the manner in which The Colorful opens up.
- **Off:** There is an item for each room of The Colorful, meaning that - **Off:** There is an item for each room of The Colorful, meaning that
random rooms in the middle of the sequence can open up without giving you random rooms in the middle of the sequence can open up without giving you
@ -253,6 +263,7 @@ lingo_option_groups = [
@dataclass @dataclass
class LingoOptions(PerGameCommonOptions): class LingoOptions(PerGameCommonOptions):
shuffle_doors: ShuffleDoors shuffle_doors: ShuffleDoors
group_doors: GroupDoors
progressive_orange_tower: ProgressiveOrangeTower progressive_orange_tower: ProgressiveOrangeTower
progressive_colorful: ProgressiveColorful progressive_colorful: ProgressiveColorful
location_checks: LocationChecks location_checks: LocationChecks

View File

@ -7,8 +7,8 @@ from .items import ALL_ITEM_TABLE, ItemType
from .locations import ALL_LOCATION_TABLE, LocationClassification from .locations import ALL_LOCATION_TABLE, LocationClassification
from .options import LocationChecks, ShuffleDoors, SunwarpAccess, VictoryCondition from .options import LocationChecks, ShuffleDoors, SunwarpAccess, VictoryCondition
from .static_logic import DOORS_BY_ROOM, PAINTINGS, PAINTING_ENTRANCES, PAINTING_EXITS, \ 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, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, PROGRESSIVE_DOORS_BY_ROOM, \
SUNWARP_ENTRANCES, SUNWARP_EXITS PANEL_DOORS_BY_ROOM, PROGRESSIVE_PANELS_BY_ROOM, SUNWARP_ENTRANCES, SUNWARP_EXITS
if TYPE_CHECKING: if TYPE_CHECKING:
from . import LingoWorld from . import LingoWorld
@ -18,6 +18,8 @@ class AccessRequirements:
rooms: Set[str] rooms: Set[str]
doors: Set[RoomAndDoor] doors: Set[RoomAndDoor]
colors: Set[str] colors: Set[str]
items: Set[str]
progression: Dict[str, int]
the_master: bool the_master: bool
postgame: bool postgame: bool
@ -25,6 +27,8 @@ class AccessRequirements:
self.rooms = set() self.rooms = set()
self.doors = set() self.doors = set()
self.colors = set() self.colors = set()
self.items = set()
self.progression = dict()
self.the_master = False self.the_master = False
self.postgame = False self.postgame = False
@ -32,12 +36,17 @@ class AccessRequirements:
self.rooms |= other.rooms self.rooms |= other.rooms
self.doors |= other.doors self.doors |= other.doors
self.colors |= other.colors self.colors |= other.colors
self.items |= other.items
self.the_master |= other.the_master self.the_master |= other.the_master
self.postgame |= other.postgame self.postgame |= other.postgame
for progression, index in other.progression.items():
if progression not in self.progression or index > self.progression[progression]:
self.progression[progression] = index
def __str__(self): def __str__(self):
return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors}," \ return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors}, items={self.items}," \
f" the_master={self.the_master}, postgame={self.postgame})" f" progression={self.progression}), the_master={self.the_master}, postgame={self.postgame}"
class PlayerLocation(NamedTuple): class PlayerLocation(NamedTuple):
@ -117,15 +126,15 @@ class LingoPlayerLogic:
self.item_by_door.setdefault(room, {})[door] = item self.item_by_door.setdefault(room, {})[door] = item
def handle_non_grouped_door(self, room_name: str, door_data: Door, world: "LingoWorld"): def handle_non_grouped_door(self, room_name: str, door_data: Door, world: "LingoWorld"):
if room_name in PROGRESSION_BY_ROOM and door_data.name in PROGRESSION_BY_ROOM[room_name]: if room_name in PROGRESSIVE_DOORS_BY_ROOM and door_data.name in PROGRESSIVE_DOORS_BY_ROOM[room_name]:
progression_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name progression_name = PROGRESSIVE_DOORS_BY_ROOM[room_name][door_data.name].item_name
progression_handling = should_split_progression(progression_name, world) progression_handling = should_split_progression(progression_name, world)
if progression_handling == ProgressiveItemBehavior.SPLIT: if progression_handling == ProgressiveItemBehavior.SPLIT:
self.set_door_item(room_name, door_data.name, door_data.item_name) self.set_door_item(room_name, door_data.name, door_data.item_name)
self.real_items.append(door_data.item_name) self.real_items.append(door_data.item_name)
elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE: elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE:
progressive_item_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name progressive_item_name = PROGRESSIVE_DOORS_BY_ROOM[room_name][door_data.name].item_name
self.set_door_item(room_name, door_data.name, progressive_item_name) self.set_door_item(room_name, door_data.name, progressive_item_name)
self.real_items.append(progressive_item_name) self.real_items.append(progressive_item_name)
else: else:
@ -156,17 +165,31 @@ class LingoPlayerLogic:
victory_condition = world.options.victory_condition victory_condition = world.options.victory_condition
early_color_hallways = world.options.early_color_hallways early_color_hallways = world.options.early_color_hallways
if location_checks == LocationChecks.option_reduced and door_shuffle != ShuffleDoors.option_none: if location_checks == LocationChecks.option_reduced:
raise OptionError("You cannot have reduced location checks when door shuffle is on, because there would not" if door_shuffle == ShuffleDoors.option_doors:
" be enough locations for all of the door items.") raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks when door shuffle"
f" is on, because there would not be enough locations for all of the door items.")
if door_shuffle == ShuffleDoors.option_panels:
if not world.options.group_doors:
raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks when ungrouped"
f" panels mode door shuffle is on, because there would not be enough locations for"
f" all of the panel items.")
if color_shuffle:
raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks with both"
f" panels mode door shuffle and color shuffle because there would not be enough"
f" locations for all of the items.")
if world.options.sunwarp_access >= SunwarpAccess.option_individual:
raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks with both"
f" panels mode door shuffle and individual or progressive sunwarp access because"
f" there would not be enough locations for all of the items.")
# Create door items, where needed. # Create door items, where needed.
door_groups: Set[str] = set() door_groups: Set[str] = set()
for room_name, room_data in DOORS_BY_ROOM.items(): for room_name, room_data in DOORS_BY_ROOM.items():
for door_name, door_data in room_data.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.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.type == DoorType.NORMAL and door_shuffle == ShuffleDoors.option_doors:
if door_data.door_group is not None and door_shuffle == ShuffleDoors.option_simple: if door_data.door_group is not None and world.options.group_doors:
# Grouped doors are handled differently if shuffle doors is on simple. # Grouped doors are handled differently if shuffle doors is on simple.
self.set_door_item(room_name, door_name, door_data.door_group) self.set_door_item(room_name, door_name, door_data.door_group)
door_groups.add(door_data.door_group) door_groups.add(door_data.door_group)
@ -189,6 +212,28 @@ class LingoPlayerLogic:
self.real_items += door_groups self.real_items += door_groups
# Create panel items, where needed.
if world.options.shuffle_doors == ShuffleDoors.option_panels:
panel_groups: Set[str] = set()
for room_name, room_data in PANEL_DOORS_BY_ROOM.items():
for panel_door_name, panel_door_data in room_data.items():
if panel_door_data.panel_group is not None and world.options.group_doors:
panel_groups.add(panel_door_data.panel_group)
elif room_name in PROGRESSIVE_PANELS_BY_ROOM \
and panel_door_name in PROGRESSIVE_PANELS_BY_ROOM[room_name]:
progression_obj = PROGRESSIVE_PANELS_BY_ROOM[room_name][panel_door_name]
progression_handling = should_split_progression(progression_obj.item_name, world)
if progression_handling == ProgressiveItemBehavior.SPLIT:
self.real_items.append(panel_door_data.item_name)
elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE:
self.real_items.append(progression_obj.item_name)
else:
self.real_items.append(panel_door_data.item_name)
self.real_items += panel_groups
# Create color items, if needed. # Create color items, if needed.
if color_shuffle: if color_shuffle:
self.real_items += [name for name, item in ALL_ITEM_TABLE.items() if item.type == ItemType.COLOR] self.real_items += [name for name, item in ALL_ITEM_TABLE.items() if item.type == ItemType.COLOR]
@ -244,7 +289,7 @@ class LingoPlayerLogic:
elif location_checks == LocationChecks.option_insanity: elif location_checks == LocationChecks.option_insanity:
location_classification = LocationClassification.insanity location_classification = LocationClassification.insanity
if door_shuffle != ShuffleDoors.option_none and not early_color_hallways: if door_shuffle == ShuffleDoors.option_doors and not early_color_hallways:
location_classification |= LocationClassification.small_sphere_one location_classification |= LocationClassification.small_sphere_one
for location_name, location_data in ALL_LOCATION_TABLE.items(): for location_name, location_data in ALL_LOCATION_TABLE.items():
@ -286,7 +331,7 @@ class LingoPlayerLogic:
"iterations. This is very unlikely to happen on its own, and probably indicates some " "iterations. This is very unlikely to happen on its own, and probably indicates some "
"kind of logic error.") "kind of logic error.")
if door_shuffle != ShuffleDoors.option_none and location_checks != LocationChecks.option_insanity \ if door_shuffle == ShuffleDoors.option_doors and location_checks != LocationChecks.option_insanity \
and not early_color_hallways and world.multiworld.players > 1: and not early_color_hallways and world.multiworld.players > 1:
# Under the combination of door shuffle, normal location checks, and no early color hallways, sphere 1 is # Under the combination of door shuffle, normal location checks, and no early color hallways, sphere 1 is
# only three checks. In a multiplayer situation, this can be frustrating for the player because they are # only three checks. In a multiplayer situation, this can be frustrating for the player because they are
@ -301,19 +346,19 @@ class LingoPlayerLogic:
# Starting Room - Exit Door gives access to OPEN and TRACE. # Starting Room - Exit Door gives access to OPEN and TRACE.
good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"] good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"]
if not color_shuffle and not world.options.enable_pilgrimage: if not color_shuffle:
if not world.options.enable_pilgrimage:
# HOT CRUST and THIS. # HOT CRUST and THIS.
good_item_options.append("Pilgrim Room - Sun Painting") good_item_options.append("Pilgrim Room - Sun Painting")
if not color_shuffle: if world.options.group_doors:
if door_shuffle == ShuffleDoors.option_simple:
# WELCOME BACK, CLOCKWISE, and DRAWL + RUNS. # WELCOME BACK, CLOCKWISE, and DRAWL + RUNS.
good_item_options.append("Welcome Back Doors") good_item_options.append("Welcome Back Doors")
else: else:
# WELCOME BACK and CLOCKWISE. # WELCOME BACK and CLOCKWISE.
good_item_options.append("Welcome Back Area - Shortcut to Starting Room") good_item_options.append("Welcome Back Area - Shortcut to Starting Room")
if door_shuffle == ShuffleDoors.option_simple: if world.options.group_doors:
# Color hallways access (NOTE: reconsider when sunwarp shuffling exists). # Color hallways access (NOTE: reconsider when sunwarp shuffling exists).
good_item_options.append("Rhyme Room Doors") good_item_options.append("Rhyme Room Doors")
@ -359,13 +404,11 @@ class LingoPlayerLogic:
def randomize_paintings(self, world: "LingoWorld") -> bool: def randomize_paintings(self, world: "LingoWorld") -> bool:
self.painting_mapping.clear() self.painting_mapping.clear()
door_shuffle = world.options.shuffle_doors
# First, assign mappings to the required-exit paintings. We ensure that req-blocked paintings do not lead to # First, assign mappings to the required-exit paintings. We ensure that req-blocked paintings do not lead to
# required paintings. # required paintings.
req_exits = [] req_exits = []
required_painting_rooms = REQUIRED_PAINTING_ROOMS required_painting_rooms = REQUIRED_PAINTING_ROOMS
if door_shuffle == ShuffleDoors.option_none: if world.options.shuffle_doors != ShuffleDoors.option_doors:
required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS
req_exits = [painting_id for painting_id, painting in PAINTINGS.items() if painting.required_when_no_doors] req_exits = [painting_id for painting_id, painting in PAINTINGS.items() if painting.required_when_no_doors]
@ -432,7 +475,7 @@ class LingoPlayerLogic:
for painting_id, painting in PAINTINGS.items(): for painting_id, painting in PAINTINGS.items():
if painting_id not in self.painting_mapping.values() \ if painting_id not in self.painting_mapping.values() \
and (painting.required or (painting.required_when_no_doors and and (painting.required or (painting.required_when_no_doors and
door_shuffle == ShuffleDoors.option_none)): world.options.shuffle_doors != ShuffleDoors.option_doors)):
return False return False
return True return True
@ -447,12 +490,31 @@ class LingoPlayerLogic:
access_reqs = AccessRequirements() access_reqs = AccessRequirements()
panel_object = PANELS_BY_ROOM[room][panel] panel_object = PANELS_BY_ROOM[room][panel]
if world.options.shuffle_doors == ShuffleDoors.option_panels and panel_object.panel_door is not None:
panel_door_room = panel_object.panel_door.room
panel_door_name = panel_object.panel_door.panel_door
panel_door = PANEL_DOORS_BY_ROOM[panel_door_room][panel_door_name]
if panel_door.panel_group is not None and world.options.group_doors:
access_reqs.items.add(panel_door.panel_group)
elif panel_door_room in PROGRESSIVE_PANELS_BY_ROOM\
and panel_door_name in PROGRESSIVE_PANELS_BY_ROOM[panel_door_room]:
progression_obj = PROGRESSIVE_PANELS_BY_ROOM[panel_door_room][panel_door_name]
progression_handling = should_split_progression(progression_obj.item_name, world)
if progression_handling == ProgressiveItemBehavior.SPLIT:
access_reqs.items.add(panel_door.item_name)
elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE:
access_reqs.progression[progression_obj.item_name] = progression_obj.index
else:
access_reqs.items.add(panel_door.item_name)
for req_room in panel_object.required_rooms: for req_room in panel_object.required_rooms:
access_reqs.rooms.add(req_room) access_reqs.rooms.add(req_room)
for req_door in panel_object.required_doors: for req_door in panel_object.required_doors:
door_object = DOORS_BY_ROOM[room if req_door.room is None else req_door.room][req_door.door] door_object = DOORS_BY_ROOM[room if req_door.room is None else req_door.room][req_door.door]
if door_object.event or world.options.shuffle_doors == ShuffleDoors.option_none: if door_object.event or world.options.shuffle_doors != ShuffleDoors.option_doors:
sub_access_reqs = self.calculate_door_requirements( sub_access_reqs = self.calculate_door_requirements(
room if req_door.room is None else req_door.room, req_door.door, world) room if req_door.room is None else req_door.room, req_door.door, world)
access_reqs.merge(sub_access_reqs) access_reqs.merge(sub_access_reqs)
@ -522,11 +584,14 @@ class LingoPlayerLogic:
continue continue
# We won't coalesce any panels that have requirements beyond colors. To simplify things for now, we will # We won't coalesce any panels that have requirements beyond colors. To simplify things for now, we will
# only coalesce single-color panels. Chains/stacks/combo puzzles will be separate. THE MASTER has # only coalesce single-color panels. Chains/stacks/combo puzzles will be separate. Panel door locked
# special access rules and is handled separately. # puzzles will be separate if panels mode is on. THE MASTER has special access rules and is handled
# separately.
if len(panel_data.required_panels) > 0 or len(panel_data.required_doors) > 0\ if len(panel_data.required_panels) > 0 or len(panel_data.required_doors) > 0\
or len(panel_data.required_rooms) > 0\ or len(panel_data.required_rooms) > 0\
or (world.options.shuffle_colors and len(panel_data.colors) > 1)\ or (world.options.shuffle_colors and len(panel_data.colors) > 1)\
or (world.options.shuffle_doors == ShuffleDoors.option_panels
and panel_data.panel_door is not None)\
or panel_name == "THE MASTER": or panel_name == "THE MASTER":
self.counting_panel_reqs.setdefault(room_name, []).append( self.counting_panel_reqs.setdefault(room_name, []).append(
(self.calculate_panel_requirements(room_name, panel_name, world), 1)) (self.calculate_panel_requirements(room_name, panel_name, world), 1))

View File

@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
from BaseClasses import CollectionState from BaseClasses import CollectionState
from .datatypes import RoomAndDoor from .datatypes import RoomAndDoor
from .player_logic import AccessRequirements, PlayerLocation from .player_logic import AccessRequirements, PlayerLocation
from .static_logic import PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS from .static_logic import PROGRESSIVE_DOORS_BY_ROOM, PROGRESSIVE_ITEMS
if TYPE_CHECKING: if TYPE_CHECKING:
from . import LingoWorld from . import LingoWorld
@ -59,6 +59,12 @@ def _lingo_can_satisfy_requirements(state: CollectionState, access: AccessRequir
if not state.has(color.capitalize(), world.player): if not state.has(color.capitalize(), world.player):
return False return False
if not all(state.has(item, world.player) for item in access.items):
return False
if not all(state.has(item, world.player, index) for item, index in access.progression.items()):
return False
if access.the_master and not lingo_can_use_mastery_location(state, world): if access.the_master and not lingo_can_use_mastery_location(state, world):
return False return False
@ -77,7 +83,7 @@ def _lingo_can_open_door(state: CollectionState, room: str, door: str, world: "L
item_name = world.player_logic.item_by_door[room][door] item_name = world.player_logic.item_by_door[room][door]
if item_name in PROGRESSIVE_ITEMS: if item_name in PROGRESSIVE_ITEMS:
progression = PROGRESSION_BY_ROOM[room][door] progression = PROGRESSIVE_DOORS_BY_ROOM[room][door]
return state.has(item_name, world.player, progression.index) return state.has(item_name, world.player, progression.index)
return state.has(item_name, world.player) return state.has(item_name, world.player)

View File

@ -4,15 +4,17 @@ import pickle
from io import BytesIO from io import BytesIO
from typing import Dict, List, Set from typing import Dict, List, Set
from .datatypes import Door, Painting, Panel, Progression, Room from .datatypes import Door, Painting, Panel, PanelDoor, Progression, Room
ALL_ROOMS: List[Room] = [] ALL_ROOMS: List[Room] = []
DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {} DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {}
PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {} PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {}
PANEL_DOORS_BY_ROOM: Dict[str, Dict[str, PanelDoor]] = {}
PAINTINGS: Dict[str, Painting] = {} PAINTINGS: Dict[str, Painting] = {}
PROGRESSIVE_ITEMS: List[str] = [] PROGRESSIVE_ITEMS: Set[str] = set()
PROGRESSION_BY_ROOM: Dict[str, Dict[str, Progression]] = {} PROGRESSIVE_DOORS_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
PROGRESSIVE_PANELS_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
PAINTING_ENTRANCES: int = 0 PAINTING_ENTRANCES: int = 0
PAINTING_EXIT_ROOMS: Set[str] = set() PAINTING_EXIT_ROOMS: Set[str] = set()
@ -28,6 +30,8 @@ PANEL_LOCATION_IDS: Dict[str, Dict[str, int]] = {}
DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {} DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {}
DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {} DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {}
DOOR_GROUP_ITEM_IDS: Dict[str, int] = {} DOOR_GROUP_ITEM_IDS: Dict[str, int] = {}
PANEL_DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {}
PANEL_GROUP_ITEM_IDS: Dict[str, int] = {}
PROGRESSIVE_ITEM_IDS: Dict[str, int] = {} PROGRESSIVE_ITEM_IDS: Dict[str, int] = {}
HASHES: Dict[str, str] = {} HASHES: Dict[str, str] = {}
@ -68,6 +72,20 @@ def get_door_group_item_id(name: str):
return DOOR_GROUP_ITEM_IDS[name] return DOOR_GROUP_ITEM_IDS[name]
def get_panel_door_item_id(room: str, name: str):
if room not in PANEL_DOOR_ITEM_IDS or name not in PANEL_DOOR_ITEM_IDS[room]:
raise Exception(f"Item ID for panel door {room} - {name} not found in ids.yaml.")
return PANEL_DOOR_ITEM_IDS[room][name]
def get_panel_group_item_id(name: str):
if name not in PANEL_GROUP_ITEM_IDS:
raise Exception(f"Item ID for panel group {name} not found in ids.yaml.")
return PANEL_GROUP_ITEM_IDS[name]
def get_progressive_item_id(name: str): def get_progressive_item_id(name: str):
if name not in PROGRESSIVE_ITEM_IDS: if name not in PROGRESSIVE_ITEM_IDS:
raise Exception(f"Item ID for progressive item {name} not found in ids.yaml.") raise Exception(f"Item ID for progressive item {name} not found in ids.yaml.")
@ -97,8 +115,10 @@ def load_static_data_from_file():
ALL_ROOMS.extend(pickdata["ALL_ROOMS"]) ALL_ROOMS.extend(pickdata["ALL_ROOMS"])
DOORS_BY_ROOM.update(pickdata["DOORS_BY_ROOM"]) DOORS_BY_ROOM.update(pickdata["DOORS_BY_ROOM"])
PANELS_BY_ROOM.update(pickdata["PANELS_BY_ROOM"]) PANELS_BY_ROOM.update(pickdata["PANELS_BY_ROOM"])
PROGRESSIVE_ITEMS.extend(pickdata["PROGRESSIVE_ITEMS"]) PANEL_DOORS_BY_ROOM.update(pickdata["PANEL_DOORS_BY_ROOM"])
PROGRESSION_BY_ROOM.update(pickdata["PROGRESSION_BY_ROOM"]) PROGRESSIVE_ITEMS.update(pickdata["PROGRESSIVE_ITEMS"])
PROGRESSIVE_DOORS_BY_ROOM.update(pickdata["PROGRESSIVE_DOORS_BY_ROOM"])
PROGRESSIVE_PANELS_BY_ROOM.update(pickdata["PROGRESSIVE_PANELS_BY_ROOM"])
PAINTING_ENTRANCES = pickdata["PAINTING_ENTRANCES"] PAINTING_ENTRANCES = pickdata["PAINTING_ENTRANCES"]
PAINTING_EXIT_ROOMS.update(pickdata["PAINTING_EXIT_ROOMS"]) PAINTING_EXIT_ROOMS.update(pickdata["PAINTING_EXIT_ROOMS"])
PAINTING_EXITS = pickdata["PAINTING_EXITS"] PAINTING_EXITS = pickdata["PAINTING_EXITS"]
@ -111,6 +131,8 @@ def load_static_data_from_file():
DOOR_LOCATION_IDS.update(pickdata["DOOR_LOCATION_IDS"]) DOOR_LOCATION_IDS.update(pickdata["DOOR_LOCATION_IDS"])
DOOR_ITEM_IDS.update(pickdata["DOOR_ITEM_IDS"]) DOOR_ITEM_IDS.update(pickdata["DOOR_ITEM_IDS"])
DOOR_GROUP_ITEM_IDS.update(pickdata["DOOR_GROUP_ITEM_IDS"]) DOOR_GROUP_ITEM_IDS.update(pickdata["DOOR_GROUP_ITEM_IDS"])
PANEL_DOOR_ITEM_IDS.update(pickdata["PANEL_DOOR_ITEM_IDS"])
PANEL_GROUP_ITEM_IDS.update(pickdata["PANEL_GROUP_ITEM_IDS"])
PROGRESSIVE_ITEM_IDS.update(pickdata["PROGRESSIVE_ITEM_IDS"]) PROGRESSIVE_ITEM_IDS.update(pickdata["PROGRESSIVE_ITEM_IDS"])

View File

@ -3,7 +3,7 @@ from . import LingoTestBase
class TestRequiredRoomLogic(LingoTestBase): class TestRequiredRoomLogic(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"shuffle_colors": "false", "shuffle_colors": "false",
} }
@ -50,7 +50,7 @@ class TestRequiredRoomLogic(LingoTestBase):
class TestRequiredDoorLogic(LingoTestBase): class TestRequiredDoorLogic(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"shuffle_colors": "false", "shuffle_colors": "false",
} }
@ -78,7 +78,8 @@ class TestRequiredDoorLogic(LingoTestBase):
class TestSimpleDoors(LingoTestBase): class TestSimpleDoors(LingoTestBase):
options = { options = {
"shuffle_doors": "simple", "shuffle_doors": "doors",
"group_doors": "true",
"shuffle_colors": "false", "shuffle_colors": "false",
} }
@ -90,3 +91,52 @@ class TestSimpleDoors(LingoTestBase):
self.assertTrue(self.multiworld.state.can_reach("Outside The Wanderer", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Outside The Wanderer", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
class TestPanels(LingoTestBase):
options = {
"shuffle_doors": "panels"
}
def test_requirement(self):
self.assertFalse(self.can_reach_location("Starting Room - HIDDEN"))
self.assertFalse(self.can_reach_location("Hidden Room - OPEN"))
self.assertFalse(self.can_reach_location("The Seeker - Achievement"))
self.collect_by_name("Starting Room - HIDDEN (Panel)")
self.assertTrue(self.can_reach_location("Starting Room - HIDDEN"))
self.assertFalse(self.can_reach_location("Hidden Room - OPEN"))
self.assertFalse(self.can_reach_location("The Seeker - Achievement"))
self.collect_by_name("Hidden Room - OPEN (Panel)")
self.assertTrue(self.can_reach_location("Starting Room - HIDDEN"))
self.assertTrue(self.can_reach_location("Hidden Room - OPEN"))
self.assertTrue(self.can_reach_location("The Seeker - Achievement"))
class TestGroupedPanels(LingoTestBase):
options = {
"shuffle_doors": "panels",
"group_doors": "true",
"shuffle_colors": "false",
}
def test_requirement(self):
self.assertFalse(self.can_reach_location("Hub Room - SLAUGHTER"))
self.assertFalse(self.can_reach_location("Dread Hallway - DREAD"))
self.assertFalse(self.can_reach_location("The Tenacious - Achievement"))
self.collect_by_name("Tenacious Entrance Panels")
self.assertTrue(self.can_reach_location("Hub Room - SLAUGHTER"))
self.assertFalse(self.can_reach_location("Dread Hallway - DREAD"))
self.assertFalse(self.can_reach_location("The Tenacious - Achievement"))
self.collect_by_name("Outside The Agreeable - BLACK (Panel)")
self.assertTrue(self.can_reach_location("Hub Room - SLAUGHTER"))
self.assertTrue(self.can_reach_location("Dread Hallway - DREAD"))
self.assertFalse(self.can_reach_location("The Tenacious - Achievement"))
self.collect_by_name("The Tenacious - Black Palindromes (Panels)")
self.assertTrue(self.can_reach_location("Hub Room - SLAUGHTER"))
self.assertTrue(self.can_reach_location("Dread Hallway - DREAD"))
self.assertTrue(self.can_reach_location("The Tenacious - Achievement"))

View File

@ -3,7 +3,7 @@ from . import LingoTestBase
class TestMultiShuffleOptions(LingoTestBase): class TestMultiShuffleOptions(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"progressive_orange_tower": "true", "progressive_orange_tower": "true",
"shuffle_colors": "true", "shuffle_colors": "true",
"shuffle_paintings": "true", "shuffle_paintings": "true",
@ -13,7 +13,7 @@ class TestMultiShuffleOptions(LingoTestBase):
class TestPanelsanity(LingoTestBase): class TestPanelsanity(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"progressive_orange_tower": "true", "progressive_orange_tower": "true",
"location_checks": "insanity", "location_checks": "insanity",
"shuffle_colors": "true" "shuffle_colors": "true"
@ -22,7 +22,18 @@ class TestPanelsanity(LingoTestBase):
class TestAllPanelHunt(LingoTestBase): class TestAllPanelHunt(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"progressive_orange_tower": "true",
"shuffle_colors": "true",
"victory_condition": "level_2",
"level_2_requirement": "800",
"early_color_hallways": "true"
}
class TestAllPanelHuntPanelsMode(LingoTestBase):
options = {
"shuffle_doors": "panels",
"progressive_orange_tower": "true", "progressive_orange_tower": "true",
"shuffle_colors": "true", "shuffle_colors": "true",
"victory_condition": "level_2", "victory_condition": "level_2",

View File

@ -3,7 +3,7 @@ from . import LingoTestBase
class TestProgressiveOrangeTower(LingoTestBase): class TestProgressiveOrangeTower(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"progressive_orange_tower": "true" "progressive_orange_tower": "true"
} }

View File

@ -3,7 +3,7 @@ from . import LingoTestBase
class TestPanelHunt(LingoTestBase): class TestPanelHunt(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"location_checks": "insanity", "location_checks": "insanity",
"victory_condition": "level_2", "victory_condition": "level_2",
"level_2_requirement": "15" "level_2_requirement": "15"

View File

@ -18,7 +18,7 @@ class TestPilgrimageWithRoofAndPaintings(LingoTestBase):
options = { options = {
"enable_pilgrimage": "true", "enable_pilgrimage": "true",
"shuffle_colors": "false", "shuffle_colors": "false",
"shuffle_doors": "complex", "shuffle_doors": "doors",
"pilgrimage_allows_roof_access": "true", "pilgrimage_allows_roof_access": "true",
"pilgrimage_allows_paintings": "true", "pilgrimage_allows_paintings": "true",
"early_color_hallways": "false" "early_color_hallways": "false"
@ -39,7 +39,7 @@ class TestPilgrimageNoRoofYesPaintings(LingoTestBase):
options = { options = {
"enable_pilgrimage": "true", "enable_pilgrimage": "true",
"shuffle_colors": "false", "shuffle_colors": "false",
"shuffle_doors": "complex", "shuffle_doors": "doors",
"pilgrimage_allows_roof_access": "false", "pilgrimage_allows_roof_access": "false",
"pilgrimage_allows_paintings": "true", "pilgrimage_allows_paintings": "true",
"early_color_hallways": "false" "early_color_hallways": "false"
@ -62,7 +62,7 @@ class TestPilgrimageNoRoofNoPaintings(LingoTestBase):
options = { options = {
"enable_pilgrimage": "true", "enable_pilgrimage": "true",
"shuffle_colors": "false", "shuffle_colors": "false",
"shuffle_doors": "complex", "shuffle_doors": "doors",
"pilgrimage_allows_roof_access": "false", "pilgrimage_allows_roof_access": "false",
"pilgrimage_allows_paintings": "false", "pilgrimage_allows_paintings": "false",
"early_color_hallways": "false" "early_color_hallways": "false"
@ -117,7 +117,7 @@ class TestPilgrimageYesRoofNoPaintings(LingoTestBase):
options = { options = {
"enable_pilgrimage": "true", "enable_pilgrimage": "true",
"shuffle_colors": "false", "shuffle_colors": "false",
"shuffle_doors": "complex", "shuffle_doors": "doors",
"pilgrimage_allows_roof_access": "true", "pilgrimage_allows_roof_access": "true",
"pilgrimage_allows_paintings": "false", "pilgrimage_allows_paintings": "false",
"early_color_hallways": "false" "early_color_hallways": "false"

View File

@ -3,7 +3,7 @@ from . import LingoTestBase
class TestComplexProgressiveHallwayRoom(LingoTestBase): class TestComplexProgressiveHallwayRoom(LingoTestBase):
options = { options = {
"shuffle_doors": "complex" "shuffle_doors": "doors"
} }
def test_item(self): def test_item(self):
@ -54,7 +54,8 @@ class TestComplexProgressiveHallwayRoom(LingoTestBase):
class TestSimpleHallwayRoom(LingoTestBase): class TestSimpleHallwayRoom(LingoTestBase):
options = { options = {
"shuffle_doors": "simple" "shuffle_doors": "doors",
"group_doors": "true",
} }
def test_item(self): def test_item(self):
@ -81,7 +82,7 @@ class TestSimpleHallwayRoom(LingoTestBase):
class TestProgressiveArtGallery(LingoTestBase): class TestProgressiveArtGallery(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"shuffle_colors": "false", "shuffle_colors": "false",
} }

View File

@ -19,7 +19,8 @@ class TestVanillaDoorsNormalSunwarps(LingoTestBase):
class TestSimpleDoorsNormalSunwarps(LingoTestBase): class TestSimpleDoorsNormalSunwarps(LingoTestBase):
options = { options = {
"shuffle_doors": "simple", "shuffle_doors": "doors",
"group_doors": "true",
"sunwarp_access": "normal" "sunwarp_access": "normal"
} }
@ -37,7 +38,8 @@ class TestSimpleDoorsNormalSunwarps(LingoTestBase):
class TestSimpleDoorsDisabledSunwarps(LingoTestBase): class TestSimpleDoorsDisabledSunwarps(LingoTestBase):
options = { options = {
"shuffle_doors": "simple", "shuffle_doors": "doors",
"group_doors": "true",
"sunwarp_access": "disabled" "sunwarp_access": "disabled"
} }
@ -56,7 +58,8 @@ class TestSimpleDoorsDisabledSunwarps(LingoTestBase):
class TestSimpleDoorsUnlockSunwarps(LingoTestBase): class TestSimpleDoorsUnlockSunwarps(LingoTestBase):
options = { options = {
"shuffle_doors": "simple", "shuffle_doors": "doors",
"group_doors": "true",
"sunwarp_access": "unlock" "sunwarp_access": "unlock"
} }
@ -78,7 +81,8 @@ class TestSimpleDoorsUnlockSunwarps(LingoTestBase):
class TestComplexDoorsNormalSunwarps(LingoTestBase): class TestComplexDoorsNormalSunwarps(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"group_doors": "false",
"sunwarp_access": "normal" "sunwarp_access": "normal"
} }
@ -96,7 +100,8 @@ class TestComplexDoorsNormalSunwarps(LingoTestBase):
class TestComplexDoorsDisabledSunwarps(LingoTestBase): class TestComplexDoorsDisabledSunwarps(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"group_doors": "false",
"sunwarp_access": "disabled" "sunwarp_access": "disabled"
} }
@ -115,7 +120,8 @@ class TestComplexDoorsDisabledSunwarps(LingoTestBase):
class TestComplexDoorsIndividualSunwarps(LingoTestBase): class TestComplexDoorsIndividualSunwarps(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"group_doors": "false",
"sunwarp_access": "individual" "sunwarp_access": "individual"
} }
@ -142,7 +148,8 @@ class TestComplexDoorsIndividualSunwarps(LingoTestBase):
class TestComplexDoorsProgressiveSunwarps(LingoTestBase): class TestComplexDoorsProgressiveSunwarps(LingoTestBase):
options = { options = {
"shuffle_doors": "complex", "shuffle_doors": "doors",
"group_doors": "false",
"sunwarp_access": "progressive" "sunwarp_access": "progressive"
} }

View File

@ -73,6 +73,22 @@ if old_generated.include? "door_groups" then
end end
end end
end end
if old_generated.include? "panel_doors" then
old_generated["panel_doors"].each do |room, panel_doors|
panel_doors.each do |name, id|
if id >= next_item_id then
next_item_id = id + 1
end
end
end
end
if old_generated.include? "panel_groups" then
old_generated["panel_groups"].each do |name, id|
if id >= next_item_id then
next_item_id = id + 1
end
end
end
if old_generated.include? "progression" then if old_generated.include? "progression" then
old_generated["progression"].each do |name, id| old_generated["progression"].each do |name, id|
if id >= next_item_id then if id >= next_item_id then
@ -82,6 +98,7 @@ if old_generated.include? "progression" then
end end
door_groups = Set[] door_groups = Set[]
panel_groups = Set[]
config = YAML.load_file(configpath) config = YAML.load_file(configpath)
config.each do |room_name, room_data| config.each do |room_name, room_data|
@ -163,6 +180,29 @@ config.each do |room_name, room_data|
end end
end end
if room_data.include? "panel_doors"
room_data["panel_doors"].each do |panel_door_name, panel_door|
unless old_generated.include? "panel_doors" and old_generated["panel_doors"].include? room_name and old_generated["panel_doors"][room_name].include? panel_door_name then
old_generated["panel_doors"] ||= {}
old_generated["panel_doors"][room_name] ||= {}
old_generated["panel_doors"][room_name][panel_door_name] = next_item_id
next_item_id += 1
end
if panel_door.include? "panel_group" and not panel_groups.include? panel_door["panel_group"] then
panel_groups.add(panel_door["panel_group"])
unless old_generated.include? "panel_groups" and old_generated["panel_groups"].include? panel_door["panel_group"] then
old_generated["panel_groups"] ||= {}
old_generated["panel_groups"][panel_door["panel_group"]] = next_item_id
next_item_id += 1
end
end
end
end
if room_data.include? "progression" if room_data.include? "progression"
room_data["progression"].each do |progression_name, pdata| room_data["progression"].each do |progression_name, pdata|
unless old_generated.include? "progression" and old_generated["progression"].include? progression_name then unless old_generated.include? "progression" and old_generated["progression"].include? progression_name then

View File

@ -6,8 +6,8 @@ import sys
sys.path.append(os.path.join("worlds", "lingo")) sys.path.append(os.path.join("worlds", "lingo"))
sys.path.append(".") sys.path.append(".")
sys.path.append("..") sys.path.append("..")
from datatypes import Door, DoorType, EntranceType, Painting, Panel, Progression, Room, RoomAndDoor, RoomAndPanel,\ from datatypes import Door, DoorType, EntranceType, Painting, Panel, PanelDoor, Progression, Room, RoomAndDoor,\
RoomEntrance RoomAndPanel, RoomAndPanelDoor, RoomEntrance
import hashlib import hashlib
import pickle import pickle
@ -18,10 +18,12 @@ import Utils
ALL_ROOMS: List[Room] = [] ALL_ROOMS: List[Room] = []
DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {} DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {}
PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {} PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {}
PANEL_DOORS_BY_ROOM: Dict[str, Dict[str, PanelDoor]] = {}
PAINTINGS: Dict[str, Painting] = {} PAINTINGS: Dict[str, Painting] = {}
PROGRESSIVE_ITEMS: List[str] = [] PROGRESSIVE_ITEMS: Set[str] = set()
PROGRESSION_BY_ROOM: Dict[str, Dict[str, Progression]] = {} PROGRESSIVE_DOORS_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
PROGRESSIVE_PANELS_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
PAINTING_ENTRANCES: int = 0 PAINTING_ENTRANCES: int = 0
PAINTING_EXIT_ROOMS: Set[str] = set() PAINTING_EXIT_ROOMS: Set[str] = set()
@ -37,8 +39,13 @@ PANEL_LOCATION_IDS: Dict[str, Dict[str, int]] = {}
DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {} DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {}
DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {} DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {}
DOOR_GROUP_ITEM_IDS: Dict[str, int] = {} DOOR_GROUP_ITEM_IDS: Dict[str, int] = {}
PANEL_DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {}
PANEL_GROUP_ITEM_IDS: Dict[str, int] = {}
PROGRESSIVE_ITEM_IDS: Dict[str, int] = {} PROGRESSIVE_ITEM_IDS: Dict[str, int] = {}
# This doesn't need to be stored in the datafile.
PANEL_DOOR_BY_PANEL_BY_ROOM: Dict[str, Dict[str, str]] = {}
def hash_file(path): def hash_file(path):
md5 = hashlib.md5() md5 = hashlib.md5()
@ -53,7 +60,7 @@ def hash_file(path):
def load_static_data(ll1_path, ids_path): def load_static_data(ll1_path, ids_path):
global PAINTING_EXITS, SPECIAL_ITEM_IDS, PANEL_LOCATION_IDS, DOOR_LOCATION_IDS, DOOR_ITEM_IDS, \ global PAINTING_EXITS, SPECIAL_ITEM_IDS, PANEL_LOCATION_IDS, DOOR_LOCATION_IDS, DOOR_ITEM_IDS, \
DOOR_GROUP_ITEM_IDS, PROGRESSIVE_ITEM_IDS DOOR_GROUP_ITEM_IDS, PROGRESSIVE_ITEM_IDS, PANEL_DOOR_ITEM_IDS, PANEL_GROUP_ITEM_IDS
# Load in all item and location IDs. These are broken up into groups based on the type of item/location. # Load in all item and location IDs. These are broken up into groups based on the type of item/location.
with open(ids_path, "r") as file: with open(ids_path, "r") as file:
@ -86,6 +93,17 @@ def load_static_data(ll1_path, ids_path):
for item_name, item_id in config["door_groups"].items(): for item_name, item_id in config["door_groups"].items():
DOOR_GROUP_ITEM_IDS[item_name] = item_id DOOR_GROUP_ITEM_IDS[item_name] = item_id
if "panel_doors" in config:
for room_name, panel_doors in config["panel_doors"].items():
PANEL_DOOR_ITEM_IDS[room_name] = {}
for panel_door, item_id in panel_doors.items():
PANEL_DOOR_ITEM_IDS[room_name][panel_door] = item_id
if "panel_groups" in config:
for item_name, item_id in config["panel_groups"].items():
PANEL_GROUP_ITEM_IDS[item_name] = item_id
if "progression" in config: if "progression" in config:
for item_name, item_id in config["progression"].items(): for item_name, item_id in config["progression"].items():
PROGRESSIVE_ITEM_IDS[item_name] = item_id PROGRESSIVE_ITEM_IDS[item_name] = item_id
@ -147,6 +165,46 @@ def process_entrance(source_room, doors, room_obj):
room_obj.entrances.append(RoomEntrance(source_room, door, entrance_type)) room_obj.entrances.append(RoomEntrance(source_room, door, entrance_type))
def process_panel_door(room_name, panel_door_name, panel_door_data):
global PANEL_DOORS_BY_ROOM, PANEL_DOOR_BY_PANEL_BY_ROOM
panels: List[RoomAndPanel] = list()
for panel in panel_door_data["panels"]:
if isinstance(panel, dict):
panels.append(RoomAndPanel(panel["room"], panel["panel"]))
else:
panels.append(RoomAndPanel(room_name, panel))
for panel in panels:
PANEL_DOOR_BY_PANEL_BY_ROOM.setdefault(panel.room, {})[panel.panel] = RoomAndPanelDoor(room_name,
panel_door_name)
if "item_name" in panel_door_data:
item_name = panel_door_data["item_name"]
else:
panel_per_room = dict()
for panel in panels:
panel_room_name = room_name if panel.room is None else panel.room
panel_per_room.setdefault(panel_room_name, []).append(panel.panel)
room_strs = list()
for door_room_str, door_panels_str in panel_per_room.items():
room_strs.append(door_room_str + " - " + ", ".join(door_panels_str))
if len(panels) == 1:
item_name = f"{room_strs[0]} (Panel)"
else:
item_name = " and ".join(room_strs) + " (Panels)"
if "panel_group" in panel_door_data:
panel_group = panel_door_data["panel_group"]
else:
panel_group = None
panel_door_obj = PanelDoor(item_name, panel_group)
PANEL_DOORS_BY_ROOM[room_name][panel_door_name] = panel_door_obj
def process_panel(room_name, panel_name, panel_data): def process_panel(room_name, panel_name, panel_data):
global PANELS_BY_ROOM global PANELS_BY_ROOM
@ -227,13 +285,18 @@ def process_panel(room_name, panel_name, panel_data):
else: else:
non_counting = False non_counting = False
if room_name in PANEL_DOOR_BY_PANEL_BY_ROOM and panel_name in PANEL_DOOR_BY_PANEL_BY_ROOM[room_name]:
panel_door = PANEL_DOOR_BY_PANEL_BY_ROOM[room_name][panel_name]
else:
panel_door = None
if "location_name" in panel_data: if "location_name" in panel_data:
location_name = panel_data["location_name"] location_name = panel_data["location_name"]
else: else:
location_name = None location_name = None
panel_obj = Panel(required_rooms, required_doors, required_panels, colors, check, event, exclude_reduce, panel_obj = Panel(required_rooms, required_doors, required_panels, colors, check, event, exclude_reduce,
achievement, non_counting, location_name) achievement, non_counting, panel_door, location_name)
PANELS_BY_ROOM[room_name][panel_name] = panel_obj PANELS_BY_ROOM[room_name][panel_name] = panel_obj
@ -325,7 +388,7 @@ def process_door(room_name, door_name, door_data):
painting_ids = [] painting_ids = []
door_type = DoorType.NORMAL door_type = DoorType.NORMAL
if door_name.endswith(" Sunwarp"): if room_name == "Sunwarps":
door_type = DoorType.SUNWARP door_type = DoorType.SUNWARP
elif room_name == "Pilgrim Antechamber" and door_name == "Sun Painting": elif room_name == "Pilgrim Antechamber" and door_name == "Sun Painting":
door_type = DoorType.SUN_PAINTING door_type = DoorType.SUN_PAINTING
@ -404,11 +467,11 @@ def process_sunwarp(room_name, sunwarp_data):
SUNWARP_EXITS[sunwarp_data["dots"] - 1] = room_name SUNWARP_EXITS[sunwarp_data["dots"] - 1] = room_name
def process_progression(room_name, progression_name, progression_doors): def process_progressive_door(room_name, progression_name, progression_doors):
global PROGRESSIVE_ITEMS, PROGRESSION_BY_ROOM global PROGRESSIVE_ITEMS, PROGRESSIVE_DOORS_BY_ROOM
# Progressive items are configured as a list of doors. # Progressive items are configured as a list of doors.
PROGRESSIVE_ITEMS.append(progression_name) PROGRESSIVE_ITEMS.add(progression_name)
progression_index = 1 progression_index = 1
for door in progression_doors: for door in progression_doors:
@ -419,11 +482,31 @@ def process_progression(room_name, progression_name, progression_doors):
door_room = room_name door_room = room_name
door_door = door door_door = door
room_progressions = PROGRESSION_BY_ROOM.setdefault(door_room, {}) room_progressions = PROGRESSIVE_DOORS_BY_ROOM.setdefault(door_room, {})
room_progressions[door_door] = Progression(progression_name, progression_index) room_progressions[door_door] = Progression(progression_name, progression_index)
progression_index += 1 progression_index += 1
def process_progressive_panel(room_name, progression_name, progression_panel_doors):
global PROGRESSIVE_ITEMS, PROGRESSIVE_PANELS_BY_ROOM
# Progressive items are configured as a list of panel doors.
PROGRESSIVE_ITEMS.add(progression_name)
progression_index = 1
for panel_door in progression_panel_doors:
if isinstance(panel_door, Dict):
panel_door_room = panel_door["room"]
panel_door_door = panel_door["panel_door"]
else:
panel_door_room = room_name
panel_door_door = panel_door
room_progressions = PROGRESSIVE_PANELS_BY_ROOM.setdefault(panel_door_room, {})
room_progressions[panel_door_door] = Progression(progression_name, progression_index)
progression_index += 1
def process_room(room_name, room_data): def process_room(room_name, room_data):
global ALL_ROOMS global ALL_ROOMS
@ -433,6 +516,12 @@ def process_room(room_name, room_data):
for source_room, doors in room_data["entrances"].items(): for source_room, doors in room_data["entrances"].items():
process_entrance(source_room, doors, room_obj) process_entrance(source_room, doors, room_obj)
if "panel_doors" in room_data:
PANEL_DOORS_BY_ROOM[room_name] = dict()
for panel_door_name, panel_door_data in room_data["panel_doors"].items():
process_panel_door(room_name, panel_door_name, panel_door_data)
if "panels" in room_data: if "panels" in room_data:
PANELS_BY_ROOM[room_name] = dict() PANELS_BY_ROOM[room_name] = dict()
@ -454,8 +543,11 @@ def process_room(room_name, room_data):
process_sunwarp(room_name, sunwarp_data) process_sunwarp(room_name, sunwarp_data)
if "progression" in room_data: if "progression" in room_data:
for progression_name, progression_doors in room_data["progression"].items(): for progression_name, pdata in room_data["progression"].items():
process_progression(room_name, progression_name, progression_doors) if "doors" in pdata:
process_progressive_door(room_name, progression_name, pdata["doors"])
if "panel_doors" in pdata:
process_progressive_panel(room_name, progression_name, pdata["panel_doors"])
ALL_ROOMS.append(room_obj) ALL_ROOMS.append(room_obj)
@ -492,8 +584,10 @@ if __name__ == '__main__':
"ALL_ROOMS": ALL_ROOMS, "ALL_ROOMS": ALL_ROOMS,
"DOORS_BY_ROOM": DOORS_BY_ROOM, "DOORS_BY_ROOM": DOORS_BY_ROOM,
"PANELS_BY_ROOM": PANELS_BY_ROOM, "PANELS_BY_ROOM": PANELS_BY_ROOM,
"PANEL_DOORS_BY_ROOM": PANEL_DOORS_BY_ROOM,
"PROGRESSIVE_ITEMS": PROGRESSIVE_ITEMS, "PROGRESSIVE_ITEMS": PROGRESSIVE_ITEMS,
"PROGRESSION_BY_ROOM": PROGRESSION_BY_ROOM, "PROGRESSIVE_DOORS_BY_ROOM": PROGRESSIVE_DOORS_BY_ROOM,
"PROGRESSIVE_PANELS_BY_ROOM": PROGRESSIVE_PANELS_BY_ROOM,
"PAINTING_ENTRANCES": PAINTING_ENTRANCES, "PAINTING_ENTRANCES": PAINTING_ENTRANCES,
"PAINTING_EXIT_ROOMS": PAINTING_EXIT_ROOMS, "PAINTING_EXIT_ROOMS": PAINTING_EXIT_ROOMS,
"PAINTING_EXITS": PAINTING_EXITS, "PAINTING_EXITS": PAINTING_EXITS,
@ -506,6 +600,8 @@ if __name__ == '__main__':
"DOOR_LOCATION_IDS": DOOR_LOCATION_IDS, "DOOR_LOCATION_IDS": DOOR_LOCATION_IDS,
"DOOR_ITEM_IDS": DOOR_ITEM_IDS, "DOOR_ITEM_IDS": DOOR_ITEM_IDS,
"DOOR_GROUP_ITEM_IDS": DOOR_GROUP_ITEM_IDS, "DOOR_GROUP_ITEM_IDS": DOOR_GROUP_ITEM_IDS,
"PANEL_DOOR_ITEM_IDS": PANEL_DOOR_ITEM_IDS,
"PANEL_GROUP_ITEM_IDS": PANEL_GROUP_ITEM_IDS,
"PROGRESSIVE_ITEM_IDS": PROGRESSIVE_ITEM_IDS, "PROGRESSIVE_ITEM_IDS": PROGRESSIVE_ITEM_IDS,
} }

View File

@ -33,19 +33,23 @@ end
configured_rooms = Set["Menu"] configured_rooms = Set["Menu"]
configured_doors = Set[] configured_doors = Set[]
configured_panels = Set[] configured_panels = Set[]
configured_panel_doors = Set[]
mentioned_rooms = Set[] mentioned_rooms = Set[]
mentioned_doors = Set[] mentioned_doors = Set[]
mentioned_panels = Set[] mentioned_panels = Set[]
mentioned_panel_doors = Set[]
mentioned_sunwarp_entrances = Set[] mentioned_sunwarp_entrances = Set[]
mentioned_sunwarp_exits = Set[] mentioned_sunwarp_exits = Set[]
mentioned_paintings = Set[] mentioned_paintings = Set[]
door_groups = {} door_groups = {}
panel_groups = {}
directives = Set["entrances", "panels", "doors", "paintings", "sunwarps", "progression"] directives = Set["entrances", "panels", "doors", "panel_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", "location_name"] panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting", "hunt", "location_name"]
door_directives = Set["id", "painting_id", "panels", "item_name", "item_group", "location_name", "skip_location", "skip_item", "door_group", "include_reduce", "event", "warp_id"] door_directives = Set["id", "painting_id", "panels", "item_name", "item_group", "location_name", "skip_location", "skip_item", "door_group", "include_reduce", "event", "warp_id"]
panel_door_directives = Set["panels", "item_name", "panel_group"]
painting_directives = Set["id", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move", "req_blocked", "req_blocked_when_no_doors"] 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 non_counting = 0
@ -253,6 +257,43 @@ config.each do |room_name, room|
end end
end end
(room["panel_doors"] || {}).each do |panel_door_name, panel_door|
configured_panel_doors.add("#{room_name} - #{panel_door_name}")
if panel_door.include?("panels")
panel_door["panels"].each do |panel|
if panel.kind_of? Hash then
other_room = panel.include?("room") ? panel["room"] : room_name
mentioned_panels.add("#{other_room} - #{panel["panel"]}")
else
other_room = panel.include?("room") ? panel["room"] : room_name
mentioned_panels.add("#{room_name} - #{panel}")
end
end
else
puts "#{room_name} - #{panel_door_name} :::: Missing panels field"
end
if panel_door.include?("panel_group")
panel_groups[panel_door["panel_group"]] ||= 0
panel_groups[panel_door["panel_group"]] += 1
end
bad_subdirectives = []
panel_door.keys.each do |key|
unless panel_door_directives.include?(key) then
bad_subdirectives << key
end
end
unless bad_subdirectives.empty? then
puts "#{room_name} - #{panel_door_name} :::: Panel door has the following invalid subdirectives: #{bad_subdirectives.join(", ")}"
end
unless ids.include?("panel_doors") and ids["panel_doors"].include?(room_name) and ids["panel_doors"][room_name].include?(panel_door_name)
puts "#{room_name} - #{panel_door_name} :::: Panel door is missing an item ID"
end
end
(room["paintings"] || []).each do |painting| (room["paintings"] || []).each do |painting|
if painting.include?("id") and painting["id"].kind_of? String then if painting.include?("id") and painting["id"].kind_of? String then
unless paintings.include? painting["id"] then unless paintings.include? painting["id"] then
@ -327,14 +368,26 @@ config.each do |room_name, room|
end end
end end
(room["progression"] || {}).each do |progression_name, door_list| (room["progression"] || {}).each do |progression_name, pdata|
door_list.each do |door| if pdata.include? "doors" then
pdata["doors"].each do |door|
if door.kind_of? Hash then if door.kind_of? Hash then
mentioned_doors.add("#{door["room"]} - #{door["door"]}") mentioned_doors.add("#{door["room"]} - #{door["door"]}")
else else
mentioned_doors.add("#{room_name} - #{door}") mentioned_doors.add("#{room_name} - #{door}")
end end
end end
end
if pdata.include? "panel_doors" then
pdata["panel_doors"].each do |panel_door|
if panel_door.kind_of? Hash then
mentioned_panel_doors.add("#{panel_door["room"]} - #{panel_door["panel_door"]}")
else
mentioned_panel_doors.add("#{room_name} - #{panel_door}")
end
end
end
unless ids.include?("progression") and ids["progression"].include?(progression_name) unless ids.include?("progression") and ids["progression"].include?(progression_name)
puts "#{room_name} - #{progression_name} :::: Progression is missing an item ID" puts "#{room_name} - #{progression_name} :::: Progression is missing an item ID"
@ -344,17 +397,22 @@ end
errored_rooms = mentioned_rooms - configured_rooms errored_rooms = mentioned_rooms - configured_rooms
unless errored_rooms.empty? then unless errored_rooms.empty? then
puts "The folloring rooms are mentioned but do not exist: " + errored_rooms.to_s puts "The following rooms are mentioned but do not exist: " + errored_rooms.to_s
end end
errored_panels = mentioned_panels - configured_panels errored_panels = mentioned_panels - configured_panels
unless errored_panels.empty? then unless errored_panels.empty? then
puts "The folloring panels are mentioned but do not exist: " + errored_panels.to_s puts "The following panels are mentioned but do not exist: " + errored_panels.to_s
end end
errored_doors = mentioned_doors - configured_doors errored_doors = mentioned_doors - configured_doors
unless errored_doors.empty? then unless errored_doors.empty? then
puts "The folloring doors are mentioned but do not exist: " + errored_doors.to_s puts "The following doors are mentioned but do not exist: " + errored_doors.to_s
end
errored_panel_doors = mentioned_panel_doors - configured_panel_doors
unless errored_panel_doors.empty? then
puts "The following panel doors are mentioned but do not exist: " + errored_panel_doors.to_s
end end
door_groups.each do |group,num| door_groups.each do |group,num|
@ -367,6 +425,16 @@ door_groups.each do |group,num|
end end
end end
panel_groups.each do |group,num|
if num == 1 then
puts "Panel group \"#{group}\" only has one panel in it"
end
unless ids.include?("panel_groups") and ids["panel_groups"].include?(group)
puts "#{group} :::: Panel group is missing an item ID"
end
end
slashed_rooms = configured_rooms.select do |room| slashed_rooms = configured_rooms.select do |room|
room.include? "/" room.include? "/"
end end