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
|
2026-08-14 22:21:01 +02:00
|
|
|
var extractor_index: int = -1
|
2026-08-14 11:54:31 +02:00
|
|
|
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)
|
|
|
|
|
|
2026-08-14 20:32:58 +02:00
|
|
|
for resource_type in GameState.resource_types():
|
2026-08-14 11:54:31 +02:00
|
|
|
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 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,
|
Stacking distorders, menu layouts, lab rewards, enemy detail, artillery cross
DISTORDER STACKING. Each space-time distorder vessel past the first raises
the fleet's speed ceiling by 1.0, so a second one is worth crewing instead of
being dead weight. GameState gains max_fleet_speed_multiplier, pushed by
Fleet whenever composition changes - the same direction as every other
fleet-derived figure, since GameState does not reach into the scene tree to
count ships. The energy drain is normalised against the CURRENT ceiling, so
full boost costs the same per second however wide it is: extra vessels buy
more speed, not cheaper speed. Losing one re-clamps an over-ceiling boost
rather than leaving the fleet travelling faster than its crew can sustain.
TRANSFER MENU. Rows overlapped, and the last one ran into the hint text.
row_height is divided from the available space, but each row's contents used
a fixed pixel ladder - glyph at +10, buttons at +88, selector at +142 - which
needs about 244px. Three resources fit; the fourth cut rows to 180 and every
one spilled 64px into the next. Every offset is now a fraction of the row, so
it cannot overflow. Also dropped the per-row AMOUNT caption (four copies of a
word the footer hint already explains) and moved each glyph and name to the
TOP of its row, where they read as its heading rather than the next row's.
HANGAR. Six vessel types in one row gave each about 100px for a 155px label,
so the names ran together. Now a 3x2 grid per section, sized from the space
between the first heading and the status line, using the empty lower half.
LAB RESEARCH REWARD. A harder enemy lab is worth more research points, and
how hard a lab is depends on the layout authored for it - how many capture
points, how many defenders - which the planet TYPE cannot know. PlanetSetup
carries research_point_reward, set in the map editor, overriding the planet
type's flat rate. 0 means "no opinion", so every map authored before this
keeps its old value.
RESEARCH LAB BACKGROUND. Its own shader instead of the crater field with a
red tint, which read as "rocky planet, tinted" rather than someone's
facility: plated floor, seams, service conduits, lit junctions. Loop-free by
construction (#68's lesson applied up front) - conduits stay continuous
across cells by keying a horizontal run to cell.y and a vertical to cell.x,
so neighbouring cells agree without any cell scanning its neighbours.
ENEMY DETAIL. Every enemy was a single flat outline, which left the artillery
and the turret spawner as the same octagon at two sizes - the silhouette
carried no information about what the thing did. EnemyDefinition gains
detail_lines and detail_rings as DATA, so adding an enemy still means adding
a definition rather than editing Enemy.gd, and the four non-default hulls are
redrawn: a long-barrelled sniper, a squat mortar, a hive with launch bays, a
capital ship with spine and ribs.
Three things drew an enemy from its definition - the enemy, the map editor's
markers, and the palette buttons - each reimplementing it from hull_points
alone, so the new detail appeared in the game and nowhere else and an editor
marker stopped matching what it placed. They now share
EnemyDefinitions.draw_definition().
ARTILLERY. The 1.8s charge-up before the tower fired arrived long before the
threat was legible and mostly read as the tower being stuck. It fires
immediately now; the warning is the shell you can see in the air plus a small
cross on the ground at the impact point. Deliberately not a blast-radius ring
- a large translucent area was easy to read as the explosion having already
happened, where the cross says "move off this spot".
MAIN MENU. No Quit in any build (a web build has nothing to quit to, and
Android's back gesture already does it), a plain black background, and the
game is "Spaceship" - in the title and in config/name, which is what drives
the browser tab for the web export. change_scene_to_file() returns an error
and otherwise fails silently, leaving the menu up, which is indistinguishable
from an unpressed button; it is now checked and reported on screen.
Full suite green at 99/99.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:54:46 +02:00
|
|
|
"increase": increase,
|
2026-08-14 13:44:10 +02:00
|
|
|
"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
|
2026-08-14 22:21:01 +02:00
|
|
|
extractor_index = -1
|
|
|
|
|
if _destination_heading != null:
|
|
|
|
|
_destination_heading.text = heading.to_upper()
|
|
|
|
|
refresh()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func set_extractor_destination(index: int, heading: String) -> void:
|
|
|
|
|
building_id = 0
|
|
|
|
|
extractor_index = index
|
2026-08-14 11:54:31 +02:00
|
|
|
if _destination_heading != null:
|
|
|
|
|
_destination_heading.text = heading.to_upper()
|
|
|
|
|
refresh()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func refresh() -> void:
|
2026-08-14 20:32:58 +02:00
|
|
|
for resource_type in GameState.resource_types():
|
2026-08-14 11:54:31 +02:00
|
|
|
var row: Dictionary = _rows.get(resource_type, {})
|
|
|
|
|
if row.is_empty():
|
|
|
|
|
continue
|
|
|
|
|
var fleet_amount: int = GameState.fleet_resource(resource_type)
|
2026-08-14 22:21:01 +02:00
|
|
|
var local_amount: int = GameState.extractor_resource(extractor_index, resource_type) \
|
|
|
|
|
if extractor_index >= 0 else GameState.building_resource(building_id, resource_type)
|
2026-08-14 11:54:31 +02:00
|
|
|
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
|
2026-08-14 20:32:58 +02:00
|
|
|
for resource_type in GameState.resource_types():
|
2026-08-14 11:54:31 +02:00
|
|
|
var row: Dictionary = _rows[resource_type]
|
|
|
|
|
var y: float = HEADER_HEIGHT + float(row_index) * row_height
|
|
|
|
|
|
Stacking distorders, menu layouts, lab rewards, enemy detail, artillery cross
DISTORDER STACKING. Each space-time distorder vessel past the first raises
the fleet's speed ceiling by 1.0, so a second one is worth crewing instead of
being dead weight. GameState gains max_fleet_speed_multiplier, pushed by
Fleet whenever composition changes - the same direction as every other
fleet-derived figure, since GameState does not reach into the scene tree to
count ships. The energy drain is normalised against the CURRENT ceiling, so
full boost costs the same per second however wide it is: extra vessels buy
more speed, not cheaper speed. Losing one re-clamps an over-ceiling boost
rather than leaving the fleet travelling faster than its crew can sustain.
TRANSFER MENU. Rows overlapped, and the last one ran into the hint text.
row_height is divided from the available space, but each row's contents used
a fixed pixel ladder - glyph at +10, buttons at +88, selector at +142 - which
needs about 244px. Three resources fit; the fourth cut rows to 180 and every
one spilled 64px into the next. Every offset is now a fraction of the row, so
it cannot overflow. Also dropped the per-row AMOUNT caption (four copies of a
word the footer hint already explains) and moved each glyph and name to the
TOP of its row, where they read as its heading rather than the next row's.
HANGAR. Six vessel types in one row gave each about 100px for a 155px label,
so the names ran together. Now a 3x2 grid per section, sized from the space
between the first heading and the status line, using the empty lower half.
LAB RESEARCH REWARD. A harder enemy lab is worth more research points, and
how hard a lab is depends on the layout authored for it - how many capture
points, how many defenders - which the planet TYPE cannot know. PlanetSetup
carries research_point_reward, set in the map editor, overriding the planet
type's flat rate. 0 means "no opinion", so every map authored before this
keeps its old value.
RESEARCH LAB BACKGROUND. Its own shader instead of the crater field with a
red tint, which read as "rocky planet, tinted" rather than someone's
facility: plated floor, seams, service conduits, lit junctions. Loop-free by
construction (#68's lesson applied up front) - conduits stay continuous
across cells by keying a horizontal run to cell.y and a vertical to cell.x,
so neighbouring cells agree without any cell scanning its neighbours.
ENEMY DETAIL. Every enemy was a single flat outline, which left the artillery
and the turret spawner as the same octagon at two sizes - the silhouette
carried no information about what the thing did. EnemyDefinition gains
detail_lines and detail_rings as DATA, so adding an enemy still means adding
a definition rather than editing Enemy.gd, and the four non-default hulls are
redrawn: a long-barrelled sniper, a squat mortar, a hive with launch bays, a
capital ship with spine and ribs.
Three things drew an enemy from its definition - the enemy, the map editor's
markers, and the palette buttons - each reimplementing it from hull_points
alone, so the new detail appeared in the game and nowhere else and an editor
marker stopped matching what it placed. They now share
EnemyDefinitions.draw_definition().
ARTILLERY. The 1.8s charge-up before the tower fired arrived long before the
threat was legible and mostly read as the tower being stuck. It fires
immediately now; the warning is the shell you can see in the air plus a small
cross on the ground at the impact point. Deliberately not a blast-radius ring
- a large translucent area was easy to read as the explosion having already
happened, where the cross says "move off this spot".
MAIN MENU. No Quit in any build (a web build has nothing to quit to, and
Android's back gesture already does it), a plain black background, and the
game is "Spaceship" - in the title and in config/name, which is what drives
the browser tab for the web export. change_scene_to_file() returns an error
and otherwise fails silently, leaving the menu up, which is indistinguishable
from an unpressed button; it is now checked and reported on screen.
Full suite green at 99/99.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:54:46 +02:00
|
|
|
# EVERY offset below is a fraction of row_height. It used to be a
|
|
|
|
|
# fixed pixel ladder - glyph at +10, buttons at +88, selector at
|
|
|
|
|
# +142, captions under that - which needed about 244px inside a row
|
|
|
|
|
# whose height is divided from the available space. At three
|
|
|
|
|
# resources that fit; the fourth cut the row to 180 and every row
|
|
|
|
|
# spilled 64px into the one below it, and the last one into the hint
|
|
|
|
|
# text under the list. Proportions cannot overflow: the bands always
|
|
|
|
|
# sum to the row.
|
|
|
|
|
var pad: float = row_height * 0.04
|
|
|
|
|
var content: float = row_height - pad * 2.0
|
|
|
|
|
var top: float = y + pad
|
|
|
|
|
# amounts, with the resource's glyph and name between them
|
|
|
|
|
var band_totals: float = content * 0.32
|
|
|
|
|
# the arrows, and the amount selector between them
|
|
|
|
|
var band_controls: float = content * 0.48
|
|
|
|
|
# the arrow captions
|
|
|
|
|
var band_captions: float = content * 0.20
|
|
|
|
|
|
|
|
|
|
row.fleet_amount.position = Vector2(0.0, top)
|
|
|
|
|
row.fleet_amount.size = Vector2(side_width, band_totals)
|
|
|
|
|
row.local_amount.position = Vector2(right_x, top)
|
|
|
|
|
row.local_amount.size = Vector2(side_width, band_totals)
|
|
|
|
|
|
|
|
|
|
# Glyph and name sit at the TOP of their row rather than under its
|
|
|
|
|
# controls, so a row reads as one thing. Previously the name hung
|
|
|
|
|
# low enough to look like a heading for the row beneath it.
|
|
|
|
|
var glyph_size: float = minf(band_totals * 0.52, 44.0)
|
|
|
|
|
row.glyph.position = Vector2(size.x * 0.5 - glyph_size * 0.5, top)
|
|
|
|
|
row.glyph.size = Vector2(glyph_size, glyph_size)
|
|
|
|
|
row.name.position = Vector2(side_width, top + glyph_size)
|
|
|
|
|
row.name.size = Vector2(centre_width, maxf(band_totals - glyph_size, 1.0))
|
2026-08-14 11:54:31 +02:00
|
|
|
|
Stacking distorders, menu layouts, lab rewards, enemy detail, artillery cross
DISTORDER STACKING. Each space-time distorder vessel past the first raises
the fleet's speed ceiling by 1.0, so a second one is worth crewing instead of
being dead weight. GameState gains max_fleet_speed_multiplier, pushed by
Fleet whenever composition changes - the same direction as every other
fleet-derived figure, since GameState does not reach into the scene tree to
count ships. The energy drain is normalised against the CURRENT ceiling, so
full boost costs the same per second however wide it is: extra vessels buy
more speed, not cheaper speed. Losing one re-clamps an over-ceiling boost
rather than leaving the fleet travelling faster than its crew can sustain.
TRANSFER MENU. Rows overlapped, and the last one ran into the hint text.
row_height is divided from the available space, but each row's contents used
a fixed pixel ladder - glyph at +10, buttons at +88, selector at +142 - which
needs about 244px. Three resources fit; the fourth cut rows to 180 and every
one spilled 64px into the next. Every offset is now a fraction of the row, so
it cannot overflow. Also dropped the per-row AMOUNT caption (four copies of a
word the footer hint already explains) and moved each glyph and name to the
TOP of its row, where they read as its heading rather than the next row's.
HANGAR. Six vessel types in one row gave each about 100px for a 155px label,
so the names ran together. Now a 3x2 grid per section, sized from the space
between the first heading and the status line, using the empty lower half.
LAB RESEARCH REWARD. A harder enemy lab is worth more research points, and
how hard a lab is depends on the layout authored for it - how many capture
points, how many defenders - which the planet TYPE cannot know. PlanetSetup
carries research_point_reward, set in the map editor, overriding the planet
type's flat rate. 0 means "no opinion", so every map authored before this
keeps its old value.
RESEARCH LAB BACKGROUND. Its own shader instead of the crater field with a
red tint, which read as "rocky planet, tinted" rather than someone's
facility: plated floor, seams, service conduits, lit junctions. Loop-free by
construction (#68's lesson applied up front) - conduits stay continuous
across cells by keying a horizontal run to cell.y and a vertical to cell.x,
so neighbouring cells agree without any cell scanning its neighbours.
ENEMY DETAIL. Every enemy was a single flat outline, which left the artillery
and the turret spawner as the same octagon at two sizes - the silhouette
carried no information about what the thing did. EnemyDefinition gains
detail_lines and detail_rings as DATA, so adding an enemy still means adding
a definition rather than editing Enemy.gd, and the four non-default hulls are
redrawn: a long-barrelled sniper, a squat mortar, a hive with launch bays, a
capital ship with spine and ribs.
Three things drew an enemy from its definition - the enemy, the map editor's
markers, and the palette buttons - each reimplementing it from hull_points
alone, so the new detail appeared in the game and nowhere else and an editor
marker stopped matching what it placed. They now share
EnemyDefinitions.draw_definition().
ARTILLERY. The 1.8s charge-up before the tower fired arrived long before the
threat was legible and mostly read as the tower being stuck. It fires
immediately now; the warning is the shell you can see in the air plus a small
cross on the ground at the impact point. Deliberately not a blast-radius ring
- a large translucent area was easy to read as the explosion having already
happened, where the cross says "move off this spot".
MAIN MENU. No Quit in any build (a web build has nothing to quit to, and
Android's back gesture already does it), a plain black background, and the
game is "Spaceship" - in the title and in config/name, which is what drives
the browser tab for the web export. change_scene_to_file() returns an error
and otherwise fails silently, leaving the menu up, which is indistinguishable
from an unpressed button; it is now checked and reported on screen.
Full suite green at 99/99.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:54:46 +02:00
|
|
|
var controls_y: float = top + band_totals
|
|
|
|
|
var button_size: float = minf(band_controls, side_width * 0.5)
|
|
|
|
|
var button_y: float = controls_y + (band_controls - button_size) * 0.5
|
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)
|
2026-08-14 13:44:10 +02:00
|
|
|
|
Stacking distorders, menu layouts, lab rewards, enemy detail, artillery cross
DISTORDER STACKING. Each space-time distorder vessel past the first raises
the fleet's speed ceiling by 1.0, so a second one is worth crewing instead of
being dead weight. GameState gains max_fleet_speed_multiplier, pushed by
Fleet whenever composition changes - the same direction as every other
fleet-derived figure, since GameState does not reach into the scene tree to
count ships. The energy drain is normalised against the CURRENT ceiling, so
full boost costs the same per second however wide it is: extra vessels buy
more speed, not cheaper speed. Losing one re-clamps an over-ceiling boost
rather than leaving the fleet travelling faster than its crew can sustain.
TRANSFER MENU. Rows overlapped, and the last one ran into the hint text.
row_height is divided from the available space, but each row's contents used
a fixed pixel ladder - glyph at +10, buttons at +88, selector at +142 - which
needs about 244px. Three resources fit; the fourth cut rows to 180 and every
one spilled 64px into the next. Every offset is now a fraction of the row, so
it cannot overflow. Also dropped the per-row AMOUNT caption (four copies of a
word the footer hint already explains) and moved each glyph and name to the
TOP of its row, where they read as its heading rather than the next row's.
HANGAR. Six vessel types in one row gave each about 100px for a 155px label,
so the names ran together. Now a 3x2 grid per section, sized from the space
between the first heading and the status line, using the empty lower half.
LAB RESEARCH REWARD. A harder enemy lab is worth more research points, and
how hard a lab is depends on the layout authored for it - how many capture
points, how many defenders - which the planet TYPE cannot know. PlanetSetup
carries research_point_reward, set in the map editor, overriding the planet
type's flat rate. 0 means "no opinion", so every map authored before this
keeps its old value.
RESEARCH LAB BACKGROUND. Its own shader instead of the crater field with a
red tint, which read as "rocky planet, tinted" rather than someone's
facility: plated floor, seams, service conduits, lit junctions. Loop-free by
construction (#68's lesson applied up front) - conduits stay continuous
across cells by keying a horizontal run to cell.y and a vertical to cell.x,
so neighbouring cells agree without any cell scanning its neighbours.
ENEMY DETAIL. Every enemy was a single flat outline, which left the artillery
and the turret spawner as the same octagon at two sizes - the silhouette
carried no information about what the thing did. EnemyDefinition gains
detail_lines and detail_rings as DATA, so adding an enemy still means adding
a definition rather than editing Enemy.gd, and the four non-default hulls are
redrawn: a long-barrelled sniper, a squat mortar, a hive with launch bays, a
capital ship with spine and ribs.
Three things drew an enemy from its definition - the enemy, the map editor's
markers, and the palette buttons - each reimplementing it from hull_points
alone, so the new detail appeared in the game and nowhere else and an editor
marker stopped matching what it placed. They now share
EnemyDefinitions.draw_definition().
ARTILLERY. The 1.8s charge-up before the tower fired arrived long before the
threat was legible and mostly read as the tower being stuck. It fires
immediately now; the warning is the shell you can see in the air plus a small
cross on the ground at the impact point. Deliberately not a blast-radius ring
- a large translucent area was easy to read as the explosion having already
happened, where the cross says "move off this spot".
MAIN MENU. No Quit in any build (a web build has nothing to quit to, and
Android's back gesture already does it), a plain black background, and the
game is "Spaceship" - in the title and in config/name, which is what drives
the browser tab for the web export. change_scene_to_file() returns an error
and otherwise fails silently, leaving the menu up, which is indistinguishable
from an unpressed button; it is now checked and reported on screen.
Full suite green at 99/99.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:54:46 +02:00
|
|
|
var selector_size: float = minf(band_controls * 0.62, centre_width * 0.30)
|
|
|
|
|
var selector_y: float = controls_y + (band_controls - selector_size) * 0.5
|
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)
|
Stacking distorders, menu layouts, lab rewards, enemy detail, artillery cross
DISTORDER STACKING. Each space-time distorder vessel past the first raises
the fleet's speed ceiling by 1.0, so a second one is worth crewing instead of
being dead weight. GameState gains max_fleet_speed_multiplier, pushed by
Fleet whenever composition changes - the same direction as every other
fleet-derived figure, since GameState does not reach into the scene tree to
count ships. The energy drain is normalised against the CURRENT ceiling, so
full boost costs the same per second however wide it is: extra vessels buy
more speed, not cheaper speed. Losing one re-clamps an over-ceiling boost
rather than leaving the fleet travelling faster than its crew can sustain.
TRANSFER MENU. Rows overlapped, and the last one ran into the hint text.
row_height is divided from the available space, but each row's contents used
a fixed pixel ladder - glyph at +10, buttons at +88, selector at +142 - which
needs about 244px. Three resources fit; the fourth cut rows to 180 and every
one spilled 64px into the next. Every offset is now a fraction of the row, so
it cannot overflow. Also dropped the per-row AMOUNT caption (four copies of a
word the footer hint already explains) and moved each glyph and name to the
TOP of its row, where they read as its heading rather than the next row's.
HANGAR. Six vessel types in one row gave each about 100px for a 155px label,
so the names ran together. Now a 3x2 grid per section, sized from the space
between the first heading and the status line, using the empty lower half.
LAB RESEARCH REWARD. A harder enemy lab is worth more research points, and
how hard a lab is depends on the layout authored for it - how many capture
points, how many defenders - which the planet TYPE cannot know. PlanetSetup
carries research_point_reward, set in the map editor, overriding the planet
type's flat rate. 0 means "no opinion", so every map authored before this
keeps its old value.
RESEARCH LAB BACKGROUND. Its own shader instead of the crater field with a
red tint, which read as "rocky planet, tinted" rather than someone's
facility: plated floor, seams, service conduits, lit junctions. Loop-free by
construction (#68's lesson applied up front) - conduits stay continuous
across cells by keying a horizontal run to cell.y and a vertical to cell.x,
so neighbouring cells agree without any cell scanning its neighbours.
ENEMY DETAIL. Every enemy was a single flat outline, which left the artillery
and the turret spawner as the same octagon at two sizes - the silhouette
carried no information about what the thing did. EnemyDefinition gains
detail_lines and detail_rings as DATA, so adding an enemy still means adding
a definition rather than editing Enemy.gd, and the four non-default hulls are
redrawn: a long-barrelled sniper, a squat mortar, a hive with launch bays, a
capital ship with spine and ribs.
Three things drew an enemy from its definition - the enemy, the map editor's
markers, and the palette buttons - each reimplementing it from hull_points
alone, so the new detail appeared in the game and nowhere else and an editor
marker stopped matching what it placed. They now share
EnemyDefinitions.draw_definition().
ARTILLERY. The 1.8s charge-up before the tower fired arrived long before the
threat was legible and mostly read as the tower being stuck. It fires
immediately now; the warning is the shell you can see in the air plus a small
cross on the ground at the impact point. Deliberately not a blast-radius ring
- a large translucent area was easy to read as the explosion having already
happened, where the cross says "move off this spot".
MAIN MENU. No Quit in any build (a web build has nothing to quit to, and
Android's back gesture already does it), a plain black background, and the
game is "Spaceship" - in the title and in config/name, which is what drives
the browser tab for the web export. change_scene_to_file() returns an error
and otherwise fails silently, leaving the menu up, which is indistinguishable
from an unpressed button; it is now checked and reported on screen.
Full suite green at 99/99.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:54:46 +02:00
|
|
|
|
|
|
|
|
var caption_y: float = controls_y + band_controls
|
|
|
|
|
row.unload_label.position = Vector2(0.0, caption_y)
|
|
|
|
|
row.unload_label.size = Vector2(side_width, band_captions)
|
|
|
|
|
row.load_label.position = Vector2(right_x, caption_y)
|
|
|
|
|
row.load_label.size = Vector2(side_width, band_captions)
|
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)
|