forked from mirror/Archipelago
Merge branch 'ArchipelagoMW:main' into logic_bug_fixes
This commit is contained in:
13
Main.py
13
Main.py
@@ -124,14 +124,19 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||
for player in multiworld.player_ids:
|
||||
exclusion_rules(multiworld, player, multiworld.worlds[player].options.exclude_locations.value)
|
||||
multiworld.worlds[player].options.priority_locations.value -= multiworld.worlds[player].options.exclude_locations.value
|
||||
world_excluded_locations = set()
|
||||
for location_name in multiworld.worlds[player].options.priority_locations.value:
|
||||
try:
|
||||
location = multiworld.get_location(location_name, player)
|
||||
except KeyError as e: # failed to find the given location. Check if it's a legitimate location
|
||||
if location_name not in multiworld.worlds[player].location_name_to_id:
|
||||
raise Exception(f"Unable to prioritize location {location_name} in player {player}'s world.") from e
|
||||
else:
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
if location.progress_type != LocationProgressType.EXCLUDED:
|
||||
location.progress_type = LocationProgressType.PRIORITY
|
||||
else:
|
||||
logger.warning(f"Unable to prioritize location \"{location_name}\" in player {player}'s world because the world excluded it.")
|
||||
world_excluded_locations.add(location_name)
|
||||
multiworld.worlds[player].options.priority_locations.value -= world_excluded_locations
|
||||
|
||||
# Set local and non-local item rules.
|
||||
if multiworld.players > 1:
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
# APWorld Dev FAQ
|
||||
|
||||
This document is meant as a reference tool to show solutions to common problems when developing an apworld.
|
||||
It is not intended to answer every question about Archipelago and it assumes you have read the other docs,
|
||||
including [Contributing](contributing.md), [Adding Games](<adding games.md>), and [World API](<world api.md>).
|
||||
|
||||
---
|
||||
|
||||
### My game has a restrictive start that leads to fill errors
|
||||
|
||||
Hint to the Generator that an item needs to be in sphere one with local_early_items
|
||||
Hint to the Generator that an item needs to be in sphere one with local_early_items. Here, `1` represents the number of "Sword" items to attempt to place in sphere one.
|
||||
```py
|
||||
early_item_name = "Sword"
|
||||
self.multiworld.local_early_items[self.player][early_item_name] = 1
|
||||
```
|
||||
|
||||
Some alternative ways to try to fix this problem are:
|
||||
* Add more locations to sphere one of your world, potentially only when there would be a restrictive start
|
||||
* Pre-place items yourself, such as during `create_items`
|
||||
* Put items into the player's starting inventory using `push_precollected`
|
||||
* Raise an exception, such as an `OptionError` during `generate_early`, to disallow options that would lead to a restrictive start
|
||||
|
||||
---
|
||||
|
||||
### I have multiple settings that change the item/location pool counts and need to balance them out
|
||||
@@ -25,8 +33,13 @@ Note: to use self.create_filler(), self.get_filler_item_name() should be defined
|
||||
total_locations = len(self.multiworld.get_unfilled_locations(self.player))
|
||||
item_pool = self.create_non_filler_items()
|
||||
|
||||
while len(item_pool) < total_locations:
|
||||
for _ in range(total_locations - len(item_pool)):
|
||||
item_pool.append(self.create_filler())
|
||||
|
||||
self.multiworld.itempool += item_pool
|
||||
```
|
||||
|
||||
A faster alternative to the `for` loop would be to use a [list comprehension](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions):
|
||||
```py
|
||||
item_pool += [self.create_filler() for _ in range(total_locations - len(item_pool))]
|
||||
```
|
||||
|
||||
@@ -9,7 +9,7 @@ from worlds.AutoWorld import WebWorld, World
|
||||
from .datatypes import Room, RoomEntrance
|
||||
from .items import ALL_ITEM_TABLE, ITEMS_BY_GROUP, TRAP_ITEMS, LingoItem
|
||||
from .locations import ALL_LOCATION_TABLE, LOCATIONS_BY_GROUP
|
||||
from .options import LingoOptions, lingo_option_groups
|
||||
from .options import LingoOptions, lingo_option_groups, SunwarpAccess, VictoryCondition
|
||||
from .player_logic import LingoPlayerLogic
|
||||
from .regions import create_regions
|
||||
|
||||
@@ -54,14 +54,17 @@ class LingoWorld(World):
|
||||
player_logic: LingoPlayerLogic
|
||||
|
||||
def generate_early(self):
|
||||
if not (self.options.shuffle_doors or self.options.shuffle_colors or self.options.shuffle_sunwarps):
|
||||
if not (self.options.shuffle_doors or self.options.shuffle_colors or
|
||||
(self.options.sunwarp_access >= SunwarpAccess.option_unlock and
|
||||
self.options.victory_condition == VictoryCondition.option_pilgrimage)):
|
||||
if self.multiworld.players == 1:
|
||||
warning(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any progression"
|
||||
f" items. Please turn on Door Shuffle, Color Shuffle, or Sunwarp Shuffle if that doesn't seem"
|
||||
f" right.")
|
||||
warning(f"{self.player_name}'s Lingo world doesn't have any progression items. Please turn on Door"
|
||||
f" Shuffle or Color Shuffle, or use item-blocked sunwarps with the Pilgrimage victory condition"
|
||||
f" if that doesn't seem right.")
|
||||
else:
|
||||
raise OptionError(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any"
|
||||
f" progression items. Please turn on Door Shuffle, Color Shuffle or Sunwarp Shuffle.")
|
||||
raise OptionError(f"{self.player_name}'s Lingo world doesn't have any progression items. Please turn on"
|
||||
f" Door Shuffle or Color Shuffle, or use item-blocked sunwarps with the Pilgrimage"
|
||||
f" victory condition.")
|
||||
|
||||
self.player_logic = LingoPlayerLogic(self)
|
||||
|
||||
@@ -167,7 +170,8 @@ class LingoWorld(World):
|
||||
slot_options = [
|
||||
"death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels",
|
||||
"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 = {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1478,3 +1478,145 @@ progression:
|
||||
Progressive Art Gallery: 444563
|
||||
Progressive Colorful: 444580
|
||||
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
|
||||
|
||||
@@ -12,6 +12,11 @@ class RoomAndPanel(NamedTuple):
|
||||
panel: str
|
||||
|
||||
|
||||
class RoomAndPanelDoor(NamedTuple):
|
||||
room: Optional[str]
|
||||
panel_door: str
|
||||
|
||||
|
||||
class EntranceType(Flag):
|
||||
NORMAL = auto()
|
||||
PAINTING = auto()
|
||||
@@ -63,9 +68,15 @@ class Panel(NamedTuple):
|
||||
exclude_reduce: bool
|
||||
achievement: bool
|
||||
non_counting: bool
|
||||
panel_door: Optional[RoomAndPanelDoor] # This will always be fully specified.
|
||||
location_name: Optional[str]
|
||||
|
||||
|
||||
class PanelDoor(NamedTuple):
|
||||
item_name: str
|
||||
panel_group: Optional[str]
|
||||
|
||||
|
||||
class Painting(NamedTuple):
|
||||
id: str
|
||||
room: str
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Dict, List, NamedTuple, Set
|
||||
|
||||
from BaseClasses import Item, ItemClassification
|
||||
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):
|
||||
@@ -65,6 +65,21 @@ def load_item_data():
|
||||
ItemClassification.progression, ItemType.NORMAL, True, [])
|
||||
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] = {
|
||||
":)": ItemClassification.filler,
|
||||
"The Feeling of Being Lost": ItemClassification.filler,
|
||||
|
||||
@@ -8,21 +8,31 @@ from .items import TRAP_ITEMS
|
||||
|
||||
|
||||
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
|
||||
receiving an item.
|
||||
- **Complex:** The items are much more granular, and will usually only open
|
||||
a single door each.
|
||||
- **None:** Doors in the game will open the way they do in vanilla.
|
||||
- **Panels:** Doors still open as in vanilla, but the panels that open the
|
||||
doors will be locked, and an item will be required to unlock the panels.
|
||||
- **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"
|
||||
option_none = 0
|
||||
option_simple = 1
|
||||
option_complex = 2
|
||||
option_panels = 1
|
||||
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):
|
||||
"""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
|
||||
item is the only one needed to access that floor.
|
||||
@@ -33,7 +43,7 @@ class ProgressiveOrangeTower(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
|
||||
random rooms in the middle of the sequence can open up without giving you
|
||||
@@ -253,6 +263,7 @@ lingo_option_groups = [
|
||||
@dataclass
|
||||
class LingoOptions(PerGameCommonOptions):
|
||||
shuffle_doors: ShuffleDoors
|
||||
group_doors: GroupDoors
|
||||
progressive_orange_tower: ProgressiveOrangeTower
|
||||
progressive_colorful: ProgressiveColorful
|
||||
location_checks: LocationChecks
|
||||
|
||||
@@ -7,8 +7,8 @@ from .items import ALL_ITEM_TABLE, ItemType
|
||||
from .locations import ALL_LOCATION_TABLE, LocationClassification
|
||||
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, \
|
||||
SUNWARP_ENTRANCES, SUNWARP_EXITS
|
||||
PANELS_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, PROGRESSIVE_DOORS_BY_ROOM, \
|
||||
PANEL_DOORS_BY_ROOM, PROGRESSIVE_PANELS_BY_ROOM, SUNWARP_ENTRANCES, SUNWARP_EXITS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import LingoWorld
|
||||
@@ -18,6 +18,8 @@ class AccessRequirements:
|
||||
rooms: Set[str]
|
||||
doors: Set[RoomAndDoor]
|
||||
colors: Set[str]
|
||||
items: Set[str]
|
||||
progression: Dict[str, int]
|
||||
the_master: bool
|
||||
postgame: bool
|
||||
|
||||
@@ -25,6 +27,8 @@ class AccessRequirements:
|
||||
self.rooms = set()
|
||||
self.doors = set()
|
||||
self.colors = set()
|
||||
self.items = set()
|
||||
self.progression = dict()
|
||||
self.the_master = False
|
||||
self.postgame = False
|
||||
|
||||
@@ -32,12 +36,17 @@ class AccessRequirements:
|
||||
self.rooms |= other.rooms
|
||||
self.doors |= other.doors
|
||||
self.colors |= other.colors
|
||||
self.items |= other.items
|
||||
self.the_master |= other.the_master
|
||||
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):
|
||||
return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors}," \
|
||||
f" the_master={self.the_master}, postgame={self.postgame})"
|
||||
return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors}, items={self.items}," \
|
||||
f" progression={self.progression}), the_master={self.the_master}, postgame={self.postgame}"
|
||||
|
||||
|
||||
class PlayerLocation(NamedTuple):
|
||||
@@ -117,15 +126,15 @@ class LingoPlayerLogic:
|
||||
self.item_by_door.setdefault(room, {})[door] = item
|
||||
|
||||
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]:
|
||||
progression_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name
|
||||
if room_name in PROGRESSIVE_DOORS_BY_ROOM and door_data.name in PROGRESSIVE_DOORS_BY_ROOM[room_name]:
|
||||
progression_name = PROGRESSIVE_DOORS_BY_ROOM[room_name][door_data.name].item_name
|
||||
progression_handling = should_split_progression(progression_name, world)
|
||||
|
||||
if progression_handling == ProgressiveItemBehavior.SPLIT:
|
||||
self.set_door_item(room_name, door_data.name, door_data.item_name)
|
||||
self.real_items.append(door_data.item_name)
|
||||
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.real_items.append(progressive_item_name)
|
||||
else:
|
||||
@@ -156,17 +165,31 @@ class LingoPlayerLogic:
|
||||
victory_condition = world.options.victory_condition
|
||||
early_color_hallways = world.options.early_color_hallways
|
||||
|
||||
if location_checks == LocationChecks.option_reduced and door_shuffle != ShuffleDoors.option_none:
|
||||
raise OptionError("You cannot have reduced location checks when door shuffle is on, because there would not"
|
||||
" be enough locations for all of the door items.")
|
||||
if location_checks == LocationChecks.option_reduced:
|
||||
if door_shuffle == ShuffleDoors.option_doors:
|
||||
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.
|
||||
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:
|
||||
if door_data.type == DoorType.NORMAL and door_shuffle == ShuffleDoors.option_doors:
|
||||
if door_data.door_group is not None and world.options.group_doors:
|
||||
# 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)
|
||||
@@ -188,7 +211,29 @@ class LingoPlayerLogic:
|
||||
self.real_items.append(door_data.item_name)
|
||||
|
||||
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.
|
||||
if color_shuffle:
|
||||
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:
|
||||
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
|
||||
|
||||
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 "
|
||||
"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:
|
||||
# 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
|
||||
@@ -301,19 +346,19 @@ 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 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:
|
||||
if not world.options.enable_pilgrimage:
|
||||
# HOT CRUST and THIS.
|
||||
good_item_options.append("Pilgrim Room - Sun Painting")
|
||||
|
||||
if world.options.group_doors:
|
||||
# WELCOME BACK, CLOCKWISE, and DRAWL + RUNS.
|
||||
good_item_options.append("Welcome Back Doors")
|
||||
else:
|
||||
# WELCOME BACK and CLOCKWISE.
|
||||
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).
|
||||
good_item_options.append("Rhyme Room Doors")
|
||||
|
||||
@@ -359,13 +404,11 @@ class LingoPlayerLogic:
|
||||
def randomize_paintings(self, world: "LingoWorld") -> bool:
|
||||
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
|
||||
# required paintings.
|
||||
req_exits = []
|
||||
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
|
||||
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():
|
||||
if painting_id not in self.painting_mapping.values() \
|
||||
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 True
|
||||
@@ -447,12 +490,31 @@ class LingoPlayerLogic:
|
||||
access_reqs = AccessRequirements()
|
||||
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:
|
||||
access_reqs.rooms.add(req_room)
|
||||
|
||||
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]
|
||||
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(
|
||||
room if req_door.room is None else req_door.room, req_door.door, world)
|
||||
access_reqs.merge(sub_access_reqs)
|
||||
@@ -522,11 +584,14 @@ class LingoPlayerLogic:
|
||||
continue
|
||||
|
||||
# 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
|
||||
# special access rules and is handled separately.
|
||||
# only coalesce single-color panels. Chains/stacks/combo puzzles will be separate. Panel door locked
|
||||
# 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\
|
||||
or len(panel_data.required_rooms) > 0\
|
||||
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":
|
||||
self.counting_panel_reqs.setdefault(room_name, []).append(
|
||||
(self.calculate_panel_requirements(room_name, panel_name, world), 1))
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
|
||||
from BaseClasses import CollectionState
|
||||
from .datatypes import RoomAndDoor
|
||||
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:
|
||||
from . import LingoWorld
|
||||
@@ -59,6 +59,12 @@ def _lingo_can_satisfy_requirements(state: CollectionState, access: AccessRequir
|
||||
if not state.has(color.capitalize(), world.player):
|
||||
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):
|
||||
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]
|
||||
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)
|
||||
|
||||
@@ -4,15 +4,17 @@ import pickle
|
||||
from io import BytesIO
|
||||
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] = []
|
||||
DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {}
|
||||
PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {}
|
||||
PANEL_DOORS_BY_ROOM: Dict[str, Dict[str, PanelDoor]] = {}
|
||||
PAINTINGS: Dict[str, Painting] = {}
|
||||
|
||||
PROGRESSIVE_ITEMS: List[str] = []
|
||||
PROGRESSION_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
|
||||
PROGRESSIVE_ITEMS: Set[str] = set()
|
||||
PROGRESSIVE_DOORS_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
|
||||
PROGRESSIVE_PANELS_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
|
||||
|
||||
PAINTING_ENTRANCES: int = 0
|
||||
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_ITEM_IDS: Dict[str, 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] = {}
|
||||
|
||||
HASHES: Dict[str, str] = {}
|
||||
@@ -68,6 +72,20 @@ def get_door_group_item_id(name: str):
|
||||
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):
|
||||
if name not in PROGRESSIVE_ITEM_IDS:
|
||||
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"])
|
||||
DOORS_BY_ROOM.update(pickdata["DOORS_BY_ROOM"])
|
||||
PANELS_BY_ROOM.update(pickdata["PANELS_BY_ROOM"])
|
||||
PROGRESSIVE_ITEMS.extend(pickdata["PROGRESSIVE_ITEMS"])
|
||||
PROGRESSION_BY_ROOM.update(pickdata["PROGRESSION_BY_ROOM"])
|
||||
PANEL_DOORS_BY_ROOM.update(pickdata["PANEL_DOORS_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_EXIT_ROOMS.update(pickdata["PAINTING_EXIT_ROOMS"])
|
||||
PAINTING_EXITS = pickdata["PAINTING_EXITS"]
|
||||
@@ -111,6 +131,8 @@ def load_static_data_from_file():
|
||||
DOOR_LOCATION_IDS.update(pickdata["DOOR_LOCATION_IDS"])
|
||||
DOOR_ITEM_IDS.update(pickdata["DOOR_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"])
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from . import LingoTestBase
|
||||
|
||||
class TestRequiredRoomLogic(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"shuffle_colors": "false",
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class TestRequiredRoomLogic(LingoTestBase):
|
||||
|
||||
class TestRequiredDoorLogic(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"shuffle_colors": "false",
|
||||
}
|
||||
|
||||
@@ -78,7 +78,8 @@ class TestRequiredDoorLogic(LingoTestBase):
|
||||
|
||||
class TestSimpleDoors(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "simple",
|
||||
"shuffle_doors": "doors",
|
||||
"group_doors": "true",
|
||||
"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("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"))
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from . import LingoTestBase
|
||||
|
||||
class TestMultiShuffleOptions(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"progressive_orange_tower": "true",
|
||||
"shuffle_colors": "true",
|
||||
"shuffle_paintings": "true",
|
||||
@@ -13,7 +13,7 @@ class TestMultiShuffleOptions(LingoTestBase):
|
||||
|
||||
class TestPanelsanity(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"progressive_orange_tower": "true",
|
||||
"location_checks": "insanity",
|
||||
"shuffle_colors": "true"
|
||||
@@ -22,7 +22,18 @@ class TestPanelsanity(LingoTestBase):
|
||||
|
||||
class TestAllPanelHunt(LingoTestBase):
|
||||
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",
|
||||
"shuffle_colors": "true",
|
||||
"victory_condition": "level_2",
|
||||
|
||||
@@ -3,7 +3,7 @@ from . import LingoTestBase
|
||||
|
||||
class TestProgressiveOrangeTower(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"progressive_orange_tower": "true"
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from . import LingoTestBase
|
||||
|
||||
class TestPanelHunt(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"location_checks": "insanity",
|
||||
"victory_condition": "level_2",
|
||||
"level_2_requirement": "15"
|
||||
|
||||
@@ -18,7 +18,7 @@ class TestPilgrimageWithRoofAndPaintings(LingoTestBase):
|
||||
options = {
|
||||
"enable_pilgrimage": "true",
|
||||
"shuffle_colors": "false",
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"pilgrimage_allows_roof_access": "true",
|
||||
"pilgrimage_allows_paintings": "true",
|
||||
"early_color_hallways": "false"
|
||||
@@ -39,7 +39,7 @@ class TestPilgrimageNoRoofYesPaintings(LingoTestBase):
|
||||
options = {
|
||||
"enable_pilgrimage": "true",
|
||||
"shuffle_colors": "false",
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"pilgrimage_allows_roof_access": "false",
|
||||
"pilgrimage_allows_paintings": "true",
|
||||
"early_color_hallways": "false"
|
||||
@@ -62,7 +62,7 @@ class TestPilgrimageNoRoofNoPaintings(LingoTestBase):
|
||||
options = {
|
||||
"enable_pilgrimage": "true",
|
||||
"shuffle_colors": "false",
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"pilgrimage_allows_roof_access": "false",
|
||||
"pilgrimage_allows_paintings": "false",
|
||||
"early_color_hallways": "false"
|
||||
@@ -117,7 +117,7 @@ class TestPilgrimageYesRoofNoPaintings(LingoTestBase):
|
||||
options = {
|
||||
"enable_pilgrimage": "true",
|
||||
"shuffle_colors": "false",
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"pilgrimage_allows_roof_access": "true",
|
||||
"pilgrimage_allows_paintings": "false",
|
||||
"early_color_hallways": "false"
|
||||
|
||||
@@ -3,7 +3,7 @@ from . import LingoTestBase
|
||||
|
||||
class TestComplexProgressiveHallwayRoom(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex"
|
||||
"shuffle_doors": "doors"
|
||||
}
|
||||
|
||||
def test_item(self):
|
||||
@@ -54,7 +54,8 @@ class TestComplexProgressiveHallwayRoom(LingoTestBase):
|
||||
|
||||
class TestSimpleHallwayRoom(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "simple"
|
||||
"shuffle_doors": "doors",
|
||||
"group_doors": "true",
|
||||
}
|
||||
|
||||
def test_item(self):
|
||||
@@ -81,7 +82,7 @@ class TestSimpleHallwayRoom(LingoTestBase):
|
||||
|
||||
class TestProgressiveArtGallery(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"shuffle_colors": "false",
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ class TestVanillaDoorsNormalSunwarps(LingoTestBase):
|
||||
|
||||
class TestSimpleDoorsNormalSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "simple",
|
||||
"shuffle_doors": "doors",
|
||||
"group_doors": "true",
|
||||
"sunwarp_access": "normal"
|
||||
}
|
||||
|
||||
@@ -37,7 +38,8 @@ class TestSimpleDoorsNormalSunwarps(LingoTestBase):
|
||||
|
||||
class TestSimpleDoorsDisabledSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "simple",
|
||||
"shuffle_doors": "doors",
|
||||
"group_doors": "true",
|
||||
"sunwarp_access": "disabled"
|
||||
}
|
||||
|
||||
@@ -56,7 +58,8 @@ class TestSimpleDoorsDisabledSunwarps(LingoTestBase):
|
||||
|
||||
class TestSimpleDoorsUnlockSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "simple",
|
||||
"shuffle_doors": "doors",
|
||||
"group_doors": "true",
|
||||
"sunwarp_access": "unlock"
|
||||
}
|
||||
|
||||
@@ -78,7 +81,8 @@ class TestSimpleDoorsUnlockSunwarps(LingoTestBase):
|
||||
|
||||
class TestComplexDoorsNormalSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"group_doors": "false",
|
||||
"sunwarp_access": "normal"
|
||||
}
|
||||
|
||||
@@ -96,7 +100,8 @@ class TestComplexDoorsNormalSunwarps(LingoTestBase):
|
||||
|
||||
class TestComplexDoorsDisabledSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"group_doors": "false",
|
||||
"sunwarp_access": "disabled"
|
||||
}
|
||||
|
||||
@@ -115,7 +120,8 @@ class TestComplexDoorsDisabledSunwarps(LingoTestBase):
|
||||
|
||||
class TestComplexDoorsIndividualSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"group_doors": "false",
|
||||
"sunwarp_access": "individual"
|
||||
}
|
||||
|
||||
@@ -142,7 +148,8 @@ class TestComplexDoorsIndividualSunwarps(LingoTestBase):
|
||||
|
||||
class TestComplexDoorsProgressiveSunwarps(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"shuffle_doors": "doors",
|
||||
"group_doors": "false",
|
||||
"sunwarp_access": "progressive"
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,22 @@ if old_generated.include? "door_groups" then
|
||||
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
|
||||
old_generated["progression"].each do |name, id|
|
||||
if id >= next_item_id then
|
||||
@@ -82,6 +98,7 @@ if old_generated.include? "progression" then
|
||||
end
|
||||
|
||||
door_groups = Set[]
|
||||
panel_groups = Set[]
|
||||
|
||||
config = YAML.load_file(configpath)
|
||||
config.each do |room_name, room_data|
|
||||
@@ -163,6 +180,29 @@ config.each do |room_name, room_data|
|
||||
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"
|
||||
room_data["progression"].each do |progression_name, pdata|
|
||||
unless old_generated.include? "progression" and old_generated["progression"].include? progression_name then
|
||||
|
||||
@@ -6,8 +6,8 @@ import sys
|
||||
sys.path.append(os.path.join("worlds", "lingo"))
|
||||
sys.path.append(".")
|
||||
sys.path.append("..")
|
||||
from datatypes import Door, DoorType, EntranceType, Painting, Panel, Progression, Room, RoomAndDoor, RoomAndPanel,\
|
||||
RoomEntrance
|
||||
from datatypes import Door, DoorType, EntranceType, Painting, Panel, PanelDoor, Progression, Room, RoomAndDoor,\
|
||||
RoomAndPanel, RoomAndPanelDoor, RoomEntrance
|
||||
|
||||
import hashlib
|
||||
import pickle
|
||||
@@ -18,10 +18,12 @@ import Utils
|
||||
ALL_ROOMS: List[Room] = []
|
||||
DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {}
|
||||
PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {}
|
||||
PANEL_DOORS_BY_ROOM: Dict[str, Dict[str, PanelDoor]] = {}
|
||||
PAINTINGS: Dict[str, Painting] = {}
|
||||
|
||||
PROGRESSIVE_ITEMS: List[str] = []
|
||||
PROGRESSION_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
|
||||
PROGRESSIVE_ITEMS: Set[str] = set()
|
||||
PROGRESSIVE_DOORS_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
|
||||
PROGRESSIVE_PANELS_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
|
||||
|
||||
PAINTING_ENTRANCES: int = 0
|
||||
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_ITEM_IDS: Dict[str, 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] = {}
|
||||
|
||||
# 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):
|
||||
md5 = hashlib.md5()
|
||||
@@ -53,7 +60,7 @@ def hash_file(path):
|
||||
|
||||
def load_static_data(ll1_path, ids_path):
|
||||
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.
|
||||
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():
|
||||
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:
|
||||
for item_name, item_id in config["progression"].items():
|
||||
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))
|
||||
|
||||
|
||||
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):
|
||||
global PANELS_BY_ROOM
|
||||
|
||||
@@ -227,13 +285,18 @@ def process_panel(room_name, panel_name, panel_data):
|
||||
else:
|
||||
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:
|
||||
location_name = panel_data["location_name"]
|
||||
else:
|
||||
location_name = None
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -325,7 +388,7 @@ def process_door(room_name, door_name, door_data):
|
||||
painting_ids = []
|
||||
|
||||
door_type = DoorType.NORMAL
|
||||
if door_name.endswith(" Sunwarp"):
|
||||
if room_name == "Sunwarps":
|
||||
door_type = DoorType.SUNWARP
|
||||
elif room_name == "Pilgrim Antechamber" and door_name == "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
|
||||
|
||||
|
||||
def process_progression(room_name, progression_name, progression_doors):
|
||||
global PROGRESSIVE_ITEMS, PROGRESSION_BY_ROOM
|
||||
def process_progressive_door(room_name, progression_name, progression_doors):
|
||||
global PROGRESSIVE_ITEMS, PROGRESSIVE_DOORS_BY_ROOM
|
||||
|
||||
# Progressive items are configured as a list of doors.
|
||||
PROGRESSIVE_ITEMS.append(progression_name)
|
||||
PROGRESSIVE_ITEMS.add(progression_name)
|
||||
|
||||
progression_index = 1
|
||||
for door in progression_doors:
|
||||
@@ -419,11 +482,31 @@ def process_progression(room_name, progression_name, progression_doors):
|
||||
door_room = room_name
|
||||
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)
|
||||
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):
|
||||
global ALL_ROOMS
|
||||
|
||||
@@ -433,6 +516,12 @@ def process_room(room_name, room_data):
|
||||
for source_room, doors in room_data["entrances"].items():
|
||||
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:
|
||||
PANELS_BY_ROOM[room_name] = dict()
|
||||
|
||||
@@ -454,8 +543,11 @@ def process_room(room_name, room_data):
|
||||
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)
|
||||
for progression_name, pdata in room_data["progression"].items():
|
||||
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)
|
||||
|
||||
@@ -492,8 +584,10 @@ if __name__ == '__main__':
|
||||
"ALL_ROOMS": ALL_ROOMS,
|
||||
"DOORS_BY_ROOM": DOORS_BY_ROOM,
|
||||
"PANELS_BY_ROOM": PANELS_BY_ROOM,
|
||||
"PANEL_DOORS_BY_ROOM": PANEL_DOORS_BY_ROOM,
|
||||
"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_EXIT_ROOMS": PAINTING_EXIT_ROOMS,
|
||||
"PAINTING_EXITS": PAINTING_EXITS,
|
||||
@@ -506,6 +600,8 @@ if __name__ == '__main__':
|
||||
"DOOR_LOCATION_IDS": DOOR_LOCATION_IDS,
|
||||
"DOOR_ITEM_IDS": DOOR_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,
|
||||
}
|
||||
|
||||
|
||||
@@ -33,19 +33,23 @@ end
|
||||
configured_rooms = Set["Menu"]
|
||||
configured_doors = Set[]
|
||||
configured_panels = Set[]
|
||||
configured_panel_doors = Set[]
|
||||
|
||||
mentioned_rooms = Set[]
|
||||
mentioned_doors = Set[]
|
||||
mentioned_panels = Set[]
|
||||
mentioned_panel_doors = Set[]
|
||||
mentioned_sunwarp_entrances = Set[]
|
||||
mentioned_sunwarp_exits = Set[]
|
||||
mentioned_paintings = Set[]
|
||||
|
||||
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"]
|
||||
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"]
|
||||
|
||||
non_counting = 0
|
||||
@@ -253,6 +257,43 @@ config.each do |room_name, room|
|
||||
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|
|
||||
if painting.include?("id") and painting["id"].kind_of? String then
|
||||
unless paintings.include? painting["id"] then
|
||||
@@ -327,12 +368,24 @@ config.each do |room_name, room|
|
||||
end
|
||||
end
|
||||
|
||||
(room["progression"] || {}).each do |progression_name, door_list|
|
||||
door_list.each do |door|
|
||||
if door.kind_of? Hash then
|
||||
mentioned_doors.add("#{door["room"]} - #{door["door"]}")
|
||||
else
|
||||
mentioned_doors.add("#{room_name} - #{door}")
|
||||
(room["progression"] || {}).each do |progression_name, pdata|
|
||||
if pdata.include? "doors" then
|
||||
pdata["doors"].each do |door|
|
||||
if door.kind_of? Hash then
|
||||
mentioned_doors.add("#{door["room"]} - #{door["door"]}")
|
||||
else
|
||||
mentioned_doors.add("#{room_name} - #{door}")
|
||||
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
|
||||
|
||||
@@ -344,17 +397,22 @@ end
|
||||
|
||||
errored_rooms = mentioned_rooms - configured_rooms
|
||||
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
|
||||
|
||||
errored_panels = mentioned_panels - configured_panels
|
||||
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
|
||||
|
||||
errored_doors = mentioned_doors - configured_doors
|
||||
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
|
||||
|
||||
door_groups.each do |group,num|
|
||||
@@ -367,6 +425,16 @@ door_groups.each do |group,num|
|
||||
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|
|
||||
room.include? "/"
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from random import Random
|
||||
from typing import Dict, Any, Iterable, Optional, Union, List, TextIO
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState
|
||||
@@ -27,15 +28,20 @@ from .strings.goal_names import Goal as GoalName
|
||||
from .strings.metal_names import Ore
|
||||
from .strings.region_names import Region as RegionName, LogicRegion
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
STARDEW_VALLEY = "Stardew Valley"
|
||||
UNIVERSAL_TRACKER_SEED_PROPERTY = "ut_seed"
|
||||
|
||||
client_version = 0
|
||||
|
||||
|
||||
class StardewLocation(Location):
|
||||
game: str = "Stardew Valley"
|
||||
game: str = STARDEW_VALLEY
|
||||
|
||||
|
||||
class StardewItem(Item):
|
||||
game: str = "Stardew Valley"
|
||||
game: str = STARDEW_VALLEY
|
||||
|
||||
|
||||
class StardewWebWorld(WebWorld):
|
||||
@@ -60,7 +66,7 @@ class StardewValleyWorld(World):
|
||||
Stardew Valley is an open-ended country-life RPG. You can farm, fish, mine, fight, complete quests,
|
||||
befriend villagers, and uncover dark secrets.
|
||||
"""
|
||||
game = "Stardew Valley"
|
||||
game = STARDEW_VALLEY
|
||||
topology_present = False
|
||||
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
@@ -95,6 +101,17 @@ class StardewValleyWorld(World):
|
||||
self.total_progression_items = 0
|
||||
# self.all_progression_items = dict()
|
||||
|
||||
# Taking the seed specified in slot data for UT, otherwise just generating the seed.
|
||||
self.seed = getattr(multiworld, "re_gen_passthrough", {}).get(STARDEW_VALLEY, self.random.getrandbits(64))
|
||||
self.random = Random(self.seed)
|
||||
|
||||
def interpret_slot_data(self, slot_data: Dict[str, Any]) -> Optional[int]:
|
||||
# If the seed is not specified in the slot data, this mean the world was generated before Universal Tracker support.
|
||||
seed = slot_data.get(UNIVERSAL_TRACKER_SEED_PROPERTY)
|
||||
if seed is None:
|
||||
logger.warning(f"World was generated before Universal Tracker support. Tracker might not be accurate.")
|
||||
return seed
|
||||
|
||||
def generate_early(self):
|
||||
self.force_change_options_if_incompatible()
|
||||
self.content = create_content(self.options)
|
||||
@@ -108,12 +125,12 @@ class StardewValleyWorld(World):
|
||||
self.options.exclude_ginger_island.value = ExcludeGingerIsland.option_false
|
||||
goal_name = self.options.goal.current_key
|
||||
player_name = self.multiworld.player_name[self.player]
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"Goal '{goal_name}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({player_name})")
|
||||
if exclude_ginger_island and self.options.walnutsanity != Walnutsanity.preset_none:
|
||||
self.options.walnutsanity.value = Walnutsanity.preset_none
|
||||
player_name = self.multiworld.player_name[self.player]
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"Walnutsanity requires Ginger Island. Ginger Island was excluded from {self.player} ({player_name})'s world, so walnutsanity was force disabled")
|
||||
|
||||
def create_regions(self):
|
||||
@@ -413,6 +430,7 @@ class StardewValleyWorld(World):
|
||||
included_option_names: List[str] = [option_name for option_name in self.options_dataclass.type_hints if option_name not in excluded_option_names]
|
||||
slot_data = self.options.as_dict(*included_option_names)
|
||||
slot_data.update({
|
||||
UNIVERSAL_TRACKER_SEED_PROPERTY: self.seed,
|
||||
"seed": self.random.randrange(1000000000), # Seed should be max 9 digits
|
||||
"randomized_entrances": self.randomized_entrances,
|
||||
"modified_bundles": bundles,
|
||||
|
||||
@@ -137,7 +137,8 @@ vanilla_regions = [
|
||||
[Entrance.island_west_to_islandfarmhouse, Entrance.island_west_to_gourmand_cave, Entrance.island_west_to_crystals_cave,
|
||||
Entrance.island_west_to_shipwreck, Entrance.island_west_to_qi_walnut_room, Entrance.use_farm_obelisk, Entrance.parrot_express_jungle_to_docks,
|
||||
Entrance.parrot_express_jungle_to_dig_site, Entrance.parrot_express_jungle_to_volcano, LogicEntrance.grow_spring_crops_on_island,
|
||||
LogicEntrance.grow_summer_crops_on_island, LogicEntrance.grow_fall_crops_on_island, LogicEntrance.grow_winter_crops_on_island, LogicEntrance.grow_indoor_crops_on_island],
|
||||
LogicEntrance.grow_summer_crops_on_island, LogicEntrance.grow_fall_crops_on_island, LogicEntrance.grow_winter_crops_on_island,
|
||||
LogicEntrance.grow_indoor_crops_on_island],
|
||||
is_ginger_island=True),
|
||||
RegionData(Region.island_east, [Entrance.island_east_to_leo_hut, Entrance.island_east_to_island_shrine], is_ginger_island=True),
|
||||
RegionData(Region.island_shrine, is_ginger_island=True),
|
||||
@@ -536,7 +537,7 @@ def create_final_regions(world_options) -> List[RegionData]:
|
||||
def create_final_connections_and_regions(world_options) -> Tuple[Dict[str, ConnectionData], Dict[str, RegionData]]:
|
||||
regions_data: Dict[str, RegionData] = {region.name: region for region in create_final_regions(world_options)}
|
||||
connections = {connection.name: connection for connection in vanilla_connections}
|
||||
connections = modify_connections_for_mods(connections, world_options.mods)
|
||||
connections = modify_connections_for_mods(connections, sorted(world_options.mods.value))
|
||||
include_island = world_options.exclude_ginger_island == ExcludeGingerIsland.option_false
|
||||
return remove_ginger_island_regions_and_connections(regions_data, connections, include_island)
|
||||
|
||||
@@ -563,10 +564,8 @@ def remove_ginger_island_regions_and_connections(regions_by_name: Dict[str, Regi
|
||||
return connections, regions_by_name
|
||||
|
||||
|
||||
def modify_connections_for_mods(connections: Dict[str, ConnectionData], mods) -> Dict[str, ConnectionData]:
|
||||
if mods is None:
|
||||
return connections
|
||||
for mod in mods.value:
|
||||
def modify_connections_for_mods(connections: Dict[str, ConnectionData], mods: Iterable) -> Dict[str, ConnectionData]:
|
||||
for mod in mods:
|
||||
if mod not in ModDataList:
|
||||
continue
|
||||
if mod in vanilla_connections_to_remove_by_mod:
|
||||
|
||||
@@ -441,6 +441,16 @@ def setup_multiworld(test_options: Iterable[Dict[str, int]] = None, seed=None) -
|
||||
for i in range(1, len(test_options) + 1):
|
||||
multiworld.game[i] = StardewValleyWorld.game
|
||||
multiworld.player_name.update({i: f"Tester{i}"})
|
||||
args = create_args(test_options)
|
||||
multiworld.set_options(args)
|
||||
|
||||
for step in gen_steps:
|
||||
call_all(multiworld, step)
|
||||
|
||||
return multiworld
|
||||
|
||||
|
||||
def create_args(test_options):
|
||||
args = Namespace()
|
||||
for name, option in StardewValleyWorld.options_dataclass.type_hints.items():
|
||||
options = {}
|
||||
@@ -449,9 +459,4 @@ def setup_multiworld(test_options: Iterable[Dict[str, int]] = None, seed=None) -
|
||||
value = option(player_options[name]) if name in player_options else option.from_any(option.default)
|
||||
options.update({i: value})
|
||||
setattr(args, name, options)
|
||||
multiworld.set_options(args)
|
||||
|
||||
for step in gen_steps:
|
||||
call_all(multiworld, step)
|
||||
|
||||
return multiworld
|
||||
return args
|
||||
|
||||
@@ -37,7 +37,7 @@ class TestRaccoonBundlesLogic(SVTestBase):
|
||||
options.BundlePrice: options.BundlePrice.option_normal,
|
||||
options.Craftsanity: options.Craftsanity.option_all,
|
||||
}
|
||||
seed = 1234 # Magic seed that does what I want. Might need to get changed if we change the randomness behavior of raccoon bundles
|
||||
seed = 2 # Magic seed that does what I want. Might need to get changed if we change the randomness behavior of raccoon bundles
|
||||
|
||||
def test_raccoon_bundles_rely_on_previous_ones(self):
|
||||
# The first raccoon bundle is a fishing one
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from ...options import FarmType, EntranceRandomization
|
||||
from ...test import setup_solo_multiworld, allsanity_mods_6_x_x
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -10,21 +11,23 @@ if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
seed = args.seed
|
||||
|
||||
multi_world = setup_solo_multiworld(
|
||||
allsanity_mods_6_x_x(),
|
||||
seed=seed
|
||||
)
|
||||
options = allsanity_mods_6_x_x()
|
||||
options[FarmType.internal_name] = FarmType.option_standard
|
||||
options[EntranceRandomization.internal_name] = EntranceRandomization.option_buildings
|
||||
multi_world = setup_solo_multiworld(options, seed=seed)
|
||||
|
||||
world = multi_world.worlds[1]
|
||||
output = {
|
||||
"bundles": {
|
||||
bundle_room.name: {
|
||||
bundle.name: str(bundle.items)
|
||||
for bundle in bundle_room.bundles
|
||||
}
|
||||
for bundle_room in multi_world.worlds[1].modified_bundles
|
||||
for bundle_room in world.modified_bundles
|
||||
},
|
||||
"items": [item.name for item in multi_world.get_items()],
|
||||
"location_rules": {location.name: repr(location.access_rule) for location in multi_world.get_locations(1)}
|
||||
"location_rules": {location.name: repr(location.access_rule) for location in multi_world.get_locations(1)},
|
||||
"slot_data": world.fill_slot_data()
|
||||
}
|
||||
|
||||
print(json.dumps(output))
|
||||
|
||||
@@ -24,8 +24,7 @@ class TestGenerationIsStable(SVTestCase):
|
||||
if self.skip_long_tests:
|
||||
raise unittest.SkipTest("Long tests disabled")
|
||||
|
||||
# seed = get_seed(33778671150797368040) # troubleshooting seed
|
||||
seed = get_seed(74716545478307145559)
|
||||
seed = get_seed()
|
||||
|
||||
output_a = subprocess.check_output([sys.executable, '-m', 'worlds.stardew_valley.test.stability.StabilityOutputScript', '--seed', str(seed)])
|
||||
output_b = subprocess.check_output([sys.executable, '-m', 'worlds.stardew_valley.test.stability.StabilityOutputScript', '--seed', str(seed)])
|
||||
@@ -54,3 +53,6 @@ class TestGenerationIsStable(SVTestCase):
|
||||
# We check that the actual rule has the same order to make sure it is evaluated in the same order,
|
||||
# so performance tests are repeatable as much as possible.
|
||||
self.assertEqual(rule_a, rule_b, f"Location rule of {location_a} at index {i} is different between both executions. Seed={seed}")
|
||||
|
||||
for key, value in result_a["slot_data"].items():
|
||||
self.assertEqual(value, result_b["slot_data"][key], f"Slot data {key} is different between both executions. Seed={seed}")
|
||||
|
||||
52
worlds/stardew_valley/test/stability/TestUniversalTracker.py
Normal file
52
worlds/stardew_valley/test/stability/TestUniversalTracker.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
|
||||
from .. import SVTestBase, create_args, allsanity_mods_6_x_x
|
||||
from ... import STARDEW_VALLEY, FarmType, BundleRandomization, EntranceRandomization
|
||||
|
||||
|
||||
class TestUniversalTrackerGenerationIsStable(SVTestBase):
|
||||
options = allsanity_mods_6_x_x()
|
||||
options.update({
|
||||
EntranceRandomization.internal_name: EntranceRandomization.option_buildings,
|
||||
BundleRandomization.internal_name: BundleRandomization.option_shuffled,
|
||||
FarmType.internal_name: FarmType.option_standard, # Need to choose one otherwise it's random
|
||||
})
|
||||
|
||||
def test_all_locations_and_items_are_the_same_between_two_generations(self):
|
||||
# This might open a kivy window temporarily, but it's the only way to test this...
|
||||
if self.skip_long_tests:
|
||||
raise unittest.SkipTest("Long tests disabled")
|
||||
|
||||
try:
|
||||
# This test only run if UT is present, so no risk of running in the CI.
|
||||
from worlds.tracker.TrackerClient import TrackerGameContext # noqa
|
||||
except ImportError:
|
||||
raise unittest.SkipTest("UT not loaded, skipping test")
|
||||
|
||||
slot_data = self.world.fill_slot_data()
|
||||
ut_data = self.world.interpret_slot_data(slot_data)
|
||||
|
||||
fake_context = Mock()
|
||||
fake_context.re_gen_passthrough = {STARDEW_VALLEY: ut_data}
|
||||
args = create_args({0: self.options})
|
||||
args.outputpath = None
|
||||
args.outputname = None
|
||||
args.multi = 1
|
||||
args.race = None
|
||||
args.plando_options = self.multiworld.plando_options
|
||||
args.plando_items = self.multiworld.plando_items
|
||||
args.plando_texts = self.multiworld.plando_texts
|
||||
args.plando_connections = self.multiworld.plando_connections
|
||||
args.game = self.multiworld.game
|
||||
args.name = self.multiworld.player_name
|
||||
args.sprite = {}
|
||||
args.sprite_pool = {}
|
||||
args.skip_output = True
|
||||
|
||||
generated_multi_world = TrackerGameContext.TMain(fake_context, args, self.multiworld.seed)
|
||||
generated_slot_data = generated_multi_world.worlds[1].fill_slot_data()
|
||||
|
||||
# Just checking slot data should prove that UT generates the same result as AP generation.
|
||||
self.maxDiff = None
|
||||
self.assertEqual(slot_data, generated_slot_data)
|
||||
Reference in New Issue
Block a user