This commit is contained in:
Marek Le
2026-05-09 23:37:26 +02:00
parent 6d28b04c12
commit 2d4002bd3f
263 changed files with 5250 additions and 4597 deletions

497
scenes/hud/hud.gd Normal file
View File

@@ -0,0 +1,497 @@
extends CanvasLayer
@onready var hp_bar: ProgressBar = %HpBar
@onready var hp_label: Label = %HpLabel
@onready var shield_bar: ProgressBar = %ShieldBar
@onready var shield_label: Label = %ShieldLabel
@onready var xp_bar: ProgressBar = %XpBar
@onready var level_label: Label = %LevelLabel
@onready var wave_label: Label = %WaveLabel
@onready var timer_label: Label = %WaveTimer
@onready var village_bar: ProgressBar = %VillageBar
@onready var role_icon: Panel = %RoleIcon
@onready var role_label: Label = %RoleLabel
@onready var ability_box: HBoxContainer = %AbilityBox
@onready var death_overlay: Control = %DeathOverlay
@onready var death_label: Label = %DeathLabel
@onready var chat_log: RichTextLabel = %ChatLog
@onready var chat_input: LineEdit = %ChatInput
@onready var inventory_panel: Control = %InventoryPanel
@onready var inventory_list: VBoxContainer = %InventoryList
@onready var crafting_panel: Control = %CraftingPanel
@onready var crafting_list: VBoxContainer = %CraftingList
@onready var build_panel: Control = %BuildPanel
@onready var build_list: HBoxContainer = %BuildList
@onready var dialog_panel: Control = %DialogPanel
@onready var dialog_npc: Label = %DialogNpc
@onready var dialog_log: RichTextLabel = %DialogLog
@onready var dialog_input: LineEdit = %DialogInput
@onready var map_panel: Control = %MapPanel
@onready var map_canvas: Control = %MapCanvas
@onready var minimap_canvas: Control = %MinimapCanvas
@onready var pause_panel: Control = %PausePanel
@onready var game_over_overlay: Control = %GameOverOverlay
@onready var ability_buttons: Array = []
var local_player: Node = null
var dialog_npc_node: Node = null
var build_selected: int = 0
var build_rotation: float = 0.0
var build_preview: MeshInstance3D = null
var build_active: bool = false
var _minimap_accum: float = 0.0
func _ready() -> void:
add_to_group("dialog_ui")
EventBus.health_changed.connect(_on_health_changed)
EventBus.shield_changed.connect(_on_shield_changed)
EventBus.cooldown_tick.connect(_on_cooldown_tick)
EventBus.role_changed.connect(_on_role_changed)
EventBus.entity_died.connect(_on_entity_died)
EventBus.entity_respawned.connect(_on_respawned)
EventBus.wave_started.connect(_on_wave_started)
EventBus.wave_timer_tick.connect(_on_wave_tick)
EventBus.village_damaged.connect(_on_village_damaged)
EventBus.village_destroyed.connect(_on_village_destroyed)
EventBus.invasion_started.connect(_on_invasion_started)
EventBus.xp_gained.connect(_on_xp_gained)
EventBus.level_up.connect(_on_level_up)
EventBus.dialog_opened.connect(_on_dialog_opened)
EventBus.inventory_changed.connect(_on_inventory_changed)
EventBus.chat_message.connect(_on_chat)
chat_input.text_submitted.connect(_on_chat_submitted)
dialog_input.text_submitted.connect(_on_dialog_submitted)
death_overlay.visible = false
inventory_panel.visible = false
crafting_panel.visible = false
build_panel.visible = false
dialog_panel.visible = false
map_panel.visible = false
pause_panel.visible = false
game_over_overlay.visible = false
set_process(true)
_wire_ability_buttons()
call_deferred("_populate_build_list")
func _process(_delta: float) -> void:
if local_player == null:
local_player = _find_local_player()
if local_player:
var role: int = int(Stats.get_stat(local_player, "role", GameState.ROLE_DAMAGE))
_on_role_changed(local_player, role)
_refresh_vitals()
if build_active:
_update_build_preview()
_update_minimap()
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("inventory"):
_toggle_panel(inventory_panel)
_refresh_inventory()
elif event.is_action_pressed("crafting"):
_toggle_panel(crafting_panel)
_refresh_crafting()
elif event.is_action_pressed("build_mode"):
_toggle_build_mode()
elif event.is_action_pressed("map"):
_toggle_panel(map_panel)
elif event.is_action_pressed("chat"):
chat_input.grab_focus()
_capture_ui(true)
elif event.is_action_pressed("pause"):
if dialog_panel.visible:
dialog_panel.visible = false
_capture_ui(false)
elif build_active:
_toggle_build_mode()
elif inventory_panel.visible or crafting_panel.visible or map_panel.visible:
inventory_panel.visible = false
crafting_panel.visible = false
map_panel.visible = false
_capture_ui(false)
else:
_toggle_pause()
elif build_active:
if event.is_action_pressed("rotate_build"):
build_rotation = wrapf(build_rotation + PI * 0.5, 0.0, TAU)
elif event.is_action_pressed("ability_1"):
_select_build(0)
elif event.is_action_pressed("ability_2"):
_select_build(1)
elif event.is_action_pressed("ability_3"):
_select_build(2)
elif event.is_action_pressed("ability_4"):
_select_build(3)
elif event is InputEventMouseButton and event.pressed:
if event.button_index == MOUSE_BUTTON_LEFT:
_try_place()
elif event.button_index == MOUSE_BUTTON_MIDDLE:
_try_remove()
func _wire_ability_buttons() -> void:
for c in ability_box.get_children():
if c is Button:
ability_buttons.append(c)
func _toggle_panel(panel: Control) -> void:
panel.visible = not panel.visible
_capture_ui(_any_panel_visible())
func _any_panel_visible() -> bool:
return inventory_panel.visible or crafting_panel.visible or dialog_panel.visible or pause_panel.visible
func _capture_ui(v: bool) -> void:
if local_player and local_player.has_method("set_ui_capturing"):
local_player.set_ui_capturing(v)
func _find_local_player() -> Node:
for p in get_tree().get_nodes_in_group("player"):
if p.is_multiplayer_authority():
return p
return null
func _refresh_vitals() -> void:
if local_player == null:
return
var hp: float = float(Stats.get_stat(local_player, "health", 0.0))
var max_hp: float = float(Stats.get_stat(local_player, "max_health", 1.0))
_on_health_changed(local_player, hp, max_hp)
var shield: float = float(Stats.get_stat(local_player, "shield", 0.0))
var max_shield: float = float(Stats.get_stat(local_player, "max_shield", 0.0))
_on_shield_changed(local_player, shield, max_shield)
var xp: float = float(Stats.get_stat(local_player, "xp", 0.0))
var to_next: float = float(Stats.get_stat(local_player, "xp_to_next", 50.0))
xp_bar.max_value = to_next
xp_bar.value = xp
level_label.text = "Lv %d" % int(Stats.get_stat(local_player, "level", 1))
func _on_health_changed(entity: Node, current: float, max: float) -> void:
if entity != local_player:
return
hp_bar.max_value = max
hp_bar.value = current
hp_label.text = "%d / %d" % [int(current), int(max)]
func _on_shield_changed(entity: Node, current: float, max: float) -> void:
if entity != local_player:
return
shield_bar.max_value = max if max > 0 else 1
shield_bar.value = current
shield_label.text = "%d / %d" % [int(current), int(max)]
func _on_cooldown_tick(entity: Node, cds: PackedFloat32Array, _max_cds: PackedFloat32Array, gcd: float) -> void:
if entity != local_player:
return
for i in range(min(ability_buttons.size(), cds.size())):
var btn: Button = ability_buttons[i]
if cds[i] > 0.0:
btn.text = "%d\n%.1f" % [i + 1, cds[i]]
btn.disabled = true
elif gcd > 0.0:
btn.text = "%d\nGCD" % (i + 1)
btn.disabled = true
else:
btn.text = "%d" % (i + 1)
btn.disabled = false
func _on_role_changed(player: Node, role: int) -> void:
if player != local_player and player != _find_local_player():
return
if local_player == null:
local_player = player
match role:
GameState.ROLE_TANK:
role_label.text = "T"
role_icon.modulate = Color(0.3, 0.5, 0.95)
GameState.ROLE_DAMAGE:
role_label.text = "D"
role_icon.modulate = Color(0.95, 0.3, 0.3)
GameState.ROLE_HEALER:
role_label.text = "H"
role_icon.modulate = Color(0.4, 0.85, 0.4)
func _on_entity_died(entity: Node) -> void:
if entity != local_player:
return
death_overlay.visible = true
death_label.text = "Respawning..."
func _on_respawned(entity: Node) -> void:
if entity != local_player:
return
death_overlay.visible = false
func _on_wave_started(wave: int) -> void:
wave_label.text = "Wave %d" % wave
func _on_wave_tick(seconds: float) -> void:
var m := int(seconds) / 60
var s := int(seconds) % 60
timer_label.text = "%02d:%02d" % [m, s]
func _on_village_damaged(current: float, max: float) -> void:
village_bar.max_value = max
village_bar.value = current
func _on_village_destroyed() -> void:
game_over_overlay.visible = true
func _on_invasion_started() -> void:
timer_label.modulate = Color(1.0, 0.4, 0.3)
func _on_xp_gained(player: Node, _amount: float) -> void:
if player != local_player:
return
var xp: float = float(Stats.get_stat(player, "xp", 0.0))
var to_next: float = float(Stats.get_stat(player, "xp_to_next", 50.0))
xp_bar.max_value = to_next
xp_bar.value = xp
func _on_level_up(player: Node, new_level: int) -> void:
if player != local_player:
return
level_label.text = "Lv %d" % new_level
_refresh_vitals()
func _on_dialog_opened(player: Node, npc: Node) -> void:
if player != local_player:
return
dialog_panel.visible = true
dialog_npc_node = npc
dialog_npc.text = npc.profile.display_name
dialog_log.text = "[i]" + npc.profile.greeting + "[/i]\n"
dialog_input.text = ""
dialog_input.grab_focus()
_capture_ui(true)
func _on_dialog_submitted(text: String) -> void:
if dialog_npc_node == null:
return
var dialog_sys: Node = _find_system("DialogSystem")
if dialog_sys == null:
return
dialog_log.append_text("[b]Du:[/b] " + text + "\n[i]...[/i]\n")
dialog_input.text = ""
dialog_sys.ask(dialog_npc_node, local_player, text)
func show_answer(text: String) -> void:
if dialog_npc_node == null:
return
dialog_log.text = dialog_log.text.replace("[i]...[/i]\n", "")
dialog_log.append_text("[b]" + dialog_npc_node.profile.display_name + ":[/b] " + text + "\n")
func _on_inventory_changed(player: Node) -> void:
if player != local_player:
return
_refresh_inventory()
func _refresh_inventory() -> void:
for c in inventory_list.get_children():
c.queue_free()
if local_player == null:
return
var inv_sys: Node = _find_system("InventorySystem")
if inv_sys == null:
return
var inv: Dictionary = inv_sys.get_inventory(local_player)
if inv.is_empty():
var lbl := Label.new()
lbl.text = "(empty)"
inventory_list.add_child(lbl)
return
for k in inv.keys():
var lbl := Label.new()
lbl.text = "%s: %d" % [str(k), inv[k]]
inventory_list.add_child(lbl)
func _refresh_crafting() -> void:
for c in crafting_list.get_children():
c.queue_free()
if local_player == null:
return
var c_sys: Node = _find_system("CraftingSystem")
var inv_sys: Node = _find_system("InventorySystem")
if c_sys == null or inv_sys == null:
return
for r in c_sys.get_recipes():
var btn := Button.new()
var inputs_str: String = ""
for k in r.inputs.keys():
inputs_str += "%s x%d " % [str(k), r.inputs[k]]
btn.text = "%s (%s)" % [r.name, inputs_str.strip_edges()]
btn.disabled = not c_sys.can_craft(local_player, r)
btn.pressed.connect(func(): c_sys.craft(local_player, r.id); _refresh_crafting())
crafting_list.add_child(btn)
func _populate_build_list() -> void:
for c in build_list.get_children():
c.queue_free()
var b_sys: Node = _find_system("BuildingSystem")
if b_sys == null:
return
var bps: Array = b_sys.get_blueprints()
for i in range(bps.size()):
var btn := Button.new()
btn.text = "%d %s\n%s x%d" % [i + 1, bps[i].name, str(bps[i].material), bps[i].cost]
btn.toggle_mode = true
btn.button_pressed = (i == 0)
btn.pressed.connect(func(): _select_build(i))
build_list.add_child(btn)
func _select_build(idx: int) -> void:
build_selected = idx
var btns := build_list.get_children()
for i in range(btns.size()):
if btns[i] is Button:
(btns[i] as Button).button_pressed = (i == idx)
if build_preview:
_update_preview_mesh()
func _toggle_build_mode() -> void:
build_active = not build_active
build_panel.visible = build_active
if local_player and local_player.has_method("set_build_mode"):
local_player.set_build_mode(build_active)
if build_active:
_create_build_preview()
elif build_preview:
build_preview.queue_free()
build_preview = null
func _create_build_preview() -> void:
if build_preview:
build_preview.queue_free()
var world: Node = get_tree().current_scene
if world == null:
return
build_preview = MeshInstance3D.new()
build_preview.cast_shadow = MeshInstance3D.SHADOW_CASTING_SETTING_OFF
world.add_child(build_preview)
_update_preview_mesh()
func _update_preview_mesh() -> void:
if build_preview == null:
return
var b_sys: Node = _find_system("BuildingSystem")
if b_sys == null:
return
var bp: Dictionary = b_sys.get_blueprints()[build_selected]
var box := BoxMesh.new()
box.size = bp.size
build_preview.mesh = box
var mat := StandardMaterial3D.new()
var c: Color = bp.color
c.a = 0.5
mat.albedo_color = c
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.flags_unshaded = true
build_preview.material_override = mat
func _update_build_preview() -> void:
if build_preview == null or local_player == null:
return
var b_sys: Node = _find_system("BuildingSystem")
if b_sys == null:
return
var pos: Vector3 = _ground_under_cursor()
var snapped: Vector3 = b_sys.snap_position(pos)
var bp: Dictionary = b_sys.get_blueprints()[build_selected]
build_preview.global_position = snapped + Vector3(0, bp.size.y * 0.5, 0)
build_preview.rotation.y = build_rotation
func _ground_under_cursor() -> Vector3:
var cam: Camera3D = local_player.camera if local_player.has_method("get") else null
if cam == null:
return Vector3.ZERO
var mouse := get_viewport().get_mouse_position()
var from := cam.project_ray_origin(mouse)
var dir := cam.project_ray_normal(mouse)
if abs(dir.y) < 0.001:
return from
var t: float = -from.y / dir.y
if t <= 0:
return from
return from + dir * t
func _try_place() -> void:
if local_player == null:
return
var b_sys: Node = _find_system("BuildingSystem")
if b_sys == null:
return
var bps: Array = b_sys.get_blueprints()
var bp: Dictionary = bps[build_selected]
var pos: Vector3 = _ground_under_cursor()
b_sys.place(local_player, bp.id, pos, build_rotation)
func _try_remove() -> void:
if local_player == null:
return
var cam: Camera3D = local_player.camera if local_player.has_method("get") else null
if cam == null:
return
var mouse := get_viewport().get_mouse_position()
var from := cam.project_ray_origin(mouse)
var to := from + cam.project_ray_normal(mouse) * 100.0
var space: PhysicsDirectSpaceState3D = local_player.get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(from, to)
query.collision_mask = 16
var hit: Dictionary = space.intersect_ray(query)
if hit.is_empty():
return
var node: Node = hit.collider
while node and not node.is_in_group("buildings"):
node = node.get_parent()
if node:
var b_sys: Node = _find_system("BuildingSystem")
if b_sys:
b_sys.remove(local_player, node.get_path())
func _on_chat(_peer_id: int, sender: String, text: String) -> void:
chat_log.append_text("[b]%s:[/b] %s\n" % [sender, text])
func _on_chat_submitted(text: String) -> void:
var c_sys: Node = _find_system("ChatSystem")
if c_sys:
c_sys.send(text)
chat_input.text = ""
chat_input.release_focus()
_capture_ui(_any_panel_visible())
func _toggle_pause() -> void:
pause_panel.visible = not pause_panel.visible
if pause_panel.visible and multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
GameState.set_paused(true)
else:
GameState.set_paused(false)
_capture_ui(_any_panel_visible())
func _on_resume_pressed() -> void:
pause_panel.visible = false
GameState.set_paused(false)
_capture_ui(_any_panel_visible())
func _on_quit_pressed() -> void:
Net.disconnect_net()
GameState.set_paused(false)
GameState.change_scene(GameState.SCENE_MAIN_MENU)
func _on_game_over_restart() -> void:
Net.disconnect_net()
GameState.set_paused(false)
GameState.change_scene(GameState.SCENE_MAIN_MENU)
func _update_minimap() -> void:
_minimap_accum += get_process_delta_time()
if _minimap_accum < 0.20:
return
_minimap_accum = 0.0
minimap_canvas.queue_redraw()
if map_panel.visible:
map_canvas.queue_redraw()
func _find_system(name: String) -> Node:
var n: Node = get_tree().root.get_node_or_null("World/Systems/" + name)
if n == null:
n = get_tree().root.get_node_or_null("Dungeon/Systems/" + name)
return n

1
scenes/hud/hud.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://dimp3y1yydhw2

View File

@@ -1,237 +1,411 @@
[gd_scene format=3]
[gd_scene load_steps=3 format=3 uid="uid://b0hud00001"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ability_active"]
bg_color = Color(0.2, 0.2, 0.2, 0.8)
border_width_bottom = 2
border_width_left = 2
border_width_right = 2
border_width_top = 2
border_color = Color(0.8, 0.8, 0.8, 1)
[ext_resource type="Script" path="res://scenes/hud/hud.gd" id="1"]
[ext_resource type="Script" path="res://scenes/hud/minimap.gd" id="2"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ability_round"]
bg_color = Color(0.2, 0.2, 0.2, 0.8)
border_width_bottom = 2
border_width_left = 2
border_width_right = 2
border_width_top = 2
border_color = Color(0.8, 0.8, 0.8, 1)
corner_radius_top_left = 22
corner_radius_top_right = 22
corner_radius_bottom_right = 22
corner_radius_bottom_left = 22
[node name="HUD" type="CanvasLayer"]
script = ExtResource("1")
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_bg"]
bg_color = Color(0.3, 0.1, 0.1, 1)
[node name="VitalsPanel" type="PanelContainer" parent="."]
offset_left = 12.0
offset_top = 12.0
offset_right = 312.0
offset_bottom = 130.0
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill"]
bg_color = Color(0.2, 0.8, 0.2, 1)
[node name="VBox" type="VBoxContainer" parent="VitalsPanel"]
layout_mode = 2
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_bg"]
bg_color = Color(0.1, 0.1, 0.3, 1)
[node name="HpRow" type="HBoxContainer" parent="VitalsPanel/VBox"]
layout_mode = 2
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"]
bg_color = Color(0.2, 0.5, 0.9, 1)
[node name="HUD" type="CanvasLayer" groups=["hud"]]
[node name="HealthBar" type="ProgressBar" parent="."]
offset_left = 10.0
offset_top = 10.0
offset_right = 210.0
offset_bottom = 30.0
theme_override_styles/background = SubResource("StyleBoxFlat_health_bg")
theme_override_styles/fill = SubResource("StyleBoxFlat_health_fill")
[node name="HpBar" type="ProgressBar" parent="VitalsPanel/VBox/HpRow"]
unique_name_in_owner = true
custom_minimum_size = Vector2(220, 18)
layout_mode = 2
max_value = 100.0
value = 100.0
show_percentage = false
[node name="HealthLabel" type="Label" parent="HealthBar"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_font_sizes/font_size = 12
[node name="HpLabel" type="Label" parent="VitalsPanel/VBox/HpRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "100/100"
horizontal_alignment = 1
vertical_alignment = 1
[node name="ShieldBar" type="ProgressBar" parent="."]
offset_left = 10.0
offset_top = 35.0
offset_right = 210.0
offset_bottom = 55.0
theme_override_styles/background = SubResource("StyleBoxFlat_shield_bg")
theme_override_styles/fill = SubResource("StyleBoxFlat_shield_fill")
[node name="ShieldRow" type="HBoxContainer" parent="VitalsPanel/VBox"]
layout_mode = 2
[node name="ShieldBar" type="ProgressBar" parent="VitalsPanel/VBox/ShieldRow"]
unique_name_in_owner = true
custom_minimum_size = Vector2(220, 14)
layout_mode = 2
max_value = 100.0
value = 0.0
[node name="ShieldLabel" type="Label" parent="VitalsPanel/VBox/ShieldRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "0/0"
horizontal_alignment = 1
[node name="XpRow" type="HBoxContainer" parent="VitalsPanel/VBox"]
layout_mode = 2
[node name="LevelLabel" type="Label" parent="VitalsPanel/VBox/XpRow"]
unique_name_in_owner = true
layout_mode = 2
text = "Lv 1"
[node name="XpBar" type="ProgressBar" parent="VitalsPanel/VBox/XpRow"]
unique_name_in_owner = true
custom_minimum_size = Vector2(180, 12)
layout_mode = 2
size_flags_horizontal = 3
max_value = 50.0
value = 50.0
show_percentage = false
value = 0.0
[node name="ShieldLabel" type="Label" parent="ShieldBar"]
[node name="VillageRow" type="HBoxContainer" parent="VitalsPanel/VBox"]
layout_mode = 2
[node name="VillageLabelStatic" type="Label" parent="VitalsPanel/VBox/VillageRow"]
layout_mode = 2
text = "Village"
[node name="VillageBar" type="ProgressBar" parent="VitalsPanel/VBox/VillageRow"]
unique_name_in_owner = true
custom_minimum_size = Vector2(180, 12)
layout_mode = 2
size_flags_horizontal = 3
max_value = 1000.0
value = 1000.0
[node name="WaveBox" type="PanelContainer" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -120.0
offset_top = 12.0
offset_right = 120.0
offset_bottom = 64.0
[node name="VBox" type="VBoxContainer" parent="WaveBox"]
layout_mode = 2
[node name="WaveLabel" type="Label" parent="WaveBox/VBox"]
unique_name_in_owner = true
layout_mode = 2
horizontal_alignment = 1
text = "Wave 1"
theme_override_font_sizes/font_size = 18
[node name="WaveTimer" type="Label" parent="WaveBox/VBox"]
unique_name_in_owner = true
layout_mode = 2
horizontal_alignment = 1
text = "10:00"
[node name="MinimapPanel" type="PanelContainer" parent="."]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -212.0
offset_top = 12.0
offset_right = -12.0
offset_bottom = 212.0
[node name="MinimapCanvas" type="Control" parent="MinimapPanel"]
unique_name_in_owner = true
layout_mode = 2
script = ExtResource("2")
world_size = 80.0
[node name="AbilityBox" type="HBoxContainer" parent="."]
unique_name_in_owner = true
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = -200.0
offset_top = -76.0
offset_right = 280.0
offset_bottom = -12.0
theme_override_constants/separation = 6
[node name="RoleIcon" type="Panel" parent="AbilityBox"]
unique_name_in_owner = true
custom_minimum_size = Vector2(56, 56)
layout_mode = 2
[node name="RoleLabel" type="Label" parent="AbilityBox/RoleIcon"]
unique_name_in_owner = true
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
text = "D"
horizontal_alignment = 1
vertical_alignment = 1
theme_override_font_sizes/font_size = 28
[node name="A1" type="Button" parent="AbilityBox"]
custom_minimum_size = Vector2(56, 56)
layout_mode = 2
text = "1"
[node name="A2" type="Button" parent="AbilityBox"]
custom_minimum_size = Vector2(56, 56)
layout_mode = 2
text = "2"
[node name="A3" type="Button" parent="AbilityBox"]
custom_minimum_size = Vector2(56, 56)
layout_mode = 2
text = "3"
[node name="A4" type="Button" parent="AbilityBox"]
custom_minimum_size = Vector2(56, 56)
layout_mode = 2
text = "4"
[node name="ChatPanel" type="PanelContainer" parent="."]
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 12.0
offset_top = -200.0
offset_right = 360.0
offset_bottom = -12.0
[node name="VBox" type="VBoxContainer" parent="ChatPanel"]
layout_mode = 2
[node name="ChatLog" type="RichTextLabel" parent="ChatPanel/VBox"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 140)
layout_mode = 2
bbcode_enabled = true
scroll_following = true
fit_content = true
[node name="ChatInput" type="LineEdit" parent="ChatPanel/VBox"]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "Press Y to chat, Enter to send"
[node name="DeathOverlay" type="Control" parent="."]
unique_name_in_owner = true
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
[node name="Background" type="ColorRect" parent="DeathOverlay"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_font_sizes/font_size = 12
text = "50/50"
color = Color(0.0, 0.0, 0.0, 0.5)
[node name="DeathLabel" type="Label" parent="DeathOverlay"]
unique_name_in_owner = true
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -200.0
offset_top = -40.0
offset_right = 200.0
offset_bottom = 40.0
text = "Respawning..."
horizontal_alignment = 1
vertical_alignment = 1
theme_override_font_sizes/font_size = 32
[node name="RespawnTimer" type="Label" parent="."]
anchors_preset = 8
[node name="InventoryPanel" type="PanelContainer" parent="."]
unique_name_in_owner = true
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -30.0
offset_right = 50.0
offset_bottom = 30.0
grow_horizontal = 2
grow_vertical = 2
theme_override_font_sizes/font_size = 48
horizontal_alignment = 1
vertical_alignment = 1
offset_left = -200.0
offset_top = -200.0
offset_right = 200.0
offset_bottom = 200.0
[node name="AbilityBar" type="HBoxContainer" parent="."]
anchors_preset = 7
[node name="VBox" type="VBoxContainer" parent="InventoryPanel"]
layout_mode = 2
[node name="Title" type="Label" parent="InventoryPanel/VBox"]
layout_mode = 2
text = "Inventory (I)"
horizontal_alignment = 1
[node name="InventoryList" type="VBoxContainer" parent="InventoryPanel/VBox"]
unique_name_in_owner = true
layout_mode = 2
[node name="CraftingPanel" type="PanelContainer" parent="."]
unique_name_in_owner = true
anchor_left = 0.5
anchor_top = 1.0
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -260.0
offset_top = -240.0
offset_right = 260.0
offset_bottom = 240.0
[node name="VBox" type="VBoxContainer" parent="CraftingPanel"]
layout_mode = 2
[node name="Title" type="Label" parent="CraftingPanel/VBox"]
layout_mode = 2
text = "Crafting (C)"
horizontal_alignment = 1
[node name="CraftingList" type="VBoxContainer" parent="CraftingPanel/VBox"]
unique_name_in_owner = true
layout_mode = 2
[node name="BuildPanel" type="PanelContainer" parent="."]
unique_name_in_owner = true
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = -130.0
offset_top = -60.0
offset_right = 130.0
offset_bottom = -10.0
grow_horizontal = 2
grow_vertical = 0
theme_override_constants/separation = 5
offset_left = 12.0
offset_top = -260.0
offset_right = 360.0
offset_bottom = -210.0
[node name="ClassIcon" type="Panel" parent="AbilityBar"]
custom_minimum_size = Vector2(45, 45)
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_round")
[node name="VBox" type="VBoxContainer" parent="BuildPanel"]
layout_mode = 2
[node name="Label" type="Label" parent="AbilityBar/ClassIcon"]
[node name="Title" type="Label" parent="BuildPanel/VBox"]
layout_mode = 2
text = "Build Mode (B)"
horizontal_alignment = 1
[node name="BuildList" type="HBoxContainer" parent="BuildPanel/VBox"]
unique_name_in_owner = true
layout_mode = 2
[node name="DialogPanel" type="PanelContainer" parent="."]
unique_name_in_owner = true
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -300.0
offset_top = -200.0
offset_right = 300.0
offset_bottom = 200.0
[node name="VBox" type="VBoxContainer" parent="DialogPanel"]
layout_mode = 2
[node name="DialogNpc" type="Label" parent="DialogPanel/VBox"]
unique_name_in_owner = true
layout_mode = 2
text = "NPC"
horizontal_alignment = 1
theme_override_font_sizes/font_size = 22
[node name="DialogLog" type="RichTextLabel" parent="DialogPanel/VBox"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 280)
layout_mode = 2
bbcode_enabled = true
scroll_following = true
fit_content = true
[node name="DialogInput" type="LineEdit" parent="DialogPanel/VBox"]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "Frage stellen, Enter senden"
[node name="MapPanel" type="Control" parent="."]
unique_name_in_owner = true
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
[node name="Background" type="ColorRect" parent="MapPanel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_font_sizes/font_size = 20
text = "D"
color = Color(0.0, 0.0, 0.0, 0.7)
[node name="MapCanvas" type="Control" parent="MapPanel"]
unique_name_in_owner = true
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -300.0
offset_top = -300.0
offset_right = 300.0
offset_bottom = 300.0
script = ExtResource("2")
world_size = 200.0
draw_labels = true
[node name="PausePanel" type="PanelContainer" parent="."]
unique_name_in_owner = true
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -150.0
offset_top = -120.0
offset_right = 150.0
offset_bottom = 120.0
[node name="VBox" type="VBoxContainer" parent="PausePanel"]
layout_mode = 2
[node name="Title" type="Label" parent="PausePanel/VBox"]
layout_mode = 2
text = "Paused"
horizontal_alignment = 1
vertical_alignment = 1
theme_override_font_sizes/font_size = 28
[node name="Ability1" type="Panel" parent="AbilityBar"]
custom_minimum_size = Vector2(45, 45)
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
[node name="Resume" type="Button" parent="PausePanel/VBox"]
layout_mode = 2
text = "Resume"
[node name="CooldownOverlay" type="ColorRect" parent="AbilityBar/Ability1"]
layout_mode = 1
[node name="QuitToMenu" type="Button" parent="PausePanel/VBox"]
layout_mode = 2
text = "Quit to Menu"
[node name="GameOverOverlay" type="Control" parent="."]
unique_name_in_owner = true
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0, 0, 0, 0.6)
visible = false
[node name="Label" type="Label" parent="AbilityBar/Ability1"]
[node name="Background" type="ColorRect" parent="GameOverOverlay"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
text = "1"
color = Color(0.2, 0.0, 0.0, 0.8)
[node name="VBox" type="VBoxContainer" parent="GameOverOverlay"]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -200.0
offset_top = -100.0
offset_right = 200.0
offset_bottom = 100.0
[node name="Title" type="Label" parent="GameOverOverlay/VBox"]
layout_mode = 2
text = "GAME OVER"
horizontal_alignment = 1
vertical_alignment = 1
theme_override_font_sizes/font_size = 48
[node name="Ability2" type="Panel" parent="AbilityBar"]
custom_minimum_size = Vector2(45, 45)
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
[node name="Restart" type="Button" parent="GameOverOverlay/VBox"]
layout_mode = 2
text = "Back to Main Menu"
[node name="CooldownOverlay" type="ColorRect" parent="AbilityBar/Ability2"]
layout_mode = 1
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0, 0, 0, 0.6)
visible = false
[node name="Label" type="Label" parent="AbilityBar/Ability2"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
text = "2"
horizontal_alignment = 1
vertical_alignment = 1
[node name="Ability3" type="Panel" parent="AbilityBar"]
custom_minimum_size = Vector2(45, 45)
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
[node name="CooldownOverlay" type="ColorRect" parent="AbilityBar/Ability3"]
layout_mode = 1
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0, 0, 0, 0.6)
visible = false
[node name="Label" type="Label" parent="AbilityBar/Ability3"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
text = "3"
horizontal_alignment = 1
vertical_alignment = 1
[node name="Ability4" type="Panel" parent="AbilityBar"]
custom_minimum_size = Vector2(45, 45)
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
[node name="CooldownOverlay" type="ColorRect" parent="AbilityBar/Ability4"]
layout_mode = 1
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0, 0, 0, 0.6)
visible = false
[node name="Label" type="Label" parent="AbilityBar/Ability4"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
text = "4"
horizontal_alignment = 1
vertical_alignment = 1
[node name="Ability5" type="Panel" parent="AbilityBar"]
custom_minimum_size = Vector2(45, 45)
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_round")
[node name="CooldownOverlay" type="ColorRect" parent="AbilityBar/Ability5"]
layout_mode = 1
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0, 0, 0, 0.6)
visible = false
[node name="Label" type="Label" parent="AbilityBar/Ability5"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
text = "P"
horizontal_alignment = 1
vertical_alignment = 1
[connection signal="pressed" from="PausePanel/VBox/Resume" to="." method="_on_resume_pressed"]
[connection signal="pressed" from="PausePanel/VBox/QuitToMenu" to="." method="_on_quit_pressed"]
[connection signal="pressed" from="GameOverOverlay/VBox/Restart" to="." method="_on_game_over_restart"]

40
scenes/hud/minimap.gd Normal file
View File

@@ -0,0 +1,40 @@
extends Control
@export var world_size: float = 200.0
@export var label_text: String = ""
@export var draw_labels: bool = false
func _ready() -> void:
set_process(false)
func _draw() -> void:
var view_size: Vector2 = get_size()
draw_rect(Rect2(Vector2.ZERO, view_size), Color(0.05, 0.05, 0.08, 0.85))
draw_rect(Rect2(Vector2.ZERO, view_size), Color(0.6, 0.6, 0.6, 0.5), false, 2.0)
var local: Node = _local_player()
var center_offset: Vector2 = Vector2.ZERO
if local:
center_offset = Vector2((local as Node3D).global_position.x, (local as Node3D).global_position.z)
var map_sys: Node = get_tree().root.get_node_or_null("World/Systems/MapSystem")
if map_sys == null:
return
for entry in map_sys.get_marker_data():
var pos: Vector3 = entry.pos
var px: Vector2 = _to_pixel(Vector2(pos.x, pos.z), view_size, center_offset)
if px.x < 0 or px.x > view_size.x or px.y < 0 or px.y > view_size.y:
continue
draw_circle(px, 3.0, entry.color)
if draw_labels and entry.label != "":
var f: Font = ThemeDB.fallback_font
draw_string(f, px + Vector2(5, -5), entry.label, HORIZONTAL_ALIGNMENT_LEFT, -1, 12, Color.WHITE)
func _to_pixel(world: Vector2, view_size: Vector2, center: Vector2) -> Vector2:
var rel: Vector2 = world - center
var s: float = view_size.x / world_size
return view_size * 0.5 + rel * s
func _local_player() -> Node:
for p in get_tree().get_nodes_in_group("player"):
if p.is_multiplayer_authority():
return p
return null

View File

@@ -0,0 +1 @@
uid://bt0i0umsod51f