Add carrier vessel (#57)
An unarmed hauler with no weapon component - only VesselCargoComponent, so it structurally cannot fire, the same pattern that already made the shield vessel weaponless after #35. Its cargo_capacity (120, six interceptors' worth) makes it the first real payoff for #56's fleet hold cap. Wired into the crafter, hangar and ship lab menus, all of which were already generic over VesselDefinitions.fleet()/.all() - only the per-vessel UI *nodes* are hand-placed (Main.tscn), not the logic, so this repacks those three button rows to 4/4/5 columns and extends BuildController's matching onready arrays. No new signal handlers were needed. Also fixes test_vessel_composition.gd's hardcoded fleet-catalog size (3 -> 4) now that a fourth vessel is fleet-eligible.
This commit is contained in:
parent
f040327523
commit
bbb1d4b0c5
13 changed files with 489 additions and 40 deletions
|
|
@ -31,11 +31,13 @@ const VESSEL_DEFINITIONS = preload("res://VesselDefinitions.gd")
|
||||||
@onready var lab_vessel_buttons: Array = [
|
@onready var lab_vessel_buttons: Array = [
|
||||||
get_node("../UI/LabMenu/VesselA"), get_node("../UI/LabMenu/VesselB"),
|
get_node("../UI/LabMenu/VesselA"), get_node("../UI/LabMenu/VesselB"),
|
||||||
get_node("../UI/LabMenu/VesselC"), get_node("../UI/LabMenu/VesselD"),
|
get_node("../UI/LabMenu/VesselC"), get_node("../UI/LabMenu/VesselD"),
|
||||||
|
get_node("../UI/LabMenu/VesselE"),
|
||||||
]
|
]
|
||||||
@onready var lab_vessel_label: Label = get_node("../UI/LabMenu/VesselLabel")
|
@onready var lab_vessel_label: Label = get_node("../UI/LabMenu/VesselLabel")
|
||||||
@onready var lab_vessel_labels: Array = [
|
@onready var lab_vessel_labels: Array = [
|
||||||
get_node("../UI/LabMenu/VesselALabel"), get_node("../UI/LabMenu/VesselBLabel"),
|
get_node("../UI/LabMenu/VesselALabel"), get_node("../UI/LabMenu/VesselBLabel"),
|
||||||
get_node("../UI/LabMenu/VesselCLabel"), get_node("../UI/LabMenu/VesselDLabel"),
|
get_node("../UI/LabMenu/VesselCLabel"), get_node("../UI/LabMenu/VesselDLabel"),
|
||||||
|
get_node("../UI/LabMenu/VesselELabel"),
|
||||||
]
|
]
|
||||||
@onready var lab_detail: Label = get_node("../UI/LabMenu/DetailLabel")
|
@onready var lab_detail: Label = get_node("../UI/LabMenu/DetailLabel")
|
||||||
@onready var lab_status: Label = get_node("../UI/LabMenu/StatusLabel")
|
@onready var lab_status: Label = get_node("../UI/LabMenu/StatusLabel")
|
||||||
|
|
@ -63,21 +65,25 @@ const VESSEL_DEFINITIONS = preload("res://VesselDefinitions.gd")
|
||||||
get_node("../UI/HangarMenu/AvailableInterceptor"),
|
get_node("../UI/HangarMenu/AvailableInterceptor"),
|
||||||
get_node("../UI/HangarMenu/AvailableShield"),
|
get_node("../UI/HangarMenu/AvailableShield"),
|
||||||
get_node("../UI/HangarMenu/AvailableGasBomber"),
|
get_node("../UI/HangarMenu/AvailableGasBomber"),
|
||||||
|
get_node("../UI/HangarMenu/AvailableCarrier"),
|
||||||
]
|
]
|
||||||
@onready var hangar_available_labels: Array = [
|
@onready var hangar_available_labels: Array = [
|
||||||
get_node("../UI/HangarMenu/AvailableInterceptorLabel"),
|
get_node("../UI/HangarMenu/AvailableInterceptorLabel"),
|
||||||
get_node("../UI/HangarMenu/AvailableShieldLabel"),
|
get_node("../UI/HangarMenu/AvailableShieldLabel"),
|
||||||
get_node("../UI/HangarMenu/AvailableGasBomberLabel"),
|
get_node("../UI/HangarMenu/AvailableGasBomberLabel"),
|
||||||
|
get_node("../UI/HangarMenu/AvailableCarrierLabel"),
|
||||||
]
|
]
|
||||||
@onready var hangar_fleet_slots: Array = [
|
@onready var hangar_fleet_slots: Array = [
|
||||||
get_node("../UI/HangarMenu/FleetInterceptor"),
|
get_node("../UI/HangarMenu/FleetInterceptor"),
|
||||||
get_node("../UI/HangarMenu/FleetShield"),
|
get_node("../UI/HangarMenu/FleetShield"),
|
||||||
get_node("../UI/HangarMenu/FleetGasBomber"),
|
get_node("../UI/HangarMenu/FleetGasBomber"),
|
||||||
|
get_node("../UI/HangarMenu/FleetCarrier"),
|
||||||
]
|
]
|
||||||
@onready var hangar_fleet_labels: Array = [
|
@onready var hangar_fleet_labels: Array = [
|
||||||
get_node("../UI/HangarMenu/FleetInterceptorLabel"),
|
get_node("../UI/HangarMenu/FleetInterceptorLabel"),
|
||||||
get_node("../UI/HangarMenu/FleetShieldLabel"),
|
get_node("../UI/HangarMenu/FleetShieldLabel"),
|
||||||
get_node("../UI/HangarMenu/FleetGasBomberLabel"),
|
get_node("../UI/HangarMenu/FleetGasBomberLabel"),
|
||||||
|
get_node("../UI/HangarMenu/FleetCarrierLabel"),
|
||||||
]
|
]
|
||||||
var build_menu_entries: Array = []
|
var build_menu_entries: Array = []
|
||||||
|
|
||||||
|
|
@ -103,11 +109,13 @@ var lab_building_id: int = 0
|
||||||
@onready var craft_shield_label: Label = get_node("../UI/CraftMenu/ShieldLabel")
|
@onready var craft_shield_label: Label = get_node("../UI/CraftMenu/ShieldLabel")
|
||||||
@onready var craft_gas_bomber_button: IconButton = get_node("../UI/CraftMenu/GasBomberButton")
|
@onready var craft_gas_bomber_button: IconButton = get_node("../UI/CraftMenu/GasBomberButton")
|
||||||
@onready var craft_gas_bomber_label: Label = get_node("../UI/CraftMenu/GasBomberLabel")
|
@onready var craft_gas_bomber_label: Label = get_node("../UI/CraftMenu/GasBomberLabel")
|
||||||
|
@onready var craft_carrier_button: IconButton = get_node("../UI/CraftMenu/CarrierButton")
|
||||||
|
@onready var craft_carrier_label: Label = get_node("../UI/CraftMenu/CarrierLabel")
|
||||||
@onready var craft_vessel_buttons: Array = [
|
@onready var craft_vessel_buttons: Array = [
|
||||||
craft_interceptor_button, craft_shield_button, craft_gas_bomber_button,
|
craft_interceptor_button, craft_shield_button, craft_gas_bomber_button, craft_carrier_button,
|
||||||
]
|
]
|
||||||
@onready var craft_vessel_labels: Array = [
|
@onready var craft_vessel_labels: Array = [
|
||||||
craft_interceptor_label, craft_shield_label, craft_gas_bomber_label,
|
craft_interceptor_label, craft_shield_label, craft_gas_bomber_label, craft_carrier_label,
|
||||||
]
|
]
|
||||||
|
|
||||||
# --- the transfer panel (#24) ---------------------------------------------
|
# --- the transfer panel (#24) ---------------------------------------------
|
||||||
|
|
|
||||||
11
CarrierCapacityUpgrade.tres
Normal file
11
CarrierCapacityUpgrade.tres
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[gd_resource type="Resource" script_class="VesselUpgradeDefinition" load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://VesselUpgradeDefinition.gd" id="1"]
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
upgrade_id = "car_capacity"
|
||||||
|
display_name = "Reinforced Hold"
|
||||||
|
description = "+30 cargo capacity per level."
|
||||||
|
base_cost = 40
|
||||||
|
max_level = 3
|
||||||
|
icon = 21
|
||||||
|
additive_modifiers = {"cargo_capacity": 30.0}
|
||||||
25
CarrierVesselDefinition.tres
Normal file
25
CarrierVesselDefinition.tres
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
[gd_resource type="Resource" script_class="VesselDefinition" load_steps=4 format=3]
|
||||||
|
[ext_resource type="Script" path="res://VesselDefinition.gd" id="1"]
|
||||||
|
[ext_resource type="PackedScene" path="res://VesselCargoComponent.tscn" id="2"]
|
||||||
|
[ext_resource type="Resource" path="res://CarrierCapacityUpgrade.tres" id="3"]
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
definition_id = "carrier"
|
||||||
|
display_name = "Carrier"
|
||||||
|
description = "Unarmed hauler with a much larger hold than any combat vessel. Cannot fire."
|
||||||
|
build_cost = 24
|
||||||
|
build_time = 20.0
|
||||||
|
icon = 34
|
||||||
|
glow_color = Color(1, 0.78, 0.3, 1)
|
||||||
|
hull_points = PackedVector2Array(-32, -10, -18, -18, 18, -18, 32, -10, 32, 14, -32, 14)
|
||||||
|
fleet_eligible = true
|
||||||
|
capabilities = PackedStringArray("cargo")
|
||||||
|
component_scenes = Array[PackedScene]([ExtResource("2")])
|
||||||
|
base_stats = {"max_speed": 380.0, "acceleration": 360.0, "delivery_speed": 180.0, "cargo_capacity": 120.0}
|
||||||
|
upgrades = Array[Resource]([ExtResource("3")])
|
||||||
|
unlock_id = "unlock_carrier"
|
||||||
|
unlock_name = "Unlock Carrier"
|
||||||
|
unlock_description = "Research a dedicated hauler hull that trades weapons for a much larger cargo hold."
|
||||||
|
unlock_cost = 0
|
||||||
|
unlock_resource_costs = {"ore": 60}
|
||||||
|
unlock_icon = -1
|
||||||
|
|
@ -463,6 +463,7 @@ const VESSEL_INTERCEPTOR: String = "interceptor"
|
||||||
const VESSEL_SHIELD: String = "shield"
|
const VESSEL_SHIELD: String = "shield"
|
||||||
const VESSEL_GAS_BOMBER: String = "gas_bomber"
|
const VESSEL_GAS_BOMBER: String = "gas_bomber"
|
||||||
const VESSEL_TRANSPORTER: String = "transporter"
|
const VESSEL_TRANSPORTER: String = "transporter"
|
||||||
|
const VESSEL_CARRIER: String = "carrier"
|
||||||
|
|
||||||
# Compatibility constants for callers and old tests. Runtime metadata is
|
# Compatibility constants for callers and old tests. Runtime metadata is
|
||||||
# resolved from VesselDefinition below.
|
# resolved from VesselDefinition below.
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ enum Icon {
|
||||||
# inserting there would silently renumber every icon int already
|
# inserting there would silently renumber every icon int already
|
||||||
# stored in existing scenes (#54).
|
# stored in existing scenes (#54).
|
||||||
GAS_BURNER,
|
GAS_BURNER,
|
||||||
|
# Same append-only rule (#57): a wide hauler hull, distinct from every
|
||||||
|
# vessel glyph above.
|
||||||
|
CARRIER_VESSEL,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Size the glyphs were originally drawn against. Every glyph dimension is
|
# Size the glyphs were originally drawn against. Every glyph dimension is
|
||||||
|
|
@ -227,6 +230,8 @@ func _draw() -> void:
|
||||||
_draw_gas_bomb(center, pulse, s)
|
_draw_gas_bomb(center, pulse, s)
|
||||||
Icon.GAS_BURNER:
|
Icon.GAS_BURNER:
|
||||||
_draw_gas_burner_icon(center, pulse, s)
|
_draw_gas_burner_icon(center, pulse, s)
|
||||||
|
Icon.CARRIER_VESSEL:
|
||||||
|
_draw_carrier(center, pulse, s)
|
||||||
|
|
||||||
|
|
||||||
func _draw_close(center: Vector2, pulse: float, s: float) -> void:
|
func _draw_close(center: Vector2, pulse: float, s: float) -> void:
|
||||||
|
|
@ -251,6 +256,20 @@ func _draw_shield_vessel(center: Vector2, pulse: float, s: float) -> void:
|
||||||
NeonDraw.draw_glowing_polygon(self, points, _active_color(), pulse, core_color)
|
NeonDraw.draw_glowing_polygon(self, points, _active_color(), pulse, core_color)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_carrier(center: Vector2, pulse: float, s: float) -> void:
|
||||||
|
# Wide and flat-bottomed rather than pointed - reads as a hauler, not a
|
||||||
|
# combat silhouette, matching the ship's own hull_points.
|
||||||
|
var hull := PackedVector2Array([
|
||||||
|
center + Vector2(-13, -4) * s,
|
||||||
|
center + Vector2(-7, -8) * s,
|
||||||
|
center + Vector2(7, -8) * s,
|
||||||
|
center + Vector2(13, -4) * s,
|
||||||
|
center + Vector2(13, 6) * s,
|
||||||
|
center + Vector2(-13, 6) * s,
|
||||||
|
])
|
||||||
|
NeonDraw.draw_glowing_polygon(self, hull, _active_color(), pulse, core_color)
|
||||||
|
|
||||||
|
|
||||||
func _draw_gas_bomber(center: Vector2, pulse: float, s: float) -> void:
|
func _draw_gas_bomber(center: Vector2, pulse: float, s: float) -> void:
|
||||||
var hull := PackedVector2Array([
|
var hull := PackedVector2Array([
|
||||||
center + Vector2(0, -12) * s,
|
center + Vector2(0, -12) * s,
|
||||||
|
|
|
||||||
167
Main.tscn
167
Main.tscn
|
|
@ -840,8 +840,8 @@ horizontal_alignment = 1
|
||||||
[node name="AvailableShield" type="Control" parent="UI/HangarMenu"]
|
[node name="AvailableShield" type="Control" parent="UI/HangarMenu"]
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -56
|
offset_left = -134
|
||||||
offset_right = 56
|
offset_right = -22
|
||||||
offset_top = 255
|
offset_top = 255
|
||||||
offset_bottom = 375
|
offset_bottom = 375
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
|
|
@ -852,8 +852,8 @@ glow_color = Color(0.45, 0.8, 1, 1)
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -96
|
offset_left = -174
|
||||||
offset_right = 96
|
offset_right = 18
|
||||||
offset_top = 380
|
offset_top = 380
|
||||||
offset_bottom = 460
|
offset_bottom = 460
|
||||||
theme_override_font_sizes/font_size = 22
|
theme_override_font_sizes/font_size = 22
|
||||||
|
|
@ -862,8 +862,8 @@ horizontal_alignment = 1
|
||||||
[node name="AvailableGasBomber" type="Control" parent="UI/HangarMenu"]
|
[node name="AvailableGasBomber" type="Control" parent="UI/HangarMenu"]
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = 178
|
offset_left = 22
|
||||||
offset_right = 290
|
offset_right = 134
|
||||||
offset_top = 255
|
offset_top = 255
|
||||||
offset_bottom = 375
|
offset_bottom = 375
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
|
|
@ -874,6 +874,28 @@ glow_color = Color(0.96, 0.32, 0.78, 1)
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
|
offset_left = -18
|
||||||
|
offset_right = 174
|
||||||
|
offset_top = 380
|
||||||
|
offset_bottom = 460
|
||||||
|
theme_override_font_sizes/font_size = 22
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="AvailableCarrier" type="Control" parent="UI/HangarMenu"]
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = 178
|
||||||
|
offset_right = 290
|
||||||
|
offset_top = 255
|
||||||
|
offset_bottom = 375
|
||||||
|
script = ExtResource("10")
|
||||||
|
icon = 34
|
||||||
|
glow_color = Color(1, 0.78, 0.3, 1)
|
||||||
|
|
||||||
|
[node name="AvailableCarrierLabel" type="Label" parent="UI/HangarMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
offset_left = 138
|
offset_left = 138
|
||||||
offset_right = 330
|
offset_right = 330
|
||||||
offset_top = 380
|
offset_top = 380
|
||||||
|
|
@ -914,8 +936,8 @@ horizontal_alignment = 1
|
||||||
[node name="FleetShield" type="Control" parent="UI/HangarMenu"]
|
[node name="FleetShield" type="Control" parent="UI/HangarMenu"]
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -56
|
offset_left = -134
|
||||||
offset_right = 56
|
offset_right = -22
|
||||||
offset_top = 565
|
offset_top = 565
|
||||||
offset_bottom = 685
|
offset_bottom = 685
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
|
|
@ -926,8 +948,8 @@ glow_color = Color(0.45, 0.8, 1, 1)
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -96
|
offset_left = -174
|
||||||
offset_right = 96
|
offset_right = 18
|
||||||
offset_top = 690
|
offset_top = 690
|
||||||
offset_bottom = 770
|
offset_bottom = 770
|
||||||
theme_override_font_sizes/font_size = 22
|
theme_override_font_sizes/font_size = 22
|
||||||
|
|
@ -936,8 +958,8 @@ horizontal_alignment = 1
|
||||||
[node name="FleetGasBomber" type="Control" parent="UI/HangarMenu"]
|
[node name="FleetGasBomber" type="Control" parent="UI/HangarMenu"]
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = 178
|
offset_left = 22
|
||||||
offset_right = 290
|
offset_right = 134
|
||||||
offset_top = 565
|
offset_top = 565
|
||||||
offset_bottom = 685
|
offset_bottom = 685
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
|
|
@ -948,6 +970,28 @@ glow_color = Color(0.96, 0.32, 0.78, 1)
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
|
offset_left = -18
|
||||||
|
offset_right = 174
|
||||||
|
offset_top = 690
|
||||||
|
offset_bottom = 770
|
||||||
|
theme_override_font_sizes/font_size = 22
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="FleetCarrier" type="Control" parent="UI/HangarMenu"]
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = 178
|
||||||
|
offset_right = 290
|
||||||
|
offset_top = 565
|
||||||
|
offset_bottom = 685
|
||||||
|
script = ExtResource("10")
|
||||||
|
icon = 34
|
||||||
|
glow_color = Color(1, 0.78, 0.3, 1)
|
||||||
|
|
||||||
|
[node name="FleetCarrierLabel" type="Label" parent="UI/HangarMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
offset_left = 138
|
offset_left = 138
|
||||||
offset_right = 330
|
offset_right = 330
|
||||||
offset_top = 690
|
offset_top = 690
|
||||||
|
|
@ -1000,7 +1044,7 @@ script = ExtResource("24")
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -300
|
offset_left = -300
|
||||||
offset_right = -190
|
offset_right = -212
|
||||||
offset_top = 274
|
offset_top = 274
|
||||||
offset_bottom = 394
|
offset_bottom = 394
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
|
|
@ -1010,8 +1054,8 @@ glow_color = Color(0.25, 0.85, 1, 1)
|
||||||
[node name="VesselB" type="Control" parent="UI/LabMenu"]
|
[node name="VesselB" type="Control" parent="UI/LabMenu"]
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -145
|
offset_left = -172
|
||||||
offset_right = -35
|
offset_right = -84
|
||||||
offset_top = 274
|
offset_top = 274
|
||||||
offset_bottom = 394
|
offset_bottom = 394
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
|
|
@ -1021,8 +1065,8 @@ glow_color = Color(0.45, 0.8, 1, 1)
|
||||||
[node name="VesselC" type="Control" parent="UI/LabMenu"]
|
[node name="VesselC" type="Control" parent="UI/LabMenu"]
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = 35
|
offset_left = -44
|
||||||
offset_right = 145
|
offset_right = 44
|
||||||
offset_top = 274
|
offset_top = 274
|
||||||
offset_bottom = 394
|
offset_bottom = 394
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
|
|
@ -1032,14 +1076,25 @@ glow_color = Color(0.96, 0.32, 0.78, 1)
|
||||||
[node name="VesselD" type="Control" parent="UI/LabMenu"]
|
[node name="VesselD" type="Control" parent="UI/LabMenu"]
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = 190
|
offset_left = 84
|
||||||
offset_right = 300
|
offset_right = 172
|
||||||
offset_top = 274
|
offset_top = 274
|
||||||
offset_bottom = 394
|
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)
|
||||||
|
|
||||||
|
[node name="VesselE" type="Control" parent="UI/LabMenu"]
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = 212
|
||||||
|
offset_right = 300
|
||||||
|
offset_top = 274
|
||||||
|
offset_bottom = 394
|
||||||
|
script = ExtResource("10")
|
||||||
|
icon = 34
|
||||||
|
glow_color = Color(1, 0.78, 0.3, 1)
|
||||||
|
|
||||||
[node name="VesselLabel" type="Label" parent="UI/LabMenu"]
|
[node name="VesselLabel" type="Label" parent="UI/LabMenu"]
|
||||||
visible = false
|
visible = false
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
|
|
@ -1057,7 +1112,7 @@ mouse_filter = 2
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -330
|
offset_left = -330
|
||||||
offset_right = -160
|
offset_right = -182
|
||||||
offset_top = 400
|
offset_top = 400
|
||||||
offset_bottom = 460
|
offset_bottom = 460
|
||||||
theme_override_font_sizes/font_size = 22
|
theme_override_font_sizes/font_size = 22
|
||||||
|
|
@ -1068,8 +1123,8 @@ text = "Interceptor"
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = -175
|
offset_left = -202
|
||||||
offset_right = -5
|
offset_right = -54
|
||||||
offset_top = 400
|
offset_top = 400
|
||||||
offset_bottom = 460
|
offset_bottom = 460
|
||||||
theme_override_font_sizes/font_size = 22
|
theme_override_font_sizes/font_size = 22
|
||||||
|
|
@ -1080,8 +1135,8 @@ text = "Shield"
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = 5
|
offset_left = -74
|
||||||
offset_right = 175
|
offset_right = 74
|
||||||
offset_top = 400
|
offset_top = 400
|
||||||
offset_bottom = 460
|
offset_bottom = 460
|
||||||
theme_override_font_sizes/font_size = 22
|
theme_override_font_sizes/font_size = 22
|
||||||
|
|
@ -1092,14 +1147,26 @@ text = "Gas Bomber"
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
offset_left = 160
|
offset_left = 54
|
||||||
offset_right = 330
|
offset_right = 202
|
||||||
offset_top = 400
|
offset_top = 400
|
||||||
offset_bottom = 460
|
offset_bottom = 460
|
||||||
theme_override_font_sizes/font_size = 22
|
theme_override_font_sizes/font_size = 22
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
text = "Transporter"
|
text = "Transporter"
|
||||||
|
|
||||||
|
[node name="VesselELabel" type="Label" parent="UI/LabMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = 182
|
||||||
|
offset_right = 330
|
||||||
|
offset_top = 400
|
||||||
|
offset_bottom = 460
|
||||||
|
theme_override_font_sizes/font_size = 22
|
||||||
|
horizontal_alignment = 1
|
||||||
|
text = "Carrier"
|
||||||
|
|
||||||
[node name="VesselUpgradeSeparator" type="ColorRect" parent="UI/LabMenu"]
|
[node name="VesselUpgradeSeparator" type="ColorRect" parent="UI/LabMenu"]
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
|
|
@ -1345,7 +1412,7 @@ anchor_right = 0.5
|
||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -310
|
offset_left = -310
|
||||||
offset_right = -182
|
offset_right = -208
|
||||||
offset_top = -100
|
offset_top = -100
|
||||||
offset_bottom = 48
|
offset_bottom = 48
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
|
|
@ -1358,7 +1425,7 @@ anchor_right = 0.5
|
||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -330
|
offset_left = -330
|
||||||
offset_right = -162
|
offset_right = -188
|
||||||
offset_top = 56
|
offset_top = 56
|
||||||
offset_bottom = 136
|
offset_bottom = 136
|
||||||
theme_override_font_sizes/font_size = 24
|
theme_override_font_sizes/font_size = 24
|
||||||
|
|
@ -1370,8 +1437,8 @@ anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -148
|
offset_left = -174
|
||||||
offset_right = -20
|
offset_right = -72
|
||||||
offset_top = -100
|
offset_top = -100
|
||||||
offset_bottom = 48
|
offset_bottom = 48
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
|
|
@ -1384,8 +1451,8 @@ anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -168
|
offset_left = -194
|
||||||
offset_right = 0
|
offset_right = -52
|
||||||
offset_top = 56
|
offset_top = 56
|
||||||
offset_bottom = 136
|
offset_bottom = 136
|
||||||
theme_override_font_sizes/font_size = 24
|
theme_override_font_sizes/font_size = 24
|
||||||
|
|
@ -1398,8 +1465,8 @@ anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = 14
|
offset_left = -38
|
||||||
offset_right = 142
|
offset_right = 64
|
||||||
offset_top = -100
|
offset_top = -100
|
||||||
offset_bottom = 28
|
offset_bottom = 28
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
|
|
@ -1412,8 +1479,8 @@ anchor_left = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -6
|
offset_left = -58
|
||||||
offset_right = 162
|
offset_right = 84
|
||||||
offset_top = 56
|
offset_top = 56
|
||||||
offset_bottom = 150
|
offset_bottom = 150
|
||||||
theme_override_font_sizes/font_size = 24
|
theme_override_font_sizes/font_size = 24
|
||||||
|
|
@ -1421,6 +1488,34 @@ autowrap_mode = 2
|
||||||
text = "Gas Bomber"
|
text = "Gas Bomber"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="CarrierButton" type="Control" parent="UI/CraftMenu"]
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = 98
|
||||||
|
offset_right = 200
|
||||||
|
offset_top = -100
|
||||||
|
offset_bottom = 48
|
||||||
|
script = ExtResource("10")
|
||||||
|
icon = 34
|
||||||
|
glow_color = Color(1, 0.78, 0.3, 1)
|
||||||
|
|
||||||
|
[node name="CarrierLabel" type="Label" parent="UI/CraftMenu"]
|
||||||
|
mouse_filter = 2
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = 78
|
||||||
|
offset_right = 220
|
||||||
|
offset_top = 56
|
||||||
|
offset_bottom = 136
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
autowrap_mode = 2
|
||||||
|
text = "Carrier"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="QueueLabel" type="Label" parent="UI/CraftMenu"]
|
[node name="QueueLabel" type="Label" parent="UI/CraftMenu"]
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
anchor_left = 1.0
|
anchor_left = 1.0
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
class_name VesselDefinitions
|
class_name VesselDefinitions
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
const ORDER: PackedStringArray = ["interceptor", "shield", "gas_bomber", "transporter"]
|
const ORDER: PackedStringArray = ["interceptor", "shield", "gas_bomber", "transporter", "carrier"]
|
||||||
const DEFINITIONS: Dictionary = {
|
const DEFINITIONS: Dictionary = {
|
||||||
"interceptor": preload("res://InterceptorVesselDefinition.tres"),
|
"interceptor": preload("res://InterceptorVesselDefinition.tres"),
|
||||||
"shield": preload("res://ShieldVesselDefinition.tres"),
|
"shield": preload("res://ShieldVesselDefinition.tres"),
|
||||||
"gas_bomber": preload("res://GasBomberVesselDefinition.tres"),
|
"gas_bomber": preload("res://GasBomberVesselDefinition.tres"),
|
||||||
"transporter": preload("res://TransporterVesselDefinition.tres"),
|
"transporter": preload("res://TransporterVesselDefinition.tres"),
|
||||||
|
"carrier": preload("res://CarrierVesselDefinition.tres"),
|
||||||
}
|
}
|
||||||
|
|
||||||
static func get_definition(definition_id: String) -> Resource:
|
static func get_definition(definition_id: String) -> Resource:
|
||||||
|
|
|
||||||
17
tests/_carrier_crafter_preview.gd
Normal file
17
tests/_carrier_crafter_preview.gd
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
extends Node2D
|
||||||
|
|
||||||
|
# Preview, not a test - #57's crafter menu screenshot with all 4 fleet
|
||||||
|
# vessels researched, carrier included as the new 4th entry.
|
||||||
|
#
|
||||||
|
# bash tests/screenshot.sh res://tests/_carrier_crafter_preview.tscn /tmp/crafter.png 20 720x1280
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.buildings.clear()
|
||||||
|
GameState.extractors.clear()
|
||||||
|
GameState.rocks = 500
|
||||||
|
GameState.vessel_levels = {"unlock_shield": 1, "unlock_gas_bomber": 1, "unlock_carrier": 1}
|
||||||
|
var main: Node = load("res://Main.tscn").instantiate()
|
||||||
|
add_child(main)
|
||||||
|
main.get_node("UI/CraftMenu").visible = true
|
||||||
|
main.get_node("BuildController")._refresh_craft_status()
|
||||||
6
tests/_carrier_crafter_preview.tscn
Normal file
6
tests/_carrier_crafter_preview.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://tests/_carrier_crafter_preview.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="CarrierCrafterPreview" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
21
tests/_carrier_hangar_preview.gd
Normal file
21
tests/_carrier_hangar_preview.gd
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
extends Node2D
|
||||||
|
|
||||||
|
# Preview, not a test - #57's hangar menu screenshot with all 4 fleet
|
||||||
|
# vessels researched, one of each stored, and a carrier crewed into the
|
||||||
|
# fleet so both rows show the new entry.
|
||||||
|
#
|
||||||
|
# bash tests/screenshot.sh res://tests/_carrier_hangar_preview.tscn /tmp/hangar.png 20 720x1280
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.buildings.clear()
|
||||||
|
GameState.extractors.clear()
|
||||||
|
GameState.rocks = 500
|
||||||
|
GameState.vessel_levels = {"unlock_shield": 1, "unlock_gas_bomber": 1, "unlock_carrier": 1}
|
||||||
|
GameState.stored_vessels = ["shield", "gas_bomber", "carrier"]
|
||||||
|
var main: Node = load("res://Main.tscn").instantiate()
|
||||||
|
add_child(main)
|
||||||
|
var fleet: Node = main.get_node("Fleet")
|
||||||
|
fleet.spawn_ships(2, Vector2.ZERO, PackedStringArray(["interceptor", "carrier"]))
|
||||||
|
main.get_node("UI/HangarMenu").visible = true
|
||||||
|
main.get_node("BuildController")._refresh_hangar()
|
||||||
6
tests/_carrier_hangar_preview.tscn
Normal file
6
tests/_carrier_hangar_preview.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://tests/_carrier_hangar_preview.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="CarrierHangarPreview" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
239
tests/test_carrier_vessel.gd
Normal file
239
tests/test_carrier_vessel.gd
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
# Headless gameplay test: run with
|
||||||
|
# godot --headless -s res://tests/test_carrier_vessel.gd
|
||||||
|
# Forgejo #57: an unarmed hauler that substantially raises how much the
|
||||||
|
# fleet can carry (#56).
|
||||||
|
#
|
||||||
|
# Covers:
|
||||||
|
# A. crewing a carrier raises GameState.fleet_cargo_capacity() by its stat
|
||||||
|
# B. a carrier never fires - no weapon component, no projectile ever
|
||||||
|
# originates from it even with an enemy parked in interceptor fire range
|
||||||
|
# C. it is unbuildable until researched
|
||||||
|
# D. it appears in the crafter, hangar and ship lab with correct name,
|
||||||
|
# cost, icon and colour, and is buildable once researched
|
||||||
|
|
||||||
|
var game_state: Node
|
||||||
|
var scene: Node
|
||||||
|
var fleet: Node
|
||||||
|
var controller: Node
|
||||||
|
var field: Node
|
||||||
|
var crafter_id: int = 0
|
||||||
|
var mothership_id: int = 0
|
||||||
|
var definition: Resource
|
||||||
|
var capacity_before: int = 0
|
||||||
|
var enemy: Node
|
||||||
|
var stage: int = 0
|
||||||
|
var elapsed: float = 0.0
|
||||||
|
|
||||||
|
const MOTHERSHIP_AT: Vector2 = Vector2(0.0, 0.0)
|
||||||
|
const CRAFTER_AT: Vector2 = Vector2(3000.0, 0.0)
|
||||||
|
# Same slot the crafter/hangar UI arrays give carrier: VesselDefinitions.ORDER
|
||||||
|
# appends carrier after transporter, and VesselDefinitions.fleet() filters
|
||||||
|
# transporter out, so carrier lands last among the 4 fleet-eligible vessels
|
||||||
|
# (interceptor, shield, gas_bomber, carrier) in both craft_vessel_* and the
|
||||||
|
# hangar_* arrays.
|
||||||
|
const FLEET_UI_INDEX: int = 3
|
||||||
|
# The ship lab shows every vessel including the transporter, so carrier is
|
||||||
|
# the 5th and last slot there.
|
||||||
|
const LAB_UI_INDEX: int = 4
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
game_state = root.get_node("GameState")
|
||||||
|
game_state.buildings.clear()
|
||||||
|
game_state.extractors.clear()
|
||||||
|
game_state.transporters.clear()
|
||||||
|
game_state.stored_vessels.clear()
|
||||||
|
game_state.reset_progression()
|
||||||
|
game_state.build_time_scale = 0.02
|
||||||
|
game_state.rocks = 500
|
||||||
|
scene = load("res://Main.tscn").instantiate()
|
||||||
|
root.add_child(scene)
|
||||||
|
fleet = scene.get_node("Fleet")
|
||||||
|
controller = scene.get_node("BuildController")
|
||||||
|
|
||||||
|
|
||||||
|
func _clear_hazards() -> void:
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
func _process(delta: float) -> bool:
|
||||||
|
elapsed += delta
|
||||||
|
match stage:
|
||||||
|
0:
|
||||||
|
if elapsed < 0.2:
|
||||||
|
return false
|
||||||
|
scene.get_node("AsteroidField").set_process(false)
|
||||||
|
scene.get_node("EnemySpawner").set_process(false)
|
||||||
|
_clear_hazards()
|
||||||
|
|
||||||
|
# --- structural: unarmed, cargo-capable, fleet-eligible ---------
|
||||||
|
definition = game_state.vessel_definition(GameState.VESSEL_CARRIER)
|
||||||
|
if definition == null or not definition.fleet_eligible:
|
||||||
|
return _fail("carrier is not a fleet-eligible vessel definition")
|
||||||
|
if definition.capabilities.has("weapon"):
|
||||||
|
return _fail("carrier declares a weapon capability")
|
||||||
|
if not definition.capabilities.has("cargo"):
|
||||||
|
return _fail("carrier does not declare the cargo capability")
|
||||||
|
|
||||||
|
# --- A. capacity math, directly -----------------------------------
|
||||||
|
if game_state.fleet_cargo_capacity(PackedStringArray(["carrier"])) != 120:
|
||||||
|
return _fail("one carrier's capacity contribution is %d, expected 120" \
|
||||||
|
% game_state.fleet_cargo_capacity(PackedStringArray(["carrier"])))
|
||||||
|
print("structural: unarmed, cargo capability, contributes 120 alone")
|
||||||
|
|
||||||
|
# --- C. gated until researched --------------------------------
|
||||||
|
field = 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)
|
||||||
|
field.place(GameState.BUILDING_SHIP_LAB, CRAFTER_AT + Vector2(0.0, 400.0))
|
||||||
|
if game_state.vessel_is_unlocked(GameState.VESSEL_CARRIER):
|
||||||
|
return _fail("carrier starts unlocked")
|
||||||
|
if game_state.queue_order(crafter_id, GameState.VESSEL_CARRIER):
|
||||||
|
return _fail("queue_order accepted an unresearched carrier")
|
||||||
|
print("unresearched carrier is unbuildable, as expected")
|
||||||
|
|
||||||
|
# --- D. locked state in the crafter UI -------------------------
|
||||||
|
controller.craft_building_id = crafter_id
|
||||||
|
controller._refresh_craft_status()
|
||||||
|
if controller.craft_vessel_buttons[FLEET_UI_INDEX].enabled:
|
||||||
|
return _fail("a locked carrier shows enabled in the crafter menu")
|
||||||
|
|
||||||
|
stage = 1
|
||||||
|
elapsed = 0.0
|
||||||
|
return false
|
||||||
|
1:
|
||||||
|
if elapsed < 0.1:
|
||||||
|
return false
|
||||||
|
# --- C continued: unlocking ------------------------------------
|
||||||
|
if not game_state.unlock_vessel(GameState.VESSEL_CARRIER):
|
||||||
|
return _fail("unlock_vessel(carrier) failed with 500 ore in the bank")
|
||||||
|
if not game_state.vessel_is_unlocked(GameState.VESSEL_CARRIER):
|
||||||
|
return _fail("carrier still reports locked after unlocking")
|
||||||
|
if game_state.product_costs(GameState.VESSEL_CARRIER) != {GameState.RESOURCE_ORE: 24}:
|
||||||
|
return _fail("carrier's build cost is %s, expected 24 ore" \
|
||||||
|
% game_state.product_costs(GameState.VESSEL_CARRIER))
|
||||||
|
print("researched: unlocked, costs %s" % game_state.product_costs(GameState.VESSEL_CARRIER))
|
||||||
|
|
||||||
|
# --- D. unlocked state in the crafter UI ------------------------
|
||||||
|
controller._refresh_craft_status()
|
||||||
|
var craft_button: IconButton = controller.craft_vessel_buttons[FLEET_UI_INDEX]
|
||||||
|
var craft_label: Label = controller.craft_vessel_labels[FLEET_UI_INDEX]
|
||||||
|
if not craft_button.enabled:
|
||||||
|
return _fail("an unlocked carrier still shows disabled in the crafter menu")
|
||||||
|
if craft_button.icon != definition.icon or craft_button.glow_color != definition.glow_color:
|
||||||
|
return _fail("crafter carrier button does not match its definition's icon/colour")
|
||||||
|
if not craft_label.text.begins_with(definition.display_name):
|
||||||
|
return _fail("crafter carrier label reads '%s', expected to start with '%s'" \
|
||||||
|
% [craft_label.text, definition.display_name])
|
||||||
|
if not game_state.queue_order(crafter_id, GameState.VESSEL_CARRIER):
|
||||||
|
return _fail("queue_order rejected a researched carrier")
|
||||||
|
print("crafter: enabled, correct icon/colour, correct label, buildable")
|
||||||
|
|
||||||
|
# Bypass the build timer/delivery wait - that queue mechanism is
|
||||||
|
# generic and already covered for every other vessel
|
||||||
|
# (test_building_stock_queue.gd, test_resources_and_buildings.gd
|
||||||
|
# etc.); this ticket is about the carrier's OWN behaviour, not
|
||||||
|
# re-proving crafting works.
|
||||||
|
game_state.stored_vessels.append(GameState.VESSEL_CARRIER)
|
||||||
|
|
||||||
|
# --- D. hangar display -------------------------------------------
|
||||||
|
controller._refresh_hangar()
|
||||||
|
var available_slot: IconButton = controller.hangar_available_slots[FLEET_UI_INDEX]
|
||||||
|
var available_label: Label = controller.hangar_available_labels[FLEET_UI_INDEX]
|
||||||
|
if available_slot.icon != definition.icon or available_slot.glow_color != definition.glow_color:
|
||||||
|
return _fail("hangar carrier slot does not match its definition's icon/colour")
|
||||||
|
var expected_available: String = "%s\n1 available" % definition.display_name
|
||||||
|
if available_label.text != expected_available:
|
||||||
|
return _fail("hangar carrier label reads '%s', expected '%s'" \
|
||||||
|
% [available_label.text, expected_available])
|
||||||
|
print("hangar: correct icon/colour, shows 1 available")
|
||||||
|
|
||||||
|
# --- A continued: crewing it raises capacity ---------------------
|
||||||
|
# The default fleet (5 interceptors) already sits at
|
||||||
|
# max_fleet_size() with reset_progression()'s cleared skills, which
|
||||||
|
# would make _on_hangar_crew_type refuse for being full - shrink
|
||||||
|
# to 1 ship first so there is room to crew the carrier at all.
|
||||||
|
fleet.spawn_ships(1, MOTHERSHIP_AT, PackedStringArray([GameState.VESSEL_INTERCEPTOR]))
|
||||||
|
capacity_before = game_state.fleet_cargo_total_capacity()
|
||||||
|
controller._on_hangar_crew_type(FLEET_UI_INDEX)
|
||||||
|
if not fleet.has_vessel_type(GameState.VESSEL_CARRIER):
|
||||||
|
return _fail("crewing from the hangar did not add a carrier to the fleet")
|
||||||
|
var capacity_after: int = game_state.fleet_cargo_total_capacity()
|
||||||
|
if capacity_after != capacity_before + 120:
|
||||||
|
return _fail("crewing a carrier moved capacity %d -> %d, expected +120" \
|
||||||
|
% [capacity_before, capacity_after])
|
||||||
|
print("crewed: capacity %d -> %d (+120)" % [capacity_before, capacity_after])
|
||||||
|
|
||||||
|
# --- D. ship lab display -------------------------------------------
|
||||||
|
controller.lab_vessel = GameState.VESSEL_CARRIER
|
||||||
|
controller._refresh_lab()
|
||||||
|
var lab_button: IconButton = controller.lab_vessel_buttons[LAB_UI_INDEX]
|
||||||
|
var lab_label: Label = controller.lab_vessel_labels[LAB_UI_INDEX]
|
||||||
|
if lab_button.icon != definition.icon or lab_button.glow_color != definition.glow_color:
|
||||||
|
return _fail("ship lab carrier slot does not match its definition's icon/colour")
|
||||||
|
if lab_label.text != definition.display_name:
|
||||||
|
return _fail("ship lab carrier label reads '%s', expected '%s'" \
|
||||||
|
% [lab_label.text, definition.display_name])
|
||||||
|
print("ship lab: correct icon/colour/label")
|
||||||
|
|
||||||
|
stage = 2
|
||||||
|
elapsed = 0.0
|
||||||
|
return false
|
||||||
|
2:
|
||||||
|
# --- B. never fires, even with a target in range -----------------
|
||||||
|
# Isolate: JUST a carrier in the fleet, nothing else nearby that
|
||||||
|
# could fire instead and make the check meaningless.
|
||||||
|
fleet.spawn_ships(1, Vector2.ZERO, PackedStringArray([GameState.VESSEL_CARRIER]))
|
||||||
|
stage = 3
|
||||||
|
elapsed = 0.0
|
||||||
|
return false
|
||||||
|
3:
|
||||||
|
# A settling tick before the enemy appears: queue_free() on the
|
||||||
|
# ships spawn_ships() just replaced does not remove them from the
|
||||||
|
# tree instantly, so a stale interceptor's weapon could otherwise
|
||||||
|
# still get one last physics tick against a target added in the
|
||||||
|
# same frame - a false positive, not the carrier firing.
|
||||||
|
if elapsed < 0.3:
|
||||||
|
return false
|
||||||
|
var enemy_scene: PackedScene = load("res://Enemy.tscn")
|
||||||
|
enemy = enemy_scene.instantiate()
|
||||||
|
enemy.position = fleet.get_centroid() + Vector2(220.0, 0.0)
|
||||||
|
scene.add_child(enemy)
|
||||||
|
# Frozen in place: a chaser closes distance and kills on contact,
|
||||||
|
# and an unarmed carrier has nothing to stop that. Letting it
|
||||||
|
# approach would wipe the fleet, trip the "rebuild at the
|
||||||
|
# mothership" safety net (a default INTERCEPTOR), and that
|
||||||
|
# interceptor firing would look like a carrier-firing false
|
||||||
|
# positive - this test is about whether the carrier fires, not
|
||||||
|
# whether it can survive unescorted.
|
||||||
|
enemy.set_physics_process(false)
|
||||||
|
stage = 4
|
||||||
|
elapsed = 0.0
|
||||||
|
return false
|
||||||
|
4:
|
||||||
|
if elapsed < 2.0:
|
||||||
|
return false
|
||||||
|
var projectiles: Array = get_nodes_in_group("player_projectiles")
|
||||||
|
if not projectiles.is_empty():
|
||||||
|
return _fail("%d projectile(s) originated from a fleet made only of a carrier" \
|
||||||
|
% projectiles.size())
|
||||||
|
print("no projectiles after %.1fs with an enemy in interceptor fire range" % elapsed)
|
||||||
|
print("TEST_CARRIER_VESSEL: PASS")
|
||||||
|
quit(0)
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _fail(message: String) -> bool:
|
||||||
|
print("TEST_CARRIER_VESSEL: FAIL (%s)" % message)
|
||||||
|
quit(1)
|
||||||
|
return true
|
||||||
|
|
@ -23,7 +23,7 @@ func _process(delta: float) -> bool:
|
||||||
return false
|
return false
|
||||||
var definitions: Array = VESSEL_DEFINITIONS.all()
|
var definitions: Array = VESSEL_DEFINITIONS.all()
|
||||||
var fleet_definitions: Array = VESSEL_DEFINITIONS.fleet()
|
var fleet_definitions: Array = VESSEL_DEFINITIONS.fleet()
|
||||||
if fleet_definitions.size() != 3 \
|
if fleet_definitions.size() != 4 \
|
||||||
or fleet_definitions.any(func(definition): return definition.has_capability("transport")):
|
or fleet_definitions.any(func(definition): return definition.has_capability("transport")):
|
||||||
return _fail("logistics transporters leaked into the combat-fleet catalog")
|
return _fail("logistics transporters leaked into the combat-fleet catalog")
|
||||||
if ships.size() != definitions.size():
|
if ships.size() != definitions.size():
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue