#* #* agent_base.gd #* ============================================================================= #* Copyright (c) 2023-present Serhii Snitsaruk and the LimboAI contributors. #* #* Use of this source code is governed by an MIT-style #* license that can be found in the LICENSE file or at #* https://opensource.org/licenses/MIT. #* ============================================================================= #* extends CharacterBody2D ## Base agent script that is shared by all agents. signal death # Resource file to use in summon_minion() method. const MINION_RESOURCE := "res://demo/agents/03_agent_imp.tscn" # Projectile resource. const NinjaStar := preload("res://demo/agents/ninja_star/ninja_star.tscn") const Fireball := preload("res://demo/agents/fireball/fireball.tscn") var summon_count: int = 0 var _frames_since_facing_update: int = 0 var _is_dead: bool = false var _moved_this_frame: bool = false @onready var animation_player: AnimationPlayer = $AnimationPlayer @onready var health: Health = $Health @onready var root: Node2D = $Root @onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D @onready var summoning_effect: GPUParticles2D = $FX/Summoned func _ready() -> void: health.damaged.connect(_damaged) health.death.connect(die) func _physics_process(_delta: float) -> void: _post_physics_process.call_deferred() func _post_physics_process() -> void: if not _moved_this_frame: velocity = lerp(velocity, Vector2.ZERO, 0.5) _moved_this_frame = false func move(p_velocity: Vector2) -> void: velocity = lerp(velocity, p_velocity, 0.2) move_and_slide() _moved_this_frame = true ## Update agent's facing in the velocity direction. func update_facing() -> void: _frames_since_facing_update += 1 if _frames_since_facing_update > 3: face_dir(velocity.x) ## Face specified direction. func face_dir(dir: float) -> void: if dir > 0.0 and root.scale.x < 0.0: root.scale.x = 1.0; _frames_since_facing_update = 0 if dir < 0.0 and root.scale.x > 0.0: root.scale.x = -1.0; _frames_since_facing_update = 0 ## Returns 1.0 when agent is facing right. ## Returns -1.0 when agent is facing left. func get_facing() -> float: return signf(root.scale.x) func throw_ninja_star() -> void: var ninja_star := NinjaStar.instantiate() ninja_star.dir = get_facing() get_parent().add_child(ninja_star) ninja_star.global_position = global_position + Vector2.RIGHT * 100.0 * get_facing() func spit_fire() -> void: var fireball := Fireball.instantiate() fireball.dir = get_facing() get_parent().add_child(fireball) fireball.global_position = global_position + Vector2.RIGHT * 100.0 * get_facing() func summon_minion(p_position: Vector2) -> void: var minion: CharacterBody2D = load(MINION_RESOURCE).instantiate() get_parent().add_child(minion) minion.position = p_position minion.play_summoning_effect() summon_count += 1 minion.death.connect(func(): summon_count -= 1) ## Method is used when this agent is summoned from the dungeons of the castle AaaAaaAAAAAaaAAaaaaaa func play_summoning_effect() -> void: summoning_effect.emitting = true ## Is specified position inside the arena (not inside an obstacle)? func is_good_position(p_position: Vector2) -> bool: var space_state := get_world_2d().direct_space_state var params := PhysicsPointQueryParameters2D.new() params.position = p_position params.collision_mask = 1 # Obstacle layer has value 1 var collision := space_state.intersect_point(params) return collision.is_empty() ## When agent is damaged... func _damaged(_amount: float, knockback: Vector2) -> void: apply_knockback(knockback) animation_player.play(&"hurt") var btplayer := get_node_or_null(^"BTPlayer") as BTPlayer if btplayer: btplayer.set_active(false) var hsm := get_node_or_null(^"LimboHSM") if hsm: hsm.set_active(false) await animation_player.animation_finished if btplayer and not _is_dead: btplayer.restart() if hsm and not _is_dead: hsm.set_active(true) ## Push agent in the knockback direction for the specified number of physics frames. func apply_knockback(knockback: Vector2, frames: int = 10) -> void: if knockback.is_zero_approx(): return for i in range(frames): move(knockback) await get_tree().physics_frame func die() -> void: if _is_dead: return death.emit() _is_dead = true root.process_mode = Node.PROCESS_MODE_DISABLED animation_player.play(&"death") collision_shape_2d.set_deferred(&"disabled", true) for child in get_children(): if child is BTPlayer or child is LimboHSM: child.set_active(false) if get_tree(): await get_tree().create_timer(10.0).timeout queue_free() func get_health() -> Health: return health