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