using System.Collections.Generic; using UnityEngine; // Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved. namespace SciFiShipController { /// /// Class containing data for a damage region. /// [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) /// /// The name of this damage region. /// public string name; /// /// When invincible, it will not take damage however its health can still be manually decreased. /// public bool isInvincible; /// /// Position of this damage region in local space relative to the pivot point of the ship. /// public Vector3 relativePosition; /// /// Size of this damage region (in metres cubed) in local space. /// public Vector3 size; /// /// The starting health value of this damage region. /// public float startingHealth; private float health; /// /// The current health value of this damage region. /// NOTE: Health can fall below zero if health is low /// when damage is applied. /// public float Health { get { return health; } set { // Update the health value health = value; } } /// /// [READONLY] /// Normalised (0.0 – 1.0) value of the health of the damage region. /// 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; } } } /// /// 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). /// public bool useShielding; /// /// 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. /// public float shieldingDamageThreshold; /// /// How much damage the shield can absorb before it ceases to protect the damage region from damage. Only relevant if /// useShielding is enabled. /// public float shieldingAmount; private float shieldHealth; /// /// The current health value of this damage region's shield. /// When a shield is destroyed, its value is set to -0.01. /// public float ShieldHealth { get { return shieldHealth; } set { // Update the health value shieldHealth = value; } } /// /// [READONLY] /// Normalised (0.0 – 1.0) value of the shield for this damage region. If useShielding is false, it will /// always return 0. /// 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; } } } /// /// When useShielding is true, this is the rate per second that a shield will recharge (default = 0) /// [Range(0f, 100f)] public float shieldingRechargeRate; /// /// 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. /// [Range(0f, 300f)] public float shieldingRechargeDelay; /// /// 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. /// [Range(0f, 100f)] public float collisionDamageResistance; /// /// Whether the damage region is shown as expanded in the inspector window of the editor. /// public bool showInEditor; /// /// Whether the damage region is shown as selected in the scene view of the editor. /// public bool selectedInSceneView; /// /// Whether the gizmos for this damage region are shown in the scene view of the editor. /// public bool showGizmosInSceneView; /// /// 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. /// public EffectsModule destructionEffectsObject; /// /// Should the destruction EffectsObject move as the ship moves? /// public bool isMoveDestructionEffectsObject; /// /// 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. /// public DestructModule destructObject; /// /// 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. /// public Transform regionChildTransform; /// /// [INTERNAL USE ONLY] Instead, call shipControlModule.EnableRadar(damageRegion) or DisableRadar(damageRegion). /// public bool isRadarEnabled; /// /// [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. /// public int RadarId { get { return radarItemIndex; } } /// /// 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] /// [System.NonSerialized] public int effectsObjectPrefabID; /// /// Flag for whether the destruction effects object has been instantiated. /// [INTERNAL USE ONLY] /// [System.NonSerialized] public bool isDestructionEffectsObjectInstantiated; /// /// 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] /// public int guidHash; #endregion #region Private or Internal variables /// /// The array of damage multipliers for this ship. /// [SerializeField] private float[] damageMultipliersArray; /// /// This identifies the destructionEffectsObject instance that may have been instantiated. /// [System.NonSerialized] internal SSCEffectItemKey destructionEffectItemKey; /// /// 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] /// [System.NonSerialized] internal int destructObjectPrefabID; /// /// Flag for whether the destruct object has been activated. /// [INTERNAL USE ONLY] /// [System.NonSerialized] internal bool isDestructObjectActivated; /// /// This identifies the destruct object instance that may have been instantiated. /// [System.NonSerialized] internal SSCDestructItemKey destructItemKey; /// /// [INTERNAL USE ONLY] /// Flag for when the region destroy "event" has been actioned after health reaches 0 /// [System.NonSerialized] internal bool isDestroyed; // Radar variables [System.NonSerialized] internal int radarItemIndex; [System.NonSerialized] internal SSCRadarPacket sscRadarPacket; /// /// [INTERNAL USE ONLY] /// [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(); } /// /// Initialises data for the damage region. This does some precalculation to allow for performance improvements. /// 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 tempDamageMultipliersList = new List(); 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; } /// /// Returns the damage multiplier for damageType. /// /// /// 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; } } /// /// 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) ) /// /// /// 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; } /// /// Sets the damage multiplier for damageType to damageMultiplier. /// /// /// 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 } }