Files
BABA_YAGA/Assets/Opsive/UltimateCharacterController/Scripts/Items/Actions/UsableItem.cs
2026-06-14 23:57:44 +07:00

341 lines
16 KiB
C#

/// ---------------------------------------------
/// 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.Shared.Inventory;
using Opsive.UltimateCharacterController.Character;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Character.Abilities.Items;
using Opsive.UltimateCharacterController.Items.AnimatorAudioStates;
using Opsive.UltimateCharacterController.Traits;
using Opsive.UltimateCharacterController.Utility;
using UnityEngine;
/// <summary>
/// Base class for any item that can be used.
/// </summary>
public abstract class UsableItem : ItemAction, IUsableItem
{
/// <summary>
/// Defines the statue of the Use Ability when calling CanUseItem.
/// </summary>
public enum UseAbilityState
{
Start, // The Use ability is starting.
Update, // The Use ability is updating.
None // The ability is not used.
}
[Tooltip("The amount of time that must elapse before the item can be used again.")]
[SerializeField] protected float m_UseRate = 0.1f;
[Tooltip("Specifies if the inventory can equip an item that doesn't have any consumable items left.")]
[SerializeField] protected bool m_CanEquipEmptyItem = true;
[Tooltip("Should the character rotate to face the target during use?")]
[SerializeField] protected bool m_FaceTarget = true;
[Tooltip("The amount of extra time it takes for the ability to stop after use.")]
[SerializeField] protected float m_StopUseAbilityDelay = 1f;
[Tooltip("Specifies if the item should wait for the OnAnimatorItemUse animation event or wait for the specified duration before being used.")]
[SerializeField] protected AnimationEventTrigger m_UseEvent = new AnimationEventTrigger(true, 0.2f);
[Tooltip("Specifies if the item should wait for the OnAnimatorItemUseComplete animation event or wait for the specified duration before completing the use.")]
[SerializeField] protected AnimationEventTrigger m_UseCompleteEvent = new AnimationEventTrigger(false, 0.05f);
[Tooltip("Does the item require root motion position during use?")]
[SerializeField] protected bool m_ForceRootMotionPosition;
[Tooltip("Does the item require root motion rotation during use?")]
[SerializeField] protected bool m_ForceRootMotionRotation;
[Tooltip("The name of the attribute that should be adjusted when the item is used.")]
[SerializeField] protected string m_UseAttributeName;
[Tooltip("The amount to adjust the Use Attribute by when the item is used.")]
[SerializeField] protected float m_UseAttributeAmount;
[Tooltip("Should the item be dropped when the use attribute is depleted?")]
[SerializeField] protected bool m_DropWhenUseDepleted;
[Tooltip("The name of the character attribute that should be adjusted when the item is used.")]
[SerializeField] protected string m_CharacterUseAttributeName;
[Tooltip("The amount to adjust the Character Use Attribute by when the item is used.")]
[SerializeField] protected float m_CharacterUseAttributeAmount;
[Tooltip("Should the audio play when the item starts to be used? If false it will be played when the item is used.")]
[SerializeField] protected bool m_PlayAudioOnStartUse = false;
[Tooltip("Specifies the animator and audio state that should be triggered when the item is used.")]
[SerializeField] protected AnimatorAudioStateSet m_UseAnimatorAudioStateSet = new AnimatorAudioStateSet(2);
public float UseRate { get { return m_UseRate; } set { m_UseRate = value; } }
public bool CanEquipEmptyItem { get { return m_CanEquipEmptyItem; } set { m_CanEquipEmptyItem = value; } }
public bool FaceTarget { get { return m_FaceTarget; } set { m_FaceTarget = value; } }
public float StopUseAbilityDelay { get { return m_StopUseAbilityDelay; } set { m_StopUseAbilityDelay = value; } }
public AnimationEventTrigger UseEvent { get { return m_UseEvent; } set { m_UseEvent = value; } }
public AnimationEventTrigger UseCompleteEvent { get { return m_UseCompleteEvent; } set { m_UseCompleteEvent = value; } }
public bool ForceRootMotionPosition { get { return m_ForceRootMotionPosition; } set { m_ForceRootMotionPosition = value; } }
public bool ForceRootMotionRotation { get { return m_ForceRootMotionRotation; } set { m_ForceRootMotionRotation = value; } }
public string UseAttributeName
{
get { return m_UseAttributeName; }
set
{
m_UseAttributeName = value;
if (Application.isPlaying) {
if (!string.IsNullOrEmpty(m_UseAttributeName) && m_AttributeManager != null) {
m_UseAttribute = m_AttributeManager.GetAttribute(m_UseAttributeName);
} else {
m_UseAttribute = null;
}
}
}
}
public float UseAttributeAmount { get { return m_UseAttributeAmount; } set { m_UseAttributeAmount = value; } }
public bool DropWhenUseDepleted { get { return m_DropWhenUseDepleted; } set { m_DropWhenUseDepleted = value; } }
public string CharacterUseAttributeName
{
get { return m_CharacterUseAttributeName; }
set
{
m_CharacterUseAttributeName = value;
if (Application.isPlaying) {
if (!string.IsNullOrEmpty(m_CharacterUseAttributeName) && m_CharacterUseAttributeManager != null) {
m_CharacterUseAttribute = m_CharacterUseAttributeManager.GetAttribute(m_CharacterUseAttributeName);
} else {
m_CharacterUseAttribute = null;
}
}
}
}
public float CharacterUseAttributeAmount { get { return m_CharacterUseAttributeAmount; } set { m_CharacterUseAttributeAmount = value; } }
public bool PlayAudioOnStartUse { get { return m_PlayAudioOnStartUse; } set { m_PlayAudioOnStartUse = value; } }
public AnimatorAudioStateSet UseAnimatorAudioStateSet { get { return m_UseAnimatorAudioStateSet; } set { m_UseAnimatorAudioStateSet = value; } }
protected ILookSource m_LookSource;
private AttributeManager m_AttributeManager;
private AttributeManager m_CharacterUseAttributeManager;
protected Attribute m_UseAttribute;
protected Attribute m_CharacterUseAttribute;
protected float m_NextAllowedUseTime;
private bool m_InUse;
/// <summary>
/// Initialize the default values.
/// </summary>
protected override void Awake()
{
base.Awake();
// The item may have been added at runtime in which case the look source has already been populated.
var characterLocomotion = m_Character.GetCachedComponent<UltimateCharacterLocomotion>();
m_LookSource = characterLocomotion.LookSource;
m_UseAnimatorAudioStateSet.DeserializeAnimatorAudioStateSelector(m_Item, characterLocomotion);
m_UseAnimatorAudioStateSet.Awake(m_Item.gameObject);
m_AttributeManager = GetComponent<AttributeManager>();
if (m_AttributeManager != null) {
m_UseAttribute = m_AttributeManager.GetAttribute(m_UseAttributeName);
if (m_UseAttribute != null) {
EventHandler.RegisterEvent(m_UseAttribute, "OnAttributeReachedDestinationValue", UseDepleted);
}
}
m_CharacterUseAttributeManager = m_Character.GetCachedComponent<AttributeManager>();
if (m_CharacterUseAttributeManager != null) {
m_CharacterUseAttribute = m_CharacterUseAttributeManager.GetAttribute(m_CharacterUseAttributeName);
}
m_NextAllowedUseTime = Time.time;
EventHandler.RegisterEvent<ILookSource>(m_Character, "OnCharacterAttachLookSource", OnAttachLookSource);
}
/// <summary>
/// A new ILookSource object has been attached to the character.
/// </summary>
/// <param name="lookSource">The ILookSource object attached to the character.</param>
private void OnAttachLookSource(ILookSource lookSource)
{
m_LookSource = lookSource;
}
/// <summary>
/// Returns the ItemIdentifier which can be used by the item.
/// </summary>
/// <returns>The ItemIdentifier which can be used by the item.</returns>
public virtual IItemIdentifier GetConsumableItemIdentifier() { return null; }
/// <summary>
/// Returns the amount of UsableItemIdentifier which has been consumed by the UsableItem.
/// </summary>
/// <returns>The amount consumed of the UsableItemIdentifier.</returns>
public virtual int GetConsumableItemIdentifierAmount() { return 0; }
/// <summary>
/// Sets the UsableItemIdentifier amount on the UsableItem.
/// </summary>
/// <param name="amount">The amount to set the UsableItemIdentifier to.</param>
public virtual void SetConsumableItemIdentifierAmount(int amount) { }
/// <summary>
/// Removes the amount of UsableItemIdentifier which has been consumed by the UsableItem.
/// </summary>
public virtual void RemoveConsumableItemIdentifierAmount() { }
/// <summary>
/// Can the item be used?
/// </summary>
/// <param name="itemAbility">The itemAbility that is trying to use the item.</param>
/// <param name="abilityState">The state of the Use ability when calling CanUseItem.</param>
/// <returns>True if the item can be used.</returns>
public virtual bool CanUseItem(ItemAbility itemAbility, UseAbilityState abilityState)
{
// Prevent the item from being used too soon.
if (Time.time < m_NextAllowedUseTime) {
return false;
}
// The attribute may prevent the item from being used (such as if the character doesn't have enough stamina to use the item).
if ((m_UseAttribute != null && !m_UseAttribute.IsValid(abilityState == UseAbilityState.Start ? 0 : -m_UseAttributeAmount)) ||
(m_CharacterUseAttribute != null && !m_CharacterUseAttribute.IsValid(abilityState == UseAbilityState.Start ? 0 : -m_CharacterUseAttributeAmount))) {
// The item can no longer be used. Stop the ability.
if (abilityState != UseAbilityState.Start) {
itemAbility.StopAbility();
}
return false;
}
return true;
}
/// <summary>
/// Can the ability be started?
/// </summary>
/// <param name="ability">The ability that is trying to start.</param>
/// <returns>True if the ability can be started.</returns>
public virtual bool CanStartAbility(Ability ability) { return true; }
/// <summary>
/// Starts the item use.
/// </summary>
/// <param name="itemAbility">The item ability that is using the item.</param>
public virtual void StartItemUse(ItemAbility useAbility)
{
// The use AnimatorAudioState is starting.
m_UseAnimatorAudioStateSet.StartStopStateSelection(true);
m_InUse = m_UseAnimatorAudioStateSet.NextState();
if (m_InUse && m_PlayAudioOnStartUse) {
var visibleObject = m_Item.GetVisibleObject() != null ? m_Item.GetVisibleObject() : m_Character;
m_UseAnimatorAudioStateSet.PlayAudioClip(visibleObject);
}
}
/// <summary>
/// Uses the item.
/// </summary>
public virtual void UseItem()
{
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo == null || m_NetworkInfo.IsLocalPlayer()) {
#endif
m_NextAllowedUseTime = Time.time + m_UseRate;
if (m_UseAttribute != null) {
m_UseAttribute.Value -= m_UseAttributeAmount;
}
if (m_CharacterUseAttribute != null) {
m_CharacterUseAttribute.Value -= m_CharacterUseAttributeAmount;
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
}
#endif
// Optionally play a use sound based upon the use animation.
if (!m_PlayAudioOnStartUse) {
var visibleObject = m_Item.GetVisibleObject() != null ? m_Item.GetVisibleObject() : m_Character;
m_UseAnimatorAudioStateSet.PlayAudioClip(visibleObject);
}
}
/// <summary>
/// Is the item in use?
/// </summary>
/// <returns>True if the item is in use.</returns>
public virtual bool IsItemInUse()
{
return m_InUse;
}
/// <summary>
/// Is the item waiting to be used? This will return true if the item is waiting to be charged or pulled back.
/// </summary>
/// <returns>Returns true if the item is waiting to be used.</returns>
public virtual bool IsItemUsePending() { return false; }
/// <summary>
/// Returns the substate index that the item should be in.
/// </summary>
/// <returns>the substate index that the item should be in.</returns>
public virtual int GetItemSubstateIndex()
{
return m_UseAnimatorAudioStateSet.GetItemSubstateIndex();
}
/// <summary>
/// Allows the item to update while it is being used.
/// </summary>
public virtual void UseItemUpdate() { }
/// <summary>
/// The item has been used.
/// </summary>
public virtual void ItemUseComplete() { }
/// <summary>
/// Tries to stop the item use.
/// </summary>
public virtual void TryStopItemUse() { }
/// <summary>
/// Can the item use be stopped?
/// </summary>
/// <returns>True if the item use can be stopped.</returns>
public virtual bool CanStopItemUse()
{
return true;
}
/// <summary>
/// Stops the item use.
/// </summary>
public virtual void StopItemUse()
{
m_InUse = false;
// The item has been used- inform the state set.
m_UseAnimatorAudioStateSet.StartStopStateSelection(false);
}
/// <summary>
/// The item has depleted its use attribute.
/// </summary>
private void UseDepleted()
{
if (!m_DropWhenUseDepleted) {
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, int.MaxValue, false);
m_Item.Drop(int.MaxValue, true);
}
/// <summary>
/// The GameObject has been destroyed.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
m_UseAnimatorAudioStateSet.OnDestroy();
EventHandler.UnregisterEvent<ILookSource>(m_Character, "OnCharacterAttachLookSource", OnAttachLookSource);
if (m_UseAttribute != null) {
EventHandler.UnregisterEvent(m_UseAttribute, "OnAttributeReachedDestinationValue", UseDepleted);
}
}
}
}