/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.Items.Actions
{
using Opsive.Shared.Events;
using Opsive.Shared.Game;
using Opsive.UltimateCharacterController.Character;
using Opsive.UltimateCharacterController.Items.AnimatorAudioStates;
using Opsive.UltimateCharacterController.Traits;
using Opsive.UltimateCharacterController.Utility;
using UnityEngine;
///
/// The Shield will absorb damage applied to the character. It has its own strength factor so when too much damage has been taken it will no longer be effective.
///
public class Shield : ItemAction
{
[Tooltip("Does the shield only protect the player when the character is aiming?")]
[SerializeField] protected bool m_RequireAim;
[Tooltip("Determines how much damage the shield absorbs. A value of 1 will absorb all of the damage, a value of 0 will not absorb any of the damage.")]
[Range(0, 1)] [SerializeField] protected float m_AbsorptionFactor = 1;
[Tooltip("Should the shield absorb damage caused by explosions?")]
[SerializeField] protected bool m_AbsorbExplosions;
[Tooltip("Should an impact be applied when the weapon is hit by another object?")]
[SerializeField] protected bool m_ApplyImpact = true;
[Tooltip("Specifies the animator and audio state for when the shield is impacted by another object.")]
[SerializeField] protected AnimatorAudioStateSet m_ImpactAnimatorAudioStateSet = new AnimatorAudioStateSet();
[Tooltip("Specifies if the item should wait for the OnAnimatorItemImpactComplete animation event or wait for the specified duration before completing the impact.")]
[SerializeField] protected AnimationEventTrigger m_ImpactCompleteEvent = new AnimationEventTrigger(false, 0.2f);
[Tooltip("The name of the shield's durability attribute. When the durability reaches 0 the shield will not absorb any damage.")]
[SerializeField] protected string m_DurabilityAttributeName = "Durability";
[Tooltip("Should the item be dropped from the character when the durability is depleted?")]
[SerializeField] protected bool m_DropWhenDurabilityDepleted;
public bool RequireAim { get { return m_RequireAim; } set { m_RequireAim = value; } }
public float AbsorptionFactor { get { return m_AbsorptionFactor; } set { m_AbsorptionFactor = value; } }
public bool AbsorbExplosions { get { return m_AbsorbExplosions; } set { m_AbsorbExplosions = value; } }
public bool ApplyImpact { get { return m_ApplyImpact; } set { m_ApplyImpact = value; } }
public AnimatorAudioStateSet ImpactAnimatorAudioStateSet { get { return m_ImpactAnimatorAudioStateSet; } set { m_ImpactAnimatorAudioStateSet = value; } }
public AnimationEventTrigger ImpactCompleteEvent { get { return m_ImpactCompleteEvent; } set { m_ImpactCompleteEvent = value; } }
public string DurabilityAttributeName
{
get { return m_DurabilityAttributeName; }
set
{
m_DurabilityAttributeName = value;
if (Application.isPlaying) {
if (!string.IsNullOrEmpty(m_DurabilityAttributeName) && m_AttributeManager != null) {
m_DurabilityAttribute = m_AttributeManager.GetAttribute(m_DurabilityAttributeName);
} else {
m_DurabilityAttribute = null;
}
}
}
}
public bool DropWhenDurabilityDepleted { get { return m_DropWhenDurabilityDepleted; } set { m_DropWhenDurabilityDepleted = value; } }
private AttributeManager m_AttributeManager;
private Attribute m_DurabilityAttribute;
private bool m_Aiming;
private bool m_HasImpact;
public float DurabilityValue { get { return (m_DurabilityAttribute != null ? m_DurabilityAttribute.Value : 0); } }
///
/// Initialize the default values.
///
protected override void Awake()
{
base.Awake();
m_AttributeManager = GetComponent();
if (!string.IsNullOrEmpty(m_DurabilityAttributeName)) {
if (m_AttributeManager == null) {
Debug.LogError("Error: The shield " + m_GameObject.name + " has a durability attribute specified but no Attribute Manager component.");
} else {
m_DurabilityAttribute = m_AttributeManager.GetAttribute(m_DurabilityAttributeName);
if (m_DurabilityAttribute != null) {
EventHandler.RegisterEvent(m_DurabilityAttribute, "OnAttributeReachedDestinationValue", DurabilityDepleted);
}
}
}
m_ImpactAnimatorAudioStateSet.DeserializeAnimatorAudioStateSelector(m_Item, m_Character.GetCachedComponent());
m_ImpactAnimatorAudioStateSet.Awake(m_Item.gameObject);
EventHandler.RegisterEvent(m_Character, "OnAimAbilityStart", OnAim);
}
///
/// Returns the substate index that the item should be in.
///
/// the substate index that the item should be in.
public int GetItemSubstateIndex()
{
if (m_HasImpact) {
return m_ImpactAnimatorAudioStateSet.GetItemSubstateIndex();
}
return -1;
}
///
/// Damages the shield.
///
/// The object that is trying to damage the shield.
/// The amount of damage to apply/
/// The amount of damage remaining which should be applied to the character.
public float Damage(object source, float amount)
{
// The shield can't absorb damage if it requires the character to be aiming and the character isn't aiming.
if (!m_Aiming && m_RequireAim) {
return amount;
}
// The shield may not be able to absorb damage caused by explosions.
if ((source is Objects.Explosion) && !m_AbsorbExplosions) {
return amount;
}
if (m_ApplyImpact) {
m_HasImpact = true;
m_ImpactAnimatorAudioStateSet.StartStopStateSelection(true);
m_ImpactAnimatorAudioStateSet.NextState();
EventHandler.ExecuteEvent(m_Character, "OnShieldImpact", this, source);
}
// If the shield is invincible then no damage is applied to it and the resulting absorption factor should be returned.
if (m_DurabilityAttribute == null) {
return 0;
}
// If the shield's durability is depleted then the entire damage amount should be applied to the character.
if (m_DurabilityAttribute.Value == 0) {
return amount;
}
// Damage the shield and amount of damage which be applied to the character.
var damageAmount = Mathf.Min(amount * m_AbsorptionFactor, m_DurabilityAttribute.Value);
m_DurabilityAttribute.Value -= damageAmount;
return amount - damageAmount;
}
///
/// The block animation has played - reset the impact.
///
public void StopBlockImpact()
{
m_HasImpact = false;
m_ImpactAnimatorAudioStateSet.StartStopStateSelection(false);
}
///
/// The Aim ability has started or stopped.
///
/// Has the Aim ability started?
/// Was the ability started from input?
private void OnAim(bool aim, bool inputStart)
{
if (!inputStart) {
return;
}
m_Aiming = aim;
}
///
/// The shield is no longer durable.
///
private void DurabilityDepleted()
{
if (!m_DropWhenDurabilityDepleted) {
return;
}
// Remove the item from the inventory before dropping it. This will ensure the dropped prefab does not contain any ItemIdentifier amount so the
// item can't be picked up again.
m_Inventory.RemoveItem(m_Item.ItemIdentifier, m_Item.SlotID, 1, false);
m_Item.Drop(0, true);
}
///
/// The GameObject has been destroyed.
///
protected override void OnDestroy()
{
base.OnDestroy();
m_ImpactAnimatorAudioStateSet.OnDestroy();
EventHandler.UnregisterEvent(m_Character, "OnBlockAbilityStart", OnAim);
if (m_DurabilityAttribute != null) {
EventHandler.UnregisterEvent(m_DurabilityAttribute, "OnAttributeReachedDestinationValue", DurabilityDepleted);
}
EventHandler.UnregisterEvent(m_Character, "OnAimAbilityStart", OnAim);
}
}
}