#106/#107 (#98a/#98b): the Planning Center
Landed together: a board of standing orders nothing acts on is a notepad, and a planner with nowhere to read the plan from has nothing to do. The targets are GLOBAL, not per building. There is one plan for the whole game, and a second planning centre is another place to read and edit it rather than a competing one - per-building targets would let two centres hold contradictory orders and tell the crafters to satisfy both. The census is the harder half. Every vessel is counted exactly once wherever it happens to be: flying in the fleet, parked in the hangar, stationed at an outpost, or in the air between any two of those. Miss the in-flight ones and the planner queues a replacement for a vessel that is merely en route. The fleet is the one place GameState cannot see, so Fleet pushes its composition on every change, alongside the cargo capacity and distorder count that already went that way. Three rules keep the planner from fighting the player. It tops up and never rewrites, so an order placed by hand stays exactly where it is. Everything counts, including what is already queued, or it re-queues the same shortfall every tick and buries the player in hulls. And nothing happens without a planning centre - though the plan is remembered when one is destroyed, so rebuilding picks up where it left off. Vessels on order are counted separately from the census on purpose: they do not exist yet, so the readout can say "4 of 6, 2 building" rather than claiming six. The per-row note says where the vessels actually are, because a plan can look unmet while everything it asked for is sitting at an outpost. The build menu goes to three columns past eight entries. #54 already tightened its rows to 223px to fit a fourth, explicitly to avoid a scrollable menu, and a ninth structure needs a fifth row that will not fit at any spacing a button, a label and a cost can share. Three columns take nine into three rows - fewer than it uses today, not more. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
bd0cc842ff
commit
0c595d775d
12 changed files with 883 additions and 16 deletions
|
|
@ -166,6 +166,14 @@ var lab_building_id: int = 0
|
|||
@onready var garrison_close_button: IconButton = get_node("../UI/GarrisonMenu/CloseButton")
|
||||
var garrison_building_id: int = 0
|
||||
|
||||
# #106: the standing-order board. No building id is remembered because the
|
||||
# plan is global - there is one plan for the whole game, and a second
|
||||
# planning centre is another place to read it rather than a competing one.
|
||||
@onready var planning_menu: Control = get_node("../UI/PlanningMenu")
|
||||
@onready var planning_panel: Control = get_node("../UI/PlanningMenu/Panel")
|
||||
@onready var planning_status: Label = get_node("../UI/PlanningMenu/StatusLabel")
|
||||
@onready var planning_close_button: IconButton = get_node("../UI/PlanningMenu/CloseButton")
|
||||
|
||||
@onready var skill_menu: Control = get_node("../UI/SkillMenu")
|
||||
@onready var skill_tree: Control = get_node("../UI/SkillMenu/Tree")
|
||||
@onready var skill_detail: Label = get_node("../UI/SkillMenu/DetailLabel")
|
||||
|
|
@ -263,6 +271,8 @@ func _ready() -> void:
|
|||
build_close_button.pressed.connect(_close_build_menu)
|
||||
garrison_close_button.pressed.connect(_close_garrison_menu)
|
||||
garrison_panel.changed.connect(_refresh_garrison)
|
||||
planning_close_button.pressed.connect(_close_planning_menu)
|
||||
planning_panel.changed.connect(_refresh_planning)
|
||||
_configure_build_menu_entries()
|
||||
lab_close_button.pressed.connect(func(): lab_menu.visible = false)
|
||||
lab_buy_button.pressed.connect(_on_lab_buy)
|
||||
|
|
@ -598,6 +608,9 @@ func _on_action_pressed() -> void:
|
|||
elif action == "formation":
|
||||
formation_menu.visible = true
|
||||
formation_editor.queue_redraw()
|
||||
elif action == "planning":
|
||||
planning_menu.visible = true
|
||||
_refresh_planning()
|
||||
elif action == "garrison":
|
||||
garrison_building_id = current_building.building_id
|
||||
garrison_panel.building_id = garrison_building_id
|
||||
|
|
@ -617,9 +630,17 @@ func _planet_under_fleet() -> Node2D:
|
|||
return planet_field.get_planet_fleet_is_on()
|
||||
|
||||
|
||||
# Grid geometry, shared with the layout below. Rows of two, and the menu is
|
||||
# rebuilt from whatever is currently researched (#21) rather than being a
|
||||
# fixed arrangement with holes punched in it.
|
||||
# Grid geometry, shared with the layout below. The menu is rebuilt from
|
||||
# whatever is currently researched (#21) rather than being a fixed
|
||||
# arrangement with holes punched in it.
|
||||
#
|
||||
# Two profiles, because the grid ran out of room. #54 already tightened the
|
||||
# rows to 223px to fit a fourth, explicitly to avoid a scrollable menu, and a
|
||||
# ninth structure (#106's planning centre) needs a FIFTH row that will not
|
||||
# fit at any spacing a button, a label and a cost can share. So past eight
|
||||
# entries the grid goes to three columns instead, which takes nine into three
|
||||
# rows - fewer than it uses today, not more. Narrower columns mean narrower
|
||||
# labels, so those get a taller box to wrap into.
|
||||
const MENU_ROW_TOPS: Array = [-300.0, -77.0, 146.0, 369.0]
|
||||
const MENU_COL_X: float = 140.0
|
||||
const MENU_BUTTON_HALF: float = 64.0
|
||||
|
|
@ -629,13 +650,17 @@ const MENU_LABEL_H: float = 52.0
|
|||
const MENU_COST_HALF: float = 60.0
|
||||
const MENU_COST_H: float = 44.0
|
||||
# Gaps from each row's own top to where the label/cost start, sized to the
|
||||
# shrunk MENU_*_H values above (#54: fitting a 4th row meant tightening
|
||||
# MENU_ROW_TOPS' spacing to 223px, less than the original 256px a row's
|
||||
# button+label+cost needed - shrinking those closed the gap without
|
||||
# resorting to a scrollable menu).
|
||||
# shrunk MENU_*_H values above.
|
||||
const MENU_LABEL_OFFSET: float = 106.0
|
||||
const MENU_COST_OFFSET: float = 162.0
|
||||
|
||||
const MENU_WIDE_THRESHOLD: int = 8
|
||||
const MENU_ROW_TOPS_WIDE: Array = [-330.0, -85.0, 160.0]
|
||||
const MENU_COL_X_WIDE: float = 224.0
|
||||
const MENU_LABEL_HALF_WIDE: float = 108.0
|
||||
const MENU_LABEL_H_WIDE: float = 68.0
|
||||
const MENU_COST_OFFSET_WIDE: float = 178.0
|
||||
|
||||
|
||||
func _place_entry(node: Control, cx: float, top: float, half: float, height: float) -> void:
|
||||
node.offset_left = cx - half
|
||||
|
|
@ -659,16 +684,26 @@ func _layout_build_menu() -> void:
|
|||
if visible:
|
||||
shown.append(entry)
|
||||
|
||||
var wide: bool = shown.size() > MENU_WIDE_THRESHOLD
|
||||
var columns: int = 3 if wide else 2
|
||||
var tops: Array = MENU_ROW_TOPS_WIDE if wide else MENU_ROW_TOPS
|
||||
var col_x: float = MENU_COL_X_WIDE if wide else MENU_COL_X
|
||||
var label_half: float = MENU_LABEL_HALF_WIDE if wide else MENU_LABEL_HALF
|
||||
var label_h: float = MENU_LABEL_H_WIDE if wide else MENU_LABEL_H
|
||||
var cost_offset: float = MENU_COST_OFFSET_WIDE if wide else MENU_COST_OFFSET
|
||||
|
||||
for i in range(shown.size()):
|
||||
var row: int = i / 2
|
||||
# Centred when it is the only one on its row, rather than hanging in
|
||||
# the left column looking like something is missing beside it.
|
||||
var alone: bool = (i == shown.size() - 1) and (i % 2 == 0)
|
||||
var cx: float = 0.0 if alone else (-MENU_COL_X if i % 2 == 0 else MENU_COL_X)
|
||||
var top: float = MENU_ROW_TOPS[mini(row, MENU_ROW_TOPS.size() - 1)]
|
||||
var row: int = i / columns
|
||||
# A short last row is CENTRED rather than left-packed, so it does not
|
||||
# read as a row with something missing beside it.
|
||||
var in_row: int = mini(shown.size() - row * columns, columns)
|
||||
var col: int = i % columns
|
||||
var cx: float = (float(col) - (float(in_row) - 1.0) * 0.5) * col_x * 2.0 \
|
||||
/ maxf(float(columns) - 1.0, 1.0)
|
||||
var top: float = tops[mini(row, tops.size() - 1)]
|
||||
_place_entry(shown[i][1], cx, top, MENU_BUTTON_HALF, MENU_BUTTON_H)
|
||||
_place_entry(shown[i][2], cx, top + MENU_LABEL_OFFSET, MENU_LABEL_HALF, MENU_LABEL_H)
|
||||
_place_entry(shown[i][3], cx, top + MENU_COST_OFFSET, MENU_COST_HALF, MENU_COST_H)
|
||||
_place_entry(shown[i][2], cx, top + MENU_LABEL_OFFSET, label_half, label_h)
|
||||
_place_entry(shown[i][3], cx, top + cost_offset, MENU_COST_HALF, MENU_COST_H)
|
||||
|
||||
|
||||
func _refresh_build_status() -> void:
|
||||
|
|
@ -731,6 +766,25 @@ func _refresh_garrison() -> void:
|
|||
% [crewed, ordered]
|
||||
|
||||
|
||||
# --- the planning centre's standing orders (#106) ---------------------------
|
||||
|
||||
func _close_planning_menu() -> void:
|
||||
planning_menu.visible = false
|
||||
|
||||
|
||||
func _refresh_planning() -> void:
|
||||
planning_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))])
|
||||
if short.is_empty():
|
||||
planning_status.text = "Every standing order is met or already being built"
|
||||
else:
|
||||
planning_status.text = "Short: %s" % ", ".join(short)
|
||||
|
||||
|
||||
func cost_of(type: String) -> int:
|
||||
var definition: Resource = BUILDING_DEFINITIONS.get_definition(type)
|
||||
return int(definition.cost) if definition != null else 0
|
||||
|
|
|
|||
Loading…
Reference in a new issue