365 lines
9.2 KiB
C#
365 lines
9.2 KiB
C#
using Invector;
|
|
using Lean.Pool;
|
|
using UnityEngine;
|
|
|
|
namespace DemonBoss.Magic
|
|
{
|
|
/// <summary>
|
|
/// Turret component that allows player to destroy crystal turrets
|
|
/// Implements Invector interfaces for damage system integration
|
|
/// Attach to turret prefab along with appropriate collider (non-trigger)
|
|
/// </summary>
|
|
public class DestroyableTurret : MonoBehaviour, vIDamageReceiver, vIHealthController
|
|
{
|
|
[Header("Health Configuration")]
|
|
[Tooltip("Maximum health points of the turret")]
|
|
public int maxHealth = 50;
|
|
|
|
[Tooltip("Current health points (read-only)")]
|
|
[SerializeField] private int currentHealth;
|
|
|
|
[Header("Destruction Effects")]
|
|
[Tooltip("Visual effect played when turret is destroyed")]
|
|
public GameObject destructionEffect;
|
|
|
|
[Tooltip("Sound played when turret is destroyed")]
|
|
public AudioClip destructionSound;
|
|
|
|
[Tooltip("Sound played when turret receives damage")]
|
|
public AudioClip hitSound;
|
|
|
|
[Header("Visual Feedback")]
|
|
[Tooltip("Material applied when turret is damaged")]
|
|
public Material damagedMaterial;
|
|
|
|
[Tooltip("HP threshold below which damaged material is applied (percentage)")]
|
|
[Range(0f, 1f)]
|
|
public float damagedThreshold = 0.5f;
|
|
|
|
[Header("Debug")]
|
|
[Tooltip("Enable debug logging")]
|
|
public bool enableDebug = false;
|
|
|
|
// Private fields
|
|
private AudioSource audioSource;
|
|
|
|
private CrystalShooterAI shooterAI;
|
|
private Renderer turretRenderer;
|
|
private Material originalMaterial;
|
|
private bool isDestroyed = false;
|
|
|
|
// Invector system events
|
|
public UnityEngine.Events.UnityEvent OnReceiveDamage { get; set; }
|
|
|
|
public UnityEngine.Events.UnityEvent OnDead { get; set; }
|
|
|
|
// vIHealthController implementation
|
|
public int currentHealthRecovery { get; set; }
|
|
|
|
public int maxHealthRecovery { get; set; }
|
|
public bool isDead => currentHealth <= 0;
|
|
|
|
public OnReceiveDamage onStartReceiveDamage => throw new System.NotImplementedException();
|
|
|
|
public OnReceiveDamage onReceiveDamage => throw new System.NotImplementedException();
|
|
|
|
public OnDead onDead => throw new System.NotImplementedException();
|
|
|
|
float vIHealthController.currentHealth => throw new System.NotImplementedException();
|
|
|
|
public int MaxHealth => throw new System.NotImplementedException();
|
|
|
|
bool vIHealthController.isDead { get => isDead; set => throw new System.NotImplementedException(); }
|
|
|
|
/// <summary>
|
|
/// Initialize components and validate setup
|
|
/// </summary>
|
|
private void Awake()
|
|
{
|
|
InitializeComponents();
|
|
InitializeHealth();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find and configure required components
|
|
/// </summary>
|
|
private void InitializeComponents()
|
|
{
|
|
// Find components
|
|
shooterAI = GetComponent<CrystalShooterAI>();
|
|
turretRenderer = GetComponent<Renderer>();
|
|
|
|
// Store original material
|
|
if (turretRenderer != null)
|
|
{
|
|
originalMaterial = turretRenderer.material;
|
|
}
|
|
|
|
// Setup AudioSource
|
|
audioSource = GetComponent<AudioSource>();
|
|
if (audioSource == null && (destructionSound != null || hitSound != null))
|
|
{
|
|
audioSource = gameObject.AddComponent<AudioSource>();
|
|
audioSource.playOnAwake = false;
|
|
audioSource.spatialBlend = 1f; // 3D sound
|
|
}
|
|
|
|
// Validate collider setup
|
|
Collider col = GetComponent<Collider>();
|
|
if (col != null && col.isTrigger && enableDebug)
|
|
{
|
|
Debug.LogWarning("[DestroyableTurret] Collider should not be trigger for damage system!");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setup initial health values
|
|
/// </summary>
|
|
private void InitializeHealth()
|
|
{
|
|
currentHealth = maxHealth;
|
|
maxHealthRecovery = maxHealth;
|
|
currentHealthRecovery = 0;
|
|
|
|
if (enableDebug) Debug.Log($"[DestroyableTurret] Initialized with {currentHealth}/{maxHealth} HP");
|
|
}
|
|
|
|
#region vIDamageReceiver Implementation
|
|
|
|
/// <summary>
|
|
/// Handle incoming damage from Invector damage system
|
|
/// </summary>
|
|
public void TakeDamage(vDamage damage)
|
|
{
|
|
if (isDestroyed) return;
|
|
|
|
int damageValue = Mathf.RoundToInt(damage.damageValue);
|
|
TakeDamage(damageValue);
|
|
|
|
if (enableDebug)
|
|
Debug.Log($"[DestroyableTurret] Received {damageValue} damage from {damage.sender?.name}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle incoming damage with hit reaction parameter
|
|
/// </summary>
|
|
public void TakeDamage(vDamage damage, bool hitReaction)
|
|
{
|
|
TakeDamage(damage);
|
|
}
|
|
|
|
#endregion vIDamageReceiver Implementation
|
|
|
|
#region vIHealthController Implementation
|
|
|
|
/// <summary>
|
|
/// Apply damage to the turret and handle destruction
|
|
/// </summary>
|
|
public void TakeDamage(int damage)
|
|
{
|
|
if (isDestroyed || currentHealth <= 0) return;
|
|
|
|
currentHealth = Mathf.Max(0, currentHealth - damage);
|
|
|
|
if (enableDebug)
|
|
Debug.Log($"[DestroyableTurret] HP: {currentHealth}/{maxHealth} (-{damage})");
|
|
|
|
// Play hit effects
|
|
PlayHitEffects();
|
|
UpdateVisualState();
|
|
|
|
// Trigger damage event
|
|
OnReceiveDamage?.Invoke();
|
|
|
|
// Check if destroyed
|
|
if (currentHealth <= 0)
|
|
{
|
|
DestroyTurret();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Change health by specified amount (positive for healing, negative for damage)
|
|
/// </summary>
|
|
public void ChangeHealth(int value)
|
|
{
|
|
if (isDestroyed) return;
|
|
|
|
if (value < 0)
|
|
{
|
|
TakeDamage(-value);
|
|
}
|
|
else
|
|
{
|
|
currentHealth = Mathf.Min(maxHealth, currentHealth + value);
|
|
if (enableDebug) Debug.Log($"[DestroyableTurret] Healed by {value}, HP: {currentHealth}/{maxHealth}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Modify maximum health value
|
|
/// </summary>
|
|
public void ChangeMaxHealth(int value)
|
|
{
|
|
maxHealth = Mathf.Max(1, maxHealth + value);
|
|
currentHealth = Mathf.Min(currentHealth, maxHealth);
|
|
}
|
|
|
|
#endregion vIHealthController Implementation
|
|
|
|
/// <summary>
|
|
/// Play sound and visual effects when receiving damage
|
|
/// </summary>
|
|
private void PlayHitEffects()
|
|
{
|
|
// Play hit sound
|
|
if (audioSource != null && hitSound != null)
|
|
{
|
|
audioSource.PlayOneShot(hitSound);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update visual appearance based on current health
|
|
/// </summary>
|
|
private void UpdateVisualState()
|
|
{
|
|
// Change material if turret is heavily damaged
|
|
if (turretRenderer != null && damagedMaterial != null)
|
|
{
|
|
float healthPercent = (float)currentHealth / maxHealth;
|
|
if (healthPercent <= damagedThreshold && turretRenderer.material != damagedMaterial)
|
|
{
|
|
turretRenderer.material = damagedMaterial;
|
|
if (enableDebug) Debug.Log("[DestroyableTurret] Changed to damaged material");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle turret destruction sequence
|
|
/// </summary>
|
|
private void DestroyTurret()
|
|
{
|
|
if (isDestroyed) return;
|
|
isDestroyed = true;
|
|
|
|
if (enableDebug) Debug.Log("[DestroyableTurret] Turret has been destroyed!");
|
|
|
|
// Stop shooting
|
|
if (shooterAI != null)
|
|
{
|
|
shooterAI.StopShooting();
|
|
}
|
|
|
|
// Trigger death event
|
|
OnDead?.Invoke();
|
|
|
|
// Play destruction effects
|
|
PlayDestructionEffects();
|
|
|
|
// Destroy after brief delay (let effects play)
|
|
StartCoroutine(DestroyAfterDelay(0.1f));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Play visual and audio effects for turret destruction
|
|
/// </summary>
|
|
private void PlayDestructionEffects()
|
|
{
|
|
// Spawn visual effect
|
|
if (destructionEffect != null)
|
|
{
|
|
GameObject effect = Instantiate(destructionEffect, transform.position, transform.rotation);
|
|
Destroy(effect, 5f);
|
|
}
|
|
|
|
// Play destruction sound
|
|
if (audioSource != null && destructionSound != null)
|
|
{
|
|
audioSource.PlayOneShot(destructionSound);
|
|
|
|
// Keep AudioSource alive to finish playing
|
|
audioSource.transform.parent = null;
|
|
Destroy(audioSource.gameObject, destructionSound.length + 1f);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine to destroy turret after specified delay
|
|
/// </summary>
|
|
private System.Collections.IEnumerator DestroyAfterDelay(float delay)
|
|
{
|
|
yield return new UnityEngine.WaitForSeconds(delay);
|
|
|
|
// Remove through Lean Pool (if used) or regular Destroy
|
|
if (gameObject.scene.isLoaded) // Check if object still exists
|
|
{
|
|
LeanPool.Despawn(gameObject);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force immediate turret destruction (e.g., when boss dies)
|
|
/// </summary>
|
|
public void ForceDestroy()
|
|
{
|
|
if (enableDebug) Debug.Log("[DestroyableTurret] Forced destruction");
|
|
currentHealth = 0;
|
|
//isDead = true;
|
|
DestroyTurret();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns current health as percentage of maximum health
|
|
/// </summary>
|
|
public float GetHealthPercentage()
|
|
{
|
|
return maxHealth > 0 ? (float)currentHealth / maxHealth : 0f;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
/// <summary>
|
|
/// Draw turret status visualization in Scene View
|
|
/// </summary>
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
// Show turret status
|
|
if (Application.isPlaying)
|
|
{
|
|
Gizmos.color = currentHealth > maxHealth * 0.5f ? Color.green :
|
|
currentHealth > maxHealth * 0.25f ? Color.yellow : Color.red;
|
|
}
|
|
else
|
|
{
|
|
Gizmos.color = Color.green;
|
|
}
|
|
|
|
Gizmos.DrawWireCube(transform.position + Vector3.up * 2f, Vector3.one * 0.5f);
|
|
|
|
// HP text in Scene View
|
|
if (Application.isPlaying)
|
|
{
|
|
UnityEditor.Handles.Label(transform.position + Vector3.up * 2.5f, $"HP: {currentHealth}/{maxHealth}");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public void AddHealth(int value)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
public void ResetHealth(float health)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
public void ResetHealth()
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
}
|
|
} |