268 lines
10 KiB
C#
268 lines
10 KiB
C#
using System.Collections;
|
|
using Core;
|
|
using Core.Settings;
|
|
using Input;
|
|
using UnityEngine;
|
|
|
|
namespace Minigames.FortFight.Projectiles
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<IFortFightSettings>();
|
|
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<Fort.FortBlock>();
|
|
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<IFortFightSettings>();
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Explode at current position (manual detonation)
|
|
/// </summary>
|
|
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<IFortFightSettings>();
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Spawn multiple trash pieces in a cone away from the hit surface.
|
|
/// Uses hit normal + projectile momentum for realistic splash effect.
|
|
/// </summary>
|
|
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<IFortFightSettings>();
|
|
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<Rigidbody2D>();
|
|
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
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregister from input manager
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|