Refactor gameplay menus into owned scenes

This commit is contained in:
Loïc Wisniewski 2026-08-31 02:18:08 +02:00
commit a17fe88197
50 changed files with 3449 additions and 3619 deletions

View file

@ -1,20 +0,0 @@
extends Control
# #120: the rule between the hangar's two halves.
#
# The panel itself draws it rather than a separate node, because the y comes
# from BuildController._layout_hangar() - which is also what places the two
# grids - and a divider that had to be positioned independently would drift
# away from the sections it divides the first time either grid changed size.
const RULE_COLOR: Color = Color(0.45, 0.9, 0.55)
@onready var controller: Node = get_node("../../BuildController")
func _draw() -> void:
if controller == null or controller.hangar_divider_y <= 0.0:
return
var y: float = controller.hangar_divider_y
NeonDraw.draw_glowing_line(self, Vector2(24.0, y), Vector2(size.x - 24.0, y),
RULE_COLOR, 0.5, 0.4)

View file

@ -1 +0,0 @@
uid://bwqdhbksv0xgf

216
ui/menus/BuildMenu.gd Normal file
View file

@ -0,0 +1,216 @@
extends "res://ui/menus/ModalMenu.gd"
signal building_selected(type: String)
signal build_confirmed
const BUILDING_DEFINITIONS = preload("res://buildings/BuildingDefinitions.gd")
const MENU_LAYOUT = preload("res://ui/menus/MenuLayout.gd")
const BUTTON_HALF: float = 64.0
const BUTTON_HEIGHT: float = 100.0
const COST_HALF: float = 74.0
const COST_HEIGHT: float = 86.0
const LABEL_OFFSET: float = 106.0
const LABEL_GAP: float = 4.0
const NATURAL_SPACING: float = 280.0
const MIN_SPACING: float = BUTTON_HALF * 2.0 + 12.0
const LABEL_SHARE: float = 0.46
const LABEL_HEIGHT: float = 52.0
const LABEL_PADDING: float = 10.0
const COST_PADDING: float = 12.0
const SIDE_MARGIN: float = 24.0
const MAX_COLUMNS: int = 6
@onready var status: Label = $StatusLabel
@onready var info_popup: Control = $InfoPopup
@onready var info_title: Label = $InfoPopup/Panel/TitleLabel
@onready var info_message: Label = $InfoPopup/Panel/MessageLabel
@onready var info_cost: Control = $InfoPopup/Panel/CostNumber
@onready var info_build_button: Control = $InfoPopup/Panel/BuildButton
@onready var info_close_button: Control = $InfoPopup/Panel/CloseButton
var entries: Array = []
func _ready() -> void:
super._ready()
_configure_entries()
info_close_button.pressed.connect(close_info)
info_build_button.pressed.connect(func(): build_confirmed.emit())
info_popup.visible = false
func _configure_entries() -> void:
entries.clear()
for definition in BUILDING_DEFINITIONS.all():
if definition.menu_button_name == "":
continue
var button: Control = get_node(String(definition.menu_button_name))
var label: Label = get_node(String(definition.menu_label_name))
var cost_tag: Control = get_node(String(definition.menu_cost_name))
button.icon = definition.menu_icon
button.pressed.connect(
func(): building_selected.emit(String(definition.definition_id)))
label.text = definition.display_name
cost_tag.show_price(GameState.building_costs(String(definition.definition_id)))
entries.append({"definition": definition, "button": button,
"label": label, "cost": cost_tag})
func open_menu() -> void:
super.open_menu()
close_info()
func close_menu() -> void:
close_info()
super.close_menu()
func close_info() -> void:
info_popup.visible = false
func show_info(title: String, message: String, costs: Dictionary,
can_build: bool) -> void:
info_title.text = title
info_message.text = message
info_cost.show_price(costs)
info_build_button.visible = can_build
info_popup.visible = true
func set_status(message: String) -> void:
status.text = message
func refresh(planet: Node2D) -> void:
_layout_entries()
refresh_option_states(planet)
if planet == null:
status.text = ""
elif not GameState.is_planet_conquered(planet.planet_id):
status.text = "Over %s - capture it before building an extractor" % planet.planet_name
else:
status.text = "Over %s - an extractor can be built here" % planet.planet_name
func refresh_option_states(planet: Node2D) -> void:
for entry in entries:
var definition: Resource = entry.definition
entry.cost.show_price(GameState.building_costs(String(definition.definition_id)))
var enabled: bool = GameState.is_building_unlocked(definition.definition_id)
if definition.placement_mode == "planet":
enabled = enabled and planet != null \
and GameState.is_planet_conquered(planet.planet_id)
entry.button.enabled = enabled
entry.button.queue_redraw()
func menu_band() -> Vector2:
return MENU_LAYOUT.grid_band(status.offset_bottom, size.y)
func menu_column_spacing(columns: int) -> float:
var room: float = size.x - SIDE_MARGIN * 2.0
if room <= 0.0:
room = 672.0
return maxf(MIN_SPACING, minf(NATURAL_SPACING, room / float(columns)))
func menu_label_half(columns: int) -> float:
return menu_column_spacing(columns) * LABEL_SHARE
func measured_label_height(label: Label, text: String, columns: int) -> float:
var font: Font = label.get_theme_font("font")
var font_size: int = label.get_theme_font_size("font_size")
if font == null or font_size <= 0 or text == "":
return LABEL_HEIGHT
var height: float = font.get_multiline_string_size(text,
HORIZONTAL_ALIGNMENT_CENTER, menu_label_half(columns) * 2.0, font_size).y
return maxf(LABEL_HEIGHT, height + LABEL_PADDING)
func menu_label_height(columns: int) -> float:
var tallest: float = LABEL_HEIGHT
for entry in entries:
if entry.label.visible:
tallest = maxf(tallest,
measured_label_height(entry.label, entry.label.text, columns))
return tallest
func menu_cost_height() -> float:
var tallest: float = COST_HEIGHT
for entry in entries:
if entry.cost.visible and entry.cost.has_method("wanted_height"):
tallest = maxf(tallest, float(entry.cost.wanted_height()) + COST_PADDING)
return tallest
func menu_cost_offset(columns: int) -> float:
return LABEL_OFFSET + menu_label_height(columns) + LABEL_GAP
func menu_row_height(columns: int) -> float:
return menu_cost_offset(columns) + menu_cost_height()
func menu_columns(count: int) -> int:
var widest: int = maxi(mini(MAX_COLUMNS,
int(floor((size.x - SIDE_MARGIN * 2.0) / MIN_SPACING))), 2)
for columns in range(2, widest + 1):
if _rows_fit(int(ceil(float(count) / float(columns))), columns):
return columns
return widest
func menu_rows(count: int) -> int:
var columns: int = menu_columns(count)
return maxi(int(ceil(float(count) / float(columns))), 1)
func _rows_fit(rows: int, columns: int) -> bool:
var height: float = menu_row_height(columns)
var band: Vector2 = menu_band()
if rows <= 1:
return band.y - band.x >= height
return (band.y - band.x - height) / float(rows - 1) >= height
func menu_row_top(row: int, rows: int, columns: int) -> float:
return MENU_LAYOUT.grid_row_top(row, rows, menu_row_height(columns),
menu_band(), NATURAL_SPACING)
func _layout_entries() -> void:
var shown: Array = []
for entry in entries:
var definition: Resource = entry.definition
var entry_visible: bool = GameState.is_building_unlocked(definition.definition_id)
entry.button.visible = entry_visible
entry.label.visible = entry_visible
entry.cost.visible = entry_visible
if entry_visible:
entry.cost.show_price(GameState.building_costs(String(definition.definition_id)))
shown.append(entry)
var columns: int = menu_columns(shown.size())
var rows: int = menu_rows(shown.size())
var spacing: float = menu_column_spacing(columns)
var label_half: float = menu_label_half(columns)
var label_height: float = menu_label_height(columns)
var cost_offset: float = menu_cost_offset(columns)
var cost_height: float = menu_cost_height()
for i in range(shown.size()):
var row: int = i / columns
var in_row: int = mini(shown.size() - row * columns, columns)
var column: int = i % columns
var cx: float = (float(column) - (float(in_row) - 1.0) * 0.5) * spacing
var top: float = menu_row_top(row, rows, columns)
MENU_LAYOUT.place_entry(shown[i].button, cx, top, BUTTON_HALF, BUTTON_HEIGHT)
MENU_LAYOUT.place_entry(shown[i].label, cx, top + LABEL_OFFSET,
label_half, label_height)
MENU_LAYOUT.place_entry(shown[i].cost, cx, top + cost_offset,
COST_HALF, cost_height)

View file

@ -0,0 +1 @@
uid://b4fmal0r6so5

544
ui/menus/BuildMenu.tscn Normal file
View file

@ -0,0 +1,544 @@
[gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://ui/IconButton.gd" id="10"]
[ext_resource type="Script" path="res://ui/NeonPanel.gd" id="20"]
[ext_resource type="Script" path="res://ui/ResourceBar.gd" id="24"]
[ext_resource type="Script" path="res://ui/PriceTag.gd" id="price_tag"]
[ext_resource type="Script" path="res://ui/menus/BuildMenu.gd" id="menu_script"]
[node name="BuildMenu" type="Control" groups=["modal_menus"]]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("menu_script")
[node name="Backdrop" type="ColorRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.94)
[node name="TitleLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 20
offset_right = -24
offset_bottom = 140
text = "Build"
horizontal_alignment = 1
[node name="Bar" type="Control" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24.0
offset_top = 148
offset_right = -24.0
offset_bottom = 330
script = ExtResource("24")
[node name="StatusLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 338
offset_right = -24
offset_bottom = 418
theme_override_font_sizes/font_size = 40
autowrap_mode = 2
text = ""
horizontal_alignment = 1
[node name="MothershipButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -204
offset_right = -76
offset_top = -300
offset_bottom = -172
script = ExtResource("10")
icon = 8
glow_color = Color(0.45, 0.9, 0.55, 1)
[node name="MothershipLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -290
offset_right = 10
offset_top = -166
offset_bottom = -100
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = "Mothership"
horizontal_alignment = 1
[node name="MothershipCost" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -200
offset_right = -80
offset_top = -96
offset_bottom = -44
script = ExtResource("price_tag")
[node name="CrafterButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 76
offset_right = 204
offset_top = -300
offset_bottom = -172
script = ExtResource("10")
icon = 9
glow_color = Color(1, 0.72, 0.25, 1)
[node name="CrafterLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -10
offset_right = 290
offset_top = -166
offset_bottom = -100
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = "Ship Crafter"
horizontal_alignment = 1
[node name="CrafterCost" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 80
offset_right = 200
offset_top = -96
offset_bottom = -44
script = ExtResource("price_tag")
[node name="CommandButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -204
offset_right = -76
offset_top = -10
offset_bottom = 118
script = ExtResource("10")
icon = 10
glow_color = Color(0.55, 0.6, 1, 1)
[node name="CommandLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -290
offset_right = 10
offset_top = 124
offset_bottom = 190
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = "Command Center"
horizontal_alignment = 1
[node name="CommandCost" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -200
offset_right = -80
offset_top = 194
offset_bottom = 246
script = ExtResource("price_tag")
[node name="ExtractorButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 76
offset_right = 204
offset_top = -10
offset_bottom = 118
script = ExtResource("10")
icon = 11
glow_color = Color(0, 0.62, 0.7, 1)
[node name="ExtractorLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -10
offset_right = 290
offset_top = 124
offset_bottom = 190
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = "Extractor"
horizontal_alignment = 1
[node name="ExtractorCost" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 80
offset_right = 200
offset_top = 194
offset_bottom = 246
script = ExtResource("price_tag")
[node name="TransportHubButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -64
offset_right = 64
offset_top = 280
offset_bottom = 408
script = ExtResource("10")
icon = 12
glow_color = Color(0.95, 0.55, 0.85, 1)
[node name="TransportHubLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -150
offset_right = 150
offset_top = 414
offset_bottom = 480
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = "Transport Hub"
horizontal_alignment = 1
[node name="TransportHubCost" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -60
offset_right = 60
offset_top = 484
offset_bottom = 536
script = ExtResource("price_tag")
[node name="ShipLabButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 76
offset_right = 204
offset_top = 280
offset_bottom = 408
script = ExtResource("10")
icon = 13
glow_color = Color(0.55, 0.95, 0.9, 1)
[node name="ShipLabLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -10
offset_right = 290
offset_top = 414
offset_bottom = 480
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = "Ship Lab"
horizontal_alignment = 1
[node name="ShipLabCost" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 80
offset_right = 200
offset_top = 484
offset_bottom = 536
script = ExtResource("price_tag")
[node name="GasBurnerButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -64
offset_right = 64
offset_top = 570
offset_bottom = 698
script = ExtResource("10")
icon = 33
glow_color = Color(0.95, 0.35, 0.15, 1)
[node name="GasBurnerLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -150
offset_right = 150
offset_top = 704
offset_bottom = 770
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = "Gas Burner"
horizontal_alignment = 1
[node name="GasBurnerCost" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -60
offset_right = 60
offset_top = 774
offset_bottom = 826
script = ExtResource("price_tag")
[node name="OutpostButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -64
offset_right = 64
offset_top = 830
offset_bottom = 958
script = ExtResource("10")
icon = 40
glow_color = Color(0.72, 0.86, 0.45, 1)
[node name="OutpostLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -150
offset_right = 150
offset_top = 964
offset_bottom = 1030
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = "Outpost"
horizontal_alignment = 1
[node name="OutpostCost" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -60
offset_right = 60
offset_top = 1034
offset_bottom = 1086
script = ExtResource("price_tag")
[node name="PlanningButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -64
offset_right = 64
offset_top = 1090
offset_bottom = 1218
script = ExtResource("10")
icon = 41
glow_color = Color(0.62, 0.74, 0.92, 1)
[node name="PlanningLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -150
offset_right = 150
offset_top = 1224
offset_bottom = 1290
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = "Planning Center"
horizontal_alignment = 1
[node name="PlanningCost" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -60
offset_right = 60
offset_top = 1294
offset_bottom = 1346
script = ExtResource("price_tag")
[node name="WorkshopButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -64
offset_right = 64
offset_top = 1350
offset_bottom = 1478
script = ExtResource("10")
icon = 42
glow_color = Color(0.5, 0.95, 0.8, 1)
[node name="WorkshopLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -150
offset_right = 150
offset_top = 1484
offset_bottom = 1550
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = "Spacial Workshop"
horizontal_alignment = 1
[node name="WorkshopCost" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -60
offset_right = 60
offset_top = 1554
offset_bottom = 1606
script = ExtResource("price_tag")
[node name="CloseButton" type="Control" parent="."]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -172
offset_right = -24
offset_top = 24
offset_bottom = 172
script = ExtResource("10")
icon = 1
[node name="InfoPopup" type="Control" parent="." groups=["modal_menus"]]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
[node name="Dim" type="ColorRect" parent="InfoPopup"]
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.01, 0.01, 0.03, 0.55)
[node name="Panel" type="Control" parent="InfoPopup"]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -352
offset_right = 352
offset_top = -600
offset_bottom = 600
script = ExtResource("20")
glow_color = Color(0, 0.62, 0.7, 1)
[node name="TitleLabel" type="Label" parent="InfoPopup/Panel"]
mouse_filter = 2
anchor_right = 1.0
offset_left = 44
offset_right = -44
offset_top = 30
offset_bottom = 150
text = ""
horizontal_alignment = 1
[node name="MessageLabel" type="Label" parent="InfoPopup/Panel"]
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 44
offset_right = -44
offset_top = 160
offset_bottom = -440
theme_override_font_sizes/font_size = 48
text = ""
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 2
[node name="CostNumber" type="Control" parent="InfoPopup/Panel"]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = -70
offset_right = 90
offset_top = -424
offset_bottom = -334
script = ExtResource("price_tag")
[node name="BuildButton" type="Control" parent="InfoPopup/Panel"]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = -200
offset_right = -40
offset_top = -300
offset_bottom = -140
script = ExtResource("10")
icon = 5
glow_color = Color(0.45, 0.9, 0.55, 1)
[node name="CloseButton" type="Control" parent="InfoPopup/Panel"]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 40
offset_right = 200
offset_top = -300
offset_bottom = -140
script = ExtResource("10")
icon = 1
glow_color = Color(0, 0.62, 0.7, 1)

144
ui/menus/CraftMenu.gd Normal file
View file

@ -0,0 +1,144 @@
extends "res://ui/menus/ModalMenu.gd"
signal craft_requested(vessel_type: String)
signal queue_cancel_requested(index: int)
const VESSEL_DEFINITIONS = preload("res://vessels/VesselDefinitions.gd")
const MENU_LAYOUT = preload("res://ui/menus/MenuLayout.gd")
const PRICE_TAG = preload("res://ui/PriceTag.gd")
const COLUMNS := 3
const ROW_PITCH := 260.0
const COLUMN_X := 190.0
const BUTTON_HALF := 56.0
const BUTTON_HEIGHT := 100.0
const LABEL_HALF := 92.0
const LABEL_HEIGHT := 44.0
const LABEL_OFFSET := 106.0
const PRICE_HALF := 92.0
const PRICE_HEIGHT := 86.0
const PRICE_OFFSET := 154.0
@onready var status: Label = $StatusLabel
@onready var queue_detail: Label = $QueueDetail
@onready var resource_bar: Control = $Bar
@onready var vessel_buttons: Array = [$InterceptorButton, $ShieldButton,
$GasBomberButton, $CarrierButton, $LaserButton, $DistorderButton]
@onready var vessel_labels: Array = [$InterceptorLabel, $ShieldLabel,
$GasBomberLabel, $CarrierLabel, $LaserLabel, $DistorderLabel]
@onready var queue_slots: Array = [$QueueSlot0, $QueueSlot1, $QueueSlot2, $QueueSlot3]
var building_id: int = 0
var message: String = ""
var message_until: int = 0
var price_tags: Array = []
func _ready() -> void:
super._ready()
_build_price_tags()
for i in range(mini(vessel_buttons.size(), VESSEL_DEFINITIONS.fleet().size())):
var definition: Resource = VESSEL_DEFINITIONS.fleet()[i]
vessel_buttons[i].pressed.connect(
func(): craft_requested.emit(String(definition.definition_id)))
for i in range(queue_slots.size()):
queue_slots[i].pressed.connect(func(): queue_cancel_requested.emit(i))
_layout_entries()
func open_for(next_building_id: int) -> void:
building_id = next_building_id
resource_bar.building_id = building_id
open_menu()
refresh()
func close_menu() -> void:
super.close_menu()
building_id = 0
func say(next_message: String) -> void:
message = next_message
message_until = Time.get_ticks_msec() + 3000
refresh()
func refresh() -> void:
status.text = ""
var definitions: Array = VESSEL_DEFINITIONS.fleet()
for i in range(mini(definitions.size(), vessel_buttons.size())):
var definition: Resource = definitions[i]
var button: Control = vessel_buttons[i]
vessel_labels[i].text = String(definition.display_name)
if i < price_tags.size():
price_tags[i].set_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 message != "":
if Time.get_ticks_msec() < message_until:
status.text = "%s\n%s" % [message, status.text]
else:
message = ""
func refresh_queue() -> void:
var orders: Array = GameState.order_list(building_id)
for i in range(queue_slots.size()):
var slot: Control = 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))
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(vessel_type: String) -> Color:
var definition: Resource = VESSEL_DEFINITIONS.get_definition(vessel_type)
return definition.glow_color if definition != null else Color(0.25, 0.85, 1.0)
func _build_price_tags() -> void:
for i in range(vessel_buttons.size()):
var tag: Control = PRICE_TAG.new()
tag.name = "CraftPrice%d" % i
tag.set_anchors_preset(Control.PRESET_CENTER)
add_child(tag)
price_tags.append(tag)
func row_height() -> float:
return PRICE_OFFSET + PRICE_HEIGHT
func _layout_entries() -> void:
var rows: int = maxi(int(ceil(float(vessel_buttons.size()) / float(COLUMNS))), 1)
var band: Vector2 = MENU_LAYOUT.grid_band(status.offset_bottom, size.y)
for i in range(vessel_buttons.size()):
var row: int = i / COLUMNS
var column: int = i % COLUMNS
var cx: float = (float(column) - 1.0) * COLUMN_X
var top: float = MENU_LAYOUT.grid_row_top(row, rows, row_height(), band, ROW_PITCH)
MENU_LAYOUT.place_entry(vessel_buttons[i], cx, top, BUTTON_HALF, BUTTON_HEIGHT)
MENU_LAYOUT.place_entry(vessel_labels[i], cx, top + LABEL_OFFSET,
LABEL_HALF, LABEL_HEIGHT)
if i < price_tags.size():
MENU_LAYOUT.place_entry(price_tags[i], cx, top + PRICE_OFFSET,
PRICE_HALF, PRICE_HEIGHT)

View file

@ -0,0 +1 @@
uid://5oywglpqem62

308
ui/menus/CraftMenu.tscn Normal file
View file

@ -0,0 +1,308 @@
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://ui/IconButton.gd" id="10"]
[ext_resource type="Script" path="res://ui/ResourceBar.gd" id="24"]
[ext_resource type="Script" path="res://ui/menus/CraftMenu.gd" id="menu_script"]
[node name="CraftMenu" type="Control" groups=["modal_menus"]]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("menu_script")
[node name="Backdrop" type="ColorRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.94)
[node name="TitleLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 20
offset_right = -200
offset_bottom = 82
text = "Build a ship"
horizontal_alignment = 1
[node name="Bar" type="Control" parent="."]
anchor_right = 1.0
mouse_filter = 2
offset_left = 24
offset_top = 148
offset_right = -24
offset_bottom = 330
script = ExtResource("24")
[node name="StatusLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 344
offset_right = -200
offset_bottom = 490
theme_override_font_sizes/font_size = 36
autowrap_mode = 2
text = ""
horizontal_alignment = 1
[node name="InterceptorButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -310
offset_right = -245
offset_top = -100
offset_bottom = 48
script = ExtResource("10")
icon = 2
[node name="InterceptorLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -330
offset_right = -225
offset_top = 56
offset_bottom = 136
theme_override_font_sizes/font_size = 24
text = "Interceptor"
horizontal_alignment = 1
[node name="ShieldButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -221
offset_right = -156
offset_top = -100
offset_bottom = 48
script = ExtResource("10")
icon = 2
glow_color = Color(0.45, 0.8, 1, 1)
[node name="ShieldLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -241
offset_right = -136
offset_top = 56
offset_bottom = 136
theme_override_font_sizes/font_size = 24
autowrap_mode = 2
text = "Shield Vessel"
horizontal_alignment = 1
[node name="GasBomberButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -132
offset_right = -67
offset_top = -100
offset_bottom = 28
script = ExtResource("10")
icon = 31
glow_color = Color(0.96, 0.32, 0.78, 1)
[node name="GasBomberLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -152
offset_right = -47
offset_top = 56
offset_bottom = 150
theme_override_font_sizes/font_size = 24
autowrap_mode = 2
text = "Gas Bomber"
horizontal_alignment = 1
[node name="CarrierButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -43
offset_right = 22
offset_top = -100
offset_bottom = 48
script = ExtResource("10")
icon = 34
glow_color = Color(1, 0.78, 0.3, 1)
[node name="CarrierLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -63
offset_right = 42
offset_top = 56
offset_bottom = 136
theme_override_font_sizes/font_size = 24
autowrap_mode = 2
text = "Carrier"
horizontal_alignment = 1
[node name="LaserButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 46
offset_right = 111
offset_top = -100
offset_bottom = 48
script = ExtResource("10")
icon = 35
glow_color = Color(0.3, 0.9, 1, 1)
[node name="LaserLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 26
offset_right = 131
offset_top = 56
offset_bottom = 136
theme_override_font_sizes/font_size = 24
autowrap_mode = 2
text = "Laser Ship"
horizontal_alignment = 1
[node name="DistorderButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 135
offset_right = 200
offset_top = -100
offset_bottom = 48
script = ExtResource("10")
icon = 37
glow_color = Color(0.7, 0.3, 1, 1)
[node name="DistorderLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 115
offset_right = 220
offset_top = 56
offset_bottom = 136
theme_override_font_sizes/font_size = 24
autowrap_mode = 2
text = "Distorder"
horizontal_alignment = 1
[node name="QueueLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -200
offset_right = -10
offset_top = -450
offset_bottom = -390
theme_override_font_sizes/font_size = 26
autowrap_mode = 2
horizontal_alignment = 1
text = "Queue"
[node name="QueueDetail" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 24
offset_right = -24
offset_top = -150
offset_bottom = -30
theme_override_font_sizes/font_size = 26
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="QueueSlot0" type="Control" parent="."]
visible = false
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -140
offset_right = -30
offset_top = -380
offset_bottom = -270
script = ExtResource("10")
icon = 2
glow_color = Color(0.25, 0.85, 1, 1)
[node name="QueueSlot1" type="Control" parent="."]
visible = false
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -140
offset_right = -30
offset_top = -262
offset_bottom = -152
script = ExtResource("10")
icon = 2
glow_color = Color(0.25, 0.85, 1, 1)
[node name="QueueSlot2" type="Control" parent="."]
visible = false
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -140
offset_right = -30
offset_top = -144
offset_bottom = -34
script = ExtResource("10")
icon = 2
glow_color = Color(0.25, 0.85, 1, 1)
[node name="QueueSlot3" type="Control" parent="."]
visible = false
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -140
offset_right = -30
offset_top = -26
offset_bottom = 84
script = ExtResource("10")
icon = 2
glow_color = Color(0.25, 0.85, 1, 1)
[node name="CloseButton" type="Control" parent="."]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -140
offset_right = -24
offset_top = 20
offset_bottom = 136
script = ExtResource("10")
icon = 1

36
ui/menus/FormationMenu.gd Normal file
View file

@ -0,0 +1,36 @@
extends "res://ui/menus/ModalMenu.gd"
signal formation_changed
signal reset_requested
@onready var editor: Control = $Editor
@onready var reset_button: Control = $ResetButton
func _ready() -> void:
super._ready()
reset_button.pressed.connect(func(): reset_requested.emit())
editor.formation_changed.connect(func(): formation_changed.emit())
func open_menu() -> void:
super.open_menu()
editor.queue_redraw()
func redraw() -> void:
editor.queue_redraw()
func handle_unhandled_input(event: InputEvent) -> bool:
if not visible:
return false
if event is InputEventScreenTouch:
editor.handle_touch(event)
elif event is InputEventScreenDrag:
editor.handle_drag(event)
elif event is InputEventMouseButton:
editor.handle_mouse_button(event)
elif event is InputEventMouseMotion:
editor.handle_mouse_motion(event)
return true

View file

@ -0,0 +1 @@
uid://bigv0ubmyddob

View file

@ -0,0 +1,90 @@
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://ui/IconButton.gd" id="10"]
[ext_resource type="Script" path="res://ui/FormationEditor.gd" id="17"]
[ext_resource type="Script" path="res://ui/menus/FormationMenu.gd" id="menu_script"]
[node name="FormationMenu" type="Control" groups=["modal_menus"]]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("menu_script")
[node name="Backdrop" type="ColorRect" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.94)
[node name="TitleLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 70
offset_right = -24
offset_bottom = 220
theme_override_font_sizes/font_size = 44
autowrap_mode = 2
text = "Fleet formation - drag to arrange"
horizontal_alignment = 1
[node name="Editor" type="Control" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -300
offset_right = 300
offset_top = -300
offset_bottom = 300
script = ExtResource("17")
[node name="ResetButton" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = -74
offset_right = 74
offset_top = -270
offset_bottom = -122
script = ExtResource("10")
icon = 3
[node name="ResetLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = -180
offset_right = 180
offset_top = -114
offset_bottom = -8
theme_override_font_sizes/font_size = 60
text = "Reset"
horizontal_alignment = 1
[node name="CloseButton" type="Control" parent="."]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -172
offset_right = -24
offset_top = 24
offset_bottom = 172
script = ExtResource("10")
icon = 1
[node name="FadeOverlay" type="CanvasLayer" parent="."]
layer = 10
[node name="FadeRect" type="ColorRect" parent="FadeOverlay"]
mouse_filter = 2
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 0)

39
ui/menus/GarrisonMenu.gd Normal file
View file

@ -0,0 +1,39 @@
extends "res://ui/menus/ModalMenu.gd"
@onready var panel: Control = $Panel
@onready var status: Label = $StatusLabel
var building_id: int = 0
func _ready() -> void:
super._ready()
panel.changed.connect(refresh)
func open_for(next_building_id: int) -> void:
building_id = next_building_id
panel.building_id = building_id
open_menu()
refresh()
func close_menu() -> void:
super.close_menu()
building_id = 0
panel.building_id = 0
func refresh() -> void:
if building_id == 0:
return
panel.building_id = building_id
panel.refresh()
var ordered: int = GameState.garrison_desired_total(building_id)
var crewed: int = GameState.garrison_crew(building_id).size()
if ordered == 0:
status.text = "No vessels ordered. Only armed vessels can hold a position."
elif crewed >= ordered:
status.text = "Garrison at full strength"
else:
status.text = "%d of %d berths crewed - vessels are sent from the mothership as they become available" % [crewed, ordered]

View file

@ -0,0 +1 @@
uid://cdfkcdqvwdsh3

View file

@ -0,0 +1,62 @@
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://ui/IconButton.gd" id="10"]
[ext_resource type="Script" path="res://ui/GarrisonPanel.gd" id="33"]
[ext_resource type="Script" path="res://ui/menus/GarrisonMenu.gd" id="menu_script"]
[node name="GarrisonMenu" type="Control" groups=["modal_menus"]]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("menu_script")
[node name="Backdrop" type="ColorRect" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.94)
[node name="TitleLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24.0
offset_top = 20.0
offset_right = -200.0
offset_bottom = 82.0
text = "Outpost Garrison"
horizontal_alignment = 1
[node name="Panel" type="Control" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 40.0
offset_top = 120.0
offset_right = -40.0
offset_bottom = 620.0
script = ExtResource("33")
[node name="StatusLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 32.0
offset_top = -160.0
offset_right = -32.0
offset_bottom = -24.0
theme_override_font_sizes/font_size = 30
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="CloseButton" type="Control" parent="."]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -140.0
offset_top = 20.0
offset_right = -24.0
offset_bottom = 136.0
script = ExtResource("10")
icon = 1
glow_color = Color(0.72, 0.86, 0.45, 1)

138
ui/menus/HangarMenu.gd Normal file
View file

@ -0,0 +1,138 @@
extends "res://ui/menus/ModalMenu.gd"
signal crew_requested(vessel_type: String)
signal store_requested(vessel_type: String)
const VESSEL_DEFINITIONS = preload("res://vessels/VesselDefinitions.gd")
const RULE_COLOR := Color(0.45, 0.9, 0.55)
const COLUMNS := 3
const LABEL_GAP := 2.0
const DIVIDER_GAP := 10.0
const CAPTION_LINES := 3.0
const CAPTION_MIN := 13
const CAPTION_MAX := 22
const LINE_RATIO := 1.25
@onready var status: Label = $StatusLabel
@onready var available_title: Label = $AvailableTitle
@onready var fleet_title: Label = $FleetTitle
@onready var available_slots: Array = [$AvailableInterceptor, $AvailableShield,
$AvailableGasBomber, $AvailableCarrier, $AvailableLaser, $AvailableDistorder]
@onready var available_labels: Array = [$AvailableInterceptorLabel, $AvailableShieldLabel,
$AvailableGasBomberLabel, $AvailableCarrierLabel, $AvailableLaserLabel,
$AvailableDistorderLabel]
@onready var fleet_slots: Array = [$FleetInterceptor, $FleetShield, $FleetGasBomber,
$FleetCarrier, $FleetLaser, $FleetDistorder]
@onready var fleet_labels: Array = [$FleetInterceptorLabel, $FleetShieldLabel,
$FleetGasBomberLabel, $FleetCarrierLabel, $FleetLaserLabel, $FleetDistorderLabel]
var divider_y: float = 0.0
func _ready() -> void:
super._ready()
for i in range(available_slots.size()):
available_slots[i].pressed.connect(
func(): crew_requested.emit(_vessel_types()[i]))
fleet_slots[i].pressed.connect(
func(): store_requested.emit(_vessel_types()[i]))
_layout_sections()
func open_for(fleet: Node) -> void:
status.text = ""
open_menu()
refresh(fleet)
func set_status(message: String) -> void:
status.text = message
func refresh(fleet: Node) -> void:
var types: Array = _vessel_types()
for i in range(mini(types.size(), available_slots.size())):
var vessel_type: String = types[i]
var definition: Resource = VESSEL_DEFINITIONS.get_definition(vessel_type)
var available: int = GameState.stored_vessels.count(vessel_type)
var deployed: int = 0
for ship in fleet.ships:
if is_instance_valid(ship) and String(ship.vessel_type) == vessel_type:
deployed += 1
available_labels[i].text = "%s\n%d available" % [definition.display_name, available]
fleet_labels[i].text = "%s\n%d in fleet" % [definition.display_name, deployed]
for slot in [available_slots[i], fleet_slots[i]]:
slot.icon = definition.icon
slot.glow_color = definition.glow_color
available_slots[i].enabled = available > 0 \
and fleet.ships.size() < GameState.max_fleet_size()
fleet_slots[i].enabled = deployed > 0 and fleet.ships.size() > 1
available_slots[i].queue_redraw()
fleet_slots[i].queue_redraw()
func _vessel_types() -> Array:
var types: Array = []
for definition in VESSEL_DEFINITIONS.fleet():
types.append(String(definition.definition_id))
return types
func _layout_sections() -> void:
var count: int = available_slots.size()
if count == 0:
return
var columns: int = mini(COLUMNS, count)
var rows: int = int(ceil(float(count) / float(columns)))
var top: float = available_title.position.y + available_title.size.y + 8.0
var bottom: float = status.position.y - 12.0
var title_height: float = fleet_title.size.y
var section_height: float = (bottom - top - title_height - 16.0) * 0.5
if section_height <= 0.0:
return
var cell_width: float = size.x / float(columns)
var cell_height: float = section_height / float(rows)
var slot_size: float = minf(cell_height * 0.46, cell_width * 0.40)
var label_height: float = maxf(cell_height - slot_size - LABEL_GAP * 2.0, 1.0)
_layout_section(available_slots, available_labels, top, columns, cell_width,
cell_height, slot_size, label_height)
var available_bottom: float = top + section_height
divider_y = available_bottom + DIVIDER_GAP
fleet_title.position.y = divider_y + DIVIDER_GAP
var fleet_top: float = fleet_title.position.y + title_height + 8.0
_layout_section(fleet_slots, fleet_labels, fleet_top, columns, cell_width,
cell_height, slot_size, label_height)
queue_redraw()
func _layout_section(slots: Array, labels: Array, top: float, columns: int,
cell_width: float, cell_height: float, slot_size: float,
label_height: float) -> void:
if columns <= 0:
return
for i in range(slots.size()):
var column: int = i % columns
var row: int = i / columns
var cell_x: float = float(column) * cell_width
var cell_y: float = top + float(row) * cell_height
slots[i].position = Vector2(cell_x + (cell_width - slot_size) * 0.5, cell_y)
slots[i].size = Vector2(slot_size, slot_size)
labels[i].position = Vector2(cell_x, cell_y + slot_size + LABEL_GAP)
labels[i].size = Vector2(cell_width, label_height)
labels[i].horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
labels[i].vertical_alignment = VERTICAL_ALIGNMENT_TOP
labels[i].autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
labels[i].add_theme_font_size_override("font_size", caption_size(label_height))
labels[i].clip_text = true
func caption_size(label_height: float) -> int:
return clampi(int(label_height / (CAPTION_LINES * LINE_RATIO)),
CAPTION_MIN, CAPTION_MAX)
func _draw() -> void:
if divider_y <= 0.0:
return
NeonDraw.draw_glowing_line(self, Vector2(24.0, divider_y),
Vector2(size.x - 24.0, divider_y), RULE_COLOR, 0.5, 0.4)

View file

@ -0,0 +1 @@
uid://c386w72vtqmdw

388
ui/menus/HangarMenu.tscn Normal file
View file

@ -0,0 +1,388 @@
[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://ui/IconButton.gd" id="10"]
[ext_resource type="Script" path="res://ui/menus/HangarMenu.gd" id="39"]
[node name="HangarMenu" type="Control" groups=["modal_menus"]]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("39")
[node name="Backdrop" type="ColorRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.94)
[node name="TitleLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 60
offset_right = -210
offset_bottom = 170
text = "Hangar"
horizontal_alignment = 1
[node name="ListLabel" type="Label" parent="."]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 32
offset_top = 200
offset_right = -32
offset_bottom = -420
theme_override_font_sizes/font_size = 30
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="StatusLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 32
offset_top = -410
offset_right = -32
offset_bottom = -300
theme_override_font_sizes/font_size = 30
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="NextButton" type="Control" parent="."]
visible = false
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -230
offset_right = -110
offset_top = -280
offset_bottom = -160
script = ExtResource("10")
icon = 6
glow_color = Color(0.55, 0.95, 0.9, 1)
[node name="CrewButton" type="Control" parent="."]
visible = false
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = 110
offset_right = 230
offset_top = -280
offset_bottom = -160
script = ExtResource("10")
icon = 2
glow_color = Color(0.45, 0.9, 0.55, 1)
[node name="HintLabel" type="Label" parent="."]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 24
offset_top = -150
offset_right = -24
offset_bottom = -60
theme_override_font_sizes/font_size = 28
autowrap_mode = 2
horizontal_alignment = 1
text = "Next Crew"
[node name="AvailableTitle" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_top = 190
offset_bottom = 240
theme_override_font_sizes/font_size = 28
text = "AVAILABLE — TAP TO CREW"
horizontal_alignment = 1
[node name="AvailableInterceptor" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -290
offset_right = -215
offset_top = 255
offset_bottom = 375
script = ExtResource("10")
icon = 2
[node name="AvailableInterceptorLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -330
offset_right = -175
offset_top = 380
offset_bottom = 460
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="AvailableShield" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -189
offset_right = -114
offset_top = 255
offset_bottom = 375
script = ExtResource("10")
icon = 2
glow_color = Color(0.45, 0.8, 1, 1)
[node name="AvailableShieldLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -229
offset_right = -74
offset_top = 380
offset_bottom = 460
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="AvailableGasBomber" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -88
offset_right = -13
offset_top = 255
offset_bottom = 375
script = ExtResource("10")
icon = 31
glow_color = Color(0.96, 0.32, 0.78, 1)
[node name="AvailableGasBomberLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -128
offset_right = 27
offset_top = 380
offset_bottom = 460
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="AvailableCarrier" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 13
offset_right = 88
offset_top = 255
offset_bottom = 375
script = ExtResource("10")
icon = 34
glow_color = Color(1, 0.78, 0.3, 1)
[node name="AvailableCarrierLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -27
offset_right = 128
offset_top = 380
offset_bottom = 460
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="AvailableLaser" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 114
offset_right = 189
offset_top = 255
offset_bottom = 375
script = ExtResource("10")
icon = 35
glow_color = Color(0.3, 0.9, 1, 1)
[node name="AvailableLaserLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = 74
offset_right = 229
offset_top = 380
offset_bottom = 460
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="AvailableDistorder" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 215
offset_right = 290
offset_top = 255
offset_bottom = 375
script = ExtResource("10")
icon = 37
glow_color = Color(0.7, 0.3, 1, 1)
[node name="AvailableDistorderLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = 175
offset_right = 330
offset_top = 380
offset_bottom = 460
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="FleetTitle" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_top = 500
offset_bottom = 550
theme_override_font_sizes/font_size = 28
text = "FLEET — TAP TO STORE"
horizontal_alignment = 1
[node name="FleetInterceptor" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -290
offset_right = -215
offset_top = 565
offset_bottom = 685
script = ExtResource("10")
icon = 2
[node name="FleetInterceptorLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -330
offset_right = -175
offset_top = 690
offset_bottom = 770
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="FleetShield" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -189
offset_right = -114
offset_top = 565
offset_bottom = 685
script = ExtResource("10")
icon = 2
glow_color = Color(0.45, 0.8, 1, 1)
[node name="FleetShieldLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -229
offset_right = -74
offset_top = 690
offset_bottom = 770
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="FleetGasBomber" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -88
offset_right = -13
offset_top = 565
offset_bottom = 685
script = ExtResource("10")
icon = 31
glow_color = Color(0.96, 0.32, 0.78, 1)
[node name="FleetGasBomberLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -128
offset_right = 27
offset_top = 690
offset_bottom = 770
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="FleetCarrier" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 13
offset_right = 88
offset_top = 565
offset_bottom = 685
script = ExtResource("10")
icon = 34
glow_color = Color(1, 0.78, 0.3, 1)
[node name="FleetCarrierLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -27
offset_right = 128
offset_top = 690
offset_bottom = 770
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="FleetLaser" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 114
offset_right = 189
offset_top = 565
offset_bottom = 685
script = ExtResource("10")
icon = 35
glow_color = Color(0.3, 0.9, 1, 1)
[node name="FleetLaserLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = 74
offset_right = 229
offset_top = 690
offset_bottom = 770
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="FleetDistorder" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 215
offset_right = 290
offset_top = 565
offset_bottom = 685
script = ExtResource("10")
icon = 37
glow_color = Color(0.7, 0.3, 1, 1)
[node name="FleetDistorderLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = 175
offset_right = 330
offset_top = 690
offset_bottom = 770
theme_override_font_sizes/font_size = 22
horizontal_alignment = 1
[node name="CloseButton" type="Control" parent="."]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -172
offset_top = 24
offset_right = -24
offset_bottom = 172
script = ExtResource("10")
icon = 1
glow_color = Color(0.45, 0.9, 0.55, 1)

157
ui/menus/LabMenu.gd Normal file
View file

@ -0,0 +1,157 @@
extends "res://ui/menus/ModalMenu.gd"
signal vessel_selected(index: int)
signal upgrade_selected(index: int)
const PRICE_TAG = preload("res://ui/PriceTag.gd")
const TAG_HEIGHT := 38.0
const LABEL_HEIGHT := 40.0
const TAG_PADDING := 8.0
const ROW_GAP := 18.0
const VESSEL_HALF := 30.0
const VESSEL_SPACING := 90.0
const VESSEL_MARGIN := 24.0
@onready var vessel_buttons: Array = [$VesselA, $VesselB, $VesselC, $VesselD,
$VesselE, $VesselF, $VesselG, $VesselH]
@onready var vessel_labels: Array = [$VesselALabel, $VesselBLabel, $VesselCLabel,
$VesselDLabel, $VesselELabel, $VesselFLabel, $VesselGLabel, $VesselHLabel]
@onready var upgrade_buttons: Array = [$Upgrade0, $Upgrade1, $Upgrade2, $Upgrade3]
@onready var upgrade_labels: Array = [$Upgrade0Label, $Upgrade1Label,
$Upgrade2Label, $Upgrade3Label]
@onready var vessel_heading: Label = $VesselLabel
@onready var detail: Label = $DetailLabel
@onready var status: Label = $StatusLabel
@onready var resource_bar: Control = $Bar
var building_id: int = 0
var price_tags: Array = []
func _ready() -> void:
super._ready()
_build_price_tags()
for i in range(vessel_buttons.size()):
vessel_buttons[i].pressed.connect(func(): vessel_selected.emit(i))
for i in range(upgrade_buttons.size()):
upgrade_buttons[i].pressed.connect(func(): upgrade_selected.emit(i))
func open_for(next_building_id: int) -> void:
building_id = next_building_id
resource_bar.building_id = building_id
open_menu()
func close_menu() -> void:
super.close_menu()
building_id = 0
func render_vessels(definitions: Array, selected_type: String) -> void:
for i in range(mini(vessel_buttons.size(), definitions.size())):
var definition: Resource = definitions[i]
vessel_buttons[i].enabled = true
vessel_buttons[i].icon = definition.icon
vessel_buttons[i].glow_color = definition.glow_color
vessel_buttons[i].highlighted = String(definition.definition_id) == selected_type
vessel_labels[i].text = definition.display_name
vessel_buttons[i].queue_redraw()
_layout_vessels()
func render_options(options: Array, selected: int, vessel_colour: Color) -> void:
for i in range(upgrade_buttons.size()):
var shown: bool = i < options.size()
upgrade_buttons[i].visible = shown
upgrade_labels[i].visible = shown
price_tags[i].visible = shown
if not shown:
continue
var option: Dictionary = options[i]
var maxed: bool = bool(option.get("maxed", false))
upgrade_labels[i].text = String(option.label)
price_tags[i].visible = not maxed
price_tags[i].show_price({} if maxed else option.costs,
0 if maxed else int(option.points))
upgrade_buttons[i].icon = int(option.icon)
upgrade_buttons[i].glow_color = vessel_colour
upgrade_buttons[i].highlighted = i == selected
upgrade_buttons[i].enabled = true
upgrade_buttons[i].queue_redraw()
_resize_price_tags()
func show_detail(heading: String, description: String, points: int) -> void:
detail.text = "%s\n%s" % [heading, description]
if points > 0:
detail.text += "\nResearch points: %d/%d" % [GameState.research_points, points]
func clear_detail() -> void:
detail.text = ""
func set_status(message: String) -> void:
status.text = message
func _build_price_tags() -> void:
for i in range(upgrade_labels.size()):
var label: Control = upgrade_labels[i]
var tag: Control = PRICE_TAG.new()
tag.name = "LabPrice%d" % i
tag.anchor_left = label.anchor_left
tag.anchor_right = label.anchor_right
tag.anchor_top = label.anchor_top
tag.anchor_bottom = label.anchor_bottom
tag.offset_left = label.offset_left
tag.offset_right = label.offset_right
label.offset_bottom = label.offset_top + LABEL_HEIGHT
tag.offset_top = label.offset_bottom + 2.0
tag.offset_bottom = tag.offset_top + tag_height()
add_child(tag)
price_tags.append(tag)
func tag_height() -> float:
var tallest: float = TAG_HEIGHT
for tag in price_tags:
if tag != null and tag.has_method("wanted_height"):
tallest = maxf(tallest, float(tag.wanted_height()) + TAG_PADDING)
return tallest
func _layout_vessels() -> void:
var shown: Array = []
for i in range(vessel_buttons.size()):
if vessel_buttons[i].visible:
shown.append(i)
if shown.is_empty():
return
var room: float = size.x - VESSEL_MARGIN * 2.0
if room <= 0.0:
room = 672.0
var spacing: float = minf(VESSEL_SPACING, room / float(shown.size()))
for slot in range(shown.size()):
var i: int = shown[slot]
var cx: float = (float(slot) - (float(shown.size()) - 1.0) * 0.5) * spacing
vessel_buttons[i].offset_left = cx - VESSEL_HALF
vessel_buttons[i].offset_right = cx + VESSEL_HALF
vessel_labels[i].offset_left = cx - spacing * 0.5
vessel_labels[i].offset_right = cx + spacing * 0.5
func _resize_price_tags() -> void:
var height: float = tag_height()
for tag in price_tags:
tag.offset_bottom = tag.offset_top + height
if price_tags.size() < 3:
return
var shift: float = price_tags[0].offset_bottom + ROW_GAP - upgrade_buttons[2].offset_top
if absf(shift) < 0.5:
return
for i in range(2, upgrade_buttons.size()):
for node in [upgrade_buttons[i], upgrade_labels[i], price_tags[i]]:
node.offset_top += shift
node.offset_bottom += shift

1
ui/menus/LabMenu.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://dq44uh8vf0qp0

426
ui/menus/LabMenu.tscn Normal file
View file

@ -0,0 +1,426 @@
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://ui/IconButton.gd" id="10"]
[ext_resource type="Script" path="res://ui/ResourceBar.gd" id="24"]
[ext_resource type="Script" path="res://ui/menus/LabMenu.gd" id="menu_script"]
[node name="LabMenu" type="Control" groups=["modal_menus"]]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("menu_script")
[node name="Backdrop" type="ColorRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.94)
[node name="TitleLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 20
offset_right = -200
offset_bottom = 82
text = "Ship Lab"
horizontal_alignment = 1
[node name="Bar" type="Control" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 148
offset_right = -24
offset_bottom = 330
script = ExtResource("24")
[node name="VesselA" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -300
offset_right = -240
offset_top = 346
offset_bottom = 466
script = ExtResource("10")
icon = 2
glow_color = Color(0.25, 0.85, 1, 1)
[node name="VesselB" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -210
offset_right = -150
offset_top = 346
offset_bottom = 466
script = ExtResource("10")
icon = 2
glow_color = Color(0.45, 0.8, 1, 1)
[node name="VesselC" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -120
offset_right = -60
offset_top = 346
offset_bottom = 466
script = ExtResource("10")
icon = 31
glow_color = Color(0.96, 0.32, 0.78, 1)
[node name="VesselD" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -30
offset_right = 30
offset_top = 346
offset_bottom = 466
script = ExtResource("10")
icon = 12
glow_color = Color(0.95, 0.55, 0.85, 1)
[node name="VesselE" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 60
offset_right = 120
offset_top = 346
offset_bottom = 466
script = ExtResource("10")
icon = 34
glow_color = Color(1, 0.78, 0.3, 1)
[node name="VesselF" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 150
offset_right = 210
offset_top = 346
offset_bottom = 466
script = ExtResource("10")
icon = 35
glow_color = Color(0.3, 0.9, 1, 1)
[node name="VesselG" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 240
offset_right = 300
offset_top = 346
offset_bottom = 466
script = ExtResource("10")
icon = 37
glow_color = Color(0.7, 0.3, 1, 1)
[node name="VesselH" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 320
offset_right = 380
offset_top = 346
offset_bottom = 466
script = ExtResource("10")
icon = 43
glow_color = Color(0.5, 0.95, 0.8, 1)
[node name="VesselLabel" type="Label" parent="."]
visible = false
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 470
offset_right = -24
offset_bottom = 528
theme_override_font_sizes/font_size = 34
horizontal_alignment = 1
text = ""
[node name="VesselALabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -310
offset_right = -230
offset_top = 470
offset_bottom = 542
theme_override_font_sizes/font_size = 15
autowrap_mode = 2
horizontal_alignment = 1
text = "Interceptor"
[node name="VesselBLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -220
offset_right = -140
offset_top = 470
offset_bottom = 542
theme_override_font_sizes/font_size = 15
autowrap_mode = 2
horizontal_alignment = 1
text = "Shield"
[node name="VesselCLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -130
offset_right = -50
offset_top = 470
offset_bottom = 542
theme_override_font_sizes/font_size = 15
autowrap_mode = 2
horizontal_alignment = 1
text = "Gas Bomber"
[node name="VesselDLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -40
offset_right = 40
offset_top = 470
offset_bottom = 542
theme_override_font_sizes/font_size = 15
autowrap_mode = 2
horizontal_alignment = 1
text = "Transporter"
[node name="VesselELabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = 50
offset_right = 130
offset_top = 470
offset_bottom = 542
theme_override_font_sizes/font_size = 15
autowrap_mode = 2
horizontal_alignment = 1
text = "Carrier"
[node name="VesselFLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = 140
offset_right = 220
offset_top = 470
offset_bottom = 542
theme_override_font_sizes/font_size = 15
autowrap_mode = 2
horizontal_alignment = 1
text = "Laser Ship"
[node name="VesselGLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = 230
offset_right = 310
offset_top = 470
offset_bottom = 542
theme_override_font_sizes/font_size = 15
autowrap_mode = 2
horizontal_alignment = 1
text = "Distorder"
[node name="VesselHLabel" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = 310
offset_right = 390
offset_top = 470
offset_bottom = 542
theme_override_font_sizes/font_size = 15
autowrap_mode = 2
horizontal_alignment = 1
text = "Repair Drone"
[node name="VesselUpgradeSeparator" type="ColorRect" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 48
offset_top = 550
offset_right = -48
offset_bottom = 554
color = Color(0.25, 0.85, 1, 0.6)
[node name="UpgradeList" type="Label" parent="."]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 32
offset_top = 562
offset_right = -32
offset_bottom = -390
theme_override_font_sizes/font_size = 30
autowrap_mode = 2
text = ""
[node name="DetailLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 32
offset_top = -300
offset_right = -32
offset_bottom = -180
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="StatusLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 32
offset_top = -174
offset_right = -32
offset_bottom = -120
theme_override_font_sizes/font_size = 30
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="NextButton" type="Control" parent="."]
visible = false
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -230
offset_right = -110
offset_top = -112
offset_bottom = -22
script = ExtResource("10")
icon = 6
glow_color = Color(0.55, 0.95, 0.9, 1)
[node name="HintLabel" type="Label" parent="."]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 24
offset_top = -102
offset_right = -24
offset_bottom = -22
theme_override_font_sizes/font_size = 28
autowrap_mode = 2
horizontal_alignment = 1
text = "Next upgrade Buy"
[node name="Upgrade0" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -240
offset_right = -120
offset_top = 562
offset_bottom = 672
script = ExtResource("10")
icon = 13
glow_color = Color(0.25, 0.85, 1, 1)
[node name="Upgrade0Label" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -310
offset_right = -50
offset_top = 676
offset_bottom = 746
theme_override_font_sizes/font_size = 22
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="Upgrade1" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 120
offset_right = 240
offset_top = 562
offset_bottom = 672
script = ExtResource("10")
icon = 13
glow_color = Color(0.25, 0.85, 1, 1)
[node name="Upgrade1Label" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = 50
offset_right = 310
offset_top = 676
offset_bottom = 746
theme_override_font_sizes/font_size = 22
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="Upgrade2" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -240
offset_right = -120
offset_top = 790
offset_bottom = 900
script = ExtResource("10")
icon = 13
glow_color = Color(0.25, 0.85, 1, 1)
[node name="Upgrade2Label" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -310
offset_right = -50
offset_top = 904
offset_bottom = 974
theme_override_font_sizes/font_size = 22
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="Upgrade3" type="Control" parent="."]
anchor_left = 0.5
anchor_right = 0.5
offset_left = 120
offset_right = 240
offset_top = 790
offset_bottom = 900
script = ExtResource("10")
icon = 13
glow_color = Color(0.25, 0.85, 1, 1)
[node name="Upgrade3Label" type="Label" parent="."]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = 50
offset_right = 310
offset_top = 904
offset_bottom = 974
theme_override_font_sizes/font_size = 22
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="CloseButton" type="Control" parent="."]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -140
offset_top = 20
offset_right = -24
offset_bottom = 136
script = ExtResource("10")
icon = 1
glow_color = Color(0.55, 0.95, 0.9, 1)

27
ui/menus/MenuLayout.gd Normal file
View file

@ -0,0 +1,27 @@
extends RefCounted
const HEADER_GAP: float = 16.0
const BOTTOM_MARGIN: float = 28.0
static func grid_band(header_bottom: float, panel_height: float) -> Vector2:
var half: float = panel_height * 0.5
if half <= 0.0:
half = 640.0
return Vector2(header_bottom + HEADER_GAP - half, half - BOTTOM_MARGIN)
static func grid_row_top(row: int, rows: int, row_height: float, band: Vector2,
natural_pitch: float) -> float:
var pitch: float = natural_pitch
if rows > 1:
pitch = minf(pitch, (band.y - band.x - row_height) / float(rows - 1))
return band.x + maxf(pitch, row_height) * float(row)
static 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

View file

@ -0,0 +1 @@
uid://mhjg8jhbmy6f

24
ui/menus/ModalMenu.gd Normal file
View file

@ -0,0 +1,24 @@
extends Control
signal closed
@onready var close_button: Control = get_node_or_null("CloseButton")
func _ready() -> void:
visible = false
if close_button != null and close_button.has_signal("pressed"):
close_button.pressed.connect(close_menu)
func open_menu() -> void:
visible = true
func close_menu() -> void:
visible = false
closed.emit()
func handle_unhandled_input(_event: InputEvent) -> bool:
return visible

View file

@ -0,0 +1 @@
uid://dkpcfwj4unbbf

25
ui/menus/PlanningMenu.gd Normal file
View file

@ -0,0 +1,25 @@
extends "res://ui/menus/ModalMenu.gd"
@onready var panel: Control = $Panel
@onready var status: Label = $StatusLabel
func _ready() -> void:
super._ready()
panel.changed.connect(refresh)
func open_menu() -> void:
super.open_menu()
refresh()
func refresh() -> void:
panel.refresh()
var short: PackedStringArray = PackedStringArray()
for vessel_type in GameState.planned_vessel_types():
var missing: int = GameState.vessel_shortfall(String(vessel_type))
if missing > 0:
short.append("%d %s" % [missing, GameState.vessel_name(String(vessel_type))])
status.text = "Every standing order is met or already being built" if short.is_empty() \
else "Short: %s" % ", ".join(short)

View file

@ -0,0 +1 @@
uid://b80lmxhmmqw8a

View file

@ -0,0 +1,63 @@
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://ui/IconButton.gd" id="10"]
[ext_resource type="Script" path="res://ui/PlanningPanel.gd" id="35"]
[ext_resource type="Script" path="res://ui/menus/PlanningMenu.gd" id="menu_script"]
[node name="PlanningMenu" type="Control" groups=["modal_menus"]]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("menu_script")
[node name="Backdrop" type="ColorRect" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.94)
[node name="TitleLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24.0
offset_top = 20.0
offset_right = -200.0
offset_bottom = 82.0
text = "Planning Center"
horizontal_alignment = 1
[node name="Panel" type="Control" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 40.0
offset_top = 120.0
offset_right = -40.0
offset_bottom = -180.0
anchor_bottom = 1.0
script = ExtResource("35")
[node name="StatusLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 32.0
offset_top = -160.0
offset_right = -32.0
offset_bottom = -24.0
theme_override_font_sizes/font_size = 28
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="CloseButton" type="Control" parent="."]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -140.0
offset_top = 20.0
offset_right = -24.0
offset_bottom = 136.0
script = ExtResource("10")
icon = 1
glow_color = Color(0.62, 0.74, 0.92, 1)

70
ui/menus/SkillMenu.gd Normal file
View file

@ -0,0 +1,70 @@
extends "res://ui/menus/ModalMenu.gd"
signal research_requested(id: String)
@onready var tree: Control = $Tree
@onready var detail: Label = $DetailLabel
@onready var cost: Control = $CostNumber
@onready var status: Label = $StatusLabel
@onready var resource_bar: Control = $Bar
func _ready() -> void:
super._ready()
tree.node_picked.connect(refresh)
tree.node_confirmed.connect(func(id: String): research_requested.emit(id))
func open_for(building_id: int) -> void:
resource_bar.building_id = building_id
open_menu()
tree.reset_scroll()
refresh()
func selected_id() -> String:
return String(tree.selected_id)
func research_completed(id: String) -> void:
tree.queue_redraw()
refresh(id)
func refresh(id: String = "") -> void:
if id == "":
id = String(tree.selected_id)
if id == "":
detail.text = "Select a node to see what it unlocks."
cost.show_price({})
status.text = "Fleet capacity %d · Research points %d" \
% [GameState.max_fleet_size(), GameState.research_points]
return
var node: Dictionary = GameState.skill_node(id)
if node.is_empty():
return
detail.text = "%s\n%s" % [node.name, node.desc]
cost.show_price(GameState.skill_costs(id), GameState.skill_research_point_cost(id))
if GameState.is_unlocked(id):
status.text = "Researched"
elif not GameState.prerequisite_met(id):
status.text = "Needs %s first" % GameState.skill_node(node.requires).name
elif not cost.is_affordable():
status.text = ""
else:
status.text = "Tap again to research"
func handle_unhandled_input(event: InputEvent) -> bool:
if not visible:
return false
if event is InputEventScreenTouch:
tree.handle_touch(event)
elif event is InputEventScreenDrag:
tree.handle_drag(event)
elif event is InputEventMouseButton:
tree.handle_mouse_button(event)
elif event is InputEventMouseMotion:
tree.handle_mouse_motion(event)
return true

View file

@ -0,0 +1 @@
uid://bhgf1risnbb03

92
ui/menus/SkillMenu.tscn Normal file
View file

@ -0,0 +1,92 @@
[gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://ui/IconButton.gd" id="10"]
[ext_resource type="Script" path="res://ui/SkillTreeView.gd" id="21"]
[ext_resource type="Script" path="res://ui/ResourceBar.gd" id="24"]
[ext_resource type="Script" path="res://ui/PriceTag.gd" id="price_tag"]
[ext_resource type="Script" path="res://ui/menus/SkillMenu.gd" id="menu_script"]
[node name="SkillMenu" type="Control" groups=["modal_menus"]]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("menu_script")
[node name="Backdrop" type="ColorRect" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.94)
[node name="Bar" type="Control" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24.0
offset_top = 20
offset_right = -160.0
offset_bottom = 202
script = ExtResource("24")
[node name="Tree" type="Control" parent="."]
mouse_filter = 2
clip_contents = true
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 24.0
offset_top = 214
offset_right = -24.0
offset_bottom = -280.0
script = ExtResource("21")
[node name="DetailLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 32.0
offset_top = -272.0
offset_right = -300.0
offset_bottom = -152.0
theme_override_font_sizes/font_size = 34
autowrap_mode = 2
horizontal_alignment = 0
text = ""
[node name="CostNumber" type="Control" parent="."]
alignment = 2
mouse_filter = 2
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -288.0
offset_top = -272.0
offset_right = -32.0
offset_bottom = -152.0
script = ExtResource("price_tag")
[node name="StatusLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 32.0
offset_top = -76.0
offset_right = -32.0
offset_bottom = -24.0
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
horizontal_alignment = 1
text = ""
[node name="CloseButton" type="Control" parent="."]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -140.0
offset_top = 20.0
offset_right = -24.0
offset_bottom = 136.0
script = ExtResource("10")
icon = 1
glow_color = Color(0.55, 0.6, 1, 1)

56
ui/menus/TransferMenu.gd Normal file
View file

@ -0,0 +1,56 @@
extends "res://ui/menus/ModalMenu.gd"
signal transfer_requested(resource_type: String, to_building: bool, amount: int)
@onready var status: Label = $StatusLabel
@onready var resource_list: Control = $ResourceList
var building_id: int = 0
var extractor_index: int = -1
func _ready() -> void:
super._ready()
resource_list.transfer_requested.connect(
func(resource_type: String, to_building: bool, amount: int):
transfer_requested.emit(resource_type, to_building, amount))
func open_destination(next_building_id: int, next_extractor_index: int) -> void:
building_id = next_building_id
extractor_index = next_extractor_index
status.text = ""
open_menu()
refresh()
func close_menu() -> void:
super.close_menu()
building_id = 0
extractor_index = -1
func refresh() -> void:
if extractor_index >= 0:
resource_list.set_extractor_destination(extractor_index, "Planet extractor")
return
var building: Dictionary = GameState.find_building(building_id)
var heading: String = "This building"
if GameState.building_has_capability(building, "bank"):
heading = "Mothership bank"
resource_list.set_destination(building_id, heading)
func place_name() -> String:
if extractor_index >= 0:
return "the extractor"
var building: Dictionary = GameState.find_building(building_id)
if building.is_empty():
return "this building"
if GameState.building_has_capability(building, "bank"):
return "the bank"
return "this building"
func show_status(message: String) -> void:
status.text = message

View file

@ -0,0 +1 @@
uid://bw1aiy8jt817c

View file

@ -0,0 +1,73 @@
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://ui/IconButton.gd" id="10"]
[ext_resource type="Script" path="res://ui/ResourceTransferList.gd" id="27"]
[ext_resource type="Script" path="res://ui/menus/TransferMenu.gd" id="menu_script"]
[node name="TransferMenu" type="Control" groups=["modal_menus"]]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("menu_script")
[node name="Backdrop" type="ColorRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.94)
[node name="TitleLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 36
offset_right = -200
offset_bottom = 166
text = "Transfer cargo"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 2
theme_override_font_sizes/font_size = 44
[node name="StatusLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 184
offset_right = -24
offset_bottom = 284
theme_override_font_sizes/font_size = 32
autowrap_mode = 2
text = ""
horizontal_alignment = 1
[node name="ResourceList" type="Control" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 16
offset_top = 294
offset_right = -16
offset_bottom = 1080
script = ExtResource("27")
[node name="CloseButton" type="Control" parent="."]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -172
offset_right = -24
offset_top = 24
offset_bottom = 172
script = ExtResource("10")
icon = 1
glow_color = Color(1, 0.78, 0.25, 1)
[node name="HintLabel" type="Label" parent="."]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24
offset_top = 1092
offset_right = -24
offset_bottom = 1150
text = "Arrows move the selected amount from that side"
horizontal_alignment = 1
theme_override_colors/font_color = Color(0.55, 0.7, 0.78, 1)
theme_override_font_sizes/font_size = 23