extends CanvasLayer @onready var debug_toggle: Button = $HUDRoot/DebugToggle @onready var debug_info: Label = $HUDRoot/DebugInfo @onready var player: CharacterBody2D = get_parent().get_node("PlayerShip") @onready var inventory_button: Button = $HUDRoot/ShipEquipmentPanel/Contents/InventoryButton @onready var inventory_panel: Control = $HUDRoot/ShipInventoryPanel var _debug_expanded := true var _ability_buttons: Dictionary = {} var _ability_bar: HBoxContainer var _ability_ready_style: StyleBoxFlat var _ability_blocked_style: StyleBoxFlat var _inventory_clip: Control var _sidebar: PanelContainer var _sidebar_toggle: Button var _sidebar_open := false var _sidebar_tween: Tween const SIDEBAR_WIDTH := 344.0 func _ready() -> void: var energy_indicator := Node2D.new() energy_indicator.name = "EnergyIndicator" energy_indicator.set_script(preload("res://scripts/game/ship_energy_indicator.gd")) energy_indicator.set("player", player) player.add_child(energy_indicator) var feedback := Control.new() feedback.name = "CombatFeedback" feedback.set_script(preload("res://scripts/ui/combat_feedback.gd")) feedback.set("player", player) $HUDRoot.add_child(feedback) debug_info.add_theme_font_size_override("font_size", 9) _create_sidebar() _inventory_clip = Control.new() _inventory_clip.name = "InventoryDrawer" _inventory_clip.clip_contents = true _inventory_clip.mouse_filter = Control.MOUSE_FILTER_IGNORE $HUDRoot.add_child(_inventory_clip) inventory_panel.reparent(_inventory_clip, false) inventory_panel.set("layout_mode", 0) inventory_panel.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) inventory_panel.size = Vector2(780, inventory_panel.get_combined_minimum_size().y) $HUDRoot.resized.connect(func() -> void: _layout_sidebar.call_deferred()) _create_ability_bar() debug_toggle.pressed.connect(_toggle_debug) inventory_button.pressed.connect(_toggle_inventory) _update_debug_toggle() _layout_sidebar.call_deferred() func _create_sidebar() -> void: _sidebar = PanelContainer.new() _sidebar.name = "CaptainShipDrawer" var style := StyleBoxFlat.new() style.bg_color = Color(0.018, 0.028, 0.043, 0.97) style.border_color = Color("52677e") style.set_border_width_all(1) style.set_content_margin_all(8) _sidebar.add_theme_stylebox_override("panel", style) $HUDRoot.add_child(_sidebar) var scroll := ScrollContainer.new() scroll.name = "Scroll" scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED _sidebar.add_child(scroll) var contents := VBoxContainer.new() contents.name = "Contents" contents.size_flags_horizontal = Control.SIZE_EXPAND_FILL contents.add_theme_constant_override("separation", 8) scroll.add_child(contents) var captain := $HUDRoot/CaptainPanel as PanelContainer var equipment := $HUDRoot/ShipEquipmentPanel as PanelContainer for panel in [captain, equipment]: panel.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) panel.reparent(contents, false) panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL panel.add_theme_stylebox_override("panel", StyleBoxEmpty.new()) if panel == captain: contents.add_child(HSeparator.new()) _sidebar_toggle = Button.new() _sidebar_toggle.name = "CaptainShipToggle" _sidebar_toggle.text = "KAPITAN / STATEK ◀" _sidebar_toggle.add_theme_font_size_override("font_size", 12) _sidebar_toggle.focus_mode = Control.FOCUS_NONE $HUDRoot.add_child(_sidebar_toggle) _sidebar_toggle.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT) _sidebar_toggle.offset_left = -210 _sidebar_toggle.offset_right = -8 _sidebar_toggle.offset_top = 8 _sidebar_toggle.offset_bottom = 38 _sidebar_toggle.pressed.connect(_toggle_sidebar) _sidebar.hide() func _toggle_sidebar() -> void: _sidebar_open = not _sidebar_open if _sidebar_tween != null and _sidebar_tween.is_running(): _sidebar_tween.kill() _sidebar_toggle.text = "ZAMKNIJ ▶" if _sidebar_open else "KAPITAN / STATEK ◀" if _sidebar_open: _sidebar.show() else: close_inventory() _sidebar_tween = create_tween() _sidebar_tween.tween_property(_sidebar, "position:x", $HUDRoot.size.x - SIDEBAR_WIDTH - 8 if _sidebar_open else $HUDRoot.size.x, 0.25).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT if _sidebar_open else Tween.EASE_IN) if not _sidebar_open: _sidebar_tween.tween_callback(_sidebar.hide) func _layout_sidebar() -> void: if _sidebar_tween != null and _sidebar_tween.is_running(): _sidebar_tween.kill() _sidebar.size = Vector2(SIDEBAR_WIDTH, maxf(100.0, $HUDRoot.size.y - 52.0)) _sidebar.position = Vector2($HUDRoot.size.x - SIDEBAR_WIDTH - 8 if _sidebar_open else $HUDRoot.size.x, 44) _sidebar.visible = _sidebar_open _inventory_clip.size = inventory_panel.size _layout_inventory() func _layout_inventory() -> void: if _inventory_clip == null: return var equipment := _sidebar.get_node("Scroll/Contents/ShipEquipmentPanel") as Control _inventory_clip.position = Vector2(_sidebar.position.x - _inventory_clip.size.x - 8, minf(equipment.global_position.y, maxf(8.0, $HUDRoot.size.y - inventory_panel.size.y - 8))) func _process(_delta: float) -> void: _layout_inventory() _update_ability_bar() if not _debug_expanded: return var game := get_parent() debug_info.text = """POZIOM UKŁADU: %d WROGOWIE: %d PRĘDKOŚĆ: %.1f px/s AKTUALNA REGENERACJA Energia: %.1f pkt/s Tarcze: %.1f pkt/s %s""" % [ GameState.system_level, int(game.call("get_remaining_enemies")), player.velocity.length(), float(player.get("energy_regeneration")) + float(player.call("get_solar_regeneration")), float(player.get("shield_regeneration")), _get_bonus_summary(), ] func _toggle_debug() -> void: _debug_expanded = not _debug_expanded debug_info.visible = _debug_expanded _update_debug_toggle() func _update_debug_toggle() -> void: debug_toggle.text = "DEBUG [−]" if _debug_expanded else "DEBUG [+]" func _toggle_inventory() -> void: if not _sidebar_open: _toggle_sidebar() inventory_panel.call("toggle_drawer") func close_inventory() -> void: inventory_panel.call("close_drawer", true) func _get_bonus_summary() -> String: var cooldown_bonus := 0.0 var inventory := player.get_node("ShipInventory") as ShipInventory for item in inventory.weapon_slots + inventory.module_slots: if item != null: cooldown_bonus += item.get_stat_total("missile_cooldown_reduction") return """BONUSY (ADDONY + KAPITAN) Obrażenia broni: %+.1f%% Szansa na krytyk: %+.1f%% Obrażenia krytyczne: %+.1f%% Szansa na unik: %+.1f%% Redukcja obrażeń: %+.1f%% Odzysk tarcz z obrażeń: %.1f%% Regeneracja energii: %+.1f pkt/s Regeneracja tarcz: %+.1f pkt/s Kadłub: %+.1f pkt Tarcze: %+.1f pkt Energia: %+.1f pkt Prędkość maks.: %+.1f%% Skrócenie cooldownu rakiet: %.2f s Doświadczenie: %+.1f%% Kredyty: %+.1f%%""" % [ (float(player.call("get_weapon_damage_multiplier")) - 1.0) * 100.0, float(player.call("get_critical_chance")) * 100.0, float(player.get("critical_damage_bonus")) * 100.0, float(player.call("get_evasion_chance")) * 100.0, float(player.call("get_damage_reduction")) * 100.0, float(player.get("shield_on_damage_percent")), float(player.get("energy_regeneration")) - float(player.get("base_energy_regeneration")), float(player.get("shield_regeneration")) - float(player.get("base_shield_regeneration")), float(player.get("max_hull")) - float(player.get("base_max_hull")), float(player.get("max_shields")) - float(player.get("base_max_shields")), float(player.get("max_energy")) - float(player.get("base_max_energy")), (float(player.get("max_speed")) / float(player.get("base_max_speed")) - 1.0) * 100.0, cooldown_bonus, (float(player.get("experience_multiplier")) - 1.0) * 100.0, (float(player.get("credits_multiplier")) - 1.0) * 100.0, ] func _create_ability_bar() -> void: _ability_ready_style = _make_ability_style(true) _ability_blocked_style = _make_ability_style(false) var panel := PanelContainer.new() panel.name = "ActiveAbilities" $HUDRoot.add_child(panel) panel.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_LEFT) panel.offset_left = 12.0 panel.offset_right = 12.0 panel.offset_top = -54.0 panel.offset_bottom = -12.0 _ability_bar = HBoxContainer.new() _ability_bar.add_theme_constant_override("separation", 4) panel.add_child(_ability_bar) for index in 6: var button := Button.new() button.name = "Ability%d" % (index + 1) button.custom_minimum_size = Vector2(120, 40) button.add_theme_font_size_override("font_size", 11) button.pressed.connect(func() -> void: player.call("activate_ability", index)) _ability_bar.add_child(button) _ability_buttons[index] = button _update_ability_bar() func _update_ability_bar() -> void: var any_equipped := false for index in _ability_buttons: var button: Button = _ability_buttons[index] var ability: ActiveAbility = player.call("get_active_ability", index) button.visible = ability != null if ability == null: continue any_equipped = true var reason: String = player.call("ability_unavailable_reason", index) button.disabled = not reason.is_empty() button.text = "[%d] %s\n%s" % [index + 1, ability.display_name, "GOTOWE" if reason.is_empty() else reason] var style := _ability_ready_style if reason.is_empty() else _ability_blocked_style button.add_theme_stylebox_override("normal", style) button.add_theme_stylebox_override("disabled", style) button.add_theme_stylebox_override("hover", style) button.add_theme_color_override("font_color", Color("a5ffda")) button.add_theme_color_override("font_disabled_color", Color("a3aebb")) button.tooltip_text = ability.description + ("\n" + reason if not reason.is_empty() else "\nGotowe") _ability_bar.get_parent().visible = any_equipped func _make_ability_style(ready: bool) -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = Color("174237") if ready else Color("202731") style.border_color = Color("77e8bc") if ready else Color("526071") style.set_border_width_all(2 if ready else 1) style.set_content_margin_all(6) return style