spaceship/controllers/BuildController.gd
2026-09-03 00:12:08 +02:00

790 lines
27 KiB
GDScript

extends Node2D
const BUILDING_DEFINITIONS = preload("res://buildings/BuildingDefinitions.gd")
const VESSEL_DEFINITIONS = preload("res://vessels/VesselDefinitions.gd")
const ALL: int = 1 << 30
const HUD_COLUMN_MARGIN := 18.0
const HUD_ROW_GAP := 16.0
@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 planet_field: Node2D = get_node("../PlanetField")
@onready var landing_controller: Node2D = get_node("../LandingController")
@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 transfer_button: IconButton = get_node("../UI/TransferButton")
@onready var hangar_button: IconButton = get_node("../UI/HangarButton")
# Menu roots are scene APIs. BuildController deliberately never reaches into
# their child trees; each scene owns its controls, layout and input routing.
@onready var build_menu: Control = get_node("../UI/BuildMenu")
@onready var craft_menu: Control = get_node("../UI/CraftMenu")
@onready var lab_menu: Control = get_node("../UI/LabMenu")
@onready var transfer_menu: Control = get_node("../UI/TransferMenu")
@onready var hangar_menu: Control = get_node("../UI/HangarMenu")
@onready var formation_menu: Control = get_node("../UI/FormationMenu")
@onready var skill_menu: Control = get_node("../UI/SkillMenu")
@onready var planning_menu: Control = get_node("../UI/PlanningMenu")
@onready var garrison_menu: Control = get_node("../UI/GarrisonMenu")
var pending_build_type: String = ""
var craft_building_id: int = 0
var lab_building_id: int = 0
var lab_vessel: String = GameState.VESSEL_INTERCEPTOR
var lab_choice: int = 0
var lab_can_buy: bool = false
var transfer_building_id: int = 0
var transfer_extractor_index: int = -1
var garrison_building_id: int = 0
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:
_seat_hud_columns()
build_button.pressed.connect(_on_build_pressed)
action_button.pressed.connect(_on_context_action_pressed)
building_action_button.pressed.connect(_on_action_pressed)
transfer_button.pressed.connect(_on_transfer_pressed)
hangar_button.pressed.connect(_on_hangar_pressed)
build_menu.building_selected.connect(_show_building_info)
build_menu.build_confirmed.connect(_confirm_build)
build_menu.closed.connect(_on_build_menu_closed)
craft_menu.craft_requested.connect(_on_craft_vessel)
craft_menu.queue_cancel_requested.connect(_on_queue_slot)
craft_menu.closed.connect(func(): craft_building_id = 0)
lab_menu.vessel_selected.connect(_on_lab_vessel)
lab_menu.upgrade_selected.connect(_on_lab_upgrade)
lab_menu.closed.connect(func(): lab_building_id = 0)
transfer_menu.transfer_requested.connect(_on_resource_transfer)
transfer_menu.closed.connect(_clear_transfer_context)
hangar_menu.crew_requested.connect(_on_hangar_crew_type)
hangar_menu.store_requested.connect(_on_hangar_store_type)
formation_menu.formation_changed.connect(_on_formation_changed)
formation_menu.reset_requested.connect(_on_reset_formation)
skill_menu.research_requested.connect(_on_unlock_requested)
for control in [action_button, building_action_button, landing_button,
transfer_button, hangar_button]:
control.visible = false
if GameState.intro_active:
_hide_all_actions()
_layout_context_buttons()
func _seat_hud_columns() -> void:
var readout: ResourceReadout = get_node_or_null("../UI/ResourceReadout")
if readout == null:
return
var buttons: Array = [build_button, transfer_button, action_button,
landing_button, building_action_button, hangar_button]
var highest: float = INF
for button in buttons:
if button != null:
highest = minf(highest, button.position.y)
if is_inf(highest):
return
var shift: float = readout.content_bottom() + HUD_COLUMN_MARGIN - highest
if shift <= 0.0:
return
for button in buttons:
if button != null:
button.position.y += shift
func _menu_is_open() -> bool:
for menu in [build_menu, craft_menu, lab_menu, transfer_menu, hangar_menu,
formation_menu, skill_menu, planning_menu, garrison_menu]:
if menu.visible:
return true
return false
func is_showing_building_prompt() -> bool:
return _menu_is_open()
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):
_collapse_context_actions()
if skill_menu.handle_unhandled_input(event):
get_viewport().set_input_as_handled()
return
if formation_menu.handle_unhandled_input(event):
get_viewport().set_input_as_handled()
return
if world_map.visible or _menu_is_open():
get_viewport().set_input_as_handled()
func _process(delta: float) -> void:
_check_fleet_wipe()
if GameState.intro_active:
_hide_all_actions()
return
if not build_button.visible:
build_button.visible = true
if build_menu.visible:
build_menu.refresh_option_states(_planet_under_fleet())
if craft_menu.visible:
craft_menu.refresh()
if lab_menu.visible:
_refresh_lab()
if transfer_menu.visible:
transfer_menu.refresh()
if world_map.visible or _menu_is_open():
_clear_detected_context()
return
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_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_planet == current_landing_planet):
return
current_building = pending_building
current_extractor_index = pending_extractor_index
current_landing_planet = detected_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 = _building_action_icon(current_building)
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 _clear_detected_context() -> void:
current_building = null
pending_building = null
current_extractor_index = -1
pending_extractor_index = -1
current_landing_planet = null
for control in [action_button, building_action_button, landing_button,
transfer_button, hangar_button]:
control.visible = false
context_actions_expanded = false
func _layout_context_buttons() -> void:
var row_top: float = build_button.position.y + build_button.size.y + HUD_ROW_GAP
var row_height: float = build_button.size.y
action_button.offset_top = row_top
action_button.offset_bottom = row_top + row_height
var buttons: Array = _context_action_buttons()
if not context_actions_expanded:
for button in buttons:
button.visible = false
return
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 = row_top
button.offset_bottom = row_top + row_height
func _context_action_buttons() -> Array:
var buttons: Array = []
if current_building != null:
for available_action in current_building.available_actions():
var button: IconButton = _button_for_building_action(available_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:
return current_extractor_index >= 0 or current_building != null \
and current_building.available_actions().has("transfer")
func _building_action_icon(building: Node) -> int:
if building.primary_action() == "pause":
return IconButton.Icon.RESUME if GameState.is_building_paused(building.building_id) \
else IconButton.Icon.PAUSE
return int(building.definition.action_icon)
func _button_for_building_action(available_action: String) -> IconButton:
if available_action == "transfer":
return null
if available_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 context_actions_expanded:
context_actions_expanded = false
_layout_context_buttons()
func _check_fleet_wipe() -> void:
if fleet.ships.is_empty():
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
var building_id: int = current_building.building_id
var building_action: String = current_building.primary_action()
if building_action == "pause":
var paused: bool = GameState.toggle_building_paused(building_id)
build_menu.set_status("%s %s" % [current_building.definition.display_name,
"paused" if paused else "running"])
building_action_button.icon = _building_action_icon(current_building)
building_action_button.queue_redraw()
elif building_action == "research":
skill_menu.open_for(building_id)
elif building_action == "craft":
craft_building_id = building_id
craft_menu.open_for(building_id)
elif building_action == "lab":
lab_building_id = building_id
lab_menu.open_for(building_id)
_refresh_lab()
elif building_action == "formation":
formation_menu.open_menu()
elif building_action == "planning":
planning_menu.open_menu()
elif building_action == "garrison":
garrison_building_id = building_id
garrison_menu.open_for(building_id)
func _on_build_pressed() -> void:
pending_build_type = ""
build_menu.open_menu()
_refresh_build_status()
func _on_build_menu_closed() -> void:
pending_build_type = ""
func _planet_under_fleet() -> Node2D:
return planet_field.get_planet_fleet_is_on()
func cost_of(type: String) -> int:
return int(costs_of(type).get(GameState.RESOURCE_ORE, 0))
func costs_of(type: String) -> Dictionary:
return GameState.building_costs(type)
func _refresh_build_status() -> void:
build_menu.refresh(_planet_under_fleet())
func _refresh_build_option_states() -> void:
build_menu.refresh_option_states(_planet_under_fleet())
func _layout_build_menu() -> void:
build_menu.refresh(_planet_under_fleet())
func menu_columns(count: int) -> int:
return build_menu.menu_columns(count)
func menu_rows(count: int) -> int:
return build_menu.menu_rows(count)
func menu_row_top(row: int, rows: int, columns: int) -> float:
return build_menu.menu_row_top(row, rows, columns)
func menu_column_spacing(columns: int) -> float:
return build_menu.menu_column_spacing(columns)
func _show_building_info(type: String) -> void:
var definition: Resource = BUILDING_DEFINITIONS.get_definition(type)
if definition == null:
return
var costs: Dictionary = GameState.building_costs(type)
var lines: Array = [definition.description]
var can_build: bool = true
if not GameState.is_building_unlocked(type):
lines.append("Locked. Research %s at your mothership." % GameState.research_for_building(type))
can_build = false
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 not GameState.is_planet_conquered(planet.planet_id):
lines.append("%s is not captured yet - take all its capture points." % planet.planet_name)
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_resources(costs, fleet.get_centroid()):
can_build = false
pending_build_type = type if can_build else ""
build_menu.show_info(definition.display_name, "\n\n".join(lines), costs, can_build)
func _close_info() -> void:
pending_build_type = ""
build_menu.close_info()
func _confirm_build() -> void:
var type: String = pending_build_type
if type == "":
return
pending_build_type = ""
build_menu.close_info()
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_menu.set_status("No planet in reach any more")
return
if not GameState.is_planet_conquered(planet.planet_id):
build_menu.set_status("%s is not captured yet" % planet.planet_name)
return
if String(planet.resource_type) == "":
build_menu.set_status("%s has nothing to extract" % planet.planet_name)
return
if not GameState.spend_resources(GameState.building_costs(type), fleet.get_centroid()):
build_menu.set_status(_cannot_afford(type))
return
GameState.add_extractor(planet.planet_name, planet.position,
planet.resource_type, planet.planet_id)
build_menu.close_menu()
return
if definition.unique and GameState.has_building_of_type(type):
if not GameState.can_afford_resources(GameState.building_costs(type), fleet.get_centroid()):
build_menu.set_status(_cannot_afford(type))
return
GameState.remove_buildings_of_type(type)
building_field.rebuild_all()
_try_place(type)
func _cannot_afford(type: String) -> String:
var missing: Array = []
for resource_type in GameState.building_costs(type):
var id: String = String(resource_type)
var want: int = int(GameState.building_costs(type)[resource_type])
var have: int = GameState.available_resource_at(id, fleet.get_centroid())
if have < want:
missing.append("%s (need %d, have %d)" % [GameState.resource_name(id), want, have])
return "" if missing.is_empty() else "Not enough %s" % " and ".join(missing)
func _try_place(type: String) -> void:
if not GameState.spend_resources(GameState.building_costs(type), fleet.get_centroid()):
build_menu.set_status(_cannot_afford(type))
return
building_field.place(type, fleet.get_centroid())
build_menu.close_menu()
func _on_unlock_requested(id: String) -> void:
var research_building: Dictionary = GameState.find_building(skill_menu.building_id)
if research_building.is_empty():
skill_menu.set_status("The research building is no longer available")
return
var payment_position: Variant = research_building.get("position", null)
if not GameState.unlock(id, payment_position):
skill_menu.refresh(id)
return
if int(GameState.skill_node(id).get("fleet_bonus", 0)) > 0:
fleet.apply_formation()
skill_menu.research_completed(id)
func _on_unlock_pressed() -> void:
var id: String = skill_menu.selected_id()
if id != "":
_on_unlock_requested(id)
func _refresh_skill_menu(id: String = "") -> void:
skill_menu.refresh(id)
func _format_costs(costs: Dictionary) -> String:
var parts := 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()
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 _on_craft_interceptor() -> void:
_on_craft_vessel(GameState.VESSEL_INTERCEPTOR)
func _on_craft_shield() -> void:
_on_craft_vessel(GameState.VESSEL_SHIELD)
func _on_craft_vessel(vessel: String) -> void:
if craft_building_id == 0:
craft_menu.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):
craft_menu.say("%s must be unlocked at a ship lab first" % definition.display_name)
return
var missing: Dictionary = GameState.outstanding_costs(craft_building_id)
if missing.is_empty():
craft_menu.say("%s ordered - building now" % GameState.vessel_name(vessel))
else:
craft_menu.say("%s queued - deliver %s here" % [GameState.vessel_name(vessel),
_format_missing_costs(missing)])
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 GameState.cancel_order(craft_building_id, index):
craft_menu.say("%s cancelled%s" % [vessel,
" - resources refunded here" if refunded else ""])
func _refresh_craft_status() -> void:
if craft_building_id != 0 and craft_menu.building_id != craft_building_id:
craft_menu.building_id = craft_building_id
craft_menu.resource_bar.building_id = craft_building_id
craft_menu.refresh()
func _lab_options() -> Array:
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 upgrade in GameState.upgrades_for(lab_vessel):
options.append({"unlock": false, "id": upgrade.id,
"name": upgrade.name, "desc": upgrade.desc})
return options
func _on_lab_vessel(index: int) -> void:
var definitions: Array = VESSEL_DEFINITIONS.all()
if index >= 0 and index < definitions.size():
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
var confirming: bool = index == lab_choice
lab_choice = index
lab_menu.set_status("")
if confirming and lab_can_buy:
_on_lab_buy()
else:
_refresh_lab()
func _on_lab_buy() -> void:
var options: Array = _lab_options()
if options.is_empty():
return
var selected: Dictionary = options[lab_choice]
var id: String = String(selected.id)
if not GameState.queue_lab_research(lab_building_id, id):
lab_menu.set_status("Research is already queued or complete")
return
lab_menu.set_status("%s queued - deliver %s to this lab" % [
selected.name, _format_costs(GameState.research_costs(id))])
_refresh_lab()
func _refresh_lab() -> void:
lab_menu.set_status("")
lab_menu.hide_research_progress()
var definitions: Array = VESSEL_DEFINITIONS.all()
lab_menu.render_vessels(definitions, lab_vessel)
lab_menu.vessel_heading.text = "%s research · %d RP" % [
GameState.vessel_name(lab_vessel), GameState.research_points]
var options: Array = _lab_options()
lab_choice = clampi(lab_choice, 0, maxi(options.size() - 1, 0))
var rendered: Array = []
for option in options:
var maxed: bool = not bool(option.unlock) and GameState.upgrade_maxed(option.id)
var level_note: String = "" if bool(option.unlock) else " %d/%d" % [
GameState.upgrade_level(option.id), int(GameState.upgrade_def(option.id).max_level)]
rendered.append({"label": "%s%s" % [option.name, level_note],
"costs": option.costs if bool(option.unlock) else GameState.research_costs(option.id),
"points": GameState.research_point_cost(String(option.id)),
"icon": int(option.get("icon", IconButton.Icon.SHIP_LAB)), "maxed": maxed})
var definition: Resource = VESSEL_DEFINITIONS.get_definition(lab_vessel)
lab_menu.render_options(rendered, lab_choice,
definition.glow_color if definition != null else Color(0.25, 0.85, 1.0))
if options.is_empty():
lab_menu.clear_detail()
lab_can_buy = false
return
var selected: Dictionary = options[lab_choice]
var points: int = GameState.research_point_cost(String(selected.id))
lab_menu.show_detail(String(selected.name), String(selected.desc), points)
var queued: Dictionary = GameState.lab_research_order(lab_building_id)
lab_can_buy = queued.is_empty() and (selected.unlock \
or not GameState.upgrade_maxed(selected.id))
if not queued.is_empty():
var queued_points: int = GameState.research_point_cost(String(queued.id))
var progress: float = float(queued.elapsed) \
/ maxf(GameState.lab_research_time(), 0.001)
if not bool(queued.started) and GameState.research_points < queued_points:
lab_menu.show_research_progress(
"Need %d research point%s (have %d)" % [queued_points,
"" if queued_points == 1 else "s", GameState.research_points], progress)
else:
lab_menu.show_research_progress(
"Researching" if bool(queued.started) else "Waiting for resources",
progress)
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
transfer_menu.open_destination(transfer_building_id, transfer_extractor_index)
func _clear_transfer_context() -> void:
transfer_building_id = 0
transfer_extractor_index = -1
func _on_transfer(to_building: bool, amount: int) -> void:
_on_resource_transfer(GameState.RESOURCE_ORE, to_building, amount)
func _on_gas_transfer(to_building: bool) -> void:
_on_resource_transfer(GameState.RESOURCE_GAS, to_building)
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_menu.show_status("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()
if moved <= 0:
transfer_menu.show_status("The fleet is carrying nothing to unload (no %s)" \
% resource_name if to_building else "%s has no %s to load" \
% [_transfer_place_name().capitalize(), resource_name])
elif to_building:
transfer_menu.show_status("Moved %d %s into %s" % [
moved, resource_name, _transfer_place_name()])
else:
transfer_menu.show_status("Loaded %d %s into the fleet" % [moved, resource_name])
transfer_menu.refresh()
func _transfer_place_name() -> String:
return transfer_menu.place_name()
func _refresh_transfer() -> void:
if transfer_menu.building_id != transfer_building_id \
or transfer_menu.extractor_index != transfer_extractor_index:
transfer_menu.building_id = transfer_building_id
transfer_menu.extractor_index = transfer_extractor_index
transfer_menu.refresh()
func _on_hangar_pressed() -> void:
hangar_menu.open_for(fleet)
func _hangar_types() -> Array:
var types: Array = []
for definition in VESSEL_DEFINITIONS.fleet():
types.append(String(definition.definition_id))
return types
func _resolve_hangar_type(selection: Variant) -> String:
if selection is String:
return String(selection)
var index: int = int(selection)
var types: Array = _hangar_types()
return String(types[index]) if index >= 0 and index < types.size() else ""
func _on_hangar_crew_type(selection: Variant) -> void:
var vessel_type: String = _resolve_hangar_type(selection)
if vessel_type == "":
return
if fleet.ships.size() >= GameState.max_fleet_size():
hangar_menu.set_status("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(vessel_type)
if stored_index < 0:
hangar_menu.set_status("No %s available" % GameState.vessel_name(vessel_type))
return
if not fleet.add_ship(GameState.take_stored_vessel(stored_index)):
hangar_menu.set_status("Could not crew that vessel")
return
hangar_menu.set_status("%s joined the fleet" % GameState.vessel_name(vessel_type))
hangar_menu.refresh(fleet)
func _on_hangar_store_type(selection: Variant) -> void:
var vessel_type: String = _resolve_hangar_type(selection)
if vessel_type == "":
return
var ship_index: int = -1
for i in range(fleet.ships.size()):
if String(fleet.ships[i].vessel_type) == vessel_type:
ship_index = i
break
if ship_index < 0:
hangar_menu.set_status("No %s in the fleet" % GameState.vessel_name(vessel_type))
return
var stored: String = fleet.store_ship(ship_index)
if stored == "":
hangar_menu.set_status("Keep one ship deployed")
return
GameState.stored_vessels.append(stored)
hangar_menu.set_status("%s stored in the hangar" % GameState.vessel_name(stored))
hangar_menu.refresh(fleet)
func _refresh_hangar() -> void:
hangar_menu.refresh(fleet)
func _layout_hangar() -> void:
hangar_menu._layout_sections()
func _refresh_garrison() -> void:
garrison_menu.refresh()
func _refresh_planning() -> void:
planning_menu.refresh()
func _on_formation_changed() -> void:
fleet.apply_formation()
func _on_reset_formation() -> void:
GameState.reset_formation()
fleet.apply_formation()
formation_menu.redraw()
func _hide_all_actions() -> void:
_clear_detected_context()
build_button.visible = false