#define DEBUG_NPC using Godot; using SupaLidlGame.Extensions; using SupaLidlGame.Items; using SupaLidlGame.Utils; using System; namespace SupaLidlGame.Characters; public partial class NPC : Character { /// /// Time in seconds it takes for the NPC to think FeelsDankCube /// public const float ThinkTime = 0.125f; public float[] Weights => _weights; protected float _preferredWeightDistance = 64.0f; protected float _maxWeightDistance = 8.0f; protected float _preferredWeightDistanceSq = 4096.0f; protected float _maxWeightDistanceSq = 64.0f; [Export] public float PreferredWeightDistance { get => _preferredWeightDistance; protected set { _preferredWeightDistance = value; _preferredWeightDistanceSq = value * value; } } [Export] public float MaxWeightDistance { get => _maxWeightDistance; protected set { _maxWeightDistance = value; _maxWeightDistanceSq = value * value; } } [Export] public Items.Item DefaultSelectedItem { get; set; } [Export] public bool ShouldMoveWhenUsingItem { get; set; } = true; [Export] public State.Thinker.ThinkerStateMachine ThinkerStateMachine { get; set; } public bool ShouldMove { get; set; } = true; public bool CanAttack { get; set; } = true; public Vector2 LastSeenPosition { get; set; } protected float[] _weights = new float[16]; protected int _bestWeightIdx; protected double _thinkTimeElapsed = 0; protected Vector2 _blockingDir; protected static readonly Vector2[] _weightDirs = new Vector2[16]; static NPC() { for (int i = 0; i < 16; i++) { float y = Mathf.Sin(Mathf.Pi * i * 2 / 16); float x = Mathf.Cos(Mathf.Pi * i * 2 / 16); _weightDirs[i] = new Vector2(x, y); } } public override void _Ready() { base._Ready(); Array.Fill(_weights, 0); if (DefaultSelectedItem is not null) { Inventory.SelectedItem = DefaultSelectedItem; } Inventory.UsedItem += (Items.Item item) => { if (item is Items.Weapon) { if (AttackAnimation is not null) { AttackAnimation.TryPlay("attack"); } } }; } /// /// Finds the NPC's best character to target. /// public virtual Character FindBestTarget() { float bestScore = float.MaxValue; Character bestChar = null; foreach (var character in World.Instance.CurrentMap.GetCharacters()) { bool isFriendly = ((IFaction)character).AlignsWith(this); if (isFriendly || !character.IsAlive) { continue; } float score = 0; score += GlobalPosition.DistanceSquaredTo(character.GlobalPosition); if (score < bestScore) { bestScore = score; bestChar = character; } } return bestChar; } /// /// Finds the best character whose faction aligns with this character's. /// public virtual Character FindBestNeutral() { float bestScore = float.MaxValue; Character bestChar = null; // NOTE: this relies on all Characters being under the Entities node foreach (Node node in GetParent().GetChildren()) { if (node is Character character) { bool isFriendly = ((IFaction)this).AlignsWith(character); if (isFriendly || character.Health <= 0) { continue; } float score = 0; score -= Position.DistanceSquaredTo(character.Position); if (score < bestScore) { bestScore = score; bestChar = character; } } } return bestChar; } public override void _Process(double delta) { ThinkerStateMachine.Process(delta); base._Process(delta); } public override void _PhysicsProcess(double delta) { ThinkerStateMachine.PhysicsProcess(delta); base._PhysicsProcess(delta); } }