1122 lines
44 KiB
GDScript
1122 lines
44 KiB
GDScript
extends Node2D
|
|
|
|
const BUILDING_DEFINITIONS = preload("res://BuildingDefinitions.gd")
|
|
const VESSEL_DEFINITIONS = preload("res://VesselDefinitions.gd")
|
|
|
|
# Space-side building UI. Owns:
|
|
# - the build menu (place a mothership, ship crafter or command centre)
|
|
# - the shared ACTION button, which adapts to whatever the fleet is
|
|
# standing on (craft ships, configure formation, or land). One action
|
|
# fires directly; multiple actions unfold horizontally.
|
|
# - rebuilding a wiped-out fleet at the mothership
|
|
#
|
|
# Buildings themselves live in GameState (they must outlive the trip to a
|
|
# planet's surface); this only drives the UI and spends resources.
|
|
|
|
@export var prompt_debounce_time: float = 0.15
|
|
|
|
@onready var fleet: Node2D = get_node("../Fleet")
|
|
@onready var building_field: Node2D = get_node("../BuildingField")
|
|
@onready var world_map: Control = get_node("../UI/WorldMap")
|
|
@onready var build_button: IconButton = get_node("../UI/BuildButton")
|
|
@onready var action_button: IconButton = get_node("../UI/BuildActionButton")
|
|
@onready var building_action_button: IconButton = get_node("../UI/BuildingActionButton")
|
|
@onready var landing_button: IconButton = get_node("../UI/EnterAtmosphereButton")
|
|
@onready var landing_controller: Node2D = get_node("../LandingController")
|
|
|
|
@onready var build_menu: Control = get_node("../UI/BuildMenu")
|
|
@onready var build_close_button: IconButton = get_node("../UI/BuildMenu/CloseButton")
|
|
@onready var build_status: Label = get_node("../UI/BuildMenu/StatusLabel")
|
|
@onready var lab_menu: Control = get_node("../UI/LabMenu")
|
|
@onready var lab_vessel_buttons: Array = [
|
|
get_node("../UI/LabMenu/VesselA"), get_node("../UI/LabMenu/VesselB"),
|
|
get_node("../UI/LabMenu/VesselC"), get_node("../UI/LabMenu/VesselD"),
|
|
]
|
|
@onready var lab_vessel_label: Label = get_node("../UI/LabMenu/VesselLabel")
|
|
@onready var lab_vessel_labels: Array = [
|
|
get_node("../UI/LabMenu/VesselALabel"), get_node("../UI/LabMenu/VesselBLabel"),
|
|
get_node("../UI/LabMenu/VesselCLabel"), get_node("../UI/LabMenu/VesselDLabel"),
|
|
]
|
|
@onready var lab_detail: Label = get_node("../UI/LabMenu/DetailLabel")
|
|
@onready var lab_status: Label = get_node("../UI/LabMenu/StatusLabel")
|
|
@onready var lab_buy_button: IconButton = get_node("../UI/LabMenu/BuyButton")
|
|
@onready var lab_close_button: IconButton = get_node("../UI/LabMenu/CloseButton")
|
|
@onready var lab_upgrade_buttons: Array = [
|
|
get_node("../UI/LabMenu/Upgrade0"), get_node("../UI/LabMenu/Upgrade1"),
|
|
get_node("../UI/LabMenu/Upgrade2"), get_node("../UI/LabMenu/Upgrade3"),
|
|
]
|
|
@onready var lab_upgrade_labels: Array = [
|
|
get_node("../UI/LabMenu/Upgrade0Label"), get_node("../UI/LabMenu/Upgrade1Label"),
|
|
get_node("../UI/LabMenu/Upgrade2Label"), get_node("../UI/LabMenu/Upgrade3Label"),
|
|
]
|
|
@onready var craft_shield_button: IconButton = get_node("../UI/CraftMenu/ShieldButton")
|
|
@onready var queue_slots: Array = [
|
|
get_node("../UI/CraftMenu/QueueSlot0"), get_node("../UI/CraftMenu/QueueSlot1"),
|
|
get_node("../UI/CraftMenu/QueueSlot2"), get_node("../UI/CraftMenu/QueueSlot3"),
|
|
]
|
|
@onready var queue_detail: Label = get_node("../UI/CraftMenu/QueueDetail")
|
|
@onready var hangar_button: IconButton = get_node("../UI/HangarButton")
|
|
@onready var hangar_menu: Control = get_node("../UI/HangarMenu")
|
|
@onready var hangar_status: Label = get_node("../UI/HangarMenu/StatusLabel")
|
|
@onready var hangar_close_button: IconButton = get_node("../UI/HangarMenu/CloseButton")
|
|
@onready var hangar_available_slots: Array = [
|
|
get_node("../UI/HangarMenu/AvailableInterceptor"),
|
|
get_node("../UI/HangarMenu/AvailableShield"),
|
|
get_node("../UI/HangarMenu/AvailableGasBomber"),
|
|
]
|
|
@onready var hangar_available_labels: Array = [
|
|
get_node("../UI/HangarMenu/AvailableInterceptorLabel"),
|
|
get_node("../UI/HangarMenu/AvailableShieldLabel"),
|
|
get_node("../UI/HangarMenu/AvailableGasBomberLabel"),
|
|
]
|
|
@onready var hangar_fleet_slots: Array = [
|
|
get_node("../UI/HangarMenu/FleetInterceptor"),
|
|
get_node("../UI/HangarMenu/FleetShield"),
|
|
get_node("../UI/HangarMenu/FleetGasBomber"),
|
|
]
|
|
@onready var hangar_fleet_labels: Array = [
|
|
get_node("../UI/HangarMenu/FleetInterceptorLabel"),
|
|
get_node("../UI/HangarMenu/FleetShieldLabel"),
|
|
get_node("../UI/HangarMenu/FleetGasBomberLabel"),
|
|
]
|
|
var build_menu_entries: Array = []
|
|
|
|
# Which vessel the lab screen is showing, and which visible upgrade card is
|
|
# selected. Each vessel gets its own small set of direct cards; the detail and
|
|
# Buy controls below them only describe the one the player tapped.
|
|
var lab_vessel: String = GameState.VESSEL_INTERCEPTOR
|
|
var lab_choice: int = 0
|
|
var lab_building_id: int = 0
|
|
@onready var planet_field: Node2D = get_node("../PlanetField")
|
|
@onready var info_popup: Control = get_node("../UI/BuildMenu/InfoPopup")
|
|
@onready var info_title: Label = get_node("../UI/BuildMenu/InfoPopup/Panel/TitleLabel")
|
|
@onready var info_message: Label = get_node("../UI/BuildMenu/InfoPopup/Panel/MessageLabel")
|
|
@onready var info_cost: NeonNumber = get_node("../UI/BuildMenu/InfoPopup/Panel/CostNumber")
|
|
@onready var info_build_button: IconButton = get_node("../UI/BuildMenu/InfoPopup/Panel/BuildButton")
|
|
@onready var info_close_button: IconButton = get_node("../UI/BuildMenu/InfoPopup/Panel/CloseButton")
|
|
|
|
@onready var craft_menu: Control = get_node("../UI/CraftMenu")
|
|
@onready var craft_interceptor_button: IconButton = get_node("../UI/CraftMenu/InterceptorButton")
|
|
@onready var craft_close_button: IconButton = get_node("../UI/CraftMenu/CloseButton")
|
|
@onready var craft_status: Label = get_node("../UI/CraftMenu/StatusLabel")
|
|
@onready var craft_interceptor_label: Label = get_node("../UI/CraftMenu/InterceptorLabel")
|
|
@onready var craft_shield_label: Label = get_node("../UI/CraftMenu/ShieldLabel")
|
|
@onready var craft_gas_bomber_button: IconButton = get_node("../UI/CraftMenu/GasBomberButton")
|
|
@onready var craft_gas_bomber_label: Label = get_node("../UI/CraftMenu/GasBomberLabel")
|
|
@onready var craft_vessel_buttons: Array = [
|
|
craft_interceptor_button, craft_shield_button, craft_gas_bomber_button,
|
|
]
|
|
@onready var craft_vessel_labels: Array = [
|
|
craft_interceptor_label, craft_shield_label, craft_gas_bomber_label,
|
|
]
|
|
|
|
# --- the transfer panel (#24) ---------------------------------------------
|
|
# Ore used to be moved from inside whichever panel happened to be open: "load
|
|
# ore" lived in the research screen, "store ore here" in the ship crafter.
|
|
# Both are now one top-level shortcut beside Build at resource-bearing
|
|
# locations and work in both directions, so moving cargo is never buried
|
|
# under an unrelated action foldout.
|
|
@onready var transfer_button: IconButton = get_node("../UI/TransferButton")
|
|
@onready var transfer_menu: Control = get_node("../UI/TransferMenu")
|
|
@onready var transfer_status: Label = get_node("../UI/TransferMenu/StatusLabel")
|
|
@onready var transfer_close_button: IconButton = get_node("../UI/TransferMenu/CloseButton")
|
|
@onready var transfer_list: ResourceTransferList = get_node("../UI/TransferMenu/ResourceList")
|
|
|
|
# Every building menu carries the same header (#24). Held as a list because
|
|
# the only thing any of them needs is to be told which building it is
|
|
# reporting on when its panel opens.
|
|
@onready var resource_bars: Array = [
|
|
get_node("../UI/CraftMenu/Bar"), get_node("../UI/SkillMenu/Bar"),
|
|
get_node("../UI/LabMenu/Bar"),
|
|
]
|
|
|
|
@onready var skill_menu: Control = get_node("../UI/SkillMenu")
|
|
@onready var skill_tree: Control = get_node("../UI/SkillMenu/Tree")
|
|
@onready var skill_detail: Label = get_node("../UI/SkillMenu/DetailLabel")
|
|
@onready var skill_cost: NeonNumber = get_node("../UI/SkillMenu/CostNumber")
|
|
@onready var skill_status: Label = get_node("../UI/SkillMenu/StatusLabel")
|
|
@onready var skill_unlock_button: IconButton = get_node("../UI/SkillMenu/UnlockButton")
|
|
@onready var skill_close_button: IconButton = get_node("../UI/SkillMenu/CloseButton")
|
|
|
|
@onready var formation_menu: Control = get_node("../UI/FormationMenu")
|
|
@onready var formation_editor: Control = get_node("../UI/FormationMenu/Editor")
|
|
@onready var formation_close_button: IconButton = get_node("../UI/FormationMenu/CloseButton")
|
|
@onready var formation_reset_button: IconButton = get_node("../UI/FormationMenu/ResetButton")
|
|
|
|
# Which structure the popup is currently offering to build, or "" when it is
|
|
# only explaining something that cannot be built right now.
|
|
var pending_build_type: String = ""
|
|
# Which crafter the craft menu is showing (see _on_action_pressed).
|
|
var craft_building_id: int = 0
|
|
# The craft panel refreshes every frame while it is open (so a delivery
|
|
# arriving visibly works the queue down). That overwrote any message the
|
|
# moment after it was set, which made a working button look dead - the store
|
|
# button in particular. Messages therefore have to outlive a refresh.
|
|
var craft_message: String = ""
|
|
var craft_message_until: int = 0
|
|
|
|
# Which building the transfer panel is moving ore in and out of. Remembered
|
|
# for the same reason craft_building_id is: _process() clears
|
|
# current_building while any menu is open, so by the next frame the panel
|
|
# would not know which building it belongs to.
|
|
var transfer_building_id: int = 0
|
|
var transfer_extractor_index: int = -1
|
|
|
|
var current_building: Node2D = null
|
|
var pending_building: Node2D = null
|
|
var current_extractor_index: int = -1
|
|
var pending_extractor_index: int = -1
|
|
var current_landing_planet: Node2D = null
|
|
var pending_time: float = 0.0
|
|
var context_actions_expanded: bool = false
|
|
|
|
|
|
func _ready() -> void:
|
|
build_button.pressed.connect(_on_build_pressed)
|
|
build_close_button.pressed.connect(_close_build_menu)
|
|
_configure_build_menu_entries()
|
|
lab_close_button.pressed.connect(func(): lab_menu.visible = false)
|
|
lab_buy_button.pressed.connect(_on_lab_buy)
|
|
for i in range(lab_vessel_buttons.size()):
|
|
lab_vessel_buttons[i].pressed.connect(_on_lab_vessel.bind(i))
|
|
for i in range(lab_upgrade_buttons.size()):
|
|
lab_upgrade_buttons[i].pressed.connect(_on_lab_upgrade.bind(i))
|
|
info_close_button.pressed.connect(_close_info)
|
|
info_build_button.pressed.connect(_confirm_build)
|
|
|
|
action_button.pressed.connect(_on_context_action_pressed)
|
|
building_action_button.pressed.connect(_on_action_pressed)
|
|
|
|
for i in range(mini(craft_vessel_buttons.size(), VESSEL_DEFINITIONS.fleet().size())):
|
|
var definition: Resource = VESSEL_DEFINITIONS.fleet()[i]
|
|
craft_vessel_buttons[i].pressed.connect(_on_craft_vessel.bind(definition.definition_id))
|
|
craft_close_button.pressed.connect(func(): craft_menu.visible = false)
|
|
|
|
transfer_button.pressed.connect(_on_transfer_pressed)
|
|
hangar_button.pressed.connect(_on_hangar_pressed)
|
|
hangar_close_button.pressed.connect(func(): hangar_menu.visible = false)
|
|
for i in range(hangar_available_slots.size()):
|
|
hangar_available_slots[i].pressed.connect(_on_hangar_crew_type.bind(i))
|
|
hangar_fleet_slots[i].pressed.connect(_on_hangar_store_type.bind(i))
|
|
for i in range(queue_slots.size()):
|
|
queue_slots[i].pressed.connect(_on_queue_slot.bind(i))
|
|
transfer_close_button.pressed.connect(func(): transfer_menu.visible = false)
|
|
transfer_list.transfer_requested.connect(_on_resource_transfer)
|
|
|
|
formation_close_button.pressed.connect(func(): formation_menu.visible = false)
|
|
formation_reset_button.pressed.connect(_on_reset_formation)
|
|
formation_editor.formation_changed.connect(_on_formation_changed)
|
|
|
|
skill_close_button.pressed.connect(func(): skill_menu.visible = false)
|
|
skill_unlock_button.pressed.connect(_on_unlock_pressed)
|
|
skill_tree.node_picked.connect(_on_skill_node_picked)
|
|
|
|
build_menu.visible = false
|
|
info_popup.visible = false
|
|
craft_menu.visible = false
|
|
formation_menu.visible = false
|
|
skill_menu.visible = false
|
|
lab_menu.visible = false
|
|
transfer_menu.visible = false
|
|
action_button.visible = false
|
|
building_action_button.visible = false
|
|
landing_button.visible = false
|
|
transfer_button.visible = false
|
|
hangar_button.visible = false
|
|
hangar_menu.visible = false
|
|
_layout_context_buttons()
|
|
|
|
|
|
func _configure_build_menu_entries() -> void:
|
|
build_menu_entries.clear()
|
|
for definition in BUILDING_DEFINITIONS.all():
|
|
if definition.menu_button_name == "":
|
|
continue
|
|
var button: IconButton = get_node("../UI/BuildMenu/%s" % definition.menu_button_name)
|
|
var label: Label = get_node("../UI/BuildMenu/%s" % definition.menu_label_name)
|
|
var cost_number: NeonNumber = get_node("../UI/BuildMenu/%s" % definition.menu_cost_name)
|
|
button.icon = definition.menu_icon
|
|
button.pressed.connect(_show_building_info.bind(String(definition.definition_id)))
|
|
label.text = definition.display_name
|
|
cost_number.amount = definition.cost
|
|
build_menu_entries.append([definition.definition_id, button, label, cost_number, definition])
|
|
|
|
|
|
# True while a modal panel owns the shared context controls. A plain building
|
|
# prompt is not modal: landing can coexist with its actions in the same foldout.
|
|
func is_showing_building_prompt() -> bool:
|
|
return build_menu.visible or craft_menu.visible \
|
|
or formation_menu.visible or skill_menu.visible or transfer_menu.visible \
|
|
or hangar_menu.visible
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if context_actions_expanded and (event is InputEventScreenTouch and event.pressed \
|
|
or event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT):
|
|
# Buttons consume their own touch; reaching this handler means the tap
|
|
# landed somewhere else, which dismisses an expanded action list.
|
|
_collapse_context_actions()
|
|
|
|
# The formation editor and the skill tree are driven from here rather than
|
|
# via _gui_input, for the same reason as the joystick: a Control that
|
|
# consumes touches through the GUI system swallows them before they get
|
|
# here, which is what made dragging a formation slot silently do nothing.
|
|
if skill_menu.visible:
|
|
if event is InputEventScreenTouch:
|
|
skill_tree.handle_touch(event)
|
|
elif event is InputEventScreenDrag:
|
|
skill_tree.handle_drag(event)
|
|
# Handled either way. A touch that lands on the panel but misses a
|
|
# node is still a touch ON THE PANEL - letting it through moved the
|
|
# fleet and pinch-zoomed the world behind the menu, because
|
|
# GameController sits on the root node and sees whatever this leaves
|
|
# unhandled.
|
|
get_viewport().set_input_as_handled()
|
|
return
|
|
if formation_menu.visible:
|
|
if event is InputEventScreenTouch:
|
|
formation_editor.handle_touch(event)
|
|
elif event is InputEventScreenDrag:
|
|
formation_editor.handle_drag(event)
|
|
get_viewport().set_input_as_handled()
|
|
return
|
|
if build_menu.visible or craft_menu.visible or world_map.visible or lab_menu.visible \
|
|
or transfer_menu.visible or hangar_menu.visible:
|
|
# These panels have no raw-input handler of their own, but the world
|
|
# behind them must still not react.
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
_check_fleet_wipe()
|
|
|
|
if build_menu.visible:
|
|
# Keep the greyed state live while the menu is open, so flying onto
|
|
# a planet lights the option up without reopening it.
|
|
_refresh_build_option_states()
|
|
|
|
if craft_menu.visible:
|
|
# Live, so a delivery arriving while the menu is open visibly works
|
|
# the queue down instead of looking stuck.
|
|
_refresh_craft_status()
|
|
|
|
if transfer_menu.visible:
|
|
# Also live: a hauler banking ore while the panel is open should make
|
|
# the "to fleet" side become available without a reopen.
|
|
_refresh_transfer()
|
|
|
|
if world_map.visible or build_menu.visible or craft_menu.visible \
|
|
or formation_menu.visible or skill_menu.visible or lab_menu.visible \
|
|
or transfer_menu.visible or hangar_menu.visible:
|
|
current_building = null
|
|
pending_building = null
|
|
current_extractor_index = -1
|
|
pending_extractor_index = -1
|
|
current_landing_planet = null
|
|
action_button.visible = false
|
|
building_action_button.visible = false
|
|
landing_button.visible = false
|
|
transfer_button.visible = false
|
|
hangar_button.visible = false
|
|
context_actions_expanded = false
|
|
return
|
|
|
|
# Debounced exactly like the enter-atmosphere prompt, so the action
|
|
# button can't flicker while the fleet drifts on the interact boundary.
|
|
var detected: Node2D = building_field.get_building_fleet_is_on()
|
|
var detected_extractor: int = -1
|
|
if detected == null:
|
|
var planet: Node2D = _planet_under_fleet()
|
|
if planet != null:
|
|
detected_extractor = GameState.extractor_index_at(planet.planet_id)
|
|
|
|
if detected != pending_building or detected_extractor != pending_extractor_index:
|
|
pending_building = detected
|
|
pending_extractor_index = detected_extractor
|
|
pending_time = 0.0
|
|
else:
|
|
pending_time += delta
|
|
|
|
var detected_landing_planet: Node2D = landing_controller.current_planet
|
|
if pending_time < prompt_debounce_time or (pending_building == current_building \
|
|
and pending_extractor_index == current_extractor_index \
|
|
and detected_landing_planet == current_landing_planet):
|
|
return
|
|
|
|
current_building = pending_building
|
|
current_extractor_index = pending_extractor_index
|
|
current_landing_planet = detected_landing_planet
|
|
context_actions_expanded = false
|
|
hangar_button.visible = false
|
|
building_action_button.visible = false
|
|
landing_button.visible = false
|
|
transfer_button.visible = _has_transfer_context()
|
|
var actions: Array = _context_action_buttons()
|
|
action_button.visible = not actions.is_empty()
|
|
if current_building != null:
|
|
building_action_button.icon = int(current_building.definition.action_icon)
|
|
building_action_button.queue_redraw()
|
|
if not actions.is_empty():
|
|
action_button.icon = int(actions[0].icon) if actions.size() == 1 else IconButton.Icon.ACTION_MENU
|
|
action_button.queue_redraw()
|
|
_layout_context_buttons()
|
|
|
|
|
|
func _layout_context_buttons() -> void:
|
|
# Build is permanently under the resource counter. The contextual berth
|
|
# directly below it is compact until it has more than one real choice.
|
|
action_button.offset_top = 324
|
|
action_button.offset_bottom = 472
|
|
var buttons: Array = _context_action_buttons()
|
|
if not context_actions_expanded:
|
|
for button in buttons:
|
|
button.visible = false
|
|
return
|
|
# Expand sideways so the controls stay in the same visual band instead of
|
|
# pushing the player down a vertical stack. Three actions fit across the
|
|
# 720px phone layout with the same generous 148px touch targets.
|
|
for i in range(buttons.size()):
|
|
var button: Control = buttons[i]
|
|
button.visible = true
|
|
button.offset_left = 188 + i * 164
|
|
button.offset_right = button.offset_left + 148
|
|
button.offset_top = 324
|
|
button.offset_bottom = 472
|
|
|
|
|
|
func _context_action_buttons() -> Array:
|
|
var buttons: Array = []
|
|
if current_building != null:
|
|
for action in current_building.available_actions():
|
|
var button: IconButton = _button_for_building_action(action)
|
|
if button != null and button not in buttons:
|
|
buttons.append(button)
|
|
if current_landing_planet != null:
|
|
buttons.append(landing_button)
|
|
return buttons
|
|
|
|
|
|
func _has_transfer_context() -> bool:
|
|
if current_extractor_index >= 0:
|
|
return true
|
|
return current_building != null and current_building.available_actions().has("transfer")
|
|
|
|
|
|
func _button_for_building_action(action: String) -> IconButton:
|
|
if action == "transfer":
|
|
return null
|
|
if action == "hangar":
|
|
return hangar_button
|
|
return building_action_button
|
|
|
|
|
|
func _on_context_action_pressed() -> void:
|
|
if current_building == null and current_extractor_index < 0 \
|
|
and current_landing_planet == null:
|
|
return
|
|
var actions: Array = _context_action_buttons()
|
|
if actions.size() == 1:
|
|
if actions[0] == landing_button:
|
|
landing_controller._on_enter_pressed()
|
|
elif actions[0] == hangar_button:
|
|
_on_hangar_pressed()
|
|
else:
|
|
_on_action_pressed()
|
|
return
|
|
context_actions_expanded = not context_actions_expanded
|
|
_layout_context_buttons()
|
|
|
|
|
|
func _collapse_context_actions() -> void:
|
|
if not context_actions_expanded:
|
|
return
|
|
context_actions_expanded = false
|
|
_layout_context_buttons()
|
|
|
|
|
|
func _check_fleet_wipe() -> void:
|
|
if not fleet.ships.is_empty():
|
|
return
|
|
# Whole fleet destroyed: rebuild a single vessel at the mothership, or
|
|
# at the world origin when none has been built yet.
|
|
var spawn_pos: Variant = GameState.get_mothership_position()
|
|
fleet.spawn_ships(1, spawn_pos if spawn_pos != null else Vector2.ZERO)
|
|
|
|
|
|
func _on_action_pressed() -> void:
|
|
if current_building == null:
|
|
return
|
|
# Whichever panel opens, its header reports on the building it was opened
|
|
# from. Set once here rather than in each branch, so a new building menu
|
|
# cannot forget to do it and silently show a blank HERE column.
|
|
for bar in resource_bars:
|
|
bar.building_id = current_building.building_id
|
|
|
|
var action: String = current_building.primary_action()
|
|
if action == "research":
|
|
skill_menu.visible = true
|
|
skill_tree.reset_scroll()
|
|
_refresh_skill_menu()
|
|
elif action == "craft":
|
|
# Remembered explicitly: _process() clears current_building while any
|
|
# menu is open, so by the next frame the panel would no longer know
|
|
# which crafter it belongs to. Losing this line makes every button in
|
|
# the panel bail out at its `== 0` guard while the panel itself still
|
|
# opens - which reads as "the buttons do nothing".
|
|
craft_building_id = current_building.building_id
|
|
craft_menu.visible = true
|
|
_refresh_craft_status()
|
|
elif action == "lab":
|
|
lab_building_id = current_building.building_id
|
|
lab_menu.visible = true
|
|
_refresh_lab()
|
|
elif action == "formation":
|
|
formation_menu.visible = true
|
|
formation_editor.queue_redraw()
|
|
|
|
|
|
func _on_build_pressed() -> void:
|
|
build_menu.visible = true
|
|
info_popup.visible = false
|
|
pending_build_type = ""
|
|
_refresh_build_status()
|
|
|
|
|
|
func _planet_under_fleet() -> Node2D:
|
|
# An extractor mines a specific planet, so one has to be within reach.
|
|
return planet_field.get_planet_fleet_is_on()
|
|
|
|
|
|
# Grid geometry, shared with the layout below. Rows of two, and the menu is
|
|
# rebuilt from whatever is currently researched (#21) rather than being a
|
|
# fixed arrangement with holes punched in it.
|
|
const MENU_ROW_TOPS: Array = [-300.0, -10.0, 280.0]
|
|
const MENU_COL_X: float = 140.0
|
|
const MENU_BUTTON_HALF: float = 64.0
|
|
const MENU_BUTTON_H: float = 128.0
|
|
const MENU_LABEL_HALF: float = 150.0
|
|
const MENU_LABEL_H: float = 66.0
|
|
const MENU_COST_HALF: float = 60.0
|
|
const MENU_COST_H: float = 52.0
|
|
|
|
|
|
func _place_entry(node: Control, cx: float, top: float, half: float, height: float) -> void:
|
|
node.offset_left = cx - half
|
|
node.offset_right = cx + half
|
|
node.offset_top = top
|
|
node.offset_bottom = top + height
|
|
|
|
|
|
func _layout_build_menu() -> void:
|
|
# Structures the player has not researched are hidden outright (#21), and
|
|
# what remains is repacked so there are no gaps where they used to be.
|
|
# This reverses the earlier decision to always list everything greyed
|
|
# out; an option you cannot explain is apparently worse than one you
|
|
# cannot see yet.
|
|
var shown: Array = []
|
|
for entry in build_menu_entries:
|
|
var visible: bool = GameState.is_building_unlocked(entry[0])
|
|
entry[1].visible = visible
|
|
entry[2].visible = visible
|
|
entry[3].visible = visible
|
|
if visible:
|
|
shown.append(entry)
|
|
|
|
for i in range(shown.size()):
|
|
var row: int = i / 2
|
|
# Centred when it is the only one on its row, rather than hanging in
|
|
# the left column looking like something is missing beside it.
|
|
var alone: bool = (i == shown.size() - 1) and (i % 2 == 0)
|
|
var cx: float = 0.0 if alone else (-MENU_COL_X if i % 2 == 0 else MENU_COL_X)
|
|
var top: float = MENU_ROW_TOPS[mini(row, MENU_ROW_TOPS.size() - 1)]
|
|
_place_entry(shown[i][1], cx, top, MENU_BUTTON_HALF, MENU_BUTTON_H)
|
|
_place_entry(shown[i][2], cx, top + 134.0, MENU_LABEL_HALF, MENU_LABEL_H)
|
|
_place_entry(shown[i][3], cx, top + 204.0, MENU_COST_HALF, MENU_COST_H)
|
|
|
|
|
|
func _refresh_build_status() -> void:
|
|
_layout_build_menu()
|
|
# Researched but not usable right here is still shown, greyed: an
|
|
# extractor needs a planet under the fleet, which is a "not now" rather
|
|
# than a "not yet".
|
|
_refresh_build_option_states()
|
|
|
|
# Prices are shown under each option now, so this only needs to say
|
|
# whether the fleet is somewhere an extractor could go.
|
|
if _planet_under_fleet() != null:
|
|
build_status.text = "Over %s - an extractor can be built here" % _planet_under_fleet().planet_name
|
|
else:
|
|
build_status.text = ""
|
|
|
|
|
|
func _refresh_build_option_states() -> void:
|
|
for entry in build_menu_entries:
|
|
var definition: Resource = entry[4]
|
|
var enabled: bool = GameState.is_building_unlocked(definition.definition_id)
|
|
if definition.placement_mode == "planet":
|
|
enabled = enabled and _planet_under_fleet() != null
|
|
entry[1].enabled = enabled
|
|
entry[1].queue_redraw()
|
|
|
|
|
|
func _close_build_menu() -> void:
|
|
# The popup is a child panel of this menu; leaving it "open" while its
|
|
# parent is hidden is what blocked every button in the game.
|
|
info_popup.visible = false
|
|
pending_build_type = ""
|
|
build_menu.visible = false
|
|
|
|
|
|
func cost_of(type: String) -> int:
|
|
var definition: Resource = BUILDING_DEFINITIONS.get_definition(type)
|
|
return int(definition.cost) if definition != null else 0
|
|
|
|
|
|
func _show_building_info(type: String) -> void:
|
|
var definition: Resource = BUILDING_DEFINITIONS.get_definition(type)
|
|
if definition == null:
|
|
return
|
|
var cost: int = int(definition.cost)
|
|
var lines: Array = [definition.description]
|
|
var can_build: bool = true
|
|
|
|
# Locked structures still list themselves, greyed, so the player can see
|
|
# what exists and what research would open it up.
|
|
if not GameState.is_building_unlocked(type):
|
|
lines.append("Locked. Research %s at your mothership." % GameState.research_for_building(type))
|
|
can_build = false
|
|
|
|
# Anything that would stop this particular placement is spelled out in
|
|
# the same popup, rather than silently failing after the press.
|
|
if can_build and definition.placement_mode == "planet":
|
|
var planet: Node2D = _planet_under_fleet()
|
|
if planet == null:
|
|
lines.append("No planet in reach - fly over one.")
|
|
can_build = false
|
|
elif GameState.has_extractor_at(planet.planet_id):
|
|
lines.append("%s already has an extractor." % planet.planet_name)
|
|
can_build = false
|
|
else:
|
|
lines.append("Will mine %s from %s." % [GameState.resource_name(planet.resource_type), planet.planet_name])
|
|
elif can_build and definition.unique and GameState.has_building_of_type(type):
|
|
lines.append("WARNING: only one may exist - this destroys your current one.")
|
|
|
|
if can_build and not GameState.can_afford(cost):
|
|
lines.append("Not enough rocks - needs %d, you have %d." % [cost, GameState.rocks])
|
|
can_build = false
|
|
|
|
info_title.text = definition.display_name
|
|
info_message.text = "\n\n".join(lines)
|
|
info_cost.amount = cost
|
|
info_build_button.visible = can_build
|
|
pending_build_type = type if can_build else ""
|
|
info_popup.visible = true
|
|
|
|
|
|
func _close_info() -> void:
|
|
info_popup.visible = false
|
|
pending_build_type = ""
|
|
|
|
|
|
func _confirm_build() -> void:
|
|
var type: String = pending_build_type
|
|
if type == "":
|
|
return
|
|
pending_build_type = ""
|
|
info_popup.visible = false
|
|
|
|
# Everything is re-checked here rather than trusted from when the popup
|
|
# opened: the fleet keeps flying while it is up, so it can drift off the
|
|
# planet an extractor was going to mine.
|
|
if not GameState.is_building_unlocked(type):
|
|
return
|
|
var definition: Resource = BUILDING_DEFINITIONS.get_definition(type)
|
|
if definition == null:
|
|
return
|
|
|
|
if definition.placement_mode == "planet":
|
|
var planet: Node2D = _planet_under_fleet()
|
|
if planet == null or GameState.has_extractor_at(planet.planet_id):
|
|
build_status.text = "No planet in reach any more"
|
|
return
|
|
if not GameState.spend(definition.cost):
|
|
build_status.text = "Not enough rocks (need %d, have %d)" % [definition.cost, GameState.rocks]
|
|
return
|
|
GameState.add_extractor(planet.planet_name, planet.position, planet.resource_type, planet.planet_id)
|
|
build_menu.visible = false
|
|
return
|
|
|
|
if definition.unique and GameState.has_building_of_type(type):
|
|
if not GameState.can_afford(definition.cost):
|
|
build_status.text = "Not enough rocks (need %d, have %d)" % [
|
|
definition.cost, GameState.rocks
|
|
]
|
|
return
|
|
# Only now is the old one destroyed - never before the new one is
|
|
# known to be affordable, or the player would be left with none.
|
|
GameState.remove_buildings_of_type(type)
|
|
building_field.rebuild_all()
|
|
|
|
_try_place(type, cost_of(type))
|
|
|
|
|
|
func _try_place(type: String, cost: int) -> void:
|
|
if not GameState.spend(cost):
|
|
build_status.text = "Not enough rocks (need %d, have %d)" % [cost, GameState.rocks]
|
|
return
|
|
building_field.place(type, fleet.get_centroid())
|
|
build_menu.visible = false
|
|
|
|
|
|
# --- research (the mothership's skill tree) --------------------------------
|
|
|
|
func _on_skill_node_picked(id: String) -> void:
|
|
_refresh_skill_menu(id)
|
|
|
|
|
|
func _refresh_skill_menu(id: String = "") -> void:
|
|
if id == "":
|
|
id = skill_tree.selected_id
|
|
if id == "":
|
|
# Nothing chosen yet: point the player at the tree rather than
|
|
# leaving the panel blank, which reads as broken.
|
|
skill_detail.text = "Select a node to see what it unlocks."
|
|
skill_cost.amount = 0
|
|
skill_status.text = "Fleet capacity %d" % GameState.max_fleet_size()
|
|
skill_unlock_button.visible = false
|
|
return
|
|
|
|
var node: Dictionary = GameState.skill_node(id)
|
|
if node.is_empty():
|
|
return
|
|
skill_detail.text = "%s\n%s" % [node.name, node.desc]
|
|
skill_cost.amount = int(node.cost)
|
|
|
|
if GameState.is_unlocked(id):
|
|
skill_status.text = "Researched"
|
|
skill_unlock_button.visible = false
|
|
elif not GameState.prerequisite_met(id):
|
|
skill_status.text = "Needs %s first" % GameState.skill_node(node.requires).name
|
|
skill_unlock_button.visible = false
|
|
elif not GameState.can_afford(int(node.cost)):
|
|
skill_status.text = "Not enough rocks - you have %d" % GameState.rocks
|
|
skill_unlock_button.visible = false
|
|
else:
|
|
skill_status.text = ""
|
|
skill_unlock_button.visible = true
|
|
|
|
|
|
func _on_unlock_pressed() -> void:
|
|
var id: String = skill_tree.selected_id
|
|
if id == "":
|
|
return
|
|
if not GameState.unlock(id):
|
|
# Re-check rather than trust the button: rocks can be spent elsewhere
|
|
# while this menu is open, since haulers keep delivering.
|
|
_refresh_skill_menu(id)
|
|
return
|
|
# A capacity upgrade has to reach the fleet that is already flying, not
|
|
# just ones rebuilt later.
|
|
if int(GameState.skill_node(id).get("fleet_bonus", 0)) > 0:
|
|
fleet.apply_formation()
|
|
skill_tree.queue_redraw()
|
|
_refresh_skill_menu(id)
|
|
|
|
|
|
func _say(message: String) -> void:
|
|
craft_message = message
|
|
craft_message_until = Time.get_ticks_msec() + 3000
|
|
_refresh_craft_status()
|
|
|
|
|
|
func _format_costs(costs: Dictionary) -> String:
|
|
var parts: PackedStringArray = PackedStringArray()
|
|
for resource_type in GameState.resource_types():
|
|
var amount: int = int(costs.get(resource_type, 0))
|
|
if amount > 0:
|
|
parts.append("%d %s" % [amount, GameState.resource_name(resource_type).to_lower()])
|
|
return " · ".join(parts) if not parts.is_empty() else "free"
|
|
|
|
|
|
func _format_missing_costs(costs: Dictionary) -> String:
|
|
var parts: PackedStringArray = PackedStringArray()
|
|
for resource_type in GameState.resource_types():
|
|
var amount: int = int(costs.get(resource_type, 0))
|
|
if amount > 0:
|
|
parts.append("%d more %s" % [amount,
|
|
GameState.resource_name(resource_type).to_lower()])
|
|
return " · ".join(parts)
|
|
|
|
|
|
func _refresh_craft_status() -> void:
|
|
# Prices live with the buttons they describe. The top of the screen is
|
|
# intentionally reserved for the shared resource bar and a short action
|
|
# message; repeating fleet, queue and transport counts here buried the UI.
|
|
craft_status.text = ""
|
|
var definitions: Array = VESSEL_DEFINITIONS.fleet()
|
|
for i in range(mini(definitions.size(), craft_vessel_buttons.size())):
|
|
var definition: Resource = definitions[i]
|
|
var button: IconButton = craft_vessel_buttons[i]
|
|
craft_vessel_labels[i].text = "%s\n%s" % [definition.display_name,
|
|
_format_costs(GameState.product_costs(definition.definition_id))]
|
|
button.icon = definition.icon
|
|
button.glow_color = definition.glow_color
|
|
button.enabled = GameState.vessel_is_unlocked(definition.definition_id)
|
|
button.queue_redraw()
|
|
_refresh_queue()
|
|
if craft_message != "":
|
|
if Time.get_ticks_msec() < craft_message_until:
|
|
craft_status.text = "%s\n%s" % [craft_message, craft_status.text]
|
|
else:
|
|
craft_message = ""
|
|
|
|
|
|
func _on_craft_interceptor() -> void:
|
|
_on_craft_vessel(GameState.VESSEL_INTERCEPTOR)
|
|
|
|
|
|
func _on_craft_vessel(vessel: String) -> void:
|
|
if craft_building_id == 0:
|
|
_say("No crafter selected - reopen this panel from the building")
|
|
return
|
|
var definition: Resource = VESSEL_DEFINITIONS.get_definition(vessel)
|
|
if definition == null:
|
|
return
|
|
if not GameState.queue_order(craft_building_id, vessel):
|
|
if not GameState.vessel_is_unlocked(vessel):
|
|
_say("%s must be unlocked at a ship lab first" % definition.display_name)
|
|
return
|
|
_report_order(vessel)
|
|
|
|
|
|
func _report_order(product: String) -> void:
|
|
var missing: Dictionary = GameState.outstanding_costs(craft_building_id)
|
|
if not missing.is_empty():
|
|
_say("%s queued - deliver %s here" % [GameState.vessel_name(product),
|
|
_format_missing_costs(missing)])
|
|
else:
|
|
_say("%s ordered - building now" % GameState.vessel_name(product))
|
|
|
|
|
|
# --- ship lab (#5) --------------------------------------------------------
|
|
|
|
func _lab_options() -> Array:
|
|
# The shield VESSEL unlock is offered on the shield page as an extra
|
|
# entry, because unlocking the type and upgrading it belong in the same
|
|
# place - and its upgrades are pointless until it exists.
|
|
var options: Array = []
|
|
var definition: Resource = VESSEL_DEFINITIONS.get_definition(lab_vessel)
|
|
if definition == null:
|
|
return options
|
|
if not GameState.vessel_is_unlocked(lab_vessel):
|
|
options.append({"unlock": true, "id": definition.unlock_id,
|
|
"name": definition.unlock_name, "desc": definition.unlock_description,
|
|
"costs": GameState.research_costs(definition.unlock_id),
|
|
"icon": definition.unlock_icon if definition.unlock_icon >= 0 else definition.icon})
|
|
return options
|
|
for u in GameState.upgrades_for(lab_vessel):
|
|
options.append({"unlock": false, "id": u.id, "name": u.name, "desc": u.desc})
|
|
return options
|
|
|
|
|
|
func _on_lab_vessel(index: int) -> void:
|
|
var definitions: Array = VESSEL_DEFINITIONS.all()
|
|
if index < 0 or index >= definitions.size():
|
|
return
|
|
lab_vessel = definitions[index].definition_id
|
|
lab_choice = 0
|
|
_refresh_lab()
|
|
|
|
|
|
func _on_lab_upgrade(index: int) -> void:
|
|
if index < 0 or index >= _lab_options().size():
|
|
return
|
|
lab_choice = index
|
|
lab_status.text = ""
|
|
_refresh_lab()
|
|
|
|
|
|
func _on_lab_buy() -> void:
|
|
var options: Array = _lab_options()
|
|
if options.is_empty():
|
|
return
|
|
var choice: Dictionary = options[lab_choice]
|
|
var id: String = String(choice.id)
|
|
if not GameState.queue_lab_research(lab_building_id, id):
|
|
lab_status.text = "Research is already queued or complete"
|
|
return
|
|
lab_status.text = "%s queued - deliver %s to this lab" % [choice.name,
|
|
_format_costs(GameState.research_costs(id))]
|
|
_refresh_lab()
|
|
|
|
|
|
func _refresh_lab() -> void:
|
|
var definitions: Array = VESSEL_DEFINITIONS.all()
|
|
for i in range(mini(lab_vessel_buttons.size(), definitions.size())):
|
|
var definition: Resource = definitions[i]
|
|
var this_vessel: String = definition.definition_id
|
|
lab_vessel_buttons[i].enabled = true
|
|
lab_vessel_buttons[i].icon = definition.icon
|
|
lab_vessel_buttons[i].glow_color = definition.glow_color
|
|
lab_vessel_labels[i].text = definition.display_name
|
|
lab_vessel_buttons[i].highlighted = this_vessel == lab_vessel
|
|
lab_vessel_buttons[i].queue_redraw()
|
|
lab_vessel_label.text = "%s research" % GameState.vessel_name(lab_vessel)
|
|
|
|
var options: Array = _lab_options()
|
|
for i in range(lab_upgrade_buttons.size()):
|
|
var visible: bool = i < options.size()
|
|
lab_upgrade_buttons[i].visible = visible
|
|
lab_upgrade_labels[i].visible = visible
|
|
if not visible:
|
|
continue
|
|
var option: Dictionary = options[i]
|
|
var price_text: String = _format_costs(option.costs if bool(option.unlock) \
|
|
else GameState.research_costs(option.id))
|
|
if not bool(option.unlock) and GameState.upgrade_maxed(option.id):
|
|
price_text = "MAX"
|
|
var level_note: String = "" if bool(option.unlock) else " %d/%d" % [
|
|
GameState.upgrade_level(option.id), int(GameState.upgrade_def(option.id).max_level)]
|
|
lab_upgrade_labels[i].text = "%s%s\n%s" % [option.name, level_note, price_text]
|
|
lab_upgrade_buttons[i].icon = _upgrade_icon(option)
|
|
lab_upgrade_buttons[i].glow_color = _vessel_colour(lab_vessel)
|
|
lab_upgrade_buttons[i].highlighted = i == lab_choice
|
|
lab_upgrade_buttons[i].enabled = true
|
|
lab_upgrade_buttons[i].queue_redraw()
|
|
if options.is_empty():
|
|
lab_detail.text = ""
|
|
lab_buy_button.enabled = false
|
|
lab_buy_button.queue_redraw()
|
|
return
|
|
lab_choice = clampi(lab_choice, 0, options.size() - 1)
|
|
|
|
var choice: Dictionary = options[lab_choice]
|
|
lab_detail.text = "%s\n%s" % [choice.name, choice.desc]
|
|
var queued: Dictionary = GameState.lab_research_order(lab_building_id)
|
|
lab_buy_button.enabled = queued.is_empty() and (choice.unlock or not GameState.upgrade_maxed(choice.id))
|
|
if not queued.is_empty():
|
|
lab_status.text = "%s %d%%" % ["Researching" if bool(queued.started) else "Waiting for resources", int(100.0 * float(queued.elapsed) / maxf(GameState.lab_research_time(), 0.001))]
|
|
lab_buy_button.queue_redraw()
|
|
|
|
|
|
func _upgrade_icon(option: Dictionary) -> IconButton.Icon:
|
|
return int(option.get("icon", IconButton.Icon.SHIP_LAB))
|
|
|
|
|
|
func _on_craft_shield() -> void:
|
|
_on_craft_vessel(GameState.VESSEL_SHIELD)
|
|
|
|
|
|
# --- moving ore, in both directions, at any building (#24) ----------------
|
|
|
|
# Sentinel for the "All" buttons. The transfer primitives cap what they move
|
|
# by what is actually there, so asking for more than could possibly exist is
|
|
# how you say "everything" without the UI having to work out how much that
|
|
# is first - and the answer cannot go stale between the read and the press.
|
|
const ALL: int = 1 << 30
|
|
|
|
|
|
func _on_transfer_pressed() -> void:
|
|
if current_building == null and current_extractor_index < 0:
|
|
return
|
|
transfer_building_id = current_building.building_id if current_building != null else 0
|
|
transfer_extractor_index = current_extractor_index
|
|
for bar in resource_bars:
|
|
bar.building_id = transfer_building_id
|
|
transfer_menu.visible = true
|
|
transfer_status.text = ""
|
|
_refresh_transfer()
|
|
|
|
|
|
func _on_transfer(to_building: bool, amount: int) -> void:
|
|
_on_resource_transfer(GameState.RESOURCE_ORE, to_building, amount)
|
|
|
|
|
|
func _on_resource_transfer(resource_type: String, to_building: bool, amount: int = ALL) -> void:
|
|
if transfer_building_id == 0 and transfer_extractor_index < 0:
|
|
transfer_status.text = "No resource store selected - reopen this panel from its action"
|
|
return
|
|
|
|
var moved: int
|
|
if transfer_extractor_index >= 0:
|
|
moved = GameState.transfer_resource_to_extractor(transfer_extractor_index, resource_type, amount) \
|
|
if to_building else GameState.transfer_resource_from_extractor(transfer_extractor_index, resource_type, amount)
|
|
else:
|
|
moved = GameState.transfer_resource_to_building(transfer_building_id, resource_type, amount) \
|
|
if to_building else GameState.transfer_resource_to_fleet(transfer_building_id, resource_type, amount)
|
|
var resource_name: String = GameState.resource_name(resource_type).to_lower()
|
|
|
|
# Nothing moving is the common case at an empty end, and a silent no-op
|
|
# is the dead-button problem this panel exists to remove. Say which side
|
|
# was empty rather than just refusing.
|
|
if moved <= 0:
|
|
if to_building:
|
|
transfer_status.text = "The fleet is carrying nothing to unload (no %s)" % resource_name
|
|
else:
|
|
transfer_status.text = "%s has no %s to load" % [_transfer_place_name().capitalize(), resource_name]
|
|
elif to_building:
|
|
transfer_status.text = "Moved %d %s into %s" % [moved, resource_name, _transfer_place_name()]
|
|
else:
|
|
transfer_status.text = "Loaded %d %s into the fleet" % [moved, resource_name]
|
|
_refresh_transfer()
|
|
|
|
func _on_gas_transfer(to_building: bool) -> void:
|
|
_on_resource_transfer(GameState.RESOURCE_GAS, to_building)
|
|
|
|
|
|
func _transfer_place_name() -> String:
|
|
if transfer_extractor_index >= 0:
|
|
return "the extractor"
|
|
var b: Dictionary = GameState.find_building(transfer_building_id)
|
|
if b.is_empty():
|
|
return "this building"
|
|
if b.type == GameState.BUILDING_MOTHERSHIP:
|
|
# Worth naming as the bank rather than as a structure: this is the
|
|
# one building where unloading makes the ore spendable everywhere.
|
|
return "the bank"
|
|
return "this building"
|
|
|
|
|
|
func _refresh_transfer() -> void:
|
|
if transfer_extractor_index >= 0:
|
|
transfer_list.set_extractor_destination(transfer_extractor_index, "Planet extractor")
|
|
return
|
|
var building: Dictionary = GameState.find_building(transfer_building_id)
|
|
var heading: String = "This building"
|
|
if not building.is_empty() and building.type == GameState.BUILDING_MOTHERSHIP:
|
|
heading = "Mothership bank"
|
|
transfer_list.set_destination(transfer_building_id, heading)
|
|
|
|
|
|
func _refresh_queue() -> void:
|
|
# The queue down the right of the craft panel: one slot per pending
|
|
# vessel, the first being the one actually under construction. Tapping a
|
|
# slot cancels it (#23).
|
|
var orders: Array = GameState.order_list(craft_building_id)
|
|
for i in range(queue_slots.size()):
|
|
var slot: IconButton = queue_slots[i]
|
|
slot.visible = i < orders.size()
|
|
if not slot.visible:
|
|
continue
|
|
var order: Dictionary = orders[i]
|
|
var definition: Resource = VESSEL_DEFINITIONS.get_definition(String(order.product))
|
|
slot.icon = definition.icon
|
|
slot.glow_color = _vessel_colour(String(order.product))
|
|
# Only the one under construction reads as active; the rest are
|
|
# waiting, and greyed says so without needing a caption each.
|
|
slot.enabled = bool(order.started)
|
|
slot.queue_redraw()
|
|
|
|
if orders.is_empty():
|
|
queue_detail.text = "Nothing queued"
|
|
return
|
|
var head: Dictionary = orders[0]
|
|
if bool(head.started):
|
|
queue_detail.text = "Building %s %d%%\nTap to cancel" % [
|
|
GameState.vessel_name(String(head.product)),
|
|
int(round(float(head.progress) * 100.0)),
|
|
]
|
|
else:
|
|
queue_detail.text = "%s waiting for resources\nTap to cancel" % \
|
|
GameState.vessel_name(String(head.product))
|
|
|
|
|
|
func _vessel_colour(product: String) -> Color:
|
|
var definition: Resource = VESSEL_DEFINITIONS.get_definition(product)
|
|
return definition.glow_color if definition != null else Color(0.25, 0.85, 1.0)
|
|
|
|
|
|
func _on_queue_slot(index: int) -> void:
|
|
var orders: Array = GameState.order_list(craft_building_id)
|
|
if index >= orders.size():
|
|
return
|
|
var vessel: String = GameState.vessel_name(String(orders[index].product))
|
|
var refunded: bool = bool(orders[index].started)
|
|
if not GameState.cancel_order(craft_building_id, index):
|
|
return
|
|
_say("%s cancelled%s" % [vessel, " - resources refunded here" if refunded else ""])
|
|
_refresh_queue()
|
|
|
|
|
|
# --- hangar (#23) ---------------------------------------------------------
|
|
|
|
func _on_hangar_pressed() -> void:
|
|
hangar_status.text = ""
|
|
hangar_menu.visible = true
|
|
_refresh_hangar()
|
|
|
|
|
|
func _on_hangar_crew_type(index: int) -> void:
|
|
var type: String = _hangar_types()[index]
|
|
if fleet.ships.size() >= GameState.max_fleet_size():
|
|
hangar_status.text = "Fleet is full (%d/%d) - research capacity at the mothership" % [
|
|
fleet.ships.size(), GameState.max_fleet_size()
|
|
]
|
|
return
|
|
var stored_index: int = GameState.stored_vessels.find(type)
|
|
if stored_index < 0:
|
|
hangar_status.text = "No %s available" % GameState.vessel_name(type)
|
|
return
|
|
if not fleet.add_ship(GameState.take_stored_vessel(stored_index)):
|
|
hangar_status.text = "Could not crew that vessel"
|
|
return
|
|
hangar_status.text = "%s joined the fleet" % GameState.vessel_name(type)
|
|
_refresh_hangar()
|
|
|
|
|
|
func _on_hangar_store_type(index: int) -> void:
|
|
var type: String = _hangar_types()[index]
|
|
var ship_index: int = -1
|
|
for i in range(fleet.ships.size()):
|
|
if String(fleet.ships[i].vessel_type) == type:
|
|
ship_index = i
|
|
break
|
|
if ship_index < 0:
|
|
hangar_status.text = "No %s in the fleet" % GameState.vessel_name(type)
|
|
return
|
|
var stored: String = fleet.store_ship(ship_index)
|
|
if stored == "":
|
|
hangar_status.text = "Keep one ship deployed"
|
|
return
|
|
GameState.stored_vessels.append(stored)
|
|
hangar_status.text = "%s stored in the hangar" % GameState.vessel_name(stored)
|
|
_refresh_hangar()
|
|
|
|
|
|
func _hangar_types() -> Array:
|
|
var types: Array = []
|
|
for definition in VESSEL_DEFINITIONS.fleet():
|
|
types.append(definition.definition_id)
|
|
return types
|
|
|
|
|
|
func _refresh_hangar() -> void:
|
|
var types: Array = _hangar_types()
|
|
for i in range(types.size()):
|
|
var type: String = types[i]
|
|
var definition: Resource = VESSEL_DEFINITIONS.get_definition(type)
|
|
var available: int = GameState.stored_vessels.count(type)
|
|
var deployed: int = 0
|
|
for ship in fleet.ships:
|
|
if is_instance_valid(ship) and String(ship.vessel_type) == type:
|
|
deployed += 1
|
|
hangar_available_labels[i].text = "%s\n%d available" % [definition.display_name, available]
|
|
hangar_fleet_labels[i].text = "%s\n%d in fleet" % [definition.display_name, deployed]
|
|
hangar_available_slots[i].icon = definition.icon
|
|
hangar_fleet_slots[i].icon = definition.icon
|
|
hangar_available_slots[i].glow_color = definition.glow_color
|
|
hangar_fleet_slots[i].glow_color = definition.glow_color
|
|
hangar_available_slots[i].enabled = available > 0 and fleet.ships.size() < GameState.max_fleet_size()
|
|
hangar_fleet_slots[i].enabled = deployed > 0 and fleet.ships.size() > 1
|
|
hangar_available_slots[i].queue_redraw()
|
|
hangar_fleet_slots[i].queue_redraw()
|
|
|
|
|
|
func _on_formation_changed() -> void:
|
|
fleet.apply_formation()
|
|
|
|
|
|
func _on_reset_formation() -> void:
|
|
GameState.reset_formation()
|
|
fleet.apply_formation()
|
|
formation_editor.queue_redraw()
|