Laser shooter vessel and its skill shot #58
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A new fleet vessel granting a manually aimed laser beam attack. Model it closely on the gas bomber, which is the established pattern for a vessel-granted skill shot.
Reference implementation
Read these together before starting - the gas bomber is the template and its per-ship-cooldown design was hard-won in #45-era work:
VesselGasBomberComponent.gd- per-ship cooldown plus the ready/cooling hull-fill visual (_filled_hull)GasBombController.gd- press-drag-release aiming, theMAX_RANGE/DRAG_RADIUSmapping, camera zoom-out while aiming and restoration afterward, and_next_ready_bomber()selecting whichever ship still has a shotGasBombButtoninMain.tscn/PlanetSurface.tscn- the button appears only when the fleet has the capabilityScope
VesselLaserComponent.tscn+.gd,capability_id = "laser": its own cooldown,is_ready(),consume_shot(duration), and a ready/cooling hull fill in the laser's colour. Per ship, not per fleet - two laser vessels give two shots (this is the exact bug fixed ineb79efdfor bombers; do not reintroduce it).LaserController.gd+ a node in bothMain.tscnandPlanetSurface.tscn, plus aUI/LaserButtonIconButtonbeside the rotation controls,visible = fleet.has_capability("laser"),enabledonly when some laser vessel is ready.GameController._handle_touch's existing first-refusal chain - see howgas_bomb_controller.handle_touchis offered the event first). The shot is a straight beam from the firing ship along the drag direction out toMAX_RANGE, damaging every node in groupenemieswithinBEAM_HALF_WIDTHof the segment. Reuse the aim-time camera zoom-out and restoration.LaserVesselDefinition.treswithcapabilities = ["laser"], its own hull and colour, build costs, and ship-labunlock_*fields. Append any newIconButton.Iconenum entries - inserting one renumbers every icon int already saved in scenes.VesselDefinitions; add crafter/hangar nodes as described in #57 (same hand-placed constraint).Out of scope
Energy consumption per shot - that is #61, which depends on this ticket and on #54 (energy).
Acceptance criteria
Tests
tests/test_laser_vessel.gd(bash tests/run_tests.sh laser), plus a 720x1280 screenshot of the aim state with the beam preview drawn.Repo notes (read before starting)
/storage/emulated/0/GodotProjects/SpaceshipTest.touchon it. Android FUSE does not bump mtime under~/storage/shared, and Godot's import cache will silently keep serving the old version, making a correct fix look like it did nothing.bash tests/run_tests.sh <substring>; full suite with no argument. Tests areSceneTreescripts printingTEST_NAME: PASS/TEST_NAME: FAIL (reason)and callingquit(0|1)- copy the shape oftests/test_capture_points.gd.bash tests/screenshot.sh <res://Scene.tscn> <out.png> <frames> 720x1280. Use few frames (~15); the software rasterizer is slow.--headlessrenders nothing at all, so it cannot validate visuals.NeonDraw/NeonPiece; do not introduce a different visual idiom.*Definitionresources plus capability component scenes. Do not addif type == "..."branches.FileAccess, nouser://).GameStateis an in-memory autoload. Do not invent a save system.Traps that have already cost hours on this codebase
const X = preload(...)in a test file that is itself the-sentry point. It evaluates during that file's own early compilation, which can run before autoloads are registered; any script it pulls in that referencesGameStateinside a function body then fails to compile withIdentifier not found: GameState. Usevar X: Scriptplusload()inside_initialize()instead. See the header comment intests/test_capture_points.gd.[]to a typedArray[Resource]export from another script fails at runtime - the compiler only sees the target asResourceand cannot confirm the element type. Leave the declared default alone, or build the typed array inside the owning script.Fleet.spawn_ships(0, ...)does not empty the fleet - it falls back to one default interceptor by design. Clearfleet.shipsexplicitly if a test or preview needs genuinely zero ships.ship.positionwithout also settingship.move_targetletsShip._physics_processsteer it straight back toward the stale target within a few frames.SceneTreetest's own_process()clock under device load, even with--fixed-fps 60. Gate waits on the system's own state (a component's timer or flag), not onelapsed >= duration.queue_free()s itself (projectiles, shells) must not be re-read inside a per-frame wait condition - cache the scalar you need before the wait._screenshot_tool.gd's_initialize(), so their_ready()races the game scene's own_ready(). Do setup on the first_process()tick, andget_tree().paused = trueonce everything is placed if the scene must hold still.Implemented in
ed46cd7.A manually aimed instant beam, modelled closely on the gas bomber's press-drag-release aiming as instructed.
VesselLaserComponent.gd: per-ship cooldown plus the ready/cooling hull-fill visual, same shape asVesselGasBomberComponent. One deliberate difference from the bomber:damage/beam_range/cooldown_durationare resolved from the vessel's ownbase_stats/upgrades viaapply_stats()(the same patternVesselWeaponComponentuses), not hardcoded in the controller - the ticket's damage/range/cooldown ship-lab upgrades need something real to modify. Verified two laser ships give two shots (cooldown lives on the component, per ship) - the exact bug already fixed for bombers ineb79efd.LaserController.gd: same press-drag-release plumbing asGasBombController(aim-time camera zoom-out via the same fit/restore math,_next_ready_laser_ship()mirroring_next_ready_bomber()), adapted for an instant beam rather than a travelling, timed projectile - the shot lands the moment the touch releases, only a brief flash lingers for feedback. The beam is a straight segment from the firing ship's own position (not the fleet centroid - matches the bomber's identical discrepancy) out to the ship's ownbeam_range, damaging every node in groupenemieswithinBEAM_HALF_WIDTHof the segment, using the same perpendicular/along-segment math asEnemyBigLaserBeam's mirror-image (enemies -> player) case.LaserControllernode reused unmodified by bothMain.tscnandPlanetSurface.tscn(identical relative../Fleet/../Camera2D/../UI/LaserButtonpaths, matchingGasBombController), wired intoGameController's touch/drag first-refusal chain right after the bomb controller - works identically in space and on a landed surface with zero branching.LaserVesselDefinition.tres:capabilities = ["laser"],component_scenes = [VesselLaserComponent.tscn]only, its own narrow-dart hull and cyan glow, ship-labunlock_*fields, and 3 upgrades (LaserDamageUpgrade/LaserRangeUpgrade/LaserCooldownUpgrade, the last with aminimum_statsfloor so cooldown can't be upgraded to zero). Two new appendedIconButton.Iconentries (LASER_VESSELship glyph,LASER_SHOTaction-button glyph - verified their actual enum ints empirically rather than hand-counted, same as #57).VesselDefinitions, appended aftercarrier(pure append, no existing vessel's index moves). The crafter/hangar/ship-lab menus were already fully generic overVesselDefinitions.fleet()/.all()- only the per-vesselMain.tscnnodes are hand-placed, so this repacked those three rows to 5/5/6 columns and extendedBuildController's matching@onreadyarrays. No new signal handlers were needed anywhere.A genuine regression, same class as #57's:
test_vessel_composition.gd's hardcoded fleet-catalog size needed bumping again, 4 -> 5.A real, non-obvious trap surfaced and documented in
GameController.gd: a brand-newclass_namescript resolves fine as a static type when its own file is thegodot -sentry point (proven -test_laser_vessel.gdusesvar skill: LaserControllersuccessfully), but the identical type hint fails to parse ("Could not find type") when the script holding it is only reached indirectly - e.g.Main.tscnloaded from inside a_screenshot_tool.gdpreview, which is exactly how the required screenshot loads it. Fixed by typingGameController.laser_controllerasNodewith duck-typed calls instead of the strictLaserControllertype; noted in a comment so it doesn't cost the next brand-new controller class an hour of debugging.Tests:
tests/test_laser_vessel.gd(bash tests/run_tests.sh laser) covers the control appearing/disappearing with fleet composition, the beam hitting exactly the enemies on its line (withinbeam_half_width, within range) and nothing else, per-ship cooldown (two ships -> two shots -> then disabled), and camera zoom-out/restore.run_tests.sh vessel(5/5, after the fleet-catalog fix),gas_bomber/fleet/cargo/hangar/lab/build/skill/icon/enemy_combatregressions all pass. Full suite: 85/85.Screenshot taken and visually confirmed (720x1280): aim state with one laser ship ready (fully lit hull) and one rearming (partially filled), the beam-preview line drawn from the fleet to the drag target, and the
LaserButtonvisibly pressed/highlighted in the action cluster.