322 lines
14 KiB
GDScript
322 lines
14 KiB
GDScript
extends CanvasLayer
|
||
|
||
const CATALOG := preload("res://scripts/captain/captain_skills.gd")
|
||
const BUTTON_STYLE := preload("res://scripts/ui/hud_button_style.gd")
|
||
var player: CharacterBody2D
|
||
var _captain: CaptainProgress
|
||
var _screen: Control
|
||
var _panel: PanelContainer
|
||
var _contents: VBoxContainer
|
||
var _bar: HBoxContainer
|
||
var _buttons: Dictionary = {}
|
||
var _refresh_pending := false
|
||
|
||
func _ready() -> void:
|
||
name = "CaptainSkills"
|
||
layer = preload("res://scripts/ui/render_layers.gd").MANAGEMENT
|
||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||
_captain = GameState.captain
|
||
_captain.normalize_equipped_skills()
|
||
_screen = Control.new()
|
||
_screen.name = "SkillManagement"
|
||
add_child(_screen)
|
||
_screen.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
_screen.hide()
|
||
get_parent().get_node("HUDRoot/FloatingWindows").set("modal_overlay", _screen)
|
||
var dim := ColorRect.new()
|
||
dim.color = Color(0.015, 0.025, 0.045, 0.3)
|
||
_screen.add_child(dim)
|
||
dim.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
_panel = PanelContainer.new()
|
||
_panel.name = "Panel"
|
||
_panel.add_theme_stylebox_override("panel", _style(Color("567b99"), Color("0a1422c7"), 22))
|
||
_screen.add_child(_panel)
|
||
_panel.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
|
||
_contents = VBoxContainer.new()
|
||
_contents.add_theme_constant_override("separation", 7)
|
||
_panel.add_child(_contents)
|
||
_bar = HBoxContainer.new()
|
||
_bar.name = "CaptainActiveSkills"
|
||
_bar.add_theme_constant_override("separation", 6)
|
||
get_parent().get_node("HUDRoot").add_child(_bar)
|
||
_captain.changed.connect(_queue_refresh)
|
||
get_viewport().size_changed.connect(_layout)
|
||
_queue_refresh()
|
||
|
||
func _queue_refresh() -> void:
|
||
if not _refresh_pending:
|
||
_refresh_pending = true
|
||
_refresh.call_deferred()
|
||
|
||
func _label(text: String, font_size: int, color: Color = Color("dae8ef")) -> Label:
|
||
var label := Label.new()
|
||
label.text = text
|
||
label.add_theme_font_override("font", ThemeDB.fallback_font)
|
||
label.add_theme_font_size_override("font_size", preload("res://scripts/ui/compact_interface.gd").font_size(font_size - 3))
|
||
label.add_theme_color_override("font_color", color)
|
||
return label
|
||
|
||
func _style(border: Color, background: Color, margin: int) -> StyleBoxFlat:
|
||
var style := StyleBoxFlat.new()
|
||
style.bg_color = Color(background, minf(background.a, 0.48))
|
||
style.border_color = border
|
||
style.set_border_width_all(1)
|
||
style.set_corner_radius_all(4)
|
||
style.set_content_margin_all(mini(margin, 10))
|
||
return style
|
||
|
||
func _refresh() -> void:
|
||
_refresh_pending = false
|
||
_captain.prepare_skill_offers()
|
||
_rebuild_bar()
|
||
if _screen.visible:
|
||
_build_management()
|
||
|
||
func open_management() -> void:
|
||
if bool(player.get("_is_destroyed")):
|
||
return
|
||
if not _screen.visible:
|
||
_screen.show()
|
||
_build_management()
|
||
|
||
func _close() -> void:
|
||
_screen.hide()
|
||
|
||
func _build_management() -> void:
|
||
for child in _contents.get_children():
|
||
_contents.remove_child(child)
|
||
child.queue_free()
|
||
var choosing := not _captain.skill_offers.is_empty()
|
||
var heading := HBoxContainer.new()
|
||
heading.name = "Header"
|
||
_contents.add_child(heading)
|
||
var title := _label("AWANS KAPITANA" if choosing else "UMIEJĘTNOŚCI KAPITANA", 25, Color("91dbea"))
|
||
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
heading.add_child(title)
|
||
var close := Button.new()
|
||
close.name = "Close"
|
||
close.text = "WRÓĆ DO LOTU"
|
||
BUTTON_STYLE.apply(close, Color("91dbea"))
|
||
close.pressed.connect(_close)
|
||
heading.add_child(close)
|
||
_contents.add_child(_label("POZIOM %d" % _captain.level, 13, Color("91aabd")))
|
||
if choosing:
|
||
_contents.add_child(_label("Wybierz jedną umiejętność. Wybór obowiązuje przez całą rozgrywkę.", 16))
|
||
var choices := HBoxContainer.new()
|
||
choices.name = "Choices"
|
||
choices.add_theme_constant_override("separation", 7)
|
||
_contents.add_child(choices)
|
||
for skill in _captain.skill_offers:
|
||
choices.add_child(_choice_card(skill))
|
||
if _captain.learning_points > 1:
|
||
_contents.add_child(_label("Pozostałe wybory po tym awansie: %d" % (_captain.learning_points - 1), 13, Color("efb568")))
|
||
_contents.add_child(HSeparator.new())
|
||
_contents.add_child(_label("POSIADANE UMIEJĘTNOŚCI", 14, Color("91dbea")))
|
||
var owned := GridContainer.new()
|
||
owned.columns = 3
|
||
owned.add_theme_constant_override("h_separation", 12)
|
||
owned.add_theme_constant_override("v_separation", 10)
|
||
_contents.add_child(owned)
|
||
for skill in CATALOG.IDS:
|
||
var rank := _captain.get_skill_rank(skill)
|
||
if rank <= 0:
|
||
continue
|
||
var row := HBoxContainer.new()
|
||
row.custom_minimum_size.x = 220
|
||
row.tooltip_text = CATALOG.description(skill, rank)
|
||
owned.add_child(row)
|
||
row.add_child(_icon(skill, 38))
|
||
var info := VBoxContainer.new()
|
||
row.add_child(info)
|
||
info.add_child(_label(CATALOG.DATA[skill]["name"], 13))
|
||
info.add_child(_label("%s · %s" % ["●".repeat(rank) + "○".repeat(3 - rank), "AKTYWNA" if CATALOG.DATA[skill]["active"] else "PASYWNA"], 11, Color(CATALOG.DATA[skill]["color"])))
|
||
if owned.get_child_count() == 0:
|
||
_contents.add_child(_label("Nie masz jeszcze umiejętności. To początek Twojej specjalizacji.", 14, Color("91aabd")))
|
||
_build_loadout()
|
||
var manager := get_parent().get_node("HUDRoot/FloatingWindows")
|
||
manager.call("register_window", _panel, _panel, heading)
|
||
manager.call("focus_window", _panel)
|
||
_layout.call_deferred()
|
||
|
||
func _build_loadout() -> void:
|
||
_contents.add_child(HSeparator.new())
|
||
_contents.add_child(_label("AKTYWNE SLOTY F1–F5 · MAKSYMALNIE 5 UMIEJĘTNOŚCI", 13, Color("91dbea")))
|
||
var row := HBoxContainer.new()
|
||
row.name = "ActiveSkillLoadout"
|
||
row.add_theme_constant_override("separation", 6)
|
||
_contents.add_child(row)
|
||
for slot in CaptainProgress.ACTIVE_SKILL_SLOTS:
|
||
var column := VBoxContainer.new()
|
||
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
row.add_child(column)
|
||
column.add_child(_label("F%d" % (slot + 1), 12, Color("efb568")))
|
||
var picker := OptionButton.new()
|
||
picker.name = "SlotF%d" % (slot + 1)
|
||
picker.fit_to_longest_item = false
|
||
picker.custom_minimum_size = Vector2(155, 36)
|
||
picker.add_theme_font_override("font", ThemeDB.fallback_font)
|
||
BUTTON_STYLE.apply(picker, Color("91aabd"))
|
||
picker.add_theme_font_size_override("font_size", 11)
|
||
picker.add_theme_constant_override("icon_max_width", 26)
|
||
picker.get_popup().add_theme_constant_override("icon_max_width", 28)
|
||
picker.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||
picker.add_item("Pusty slot")
|
||
picker.set_item_metadata(0, "")
|
||
for skill in CATALOG.IDS:
|
||
if CATALOG.DATA[skill]["active"] and _captain.get_skill_rank(skill) > 0:
|
||
picker.add_icon_item(CATALOG.icon(skill), CATALOG.DATA[skill]["name"])
|
||
var index := picker.item_count - 1
|
||
picker.set_item_metadata(index, skill)
|
||
if _captain.equipped_skills[slot] == skill:
|
||
picker.select(index)
|
||
picker.tooltip_text = "Wybierz aktywną umiejętność dla F%d. Pasywne działają bez zajmowania slotu." % (slot + 1)
|
||
picker.item_selected.connect(func(index: int) -> void: _captain.equip_skill(slot, str(picker.get_item_metadata(index))))
|
||
column.add_child(picker)
|
||
|
||
func _icon(skill: String, dimensions: float) -> TextureRect:
|
||
var icon := TextureRect.new()
|
||
icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
|
||
icon.texture = CATALOG.icon(skill)
|
||
icon.custom_minimum_size = Vector2.ONE * dimensions
|
||
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
return icon
|
||
|
||
func _choice_card(skill: String) -> PanelContainer:
|
||
var rank := _captain.get_skill_rank(skill)
|
||
var next_rank := rank + 1
|
||
var data: Dictionary = CATALOG.DATA[skill]
|
||
var color := Color(data["color"])
|
||
var card := PanelContainer.new()
|
||
card.name = skill
|
||
card.custom_minimum_size = Vector2(240, 270)
|
||
card.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
card.add_theme_stylebox_override("panel", _style(color.darkened(0.4), Color("101e2e"), 16))
|
||
var body := VBoxContainer.new()
|
||
body.add_theme_constant_override("separation", 6)
|
||
card.add_child(body)
|
||
body.add_child(_label("NOWA UMIEJĘTNOŚĆ" if rank == 0 else "ULEPSZENIE %s → %s" % [str(rank), str(next_rank)], 11, color))
|
||
body.add_child(_icon(skill, 54))
|
||
var title := _label(data["name"], 19)
|
||
title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
body.add_child(title)
|
||
body.add_child(_label("POZIOM %s / III · %s" % [["I", "II", "III"][next_rank - 1], "AKTYWNA" if data["active"] else "PASYWNA"], 11, color))
|
||
var description := _label(CATALOG.description(skill, next_rank), 14)
|
||
description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
description.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
body.add_child(description)
|
||
if data["active"]:
|
||
body.add_child(_label("DZIAŁANIE %d s · ODNOWIENIE 30 s" % int(CATALOG.duration(skill)), 11, Color("91aabd")))
|
||
else:
|
||
body.add_child(_label("STAŁY EFEKT", 11, Color("91aabd")))
|
||
var select := Button.new()
|
||
select.name = "Choose"
|
||
select.text = "WYBIERZ" if rank == 0 else "ULEPSZ DO %s" % ["I", "II", "III"][next_rank - 1]
|
||
select.custom_minimum_size.y = 30
|
||
select.focus_mode = Control.FOCUS_NONE
|
||
BUTTON_STYLE.apply(select, color)
|
||
select.pressed.connect(_choose.bind(skill))
|
||
body.add_child(select)
|
||
return card
|
||
|
||
func _choose(skill: String) -> void:
|
||
if not _captain.train_skill(skill):
|
||
return
|
||
# Clear any held mouse fire state before resuming the simulation.
|
||
player.get_node("WeaponController").set("_fire_buttons_down", [false, false, false])
|
||
player.get_node("WeaponController").set("_fire_just_pressed", [false, false, false])
|
||
if _captain.skill_offers.is_empty():
|
||
_close()
|
||
|
||
func _rebuild_bar() -> void:
|
||
for child in _bar.get_children():
|
||
_bar.remove_child(child)
|
||
child.queue_free()
|
||
_buttons.clear()
|
||
_captain.normalize_equipped_skills()
|
||
for index in CaptainProgress.ACTIVE_SKILL_SLOTS:
|
||
var skill := _captain.equipped_skills[index]
|
||
var column := VBoxContainer.new()
|
||
_bar.add_child(column)
|
||
var button := Button.new()
|
||
button.set_script(preload("res://scripts/ui/captain_skill_button.gd"))
|
||
button.name = "SlotF%d" % (index + 1)
|
||
button.call("configure", CATALOG.icon(skill) if not skill.is_empty() else null, Color(CATALOG.DATA[skill]["color"]) if not skill.is_empty() else Color("344657"))
|
||
button.disabled = skill.is_empty()
|
||
button.pressed.connect(_activate.bind(skill))
|
||
column.add_child(button)
|
||
var binding := _label("F%d" % (index + 1), 9)
|
||
binding.position = Vector2(4, 2)
|
||
binding.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
binding.add_theme_color_override("font_outline_color", Color("030810"))
|
||
binding.add_theme_constant_override("outline_size", 4)
|
||
button.add_child(binding)
|
||
var status := _label("", 9)
|
||
status.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||
status.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
status.add_theme_color_override("font_outline_color", Color("030810"))
|
||
status.add_theme_constant_override("outline_size", 4)
|
||
button.add_child(status)
|
||
status.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT)
|
||
status.offset_left = -33
|
||
status.offset_top = -14
|
||
status.offset_right = -3
|
||
status.offset_bottom = -1
|
||
_buttons[index] = {"button": button, "status": status, "skill": skill, "key": KEY_F1 + index, "binding": "F%d" % (index + 1)}
|
||
_layout.call_deferred()
|
||
|
||
func _activate(skill: String) -> void:
|
||
if not get_tree().paused:
|
||
_captain.activate_skill(skill, player)
|
||
|
||
func _unhandled_key_input(event: InputEvent) -> void:
|
||
if event is InputEventKey and event.pressed and not event.echo:
|
||
if event.keycode == KEY_ESCAPE and _screen.visible:
|
||
_close()
|
||
get_viewport().set_input_as_handled()
|
||
elif not get_tree().paused:
|
||
for index in _buttons:
|
||
if event.physical_keycode == _buttons[index]["key"] or event.keycode == _buttons[index]["key"]:
|
||
_activate(_buttons[index]["skill"])
|
||
get_viewport().set_input_as_handled()
|
||
|
||
func _process(_delta: float) -> void:
|
||
for index in _buttons:
|
||
var entry: Dictionary = _buttons[index]
|
||
var skill: String = entry["skill"]
|
||
var button: Button = entry["button"]
|
||
var status: Label = entry["status"]
|
||
if skill.is_empty():
|
||
button.tooltip_text = "[%s] Pusty slot — przypisz skill w panelu kapitana" % entry["binding"]
|
||
continue
|
||
var duration := float(_captain.skill_durations.get(skill, 0.0))
|
||
var cooldown := float(_captain.skill_cooldowns.get(skill, 0.0))
|
||
var reason := _captain.skill_unavailable_reason(skill, player)
|
||
button.disabled = get_tree().paused or not reason.is_empty()
|
||
button.call("set_visual_state", cooldown / CATALOG.COOLDOWN, duration > 0.0, button.disabled)
|
||
button.tooltip_text = "%s · poziom %d\n%s\n[%s] · Odnowienie 30 s%s" % [CATALOG.DATA[skill]["name"], _captain.get_skill_rank(skill), CATALOG.description(skill, _captain.get_skill_rank(skill)), entry["binding"], "\n" + reason if not reason.is_empty() else ""]
|
||
status.text = "%.1f" % duration if duration > 0.0 else (str(ceili(cooldown)) if cooldown > 0.0 else "")
|
||
status.modulate = Color("a9ef83") if duration > 0.0 else Color("dae8ef")
|
||
_bar.visible = not bool(player.get("_is_destroyed")) and not _screen.visible
|
||
|
||
func _layout() -> void:
|
||
if not is_inside_tree():
|
||
return
|
||
var viewport_size := get_viewport().get_visible_rect().size
|
||
var base_size := _panel.get_combined_minimum_size().max(Vector2(760, 160))
|
||
var ui_scale := minf(1.0, minf((viewport_size.x - 32) / base_size.x, (viewport_size.y - 32) / base_size.y))
|
||
_panel.size = base_size
|
||
_panel.scale = Vector2.ONE * ui_scale
|
||
if _panel.has_meta("floating_window_dragged"):
|
||
get_parent().get_node("HUDRoot/FloatingWindows").call("clamp_window", _panel)
|
||
else:
|
||
_panel.position = (viewport_size - base_size * ui_scale) * 0.5
|
||
var cockpit := get_parent().get("_cockpit") as Control
|
||
if is_instance_valid(cockpit):
|
||
_bar.position = cockpit.position + Vector2(0, -40)
|
||
else:
|
||
_bar.position = Vector2(16, viewport_size.y - 190)
|
||
|
||
|