From 7544b11c86fd597321a507747fbd0fe1491ccbd8 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 22 Oct 2025 21:58:43 -0400 Subject: Implemented latched doors in the client --- apworld/client/client.gd | 17 +++++++++++++++++ apworld/client/door.gd | 31 ++++++++++++++++++++++++++++++- apworld/client/manager.gd | 15 +++++++++++++++ apworld/context.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 108 insertions(+), 2 deletions(-) diff --git a/apworld/client/client.gd b/apworld/client/client.gd index 9a4b402..ce5ac7e 100644 --- a/apworld/client/client.gd +++ b/apworld/client/client.gd @@ -25,6 +25,7 @@ var _slot_data = {} var _accessible_locations = [] var _accessible_worldports = [] var _goal_accessible = false +var _latched_doors = [] signal could_not_connect signal connect_status @@ -34,6 +35,7 @@ signal location_scout_received(location_id, item_name, player_name, flags, for_s signal text_message_received(message) signal item_sent_notification(message) signal hint_received(message) +signal door_latched(id) signal accessible_locations_updated signal checked_locations_updated signal checked_worldports_updated @@ -189,6 +191,14 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo message["type"], int(message.get("id", null)), message["path"] ) + elif cmd == "UpdateLatches": + for id in message["latches"]: + var iid = int(id) + if not _latched_doors.has(iid): + _latched_doors.append(iid) + + door_latched.emit(iid) + func connectToServer(server, un, pw): sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}]) @@ -255,6 +265,13 @@ func checkWorldport(port_id): sendMessage([{"cmd": "CheckWorldport", "port_id": port_id}]) +func latchDoor(id): + if not _latched_doors.has(id): + _latched_doors.append(id) + + sendMessage([{"cmd": "LatchDoor", "door": id}]) + + func getLogicalPath(object_type, object_id): var msg = {"cmd": "GetPath", "type": object_type} if object_id != null: diff --git a/apworld/client/door.gd b/apworld/client/door.gd index 49f5728..63cfa99 100644 --- a/apworld/client/door.gd +++ b/apworld/client/door.gd @@ -1,7 +1,9 @@ extends "res://scripts/nodes/door.gd" +var door_id var item_id var item_amount +var latched = false func _ready(): @@ -10,7 +12,7 @@ func _ready(): ) var gamedata = global.get_node("Gamedata") - var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) + door_id = gamedata.get_door_for_map_node_path(global.map, node_path) if door_id != null: var ap = global.get_node("Archipelago") var item_lock = ap.get_item_id_for_door(door_id) @@ -27,6 +29,12 @@ func _ready(): self.excludeSenders = [] call_deferred("_readier") + else: + var door_data = gamedata.objects.get_doors()[door_id] + if door_data.has_latch() and door_data.get_latch(): + _check_latched.call_deferred(door_id) + + latched = true if global.map == "the_sun_temple": if name == "spe_EndPlatform" or name == "spe_entry_2": @@ -44,3 +52,24 @@ func _readier(): if ap.client.getItemAmount(item_id) >= item_amount: handleTriggered() + + +func _check_latched(door_id): + var ap = global.get_node("Archipelago") + + if ap.client._latched_doors.has(door_id): + triggered = total + handleTriggered() + + +func handleTriggered(): + super.handleTriggered() + + if latched and ran: + var ap = global.get_node("Archipelago") + ap.client.latchDoor(door_id) + + +func handleUntriggered(): + if not latched or not ran: + super.handleUntriggered() diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index dac09b2..a17bee8 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd @@ -141,6 +141,7 @@ func _ready(): client.accessible_locations_updated.connect(_on_accessible_locations_updated) client.checked_locations_updated.connect(_on_checked_locations_updated) client.checked_worldports_updated.connect(_on_checked_worldports_updated) + client.door_latched.connect(_on_door_latched) client.could_not_connect.connect(_client_could_not_connect) client.connect_status.connect(_client_connect_status) @@ -376,6 +377,20 @@ func _on_checked_worldports_updated(): textclient_node.update_worldports() +func _on_door_latched(door_id): + var gamedata = global.get_node("Gamedata") + if gamedata.get_door_map_name(door_id) != global.map: + return + + var receivers = gamedata.get_door_receivers(door_id) + var scene = get_tree().get_root().get_node_or_null("scene") + if scene != null: + for receiver in receivers: + var rnode = scene.get_node_or_null(receiver) + if rnode != null: + rnode.handleTriggered() + + func _client_could_not_connect(message): could_not_connect.emit(message) diff --git a/apworld/context.py b/apworld/context.py index d59bf9d..7975686 100644 --- a/apworld/context.py +++ b/apworld/context.py @@ -38,6 +38,7 @@ class Lingo2Manager: keyboard: dict[str, int] worldports: set[int] goaled: bool + latches: set[int] def __init__(self, game_ctx: "Lingo2GameContext", client_ctx: "Lingo2ClientContext"): self.game_ctx = game_ctx @@ -47,6 +48,7 @@ class Lingo2Manager: self.tracker = Tracker(self) self.keyboard = {} self.worldports = set() + self.latches = set() self.reset() @@ -56,6 +58,7 @@ class Lingo2Manager: self.worldports = set() self.goaled = False + self.latches = set() def update_keyboard(self, new_keyboard: dict[str, int]) -> dict[str, int]: ret: dict[str, int] = {} @@ -81,6 +84,12 @@ class Lingo2Manager: return ret + def update_latches(self, new_latches: set[int]) -> set[int]: + ret = new_latches.difference(self.latches) + self.latches.update(new_latches) + + return ret + class Lingo2GameContext: server: Endpoint | None @@ -244,6 +253,17 @@ class Lingo2GameContext: async_start(self.send_msgs([msg]), name="path reply") + def send_update_latches(self, latches): + if self.server is None: + return + + msg = { + "cmd": "UpdateLatches", + "latches": latches, + } + + async_start(self.send_msgs([msg]), name="update latches") + async def send_msgs(self, msgs: list[Any]) -> None: """ `msgs` JSON serializable """ if not self.server or not self.server.socket.open or self.server.socket.closed: @@ -298,7 +318,7 @@ class Lingo2ClientContext(CommonContext): self.victory_data_storage_key = f"_read_client_status_{self.team}_{self.slot}" self.set_notify(self.get_datastorage_key("keyboard1"), self.get_datastorage_key("keyboard2"), - self.victory_data_storage_key) + self.victory_data_storage_key, self.get_datastorage_key("latches")) msg_batch = [{ "cmd": "Set", "key": self.get_datastorage_key("keyboard1"), @@ -311,6 +331,12 @@ class Lingo2ClientContext(CommonContext): "default": 0, "want_reply": True, "operations": [{"operation": "default", "value": 0}] + }, { + "cmd": "Set", + "key": self.get_datastorage_key("latches"), + "default": [], + "want_reply": True, + "operations": [{"operation": "default", "value": []}] }] if self.slot_data.get("shuffle_worldports", False): @@ -420,6 +446,10 @@ class Lingo2ClientContext(CommonContext): self.manager.game_ctx.send_update_worldports(updates) elif args["key"] == self.victory_data_storage_key: self.handle_status_update(args["value"]) + elif args["key"] == self.get_datastorage_key("latches"): + updates = self.manager.update_latches(set(args["value"])) + if len(updates) > 0: + self.manager.game_ctx.send_update_latches(updates) def get_datastorage_key(self, name: str): return f"Lingo2_{self.slot}_{name}" @@ -501,6 +531,17 @@ class Lingo2ClientContext(CommonContext): self.manager.tracker.refresh_state() self.manager.game_ctx.send_accessible_locations() + async def update_latches(self, updates: set[int]): + await self.send_msgs([{ + "cmd": "Set", + "key": self.get_datastorage_key("latches"), + "want_reply": True, + "operations": [{ + "operation": "update", + "value": updates + }] + }]) + async def pipe_loop(manager: Lingo2Manager): while not manager.client_ctx.exit_event.is_set(): @@ -571,6 +612,10 @@ async def process_game_cmd(manager: Lingo2Manager, args: dict): path = manager.tracker.get_path_to_goal() manager.game_ctx.send_path_reply(args["type"], args.get("id", None), path) + elif cmd == "LatchDoor": + updates = manager.update_latches({args["door"]}) + if len(updates) > 0: + async_start(manager.client_ctx.update_latches(updates), name="client update latches") elif cmd == "Quit": manager.client_ctx.exit_event.set() -- cgit 1.4.1