diff options
Diffstat (limited to 'apworld')
| -rw-r--r-- | apworld/client/client.gd | 25 | ||||
| -rw-r--r-- | apworld/client/gamedata.gd | 120 | ||||
| -rw-r--r-- | apworld/client/manager.gd | 14 | ||||
| -rw-r--r-- | apworld/client/textclient.gd | 86 | ||||
| -rw-r--r-- | apworld/context.py | 42 | ||||
| -rw-r--r-- | apworld/tracker.py | 67 |
6 files changed, 321 insertions, 33 deletions
| diff --git a/apworld/client/client.gd b/apworld/client/client.gd index 67edf29..05b2b6c 100644 --- a/apworld/client/client.gd +++ b/apworld/client/client.gd | |||
| @@ -21,6 +21,7 @@ var _checked_locations = [] | |||
| 21 | var _received_indexes = [] | 21 | var _received_indexes = [] |
| 22 | var _received_items = {} | 22 | var _received_items = {} |
| 23 | var _slot_data = {} | 23 | var _slot_data = {} |
| 24 | var _accessible_locations = [] | ||
| 24 | 25 | ||
| 25 | signal could_not_connect | 26 | signal could_not_connect |
| 26 | signal connect_status | 27 | signal connect_status |
| @@ -30,6 +31,8 @@ signal location_scout_received(location_id, item_name, player_name, flags, for_s | |||
| 30 | signal text_message_received(message) | 31 | signal text_message_received(message) |
| 31 | signal item_sent_notification(message) | 32 | signal item_sent_notification(message) |
| 32 | signal hint_received(message) | 33 | signal hint_received(message) |
| 34 | signal accessible_locations_updated | ||
| 35 | signal checked_locations_updated | ||
| 33 | 36 | ||
| 34 | 37 | ||
| 35 | func _init(): | 38 | func _init(): |
| @@ -51,6 +54,7 @@ func _reset_state(): | |||
| 51 | _should_process = false | 54 | _should_process = false |
| 52 | _received_items = {} | 55 | _received_items = {} |
| 53 | _received_indexes = [] | 56 | _received_indexes = [] |
| 57 | _accessible_locations = [] | ||
| 54 | 58 | ||
| 55 | 59 | ||
| 56 | func disconnect_from_ap(): | 60 | func disconnect_from_ap(): |
| @@ -92,15 +96,26 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo | |||
| 92 | _gen_version = message["generator_version"] | 96 | _gen_version = message["generator_version"] |
| 93 | _team = message["team"] | 97 | _team = message["team"] |
| 94 | _slot = message["slot"] | 98 | _slot = message["slot"] |
| 95 | _checked_locations = message["checked_locations"] | ||
| 96 | _slot_data = message["slot_data"] | 99 | _slot_data = message["slot_data"] |
| 97 | 100 | ||
| 101 | _checked_locations = [] | ||
| 102 | for location in message["checked_locations"]: | ||
| 103 | _checked_locations.append(int(message["checked_locations"])) | ||
| 104 | |||
| 98 | client_connected.emit(_slot_data) | 105 | client_connected.emit(_slot_data) |
| 99 | 106 | ||
| 100 | elif cmd == "ConnectionRefused": | 107 | elif cmd == "ConnectionRefused": |
| 101 | could_not_connect.emit(message["text"]) | 108 | could_not_connect.emit(message["text"]) |
| 102 | global._print("Connection to AP refused") | 109 | global._print("Connection to AP refused") |
| 103 | 110 | ||
| 111 | elif cmd == "UpdateLocations": | ||
| 112 | for location in message["locations"]: | ||
| 113 | var lint = int(location) | ||
| 114 | if not _checked_locations.has(lint): | ||
| 115 | _checked_locations.append(lint) | ||
| 116 | |||
| 117 | checked_locations_updated.emit() | ||
| 118 | |||
| 104 | elif cmd == "ItemReceived": | 119 | elif cmd == "ItemReceived": |
| 105 | for item in message["items"]: | 120 | for item in message["items"]: |
| 106 | var index = int(item["index"]) | 121 | var index = int(item["index"]) |
| @@ -134,6 +149,14 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo | |||
| 134 | int(loc["for_self"]) | 149 | int(loc["for_self"]) |
| 135 | ) | 150 | ) |
| 136 | 151 | ||
| 152 | elif cmd == "AccessibleLocations": | ||
| 153 | _accessible_locations.clear() | ||
| 154 | |||
| 155 | for loc in message["locations"]: | ||
| 156 | _accessible_locations.append(int(loc)) | ||
| 157 | |||
| 158 | accessible_locations_updated.emit() | ||
| 159 | |||
| 137 | 160 | ||
| 138 | func connectToServer(server, un, pw): | 161 | func connectToServer(server, un, pw): |
| 139 | sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}]) | 162 | sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}]) |
| diff --git a/apworld/client/gamedata.gd b/apworld/client/gamedata.gd index 9eeec3b..39e0583 100644 --- a/apworld/client/gamedata.gd +++ b/apworld/client/gamedata.gd | |||
| @@ -13,6 +13,7 @@ var progressive_id_by_ap_id = {} | |||
| 13 | var letter_id_by_ap_id = {} | 13 | var letter_id_by_ap_id = {} |
| 14 | var symbol_item_ids = [] | 14 | var symbol_item_ids = [] |
| 15 | var anti_trap_ids = {} | 15 | var anti_trap_ids = {} |
| 16 | var location_name_by_id = {} | ||
| 16 | 17 | ||
| 17 | var kSYMBOL_ITEMS | 18 | var kSYMBOL_ITEMS |
| 18 | 19 | ||
| @@ -70,6 +71,7 @@ func load(data_bytes): | |||
| 70 | 71 | ||
| 71 | if door.has_ap_id(): | 72 | if door.has_ap_id(): |
| 72 | door_id_by_ap_id[door.get_ap_id()] = door.get_id() | 73 | door_id_by_ap_id[door.get_ap_id()] = door.get_id() |
| 74 | location_name_by_id[door.get_ap_id()] = _get_door_location_name(door) | ||
| 73 | 75 | ||
| 74 | for painting in objects.get_paintings(): | 76 | for painting in objects.get_paintings(): |
| 75 | var room = objects.get_rooms()[painting.get_room_id()] | 77 | var room = objects.get_rooms()[painting.get_room_id()] |
| @@ -95,6 +97,17 @@ func load(data_bytes): | |||
| 95 | 97 | ||
| 96 | for letter in objects.get_letters(): | 98 | for letter in objects.get_letters(): |
| 97 | letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id() | 99 | letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id() |
| 100 | location_name_by_id[letter.get_ap_id()] = _get_letter_location_name(letter) | ||
| 101 | |||
| 102 | for mastery in objects.get_masteries(): | ||
| 103 | location_name_by_id[mastery.get_ap_id()] = _get_mastery_location_name(mastery) | ||
| 104 | |||
| 105 | for ending in objects.get_endings(): | ||
| 106 | location_name_by_id[ending.get_ap_id()] = _get_ending_location_name(ending) | ||
| 107 | |||
| 108 | for keyholder in objects.get_keyholders(): | ||
| 109 | if keyholder.has_key(): | ||
| 110 | location_name_by_id[keyholder.get_ap_id()] = _get_keyholder_location_name(keyholder) | ||
| 98 | 111 | ||
| 99 | for panel in objects.get_panels(): | 112 | for panel in objects.get_panels(): |
| 100 | var room = objects.get_rooms()[panel.get_room_id()] | 113 | var room = objects.get_rooms()[panel.get_room_id()] |
| @@ -153,7 +166,106 @@ func get_door_receivers(door_id): | |||
| 153 | return door.get_receivers() | 166 | return door.get_receivers() |
| 154 | 167 | ||
| 155 | 168 | ||
| 156 | func get_door_map_name(door_id): | 169 | func _get_map_object_map_name(obj): |
| 157 | var door = objects.get_doors()[door_id] | 170 | return objects.get_maps()[obj.get_map_id()].get_display_name() |
| 158 | var map = objects.get_maps()[door.get_map_id()] | 171 | |
| 159 | return map.get_name() | 172 | |
| 173 | func _get_room_object_map_name(obj): | ||
| 174 | return _get_map_object_map_name(objects.get_rooms()[obj.get_room_id()]) | ||
| 175 | |||
| 176 | |||
| 177 | func _get_room_object_location_prefix(obj): | ||
| 178 | var room = objects.get_rooms()[obj.get_room_id()] | ||
| 179 | var game_map = objects.get_maps()[room.get_map_id()] | ||
| 180 | |||
| 181 | if room.has_panel_display_name(): | ||
| 182 | return "%s (%s)" % [game_map.get_display_name(), room.get_panel_display_name()] | ||
| 183 | else: | ||
| 184 | return game_map.get_display_name() | ||
| 185 | |||
| 186 | |||
| 187 | func _get_door_location_name(door): | ||
| 188 | var map_part = _get_room_object_location_prefix(door) | ||
| 189 | |||
| 190 | if door.has_location_name(): | ||
| 191 | return "%s - %s" % [map_part, door.get_location_name()] | ||
| 192 | |||
| 193 | var generated_location_name = _get_generated_door_location_name(door) | ||
| 194 | if generated_location_name != null: | ||
| 195 | return generated_location_name | ||
| 196 | |||
| 197 | return "%s - %s" % [map_part, door.get_name()] | ||
| 198 | |||
| 199 | |||
| 200 | func _get_generated_door_location_name(door): | ||
| 201 | if door.get_type() != SCRIPT_proto.DoorType.STANDARD: | ||
| 202 | return null | ||
| 203 | |||
| 204 | if door.get_keyholders().size() > 0 or door.get_endings().size() > 0 or door.has_complete_at(): | ||
| 205 | return null | ||
| 206 | |||
| 207 | if door.get_panels().size() > 4: | ||
| 208 | return null | ||
| 209 | |||
| 210 | var map_areas = [] | ||
| 211 | for panel_id in door.get_panels(): | ||
| 212 | var panel = objects.get_panels()[panel_id.get_panel()] | ||
| 213 | var panel_room = objects.get_rooms()[panel.get_room_id()] | ||
| 214 | # It's okay if panel_display_name is not present because then it's coalesced with other unnamed areas. | ||
| 215 | if not map_areas.has(panel_room.get_panel_display_name()): | ||
| 216 | map_areas.append(panel_room.get_panel_display_name()) | ||
| 217 | |||
| 218 | if map_areas.size() > 1: | ||
| 219 | return null | ||
| 220 | |||
| 221 | var game_map = objects.get_maps()[door.get_map_id()] | ||
| 222 | var map_area = map_areas[0] | ||
| 223 | var map_part | ||
| 224 | if map_area == "": | ||
| 225 | map_part = game_map.get_display_name() | ||
| 226 | else: | ||
| 227 | map_part = "%s (%s)" % [game_map.get_display_name(), map_area] | ||
| 228 | |||
| 229 | var panel_names = [] | ||
| 230 | for panel_id in door.get_panels(): | ||
| 231 | var panel_data = objects.get_panels()[panel_id.get_panel()] | ||
| 232 | var panel_name | ||
| 233 | if panel_data.has_display_name(): | ||
| 234 | panel_name = panel_data.get_display_name() | ||
| 235 | else: | ||
| 236 | panel_name = panel_data.get_name() | ||
| 237 | |||
| 238 | var location_part | ||
| 239 | if panel_id.has_answer(): | ||
| 240 | location_part = "%s/%s" % [panel_name, panel_id.get_answer().to_upper()] | ||
| 241 | else: | ||
| 242 | location_part = panel_name | ||
| 243 | |||
| 244 | panel_names.append(location_part) | ||
| 245 | |||
| 246 | panel_names.sort() | ||
| 247 | |||
| 248 | return map_part + " - " + ", ".join(panel_names) | ||
| 249 | |||
| 250 | |||
| 251 | func _get_letter_location_name(letter): | ||
| 252 | var letter_level = 2 if letter.get_level2() else 1 | ||
| 253 | var letter_name = "%s%d" % [letter.get_key().to_upper(), letter_level] | ||
| 254 | return "%s - %s" % [_get_room_object_map_name(letter), letter_name] | ||
| 255 | |||
| 256 | |||
| 257 | func _get_mastery_location_name(mastery): | ||
| 258 | return "%s - Mastery" % _get_room_object_map_name(mastery) | ||
| 259 | |||
| 260 | |||
| 261 | func _get_ending_location_name(ending): | ||
| 262 | return ( | ||
| 263 | "%s - %s Ending" % [_get_room_object_map_name(ending), ending.get_name().to_pascal_case()] | ||
| 264 | ) | ||
| 265 | |||
| 266 | |||
| 267 | func _get_keyholder_location_name(keyholder): | ||
| 268 | return ( | ||
| 269 | "%s - %s Keyholder" | ||
| 270 | % [_get_room_object_location_prefix(keyholder), keyholder.get_key().to_upper()] | ||
| 271 | ) | ||
| diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index 955d470..7f4a8a6 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd | |||
| @@ -99,6 +99,8 @@ func _ready(): | |||
| 99 | client.text_message_received.connect(_process_text_message) | 99 | client.text_message_received.connect(_process_text_message) |
| 100 | client.item_sent_notification.connect(_process_item_sent_notification) | 100 | client.item_sent_notification.connect(_process_item_sent_notification) |
| 101 | client.hint_received.connect(_process_hint_received) | 101 | client.hint_received.connect(_process_hint_received) |
| 102 | client.accessible_locations_updated.connect(_on_accessible_locations_updated) | ||
| 103 | client.checked_locations_updated.connect(_on_checked_locations_updated) | ||
| 102 | 104 | ||
| 103 | client.could_not_connect.connect(_client_could_not_connect) | 105 | client.could_not_connect.connect(_client_could_not_connect) |
| 104 | client.connect_status.connect(_client_connect_status) | 106 | client.connect_status.connect(_client_connect_status) |
| @@ -302,6 +304,18 @@ func _process_location_scout(location_id, item_name, player_name, flags, for_sel | |||
| 302 | collectable.setScoutedText(item_name) | 304 | collectable.setScoutedText(item_name) |
| 303 | 305 | ||
| 304 | 306 | ||
| 307 | func _on_accessible_locations_updated(): | ||
| 308 | var textclient_node = global.get_node("Textclient") | ||
| 309 | if textclient_node != null: | ||
| 310 | textclient_node.update_locations() | ||
| 311 | |||
| 312 | |||
| 313 | func _on_checked_locations_updated(): | ||
| 314 | var textclient_node = global.get_node("Textclient") | ||
| 315 | if textclient_node != null: | ||
| 316 | textclient_node.update_locations() | ||
| 317 | |||
| 318 | |||
| 305 | func _client_could_not_connect(message): | 319 | func _client_could_not_connect(message): |
| 306 | could_not_connect.emit(message) | 320 | could_not_connect.emit(message) |
| 307 | 321 | ||
| diff --git a/apworld/client/textclient.gd b/apworld/client/textclient.gd index 9841063..e345489 100644 --- a/apworld/client/textclient.gd +++ b/apworld/client/textclient.gd | |||
| @@ -1,8 +1,10 @@ | |||
| 1 | extends CanvasLayer | 1 | extends CanvasLayer |
| 2 | 2 | ||
| 3 | var tabs | ||
| 3 | var panel | 4 | var panel |
| 4 | var label | 5 | var label |
| 5 | var entry | 6 | var entry |
| 7 | var tracker_label | ||
| 6 | var is_open = false | 8 | var is_open = false |
| 7 | 9 | ||
| 8 | 10 | ||
| @@ -10,26 +12,32 @@ func _ready(): | |||
| 10 | process_mode = ProcessMode.PROCESS_MODE_ALWAYS | 12 | process_mode = ProcessMode.PROCESS_MODE_ALWAYS |
| 11 | layer = 2 | 13 | layer = 2 |
| 12 | 14 | ||
| 13 | panel = Panel.new() | 15 | tabs = TabContainer.new() |
| 14 | panel.set_name("Panel") | 16 | tabs.name = "Tabs" |
| 15 | panel.offset_left = 100 | 17 | tabs.offset_left = 100 |
| 16 | panel.offset_right = 1820 | 18 | tabs.offset_right = 1820 |
| 17 | panel.offset_top = 100 | 19 | tabs.offset_top = 100 |
| 18 | panel.offset_bottom = 980 | 20 | tabs.offset_bottom = 980 |
| 19 | panel.visible = false | 21 | tabs.visible = false |
| 20 | add_child(panel) | 22 | tabs.theme = preload("res://assets/themes/baseUI.tres") |
| 23 | tabs.add_theme_font_size_override("font_size", 36) | ||
| 24 | add_child(tabs) | ||
| 25 | |||
| 26 | panel = MarginContainer.new() | ||
| 27 | panel.name = "Text Client" | ||
| 28 | panel.add_theme_constant_override("margin_top", 60) | ||
| 29 | panel.add_theme_constant_override("margin_left", 60) | ||
| 30 | panel.add_theme_constant_override("margin_right", 60) | ||
| 31 | panel.add_theme_constant_override("margin_bottom", 60) | ||
| 32 | tabs.add_child(panel) | ||
| 21 | 33 | ||
| 22 | label = RichTextLabel.new() | 34 | label = RichTextLabel.new() |
| 23 | label.set_name("Label") | 35 | label.set_name("Label") |
| 24 | label.offset_left = 80 | ||
| 25 | label.offset_right = 1640 | ||
| 26 | label.offset_top = 80 | ||
| 27 | label.offset_bottom = 720 | ||
| 28 | label.scroll_following = true | 36 | label.scroll_following = true |
| 29 | label.selection_enabled = true | 37 | label.selection_enabled = true |
| 30 | panel.add_child(label) | 38 | label.size_flags_horizontal = Control.SIZE_EXPAND_FILL |
| 31 | 39 | label.size_flags_vertical = Control.SIZE_EXPAND_FILL | |
| 32 | label.push_font(load("res://assets/fonts/Lingo2.ttf")) | 40 | label.push_font(preload("res://assets/fonts/Lingo2.ttf")) |
| 33 | label.push_font_size(36) | 41 | label.push_font_size(36) |
| 34 | 42 | ||
| 35 | var entry_style = StyleBoxFlat.new() | 43 | var entry_style = StyleBoxFlat.new() |
| @@ -37,18 +45,30 @@ func _ready(): | |||
| 37 | 45 | ||
| 38 | entry = LineEdit.new() | 46 | entry = LineEdit.new() |
| 39 | entry.set_name("Entry") | 47 | entry.set_name("Entry") |
| 40 | entry.offset_left = 80 | 48 | entry.add_theme_font_override("font", preload("res://assets/fonts/Lingo2.ttf")) |
| 41 | entry.offset_right = 1640 | ||
| 42 | entry.offset_top = 760 | ||
| 43 | entry.offset_bottom = 840 | ||
| 44 | entry.add_theme_font_override("font", load("res://assets/fonts/Lingo2.ttf")) | ||
| 45 | entry.add_theme_font_size_override("font_size", 36) | 49 | entry.add_theme_font_size_override("font_size", 36) |
| 46 | entry.add_theme_color_override("font_color", Color(0, 0, 0, 1)) | 50 | entry.add_theme_color_override("font_color", Color(0, 0, 0, 1)) |
| 47 | entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1)) | 51 | entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1)) |
| 48 | entry.add_theme_stylebox_override("focus", entry_style) | 52 | entry.add_theme_stylebox_override("focus", entry_style) |
| 49 | panel.add_child(entry) | ||
| 50 | entry.text_submitted.connect(text_entered) | 53 | entry.text_submitted.connect(text_entered) |
| 51 | 54 | ||
| 55 | var tc_arranger = VBoxContainer.new() | ||
| 56 | tc_arranger.add_child(label) | ||
| 57 | tc_arranger.add_child(entry) | ||
| 58 | tc_arranger.add_theme_constant_override("separation", 40) | ||
| 59 | panel.add_child(tc_arranger) | ||
| 60 | |||
| 61 | var tracker_margins = MarginContainer.new() | ||
| 62 | tracker_margins.name = "Locations" | ||
| 63 | tracker_margins.add_theme_constant_override("margin_top", 60) | ||
| 64 | tracker_margins.add_theme_constant_override("margin_left", 60) | ||
| 65 | tracker_margins.add_theme_constant_override("margin_right", 60) | ||
| 66 | tracker_margins.add_theme_constant_override("margin_bottom", 60) | ||
| 67 | tabs.add_child(tracker_margins) | ||
| 68 | |||
| 69 | tracker_label = RichTextLabel.new() | ||
| 70 | tracker_margins.add_child(tracker_label) | ||
| 71 | |||
| 52 | 72 | ||
| 53 | func _input(event): | 73 | func _input(event): |
| 54 | if global.loaded and event is InputEventKey and event.pressed: | 74 | if global.loaded and event is InputEventKey and event.pressed: |
| @@ -57,7 +77,7 @@ func _input(event): | |||
| 57 | is_open = true | 77 | is_open = true |
| 58 | get_tree().paused = true | 78 | get_tree().paused = true |
| 59 | Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) | 79 | Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) |
| 60 | panel.visible = true | 80 | tabs.visible = true |
| 61 | entry.grab_focus() | 81 | entry.grab_focus() |
| 62 | get_viewport().set_input_as_handled() | 82 | get_viewport().set_input_as_handled() |
| 63 | else: | 83 | else: |
| @@ -72,7 +92,7 @@ func dismiss(): | |||
| 72 | if is_open: | 92 | if is_open: |
| 73 | get_tree().paused = false | 93 | get_tree().paused = false |
| 74 | Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) | 94 | Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) |
| 75 | panel.visible = false | 95 | tabs.visible = false |
| 76 | is_open = false | 96 | is_open = false |
| 77 | 97 | ||
| 78 | 98 | ||
| @@ -93,3 +113,23 @@ func text_entered(text): | |||
| 93 | return | 113 | return |
| 94 | 114 | ||
| 95 | ap.client.say(cmd) | 115 | ap.client.say(cmd) |
| 116 | |||
| 117 | |||
| 118 | func update_locations(): | ||
| 119 | var ap = global.get_node("Archipelago") | ||
| 120 | var gamedata = global.get_node("Gamedata") | ||
| 121 | |||
| 122 | tracker_label.clear() | ||
| 123 | tracker_label.push_font(preload("res://assets/fonts/Lingo2.ttf")) | ||
| 124 | tracker_label.push_font_size(24) | ||
| 125 | |||
| 126 | var location_names = [] | ||
| 127 | for location_id in ap.client._accessible_locations: | ||
| 128 | if not ap.client._checked_locations.has(location_id): | ||
| 129 | var location_name = gamedata.location_name_by_id.get(location_id, "(Unknown)") | ||
| 130 | location_names.append(location_name) | ||
| 131 | |||
| 132 | location_names.sort() | ||
| 133 | |||
| 134 | for location_name in location_names: | ||
| 135 | tracker_label.append_text("[p]%s[/p]" % location_name) | ||
| diff --git a/apworld/context.py b/apworld/context.py index 05f75a3..2a2149f 100644 --- a/apworld/context.py +++ b/apworld/context.py | |||
| @@ -8,9 +8,12 @@ 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 | PORT = 43182 |
| 16 | MESSAGE_MAX_SIZE = 16*1024*1024 | 19 | MESSAGE_MAX_SIZE = 16*1024*1024 |
| @@ -19,9 +22,11 @@ MESSAGE_MAX_SIZE = 16*1024*1024 | |||
| 19 | class Lingo2GameContext: | 22 | class Lingo2GameContext: |
| 20 | server: Endpoint | None | 23 | server: Endpoint | None |
| 21 | client: "Lingo2ClientContext" | 24 | client: "Lingo2ClientContext" |
| 25 | tracker: Tracker | ||
| 22 | 26 | ||
| 23 | def __init__(self): | 27 | def __init__(self): |
| 24 | self.server = None | 28 | self.server = None |
| 29 | self.tracker = Tracker() | ||
| 25 | 30 | ||
| 26 | def send_connected(self): | 31 | def send_connected(self): |
| 27 | msg = { | 32 | msg = { |
| @@ -84,6 +89,22 @@ class Lingo2GameContext: | |||
| 84 | 89 | ||
| 85 | async_start(self.send_msgs([msg]), name="notif") | 90 | async_start(self.send_msgs([msg]), name="notif") |
| 86 | 91 | ||
| 92 | def send_accessible_locations(self): | ||
| 93 | msg = { | ||
| 94 | "cmd": "AccessibleLocations", | ||
| 95 | "locations": list(self.tracker.accessible_locations), | ||
| 96 | } | ||
| 97 | |||
| 98 | async_start(self.send_msgs([msg]), name="accessible locations") | ||
| 99 | |||
| 100 | def send_update_locations(self, locations): | ||
| 101 | msg = { | ||
| 102 | "cmd": "UpdateLocations", | ||
| 103 | "locations": locations, | ||
| 104 | } | ||
| 105 | |||
| 106 | async_start(self.send_msgs([msg]), name="update locations") | ||
| 107 | |||
| 87 | async def send_msgs(self, msgs: list[Any]) -> None: | 108 | async def send_msgs(self, msgs: list[Any]) -> None: |
| 88 | """ `msgs` JSON serializable """ | 109 | """ `msgs` JSON serializable """ |
| 89 | if not self.server or not self.server.socket.open or self.server.socket.closed: | 110 | if not self.server or not self.server.socket.open or self.server.socket.closed: |
| @@ -119,7 +140,14 @@ class Lingo2ClientContext(CommonContext): | |||
| 119 | 140 | ||
| 120 | if self.game_ctx.server is not None: | 141 | if self.game_ctx.server is not None: |
| 121 | self.game_ctx.send_connected() | 142 | self.game_ctx.send_connected() |
| 143 | |||
| 144 | self.game_ctx.tracker.setup_slot(self.slot_data) | ||
| 145 | elif cmd == "RoomUpdate": | ||
| 146 | if self.game_ctx.server is not None: | ||
| 147 | self.game_ctx.send_update_locations(args["checked_locations"]) | ||
| 122 | elif cmd == "ReceivedItems": | 148 | elif cmd == "ReceivedItems": |
| 149 | self.game_ctx.tracker.set_collected_items(self.items_received) | ||
| 150 | |||
| 123 | if self.game_ctx.server is not None: | 151 | if self.game_ctx.server is not None: |
| 124 | cur_index = 0 | 152 | cur_index = 0 |
| 125 | items = [] | 153 | items = [] |
| @@ -141,6 +169,9 @@ class Lingo2ClientContext(CommonContext): | |||
| 141 | items.append(item_msg) | 169 | items.append(item_msg) |
| 142 | 170 | ||
| 143 | self.game_ctx.send_item_received(items) | 171 | self.game_ctx.send_item_received(items) |
| 172 | |||
| 173 | if any(ItemClassification.progression in ItemClassification(item.flags) for item in args["items"]): | ||
| 174 | self.game_ctx.send_accessible_locations() | ||
| 144 | elif cmd == "PrintJSON": | 175 | elif cmd == "PrintJSON": |
| 145 | if self.game_ctx.server is not None: | 176 | if self.game_ctx.server is not None: |
| 146 | if "receiving" in args and "item" in args and args["item"].player == self.slot: | 177 | if "receiving" in args and "item" in args and args["item"].player == self.slot: |
| @@ -195,6 +226,9 @@ class Lingo2ClientContext(CommonContext): | |||
| 195 | 226 | ||
| 196 | self.game_ctx.send_location_info(locations) | 227 | self.game_ctx.send_location_info(locations) |
| 197 | 228 | ||
| 229 | if cmd in ["Connected", "RoomUpdate"]: | ||
| 230 | self.game_ctx.tracker.set_checked_locations(self.checked_locations) | ||
| 231 | |||
| 198 | 232 | ||
| 199 | async def pipe_loop(ctx: Lingo2GameContext): | 233 | async def pipe_loop(ctx: Lingo2GameContext): |
| 200 | while not ctx.client.exit_event.is_set(): | 234 | while not ctx.client.exit_event.is_set(): |
| @@ -205,6 +239,7 @@ async def pipe_loop(ctx: Lingo2GameContext): | |||
| 205 | logger.info("Connected to Lingo 2!") | 239 | logger.info("Connected to Lingo 2!") |
| 206 | if ctx.client.auth is not None: | 240 | if ctx.client.auth is not None: |
| 207 | ctx.send_connected() | 241 | ctx.send_connected() |
| 242 | ctx.send_accessible_locations() | ||
| 208 | async for data in ctx.server.socket: | 243 | async for data in ctx.server.socket: |
| 209 | for msg in decode(data): | 244 | for msg in decode(data): |
| 210 | await process_game_cmd(ctx, msg) | 245 | await process_game_cmd(ctx, msg) |
| @@ -237,10 +272,7 @@ async def process_game_cmd(ctx: Lingo2GameContext, args: dict): | |||
| 237 | async def run_game(): | 272 | async def run_game(): |
| 238 | exe_file = settings.get_settings().lingo2_options.exe_file | 273 | exe_file = settings.get_settings().lingo2_options.exe_file |
| 239 | 274 | ||
| 240 | from worlds import AutoWorldRegister | 275 | if Lingo2World.zip_path is not None: |
| 241 | world = AutoWorldRegister.world_types["Lingo 2"] | ||
| 242 | |||
| 243 | if world.zip_path is not None: | ||
| 244 | # This is a packaged apworld. | 276 | # This is a packaged apworld. |
| 245 | init_scene = pkgutil.get_data(__name__, "client/run_from_apworld.tscn") | 277 | init_scene = pkgutil.get_data(__name__, "client/run_from_apworld.tscn") |
| 246 | init_path = Utils.local_path("data", "lingo2_init.tscn") | 278 | init_path = Utils.local_path("data", "lingo2_init.tscn") |
| @@ -254,7 +286,7 @@ async def run_game(): | |||
| 254 | "--scene", | 286 | "--scene", |
| 255 | init_path, | 287 | init_path, |
| 256 | "--", | 288 | "--", |
| 257 | str(world.zip_path.absolute()), | 289 | str(Lingo2World.zip_path.absolute()), |
| 258 | ], | 290 | ], |
| 259 | cwd=os.path.dirname(exe_file), | 291 | cwd=os.path.dirname(exe_file), |
| 260 | ) | 292 | ) |
| diff --git a/apworld/tracker.py b/apworld/tracker.py new file mode 100644 index 0000000..721e9b3 --- /dev/null +++ b/apworld/tracker.py | |||
| @@ -0,0 +1,67 @@ | |||
| 1 | from BaseClasses import MultiWorld, CollectionState, ItemClassification | ||
| 2 | from NetUtils import NetworkItem | ||
| 3 | from . import Lingo2World, Lingo2Item | ||
| 4 | from .regions import connect_ports_from_ut | ||
| 5 | from .options import Lingo2Options | ||
| 6 | |||
| 7 | PLAYER_NUM = 1 | ||
| 8 | |||
| 9 | |||
| 10 | class Tracker: | ||
| 11 | multiworld: MultiWorld | ||
| 12 | |||
| 13 | collected_items: dict[int, int] | ||
| 14 | checked_locations: set[int] | ||
| 15 | accessible_locations: set[int] | ||
| 16 | |||
| 17 | state: CollectionState | ||
| 18 | |||
| 19 | def __init__(self): | ||
| 20 | self.collected_items = {} | ||
| 21 | self.checked_locations = set() | ||
| 22 | self.accessible_locations = set() | ||
| 23 | |||
| 24 | def setup_slot(self, slot_data): | ||
| 25 | self.multiworld = MultiWorld(players=PLAYER_NUM) | ||
| 26 | world = Lingo2World(self.multiworld, PLAYER_NUM) | ||
| 27 | self.multiworld.worlds[1] = world | ||
| 28 | world.options = Lingo2Options(**{k: t(slot_data.get(k, t.default)) | ||
| 29 | for k, t in Lingo2Options.type_hints.items()}) | ||
| 30 | |||
| 31 | world.generate_early() | ||
| 32 | world.create_regions() | ||
| 33 | |||
| 34 | if world.options.shuffle_worldports: | ||
| 35 | port_pairings = {int(fp): int(tp) for fp, tp in slot_data["port_pairings"].items()} | ||
| 36 | connect_ports_from_ut(port_pairings, world) | ||
| 37 | |||
| 38 | self.state = CollectionState(self.multiworld) | ||
| 39 | |||
| 40 | def set_checked_locations(self, checked_locations: set[int]): | ||
| 41 | self.checked_locations = checked_locations.copy() | ||
| 42 | |||
| 43 | def set_collected_items(self, network_items: list[NetworkItem]): | ||
| 44 | self.collected_items = {} | ||
| 45 | |||
| 46 | for item in network_items: | ||
| 47 | self.collected_items[item.item] = self.collected_items.get(item.item, 0) + 1 | ||
| 48 | |||
| 49 | self.refresh_state() | ||
| 50 | |||
| 51 | def refresh_state(self): | ||
| 52 | self.state = CollectionState(self.multiworld) | ||
| 53 | |||
| 54 | for item_id, item_amount in self.collected_items.items(): | ||
| 55 | for i in range(item_amount): | ||
| 56 | self.state.collect(Lingo2Item(Lingo2World.static_logic.item_id_to_name.get(item_id), | ||
| 57 | ItemClassification.progression, item_id, PLAYER_NUM), prevent_sweep=True) | ||
| 58 | |||
| 59 | self.state.sweep_for_advancements() | ||
| 60 | |||
| 61 | self.accessible_locations = set() | ||
| 62 | |||
| 63 | for region in self.state.reachable_regions[PLAYER_NUM]: | ||
| 64 | for location in region.locations: | ||
| 65 | if location.address not in self.checked_locations and location.access_rule(self.state): | ||
| 66 | if location.address is not None: | ||
| 67 | self.accessible_locations.add(location.address) | ||
