extends RefCounted const CARD := preload("res://scripts/planets/poi_presentation.gd") const SERVICE := preload("res://scripts/planets/time_engineer.gd") const CYAN := Color("9be7ff") const MENU := preload("res://scripts/planets/freighter_menu.gd") const CRYSTAL := preload("res://assets/icons/resources/time_crystals.png") static func build(screen: Control, message: String = "", freighter := false) -> void: if freighter: _build_compact(screen,message) return var parent: VBoxContainer = screen._content if freighter else screen._choices var progress: Resource = screen.captain.expedition if freighter else screen.progress var art := TextureRect.new() art.texture = load("res://assets/planets/props/time_engineer.png") art.expand_mode = TextureRect.EXPAND_IGNORE_SIZE art.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED art.custom_minimum_size.y = 125 parent.add_child(art) CARD.text(parent,"INŻYNIER CHRONALNY",18,CYAN) CARD.text(parent,"Wybierz przedmiot i konkretny addon. Inżynier ustawi tylko jego wartość na maksimum dla obecnego poziomu przedmiotu.",12) if not message.is_empty(): CARD.text(parent,message,12,CARD.GOLD) CARD.text(parent,"Kryształy czasu: %d" % progress.time_crystals,14,CYAN) CARD.text(parent,"Koszt: 10 kryształów × poziom przedmiotu. Gotowe maksima nie pobierają opłaty.",12) if not is_instance_valid(screen.inventory if freighter else screen.flight_ship): CARD.text(parent,"Brak połączenia z wyposażeniem statku.",12) _back(parent,screen,freighter) return var inventory: Node = screen.inventory if freighter else screen.flight_ship.get_node("ShipInventory") var items: Array[ItemInstance] = [] var locations: Array[String] = [] for kind in ["weapon","module","extension","cargo"]: var slots: Array = inventory._get_slots(kind) for index in slots.size(): var item: ItemInstance = slots[index] if not inventory.is_slot_unlocked(kind,index) or item == null or item.definition == null or item.additional_modifiers.is_empty() or items.has(item): continue items.append(item) locations.append({"weapon":"Broń","module":"Moduł","extension":"Rozszerzenie","cargo":"Ładownia"}[kind]) if items.is_empty(): CARD.text(parent,"Brak przedmiotów z addonami na statku. Przenieś wyposażenie z magazynu fregaty do ładowni.",12,CARD.GOLD) _back(parent,screen,freighter) return if not items.has(screen._engineer_item): screen._engineer_item = items[0] screen._engineer_addon = 0 CARD.text(parent,"PRZEDMIOT",11,CARD.MUTED) var choice := _choice(parent,"EngineerItem") for index in items.size(): var item: ItemInstance = items[index] choice.add_item("%s · poz. %d · %s" % [item.definition.display_name,item.item_level,locations[index]]) choice.set_item_metadata(index,item) choice.select(items.find(screen._engineer_item)) choice.item_selected.connect(func(index): screen._engineer_item = choice.get_item_metadata(index) screen._engineer_addon = 0 screen._refresh() if freighter else screen._show_poi() ) var item: ItemInstance = screen._engineer_item CARD.text(parent,"ADDON DO MAKSYMALIZACJI",11,CARD.MUTED) var addons := _choice(parent,"EngineerAddon") var selected := 0 for index in item.additional_modifiers.size(): var modifier: ItemStatModifier = item.additional_modifiers[index] if modifier == null: continue var affix := SERVICE.affix_for(item,index) var suffix := " · brak zakresu" if affix == null else (" · MAX" if not SERVICE.can_maximize(item,index) else "") addons.add_item(modifier.get_display_text()+suffix) var option := addons.item_count-1 addons.set_item_metadata(option,index) if index == screen._engineer_addon: selected = option if addons.item_count == 0: CARD.text(parent,"Brak dostępnych addonów.",12) _back(parent,screen,freighter) return addons.select(selected) screen._engineer_addon = int(addons.get_item_metadata(selected)) addons.item_selected.connect(func(index): screen._engineer_addon = int(addons.get_item_metadata(index)) screen._refresh() if freighter else screen._show_poi() ) var index: int = screen._engineer_addon var affix := SERVICE.affix_for(item,index) if affix != null: var multiplier := ItemInstance.ENERGY_REGENERATION_BONUS_MULTIPLIER if item.additional_modifiers[index].stat_key == "energy_regeneration" else 1.0 CARD.text(parent,"Obecnie: %.2f %s → maksimum: %.2f %s" % [item.additional_modifiers[index].value*multiplier,affix.unit,SERVICE.maximum(item,index)*multiplier,affix.unit],13,CARD.GOLD) var cost := SERVICE.cost(item) var ready := SERVICE.can_maximize(item,index) var apply := CARD.button(parent,"MAKSYMALIZUJ · %d KRYSZTAŁÓW" % cost,screen._maximize_addon,true) apply.name = "MaximizeSelectedAddon" apply.disabled = not ready or progress.time_crystals < cost if not ready: CARD.text(parent,"Ten addon ma już maksymalną wartość lub nie posiada zakresu losowania.",12,CARD.MUTED) elif progress.time_crystals < cost: CARD.text(parent,"Brakuje %d kryształów czasu." % (cost-progress.time_crystals),12,CARD.GOLD) _back(parent,screen,freighter) static func _choice(parent: Node, node_name: String) -> OptionButton: var choice := OptionButton.new() choice.name = node_name choice.size_flags_horizontal = Control.SIZE_EXPAND_FILL choice.fit_to_longest_item = false choice.add_theme_font_override("font",ThemeDB.fallback_font) choice.add_theme_font_size_override("font_size",12) parent.add_child(choice) return choice static func _back(parent: Node, screen: Control, freighter := false) -> void: if freighter: return CARD.button(parent,"Wróć do wyprawy",func(): screen._dialog.hide()) static func refresh(screen: Control) -> void: var old: Node = screen._content.get_node_or_null("ChronalBench") var index: int = old.get_index() if old != null else screen._content.get_child_count() if old != null: screen._content.remove_child(old) old.queue_free() _build_compact(screen,screen._engineer_notice) screen._content.move_child(screen._content.get_node("ChronalBench"),index) static func _build_compact(screen: Control, message: String) -> void: var panel := PanelContainer.new() panel.name = "ChronalBench" panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL panel.add_theme_stylebox_override("panel",_box(Color("101e2bf2"),Color("416b7c"),10)) screen._content.add_child(panel) var body := VBoxContainer.new() body.add_theme_constant_override("separation",9) panel.add_child(body) var progress: Resource = screen.captain.expedition var header := MENU.row(body) MENU.icon(header,preload("res://assets/icons/screwdriver.png"),23) var title := _text(header,"MAKSYMALIZACJA ADDONU",CYAN) title.size_flags_horizontal = Control.SIZE_EXPAND_FILL MENU.icon(header,CRYSTAL,24) _text(header,str(progress.time_crystals),MENU.GOLD) header.tooltip_text = "Kryształy czasu · koszt maksymalizacji: 10 × poziom przedmiotu." if not is_instance_valid(screen.inventory): _text(body,"Zadokuj statkiem, aby wybrać wyposażenie.",MENU.MUTED) return var inventory: Node = screen.inventory var items: Array[ItemInstance] = [] var locations: Array[String] = [] for kind in ["weapon","module","extension","cargo"]: var slots: Array = inventory._get_slots(kind) for index in slots.size(): var item: ItemInstance = slots[index] if not inventory.is_slot_unlocked(kind,index) or item == null or item.definition == null or item.additional_modifiers.is_empty() or items.has(item): continue items.append(item) locations.append({"weapon":"Broń","module":"Moduł","extension":"Rozszerzenie","cargo":"Ładownia"}[kind]) if items.is_empty(): var empty := MENU.row(body) MENU.icon(empty,preload("res://assets/icons/cargo_crate.svg"),32) var hint := _text(empty,"Brak wyposażenia z addonami",MENU.MUTED) hint.tooltip_text = "Przenieś przedmiot z addonami do ładowni lub zamontuj go na statku." return if not items.has(screen._engineer_item): screen._engineer_item = items[0] screen._engineer_addon = 0 var item: ItemInstance = screen._engineer_item var scroll := ScrollContainer.new() scroll.name = "EngineerItem" scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED scroll.custom_minimum_size.y = 72 scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL body.add_child(scroll) var tray := HBoxContainer.new() tray.add_theme_constant_override("separation",6) scroll.add_child(tray) for index in items.size(): var candidate: ItemInstance = items[index] var pick := Button.new() pick.name = "EngineerItem_%d" % index pick.custom_minimum_size = Vector2(54,54) pick.size_flags_vertical = Control.SIZE_SHRINK_BEGIN pick.icon = candidate.definition.icon pick.expand_icon = true pick.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER pick.add_theme_constant_override("icon_max_width",34) pick.tooltip_text = "%s · %s\n%s" % [locations[index],candidate.definition.display_name,candidate.get_tooltip_text()] _skin(pick,candidate == item) tray.add_child(pick) if candidate == item: scroll.ensure_control_visible.call_deferred(pick) var level := _text(pick,str(candidate.item_level),CYAN if candidate == item else MENU.MUTED) level.position = Vector2(4,35) level.add_theme_font_size_override("font_size",10) level.mouse_filter = Control.MOUSE_FILTER_IGNORE pick.pressed.connect(func(): screen._engineer_item = candidate screen._engineer_addon = 0 screen._engineer_notice = "" refresh(screen)) var selected := MENU.row(body) var item_name := _text(selected,item.definition.display_name,MENU.TEXT) item_name.size_flags_horizontal = Control.SIZE_EXPAND_FILL item_name.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS item_name.tooltip_text = item.get_tooltip_text() _text(selected,"POZ. %d" % item.item_level,MENU.MUTED) var addons := VBoxContainer.new() addons.name = "EngineerAddon" addons.add_theme_constant_override("separation",5) body.add_child(addons) var first := -1 for index in item.additional_modifiers.size(): if item.additional_modifiers[index] != null: if first < 0: first = index if first < 0: _text(body,"Brak dostępnych addonów.",MENU.MUTED) return if screen._engineer_addon < 0 or screen._engineer_addon >= item.additional_modifiers.size() or item.additional_modifiers[screen._engineer_addon] == null: screen._engineer_addon = first for index in item.additional_modifiers.size(): var modifier: ItemStatModifier = item.additional_modifiers[index] if modifier == null: continue _addon_button(screen,addons,item,index) var index: int = screen._engineer_addon var ready := SERVICE.can_maximize(item,index) var cost := SERVICE.cost(item) var footer := MENU.row(body) MENU.icon(footer,CRYSTAL,28) var price := _text(footer,str(cost),MENU.GOLD if progress.time_crystals >= cost else Color("ef9389")) price.size_flags_horizontal = Control.SIZE_EXPAND_FILL price.tooltip_text = "Koszt: 10 kryształów × poziom przedmiotu." var apply := MENU.button(footer,"MAKSYMALIZUJ" if ready else "✓ MAX",screen._maximize_addon,true) apply.name = "MaximizeSelectedAddon" apply.custom_minimum_size = Vector2(158,36) apply.icon = preload("res://assets/icons/screwdriver.png") apply.expand_icon = true apply.add_theme_constant_override("icon_max_width",18) apply.size_flags_vertical = Control.SIZE_SHRINK_CENTER apply.disabled = not ready or progress.time_crystals < cost apply.tooltip_text = "Maksymalizuj tylko wybrany addon. Pozostałe parametry zostają bez zmian." if ready and progress.time_crystals < cost: _text(body,"Brakuje %d kryształów" % (cost-progress.time_crystals),Color("ef9389")) elif not message.is_empty(): _text(body,message,CYAN) static func _addon_button(screen: Control, parent: Node, item: ItemInstance, index: int) -> void: var modifier: ItemStatModifier = item.additional_modifiers[index] var affix := SERVICE.affix_for(item,index) var maximum := SERVICE.maximum(item,index) var ready := SERVICE.can_maximize(item,index) var active: bool = screen._engineer_addon == index var button := Button.new() button.name = "EngineerAddon_%d" % index button.custom_minimum_size.y = 46 button.size_flags_horizontal = Control.SIZE_EXPAND_FILL button.tooltip_text = modifier.get_display_text()+"\n"+("Kliknij, aby wybrać addon." if ready else "Addon ma już maksimum." if affix != null else "Ten addon nie ma zakresu maksymalizacji.") _skin(button,active) parent.add_child(button) var margin := MarginContainer.new() margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) for side in ["left","right"]: margin.add_theme_constant_override("margin_"+side,10) for side in ["top","bottom"]: margin.add_theme_constant_override("margin_"+side,7) margin.mouse_filter = Control.MOUSE_FILTER_IGNORE button.add_child(margin) var row := HBoxContainer.new() row.add_theme_constant_override("separation",10) row.mouse_filter = Control.MOUSE_FILTER_IGNORE margin.add_child(row) var icon := MENU.icon(row,_stat_icon(modifier.stat_key),27) icon.mouse_filter = Control.MOUSE_FILTER_IGNORE icon.modulate = CYAN if active else Color("b4c8d3") var detail := VBoxContainer.new() detail.size_flags_horizontal = Control.SIZE_EXPAND_FILL detail.add_theme_constant_override("separation",4) detail.mouse_filter = Control.MOUSE_FILTER_IGNORE row.add_child(detail) var top := HBoxContainer.new() top.add_theme_constant_override("separation",6) top.mouse_filter = Control.MOUSE_FILTER_IGNORE detail.add_child(top) var title := _text(top,affix.display_name if affix != null else ItemInstance.stat_name(modifier.stat_key),CYAN if active else MENU.TEXT) title.size_flags_horizontal = Control.SIZE_EXPAND_FILL title.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS title.mouse_filter = Control.MOUSE_FILTER_IGNORE var multiplier := ItemInstance.ENERGY_REGENERATION_BONUS_MULTIPLIER if modifier.stat_key == "energy_regeneration" else 1.0 var unit := affix.unit if affix != null else "" var numbers := "%s → %s %s" % [_number(modifier.value*multiplier),_number(maximum*multiplier),unit] if ready else "✓ MAX" if affix != null else "—" var value := _text(top,numbers,MENU.GOLD if ready else Color("86cda7")) value.mouse_filter = Control.MOUSE_FILTER_IGNORE var bar := ProgressBar.new() bar.custom_minimum_size.y = 4 bar.show_percentage = false bar.value = clampf(modifier.value/maxf(0.00001,maximum)*100,0,100) if affix != null else 0.0 bar.mouse_filter = Control.MOUSE_FILTER_IGNORE bar.add_theme_stylebox_override("background",_box(Color("09121a"),Color.TRANSPARENT,0)) bar.add_theme_stylebox_override("fill",_box(CYAN if active else Color("547d85"),Color.TRANSPARENT,0)) detail.add_child(bar) button.pressed.connect(func(): screen._engineer_addon = index screen._engineer_notice = "" refresh(screen)) static func _text(parent: Node, value: String, color: Color) -> Label: var label := MENU.label(parent,value,13,color) label.autowrap_mode = TextServer.AUTOWRAP_OFF return label static func _number(value: float) -> String: return str(roundi(value)) if is_equal_approx(value,roundf(value)) else "%.2f" % value static func _box(background: Color, border: Color, inset: int) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = background box.border_color = border box.set_border_width_all(1) box.set_corner_radius_all(5) box.set_content_margin_all(inset) return box static func _skin(button: Button, selected: bool) -> void: button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND for state in ["normal","hover","pressed","focus"]: var background := Color("1c3648") if selected else Color("15232f") if state in ["hover","pressed"]: background = background.lightened(0.12) button.add_theme_stylebox_override(state,_box(background,CYAN if selected or state == "focus" else Color("3d5262"),5)) static func _stat_icon(key: String) -> Texture2D: var path := "computer.png" if "shield" in key or "absorption" in key: path = "shield.png" elif "damage" in key or "critical" in key: path = "plasma.png" elif "speed" in key or "cooldown" in key: path = "engine.png" elif "hull" in key: path = "armor.png" elif "energy" in key: path = "reactor.png" elif "credits" in key: path = "resources/credits.svg" return load("res://assets/icons/"+path) as Texture2D