Remove the one-way ore API superseded by the transfer menu
The loose end from #24. withdraw_to_fleet, unload_into_building and deliver_cargo were the old one-way moves behind "Load ore" and "Store ore here"; every one of them is a special case of transfer_to_building / transfer_to_fleet, and none had a caller left outside tests. The cargo_delivered signal had no listeners at all. Not just tidying: unload_into_building routed through deposit_to_building, which does NOT special-case the mothership - it writes b.stock, while building_holdings() returns `rocks` for a mothership and ignores stock entirely. So unload_into_building( mothership_id) would have parked ore in a field nothing reads, with no error. Nothing called it that way, but it is a plausibly-named function sitting there to be reached for later. transfer_to_building handles it. CARGO_LOAD_BATCH stays - the transfer menu's batch buttons use it; only its role as withdraw_to_fleet's default argument is gone. The #24 note expected folding these in to be costly for test_cargo_readout, which places no buildings. It is not: the transfer primitives work off GameState records rather than spawned nodes, so a single add_building() in setup is enough and it stays a display test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
fe1c721d64
commit
b6427b080d
27 changed files with 1085 additions and 275 deletions
|
|
@ -74,12 +74,34 @@ var lab_choice: int = 0
|
||||||
@onready var craft_menu: Control = get_node("../UI/CraftMenu")
|
@onready var craft_menu: Control = get_node("../UI/CraftMenu")
|
||||||
@onready var craft_interceptor_button: IconButton = get_node("../UI/CraftMenu/InterceptorButton")
|
@onready var craft_interceptor_button: IconButton = get_node("../UI/CraftMenu/InterceptorButton")
|
||||||
@onready var craft_transporter_button: IconButton = get_node("../UI/CraftMenu/TransporterButton")
|
@onready var craft_transporter_button: IconButton = get_node("../UI/CraftMenu/TransporterButton")
|
||||||
@onready var craft_store_button: IconButton = get_node("../UI/CraftMenu/StoreOreButton")
|
|
||||||
@onready var craft_store_label: Label = get_node("../UI/CraftMenu/StoreOreLabel")
|
|
||||||
@onready var craft_close_button: IconButton = get_node("../UI/CraftMenu/CloseButton")
|
@onready var craft_close_button: IconButton = get_node("../UI/CraftMenu/CloseButton")
|
||||||
@onready var craft_status: Label = get_node("../UI/CraftMenu/StatusLabel")
|
@onready var craft_status: Label = get_node("../UI/CraftMenu/StatusLabel")
|
||||||
@onready var load_ore_button: IconButton = get_node("../UI/SkillMenu/LoadOreButton")
|
|
||||||
@onready var load_ore_label: Label = get_node("../UI/SkillMenu/LoadOreLabel")
|
# --- the transfer panel (#24) ---------------------------------------------
|
||||||
|
# Ore used to be moved from inside whichever panel happened to be open: "load
|
||||||
|
# ore" lived in the research screen, "store ore here" in the ship crafter.
|
||||||
|
# Both are now one top-level button that works at every building and in both
|
||||||
|
# directions, so moving ore is never buried under an unrelated menu.
|
||||||
|
@onready var transfer_button: IconButton = get_node("../UI/TransferButton")
|
||||||
|
@onready var transfer_menu: Control = get_node("../UI/TransferMenu")
|
||||||
|
@onready var transfer_status: Label = get_node("../UI/TransferMenu/StatusLabel")
|
||||||
|
@onready var transfer_close_button: IconButton = get_node("../UI/TransferMenu/CloseButton")
|
||||||
|
@onready var to_building_label: Label = get_node("../UI/TransferMenu/ToBuildingLabel")
|
||||||
|
@onready var to_building_batch_button: IconButton = get_node("../UI/TransferMenu/ToBuildingBatchButton")
|
||||||
|
@onready var to_building_batch_label: Label = get_node("../UI/TransferMenu/ToBuildingBatchLabel")
|
||||||
|
@onready var to_building_all_button: IconButton = get_node("../UI/TransferMenu/ToBuildingAllButton")
|
||||||
|
@onready var to_fleet_label: Label = get_node("../UI/TransferMenu/ToFleetLabel")
|
||||||
|
@onready var to_fleet_batch_button: IconButton = get_node("../UI/TransferMenu/ToFleetBatchButton")
|
||||||
|
@onready var to_fleet_batch_label: Label = get_node("../UI/TransferMenu/ToFleetBatchLabel")
|
||||||
|
@onready var to_fleet_all_button: IconButton = get_node("../UI/TransferMenu/ToFleetAllButton")
|
||||||
|
|
||||||
|
# Every building menu carries the same header (#24). Held as a list because
|
||||||
|
# the only thing any of them needs is to be told which building it is
|
||||||
|
# reporting on when its panel opens.
|
||||||
|
@onready var resource_bars: Array = [
|
||||||
|
get_node("../UI/CraftMenu/Bar"), get_node("../UI/SkillMenu/Bar"),
|
||||||
|
get_node("../UI/LabMenu/Bar"), get_node("../UI/TransferMenu/Bar"),
|
||||||
|
]
|
||||||
|
|
||||||
@onready var skill_menu: Control = get_node("../UI/SkillMenu")
|
@onready var skill_menu: Control = get_node("../UI/SkillMenu")
|
||||||
@onready var skill_tree: Control = get_node("../UI/SkillMenu/Tree")
|
@onready var skill_tree: Control = get_node("../UI/SkillMenu/Tree")
|
||||||
|
|
@ -141,6 +163,12 @@ var craft_building_id: int = 0
|
||||||
var craft_message: String = ""
|
var craft_message: String = ""
|
||||||
var craft_message_until: int = 0
|
var craft_message_until: int = 0
|
||||||
|
|
||||||
|
# Which building the transfer panel is moving ore in and out of. Remembered
|
||||||
|
# for the same reason craft_building_id is: _process() clears
|
||||||
|
# current_building while any menu is open, so by the next frame the panel
|
||||||
|
# would not know which building it belongs to.
|
||||||
|
var transfer_building_id: int = 0
|
||||||
|
|
||||||
var current_building: Node2D = null
|
var current_building: Node2D = null
|
||||||
var pending_building: Node2D = null
|
var pending_building: Node2D = null
|
||||||
var pending_time: float = 0.0
|
var pending_time: float = 0.0
|
||||||
|
|
@ -167,11 +195,16 @@ func _ready() -> void:
|
||||||
action_button.pressed.connect(_on_action_pressed)
|
action_button.pressed.connect(_on_action_pressed)
|
||||||
|
|
||||||
craft_interceptor_button.pressed.connect(_on_craft_interceptor)
|
craft_interceptor_button.pressed.connect(_on_craft_interceptor)
|
||||||
load_ore_button.pressed.connect(_on_load_ore)
|
|
||||||
craft_transporter_button.pressed.connect(_on_craft_transporter)
|
craft_transporter_button.pressed.connect(_on_craft_transporter)
|
||||||
craft_store_button.pressed.connect(_on_store_ore)
|
|
||||||
craft_close_button.pressed.connect(func(): craft_menu.visible = false)
|
craft_close_button.pressed.connect(func(): craft_menu.visible = false)
|
||||||
|
|
||||||
|
transfer_button.pressed.connect(_on_transfer_pressed)
|
||||||
|
transfer_close_button.pressed.connect(func(): transfer_menu.visible = false)
|
||||||
|
to_building_batch_button.pressed.connect(_on_transfer.bind(true, GameState.CARGO_LOAD_BATCH))
|
||||||
|
to_building_all_button.pressed.connect(_on_transfer.bind(true, ALL))
|
||||||
|
to_fleet_batch_button.pressed.connect(_on_transfer.bind(false, GameState.CARGO_LOAD_BATCH))
|
||||||
|
to_fleet_all_button.pressed.connect(_on_transfer.bind(false, ALL))
|
||||||
|
|
||||||
formation_close_button.pressed.connect(func(): formation_menu.visible = false)
|
formation_close_button.pressed.connect(func(): formation_menu.visible = false)
|
||||||
formation_reset_button.pressed.connect(_on_reset_formation)
|
formation_reset_button.pressed.connect(_on_reset_formation)
|
||||||
formation_editor.formation_changed.connect(_on_formation_changed)
|
formation_editor.formation_changed.connect(_on_formation_changed)
|
||||||
|
|
@ -186,14 +219,16 @@ func _ready() -> void:
|
||||||
formation_menu.visible = false
|
formation_menu.visible = false
|
||||||
skill_menu.visible = false
|
skill_menu.visible = false
|
||||||
lab_menu.visible = false
|
lab_menu.visible = false
|
||||||
|
transfer_menu.visible = false
|
||||||
action_button.visible = false
|
action_button.visible = false
|
||||||
|
transfer_button.visible = false
|
||||||
|
|
||||||
|
|
||||||
# True while a building prompt or panel owns the shared action slot, so the
|
# True while a building prompt or panel owns the shared action slot, so the
|
||||||
# enter-atmosphere prompt knows to stay out of the way.
|
# enter-atmosphere prompt knows to stay out of the way.
|
||||||
func is_showing_building_prompt() -> bool:
|
func is_showing_building_prompt() -> bool:
|
||||||
return current_building != null or build_menu.visible or craft_menu.visible \
|
return current_building != null or build_menu.visible or craft_menu.visible \
|
||||||
or formation_menu.visible or skill_menu.visible
|
or formation_menu.visible or skill_menu.visible or transfer_menu.visible
|
||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
|
@ -218,7 +253,8 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
formation_editor.handle_drag(event)
|
formation_editor.handle_drag(event)
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
return
|
return
|
||||||
if build_menu.visible or craft_menu.visible or world_map.visible or lab_menu.visible:
|
if build_menu.visible or craft_menu.visible or world_map.visible or lab_menu.visible \
|
||||||
|
or transfer_menu.visible:
|
||||||
# These panels have no raw-input handler of their own, but the world
|
# These panels have no raw-input handler of their own, but the world
|
||||||
# behind them must still not react.
|
# behind them must still not react.
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
|
|
@ -239,11 +275,18 @@ func _process(delta: float) -> void:
|
||||||
# the queue down instead of looking stuck.
|
# the queue down instead of looking stuck.
|
||||||
_refresh_craft_status()
|
_refresh_craft_status()
|
||||||
|
|
||||||
|
if transfer_menu.visible:
|
||||||
|
# Also live: a hauler banking ore while the panel is open should make
|
||||||
|
# the "to fleet" side become available without a reopen.
|
||||||
|
_refresh_transfer()
|
||||||
|
|
||||||
if world_map.visible or build_menu.visible or craft_menu.visible \
|
if world_map.visible or build_menu.visible or craft_menu.visible \
|
||||||
or formation_menu.visible or skill_menu.visible or lab_menu.visible:
|
or formation_menu.visible or skill_menu.visible or lab_menu.visible \
|
||||||
|
or transfer_menu.visible:
|
||||||
current_building = null
|
current_building = null
|
||||||
pending_building = null
|
pending_building = null
|
||||||
action_button.visible = false
|
action_button.visible = false
|
||||||
|
transfer_button.visible = false
|
||||||
return
|
return
|
||||||
|
|
||||||
# Debounced exactly like the enter-atmosphere prompt, so the action
|
# Debounced exactly like the enter-atmosphere prompt, so the action
|
||||||
|
|
@ -261,6 +304,10 @@ func _process(delta: float) -> void:
|
||||||
|
|
||||||
current_building = pending_building
|
current_building = pending_building
|
||||||
action_button.visible = current_building != null
|
action_button.visible = current_building != null
|
||||||
|
# Every building can hold ore, so the transfer button rides alongside the
|
||||||
|
# action button rather than being one of the things the action button
|
||||||
|
# turns into - the two are independent choices at the same structure.
|
||||||
|
transfer_button.visible = current_building != null
|
||||||
if current_building != null:
|
if current_building != null:
|
||||||
action_button.icon = _action_icon_for(current_building.building_type)
|
action_button.icon = _action_icon_for(current_building.building_type)
|
||||||
action_button.queue_redraw()
|
action_button.queue_redraw()
|
||||||
|
|
@ -292,9 +339,14 @@ func _check_fleet_wipe() -> void:
|
||||||
func _on_action_pressed() -> void:
|
func _on_action_pressed() -> void:
|
||||||
if current_building == null:
|
if current_building == null:
|
||||||
return
|
return
|
||||||
|
# Whichever panel opens, its header reports on the building it was opened
|
||||||
|
# from. Set once here rather than in each branch, so a new building menu
|
||||||
|
# cannot forget to do it and silently show a blank HERE column.
|
||||||
|
for bar in resource_bars:
|
||||||
|
bar.building_id = current_building.building_id
|
||||||
|
|
||||||
if current_building.building_type == GameState.BUILDING_MOTHERSHIP:
|
if current_building.building_type == GameState.BUILDING_MOTHERSHIP:
|
||||||
skill_menu.visible = true
|
skill_menu.visible = true
|
||||||
_refresh_load_ore()
|
|
||||||
_refresh_skill_menu()
|
_refresh_skill_menu()
|
||||||
elif current_building.building_type == GameState.BUILDING_SHIP_CRAFTER:
|
elif current_building.building_type == GameState.BUILDING_SHIP_CRAFTER:
|
||||||
# Remembered explicitly: _process() clears current_building while any
|
# Remembered explicitly: _process() clears current_building while any
|
||||||
|
|
@ -591,14 +643,14 @@ func _say(message: String) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _refresh_craft_status() -> void:
|
func _refresh_craft_status() -> void:
|
||||||
# "Here" is the ore delivered to THIS crafter, not the bank: since #16 a
|
# The bank/hold/here figures moved out to the shared header (#24), so this
|
||||||
# building builds out of its own stock, and showing the bank would say
|
# line is now only what is specific to a crafter: prices, the queue, and
|
||||||
# you can afford something the crafter cannot actually make.
|
# berth pressure. It used to restate "Here %d" itself, which is exactly
|
||||||
|
# the per-panel improvisation the header replaces.
|
||||||
var queued: int = GameState.pending_orders(craft_building_id)
|
var queued: int = GameState.pending_orders(craft_building_id)
|
||||||
var stock: int = GameState.building_stock(craft_building_id)
|
|
||||||
var queue_text: String = "" if queued == 0 else " Queued %d" % queued
|
var queue_text: String = "" if queued == 0 else " Queued %d" % queued
|
||||||
craft_status.text = "Interceptor %d Transporter %d Here %d%s Transporters %d/%d" % [
|
craft_status.text = "Interceptor %d Transporter %d%s Transporters %d/%d" % [
|
||||||
GameState.SHIP_COST, GameState.TRANSPORTER_COST, stock, queue_text,
|
GameState.SHIP_COST, GameState.TRANSPORTER_COST, queue_text,
|
||||||
GameState.transporters_claimed(), GameState.transport_capacity()
|
GameState.transporters_claimed(), GameState.transport_capacity()
|
||||||
]
|
]
|
||||||
# Nowhere to station one means the order would be refused, so say so
|
# Nowhere to station one means the order would be refused, so say so
|
||||||
|
|
@ -609,12 +661,6 @@ func _refresh_craft_status() -> void:
|
||||||
|
|
||||||
craft_shield_button.enabled = GameState.shield_vessel_unlocked()
|
craft_shield_button.enabled = GameState.shield_vessel_unlocked()
|
||||||
craft_shield_button.queue_redraw()
|
craft_shield_button.queue_redraw()
|
||||||
craft_store_label.text = "Store ore here\nHold %d" % GameState.fleet_cargo
|
|
||||||
# Deliberately left ENABLED with an empty hold: a greyed-out button that
|
|
||||||
# cannot say why is exactly the dead-button problem this panel already
|
|
||||||
# had. Pressing it explains instead.
|
|
||||||
craft_store_button.enabled = true
|
|
||||||
craft_store_button.queue_redraw()
|
|
||||||
|
|
||||||
# A full fleet cannot take another vessel, so the order would be refused.
|
# A full fleet cannot take another vessel, so the order would be refused.
|
||||||
# Showing that on the button is the difference between "not available yet"
|
# Showing that on the button is the difference between "not available yet"
|
||||||
|
|
@ -663,17 +709,6 @@ func _on_craft_interceptor() -> void:
|
||||||
_report_order(GameState.PRODUCT_SHIP)
|
_report_order(GameState.PRODUCT_SHIP)
|
||||||
|
|
||||||
|
|
||||||
func _refresh_load_ore() -> void:
|
|
||||||
load_ore_label.text = "Load ore\nBank %d Hold %d" % [GameState.rocks, GameState.fleet_cargo]
|
|
||||||
load_ore_button.enabled = GameState.rocks > 0
|
|
||||||
load_ore_button.queue_redraw()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_load_ore() -> void:
|
|
||||||
GameState.withdraw_to_fleet()
|
|
||||||
_refresh_load_ore()
|
|
||||||
|
|
||||||
|
|
||||||
func _report_order(product: String) -> void:
|
func _report_order(product: String) -> void:
|
||||||
var short: int = GameState.outstanding_cost(craft_building_id)
|
var short: int = GameState.outstanding_cost(craft_building_id)
|
||||||
if short > 0:
|
if short > 0:
|
||||||
|
|
@ -684,17 +719,6 @@ func _report_order(product: String) -> void:
|
||||||
_say("%s ordered - building now" % GameState.VESSEL_NAMES.get(product, product))
|
_say("%s ordered - building now" % GameState.VESSEL_NAMES.get(product, product))
|
||||||
|
|
||||||
|
|
||||||
func _on_store_ore() -> void:
|
|
||||||
if craft_building_id == 0:
|
|
||||||
_say("No crafter selected - reopen this panel from the building")
|
|
||||||
return
|
|
||||||
if GameState.fleet_cargo <= 0:
|
|
||||||
_say("The fleet is carrying nothing - load ore at the mothership first")
|
|
||||||
return
|
|
||||||
var moved: int = GameState.unload_into_building(craft_building_id)
|
|
||||||
_say("Stored %d ore here" % moved)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_craft_transporter() -> void:
|
func _on_craft_transporter() -> void:
|
||||||
if craft_building_id == 0:
|
if craft_building_id == 0:
|
||||||
_say("No crafter selected - reopen this panel from the building")
|
_say("No crafter selected - reopen this panel from the building")
|
||||||
|
|
@ -823,6 +847,86 @@ func _on_craft_shield() -> void:
|
||||||
_report_order(GameState.VESSEL_SHIELD)
|
_report_order(GameState.VESSEL_SHIELD)
|
||||||
|
|
||||||
|
|
||||||
|
# --- moving ore, in both directions, at any building (#24) ----------------
|
||||||
|
|
||||||
|
# Sentinel for the "All" buttons. The transfer primitives cap what they move
|
||||||
|
# by what is actually there, so asking for more than could possibly exist is
|
||||||
|
# how you say "everything" without the UI having to work out how much that
|
||||||
|
# is first - and the answer cannot go stale between the read and the press.
|
||||||
|
const ALL: int = 1 << 30
|
||||||
|
|
||||||
|
|
||||||
|
func _on_transfer_pressed() -> void:
|
||||||
|
if current_building == null:
|
||||||
|
return
|
||||||
|
transfer_building_id = current_building.building_id
|
||||||
|
transfer_menu.visible = true
|
||||||
|
transfer_status.text = ""
|
||||||
|
_refresh_transfer()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_transfer(to_building: bool, amount: int) -> void:
|
||||||
|
if transfer_building_id == 0:
|
||||||
|
transfer_status.text = "No building selected - reopen this panel from the building"
|
||||||
|
return
|
||||||
|
|
||||||
|
var moved: int = 0
|
||||||
|
if to_building:
|
||||||
|
moved = GameState.transfer_to_building(transfer_building_id, amount)
|
||||||
|
else:
|
||||||
|
moved = GameState.transfer_to_fleet(transfer_building_id, amount)
|
||||||
|
|
||||||
|
# Nothing moving is the common case at an empty end, and a silent no-op
|
||||||
|
# is the dead-button problem this panel exists to remove. Say which side
|
||||||
|
# was empty rather than just refusing.
|
||||||
|
if moved <= 0:
|
||||||
|
if to_building:
|
||||||
|
transfer_status.text = "The fleet is carrying nothing to unload"
|
||||||
|
else:
|
||||||
|
transfer_status.text = "%s has no ore to load" % _transfer_place_name()
|
||||||
|
elif to_building:
|
||||||
|
transfer_status.text = "Moved %d ore into %s" % [moved, _transfer_place_name()]
|
||||||
|
else:
|
||||||
|
transfer_status.text = "Loaded %d ore into the fleet" % moved
|
||||||
|
_refresh_transfer()
|
||||||
|
|
||||||
|
|
||||||
|
func _transfer_place_name() -> String:
|
||||||
|
var b: Dictionary = GameState.find_building(transfer_building_id)
|
||||||
|
if b.is_empty():
|
||||||
|
return "this building"
|
||||||
|
if b.type == GameState.BUILDING_MOTHERSHIP:
|
||||||
|
# Worth naming as the bank rather than as a structure: this is the
|
||||||
|
# one building where unloading makes the ore spendable everywhere.
|
||||||
|
return "the bank"
|
||||||
|
return "this building"
|
||||||
|
|
||||||
|
|
||||||
|
func _refresh_transfer() -> void:
|
||||||
|
for bar in resource_bars:
|
||||||
|
bar.building_id = transfer_building_id
|
||||||
|
|
||||||
|
var hold: int = GameState.fleet_cargo
|
||||||
|
var here: int = GameState.building_holdings(transfer_building_id)
|
||||||
|
var batch: int = GameState.CARGO_LOAD_BATCH
|
||||||
|
|
||||||
|
to_building_label.text = "Unload into %s" % _transfer_place_name()
|
||||||
|
to_fleet_label.text = "Load from %s" % _transfer_place_name()
|
||||||
|
# The batch buttons say what they would actually move, not the nominal
|
||||||
|
# batch size: offering "25" when the hold has 4 in it is a promise the
|
||||||
|
# press cannot keep.
|
||||||
|
to_building_batch_label.text = "%d" % mini(batch, hold)
|
||||||
|
to_fleet_batch_label.text = "%d" % mini(batch, here)
|
||||||
|
|
||||||
|
to_building_batch_button.enabled = hold > 0
|
||||||
|
to_building_all_button.enabled = hold > 0
|
||||||
|
to_fleet_batch_button.enabled = here > 0
|
||||||
|
to_fleet_all_button.enabled = here > 0
|
||||||
|
for button in [to_building_batch_button, to_building_all_button,
|
||||||
|
to_fleet_batch_button, to_fleet_all_button]:
|
||||||
|
button.queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
func _on_formation_changed() -> void:
|
func _on_formation_changed() -> void:
|
||||||
fleet.apply_formation()
|
fleet.apply_formation()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,33 +11,20 @@ extends Node2D
|
||||||
|
|
||||||
var building_scene: PackedScene = preload("res://Building.tscn")
|
var building_scene: PackedScene = preload("res://Building.tscn")
|
||||||
var spawned: Array = []
|
var spawned: Array = []
|
||||||
# Whether the fleet was already standing on the mothership last frame - see
|
|
||||||
# the edge-triggered delivery in _process().
|
# Nothing happens here per frame any more. Arriving at the mothership used to
|
||||||
var _was_on_mothership: bool = false
|
# bank the fleet's hold automatically (#14), edge-triggered so that ore
|
||||||
|
# withdrawn while parked was not instantly taken back. #24 removes it
|
||||||
|
# entirely: unloading is now an explicit move in the transfer menu, in both
|
||||||
|
# directions and at every building, so an automatic one-way exception at the
|
||||||
|
# mothership was the odd case out - and it silently overrode a player who had
|
||||||
|
# just loaded the hold in order to carry ore somewhere else.
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
rebuild_all()
|
rebuild_all()
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
# Unloading the fleet's hold (#14). Done here because this is the node
|
|
||||||
# that already knows where the buildings are and already answers "is the
|
|
||||||
# fleet standing on one?" - and it uses the fleet CENTROID, the same
|
|
||||||
# stable single-number test Planet and Building already use, rather than
|
|
||||||
# per-ship collision that flickers as ships jostle in formation.
|
|
||||||
# EDGE-triggered on arrival, not continuous. Delivering every frame the
|
|
||||||
# fleet is parked on the mothership would make withdrawing impossible:
|
|
||||||
# ore loaded into the hold at the mothership (#17) would be banked again
|
|
||||||
# the very next frame, and the button would look broken.
|
|
||||||
var under: Node2D = get_building_fleet_is_on()
|
|
||||||
var on_mothership: bool = under != null \
|
|
||||||
and under.building_type == GameState.BUILDING_MOTHERSHIP
|
|
||||||
if on_mothership and not _was_on_mothership and GameState.fleet_cargo > 0:
|
|
||||||
GameState.deliver_cargo()
|
|
||||||
_was_on_mothership = on_mothership
|
|
||||||
|
|
||||||
|
|
||||||
func rebuild_all() -> void:
|
func rebuild_all() -> void:
|
||||||
for b in spawned:
|
for b in spawned:
|
||||||
if is_instance_valid(b):
|
if is_instance_valid(b):
|
||||||
|
|
|
||||||
81
GameState.gd
81
GameState.gd
|
|
@ -38,53 +38,70 @@ var rocks: int = 500
|
||||||
# surface would otherwise be dropped the moment the player took off.
|
# surface would otherwise be dropped the moment the player took off.
|
||||||
var fleet_cargo: int = 0
|
var fleet_cargo: int = 0
|
||||||
|
|
||||||
signal cargo_delivered(amount: int)
|
|
||||||
|
|
||||||
|
|
||||||
func collect_cargo(amount: int = 1) -> void:
|
func collect_cargo(amount: int = 1) -> void:
|
||||||
fleet_cargo += amount
|
fleet_cargo += amount
|
||||||
|
|
||||||
|
|
||||||
# One press of "load ore" at the mothership. A batch rather than the whole
|
# The batch size the transfer menu offers, next to "All". A batch rather
|
||||||
# bank: the hold is the part that can be lost, and emptying the bank in one
|
# than only all-or-nothing: the hold is the part that can be lost, and
|
||||||
# tap would also strand research, which still spends from it.
|
# emptying the bank in one tap would strand research, which spends from it.
|
||||||
const CARGO_LOAD_BATCH: int = 25
|
const CARGO_LOAD_BATCH: int = 25
|
||||||
|
|
||||||
|
|
||||||
func withdraw_to_fleet(amount: int = CARGO_LOAD_BATCH) -> int:
|
# --- moving ore by hand, in either direction (#24) --------------------------
|
||||||
# Banked ore back into the hold, so the fleet can carry it to a building
|
#
|
||||||
# that is waiting on it (#17).
|
# One pair of primitives the transfer menu drives, rather than the two
|
||||||
var taken: int = mini(amount, rocks)
|
# one-way special cases that used to live inside the skill and craft panels
|
||||||
if taken <= 0:
|
# ("load ore" at the mothership, "store ore here" at a crafter).
|
||||||
return 0
|
#
|
||||||
rocks -= taken
|
# The mothership is deliberately not a special case in the UI: it is simply
|
||||||
fleet_cargo += taken
|
# the building whose holdings ARE the bank. Depositing at one banks the ore,
|
||||||
return taken
|
# withdrawing from one draws on the bank, and every other building keeps its
|
||||||
|
# own stock (#16). That is what lets one menu serve every structure.
|
||||||
|
|
||||||
|
|
||||||
func unload_into_building(id: int) -> int:
|
func building_holdings(id: int) -> int:
|
||||||
# Everything the fleet is carrying, into this building's stock. Returns
|
var b: Dictionary = find_building(id)
|
||||||
# how much moved so a caller can tell the player something happened.
|
if b.is_empty():
|
||||||
if fleet_cargo <= 0:
|
|
||||||
return 0
|
return 0
|
||||||
var moved: int = fleet_cargo
|
if b.type == BUILDING_MOTHERSHIP:
|
||||||
if deposit_to_building(id, moved) <= 0:
|
return rocks
|
||||||
|
return int(b.get("stock", 0))
|
||||||
|
|
||||||
|
|
||||||
|
func transfer_to_building(id: int, amount: int) -> int:
|
||||||
|
# Hold -> this building. Returns how much actually moved, which is capped
|
||||||
|
# by the hold: a caller asking for "all" passes a number too big on
|
||||||
|
# purpose and reads the result back.
|
||||||
|
var b: Dictionary = find_building(id)
|
||||||
|
if b.is_empty():
|
||||||
return 0
|
return 0
|
||||||
fleet_cargo = 0
|
var moved: int = mini(amount, fleet_cargo)
|
||||||
|
if moved <= 0:
|
||||||
|
return 0
|
||||||
|
fleet_cargo -= moved
|
||||||
|
if b.type == BUILDING_MOTHERSHIP:
|
||||||
|
rocks += moved
|
||||||
|
else:
|
||||||
|
b.stock = int(b.get("stock", 0)) + moved
|
||||||
return moved
|
return moved
|
||||||
|
|
||||||
|
|
||||||
func deliver_cargo() -> int:
|
func transfer_to_fleet(id: int, amount: int) -> int:
|
||||||
# Everything the fleet is carrying, banked at once. Returns how much moved
|
# This building -> hold. Capped by what the building actually holds, so
|
||||||
# so a caller can react to a delivery actually having happened rather than
|
# there is no overdraft in either direction.
|
||||||
# to merely being in range.
|
var b: Dictionary = find_building(id)
|
||||||
if fleet_cargo <= 0:
|
if b.is_empty():
|
||||||
return 0
|
return 0
|
||||||
var delivered: int = fleet_cargo
|
var moved: int = mini(amount, building_holdings(id))
|
||||||
rocks += delivered
|
if moved <= 0:
|
||||||
fleet_cargo = 0
|
return 0
|
||||||
cargo_delivered.emit(delivered)
|
if b.type == BUILDING_MOTHERSHIP:
|
||||||
return delivered
|
rocks -= moved
|
||||||
|
else:
|
||||||
|
b.stock = int(b.get("stock", 0)) - moved
|
||||||
|
fleet_cargo += moved
|
||||||
|
return moved
|
||||||
|
|
||||||
const MOTHERSHIP_COST: int = 20
|
const MOTHERSHIP_COST: int = 20
|
||||||
const SHIP_CRAFTER_COST: int = 12
|
const SHIP_CRAFTER_COST: int = 12
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ enum Icon {
|
||||||
# and the command centre sharing a folded-map glyph and neither of them
|
# and the command centre sharing a folded-map glyph and neither of them
|
||||||
# looking anything like the structure they build.
|
# looking anything like the structure they build.
|
||||||
MOTHERSHIP, CRAFTER, COMMAND_CENTER, EXTRACTOR, TRANSPORT_HUB, SHIP_LAB,
|
MOTHERSHIP, CRAFTER, COMMAND_CENTER, EXTRACTOR, TRANSPORT_HUB, SHIP_LAB,
|
||||||
|
# Moving ore between the fleet and a building (#24). Two arrows passing
|
||||||
|
# each other: the single up/down arrows already mean "load" and "store"
|
||||||
|
# one way each, and this button is the place where both live.
|
||||||
|
TRANSFER,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Size the glyphs were originally drawn against. Every glyph dimension is
|
# Size the glyphs were originally drawn against. Every glyph dimension is
|
||||||
|
|
@ -147,6 +151,8 @@ func _draw() -> void:
|
||||||
_draw_transport_hub_icon(center, pulse, s)
|
_draw_transport_hub_icon(center, pulse, s)
|
||||||
Icon.SHIP_LAB:
|
Icon.SHIP_LAB:
|
||||||
_draw_ship_lab_icon(center, pulse, s)
|
_draw_ship_lab_icon(center, pulse, s)
|
||||||
|
Icon.TRANSFER:
|
||||||
|
_draw_transfer_icon(center, pulse, s)
|
||||||
|
|
||||||
|
|
||||||
func _draw_close(center: Vector2, pulse: float, s: float) -> void:
|
func _draw_close(center: Vector2, pulse: float, s: float) -> void:
|
||||||
|
|
@ -172,6 +178,27 @@ func _draw_arrow(center: Vector2, pulse: float, direction: float, s: float) -> v
|
||||||
NeonDraw.draw_glowing_line(self, tip, tip + Vector2(r * 0.7, -r * 0.8 * direction), _active_color(), pulse, 0.5 * s, core_color)
|
NeonDraw.draw_glowing_line(self, tip, tip + Vector2(r * 0.7, -r * 0.8 * direction), _active_color(), pulse, 0.5 * s, core_color)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_transfer_icon(center: Vector2, pulse: float, s: float) -> void:
|
||||||
|
# Two shafts side by side pointing opposite ways. Drawn as a pair rather
|
||||||
|
# than as one double-headed arrow because the button's whole job is that
|
||||||
|
# ore moves BOTH ways here, and a double-headed arrow reads as one thing
|
||||||
|
# sliding back and forth.
|
||||||
|
var r: float = 10.0 * s
|
||||||
|
var gap: float = 7.0 * s
|
||||||
|
for side in [-1.0, 1.0]:
|
||||||
|
# Left shaft points up (into the fleet), right shaft points down.
|
||||||
|
var direction: float = side
|
||||||
|
var x: float = gap * side
|
||||||
|
var tip: Vector2 = center + Vector2(x, r * direction)
|
||||||
|
var tail: Vector2 = center + Vector2(x, -r * direction)
|
||||||
|
NeonDraw.draw_glowing_line(self, tail, tip, _active_color(), pulse, 0.42 * s, core_color)
|
||||||
|
var head: float = 5.0 * s
|
||||||
|
NeonDraw.draw_glowing_line(self, tip, tip + Vector2(-head, -head * direction),
|
||||||
|
_active_color(), pulse, 0.42 * s, core_color)
|
||||||
|
NeonDraw.draw_glowing_line(self, tip, tip + Vector2(head, -head * direction),
|
||||||
|
_active_color(), pulse, 0.42 * s, core_color)
|
||||||
|
|
||||||
|
|
||||||
func _draw_build(center: Vector2, pulse: float, s: float) -> void:
|
func _draw_build(center: Vector2, pulse: float, s: float) -> void:
|
||||||
# A plus inside a frame - "add a structure".
|
# A plus inside a frame - "add a structure".
|
||||||
var r: float = 11.0 * s
|
var r: float = 11.0 * s
|
||||||
|
|
|
||||||
328
Main.tscn
328
Main.tscn
|
|
@ -1,4 +1,4 @@
|
||||||
[gd_scene load_steps=20 format=3]
|
[gd_scene load_steps=21 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://Starfield.gd" id="1"]
|
[ext_resource type="Script" path="res://Starfield.gd" id="1"]
|
||||||
[ext_resource type="Script" path="res://Fleet.gd" id="2"]
|
[ext_resource type="Script" path="res://Fleet.gd" id="2"]
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
[ext_resource type="Script" path="res://SkillTreeView.gd" id="21"]
|
[ext_resource type="Script" path="res://SkillTreeView.gd" id="21"]
|
||||||
[ext_resource type="Script" path="res://CargoCounter.gd" id="22"]
|
[ext_resource type="Script" path="res://CargoCounter.gd" id="22"]
|
||||||
[ext_resource type="Script" path="res://TransporterField.gd" id="23"]
|
[ext_resource type="Script" path="res://TransporterField.gd" id="23"]
|
||||||
|
[ext_resource type="Script" path="res://ResourceBar.gd" id="24"]
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="1"]
|
[sub_resource type="ShaderMaterial" id="1"]
|
||||||
shader = ExtResource("5")
|
shader = ExtResource("5")
|
||||||
|
|
@ -144,6 +145,18 @@ offset_bottom = -572
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
icon = 2
|
icon = 2
|
||||||
|
|
||||||
|
[node name="TransferButton" type="Control" parent="UI"]
|
||||||
|
visible = false
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 24
|
||||||
|
offset_right = 172
|
||||||
|
offset_top = -600
|
||||||
|
offset_bottom = -452
|
||||||
|
script = ExtResource("10")
|
||||||
|
icon = 14
|
||||||
|
glow_color = Color(1, 0.78, 0.25, 1)
|
||||||
|
|
||||||
[node name="MapButton" type="Control" parent="UI"]
|
[node name="MapButton" type="Control" parent="UI"]
|
||||||
anchor_left = 1.0
|
anchor_left = 1.0
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
|
|
@ -633,19 +646,27 @@ color = Color(0.03, 0.03, 0.08, 0.94)
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
offset_left = 24
|
offset_left = 24
|
||||||
offset_top = 60
|
offset_top = 36
|
||||||
offset_right = -24
|
offset_right = -200
|
||||||
offset_bottom = 170
|
offset_bottom = 166
|
||||||
text = "Ship Lab"
|
text = "Ship Lab"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Bar" type="Control" parent="UI/LabMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
offset_left = 24
|
||||||
|
offset_top = 180
|
||||||
|
offset_right = 520
|
||||||
|
offset_bottom = 260
|
||||||
|
script = ExtResource("24")
|
||||||
|
|
||||||
[node name="VesselA" type="Control" parent="UI/LabMenu"]
|
[node name="VesselA" type="Control" parent="UI/LabMenu"]
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -290
|
offset_left = -290
|
||||||
offset_right = -170
|
offset_right = -170
|
||||||
offset_top = 190
|
offset_top = 274
|
||||||
offset_bottom = 310
|
offset_bottom = 394
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
icon = 2
|
icon = 2
|
||||||
glow_color = Color(0.25, 0.85, 1, 1)
|
glow_color = Color(0.25, 0.85, 1, 1)
|
||||||
|
|
@ -655,8 +676,8 @@ anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -60
|
offset_left = -60
|
||||||
offset_right = 60
|
offset_right = 60
|
||||||
offset_top = 190
|
offset_top = 274
|
||||||
offset_bottom = 310
|
offset_bottom = 394
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
icon = 2
|
icon = 2
|
||||||
glow_color = Color(0.45, 0.8, 1, 1)
|
glow_color = Color(0.45, 0.8, 1, 1)
|
||||||
|
|
@ -666,8 +687,8 @@ anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = 170
|
offset_left = 170
|
||||||
offset_right = 290
|
offset_right = 290
|
||||||
offset_top = 190
|
offset_top = 274
|
||||||
offset_bottom = 310
|
offset_bottom = 394
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
icon = 12
|
icon = 12
|
||||||
glow_color = Color(0.95, 0.55, 0.85, 1)
|
glow_color = Color(0.95, 0.55, 0.85, 1)
|
||||||
|
|
@ -676,9 +697,9 @@ glow_color = Color(0.95, 0.55, 0.85, 1)
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
offset_left = 24
|
offset_left = 24
|
||||||
offset_top = 318
|
offset_top = 402
|
||||||
offset_right = -24
|
offset_right = -24
|
||||||
offset_bottom = 380
|
offset_bottom = 460
|
||||||
theme_override_font_sizes/font_size = 34
|
theme_override_font_sizes/font_size = 34
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
text = ""
|
text = ""
|
||||||
|
|
@ -688,7 +709,7 @@ mouse_filter = 2
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
offset_left = 32
|
offset_left = 32
|
||||||
offset_top = 396
|
offset_top = 470
|
||||||
offset_right = -32
|
offset_right = -32
|
||||||
offset_bottom = -470
|
offset_bottom = -470
|
||||||
theme_override_font_sizes/font_size = 30
|
theme_override_font_sizes/font_size = 30
|
||||||
|
|
@ -788,20 +809,28 @@ color = Color(0.03, 0.03, 0.08, 0.94)
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
offset_left = 24
|
offset_left = 24
|
||||||
offset_top = 70
|
offset_top = 36
|
||||||
offset_right = -24
|
offset_right = -200
|
||||||
offset_bottom = 196
|
offset_bottom = 166
|
||||||
text = "Build a ship"
|
text = "Build a ship"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Bar" type="Control" parent="UI/CraftMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
offset_left = 24
|
||||||
|
offset_top = 180
|
||||||
|
offset_right = 520
|
||||||
|
offset_bottom = 260
|
||||||
|
script = ExtResource("24")
|
||||||
|
|
||||||
[node name="StatusLabel" type="Label" parent="UI/CraftMenu"]
|
[node name="StatusLabel" type="Label" parent="UI/CraftMenu"]
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
offset_left = 24
|
offset_left = 24
|
||||||
offset_top = 206
|
offset_top = 274
|
||||||
offset_right = -24
|
offset_right = -24
|
||||||
offset_bottom = 360
|
offset_bottom = 420
|
||||||
theme_override_font_sizes/font_size = 40
|
theme_override_font_sizes/font_size = 36
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
text = ""
|
text = ""
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
@ -813,8 +842,8 @@ anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -234
|
offset_left = -234
|
||||||
offset_right = -86
|
offset_right = -86
|
||||||
offset_top = -74
|
offset_top = 60
|
||||||
offset_bottom = 74
|
offset_bottom = 208
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
icon = 2
|
icon = 2
|
||||||
|
|
||||||
|
|
@ -826,8 +855,8 @@ anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -300
|
offset_left = -300
|
||||||
offset_right = -20
|
offset_right = -20
|
||||||
offset_top = 100
|
offset_top = 216
|
||||||
offset_bottom = 200
|
offset_bottom = 296
|
||||||
theme_override_font_sizes/font_size = 32
|
theme_override_font_sizes/font_size = 32
|
||||||
text = "Interceptor"
|
text = "Interceptor"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
@ -839,8 +868,8 @@ anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = 86
|
offset_left = 86
|
||||||
offset_right = 234
|
offset_right = 234
|
||||||
offset_top = -74
|
offset_top = 60
|
||||||
offset_bottom = 74
|
offset_bottom = 208
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
icon = 12
|
icon = 12
|
||||||
glow_color = Color(0.95, 0.55, 0.85, 1)
|
glow_color = Color(0.95, 0.55, 0.85, 1)
|
||||||
|
|
@ -853,8 +882,8 @@ anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = 20
|
offset_left = 20
|
||||||
offset_right = 300
|
offset_right = 300
|
||||||
offset_top = 96
|
offset_top = 216
|
||||||
offset_bottom = 176
|
offset_bottom = 296
|
||||||
theme_override_font_sizes/font_size = 32
|
theme_override_font_sizes/font_size = 32
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
text = "Transporter"
|
text = "Transporter"
|
||||||
|
|
@ -867,8 +896,8 @@ anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -74
|
offset_left = -74
|
||||||
offset_right = 74
|
offset_right = 74
|
||||||
offset_top = -260
|
offset_top = -200
|
||||||
offset_bottom = -112
|
offset_bottom = -52
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
icon = 2
|
icon = 2
|
||||||
glow_color = Color(0.45, 0.8, 1, 1)
|
glow_color = Color(0.45, 0.8, 1, 1)
|
||||||
|
|
@ -881,41 +910,13 @@ anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -160
|
offset_left = -160
|
||||||
offset_right = 160
|
offset_right = 160
|
||||||
offset_top = -104
|
offset_top = -44
|
||||||
offset_bottom = -44
|
offset_bottom = 16
|
||||||
theme_override_font_sizes/font_size = 32
|
theme_override_font_sizes/font_size = 32
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
text = "Shield Vessel"
|
text = "Shield Vessel"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="StoreOreButton" type="Control" parent="UI/CraftMenu"]
|
|
||||||
anchor_left = 0.5
|
|
||||||
anchor_top = 1.0
|
|
||||||
anchor_right = 0.5
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_left = -74
|
|
||||||
offset_top = -304
|
|
||||||
offset_right = 74
|
|
||||||
offset_bottom = -156
|
|
||||||
script = ExtResource("10")
|
|
||||||
icon = 3
|
|
||||||
glow_color = Color(1, 0.78, 0.25, 1)
|
|
||||||
|
|
||||||
[node name="StoreOreLabel" type="Label" parent="UI/CraftMenu"]
|
|
||||||
mouse_filter = 2
|
|
||||||
anchor_left = 0.5
|
|
||||||
anchor_top = 1.0
|
|
||||||
anchor_right = 0.5
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_left = -260
|
|
||||||
offset_top = -148
|
|
||||||
offset_right = 260
|
|
||||||
offset_bottom = -24
|
|
||||||
theme_override_font_sizes/font_size = 32
|
|
||||||
autowrap_mode = 2
|
|
||||||
text = "Store ore here"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="CloseButton" type="Control" parent="UI/CraftMenu"]
|
[node name="CloseButton" type="Control" parent="UI/CraftMenu"]
|
||||||
anchor_left = 1.0
|
anchor_left = 1.0
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
|
|
@ -926,6 +927,171 @@ offset_bottom = 172
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
icon = 1
|
icon = 1
|
||||||
|
|
||||||
|
[node name="TransferMenu" type="Control" parent="UI" groups=["modal_menus"]]
|
||||||
|
visible = false
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
|
||||||
|
[node name="Backdrop" type="ColorRect" parent="UI/TransferMenu"]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
color = Color(0.03, 0.03, 0.08, 0.94)
|
||||||
|
|
||||||
|
[node name="TitleLabel" type="Label" parent="UI/TransferMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = 24
|
||||||
|
offset_top = 36
|
||||||
|
offset_right = -200
|
||||||
|
offset_bottom = 166
|
||||||
|
text = "Move ore"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Bar" type="Control" parent="UI/TransferMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
offset_left = 24
|
||||||
|
offset_top = 180
|
||||||
|
offset_right = 520
|
||||||
|
offset_bottom = 260
|
||||||
|
script = ExtResource("24")
|
||||||
|
|
||||||
|
[node name="StatusLabel" type="Label" parent="UI/TransferMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = 24
|
||||||
|
offset_top = 274
|
||||||
|
offset_right = -24
|
||||||
|
offset_bottom = 396
|
||||||
|
theme_override_font_sizes/font_size = 34
|
||||||
|
autowrap_mode = 2
|
||||||
|
text = ""
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="ToBuildingLabel" type="Label" parent="UI/TransferMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = 24
|
||||||
|
offset_top = 400
|
||||||
|
offset_right = -24
|
||||||
|
offset_bottom = 452
|
||||||
|
theme_override_font_sizes/font_size = 34
|
||||||
|
text = ""
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="ToBuildingBatchButton" type="Control" parent="UI/TransferMenu"]
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = -184
|
||||||
|
offset_right = -36
|
||||||
|
offset_top = 462
|
||||||
|
offset_bottom = 610
|
||||||
|
script = ExtResource("10")
|
||||||
|
icon = 3
|
||||||
|
glow_color = Color(0.45, 0.9, 0.55, 1)
|
||||||
|
|
||||||
|
[node name="ToBuildingBatchLabel" type="Label" parent="UI/TransferMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = -230
|
||||||
|
offset_right = 10
|
||||||
|
offset_top = 616
|
||||||
|
offset_bottom = 668
|
||||||
|
theme_override_font_sizes/font_size = 30
|
||||||
|
text = ""
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="ToBuildingAllButton" type="Control" parent="UI/TransferMenu"]
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = 36
|
||||||
|
offset_right = 184
|
||||||
|
offset_top = 462
|
||||||
|
offset_bottom = 610
|
||||||
|
script = ExtResource("10")
|
||||||
|
icon = 3
|
||||||
|
glow_color = Color(0.45, 0.9, 0.55, 1)
|
||||||
|
|
||||||
|
[node name="ToBuildingAllLabel" type="Label" parent="UI/TransferMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = -10
|
||||||
|
offset_right = 230
|
||||||
|
offset_top = 616
|
||||||
|
offset_bottom = 668
|
||||||
|
theme_override_font_sizes/font_size = 30
|
||||||
|
text = "All"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="ToFleetLabel" type="Label" parent="UI/TransferMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = 24
|
||||||
|
offset_top = 694
|
||||||
|
offset_right = -24
|
||||||
|
offset_bottom = 746
|
||||||
|
theme_override_font_sizes/font_size = 34
|
||||||
|
text = ""
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="ToFleetBatchButton" type="Control" parent="UI/TransferMenu"]
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = -184
|
||||||
|
offset_right = -36
|
||||||
|
offset_top = 756
|
||||||
|
offset_bottom = 904
|
||||||
|
script = ExtResource("10")
|
||||||
|
icon = 4
|
||||||
|
glow_color = Color(1, 0.78, 0.25, 1)
|
||||||
|
|
||||||
|
[node name="ToFleetBatchLabel" type="Label" parent="UI/TransferMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = -230
|
||||||
|
offset_right = 10
|
||||||
|
offset_top = 910
|
||||||
|
offset_bottom = 962
|
||||||
|
theme_override_font_sizes/font_size = 30
|
||||||
|
text = ""
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="ToFleetAllButton" type="Control" parent="UI/TransferMenu"]
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = 36
|
||||||
|
offset_right = 184
|
||||||
|
offset_top = 756
|
||||||
|
offset_bottom = 904
|
||||||
|
script = ExtResource("10")
|
||||||
|
icon = 4
|
||||||
|
glow_color = Color(1, 0.78, 0.25, 1)
|
||||||
|
|
||||||
|
[node name="ToFleetAllLabel" type="Label" parent="UI/TransferMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = -10
|
||||||
|
offset_right = 230
|
||||||
|
offset_top = 910
|
||||||
|
offset_bottom = 962
|
||||||
|
theme_override_font_sizes/font_size = 30
|
||||||
|
text = "All"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="CloseButton" type="Control" parent="UI/TransferMenu"]
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = -172
|
||||||
|
offset_right = -24
|
||||||
|
offset_top = 24
|
||||||
|
offset_bottom = 172
|
||||||
|
script = ExtResource("10")
|
||||||
|
icon = 1
|
||||||
|
glow_color = Color(1, 0.78, 0.25, 1)
|
||||||
|
|
||||||
[node name="FormationMenu" type="Control" parent="UI" groups=["modal_menus"]]
|
[node name="FormationMenu" type="Control" parent="UI" groups=["modal_menus"]]
|
||||||
visible = false
|
visible = false
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
|
|
@ -1026,18 +1192,26 @@ color = Color(0.03, 0.03, 0.08, 0.94)
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
offset_left = 24.0
|
offset_left = 24.0
|
||||||
offset_top = 70.0
|
offset_top = 36.0
|
||||||
offset_right = -24.0
|
offset_right = -200.0
|
||||||
offset_bottom = 196.0
|
offset_bottom = 166.0
|
||||||
text = "Research"
|
text = "Research"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Bar" type="Control" parent="UI/SkillMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
offset_left = 24.0
|
||||||
|
offset_top = 180.0
|
||||||
|
offset_right = 520.0
|
||||||
|
offset_bottom = 260.0
|
||||||
|
script = ExtResource("24")
|
||||||
|
|
||||||
[node name="Tree" type="Control" parent="UI/SkillMenu"]
|
[node name="Tree" type="Control" parent="UI/SkillMenu"]
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
offset_left = 24.0
|
offset_left = 24.0
|
||||||
offset_top = 210.0
|
offset_top = 274.0
|
||||||
offset_right = -24.0
|
offset_right = -24.0
|
||||||
offset_bottom = -620.0
|
offset_bottom = -620.0
|
||||||
script = ExtResource("21")
|
script = ExtResource("21")
|
||||||
|
|
@ -1096,34 +1270,6 @@ script = ExtResource("10")
|
||||||
icon = 5
|
icon = 5
|
||||||
glow_color = Color(0.45, 0.9, 0.55, 1)
|
glow_color = Color(0.45, 0.9, 0.55, 1)
|
||||||
|
|
||||||
[node name="LoadOreButton" type="Control" parent="UI/SkillMenu"]
|
|
||||||
anchor_left = 0.5
|
|
||||||
anchor_top = 1.0
|
|
||||||
anchor_right = 0.5
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_left = -256.0
|
|
||||||
offset_top = -304.0
|
|
||||||
offset_right = -108.0
|
|
||||||
offset_bottom = -156.0
|
|
||||||
script = ExtResource("10")
|
|
||||||
icon = 4
|
|
||||||
glow_color = Color(1, 0.78, 0.25, 1)
|
|
||||||
|
|
||||||
[node name="LoadOreLabel" type="Label" parent="UI/SkillMenu"]
|
|
||||||
mouse_filter = 2
|
|
||||||
anchor_left = 0.5
|
|
||||||
anchor_top = 1.0
|
|
||||||
anchor_right = 0.5
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_left = -330.0
|
|
||||||
offset_top = -150.0
|
|
||||||
offset_right = -30.0
|
|
||||||
offset_bottom = -14.0
|
|
||||||
theme_override_font_sizes/font_size = 28
|
|
||||||
horizontal_alignment = 1
|
|
||||||
autowrap_mode = 2
|
|
||||||
text = "Load ore"
|
|
||||||
|
|
||||||
[node name="CloseButton" type="Control" parent="UI/SkillMenu"]
|
[node name="CloseButton" type="Control" parent="UI/SkillMenu"]
|
||||||
anchor_left = 1.0
|
anchor_left = 1.0
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
|
|
|
||||||
111
ResourceBar.gd
Normal file
111
ResourceBar.gd
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
class_name ResourceBar
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
# The header every building menu now carries (#24): banked ore, what the
|
||||||
|
# fleet is holding, and what THIS building has. Before this, each panel
|
||||||
|
# improvised its own - the crafter spelled the same three figures into a
|
||||||
|
# run-on StatusLabel string, the research screen printed a bank total inside
|
||||||
|
# a button caption - so the same numbers looked different in every room.
|
||||||
|
#
|
||||||
|
# Three columns rather than a sentence, because the point of the bar is that
|
||||||
|
# a figure stays in the same place as the player moves between menus.
|
||||||
|
#
|
||||||
|
# Reads GameState directly every frame, like ResourceCounter and CargoCounter
|
||||||
|
# do, so it stays correct across the space <-> planet-surface scene swap with
|
||||||
|
# nobody having to push updates into it. Only `building_id` is pushed in,
|
||||||
|
# because "here" is the one thing the bar cannot work out for itself.
|
||||||
|
|
||||||
|
# Which building the HERE column is reporting. 0 means none, which greys the
|
||||||
|
# column rather than showing a confident zero.
|
||||||
|
var building_id: int = 0
|
||||||
|
|
||||||
|
# The caption box has to be a full line tall, not just the font size: a Label
|
||||||
|
# renders its line height, not its point size, and the first version sat the
|
||||||
|
# digits 26px down from the top straight through the middle of the caption.
|
||||||
|
const CAPTION_HEIGHT: float = 34.0
|
||||||
|
const CAPTION_GAP: float = 4.0
|
||||||
|
const CAPTION_FONT_SIZE: int = 24
|
||||||
|
const DIGIT_HEIGHT: float = 34.0
|
||||||
|
|
||||||
|
# Bank keeps NeonNumber's own ore-teal, and the hold keeps the amber
|
||||||
|
# CargoCounter established for ore in transit. Only HERE needed a new colour;
|
||||||
|
# it borrows the mothership's green, which already reads as "landed
|
||||||
|
# somewhere and staying there" everywhere else in the game.
|
||||||
|
const BANK_GLOW: Color = Color(0.0, 0.62, 0.7)
|
||||||
|
const BANK_CORE: Color = Color(0.0, 0.12, 0.15)
|
||||||
|
const HOLD_GLOW: Color = Color(1.0, 0.78, 0.25)
|
||||||
|
const HOLD_CORE: Color = Color(0.22, 0.12, 0.0)
|
||||||
|
const HERE_GLOW: Color = Color(0.45, 0.9, 0.55)
|
||||||
|
const HERE_CORE: Color = Color(0.05, 0.16, 0.08)
|
||||||
|
const RULE_COLOR: Color = Color(0.25, 0.55, 0.7)
|
||||||
|
|
||||||
|
var _numbers: Array[NeonNumber] = []
|
||||||
|
var _captions: Array[Label] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
var columns: Array = [
|
||||||
|
["BANK", BANK_GLOW, BANK_CORE],
|
||||||
|
["HOLD", HOLD_GLOW, HOLD_CORE],
|
||||||
|
["HERE", HERE_GLOW, HERE_CORE],
|
||||||
|
]
|
||||||
|
for column in columns:
|
||||||
|
var caption: Label = Label.new()
|
||||||
|
caption.text = column[0]
|
||||||
|
caption.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
caption.add_theme_font_size_override("font_size", CAPTION_FONT_SIZE)
|
||||||
|
caption.add_theme_color_override("font_color", column[1])
|
||||||
|
add_child(caption)
|
||||||
|
_captions.append(caption)
|
||||||
|
|
||||||
|
var number: NeonNumber = NeonNumber.new()
|
||||||
|
number.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
number.glow_color = column[1]
|
||||||
|
number.core_color = column[2]
|
||||||
|
number.digit_height = DIGIT_HEIGHT
|
||||||
|
add_child(number)
|
||||||
|
_numbers.append(number)
|
||||||
|
|
||||||
|
_layout()
|
||||||
|
resized.connect(_layout)
|
||||||
|
|
||||||
|
|
||||||
|
func _layout() -> void:
|
||||||
|
var column_width: float = size.x / float(_numbers.size())
|
||||||
|
for i in _numbers.size():
|
||||||
|
var x: float = column_width * float(i)
|
||||||
|
_captions[i].position = Vector2(x, 0.0)
|
||||||
|
_captions[i].size = Vector2(column_width, CAPTION_HEIGHT)
|
||||||
|
# The hexagon and digits are drawn from the node's own origin, so the
|
||||||
|
# number is positioned rather than sized.
|
||||||
|
_numbers[i].position = Vector2(x, CAPTION_HEIGHT + CAPTION_GAP)
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
_numbers[0].set_amount(GameState.rocks)
|
||||||
|
_numbers[1].set_amount(GameState.fleet_cargo)
|
||||||
|
|
||||||
|
# A building with no id is not a building: showing "0" there would claim
|
||||||
|
# the store is empty rather than that there is no store to look at.
|
||||||
|
var known: bool = building_id != 0
|
||||||
|
_numbers[2].visible = known
|
||||||
|
_captions[2].visible = known
|
||||||
|
if known:
|
||||||
|
_numbers[2].set_amount(GameState.building_holdings(building_id))
|
||||||
|
|
||||||
|
|
||||||
|
# The height the bar needs for a caption line and a row of digits. The scene
|
||||||
|
# sizes the node, so this is what those offsets have to add up to; anything
|
||||||
|
# less and the rule is drawn back through the digits.
|
||||||
|
static func natural_height() -> float:
|
||||||
|
return CAPTION_HEIGHT + CAPTION_GAP + DIGIT_HEIGHT + 8.0
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
# One rule under the whole bar, so the header reads as a band separating
|
||||||
|
# the standing figures from whatever the panel below them does.
|
||||||
|
var rule_y: float = maxf(size.y, natural_height())
|
||||||
|
NeonDraw.draw_glowing_line(
|
||||||
|
self, Vector2(0.0, rule_y), Vector2(size.x, rule_y), RULE_COLOR, 0.55, 1.0
|
||||||
|
)
|
||||||
1
ResourceBar.gd.uid
Normal file
1
ResourceBar.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c3x1o7bh4tl5
|
||||||
BIN
shots/craft.png
Normal file
BIN
shots/craft.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
40
shots/craft.png.import
Normal file
40
shots/craft.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dgs8khl5js3ip"
|
||||||
|
path="res://.godot/imported/craft.png-a8697b6db303e087da4d7b82087549c2.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://shots/craft.png"
|
||||||
|
dest_files=["res://.godot/imported/craft.png-a8697b6db303e087da4d7b82087549c2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
shots/lab.png
Normal file
BIN
shots/lab.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
40
shots/lab.png.import
Normal file
40
shots/lab.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://m1obuogb3oca"
|
||||||
|
path="res://.godot/imported/lab.png-3565548fdb7eb90f645dd6a0f6721400.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://shots/lab.png"
|
||||||
|
dest_files=["res://.godot/imported/lab.png-3565548fdb7eb90f645dd6a0f6721400.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
shots/skill.png
Normal file
BIN
shots/skill.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
40
shots/skill.png.import
Normal file
40
shots/skill.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://btv6h40i8dvcw"
|
||||||
|
path="res://.godot/imported/skill.png-7e1397f0de9b8dc691fffdc4231115e7.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://shots/skill.png"
|
||||||
|
dest_files=["res://.godot/imported/skill.png-7e1397f0de9b8dc691fffdc4231115e7.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
shots/transfer.png
Normal file
BIN
shots/transfer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
40
shots/transfer.png.import
Normal file
40
shots/transfer.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b32illfmrcwlt"
|
||||||
|
path="res://.godot/imported/transfer.png-6949bd9dcafcd9892b4949e2a2071ddc.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://shots/transfer.png"
|
||||||
|
dest_files=["res://.godot/imported/transfer.png-6949bd9dcafcd9892b4949e2a2071ddc.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
# Preview, not a test. Opens the CraftMenu so its layout can be checked on a
|
# Preview, not a test. Opens the CraftMenu so its layout can be checked on a
|
||||||
# real viewport - the mothership's "Load ore" label was falling off the bottom
|
# real viewport - label collisions and things falling off the bottom of
|
||||||
# of shorter screens, which no headless test can see.
|
# shorter screens are what no headless test can see.
|
||||||
#
|
#
|
||||||
# bash tests/screenshot.sh res://tests/_craft_menu_preview.tscn /tmp/m.png 20 720x1280
|
# bash tests/screenshot.sh res://tests/_craft_menu_preview.tscn /tmp/m.png 20 720x1280
|
||||||
# ... and again at 720x960 to check a shorter screen format.
|
# ... and again at 720x960 to check a shorter screen format.
|
||||||
|
|
@ -17,11 +17,14 @@ func _ready() -> void:
|
||||||
GameState.unlocked = ["shipyards", "logistics", "freight"]
|
GameState.unlocked = ["shipyards", "logistics", "freight"]
|
||||||
var main: Node = load("res://Main.tscn").instantiate()
|
var main: Node = load("res://Main.tscn").instantiate()
|
||||||
add_child(main)
|
add_child(main)
|
||||||
main.get_node("BuildingField").place(GameState.BUILDING_TRANSPORT_HUB, Vector2(400.0, 0.0))
|
main.get_node("BuildingField").place(GameState.BUILDING_SHIP_CRAFTER, Vector2(400.0, 0.0))
|
||||||
|
var crafter_id: int = int(GameState.buildings[-1].id)
|
||||||
|
GameState.deposit_to_building(crafter_id, 42)
|
||||||
var bc: Node = main.get_node("BuildController")
|
var bc: Node = main.get_node("BuildController")
|
||||||
bc.craft_building_id = 1
|
bc.craft_building_id = crafter_id
|
||||||
|
# The shared header (#24) is the thing worth eyeballing here, so give it
|
||||||
|
# a building to report on - otherwise its HERE column hides itself.
|
||||||
|
for bar in bc.resource_bars:
|
||||||
|
bar.building_id = crafter_id
|
||||||
main.get_node("UI/CraftMenu").visible = true
|
main.get_node("UI/CraftMenu").visible = true
|
||||||
if "Craft" == "Craft":
|
|
||||||
bc._refresh_craft_status()
|
bc._refresh_craft_status()
|
||||||
else:
|
|
||||||
bc._refresh_load_ore()
|
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,17 @@ func _ready() -> void:
|
||||||
var main: Node = load("res://Main.tscn").instantiate()
|
var main: Node = load("res://Main.tscn").instantiate()
|
||||||
add_child(main)
|
add_child(main)
|
||||||
main.get_node("BuildingField").place(GameState.BUILDING_SHIP_LAB, Vector2(400.0, 0.0))
|
main.get_node("BuildingField").place(GameState.BUILDING_SHIP_LAB, Vector2(400.0, 0.0))
|
||||||
|
var lab_id: int = int(GameState.buildings[-1].id)
|
||||||
|
GameState.deposit_to_building(lab_id, 18)
|
||||||
|
GameState.fleet_cargo = 12
|
||||||
# A level already bought, so the list shows both a researched and an
|
# A level already bought, so the list shows both a researched and an
|
||||||
# unresearched line rather than a uniform page of zeroes.
|
# unresearched line rather than a uniform page of zeroes.
|
||||||
GameState.vessel_levels["int_damage"] = 1
|
GameState.vessel_levels["int_damage"] = 1
|
||||||
var bc: Node = main.get_node("BuildController")
|
var bc: Node = main.get_node("BuildController")
|
||||||
|
# The shared header (#24) hides its HERE column when it has no building,
|
||||||
|
# so bind it the way opening the panel from the building does.
|
||||||
|
for bar in bc.resource_bars:
|
||||||
|
bar.building_id = lab_id
|
||||||
main.get_node("UI/LabMenu").visible = true
|
main.get_node("UI/LabMenu").visible = true
|
||||||
bc.lab_vessel = GameState.VESSEL_INTERCEPTOR
|
bc.lab_vessel = GameState.VESSEL_INTERCEPTOR
|
||||||
bc._refresh_lab()
|
bc._refresh_lab()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
# Preview, not a test. Opens the SkillMenu so its layout can be checked on a
|
# Preview, not a test. Opens the SkillMenu so its layout can be checked on a
|
||||||
# real viewport - the mothership's "Load ore" label was falling off the bottom
|
# real viewport - the tree lost vertical room to the shared resource header
|
||||||
# of shorter screens, which no headless test can see.
|
# (#24), and whether it still fits is a thing only pixels can answer.
|
||||||
#
|
#
|
||||||
# bash tests/screenshot.sh res://tests/_skill_menu_preview.tscn /tmp/m.png 20 720x1280
|
# bash tests/screenshot.sh res://tests/_skill_menu_preview.tscn /tmp/m.png 20 720x1280
|
||||||
# ... and again at 720x960 to check a shorter screen format.
|
# ... and again at 720x960 to check a shorter screen format.
|
||||||
|
|
@ -17,15 +17,13 @@ func _ready() -> void:
|
||||||
GameState.unlocked = ["shipyards", "logistics", "freight"]
|
GameState.unlocked = ["shipyards", "logistics", "freight"]
|
||||||
var main: Node = load("res://Main.tscn").instantiate()
|
var main: Node = load("res://Main.tscn").instantiate()
|
||||||
add_child(main)
|
add_child(main)
|
||||||
main.get_node("BuildingField").place(GameState.BUILDING_TRANSPORT_HUB, Vector2(400.0, 0.0))
|
main.get_node("BuildingField").place(GameState.BUILDING_MOTHERSHIP, Vector2(400.0, 0.0))
|
||||||
|
var mothership_id: int = int(GameState.buildings[-1].id)
|
||||||
var bc: Node = main.get_node("BuildController")
|
var bc: Node = main.get_node("BuildController")
|
||||||
bc.craft_building_id = 1
|
for bar in bc.resource_bars:
|
||||||
|
bar.building_id = mothership_id
|
||||||
main.get_node("UI/SkillMenu").visible = true
|
main.get_node("UI/SkillMenu").visible = true
|
||||||
if "Skill" == "Craft":
|
# Select a node so the description, price and status all have text - an
|
||||||
bc._refresh_craft_status()
|
# empty description cannot collide with anything.
|
||||||
else:
|
|
||||||
bc._refresh_load_ore()
|
|
||||||
# Select a node so the description, price and status all have text -
|
|
||||||
# an empty description cannot collide with anything.
|
|
||||||
bc.skill_tree.selected_id = "freight"
|
bc.skill_tree.selected_id = "freight"
|
||||||
bc._refresh_skill_menu("freight")
|
bc._refresh_skill_menu("freight")
|
||||||
|
|
|
||||||
27
tests/_transfer_menu_preview.gd
Normal file
27
tests/_transfer_menu_preview.gd
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
extends Node2D
|
||||||
|
|
||||||
|
# Preview, not a test. Opens the TransferMenu (#24) so its layout can be
|
||||||
|
# checked on a real viewport - four buttons, two captions and the shared
|
||||||
|
# header have to coexist without collisions, which no headless test can see.
|
||||||
|
#
|
||||||
|
# bash tests/screenshot.sh res://tests/_transfer_menu_preview.tscn /tmp/m.png 20 720x1280
|
||||||
|
# ... and again at 720x960 to check a shorter screen format.
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.buildings.clear()
|
||||||
|
GameState.extractors.clear()
|
||||||
|
GameState.transporters.clear()
|
||||||
|
GameState.rocks = 180
|
||||||
|
GameState.fleet_cargo = 24
|
||||||
|
GameState.unlocked = ["shipyards", "logistics", "freight"]
|
||||||
|
var main: Node = load("res://Main.tscn").instantiate()
|
||||||
|
add_child(main)
|
||||||
|
main.get_node("BuildingField").place(GameState.BUILDING_SHIP_CRAFTER, Vector2(400.0, 0.0))
|
||||||
|
var crafter_id: int = int(GameState.buildings[-1].id)
|
||||||
|
GameState.deposit_to_building(crafter_id, 42)
|
||||||
|
var bc: Node = main.get_node("BuildController")
|
||||||
|
bc.transfer_building_id = crafter_id
|
||||||
|
main.get_node("UI/TransferMenu").visible = true
|
||||||
|
bc._refresh_transfer()
|
||||||
|
bc.transfer_status.text = "Moved 25 ore into this building"
|
||||||
1
tests/_transfer_menu_preview.gd.uid
Normal file
1
tests/_transfer_menu_preview.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://b8omh1o4ysy6p
|
||||||
6
tests/_transfer_menu_preview.tscn
Normal file
6
tests/_transfer_menu_preview.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://tests/_transfer_menu_preview.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="TransferMenuPreview" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
@ -27,6 +27,7 @@ var scene: Node
|
||||||
var banked: Control
|
var banked: Control
|
||||||
var cargo: Control
|
var cargo: Control
|
||||||
var stage: int = 0
|
var stage: int = 0
|
||||||
|
var mothership_id: int = 0
|
||||||
var elapsed: float = 0.0
|
var elapsed: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,6 +37,11 @@ func _initialize() -> void:
|
||||||
game_state.extractors.clear()
|
game_state.extractors.clear()
|
||||||
game_state.rocks = 42
|
game_state.rocks = 42
|
||||||
game_state.fleet_cargo = 0
|
game_state.fleet_cargo = 0
|
||||||
|
# Banking is a transfer TO the mothership now (#24), and a transfer needs
|
||||||
|
# a building to aim at. Only the record is required - the transfer
|
||||||
|
# primitives work off GameState, not off the spawned node - so this stays
|
||||||
|
# a display test rather than becoming one about the building system.
|
||||||
|
mothership_id = game_state.add_building(GameState.BUILDING_MOTHERSHIP, Vector2.ZERO)
|
||||||
scene = load("res://Main.tscn").instantiate()
|
scene = load("res://Main.tscn").instantiate()
|
||||||
root.add_child(scene)
|
root.add_child(scene)
|
||||||
banked = scene.get_node("UI/ResourceCounter")
|
banked = scene.get_node("UI/ResourceCounter")
|
||||||
|
|
@ -95,7 +101,7 @@ func _process(delta: float) -> bool:
|
||||||
])
|
])
|
||||||
|
|
||||||
# --- 4. delivery moves it across ----------------------------
|
# --- 4. delivery moves it across ----------------------------
|
||||||
game_state.deliver_cargo()
|
game_state.transfer_to_building(mothership_id, game_state.fleet_cargo)
|
||||||
elapsed = 0.0
|
elapsed = 0.0
|
||||||
stage = 2
|
stage = 2
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -5,29 +5,28 @@ extends SceneTree
|
||||||
# Every press in the ship crafter must say what happened. Reported twice from
|
# Every press in the ship crafter must say what happened. Reported twice from
|
||||||
# play-testing as "the buttons do nothing".
|
# play-testing as "the buttons do nothing".
|
||||||
#
|
#
|
||||||
# There is no silent-failure path left to press: storing with an empty hold,
|
# The queue is invisible by nature - an order that cannot be afforded is
|
||||||
# ordering something the crafter cannot pay for yet, and storing successfully
|
# accepted and waits (#16), which looks exactly like a dead button unless it
|
||||||
# all report. The queue in particular is invisible by nature - an order that
|
# says so.
|
||||||
# cannot be afforded is accepted and waits (#16), which looks exactly like a
|
|
||||||
# dead button unless it says so.
|
|
||||||
#
|
#
|
||||||
# Verifies:
|
# Verifies:
|
||||||
# 1. storing with an empty hold explains where ore comes from
|
# 1. ordering against an empty stock says it is queued AND how much more
|
||||||
# 2. ordering against an empty stock says it is queued AND how much more
|
|
||||||
# ore the building needs
|
# ore the building needs
|
||||||
# 3. a successful store reports the amount
|
# 2. the message survives the per-frame status refresh - it used to be
|
||||||
# 4. the message survives the per-frame status refresh - it used to be
|
|
||||||
# overwritten on the very next frame, which is what made a working
|
# overwritten on the very next frame, which is what made a working
|
||||||
# button look dead
|
# button look dead
|
||||||
# 5. the message is the FIRST line, not buried under the standing state
|
# 3. the message is the FIRST line, not buried under the standing state
|
||||||
# 6. the panel is opened the way the GAME opens it - park the fleet on the
|
# 4. the panel is opened the way the GAME opens it - park the fleet on the
|
||||||
# crafter and press the action button - and every button then works
|
# crafter and press the action button - and its buttons then work
|
||||||
#
|
#
|
||||||
# Point 6 exists because this test used to set craft_building_id by hand.
|
# Point 4 exists because this test used to set craft_building_id by hand.
|
||||||
# That skipped the one step that was actually broken: opening the panel
|
# That skipped the one step that was actually broken: opening the panel
|
||||||
# stopped recording WHICH crafter, so every handler bailed out at its `== 0`
|
# stopped recording WHICH crafter, so every handler bailed out at its `== 0`
|
||||||
# guard while the panel still opened. Reported four times as "the buttons do
|
# guard while the panel still opened. Reported four times as "the buttons do
|
||||||
# nothing", and invisible to every test that set the id itself.
|
# nothing", and invisible to every test that set the id itself.
|
||||||
|
#
|
||||||
|
# Storing ore used to be tested here too. It moved out of this panel entirely
|
||||||
|
# in #24 - see test_resource_transfer.gd.
|
||||||
|
|
||||||
var game_state: Node
|
var game_state: Node
|
||||||
var scene: Node
|
var scene: Node
|
||||||
|
|
@ -44,7 +43,11 @@ func _initialize() -> void:
|
||||||
game_state.extractors.clear()
|
game_state.extractors.clear()
|
||||||
game_state.transporters.clear()
|
game_state.transporters.clear()
|
||||||
game_state.reset_progression()
|
game_state.reset_progression()
|
||||||
game_state.unlocked = ["shipyards", "coordination", "wing"]
|
# Every capacity node, so the interceptor button stays available for the
|
||||||
|
# real-tap checks below. A full fleet correctly DISABLES it, and a
|
||||||
|
# disabled button not reacting to a tap is not the bug those checks are
|
||||||
|
# looking for.
|
||||||
|
game_state.unlocked = ["shipyards", "coordination", "wing", "squadron", "armada"]
|
||||||
game_state.rocks = 100
|
game_state.rocks = 100
|
||||||
game_state.fleet_cargo = 0
|
game_state.fleet_cargo = 0
|
||||||
game_state.vessels_owed.clear()
|
game_state.vessels_owed.clear()
|
||||||
|
|
@ -104,17 +107,7 @@ func _process(delta: float) -> bool:
|
||||||
return true
|
return true
|
||||||
print("panel opened from the building, bound to crafter %d" % controller.craft_building_id)
|
print("panel opened from the building, bound to crafter %d" % controller.craft_building_id)
|
||||||
|
|
||||||
# --- 1. store with an empty hold ----------------------------
|
# --- 1. order against an empty stock ------------------------
|
||||||
controller._on_store_ore()
|
|
||||||
var empty_msg: String = _first_line()
|
|
||||||
if "carrying nothing" not in empty_msg:
|
|
||||||
print("got: %s" % empty_msg)
|
|
||||||
print("TEST_CRAFTER_FEEDBACK: FAIL (storing with an empty hold said nothing useful)")
|
|
||||||
quit(1)
|
|
||||||
return true
|
|
||||||
print("empty hold -> %s" % empty_msg)
|
|
||||||
|
|
||||||
# --- 2. order against an empty stock ------------------------
|
|
||||||
controller._on_craft_interceptor()
|
controller._on_craft_interceptor()
|
||||||
var queued_msg: String = _first_line()
|
var queued_msg: String = _first_line()
|
||||||
if "queued" not in queued_msg.to_lower() or "more ore" not in queued_msg:
|
if "queued" not in queued_msg.to_lower() or "more ore" not in queued_msg:
|
||||||
|
|
@ -127,71 +120,47 @@ func _process(delta: float) -> bool:
|
||||||
quit(1)
|
quit(1)
|
||||||
return true
|
return true
|
||||||
print("unaffordable order -> %s" % queued_msg)
|
print("unaffordable order -> %s" % queued_msg)
|
||||||
|
|
||||||
# --- 3. a successful store ----------------------------------
|
|
||||||
game_state.withdraw_to_fleet(30)
|
|
||||||
controller._on_store_ore()
|
|
||||||
var stored_msg: String = _first_line()
|
|
||||||
if "Stored" not in stored_msg:
|
|
||||||
print("got: %s" % stored_msg)
|
|
||||||
print("TEST_CRAFTER_FEEDBACK: FAIL (a successful store did not report)")
|
|
||||||
quit(1)
|
|
||||||
return true
|
|
||||||
print("successful store -> %s" % stored_msg)
|
|
||||||
elapsed = 0.0
|
elapsed = 0.0
|
||||||
stage = 2
|
stage = 2
|
||||||
return false
|
return false
|
||||||
2:
|
2:
|
||||||
# --- 4 + 5. it survives the refresh, and stays first --------
|
# --- 2 + 3. it survives the refresh, and stays first --------
|
||||||
if elapsed < 0.5:
|
if elapsed < 0.5:
|
||||||
return false
|
return false
|
||||||
controller._refresh_craft_status()
|
controller._refresh_craft_status()
|
||||||
var still: String = _first_line()
|
var still: String = _first_line()
|
||||||
if "Stored" not in still:
|
if "queued" not in still.to_lower():
|
||||||
print("first line half a second later: %s" % still)
|
print("first line half a second later: %s" % still)
|
||||||
print("TEST_CRAFTER_FEEDBACK: FAIL (the message was overwritten by the per-frame refresh)")
|
print("TEST_CRAFTER_FEEDBACK: FAIL (the message was overwritten by the per-frame refresh)")
|
||||||
quit(1)
|
quit(1)
|
||||||
return true
|
return true
|
||||||
print("half a second later, still first: %s" % still)
|
print("half a second later, still first: %s" % still)
|
||||||
|
|
||||||
# --- 6. a REAL tap, through the input path ------------------
|
# --- 4. a REAL tap, through the input path ------------------
|
||||||
# Everything above calls the handlers directly, which is how a
|
# Everything above calls the handlers directly, which is how a
|
||||||
# whole class of bug kept getting missed: the crafter logic was
|
# whole class of bug kept getting missed: the crafter logic was
|
||||||
# fine and the BUTTON was inert, blocked by a modal guard. This
|
# fine and the BUTTON was inert, blocked by a modal guard. This
|
||||||
# taps the control the way a touch does.
|
# taps the control the way a touch does.
|
||||||
game_state.withdraw_to_fleet(20)
|
var queued_before: int = game_state.pending_orders(crafter_id)
|
||||||
var stock_before: int = game_state.building_stock(crafter_id)
|
_tap(scene.get_node("UI/CraftMenu/InterceptorButton"))
|
||||||
var store_button: Control = scene.get_node("UI/CraftMenu/StoreOreButton")
|
if game_state.pending_orders(crafter_id) <= queued_before:
|
||||||
for pressed in [true, false]:
|
print("orders %d -> %d after tapping Interceptor" % [
|
||||||
var ev: InputEventScreenTouch = InputEventScreenTouch.new()
|
queued_before, game_state.pending_orders(crafter_id)
|
||||||
ev.pressed = pressed
|
|
||||||
ev.position = store_button.size / 2.0
|
|
||||||
store_button._gui_input(ev)
|
|
||||||
if game_state.building_stock(crafter_id) <= stock_before:
|
|
||||||
print("stock %d -> %d after tapping Store ore" % [
|
|
||||||
stock_before, game_state.building_stock(crafter_id)
|
|
||||||
])
|
])
|
||||||
print("TEST_CRAFTER_FEEDBACK: FAIL (tapping the store button did nothing)")
|
print("TEST_CRAFTER_FEEDBACK: FAIL (tapping the interceptor button did nothing)")
|
||||||
quit(1)
|
quit(1)
|
||||||
return true
|
return true
|
||||||
print("real tap on Store ore moved %d ore into the crafter" % [
|
print("real tap on Interceptor queued another order")
|
||||||
game_state.building_stock(crafter_id) - stock_before
|
|
||||||
])
|
|
||||||
|
|
||||||
# And the same tap must still work after the build menu has been
|
# And the same tap must still work after the build menu has been
|
||||||
# opened and closed, which used to block every button forever.
|
# opened and closed, which used to block every button forever.
|
||||||
controller._on_build_pressed()
|
controller._on_build_pressed()
|
||||||
controller._show_building_info(GameState.BUILDING_SHIP_CRAFTER)
|
controller._show_building_info(GameState.BUILDING_SHIP_CRAFTER)
|
||||||
scene.get_node("UI/BuildMenu").visible = false
|
scene.get_node("UI/BuildMenu").visible = false
|
||||||
game_state.withdraw_to_fleet(10)
|
var queued_mid: int = game_state.pending_orders(crafter_id)
|
||||||
var stock_mid: int = game_state.building_stock(crafter_id)
|
_tap(scene.get_node("UI/CraftMenu/InterceptorButton"))
|
||||||
for pressed2 in [true, false]:
|
if game_state.pending_orders(crafter_id) <= queued_mid:
|
||||||
var ev2: InputEventScreenTouch = InputEventScreenTouch.new()
|
print("TEST_CRAFTER_FEEDBACK: FAIL (craft button dead after the build menu was opened and closed)")
|
||||||
ev2.pressed = pressed2
|
|
||||||
ev2.position = store_button.size / 2.0
|
|
||||||
store_button._gui_input(ev2)
|
|
||||||
if game_state.building_stock(crafter_id) <= stock_mid:
|
|
||||||
print("TEST_CRAFTER_FEEDBACK: FAIL (store button dead after the build menu was opened and closed)")
|
|
||||||
quit(1)
|
quit(1)
|
||||||
return true
|
return true
|
||||||
print("still works after opening and closing the build menu")
|
print("still works after opening and closing the build menu")
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ extends SceneTree
|
||||||
#
|
#
|
||||||
# Verifies:
|
# Verifies:
|
||||||
# 1. mining fills the hold and leaves the bank alone
|
# 1. mining fills the hold and leaves the bank alone
|
||||||
# 2. reaching the mothership banks the lot, and empties the hold
|
# 2. unloading at the mothership banks the lot and empties the hold - but
|
||||||
|
# only when the player asks. Arriving used to do it by itself; #24
|
||||||
|
# removed that, so this now checks BOTH halves: parking changes
|
||||||
|
# nothing, and the explicit transfer moves everything
|
||||||
# 3. with no mothership there is nowhere to deliver, so cargo just
|
# 3. with no mothership there is nowhere to deliver, so cargo just
|
||||||
# accumulates - it is never quietly banked anyway
|
# accumulates - it is never quietly banked anyway
|
||||||
# 4. sitting on a DIFFERENT building is not a delivery
|
# 4. sitting on a DIFFERENT building is not a delivery
|
||||||
|
|
@ -27,6 +30,7 @@ var field: Node
|
||||||
var stage: int = 0
|
var stage: int = 0
|
||||||
var elapsed: float = 0.0
|
var elapsed: float = 0.0
|
||||||
var banked_before: int = 0
|
var banked_before: int = 0
|
||||||
|
var mothership_id: int = 0
|
||||||
|
|
||||||
const MOTHERSHIP_AT: Vector2 = Vector2(1200.0, 0.0)
|
const MOTHERSHIP_AT: Vector2 = Vector2(1200.0, 0.0)
|
||||||
const CRAFTER_AT: Vector2 = Vector2(-4000.0, 0.0)
|
const CRAFTER_AT: Vector2 = Vector2(-4000.0, 0.0)
|
||||||
|
|
@ -117,6 +121,7 @@ func _process(delta: float) -> bool:
|
||||||
# --- 4. a different building is not a delivery point --------
|
# --- 4. a different building is not a delivery point --------
|
||||||
field.place(GameState.BUILDING_SHIP_CRAFTER, CRAFTER_AT)
|
field.place(GameState.BUILDING_SHIP_CRAFTER, CRAFTER_AT)
|
||||||
field.place(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
|
field.place(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
|
||||||
|
mothership_id = int(game_state.buildings[-1].id)
|
||||||
_park_fleet_on(CRAFTER_AT)
|
_park_fleet_on(CRAFTER_AT)
|
||||||
elapsed = 0.0
|
elapsed = 0.0
|
||||||
stage = 2
|
stage = 2
|
||||||
|
|
@ -139,6 +144,14 @@ func _process(delta: float) -> bool:
|
||||||
if elapsed < 0.4:
|
if elapsed < 0.4:
|
||||||
return false
|
return false
|
||||||
var total_before: int = banked_before + 3
|
var total_before: int = banked_before + 3
|
||||||
|
# Parking alone must do nothing now (#24).
|
||||||
|
if game_state.fleet_cargo != 3 or game_state.rocks != banked_before:
|
||||||
|
print("TEST_FLEET_CARGO: FAIL (arriving at the mothership banked the hold by itself)")
|
||||||
|
quit(1)
|
||||||
|
return true
|
||||||
|
print("parked on the mothership -> hold still %d (no auto-banking)" % game_state.fleet_cargo)
|
||||||
|
|
||||||
|
game_state.transfer_to_building(mothership_id, 999)
|
||||||
print("delivered: hold=%d bank=%d (expected hold=0 bank=%d)" % [
|
print("delivered: hold=%d bank=%d (expected hold=0 bank=%d)" % [
|
||||||
game_state.fleet_cargo, game_state.rocks, total_before
|
game_state.fleet_cargo, game_state.rocks, total_before
|
||||||
])
|
])
|
||||||
|
|
@ -156,10 +169,6 @@ func _process(delta: float) -> bool:
|
||||||
# Not simulated by loading PlanetSurface.tscn here: the point is
|
# Not simulated by loading PlanetSurface.tscn here: the point is
|
||||||
# that cargo lives in the autoload rather than in Main.tscn, so
|
# that cargo lives in the autoload rather than in Main.tscn, so
|
||||||
# tearing Main.tscn down must not touch it.
|
# tearing Main.tscn down must not touch it.
|
||||||
# Fly off the mothership first, or this new cargo is (correctly)
|
|
||||||
# banked the moment BuildingField next runs, and the teardown
|
|
||||||
# check below would be measuring a delivery instead.
|
|
||||||
_park_fleet_on(CRAFTER_AT)
|
|
||||||
game_state.collect_cargo(5)
|
game_state.collect_cargo(5)
|
||||||
scene.queue_free()
|
scene.queue_free()
|
||||||
elapsed = 0.0
|
elapsed = 0.0
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@ extends SceneTree
|
||||||
# Verifies:
|
# Verifies:
|
||||||
# 1. withdrawing moves ore bank -> hold, and conserves the total
|
# 1. withdrawing moves ore bank -> hold, and conserves the total
|
||||||
# 2. withdrawing is capped by what is actually banked (no overdraft)
|
# 2. withdrawing is capped by what is actually banked (no overdraft)
|
||||||
# 3. arriving at the mothership banks the hold, but doing so is EDGE
|
# 3. arriving at the mothership does NOT bank the hold. It used to, edge
|
||||||
# triggered - ore withdrawn while parked there is not instantly taken
|
# triggered on arrival (#14); #24 removed that entirely, because an
|
||||||
# back, which would make the button look broken
|
# automatic one-way move overrides a player who has just loaded the
|
||||||
|
# hold in order to carry ore somewhere else
|
||||||
# 4. a building takes the fleet's whole hold when opened
|
# 4. a building takes the fleet's whole hold when opened
|
||||||
# 5. end to end: a queued order the bank cannot reach is fulfilled using
|
# 5. end to end: a queued order the bank cannot reach is fulfilled using
|
||||||
# only the fleet, with no Transport Hub in existence
|
# only the fleet, with no Transport Hub in existence
|
||||||
|
|
@ -28,6 +29,7 @@ var fleet: Node
|
||||||
var field: Node
|
var field: Node
|
||||||
var controller: Node
|
var controller: Node
|
||||||
var crafter_id: int = 0
|
var crafter_id: int = 0
|
||||||
|
var mothership_id: int = 0
|
||||||
var stage: int = 0
|
var stage: int = 0
|
||||||
var elapsed: float = 0.0
|
var elapsed: float = 0.0
|
||||||
var ships_before: int = 0
|
var ships_before: int = 0
|
||||||
|
|
@ -74,6 +76,7 @@ func _process(delta: float) -> bool:
|
||||||
_clear_hazards()
|
_clear_hazards()
|
||||||
fleet.spawn_ships(2, MOTHERSHIP_AT)
|
fleet.spawn_ships(2, MOTHERSHIP_AT)
|
||||||
field.place(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
|
field.place(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
|
||||||
|
mothership_id = int(game_state.buildings[-1].id)
|
||||||
field.place(GameState.BUILDING_SHIP_CRAFTER, CRAFTER_AT)
|
field.place(GameState.BUILDING_SHIP_CRAFTER, CRAFTER_AT)
|
||||||
crafter_id = int(game_state.buildings[-1].id)
|
crafter_id = int(game_state.buildings[-1].id)
|
||||||
total_before = _total()
|
total_before = _total()
|
||||||
|
|
@ -107,7 +110,7 @@ func _process(delta: float) -> bool:
|
||||||
if elapsed < 0.3:
|
if elapsed < 0.3:
|
||||||
return false
|
return false
|
||||||
var bank_before: int = game_state.rocks
|
var bank_before: int = game_state.rocks
|
||||||
var took: int = game_state.withdraw_to_fleet(GameState.SHIP_COST)
|
var took: int = game_state.transfer_to_fleet(mothership_id, GameState.SHIP_COST)
|
||||||
if took != GameState.SHIP_COST or game_state.fleet_cargo != GameState.SHIP_COST:
|
if took != GameState.SHIP_COST or game_state.fleet_cargo != GameState.SHIP_COST:
|
||||||
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (withdrew %d into a hold of %d)" % [took, game_state.fleet_cargo])
|
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (withdrew %d into a hold of %d)" % [took, game_state.fleet_cargo])
|
||||||
quit(1)
|
quit(1)
|
||||||
|
|
@ -123,14 +126,22 @@ func _process(delta: float) -> bool:
|
||||||
stage = 4
|
stage = 4
|
||||||
return false
|
return false
|
||||||
4:
|
4:
|
||||||
# --- 3. still parked on the mothership ----------------------
|
# --- 3. parked on the mothership, and arriving again --------
|
||||||
if elapsed < 0.5:
|
if elapsed < 0.5:
|
||||||
return false
|
return false
|
||||||
if game_state.fleet_cargo != GameState.SHIP_COST:
|
if game_state.fleet_cargo != GameState.SHIP_COST:
|
||||||
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (ore withdrawn while parked on the mothership was instantly banked again - delivery is not edge triggered)")
|
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (ore withdrawn while parked on the mothership was banked again by itself)")
|
||||||
quit(1)
|
quit(1)
|
||||||
return true
|
return true
|
||||||
print("still parked -> hold kept its %d (delivery is edge triggered)" % game_state.fleet_cargo)
|
# Leaving and coming back is the case the old edge-triggered
|
||||||
|
# delivery fired on, so it is the one worth re-entering.
|
||||||
|
_park_fleet_on(CRAFTER_AT)
|
||||||
|
_park_fleet_on(MOTHERSHIP_AT)
|
||||||
|
if game_state.fleet_cargo != GameState.SHIP_COST:
|
||||||
|
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (arriving at the mothership auto-banked the hold - #24 removed that)")
|
||||||
|
quit(1)
|
||||||
|
return true
|
||||||
|
print("hold kept its %d across an arrival (no auto-banking)" % game_state.fleet_cargo)
|
||||||
|
|
||||||
# --- 4. carry it to the crafter and open it -----------------
|
# --- 4. carry it to the crafter and open it -----------------
|
||||||
_park_fleet_on(CRAFTER_AT)
|
_park_fleet_on(CRAFTER_AT)
|
||||||
|
|
@ -140,7 +151,7 @@ func _process(delta: float) -> bool:
|
||||||
5:
|
5:
|
||||||
if elapsed < 0.6:
|
if elapsed < 0.6:
|
||||||
return false
|
return false
|
||||||
var moved: int = game_state.unload_into_building(crafter_id)
|
var moved: int = game_state.transfer_to_building(crafter_id, game_state.fleet_cargo)
|
||||||
if moved != GameState.SHIP_COST or game_state.fleet_cargo != 0:
|
if moved != GameState.SHIP_COST or game_state.fleet_cargo != 0:
|
||||||
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (unloaded %d, hold left at %d)" % [moved, game_state.fleet_cargo])
|
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (unloaded %d, hold left at %d)" % [moved, game_state.fleet_cargo])
|
||||||
quit(1)
|
quit(1)
|
||||||
|
|
@ -180,7 +191,7 @@ func _process(delta: float) -> bool:
|
||||||
|
|
||||||
# --- 2. no overdraft ----------------------------------------
|
# --- 2. no overdraft ----------------------------------------
|
||||||
game_state.rocks = 3
|
game_state.rocks = 3
|
||||||
var over: int = game_state.withdraw_to_fleet(999)
|
var over: int = game_state.transfer_to_fleet(mothership_id, 999)
|
||||||
if over != 3 or game_state.rocks != 0:
|
if over != 3 or game_state.rocks != 0:
|
||||||
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (overdrew: took %d from a bank of 3, leaving %d)" % [over, game_state.rocks])
|
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (overdrew: took %d from a bank of 3, leaving %d)" % [over, game_state.rocks])
|
||||||
quit(1)
|
quit(1)
|
||||||
|
|
|
||||||
219
tests/test_resource_transfer.gd
Normal file
219
tests/test_resource_transfer.gd
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
# Headless gameplay test: run with
|
||||||
|
# godot --headless -s res://tests/test_resource_transfer.gd
|
||||||
|
# Forgejo #24: moving ore between the fleet and a building is its own
|
||||||
|
# top-level action, not something buried inside the ship crafter or the
|
||||||
|
# research screen.
|
||||||
|
#
|
||||||
|
# Verifies:
|
||||||
|
# 1. the transfer button appears alongside the action button whenever the
|
||||||
|
# fleet is standing on a building, and opens the transfer panel bound to
|
||||||
|
# THAT building - the `== 0` guard failure that made every crafter
|
||||||
|
# button look dead is the same trap here
|
||||||
|
# 2. ore moves both ways at an ordinary building, capped by what is
|
||||||
|
# actually at each end (no overdraft in either direction)
|
||||||
|
# 3. at a mothership the building's holdings ARE the bank, so unloading
|
||||||
|
# there banks the ore and loading there draws on it
|
||||||
|
# 4. a REAL tap through the input path moves ore, not just the handler
|
||||||
|
# 5. pressing against an empty end reports why instead of doing nothing
|
||||||
|
# 6. the shared resource bar reports the building the panel was opened from
|
||||||
|
# 7. ore is conserved across the whole exercise
|
||||||
|
|
||||||
|
var game_state: Node
|
||||||
|
var scene: Node
|
||||||
|
var fleet: Node
|
||||||
|
var controller: Node
|
||||||
|
var crafter_id: int = 0
|
||||||
|
var mothership_id: int = 0
|
||||||
|
var stage: int = 0
|
||||||
|
var elapsed: float = 0.0
|
||||||
|
var total_before: int = 0
|
||||||
|
var failures: PackedStringArray = PackedStringArray()
|
||||||
|
|
||||||
|
const MOTHERSHIP_AT: Vector2 = Vector2(0.0, 0.0)
|
||||||
|
const CRAFTER_AT: Vector2 = Vector2(3000.0, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _total() -> int:
|
||||||
|
var sum: int = game_state.rocks + game_state.fleet_cargo
|
||||||
|
for b in game_state.buildings:
|
||||||
|
if b.type != GameState.BUILDING_MOTHERSHIP:
|
||||||
|
sum += int(b.get("stock", 0))
|
||||||
|
return sum
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
game_state = root.get_node("GameState")
|
||||||
|
game_state.buildings.clear()
|
||||||
|
game_state.extractors.clear()
|
||||||
|
game_state.transporters.clear()
|
||||||
|
game_state.rocks = 100
|
||||||
|
game_state.fleet_cargo = 0
|
||||||
|
game_state.vessels_owed.clear()
|
||||||
|
game_state.unlocked = ["shipyards", "coordination", "wing", "squadron", "armada"]
|
||||||
|
scene = load("res://Main.tscn").instantiate()
|
||||||
|
root.add_child(scene)
|
||||||
|
fleet = scene.get_node("Fleet")
|
||||||
|
controller = scene.get_node("BuildController")
|
||||||
|
|
||||||
|
|
||||||
|
func _park_fleet_on(at: Vector2) -> void:
|
||||||
|
for ship in fleet.ships:
|
||||||
|
ship.position = at
|
||||||
|
fleet.move_to(at)
|
||||||
|
|
||||||
|
|
||||||
|
func _tap(button: Control) -> void:
|
||||||
|
for pressed in [true, false]:
|
||||||
|
var ev: InputEventScreenTouch = InputEventScreenTouch.new()
|
||||||
|
ev.pressed = pressed
|
||||||
|
ev.position = button.size / 2.0
|
||||||
|
button._gui_input(ev)
|
||||||
|
|
||||||
|
|
||||||
|
func _expect(condition: bool, message: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append("stage %d: %s" % [stage, message])
|
||||||
|
|
||||||
|
|
||||||
|
func _process(delta: float) -> bool:
|
||||||
|
elapsed += delta
|
||||||
|
match stage:
|
||||||
|
0:
|
||||||
|
scene.get_node("AsteroidField").set_process(false)
|
||||||
|
scene.get_node("EnemySpawner").set_process(false)
|
||||||
|
_clear_hazards()
|
||||||
|
fleet.spawn_ships(2, MOTHERSHIP_AT)
|
||||||
|
var field: Node = scene.get_node("BuildingField")
|
||||||
|
field.place(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
|
||||||
|
mothership_id = int(game_state.buildings[-1].id)
|
||||||
|
field.place(GameState.BUILDING_SHIP_CRAFTER, CRAFTER_AT)
|
||||||
|
crafter_id = int(game_state.buildings[-1].id)
|
||||||
|
total_before = _total()
|
||||||
|
_park_fleet_on(CRAFTER_AT)
|
||||||
|
elapsed = 0.0
|
||||||
|
stage = 1
|
||||||
|
return false
|
||||||
|
|
||||||
|
1:
|
||||||
|
# --- 1. the button appears, and binds to this building ------
|
||||||
|
if elapsed < 1.2:
|
||||||
|
return false
|
||||||
|
_expect(controller.current_building != null,
|
||||||
|
"parking on the crafter never registered")
|
||||||
|
_expect(controller.transfer_button.visible,
|
||||||
|
"the transfer button did not appear at a building")
|
||||||
|
_tap(controller.transfer_button)
|
||||||
|
_expect(controller.transfer_menu.visible,
|
||||||
|
"tapping the transfer button did not open the panel")
|
||||||
|
_expect(controller.transfer_building_id == crafter_id,
|
||||||
|
"panel bound to building %d, crafter is %d"
|
||||||
|
% [controller.transfer_building_id, crafter_id])
|
||||||
|
|
||||||
|
# --- 6. the shared bar follows the panel --------------------
|
||||||
|
for bar in controller.resource_bars:
|
||||||
|
_expect(bar.building_id == crafter_id,
|
||||||
|
"a resource bar is reporting building %d, not the crafter %d"
|
||||||
|
% [bar.building_id, crafter_id])
|
||||||
|
|
||||||
|
# --- 5. an empty hold explains itself -----------------------
|
||||||
|
game_state.fleet_cargo = 0
|
||||||
|
controller._on_transfer(true, GameState.CARGO_LOAD_BATCH)
|
||||||
|
_expect("carrying nothing" in controller.transfer_status.text,
|
||||||
|
"unloading an empty hold said: %s" % controller.transfer_status.text)
|
||||||
|
|
||||||
|
# --- 2. both directions at an ordinary building -------------
|
||||||
|
# Baseline taken AFTER the last hand-set figure: the conservation
|
||||||
|
# check at the end is about the transfers, and ore conjured into
|
||||||
|
# the hold to set the scene would otherwise read as a leak.
|
||||||
|
game_state.fleet_cargo = 40
|
||||||
|
total_before = _total()
|
||||||
|
controller._on_transfer(true, GameState.CARGO_LOAD_BATCH)
|
||||||
|
_expect(game_state.building_stock(crafter_id) == GameState.CARGO_LOAD_BATCH,
|
||||||
|
"a batch into the crafter left stock at %d"
|
||||||
|
% game_state.building_stock(crafter_id))
|
||||||
|
_expect(game_state.fleet_cargo == 40 - GameState.CARGO_LOAD_BATCH,
|
||||||
|
"the hold paid %d, not the batch" % (40 - game_state.fleet_cargo))
|
||||||
|
|
||||||
|
controller._on_transfer(false, 999)
|
||||||
|
_expect(game_state.building_stock(crafter_id) == 0,
|
||||||
|
"loading ALL left %d in the crafter" % game_state.building_stock(crafter_id))
|
||||||
|
_expect(game_state.fleet_cargo == 40,
|
||||||
|
"the hold came back to %d, not 40" % game_state.fleet_cargo)
|
||||||
|
|
||||||
|
# Asking for more than exists moves only what is there.
|
||||||
|
var over: int = game_state.transfer_to_fleet(crafter_id, 999)
|
||||||
|
_expect(over == 0, "overdrew %d from an empty crafter" % over)
|
||||||
|
|
||||||
|
_park_fleet_on(MOTHERSHIP_AT)
|
||||||
|
controller.transfer_menu.visible = false
|
||||||
|
elapsed = 0.0
|
||||||
|
stage = 2
|
||||||
|
return false
|
||||||
|
|
||||||
|
2:
|
||||||
|
# --- 3. the mothership's holdings are the bank --------------
|
||||||
|
if elapsed < 1.2:
|
||||||
|
return false
|
||||||
|
_expect(controller.transfer_button.visible,
|
||||||
|
"the transfer button did not appear at the mothership")
|
||||||
|
_tap(controller.transfer_button)
|
||||||
|
_expect(controller.transfer_building_id == mothership_id,
|
||||||
|
"panel bound to %d, mothership is %d"
|
||||||
|
% [controller.transfer_building_id, mothership_id])
|
||||||
|
_expect(game_state.building_holdings(mothership_id) == game_state.rocks,
|
||||||
|
"mothership holdings %d != bank %d"
|
||||||
|
% [game_state.building_holdings(mothership_id), game_state.rocks])
|
||||||
|
|
||||||
|
var bank_before: int = game_state.rocks
|
||||||
|
var hold_before: int = game_state.fleet_cargo
|
||||||
|
controller._on_transfer(true, 999)
|
||||||
|
_expect(game_state.fleet_cargo == 0,
|
||||||
|
"unloading ALL at the mothership left %d in the hold" % game_state.fleet_cargo)
|
||||||
|
_expect(game_state.rocks == bank_before + hold_before,
|
||||||
|
"the bank went to %d, expected %d" % [game_state.rocks, bank_before + hold_before])
|
||||||
|
|
||||||
|
# --- 4. a REAL tap, through the input path ------------------
|
||||||
|
# The handler working while the BUTTON is inert is the exact bug
|
||||||
|
# class this project keeps hitting, so one press goes the long way.
|
||||||
|
var bank_at_tap: int = game_state.rocks
|
||||||
|
_tap(scene.get_node("UI/TransferMenu/ToFleetBatchButton"))
|
||||||
|
_expect(game_state.fleet_cargo == GameState.CARGO_LOAD_BATCH,
|
||||||
|
"a real tap loaded %d, expected a batch of %d"
|
||||||
|
% [game_state.fleet_cargo, GameState.CARGO_LOAD_BATCH])
|
||||||
|
_expect(game_state.rocks == bank_at_tap - GameState.CARGO_LOAD_BATCH,
|
||||||
|
"the bank did not pay for the tapped withdrawal")
|
||||||
|
|
||||||
|
# --- 7. conservation ----------------------------------------
|
||||||
|
_expect(_total() == total_before,
|
||||||
|
"ore created or destroyed: %d -> %d" % [total_before, _total()])
|
||||||
|
|
||||||
|
_finish()
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
if failures.is_empty():
|
||||||
|
print("TEST_RESOURCE_TRANSFER: PASS")
|
||||||
|
quit(0)
|
||||||
|
else:
|
||||||
|
print("bank=%d hold=%d crafter=%d" % [
|
||||||
|
game_state.rocks, game_state.fleet_cargo, game_state.building_stock(crafter_id)
|
||||||
|
])
|
||||||
|
for f in failures:
|
||||||
|
print("FAIL: %s" % f)
|
||||||
|
print("TEST_RESOURCE_TRANSFER: FAIL")
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
|
||||||
|
func _clear_hazards() -> void:
|
||||||
|
# Drifting rocks can destroy a parked ship and change the fleet size out
|
||||||
|
# from under a logistics test. Same reason as test_manual_supply_route.
|
||||||
|
for a in get_nodes_in_group("asteroids"):
|
||||||
|
if is_instance_valid(a):
|
||||||
|
a.queue_free()
|
||||||
|
for e in get_nodes_in_group("enemies"):
|
||||||
|
if is_instance_valid(e):
|
||||||
|
e.queue_free()
|
||||||
1
tests/test_resource_transfer.gd.uid
Normal file
1
tests/test_resource_transfer.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://j27f3sjneewj
|
||||||
Loading…
Reference in a new issue