diff options
Diffstat (limited to 'apworld/client/textclient.gd')
-rw-r--r-- | apworld/client/textclient.gd | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/apworld/client/textclient.gd b/apworld/client/textclient.gd new file mode 100644 index 0000000..f785a03 --- /dev/null +++ b/apworld/client/textclient.gd | |||
@@ -0,0 +1,438 @@ | |||
1 | extends CanvasLayer | ||
2 | |||
3 | var tabs | ||
4 | var panel | ||
5 | var label | ||
6 | var entry | ||
7 | var is_open = false | ||
8 | |||
9 | var locations_overlay | ||
10 | var location_texture | ||
11 | var worldport_texture | ||
12 | var goal_texture | ||
13 | |||
14 | var tracker_tree | ||
15 | var tracker_loc_tree_item_by_id = {} | ||
16 | var tracker_port_tree_item_by_id = {} | ||
17 | var tracker_goal_tree_item = null | ||
18 | var tracker_object_by_index = {} | ||
19 | |||
20 | var worldports_tab | ||
21 | var worldports_tree | ||
22 | var port_tree_item_by_map = {} | ||
23 | var port_tree_item_by_map_port = {} | ||
24 | |||
25 | const kLocation = 0 | ||
26 | const kWorldport = 1 | ||
27 | const kGoal = 2 | ||
28 | |||
29 | |||
30 | func _ready(): | ||
31 | process_mode = ProcessMode.PROCESS_MODE_ALWAYS | ||
32 | layer = 2 | ||
33 | |||
34 | locations_overlay = RichTextLabel.new() | ||
35 | locations_overlay.name = "LocationsOverlay" | ||
36 | locations_overlay.offset_top = 220 | ||
37 | locations_overlay.offset_bottom = 720 | ||
38 | locations_overlay.offset_left = 20 | ||
39 | locations_overlay.anchor_right = 1.0 | ||
40 | locations_overlay.offset_right = -10 | ||
41 | locations_overlay.scroll_active = false | ||
42 | locations_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE | ||
43 | locations_overlay.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST | ||
44 | add_child(locations_overlay) | ||
45 | update_locations_visibility() | ||
46 | |||
47 | tabs = TabContainer.new() | ||
48 | tabs.name = "Tabs" | ||
49 | tabs.offset_left = 100 | ||
50 | tabs.offset_right = 1820 | ||
51 | tabs.offset_top = 100 | ||
52 | tabs.offset_bottom = 980 | ||
53 | tabs.visible = false | ||
54 | tabs.theme = preload("res://assets/themes/baseUI.tres") | ||
55 | tabs.add_theme_font_size_override("font_size", 36) | ||
56 | add_child(tabs) | ||
57 | |||
58 | panel = MarginContainer.new() | ||
59 | panel.name = "Text Client" | ||
60 | panel.add_theme_constant_override("margin_top", 60) | ||
61 | panel.add_theme_constant_override("margin_left", 60) | ||
62 | panel.add_theme_constant_override("margin_right", 60) | ||
63 | panel.add_theme_constant_override("margin_bottom", 60) | ||
64 | tabs.add_child(panel) | ||
65 | |||
66 | label = RichTextLabel.new() | ||
67 | label.set_name("Label") | ||
68 | label.scroll_following = true | ||
69 | label.selection_enabled = true | ||
70 | label.size_flags_horizontal = Control.SIZE_EXPAND_FILL | ||
71 | label.size_flags_vertical = Control.SIZE_EXPAND_FILL | ||
72 | label.push_font(preload("res://assets/fonts/Lingo2.ttf")) | ||
73 | label.push_font_size(30) | ||
74 | |||
75 | var entry_style = StyleBoxFlat.new() | ||
76 | entry_style.bg_color = Color(0.9, 0.9, 0.9, 1) | ||
77 | |||
78 | entry = LineEdit.new() | ||
79 | entry.set_name("Entry") | ||
80 | entry.add_theme_font_override("font", preload("res://assets/fonts/Lingo2.ttf")) | ||
81 | entry.add_theme_font_size_override("font_size", 36) | ||
82 | entry.add_theme_color_override("font_color", Color(0, 0, 0, 1)) | ||
83 | entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1)) | ||
84 | entry.add_theme_stylebox_override("focus", entry_style) | ||
85 | entry.text_submitted.connect(text_entered) | ||
86 | |||
87 | var tc_arranger = VBoxContainer.new() | ||
88 | tc_arranger.add_child(label) | ||
89 | tc_arranger.add_child(entry) | ||
90 | tc_arranger.add_theme_constant_override("separation", 40) | ||
91 | panel.add_child(tc_arranger) | ||
92 | |||
93 | var tracker_margins = MarginContainer.new() | ||
94 | tracker_margins.name = "Locations" | ||
95 | tracker_margins.add_theme_constant_override("margin_top", 60) | ||
96 | tracker_margins.add_theme_constant_override("margin_left", 60) | ||
97 | tracker_margins.add_theme_constant_override("margin_right", 60) | ||
98 | tracker_margins.add_theme_constant_override("margin_bottom", 60) | ||
99 | tabs.add_child(tracker_margins) | ||
100 | |||
101 | tracker_tree = Tree.new() | ||
102 | tracker_tree.columns = 3 | ||
103 | tracker_tree.hide_root = true | ||
104 | tracker_tree.add_theme_font_size_override("font_size", 24) | ||
105 | tracker_tree.add_theme_color_override("font_color", Color(0.8, 0.8, 0.8, 1)) | ||
106 | tracker_tree.add_theme_constant_override("v_separation", 1) | ||
107 | tracker_tree.item_edited.connect(_on_tracker_button_clicked) | ||
108 | tracker_tree.set_column_expand(0, false) | ||
109 | tracker_tree.set_column_expand(1, true) | ||
110 | tracker_tree.set_column_expand(2, false) | ||
111 | tracker_tree.set_column_custom_minimum_width(2, 200) | ||
112 | tracker_margins.add_child(tracker_tree) | ||
113 | |||
114 | worldports_tab = MarginContainer.new() | ||
115 | worldports_tab.name = "Worldports" | ||
116 | worldports_tab.add_theme_constant_override("margin_top", 60) | ||
117 | worldports_tab.add_theme_constant_override("margin_left", 60) | ||
118 | worldports_tab.add_theme_constant_override("margin_right", 60) | ||
119 | worldports_tab.add_theme_constant_override("margin_bottom", 60) | ||
120 | tabs.add_child(worldports_tab) | ||
121 | tabs.set_tab_hidden(2, true) | ||
122 | |||
123 | worldports_tree = Tree.new() | ||
124 | worldports_tree.columns = 2 | ||
125 | worldports_tree.hide_root = true | ||
126 | worldports_tree.theme = preload("res://assets/themes/baseUI.tres") | ||
127 | worldports_tree.add_theme_font_size_override("font_size", 24) | ||
128 | worldports_tab.add_child(worldports_tree) | ||
129 | |||
130 | var runtime = global.get_node("Runtime") | ||
131 | var location_image = Image.new() | ||
132 | location_image.load_png_from_buffer(runtime.read_path("assets/location.png")) | ||
133 | location_texture = ImageTexture.create_from_image(location_image) | ||
134 | |||
135 | var worldport_image = Image.new() | ||
136 | worldport_image.load_png_from_buffer(runtime.read_path("assets/worldport.png")) | ||
137 | worldport_texture = ImageTexture.create_from_image(worldport_image) | ||
138 | |||
139 | var goal_image = Image.new() | ||
140 | goal_image.load_png_from_buffer(runtime.read_path("assets/goal.png")) | ||
141 | goal_texture = ImageTexture.create_from_image(goal_image) | ||
142 | |||
143 | |||
144 | func _input(event): | ||
145 | if global.loaded and event is InputEventKey and event.pressed: | ||
146 | if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT): | ||
147 | if !get_tree().paused: | ||
148 | is_open = true | ||
149 | get_tree().paused = true | ||
150 | Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) | ||
151 | tabs.visible = true | ||
152 | entry.grab_focus() | ||
153 | get_viewport().set_input_as_handled() | ||
154 | else: | ||
155 | dismiss() | ||
156 | elif event.keycode == KEY_ESCAPE: | ||
157 | if is_open: | ||
158 | dismiss() | ||
159 | get_viewport().set_input_as_handled() | ||
160 | |||
161 | |||
162 | func dismiss(): | ||
163 | if is_open: | ||
164 | get_tree().paused = false | ||
165 | Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) | ||
166 | tabs.visible = false | ||
167 | is_open = false | ||
168 | |||
169 | |||
170 | func parse_printjson(text): | ||
171 | label.append_text("[p]" + text + "[/p]") | ||
172 | |||
173 | |||
174 | func text_entered(text): | ||
175 | var ap = global.get_node("Archipelago") | ||
176 | var cmd = text.trim_suffix("\n") | ||
177 | entry.text = "" | ||
178 | if OS.is_debug_build(): | ||
179 | if cmd.begins_with("/tp_map "): | ||
180 | var new_map = cmd.substr(8) | ||
181 | global.map = new_map | ||
182 | global.sets_entry_point = false | ||
183 | switcher.switch_map("res://objects/scenes/%s.tscn" % new_map) | ||
184 | return | ||
185 | |||
186 | ap.client.say(cmd) | ||
187 | |||
188 | |||
189 | func update_locations(reset_locations = true): | ||
190 | var ap = global.get_node("Archipelago") | ||
191 | var gamedata = global.get_node("Gamedata") | ||
192 | |||
193 | locations_overlay.clear() | ||
194 | locations_overlay.push_font(preload("res://assets/fonts/Lingo2.ttf")) | ||
195 | locations_overlay.push_font_size(24) | ||
196 | locations_overlay.push_color(Color(0.9, 0.9, 0.9, 1)) | ||
197 | locations_overlay.push_outline_color(Color(0, 0, 0, 1)) | ||
198 | locations_overlay.push_outline_size(2) | ||
199 | |||
200 | var locations = [] | ||
201 | for location_id in ap.client._accessible_locations: | ||
202 | if not ap.client._checked_locations.has(location_id): | ||
203 | var location_name = gamedata.location_name_by_id.get(location_id, "(Unknown)") | ||
204 | ( | ||
205 | locations | ||
206 | . append( | ||
207 | { | ||
208 | "name": location_name, | ||
209 | "type": kLocation, | ||
210 | "id": location_id, | ||
211 | } | ||
212 | ) | ||
213 | ) | ||
214 | |||
215 | for port_id in ap.client._accessible_worldports: | ||
216 | if not ap.client._checked_worldports.has(port_id): | ||
217 | var port_name = gamedata.get_worldport_display_name(port_id) | ||
218 | ( | ||
219 | locations | ||
220 | . append( | ||
221 | { | ||
222 | "name": port_name, | ||
223 | "type": kWorldport, | ||
224 | "id": port_id, | ||
225 | } | ||
226 | ) | ||
227 | ) | ||
228 | |||
229 | locations.sort_custom(func(a, b): return a["name"] < b["name"]) | ||
230 | |||
231 | if ap.client._goal_accessible: | ||
232 | var location_name = gamedata.ending_display_name_by_name[ap.kEndingNameByVictoryValue[ | ||
233 | ap.victory_condition | ||
234 | ]] | ||
235 | ( | ||
236 | locations | ||
237 | . push_front( | ||
238 | { | ||
239 | "name": location_name, | ||
240 | "type": kGoal, | ||
241 | } | ||
242 | ) | ||
243 | ) | ||
244 | |||
245 | var count = 0 | ||
246 | for location in locations: | ||
247 | if count < 18: | ||
248 | locations_overlay.push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT) | ||
249 | locations_overlay.append_text(location["name"]) | ||
250 | locations_overlay.append_text(" ") | ||
251 | if location["type"] == kLocation: | ||
252 | locations_overlay.add_image(location_texture) | ||
253 | elif location["type"] == kWorldport: | ||
254 | locations_overlay.add_image(worldport_texture) | ||
255 | elif location["type"] == kGoal: | ||
256 | locations_overlay.add_image(goal_texture) | ||
257 | locations_overlay.pop() | ||
258 | count += 1 | ||
259 | |||
260 | if count > 18: | ||
261 | locations_overlay.append_text("[p align=right][lb]...[rb][/p]") | ||
262 | |||
263 | if reset_locations: | ||
264 | reset_tracker_tab() | ||
265 | |||
266 | var root_ti = tracker_tree.create_item(null) | ||
267 | |||
268 | for location in locations: | ||
269 | var loc_row = root_ti.create_child() | ||
270 | loc_row.set_cell_mode(0, TreeItem.CELL_MODE_ICON) | ||
271 | loc_row.set_selectable(0, false) | ||
272 | loc_row.set_text(1, location["name"]) | ||
273 | loc_row.set_selectable(1, false) | ||
274 | loc_row.set_cell_mode(2, TreeItem.CELL_MODE_CUSTOM) | ||
275 | loc_row.set_text(2, "Show Path") | ||
276 | loc_row.set_custom_as_button(2, true) | ||
277 | loc_row.set_editable(2, true) | ||
278 | loc_row.set_selectable(2, false) | ||
279 | loc_row.set_text_alignment(2, HORIZONTAL_ALIGNMENT_CENTER) | ||
280 | |||
281 | if location["type"] == kLocation: | ||
282 | loc_row.set_icon(0, location_texture) | ||
283 | tracker_loc_tree_item_by_id[location["id"]] = loc_row | ||
284 | elif location["type"] == kWorldport: | ||
285 | loc_row.set_icon(0, worldport_texture) | ||
286 | tracker_port_tree_item_by_id[location["id"]] = loc_row | ||
287 | elif location["type"] == kGoal: | ||
288 | loc_row.set_icon(0, goal_texture) | ||
289 | tracker_goal_tree_item = loc_row | ||
290 | |||
291 | tracker_object_by_index[loc_row.get_index()] = location | ||
292 | else: | ||
293 | for loc_row in tracker_tree.get_root().get_children(): | ||
294 | loc_row.visible = false | ||
295 | |||
296 | for location_id in tracker_loc_tree_item_by_id.keys(): | ||
297 | if ( | ||
298 | ap.client._accessible_locations.has(location_id) | ||
299 | and not ap.client._checked_locations.has(location_id) | ||
300 | ): | ||
301 | tracker_loc_tree_item_by_id[location_id].visible = true | ||
302 | |||
303 | for port_id in tracker_port_tree_item_by_id.keys(): | ||
304 | if ( | ||
305 | ap.client._accessible_worldports.has(port_id) | ||
306 | and not ap.client._checked_worldports.has(port_id) | ||
307 | ): | ||
308 | tracker_port_tree_item_by_id[port_id].visible = true | ||
309 | |||
310 | if tracker_goal_tree_item != null and ap.client._goal_accessible: | ||
311 | tracker_goal_tree_item.visible = true | ||
312 | |||
313 | |||
314 | func update_locations_visibility(): | ||
315 | var ap = global.get_node("Archipelago") | ||
316 | locations_overlay.visible = ap.show_locations | ||
317 | |||
318 | |||
319 | func _on_tracker_button_clicked(): | ||
320 | var edited_item = tracker_tree.get_edited() | ||
321 | var edited_index = edited_item.get_index() | ||
322 | |||
323 | if tracker_object_by_index.has(edited_index): | ||
324 | var tracker_object = tracker_object_by_index[edited_index] | ||
325 | var ap = global.get_node("Archipelago") | ||
326 | var type_str = "" | ||
327 | if tracker_object["type"] == kLocation: | ||
328 | type_str = "location" | ||
329 | elif tracker_object["type"] == kWorldport: | ||
330 | type_str = "worldport" | ||
331 | elif tracker_object["type"] == kGoal: | ||
332 | type_str = "goal" | ||
333 | ap.client.getLogicalPath(type_str, tracker_object.get("id", null)) | ||
334 | |||
335 | |||
336 | func display_logical_path(object_type, object_id, paths): | ||
337 | var ap = global.get_node("Archipelago") | ||
338 | var gamedata = global.get_node("Gamedata") | ||
339 | |||
340 | var location_name = "(Unknown)" | ||
341 | if object_type == "location" and object_id != null: | ||
342 | location_name = gamedata.location_name_by_id.get(object_id, "(Unknown)") | ||
343 | elif object_type == "worldport" and object_id != null: | ||
344 | location_name = gamedata.get_worldport_display_name(object_id) | ||
345 | elif object_type == "goal": | ||
346 | location_name = gamedata.ending_display_name_by_name[ap.kEndingNameByVictoryValue[ | ||
347 | ap.victory_condition | ||
348 | ]] | ||
349 | |||
350 | label.append_text("[p]Path to %s:[/p]" % location_name) | ||
351 | label.append_text("[ol]" + "\n".join(paths) + "[/ol]") | ||
352 | |||
353 | panel.visible = true | ||
354 | |||
355 | |||
356 | func setup_worldports(): | ||
357 | tabs.set_tab_hidden(2, false) | ||
358 | |||
359 | var root_ti = worldports_tree.create_item(null) | ||
360 | |||
361 | var ports_by_map_id = {} | ||
362 | var display_names_by_map_id = {} | ||
363 | var display_names_by_port_id = {} | ||
364 | |||
365 | var ap = global.get_node("Archipelago") | ||
366 | var gamedata = global.get_node("Gamedata") | ||
367 | for fpid in ap.port_pairings: | ||
368 | var port = gamedata.objects.get_ports()[fpid] | ||
369 | var room = gamedata.objects.get_rooms()[port.get_room_id()] | ||
370 | |||
371 | if not ports_by_map_id.has(room.get_map_id()): | ||
372 | ports_by_map_id[room.get_map_id()] = [] | ||
373 | |||
374 | var map = gamedata.objects.get_maps()[room.get_map_id()] | ||
375 | display_names_by_map_id[map.get_id()] = map.get_display_name() | ||
376 | |||
377 | ports_by_map_id[room.get_map_id()].append(fpid) | ||
378 | display_names_by_port_id[fpid] = port.get_display_name() | ||
379 | |||
380 | var sorted_map_ids = ports_by_map_id.keys().duplicate() | ||
381 | sorted_map_ids.sort_custom( | ||
382 | func(a, b): return display_names_by_map_id[a] < display_names_by_map_id[b] | ||
383 | ) | ||
384 | |||
385 | for map_id in sorted_map_ids: | ||
386 | var map_ti = root_ti.create_child() | ||
387 | map_ti.set_text(0, display_names_by_map_id[map_id]) | ||
388 | map_ti.visible = false | ||
389 | map_ti.collapsed = true | ||
390 | port_tree_item_by_map[map_id] = map_ti | ||
391 | port_tree_item_by_map_port[map_id] = {} | ||
392 | |||
393 | var port_ids = ports_by_map_id[map_id] | ||
394 | port_ids.sort_custom( | ||
395 | func(a, b): return display_names_by_port_id[a] < display_names_by_port_id[b] | ||
396 | ) | ||
397 | |||
398 | for port_id in port_ids: | ||
399 | var port_ti = map_ti.create_child() | ||
400 | port_ti.set_text(0, display_names_by_port_id[port_id]) | ||
401 | port_ti.set_text(1, gamedata.get_worldport_display_name(ap.port_pairings[port_id])) | ||
402 | port_ti.visible = false | ||
403 | port_tree_item_by_map_port[map_id][port_id] = port_ti | ||
404 | |||
405 | update_worldports() | ||
406 | |||
407 | |||
408 | func update_worldports(): | ||
409 | var ap = global.get_node("Archipelago") | ||
410 | |||
411 | for map_id in port_tree_item_by_map_port.keys(): | ||
412 | var map_visible = false | ||
413 | |||
414 | for port_id in port_tree_item_by_map_port[map_id].keys(): | ||
415 | var ti = port_tree_item_by_map_port[map_id][port_id] | ||
416 | ti.visible = ap.client._checked_worldports.has(port_id) | ||
417 | |||
418 | if ti.visible: | ||
419 | map_visible = true | ||
420 | |||
421 | port_tree_item_by_map[map_id].visible = map_visible | ||
422 | |||
423 | |||
424 | func reset(): | ||
425 | locations_overlay.clear() | ||
426 | tabs.set_tab_hidden(2, true) | ||
427 | port_tree_item_by_map.clear() | ||
428 | port_tree_item_by_map_port.clear() | ||
429 | worldports_tree.clear() | ||
430 | reset_tracker_tab() | ||
431 | |||
432 | |||
433 | func reset_tracker_tab(): | ||
434 | tracker_loc_tree_item_by_id.clear() | ||
435 | tracker_port_tree_item_by_id.clear() | ||
436 | tracker_goal_tree_item = null | ||
437 | tracker_object_by_index.clear() | ||
438 | tracker_tree.clear() | ||