using DemonBoss.AI; using Invector.vCharacterController.AI.FSMBehaviour; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Playables; namespace DemonBoss.Summoner { /// /// FSM Action: cast a single fireball with optional overlay clip /// [CreateAssetMenu(menuName = "Invector/FSM/Actions/Summoner/Cast Fireball")] public class SA_CastFireball : vStateAction { public override string categoryName => "Summoner"; public override string defaultName => "Cast Fireball"; [Header("Timing")] [Tooltip("Optional delay before the fireball is spawned")] public float castDelay = 0f; [Header("One-off Overlay Clip (No Animator Params)")] public AnimationClip overlayClip; [Tooltip("Playback speed (1 = normal)")] public float overlaySpeed = 1f; [Tooltip("Blend-in seconds (instant in this minimal impl)")] public float overlayFadeIn = 0.10f; [Tooltip("Blend-out seconds (instant in this minimal impl)")] public float overlayFadeOut = 0.10f; [Header("Debug")] public bool enableDebug = false; private SummonerAI _summoner; private bool _castScheduled; // --- Playables runtime --- private PlayableGraph _overlayGraph; private AnimationPlayableOutput _overlayOutput; private AnimationClipPlayable _overlayPlayable; private bool _overlayPlaying; private float _overlayStopAtTime; public override void DoAction(vIFSMBehaviourController fsm, vFSMComponentExecutionType execType = vFSMComponentExecutionType.OnStateUpdate) { if (execType == vFSMComponentExecutionType.OnStateEnter) { OnEnter(fsm); } else if (execType == vFSMComponentExecutionType.OnStateUpdate) { if (_overlayPlaying && Time.time >= _overlayStopAtTime) { StopOverlayWithFade(); } } else if (execType == vFSMComponentExecutionType.OnStateExit) { OnExit(); } } private void OnEnter(vIFSMBehaviourController fsm) { _summoner = fsm.gameObject.GetComponent(); if (_summoner == null) { if (enableDebug) Debug.LogWarning("[SA_CastFireball] No SummonerAI component found!"); return; } _castScheduled = false; // Play overlay clip (no Animator params) PlayOverlayOnce(fsm.transform); if (castDelay > 0f) { fsm.gameObject.AddComponent().Init(castDelay, SpawnFireballNow); _castScheduled = true; } else { SpawnFireballNow(); } } private void OnExit() { StopOverlayImmediate(); _castScheduled = false; } private void SpawnFireballNow() { if (_summoner == null) return; _summoner.CastFireball(); if (enableDebug) Debug.Log("[SA_CastFireball] Fireball cast"); } // ------------------ Overlay helpers ------------------ private void PlayOverlayOnce(Transform owner) { if (overlayClip == null || owner == null) return; Animator anim = owner.GetComponent(); if (anim == null) return; _overlayGraph = PlayableGraph.Create("ActionOverlay(CastFireball)"); _overlayGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); _overlayPlayable = AnimationClipPlayable.Create(_overlayGraph, overlayClip); _overlayPlayable.SetSpeed(Mathf.Max(0.0001f, overlaySpeed)); _overlayPlayable.SetApplyFootIK(false); _overlayPlayable.SetApplyPlayableIK(false); _overlayOutput = AnimationPlayableOutput.Create(_overlayGraph, "AnimOut", anim); _overlayOutput.SetSourcePlayable(_overlayPlayable); _overlayOutput.SetWeight(1f); _overlayGraph.Play(); float duration = (float)overlayClip.length / Mathf.Max(0.0001f, overlaySpeed); _overlayStopAtTime = Time.time + duration; _overlayPlaying = true; } private void StopOverlayWithFade() { StopOverlayImmediate(); } private void StopOverlayImmediate() { if (!_overlayGraph.IsValid()) return; _overlayGraph.Stop(); _overlayGraph.Destroy(); _overlayPlaying = false; } } }