328 lines
16 KiB
GDScript
328 lines
16 KiB
GDScript
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
|