482 lines
20 KiB
C#
482 lines
20 KiB
C#
|
using System.Collections.Generic;
|
|||
|
using UnityEngine;
|
|||
|
|
|||
|
// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
|
|||
|
namespace SciFiShipController
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Class containing data for a damage region.
|
|||
|
/// </summary>
|
|||
|
[System.Serializable]
|
|||
|
public class DamageRegion
|
|||
|
{
|
|||
|
#region Public variables and properties
|
|||
|
|
|||
|
// IMPORTANT - when changing this section also update SetClassDefault()
|
|||
|
// Also update ClassName(ClassName className) Clone Constructor (if there is one)
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The name of this damage region.
|
|||
|
/// </summary>
|
|||
|
public string name;
|
|||
|
/// <summary>
|
|||
|
/// When invincible, it will not take damage however its health can still be manually decreased.
|
|||
|
/// </summary>
|
|||
|
public bool isInvincible;
|
|||
|
/// <summary>
|
|||
|
/// Position of this damage region in local space relative to the pivot point of the ship.
|
|||
|
/// </summary>
|
|||
|
public Vector3 relativePosition;
|
|||
|
/// <summary>
|
|||
|
/// Size of this damage region (in metres cubed) in local space.
|
|||
|
/// </summary>
|
|||
|
public Vector3 size;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The starting health value of this damage region.
|
|||
|
/// </summary>
|
|||
|
public float startingHealth;
|
|||
|
|
|||
|
private float health;
|
|||
|
/// <summary>
|
|||
|
/// The current health value of this damage region.
|
|||
|
/// NOTE: Health can fall below zero if health is low
|
|||
|
/// when damage is applied.
|
|||
|
/// </summary>
|
|||
|
public float Health
|
|||
|
{
|
|||
|
get { return health; }
|
|||
|
set
|
|||
|
{
|
|||
|
// Update the health value
|
|||
|
health = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY]
|
|||
|
/// Normalised (0.0 – 1.0) value of the health of the damage region.
|
|||
|
/// </summary>
|
|||
|
public float HealthNormalised
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
float _healthN = startingHealth == 0f ? 0f : health / startingHealth;
|
|||
|
if (_healthN > 1f) { return 1f; }
|
|||
|
else if (_healthN < 0f) { return 0f; }
|
|||
|
else { return _healthN; }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Whether this damage region uses shielding. Up until a point, shielding protects the damage region from damage
|
|||
|
/// (which can affect the performance of parts associated with the damage region).
|
|||
|
/// </summary>
|
|||
|
public bool useShielding;
|
|||
|
/// <summary>
|
|||
|
/// Damage below this value will not affect the shield or the damage region's health while the shield is still active
|
|||
|
/// (i.e. until the shield has absorbed damage more than or equal to the shieldingAmount value from damage events above the
|
|||
|
/// damage threshold). Only relevant if useShielding is enabled.
|
|||
|
/// </summary>
|
|||
|
public float shieldingDamageThreshold;
|
|||
|
/// <summary>
|
|||
|
/// How much damage the shield can absorb before it ceases to protect the damage region from damage. Only relevant if
|
|||
|
/// useShielding is enabled.
|
|||
|
/// </summary>
|
|||
|
public float shieldingAmount;
|
|||
|
|
|||
|
private float shieldHealth;
|
|||
|
/// <summary>
|
|||
|
/// The current health value of this damage region's shield.
|
|||
|
/// When a shield is destroyed, its value is set to -0.01.
|
|||
|
/// </summary>
|
|||
|
public float ShieldHealth
|
|||
|
{
|
|||
|
get { return shieldHealth; }
|
|||
|
set
|
|||
|
{
|
|||
|
// Update the health value
|
|||
|
shieldHealth = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY]
|
|||
|
/// Normalised (0.0 – 1.0) value of the shield for this damage region. If useShielding is false, it will
|
|||
|
/// always return 0.
|
|||
|
/// </summary>
|
|||
|
public float ShieldNormalised
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
float _shieldN = !useShielding || shieldingAmount == 0f ? 0f : shieldHealth / shieldingAmount;
|
|||
|
if (_shieldN > 1f) { return 1f; }
|
|||
|
else if (_shieldN < 0f) { return 0f; }
|
|||
|
else { return _shieldN; }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// When useShielding is true, this is the rate per second that a shield will recharge (default = 0)
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 100f)] public float shieldingRechargeRate;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// When useShielding is true, and shieldingRechargeRate is greater than 0, this is the delay, in seconds,
|
|||
|
/// between when damage occurs to a shield and it begins to recharge.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 300f)] public float shieldingRechargeDelay;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Value indicating the resistance of the damage region to damage caused by collisions. Increasing this value will decrease the amount
|
|||
|
/// of damage caused to the damage region by collisions.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 100f)] public float collisionDamageResistance;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Whether the damage region is shown as expanded in the inspector window of the editor.
|
|||
|
/// </summary>
|
|||
|
public bool showInEditor;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Whether the damage region is shown as selected in the scene view of the editor.
|
|||
|
/// </summary>
|
|||
|
public bool selectedInSceneView;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Whether the gizmos for this damage region are shown in the scene view of the editor.
|
|||
|
/// </summary>
|
|||
|
public bool showGizmosInSceneView;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The sound or particle FX used when region is destroyed (if this is the main damage region, this will occur when the
|
|||
|
/// ship is destroyed). It should automatically be despawned when the whole ship is despawned.
|
|||
|
/// If you modify this, call ReinitialiseShipProjectilesAndEffects() on the ShipControlModule.
|
|||
|
/// </summary>
|
|||
|
public EffectsModule destructionEffectsObject;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Should the destruction EffectsObject move as the ship moves?
|
|||
|
/// </summary>
|
|||
|
public bool isMoveDestructionEffectsObject;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This is used when you want pre-build fragments of the ship to explode out from the ship position when it is destroyed.
|
|||
|
/// If you modify this, call UpdateDestructObjects() on the ShipControlModule.
|
|||
|
/// </summary>
|
|||
|
public DestructModule destructObject;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The child transform of the ship that contains the mesh(es) for this local region. If set, it is disabled when the region's health reaches 0.
|
|||
|
/// Setting this can also assist other weapons in the scene with determining Line-of-Sight to a damage region.
|
|||
|
/// NOTE: It is ignored for the mainRegion.
|
|||
|
/// </summary>
|
|||
|
public Transform regionChildTransform;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL USE ONLY] Instead, call shipControlModule.EnableRadar(damageRegion) or DisableRadar(damageRegion).
|
|||
|
/// </summary>
|
|||
|
public bool isRadarEnabled;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The number used by the SSCRadar system to identify this ship's damage region at a point in time.
|
|||
|
/// This should not be stored across frames and is updated as required by the system.
|
|||
|
/// </summary>
|
|||
|
public int RadarId { get { return radarItemIndex; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The ID number for this damage region's destruction effects object prefab (as assigned by the Ship Controller Manager in the scene).
|
|||
|
/// This is the index in the SSCManager effectsObjectTemplatesList.
|
|||
|
/// [INTERNAL USE ONLY]
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] public int effectsObjectPrefabID;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Flag for whether the destruction effects object has been instantiated.
|
|||
|
/// [INTERNAL USE ONLY]
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] public bool isDestructionEffectsObjectInstantiated;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Hashed GUID code to uniquely identify a damage region on a ship. Used instead of the
|
|||
|
/// name to avoid GC when comparing two damage regions.
|
|||
|
/// [INTERNAL USE ONLY]
|
|||
|
/// </summary>
|
|||
|
public int guidHash;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private or Internal variables
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The array of damage multipliers for this ship.
|
|||
|
/// </summary>
|
|||
|
[SerializeField] private float[] damageMultipliersArray;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This identifies the destructionEffectsObject instance that may have been instantiated.
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] internal SSCEffectItemKey destructionEffectItemKey;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The ID number for this damage region's destruct prefab (as assigned by the SSCManager in the scene).
|
|||
|
/// This is the index in the SSCManager destructTemplateList.
|
|||
|
/// [INTERNAL USE ONLY]
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] internal int destructObjectPrefabID;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Flag for whether the destruct object has been activated.
|
|||
|
/// [INTERNAL USE ONLY]
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] internal bool isDestructObjectActivated;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This identifies the destruct object instance that may have been instantiated.
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] internal SSCDestructItemKey destructItemKey;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL USE ONLY]
|
|||
|
/// Flag for when the region destroy "event" has been actioned after health reaches 0
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] internal bool isDestroyed;
|
|||
|
|
|||
|
// Radar variables
|
|||
|
[System.NonSerialized] internal int radarItemIndex;
|
|||
|
[System.NonSerialized] internal SSCRadarPacket sscRadarPacket;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL USE ONLY]
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] internal float shieldRechargeDelayTimer;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Class constructors
|
|||
|
|
|||
|
public DamageRegion()
|
|||
|
{
|
|||
|
SetClassDefaults();
|
|||
|
}
|
|||
|
|
|||
|
// Copy constructor
|
|||
|
public DamageRegion(DamageRegion damageRegion)
|
|||
|
{
|
|||
|
if (damageRegion == null) { SetClassDefaults(); }
|
|||
|
else
|
|||
|
{
|
|||
|
this.name = damageRegion.name;
|
|||
|
this.isInvincible = damageRegion.isInvincible;
|
|||
|
this.relativePosition = damageRegion.relativePosition;
|
|||
|
this.size = damageRegion.size;
|
|||
|
this.startingHealth = damageRegion.startingHealth;
|
|||
|
this.Health = damageRegion.Health;
|
|||
|
this.useShielding = damageRegion.useShielding;
|
|||
|
this.shieldingDamageThreshold = damageRegion.shieldingDamageThreshold;
|
|||
|
this.shieldingAmount = damageRegion.shieldingAmount;
|
|||
|
this.ShieldHealth = damageRegion.ShieldHealth;
|
|||
|
this.shieldingRechargeRate = damageRegion.shieldingRechargeRate;
|
|||
|
this.shieldingRechargeDelay = damageRegion.shieldingRechargeDelay;
|
|||
|
this.collisionDamageResistance = damageRegion.collisionDamageResistance;
|
|||
|
this.showInEditor = damageRegion.showInEditor;
|
|||
|
this.selectedInSceneView = damageRegion.selectedInSceneView;
|
|||
|
this.showGizmosInSceneView = damageRegion.showGizmosInSceneView;
|
|||
|
this.destructionEffectsObject = damageRegion.destructionEffectsObject;
|
|||
|
this.isMoveDestructionEffectsObject = damageRegion.isMoveDestructionEffectsObject;
|
|||
|
this.destructObject = damageRegion.destructObject;
|
|||
|
this.regionChildTransform = damageRegion.regionChildTransform;
|
|||
|
this.isRadarEnabled = damageRegion.isRadarEnabled;
|
|||
|
//this.effectsObjectPrefabID = damageRegion.effectsObjectPrefabID;
|
|||
|
//this.isDestructionEffectsObjectInstantiated = damageRegion.isDestructionEffectsObjectInstantiated;
|
|||
|
this.guidHash = damageRegion.guidHash;
|
|||
|
this.Initialise();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Internal Non-Static Methods
|
|||
|
|
|||
|
internal void CheckShieldRecharge()
|
|||
|
{
|
|||
|
// Is shield recharging enabled?
|
|||
|
if (health > 0 && useShielding && shieldingRechargeRate > 0)
|
|||
|
{
|
|||
|
// Can the shield be recharged?
|
|||
|
if (shieldingRechargeDelay > shieldRechargeDelayTimer)
|
|||
|
{
|
|||
|
shieldRechargeDelayTimer += Time.deltaTime;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
shieldHealth += shieldingRechargeRate * Time.deltaTime;
|
|||
|
|
|||
|
if (shieldHealth > shieldingAmount) { shieldHealth = shieldingAmount; }
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public Non-Static Methods
|
|||
|
|
|||
|
public void SetClassDefaults()
|
|||
|
{
|
|||
|
this.name = "Damage Region";
|
|||
|
this.isInvincible = false;
|
|||
|
this.relativePosition = Vector3.zero;
|
|||
|
this.size = Vector3.one * 10f;
|
|||
|
this.startingHealth = 100f;
|
|||
|
this.Health = 100f;
|
|||
|
this.useShielding = false;
|
|||
|
this.shieldingDamageThreshold = 10f;
|
|||
|
this.shieldingAmount = 100f;
|
|||
|
this.ShieldHealth = 100f;
|
|||
|
this.shieldingRechargeRate = 0f;
|
|||
|
this.shieldingRechargeDelay = 10f;
|
|||
|
this.collisionDamageResistance = 10f;
|
|||
|
this.showInEditor = true;
|
|||
|
this.selectedInSceneView = false;
|
|||
|
this.showGizmosInSceneView = true;
|
|||
|
this.destructionEffectsObject = null;
|
|||
|
this.isMoveDestructionEffectsObject = false;
|
|||
|
this.destructObject = null;
|
|||
|
this.regionChildTransform = null;
|
|||
|
this.isRadarEnabled = false;
|
|||
|
//this.effectsObjectPrefabID = -1;
|
|||
|
//this.isDestructionEffectsObjectInstantiated = false;
|
|||
|
this.damageMultipliersArray = new float[] { 1f, 1f, 1f, 1f, 1f, 1f };
|
|||
|
|
|||
|
// Get a unique GUID then convert it to a hash for efficient non-GC access.
|
|||
|
guidHash = SSCMath.GetHashCodeFromGuid();
|
|||
|
|
|||
|
this.Initialise();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initialises data for the damage region. This does some precalculation to allow for performance improvements.
|
|||
|
/// </summary>
|
|||
|
public void Initialise()
|
|||
|
{
|
|||
|
// Check that the damage multipliers array exists
|
|||
|
if (damageMultipliersArray == null)
|
|||
|
{
|
|||
|
damageMultipliersArray = new float[] { 1f, 1f, 1f, 1f, 1f, 1f };
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Check that the damage multipliers array is of the correct length
|
|||
|
int damageMultipliersArrayLength = damageMultipliersArray.Length;
|
|||
|
if (damageMultipliersArrayLength != 6)
|
|||
|
{
|
|||
|
// If it is not the correct length, resize it
|
|||
|
// Convert the array into a list
|
|||
|
List<float> tempDamageMultipliersList = new List<float>();
|
|||
|
tempDamageMultipliersList.AddRange(damageMultipliersArray);
|
|||
|
if (damageMultipliersArrayLength > 6)
|
|||
|
{
|
|||
|
// If we have too many items in the array, remove some
|
|||
|
for (int i = damageMultipliersArrayLength; i > 6; i--) { tempDamageMultipliersList.RemoveAt(i - 1); }
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If we don't have enough items in the array, add some
|
|||
|
for (int i = damageMultipliersArrayLength; i < 6; i++) { tempDamageMultipliersList.Add(1f); }
|
|||
|
}
|
|||
|
// Convert the list back into an array
|
|||
|
damageMultipliersArray = tempDamageMultipliersList.ToArray();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
effectsObjectPrefabID = -1;
|
|||
|
isDestructionEffectsObjectInstantiated = false;
|
|||
|
destructionEffectItemKey = new SSCEffectItemKey(-1, -1, 0);
|
|||
|
|
|||
|
destructObjectPrefabID = -1;
|
|||
|
isDestructObjectActivated = false;
|
|||
|
destructItemKey = new SSCDestructItemKey(-1, -1, 0);
|
|||
|
|
|||
|
isDestroyed = false;
|
|||
|
|
|||
|
radarItemIndex = -1;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the damage multiplier for damageType.
|
|||
|
/// </summary>
|
|||
|
/// <param name="damageType"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public float GetDamageMultiplier (ProjectileModule.DamageType damageType)
|
|||
|
{
|
|||
|
switch ((int)damageType)
|
|||
|
{
|
|||
|
// Hardcoded int values for performance
|
|||
|
// Default = 0
|
|||
|
case 0: return 1f;
|
|||
|
// Type A = 100
|
|||
|
case 100: return damageMultipliersArray[0];
|
|||
|
// Type B = 105
|
|||
|
case 105: return damageMultipliersArray[1];
|
|||
|
// Type C = 110
|
|||
|
case 110: return damageMultipliersArray[2];
|
|||
|
// Type D = 115
|
|||
|
case 115: return damageMultipliersArray[3];
|
|||
|
// Type E = 120
|
|||
|
case 120: return damageMultipliersArray[4];
|
|||
|
// Type F = 125
|
|||
|
case 125: return damageMultipliersArray[5];
|
|||
|
// Default case
|
|||
|
default: return 1f;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Is the hitPoint within the bounds of the region?
|
|||
|
/// Use this when calculating if a projectile or beam hits
|
|||
|
/// the damage region. See also ship.IsPointInDamageRegion(damageRegion, worldSpacePoint).
|
|||
|
/// USAGE: damageRegion.IsHit(ship.TransformInverseRotation * (damagePositionWS - ship.TransformPosition) )
|
|||
|
/// </summary>
|
|||
|
/// <param name="hitPoint"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public bool IsHit (Vector3 localDamagePosition)
|
|||
|
{
|
|||
|
// Then check whether the position is within the bounds or volume of this damage region
|
|||
|
// For some reason we need to slightly expand the damage region when the collider is exactly the same size.
|
|||
|
return localDamagePosition.x >= relativePosition.x - (size.x / 2) - 0.0001f &&
|
|||
|
localDamagePosition.x <= relativePosition.x + (size.x / 2) + 0.0001f &&
|
|||
|
localDamagePosition.y >= relativePosition.y - (size.y / 2) - 0.0001f &&
|
|||
|
localDamagePosition.y <= relativePosition.y + (size.y / 2) + 0.0001f &&
|
|||
|
localDamagePosition.z >= relativePosition.z - (size.z / 2) - 0.0001f &&
|
|||
|
localDamagePosition.z <= relativePosition.z + (size.z / 2) + 0.0001f;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets the damage multiplier for damageType to damageMultiplier.
|
|||
|
/// </summary>
|
|||
|
/// <param name="damageType"></param>
|
|||
|
/// <param name="damageMultiplier"></param>
|
|||
|
public void SetDamageMultiplier(ProjectileModule.DamageType damageType, float damageMultiplier)
|
|||
|
{
|
|||
|
switch ((int)damageType)
|
|||
|
{
|
|||
|
// Hardcoded int values for performance
|
|||
|
// Type A = 100
|
|||
|
case 100: damageMultipliersArray[0] = damageMultiplier; break;
|
|||
|
// Type B = 105
|
|||
|
case 105: damageMultipliersArray[1] = damageMultiplier; break;
|
|||
|
// Type C = 110
|
|||
|
case 110: damageMultipliersArray[2] = damageMultiplier; break;
|
|||
|
// Type D = 115
|
|||
|
case 115: damageMultipliersArray[3] = damageMultiplier; break;
|
|||
|
// Type E = 120
|
|||
|
case 120: damageMultipliersArray[4] = damageMultiplier; break;
|
|||
|
// Type F = 125
|
|||
|
case 125: damageMultipliersArray[5] = damageMultiplier; break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|