diff options
Diffstat (limited to 'apworld/context.py')
-rw-r--r-- | apworld/context.py | 469 |
1 files changed, 368 insertions, 101 deletions
diff --git a/apworld/context.py b/apworld/context.py index 05f75a3..4a85868 100644 --- a/apworld/context.py +++ b/apworld/context.py | |||
@@ -8,37 +8,106 @@ import websockets | |||
8 | 8 | ||
9 | import Utils | 9 | import Utils |
10 | import settings | 10 | import settings |
11 | from BaseClasses import ItemClassification | ||
11 | from CommonClient import CommonContext, server_loop, gui_enabled, logger, get_base_parser, handle_url_arg | 12 | from CommonClient import CommonContext, server_loop, gui_enabled, logger, get_base_parser, handle_url_arg |
12 | from NetUtils import Endpoint, decode, encode | 13 | from NetUtils import Endpoint, decode, encode |
13 | from Utils import async_start | 14 | from Utils import async_start |
15 | from . import Lingo2World | ||
16 | from .tracker import Tracker | ||
14 | 17 | ||
15 | PORT = 43182 | 18 | ALL_LETTERS = "abcdefghijklmnopqrstuvwxyz" |
16 | MESSAGE_MAX_SIZE = 16*1024*1024 | 19 | MESSAGE_MAX_SIZE = 16*1024*1024 |
20 | PORT = 43182 | ||
21 | |||
22 | KEY_STORAGE_MAPPING = { | ||
23 | "a": (1, 0), "b": (1, 1), "c": (1, 2), "d": (1, 3), "e": (1, 4), "f": (1, 5), "g": (1, 6), "h": (1, 7), "i": (1, 8), | ||
24 | "j": (1, 9), "k": (1, 10), "l": (1, 11), "m": (1, 12), "n": (2, 0), "o": (2, 1), "p": (2, 2), "q": (2, 3), | ||
25 | "r": (2, 4), "s": (2, 5), "t": (2, 6), "u": (2, 7), "v": (2, 8), "w": (2, 9), "x": (2, 10), "y": (2, 11), | ||
26 | "z": (2, 12), | ||
27 | } | ||
28 | |||
29 | REVERSE_KEY_STORAGE_MAPPING = {t: k for k, t in KEY_STORAGE_MAPPING.items()} | ||
30 | |||
31 | |||
32 | class Lingo2Manager: | ||
33 | game_ctx: "Lingo2GameContext" | ||
34 | client_ctx: "Lingo2ClientContext" | ||
35 | tracker: Tracker | ||
36 | |||
37 | keyboard: dict[str, int] | ||
38 | worldports: set[int] | ||
39 | |||
40 | def __init__(self, game_ctx: "Lingo2GameContext", client_ctx: "Lingo2ClientContext"): | ||
41 | self.game_ctx = game_ctx | ||
42 | self.game_ctx.manager = self | ||
43 | self.client_ctx = client_ctx | ||
44 | self.client_ctx.manager = self | ||
45 | self.tracker = Tracker(self) | ||
46 | self.keyboard = {} | ||
47 | self.worldports = set() | ||
48 | |||
49 | self.reset() | ||
50 | |||
51 | def reset(self): | ||
52 | for k in ALL_LETTERS: | ||
53 | self.keyboard[k] = 0 | ||
54 | |||
55 | self.worldports = set() | ||
56 | |||
57 | def update_keyboard(self, new_keyboard: dict[str, int]) -> dict[str, int]: | ||
58 | ret: dict[str, int] = {} | ||
59 | |||
60 | for k, v in new_keyboard.items(): | ||
61 | if v > self.keyboard.get(k, 0): | ||
62 | self.keyboard[k] = v | ||
63 | ret[k] = v | ||
64 | |||
65 | if len(ret) > 0: | ||
66 | self.tracker.refresh_state() | ||
67 | self.game_ctx.send_accessible_locations() | ||
68 | |||
69 | return ret | ||
70 | |||
71 | def update_worldports(self, new_worldports: set[int]) -> set[int]: | ||
72 | ret = new_worldports.difference(self.worldports) | ||
73 | self.worldports.update(new_worldports) | ||
74 | |||
75 | if len(ret) > 0: | ||
76 | self.tracker.refresh_state() | ||
77 | self.game_ctx.send_accessible_locations() | ||
78 | |||
79 | return ret | ||
17 | 80 | ||
18 | 81 | ||
19 | class Lingo2GameContext: | 82 | class Lingo2GameContext: |
20 | server: Endpoint | None | 83 | server: Endpoint | None |
21 | client: "Lingo2ClientContext" | 84 | manager: Lingo2Manager |
22 | 85 | ||
23 | def __init__(self): | 86 | def __init__(self): |
24 | self.server = None | 87 | self.server = None |
25 | 88 | ||
26 | def send_connected(self): | 89 | def send_connected(self): |
90 | if self.server is None: | ||
91 | return | ||
92 | |||
27 | msg = { | 93 | msg = { |
28 | "cmd": "Connected", | 94 | "cmd": "Connected", |
29 | "user": self.client.username, | 95 | "user": self.manager.client_ctx.username, |
30 | "seed_name": self.client.seed_name, | 96 | "seed_name": self.manager.client_ctx.seed_name, |
31 | "version": self.client.server_version, | 97 | "version": self.manager.client_ctx.server_version, |
32 | "generator_version": self.client.generator_version, | 98 | "generator_version": self.manager.client_ctx.generator_version, |
33 | "team": self.client.team, | 99 | "team": self.manager.client_ctx.team, |
34 | "slot": self.client.slot, | 100 | "slot": self.manager.client_ctx.slot, |
35 | "checked_locations": self.client.checked_locations, | 101 | "checked_locations": self.manager.client_ctx.checked_locations, |
36 | "slot_data": self.client.slot_data, | 102 | "slot_data": self.manager.client_ctx.slot_data, |
37 | } | 103 | } |
38 | 104 | ||
39 | async_start(self.send_msgs([msg]), name="game Connected") | 105 | async_start(self.send_msgs([msg]), name="game Connected") |
40 | 106 | ||
41 | def send_item_sent_notification(self, item_name, receiver_name, item_flags): | 107 | def send_item_sent_notification(self, item_name, receiver_name, item_flags): |
108 | if self.server is None: | ||
109 | return | ||
110 | |||
42 | msg = { | 111 | msg = { |
43 | "cmd": "ItemSentNotif", | 112 | "cmd": "ItemSentNotif", |
44 | "item_name": item_name, | 113 | "item_name": item_name, |
@@ -49,6 +118,9 @@ class Lingo2GameContext: | |||
49 | async_start(self.send_msgs([msg]), name="item sent notif") | 118 | async_start(self.send_msgs([msg]), name="item sent notif") |
50 | 119 | ||
51 | def send_hint_received(self, item_name, location_name, receiver_name, item_flags, for_self): | 120 | def send_hint_received(self, item_name, location_name, receiver_name, item_flags, for_self): |
121 | if self.server is None: | ||
122 | return | ||
123 | |||
52 | msg = { | 124 | msg = { |
53 | "cmd": "HintReceived", | 125 | "cmd": "HintReceived", |
54 | "item_name": item_name, | 126 | "item_name": item_name, |
@@ -61,6 +133,9 @@ class Lingo2GameContext: | |||
61 | async_start(self.send_msgs([msg]), name="hint received notif") | 133 | async_start(self.send_msgs([msg]), name="hint received notif") |
62 | 134 | ||
63 | def send_item_received(self, items): | 135 | def send_item_received(self, items): |
136 | if self.server is None: | ||
137 | return | ||
138 | |||
64 | msg = { | 139 | msg = { |
65 | "cmd": "ItemReceived", | 140 | "cmd": "ItemReceived", |
66 | "items": items, | 141 | "items": items, |
@@ -69,6 +144,9 @@ class Lingo2GameContext: | |||
69 | async_start(self.send_msgs([msg]), name="item received") | 144 | async_start(self.send_msgs([msg]), name="item received") |
70 | 145 | ||
71 | def send_location_info(self, locations): | 146 | def send_location_info(self, locations): |
147 | if self.server is None: | ||
148 | return | ||
149 | |||
72 | msg = { | 150 | msg = { |
73 | "cmd": "LocationInfo", | 151 | "cmd": "LocationInfo", |
74 | "locations": locations, | 152 | "locations": locations, |
@@ -77,6 +155,9 @@ class Lingo2GameContext: | |||
77 | async_start(self.send_msgs([msg]), name="location info") | 155 | async_start(self.send_msgs([msg]), name="location info") |
78 | 156 | ||
79 | def send_text_message(self, parts): | 157 | def send_text_message(self, parts): |
158 | if self.server is None: | ||
159 | return | ||
160 | |||
80 | msg = { | 161 | msg = { |
81 | "cmd": "TextMessage", | 162 | "cmd": "TextMessage", |
82 | "data": parts, | 163 | "data": parts, |
@@ -84,6 +165,53 @@ class Lingo2GameContext: | |||
84 | 165 | ||
85 | async_start(self.send_msgs([msg]), name="notif") | 166 | async_start(self.send_msgs([msg]), name="notif") |
86 | 167 | ||
168 | def send_accessible_locations(self): | ||
169 | if self.server is None: | ||
170 | return | ||
171 | |||
172 | msg = { | ||
173 | "cmd": "AccessibleLocations", | ||
174 | "locations": list(self.manager.tracker.accessible_locations), | ||
175 | } | ||
176 | |||
177 | if len(self.manager.tracker.accessible_worldports) > 0: | ||
178 | msg["worldports"] = list(self.manager.tracker.accessible_worldports) | ||
179 | |||
180 | async_start(self.send_msgs([msg]), name="accessible locations") | ||
181 | |||
182 | def send_update_locations(self, locations): | ||
183 | if self.server is None: | ||
184 | return | ||
185 | |||
186 | msg = { | ||
187 | "cmd": "UpdateLocations", | ||
188 | "locations": locations, | ||
189 | } | ||
190 | |||
191 | async_start(self.send_msgs([msg]), name="update locations") | ||
192 | |||
193 | def send_update_keyboard(self, updates): | ||
194 | if self.server is None: | ||
195 | return | ||
196 | |||
197 | msg = { | ||
198 | "cmd": "UpdateKeyboard", | ||
199 | "updates": updates, | ||
200 | } | ||
201 | |||
202 | async_start(self.send_msgs([msg]), name="update keyboard") | ||
203 | |||
204 | def send_update_worldports(self, worldports): | ||
205 | if self.server is None: | ||
206 | return | ||
207 | |||
208 | msg = { | ||
209 | "cmd": "UpdateWorldports", | ||
210 | "worldports": worldports, | ||
211 | } | ||
212 | |||
213 | async_start(self.send_msgs([msg]), name="update worldports") | ||
214 | |||
87 | async def send_msgs(self, msgs: list[Any]) -> None: | 215 | async def send_msgs(self, msgs: list[Any]) -> None: |
88 | """ `msgs` JSON serializable """ | 216 | """ `msgs` JSON serializable """ |
89 | if not self.server or not self.server.socket.open or self.server.socket.closed: | 217 | if not self.server or not self.server.socket.open or self.server.socket.closed: |
@@ -92,7 +220,7 @@ class Lingo2GameContext: | |||
92 | 220 | ||
93 | 221 | ||
94 | class Lingo2ClientContext(CommonContext): | 222 | class Lingo2ClientContext(CommonContext): |
95 | game_ctx: Lingo2GameContext | 223 | manager: Lingo2Manager |
96 | 224 | ||
97 | game = "Lingo 2" | 225 | game = "Lingo 2" |
98 | items_handling = 0b111 | 226 | items_handling = 0b111 |
@@ -117,104 +245,226 @@ class Lingo2ClientContext(CommonContext): | |||
117 | elif cmd == "Connected": | 245 | elif cmd == "Connected": |
118 | self.slot_data = args.get("slot_data", None) | 246 | self.slot_data = args.get("slot_data", None) |
119 | 247 | ||
120 | if self.game_ctx.server is not None: | 248 | self.manager.reset() |
121 | self.game_ctx.send_connected() | 249 | |
250 | self.manager.game_ctx.send_connected() | ||
251 | |||
252 | self.manager.tracker.setup_slot(self.slot_data) | ||
253 | self.manager.tracker.set_checked_locations(self.checked_locations) | ||
254 | self.manager.game_ctx.send_accessible_locations() | ||
255 | |||
256 | self.set_notify(self.get_datastorage_key("keyboard1"), self.get_datastorage_key("keyboard2")) | ||
257 | msg_batch = [{ | ||
258 | "cmd": "Set", | ||
259 | "key": self.get_datastorage_key("keyboard1"), | ||
260 | "default": 0, | ||
261 | "want_reply": True, | ||
262 | "operations": [{"operation": "default", "value": 0}] | ||
263 | }, { | ||
264 | "cmd": "Set", | ||
265 | "key": self.get_datastorage_key("keyboard2"), | ||
266 | "default": 0, | ||
267 | "want_reply": True, | ||
268 | "operations": [{"operation": "default", "value": 0}] | ||
269 | }] | ||
270 | |||
271 | if self.slot_data["shuffle_worldports"]: | ||
272 | self.set_notify(self.get_datastorage_key("worldports")) | ||
273 | msg_batch.append({ | ||
274 | "cmd": "Set", | ||
275 | "key": self.get_datastorage_key("worldports"), | ||
276 | "default": [], | ||
277 | "want_reply": True, | ||
278 | "operations": [{"operation": "default", "value": []}] | ||
279 | }) | ||
280 | |||
281 | async_start(self.send_msgs(msg_batch), name="default keys") | ||
282 | elif cmd == "RoomUpdate": | ||
283 | self.manager.tracker.set_checked_locations(self.checked_locations) | ||
284 | self.manager.game_ctx.send_update_locations(args["checked_locations"]) | ||
122 | elif cmd == "ReceivedItems": | 285 | elif cmd == "ReceivedItems": |
123 | if self.game_ctx.server is not None: | 286 | self.manager.tracker.set_collected_items(self.items_received) |
124 | cur_index = 0 | ||
125 | items = [] | ||
126 | 287 | ||
127 | for item in args["items"]: | 288 | cur_index = 0 |
128 | index = cur_index + args["index"] | 289 | items = [] |
129 | cur_index += 1 | ||
130 | 290 | ||
131 | item_msg = { | 291 | for item in args["items"]: |
132 | "id": item.item, | 292 | index = cur_index + args["index"] |
133 | "index": index, | 293 | cur_index += 1 |
134 | "flags": item.flags, | ||
135 | "text": self.item_names.lookup_in_slot(item.item, self.slot), | ||
136 | } | ||
137 | 294 | ||
138 | if item.player != self.slot: | 295 | item_msg = { |
139 | item_msg["sender"] = self.player_names.get(item.player) | 296 | "id": item.item, |
297 | "index": index, | ||
298 | "flags": item.flags, | ||
299 | "text": self.item_names.lookup_in_slot(item.item, self.slot), | ||
300 | } | ||
140 | 301 | ||
141 | items.append(item_msg) | 302 | if item.player != self.slot: |
303 | item_msg["sender"] = self.player_names.get(item.player) | ||
142 | 304 | ||
143 | self.game_ctx.send_item_received(items) | 305 | items.append(item_msg) |
144 | elif cmd == "PrintJSON": | ||
145 | if self.game_ctx.server is not None: | ||
146 | if "receiving" in args and "item" in args and args["item"].player == self.slot: | ||
147 | item_name = self.item_names.lookup_in_slot(args["item"].item, args["receiving"]) | ||
148 | location_name = self.location_names.lookup_in_slot(args["item"].location, args["item"].player) | ||
149 | receiver_name = self.player_names.get(args["receiving"]) | ||
150 | |||
151 | if args["type"] == "Hint" and not args.get("found", False): | ||
152 | self.game_ctx.send_hint_received(item_name, location_name, receiver_name, args["item"].flags, | ||
153 | int(args["receiving"]) == self.slot) | ||
154 | elif args["receiving"] != self.slot: | ||
155 | self.game_ctx.send_item_sent_notification(item_name, receiver_name, args["item"].flags) | ||
156 | |||
157 | parts = [] | ||
158 | for message_part in args["data"]: | ||
159 | if "type" not in message_part and "text" in message_part: | ||
160 | parts.append({"type": "text", "text": message_part["text"]}) | ||
161 | elif message_part["type"] == "player_id": | ||
162 | parts.append({ | ||
163 | "type": "player", | ||
164 | "text": self.player_names.get(int(message_part["text"])), | ||
165 | "self": int(int(message_part["text"]) == self.slot), | ||
166 | }) | ||
167 | elif message_part["type"] == "item_id": | ||
168 | parts.append({ | ||
169 | "type": "item", | ||
170 | "text": self.item_names.lookup_in_slot(int(message_part["text"]), message_part["player"]), | ||
171 | "flags": message_part["flags"], | ||
172 | }) | ||
173 | elif message_part["type"] == "location_id": | ||
174 | parts.append({ | ||
175 | "type": "location", | ||
176 | "text": self.location_names.lookup_in_slot(int(message_part["text"]), | ||
177 | message_part["player"]) | ||
178 | }) | ||
179 | elif "text" in message_part: | ||
180 | parts.append({"type": "text", "text": message_part["text"]}) | ||
181 | |||
182 | self.game_ctx.send_text_message(parts) | ||
183 | elif cmd == "LocationInfo": | ||
184 | if self.game_ctx.server is not None: | ||
185 | locations = [] | ||
186 | |||
187 | for location in args["locations"]: | ||
188 | locations.append({ | ||
189 | "id": location.location, | ||
190 | "item": self.item_names.lookup_in_slot(location.item, location.player), | ||
191 | "player": self.player_names.get(location.player), | ||
192 | "flags": location.flags, | ||
193 | "self": int(location.player) == self.slot, | ||
194 | }) | ||
195 | 306 | ||
196 | self.game_ctx.send_location_info(locations) | 307 | self.manager.game_ctx.send_item_received(items) |
197 | 308 | ||
309 | if any(ItemClassification.progression in ItemClassification(item.flags) for item in args["items"]): | ||
310 | self.manager.game_ctx.send_accessible_locations() | ||
311 | elif cmd == "PrintJSON": | ||
312 | if "receiving" in args and "item" in args and args["item"].player == self.slot: | ||
313 | item_name = self.item_names.lookup_in_slot(args["item"].item, args["receiving"]) | ||
314 | location_name = self.location_names.lookup_in_slot(args["item"].location, args["item"].player) | ||
315 | receiver_name = self.player_names.get(args["receiving"]) | ||
316 | |||
317 | if args["type"] == "Hint" and not args.get("found", False): | ||
318 | self.manager.game_ctx.send_hint_received(item_name, location_name, receiver_name, args["item"].flags, | ||
319 | int(args["receiving"]) == self.slot) | ||
320 | elif args["receiving"] != self.slot: | ||
321 | self.manager.game_ctx.send_item_sent_notification(item_name, receiver_name, args["item"].flags) | ||
322 | |||
323 | parts = [] | ||
324 | for message_part in args["data"]: | ||
325 | if "type" not in message_part and "text" in message_part: | ||
326 | parts.append({"type": "text", "text": message_part["text"]}) | ||
327 | elif message_part["type"] == "player_id": | ||
328 | parts.append({ | ||
329 | "type": "player", | ||
330 | "text": self.player_names.get(int(message_part["text"])), | ||
331 | "self": int(int(message_part["text"]) == self.slot), | ||
332 | }) | ||
333 | elif message_part["type"] == "item_id": | ||
334 | parts.append({ | ||
335 | "type": "item", | ||
336 | "text": self.item_names.lookup_in_slot(int(message_part["text"]), message_part["player"]), | ||
337 | "flags": message_part["flags"], | ||
338 | }) | ||
339 | elif message_part["type"] == "location_id": | ||
340 | parts.append({ | ||
341 | "type": "location", | ||
342 | "text": self.location_names.lookup_in_slot(int(message_part["text"]), | ||
343 | message_part["player"]) | ||
344 | }) | ||
345 | elif "text" in message_part: | ||
346 | parts.append({"type": "text", "text": message_part["text"]}) | ||
198 | 347 | ||
199 | async def pipe_loop(ctx: Lingo2GameContext): | 348 | self.manager.game_ctx.send_text_message(parts) |
200 | while not ctx.client.exit_event.is_set(): | 349 | elif cmd == "LocationInfo": |
350 | locations = [] | ||
351 | |||
352 | for location in args["locations"]: | ||
353 | locations.append({ | ||
354 | "id": location.location, | ||
355 | "item": self.item_names.lookup_in_slot(location.item, location.player), | ||
356 | "player": self.player_names.get(location.player), | ||
357 | "flags": location.flags, | ||
358 | "self": int(location.player) == self.slot, | ||
359 | }) | ||
360 | |||
361 | self.manager.game_ctx.send_location_info(locations) | ||
362 | elif cmd == "SetReply": | ||
363 | if args["key"] == self.get_datastorage_key("keyboard1"): | ||
364 | self.handle_keyboard_update(1, args) | ||
365 | elif args["key"] == self.get_datastorage_key("keyboard2"): | ||
366 | self.handle_keyboard_update(2, args) | ||
367 | elif args["key"] == self.get_datastorage_key("worldports"): | ||
368 | updates = self.manager.update_worldports(set(args["value"])) | ||
369 | if len(updates) > 0: | ||
370 | self.manager.game_ctx.send_update_worldports(updates) | ||
371 | |||
372 | def get_datastorage_key(self, name: str): | ||
373 | return f"Lingo2_{self.slot}_{name}" | ||
374 | |||
375 | async def update_keyboard(self, updates: dict[str, int]): | ||
376 | kb1 = 0 | ||
377 | kb2 = 0 | ||
378 | |||
379 | for k, v in updates.items(): | ||
380 | if v == 0: | ||
381 | continue | ||
382 | |||
383 | effect = 0 | ||
384 | if v >= 1: | ||
385 | effect |= 1 | ||
386 | if v == 2: | ||
387 | effect |= 2 | ||
388 | |||
389 | pos = KEY_STORAGE_MAPPING[k] | ||
390 | if pos[0] == 1: | ||
391 | kb1 |= (effect << pos[1] * 2) | ||
392 | else: | ||
393 | kb2 |= (effect << pos[1] * 2) | ||
394 | |||
395 | msgs = [] | ||
396 | |||
397 | if kb1 != 0: | ||
398 | msgs.append({ | ||
399 | "cmd": "Set", | ||
400 | "key": self.get_datastorage_key("keyboard1"), | ||
401 | "want_reply": True, | ||
402 | "operations": [{ | ||
403 | "operation": "or", | ||
404 | "value": kb1 | ||
405 | }] | ||
406 | }) | ||
407 | |||
408 | if kb2 != 0: | ||
409 | msgs.append({ | ||
410 | "cmd": "Set", | ||
411 | "key": self.get_datastorage_key("keyboard2"), | ||
412 | "want_reply": True, | ||
413 | "operations": [{ | ||
414 | "operation": "or", | ||
415 | "value": kb2 | ||
416 | }] | ||
417 | }) | ||
418 | |||
419 | if len(msgs) > 0: | ||
420 | await self.send_msgs(msgs) | ||
421 | |||
422 | def handle_keyboard_update(self, field: int, args: dict[str, Any]): | ||
423 | keys = {} | ||
424 | value = args["value"] | ||
425 | |||
426 | for i in range(0, 13): | ||
427 | if (value & (1 << (i * 2))) != 0: | ||
428 | keys[REVERSE_KEY_STORAGE_MAPPING[(field, i)]] = 1 | ||
429 | if (value & (1 << (i * 2 + 1))) != 0: | ||
430 | keys[REVERSE_KEY_STORAGE_MAPPING[(field, i)]] = 2 | ||
431 | |||
432 | updates = self.manager.update_keyboard(keys) | ||
433 | if len(updates) > 0: | ||
434 | self.manager.game_ctx.send_update_keyboard(updates) | ||
435 | |||
436 | async def update_worldports(self, updates: set[int]): | ||
437 | await self.send_msgs([{ | ||
438 | "cmd": "Set", | ||
439 | "key": self.get_datastorage_key("worldports"), | ||
440 | "want_reply": True, | ||
441 | "operations": [{ | ||
442 | "operation": "update", | ||
443 | "value": updates | ||
444 | }] | ||
445 | }]) | ||
446 | |||
447 | |||
448 | async def pipe_loop(manager: Lingo2Manager): | ||
449 | while not manager.client_ctx.exit_event.is_set(): | ||
201 | try: | 450 | try: |
202 | socket = await websockets.connect("ws://localhost", port=PORT, ping_timeout=None, ping_interval=None, | 451 | socket = await websockets.connect("ws://localhost", port=PORT, ping_timeout=None, ping_interval=None, |
203 | max_size=MESSAGE_MAX_SIZE) | 452 | max_size=MESSAGE_MAX_SIZE) |
204 | ctx.server = Endpoint(socket) | 453 | manager.game_ctx.server = Endpoint(socket) |
205 | logger.info("Connected to Lingo 2!") | 454 | logger.info("Connected to Lingo 2!") |
206 | if ctx.client.auth is not None: | 455 | if manager.client_ctx.auth is not None: |
207 | ctx.send_connected() | 456 | manager.game_ctx.send_connected() |
208 | async for data in ctx.server.socket: | 457 | manager.game_ctx.send_accessible_locations() |
458 | async for data in manager.game_ctx.server.socket: | ||
209 | for msg in decode(data): | 459 | for msg in decode(data): |
210 | await process_game_cmd(ctx, msg) | 460 | await process_game_cmd(manager, msg) |
211 | except ConnectionRefusedError: | 461 | except ConnectionRefusedError: |
212 | logger.info("Could not connect to Lingo 2.") | 462 | logger.info("Could not connect to Lingo 2.") |
213 | finally: | 463 | finally: |
214 | ctx.server = None | 464 | manager.game_ctx.server = None |
215 | 465 | ||
216 | 466 | ||
217 | async def process_game_cmd(ctx: Lingo2GameContext, args: dict): | 467 | async def process_game_cmd(manager: Lingo2Manager, args: dict): |
218 | cmd = args["cmd"] | 468 | cmd = args["cmd"] |
219 | 469 | ||
220 | if cmd == "Connect": | 470 | if cmd == "Connect": |
@@ -227,20 +477,38 @@ async def process_game_cmd(ctx: Lingo2GameContext, args: dict): | |||
227 | else: | 477 | else: |
228 | server_address = f"{player}:None@{server}" | 478 | server_address = f"{player}:None@{server}" |
229 | 479 | ||
230 | async_start(ctx.client.connect(server_address), name="client connect") | 480 | async_start(manager.client_ctx.connect(server_address), name="client connect") |
231 | elif cmd == "Disconnect": | 481 | elif cmd == "Disconnect": |
232 | async_start(ctx.client.disconnect(), name="client disconnect") | 482 | async_start(manager.client_ctx.disconnect(), name="client disconnect") |
233 | elif cmd in ["Sync", "LocationChecks", "Say", "StatusUpdate", "LocationScouts"]: | 483 | elif cmd in ["Sync", "LocationChecks", "Say", "StatusUpdate", "LocationScouts"]: |
234 | async_start(ctx.client.send_msgs([args]), name="client forward") | 484 | async_start(manager.client_ctx.send_msgs([args]), name="client forward") |
485 | elif cmd == "UpdateKeyboard": | ||
486 | updates = manager.update_keyboard(args["keyboard"]) | ||
487 | if len(updates) > 0: | ||
488 | async_start(manager.client_ctx.update_keyboard(updates), name="client update keyboard") | ||
489 | elif cmd == "CheckWorldport": | ||
490 | port_id = args["port_id"] | ||
491 | worldports = {port_id} | ||
492 | if str(port_id) in manager.client_ctx.slot_data["port_pairings"]: | ||
493 | worldports.add(manager.client_ctx.slot_data["port_pairings"][str(port_id)]) | ||
494 | |||
495 | updates = manager.update_worldports(worldports) | ||
496 | if len(updates) > 0: | ||
497 | async_start(manager.client_ctx.update_worldports(updates), name="client update worldports") | ||
498 | elif cmd == "Quit": | ||
499 | manager.client_ctx.exit_event.set() | ||
235 | 500 | ||
236 | 501 | ||
237 | async def run_game(): | 502 | async def run_game(): |
238 | exe_file = settings.get_settings().lingo2_options.exe_file | 503 | exe_file = settings.get_settings().lingo2_options.exe_file |
239 | 504 | ||
240 | from worlds import AutoWorldRegister | 505 | # This ensures we can use Steam features without having to open the game |
241 | world = AutoWorldRegister.world_types["Lingo 2"] | 506 | # through steam. |
507 | steam_appid_path = os.path.join(os.path.dirname(exe_file), "steam_appid.txt") | ||
508 | with open(steam_appid_path, "w") as said_handle: | ||
509 | said_handle.write("2523310") | ||
242 | 510 | ||
243 | if world.zip_path is not None: | 511 | if Lingo2World.zip_path is not None: |
244 | # This is a packaged apworld. | 512 | # This is a packaged apworld. |
245 | init_scene = pkgutil.get_data(__name__, "client/run_from_apworld.tscn") | 513 | init_scene = pkgutil.get_data(__name__, "client/run_from_apworld.tscn") |
246 | init_path = Utils.local_path("data", "lingo2_init.tscn") | 514 | init_path = Utils.local_path("data", "lingo2_init.tscn") |
@@ -254,7 +522,7 @@ async def run_game(): | |||
254 | "--scene", | 522 | "--scene", |
255 | init_path, | 523 | init_path, |
256 | "--", | 524 | "--", |
257 | str(world.zip_path.absolute()), | 525 | str(Lingo2World.zip_path.absolute()), |
258 | ], | 526 | ], |
259 | cwd=os.path.dirname(exe_file), | 527 | cwd=os.path.dirname(exe_file), |
260 | ) | 528 | ) |
@@ -278,9 +546,7 @@ def client_main(*launch_args: str) -> None: | |||
278 | 546 | ||
279 | client_ctx = Lingo2ClientContext(args.connect, args.password) | 547 | client_ctx = Lingo2ClientContext(args.connect, args.password) |
280 | game_ctx = Lingo2GameContext() | 548 | game_ctx = Lingo2GameContext() |
281 | 549 | manager = Lingo2Manager(game_ctx, client_ctx) | |
282 | client_ctx.game_ctx = game_ctx | ||
283 | game_ctx.client = client_ctx | ||
284 | 550 | ||
285 | client_ctx.server_task = asyncio.create_task(server_loop(client_ctx), name="ServerLoop") | 551 | client_ctx.server_task = asyncio.create_task(server_loop(client_ctx), name="ServerLoop") |
286 | 552 | ||
@@ -288,7 +554,7 @@ def client_main(*launch_args: str) -> None: | |||
288 | client_ctx.run_gui() | 554 | client_ctx.run_gui() |
289 | client_ctx.run_cli() | 555 | client_ctx.run_cli() |
290 | 556 | ||
291 | pipe_task = asyncio.create_task(pipe_loop(game_ctx), name="GameWatcher") | 557 | pipe_task = asyncio.create_task(pipe_loop(manager), name="GameWatcher") |
292 | 558 | ||
293 | try: | 559 | try: |
294 | await pipe_task | 560 | await pipe_task |
@@ -296,6 +562,7 @@ def client_main(*launch_args: str) -> None: | |||
296 | logger.exception(e) | 562 | logger.exception(e) |
297 | 563 | ||
298 | await client_ctx.exit_event.wait() | 564 | await client_ctx.exit_event.wait() |
565 | client_ctx.ui.stop() | ||
299 | await client_ctx.shutdown() | 566 | await client_ctx.shutdown() |
300 | 567 | ||
301 | Utils.init_logging("Lingo2Client", exception_logger="Client") | 568 | Utils.init_logging("Lingo2Client", exception_logger="Client") |