#89: the default gun bends onto a target near the aimed heading

Aiming with a thumb on a stick is never exact, and at 950 units a couple of
degrees is a long miss. A shot fired roughly toward an enemy now goes at it.

28 degrees of cone, chosen so that pointing at something hits it while the
shot still visibly goes where the player pointed. Past about 45 the gun starts
picking its own targets, which is the behaviour #80 deliberately removed.

Enemies only, never asteroids. Snapping onto a rock while the player is trying
to hit the thing shooting at them would be the assist working against them,
and rocks are large and stationary enough to hit on purpose.

The aim guide is drawn through the SAME helper the gun fires through, so the
dotted line bends with the shot. A guide showing the raw stick heading while
the bolt took an assisted one would be the same class of mismatch #86 removed
between the guide's length and a bolt's actual reach - and this one would have
been harder to notice, because the line would look right most of the time.

Range 620 -> 950, the other half of the ticket.

Also swept the project root again: Godot keeps resurrecting pre-#84 copies of
moved files there from its own cache. None this time.

Suite 120/120.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Loïc Wisniewski 2026-08-22 03:19:52 +02:00
commit 5f3615026d
5 changed files with 204 additions and 6 deletions

View file

@ -17,5 +17,5 @@ glow_color = Color(0.25, 0.85, 1, 1)
fleet_eligible = true
capabilities = PackedStringArray("weapon", "cargo")
component_scenes = Array[PackedScene]([ExtResource("2"), ExtResource("7")])
base_stats = {"damage": 1.0, "fire_cooldown": 0.5, "fire_range": 620.0, "max_speed": 690.0, "acceleration": 2025.0, "delivery_speed": 180.0, "cargo_capacity": 20.0}
base_stats = {"damage": 1.0, "fire_cooldown": 0.5, "fire_range": 950.0, "max_speed": 690.0, "acceleration": 2025.0, "delivery_speed": 180.0, "cargo_capacity": 20.0}
upgrades = Array[Resource]([ExtResource("3"), ExtResource("4"), ExtResource("5")])

View file

@ -16,7 +16,7 @@ fleet_eligible = true
capabilities = PackedStringArray("shield")
component_scenes = Array[PackedScene]([ExtResource("3")])
hull_points = PackedVector2Array(0, -24, 20.7846, -12, 20.7846, 12, 0, 24, -20.7846, 12, -20.7846, -12)
base_stats = {"damage": 1.0, "fire_cooldown": 0.5, "fire_range": 620.0, "max_speed": 690.0, "acceleration": 2025.0, "shield_charges": 1.0, "shield_recharge": 6.0, "delivery_speed": 180.0}
base_stats = {"damage": 1.0, "fire_cooldown": 0.5, "fire_range": 950.0, "max_speed": 690.0, "acceleration": 2025.0, "shield_charges": 1.0, "shield_recharge": 6.0, "delivery_speed": 180.0}
minimum_stats = {"shield_recharge": 2.0}
upgrades = Array[Resource]([ExtResource("4"), ExtResource("5")])
unlock_id = "unlock_shield"

View file

@ -33,6 +33,50 @@ func apply_stats() -> void:
projectile_damage = int(state.vessel_stat(ship.vessel_type, "damage"))
# Forgejo #89: how far off the aimed heading a target may sit and still be
# picked up. 28 degrees is wide enough that pointing roughly at something hits
# it, and narrow enough that the shot still goes where the player pointed -
# past about 45 the gun starts choosing targets on its own, which is the
# behaviour #80 deliberately took away.
const ASSIST_CONE_DEGREES: float = 28.0
# The heading a shot fired from `origin` along `direction` should actually
# take: straight, unless an enemy sits inside the assist cone and within
# reach, in which case it points at the nearest one.
#
# Static, and used by BOTH the gun and the aim guide (see
# FireStickController._draw). If the guide drew the raw stick heading while
# the shot took an assisted one, the line would be telling the player
# something the gun does not do - the same class of mismatch #86 removed
# between the guide's length and a bolt's actual reach.
#
# Enemies only, never asteroids. Snapping onto a rock while the player is
# trying to hit the thing shooting at them would be the assist working
# against them; rocks are large, stationary enough, and easy to hit on
# purpose.
static func assisted_heading(tree: SceneTree, origin: Vector2, direction: Vector2,
reach: float) -> Vector2:
var heading: Vector2 = direction.normalized()
if heading == Vector2.ZERO or tree == null:
return heading
var cone: float = cos(deg_to_rad(ASSIST_CONE_DEGREES))
var nearest: Vector2 = Vector2.ZERO
var nearest_distance: float = INF
for enemy in tree.get_nodes_in_group("enemies"):
if not is_instance_valid(enemy) or not (enemy is Node2D):
continue
var to_enemy: Vector2 = (enemy as Node2D).global_position - origin
var distance: float = to_enemy.length()
if distance > reach or distance < 0.001 or distance >= nearest_distance:
continue
if to_enemy.normalized().dot(heading) < cone:
continue
nearest_distance = distance
nearest = to_enemy
return heading if nearest == Vector2.ZERO else nearest.normalized()
# Called by FireStickController. A zero vector stops the gun.
func set_fire_direction(direction: Vector2) -> void:
fire_direction = direction if direction.length() > 0.001 else Vector2.ZERO
@ -59,9 +103,10 @@ func tick(delta: float) -> void:
ship.get_tree().root.add_child(projectile)
# Aimed by DIRECTION, not at a target: launch() takes a displacement, so
# the gun's own range decides how far the shot is sent.
projectile.launch(fire_direction.normalized() * fire_range)
var heading: Vector2 = assisted_heading(ship.get_tree(), ship.global_position,
fire_direction, fire_range)
projectile.launch(heading * fire_range)
# #87: sparks off the hull that fired, so a shot is visibly attached to
# the vessel it came from.
MUZZLE_SPARK.spawn(ship.get_tree().root, ship.global_position,
fire_direction.normalized())
MUZZLE_SPARK.spawn(ship.get_tree().root, ship.global_position, heading)
fire_timer = fire_cooldown