2026-08-14 11:54:31 +02:00
|
|
|
class_name ResourceTransferList
|
|
|
|
|
extends Control
|
|
|
|
|
|
|
|
|
|
# Two-sided cargo control. Resources are rows and locations stay fixed:
|
|
|
|
|
# fleet on the left, the current building on the right. Horizontal arrows
|
|
|
|
|
# make the result of a press visible before the player commits to it.
|
|
|
|
|
|
2026-08-14 13:44:10 +02:00
|
|
|
signal transfer_requested(resource_type: String, to_building: bool, amount: int)
|
2026-08-14 11:54:31 +02:00
|
|
|
|
2026-08-14 15:45:08 +02:00
|
|
|
const HEADER_HEIGHT: float = 66.0
|
2026-08-14 11:54:31 +02:00
|
|
|
const FLEET_COLOR: Color = Color(1.0, 0.78, 0.25)
|
|
|
|
|
const LOCAL_COLOR: Color = Color(0.45, 0.9, 0.55)
|
|
|
|
|
const RULE_COLOR: Color = Color(0.18, 0.38, 0.52)
|
2026-08-14 13:44:10 +02:00
|
|
|
const ALL_AMOUNT: int = 1 << 30
|
|
|
|
|
const AMOUNT_OPTIONS: Array[int] = [1, 5, 25, ALL_AMOUNT]
|
2026-08-14 11:54:31 +02:00
|
|
|
|
|
|
|
|
var building_id: int = 0
|
|
|
|
|
var _destination_heading: Label
|
|
|
|
|
var _rows: Dictionary = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
2026-08-14 15:45:08 +02:00
|
|
|
var fleet_heading := _make_label("FLEET HOLD", 26, FLEET_COLOR)
|
2026-08-14 11:54:31 +02:00
|
|
|
fleet_heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
add_child(fleet_heading)
|
2026-08-14 15:45:08 +02:00
|
|
|
_destination_heading = _make_label("THIS BUILDING", 26, LOCAL_COLOR)
|
2026-08-14 11:54:31 +02:00
|
|
|
_destination_heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
add_child(_destination_heading)
|
|
|
|
|
|
|
|
|
|
for resource_type in GameState.RESOURCE_TYPES:
|
|
|
|
|
var colour: Color = GameState.resource_color(resource_type)
|
|
|
|
|
var glyph := ResourceGlyph.new()
|
|
|
|
|
glyph.resource_type = resource_type
|
|
|
|
|
add_child(glyph)
|
2026-08-14 15:45:08 +02:00
|
|
|
var name_label := _make_label(GameState.resource_name(resource_type).to_upper(), 29, colour)
|
2026-08-14 11:54:31 +02:00
|
|
|
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
add_child(name_label)
|
2026-08-14 15:45:08 +02:00
|
|
|
var fleet_amount := _make_label("0", 48, FLEET_COLOR)
|
2026-08-14 11:54:31 +02:00
|
|
|
fleet_amount.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
add_child(fleet_amount)
|
2026-08-14 15:45:08 +02:00
|
|
|
var local_amount := _make_label("0", 48, LOCAL_COLOR)
|
2026-08-14 11:54:31 +02:00
|
|
|
local_amount.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
add_child(local_amount)
|
|
|
|
|
|
|
|
|
|
var unload := IconButton.new()
|
|
|
|
|
unload.name = "%sToBuildingButton" % GameState.resource_name(resource_type)
|
|
|
|
|
unload.icon = IconButton.Icon.RIGHT_ARROW
|
|
|
|
|
unload.glow_color = colour
|
|
|
|
|
unload.pressed.connect(_request_transfer.bind(resource_type, true))
|
|
|
|
|
add_child(unload)
|
2026-08-14 15:45:08 +02:00
|
|
|
var unload_label := _make_label("UNLOAD →", 23, colour)
|
2026-08-14 11:54:31 +02:00
|
|
|
unload_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
add_child(unload_label)
|
|
|
|
|
|
|
|
|
|
var load := IconButton.new()
|
|
|
|
|
load.name = "%sToFleetButton" % GameState.resource_name(resource_type)
|
|
|
|
|
load.icon = IconButton.Icon.LEFT_ARROW
|
|
|
|
|
load.glow_color = colour
|
|
|
|
|
load.pressed.connect(_request_transfer.bind(resource_type, false))
|
|
|
|
|
add_child(load)
|
2026-08-14 15:45:08 +02:00
|
|
|
var load_label := _make_label("← LOAD", 23, colour)
|
2026-08-14 11:54:31 +02:00
|
|
|
load_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
add_child(load_label)
|
|
|
|
|
|
2026-08-14 13:44:10 +02:00
|
|
|
var decrease := IconButton.new()
|
|
|
|
|
decrease.name = "%sDecreaseButton" % GameState.resource_name(resource_type)
|
|
|
|
|
decrease.icon = IconButton.Icon.MINUS
|
|
|
|
|
decrease.glow_color = colour
|
|
|
|
|
decrease.pressed.connect(_change_amount.bind(resource_type, -1))
|
|
|
|
|
add_child(decrease)
|
2026-08-14 15:45:08 +02:00
|
|
|
var amount_label := _make_label("ALL", 28, colour)
|
2026-08-14 13:44:10 +02:00
|
|
|
amount_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
add_child(amount_label)
|
|
|
|
|
var increase := IconButton.new()
|
|
|
|
|
increase.name = "%sIncreaseButton" % GameState.resource_name(resource_type)
|
|
|
|
|
increase.icon = IconButton.Icon.PLUS
|
|
|
|
|
increase.glow_color = colour
|
|
|
|
|
increase.pressed.connect(_change_amount.bind(resource_type, 1))
|
|
|
|
|
add_child(increase)
|
2026-08-14 15:45:08 +02:00
|
|
|
var amount_caption := _make_label("AMOUNT", 18, Color(0.62, 0.78, 0.86))
|
2026-08-14 13:44:10 +02:00
|
|
|
amount_caption.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
add_child(amount_caption)
|
|
|
|
|
|
2026-08-14 11:54:31 +02:00
|
|
|
_rows[resource_type] = {
|
|
|
|
|
"glyph": glyph, "name": name_label,
|
|
|
|
|
"fleet_amount": fleet_amount, "local_amount": local_amount,
|
|
|
|
|
"unload": unload, "unload_label": unload_label,
|
|
|
|
|
"load": load, "load_label": load_label,
|
2026-08-14 13:44:10 +02:00
|
|
|
"decrease": decrease, "amount_label": amount_label,
|
|
|
|
|
"increase": increase, "amount_caption": amount_caption,
|
|
|
|
|
"amount_index": AMOUNT_OPTIONS.size() - 1,
|
2026-08-14 11:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resized.connect(_layout)
|
|
|
|
|
_layout()
|
|
|
|
|
refresh()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
|
|
|
return label
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _request_transfer(resource_type: String, to_building: bool) -> void:
|
2026-08-14 13:44:10 +02:00
|
|
|
var row: Dictionary = _rows.get(resource_type, {})
|
|
|
|
|
if row.is_empty():
|
|
|
|
|
return
|
|
|
|
|
transfer_requested.emit(resource_type, to_building,
|
|
|
|
|
AMOUNT_OPTIONS[int(row.amount_index)])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _change_amount(resource_type: String, direction: int) -> void:
|
|
|
|
|
var row: Dictionary = _rows.get(resource_type, {})
|
|
|
|
|
if row.is_empty():
|
|
|
|
|
return
|
|
|
|
|
row.amount_index = clampi(int(row.amount_index) + direction, 0, AMOUNT_OPTIONS.size() - 1)
|
|
|
|
|
_refresh_amount_controls(row)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_amount_controls(row: Dictionary) -> void:
|
|
|
|
|
var index: int = int(row.amount_index)
|
|
|
|
|
var amount: int = AMOUNT_OPTIONS[index]
|
|
|
|
|
row.amount_label.text = "ALL" if amount == ALL_AMOUNT else str(amount)
|
|
|
|
|
row.decrease.enabled = index > 0
|
|
|
|
|
row.increase.enabled = index < AMOUNT_OPTIONS.size() - 1
|
|
|
|
|
row.decrease.queue_redraw()
|
|
|
|
|
row.increase.queue_redraw()
|
2026-08-14 11:54:31 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func set_destination(id: int, heading: String) -> void:
|
|
|
|
|
building_id = id
|
|
|
|
|
if _destination_heading != null:
|
|
|
|
|
_destination_heading.text = heading.to_upper()
|
|
|
|
|
refresh()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func refresh() -> void:
|
|
|
|
|
for resource_type in GameState.RESOURCE_TYPES:
|
|
|
|
|
var row: Dictionary = _rows.get(resource_type, {})
|
|
|
|
|
if row.is_empty():
|
|
|
|
|
continue
|
|
|
|
|
var fleet_amount: int = GameState.fleet_resource(resource_type)
|
|
|
|
|
var local_amount: int = GameState.building_resource(building_id, resource_type)
|
|
|
|
|
row.fleet_amount.text = str(fleet_amount)
|
|
|
|
|
row.local_amount.text = str(local_amount)
|
|
|
|
|
row.unload.enabled = fleet_amount > 0
|
|
|
|
|
row.load.enabled = local_amount > 0
|
|
|
|
|
row.unload.queue_redraw()
|
|
|
|
|
row.load.queue_redraw()
|
2026-08-14 13:44:10 +02:00
|
|
|
_refresh_amount_controls(row)
|
2026-08-14 11:54:31 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func _layout() -> void:
|
|
|
|
|
if _destination_heading == null:
|
|
|
|
|
return
|
2026-08-14 15:45:08 +02:00
|
|
|
var side_width: float = size.x * 0.35
|
2026-08-14 11:54:31 +02:00
|
|
|
var centre_width: float = size.x - side_width * 2.0
|
|
|
|
|
var right_x: float = side_width + centre_width
|
|
|
|
|
var fleet_heading: Label = get_child(0)
|
|
|
|
|
fleet_heading.position = Vector2(0.0, 0.0)
|
|
|
|
|
fleet_heading.size = Vector2(side_width, HEADER_HEIGHT)
|
|
|
|
|
_destination_heading.position = Vector2(right_x, 0.0)
|
|
|
|
|
_destination_heading.size = Vector2(side_width, HEADER_HEIGHT)
|
|
|
|
|
|
|
|
|
|
var row_height: float = (size.y - HEADER_HEIGHT) / maxf(1.0, float(_rows.size()))
|
|
|
|
|
var row_index: int = 0
|
|
|
|
|
for resource_type in GameState.RESOURCE_TYPES:
|
|
|
|
|
var row: Dictionary = _rows[resource_type]
|
|
|
|
|
var y: float = HEADER_HEIGHT + float(row_index) * row_height
|
2026-08-14 15:45:08 +02:00
|
|
|
var glyph_size: float = minf(66.0, row_height * 0.23)
|
|
|
|
|
row.glyph.position = Vector2(size.x * 0.5 - glyph_size * 0.5, y + 10.0)
|
2026-08-14 11:54:31 +02:00
|
|
|
row.glyph.size = Vector2(glyph_size, glyph_size)
|
2026-08-14 15:45:08 +02:00
|
|
|
row.name.position = Vector2(side_width, y + 10.0 + glyph_size)
|
|
|
|
|
row.name.size = Vector2(centre_width, 44.0)
|
2026-08-14 11:54:31 +02:00
|
|
|
|
2026-08-14 15:45:08 +02:00
|
|
|
row.fleet_amount.position = Vector2(0.0, y + 10.0)
|
|
|
|
|
row.fleet_amount.size = Vector2(side_width, 70.0)
|
|
|
|
|
row.local_amount.position = Vector2(right_x, y + 10.0)
|
|
|
|
|
row.local_amount.size = Vector2(side_width, 70.0)
|
2026-08-14 11:54:31 +02:00
|
|
|
|
2026-08-14 15:45:08 +02:00
|
|
|
var button_size: float = minf(132.0, row_height * 0.44)
|
|
|
|
|
var button_y: float = y + 88.0
|
2026-08-14 11:54:31 +02:00
|
|
|
row.unload.position = Vector2(side_width * 0.5 - button_size * 0.5, button_y)
|
|
|
|
|
row.unload.size = Vector2(button_size, button_size)
|
|
|
|
|
row.load.position = Vector2(right_x + side_width * 0.5 - button_size * 0.5, button_y)
|
|
|
|
|
row.load.size = Vector2(button_size, button_size)
|
|
|
|
|
var caption_y: float = button_y + button_size + 2.0
|
|
|
|
|
row.unload_label.position = Vector2(0.0, caption_y)
|
2026-08-14 15:45:08 +02:00
|
|
|
row.unload_label.size = Vector2(side_width, 42.0)
|
2026-08-14 11:54:31 +02:00
|
|
|
row.load_label.position = Vector2(right_x, caption_y)
|
2026-08-14 15:45:08 +02:00
|
|
|
row.load_label.size = Vector2(side_width, 42.0)
|
2026-08-14 13:44:10 +02:00
|
|
|
|
2026-08-14 15:45:08 +02:00
|
|
|
var selector_size: float = minf(66.0, centre_width * 0.33)
|
|
|
|
|
var selector_y: float = y + 142.0
|
2026-08-14 13:44:10 +02:00
|
|
|
row.decrease.position = Vector2(side_width + 2.0, selector_y)
|
|
|
|
|
row.decrease.size = Vector2(selector_size, selector_size)
|
|
|
|
|
row.increase.position = Vector2(right_x - selector_size - 2.0, selector_y)
|
|
|
|
|
row.increase.size = Vector2(selector_size, selector_size)
|
|
|
|
|
row.amount_label.position = Vector2(side_width + selector_size + 2.0, selector_y)
|
|
|
|
|
row.amount_label.size = Vector2(centre_width - selector_size * 2.0 - 4.0, selector_size)
|
|
|
|
|
row.amount_caption.position = Vector2(side_width, selector_y + selector_size + 2.0)
|
2026-08-14 15:45:08 +02:00
|
|
|
row.amount_caption.size = Vector2(centre_width, 34.0)
|
2026-08-14 11:54:31 +02:00
|
|
|
row_index += 1
|
|
|
|
|
queue_redraw()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _draw() -> void:
|
|
|
|
|
var row_height: float = (size.y - HEADER_HEIGHT) / maxf(1.0, float(_rows.size()))
|
|
|
|
|
NeonDraw.draw_glowing_line(self, Vector2(0.0, HEADER_HEIGHT), Vector2(size.x, HEADER_HEIGHT),
|
|
|
|
|
RULE_COLOR, 0.5, 0.7)
|
|
|
|
|
for i in range(1, _rows.size()):
|
|
|
|
|
var y: float = HEADER_HEIGHT + float(i) * row_height
|
|
|
|
|
NeonDraw.draw_glowing_line(self, Vector2(0.0, y), Vector2(size.x, y),
|
|
|
|
|
RULE_COLOR, 0.4, 0.6)
|