Add escape menu and weapon slot tooltips to character sheet

- Escape Menu:
  - Opens with Escape key from base game screen
  - Resume button to return to game
  - Exit Game button to quit application
  - Character sheet closes with Escape if open (takes priority)
  - Mouse capture/release handled by UI components

- Weapon Slot System:
  - Visual weapon boxes (80x80) in character sheet
  - Display weapon icons from WeaponData
  - Empty slots appear dimmed
  - Hover tooltips show weapon stats (not clipped by containers)
  - Tooltips display: slot name, weapon name, damage, range, cooldown, knockback, block percentage
  - Tooltips positioned globally to avoid ScrollContainer clipping

- Bug Fixes:
  - Fixed typo in lilguy_body.gd (dwextends -> extends)
  - Removed manual mouse toggle from player (now handled by UI)

- Updated character_sheet.gd to use weapon slot components instead of text labels
- Added weapon_tooltip.tscn as separate scene for unclipped tooltips
Dashfix
Scott 3 weeks ago
parent 15865dd15f
commit aba42e2a02
  1. 11
      level/scripts/player.gd
  2. 106
      level/ui/scenes/escape_menu.tscn
  3. 44
      level/ui/scenes/weapon_slot.tscn
  4. 44
      level/ui/scenes/weapon_tooltip.tscn
  5. 54
      level/ui/scripts/character_sheet.gd
  6. 46
      level/ui/scripts/escape_menu.gd
  7. 1
      level/ui/scripts/escape_menu.gd.uid
  8. 14
      level/ui/scripts/hud_manager.gd
  9. 98
      level/ui/scripts/weapon_slot.gd
  10. 1
      level/ui/scripts/weapon_slot.gd.uid

@ -213,17 +213,6 @@ func _process(delta):
_check_fall_and_respawn() _check_fall_and_respawn()
# Handle mouse capture toggle (Escape to release, click to recapture)
if Input.is_action_just_pressed("quit"):
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
else:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
# Recapture mouse on click if it was released
if Input.is_action_just_pressed("attack") and Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
# Update attack cooldown # Update attack cooldown
if _attack_timer > 0: if _attack_timer > 0:
_attack_timer -= delta _attack_timer -= delta

@ -0,0 +1,106 @@
[gd_scene load_steps=4 format=3 uid="uid://cepm3qkjr8h1h"]
[ext_resource type="Script" path="res://level/ui/scripts/escape_menu.gd" id="1_script"]
[ext_resource type="Theme" uid="uid://dvsh7tuhulnfm" path="res://level/ui/theme/wow_style.tres" id="2_theme"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_menu"]
bg_color = Color(0.12, 0.1, 0.08, 0.95)
border_width_left = 4
border_width_top = 4
border_width_right = 4
border_width_bottom = 4
border_color = Color(0.6, 0.5, 0.2, 1)
corner_radius_top_left = 8
corner_radius_top_right = 8
corner_radius_bottom_right = 8
corner_radius_bottom_left = 8
[node name="EscapeMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("2_theme")
script = ExtResource("1_script")
[node name="DarkBackground" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 0.7)
[node name="Panel" type="PanelContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -200.0
offset_top = -150.0
offset_right = 200.0
offset_bottom = 150.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_menu")
[node name="MarginContainer" type="MarginContainer" parent="Panel"]
layout_mode = 2
theme_override_constants/margin_left = 30
theme_override_constants/margin_top = 30
theme_override_constants/margin_right = 30
theme_override_constants/margin_bottom = 30
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="TitleLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(1, 0.8, 0, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 32
text = "MENU"
horizontal_alignment = 1
[node name="Spacer" type="Control" parent="Panel/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
[node name="ResumeButton" type="Button" parent="Panel/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_color = Color(1, 0.8, 0, 1)
theme_override_font_sizes/font_size = 20
text = "Resume"
[node name="ExitButton" type="Button" parent="Panel/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_color = Color(1, 0.8, 0, 1)
theme_override_font_sizes/font_size = 20
text = "Exit Game"
[node name="Spacer2" type="Control" parent="Panel/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 10)
layout_mode = 2
[node name="HintLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0.7, 0.7, 0.7, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 1
theme_override_font_sizes/font_size = 14
text = "Press ESC to close"
horizontal_alignment = 1
[connection signal="pressed" from="Panel/MarginContainer/VBoxContainer/ResumeButton" to="." method="_on_resume_pressed"]
[connection signal="pressed" from="Panel/MarginContainer/VBoxContainer/ExitButton" to="." method="_on_exit_pressed"]

@ -0,0 +1,44 @@
[gd_scene load_steps=4 format=3 uid="uid://dnwg8b7xmh5qa"]
[ext_resource type="Script" path="res://level/ui/scripts/weapon_slot.gd" id="1_script"]
[ext_resource type="Theme" uid="uid://dvsh7tuhulnfm" path="res://level/ui/theme/wow_style.tres" id="2_theme"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_slot"]
bg_color = Color(0.15, 0.15, 0.15, 0.9)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.6, 0.5, 0.2, 1)
corner_radius_top_left = 4
corner_radius_top_right = 4
corner_radius_bottom_right = 4
corner_radius_bottom_left = 4
[node name="WeaponSlot" type="Control"]
custom_minimum_size = Vector2(80, 80)
mouse_filter = 0
theme = ExtResource("2_theme")
script = ExtResource("1_script")
[node name="SlotPanel" type="PanelContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_slot")
[node name="MarginContainer" type="MarginContainer" parent="SlotPanel"]
layout_mode = 2
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
[node name="IconRect" type="TextureRect" parent="SlotPanel/MarginContainer"]
layout_mode = 2
expand_mode = 1
stretch_mode = 5

@ -0,0 +1,44 @@
[gd_scene load_steps=3 format=3 uid="uid://ck5y3rmj8vx2p"]
[ext_resource type="Theme" uid="uid://dvsh7tuhulnfm" path="res://level/ui/theme/wow_style.tres" id="1_theme"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tooltip"]
bg_color = Color(0.15, 0.15, 0.15, 0.95)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.6, 0.5, 0.2, 1)
corner_radius_top_left = 4
corner_radius_top_right = 4
corner_radius_bottom_right = 4
corner_radius_bottom_left = 4
[node name="WeaponTooltip" type="PanelContainer"]
z_index = 1000
offset_right = 200.0
offset_bottom = 150.0
theme = ExtResource("1_theme")
theme_override_styles/panel = SubResource("StyleBoxFlat_tooltip")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="StatsLabel" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 1
theme_override_font_sizes/font_size = 14
text = "Weapon Name
---
Damage: 10
Range: 3
Cooldown: 0.5s"

@ -20,6 +20,12 @@ func _ready():
is_visible = false is_visible = false
func _input(event): func _input(event):
# Close on Escape press if open
if event.is_action_pressed("quit") and is_visible:
toggle_sheet()
get_viewport().set_input_as_handled()
return
# Toggle on Tab press # Toggle on Tab press
if event.is_action_pressed("toggle_character_sheet"): if event.is_action_pressed("toggle_character_sheet"):
toggle_sheet() toggle_sheet()
@ -126,38 +132,34 @@ func _refresh_weapons():
spacer.custom_minimum_size = Vector2(0, 10) spacer.custom_minimum_size = Vector2(0, 10)
weapons_container.add_child(spacer) weapons_container.add_child(spacer)
# Main hand weapon # Create horizontal container for weapon slots
_add_stat_label("--- MAIN HAND ---", Color(0.8, 0.8, 0.8)) var slots_container = HBoxContainer.new()
slots_container.add_theme_constant_override("separation", 15)
weapons_container.add_child(slots_container)
# Load weapon slot scene
var weapon_slot_scene = load("res://level/ui/scenes/weapon_slot.tscn")
if not weapon_slot_scene:
push_error("[CharacterSheet] Failed to load weapon_slot.tscn")
return
# Create main hand slot
var main_hand_slot = weapon_slot_scene.instantiate()
slots_container.add_child(main_hand_slot)
if player.equipped_weapon and player.equipped_weapon.weapon_data: if player.equipped_weapon and player.equipped_weapon.weapon_data:
var wd = player.equipped_weapon.weapon_data main_hand_slot.set_weapon(player.equipped_weapon.weapon_data, "Main Hand")
_add_stat_label("Name: " + wd.weapon_name, Color(0.0, 1.0, 0.5))
_add_stat_label("Damage: " + str(wd.damage))
_add_stat_label("Range: " + str(wd.attack_range))
_add_stat_label("Cooldown: " + str(wd.attack_cooldown) + "s")
_add_stat_label("Knockback: " + str(wd.knockback_force))
if wd.can_block:
_add_stat_label("Block: " + str(int(wd.block_reduction * 100)) + "%")
else: else:
_add_stat_label("No weapon equipped", Color(0.7, 0.7, 0.7)) main_hand_slot.clear_weapon()
# Spacer # Create off-hand slot
var spacer2 = Control.new() var off_hand_slot = weapon_slot_scene.instantiate()
spacer2.custom_minimum_size = Vector2(0, 10) slots_container.add_child(off_hand_slot)
weapons_container.add_child(spacer2)
# Off-hand weapon
_add_stat_label("--- OFF-HAND ---", Color(0.8, 0.8, 0.8))
if player.equipped_offhand and player.equipped_offhand.weapon_data: if player.equipped_offhand and player.equipped_offhand.weapon_data:
var wd = player.equipped_offhand.weapon_data off_hand_slot.set_weapon(player.equipped_offhand.weapon_data, "Off-Hand")
_add_stat_label("Name: " + wd.weapon_name, Color(0.0, 1.0, 0.5))
_add_stat_label("Damage: " + str(wd.damage))
_add_stat_label("Range: " + str(wd.attack_range))
_add_stat_label("Cooldown: " + str(wd.attack_cooldown) + "s")
_add_stat_label("Knockback: " + str(wd.knockback_force))
if wd.can_block:
_add_stat_label("Block: " + str(int(wd.block_reduction * 100)) + "%")
else: else:
_add_stat_label("No weapon equipped", Color(0.7, 0.7, 0.7)) off_hand_slot.clear_weapon()
## Refresh abilities list ## Refresh abilities list
func _refresh_abilities(): func _refresh_abilities():

@ -0,0 +1,46 @@
extends Control
class_name EscapeMenu
## Escape menu that opens when Escape is pressed
## Allows player to exit the game or resume playing
# Visibility
var is_visible: bool = false
# Player reference (to control mouse mode)
var player: Character = null
func _ready():
# Start hidden
hide()
is_visible = false
func _input(event):
# Toggle on Escape press
if event.is_action_pressed("quit"):
toggle_menu()
get_viewport().set_input_as_handled()
## Set the player reference
func set_player(p: Character):
player = p
## Toggle menu visibility
func toggle_menu():
is_visible = !is_visible
if is_visible:
show()
# Release mouse when opening menu
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
else:
hide()
# Recapture mouse when closing menu
if player and player.is_multiplayer_authority():
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
## Called when Resume button is clicked
func _on_resume_pressed():
toggle_menu()
## Called when Exit button is clicked
func _on_exit_pressed():
get_tree().quit()

@ -8,6 +8,7 @@ var unit_frame: Control = null
var target_frame: Control = null var target_frame: Control = null
var character_sheet: Control = null var character_sheet: Control = null
var tab_hint: Control = null var tab_hint: Control = null
var escape_menu: Control = null
# Player reference # Player reference
var local_player: Character = null var local_player: Character = null
@ -76,6 +77,7 @@ func _create_ui_components():
_create_unit_frame() _create_unit_frame()
_create_character_sheet() _create_character_sheet()
_create_tab_hint() _create_tab_hint()
_create_escape_menu()
## Create action bar at bottom of screen ## Create action bar at bottom of screen
func _create_action_bar(): func _create_action_bar():
@ -123,6 +125,18 @@ func _create_tab_hint():
else: else:
push_error("[HUD] Failed to load tab_hint.tscn") push_error("[HUD] Failed to load tab_hint.tscn")
## Create escape menu (toggle with Escape)
func _create_escape_menu():
var escape_menu_scene = load("res://level/ui/scenes/escape_menu.tscn")
if escape_menu_scene:
escape_menu = escape_menu_scene.instantiate()
add_child(escape_menu)
if escape_menu and local_player:
escape_menu.set_player(local_player)
print("[HUD] Escape menu created")
else:
push_error("[HUD] Failed to load escape_menu.tscn")
## Show all UI components ## Show all UI components
func show_hud(): func show_hud():
show() show()

@ -0,0 +1,98 @@
extends Control
class_name WeaponSlot
## Weapon slot that displays weapon icon and shows tooltip on hover
# References
@onready var icon_rect: TextureRect = $SlotPanel/MarginContainer/IconRect
var tooltip: PanelContainer = null
# Weapon data
var weapon_data: WeaponData = null
var slot_name: String = "Main Hand"
func _ready():
# Create tooltip as a top-level CanvasLayer child so it's not clipped
_create_tooltip()
# Connect mouse events
mouse_entered.connect(_on_mouse_entered)
mouse_exited.connect(_on_mouse_exited)
## Create tooltip as a top-level node to avoid clipping
func _create_tooltip():
# Load tooltip scene template
var tooltip_scene = load("res://level/ui/scenes/weapon_tooltip.tscn")
if tooltip_scene:
tooltip = tooltip_scene.instantiate()
# Add to the root CanvasLayer (HUD) so it's not clipped
var hud = get_node_or_null("/root/HUD")
if hud:
hud.add_child(tooltip)
tooltip.hide()
else:
push_error("[WeaponSlot] Could not find HUD node")
else:
push_error("[WeaponSlot] Failed to load weapon_tooltip.tscn")
## Set weapon data and update display
func set_weapon(data: WeaponData, name: String = "Main Hand"):
weapon_data = data
slot_name = name
_update_display()
## Clear the weapon slot
func clear_weapon():
weapon_data = null
_update_display()
## Update the icon display
func _update_display():
if not icon_rect:
return
if weapon_data and weapon_data.icon:
icon_rect.texture = weapon_data.icon
icon_rect.modulate = Color.WHITE
else:
icon_rect.texture = null
icon_rect.modulate = Color(0.3, 0.3, 0.3, 0.5) # Dim empty slot
## Show tooltip on mouse enter
func _on_mouse_entered():
if weapon_data and tooltip:
_update_tooltip()
# Position tooltip to the right of the slot
var global_pos = global_position
tooltip.position = Vector2(global_pos.x + size.x + 10, global_pos.y)
tooltip.show()
## Hide tooltip on mouse exit
func _on_mouse_exited():
if tooltip:
tooltip.hide()
## Update tooltip content
func _update_tooltip():
if not tooltip or not weapon_data:
return
var tooltip_label = tooltip.get_node_or_null("MarginContainer/VBoxContainer/StatsLabel")
if not tooltip_label:
return
var text = ""
text += slot_name.to_upper() + "\n"
text += weapon_data.weapon_name + "\n"
text += "---\n"
text += "Damage: " + str(weapon_data.damage) + "\n"
text += "Range: " + str(weapon_data.attack_range) + "\n"
text += "Cooldown: " + str(weapon_data.attack_cooldown) + "s\n"
text += "Knockback: " + str(weapon_data.knockback_force) + "\n"
if weapon_data.can_block:
text += "Block: " + str(int(weapon_data.block_reduction * 100)) + "%\n"
if weapon_data.description:
text += "\n" + weapon_data.description
tooltip_label.text = text
Loading…
Cancel
Save