projectEli/Assets/NeoFPS/Core/Damage/ShieldManager.cs
2022-11-06 20:28:33 -05:00

403 lines
14 KiB
C#

using System;
using UnityEngine;
using NeoSaveGames.Serialization;
using NeoSaveGames;
namespace NeoFPS
{
[HelpURL("https://docs.neofps.com/manual/healthref-mb-shieldmanager.html")]
public class ShieldManager : MonoBehaviour, IShieldManager, INeoSerializableComponent
{
[Header("Capacity")]
[SerializeField, Tooltip("The starting shield amount.")]
private float m_Shield = 100f;
[SerializeField, Tooltip("The shield capacity of each shield step / block.")]
private float m_StepCapacity = 100f;
[SerializeField, Tooltip("The number of shield steps / blocks.")]
private int m_StepCount = 1;
[Header("Damage")]
[SerializeField, Range(0f, 1f), Tooltip("The amount of damage (multiplier) that the shield negates.")]
private float m_DamageMitigation = 1f;
//[SerializeField, Tooltip("If false, only the current shield step can be consumed per damage event. If true, the damage will be taken from every shield step as required.")]
//private bool m_DamageOverflow = true;
[Header("Charging")]
[SerializeField, Tooltip("The recharge speed for shield regeneration.")]
private float m_ChargeRate = 5f;
[SerializeField, Tooltip("The delay between taking damage and starting shield regen.")]
private float m_ChargeDelay = 5f;
[SerializeField, Tooltip("Shield steps only recharge if the shield value is greater than their starting level. If this property is false, step 1 will always recharge, even if it hits zero.")]
private bool m_CanBreakStep1 = true;
public event ShieldDelegates.OnShieldValueChanged onShieldValueChanged;
public event ShieldDelegates.OnShieldStateChanged onShieldStateChanged;
public event ShieldDelegates.OnShieldConfigChanged onShieldConfigChanged;
private ShieldState m_ShieldState = ShieldState.Stable;
private float m_InterruptTimer = 0f;
private int m_CurrentStep = 0;
public ShieldState shieldState
{
get { return m_ShieldState; }
private set
{
if (m_ShieldState != value)
{
m_ShieldState = value;
// Fire event
if (onShieldStateChanged != null)
onShieldStateChanged(this, m_ShieldState);
}
}
}
public float shield
{
get { return m_Shield; }
set
{
// Clamp
value = Mathf.Clamp(value, 0f, m_StepCount * m_StepCapacity);
// Only assign if changed
if (m_Shield != value)
{
// Record old value
float old = m_Shield;
// Set new value
m_Shield = value;
// Interrupt recharge
CheckInterrupt(old, m_Shield);
// Fire event
if (onShieldValueChanged != null)
onShieldValueChanged(this, old, m_Shield);
}
}
}
public float shieldChargeRate
{
get { return m_ChargeRate; }
set
{
// Clamp
if (value < 0f)
value = 0f;
// Only assign if changed
if (m_ChargeRate != value)
{
m_ChargeRate = value;
// Fire config change event
if (onShieldConfigChanged != null)
onShieldConfigChanged(this);
// Check if new rate froze recharge
if (m_ChargeRate == 0f && shieldState == ShieldState.Recharging)
shieldState = ShieldState.Interrupted;
}
}
}
public float shieldStepCapacity
{
get { return m_StepCapacity; }
set
{
// Clamp
if (value < 0f)
value = 0f;
// Only assign if changed
if (m_StepCapacity != value)
{
// Record old value
float old = m_StepCapacity;
// Set new value
m_StepCapacity = value;
// Scale shield value to maintain relative
shield *= m_StepCapacity / old;
// Fire event
if (onShieldConfigChanged != null)
onShieldConfigChanged(this);
}
}
}
public int shieldStepCount
{
get { return m_StepCount; }
set
{
// Clamp
if (value < 1)
value = 1;
if (m_StepCount != value)
{
// Set new value
m_StepCount = value;
// Update shield
float max = m_StepCount * shieldStepCapacity;
if (shield > max)
shield = max;
// Fire event
if (onShieldConfigChanged != null)
onShieldConfigChanged(this);
}
}
}
void OnValidate()
{
m_Shield = Mathf.Clamp(m_Shield, 1f, 10000f);
m_StepCapacity = Mathf.Clamp(m_StepCapacity, 1f, 10000f);
m_StepCount = Mathf.Clamp(m_StepCount, 1, 99);
m_ChargeRate = Mathf.Clamp(m_ChargeRate, 0f, 1000f);
m_ChargeDelay = Mathf.Clamp(m_ChargeDelay, 0f, 300f);
}
void Start()
{
// Get the current step
CheckCurrentStep();
}
void FixedUpdate()
{
// Check if interrupted
if (m_InterruptTimer > 0f)
{
// Update interrupt timer
m_InterruptTimer -= Time.deltaTime;
if (m_InterruptTimer < 0f)
m_InterruptTimer = 0f;
}
else
{
if (m_ChargeRate > 0f)
{
// Get recharge limit
float limit = m_CurrentStep * shieldStepCapacity;
if (!m_CanBreakStep1 && limit == 0f)
limit += shieldStepCapacity;
// Regen health if required
if (shield < limit)
{
float to = shield + m_ChargeRate * Time.deltaTime;
if (to > limit)
to = limit;
shield = to;
}
}
}
}
void CheckCurrentStep()
{
m_CurrentStep = Mathf.CeilToInt((shield - 0.01f) / shieldStepCapacity);
}
void CheckInterrupt(float from, float to)
{
if (from > to)
{
if (!Mathf.Approximately(GetPendingShield(), 0f))
{
// Reset the timer
m_InterruptTimer = m_ChargeDelay;
// Set the shield state
shieldState = ShieldState.Interrupted;
}
}
// Get the current step
CheckCurrentStep();
}
float GetPendingShield()
{
int full = Mathf.FloorToInt((shield + 0.01f) / shieldStepCapacity);
if (full < shieldStepCount)
{
float remainder = shield - (full * shieldStepCapacity);
return shieldStepCapacity - remainder;
}
else
return 0f; // None left to fill
}
public int FillShieldSteps(int count = 1)
{
// Check for invalid parameter
if (count < 1)
return 0;
// Get the number of filled shield steps
int full = Mathf.FloorToInt((shield + 0.01f) / shieldStepCapacity);
if (full < shieldStepCount)
{
// Get the available step count
int available = shieldStepCount - full;
// Get the amount of steps to actually fill
int toFill = Mathf.Min(available, count);
// Fill the shield
shield = (full + toFill) * shieldStepCapacity;
shieldState = ShieldState.Stable;
// Return filled (allows partial consume of pickups, etc)
return toFill;
}
else
return 0; // None left to fill
}
public int EmptyShieldSteps(int count = 1)
{
// Check for invalid parameter
if (count < 1)
return 0;
// Get the number of filled shield steps
int active = Mathf.CeilToInt((shield - 0.01f) / shieldStepCapacity);
if (active > 0)
{
// Get the amount of steps to actually fill
int toClear = Mathf.Min(active, count);
// Reset the shield
shield = (active - toClear) * shieldStepCapacity;
shieldState = ShieldState.Stable;
// Return cleared (allows partial consume of pickups, etc)
return toClear;
}
else
return 0; // Already empty
}
protected virtual float GetDamageMultiplier(DamageType t)
{
return 1f;
}
public float GetShieldedDamage(float damage, DamageType t)
{
// Do nothing to damage if shields are down
if (shield == 0f || damage <= 0f || m_DamageMitigation == 0f)
return damage;
// Get shield damage multiplier
float shieldDamageMultiplier = GetDamageMultiplier(t);
if (shieldDamageMultiplier == 0f)
return damage;
// Get mitigated damage amount
float mitigated = damage * m_DamageMitigation;
// Remove damage overflow option, as having this false doesn't play nice with explosions or scatter weapons
//if (m_DamageOverflow)
//{
// Scale shield damage
mitigated *= shieldDamageMultiplier;
// Clamp mitigated damage to shield
if (mitigated > shield)
mitigated = shield;
// Set new shield value
shield -= mitigated;
// Reverse damage scale (to calculate absorbed)
mitigated /= shieldDamageMultiplier;
//}
//else
//{
// // Get the number of filled shield steps & remainder
// int prevStep = Mathf.FloorToInt((shield + 0.01f) / shieldStepCapacity);
// float remainder = shield - (prevStep * shieldStepCapacity);
//
// // If no remainder, can use full step
// if (Mathf.Approximately(remainder, 0f))
// {
// --prevStep;
// remainder = shieldStepCapacity;
// }
//
// // Scale shield damage
// float scaled = mitigated * shieldDamageMultiplier;
//
// // Clamp mitigated damage to shield
// if (scaled > remainder)
// scaled = remainder;
//
// // Set new shield value
// Debug.Log("Removing shield: " + scaled);
// shield -= scaled;
//}
// Return unabsorbed damage
return damage - mitigated;
}
#region INeoSerializableComponent IMPLEMENTATION
private static readonly NeoSerializationKey k_ShieldKey = new NeoSerializationKey("shield");
private static readonly NeoSerializationKey k_StepCapacityKey = new NeoSerializationKey("stepCapacity");
private static readonly NeoSerializationKey k_StepCountKey = new NeoSerializationKey("stepCount");
private static readonly NeoSerializationKey k_RechargeRateKey = new NeoSerializationKey("rechargeRate");
private static readonly NeoSerializationKey k_InterruptKey = new NeoSerializationKey("interrupt");
private static readonly NeoSerializationKey k_DelayKey = new NeoSerializationKey("delay");
private static readonly NeoSerializationKey k_StateKey = new NeoSerializationKey("state");
public void WriteProperties(INeoSerializer writer, NeoSerializedGameObject nsgo, SaveMode saveMode)
{
writer.WriteValue(k_ShieldKey, m_Shield);
writer.WriteValue(k_StepCapacityKey, m_StepCapacity);
writer.WriteValue(k_StepCountKey, m_StepCount);
writer.WriteValue(k_RechargeRateKey, m_ChargeRate);
writer.WriteValue(k_InterruptKey, m_InterruptTimer);
writer.WriteValue(k_DelayKey, m_ChargeDelay);
writer.WriteValue(k_StateKey, (int)m_ShieldState);
}
public void ReadProperties(INeoDeserializer reader, NeoSerializedGameObject nsgo)
{
reader.TryReadValue(k_StepCapacityKey, out m_StepCapacity, m_StepCapacity);
reader.TryReadValue(k_StepCountKey, out m_StepCount, m_StepCount);
reader.TryReadValue(k_RechargeRateKey, out m_ChargeRate, m_ChargeRate);
reader.TryReadValue(k_InterruptKey, out m_InterruptTimer, m_InterruptTimer);
reader.TryReadValue(k_DelayKey, out m_ChargeDelay, m_ChargeDelay);
// Set shield value
float floatValue;
if (reader.TryReadValue(k_ShieldKey, out floatValue, m_Shield))
shield = floatValue;
// set state
int intValue;
if (reader.TryReadValue(k_StateKey, out intValue, (int)shieldState))
shieldState = (ShieldState)intValue;
// Get the current step
CheckCurrentStep();
}
#endregion
}
}