Randoloot/scripts/game/combat_drone.gd
2026-10-16 23:36:49 +02:00

117 lines
5.3 KiB
GDScript

extends Node2D
const PROJECTILE := preload("res://scenes/game/weapon_projectile.tscn")
const FIRE_INTERVAL := 0.25
const HULL_TEXTURE := preload("res://assets/Ships/combat_drone.png")
var mother: CharacterBody2D
var formation_index := 0
var damage_per_shot := 1.0
var remaining := 10.0
var _fire_timer := 0.0
var _elapsed := 0.0
var _flash := 0.0
var velocity := Vector2.ZERO
var _turn_velocity := 0.0
const FORMATION := [Vector2(0, -110), Vector2(-35, -78), Vector2(35, -78)]
func _ready() -> void:
add_to_group("player_combat_drones")
z_index = 4
scale = Vector2.ONE * 0.5
rotation = mother.rotation
velocity = mother.velocity
texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
_fire_timer = formation_index * FIRE_INTERVAL / 3.0
queue_redraw()
func _physics_process(delta: float) -> void:
remaining -= delta
if remaining <= 0.0 or not is_instance_valid(mother) or bool(mother.get("_is_destroyed")) or bool(mother.get("is_docked")):
queue_free()
return
_elapsed += delta
_flash = maxf(0.0, _flash - delta)
_fire_timer = maxf(0.0, _fire_timer - delta)
var target := _find_target()
var destination := formation_position()
var escort_velocity := mother.velocity
if target != null:
var anchor := mother.global_position.lerp(target.global_position, 0.45)
destination = anchor + Vector2.from_angle(TAU * formation_index / 3.0 + _elapsed * 0.7) * 85.0
escort_velocity *= 0.55
else:
# Rotating formation slots have tangential velocity as the mother turns.
var offset := destination - mother.global_position
escort_velocity += Vector2(-offset.y, offset.x) * float(mother.get("angular_velocity"))
var error := destination - global_position
var correction := (error * 3.0).limit_length(260.0)
var desired_velocity := (escort_velocity + correction).limit_length(maxf(360.0, mother.velocity.length() + 200.0))
velocity = velocity.move_toward(desired_velocity, maxf(650.0, mother.velocity.length()) * delta)
global_position += velocity * delta
var desired_rotation := mother.rotation
if target != null:
var aim := target.global_position - global_position
desired_rotation = aim.angle() + PI * 0.5
if _fire_timer <= 0.0 and aim.length() <= 650.0 and _clear_shot(target):
_fire(target)
_fire_timer = FIRE_INTERVAL
elif error.length() > 55.0 and velocity.length() > 30.0:
desired_rotation = velocity.angle() + PI * 0.5
var angle_error := angle_difference(rotation, desired_rotation)
_turn_velocity = move_toward(_turn_velocity, clampf(angle_error * 6.0, -4.5, 4.5), 14.0 * delta)
var turn := _turn_velocity * delta
if absf(turn) >= absf(angle_error) and signf(turn) == signf(angle_error):
rotation = desired_rotation
_turn_velocity = 0.0
else:
rotation += turn
queue_redraw()
func formation_position() -> Vector2:
return mother.global_position + FORMATION[clampi(formation_index, 0, 2)].rotated(mother.rotation)
func _find_target() -> Node2D:
var closest: Node2D
var distance := 800.0
for enemy in get_tree().get_nodes_in_group("enemies"):
if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0:
continue
var candidate := global_position.distance_to(enemy.global_position)
if candidate < distance and mother.global_position.distance_to(enemy.global_position) < 1000.0:
distance = candidate
closest = enemy
return closest
func _clear_shot(target: Node2D) -> bool:
var query := PhysicsRayQueryParameters2D.create(global_position, target.global_position, 13)
var hit := get_world_2d().direct_space_state.intersect_ray(query)
return hit.is_empty() or hit.get("collider") == target
func _fire(target: Node2D) -> void:
var direction := (target.global_position - global_position).normalized()
var bullet := PROJECTILE.instantiate()
bullet.call("configure", global_position + direction * 9.0, direction, ItemDefinition.WeaponKind.MACHINE_GUN, 1100.0, damage_per_shot, 650.0)
get_parent().add_child(bullet)
_flash = 0.06
mother.get_node("ExtensionSystems").call("break_cloak_on_fire")
func _draw() -> void:
modulate.a = minf(clampf(_elapsed / 0.2, 0.0, 1.0), clampf(remaining / 0.6, 0.0, 1.0))
var flicker := sin(_elapsed * 33.0 + formation_index * 2.1)
for x in [-5.5, 5.5]:
var exhaust := Vector2(x, 19.5)
draw_circle(exhaust + Vector2(0, 5), 11.0 + flicker, Color(0.08, 0.65, 1.0, 0.08))
draw_circle(exhaust + Vector2(0, 3), 6.0, Color(0.12, 0.8, 1.0, 0.18))
draw_colored_polygon(PackedVector2Array([exhaust + Vector2(-3, 0), exhaust + Vector2(0, 15 + flicker * 2), exhaust + Vector2(3, 0)]), Color(0.15, 0.72, 1.0, 0.65))
draw_colored_polygon(PackedVector2Array([exhaust + Vector2(-1.4, 0), exhaust + Vector2(0, 8 + flicker), exhaust + Vector2(1.4, 0)]), Color("c8fbff"))
var hull_size := HULL_TEXTURE.get_size() * (60.0 / HULL_TEXTURE.get_height())
var hull_rect := Rect2(-hull_size * 0.5, hull_size)
draw_texture_rect(HULL_TEXTURE, Rect2(hull_rect.position + Vector2(3, 5), hull_size), false, Color(0, 0, 0, 0.4))
draw_texture_rect(HULL_TEXTURE, hull_rect, false)
if _flash > 0.0:
for x in [-5.5, 5.5]:
var muzzle := Vector2(x, -20)
draw_circle(muzzle, 8.0, Color(1.0, 0.6, 0.18, 0.15))
draw_colored_polygon(PackedVector2Array([muzzle + Vector2(-3, 0), muzzle + Vector2(-1.5, -4), muzzle + Vector2(0, -12), muzzle + Vector2(1.5, -4), muzzle + Vector2(3, 0)]), Color("ffbd68"))
draw_line(muzzle, muzzle + Vector2(0, -7), Color("fff2cb"), 2.0, true)