extends Area3D class_name Alien const DEATH_SCENE : PackedScene = preload("res://entities/alien/alien_death_effect/alien_death_effect.tscn") as PackedScene const DEATH_SCENE_TIME : float = 10. const COLLISION_HEIGHT = 5 const COLLISION_RADIUS = 1.5 const NEIGHBOR_DETECTION_MARGIN = 0.5 const ATTACK_DISTANCE = 6. const NEIGHBOR_ROTATION = 0.5 const NEIGHBOR_BUMP_FORCE = 4. * AlienHordeManager.CALCULATE_SCATTER_FREQUENCY const NEIGHBOR_BLOCKED_SPEED_FACTOR = 0.5 const DIRECTION_DRIFT_RANGE = PI/8 const DAMAGE_EFFECT_DURATION : float = 0.5 const DEFAULT_HP : float = 20. const DEFAULT_DAMAGE_RATE : float = 1. signal died enum State {RUNNING, ATTACKING, DEAD} @export var size : float = 1.0 @export var speed : float = 10. @export var turn_speed : float = 20. @onready var damage_rate : float = size * DEFAULT_DAMAGE_RATE @onready var hp : float = size * DEFAULT_HP var state : State = State.RUNNING var damage_effect_last : float = 0. var id : int = 0 var target : Node3D @onready var collision_shape : CollisionShape3D = generate_collision_shape() func _ready(): monitoring = false func generate_collision_shape() -> CollisionShape3D: var new_collision_shape : CollisionShape3D= CollisionShape3D.new() var cylinder_shape : CylinderShape3D = CylinderShape3D.new() cylinder_shape.height = COLLISION_HEIGHT cylinder_shape.radius = COLLISION_RADIUS new_collision_shape.shape = cylinder_shape new_collision_shape.position = Vector3(0,COLLISION_HEIGHT/2.,0) add_child(new_collision_shape) return new_collision_shape func move( delta: float, calculate_direction: bool, calculate_scatter : bool ): var target_angle = rotation.y var current_speed = speed if calculate_direction and target: target_angle = calculate_target_angle() if calculate_scatter: var collision_right = has_neighbor(1) var collision_left = has_neighbor(-1) if collision_left and not collision_right: target_angle += - NEIGHBOR_ROTATION position += Vector3.RIGHT.rotated(Vector3.UP, rotation.y) * NEIGHBOR_BUMP_FORCE * delta elif collision_right and not collision_left: target_angle += NEIGHBOR_ROTATION position += Vector3.LEFT.rotated(Vector3.UP, rotation.y) * NEIGHBOR_BUMP_FORCE * delta elif collision_left and collision_right: current_speed = speed * get_random().randf_range(0., NEIGHBOR_BLOCKED_SPEED_FACTOR) rotation.y = lerp_angle( rotation.y, target_angle, min(1, delta*turn_speed) ) position += Vector3.FORWARD.rotated(Vector3.UP, rotation.y) * current_speed * delta func calculate_target_angle() -> float: var target_angle : float = ( Vector2(global_position.z,global_position.x) - Vector2(target.global_position.z,target.global_position.x) ).normalized().angle() # Random drift in direction target_angle += get_random().randf_range(-DIRECTION_DRIFT_RANGE, DIRECTION_DRIFT_RANGE) return target_angle func has_neighbor(direction = 1) -> bool: var space_state = get_world_3d().direct_space_state var query = PhysicsRayQueryParameters3D.create( global_position, global_position + Vector3( direction * (COLLISION_RADIUS + NEIGHBOR_DETECTION_MARGIN), 0,0 ).rotated(Vector3.UP, rotation.y) ) query.collide_with_areas = true query.exclude.append(get_rid()) var result = space_state.intersect_ray(query) return result != {} func attack_target(delta : float): if target and target.has_method("hit"): target.hit(delta * damage_rate) func hit(damage = 1.): hp = max(0, hp - damage) damage_effect_last = DAMAGE_EFFECT_DURATION if hp == 0: die() func die(): died.emit() state = State.DEAD collision_shape.disabled = true add_child(DEATH_SCENE.instantiate()) await get_tree().create_timer(DEATH_SCENE_TIME).timeout queue_free() func get_random() -> RandomNumberGenerator: var random : RandomNumberGenerator = RandomNumberGenerator.new() random.seed = id return random