Randoloot/scripts/game/flight_hud.gd
2026-10-15 18:32:38 +02:00

452 lines
20 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 := false
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 := 306.0
const EQUIPMENT_UI_SCALE := 0.8
var _debug_scroll: ScrollContainer
var _debug_actions: VBoxContainer
var _debug_item_button: Button
var _debug_item_status: Label
var _captain_panel: PanelContainer
var _cockpit: Control
var _crafting_window: PanelContainer
var _skills_window: PanelContainer
func _create_navigation() -> void:
var navigation := HBoxContainer.new()
navigation.name = "TopNavigation"
$HUDRoot.add_child(navigation)
navigation.set_anchors_and_offsets_preset(Control.PRESET_CENTER_TOP)
navigation.offset_left = -60
navigation.offset_right = 60
navigation.offset_top = 12
navigation.offset_bottom = 64
navigation.add_theme_constant_override("separation", 8)
_crafting_window = _create_window("CraftingWindow", "TWÓRZ", Vector2(440, 250))
var crafting := PanelContainer.new()
crafting.name = "CraftingPanel"
crafting.set_script(preload("res://scripts/ui/ship_crafting_panel.gd"))
crafting.set("standalone", true)
_crafting_window.get_node("Contents").add_child(crafting)
_skills_window = _create_window("CaptainSkillsWindow", "UMIEJĘTNOŚCI KAPITANA", Vector2(480, 300))
var empty := Control.new()
empty.name = "SkillsContent"
empty.custom_minimum_size = Vector2(440, 230)
_skills_window.get_node("Contents").add_child(empty)
for entry in [["Twórz", _crafting_window, "res://assets/icons/ui/crafting.svg", Color("efb568")], ["Umiejętności kapitana", _skills_window, "res://assets/icons/ui/captain_skills.svg", Color("b9a0ef")]]:
var button := Button.new()
button.tooltip_text = entry[0]
button.icon = load(entry[2]) as Texture2D
button.expand_icon = true
button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
button.custom_minimum_size = Vector2(56, 52)
button.add_theme_constant_override("icon_max_width", 36)
preload("res://scripts/ui/hud_button_style.gd").apply(button, entry[3])
button.add_theme_constant_override("h_separation", 0)
for state in ["normal", "hover", "pressed", "disabled"]:
var style := button.get_theme_stylebox(state).duplicate() as StyleBoxFlat
style.set_content_margin_all(8)
button.add_theme_stylebox_override(state, style)
navigation.add_child(button)
button.pressed.connect(_toggle_window.bind(entry[1]))
func _create_window(node_name: String, title: String, dimensions: Vector2) -> PanelContainer:
var window := PanelContainer.new()
window.name = node_name
var style := StyleBoxFlat.new()
style.bg_color = Color("0b1520fa")
style.border_color = Color("376779")
style.set_border_width_all(1)
style.set_corner_radius_all(8)
style.set_content_margin_all(16)
window.add_theme_stylebox_override("panel", style)
$HUDRoot.add_child(window)
window.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
window.offset_left = -dimensions.x * 0.5
window.offset_right = dimensions.x * 0.5
window.offset_top = -dimensions.y * 0.5
window.offset_bottom = dimensions.y * 0.5
var contents := VBoxContainer.new()
contents.name = "Contents"
contents.add_theme_constant_override("separation", 12)
window.add_child(contents)
var header := HBoxContainer.new()
contents.add_child(header)
var label := Label.new()
label.text = title
label.add_theme_font_size_override("font_size", 14)
label.add_theme_color_override("font_color", Color("91dbea"))
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header.add_child(label)
var close := Button.new()
close.text = "×"
close.tooltip_text = "Zamknij"
close.custom_minimum_size = Vector2(32, 28)
close.pressed.connect(window.hide)
header.add_child(close)
window.hide()
return window
func _toggle_window(window: PanelContainer) -> void:
var opening := not window.visible
_crafting_window.hide()
_skills_window.hide()
window.visible = opening
func _ready() -> void:
_sidebar_open = bool(GameState.hud_panel_state.get("ship_open", false))
_debug_expanded = bool(GameState.hud_panel_state.get("debug_expanded", false))
debug_info.visible = _debug_expanded
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", 10)
# The display font has unusually tall vertical metrics; keep debug rows compact.
debug_info.add_theme_constant_override("line_spacing", mini(0, 13 - ceili(debug_info.get_theme_font("font").get_height(10))))
_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(560, inventory_panel.get_combined_minimum_size().y)
inventory_panel.scale = Vector2.ONE * EQUIPMENT_UI_SCALE
inventory_panel.set("drawer_width", inventory_panel.size.x * EQUIPMENT_UI_SCALE)
$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()
if _sidebar_open and bool(GameState.hud_panel_state.get("inventory_open", false)):
inventory_panel.call("_set_drawer_open", true)
func _create_sidebar() -> void:
_captain_panel = $HUDRoot/CaptainPanel
_captain_panel.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
_captain_panel.position = Vector2(12, 12)
_captain_panel.size = Vector2(320, _captain_panel.get_combined_minimum_size().y)
_captain_panel.resized.connect(_layout_debug)
_debug_actions = VBoxContainer.new()
_debug_actions.name = "DebugActions"
_debug_actions.add_theme_constant_override("separation", 6)
$HUDRoot.add_child(_debug_actions)
_debug_item_button = Button.new()
_debug_item_button.name = "GenerateItem"
_debug_item_button.custom_minimum_size.y = 36
preload("res://scripts/ui/hud_button_style.gd").apply(_debug_item_button, Color("a9ef83"))
_debug_item_button.pressed.connect(_give_debug_item)
_debug_actions.add_child(_debug_item_button)
_debug_item_status = Label.new()
_debug_item_status.add_theme_font_size_override("font_size", 10)
_debug_item_status.add_theme_color_override("font_color", Color("a9ef83"))
_debug_item_status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_debug_item_status.hide()
_debug_actions.add_child(_debug_item_status)
_debug_actions.minimum_size_changed.connect(func() -> void: _layout_debug.call_deferred())
_debug_scroll = ScrollContainer.new()
_debug_scroll.name = "DebugScroll"
_debug_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
$HUDRoot.add_child(_debug_scroll)
debug_info.reparent(_debug_scroll, false)
debug_info.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
debug_info.size_flags_horizontal = Control.SIZE_EXPAND_FILL
debug_info.mouse_filter = Control.MOUSE_FILTER_IGNORE
_sidebar = PanelContainer.new()
_sidebar.name = "ShipDrawer"
var style := StyleBoxFlat.new()
style.bg_color = Color(0.025, 0.042, 0.064, 0.97)
style.border_color = Color("376779")
style.set_border_width_all(1)
style.set_corner_radius_all(8)
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
scroll.add_child(contents)
var equipment := $HUDRoot/ShipEquipmentPanel as PanelContainer
equipment.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
equipment.reparent(contents, false)
equipment.size_flags_horizontal = Control.SIZE_EXPAND_FILL
equipment.add_theme_stylebox_override("panel", StyleBoxEmpty.new())
contents.minimum_size_changed.connect(func() -> void: _layout_sidebar.call_deferred())
_sidebar_toggle = Button.new()
_sidebar_toggle.set_script(preload("res://scripts/ui/ship_profile.gd"))
_sidebar_toggle.set("player", player)
_sidebar_toggle.name = "ShipToggle"
_sidebar_toggle.tooltip_text = "Zamknij okno statku" if _sidebar_open else "Otwórz statek i sloty wyposażenia"
_sidebar_toggle.add_theme_font_size_override("font_size", 12)
preload("res://scripts/ui/hud_button_style.gd").apply(_sidebar_toggle)
preload("res://scripts/ui/hud_button_style.gd").apply(inventory_button, Color("b9a0ef"))
$HUDRoot.add_child(_sidebar_toggle)
_sidebar_toggle.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT)
_sidebar_toggle.offset_left = -352
_sidebar_toggle.offset_right = -12
_sidebar_toggle.offset_top = 12
_sidebar_toggle.offset_bottom = 122
_sidebar_toggle.pressed.connect(_toggle_sidebar)
_cockpit = Control.new()
_cockpit.name = "Cockpit"
_cockpit.set_script(preload("res://scripts/ui/flight_cockpit.gd"))
_cockpit.set("player", player)
_cockpit.set("game", get_parent())
$HUDRoot.add_child(_cockpit)
_create_navigation()
_layout_debug()
func _layout_debug() -> void:
if not is_instance_valid(_debug_scroll) or not is_instance_valid(_captain_panel):
return
var top := _captain_panel.position.y + _captain_panel.size.y + 6.0
debug_toggle.position = Vector2(12, top)
_debug_actions.position = Vector2(18, top + 30)
_debug_actions.size = Vector2(310, _debug_actions.get_combined_minimum_size().y)
_debug_actions.visible = _debug_expanded
var scroll_top := _debug_actions.position.y + _debug_actions.size.y + 8.0
_debug_scroll.position = Vector2(18, scroll_top)
_debug_scroll.size = Vector2(310, maxf(40, $HUDRoot.size.y - scroll_top - 12))
_debug_scroll.visible = _debug_expanded
func _toggle_sidebar() -> void:
_sidebar_open = not _sidebar_open
GameState.hud_panel_state["ship_open"] = _sidebar_open
if _sidebar_tween != null and _sidebar_tween.is_running():
_sidebar_tween.kill()
_sidebar_toggle.tooltip_text = "Zamknij okno statku" if _sidebar_open else "Otwórz statek i sloty wyposażenia"
if _sidebar_open:
_sidebar.show()
else:
close_inventory()
_sidebar_tween = create_tween()
_sidebar_tween.tween_property(_sidebar, "position:x", $HUDRoot.size.x - _sidebar.size.x * EQUIPMENT_UI_SCALE - 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 not is_instance_valid(_sidebar) or not is_instance_valid(_cockpit):
return
if _sidebar_tween != null and _sidebar_tween.is_running():
_sidebar_tween.kill()
_sidebar.scale = Vector2.ONE * EQUIPMENT_UI_SCALE
_sidebar.size = Vector2(maxf(SIDEBAR_WIDTH, _sidebar.get_combined_minimum_size().x), minf(_sidebar.get_node("Scroll/Contents").get_combined_minimum_size().y + 16.0, maxf(100.0, ($HUDRoot.size.y - 148.0) / EQUIPMENT_UI_SCALE)))
_cockpit.scale = Vector2.ONE * 0.85
_cockpit.size = Vector2(minf(850.0, ($HUDRoot.size.x - 24) / 0.85), 120)
_cockpit.position = Vector2(($HUDRoot.size.x - _cockpit.size.x * 0.85) * 0.5, $HUDRoot.size.y - _cockpit.size.y * 0.85 - 12)
if _ability_bar != null:
var abilities := _ability_bar.get_parent() as Control
abilities.offset_bottom = _cockpit.position.y - $HUDRoot.size.y - 14.0
abilities.offset_top = abilities.offset_bottom - 44.0
_layout_debug()
_sidebar.position = Vector2($HUDRoot.size.x - _sidebar.size.x * EQUIPMENT_UI_SCALE - 8 if _sidebar_open else $HUDRoot.size.x, 136)
_sidebar.visible = _sidebar_open
_inventory_clip.size = inventory_panel.size * EQUIPMENT_UI_SCALE
_layout_inventory()
func _layout_inventory() -> void:
if not is_instance_valid(_inventory_clip) or not is_instance_valid(_sidebar):
return
var equipment := _sidebar.get_node("Scroll/Contents/ShipEquipmentPanel") as Control
_inventory_clip.position = Vector2(maxf(12.0, _sidebar.position.x - _inventory_clip.size.x - 8), minf(equipment.global_position.y, maxf(8.0, $HUDRoot.size.y - inventory_panel.size.y * EQUIPMENT_UI_SCALE - 8)))
func _process(_delta: float) -> void:
_layout_inventory()
_update_ability_bar()
if not _debug_expanded:
return
var inventory := player.get_node("ShipInventory") as ShipInventory
_debug_item_button.text = "LOSUJ PRZEDMIOT · POZ. %d" % GameState.system_level
_debug_item_button.disabled = inventory.get_cargo_used() >= ShipInventory.CARGO_SIZE
_debug_item_button.tooltip_text = "Magazyn jest pełny — zwolnij miejsce." if _debug_item_button.disabled else "Losuje przedmiot i addony dla poziomu aktualnego układu."
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""".replace("\r", "") % [
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
GameState.hud_panel_state["debug_expanded"] = _debug_expanded
debug_info.visible = _debug_expanded
_debug_scroll.visible = _debug_expanded
_debug_actions.visible = _debug_expanded
_update_debug_toggle()
_layout_debug()
func _give_debug_item() -> bool:
var inventory := player.get_node("ShipInventory") as ShipInventory
_debug_item_status.show()
if inventory.get_cargo_used() >= ShipInventory.CARGO_SIZE:
_debug_item_status.text = "Magazyn pełny — zwolnij miejsce."
_debug_item_status.add_theme_color_override("font_color", Color("ff8989"))
_layout_debug.call_deferred()
return false
var item := preload("res://scripts/items/item_generator.gd").generate(GameState.system_level)
if not inventory.add_item(item):
return false
_debug_item_status.text = "Dodano: %s · Poz. %d" % [item.definition.display_name, item.item_level]
_debug_item_status.add_theme_color_override("font_color", Color("a9ef83"))
_layout_debug.call_deferred()
return true
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%%""".replace("\r", "") % [
(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_CENTER_BOTTOM)
panel.offset_left = -120.0
panel.offset_right = 120.0
panel.offset_top = -220.0
panel.offset_bottom = -176.0
panel.add_theme_stylebox_override("panel", StyleBoxEmpty.new())
_ability_bar = HBoxContainer.new()
_ability_bar.name = "AbilityButtons"
_ability_bar.alignment = BoxContainer.ALIGNMENT_CENTER
_ability_bar.add_theme_constant_override("separation", 4)
panel.add_child(_ability_bar)
for index in ShipInventory.EXTENSION_SLOT_COUNT:
var button := Button.new()
button.name = "Ability%d" % (index + 1)
button.custom_minimum_size = Vector2(44, 44)
button.expand_icon = true
button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
button.add_theme_constant_override("icon_max_width", 30)
button.focus_mode = Control.FOCUS_NONE
var shortcut := Label.new()
shortcut.text = str(index + 1)
shortcut.position = Vector2(3, 1)
shortcut.mouse_filter = Control.MOUSE_FILTER_IGNORE
shortcut.add_theme_font_size_override("font_size", 9)
button.add_child(shortcut)
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()
var item: ItemInstance = player.get_node("ShipInventory").get_item("extension", index)
button.icon = item.definition.icon
button.text = ""
button.add_theme_color_override("icon_disabled_color", Color(0.45, 0.5, 0.55, 0.65))
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 = "[%d] %s\n" % [index + 1, ability.display_name] + 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