using Chickensoft.AutoInject; using Chickensoft.Introspection; using Godot; namespace Zennysoft.Game.Ma; [Meta(typeof(IAutoNode))] public partial class EnemyModelView2D : Node3D, IEnemyModelView { protected const string PRIMARY_ATTACK = "primary_attack"; protected const string PRIMARY_ATTACK_LEFT = "primary_attack_left"; protected const string PRIMARY_ATTACK_RIGHT = "primary_attack_right"; protected const string PRIMARY_ATTACK_BACK = "primary_attack_back"; protected const string SECONDARY_ATTACK = "secondary_attack"; protected const string SECONDARY_ATTACK_LEFT = "secondary_attack_left"; protected const string SECONDARY_ATTACK_RIGHT = "secondary_attack_right"; protected const string SECONDARY_ATTACK_BACK = "secondary_attack_back"; protected const string PRIMARY_SKILL = "primary_skill"; protected const string IDLE_FORWARD = "idle_front"; protected const string IDLE_LEFT = "idle_left"; protected const string IDLE_RIGHT = "idle_right"; protected const string IDLE_BACK = "idle_back"; protected const string IDLE_FORWARD_WALK = "idle_front_walk"; protected const string IDLE_LEFT_WALK = "idle_left_walk"; protected const string IDLE_RIGHT_WALK = "idle_right_walk"; protected const string IDLE_BACK_WALK = "idle_back_walk"; protected const string PARAMETERS_PLAYBACK = "parameters/playback"; public override void _Notification(int what) => this.Notify(what); [Export] public EnemyLoreInfo EnemyLoreInfo { get; set; } = default!; [Node] public AnimatedSprite2D AnimatedSprite { get; set; } = default!; [Node] public IHitbox Hitbox { get; set; } = default!; [Node] public AnimationPlayer AnimationPlayer { get; set; } = default!; [Node] public AnimationTree AnimationTree { get; set; } = default!; protected DirectionType _currentDirection = DirectionType.FORWARD; public void SetCurrentDirection(Basis enemyBasis, Vector3 cameraDirection) => _currentDirection = GetEnemyDirection(enemyBasis, cameraDirection, 0.55f, 0.45f); public void PlayPrimaryAttackAnimation() { switch (_currentDirection) { case DirectionType.FORWARD: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(PRIMARY_ATTACK); break; case DirectionType.RIGHT: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(PRIMARY_ATTACK_RIGHT); break; case DirectionType.LEFT: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(PRIMARY_ATTACK_LEFT); break; case DirectionType.BACKWARD: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(PRIMARY_ATTACK_BACK); break; } } public void PlaySecondaryAttackAnimation() { switch (_currentDirection) { case DirectionType.FORWARD: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(SECONDARY_ATTACK); break; case DirectionType.RIGHT: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(SECONDARY_ATTACK_RIGHT); break; case DirectionType.LEFT: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(SECONDARY_ATTACK_LEFT); break; case DirectionType.BACKWARD: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(SECONDARY_ATTACK_BACK); break; } } public void PlayPrimarySkillAnimation() { AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(PRIMARY_SKILL); } public void PlayHitAnimation() { LoadShader("res://src/vfx/shaders/DamageHit.gdshader"); var tweener = GetTree().CreateTween(); tweener.TweenMethod(Callable.From((float x) => SetShaderValue(x)), 0.0f, 1.0f, 1.0f); } public void PlayDeathAnimation() { LoadShader("res://src/vfx/shaders/PixelMelt.gdshader"); var tweener = GetTree().CreateTween(); tweener.TweenMethod(Callable.From((float x) => SetShaderValue(x)), 0.0f, 1.0f, 0.8f); tweener.TweenCallback(Callable.From(QueueFree)); } public virtual void PlayIdleAnimation() { switch (_currentDirection) { case DirectionType.FORWARD: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(IDLE_FORWARD); break; case DirectionType.RIGHT: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(IDLE_RIGHT); break; case DirectionType.LEFT: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(IDLE_LEFT); break; case DirectionType.BACKWARD: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(IDLE_BACK); break; } } public virtual void PlayWalkAnimation() { switch (_currentDirection) { case DirectionType.FORWARD: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(IDLE_FORWARD_WALK); break; case DirectionType.RIGHT: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(IDLE_RIGHT_WALK); break; case DirectionType.LEFT: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(IDLE_LEFT_WALK); break; case DirectionType.BACKWARD: AnimationTree.Get(PARAMETERS_PLAYBACK).As().Travel(IDLE_BACK_WALK); break; } } private DirectionType GetEnemyDirection( Basis enemyBasis, Vector3 cameraDirection, float rotateUpperThreshold, float rotateLowerThreshold) { var enemyForwardDirection = enemyBasis.Z; var enemyLeftDirection = enemyBasis.X; var leftDotProduct = enemyLeftDirection.Dot(cameraDirection); var forwardDotProduct = enemyForwardDirection.Dot(cameraDirection); // Check if forward facing. If the dot product is -1, the enemy is facing the camera. if (forwardDotProduct < -rotateUpperThreshold) return DirectionType.FORWARD; // Check if backward facing. If the dot product is 1, the enemy is facing the same direction as the camera. else if (forwardDotProduct > rotateUpperThreshold) return DirectionType.BACKWARD; else { // If the dot product of the perpendicular direction is positive (up to 1), the enemy is facing to the left (since it's mirrored). if (leftDotProduct > 0) return DirectionType.RIGHT; // Check if side facing. If the dot product is close to zero in the positive or negative direction, its close to the threshold for turning. if (Mathf.Abs(forwardDotProduct) < rotateLowerThreshold) return DirectionType.LEFT; } return _currentDirection; } private void LoadShader(string shaderPath) { var shader = GD.Load(shaderPath); AnimatedSprite.Material = new ShaderMaterial(); var shaderMaterial = (ShaderMaterial)AnimatedSprite.Material; shaderMaterial.Shader = shader; } private void SetShaderValue(float shaderValue) { var shaderMaterial = (ShaderMaterial)AnimatedSprite.Material; shaderMaterial.SetShaderParameter("progress", shaderValue); } }