extends Node enum State { IDLE, CHASE, ATTACK, RETURN } func _physics_process(delta: float) -> void: _process_group(delta, EnemyData) _process_group(delta, BossData) func _process_group(delta: float, data_source: Node) -> void: for entity in data_source.entities: if not is_instance_valid(entity) or not data_source.is_alive(entity): continue var data: Dictionary = data_source.entities[entity] if entity.is_in_group("invasion"): _force_invasion_target(entity, data) var state: int = data["state"] match state: State.IDLE: entity.velocity.x = 0 entity.velocity.z = 0 State.CHASE: _chase(entity, data, data_source) State.ATTACK: _attack(entity, data, data_source, delta) State.RETURN: _return_to_spawn(entity, data, data_source, delta) func _force_invasion_target(entity: Node, data: Dictionary) -> void: var tavern: Node = get_tree().get_first_node_in_group("tavern") if not tavern: return data["target"] = tavern if data["state"] == State.IDLE or data["state"] == State.RETURN: data["state"] = State.CHASE func _chase(entity: Node, data: Dictionary, data_source: Node) -> void: if not is_instance_valid(data["target"]): data["state"] = State.RETURN return var base: EnemyStats = data_source.get_base(entity) var attack_range: float = base.attack_range if data["target"].is_in_group("tavern"): attack_range += 3.0 var dist: float = entity.global_position.distance_to(data["target"].global_position) if dist <= attack_range: data["state"] = State.ATTACK return var nav_agent: NavigationAgent3D = entity.get_node_or_null("NavigationAgent3D") if not nav_agent: return nav_agent.target_position = data["target"].global_position var next_pos := nav_agent.get_next_path_position() var direction: Vector3 = (next_pos - entity.global_position).normalized() direction.y = 0 entity.velocity.x = direction.x * base.speed entity.velocity.z = direction.z * base.speed _face_target(entity, data["target"]) func _attack(entity: Node, data: Dictionary, data_source: Node, delta: float) -> void: data["attack_timer"] -= delta if not is_instance_valid(data["target"]): data["state"] = State.RETURN return var base: EnemyStats = data_source.get_base(entity) var attack_range: float = base.attack_range if data["target"].is_in_group("tavern"): attack_range += 3.0 var dist: float = entity.global_position.distance_to(data["target"].global_position) if dist > attack_range: data["state"] = State.CHASE return if data["attack_timer"] <= 0: data["attack_timer"] = base.attack_cooldown var scale: float = data.get("scale", 1.0) EventBus.damage_requested.emit(entity, data["target"], base.attack_damage * scale) EventBus.attack_executed.emit(entity, entity.global_position, -entity.global_transform.basis.z, base.attack_damage * scale) entity.velocity.x = 0 entity.velocity.z = 0 _face_target(entity, data["target"]) func _face_target(entity: Node3D, target: Node3D) -> void: if not is_instance_valid(target): return var to_target: Vector3 = target.global_position - entity.global_position to_target.y = 0 if to_target.length() < 0.01: return var yaw: float = atan2(-to_target.x, -to_target.z) var delta: float = get_physics_process_delta_time() entity.rotation.y = lerp_angle(entity.rotation.y, yaw, 8.0 * delta) func _return_to_spawn(entity: Node, data: Dictionary, data_source: Node, delta: float) -> void: var spawn_pos: Vector3 = data["spawn_position"] var dist: float = entity.global_position.distance_to(spawn_pos) if dist < 1.0: data["state"] = State.IDLE entity.velocity.x = 0 entity.velocity.z = 0 return var base: EnemyStats = data_source.get_base(entity) var nav_agent: NavigationAgent3D = entity.get_node_or_null("NavigationAgent3D") if not nav_agent: return nav_agent.target_position = spawn_pos var next_pos := nav_agent.get_next_path_position() var direction: Vector3 = (next_pos - entity.global_position).normalized() direction.y = 0 entity.velocity.x = direction.x * base.speed entity.velocity.z = direction.z * base.speed _regenerate(entity, data, data_source, delta) func _regenerate(entity: Node, data: Dictionary, data_source: Node, delta: float) -> void: var health: float = data["health"] var max_health: float = data["max_health"] if health < max_health: var base: EnemyStats = data_source.get_base(entity) var rate: float = base.regen_fast if health >= max_health * 0.99: rate = base.regen_slow health = min(health + max_health * rate * delta, max_health) data_source.set_health(entity, health)