using Cirno.Scripts; using Godot; using System; using System.Diagnostics; public partial class Enemy : CharacterBody2D { private InteractionController _cachedPlayer; private EnemyState _currentState = EnemyState.Idle; [Export] public float Health = 4f; [Export] public float WalkSpeed = 2500f; [Export] public float PlayerDisengageRange = 500f; [Export] public Weapon EquippedWeapon; private float _currentHealth = 0f; private bool _isDestroyed = false; private NavigationAgent2D _navigationAgent; private bool _isPlayerInRange = false; private Vector2? _lastPlayerPosition = null; [Export] public bool NavigationEnabled { get; set; } = false; // Called when the node enters the scene tree for the first time. public override void _Ready() { _currentHealth = Health; _navigationAgent = GetNode("NavigationAgent2D"); //CallDeferred("Setup"); } // private void Setup() // { // // } // Called every frame. 'delta' is the elapsed time since the previous frame. public override void _Process(double delta) { switch (_currentState) { case EnemyState.Idle: break; case EnemyState.Alert: break; //case EnemyState.Shooting: // Shoot //break; default: break; } } public override void _PhysicsProcess(double delta) { switch (_currentState) { case EnemyState.Idle: HandlePlayerDetection(); break; case EnemyState.Alert: // Update last known player position if it's in range if (_isPlayerInRange) { _lastPlayerPosition = _cachedPlayer.GlobalPosition; } if (_lastPlayerPosition.HasValue) { _navigationAgent.SetTargetPosition(_lastPlayerPosition.Value); } var currentAgentPosition = GlobalPosition; var nextPathPosition = _navigationAgent.GetNextPathPosition(); var newVelocity = currentAgentPosition.DirectionTo(nextPathPosition) * (float)(WalkSpeed * delta); // Navigation is over, can do other things like shooting if (_navigationAgent.IsNavigationFinished()) { // Shoot player Shoot(); // TODO: If player totally left the max range it should stop shooting and go back to idle return; } if (_navigationAgent.AvoidanceEnabled) { _navigationAgent.SetVelocity(newVelocity); } else { _on_navigation_agent_2d_velocity_computed(newVelocity); } MoveAndSlide(); break; case EnemyState.Patrolling: break; default: throw new ArgumentOutOfRangeException(); } } public void _on_navigation_agent_2d_velocity_computed(Vector2 safeVelocity) { this.Velocity = safeVelocity; } private void HandlePlayerDetection() { if (_cachedPlayer == null) { return; } if (IsPlayerInSight()) { // Update player position only if player is in sight // if (NavigationEnabled) // { // _navigationAgent.SetTargetPosition(_cachedPlayer.GlobalPosition); // } //Shoot(); _currentState = EnemyState.Alert; } } private void Shoot() { if (EquippedWeapon == null || !_lastPlayerPosition.HasValue) return; // Shoot at the player's last known position EquippedWeapon.ShootDirection = (_lastPlayerPosition.Value - this.GlobalPosition).Normalized(); EquippedWeapon.Shoot(); // // SHOOT // var bullet = this.CreateChild(BulletScene); // // var bullet = BulletScene.Instantiate(); // // Owner.AddChild(bullet); // // bullet.Transform = this.GlobalTransform; // // bullet.Position = this.Position; // bullet.SetDirection((_cachedPlayer.GlobalPosition - this.GlobalPosition).Normalized()); // bullet.Speed = BulletSpeed; // // _ammo -= 1; // // if (_ammo <= 0) // { // _ammo = BulletCount; // _cooldownTimer.Start(ReloadTime); // } // else // { // _cooldownTimer.Start(RateOfFire); // } } private bool IsPlayerInSight() { var spaceState = GetWorld2D().DirectSpaceState; var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, _cachedPlayer.GlobalPosition, CollisionMask, new Godot.Collections.Array { GetRid() }); var result = spaceState.IntersectRay(query); // If count is 0 then the player is in sight, otherwise there is level geometry in the way return result.Count == 0; // if (result.Count > 0) // GD.Print("Hit at point: ", result["position"]); } private void _on_player_detection_area_entered(Area2D area) { // Assume area is player for now if (area is InteractionController player) { Debug.WriteLine("Enemy detection area Entered by interaction controller"); _cachedPlayer = player; _isPlayerInRange = true; // if (_currentState is EnemyState.Idle) // { // _currentState = EnemyState.Primed; // } } } private void _on_player_detection_area_exited(Area2D area) { _isPlayerInRange = false; // if (_currentState is EnemyState.Primed) // { // _currentState = EnemyState.Idle; // } } private void _on_damage_hitbox_area_entered(Area2D area) { if (area is not Bullet bullet) return; GD.Print("Enemy Received damage"); this.Hit(bullet.Damage); bullet.QueueFree(); } // Bullets collision private void _on_area_entered(Area2D area) { } private void Explode() { Debug.WriteLine("Ded"); //CreateParticles(); //CreateDebris(); QueueFree(); } public void Hit(float damage) { if (_isDestroyed) return; _currentHealth -= damage; if (!(_currentHealth <= 0)) return; _isDestroyed = true; Explode(); } public bool IsDestroyed() { return _isDestroyed; } private enum EnemyState { Idle, Alert, Patrolling } }