Randoloot/scripts/planets/time_engineer_panel.gd
2026-10-31 16:08:17 +01:00

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