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 # 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. var vertical_offset: float = (abs(index_from_center) * weapon_data.projectile_vertical_spacing) / 2.0 var position: Vector2 = spawn_position + Vector2(bullet_horizontal_offset, +vertical_offset) # Angle (only when projectiles >= 3, division-by-zero guard) var bullet_angle: float = 0.0 if total_projectiles >= 3: bullet_angle = (index_from_center / center_index) * (weapon_data.spread_angle / 2.0) # 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) if group.bullet_count >= 3: var 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. var vertical_offset: float = (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, }