Randoloot/scripts/ui/ship_statistics.gd
2026-10-25 16:12:08 +01:00

260 lines
16 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 PanelContainer
const UI := preload("res://scripts/planets/freighter_menu.gd")
const DEFENSE := Color("e9a08a")
const ENERGY := Color("89d2b2")
const CYAN := Color("83c7e6")
const GOLD := Color("dfb775")
var player: CharacterBody2D
var inventory: ShipInventory
var _body: VBoxContainer
var _grid: GridContainer
var _header: HBoxContainer
var _subtitle: Label
var _tabs: Dictionary = {}
var _bindings: Dictionary = {}
var _tab := "parameters"
var _elapsed := 0.0
func _ready() -> void:
name = "ShipStatistics"
UI.style(self, 12, 0.48)
var frame := preload("res://scripts/ui/ornamental_frame.gd").new()
frame.corner_size = 18
frame.opacity = 0.75
add_child(frame)
var stack := VBoxContainer.new()
stack.add_theme_constant_override("separation", 5)
add_child(stack)
_header = UI.row(stack)
var title := UI.label(_header, "STATYSTYKI STATKU", 17, GOLD)
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
title.autowrap_mode = TextServer.AUTOWRAP_OFF
title.mouse_filter = Control.MOUSE_FILTER_IGNORE
UI.button(_header, "×", hide).custom_minimum_size.x = 32
_subtitle = UI.label(stack, "", 11, UI.MUTED)
_subtitle.autowrap_mode = TextServer.AUTOWRAP_OFF
var navigation := UI.row(stack)
for entry in [["parameters", "PARAMETRY"], ["weapons", "UZBROJENIE"], ["equipment", "SYSTEMY I MODUŁY"]]:
var button := UI.button(navigation, entry[1], _select_tab.bind(entry[0]))
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button.custom_minimum_size.y = 26
button.add_theme_font_size_override("font_size", 10)
_tabs[entry[0]] = button
var scroll := ScrollContainer.new()
scroll.name = "StatisticsScroll"
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
stack.add_child(scroll)
_body = VBoxContainer.new()
_body.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_body.add_theme_constant_override("separation", 8)
scroll.add_child(_body)
inventory.inventory_changed.connect(_equipment_changed)
get_viewport().size_changed.connect(_layout)
_layout()
hide()
func open() -> void:
_rebuild()
_layout()
show()
get_parent().get_node("FloatingWindows").focus_window(self)
func _layout() -> void:
var viewport_size := get_viewport_rect().size
size = Vector2(minf(690, viewport_size.x - 32), minf(490, viewport_size.y - 48))
if not has_meta("floating_window_dragged"):
position = (viewport_size - size) * 0.5
else:
get_parent().get_node("FloatingWindows").clamp_window(self)
if is_instance_valid(_grid): _grid.columns = 2 if size.x >= 600 else 1
func _select_tab(tab: String) -> void:
_tab = tab
_rebuild()
_body.get_parent().scroll_vertical = 0
func _equipment_changed() -> void:
if visible: _rebuild.call_deferred()
func _process(delta: float) -> void:
if not visible: return
_layout()
_elapsed += delta
if _elapsed >= 0.2:
_elapsed = 0
_refresh_values()
func _unhandled_key_input(event: InputEvent) -> void:
if visible and event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
hide()
get_viewport().set_input_as_handled()
func _rebuild() -> void:
_bindings.clear()
for child in _body.get_children():
_body.remove_child(child)
child.queue_free()
for tab in _tabs:
_tabs[tab].modulate = GOLD if tab == _tab else Color.WHITE
_grid = GridContainer.new()
_grid.columns = 2 if size.x >= 640 else 1
_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_grid.add_theme_constant_override("h_separation", 6)
_grid.add_theme_constant_override("v_separation", 6)
_body.add_child(_grid)
match _tab:
"parameters": _parameters()
"weapons": _weapons()
"equipment": _equipment()
_refresh_values()
_layout.call_deferred()
func _section(title: String, color: Color) -> VBoxContainer:
var card := UI.card(_grid)
card.get_parent().size_flags_horizontal = Control.SIZE_EXPAND_FILL
card.get_parent().custom_minimum_size.x = 260
UI.style(card.get_parent(), 6, 0.24)
card.add_theme_constant_override("separation", 1)
var heading := UI.row(card)
heading.add_theme_constant_override("separation", 5)
var icon_path := "res://assets/icons/computer.png"
if title.begins_with("01"): icon_path = "res://assets/freighter/research/hull.png"
elif title.begins_with("02"): icon_path = "res://assets/freighter/research/shields.png"
elif title.begins_with("03") or title.begins_with("BROŃ"): icon_path = "res://assets/freighter/research/weapons.png"
elif title.begins_with("04"): icon_path = "res://assets/icons/engine.png"
elif title.begins_with("05"): icon_path = "res://assets/icons/cargo_crate.svg"
elif title.begins_with("06"): icon_path = "res://assets/freighter/research/general.png"
UI.icon(heading,load(icon_path),16).mouse_filter = Control.MOUSE_FILTER_IGNORE
UI.label(heading, title, 12, color).autowrap_mode = TextServer.AUTOWRAP_OFF
var line := HSeparator.new()
line.modulate = Color(color, 0.3)
card.add_child(line)
return card
func _metric(card: VBoxContainer, key: String, caption: String, read_value: Callable, color := CYAN, hint := "") -> void:
var row := UI.row(card)
row.tooltip_text = hint
var label := UI.label(row, caption, 12, UI.MUTED)
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
label.autowrap_mode = TextServer.AUTOWRAP_OFF
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
var value := UI.label(row, "", 12, color)
value.autowrap_mode = TextServer.AUTOWRAP_OFF
value.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
value.mouse_filter = Control.MOUSE_FILTER_IGNORE
_bindings[key] = {"label":value, "read":read_value}
func _refresh_values() -> void:
if not is_instance_valid(player): return
var ship := ShipCatalog.for_path(GameState.selected_ship_path)
_subtitle.text = "%s / System %d / %s / DPS %.1f" % [ship.name, GameState.system_level, "HARD" if preload("res://scripts/levels/level_balance.gd").is_hard(GameState.system_level) else "NORMALNY", inventory.get_total_dps()]
for entry in _bindings.values():
entry.label.text = str(entry.read.call())
func _parameters() -> void:
var card := _section("01 / PANCERZ I OCHRONA", DEFENSE)
_metric(card, "hull", "Kadłub", func(): return "%.0f / %.0f" % [player.hull,player.max_hull], DEFENSE)
var hull_research := preload("res://scripts/planets/alien_research.gd")
if hull_research.learned("hull_b_next"):
_metric(card,"reactive_armor","Pancerz reaktywny",func(): return "%.1f s" % player._reactive_armor_cooldown if player._reactive_armor_cooldown > 0 else "GOTOWY",DEFENSE)
if hull_research.learned("hull_b_next_next"):
_metric(card,"bulkheads","Awaryjne grodzie",func(): return "NAPRAWA" if player._bulkhead_repair_remaining > 0 else ("%.1f s" % player._bulkhead_cooldown if player._bulkhead_cooldown > 0 else "GOTOWE"),DEFENSE)
if hull_research.learned("hull_b_next_next_next"):
_metric(card,"last_bastion","Ostatni bastion",func(): return "ZUŻYTY" if GameState.hull_bastion_used else "GOTOWY",DEFENSE)
_metric(card, "reduction", "Redukcja obrażeń", func(): return "%.1f%%" % (player.get_damage_reduction()*100), DEFENSE, "Uwzględnia mechanika i aktywny efekt pancerza z tytanu.")
_metric(card, "evasion", "Szansa na unik", func(): return "%.1f%%" % (player.get_evasion_chance()*100), DEFENSE)
for index in range(3):
_metric(card, DamageTypes.KEYS[index], "Absorpcja: " + DamageTypes.NAMES[index].to_lower(), func(): return "%.1f%%" % player.absorption[index], DEFENSE)
card = _section("02 / TARCZE I ENERGIA", ENERGY)
_metric(card, "shields", "Tarcze", func(): return "%.0f / %.0f" % [player.shields,player.max_shields], CYAN)
if preload("res://scripts/planets/alien_research.gd").learned("shields_b_next"):
_metric(card,"shield_reserve","Rezerwa kondensatorów",func(): return "AKTYWNA %.1f s" % player._shield_reserve_remaining if player._shield_reserve_remaining > 0 else ("%.1f s" % player._shield_reserve_cooldown if player._shield_reserve_cooldown > 0 else "GOTOWA"),CYAN)
if preload("res://scripts/planets/alien_research.gd").learned("shields_b_next_next"):
_metric(card,"salvo","Rozproszenie salwy",func(): return "%.1f s" % player._salvo_cooldown if player._salvo_cooldown > 0 else "GOTOWE",CYAN)
if preload("res://scripts/planets/alien_research.gd").learned("shields_b_next_next_next"):
_metric(card,"emergency_shield","Tarcza awaryjna",func(): return "AKTYWNA %.1f s" % player._emergency_shield_remaining if player._emergency_shield_remaining > 0 else ("%.1f s" % player._emergency_shield_cooldown if player._emergency_shield_cooldown > 0 else "GOTOWA"),CYAN)
_metric(card, "shield_regeneration", "Regeneracja tarcz", func(): return "%.1f / s" % player.shield_regeneration, CYAN, "Do 3% maksymalnych tarcz na sekundę.")
_metric(card, "shield_on_damage", "Odzysk tarcz z obrażeń", func(): return "%.1f%%" % player.shield_on_damage_percent, CYAN, "Łącznie do 10% faktycznie zadanych obrażeń. Tempo: do 5% maksymalnych tarcz/s, z rezerwą na jedno takie odnowienie.")
_metric(card, "energy", "Energia", func(): return "%.0f / %.0f" % [player.energy,player.max_energy], ENERGY)
_metric(card, "energy_regeneration", "Regeneracja energii", func(): return "%.1f / s" % player.get_energy_regeneration(), ENERGY, "Sprzęt: do 15% pojemności generatora/s. Ładowanie przy gwieździe i czasowe umiejętności dodają osobne premie.")
_metric(card, "solar", "Ładowanie słoneczne", func(): return "%.1f / s" % player.get_solar_regeneration(), ENERGY)
card = _section("03 / SIŁA OGNIA", GOLD)
_metric(card, "dps", "Łączny DPS", func(): return "%.1f" % inventory.get_total_dps(), GOLD, "Teoretyczny DPS: uwzględnia krytyki i promieniowanie; przed obroną celu, przy dostępnej energii i amunicji.")
_metric(card, "damage", "Premia do obrażeń", func(): return "%+.1f%%" % ((player.get_weapon_damage_multiplier()-1)*100), GOLD, "Premia ze sprzętu: do +100%. Umiejętności kapitana działają osobno.")
_metric(card, "critical", "Szansa na krytyk", func(): return "%.1f%%" % (player.get_critical_chance()*100), GOLD, "Sprzęt: do 40%. Łącznie z umiejętnościami: do 60%.")
_metric(card, "critical_damage", "Mnożnik krytyka", func(): return "×%.2f" % player.get_critical_multiplier(), GOLD, "Sprzęt: do +100% mnożnika bazowego, maksymalnie ×4.")
card = _section("04 / NAPĘD I NAWIGACJA", CYAN)
_metric(card, "speed", "Prędkość aktualna", func(): return "%.0f px/s" % player.velocity.length())
_metric(card, "max_speed", "Prędkość maksymalna", func(): return "%.0f px/s" % player.max_speed, CYAN, "Zwykły lot: do 240 px/s. TAB: do 480 px/s. Aktywny dopalacz: do 600 px/s.")
_metric(card, "acceleration", "Przyspieszenie", func(): return "%.0f px/s²" % player.thrust_acceleration)
_metric(card, "rotation", "Prędkość obrotu", func(): return "%.0f °/s" % rad_to_deg(player.max_turn_speed))
_metric(card, "scanner", "Zasięg skanera", func(): return "%.0f px" % player.scanner_range)
card = _section("05 / ŁADOWNIA I ZAOPATRZENIE", GOLD)
_metric(card, "cargo", "Zajęte miejsca", func(): return "%d / %d" % [inventory.get_cargo_used(),inventory.get_cargo_capacity()], GOLD)
_metric(card, "ammunition", "Amunicja zwykła", func(): return str(inventory.ammunition), GOLD)
_metric(card, "gamma", "Amunicja radiacyjna", func(): return str(inventory.irradiated_ammunition), GOLD)
_metric(card, "missiles", "Rakiety", func(): return str(inventory.missile_ammunition), GOLD)
_metric(card, "banks", "Banki energii", func(): return str(inventory.energy_banks), ENERGY)
card = _section("06 / PREMIE I BADANIA", Color("b6a0d7"))
_metric(card, "xp", "Premia do doświadczenia", func(): return "%+.1f%%" % ((player.experience_multiplier-1)*100), GOLD)
_metric(card, "credits", "Premia do kredytów", func(): return "%+.1f%%" % ((player.credits_multiplier-1)*100), GOLD)
_metric(card, "luck", "Szansa szczęścia", func(): return "%.1f%%" % (player.get_luck_chance()*100), GOLD)
var research := preload("res://scripts/planets/alien_research.gd")
for branch in research.BRANCHES:
_metric(card, "research_" + branch, {"weapons":"Osłabienie broni obcych","hull":"Obrażenia kadłubów obcych","shields":"Obrażenia tarcz obcych"}[branch], func(): return "%.0f%%" % ((research.bonus(GameState.captain.expedition,branch)+(0.05 if branch=="hull" and research.learned("hull_a") else (0.05 if branch=="shields" and research.learned("shields_a") else 0.0)))*100), ENERGY)
func _weapons() -> void:
for index in range(inventory.WEAPON_SLOT_COUNT):
var card := _section("BROŃ %d / %s" % [index+1,["LPM","PPM","ŚPM"][index]], GOLD)
if not inventory.is_slot_unlocked("weapon",index):
UI.label(card, "Slot zablokowany • Odblokuj w hangarze", 12, UI.MUTED)
continue
var item := inventory.get_item("weapon", index)
if item == null:
UI.label(card, "Brak zamontowanej broni", 13, UI.MUTED)
continue
_item_header(card,item)
_metric(card, "weapon_dps_%d" % index, "DPS", func(): return "%.1f" % inventory.get_weapon_dps(index), GOLD)
_metric(card, "weapon_damage_%d" % index, "Obrażenia trafienia", func(): return "%.1f – %.1f" % [item.get_damage_range().x*player.get_weapon_damage_multiplier(), item.get_damage_range().y*player.get_weapon_damage_multiplier()], GOLD, "Zakres przed krytykiem i obroną przeciwnika.")
_metric(card, "weapon_interval_%d" % index, "Odstęp między strzałami", func(): return "%.2f s" % (inventory.get_missile_cooldown(item) if item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE else item.get_fire_interval()))
_metric(card, "weapon_energy_%d" % index, "Energia na strzał", func(): return "%.1f" % player.get_weapon_energy_cost(item.get_energy_cost()), ENERGY)
_metric(card, "weapon_range_%d" % index, "Zasięg", func(): return "%.0f px" % item.definition.weapon_range)
UI.label(card,"Typ: " + DamageTypes.NAMES[item.get_damage_type()],12,UI.MUTED)
var info := _section("ODCZYT PARAMETRÓW", CYAN)
UI.label(info,"DPS uwzględnia premie wyposażenia, kapitana, krytyki i promieniowanie. To wartość teoretyczna, przed obroną celu; rzeczywisty wynik zależy od trafień, energii i amunicji.",13,UI.MUTED)
UI.label(info,"Najedź na nazwę przedmiotu, aby zobaczyć wszystkie addony i parametry.",13,UI.MUTED)
func _item_header(card: VBoxContainer, item: ItemInstance) -> void:
var row := UI.row(card)
row.tooltip_text = item.get_tooltip_text()
UI.icon(row,item.definition.icon,24).mouse_filter = Control.MOUSE_FILTER_IGNORE
var label := UI.label(row,"%s • poz. %d" % [item.definition.display_name,item.item_level],12,item.get_type_color())
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
func _equipment() -> void:
var card := _section("MODUŁY STATKU", CYAN)
for index in range(inventory.MODULE_SLOT_COUNT):
UI.label(card,inventory.MODULE_NAMES[index].to_upper(),12,CYAN)
var item := inventory.get_item("module",index)
if item == null: UI.label(card,"Brak modułu",13,UI.MUTED)
else:
_item_header(card,item)
UI.label(card,"%s: %.1f %s" % [ItemInstance.stat_name(item.definition.primary_stat), item.get_stat_total(item.definition.primary_stat),item.definition.stat_suffix],12,UI.MUTED)
card = _section("ROZSZERZENIA", ENERGY)
for index in range(inventory.EXTENSION_SLOT_COUNT):
UI.label(card,"SLOT %d" % (index+1),12,ENERGY)
if not inventory.is_slot_unlocked("extension",index):
UI.label(card,"Zablokowany",13,UI.MUTED)
continue
var item := inventory.get_item("extension",index)
if item == null:
UI.label(card,"Brak rozszerzenia",13,UI.MUTED)
continue
_item_header(card,item)
if item.is_passive_extension(): UI.label(card,item.get_passive_description(),13,UI.MUTED)
else:
UI.label(card, "Odnowienie: %.0f s" % item.get_active_ability().cooldown, 13, UI.MUTED)
_metric(card,"extension_%d" % index,"Pozostało",func(): return "%.1f s" % player.get_ability_cooldown(index),ENERGY)