2026-08-14 22:12:57 +02:00
|
|
|
class_name VesselDefinition
|
|
|
|
|
extends Resource
|
|
|
|
|
|
Implement #77, #78 and #79
#77 - the Distorder becomes the Energy Propulser. Renamed everywhere the
player reads it, reskinned to match: a symmetric hull flaring into an engine
skirt instead of the lopsided warped lump, an engine bell with a thrust plume
instead of the wobbling ring glyph, and energy's own yellow instead of purple,
since energy is what it burns. The internal id stays "distorder" - it is the
capability string, the unlock key and the component scene name, and renaming
the wiring would be a large mechanical change with no player-visible effect.
Its speed ceiling now grows with the LOGARITHM of how many fly. Each one used
to add a flat 1.0, so a fleet that could afford six simply drove at six times
cruise and the vessel stopped being a choice. Base-2 makes the rule one
sentence - every doubling adds another 1.0 - and leaves two propulsers worth
exactly what they were worth before, so nothing already built is devalued.
#78 - a gas burner can be paused. It converts gas the player may want for
bombs into energy they may not need yet, and the only way to stop it was to
demolish it. The action is data-driven like every other: the visual's
ActionProvider lists "pause", and any converter can adopt it. Pause is the
burner's PRIMARY action, so it is one tap rather than a menu; the button shows
what it will DO, bars while running and a play triangle while stopped; and the
building itself goes visibly cold, because a paused building that looked
identical to a running one would be a setting to remember rather than see.
Conversion progress is left where it was rather than reset, so switching off
and on again does not discard a part-finished unit.
#79 - picking something up says so. A "+N" with the resource's own mark rises
and fades where it happened. Built from the SAME glyph the resource tables and
transfer rows use, so a material added to the catalog later cannot end up with
one mark in the HUD and a different one in the world. A pickup a full hold
REFUSES raises nothing.
Two Godot traps cost real time here and are now written down where the next
person will hit them:
- a `#` comment inside a .tres [resource] block is not a comment. It parses
as part of the preceding value and silently blanks the property after it.
One in DistorderVesselDefinition.tres left definition_id empty, which
surfaced as "<blank> did not resolve its definition" in a test far from
the edit. The note lives in VesselDefinition.gd now.
- a global class_name is not reliably resolvable, INCLUDING a script's own
inside its own static function. ResourcePickupVFX.new() failed to compile
when reached through a preview scene, taking Rock and the gas vents with
it. It has no class_name now and constructs itself by path.
Suite 114/114.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 17:38:30 +02:00
|
|
|
# The stable internal key: capability strings, unlock ids in vessel_levels,
|
|
|
|
|
# component scene names and every test refer to a vessel by this, NOT by
|
|
|
|
|
# display_name. The two are deliberately allowed to diverge - #77 renamed the
|
|
|
|
|
# "distorder" to the Energy Propulser purely in what the player sees, and
|
|
|
|
|
# nothing underneath had to be migrated for it.
|
|
|
|
|
#
|
|
|
|
|
# Note for editors of the .tres files themselves: Godot's resource format does
|
|
|
|
|
# NOT accept `#` comments inside a [resource] block. One added there parses as
|
|
|
|
|
# part of the preceding value and silently blanks the property after it, which
|
|
|
|
|
# presents as "<blank> did not resolve its definition" a long way from the
|
|
|
|
|
# edit. Comments about the data go here, in the script.
|
2026-08-14 22:12:57 +02:00
|
|
|
@export var definition_id: String
|
|
|
|
|
@export var display_name: String
|
|
|
|
|
@export_multiline var description: String
|
|
|
|
|
@export var build_cost: int
|
2026-08-15 04:45:15 +02:00
|
|
|
@export var build_resource_costs: Dictionary = {}
|
2026-08-14 22:12:57 +02:00
|
|
|
@export var build_time: float = 10.0
|
|
|
|
|
@export var icon: int = 2
|
|
|
|
|
@export var glow_color: Color = Color(0.25, 0.85, 1.0)
|
|
|
|
|
@export var hull_points: PackedVector2Array = PackedVector2Array([
|
|
|
|
|
Vector2(0, -22), Vector2(-14, 16), Vector2(14, 16),
|
|
|
|
|
])
|
2026-08-15 00:09:10 +02:00
|
|
|
@export var fleet_eligible: bool = false
|
Build the logistics crews, and let them burn energy
#216: transport hubs and spacial workshops no longer conjure their crews.
Transporters and repair drones are ordered at a ship crafter, assigned per
building through a new Station menu, flown out from the mothership hangar,
and spent by the work they do - 20 runs, 12 repairs, counted where the run
ENDS so nothing retires with cargo in the air or a building half repaired.
A spent vessel is gone, so the structure is short again and the player has
to build another. The menu shows what each one has left, worst first.
Craftability needed its own flag: "the crafter can make it" and "it can take
a berth in the fleet" stopped being the same question.
#217: transporters crawl at 120 rather than 150, and a Burn Drive lets them
carry energy and spend it to travel far faster. Fetching it is a leg of its
own, to the nearest store that has any - so a refuelling transporter cannot
be dispatched mid-errand - and the leg flown to COLLECT fuel is not charged
for it. Unresearched, nothing detours and nothing changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
2026-09-05 19:09:35 +02:00
|
|
|
# #216: orderable at a ship crafter without joining the fleet. Transporters
|
|
|
|
|
# and repair drones used to appear with their building for nothing; they are
|
|
|
|
|
# built now, and they staff a structure rather than flying with the player.
|
|
|
|
|
# Separate from fleet_eligible because "the crafter can make it" and "it can
|
|
|
|
|
# take a berth in the fleet" stopped being the same question.
|
|
|
|
|
@export var support_craftable: bool = false
|
Garrison laser vessels and gas bombers, on a shared ability seam
#105 shipped with interceptors as the only garrisonable vessel, and the
reason was a design fault rather than a decision: firing the laser and the
gas bomb lived in the player's fleet controllers, so a stationed laser would
have held its position and never shot.
The shot is now a thing in the world rather than something a controller
draws. LaserBeamEffect and GasBombEffect are self-contained, self-animating
and self-freeing, added to the tree root - the same shape EnemyBigLaserBeam
and ArtilleryShell already had on the enemy side - and they outlive the
vessel that fired them.
Above them sits one seam, on VesselCooldownFillComponent: ability_range(),
ability_ammo_resource(), ability_ammo_cost() and fire_ability(). A beam and a
bomb are now the same shape of thing to whoever pulls the trigger, so the
garrison AI never names an ability - the test asserts that literally, by
reading the AI's own source. A third armed vessel needs to implement the
seam and nothing else.
Ammunition is the CALLER's business, deliberately: the fleet spends from its
hold, a garrison from its outpost's store, and neither concern belongs in
the component. A garrison with an empty magazine holds fire rather than
falling back on the fleet's hold.
Outpost storage: 50 gas per stationed bomber and 50 energy per stationed
laser, off the VESSEL definitions, so an outpost never learns which vessel
needs what. It is a capacity, not a stock - hub transporters fill it exactly
as they fill a gas burner's buffer, because building_logistics_deficit now
takes the larger of the definition's static buffer and the garrison's
magazine. Every other resource is capped at zero: an outpost is not a depot,
and ore parked at one has nothing to do there and no transporter coming for
it.
Also restores two properties the Godot editor dropped from
GasBurnerBuildingDefinition.tres when it re-saved the file:
collected_outputs and capabilities. Without the first, nothing ever collects
the burner's energy - test_gas_burner caught it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 19:23:35 +02:00
|
|
|
# What one of these keeps beside it when stationed at an outpost, and which
|
|
|
|
|
# resource that is. Data here rather than a table in the outpost, so a new
|
|
|
|
|
# armed vessel brings its own magazine with it and the outpost never has to
|
|
|
|
|
# learn about it. 0 means it needs no ammunition to fight - the interceptor's
|
|
|
|
|
# gun is the case that does.
|
|
|
|
|
@export var garrison_ammo_resource: String = ""
|
|
|
|
|
@export var garrison_ammo_capacity: int = 0
|
2026-08-31 15:23:25 +02:00
|
|
|
# Do not add an explicit `= []` default here. Godot 4.7.1 can discard a
|
|
|
|
|
# PackedStringArray override while converting custom resources to binary .res.
|
|
|
|
|
@export var capabilities: PackedStringArray
|
2026-08-14 22:12:57 +02:00
|
|
|
@export var component_scenes: Array[PackedScene] = []
|
|
|
|
|
@export var base_stats: Dictionary = {}
|
|
|
|
|
@export var minimum_stats: Dictionary = {}
|
|
|
|
|
@export var upgrades: Array[Resource] = []
|
|
|
|
|
@export var unlock_id: String
|
|
|
|
|
@export var unlock_name: String
|
|
|
|
|
@export_multiline var unlock_description: String
|
|
|
|
|
@export var unlock_cost: int
|
2026-08-15 04:45:15 +02:00
|
|
|
@export var unlock_resource_costs: Dictionary = {}
|
2026-08-16 20:14:29 +02:00
|
|
|
# #63: spent from GameState.research_points, alongside unlock_resource_costs
|
|
|
|
|
# rather than instead of it. 0 (the default) means this unlock needs no
|
|
|
|
|
# research points at all.
|
|
|
|
|
@export var research_point_cost: int = 0
|
2026-08-15 04:45:15 +02:00
|
|
|
@export var unlock_icon: int = -1
|
2026-08-14 22:12:57 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func has_capability(capability: String) -> bool:
|
|
|
|
|
return capability in capabilities
|