403 lines
14 KiB
C#
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
|
|
}
|
|
}
|