class_name GasBombController extends "res://controllers/AimedFleetAbilityController.gd" const BOMB_COLOR := Color(0.96, 0.22, 0.72) const TARGET_COLOR := Color(0.78, 0.32, 1.0) const MAX_RANGE := 950.0 const AOE_RADIUS := 180.0 const DRAG_RADIUS := 260.0 const PROJECTILE_SPEED := 720.0 const DAMAGE := 3 const COOLDOWN := 5.0 const GAS_PER_SHOT: int = 5 var projectiles: Array = [] var explosions: Array = [] var last_hit_count: int = 0 var projectile_active: bool: get: return not projectiles.is_empty() var explosion_time: float: get: var longest: float = 0.0 for explosion in explosions: longest = maxf(longest, float(explosion.time)) return longest func _button_path() -> NodePath: return "../UI/GasBombButton" func _status_path() -> NodePath: return "../UI/GasBombStatus" func _component_id() -> String: return "gas_bomber" func _ammo_resource() -> String: return GameState.RESOURCE_GAS func _ammo_cost() -> int: return GAS_PER_SHOT func _aim_padding() -> float: return AOE_RADIUS func _aim_max_range() -> float: return MAX_RANGE func _has_effect_visuals() -> bool: return not projectiles.is_empty() or not explosions.is_empty() func _calculate_aim_target(drag: Vector2, origin: Vector2) -> Vector2: if drag.length() < 0.001: return origin var distance: float = minf(drag.length() / DRAG_RADIUS, 1.0) * MAX_RANGE return origin + drag.normalized() * distance func _consume_shot(component: Node) -> bool: return component.consume_shot(COOLDOWN) func _perform_shot(ship: Node2D, _component: Node) -> void: projectiles.append({ "position": ship.global_position, "target": aim_target, "trail": PackedVector2Array([ship.global_position]), }) func _next_ready_bomber() -> Node2D: return _next_ready_ship() func _advance_effects(delta: float) -> void: for i in range(projectiles.size() - 1, -1, -1): var projectile: Dictionary = projectiles[i] var distance: float = (projectile.position as Vector2).distance_to(projectile.target) if distance <= PROJECTILE_SPEED * delta: projectile.position = projectile.target _explode(projectile.target) projectiles.remove_at(i) else: projectile.position = (projectile.position as Vector2).move_toward( projectile.target, PROJECTILE_SPEED * delta) var trail: PackedVector2Array = projectile.trail trail.append(projectile.position) while trail.size() > 12: trail.remove_at(0) projectile.trail = trail for i in range(explosions.size() - 1, -1, -1): var explosion: Dictionary = explosions[i] explosion.time = maxf(float(explosion.time) - delta, 0.0) if float(explosion.time) <= 0.0: explosions.remove_at(i) func _explode(at: Vector2) -> void: explosions.append({"position": at, "time": 0.8}) last_hit_count = 0 # Asteroids too - a blast that clears everything around it but leaves the # rocks untouched would need explaining, and they take damage the same way. for enemy in get_tree().get_nodes_in_group("enemies") \ + get_tree().get_nodes_in_group("asteroids"): if is_instance_valid(enemy) and enemy is Node2D \ and enemy.global_position.distance_to(at) <= AOE_RADIUS: enemy.take_damage(DAMAGE) last_hit_count += 1 func _draw() -> void: if aiming: NeonDraw.draw_glowing_line(self, _aim_origin(), aim_target, TARGET_COLOR, 0.7, 0.8) _draw_ring(aim_target, AOE_RADIUS, TARGET_COLOR, 0.8) _draw_ring(aim_target, 18.0, BOMB_COLOR, 1.0) for projectile in projectiles: var trail: PackedVector2Array = projectile.trail if trail.size() >= 2: for i in range(1, trail.size()): var alpha: float = float(i) / trail.size() draw_line(trail[i - 1], trail[i], Color(BOMB_COLOR, alpha * 0.65), 4.0 + alpha * 4.0, true) draw_circle(projectile.position, 12.0, Color(BOMB_COLOR, 0.35)) NeonDraw.draw_glowing_circle(self, projectile.position, 7.0, BOMB_COLOR, 1.0) for explosion in explosions: var progress: float = 1.0 - float(explosion.time) / 0.8 var radius: float = lerpf(24.0, AOE_RADIUS, minf(progress * 1.5, 1.0)) draw_circle(explosion.position, radius, Color(BOMB_COLOR, (1.0 - progress) * 0.16)) _draw_ring(explosion.position, radius, BOMB_COLOR, 1.0 - progress) for i in range(7): var angle: float = TAU * float(i) / 7.0 + progress var cloud_at: Vector2 = explosion.position \ + Vector2(cos(angle), sin(angle)) * radius * 0.55 draw_circle(cloud_at, 18.0 + 14.0 * progress, Color(BOMB_COLOR, (1.0 - progress) * 0.22)) func _draw_ring(center: Vector2, radius: float, color: Color, alpha: float) -> void: draw_arc(center, radius, 0.0, TAU, 64, Color(color, 0.16 * alpha), 16.0, true) draw_arc(center, radius, 0.0, TAU, 64, Color(color, 0.5 * alpha), 6.0, true) draw_arc(center, radius, 0.0, TAU, 64, Color(1.0, 0.86, 0.96, 0.9 * alpha), 2.0, true)