using Invector.vCharacterController.AI.FSMBehaviour; using Lean.Pool; using UnityEngine; namespace DemonBoss.Magic { /// /// StateAction for intelligent crystal turret spawning /// Searches for optimal position in 2-6m ring from boss, prefers "behind boss" relative to player /// [CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Spawn Turret Smart")] public class SA_SpawnTurretSmart : vStateAction { public override string categoryName => "DemonBoss/Magic"; public override string defaultName => "Spawn Turret Smart"; [Header("Turret Configuration")] [Tooltip("Crystal prefab with CrystalShooterAI component")] public GameObject crystalPrefab; [Tooltip("Minimum distance from boss for crystal spawn")] public float minSpawnDistance = 2f; [Tooltip("Maximum distance from boss for crystal spawn")] public float maxSpawnDistance = 6f; [Tooltip("Collision check radius when choosing position")] public float obstacleCheckRadius = 1f; [Tooltip("Height above ground for raycast ground checking")] public float groundCheckHeight = 2f; [Tooltip("Layer mask for obstacles")] public LayerMask obstacleLayerMask = -1; [Tooltip("Layer mask for ground")] public LayerMask groundLayerMask = -1; [Tooltip("Animator trigger name for crystal casting animation")] public string animatorTrigger = "CastCrystal"; [Header("Smart Positioning")] [Tooltip("Preference multiplier for positions behind boss (relative to player)")] public float backPreferenceMultiplier = 2f; [Tooltip("Number of attempts to find valid position")] public int maxSpawnAttempts = 12; [Header("Debug")] [Tooltip("Enable debug logging")] public bool enableDebug = false; [Tooltip("Show gizmos in Scene View")] public bool showGizmos = true; private GameObject spawnedCrystal; private Transform playerTransform; /// /// Main action execution method called by FSM /// public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate) { if (executionType == vFSMComponentExecutionType.OnStateEnter) { OnStateEnter(fsmBehaviour); } } /// /// Called when entering state - intelligently spawns crystal /// private void OnStateEnter(vIFSMBehaviourController fsmBehaviour) { if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Starting intelligent crystal spawn"); FindPlayer(fsmBehaviour); var animator = fsmBehaviour.transform.GetComponent(); if (animator != null && !string.IsNullOrEmpty(animatorTrigger)) { animator.SetTrigger(animatorTrigger); if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Set trigger: {animatorTrigger}"); } SpawnCrystalSmart(fsmBehaviour); DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Turret", 12f); } /// /// Finds player transform /// private void FindPlayer(vIFSMBehaviourController fsmBehaviour) { GameObject player = GameObject.FindGameObjectWithTag("Player"); if (player != null) { playerTransform = player.transform; if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] 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_SpawnTurretSmart] Player found through AI target"); return; } if (enableDebug) Debug.LogWarning("[SA_SpawnTurretSmart] Player not found!"); } /// /// Intelligently spawns crystal in optimal position /// private void SpawnCrystalSmart(vIFSMBehaviourController fsmBehaviour) { if (crystalPrefab == null) { Debug.LogError("[SA_SpawnTurretSmart] Missing crystalPrefab!"); return; } Vector3 bestPosition = Vector3.zero; bool foundValidPosition = false; float bestScore = float.MinValue; Vector3 bossPos = fsmBehaviour.transform.position; Vector3 playerDirection = Vector3.zero; if (playerTransform != null) { playerDirection = (playerTransform.position - bossPos).normalized; } for (int i = 0; i < maxSpawnAttempts; i++) { float angle = (360f / maxSpawnAttempts) * i + Random.Range(-15f, 15f); Vector3 direction = new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), 0, Mathf.Sin(angle * Mathf.Deg2Rad)); float distance = Random.Range(minSpawnDistance, maxSpawnDistance); Vector3 testPosition = bossPos + direction * distance; if (IsPositionValid(testPosition, out Vector3 groundPosition)) { float score = EvaluatePosition(groundPosition, playerDirection, direction); if (score > bestScore) { bestScore = score; bestPosition = groundPosition; foundValidPosition = true; } } } if (foundValidPosition) { SpawnCrystal(bestPosition, fsmBehaviour); if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Crystal spawned at position: {bestPosition} (score: {bestScore:F2})"); } else { Vector3 fallbackPos = bossPos + fsmBehaviour.transform.forward * minSpawnDistance; SpawnCrystal(fallbackPos, fsmBehaviour); if (enableDebug) Debug.LogWarning("[SA_SpawnTurretSmart] Using fallback position"); } } /// /// Checks if position is valid (no obstacles, has ground) /// private bool IsPositionValid(Vector3 position, out Vector3 groundPosition) { groundPosition = position; if (Physics.CheckSphere(position + Vector3.up * obstacleCheckRadius, obstacleCheckRadius, obstacleLayerMask)) { return false; } Ray groundRay = new Ray(position + Vector3.up * groundCheckHeight, Vector3.down); if (Physics.Raycast(groundRay, out RaycastHit hit, groundCheckHeight + 2f, groundLayerMask)) { groundPosition = hit.point; if (Physics.CheckSphere(groundPosition + Vector3.up * obstacleCheckRadius, obstacleCheckRadius, obstacleLayerMask)) { return false; } return true; } return false; } /// /// Evaluates position quality (higher score = better position) /// private float EvaluatePosition(Vector3 position, Vector3 playerDirection, Vector3 positionDirection) { float score = 0f; if (playerTransform != null && playerDirection != Vector3.zero) { float angleToPlayer = Vector3.Angle(-playerDirection, positionDirection); // The smaller the angle (closer to "behind"), the better the score float backScore = (180f - angleToPlayer) / 180f; score += backScore * backPreferenceMultiplier; } Vector3 bossPos = new Vector3(); float distance = Vector3.Distance(position, bossPos); float optimalDistance = (minSpawnDistance + maxSpawnDistance) * 0.5f; float distanceScore = 1f - Mathf.Abs(distance - optimalDistance) / maxSpawnDistance; score += distanceScore; score += Random.Range(-0.1f, 0.1f); return score; } /// /// Spawns crystal at given position /// private void SpawnCrystal(Vector3 position, vIFSMBehaviourController fsmBehaviour) { Quaternion rotation = Quaternion.identity; if (playerTransform != null) { Vector3 lookDirection = (playerTransform.position - position).normalized; lookDirection.y = 0; if (lookDirection != Vector3.zero) { rotation = Quaternion.LookRotation(lookDirection); } } spawnedCrystal = LeanPool.Spawn(crystalPrefab, position, rotation); var shooterAI = spawnedCrystal.GetComponent(); if (shooterAI == null) { Debug.LogError("[SA_SpawnTurretSmart] Crystal prefab doesn't have CrystalShooterAI component!"); } else { if (playerTransform != null) { shooterAI.SetTarget(playerTransform); } } } /// /// Draws gizmos in Scene View for debugging /// private void OnDrawGizmosSelected() { if (!showGizmos) return; Vector3 pos = new Vector3(); // Spawn ring Gizmos.color = Color.green; DrawWireCircle(pos, minSpawnDistance); Gizmos.color = Color.red; DrawWireCircle(pos, maxSpawnDistance); // Obstacle check radius Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(pos + Vector3.up * obstacleCheckRadius, obstacleCheckRadius); } /// /// Helper method for drawing circles /// private void DrawWireCircle(Vector3 center, float radius) { int segments = 32; float angle = 0f; Vector3 prevPoint = center + new Vector3(radius, 0, 0); for (int i = 1; i <= segments; i++) { angle = (float)i / segments * 360f * Mathf.Deg2Rad; Vector3 newPoint = center + new Vector3(Mathf.Cos(angle) * radius, 0, Mathf.Sin(angle) * radius); Gizmos.DrawLine(prevPoint, newPoint); prevPoint = newPoint; } } } }