summary refs log tree commit diff stats
path: root/player_logic.py
diff options
context:
space:
mode:
Diffstat (limited to 'player_logic.py')
-rw-r--r--player_logic.py121
1 files changed, 93 insertions, 28 deletions
diff --git a/player_logic.py b/player_logic.py index 35080ac..b21735c 100644 --- a/player_logic.py +++ b/player_logic.py
@@ -7,8 +7,8 @@ from .items import ALL_ITEM_TABLE, ItemType
7from .locations import ALL_LOCATION_TABLE, LocationClassification 7from .locations import ALL_LOCATION_TABLE, LocationClassification
8from .options import LocationChecks, ShuffleDoors, SunwarpAccess, VictoryCondition 8from .options import LocationChecks, ShuffleDoors, SunwarpAccess, VictoryCondition
9from .static_logic import DOORS_BY_ROOM, PAINTINGS, PAINTING_ENTRANCES, PAINTING_EXITS, \ 9from .static_logic import DOORS_BY_ROOM, PAINTINGS, PAINTING_ENTRANCES, PAINTING_EXITS, \
10 PANELS_BY_ROOM, PROGRESSION_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, \ 10 PANELS_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, PROGRESSIVE_DOORS_BY_ROOM, \
11 SUNWARP_ENTRANCES, SUNWARP_EXITS 11 PANEL_DOORS_BY_ROOM, PROGRESSIVE_PANELS_BY_ROOM, SUNWARP_ENTRANCES, SUNWARP_EXITS
12 12
13if TYPE_CHECKING: 13if TYPE_CHECKING:
14 from . import LingoWorld 14 from . import LingoWorld
@@ -18,6 +18,8 @@ class AccessRequirements:
18 rooms: Set[str] 18 rooms: Set[str]
19 doors: Set[RoomAndDoor] 19 doors: Set[RoomAndDoor]
20 colors: Set[str] 20 colors: Set[str]
21 items: Set[str]
22 progression: Dict[str, int]
21 the_master: bool 23 the_master: bool
22 postgame: bool 24 postgame: bool
23 25
@@ -25,6 +27,8 @@ class AccessRequirements:
25 self.rooms = set() 27 self.rooms = set()
26 self.doors = set() 28 self.doors = set()
27 self.colors = set() 29 self.colors = set()
30 self.items = set()
31 self.progression = dict()
28 self.the_master = False 32 self.the_master = False
29 self.postgame = False 33 self.postgame = False
30 34
@@ -32,12 +36,17 @@ class AccessRequirements:
32 self.rooms |= other.rooms 36 self.rooms |= other.rooms
33 self.doors |= other.doors 37 self.doors |= other.doors
34 self.colors |= other.colors 38 self.colors |= other.colors
39 self.items |= other.items
35 self.the_master |= other.the_master 40 self.the_master |= other.the_master
36 self.postgame |= other.postgame 41 self.postgame |= other.postgame
37 42
43 for progression, index in other.progression.items():
44 if progression not in self.progression or index > self.progression[progression]:
45 self.progression[progression] = index
46
38 def __str__(self): 47 def __str__(self):
39 return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors}," \ 48 return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors}, items={self.items}," \
40 f" the_master={self.the_master}, postgame={self.postgame})" 49 f" progression={self.progression}), the_master={self.the_master}, postgame={self.postgame}"
41 50
42 51
43class PlayerLocation(NamedTuple): 52class PlayerLocation(NamedTuple):
@@ -117,15 +126,15 @@ class LingoPlayerLogic:
117 self.item_by_door.setdefault(room, {})[door] = item 126 self.item_by_door.setdefault(room, {})[door] = item
118 127
119 def handle_non_grouped_door(self, room_name: str, door_data: Door, world: "LingoWorld"): 128 def handle_non_grouped_door(self, room_name: str, door_data: Door, world: "LingoWorld"):
120 if room_name in PROGRESSION_BY_ROOM and door_data.name in PROGRESSION_BY_ROOM[room_name]: 129 if room_name in PROGRESSIVE_DOORS_BY_ROOM and door_data.name in PROGRESSIVE_DOORS_BY_ROOM[room_name]:
121 progression_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name 130 progression_name = PROGRESSIVE_DOORS_BY_ROOM[room_name][door_data.name].item_name
122 progression_handling = should_split_progression(progression_name, world) 131 progression_handling = should_split_progression(progression_name, world)
123 132
124 if progression_handling == ProgressiveItemBehavior.SPLIT: 133 if progression_handling == ProgressiveItemBehavior.SPLIT:
125 self.set_door_item(room_name, door_data.name, door_data.item_name) 134 self.set_door_item(room_name, door_data.name, door_data.item_name)
126 self.real_items.append(door_data.item_name) 135 self.real_items.append(door_data.item_name)
127 elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE: 136 elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE:
128 progressive_item_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name 137 progressive_item_name = PROGRESSIVE_DOORS_BY_ROOM[room_name][door_data.name].item_name
129 self.set_door_item(room_name, door_data.name, progressive_item_name) 138 self.set_door_item(room_name, door_data.name, progressive_item_name)
130 self.real_items.append(progressive_item_name) 139 self.real_items.append(progressive_item_name)
131 else: 140 else:
@@ -156,17 +165,31 @@ class LingoPlayerLogic:
156 victory_condition = world.options.victory_condition 165 victory_condition = world.options.victory_condition
157 early_color_hallways = world.options.early_color_hallways 166 early_color_hallways = world.options.early_color_hallways
158 167
159 if location_checks == LocationChecks.option_reduced and door_shuffle != ShuffleDoors.option_none: 168 if location_checks == LocationChecks.option_reduced:
160 raise OptionError("You cannot have reduced location checks when door shuffle is on, because there would not" 169 if door_shuffle == ShuffleDoors.option_doors:
161 " be enough locations for all of the door items.") 170 raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks when door shuffle"
171 f" is on, because there would not be enough locations for all of the door items.")
172 if door_shuffle == ShuffleDoors.option_panels:
173 if not world.options.group_doors:
174 raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks when ungrouped"
175 f" panels mode door shuffle is on, because there would not be enough locations for"
176 f" all of the panel items.")
177 if color_shuffle:
178 raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks with both"
179 f" panels mode door shuffle and color shuffle because there would not be enough"
180 f" locations for all of the items.")
181 if world.options.sunwarp_access >= SunwarpAccess.option_individual:
182 raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks with both"
183 f" panels mode door shuffle and individual or progressive sunwarp access because"
184 f" there would not be enough locations for all of the items.")
162 185
163 # Create door items, where needed. 186 # Create door items, where needed.
164 door_groups: Set[str] = set() 187 door_groups: Set[str] = set()
165 for room_name, room_data in DOORS_BY_ROOM.items(): 188 for room_name, room_data in DOORS_BY_ROOM.items():
166 for door_name, door_data in room_data.items(): 189 for door_name, door_data in room_data.items():
167 if door_data.skip_item is False and door_data.event is False: 190 if door_data.skip_item is False and door_data.event is False:
168 if door_data.type == DoorType.NORMAL and door_shuffle != ShuffleDoors.option_none: 191 if door_data.type == DoorType.NORMAL and door_shuffle == ShuffleDoors.option_doors:
169 if door_data.door_group is not None and door_shuffle == ShuffleDoors.option_simple: 192 if door_data.door_group is not None and world.options.group_doors:
170 # Grouped doors are handled differently if shuffle doors is on simple. 193 # Grouped doors are handled differently if shuffle doors is on simple.
171 self.set_door_item(room_name, door_name, door_data.door_group) 194 self.set_door_item(room_name, door_name, door_data.door_group)
172 door_groups.add(door_data.door_group) 195 door_groups.add(door_data.door_group)
@@ -188,7 +211,29 @@ class LingoPlayerLogic:
188 self.real_items.append(door_data.item_name) 211 self.real_items.append(door_data.item_name)
189 212
190 self.real_items += door_groups 213 self.real_items += door_groups
191 214
215 # Create panel items, where needed.
216 if world.options.shuffle_doors == ShuffleDoors.option_panels:
217 panel_groups: Set[str] = set()
218
219 for room_name, room_data in PANEL_DOORS_BY_ROOM.items():
220 for panel_door_name, panel_door_data in room_data.items():
221 if panel_door_data.panel_group is not None and world.options.group_doors:
222 panel_groups.add(panel_door_data.panel_group)
223 elif room_name in PROGRESSIVE_PANELS_BY_ROOM \
224 and panel_door_name in PROGRESSIVE_PANELS_BY_ROOM[room_name]:
225 progression_obj = PROGRESSIVE_PANELS_BY_ROOM[room_name][panel_door_name]
226 progression_handling = should_split_progression(progression_obj.item_name, world)
227
228 if progression_handling == ProgressiveItemBehavior.SPLIT:
229 self.real_items.append(panel_door_data.item_name)
230 elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE:
231 self.real_items.append(progression_obj.item_name)
232 else:
233 self.real_items.append(panel_door_data.item_name)
234
235 self.real_items += panel_groups
236
192 # Create color items, if needed. 237 # Create color items, if needed.
193 if color_shuffle: 238 if color_shuffle:
194 self.real_items += [name for name, item in ALL_ITEM_TABLE.items() if item.type == ItemType.COLOR] 239 self.real_items += [name for name, item in ALL_ITEM_TABLE.items() if item.type == ItemType.COLOR]
@@ -244,7 +289,7 @@ class LingoPlayerLogic:
244 elif location_checks == LocationChecks.option_insanity: 289 elif location_checks == LocationChecks.option_insanity:
245 location_classification = LocationClassification.insanity 290 location_classification = LocationClassification.insanity
246 291
247 if door_shuffle != ShuffleDoors.option_none and not early_color_hallways: 292 if door_shuffle == ShuffleDoors.option_doors and not early_color_hallways:
248 location_classification |= LocationClassification.small_sphere_one 293 location_classification |= LocationClassification.small_sphere_one
249 294
250 for location_name, location_data in ALL_LOCATION_TABLE.items(): 295 for location_name, location_data in ALL_LOCATION_TABLE.items():
@@ -286,7 +331,7 @@ class LingoPlayerLogic:
286 "iterations. This is very unlikely to happen on its own, and probably indicates some " 331 "iterations. This is very unlikely to happen on its own, and probably indicates some "
287 "kind of logic error.") 332 "kind of logic error.")
288 333
289 if door_shuffle != ShuffleDoors.option_none and location_checks != LocationChecks.option_insanity \ 334 if door_shuffle == ShuffleDoors.option_doors and location_checks != LocationChecks.option_insanity \
290 and not early_color_hallways and world.multiworld.players > 1: 335 and not early_color_hallways and world.multiworld.players > 1:
291 # Under the combination of door shuffle, normal location checks, and no early color hallways, sphere 1 is 336 # Under the combination of door shuffle, normal location checks, and no early color hallways, sphere 1 is
292 # only three checks. In a multiplayer situation, this can be frustrating for the player because they are 337 # only three checks. In a multiplayer situation, this can be frustrating for the player because they are
@@ -301,19 +346,19 @@ class LingoPlayerLogic:
301 # Starting Room - Exit Door gives access to OPEN and TRACE. 346 # Starting Room - Exit Door gives access to OPEN and TRACE.
302 good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"] 347 good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"]
303 348
304 if not color_shuffle and not world.options.enable_pilgrimage:
305 # HOT CRUST and THIS.
306 good_item_options.append("Pilgrim Room - Sun Painting")
307
308 if not color_shuffle: 349 if not color_shuffle:
309 if door_shuffle == ShuffleDoors.option_simple: 350 if not world.options.enable_pilgrimage:
351 # HOT CRUST and THIS.
352 good_item_options.append("Pilgrim Room - Sun Painting")
353
354 if world.options.group_doors:
310 # WELCOME BACK, CLOCKWISE, and DRAWL + RUNS. 355 # WELCOME BACK, CLOCKWISE, and DRAWL + RUNS.
311 good_item_options.append("Welcome Back Doors") 356 good_item_options.append("Welcome Back Doors")
312 else: 357 else:
313 # WELCOME BACK and CLOCKWISE. 358 # WELCOME BACK and CLOCKWISE.
314 good_item_options.append("Welcome Back Area - Shortcut to Starting Room") 359 good_item_options.append("Welcome Back Area - Shortcut to Starting Room")
315 360
316 if door_shuffle == ShuffleDoors.option_simple: 361 if world.options.group_doors:
317 # Color hallways access (NOTE: reconsider when sunwarp shuffling exists). 362 # Color hallways access (NOTE: reconsider when sunwarp shuffling exists).
318 good_item_options.append("Rhyme Room Doors") 363 good_item_options.append("Rhyme Room Doors")
319 364
@@ -359,13 +404,11 @@ class LingoPlayerLogic:
359 def randomize_paintings(self, world: "LingoWorld") -> bool: 404 def randomize_paintings(self, world: "LingoWorld") -> bool:
360 self.painting_mapping.clear() 405 self.painting_mapping.clear()
361 406
362 door_shuffle = world.options.shuffle_doors
363
364 # First, assign mappings to the required-exit paintings. We ensure that req-blocked paintings do not lead to 407 # First, assign mappings to the required-exit paintings. We ensure that req-blocked paintings do not lead to
365 # required paintings. 408 # required paintings.
366 req_exits = [] 409 req_exits = []
367 required_painting_rooms = REQUIRED_PAINTING_ROOMS 410 required_painting_rooms = REQUIRED_PAINTING_ROOMS
368 if door_shuffle == ShuffleDoors.option_none: 411 if world.options.shuffle_doors != ShuffleDoors.option_doors:
369 required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS 412 required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS
370 req_exits = [painting_id for painting_id, painting in PAINTINGS.items() if painting.required_when_no_doors] 413 req_exits = [painting_id for painting_id, painting in PAINTINGS.items() if painting.required_when_no_doors]
371 414
@@ -432,7 +475,7 @@ class LingoPlayerLogic:
432 for painting_id, painting in PAINTINGS.items(): 475 for painting_id, painting in PAINTINGS.items():
433 if painting_id not in self.painting_mapping.values() \ 476 if painting_id not in self.painting_mapping.values() \
434 and (painting.required or (painting.required_when_no_doors and 477 and (painting.required or (painting.required_when_no_doors and
435 door_shuffle == ShuffleDoors.option_none)): 478 world.options.shuffle_doors != ShuffleDoors.option_doors)):
436 return False 479 return False
437 480
438 return True 481 return True
@@ -447,12 +490,31 @@ class LingoPlayerLogic:
447 access_reqs = AccessRequirements() 490 access_reqs = AccessRequirements()
448 panel_object = PANELS_BY_ROOM[room][panel] 491 panel_object = PANELS_BY_ROOM[room][panel]
449 492
493 if world.options.shuffle_doors == ShuffleDoors.option_panels and panel_object.panel_door is not None:
494 panel_door_room = panel_object.panel_door.room
495 panel_door_name = panel_object.panel_door.panel_door
496 panel_door = PANEL_DOORS_BY_ROOM[panel_door_room][panel_door_name]
497
498 if panel_door.panel_group is not None and world.options.group_doors:
499 access_reqs.items.add(panel_door.panel_group)
500 elif panel_door_room in PROGRESSIVE_PANELS_BY_ROOM\
501 and panel_door_name in PROGRESSIVE_PANELS_BY_ROOM[panel_door_room]:
502 progression_obj = PROGRESSIVE_PANELS_BY_ROOM[panel_door_room][panel_door_name]
503 progression_handling = should_split_progression(progression_obj.item_name, world)
504
505 if progression_handling == ProgressiveItemBehavior.SPLIT:
506 access_reqs.items.add(panel_door.item_name)
507 elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE:
508 access_reqs.progression[progression_obj.item_name] = progression_obj.index
509 else:
510 access_reqs.items.add(panel_door.item_name)
511
450 for req_room in panel_object.required_rooms: 512 for req_room in panel_object.required_rooms:
451 access_reqs.rooms.add(req_room) 513 access_reqs.rooms.add(req_room)
452 514
453 for req_door in panel_object.required_doors: 515 for req_door in panel_object.required_doors:
454 door_object = DOORS_BY_ROOM[room if req_door.room is None else req_door.room][req_door.door] 516 door_object = DOORS_BY_ROOM[room if req_door.room is None else req_door.room][req_door.door]
455 if door_object.event or world.options.shuffle_doors == ShuffleDoors.option_none: 517 if door_object.event or world.options.shuffle_doors != ShuffleDoors.option_doors:
456 sub_access_reqs = self.calculate_door_requirements( 518 sub_access_reqs = self.calculate_door_requirements(
457 room if req_door.room is None else req_door.room, req_door.door, world) 519 room if req_door.room is None else req_door.room, req_door.door, world)
458 access_reqs.merge(sub_access_reqs) 520 access_reqs.merge(sub_access_reqs)
@@ -522,11 +584,14 @@ class LingoPlayerLogic:
522 continue 584 continue
523 585
524 # We won't coalesce any panels that have requirements beyond colors. To simplify things for now, we will 586 # We won't coalesce any panels that have requirements beyond colors. To simplify things for now, we will
525 # only coalesce single-color panels. Chains/stacks/combo puzzles will be separate. THE MASTER has 587 # only coalesce single-color panels. Chains/stacks/combo puzzles will be separate. Panel door locked
526 # special access rules and is handled separately. 588 # puzzles will be separate if panels mode is on. THE MASTER has special access rules and is handled
589 # separately.
527 if len(panel_data.required_panels) > 0 or len(panel_data.required_doors) > 0\ 590 if len(panel_data.required_panels) > 0 or len(panel_data.required_doors) > 0\
528 or len(panel_data.required_rooms) > 0\ 591 or len(panel_data.required_rooms) > 0\
529 or (world.options.shuffle_colors and len(panel_data.colors) > 1)\ 592 or (world.options.shuffle_colors and len(panel_data.colors) > 1)\
593 or (world.options.shuffle_doors == ShuffleDoors.option_panels
594 and panel_data.panel_door is not None)\
530 or panel_name == "THE MASTER": 595 or panel_name == "THE MASTER":
531 self.counting_panel_reqs.setdefault(room_name, []).append( 596 self.counting_panel_reqs.setdefault(room_name, []).append(
532 (self.calculate_panel_requirements(room_name, panel_name, world), 1)) 597 (self.calculate_panel_requirements(room_name, panel_name, world), 1))