#220: every berth past the first paid one costs 1.12x the one before it, on top of the flat per-vessel charge. A linear bill makes the tenth hull no more of a decision than the second; compounding weighs each addition against the whole fleet behind it. Shallow on purpose - the thirteenth berth costs about three and a half times the second, the twentieth about seven and a half. Fleet Offices gain a menu naming the whole bill per minute, a line per paying berth with its multiplier, and the arithmetic in words. It renders fleet_upkeep_breakdown(), which is the calculation the charging loop itself runs: a readout that summed independently could quote a bill the game does not take, which is the thing worth preventing here. The test pins that what the menu quotes for a berth is what that office is charged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
823 lines
28 KiB
GDScript
823 lines
28 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")
|
|
# #216: where a hub or a workshop is told how many craft to keep.
|
|
@onready var station_menu: Control = get_node("../UI/StationMenu")
|
|
# #220: what the fleet costs to keep, and the arithmetic behind it.
|
|
@onready var upkeep_menu: Control = get_node("../UI/FleetUpkeepMenu")
|
|
@onready var scanner_menu: Control = get_node("../UI/ScannerMenu")
|
|
|
|
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 scanner_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)
|
|
scanner_menu.scan_requested.connect(_on_scan_requested)
|
|
scanner_menu.closed.connect(func(): scanner_building_id = 0)
|
|
|
|
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, scanner_menu,
|
|
station_menu, upkeep_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)
|
|
elif building_action == "scanner":
|
|
scanner_building_id = building_id
|
|
scanner_menu.open_menu()
|
|
elif building_action == "staff":
|
|
station_menu.open_for(building_id)
|
|
elif building_action == "upkeep":
|
|
upkeep_menu.open_for(building_id)
|
|
|
|
|
|
func _on_scan_requested(definition_id: String) -> void:
|
|
var scanner: Dictionary = GameState.find_building(scanner_building_id)
|
|
if scanner.is_empty():
|
|
scanner_menu.show_failure(definition_id)
|
|
return
|
|
var result: Dictionary = GameState.scan_for_planet(definition_id,
|
|
Vector2(scanner.position))
|
|
if result.is_empty():
|
|
scanner_menu.show_failure(definition_id)
|
|
return
|
|
scanner_menu.close_menu()
|
|
world_map.open_map()
|
|
world_map.focus_world_position(Vector2(result.world_position))
|
|
|
|
|
|
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()
|
|
station_menu.refresh()
|
|
upkeep_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
|