266 lines
16 KiB
GDScript
266 lines
16 KiB
GDScript
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)
|
||
var external := item.definition.weapon_effect == "external_turret"
|
||
_metric(card, "weapon_dps_%d" % index, "DPS jednego działka" if external else "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 strzałów działka" if external else "Odstęp między strzałami", func(): return "%.2f s" % (item.get_turret_fire_interval() if external else (inventory.get_missile_cooldown(item) if item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE else item.get_fire_interval())))
|
||
if external:
|
||
_metric(card,"turret_deploy_%d" % index,"Odstęp rozstawiania",func(): return "%.2f s" % item.get_fire_interval())
|
||
UI.label(card,"50 amunicji → 1 działko z 50 pociskami",12,UI.MUTED)
|
||
_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)
|
||
if item.definition.shield_regeneration_bonus > 0.0:
|
||
UI.label(card,"Regeneracja tarcz: +%.1f pkt/s" % item.get_base_shield_regeneration(),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)
|