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) 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)