Typed owed vessels, ship lab building type, shield vessel gate
Groundwork for #5. vessels_owed carries the VESSEL TYPE rather than a bare count, so a shield vessel finishing while the player is on a planet comes back as a shield vessel. Fleet.add_ship takes a type, and vessels already flying are re-statted when an upgrade is researched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
004808f067
commit
19ac758da1
9 changed files with 80 additions and 27 deletions
|
|
@ -557,7 +557,7 @@ func _refresh_craft_status() -> void:
|
||||||
# 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"
|
||||||
# and a button that looks live and silently does nothing when pressed.
|
# and a button that looks live and silently does nothing when pressed.
|
||||||
var room: bool = fleet.ships.size() + GameState.ships_owed \
|
var room: bool = fleet.ships.size() + GameState.vessels_owed.size() \
|
||||||
+ GameState.pending_orders_of(craft_building_id, GameState.PRODUCT_SHIP) \
|
+ GameState.pending_orders_of(craft_building_id, GameState.PRODUCT_SHIP) \
|
||||||
< GameState.max_fleet_size()
|
< GameState.max_fleet_size()
|
||||||
craft_interceptor_button.enabled = room
|
craft_interceptor_button.enabled = room
|
||||||
|
|
@ -568,7 +568,7 @@ func _refresh_craft_status() -> void:
|
||||||
craft_status.text += " Fleet %d/%d" % [fleet.ships.size(), GameState.max_fleet_size()]
|
craft_status.text += " Fleet %d/%d" % [fleet.ships.size(), GameState.max_fleet_size()]
|
||||||
else:
|
else:
|
||||||
var queued_ships: int = GameState.pending_orders_of(craft_building_id, GameState.PRODUCT_SHIP)
|
var queued_ships: int = GameState.pending_orders_of(craft_building_id, GameState.PRODUCT_SHIP)
|
||||||
var owed: int = GameState.ships_owed + queued_ships
|
var owed: int = GameState.vessels_owed.size() + queued_ships
|
||||||
var pending_note: String = "" if owed == 0 else " (%d already on order)" % owed
|
var pending_note: String = "" if owed == 0 else " (%d already on order)" % owed
|
||||||
craft_status.text = "No room in the fleet: %d/%d%s - research capacity at the mothership\n%s" % [
|
craft_status.text = "No room in the fleet: %d/%d%s - research capacity at the mothership\n%s" % [
|
||||||
fleet.ships.size(), GameState.max_fleet_size(), pending_note, craft_status.text
|
fleet.ships.size(), GameState.max_fleet_size(), pending_note, craft_status.text
|
||||||
|
|
@ -586,8 +586,8 @@ func _on_craft_interceptor() -> void:
|
||||||
if craft_building_id == 0:
|
if craft_building_id == 0:
|
||||||
return
|
return
|
||||||
# Still refused when there is nowhere to put the vessel - queueing an
|
# Still refused when there is nowhere to put the vessel - queueing an
|
||||||
# order that can never be delivered would just bank ships_owed forever.
|
# order that can never be delivered would just bank vessels_owed forever.
|
||||||
if fleet.ships.size() + GameState.ships_owed \
|
if fleet.ships.size() + GameState.vessels_owed.size() \
|
||||||
+ GameState.pending_orders_of(craft_building_id, GameState.PRODUCT_SHIP) \
|
+ GameState.pending_orders_of(craft_building_id, GameState.PRODUCT_SHIP) \
|
||||||
>= GameState.max_fleet_size():
|
>= GameState.max_fleet_size():
|
||||||
_say("Fleet is full (%d slots)" % GameState.max_fleet_size())
|
_say("Fleet is full (%d slots)" % GameState.max_fleet_size())
|
||||||
|
|
|
||||||
25
Fleet.gd
25
Fleet.gd
|
|
@ -32,6 +32,18 @@ var anchor: Vector2 = Vector2.ZERO
|
||||||
var steer_direction: Vector2 = Vector2.ZERO
|
var steer_direction: Vector2 = Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# Researching a vessel upgrade improves the ships already flying, not
|
||||||
|
# just the next one built (#5).
|
||||||
|
GameState.vessel_upgraded.connect(_on_vessel_upgraded)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_vessel_upgraded(_id: String) -> void:
|
||||||
|
for ship in ships:
|
||||||
|
if is_instance_valid(ship):
|
||||||
|
ship.apply_vessel_stats()
|
||||||
|
|
||||||
|
|
||||||
func spawn_ships(count: int, center: Vector2 = Vector2.ZERO) -> void:
|
func spawn_ships(count: int, center: Vector2 = Vector2.ZERO) -> void:
|
||||||
for ship in ships:
|
for ship in ships:
|
||||||
if is_instance_valid(ship):
|
if is_instance_valid(ship):
|
||||||
|
|
@ -55,13 +67,16 @@ func spawn_ships(count: int, center: Vector2 = Vector2.ZERO) -> void:
|
||||||
fleet_target = center
|
fleet_target = center
|
||||||
|
|
||||||
|
|
||||||
func add_ship() -> bool:
|
func add_ship(vessel_type: String = GameState.VESSEL_INTERCEPTOR) -> bool:
|
||||||
# Adds one vessel in the next free formation slot (bought from a ship
|
# Adds one vessel in the next free formation slot (bought from a ship
|
||||||
# crafter). Returns false when the wedge is already full.
|
# crafter). Returns false when the wedge is already full.
|
||||||
if ships.size() >= GameState.max_fleet_size():
|
if ships.size() >= GameState.max_fleet_size():
|
||||||
return false
|
return false
|
||||||
var offset: Vector2 = GameState.formation[ships.size()]
|
var offset: Vector2 = GameState.formation[ships.size()]
|
||||||
var ship: Area2D = ship_scene.instantiate()
|
var ship: Area2D = ship_scene.instantiate()
|
||||||
|
# Before add_child, so Ship._ready() builds the right vessel - the shield
|
||||||
|
# ring is created there and only for a shield vessel.
|
||||||
|
ship.vessel_type = vessel_type
|
||||||
# From the formation origin, not the centroid: with a lopsided layout the
|
# From the formation origin, not the centroid: with a lopsided layout the
|
||||||
# centroid is already displaced by the average offset, so adding the new
|
# centroid is already displaced by the average offset, so adding the new
|
||||||
# ship's own offset to it would drop it well outside the wedge.
|
# ship's own offset to it would drop it well outside the wedge.
|
||||||
|
|
@ -128,12 +143,12 @@ func _process(delta: float) -> void:
|
||||||
func _claim_owed_ships() -> void:
|
func _claim_owed_ships() -> void:
|
||||||
# Production completes in the GameState autoload, which may well happen
|
# Production completes in the GameState autoload, which may well happen
|
||||||
# while the player is on a planet's surface with no Fleet node in
|
# while the player is on a planet's surface with no Fleet node in
|
||||||
# existence to hand a vessel to. Those ships wait as `ships_owed` until a
|
# existence to hand a vessel to. Those vessels wait in `vessels_owed`
|
||||||
# fleet with room can take them.
|
# fleet with room can take them.
|
||||||
while GameState.ships_owed > 0 and ships.size() < GameState.max_fleet_size():
|
while not GameState.vessels_owed.is_empty() and ships.size() < GameState.max_fleet_size():
|
||||||
if not add_ship():
|
if not add_ship(String(GameState.vessels_owed[0])):
|
||||||
return
|
return
|
||||||
GameState.ships_owed -= 1
|
GameState.vessels_owed.pop_front()
|
||||||
|
|
||||||
|
|
||||||
func _assign_slot_targets() -> void:
|
func _assign_slot_targets() -> void:
|
||||||
|
|
|
||||||
53
GameState.gd
53
GameState.gd
|
|
@ -104,10 +104,13 @@ const BUILDING_EXTRACTOR: String = "extractor"
|
||||||
# Hosts the transport vessels that supply other buildings (#18). Built in
|
# Hosts the transport vessels that supply other buildings (#18). Built in
|
||||||
# space like the rest; it is the transporters (#19) that do the moving.
|
# space like the rest; it is the transporters (#19) that do the moving.
|
||||||
const BUILDING_TRANSPORT_HUB: String = "transport_hub"
|
const BUILDING_TRANSPORT_HUB: String = "transport_hub"
|
||||||
|
# Where vessel types are researched and new ones unlocked (#5).
|
||||||
|
const BUILDING_SHIP_LAB: String = "ship_lab"
|
||||||
|
|
||||||
const COMMAND_CENTER_COST: int = 15
|
const COMMAND_CENTER_COST: int = 15
|
||||||
const EXTRACTOR_COST: int = 25
|
const EXTRACTOR_COST: int = 25
|
||||||
const TRANSPORT_HUB_COST: int = 30
|
const TRANSPORT_HUB_COST: int = 30
|
||||||
|
const SHIP_LAB_COST: int = 35
|
||||||
|
|
||||||
var buildings: Array = []
|
var buildings: Array = []
|
||||||
|
|
||||||
|
|
@ -145,6 +148,11 @@ const SKILL_TREE: Array = [
|
||||||
"x": 0.18, "unlocks": BUILDING_TRANSPORT_HUB,
|
"x": 0.18, "unlocks": BUILDING_TRANSPORT_HUB,
|
||||||
"desc": "Build transport hubs, which supply your other buildings with ore.",
|
"desc": "Build transport hubs, which supply your other buildings with ore.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "vessel_science", "name": "Vessel Science", "cost": 35, "requires": "shipyards",
|
||||||
|
"x": 0.62, "unlocks": BUILDING_SHIP_LAB,
|
||||||
|
"desc": "Build ship labs, where each vessel type is upgraded and new ones unlocked.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "freight_yards", "name": "Freight Yards", "cost": 80, "requires": "freight",
|
"id": "freight_yards", "name": "Freight Yards", "cost": 80, "requires": "freight",
|
||||||
"x": 0.05, "hub_bonus": 1,
|
"x": 0.05, "hub_bonus": 1,
|
||||||
|
|
@ -269,12 +277,12 @@ func reset_progression() -> void:
|
||||||
# on the same spot.
|
# on the same spot.
|
||||||
var next_building_id: int = 1
|
var next_building_id: int = 1
|
||||||
|
|
||||||
# Ships that have been paid for and built but not yet handed to the fleet.
|
# Vessels that have been paid for and built but not yet handed to the fleet,
|
||||||
# Production is simulated here, in the autoload, the same way extractor
|
# as a list of VESSEL_* types - a shield vessel finishing while the player is
|
||||||
# haulers are - so an order can complete while the player is down on a
|
# on a planet must not come back as an interceptor. Production is simulated
|
||||||
# planet's surface, where there is no Fleet node to hand a ship to. The fleet
|
# here, in the autoload, the same way extractor haulers are, so an order can
|
||||||
# claims them when it next can.
|
# complete where there is no Fleet node to hand a ship to.
|
||||||
var ships_owed: int = 0
|
var vessels_owed: Array = []
|
||||||
|
|
||||||
signal order_completed(building_id: int, type: String)
|
signal order_completed(building_id: int, type: String)
|
||||||
|
|
||||||
|
|
@ -372,6 +380,33 @@ var vessel_levels: Dictionary = {}
|
||||||
signal vessel_upgraded(upgrade_id: String)
|
signal vessel_upgraded(upgrade_id: String)
|
||||||
|
|
||||||
|
|
||||||
|
func has_ship_lab() -> bool:
|
||||||
|
for b in buildings:
|
||||||
|
if b.type == BUILDING_SHIP_LAB:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func shield_vessel_unlocked() -> bool:
|
||||||
|
# Unlocked AT the lab, by paying for it there - a vessel type rather than
|
||||||
|
# a stat, so it is a one-off rather than a level.
|
||||||
|
return int(vessel_levels.get(UNLOCK_SHIELD_VESSEL, 0)) > 0
|
||||||
|
|
||||||
|
|
||||||
|
const UNLOCK_SHIELD_VESSEL: String = "unlock_shield"
|
||||||
|
const UNLOCK_SHIELD_COST: int = 45
|
||||||
|
|
||||||
|
|
||||||
|
func unlock_shield_vessel() -> bool:
|
||||||
|
if shield_vessel_unlocked() or not has_ship_lab():
|
||||||
|
return false
|
||||||
|
if not spend(UNLOCK_SHIELD_COST):
|
||||||
|
return false
|
||||||
|
vessel_levels[UNLOCK_SHIELD_VESSEL] = 1
|
||||||
|
vessel_upgraded.emit(UNLOCK_SHIELD_VESSEL)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
func upgrade_def(id: String) -> Dictionary:
|
func upgrade_def(id: String) -> Dictionary:
|
||||||
for u in VESSEL_UPGRADES:
|
for u in VESSEL_UPGRADES:
|
||||||
if u.id == id:
|
if u.id == id:
|
||||||
|
|
@ -404,7 +439,7 @@ func upgrade_cost(id: String) -> int:
|
||||||
|
|
||||||
|
|
||||||
func can_buy_upgrade(id: String) -> bool:
|
func can_buy_upgrade(id: String) -> bool:
|
||||||
return not upgrade_maxed(id) and can_afford(upgrade_cost(id))
|
return has_ship_lab() and not upgrade_maxed(id) and can_afford(upgrade_cost(id))
|
||||||
|
|
||||||
|
|
||||||
func buy_upgrade(id: String) -> bool:
|
func buy_upgrade(id: String) -> bool:
|
||||||
|
|
@ -531,6 +566,8 @@ func queue_order(id: int, product: String = PRODUCT_SHIP) -> bool:
|
||||||
var b: Dictionary = find_building(id)
|
var b: Dictionary = find_building(id)
|
||||||
if b.is_empty() or not can_produce(b.type):
|
if b.is_empty() or not can_produce(b.type):
|
||||||
return false
|
return false
|
||||||
|
if product == VESSEL_SHIELD and not shield_vessel_unlocked():
|
||||||
|
return false
|
||||||
if product == PRODUCT_TRANSPORTER and not has_transport_hub():
|
if product == PRODUCT_TRANSPORTER and not has_transport_hub():
|
||||||
# Nowhere to station it. Refused rather than queued, so the player is
|
# Nowhere to station it. Refused rather than queued, so the player is
|
||||||
# told now instead of watching an order sit forever.
|
# told now instead of watching an order sit forever.
|
||||||
|
|
@ -570,7 +607,7 @@ func _advance_production() -> void:
|
||||||
if product == PRODUCT_TRANSPORTER:
|
if product == PRODUCT_TRANSPORTER:
|
||||||
_station_transporter(b.position)
|
_station_transporter(b.position)
|
||||||
else:
|
else:
|
||||||
ships_owed += 1
|
vessels_owed.append(product)
|
||||||
order_completed.emit(int(b.id), product)
|
order_completed.emit(int(b.id), product)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func _initialize() -> void:
|
||||||
game_state.extractors.clear()
|
game_state.extractors.clear()
|
||||||
game_state.rocks = 500
|
game_state.rocks = 500
|
||||||
game_state.fleet_cargo = 0
|
game_state.fleet_cargo = 0
|
||||||
game_state.ships_owed = 0
|
game_state.vessels_owed.clear()
|
||||||
# Room to grow: the default cap is BASE_FLEET_SIZE (5) and the fleet
|
# Room to grow: the default cap is BASE_FLEET_SIZE (5) and the fleet
|
||||||
# starts at exactly 5, so without this every completed order would have
|
# starts at exactly 5, so without this every completed order would have
|
||||||
# nowhere to put its vessel and the test would be measuring the cap
|
# nowhere to put its vessel and the test would be measuring the cap
|
||||||
|
|
@ -180,7 +180,7 @@ func _process(delta: float) -> bool:
|
||||||
# --- 6 + 7. no scene, no fleet: production still completes --
|
# --- 6 + 7. no scene, no fleet: production still completes --
|
||||||
# The scene has to be GONE before the order is placed. With a
|
# The scene has to be GONE before the order is placed. With a
|
||||||
# fleet still alive and under its cap it claims the vessel the
|
# fleet still alive and under its cap it claims the vessel the
|
||||||
# same frame production finishes, which would leave ships_owed
|
# same frame production finishes, which would leave vessels_owed
|
||||||
# back at 0 and prove nothing about orders surviving without it.
|
# back at 0 and prove nothing about orders surviving without it.
|
||||||
ships_at_start = fleet.ships.size()
|
ships_at_start = fleet.ships.size()
|
||||||
scene.queue_free()
|
scene.queue_free()
|
||||||
|
|
@ -207,11 +207,11 @@ func _process(delta: float) -> bool:
|
||||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (order stalled once the space scene was gone)")
|
print("TEST_BUILDING_STOCK_QUEUE: FAIL (order stalled once the space scene was gone)")
|
||||||
quit(1)
|
quit(1)
|
||||||
return true
|
return true
|
||||||
if game_state.ships_owed < 1:
|
if game_state.vessels_owed.is_empty():
|
||||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (vessel completed with no fleet present was lost, not owed)")
|
print("TEST_BUILDING_STOCK_QUEUE: FAIL (vessel completed with no fleet present was lost, not owed)")
|
||||||
quit(1)
|
quit(1)
|
||||||
return true
|
return true
|
||||||
print("no scene at all -> order completed, %d vessel owed" % game_state.ships_owed)
|
print("no scene at all -> order completed, %d vessel owed" % game_state.vessels_owed.size())
|
||||||
|
|
||||||
# Come back to space: the fleet should claim what it is owed.
|
# Come back to space: the fleet should claim what it is owed.
|
||||||
scene = load("res://Main.tscn").instantiate()
|
scene = load("res://Main.tscn").instantiate()
|
||||||
|
|
@ -223,8 +223,8 @@ func _process(delta: float) -> bool:
|
||||||
8:
|
8:
|
||||||
if elapsed < 0.5:
|
if elapsed < 0.5:
|
||||||
return false
|
return false
|
||||||
if game_state.ships_owed != 0:
|
if not game_state.vessels_owed.is_empty():
|
||||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (returning fleet never claimed its %d owed vessel(s))" % game_state.ships_owed)
|
print("TEST_BUILDING_STOCK_QUEUE: FAIL (returning fleet never claimed its %d owed vessel(s))" % game_state.vessels_owed.size())
|
||||||
quit(1)
|
quit(1)
|
||||||
return true
|
return true
|
||||||
print("fleet returned and claimed the owed vessel (ships=%d)" % fleet.ships.size())
|
print("fleet returned and claimed the owed vessel (ships=%d)" % fleet.ships.size())
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ func _initialize() -> void:
|
||||||
game_state.unlocked = ["shipyards", "coordination", "wing"]
|
game_state.unlocked = ["shipyards", "coordination", "wing"]
|
||||||
game_state.rocks = 100
|
game_state.rocks = 100
|
||||||
game_state.fleet_cargo = 0
|
game_state.fleet_cargo = 0
|
||||||
game_state.ships_owed = 0
|
game_state.vessels_owed.clear()
|
||||||
scene = load("res://Main.tscn").instantiate()
|
scene = load("res://Main.tscn").instantiate()
|
||||||
root.add_child(scene)
|
root.add_child(scene)
|
||||||
controller = scene.get_node("BuildController")
|
controller = scene.get_node("BuildController")
|
||||||
|
|
|
||||||
1
tests/test_crafter_feedback.gd.uid
Normal file
1
tests/test_crafter_feedback.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c33061yx51h5s
|
||||||
|
|
@ -34,7 +34,7 @@ func _initialize() -> void:
|
||||||
game_state.transporters.clear()
|
game_state.transporters.clear()
|
||||||
game_state.rocks = 900
|
game_state.rocks = 900
|
||||||
game_state.fleet_cargo = 0
|
game_state.fleet_cargo = 0
|
||||||
game_state.ships_owed = 0
|
game_state.vessels_owed.clear()
|
||||||
game_state.unlocked = ["shipyards", "logistics", "freight"]
|
game_state.unlocked = ["shipyards", "logistics", "freight"]
|
||||||
scene = load("res://Main.tscn").instantiate()
|
scene = load("res://Main.tscn").instantiate()
|
||||||
root.add_child(scene)
|
root.add_child(scene)
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ func _initialize() -> void:
|
||||||
game_state.extractors.clear()
|
game_state.extractors.clear()
|
||||||
game_state.rocks = 40
|
game_state.rocks = 40
|
||||||
game_state.fleet_cargo = 0
|
game_state.fleet_cargo = 0
|
||||||
game_state.ships_owed = 0
|
game_state.vessels_owed.clear()
|
||||||
game_state.unlocked = ["shipyards", "coordination", "wing", "squadron", "armada"]
|
game_state.unlocked = ["shipyards", "coordination", "wing", "squadron", "armada"]
|
||||||
scene = load("res://Main.tscn").instantiate()
|
scene = load("res://Main.tscn").instantiate()
|
||||||
root.add_child(scene)
|
root.add_child(scene)
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ func _initialize() -> void:
|
||||||
game_state.transporters.clear()
|
game_state.transporters.clear()
|
||||||
game_state.rocks = 200
|
game_state.rocks = 200
|
||||||
game_state.fleet_cargo = 0
|
game_state.fleet_cargo = 0
|
||||||
game_state.ships_owed = 0
|
game_state.vessels_owed.clear()
|
||||||
game_state.unlocked = ["shipyards", "logistics", "freight", "coordination", "wing", "squadron", "armada"]
|
game_state.unlocked = ["shipyards", "logistics", "freight", "coordination", "wing", "squadron", "armada"]
|
||||||
scene = load("res://Main.tscn").instantiate()
|
scene = load("res://Main.tscn").instantiate()
|
||||||
root.add_child(scene)
|
root.add_child(scene)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue