using Invector.vCharacterController.AI.FSMBehaviour; using Lean.Pool; using System.Collections; using UnityEngine; namespace DemonBoss.Magic { /// /// StateAction for Meteor Strike spell - boss summons meteor falling on player /// First places decal at player position, after 1.5s checks if path is clear and drops rock /// [CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Call Meteor")] public class SA_CallMeteor : vStateAction { public override string categoryName => "DemonBoss/Magic"; public override string defaultName => "Call Meteor"; [Header("Meteor Configuration")] [Tooltip("Meteor rock prefab")] public GameObject rockPrefab; [Tooltip("Decal prefab showing impact location")] public GameObject decalPrefab; [Tooltip("Height from which meteor falls")] public float meteorHeight = 30f; [Tooltip("Delay between placing decal and spawning meteor")] public float castDelay = 1.5f; [Tooltip("Obstacle check radius above target")] public float obstacleCheckRadius = 2f; [Tooltip("Layer mask for obstacles blocking meteor")] public LayerMask obstacleLayerMask = -1; [Tooltip("Layer mask for ground")] public LayerMask groundLayerMask = -1; [Tooltip("Animator trigger name for meteor summoning animation")] public string animatorTrigger = "CastMeteor"; [Header("Targeting")] [Tooltip("Maximum raycast distance to find ground")] public float maxGroundDistance = 100f; [Tooltip("Height above ground for obstacle checking")] public float airCheckHeight = 5f; [Header("Debug")] [Tooltip("Enable debug logging")] public bool enableDebug = false; [Tooltip("Show gizmos in Scene View")] public bool showGizmos = true; private GameObject spawnedDecal; private Transform playerTransform; private Vector3 targetPosition; private bool meteorCasting = false; private MonoBehaviour coroutineRunner; /// /// Main action execution method called by FSM /// public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate) { if (executionType == vFSMComponentExecutionType.OnStateEnter) { OnStateEnter(fsmBehaviour); } else if (executionType == vFSMComponentExecutionType.OnStateExit) { OnStateExit(fsmBehaviour); } } /// /// Called when entering state - starts meteor casting /// private void OnStateEnter(vIFSMBehaviourController fsmBehaviour) { if (enableDebug) Debug.Log("[SA_CallMeteor] Starting meteor casting"); FindPlayer(fsmBehaviour); var animator = fsmBehaviour.transform.GetComponent(); if (animator != null && !string.IsNullOrEmpty(animatorTrigger)) { animator.SetTrigger(animatorTrigger); if (enableDebug) Debug.Log($"[SA_CallMeteor] Set trigger: {animatorTrigger}"); } StartMeteorCast(fsmBehaviour); } /// /// Called when exiting state - cleanup /// private void OnStateExit(vIFSMBehaviourController fsmBehaviour) { if (enableDebug) Debug.Log("[SA_CallMeteor] Exiting meteor state"); meteorCasting = false; } /// /// Finds player transform /// private void FindPlayer(vIFSMBehaviourController fsmBehaviour) { GameObject player = GameObject.FindGameObjectWithTag("Player"); if (player != null) { playerTransform = player.transform; if (enableDebug) Debug.Log("[SA_CallMeteor] Player found by tag"); return; } var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI; if (aiController != null && aiController.currentTarget != null) { playerTransform = aiController.currentTarget.transform; if (enableDebug) Debug.Log("[SA_CallMeteor] Player found through AI target"); return; } if (enableDebug) Debug.LogWarning("[SA_CallMeteor] Player not found!"); } /// /// Starts meteor casting process /// private void StartMeteorCast(vIFSMBehaviourController fsmBehaviour) { if (playerTransform == null) { if (enableDebug) Debug.LogWarning("[SA_CallMeteor] No target - finishing state"); return; } if (FindGroundUnderPlayer(out Vector3 groundPos)) { targetPosition = groundPos; SpawnDecal(targetPosition); coroutineRunner = fsmBehaviour.transform.GetComponent(); if (coroutineRunner != null) { coroutineRunner.StartCoroutine(MeteorCastCoroutine(fsmBehaviour)); } else { CastMeteorImmediate(); } meteorCasting = true; if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor target: {targetPosition}"); } else { if (enableDebug) Debug.LogWarning("[SA_CallMeteor] Cannot find ground under player"); } } /// /// Finds ground under player using raycast /// private bool FindGroundUnderPlayer(out Vector3 groundPosition) { groundPosition = Vector3.zero; Vector3 playerPos = playerTransform.position; Vector3 rayStart = playerPos + Vector3.up * 5f; Ray groundRay = new Ray(rayStart, Vector3.down); if (Physics.Raycast(groundRay, out RaycastHit hit, maxGroundDistance, groundLayerMask)) { groundPosition = hit.point; return true; } groundPosition = playerPos; return true; } /// /// Spawns decal showing meteor impact location /// private void SpawnDecal(Vector3 position) { if (decalPrefab == null) { if (enableDebug) Debug.LogWarning("[SA_CallMeteor] Missing decal prefab"); return; } Quaternion decalRotation = Quaternion.identity; Ray surfaceRay = new Ray(position + Vector3.up * 2f, Vector3.down); if (Physics.Raycast(surfaceRay, out RaycastHit hit, 5f, groundLayerMask)) { decalRotation = Quaternion.FromToRotation(Vector3.up, hit.normal); } spawnedDecal = LeanPool.Spawn(decalPrefab, position, decalRotation); if (enableDebug) Debug.Log($"[SA_CallMeteor] Decal spawned at: {position}"); } /// /// Coroutine handling meteor casting process with delay /// private IEnumerator MeteorCastCoroutine(vIFSMBehaviourController fsmBehaviour) { yield return new WaitForSeconds(castDelay); if (enableDebug) Debug.Log("[SA_CallMeteor] Checking if path is clear for meteor"); if (IsPathClearForMeteor(targetPosition)) { SpawnMeteor(targetPosition); if (enableDebug) Debug.Log("[SA_CallMeteor] Meteor spawned"); } else { if (enableDebug) Debug.Log("[SA_CallMeteor] Path blocked - meteor not spawned"); } CleanupDecal(); DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Meteor", 20f); meteorCasting = false; } /// /// Immediate meteor cast without coroutine (fallback) /// private void CastMeteorImmediate() { if (IsPathClearForMeteor(targetPosition)) { SpawnMeteor(targetPosition); } CleanupDecal(); } /// /// Checks if path above target is clear for meteor /// private bool IsPathClearForMeteor(Vector3 targetPos) { Vector3 checkStart = targetPos + Vector3.up * airCheckHeight; Vector3 checkEnd = targetPos + Vector3.up * meteorHeight; if (Physics.CheckSphere(checkStart, obstacleCheckRadius, obstacleLayerMask)) { return false; } Ray pathRay = new Ray(checkEnd, Vector3.down); if (Physics.SphereCast(pathRay, obstacleCheckRadius, meteorHeight - airCheckHeight, obstacleLayerMask)) { return false; } return true; } /// /// Spawns meteor in air above target /// private void SpawnMeteor(Vector3 targetPos) { if (rockPrefab == null) { Debug.LogError("[SA_CallMeteor] Missing meteor prefab!"); return; } Vector3 meteorSpawnPos = targetPos + Vector3.up * meteorHeight; GameObject meteor = LeanPool.Spawn(rockPrefab, meteorSpawnPos, Quaternion.identity); Rigidbody meteorRb = meteor.GetComponent(); if (meteorRb != null) { meteorRb.linearVelocity = Vector3.down * 5f; meteorRb.angularVelocity = Random.insideUnitSphere * 2f; } if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor spawned at height: {meteorHeight}m"); } /// /// Removes decal from map /// private void CleanupDecal() { if (spawnedDecal != null) { LeanPool.Despawn(spawnedDecal); spawnedDecal = null; if (enableDebug) Debug.Log("[SA_CallMeteor] Decal removed"); } } /// /// Checks if meteor is currently being cast /// public bool IsCasting() { return meteorCasting; } /// /// Returns meteor target position /// public Vector3 GetTargetPosition() { return targetPosition; } /// /// Draws gizmos in Scene View for debugging /// private void OnDrawGizmosSelected() { if (!showGizmos) return; if (meteorCasting && targetPosition != Vector3.zero) { Gizmos.color = Color.red; Gizmos.DrawWireSphere(targetPosition, 1f); Vector3 spawnPos = targetPosition + Vector3.up * meteorHeight; Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(spawnPos, 0.5f); Gizmos.color = Color.yellow; Gizmos.DrawLine(spawnPos, targetPosition); Vector3 checkPos = targetPosition + Vector3.up * airCheckHeight; Gizmos.color = Color.blue; Gizmos.DrawWireSphere(checkPos, obstacleCheckRadius); } } } }