diff options
Diffstat (limited to 'apworld/player_logic.py')
-rw-r--r-- | apworld/player_logic.py | 60 |
1 files changed, 42 insertions, 18 deletions
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index c94b809..42b36e6 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py | |||
@@ -1,6 +1,7 @@ | |||
1 | from enum import IntEnum, auto | 1 | from enum import IntEnum, auto |
2 | 2 | ||
3 | from .generated import data_pb2 as data_pb2 | 3 | from .generated import data_pb2 as data_pb2 |
4 | from .items import SYMBOL_ITEMS | ||
4 | from typing import TYPE_CHECKING, NamedTuple | 5 | from typing import TYPE_CHECKING, NamedTuple |
5 | 6 | ||
6 | from .options import VictoryCondition, ShuffleLetters, CyanDoorBehavior | 7 | from .options import VictoryCondition, ShuffleLetters, CyanDoorBehavior |
@@ -23,7 +24,6 @@ class AccessRequirements: | |||
23 | items: set[str] | 24 | items: set[str] |
24 | progressives: dict[str, int] | 25 | progressives: dict[str, int] |
25 | rooms: set[str] | 26 | rooms: set[str] |
26 | symbols: set[str] | ||
27 | letters: dict[str, int] | 27 | letters: dict[str, int] |
28 | cyans: bool | 28 | cyans: bool |
29 | 29 | ||
@@ -34,7 +34,6 @@ class AccessRequirements: | |||
34 | self.items = set() | 34 | self.items = set() |
35 | self.progressives = dict() | 35 | self.progressives = dict() |
36 | self.rooms = set() | 36 | self.rooms = set() |
37 | self.symbols = set() | ||
38 | self.letters = dict() | 37 | self.letters = dict() |
39 | self.cyans = False | 38 | self.cyans = False |
40 | self.or_logic = list() | 39 | self.or_logic = list() |
@@ -49,9 +48,6 @@ class AccessRequirements: | |||
49 | for room in other.rooms: | 48 | for room in other.rooms: |
50 | self.rooms.add(room) | 49 | self.rooms.add(room) |
51 | 50 | ||
52 | for symbol in other.symbols: | ||
53 | self.symbols.add(symbol) | ||
54 | |||
55 | for letter, level in other.letters.items(): | 51 | for letter, level in other.letters.items(): |
56 | self.letters[letter] = max(self.letters.get(letter, 0), level) | 52 | self.letters[letter] = max(self.letters.get(letter, 0), level) |
57 | 53 | ||
@@ -60,6 +56,10 @@ class AccessRequirements: | |||
60 | for disjunction in other.or_logic: | 56 | for disjunction in other.or_logic: |
61 | self.or_logic.append(disjunction) | 57 | self.or_logic.append(disjunction) |
62 | 58 | ||
59 | def is_empty(self) -> bool: | ||
60 | return (len(self.items) == 0 and len(self.progressives) == 0 and len(self.rooms) == 0 and len(self.letters) == 0 | ||
61 | and not self.cyans and len(self.or_logic) == 0) | ||
62 | |||
63 | def __repr__(self): | 63 | def __repr__(self): |
64 | parts = [] | 64 | parts = [] |
65 | if len(self.items) > 0: | 65 | if len(self.items) > 0: |
@@ -68,8 +68,6 @@ class AccessRequirements: | |||
68 | parts.append(f"progressives={self.progressives}") | 68 | parts.append(f"progressives={self.progressives}") |
69 | if len(self.rooms) > 0: | 69 | if len(self.rooms) > 0: |
70 | parts.append(f"rooms={self.rooms}") | 70 | parts.append(f"rooms={self.rooms}") |
71 | if len(self.symbols) > 0: | ||
72 | parts.append(f"symbols={self.symbols}") | ||
73 | if len(self.letters) > 0: | 71 | if len(self.letters) > 0: |
74 | parts.append(f"letters={self.letters}") | 72 | parts.append(f"letters={self.letters}") |
75 | if self.cyans: | 73 | if self.cyans: |
@@ -231,6 +229,10 @@ class Lingo2PlayerLogic: | |||
231 | self.locations_by_room.setdefault(keyholder.room_id, []).append(PlayerLocation(keyholder.ap_id, | 229 | self.locations_by_room.setdefault(keyholder.room_id, []).append(PlayerLocation(keyholder.ap_id, |
232 | reqs)) | 230 | reqs)) |
233 | 231 | ||
232 | if self.world.options.shuffle_symbols: | ||
233 | for symbol_name in SYMBOL_ITEMS.values(): | ||
234 | self.real_items.append(symbol_name) | ||
235 | |||
234 | def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: | 236 | def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: |
235 | if answer is None: | 237 | if answer is None: |
236 | if panel_id not in self.panel_reqs: | 238 | if panel_id not in self.panel_reqs: |
@@ -253,25 +255,35 @@ class Lingo2PlayerLogic: | |||
253 | self.add_solution_reqs(reqs, answer) | 255 | self.add_solution_reqs(reqs, answer) |
254 | elif len(panel.proxies) > 0: | 256 | elif len(panel.proxies) > 0: |
255 | possibilities = [] | 257 | possibilities = [] |
258 | already_filled = False | ||
256 | 259 | ||
257 | for proxy in panel.proxies: | 260 | for proxy in panel.proxies: |
258 | proxy_reqs = AccessRequirements() | 261 | proxy_reqs = AccessRequirements() |
259 | self.add_solution_reqs(proxy_reqs, proxy.answer) | 262 | self.add_solution_reqs(proxy_reqs, proxy.answer) |
260 | 263 | ||
261 | possibilities.append(proxy_reqs) | 264 | if not proxy_reqs.is_empty(): |
265 | possibilities.append(proxy_reqs) | ||
266 | else: | ||
267 | already_filled = True | ||
268 | break | ||
262 | 269 | ||
263 | if not any(proxy.answer == panel.answer for proxy in panel.proxies): | 270 | if not already_filled and not any(proxy.answer == panel.answer for proxy in panel.proxies): |
264 | proxy_reqs = AccessRequirements() | 271 | proxy_reqs = AccessRequirements() |
265 | self.add_solution_reqs(proxy_reqs, panel.answer) | 272 | self.add_solution_reqs(proxy_reqs, panel.answer) |
266 | 273 | ||
267 | possibilities.append(proxy_reqs) | 274 | if not proxy_reqs.is_empty(): |
275 | possibilities.append(proxy_reqs) | ||
276 | else: | ||
277 | already_filled = True | ||
268 | 278 | ||
269 | reqs.or_logic.append(possibilities) | 279 | if not already_filled: |
280 | reqs.or_logic.append(possibilities) | ||
270 | else: | 281 | else: |
271 | self.add_solution_reqs(reqs, panel.answer) | 282 | self.add_solution_reqs(reqs, panel.answer) |
272 | 283 | ||
273 | for symbol in panel.symbols: | 284 | if self.world.options.shuffle_symbols: |
274 | reqs.symbols.add(symbol) | 285 | for symbol in panel.symbols: |
286 | reqs.items.add(SYMBOL_ITEMS.get(symbol)) | ||
275 | 287 | ||
276 | if panel.HasField("required_door"): | 288 | if panel.HasField("required_door"): |
277 | door_reqs = self.get_door_open_reqs(panel.required_door) | 289 | door_reqs = self.get_door_open_reqs(panel.required_door) |
@@ -300,9 +312,16 @@ class Lingo2PlayerLogic: | |||
300 | panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) | 312 | panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) |
301 | reqs.merge(panel_reqs) | 313 | reqs.merge(panel_reqs) |
302 | elif door.complete_at == 1: | 314 | elif door.complete_at == 1: |
303 | reqs.or_logic.append([self.get_panel_reqs(proxy.panel, | 315 | disjunction = [] |
304 | proxy.answer if proxy.HasField("answer") else None) | 316 | for proxy in door.panels: |
305 | for proxy in door.panels]) | 317 | proxy_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) |
318 | if proxy_reqs.is_empty(): | ||
319 | disjunction.clear() | ||
320 | break | ||
321 | else: | ||
322 | disjunction.append(proxy_reqs) | ||
323 | if len(disjunction) > 0: | ||
324 | reqs.or_logic.append(disjunction) | ||
306 | else: | 325 | else: |
307 | # TODO: Handle complete_at > 1 | 326 | # TODO: Handle complete_at > 1 |
308 | pass | 327 | pass |
@@ -316,7 +335,8 @@ class Lingo2PlayerLogic: | |||
316 | if self.world.options.cyan_door_behavior == CyanDoorBehavior.option_collect_h2: | 335 | if self.world.options.cyan_door_behavior == CyanDoorBehavior.option_collect_h2: |
317 | reqs.rooms.add("The Repetitive - Main Room") | 336 | reqs.rooms.add("The Repetitive - Main Room") |
318 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_any_double_letter: | 337 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_any_double_letter: |
319 | reqs.cyans = True | 338 | if self.world.options.shuffle_letters != ShuffleLetters.option_unlocked: |
339 | reqs.cyans = True | ||
320 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_item: | 340 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_item: |
321 | # There shouldn't be any locations that are cyan doors. | 341 | # There shouldn't be any locations that are cyan doors. |
322 | pass | 342 | pass |
@@ -335,7 +355,11 @@ class Lingo2PlayerLogic: | |||
335 | 355 | ||
336 | for ending_id in door.endings: | 356 | for ending_id in door.endings: |
337 | ending = self.world.static_logic.objects.endings[ending_id] | 357 | ending = self.world.static_logic.objects.endings[ending_id] |
338 | reqs.items.add(f"{ending.name.capitalize()} Ending (Achieved)") | 358 | |
359 | if self.world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: | ||
360 | reqs.items.add("Victory") | ||
361 | else: | ||
362 | reqs.items.add(f"{ending.name.capitalize()} Ending (Achieved)") | ||
339 | 363 | ||
340 | for sub_door_id in door.doors: | 364 | for sub_door_id in door.doors: |
341 | sub_reqs = self.get_door_open_reqs(sub_door_id) | 365 | sub_reqs = self.get_door_open_reqs(sub_door_id) |