302 lines
10 KiB
GDScript
302 lines
10 KiB
GDScript
extends CanvasLayer
|
|
|
|
# Ingame HUD - Displays player health, level, time, and boss health
|
|
|
|
var label_life: Label = null
|
|
var texture_progress_bar_hp: TextureProgressBar = null
|
|
var label_keys: Label = null
|
|
var label_keys_value: Label = null
|
|
var label_level: Label = null
|
|
var label_level_value: Label = null
|
|
var label_time: Label = null
|
|
var label_time_value: Label = null
|
|
var label_boss: Label = null
|
|
var texture_progress_bar_boss_hp: TextureProgressBar = null
|
|
var label_host: Label = null
|
|
var label_player_count: Label = null
|
|
var label_room_code: Label = null
|
|
var label_disconnected: Label = null
|
|
|
|
var game_world: Node = null
|
|
var network_manager: Node = null
|
|
var local_player: Node = null
|
|
var level_start_time: float = 0.0
|
|
var player_search_attempts: int = 0
|
|
var max_player_search_attempts: int = 100 # Limit retries to prevent infinite recursion
|
|
var timer_running: bool = true # Flag to stop/start timer
|
|
const HUD_BASE_SIZE: Vector2 = Vector2(1280, 720)
|
|
|
|
func _ready():
|
|
print("IngameHUD: _ready() called")
|
|
|
|
# Find nodes safely (using get_node_or_null to avoid crashes)
|
|
label_life = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerLIFE/LabelLife")
|
|
texture_progress_bar_hp = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerLIFE/TextureProgressBarHP")
|
|
label_keys = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerKeys/LabelKeys")
|
|
label_keys_value = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerKeys/HBoxContainer/LabelKeysValue")
|
|
label_level = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerLevel/LabelLevel")
|
|
label_level_value = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerLevel/LabelLevelValue")
|
|
label_time = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerTime/LabelTime")
|
|
label_time_value = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerTime/LabelTimeValue")
|
|
label_boss = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerBoss/LabelBoss")
|
|
texture_progress_bar_boss_hp = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerBoss/TextureProgressBarBossHP")
|
|
label_host = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerHost/LabelHost")
|
|
label_player_count = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerHost/LabelPlayerCount")
|
|
label_room_code = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerHost/LabelRoomCode")
|
|
label_disconnected = get_node_or_null("CenterTop/LabelDisconnected")
|
|
|
|
# Find network manager
|
|
network_manager = get_node_or_null("/root/NetworkManager")
|
|
if network_manager:
|
|
# Connect to player connection signals to update player count
|
|
network_manager.player_connected.connect(_on_player_connected)
|
|
network_manager.player_disconnected.connect(_on_player_disconnected)
|
|
network_manager.connection_failed.connect(_on_connection_failed)
|
|
network_manager.connection_succeeded.connect(_on_connection_succeeded)
|
|
|
|
# Debug: Check if nodes were found
|
|
if not label_time_value:
|
|
print("IngameHUD: ERROR - label_time_value not found!")
|
|
else:
|
|
print("IngameHUD: Nodes found successfully")
|
|
|
|
# Ensure visibility
|
|
visible = true
|
|
layer = 100 # High layer to ensure HUD is on top
|
|
|
|
# Find game world
|
|
game_world = get_tree().get_first_node_in_group("game_world")
|
|
if not game_world:
|
|
print("IngameHUD: WARNING - game_world not found in group")
|
|
|
|
# Initially hide boss health bar
|
|
if texture_progress_bar_boss_hp:
|
|
texture_progress_bar_boss_hp.visible = false
|
|
if label_boss:
|
|
label_boss.visible = false
|
|
|
|
# Update host info display
|
|
_update_host_info()
|
|
|
|
# Start level timer
|
|
level_start_time = Time.get_ticks_msec() / 1000.0
|
|
|
|
# Keep HUD text crisp with integer scaling
|
|
_update_hud_scale()
|
|
get_viewport().size_changed.connect(_update_hud_scale)
|
|
|
|
# Find local player (with retry limit)
|
|
player_search_attempts = 0
|
|
_find_local_player()
|
|
|
|
func _on_player_connected(_peer_id: int, _player_info: Dictionary):
|
|
_update_host_info()
|
|
|
|
func _on_player_disconnected(_peer_id: int, _player_info: Dictionary):
|
|
_update_host_info()
|
|
|
|
func _on_connection_failed():
|
|
# Show disconnection message
|
|
if label_disconnected:
|
|
label_disconnected.visible = true
|
|
# Show different message for host vs joiner
|
|
if network_manager and network_manager.is_hosting:
|
|
label_disconnected.text = "Lost connection to Matchbox server - Retrying..."
|
|
else:
|
|
label_disconnected.text = "Disconnected - Reconnecting..."
|
|
|
|
func _on_connection_succeeded():
|
|
# Hide disconnection message
|
|
if label_disconnected:
|
|
label_disconnected.visible = false
|
|
|
|
func _update_host_info():
|
|
if not network_manager:
|
|
return
|
|
|
|
# Update HOST label visibility
|
|
if label_host:
|
|
label_host.visible = network_manager.is_hosting
|
|
|
|
# Update player count
|
|
if label_player_count and network_manager.players_info:
|
|
var total_players = 0
|
|
for peer_id in network_manager.players_info.keys():
|
|
var info = network_manager.players_info[peer_id]
|
|
total_players += info.get("local_player_count", 1)
|
|
label_player_count.text = "Players: " + str(total_players)
|
|
|
|
# Update room code (only show if WebRTC/WebSocket and hosting)
|
|
if label_room_code:
|
|
var mode = network_manager.network_mode
|
|
if (mode == 1 or mode == 2) and network_manager.is_hosting: # WebRTC or WebSocket
|
|
var room_id = network_manager.get_room_id()
|
|
if not room_id.is_empty():
|
|
label_room_code.text = "Room: " + room_id
|
|
label_room_code.visible = true
|
|
else:
|
|
label_room_code.visible = false
|
|
else:
|
|
label_room_code.visible = false
|
|
|
|
func _find_local_player():
|
|
# Prevent infinite recursion
|
|
player_search_attempts += 1
|
|
if player_search_attempts > max_player_search_attempts:
|
|
print("IngameHUD: Warning - Could not find local player after ", max_player_search_attempts, " attempts. HUD will continue without player data.")
|
|
return
|
|
|
|
# Find the local player (first player with authority)
|
|
var players = get_tree().get_nodes_in_group("player")
|
|
|
|
# Check if we're in multiplayer mode
|
|
var is_multiplayer = multiplayer and multiplayer.has_multiplayer_peer()
|
|
|
|
if is_multiplayer:
|
|
# In multiplayer mode, find player with authority
|
|
for player in players:
|
|
if player.has_method("is_multiplayer_authority") and player.is_multiplayer_authority():
|
|
local_player = player
|
|
print("IngameHUD: Found local player")
|
|
return
|
|
else:
|
|
# In single-player mode, use first player
|
|
if players.size() > 0:
|
|
local_player = players[0]
|
|
print("IngameHUD: Found player (single-player mode)")
|
|
return
|
|
|
|
# If not found, try again later (but only if we haven't exceeded attempts)
|
|
if not local_player and player_search_attempts <= max_player_search_attempts:
|
|
call_deferred("_find_local_player")
|
|
|
|
func _process(_delta):
|
|
# Only update if nodes exist
|
|
if not label_time_value:
|
|
return # Nodes not initialized yet, skip updates
|
|
|
|
# Update player health
|
|
if local_player and is_instance_valid(local_player):
|
|
_update_player_health()
|
|
_update_keys_display()
|
|
|
|
# Update level display
|
|
_update_level_display()
|
|
|
|
# Update time display
|
|
_update_time_display()
|
|
|
|
# Update boss health (if boss exists)
|
|
_update_boss_health()
|
|
|
|
func _update_hud_scale():
|
|
# Scale HUD to an integer factor to keep pixel text crisp
|
|
var viewport_size = get_viewport().get_visible_rect().size
|
|
var scale_factor = int(floor(min(viewport_size.x / HUD_BASE_SIZE.x, viewport_size.y / HUD_BASE_SIZE.y)))
|
|
if scale_factor < 1:
|
|
scale_factor = 1
|
|
scale = Vector2.ONE * scale_factor
|
|
|
|
func _update_player_health():
|
|
if not local_player or not texture_progress_bar_hp:
|
|
return
|
|
|
|
var health = 0
|
|
var max_health = 100
|
|
|
|
# Try to get health from character_stats first (property always exists in player.gd)
|
|
if local_player.character_stats:
|
|
health = local_player.character_stats.hp
|
|
max_health = local_player.character_stats.maxhp
|
|
else:
|
|
# Fallback to direct properties (these are getters in player.gd, always available)
|
|
health = local_player.current_health
|
|
max_health = local_player.max_health
|
|
|
|
# Update progress bar
|
|
texture_progress_bar_hp.max_value = max_health
|
|
texture_progress_bar_hp.value = health
|
|
|
|
func _update_keys_display():
|
|
if not local_player or not label_keys_value:
|
|
return
|
|
|
|
# Get key count from player (keys property always exists in player.gd)
|
|
var key_count = local_player.keys
|
|
label_keys_value.text = str(key_count)
|
|
|
|
func _update_level_display():
|
|
if not label_level_value:
|
|
return
|
|
|
|
# current_level is always defined in game_world.gd
|
|
if game_world:
|
|
var level = game_world.current_level
|
|
label_level_value.text = str(level)
|
|
|
|
func _update_time_display():
|
|
if not label_time_value:
|
|
return
|
|
|
|
# Only update if timer is running
|
|
if not timer_running:
|
|
return
|
|
|
|
# Calculate elapsed time since level start
|
|
var current_time = Time.get_ticks_msec() / 1000.0
|
|
var elapsed_time = current_time - level_start_time
|
|
|
|
# Format as MM:SS
|
|
var minutes = int(elapsed_time / 60)
|
|
var seconds = int(elapsed_time) % 60
|
|
label_time_value.text = "%02d:%02d" % [minutes, seconds]
|
|
|
|
func get_level_time() -> float:
|
|
# Get the current level time (even if timer is stopped)
|
|
var current_time = Time.get_ticks_msec() / 1000.0
|
|
return current_time - level_start_time
|
|
|
|
func stop_timer():
|
|
# Stop the timer when level completes
|
|
timer_running = false
|
|
|
|
func start_timer():
|
|
# Start/reset the timer for new level
|
|
timer_running = true
|
|
level_start_time = Time.get_ticks_msec() / 1000.0
|
|
|
|
func _update_boss_health():
|
|
# Find boss enemy (if any)
|
|
var boss_enemy = null
|
|
var enemies = get_tree().get_nodes_in_group("enemy")
|
|
for enemy in enemies:
|
|
# Check if enemy is a boss (could check metadata or name)
|
|
if enemy.has_meta("is_boss") and enemy.get_meta("is_boss"):
|
|
boss_enemy = enemy
|
|
break
|
|
|
|
if boss_enemy and is_instance_valid(boss_enemy):
|
|
# Show boss health bar
|
|
if texture_progress_bar_boss_hp:
|
|
texture_progress_bar_boss_hp.visible = true
|
|
if label_boss:
|
|
label_boss.visible = true
|
|
|
|
# Update boss health (properties are always defined in enemy_base.gd)
|
|
var health = boss_enemy.current_health
|
|
var max_health = boss_enemy.max_health
|
|
|
|
if texture_progress_bar_boss_hp:
|
|
texture_progress_bar_boss_hp.max_value = max_health
|
|
texture_progress_bar_boss_hp.value = health
|
|
else:
|
|
# Hide boss health bar if no boss
|
|
if texture_progress_bar_boss_hp:
|
|
texture_progress_bar_boss_hp.visible = false
|
|
if label_boss:
|
|
label_boss.visible = false
|
|
|
|
func reset_level_timer():
|
|
# Reset timer when starting a new level
|
|
start_timer()
|