Landed together because an outpost you cannot crew is decoration, and a garrison menu with no building to hang it off is nothing at all. #102, the building. Researched at "Forward Bases", two prerequisites deep and cheaper than any mid-game structure - the ticket asks for early, and the answer to waves attacking your buildings should arrive at about the time the waves can hurt them. It is drawn as a watch mast on a tripod with two open berths, deliberately the least machinery of any structure here: it makes nothing and refines nothing, it holds a position. 120hp, more than a crafter, because being shot at is its job. #103, the garrison. Two separate things live on its record, and conflating them was the first thing to get wrong: the ORDER, what the player asked for, and the CREW, who is actually there. This menu edits the order only; #104 fills the crew. Keeping the order as a wish is what lets an outpost ask for a hull nobody has built yet and wait for it, rather than the request evaporating - and it is why the panel shows "here / ordered" instead of one number. "Only ships that attack can be assigned" is expressed as capabilities rather than a list of types, so a new armed vessel is eligible the day it is added and an unarmed one never is by accident. Today that means the interceptor and the laser vessel; a shield, a hauler and an engine block cannot hold a position however much one might want them to. Two berths by default, and Garrison Bays raises it at outposts ALREADY standing - the same shape as freight_yards for hubs, because a bonus that only reached new buildings would mean demolishing and rebuilding to collect it. The panel builds its own rows from the catalog rather than from nodes placed in the scene, so adding an armed vessel later needs no scene surgery. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
134 lines
4.8 KiB
GDScript
134 lines
4.8 KiB
GDScript
extends Control
|
|
|
|
# #103 (#97e): "A dedicated menu on each outpost should let you compose how
|
|
# you want the outpost to be filled."
|
|
#
|
|
# Builds its own rows from GameState.garrison_vessel_types() rather than
|
|
# from nodes placed in Main.tscn. Which vessels can hold a position is
|
|
# decided by capability, not by a list of types, so the day an armed vessel
|
|
# is added it gets a row here and an unarmed one never does by accident -
|
|
# and neither change touches the scene.
|
|
#
|
|
# The panel edits the ORDER, not the crew. What the player sets here is what
|
|
# they want stationed; #104 is what actually gets vessels there. The two are
|
|
# shown side by side ("1/2") so an outpost that is waiting on a hull it does
|
|
# not have yet reads as waiting rather than as broken.
|
|
|
|
# Emitted when the player changes the order, so whatever hosts the panel can
|
|
# update its own readout rather than polling.
|
|
signal changed
|
|
|
|
const ROW_HEIGHT: float = 96.0
|
|
const ROW_GAP: float = 12.0
|
|
const BUTTON_SIZE: float = 76.0
|
|
const NAME_FONT_SIZE: int = 30
|
|
const COUNT_FONT_SIZE: int = 34
|
|
const HEADER_FONT_SIZE: int = 22
|
|
|
|
var building_id: int = 0
|
|
|
|
var _rows: Array = []
|
|
var _capacity_label: Label
|
|
|
|
|
|
func _ready() -> void:
|
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
# _make_label already parents what it builds.
|
|
_capacity_label = _make_label("", HEADER_FONT_SIZE, Color(0.62, 0.78, 0.86))
|
|
_capacity_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
for vessel_type in GameState.garrison_vessel_types():
|
|
var row: Dictionary = _make_row(vessel_type)
|
|
_rows.append(row)
|
|
resized.connect(_layout)
|
|
_layout()
|
|
|
|
|
|
func _make_label(text_value: String, font_size: int, colour: Color) -> Label:
|
|
var label := Label.new()
|
|
label.text = text_value
|
|
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
label.add_theme_font_size_override("font_size", font_size)
|
|
label.add_theme_color_override("font_color", colour)
|
|
label.add_theme_color_override("font_outline_color", Color(0.01, 0.01, 0.035, 0.95))
|
|
label.add_theme_constant_override("outline_size", 2)
|
|
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
add_child(label)
|
|
return label
|
|
|
|
|
|
func _make_row(vessel_type: String) -> Dictionary:
|
|
var definition: Resource = GameState.vessel_definition(vessel_type)
|
|
var colour: Color = definition.glow_color if definition != null else Color.WHITE
|
|
|
|
var name_label: Label = _make_label(GameState.vessel_name(vessel_type), NAME_FONT_SIZE, colour)
|
|
var count_label: Label = _make_label("0", COUNT_FONT_SIZE, Color(0.94, 0.98, 1.0))
|
|
count_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
var minus := IconButton.new()
|
|
minus.icon = IconButton.Icon.MINUS
|
|
minus.glow_color = colour
|
|
minus.pressed.connect(_on_adjust.bind(vessel_type, -1))
|
|
add_child(minus)
|
|
|
|
var plus := IconButton.new()
|
|
plus.icon = IconButton.Icon.PLUS
|
|
plus.glow_color = colour
|
|
plus.pressed.connect(_on_adjust.bind(vessel_type, 1))
|
|
add_child(plus)
|
|
|
|
return {"type": vessel_type, "name": name_label, "count": count_label,
|
|
"minus": minus, "plus": plus}
|
|
|
|
|
|
func _on_adjust(vessel_type: String, delta: int) -> void:
|
|
if building_id == 0:
|
|
return
|
|
GameState.adjust_garrison_desired(building_id, vessel_type, delta)
|
|
refresh()
|
|
changed.emit()
|
|
|
|
|
|
func _layout() -> void:
|
|
if _rows.is_empty():
|
|
return
|
|
_capacity_label.position = Vector2.ZERO
|
|
_capacity_label.size = Vector2(size.x, 44.0)
|
|
var y: float = 52.0
|
|
for row in _rows:
|
|
row.name.position = Vector2(0.0, y)
|
|
row.name.size = Vector2(size.x * 0.46, ROW_HEIGHT)
|
|
var right: float = size.x
|
|
row.plus.position = Vector2(right - BUTTON_SIZE, y + (ROW_HEIGHT - BUTTON_SIZE) * 0.5)
|
|
row.plus.size = Vector2(BUTTON_SIZE, BUTTON_SIZE)
|
|
row.count.position = Vector2(right - BUTTON_SIZE * 3.0, y)
|
|
row.count.size = Vector2(BUTTON_SIZE * 2.0, ROW_HEIGHT)
|
|
row.minus.position = Vector2(right - BUTTON_SIZE * 4.0,
|
|
y + (ROW_HEIGHT - BUTTON_SIZE) * 0.5)
|
|
row.minus.size = Vector2(BUTTON_SIZE, BUTTON_SIZE)
|
|
y += ROW_HEIGHT + ROW_GAP
|
|
|
|
|
|
func natural_height() -> float:
|
|
return 52.0 + float(_rows.size()) * (ROW_HEIGHT + ROW_GAP)
|
|
|
|
|
|
func refresh() -> void:
|
|
if _rows.is_empty():
|
|
return
|
|
var capacity: int = GameState.outpost_slot_capacity()
|
|
var ordered: int = GameState.garrison_desired_total(building_id)
|
|
_capacity_label.text = "BERTHS %d / %d" % [ordered, capacity]
|
|
for row in _rows:
|
|
var vessel_type: String = String(row.type)
|
|
var want: int = GameState.garrison_desired_of(building_id, vessel_type)
|
|
var here: int = GameState.garrison_crew_of(building_id, vessel_type)
|
|
# "here / ordered", so an outpost waiting on a hull nobody has built
|
|
# yet reads as waiting rather than as having lost one.
|
|
row.count.text = "%d/%d" % [here, want]
|
|
var unlocked: bool = GameState.vessel_is_unlocked(vessel_type)
|
|
row.name.modulate.a = 1.0 if unlocked else 0.45
|
|
row.minus.enabled = unlocked and want > 0
|
|
row.plus.enabled = unlocked and ordered < capacity
|
|
row.minus.queue_redraw()
|
|
row.plus.queue_redraw()
|