extends Control signal closed const UI := preload("res://scripts/planets/expedition_ui.gd") const GEAR := preload("res://scripts/planets/captain_equipment.gd") const CATALOG := preload("res://scripts/game/ship_catalog.gd") var captain: Resource var inventory: Node var _content: VBoxContainer var _tab := "overview" var _drawer: PanelContainer var _drawer_title: Label var _wallet: Label var _hint: Label var _owned_open := false @onready var deck: Control = $Deck @onready var _audio: Node = $FreighterAudio func _ready() -> void: if captain == null: captain = GameState.captain deck.sync_rooms(captain.expedition.freighter_rooms) deck.section_selected.connect(_select_section) deck.section_hovered.connect(_hover_section) var chassis := CATALOG.for_path(GameState.selected_ship_path) deck.ship_name = str(chassis.name) var sprite: Sprite2D if is_instance_valid(inventory): var ship: Node = inventory.get_parent() sprite = ship.get_node_or_null("Sprite2D") as Sprite2D deck.ship_texture = sprite.texture if sprite != null else load(CATALOG.texture_path(chassis)) if sprite != null: deck.ship_name = str(CATALOG.for_path(sprite.texture.resource_path).name) _build_header() _build_footer() _build_drawer() resized.connect(_layout) captain.changed.connect(_sync_header) _sync_header() _layout() _refresh() func _build_header() -> void: var header := HBoxContainer.new() header.name = "Header" header.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE) header.offset_left = 26 header.offset_right = -26 header.offset_top = 20 header.add_theme_constant_override("separation",22) add_child(header) var heading := VBoxContainer.new() heading.size_flags_horizontal = Control.SIZE_EXPAND_FILL header.add_child(heading) UI.label(heading,"FREGATA / POKŁAD OPERACYJNY",24,Color("e3e9e8")) UI.label(heading,"01 / POKŁAD GŁÓWNY · POŁĄCZENIE ZABEZPIECZONE",11,Color("77969f")) _wallet = UI.label(header,"",13,Color("c9aa7c")) _wallet.vertical_alignment = VERTICAL_ALIGNMENT_CENTER UI.button(header,"MAGAZYN",func(): _select_section("storage")) UI.button(header,"ODLOT [L]",func(): closed.emit()) func _build_footer() -> void: var footer := HBoxContainer.new() footer.name = "Footer" footer.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_WIDE) footer.offset_left = 26 footer.offset_right = -26 footer.offset_top = -43 footer.offset_bottom = -12 add_child(footer) _hint = UI.label(footer,"Wybierz pomieszczenie na pokładzie. · Warsztat i hangar są dostępne.",12,Color("91a9ae")) _hint.size_flags_horizontal = Control.SIZE_EXPAND_FILL footer.add_theme_constant_override("separation",18) UI.label(footer,"● OPERACYJNE",11,Color("79d9d8")) UI.label(footer,"● W REMONCIE",11,Color("bca078")) func _build_drawer() -> void: _drawer = UI.panel(self) _drawer.name = "SectionPanel" var stack := VBoxContainer.new() stack.add_theme_constant_override("separation",14) _drawer.add_child(stack) var row := HBoxContainer.new() stack.add_child(row) _drawer_title = UI.label(row,"",20,UI.CYAN) _drawer_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL UI.button(row,"×",_close_section) var scroll := ScrollContainer.new() scroll.name = "SectionScroll" scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL stack.add_child(scroll) _content = VBoxContainer.new() _content.size_flags_horizontal = Control.SIZE_EXPAND_FILL _content.add_theme_constant_override("separation",14) scroll.add_child(_content) func _layout() -> void: if not is_instance_valid(_drawer): return var viewport_size := size var width := minf(760 if _tab == "storage" or (_tab == "expansion_3" and deck.is_built(_tab)) else 460,viewport_size.x-40) _drawer.position = Vector2(20 if _tab == "hangar" else viewport_size.x-width-20,94) _drawer.size = Vector2(width,maxf(240,viewport_size.y-154)) func _sync_header() -> void: if is_instance_valid(_wallet): _wallet.text = "%d KREDYTÓW\n%d CZĘŚCI" % [captain.credits,floori(captain.parts)] func _hover_section(section: String) -> void: var data: Dictionary = deck.room(section) _hint.text = "%s / %s" % [data.title,data.status] if not data.is_empty() else "Wybierz pomieszczenie na pokładzie. · Warsztat i hangar są dostępne." func _select_section(section: String) -> void: var room: Dictionary = deck.room("expansion_3" if section == "storage" else section) var pan: float = 0.0 if room.is_empty() else ((room.rect as Rect2).get_center().x / deck.DESIGN_SIZE.x - 0.5)*1.4 _audio.enter_section(section,pan) _tab = section deck.selected = section _refresh() func _close_section() -> void: _audio.close_section() _tab = "overview" deck.selected = "" _drawer.hide() deck.queue_redraw() func _input(event: InputEvent) -> void: if not event is InputEventKey or not event.pressed or event.echo: return if event.keycode not in [KEY_L,KEY_ESCAPE]: return # Docking removes this scene immediately, so consume input before emitting closed. get_viewport().set_input_as_handled() if event.keycode == KEY_L: closed.emit() elif _drawer.visible: _close_section() else: closed.emit() func _label(text: String, size_value := 14, color := Color("c5d3d7")) -> Label: var node := UI.label(_content,text,size_value,color) node.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART return node func _refresh() -> void: _audio.set_maintenance_needed(captain.expedition.lander_hp < 30) for child in _content.get_children(): _content.remove_child(child) child.queue_free() _sync_header() if _tab == "overview": _drawer.hide() return _drawer.show() _layout() _content.get_parent().set("scroll_vertical",0) if _tab == "workshop": _drawer_title.text = "WARSZTAT" _build_workshop() elif _tab == "hangar": _drawer_title.text = "HANGAR / OBSŁUGA" _build_hangar() elif _tab == "command": _drawer_title.text = "CENTRUM DOWODZENIA" _build_command() elif _tab == "storage" or (_tab == "expansion_3" and deck.is_built(_tab)): _drawer_title.text = "MAGAZYN FRACHTOWCA" _build_storage() else: _build_room() func _build_room() -> void: var section: Dictionary = deck.room(_tab) _drawer_title.text = str(section.get("title","SEKCJA ROZWOJOWA")) if not deck.is_buildable(_tab): _label("Wybierz pomieszczenie na pokładzie.") return var plan: Dictionary = deck.plan(_tab) _label("ZBUDOWANO" if deck.is_built(_tab) else "W REMONCIE",21,UI.CYAN if deck.is_built(_tab) else Color("e0b16a")) _label(str(plan.description)) if str(plan.kind) == "alien_research": _label("Próbki oczekujące na analizę: %d" % captain.expedition.analysis_samples,16,UI.CYAN) _label("Próbki z jaskiń są przechowywane po powrocie. Analiza będzie dostępna później.",13) if deck.is_built(_tab): _label("Pomieszczenie jest wyposażone. Jego mechanika zostanie dodana w kolejnym etapie.",14,Color("849da5")) else: _label("Budowa jest teraz bezpłatna. Wyposażenie pojawi się od razu na pokładzie.",14,Color("849da5")) UI.button(_content,"ZBUDUJ · BEZ KOSZTÓW",_construct_room.bind(_tab)) UI.button(_content,"Wróć na pokład",_close_section) func _construct_room(id: String) -> void: if not deck.is_buildable(id) or deck.is_built(id): return captain.expedition.freighter_rooms[id] = str(deck.plan(id).kind) captain.expedition.emit_changed() deck.sync_rooms(captain.expedition.freighter_rooms) _audio.service_completed() _hover_section(id) _refresh() func _build_hangar() -> void: var progress: Resource = captain.expedition _build_slot_upgrades() _label("ZADOKOWANO / "+deck.ship_name.to_upper(),15,Color("8bd9d0")) _label("Twój statek stoi na stanowisku serwisowym. Obsługa lądownika i odbiór odzyskanych modułów są dostępne poniżej.") _label("Lądownik: %d / 100 HP" % ceili(progress.lander_hp),18) var bar := ProgressBar.new() bar.value = progress.lander_hp bar.custom_minimum_size.y = 17 _content.add_child(bar) var cost: int = progress.repair_cost() _label("Części: %d · potrzebne do naprawy: %d" % [captain.parts,cost],13) var repair := UI.button(_content,"NAPRAW DO 100%% · %d części" % cost,func(): if progress.repair(captain): _audio.service_completed(0.65) _refresh()) repair.disabled = cost == 0 or captain.parts < cost _label("1 część przywraca 1 punkt procentowy HP lądownika. Do następnego lądowania wymagany jest pełny kadłub.",13,Color("819ca5")) _label("MODUŁY DO ODEBRANIA: %d" % progress.delivered_modules.size(),16,UI.CYAN) var delivery := UI.button(_content,"ODBIERZ DO ŁADOWNI STATKU",func(): if is_instance_valid(inventory): progress.deliver_modules(inventory) _refresh()) delivery.disabled = progress.delivered_modules.is_empty() or not is_instance_valid(inventory) _label("Moduły bez miejsca w ładowni pozostają bezpiecznie na pokładzie.",13,Color("819ca5")) func _build_slot_upgrades() -> void: _label("ROZBUDOWA SLOTÓW STATKU",17,UI.CYAN) if not is_instance_valid(inventory): _label("Zadokuj swoim statkiem, aby rozbudować sloty.") return _label("Żelazo: %d · koszt kolejnego slotu: 10" % int(captain.minerals.get("iron",0)),14) _label("Ładownia: %d / %d miejsc · rzędy %d / %d" % [inventory.get_cargo_capacity(),inventory.CARGO_SIZE,inventory.unlocked_cargo_rows,inventory.CARGO_ROW_COUNT],14) if inventory.unlocked_cargo_rows < inventory.CARGO_ROW_COUNT: var cargo_unlock := UI.button(_content,"ODBLOKUJ RZĄD ŁADOWNI · 10 żelaza",func(): inventory.unlock_cargo_row(captain) _refresh()) cargo_unlock.disabled = not inventory.can_unlock_cargo_row(captain) else: _label("Wszystkie rzędy ładowni odblokowane.",13,Color("849da5")) for kind in ["weapon","extension"]: var count: int = inventory.unlocked_weapon_slots if kind == "weapon" else inventory.unlocked_extension_slots var limit: int = inventory.WEAPON_SLOT_COUNT if kind == "weapon" else inventory.EXTENSION_SLOT_COUNT var title := "BRONI" if kind == "weapon" else "ROZSZERZENIA" _label("Sloty %s: %d / %d" % [title.to_lower(),count,limit],14) if count < limit: var unlock := UI.button(_content,"ODBLOKUJ SLOT %s · 10 żelaza" % title,_unlock_ship_slot.bind(kind)) unlock.disabled = not inventory.can_unlock_slot(kind,captain) else: _label("Wszystkie sloty odblokowane.",13,Color("849da5")) func _unlock_ship_slot(kind: String) -> void: if is_instance_valid(inventory): inventory.unlock_slot(kind,captain) _refresh() func _build_storage() -> void: if not deck.is_built("expansion_3"): _label("SKŁAD MODUŁÓW W REMONCIE",19,Color("e0b16a")) _label("Zbuduj skład modułów, aby przechowywać wyposażenie na frachtowcu. Budowa jest teraz bezpłatna.") UI.button(_content,"ZBUDUJ SKŁAD · BEZ KOSZTÓW",_construct_room.bind("expansion_3")) return if not is_instance_valid(inventory): _label("Zadokuj swoim statkiem, aby przenosić wyposażenie.") return _label("Przechowuj moduły, broń i rozszerzenia. Wybierz przedmiot, aby przenieść go między ładownią a magazynem. Zamontowane wyposażenie najpierw zdejmij do ładowni.",13) var columns := HBoxContainer.new() columns.add_theme_constant_override("separation",18) _content.add_child(columns) var cargo_column := VBoxContainer.new() cargo_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL cargo_column.size_flags_stretch_ratio = 1 columns.add_child(cargo_column) var storage_column := VBoxContainer.new() storage_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL storage_column.size_flags_stretch_ratio = 1 columns.add_child(storage_column) UI.label(cargo_column,"STATEK · %d / %d" % [inventory.get_cargo_used(),inventory.get_cargo_capacity()],16,UI.CYAN) var storage: Array = captain.expedition.freighter_storage UI.label(storage_column,"FRACHTOWIEC · %d" % storage.size(),16,UI.CYAN) var used := 0 for index in inventory.get_cargo_capacity(): var item: ItemInstance = inventory.get_item("cargo",index) if item == null: continue used += 1 _storage_card(cargo_column,item,"ODŁÓŻ →",_transfer_storage.bind(false,index,item)) if used == 0: UI.label(cargo_column,"Ładownia jest pusta.",13,Color("849da5")) for index in storage.size(): var item: ItemInstance = storage[index] var button := _storage_card(storage_column,item,"← ODBIERZ",_transfer_storage.bind(true,index,item)) button.disabled = inventory.get_cargo_used() >= inventory.get_cargo_capacity() if storage.is_empty(): UI.label(storage_column,"Magazyn jest pusty.",13,Color("849da5")) if inventory.get_cargo_used() >= inventory.get_cargo_capacity(): _label("Ładownia statku jest pełna. Odłóż przedmiot lub odblokuj kolejny rząd w hangarze.",13,Color("e0b16a")) func _storage_card(parent: Control, item: ItemInstance, action: String, callback: Callable) -> Button: var card := UI.panel(parent) var stack := VBoxContainer.new() card.add_child(stack) var row := HBoxContainer.new() stack.add_child(row) var icon := TextureRect.new() icon.custom_minimum_size = Vector2(38,38) icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED icon.texture = item.definition.icon row.add_child(icon) var title := UI.label(row,"%s · +%d" % [item.definition.display_name,item.item_level],13,item.get_type_color()) title.size_flags_horizontal = Control.SIZE_EXPAND_FILL title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART card.tooltip_text = item.get_tooltip_text() return UI.button(stack,action,callback) func _transfer_storage(retrieve: bool, index: int, expected: ItemInstance) -> void: if not is_instance_valid(inventory): return if retrieve: inventory.retrieve_from_freighter(index,expected,captain) else: inventory.store_on_freighter(index,expected,captain) _refresh() func _build_workshop() -> void: _label("FABRYKATOR / WYPOSAŻENIE KAPITANA",13,Color("8bd9d0")) _label("Każdy poziom wyposażenia usuwa 2 pp odpowiadającego mu zagrożenia. L50 zapewnia pełną ochronę tej składowej.",13) for kind in GEAR.KINDS: var card := UI.panel(_content) var row := HBoxContainer.new() row.add_theme_constant_override("separation",12) card.add_child(row) var icon := TextureRect.new() icon.texture = GEAR.icon(kind) icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED icon.custom_minimum_size = Vector2(46,46) row.add_child(icon) var column := VBoxContainer.new() column.size_flags_horizontal = Control.SIZE_EXPAND_FILL row.add_child(column) var title := UI.label(column,"%s · L%d" % [GEAR.NAMES[kind],GEAR.next_level(captain,kind)],15,Color("e4e8e5")) title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART var recipe := UI.label(column,GEAR.recipe_text(captain,kind),12,Color("a1b3b8")) recipe.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART var build := UI.button(column,"WYTWÓRZ I ZAŁÓŻ",_craft.bind(kind)) build.add_theme_font_size_override("font_size",13) build.disabled = not GEAR.can_craft(captain,kind) UI.button(_content,"POSIADANE WYPOSAŻENIE "+("−" if _owned_open else "+"),func(): _owned_open = not _owned_open _refresh()) if _owned_open: for gear in captain.gear_items: var equipped: bool = captain.gear_slots.get(gear.kind,-1) == gear.uid var label_text := "%s · L%d%s" % [GEAR.NAMES[gear.kind],gear.level," [ZAŁOŻONE]" if equipped else ""] var button := UI.button(_content,label_text,_equip.bind(gear.kind,gear.uid)) button.add_theme_font_size_override("font_size",13) button.disabled = equipped _label("ZASOBY: "+UI.material_text(captain.minerals),12,Color("819ca5")) func _build_command() -> void: var progress: Resource = captain.expedition _label("MOSTEK / SYSTEMY NAWIGACYJNE",13,Color("8bd9d0")) _label("Łączność z twoim statkiem jest stabilna. Fregata pozostaje gotowa do przejścia do kolejnego układu.") _label("Zeskanowane planety: %d" % progress.scans.size(),16) _label("Próbki do analizy w dziale badań obcych: %d" % progress.analysis_samples,15,UI.CYAN) _label("ODZYSKANE ARCHIWA",15,UI.CYAN) if progress.research_records.is_empty(): _label("Brak odzyskanych zapisów.",13,Color("819ca5")) for key in progress.research_records: _label("%s ×%d" % [key,progress.research_records[key]],13) _label("Rozbudowa centrum dowodzenia oraz pozostałych sekcji będzie dostępna później.",13,Color("bc9e72")) func _craft(kind: String) -> void: var gear: Dictionary = GEAR.craft(captain,kind) if not gear.is_empty(): GEAR.equip(captain,kind,int(gear.uid)) _audio.service_completed(-0.48) _refresh() func _equip(kind: String, uid: int) -> void: GEAR.equip(captain,kind,uid) _refresh()