class_name GasBombEffect extends Node2D # The gas bomb itself: the flight, the blast and the picture of both, with # nothing in it about who threw it. # # Lifted out of GasBombController for the same reason LaserBeamEffect was # lifted out of LaserController - the shot was something only the FLEET could # do, and a garrisoned bomber needs the same shot from a different trigger # and a different magazine. Added to the tree root, self-animating, # self-freeing, and it outlives the vessel that threw it. signal struck(hit_count: int) const BOMB_COLOR := Color(0.96, 0.22, 0.72) const AOE_RADIUS := 180.0 const PROJECTILE_SPEED := 720.0 const EXPLOSION_DURATION := 0.8 var target: Vector2 = Vector2.ZERO var damage: int = 3 var trail: PackedVector2Array = PackedVector2Array() var exploded: bool = false var explosion_time: float = 0.0 var hit_count: int = 0 static func launch(world: Node, origin: Vector2, at: Vector2, blast_damage: int = 3) -> GasBombEffect: var effect := GasBombEffect.new() effect.position = origin effect.target = at effect.damage = blast_damage effect.trail = PackedVector2Array([origin]) world.add_child(effect) return effect func in_flight() -> bool: return not exploded func _process(delta: float) -> void: if not exploded: _advance_flight(delta) queue_redraw() return explosion_time = maxf(explosion_time - delta, 0.0) queue_redraw() if explosion_time <= 0.0: queue_free() func _advance_flight(delta: float) -> void: var distance: float = position.distance_to(target) if distance <= PROJECTILE_SPEED * delta: position = target _explode() return position = position.move_toward(target, PROJECTILE_SPEED * delta) trail.append(position) while trail.size() > 12: trail.remove_at(0) func _explode() -> void: exploded = true explosion_time = EXPLOSION_DURATION 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(target) <= AOE_RADIUS: enemy.take_damage(damage) hit_count += 1 struck.emit(hit_count) func _draw() -> void: # Drawn in this node's own space while it flies (it moves), and around the # blast point once it lands (it does not). if not exploded: if trail.size() >= 2: for i in range(1, trail.size()): var alpha: float = float(i) / trail.size() draw_line(trail[i - 1] - position, trail[i] - position, Color(BOMB_COLOR, alpha * 0.65), 4.0 + alpha * 4.0, true) draw_circle(Vector2.ZERO, 12.0, Color(BOMB_COLOR, 0.35)) NeonDraw.draw_glowing_circle(self, Vector2.ZERO, 7.0, BOMB_COLOR, 1.0) return var progress: float = 1.0 - explosion_time / EXPLOSION_DURATION var radius: float = lerpf(24.0, AOE_RADIUS, minf(progress * 1.5, 1.0)) draw_circle(Vector2.ZERO, radius, Color(BOMB_COLOR, (1.0 - progress) * 0.16)) draw_ring(Vector2.ZERO, radius, BOMB_COLOR, 1.0 - progress) for i in range(7): var angle: float = TAU * float(i) / 7.0 + progress var cloud_at: Vector2 = 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)