From 1cb8fc54e98940bd2c96e7ffdccc7b6ad5caf268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Wisniewski?= Date: Fri, 4 Sep 2026 22:34:29 +0200 Subject: [PATCH] Sharpen the gas bomb and the laser Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD --- controllers/LaserController.gd | 30 +++++++++++++++++++++-- tests/test_gas_bomber.gd | 6 +++++ tests/test_laser_vessel.gd | 37 +++++++++++++++++++++++++++++ vessels/LaserVesselDefinition.tres | 2 +- vessels/VesselGasBomberComponent.gd | 4 +++- vessels/VesselLaserComponent.gd | 2 +- 6 files changed, 76 insertions(+), 5 deletions(-) diff --git a/controllers/LaserController.gd b/controllers/LaserController.gd index 3470864..f017811 100644 --- a/controllers/LaserController.gd +++ b/controllers/LaserController.gd @@ -11,6 +11,9 @@ extends "res://controllers/AimedFleetAbilityController.gd" # while they aim it. const BEAM_EFFECT = preload("res://vfx/LaserBeamEffect.gd") +# #201: the beam borrows the fire stick's aim helper, so it is loaded here +# for its static assist rather than for anything a laser hull carries. +const WEAPON_COMPONENT = preload("res://vessels/VesselWeaponComponent.gd") const BEAM_COLOR := BEAM_EFFECT.BEAM_COLOR const TARGET_COLOR := Color(0.6, 0.95, 1.0) const BEAM_HALF_WIDTH := BEAM_EFFECT.BEAM_HALF_WIDTH @@ -51,7 +54,25 @@ func _aim_max_range() -> float: func _calculate_aim_target(drag: Vector2, origin: Vector2) -> Vector2: # A beam always reaches full range; dragging chooses its direction only. var direction: Vector2 = drag.normalized() if drag.length() > 0.001 else Vector2.UP - return origin + direction * _aim_max_range() + return _assisted_target(origin, origin + direction * _aim_max_range()) + + +# The mouse path (#80) names a world point directly instead of dragging one +# into existence. It has a real cursor, so it is assisted from that rather +# than from a virtual one at the end of the beam - otherwise the crosshair +# would be the only aim in the game the helper does not touch. +func fire_at(world_target: Vector2) -> bool: + return super(_assisted_target(_aim_origin(), world_target)) + + +# #201: ordinary fire's aim helper, applied to the beam. It answers with a +# PLACE, not a heading - the same thing the fire stick resolves and every +# hull then converges on - so a drag that ends near an enemy, or merely +# inside the assist cone, passes through that enemy instead of a few degrees +# past it. The beam still runs on to its full range from there. +func _assisted_target(origin: Vector2, point: Vector2) -> Vector2: + return WEAPON_COMPONENT.assisted_point(get_tree(), origin, point, + _aim_max_range()) func _consume_shot(component: Node) -> bool: @@ -62,7 +83,12 @@ func _perform_shot(ship: Node2D, component: Node) -> void: # The cooldown was already spent by _consume_shot() above, so this fires # the effect directly rather than going through fire_ability() and # spending it twice. - var direction: Vector2 = aim_target - _aim_origin() + # Aimed from the HULL, not from the fleet centre. aim_target is a place + # now that the assist can move it onto an enemy (#201), and ordinary fire + # converges on such a place by letting each hull find its own heading to + # it - a shared heading measured at the centroid would walk this beam off + # the very target the assist just picked, by the ship's formation offset. + var direction: Vector2 = aim_target - ship.global_position direction = direction.normalized() if direction.length() > 0.001 else Vector2.UP var effect: Node2D = BEAM_EFFECT.fire(get_tree().root, ship.global_position, direction, component.beam_range, int(component.damage)) diff --git a/tests/test_gas_bomber.gd b/tests/test_gas_bomber.gd index 7412779..6af6ee3 100644 --- a/tests/test_gas_bomber.gd +++ b/tests/test_gas_bomber.gd @@ -144,6 +144,12 @@ func _process(delta: float) -> bool: main._handle_touch(release) if not skill.projectile_active: return _fail_now("release did not throw a gas bomb") + # #201 doubled the blast. Read off the bomb actually in flight, so + # a constant that stops reaching the effect fails here rather than + # passing on the strength of its own declaration. + if int(skill.projectiles[0].damage) != 6: + return _fail_now("gas bomb carries %d damage, expected the doubled 6" \ + % int(skill.projectiles[0].damage)) elapsed = 0.0 stage = 1 1: diff --git a/tests/test_laser_vessel.gd b/tests/test_laser_vessel.gd index 3177010..5dd6239 100644 --- a/tests/test_laser_vessel.gd +++ b/tests/test_laser_vessel.gd @@ -99,6 +99,12 @@ func _process(delta: float) -> bool: return _fail_now("no laser ship is ready right after spawning") if absf(first_shooter.get_component("laser").beam_range - 2900.0) > 0.1: return _fail_now("laser ship beam range was not doubled to 2900") + # #201 tripled the beam's damage. Read off the COMPONENT rather + # than off the definition, so a stat that stops reaching the hull + # fails here instead of passing on the strength of its own file. + if first_shooter.get_component("laser").damage != 6: + return _fail_now("laser damage is %d, expected the tripled 6" \ + % first_shooter.get_component("laser").damage) var press := InputEventScreenTouch.new() press.index = 7 @@ -203,6 +209,37 @@ func _process(delta: float) -> bool: if absf(main.get_node("Camera2D").zoom.x - original_zoom) > 0.03: return _fail_now("camera did not restore its pre-aim zoom") print("camera zoom restored") + + # --- E. #201: the beam gets ordinary fire's aim helper ---------- + # Checked on the resolution itself rather than by firing again: + # both hulls are still cooling down, and what the ticket asks for + # is exactly that a drag resolves onto a nearby enemy. + for spent in on_line_enemies + [off_line_enemy, beyond_range_enemy]: + if is_instance_valid(spent): + spent.free() + var origin: Vector2 = skill._aim_origin() + var in_cone: Node = load("res://enemies/Enemy.tscn").instantiate() + in_cone.position = origin + Vector2(800.0, 0.0).rotated(deg_to_rad(20.0)) + main.add_child(in_cone) + in_cone.speed = 0.0 + # Nearer than the one in the cone, and deliberately outside it: + # the assist must not treat "closest" as "aimed at". + var out_of_cone: Node = load("res://enemies/Enemy.tscn").instantiate() + out_of_cone.position = origin + Vector2(600.0, 0.0).rotated(deg_to_rad(60.0)) + main.add_child(out_of_cone) + out_of_cone.speed = 0.0 + + var assisted: Vector2 = skill._calculate_aim_target(Vector2(300.0, 0.0), origin) + if assisted.distance_to(in_cone.global_position) > 1.0: + return _fail_now("a drag past an enemy inside the assist cone did not snap onto it") + print("aiming snapped onto the enemy inside the assist cone") + + in_cone.free() + var unassisted: Vector2 = skill._calculate_aim_target(Vector2(300.0, 0.0), origin) + if unassisted.distance_to(origin + Vector2(2900.0, 0.0)) > 1.0: + return _fail_now("the aim bent toward an enemy outside the assist cone") + print("an enemy outside the cone left the aim where it was pointed") + print("TEST_LASER_VESSEL: PASS") quit(0) return true diff --git a/vessels/LaserVesselDefinition.tres b/vessels/LaserVesselDefinition.tres index 820b6cb..f76690d 100644 --- a/vessels/LaserVesselDefinition.tres +++ b/vessels/LaserVesselDefinition.tres @@ -18,7 +18,7 @@ hull_points = PackedVector2Array(0, -26, -9, 14, -4, 10, 4, 10, 9, 14) fleet_eligible = true capabilities = PackedStringArray("laser") component_scenes = Array[PackedScene]([ExtResource("2")]) -base_stats = {"max_speed": 660.0, "acceleration": 1890.0, "delivery_speed": 180.0, "damage": 2.0, "beam_range": 2900.0, "beam_cooldown": 4.0} +base_stats = {"max_speed": 660.0, "acceleration": 1890.0, "delivery_speed": 180.0, "damage": 6.0, "beam_range": 2900.0, "beam_cooldown": 4.0} minimum_stats = {"beam_cooldown": 1.5} upgrades = Array[Resource]([ExtResource("3"), ExtResource("4"), ExtResource("5")]) unlock_id = "unlock_laser" diff --git a/vessels/VesselGasBomberComponent.gd b/vessels/VesselGasBomberComponent.gd index d9a0ede..1d02f3c 100644 --- a/vessels/VesselGasBomberComponent.gd +++ b/vessels/VesselGasBomberComponent.gd @@ -14,7 +14,9 @@ func _init() -> void: const BOMB_EFFECT = preload("res://vfx/GasBombEffect.gd") const GAS_PER_SHOT: int = 5 const MAX_RANGE: float = 950.0 -const BLAST_DAMAGE: int = 3 +# #201: doubled. The bomb is the fleet's only area weapon and one slow, +# expensive throw was worth less than a couple of seconds of ordinary fire. +const BLAST_DAMAGE: int = 6 const SHOT_COOLDOWN: float = 5.0 diff --git a/vessels/VesselLaserComponent.gd b/vessels/VesselLaserComponent.gd index 553357d..a34541b 100644 --- a/vessels/VesselLaserComponent.gd +++ b/vessels/VesselLaserComponent.gd @@ -2,7 +2,7 @@ class_name VesselLaserComponent extends "res://vessels/VesselCooldownFillComponent.gd" var capability_id := "laser" -var damage: int = 2 +var damage: int = 6 var beam_range: float = 2900.0 const READY_COLOR := Color(0.3, 0.9, 1.0)