diff options
Diffstat (limited to 'apworld/context.py')
-rw-r--r-- | apworld/context.py | 54 |
1 files changed, 51 insertions, 3 deletions
diff --git a/apworld/context.py b/apworld/context.py index a0ee34d..7975686 100644 --- a/apworld/context.py +++ b/apworld/context.py | |||
@@ -38,6 +38,7 @@ class Lingo2Manager: | |||
38 | keyboard: dict[str, int] | 38 | keyboard: dict[str, int] |
39 | worldports: set[int] | 39 | worldports: set[int] |
40 | goaled: bool | 40 | goaled: bool |
41 | latches: set[int] | ||
41 | 42 | ||
42 | def __init__(self, game_ctx: "Lingo2GameContext", client_ctx: "Lingo2ClientContext"): | 43 | def __init__(self, game_ctx: "Lingo2GameContext", client_ctx: "Lingo2ClientContext"): |
43 | self.game_ctx = game_ctx | 44 | self.game_ctx = game_ctx |
@@ -47,6 +48,7 @@ class Lingo2Manager: | |||
47 | self.tracker = Tracker(self) | 48 | self.tracker = Tracker(self) |
48 | self.keyboard = {} | 49 | self.keyboard = {} |
49 | self.worldports = set() | 50 | self.worldports = set() |
51 | self.latches = set() | ||
50 | 52 | ||
51 | self.reset() | 53 | self.reset() |
52 | 54 | ||
@@ -56,6 +58,7 @@ class Lingo2Manager: | |||
56 | 58 | ||
57 | self.worldports = set() | 59 | self.worldports = set() |
58 | self.goaled = False | 60 | self.goaled = False |
61 | self.latches = set() | ||
59 | 62 | ||
60 | def update_keyboard(self, new_keyboard: dict[str, int]) -> dict[str, int]: | 63 | def update_keyboard(self, new_keyboard: dict[str, int]) -> dict[str, int]: |
61 | ret: dict[str, int] = {} | 64 | ret: dict[str, int] = {} |
@@ -81,6 +84,12 @@ class Lingo2Manager: | |||
81 | 84 | ||
82 | return ret | 85 | return ret |
83 | 86 | ||
87 | def update_latches(self, new_latches: set[int]) -> set[int]: | ||
88 | ret = new_latches.difference(self.latches) | ||
89 | self.latches.update(new_latches) | ||
90 | |||
91 | return ret | ||
92 | |||
84 | 93 | ||
85 | class Lingo2GameContext: | 94 | class Lingo2GameContext: |
86 | server: Endpoint | None | 95 | server: Endpoint | None |
@@ -244,6 +253,17 @@ class Lingo2GameContext: | |||
244 | 253 | ||
245 | async_start(self.send_msgs([msg]), name="path reply") | 254 | async_start(self.send_msgs([msg]), name="path reply") |
246 | 255 | ||
256 | def send_update_latches(self, latches): | ||
257 | if self.server is None: | ||
258 | return | ||
259 | |||
260 | msg = { | ||
261 | "cmd": "UpdateLatches", | ||
262 | "latches": latches, | ||
263 | } | ||
264 | |||
265 | async_start(self.send_msgs([msg]), name="update latches") | ||
266 | |||
247 | async def send_msgs(self, msgs: list[Any]) -> None: | 267 | async def send_msgs(self, msgs: list[Any]) -> None: |
248 | """ `msgs` JSON serializable """ | 268 | """ `msgs` JSON serializable """ |
249 | if not self.server or not self.server.socket.open or self.server.socket.closed: | 269 | if not self.server or not self.server.socket.open or self.server.socket.closed: |
@@ -298,7 +318,7 @@ class Lingo2ClientContext(CommonContext): | |||
298 | self.victory_data_storage_key = f"_read_client_status_{self.team}_{self.slot}" | 318 | self.victory_data_storage_key = f"_read_client_status_{self.team}_{self.slot}" |
299 | 319 | ||
300 | self.set_notify(self.get_datastorage_key("keyboard1"), self.get_datastorage_key("keyboard2"), | 320 | self.set_notify(self.get_datastorage_key("keyboard1"), self.get_datastorage_key("keyboard2"), |
301 | self.victory_data_storage_key) | 321 | self.victory_data_storage_key, self.get_datastorage_key("latches")) |
302 | msg_batch = [{ | 322 | msg_batch = [{ |
303 | "cmd": "Set", | 323 | "cmd": "Set", |
304 | "key": self.get_datastorage_key("keyboard1"), | 324 | "key": self.get_datastorage_key("keyboard1"), |
@@ -311,6 +331,12 @@ class Lingo2ClientContext(CommonContext): | |||
311 | "default": 0, | 331 | "default": 0, |
312 | "want_reply": True, | 332 | "want_reply": True, |
313 | "operations": [{"operation": "default", "value": 0}] | 333 | "operations": [{"operation": "default", "value": 0}] |
334 | }, { | ||
335 | "cmd": "Set", | ||
336 | "key": self.get_datastorage_key("latches"), | ||
337 | "default": [], | ||
338 | "want_reply": True, | ||
339 | "operations": [{"operation": "default", "value": []}] | ||
314 | }] | 340 | }] |
315 | 341 | ||
316 | if self.slot_data.get("shuffle_worldports", False): | 342 | if self.slot_data.get("shuffle_worldports", False): |
@@ -420,6 +446,10 @@ class Lingo2ClientContext(CommonContext): | |||
420 | self.manager.game_ctx.send_update_worldports(updates) | 446 | self.manager.game_ctx.send_update_worldports(updates) |
421 | elif args["key"] == self.victory_data_storage_key: | 447 | elif args["key"] == self.victory_data_storage_key: |
422 | self.handle_status_update(args["value"]) | 448 | self.handle_status_update(args["value"]) |
449 | elif args["key"] == self.get_datastorage_key("latches"): | ||
450 | updates = self.manager.update_latches(set(args["value"])) | ||
451 | if len(updates) > 0: | ||
452 | self.manager.game_ctx.send_update_latches(updates) | ||
423 | 453 | ||
424 | def get_datastorage_key(self, name: str): | 454 | def get_datastorage_key(self, name: str): |
425 | return f"Lingo2_{self.slot}_{name}" | 455 | return f"Lingo2_{self.slot}_{name}" |
@@ -501,6 +531,17 @@ class Lingo2ClientContext(CommonContext): | |||
501 | self.manager.tracker.refresh_state() | 531 | self.manager.tracker.refresh_state() |
502 | self.manager.game_ctx.send_accessible_locations() | 532 | self.manager.game_ctx.send_accessible_locations() |
503 | 533 | ||
534 | async def update_latches(self, updates: set[int]): | ||
535 | await self.send_msgs([{ | ||
536 | "cmd": "Set", | ||
537 | "key": self.get_datastorage_key("latches"), | ||
538 | "want_reply": True, | ||
539 | "operations": [{ | ||
540 | "operation": "update", | ||
541 | "value": updates | ||
542 | }] | ||
543 | }]) | ||
544 | |||
504 | 545 | ||
505 | async def pipe_loop(manager: Lingo2Manager): | 546 | async def pipe_loop(manager: Lingo2Manager): |
506 | while not manager.client_ctx.exit_event.is_set(): | 547 | while not manager.client_ctx.exit_event.is_set(): |
@@ -550,8 +591,11 @@ async def process_game_cmd(manager: Lingo2Manager, args: dict): | |||
550 | elif cmd == "CheckWorldport": | 591 | elif cmd == "CheckWorldport": |
551 | port_id = args["port_id"] | 592 | port_id = args["port_id"] |
552 | worldports = {port_id} | 593 | worldports = {port_id} |
553 | if str(port_id) in manager.client_ctx.slot_data["port_pairings"]: | 594 | |
554 | worldports.add(manager.client_ctx.slot_data["port_pairings"][str(port_id)]) | 595 | # Also check the reverse port if it's a two-way connection. |
596 | port_pairings = manager.client_ctx.slot_data["port_pairings"] | ||
597 | if str(port_id) in port_pairings and port_pairings.get(str(port_pairings[str(port_id)]), None) == port_id: | ||
598 | worldports.add(port_pairings[str(port_id)]) | ||
555 | 599 | ||
556 | updates = manager.update_worldports(worldports) | 600 | updates = manager.update_worldports(worldports) |
557 | if len(updates) > 0: | 601 | if len(updates) > 0: |
@@ -568,6 +612,10 @@ async def process_game_cmd(manager: Lingo2Manager, args: dict): | |||
568 | path = manager.tracker.get_path_to_goal() | 612 | path = manager.tracker.get_path_to_goal() |
569 | 613 | ||
570 | manager.game_ctx.send_path_reply(args["type"], args.get("id", None), path) | 614 | manager.game_ctx.send_path_reply(args["type"], args.get("id", None), path) |
615 | elif cmd == "LatchDoor": | ||
616 | updates = manager.update_latches({args["door"]}) | ||
617 | if len(updates) > 0: | ||
618 | async_start(manager.client_ctx.update_latches(updates), name="client update latches") | ||
571 | elif cmd == "Quit": | 619 | elif cmd == "Quit": |
572 | manager.client_ctx.exit_event.set() | 620 | manager.client_ctx.exit_event.set() |
573 | 621 | ||