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()