using System.Collections; using Core; using Core.Settings; using Input; using UnityEngine; namespace Minigames.FortFight.Projectiles { /// /// Trash Bag projectile - splits into multiple smaller pieces on impact. /// Deals AOE damage in a forward cone. /// Can be manually detonated mid-flight by tapping. /// public class TrashBagProjectile : ProjectileBase, ITouchInputConsumer { [Header("Trash Bag Specific")] [Tooltip("Prefab for individual trash pieces (small debris)")] [SerializeField] private GameObject trashPiecePrefab; [Tooltip("Visual indicator showing tap-to-explode is available")] [SerializeField] private GameObject indicator; private bool inputEnabled = false; private bool hasExploded = false; public override void Launch(Vector2 direction, float force) { base.Launch(direction, force); // Start activation delay coroutine for tap-to-explode StartCoroutine(ActivationDelayCoroutine()); } private IEnumerator ActivationDelayCoroutine() { // Get activation delay from settings (use TrashBag-specific or default to 0.3s) var settings = GameManager.GetSettingsObject(); float activationDelay = settings?.CeilingFanActivationDelay ?? 0.3f; // Reuse CeilingFan delay setting // Wait for delay yield return new WaitForSeconds(activationDelay); // Enable input and show indicator (if not already exploded) if (!hasExploded && !AbilityActivated) { inputEnabled = true; if (indicator != null) { indicator.SetActive(true); } // Only register with InputManager if NOT AI-controlled if (!IsAIControlled && InputManager.Instance != null) { InputManager.Instance.RegisterOverrideConsumer(this); Logging.Debug("[TrashBagProjectile] Tap-to-explode now available (Player controlled)"); } else if (IsAIControlled) { Logging.Debug("[TrashBagProjectile] AI-controlled projectile, input disabled"); } } } public override void ActivateAbility() { base.ActivateAbility(); if (AbilityActivated && !hasExploded) { Logging.Debug("[TrashBagProjectile] Ability activated - manual detonation"); ExplodeAtCurrentPosition(); } } protected override void OnHit(Collision2D collision) { // Prevent double-explosion if already manually detonated if (hasExploded) return; // Deal initial damage from trash bag itself var block = collision.gameObject.GetComponent(); if (block != null) { block.TakeDamage(Damage); Logging.Debug($"[TrashBagProjectile] Dealt {Damage} damage to {block.gameObject.name}"); } // Get settings for trash pieces var settings = GameManager.GetSettingsObject(); int pieceCount = settings?.TrashBagPieceCount ?? 8; Logging.Debug($"[TrashBagProjectile] Impact explosion - splitting into {pieceCount} pieces"); // Get contact normal and impact point Vector2 hitNormal = collision.contacts[0].normal; Vector2 impactPoint = collision.contacts[0].point; // Mark as exploded and spawn trash pieces hasExploded = true; SpawnTrashPieces(impactPoint, hitNormal); // Destroy trash bag after spawning pieces DestroyProjectile(); } /// /// Explode at current position (manual detonation) /// private void ExplodeAtCurrentPosition() { if (hasExploded) return; hasExploded = true; // Unregister from input immediately UnregisterFromInput(); // Hide indicator if (indicator != null) { indicator.SetActive(false); } // Get current position and use velocity as "hit normal" direction Vector2 explosionPoint = transform.position; Vector2 explosionDirection = rb2D.linearVelocity.normalized; if (explosionDirection == Vector2.zero) { explosionDirection = LaunchDirection; } var settings = GameManager.GetSettingsObject(); int pieceCount = settings?.TrashBagPieceCount ?? 8; Logging.Debug($"[TrashBagProjectile] Manual detonation - splitting into {pieceCount} pieces"); // Spawn trash pieces SpawnTrashPieces(explosionPoint, explosionDirection); // Destroy the trash bag DestroyProjectile(); } /// /// Spawn multiple trash pieces in a cone away from the hit surface. /// Uses hit normal + projectile momentum for realistic splash effect. /// private void SpawnTrashPieces(Vector2 impactPoint, Vector2 hitNormal) { if (trashPiecePrefab == null) { Logging.Warning("[TrashBagProjectile] No trash piece prefab assigned!"); return; } // Get settings var settings = GameManager.GetSettingsObject(); int pieceCount = settings?.TrashBagPieceCount ?? 8; float pieceForce = settings?.TrashBagPieceForce ?? 10f; float spreadAngle = settings?.TrashBagSpreadAngle ?? 60f; // Calculate projectile's incoming direction (momentum) Vector2 incomingDirection = rb2D.linearVelocity.normalized; if (incomingDirection == Vector2.zero) { incomingDirection = LaunchDirection; } // Calculate reflection direction from hit normal // This creates a bounce-like effect Vector2 reflectDirection = Vector2.Reflect(incomingDirection, hitNormal); // Blend between reflection and pure normal for more variety // 70% normal (splash away from surface) + 30% reflection (maintain some momentum direction) Vector2 baseDirection = (hitNormal * 0.7f + reflectDirection * 0.3f).normalized; // Spawn pieces in a cone around the base direction for (int i = 0; i < pieceCount; i++) { // Calculate angle offset for this piece within the spread cone float t = pieceCount > 1 ? i / (float)(pieceCount - 1) : 0.5f; float angleOffset = Mathf.Lerp(-spreadAngle / 2f, spreadAngle / 2f, t); float angleRadians = Mathf.Atan2(baseDirection.y, baseDirection.x) + angleOffset * Mathf.Deg2Rad; Vector2 pieceDirection = new Vector2(Mathf.Cos(angleRadians), Mathf.Sin(angleRadians)); // Spawn trash piece slightly offset from impact point Vector2 spawnOffset = pieceDirection * 0.2f; // Small offset to prevent clipping GameObject piece = Instantiate(trashPiecePrefab, (Vector2)impactPoint + spawnOffset, Quaternion.identity); // Setup trash piece physics Rigidbody2D pieceRb = piece.GetComponent(); if (pieceRb != null) { // Apply force with some randomness for more natural spread float randomForce = pieceForce * Random.Range(0.8f, 1.2f); pieceRb.AddForce(pieceDirection * randomForce, ForceMode2D.Impulse); // Add some random spin pieceRb.AddTorque(Random.Range(-100f, 100f)); } Logging.Debug($"[TrashBagProjectile] Spawned trash piece {i} in direction {pieceDirection}"); } } #region ITouchInputConsumer Implementation public void OnTap(Vector2 worldPosition) { // Only respond if input is enabled if (inputEnabled && !hasExploded && !AbilityActivated) { Logging.Debug("[TrashBagProjectile] Tap detected - activating manual detonation"); // Hide indicator if (indicator != null) { indicator.SetActive(false); } ActivateAbility(); // Unregister immediately after tap UnregisterFromInput(); } } public void OnHoldStart(Vector2 worldPosition) { // Not used for trash bag } public void OnHoldMove(Vector2 worldPosition) { // Not used for trash bag } public void OnHoldEnd(Vector2 worldPosition) { // Not used for trash bag } /// /// Unregister from input manager /// private void UnregisterFromInput() { if (InputManager.Instance != null) { InputManager.Instance.UnregisterOverrideConsumer(this); inputEnabled = false; } } #endregion protected override void DestroyProjectile() { // Ensure we unregister from input before destruction UnregisterFromInput(); base.DestroyProjectile(); } } }