extends CharacterBody3D class_name AIBase @export_group("Settings") @export var speed: float = 4.0 @export_flags_3d_navigation var navigation_layers: int = 1 var _enable_state_machine: bool = true @export var enable_state_machine: bool: set(value): _enable_state_machine = value toggle_enable_state_machine() get: return _enable_state_machine @export var anim_player: AnimationPlayer @export_group("Entities") enum EntityType { FARMER, CITIZEN_WALKER, CITIZEN_SITTER, CAT, CROW, FOX, GOAT, RACOON, WAGYU } @export var entity_type: EntityType = EntityType.FARMER @export var entity_name: String = "" @export var allowed_biomes: Array[String] = [] @export_range(0.0, 100.0, 0.1) var spawn_uniqueness: float = 1.0 @onready var patrol_radius_shape: CollisionShape3D = $%PatrolRadiusShape @onready var nav_agent: NavigationAgent3D = $%NavigationAgent3D @onready var state_machine: StateMachine = $%StateMachine func _ready() -> void: randomize() if nav_agent: nav_agent.navigation_layers = navigation_layers toggle_enable_state_machine() func toggle_enable_state_machine() -> void: state_machine.enable = _enable_state_machine func _physics_process(delta: float) -> void: if !_enable_state_machine: return state_machine.physics_process(delta) if nav_agent.is_navigation_finished(): velocity = Vector3.ZERO move_and_slide() return var next_point = nav_agent.get_next_path_position() var direction = global_position.direction_to(next_point) velocity.x = direction.x * speed velocity.z = direction.z * speed if velocity.length_squared() > 0.1: var look_dir = Vector3(velocity.x, 0, velocity.z) var target_transform = global_transform.looking_at(global_position - look_dir, Vector3.UP) global_transform = global_transform.interpolate_with(target_transform, delta * 10.0) move_and_slide() func navigate_to_random_point() -> void: var nav_map_rid = get_world_3d().get_navigation_map() var patrol_radius = patrol_radius_shape.shape.radius for i in range(10): var target_point_in_space = global_position + Vector3(randf_range(-1, 1), 0, randf_range(-1, 1)).normalized() * randf_range(0, patrol_radius) var closest_valid_point = NavigationServer3D.map_get_closest_point(nav_map_rid, target_point_in_space) nav_agent.target_position = closest_valid_point if nav_agent.is_target_reachable(): return # Fallback: if we can't find a reachable point (e.g. we drifted slightly off navmesh), # try to navigate to the closest navmesh point to our CURRENT position to get unstuck. nav_agent.target_position = NavigationServer3D.map_get_closest_point(nav_map_rid, global_position)