class_name MeetSpookMob extends Node3D ## Mob spawned and controlled by a meet-spook event enum State { WAITING, FREEZE, MOVING } const RAY_MASK := 0b00000101 const BASE_SPEED := 6.0 @export var source: MeetSpook @export var target: Node3D @export var freeze_time: float @export var lifespan: float var state := State.WAITING @onready var camera := get_viewport().get_camera_3d() func activate() -> void: print_debug(self, " spotted!") state = State.FREEZE get_tree().create_timer(freeze_time).timeout.connect(_unfreeze) get_tree().create_timer(lifespan).timeout.connect(destroy) source.resolve() func is_active() -> bool: return state != State.WAITING func move_toward_target(delta: float) -> void: # Should probably be overridden by implementations global_position = global_position.move_toward(target.global_position, BASE_SPEED * delta) func _unfreeze() -> void: state = State.MOVING func _physics_process(delta: float) -> void: match state: State.WAITING: # Not yet seen by the player if camera.is_position_in_frustum(global_position): var query := PhysicsRayQueryParameters3D.create( camera.global_position, global_position, RAY_MASK ) # TODO exceptions should include this mob's collision body var raycast := get_world_3d().direct_space_state.intersect_ray(query) if not raycast: # Player has line-of-sight! activate() State.FREEZE: # Just stand there for a sec pass State.MOVING: move_toward_target(delta) func destroy() -> void: print_debug("Destroying ", self) queue_free()