Reviewed against the rule issues #6 and #10 set for the buildings: geometry is rasterised once and thereafter only moved, scaled or faded, because every NeonDraw call expands into four antialiased polylines. The planet visuals never got that refactor. The black hole rebuilt ten glowing paths of twenty-five points every frame - forty polylines - to spin five rings; each ring is a NeonPiece that rotates now. The research lab spent nine draw calls a frame, eight of them fixed geometry, to turn one dish and blink six lights; the dish is a child that rotates and the lights a child whose modulate breathes. The gas giant's bands genuinely deform, so they still draw, but its constant rim moved out to a child. NeonPiece gained add_path, which it needed for open arcs. And I had broken the rule myself. ScannerGuidance re-queued a redraw of its arrow every frame for a colour that changes only with the destination - and it runs with PROCESS_MODE_ALWAYS, so it was re-tessellating through every dialog that stops the rest of the game. Guarded, and pinned by a test that counts CanvasItem's own draw signal: 36 rasterisations in 0.6s before, one after. Two smaller things from the same pass: the station menu drew its condition bar by hand where the ship lab already uses a ProgressBar node, so it uses one now and its _draw is gone; and station/upkeep refreshes were wired into _refresh_garrison(), which nothing but a test calls, so both menus showed whatever was true when they opened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
114 lines
4.5 KiB
GDScript
114 lines
4.5 KiB
GDScript
extends "res://ui/menus/ModalMenu.gd"
|
|
|
|
# Forgejo #216: transport hubs and spacial workshops used to conjure their own
|
|
# crews, free and permanent. The player orders them at a ship crafter now, and
|
|
# this is where they say how many they want standing HERE.
|
|
#
|
|
# The menu edits the ORDER, not the crew - the same split GarrisonPanel makes
|
|
# for outposts, and for the same reason: a hub that has asked for three and
|
|
# been sent one is waiting, not broken, and has to read that way.
|
|
#
|
|
# It also answers "how long will these last". Each vessel is spent by the work
|
|
# it does, so the bar row below shows what each of them has left, worst first,
|
|
# which is the number that decides whether another needs building.
|
|
|
|
# One bar, for the vessel closest to being spent - the one that decides
|
|
# whether another needs building. The per-vessel figures are listed as TEXT
|
|
# beneath it: a row of small bars was unreadable at the size a phone draws
|
|
# this menu, and "cycles left" is a number the player wants to read.
|
|
#
|
|
# A ProgressBar NODE rather than rects in _draw(), the way the ship lab draws
|
|
# its research progress. Issues #6/#10 set the rule for the rest of the game -
|
|
# geometry is rasterised once and thereafter only moved, scaled or recoloured
|
|
# - and a menu that refreshes while it is open would otherwise re-tessellate
|
|
# its bar every frame to move one edge.
|
|
const WORN_COLOR: Color = Color(1.0, 0.35, 0.25)
|
|
const FRESH_COLOR: Color = Color(0.5, 1.0, 0.55)
|
|
|
|
@onready var title: Label = $TitleLabel
|
|
@onready var assigned_label: Label = $AssignedLabel
|
|
@onready var status: Label = $StatusLabel
|
|
@onready var condition_label: Label = $ConditionLabel
|
|
@onready var condition_list: Label = $ConditionList
|
|
@onready var condition_bar: ProgressBar = $ConditionBar
|
|
@onready var less_button: IconButton = $LessButton
|
|
@onready var more_button: IconButton = $MoreButton
|
|
|
|
var building_id: int = 0
|
|
|
|
|
|
func _ready() -> void:
|
|
super._ready()
|
|
# ModalMenu already owns $CloseButton and wires it.
|
|
less_button.pressed.connect(func(): _step(-1))
|
|
more_button.pressed.connect(func(): _step(1))
|
|
|
|
|
|
func open_for(next_building_id: int) -> void:
|
|
building_id = next_building_id
|
|
open_menu()
|
|
refresh()
|
|
|
|
|
|
func close_menu() -> void:
|
|
super.close_menu()
|
|
building_id = 0
|
|
|
|
|
|
func _step(delta: int) -> void:
|
|
if building_id == 0:
|
|
return
|
|
GameState.set_station_desired(building_id,
|
|
GameState.station_desired(building_id) + delta)
|
|
refresh()
|
|
|
|
|
|
func refresh() -> void:
|
|
if building_id == 0:
|
|
return
|
|
var building: Dictionary = GameState.find_building(building_id)
|
|
if building.is_empty():
|
|
close_menu()
|
|
return
|
|
var vessel_type: String = GameState.station_vessel_type(building)
|
|
var wanted: int = GameState.station_desired(building_id)
|
|
var here: int = GameState.station_crew(building_id)
|
|
var capacity: int = GameState.station_capacity(building)
|
|
|
|
title.text = String(building.get("type", "")).to_upper().replace("_", " ")
|
|
assigned_label.text = "%d / %d ASSIGNED (room for %d)" % [here, wanted, capacity]
|
|
less_button.enabled = wanted > 0
|
|
more_button.enabled = wanted < capacity
|
|
|
|
var name: String = GameState.vessel_name(vessel_type)
|
|
if wanted == 0:
|
|
status.text = "None assigned. Build %s at a ship crafter, then ask for them here." % name.to_lower()
|
|
elif here >= wanted:
|
|
status.text = "Fully crewed. Each one wears out with use and is not replaced by itself."
|
|
else:
|
|
status.text = "%d on order - sent from the mothership hangar as they are built." % (wanted - here)
|
|
|
|
var left: Array = GameState.station_condition(building_id)
|
|
var full: int = maxi(GameState.station_work_capacity(building_id), 1)
|
|
var unit: String = "RUNS" if vessel_type == GameState.VESSEL_TRANSPORTER else "REPAIRS"
|
|
condition_bar.visible = not left.is_empty()
|
|
if left.is_empty():
|
|
condition_label.text = ""
|
|
condition_list.text = ""
|
|
else:
|
|
condition_label.text = "CONDITION - %s LEFT" % unit
|
|
# Worst first, which is the order station_condition() answers in: the
|
|
# one about to be spent is the one being decided about.
|
|
var lines: PackedStringArray = PackedStringArray()
|
|
for i in range(left.size()):
|
|
lines.append("%s %d %d / %d %s" % [
|
|
name.to_upper(), i + 1, int(left[i]), full, unit])
|
|
condition_list.text = "\n".join(lines)
|
|
# Worst first, so this is the one about to go.
|
|
var fraction: float = clampf(float(left[0]) / float(full), 0.0, 1.0)
|
|
condition_bar.value = fraction
|
|
# The fill is recoloured rather than redrawn: a StyleBox is shared
|
|
# geometry, and setting its colour does not re-tessellate anything.
|
|
var fill: StyleBoxFlat = condition_bar.get_theme_stylebox("fill")
|
|
if fill != null:
|
|
fill.bg_color = WORN_COLOR.lerp(FRESH_COLOR, fraction)
|