diff options
| -rw-r--r-- | apworld/client/client.gd | 10 | ||||
| -rw-r--r-- | apworld/client/manager.gd | 7 | ||||
| -rw-r--r-- | apworld/client/textclient.gd | 48 | ||||
| -rw-r--r-- | apworld/context.py | 33 | 
4 files changed, 84 insertions, 14 deletions
| diff --git a/apworld/client/client.gd b/apworld/client/client.gd index 54d3d67..c149482 100644 --- a/apworld/client/client.gd +++ b/apworld/client/client.gd | |||
| @@ -26,6 +26,7 @@ var _accessible_locations = [] | |||
| 26 | var _accessible_worldports = [] | 26 | var _accessible_worldports = [] | 
| 27 | var _goal_accessible = false | 27 | var _goal_accessible = false | 
| 28 | var _latched_doors = [] | 28 | var _latched_doors = [] | 
| 29 | var _hinted_locations = [] | ||
| 29 | 30 | ||
| 30 | signal could_not_connect | 31 | signal could_not_connect | 
| 31 | signal connect_status | 32 | signal connect_status | 
| @@ -41,6 +42,7 @@ signal checked_locations_updated | |||
| 41 | signal ignored_locations_updated(locations) | 42 | signal ignored_locations_updated(locations) | 
| 42 | signal checked_worldports_updated | 43 | signal checked_worldports_updated | 
| 43 | signal keyboard_update_received | 44 | signal keyboard_update_received | 
| 45 | signal hinted_locations_updated | ||
| 44 | 46 | ||
| 45 | 47 | ||
| 46 | func _init(): | 48 | func _init(): | 
| @@ -207,6 +209,14 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo | |||
| 207 | 209 | ||
| 208 | ignored_locations_updated.emit(locs) | 210 | ignored_locations_updated.emit(locs) | 
| 209 | 211 | ||
| 212 | elif cmd == "UpdateHintedLocations": | ||
| 213 | for id in message["locations"]: | ||
| 214 | var iid = int(id) | ||
| 215 | if !_hinted_locations.has(iid): | ||
| 216 | _hinted_locations.append(iid) | ||
| 217 | |||
| 218 | hinted_locations_updated.emit() | ||
| 219 | |||
| 210 | 220 | ||
| 211 | func connectToServer(server, un, pw): | 221 | func connectToServer(server, un, pw): | 
| 212 | sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}]) | 222 | sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}]) | 
| diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index b7bb5fd..830ebb8 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd | |||
| @@ -146,6 +146,7 @@ func _ready(): | |||
| 146 | client.accessible_locations_updated.connect(_on_accessible_locations_updated) | 146 | client.accessible_locations_updated.connect(_on_accessible_locations_updated) | 
| 147 | client.checked_locations_updated.connect(_on_checked_locations_updated) | 147 | client.checked_locations_updated.connect(_on_checked_locations_updated) | 
| 148 | client.ignored_locations_updated.connect(_on_ignored_locations_updated) | 148 | client.ignored_locations_updated.connect(_on_ignored_locations_updated) | 
| 149 | client.hinted_locations_updated.connect(_on_hinted_locations_updated) | ||
| 149 | client.checked_worldports_updated.connect(_on_checked_worldports_updated) | 150 | client.checked_worldports_updated.connect(_on_checked_worldports_updated) | 
| 150 | client.door_latched.connect(_on_door_latched) | 151 | client.door_latched.connect(_on_door_latched) | 
| 151 | 152 | ||
| @@ -394,6 +395,12 @@ func _on_ignored_locations_updated(locations): | |||
| 394 | textclient_node.update_locations() | 395 | textclient_node.update_locations() | 
| 395 | 396 | ||
| 396 | 397 | ||
| 398 | func _on_hinted_locations_updated(): | ||
| 399 | var textclient_node = global.get_node("Textclient") | ||
| 400 | if textclient_node != null: | ||
| 401 | textclient_node.update_locations() | ||
| 402 | |||
| 403 | |||
| 397 | func _on_door_latched(door_id): | 404 | func _on_door_latched(door_id): | 
| 398 | var gamedata = global.get_node("Gamedata") | 405 | var gamedata = global.get_node("Gamedata") | 
| 399 | if gamedata.get_door_map_name(door_id) != global.map: | 406 | if gamedata.get_door_map_name(door_id) != global.map: | 
| diff --git a/apworld/client/textclient.gd b/apworld/client/textclient.gd index 8356f92..ce28a3a 100644 --- a/apworld/client/textclient.gd +++ b/apworld/client/textclient.gd | |||
| @@ -17,6 +17,7 @@ var tracker_port_tree_item_by_id = {} | |||
| 17 | var tracker_goal_tree_item = null | 17 | var tracker_goal_tree_item = null | 
| 18 | var tracker_object_by_index = {} | 18 | var tracker_object_by_index = {} | 
| 19 | var tracker_object_by_ignored_index = {} | 19 | var tracker_object_by_ignored_index = {} | 
| 20 | var tracker_ignored_group = null | ||
| 20 | 21 | ||
| 21 | var worldports_tab | 22 | var worldports_tab | 
| 22 | var worldports_tree | 23 | var worldports_tree | 
| @@ -212,6 +213,7 @@ func update_locations(reset_locations = true): | |||
| 212 | "type": kLocation, | 213 | "type": kLocation, | 
| 213 | "id": location_id, | 214 | "id": location_id, | 
| 214 | "ignored": ap._ignored_locations.has(location_id), | 215 | "ignored": ap._ignored_locations.has(location_id), | 
| 216 | "hint": ap.client._hinted_locations.has(location_id), | ||
| 215 | } | 217 | } | 
| 216 | ) | 218 | ) | 
| 217 | ) | 219 | ) | 
| @@ -227,13 +229,12 @@ func update_locations(reset_locations = true): | |||
| 227 | "type": kWorldport, | 229 | "type": kWorldport, | 
| 228 | "id": port_id, | 230 | "id": port_id, | 
| 229 | "ignored": false, | 231 | "ignored": false, | 
| 232 | "hint": false, | ||
| 230 | } | 233 | } | 
| 231 | ) | 234 | ) | 
| 232 | ) | 235 | ) | 
| 233 | 236 | ||
| 234 | locations.sort_custom( | 237 | locations.sort_custom(_cmp_tracker_objects) | 
| 235 | func(a, b): return a["name"] < b["name"] if a["ignored"] == b["ignored"] else !a["ignored"] | ||
| 236 | ) | ||
| 237 | 238 | ||
| 238 | if ap.client._goal_accessible: | 239 | if ap.client._goal_accessible: | 
| 239 | var location_name = gamedata.ending_display_name_by_name[ap.kEndingNameByVictoryValue[ | 240 | var location_name = gamedata.ending_display_name_by_name[ap.kEndingNameByVictoryValue[ | 
| @@ -246,6 +247,7 @@ func update_locations(reset_locations = true): | |||
| 246 | "name": location_name, | 247 | "name": location_name, | 
| 247 | "type": kGoal, | 248 | "type": kGoal, | 
| 248 | "ignored": false, | 249 | "ignored": false, | 
| 250 | "hint": false, | ||
| 249 | } | 251 | } | 
| 250 | ) | 252 | ) | 
| 251 | ) | 253 | ) | 
| @@ -254,6 +256,8 @@ func update_locations(reset_locations = true): | |||
| 254 | for location in locations: | 256 | for location in locations: | 
| 255 | if count < 18 and not location["ignored"]: | 257 | if count < 18 and not location["ignored"]: | 
| 256 | locations_overlay.push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT) | 258 | locations_overlay.push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT) | 
| 259 | if location["hint"]: | ||
| 260 | locations_overlay.push_color(Color("#fafad2")) | ||
| 257 | locations_overlay.append_text(location["name"]) | 261 | locations_overlay.append_text(location["name"]) | 
| 258 | locations_overlay.append_text(" ") | 262 | locations_overlay.append_text(" ") | 
| 259 | if location["type"] == kLocation: | 263 | if location["type"] == kLocation: | 
| @@ -262,6 +266,8 @@ func update_locations(reset_locations = true): | |||
| 262 | locations_overlay.add_image(worldport_texture) | 266 | locations_overlay.add_image(worldport_texture) | 
| 263 | elif location["type"] == kGoal: | 267 | elif location["type"] == kGoal: | 
| 264 | locations_overlay.add_image(goal_texture) | 268 | locations_overlay.add_image(goal_texture) | 
| 269 | if location["hint"]: | ||
| 270 | locations_overlay.pop() | ||
| 265 | locations_overlay.pop() | 271 | locations_overlay.pop() | 
| 266 | count += 1 | 272 | count += 1 | 
| 267 | 273 | ||
| @@ -272,21 +278,20 @@ func update_locations(reset_locations = true): | |||
| 272 | reset_tracker_tab() | 278 | reset_tracker_tab() | 
| 273 | 279 | ||
| 274 | var root_ti = tracker_tree.create_item(null) | 280 | var root_ti = tracker_tree.create_item(null) | 
| 275 | var ignored_loc_header = null | ||
| 276 | 281 | ||
| 277 | for location in locations: | 282 | for location in locations: | 
| 278 | var loc_row | 283 | var loc_row | 
| 279 | 284 | ||
| 280 | if location["ignored"]: | 285 | if location["ignored"]: | 
| 281 | if ignored_loc_header == null: | 286 | if tracker_ignored_group == null: | 
| 282 | ignored_loc_header = root_ti.create_child() | 287 | tracker_ignored_group = root_ti.create_child() | 
| 283 | ignored_loc_header.set_text(1, "Ignored Locations") | 288 | tracker_ignored_group.set_text(1, "Ignored Locations") | 
| 284 | ignored_loc_header.set_selectable(0, false) | 289 | tracker_ignored_group.set_selectable(0, false) | 
| 285 | ignored_loc_header.set_selectable(1, false) | 290 | tracker_ignored_group.set_selectable(1, false) | 
| 286 | ignored_loc_header.set_selectable(2, false) | 291 | tracker_ignored_group.set_selectable(2, false) | 
| 287 | ignored_loc_header.set_selectable(3, false) | 292 | tracker_ignored_group.set_selectable(3, false) | 
| 288 | 293 | ||
| 289 | loc_row = ignored_loc_header.create_child() | 294 | loc_row = tracker_ignored_group.create_child() | 
| 290 | else: | 295 | else: | 
| 291 | loc_row = root_ti.create_child() | 296 | loc_row = root_ti.create_child() | 
| 292 | 297 | ||
| @@ -294,6 +299,8 @@ func update_locations(reset_locations = true): | |||
| 294 | loc_row.set_selectable(0, false) | 299 | loc_row.set_selectable(0, false) | 
| 295 | loc_row.set_text(1, location["name"]) | 300 | loc_row.set_text(1, location["name"]) | 
| 296 | loc_row.set_selectable(1, false) | 301 | loc_row.set_selectable(1, false) | 
| 302 | if location["hint"]: | ||
| 303 | loc_row.set_custom_color(1, Color("#fafad2")) | ||
| 297 | loc_row.set_cell_mode(2, TreeItem.CELL_MODE_CUSTOM) | 304 | loc_row.set_cell_mode(2, TreeItem.CELL_MODE_CUSTOM) | 
| 298 | loc_row.set_text(2, "Show Path") | 305 | loc_row.set_text(2, "Show Path") | 
| 299 | loc_row.set_custom_as_button(2, true) | 306 | loc_row.set_custom_as_button(2, true) | 
| @@ -346,6 +353,18 @@ func update_locations(reset_locations = true): | |||
| 346 | if tracker_goal_tree_item != null and ap.client._goal_accessible: | 353 | if tracker_goal_tree_item != null and ap.client._goal_accessible: | 
| 347 | tracker_goal_tree_item.visible = true | 354 | tracker_goal_tree_item.visible = true | 
| 348 | 355 | ||
| 356 | if tracker_ignored_group != null: | ||
| 357 | tracker_ignored_group.visible = true | ||
| 358 | |||
| 359 | |||
| 360 | func _cmp_tracker_objects(a, b) -> bool: | ||
| 361 | if a["ignored"] != b["ignored"]: | ||
| 362 | return !a["ignored"] | ||
| 363 | elif a["hint"] != b["hint"]: | ||
| 364 | return a["hint"] | ||
| 365 | else: | ||
| 366 | return a["name"] < b["name"] | ||
| 367 | |||
| 349 | 368 | ||
| 350 | func update_locations_visibility(): | 369 | func update_locations_visibility(): | 
| 351 | var ap = global.get_node("Archipelago") | 370 | var ap = global.get_node("Archipelago") | 
| @@ -372,7 +391,7 @@ func _on_tracker_button_clicked(): | |||
| 372 | ap.client.getLogicalPath(type_str, tracker_object.get("id", null)) | 391 | ap.client.getLogicalPath(type_str, tracker_object.get("id", null)) | 
| 373 | elif tracker_tree.get_edited_column() == 3: | 392 | elif tracker_tree.get_edited_column() == 3: | 
| 374 | ap.toggle_ignored_location(tracker_object["id"]) | 393 | ap.toggle_ignored_location(tracker_object["id"]) | 
| 375 | else: | 394 | elif edited_item.get_parent() == tracker_ignored_group: | 
| 376 | # This is the ignored locations group. | 395 | # This is the ignored locations group. | 
| 377 | if ( | 396 | if ( | 
| 378 | tracker_object_by_ignored_index.has(edited_index) | 397 | tracker_object_by_ignored_index.has(edited_index) | 
| @@ -485,4 +504,5 @@ func reset_tracker_tab(): | |||
| 485 | tracker_goal_tree_item = null | 504 | tracker_goal_tree_item = null | 
| 486 | tracker_object_by_index.clear() | 505 | tracker_object_by_index.clear() | 
| 487 | tracker_object_by_ignored_index.clear() | 506 | tracker_object_by_ignored_index.clear() | 
| 507 | tracker_ignored_group = null | ||
| 488 | tracker_tree.clear() | 508 | tracker_tree.clear() | 
| diff --git a/apworld/context.py b/apworld/context.py index e5ba3cf..86392f9 100644 --- a/apworld/context.py +++ b/apworld/context.py | |||
| @@ -49,6 +49,7 @@ class Lingo2Manager: | |||
| 49 | worldports: set[int] | 49 | worldports: set[int] | 
| 50 | goaled: bool | 50 | goaled: bool | 
| 51 | latches: set[int] | 51 | latches: set[int] | 
| 52 | hinted_locations: set[int] | ||
| 52 | 53 | ||
| 53 | def __init__(self, game_ctx: "Lingo2GameContext", client_ctx: "Lingo2ClientContext"): | 54 | def __init__(self, game_ctx: "Lingo2GameContext", client_ctx: "Lingo2ClientContext"): | 
| 54 | self.game_ctx = game_ctx | 55 | self.game_ctx = game_ctx | 
| @@ -67,6 +68,7 @@ class Lingo2Manager: | |||
| 67 | self.worldports = set() | 68 | self.worldports = set() | 
| 68 | self.goaled = False | 69 | self.goaled = False | 
| 69 | self.latches = set() | 70 | self.latches = set() | 
| 71 | self.hinted_locations = set() | ||
| 70 | 72 | ||
| 71 | def update_keyboard(self, new_keyboard: dict[str, int]) -> dict[str, int]: | 73 | def update_keyboard(self, new_keyboard: dict[str, int]) -> dict[str, int]: | 
| 72 | ret: dict[str, int] = {} | 74 | ret: dict[str, int] = {} | 
| @@ -99,6 +101,12 @@ class Lingo2Manager: | |||
| 99 | 101 | ||
| 100 | return ret | 102 | return ret | 
| 101 | 103 | ||
| 104 | def update_hinted_locations(self, new_locs: set[int]) -> set[int]: | ||
| 105 | ret = new_locs.difference(self.hinted_locations) | ||
| 106 | self.hinted_locations.update(new_locs) | ||
| 107 | |||
| 108 | return ret | ||
| 109 | |||
| 102 | 110 | ||
| 103 | class Lingo2GameContext: | 111 | class Lingo2GameContext: | 
| 104 | server: Endpoint | None | 112 | server: Endpoint | None | 
| @@ -285,6 +293,17 @@ class Lingo2GameContext: | |||
| 285 | 293 | ||
| 286 | async_start(self.send_msgs([msg]), name="set ignored locations") | 294 | async_start(self.send_msgs([msg]), name="set ignored locations") | 
| 287 | 295 | ||
| 296 | def send_update_hinted_locations(self, hinted_locations): | ||
| 297 | if self.server is None: | ||
| 298 | return | ||
| 299 | |||
| 300 | msg = { | ||
| 301 | "cmd": "UpdateHintedLocations", | ||
| 302 | "locations": hinted_locations, | ||
| 303 | } | ||
| 304 | |||
| 305 | async_start(self.send_msgs([msg]), name="update hinted locations") | ||
| 306 | |||
| 288 | async def send_msgs(self, msgs: list[Any]) -> None: | 307 | async def send_msgs(self, msgs: list[Any]) -> None: | 
| 289 | """ `msgs` JSON serializable """ | 308 | """ `msgs` JSON serializable """ | 
| 290 | if not self.server or not self.server.socket.open or self.server.socket.closed: | 309 | if not self.server or not self.server.socket.open or self.server.socket.closed: | 
| @@ -299,6 +318,7 @@ class Lingo2ClientContext(CommonContext): | |||
| 299 | items_handling = 0b111 | 318 | items_handling = 0b111 | 
| 300 | 319 | ||
| 301 | slot_data: dict[str, Any] | None | 320 | slot_data: dict[str, Any] | None | 
| 321 | hints_data_storage_key: str | ||
| 302 | victory_data_storage_key: str | 322 | victory_data_storage_key: str | 
| 303 | 323 | ||
| 304 | def __init__(self, server_address: str | None = None, password: str | None = None): | 324 | def __init__(self, server_address: str | None = None, password: str | None = None): | 
| @@ -336,6 +356,7 @@ class Lingo2ClientContext(CommonContext): | |||
| 336 | self.manager.tracker.set_checked_locations(self.checked_locations) | 356 | self.manager.tracker.set_checked_locations(self.checked_locations) | 
| 337 | self.manager.game_ctx.send_accessible_locations() | 357 | self.manager.game_ctx.send_accessible_locations() | 
| 338 | 358 | ||
| 359 | self.hints_data_storage_key = f"_read_hints_{self.team}_{self.slot}" | ||
| 339 | self.victory_data_storage_key = f"_read_client_status_{self.team}_{self.slot}" | 360 | self.victory_data_storage_key = f"_read_client_status_{self.team}_{self.slot}" | 
| 340 | 361 | ||
| 341 | self.set_notify(self.get_datastorage_key("keyboard1"), self.get_datastorage_key("keyboard2"), | 362 | self.set_notify(self.get_datastorage_key("keyboard1"), self.get_datastorage_key("keyboard2"), | 
| @@ -463,6 +484,8 @@ class Lingo2ClientContext(CommonContext): | |||
| 463 | for k, v in args["keys"].items(): | 484 | for k, v in args["keys"].items(): | 
| 464 | if k == self.victory_data_storage_key: | 485 | if k == self.victory_data_storage_key: | 
| 465 | self.handle_status_update(v) | 486 | self.handle_status_update(v) | 
| 487 | elif k == self.hints_data_storage_key: | ||
| 488 | self.update_hints() | ||
| 466 | elif cmd == "SetReply": | 489 | elif cmd == "SetReply": | 
| 467 | if args["key"] == self.get_datastorage_key("keyboard1"): | 490 | if args["key"] == self.get_datastorage_key("keyboard1"): | 
| 468 | self.handle_keyboard_update(1, args) | 491 | self.handle_keyboard_update(1, args) | 
| @@ -482,6 +505,8 @@ class Lingo2ClientContext(CommonContext): | |||
| 482 | self.manager.game_ctx.send_update_latches(updates) | 505 | self.manager.game_ctx.send_update_latches(updates) | 
| 483 | elif args["key"] == self.get_datastorage_key("ignored_locations"): | 506 | elif args["key"] == self.get_datastorage_key("ignored_locations"): | 
| 484 | self.manager.game_ctx.send_ignored_locations(args["value"]) | 507 | self.manager.game_ctx.send_ignored_locations(args["value"]) | 
| 508 | elif args["key"] == self.hints_data_storage_key: | ||
| 509 | self.update_hints() | ||
| 485 | 510 | ||
| 486 | def get_datastorage_key(self, name: str): | 511 | def get_datastorage_key(self, name: str): | 
| 487 | return f"Lingo2_{self.slot}_{name}" | 512 | return f"Lingo2_{self.slot}_{name}" | 
| @@ -599,6 +624,14 @@ class Lingo2ClientContext(CommonContext): | |||
| 599 | }] | 624 | }] | 
| 600 | }]) | 625 | }]) | 
| 601 | 626 | ||
| 627 | def update_hints(self): | ||
| 628 | hints = self.stored_data.get(self.hints_data_storage_key, []) | ||
| 629 | |||
| 630 | hinted_locations = set(hint["location"] for hint in hints if hint["finding_player"] == self.slot) | ||
| 631 | updates = self.manager.update_hinted_locations(hinted_locations) | ||
| 632 | if len(updates) > 0: | ||
| 633 | self.manager.game_ctx.send_update_hinted_locations(updates) | ||
| 634 | |||
| 602 | 635 | ||
| 603 | async def pipe_loop(manager: Lingo2Manager): | 636 | async def pipe_loop(manager: Lingo2Manager): | 
| 604 | while not manager.client_ctx.exit_event.is_set(): | 637 | while not manager.client_ctx.exit_event.is_set(): | 
