about summary refs log tree commit diff stats
path: root/apworld/client/textclient.gd
blob: 0c4e675acdbb5ecb3ca513887f541f3afa1a55fa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
from typing import TYPE_CHECKING

from BaseClasses import Region
from .locations import Lingo2Location
from .player_logic import AccessRequirements
from .rules import make_location_lambda

if TYPE_CHECKING:
    from . import Lingo2World


def create_region(room, world: "Lingo2World") -> Region:
    new_region = Region(world.static_logic.get_room_region_name(room.id), world.player, world.multiworld)

    for location in world.player_logic.locations_by_room.get(room
extends CanvasLayer

var tabs
var panel
var label
var entry
var tracker_label
var is_open = false

var locations_overlay
var location_texture
var worldport_texture

var worldports_tab
var worldports_tree
var port_tree_item_by_map = {}
var port_tree_item_by_map_port = {}


func _ready():
	process_mode = ProcessMode.PROCESS_MODE_ALWAYS
	layer = 2

	locations_overlay = RichTextLabel.new()
	locations_overlay.name = "LocationsOverlay"
	locations_overlay.offset_top = 220
	locations_overlay.offset_bottom = 720
	locations_overlay.offset_left = 20
	locations_overlay.anchor_right = 1.0
	locations_overlay.offset_right = -10
	locations_overlay.scroll_active = false
	locations_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
	locations_overlay.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
	add_child(locations_overlay)
	update_locations_visibility()

	tabs = TabContainer.new()
	tabs.name = "Tabs"
	tabs.offset_left = 100
	tabs.offset_right = 1820
	tabs.offset_top = 100
	tabs.offset_bottom = 980
	tabs.visible = false
	tabs.theme = preload("res://assets/themes/baseUI.tres")
	tabs.add_theme_font_size_override("font_size", 36)
	add_child(tabs)

	panel = MarginContainer.new()
	panel.name = "Text Client"
	panel.add_theme_constant_override("margin_top", 60)
	panel.add_theme_constant_override("margin_left", 60)
	panel.add_theme_constant_override("margin_right", 60)
	panel.add_theme_constant_override("margin_bottom", 60)
	tabs.add_child(panel)

	label = RichTextLabel.new()
	label.set_name("Label")
	label.scroll_following = true
	label.selection_enabled = true
	label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	label.size_flags_vertical = Control.SIZE_EXPAND_FILL
	label.push_font(preload("res://assets/fonts/Lingo2.ttf"))
	label.push_font_size(36)

	var entry_style = StyleBoxFlat.new()
	entry_style.bg_color = Color(0.9, 0.9, 0.9, 1)

	entry = LineEdit.new()
	entry.set_name("Entry")
	entry.add_theme_font_override("font", preload("res://assets/fonts/Lingo2.ttf"))
	entry.add_theme_font_size_override("font_size", 36)
	entry.add_theme_color_override("font_color", Color(0, 0, 0, 1))
	entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1))
	entry.add_theme_stylebox_override("focus", entry_style)
	entry.text_submitted.connect(text_entered)

	var tc_arranger = VBoxContainer.new()
	tc_arranger.add_child(label)
	tc_arranger.add_child(entry)
	tc_arranger.add_theme_constant_override("separation", 40)
	panel.add_child(tc_arranger)

	var tracker_margins = MarginContainer.new()
	tracker_margins.name = "Locations"
	tracker_margins.add_theme_constant_override("margin_top", 60)
	tracker_margins.add_theme_constant_override("margin_left", 60)
	tracker_margins.add_theme_constant_override("margin_right", 60)
	tracker_margins.add_theme_constant_override("margin_bottom", 60)
	tabs.add_child(tracker_margins)

	tracker_label = RichTextLabel.new()
	tracker_margins.add_child(tracker_label)

	worldports_tab = MarginContainer.new()
	worldports_tab.name = "Worldports"
	worldports_tab.add_theme_constant_override("margin_top", 60)
	worldports_tab.add_theme_constant_override("margin_left", 60)
	worldports_tab.add_theme_constant_override("margin_right", 60)
	worldports_tab.add_theme_constant_override("margin_bottom", 60)
	tabs.add_child(worldports_tab)
	tabs.set_tab_hidden(2, true)

	worldports_tree = Tree.new()
	worldports_tree.columns = 2
	worldports_tree.hide_root = true
	worldports_tree.theme = preload("res://assets/themes/baseUI.tres")
	worldports_tree.add_theme_font_size_override("font_size", 24)
	worldports_tab.add_child(worldports_tree)

	var runtime = global.get_node("Runtime")
	var location_image = Image.new()
	location_image.load_png_from_buffer(runtime.read_path("assets/location.png"))
	location_texture = ImageTexture.create_from_image(location_image)

	var worldport_image = Image.new()
	worldport_image.load_png_from_buffer(runtime.read_path("assets/worldport.png"))
	worldport_texture = ImageTexture.create_from_image(worldport_image)


func _input(event):
	if global.loaded and event is InputEventKey and event.pressed:
		if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT):
			if !get_tree().paused:
				is_open = true
				get_tree().paused = true
				Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
				tabs.visible = true
				entry.grab_focus()
				get_viewport().set_input_as_handled()
			else:
				dismiss()
		elif event.keycode == KEY_ESCAPE:
			if is_open:
				dismiss()
				get_viewport().set_input_as_handled()


func dismiss():
	if is_open:
		get_tree().paused = false
		Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
		tabs.visible = false
		is_open = false


func parse_printjson(text):
	label.append_text("[p]" + text + "[/p]")


func text_entered(text):
	var ap = global.get_node("Archipelago")
	var cmd = text.trim_suffix("\n")
	entry.text = ""
	if OS.is_debug_build():
		if cmd.begins_with("/tp_map "):
			var new_map = cmd.substr(8)
			global.map = new_map
			global.sets_entry_point = false
			switcher.switch_map("res://objects/scenes/%s.tscn" % new_map)
			return

	ap.client.say(cmd)


func update_locations():
	var ap = global.get_node("Archipelago")
	var gamedata = global.get_node("Gamedata")

	tracker_label.clear()
	tracker_label.push_font(preload("res://assets/fonts/Lingo2.ttf"))
	tracker_label.push_font_size(24)

	locations_overlay.clear()
	locations_overlay.push_font(preload("res://assets/fonts/Lingo2.ttf"))
	locations_overlay.push_font_size(24)
	locations_overlay.push_color(Color(0.9, 0.9, 0.9, 1))
	locations_overlay.push_outline_color(Color(0, 0, 0, 1))
	locations_overlay.push_outline_size(2)

	const kLocation = 0
	const kWorldport = 1

	var location_names = []
	var type_by_name = {}
	for location_id in ap.client._accessible_locations:
		if not ap.client._checked_locations.has(location_id):
			var location_name = gamedata.location_name_by_id.get(location_id, "(Unknown)")
			location_names.append(location_name)
			type_by_name[location_name] = kLocation

	for port_id in ap.client._accessible_worldports:
		if not ap.client._checked_worldports.has(port_id):
			var port_name = gamedata.get_worldport_display_name(port_id)
			location_names.append(port_name)
			type_by_name[port_name] = kWorldport

	location_names.sort()

	var count = 0
	for location_name in location_names:
		tracker_label.append_text("[p]%s[/p]" % location_name)
		if count < 18:
			locations_overlay.push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT)
			locations_overlay.append_text(location_name)
			locations_overlay.append_text(" ")
			if type_by_name[location_name] == kLocation:
				locations_overlay.add_image(location_texture)
			elif type_by_name[location_name] == kWorldport:
				locations_overlay.add_image(worldport_texture)
			locations_overlay.pop()
		count += 1

	if count > 18:
		locations_overlay.append_text("[p align=right][lb]...[rb][/p]")


func update_locations_visibility():
	var ap = global.get_node("Archipelago")
	locations_overlay.visible = ap.show_locations


func setup_worldports():
	tabs.set_tab_hidden(2, false)

	var root_ti = worldports_tree.create_item(null)

	var ports_by_map_id = {}
	var display_names_by_map_id = {}
	var display_names_by_port_id = {}

	var ap = global.get_node("Archipelago")
	var gamedata = global.get_node("Gamedata")
	for fpid in ap.port_pairings:
		var port = gamedata.objects.get_ports()[fpid]
		var room = gamedata.objects.get_rooms()[port.get_room_id()]

		if not ports_by_map_id.has(room.get_map_id()):
			ports_by_map_id[room.get_map_id()] = []

			var map = gamedata.objects.get_maps()[room.get_map_id()]
			display_names_by_map_id[map.get_id()] = map.get_display_name()

		ports_by_map_id[room.get_map_id()].append(fpid)
		display_names_by_port_id[fpid] = port.get_display_name()

	var sorted_map_ids = ports_by_map_id.keys().duplicate()
	sorted_map_ids.sort_custom(
		func(a, b): return display_names_by_map_id[a] < display_names_by_map_id[b]
	)

	for map_id in sorted_map_ids:
		var map_ti = root_ti.create_child()
		map_ti.set_text(0, display_names_by_map_id[map_id])
		map_ti.visible = false
		map_ti.collapsed = true
		port_tree_item_by_map[map_id] = map_ti
		port_tree_item_by_map_port[map_id] = {}

		var port_ids = ports_by_map_id[map_id]
		port_ids.sort_custom(
			func(a, b): return display_names_by_port_id[a] < display_names_by_port_id[b]
		)

		for port_id in port_ids:
			var port_ti = map_ti.create_child()
			port_ti.set_text(0, display_names_by_port_id[port_id])
			port_ti.set_text(1, gamedata.get_worldport_display_name(ap.port_pairings[port_id]))
			port_ti.visible = false
			port_tree_item_by_map_port[map_id][port_id] = port_ti

	update_worldports()


func update_worldports():
	var ap = global.get_node("Archipelago")

	for map_id in port_tree_item_by_map_port.keys():
		var map_visible = false

		for port_id in port_tree_item_by_map_port[map_id].keys():
			var ti = port_tree_item_by_map_port[map_id][port_id]
			ti.visible = ap.client._checked_worldports.has(port_id)

			if ti.visible:
				map_visible = true

		port_tree_item_by_map[map_id].visible = map_visible


func reset():
	locations_overlay.clear()
	tabs.set_tab_hidden(2, true)
	port_tree_item_by_map.clear()
	port_tree_item_by_map_port.clear()
	worldports_tree.clear()