rabidus-test/Assets/BNG Framework/Scripts/Components/Damageable.cs

219 lines
7.1 KiB
C#
Raw Normal View History

2023-07-24 16:38:13 +03:00
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
#if INVECTOR_BASIC || INVECTOR_AI_TEMPLATE
using Invector;
#endif
namespace BNG {
/// <summary>
/// A basic damage implementation. Call a function on death. Allow for respawning.
/// </summary>
public class Damageable : MonoBehaviour {
public float Health = 100;
private float _startingHealth;
[Tooltip("If specified, this GameObject will be instantiated at this transform's position on death.")]
public GameObject SpawnOnDeath;
[Tooltip("Activate these GameObjects on Death")]
public List<GameObject> ActivateGameObjectsOnDeath;
[Tooltip("Deactivate these GameObjects on Death")]
public List<GameObject> DeactivateGameObjectsOnDeath;
[Tooltip("Deactivate these Colliders on Death")]
public List<Collider> DeactivateCollidersOnDeath;
/// <summary>
/// Destroy this object on Death? False if need to respawn.
/// </summary>
[Tooltip("Destroy this object on Death? False if need to respawn.")]
public bool DestroyOnDeath = true;
[Tooltip("If this object is a Grabbable it can be dropped on Death")]
public bool DropOnDeath = true;
/// <summary>
/// How long to wait before destroying this objects
/// </summary>
[Tooltip("How long to wait before destroying this objects")]
public float DestroyDelay = 0f;
/// <summary>
/// If true the object will be reactivated according to RespawnTime
/// </summary>
[Tooltip("If true the object will be reactivated according to RespawnTime")]
public bool Respawn = false;
/// <summary>
/// If Respawn true, this gameObject will reactivate after RespawnTime. In seconds.
/// </summary>
[Tooltip("If Respawn true, this gameObject will reactivate after RespawnTime. In seconds.")]
public float RespawnTime = 10f;
/// <summary>
/// Remove any decals that were parented to this object on death. Useful for clearing unused decals.
/// </summary>
[Tooltip("Remove any decals that were parented to this object on death. Useful for clearing unused decals.")]
public bool RemoveBulletHolesOnDeath = true;
[Header("Events")]
[Tooltip("Optional Event to be called when receiving damage. Takes damage amount as a float parameter.")]
public FloatEvent onDamaged;
[Tooltip("Optional Event to be called once health is <= 0")]
public UnityEvent onDestroyed;
[Tooltip("Optional Event to be called once the object has been respawned, if Respawn is true and after RespawnTime")]
public UnityEvent onRespawn;
#if INVECTOR_BASIC || INVECTOR_AI_TEMPLATE
// Invector damage integration
[Header("Invector Integration")]
[Tooltip("If true, damage data will be sent to Invector object using 'ApplyDamage'")]
public bool SendDamageToInvector = true;
#endif
bool destroyed = false;
Rigidbody rigid;
bool initialWasKinematic;
private void Start() {
_startingHealth = Health;
rigid = GetComponent<Rigidbody>();
if (rigid) {
initialWasKinematic = rigid.isKinematic;
}
}
public virtual void DealDamage(float damageAmount) {
DealDamage(damageAmount, transform.position);
}
public virtual void DealDamage(float damageAmount, Vector3? hitPosition = null, Vector3? hitNormal = null, bool reactToHit = true, GameObject sender = null, GameObject receiver = null) {
if (destroyed) {
return;
}
Health -= damageAmount;
onDamaged?.Invoke(damageAmount);
// Invector Integration
#if INVECTOR_BASIC || INVECTOR_AI_TEMPLATE
if(SendDamageToInvector) {
var d = new Invector.vDamage();
d.hitReaction = reactToHit;
d.hitPosition = (Vector3)hitPosition;
d.receiver = receiver == null ? this.gameObject.transform : null;
d.damageValue = (int)damageAmount;
this.gameObject.ApplyDamage(new Invector.vDamage(d));
}
#endif
if (Health <= 0) {
DestroyThis();
}
}
public virtual void DestroyThis() {
Health = 0;
destroyed = true;
// Activate
foreach (var go in ActivateGameObjectsOnDeath) {
go.SetActive(true);
}
// Deactivate
foreach (var go in DeactivateGameObjectsOnDeath) {
go.SetActive(false);
}
// Colliders
foreach (var col in DeactivateCollidersOnDeath) {
col.enabled = false;
}
// Spawn object
if (SpawnOnDeath != null) {
var go = GameObject.Instantiate(SpawnOnDeath);
go.transform.position = transform.position;
go.transform.rotation = transform.rotation;
}
// Force to kinematic if rigid present
if (rigid) {
rigid.isKinematic = true;
}
// Invoke Callback Event
if (onDestroyed != null) {
onDestroyed.Invoke();
}
if (DestroyOnDeath) {
Destroy(this.gameObject, DestroyDelay);
}
else if (Respawn) {
StartCoroutine(RespawnRoutine(RespawnTime));
}
// Drop this if the player is holding it
Grabbable grab = GetComponent<Grabbable>();
if (DropOnDeath && grab != null && grab.BeingHeld) {
grab.DropItem(false, true);
}
// Remove an decals that may have been parented to this object
if (RemoveBulletHolesOnDeath) {
BulletHole[] holes = GetComponentsInChildren<BulletHole>();
foreach (var hole in holes) {
GameObject.Destroy(hole.gameObject);
}
Transform decal = transform.Find("Decal");
if (decal) {
GameObject.Destroy(decal.gameObject);
}
}
}
IEnumerator RespawnRoutine(float seconds) {
yield return new WaitForSeconds(seconds);
Health = _startingHealth;
destroyed = false;
// Deactivate
foreach (var go in ActivateGameObjectsOnDeath) {
go.SetActive(false);
}
// Re-Activate
foreach (var go in DeactivateGameObjectsOnDeath) {
go.SetActive(true);
}
foreach (var col in DeactivateCollidersOnDeath) {
col.enabled = true;
}
// Reset kinematic property if applicable
if (rigid) {
rigid.isKinematic = initialWasKinematic;
}
// Call events
if (onRespawn != null) {
onRespawn.Invoke();
}
}
}
}