class_name VisionCone extends Node3D

signal detected(body: Node3D)
signal lost_detection(body: Node3D)

const COLLISION_MASK: int = 0x0001

@export var excluded: Array[CollisionObject3D] = []:
	set(value):
		excluded = value
		_excluded_rid.assign(excluded.map(func(o: CollisionObject3D) -> RID: return o.get_rid()))
	get:
		return excluded

@export var body_offset: Vector3 = Vector3.ZERO

var _excluded_rid: Array[RID]

var _tracked_bodies: Dictionary = {}
var _detected_bodies: Array[Node3D] = []


func _physics_process(_delta: float) -> void:
	var now_detected: Array[Node3D] = []
	var space_state: PhysicsDirectSpaceState3D = get_world_3d().direct_space_state
	for body: Node3D in _tracked_bodies.keys():
		var occlusion: Dictionary = space_state.intersect_ray(
			PhysicsRayQueryParameters3D.create(
				global_position, body.global_position + body_offset, COLLISION_MASK, _excluded_rid
			)
		)
		# If there is no occlusion, the body has been detected
		if not occlusion:
			now_detected.push_back(body)

	for body: Node3D in _detected_bodies:
		if body not in now_detected:
			lost_detection.emit(body)

	for body: Node3D in now_detected:
		if body not in _detected_bodies:
			detected.emit(body)

	_detected_bodies = now_detected


func _on_area_3d_body_entered(body: Node3D) -> void:
	_tracked_bodies[body] = true


func _on_area_3d_body_exited(body: Node3D) -> void:
	_tracked_bodies.erase(body)
