diff options
Diffstat (limited to 'player_logic.py')
| -rw-r--r-- | player_logic.py | 121 |
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 | |||
| 7 | from .locations import ALL_LOCATION_TABLE, LocationClassification | 7 | from .locations import ALL_LOCATION_TABLE, LocationClassification |
| 8 | from .options import LocationChecks, ShuffleDoors, SunwarpAccess, VictoryCondition | 8 | from .options import LocationChecks, ShuffleDoors, SunwarpAccess, VictoryCondition |
| 9 | from .static_logic import DOORS_BY_ROOM, PAINTINGS, PAINTING_ENTRANCES, PAINTING_EXITS, \ | 9 | from .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 | ||
| 13 | if TYPE_CHECKING: | 13 | if 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 | ||
| 43 | class PlayerLocation(NamedTuple): | 52 | class 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)) |
