about summary refs log tree commit diff stats
path: root/apworld/player_logic.py
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2025-09-09 16:44:09 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2025-09-09 16:44:09 -0400
commit8de745f4d3350ac848c9362a33e223c0ff94fdcf (patch)
tree102db82fd9c7822dd6c0d6e9a555181faf9ba00e /apworld/player_logic.py
parent6dc4fae2ee9b7b9145a8938e95080dba448cf44a (diff)
downloadlingo2-archipelago-8de745f4d3350ac848c9362a33e223c0ff94fdcf.tar.gz
lingo2-archipelago-8de745f4d3350ac848c9362a33e223c0ff94fdcf.tar.bz2
lingo2-archipelago-8de745f4d3350ac848c9362a33e223c0ff94fdcf.zip
Added symbol shuffle
Also fixed unlocked letters + any double letter cyan doors, and tweaked
some logic related to important panels with symbols on them.
Diffstat (limited to 'apworld/player_logic.py')
-rw-r--r--apworld/player_logic.py54
1 files changed, 37 insertions, 17 deletions
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index dbd340c..42b36e6 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -1,6 +1,7 @@
1from enum import IntEnum, auto 1from enum import IntEnum, auto
2 2
3from .generated import data_pb2 as data_pb2 3from .generated import data_pb2 as data_pb2
4from .items import SYMBOL_ITEMS
4from typing import TYPE_CHECKING, NamedTuple 5from typing import TYPE_CHECKING, NamedTuple
5 6
6from .options import VictoryCondition, ShuffleLetters, CyanDoorBehavior 7from .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