129 lines
6.1 KiB
GDScript
129 lines
6.1 KiB
GDScript
class_name WeaponFireHelper extends Node
|
|
|
|
|
|
## Compute bullet properties for a standard shot.
|
|
## Returns a Dictionary with: position, angle, fire_time, time_offset, index_from_center, animation_offset
|
|
func compute_standard_props(weapon_data: WeaponShot, projectile_index: int, spawn_position: Vector2, current_time: float) -> Dictionary:
|
|
var total_projectiles: int = weapon_data.projectiles
|
|
var center_index: float = (total_projectiles - 1) / 2.0
|
|
|
|
var index_from_center: float = projectile_index - center_index
|
|
|
|
# Horizontal offset (symmetrical: left=-ve, right=+ve)
|
|
var bullet_horizontal_offset: float = index_from_center * weapon_data.horizontal_offset
|
|
|
|
# Timing: center-first, pairs outward
|
|
var time_offset: float = abs(index_from_center) * weapon_data.stagger_offset
|
|
|
|
# Angle (only when projectiles >= 3, division-by-zero guard)
|
|
# Moved before vertical offset to support compensation
|
|
var bullet_angle: float = 0.0
|
|
if total_projectiles >= 3:
|
|
bullet_angle = (index_from_center / center_index) * (weapon_data.spread_angle / 2.0)
|
|
|
|
# Vertical spacing creates the ^ (inverted-V) geometry: outer projectiles fire lower
|
|
# (higher Y values, further from the player) than the center projectile, which sits
|
|
# higher on screen (lower Y values). This creates a ^ pattern relative to the origin.
|
|
# When compensate_spawn_geometry is enabled and spread_angle > 0, compute vertical
|
|
# offsets to align the geometric spread with velocity vectors.
|
|
var vertical_offset: float
|
|
if weapon_data.compensate_spawn_geometry and weapon_data.spread_angle > 0 and total_projectiles >= 3:
|
|
var velocity_angle_rad: float = abs(bullet_angle) * PI / 180.0
|
|
if index_from_center == 0:
|
|
# Center bullet in compensated mode: use full projectile_vertical_spacing
|
|
# so the V-shape stays visually smooth (center = 35, outer ≈ 75)
|
|
# instead of a broken gap (center = 8.75, outer ≈ 75).
|
|
vertical_offset = weapon_data.projectile_vertical_spacing
|
|
elif velocity_angle_rad > 0.1: # Guard against division by zero for very small angles
|
|
vertical_offset = abs(bullet_horizontal_offset) / tan(velocity_angle_rad)
|
|
else:
|
|
# Very small angle: fall back to full spacing
|
|
vertical_offset = weapon_data.projectile_vertical_spacing
|
|
else:
|
|
vertical_offset = (abs(index_from_center) * weapon_data.projectile_vertical_spacing) / 2.0
|
|
var position: Vector2 = spawn_position + Vector2(bullet_horizontal_offset, +vertical_offset)
|
|
|
|
# Fire time
|
|
var fire_time: float = current_time + time_offset
|
|
|
|
# Animation offset (if staggered animation is enabled)
|
|
var animation_offset: float = index_from_center * weapon_data.stagger_offset
|
|
|
|
return {
|
|
"position": position,
|
|
"angle": bullet_angle,
|
|
"fire_time": fire_time,
|
|
"time_offset": time_offset,
|
|
"index_from_center": index_from_center,
|
|
"animation_offset": animation_offset,
|
|
}
|
|
|
|
|
|
## Compute bullet properties for a grouped shot, with optional mirroring.
|
|
## Returns a Dictionary with: position, angle, fire_time, time_offset, index_from_center, animation_offset
|
|
func compute_grouped_props(group: WeaponProjectileGroup, weapon_data: WeaponShot,
|
|
bullet_index: int, spawn_position: Vector2, current_time: float,
|
|
mirror: bool = false) -> Dictionary:
|
|
var center_index: float = (group.bullet_count - 1) / 2.0
|
|
var index_from_center: float = bullet_index - center_index
|
|
|
|
# Horizontal offset: group offset + within-group spread
|
|
var bullet_horizontal_offset: float = group.horizontal_offset + index_from_center * group.horizontal_spacing
|
|
if mirror:
|
|
bullet_horizontal_offset = -bullet_horizontal_offset
|
|
|
|
# Base angle: group angle (negated when mirrored) + within-group spread
|
|
var bullet_angle: float = group.angle_offset
|
|
if mirror:
|
|
bullet_angle = -group.angle_offset
|
|
|
|
# Symmetrical spread (guard against < 3 bullets)
|
|
var spread_contribution: float = 0.0
|
|
if group.bullet_count >= 3:
|
|
spread_contribution = (index_from_center / center_index) * (group.spread_angle / 2.0)
|
|
if mirror:
|
|
bullet_angle -= spread_contribution
|
|
else:
|
|
bullet_angle += spread_contribution
|
|
|
|
# Vertical spacing creates the ^ (inverted-V) geometry: outer projectiles fire lower
|
|
# (higher Y values, further from the player) than the center projectile, which sits
|
|
# higher on screen (lower Y values). This creates a ^ pattern relative to the origin.
|
|
# When compensate_spawn_geometry is enabled and group.spread_angle > 0, compute
|
|
# vertical offsets to preserve the within-group V-shape independently of
|
|
# angle_offset, which handles global tilt.
|
|
#
|
|
# angle_offset tilts the entire group — mixing it into the compensation formula
|
|
# breaks the V-shape because the formula assumes velocity vectors converge at the
|
|
# origin, but with angle_offset > 0 they fan out from an offset line instead.
|
|
# Solution: calculate compensation using only within-group parameters.
|
|
var vertical_offset: float
|
|
if weapon_data.compensate_spawn_geometry and group.spread_angle > 0 and group.bullet_count >= 3 and index_from_center != 0:
|
|
var within_group_angle: float = abs(spread_contribution) # exclude angle_offset
|
|
var within_group_angle_rad: float = within_group_angle * PI / 180.0
|
|
var within_group_horizontal: float = abs(index_from_center * group.horizontal_spacing) # exclude group.horizontal_offset
|
|
if within_group_angle_rad > 0.1: # Guard against division by zero for very small angles
|
|
vertical_offset = within_group_horizontal / tan(within_group_angle_rad)
|
|
else:
|
|
vertical_offset = (abs(index_from_center) * weapon_data.projectile_vertical_spacing) / 2.0
|
|
else:
|
|
vertical_offset = (abs(index_from_center) * weapon_data.projectile_vertical_spacing) / 2.0
|
|
var position: Vector2 = spawn_position + Vector2(bullet_horizontal_offset, +vertical_offset)
|
|
|
|
# Timing: group delay + within-group stagger
|
|
var time_offset: float = group.group_delay + abs(index_from_center) * group.stagger_offset
|
|
|
|
# Fire time
|
|
var fire_time: float = current_time + time_offset
|
|
|
|
# Animation offset (if staggered animation is enabled)
|
|
var animation_offset: float = index_from_center * weapon_data.stagger_offset
|
|
|
|
return {
|
|
"position": position,
|
|
"angle": bullet_angle,
|
|
"fire_time": fire_time,
|
|
"time_offset": time_offset,
|
|
"index_from_center": index_from_center,
|
|
"animation_offset": animation_offset,
|
|
}
|