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>
112 lines
4.7 KiB
GDScript
112 lines
4.7 KiB
GDScript
class_name VesselWeaponComponent
|
|
extends Node
|
|
|
|
# The fleet's gun. It does NOT pick its own targets: the player aims it with
|
|
# the fire stick, and it shoots along whatever heading that stick reports for
|
|
# as long as it is held.
|
|
#
|
|
# It used to seek the nearest enemy in range and fire on its own, which meant
|
|
# combat happened whether or not the player did anything - there was nothing
|
|
# to aim, and nothing to decide. Firing is now an action like any other, and
|
|
# the right-hand stick is what performs it.
|
|
#
|
|
# `fire_direction` is set every frame by FireStickController from the stick's
|
|
# own reading, and cleared the moment it is released. Zero means "not firing",
|
|
# which is the resting state.
|
|
|
|
var capability_id: String = "weapon"
|
|
var fire_range: float
|
|
var fire_cooldown: float
|
|
var projectile_damage: int
|
|
var fire_timer: float = 0.0
|
|
var fire_direction: Vector2 = Vector2.ZERO
|
|
var projectile_scene: PackedScene = preload("res://vessels/Projectile.tscn")
|
|
# By path, not by class name - see the note at the top of MuzzleSparkVFX.
|
|
const MUZZLE_SPARK = preload("res://vfx/MuzzleSparkVFX.gd")
|
|
|
|
|
|
func apply_stats() -> void:
|
|
var ship: Node = get_parent()
|
|
var state: Node = ship.get_tree().root.get_node("GameState")
|
|
fire_range = state.vessel_stat(ship.vessel_type, "fire_range")
|
|
fire_cooldown = state.vessel_stat(ship.vessel_type, "fire_cooldown")
|
|
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
|
|
|
|
|
|
func is_firing() -> bool:
|
|
return fire_direction != Vector2.ZERO
|
|
|
|
|
|
func tick(delta: float) -> void:
|
|
# The cooldown runs down whether or not the trigger is held, so tapping
|
|
# the stick in a new direction is not punished by a timer that only
|
|
# started when the stick moved.
|
|
fire_timer -= delta
|
|
if fire_direction == Vector2.ZERO or fire_timer > 0.0:
|
|
return
|
|
var ship: Node2D = get_parent()
|
|
var projectile: Node2D = projectile_scene.instantiate()
|
|
projectile.position = ship.position
|
|
projectile.damage = projectile_damage
|
|
# The bolt stops where the gun's range ends, and the aim guide draws that
|
|
# same range - one number, one meaning.
|
|
projectile.set_range(fire_range)
|
|
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.
|
|
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, heading)
|
|
fire_timer = fire_cooldown
|