/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.Inventory
{
using Opsive.Shared.Events;
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
using Opsive.Shared.Game;
#endif
using Opsive.Shared.Inventory;
using Opsive.UltimateCharacterController.Events;
using Opsive.UltimateCharacterController.Items;
using Opsive.UltimateCharacterController.Items.Actions;
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
using Opsive.UltimateCharacterController.Networking;
using Opsive.UltimateCharacterController.Networking.Character;
#endif
using System.Collections.Generic;
using UnityEngine;
///
/// Provides a common base class for any character Inventory.
///
public abstract class InventoryBase : MonoBehaviour
{
[Tooltip("Should all of the IItemIdentifier be removed when the character dies?")]
[SerializeField] protected bool m_RemoveAllOnDeath = true;
[Tooltip("Should the default loadout be loaded when the character respawns?")]
[SerializeField] protected bool m_LoadDefaultLoadoutOnRespawn = true;
[Tooltip("The name of the state when the inventory is unequipped.")]
[SerializeField] protected string m_UnequippedStateName = "Unequipped";
[Tooltip("Unity event that is invoked when an item is initially added to the inventory.")]
[SerializeField] protected UnityItemEvent m_OnAddItemEvent;
[Tooltip("Unity event that is invoked when an IItemIdentifier is picked up.")]
[SerializeField] protected UnityItemIdentifierFloatBoolBoolEvent m_OnPickupItemIdentifierEvent;
[Tooltip("Unity event that is invoked when an item is picked up.")]
[SerializeField] protected UnityItemFloatBoolBoolEvent m_OnPickupItemEvent;
[Tooltip("Unity event that is invoked when an item is equipped.")]
[SerializeField] protected UnityItemIntEvent m_OnEquipItemEvent;
[Tooltip("Unity event that is invoked when an IItemIdentifier is adjusted.")]
[SerializeField] protected UnityItemIdentifierFloatEvent m_OnAdjustItemIdentifierAmountEvent;
[Tooltip("Unity event that is invoked when an item is unequipped.")]
[SerializeField] protected UnityItemIntEvent m_OnUnequipItemEvent;
[Tooltip("Unity event that is invoked when an item is removed.")]
[SerializeField] protected UnityItemIntEvent m_OnRemoveItemEvent;
public bool RemoveAllOnDeath { get { return m_RemoveAllOnDeath; } set { m_RemoveAllOnDeath = value; } }
public bool LoadDefaultLoadoutOnRespawn { get { return m_LoadDefaultLoadoutOnRespawn; } set { m_LoadDefaultLoadoutOnRespawn = value; } }
public string UnequippedStateName { get { return m_UnequippedStateName; } set { m_UnequippedStateName = value; } }
public UnityItemEvent OnAddItemEvent { get { return m_OnAddItemEvent; } set { m_OnAddItemEvent = value; } }
public UnityItemIdentifierFloatBoolBoolEvent OnPickupItemIdentifierEvent { get { return m_OnPickupItemIdentifierEvent; } set { m_OnPickupItemIdentifierEvent = value; } }
public UnityItemFloatBoolBoolEvent OnPickupItemEvent { get { return m_OnPickupItemEvent; } set { m_OnPickupItemEvent = value; } }
public UnityItemIntEvent OnEquipItemEvent { get { return m_OnEquipItemEvent; } set { m_OnEquipItemEvent = value; } }
public UnityItemIdentifierFloatEvent OnAdjustItemIdentifierAmountEvent { get { return m_OnAdjustItemIdentifierAmountEvent; } set { m_OnAdjustItemIdentifierAmountEvent = value; } }
public UnityItemIntEvent OnUnequipItemEvent { get { return m_OnUnequipItemEvent; } set { m_OnUnequipItemEvent = value; } }
public UnityItemIntEvent OnRemoveItemEvent { get { return m_OnRemoveItemEvent; } set { m_OnRemoveItemEvent = value; } }
protected GameObject m_GameObject;
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
private INetworkInfo m_NetworkInfo;
private INetworkCharacter m_NetworkCharacter;
#endif
protected int m_SlotCount = 1;
private List- m_AllItems = new List
- ();
private List m_AllItemIdentifiers = new List();
public int SlotCount { get {
#if UNITY_EDITOR
if (!Application.isPlaying) { DetermineSlotCount(); }
#endif
return m_SlotCount;
} }
///
/// Initialize the default values.
///
protected virtual void Awake()
{
m_GameObject = gameObject;
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
m_NetworkInfo = m_GameObject.GetCachedComponent();
m_NetworkCharacter = m_GameObject.GetCachedComponent();
#endif
DetermineSlotCount();
EventHandler.RegisterEvent(m_GameObject, "OnDeath", OnDeath);
EventHandler.RegisterEvent(m_GameObject, "OnRespawn", OnRespawn);
}
///
/// Determines the number of slots on the character.
///
public void DetermineSlotCount()
{
// The number of slots depends on the maximum number of ItemSlot IDs.
var itemSlots = GetComponentsInChildren(true);
for (int i = 0; i < itemSlots.Length; ++i) {
if (m_SlotCount <= itemSlots[i].ID) {
m_SlotCount = itemSlots[i].ID + 1;
}
}
}
///
/// Loads the default loadout.
///
protected virtual void Start()
{
// The character starts out unequipped.
if (!string.IsNullOrEmpty(m_UnequippedStateName)) {
StateSystem.StateManager.SetState(m_GameObject, m_UnequippedStateName, true);
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo == null || m_NetworkInfo.IsLocalPlayer()) {
if (m_NetworkInfo != null) {
// Load the default loadout on the network first to ensure it is received before any equip events.
m_NetworkCharacter.LoadDefaultLoadout();
}
#endif
LoadDefaultLoadout();
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
}
#endif
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterSnapAnimator");
}
///
/// Pick up each ItemIdentifier within the DefaultLoadout.
///
public abstract void LoadDefaultLoadout();
///
/// Determines if the character has the specified item.
///
/// The item to check against.
/// True if the character has the item.
public bool HasItem(Item item) { return HasItemInternal(item); }
///
/// Internal method which determines if the character has the specified item.
///
/// The item to check against.
/// True if the character has the item.
protected abstract bool HasItemInternal(Item item);
///
/// Adds the item to the inventory. This does not add the actual ItemIdentifier - PickupItem does that.
///
/// The Item to add.
/// Can the item be equipped immediately?
/// Should the item be force equipped?
public void AddItem(Item item, bool immediateEquip, bool forceEquip)
{
if (AddItemInternal(item)) {
m_AllItems.Add(item);
// Notify those interested that an item has been added.
EventHandler.ExecuteEvent(m_GameObject, "OnInventoryAddItem", item);
if (m_OnAddItemEvent != null) {
m_OnAddItemEvent.Invoke(item);
}
// The ItemIdentifier event should also be called in cases where the amount is greater than 0.
// This allows the ItemIdentifier to be picked up before the item has been added.
if (GetItemIdentifierAmount(item.ItemIdentifier) > 0) {
ItemIdentifierPickedUp(item.ItemIdentifier, 1, item.SlotID, immediateEquip, forceEquip);
}
}
}
///
/// Internal method which adds the item to the Inventory. This does not add the actual IItemIdentifier - PickupItem does that.
///
/// The item to add.
/// True if the item was added to the inventory.
protected abstract bool AddItemInternal(Item item);
///
/// Adds the specified amount of the ItemIdentifier to the inventory.
///
/// The ItemIdentifier to add.
/// The amount of ItemIdentifier to add.
/// The slot ID that picked up the item. A -1 value will indicate no specified slot.
/// Should the item be picked up immediately?
/// Should the item be force equipped?
/// True if the ItemIdentifier was picked up.
public bool Pickup(IItemIdentifier itemIdentifier, int amount, int slotID, bool immediatePickup, bool forceEquip)
{
return Pickup(itemIdentifier, amount, slotID, immediatePickup, forceEquip, true);
}
///
/// Adds the specified amount of the ItemIdentifier to the inventory.
///
/// The ItemIdentifier to add.
/// The amount of ItemIdentifier to add.
/// The slot ID that picked up the item. A -1 value will indicate no specified slot.
/// Should the item be picked up immediately?
/// Should the item be force equipped?
/// Should other objects be notified that the ItemIdentifier was picked up?
/// True if the ItemIdentifier was picked up.
public bool Pickup(IItemIdentifier itemIdentifier, int amount, int slotID, bool immediatePickup, bool forceEquip, bool notifyOnPickup)
{
// Prevent pickup when the inventory isn't enabled.
if (itemIdentifier == null || !enabled || amount == 0) {
return false;
}
var pickedUp = PickupInternal(itemIdentifier, amount);
// Notify those interested that an item has been picked up.
if (pickedUp && notifyOnPickup) {
if (slotID == -1) {
// Find the slot that the item belongs to (if any).
for (int i = 0; i < m_SlotCount; ++i) {
if (GetItem(itemIdentifier, i) != null) {
ItemIdentifierPickedUp(itemIdentifier, amount, i, immediatePickup, forceEquip);
slotID = i;
}
}
if (slotID == -1) {
// The ItemIdentifier doesn't correspond to an item so execute the event once.
ItemIdentifierPickedUp(itemIdentifier, amount, -1, immediatePickup, forceEquip);
}
} else {
ItemIdentifierPickedUp(itemIdentifier, amount, slotID, immediatePickup, forceEquip);
}
// If the slot ID isn't -1 then AddItem has already run. Add the item if it hasn't already been added. This will occur if the item is removed
// and then later added again.
if (slotID != -1) {
var item = GetItem(itemIdentifier, slotID);
if (item != null && !m_AllItems.Contains(item)) {
m_AllItems.Add(item);
}
}
}
return pickedUp;
}
///
/// Internal method which adds the specified amount of the ItemIdentifier to the inventory.
///
/// The IItemIdentifier to add.
/// The amount of ItemIdentifier to add.
/// True if the ItemIdentifier was picked up successfully.
protected abstract bool PickupInternal(IItemIdentifier itemIdentifier, int amount);
///
/// The ItemIdentifier has been picked up. Notify interested objects.
///
/// The ItemIdentifier that was picked up.
/// The number of ItemIdentifier picked up.
/// The ID of the slot which the item belongs to.
/// Was the item be picked up immediately?
/// Should the item be force equipped?
protected void ItemIdentifierPickedUp(IItemIdentifier itemIdentifier, int amount, int slotID, bool immediatePickup, bool forceEquip)
{
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.ItemIdentifierPickup(itemIdentifier.ID, amount, slotID, immediatePickup, forceEquip);
}
#endif
EventHandler.ExecuteEvent(m_GameObject, "OnInventoryPickupItemIdentifier", itemIdentifier, amount, immediatePickup, forceEquip);
if (m_OnPickupItemIdentifierEvent != null) {
m_OnPickupItemIdentifierEvent.Invoke(itemIdentifier, amount, immediatePickup, forceEquip);
}
if (slotID != -1) {
var item = GetItem(itemIdentifier, slotID);
if (item != null) {
item.Pickup();
EventHandler.ExecuteEvent(m_GameObject, "OnInventoryPickupItem", item, amount, immediatePickup, forceEquip);
if (m_OnPickupItemEvent != null) {
m_OnPickupItemEvent.Invoke(item, amount, immediatePickup, forceEquip);
}
}
}
if (!m_AllItemIdentifiers.Contains(itemIdentifier)) {
m_AllItemIdentifiers.Add(itemIdentifier);
}
}
///
/// Returns the active item in the specified slot.
///
/// The ID of the slot.
/// The active item which occupies the specified slot. Can be null.
public Item GetActiveItem(int slotID) { return GetActiveItemInternal(slotID); }
///
/// Internal method which returns the active item in the specified slot.
///
/// The ID of the slot which the item belongs to.
/// The active item which occupies the specified slot. Can be null.
protected abstract Item GetActiveItemInternal(int slotID);
///
/// Returns the item that corresponds to the specified ItemIdentifier.
///
/// The ItemIdentifier of the item.
/// The ID of the slot which the item belongs to.
/// The item which occupies the specified slot. Can be null.
public Item GetItem(IItemIdentifier itemIdentifier, int slotID) { return GetItemInternal(itemIdentifier, slotID); }
///
/// Internal method which returns the item that corresponds to the specified IItemIdentifier.
///
/// The ItemIdentifier of the item.
/// The ID of the slot which the item belongs to.
/// The item which occupies the specified slot. Can be null.
protected abstract Item GetItemInternal(IItemIdentifier itemIdentifier, int slotID);
///
/// Returns a list of all of the items in the inventory.
///
/// A list of all of the items in the inventory.
public List
- GetAllItems() { return m_AllItems; }
///
/// Returns a list of all of the ItemIdentifier in the inventory. Only used by the editor for the inventory inspector.
///
/// A list of all of the ItemIdentifier in the inventory.
public List GetAllItemIdentifiers() { return m_AllItemIdentifiers; }
///
/// Equips the ItemIdentifier in the specified slot.
///
/// The ItemIdentifier to equip.
/// The ID of the slot.
/// Is the item being equipped immediately? Immediate equips will occur from the default loadout or quickly switching to the item.
public void EquipItem(IItemIdentifier itemIdentifier, int slotID, bool immediateEquip)
{
if (itemIdentifier == null) {
return;
}
var currentItem = GetActiveItem(slotID);
if (currentItem != null && currentItem.ItemIdentifier != itemIdentifier) {
UnequipItem(slotID);
}
var item = EquipItemInternal(itemIdentifier, slotID);
if (item != null) {
item.Equip(immediateEquip);
// Notify those interested that an item has been equipped.
EventHandler.ExecuteEvent(m_GameObject, "OnInventoryEquipItem", item, slotID);
if (m_OnEquipItemEvent != null) {
m_OnEquipItemEvent.Invoke(item, slotID);
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.EquipUnequipItem(itemIdentifier.ID, slotID, true);
}
#endif
if (!string.IsNullOrEmpty(m_UnequippedStateName)) {
StateSystem.StateManager.SetState(m_GameObject, m_UnequippedStateName, false);
}
}
}
///
/// Internal method which equips the ItemIdentifier in the specified slot.
///
/// The ItemIdentifier to equip.
/// The ID of the slot.
/// The item which corresponds to the ItemIdentifier. Can be null.
protected abstract Item EquipItemInternal(IItemIdentifier itemIdentifier, int slotID);
///
/// Unequips the specified ItemIdentifier in the specified slot.
///
/// The ItemIdentifier to unequip. If the ItemIdentifier isn't currently equipped then no changes will be made.
/// The ID of the slot.
public void UnequipItem(IItemIdentifier itemIdentifier, int slotID)
{
// No need to unequip if the item is already unequipped or the ItemIdentifier don't match.
var currentItem = GetActiveItem(slotID);
if (currentItem == null || currentItem.ItemIdentifier != itemIdentifier) {
return;
}
UnequipItem(slotID);
}
///
/// Unequips the item in the specified slot.
///
/// The ID of the slot.
public void UnequipItem(int slotID)
{
// No need to unequip if the item is already unequipped.
var currentItem = GetActiveItem(slotID);
if (currentItem == null) {
return;
}
var item = UnequipItemInternal(slotID);
if (item != null) {
item.Unequip();
// Notify those interested that an item has been unequipped.
EventHandler.ExecuteEvent(m_GameObject, "OnInventoryUnequipItem", item, slotID);
if (m_OnUnequipItemEvent != null) {
m_OnUnequipItemEvent.Invoke(item, slotID);
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.EquipUnequipItem(item.ItemIdentifier.ID, slotID, false);
}
#endif
// Optionally enable a state when the inventory is unequipped.
if (!string.IsNullOrEmpty(m_UnequippedStateName)) {
var unequipped = true;
for (int i = 0; i < m_SlotCount; ++i) {
if (i == slotID) {
continue;
}
if (GetActiveItem(i) != null) {
unequipped = false;
}
}
if (unequipped) {
StateSystem.StateManager.SetState(m_GameObject, m_UnequippedStateName, true);
}
}
}
}
///
/// Internal method which unequips the item in the specified slot.
///
/// The ID of the slot.
/// The item that was unequipped.
protected abstract Item UnequipItemInternal(int slotID);
///
/// Returns the amount of the specified ItemIdentifier.
///
/// The ItemIdentifier to get the amount of.
/// The amount of the specified ItemIdentifier.
public int GetItemIdentifierAmount(IItemIdentifier itemIdentifier) { if (itemIdentifier == null) { return 0; } return GetItemIdentifierAmountInternal(itemIdentifier); }
///
/// Internal method which returns the amount of the specified ItemIdentifier.
///
/// The ItemIdentifier to get the amount of.
/// The amount of the specified ItemIdentifier.
protected abstract int GetItemIdentifierAmountInternal(IItemIdentifier itemIdentifier);
///
/// Adjusts the amount of the specified ItemIdentifier.
///
/// The ItemIdentifier to adjust.
/// The amount of ItemIdentifier to adjust.
public void AdjustItemIdentifierAmount(IItemIdentifier itemIdentifier, int amount)
{
if (itemIdentifier == null || amount == 0) {
return;
}
AdjustItemIdentifierAmountInternal(itemIdentifier, amount);
// Notify those interested that an item has been adjusted.
var remaining = GetItemIdentifierAmount(itemIdentifier);
EventHandler.ExecuteEvent(m_GameObject, "OnInventoryAdjustItemIdentifierAmount", itemIdentifier, remaining);
if (m_OnAdjustItemIdentifierAmountEvent != null) {
m_OnAdjustItemIdentifierAmountEvent.Invoke(itemIdentifier, remaining);
}
}
///
/// Internal method which adjusts the amount of the specified ItemIdentifier.
///
/// The ItemIdentifier to adjust.
/// The amount of ItemIdentifier to adjust.
protected abstract void AdjustItemIdentifierAmountInternal(IItemIdentifier itemIdentifier, int amount);
///
/// Removes the ItemIdentifier from the inventory.
///
/// The ItemIdentifier to remove.
/// The ID of the slot.
/// The amount of the ItemIdentnfier that should be removed.
/// Should the item be dropped when removed?
public void RemoveItem(IItemIdentifier itemIdentifier, int slotID, int amount, bool drop)
{
var item = GetItem(itemIdentifier, slotID);
if (item != null) {
// The item should be dropped before unequipped so the drop position will be correct.
if (drop) {
item.Drop(amount, false);
}
// An equipped item needs to be unequipped.
UnequipItem(itemIdentifier, slotID);
// If the item isn't dropped then it is removed immediately.
if (!drop) {
item.Remove();
}
if (item.DropConsumableItems) {
var itemActions = item.ItemActions;
if (itemActions != null) {
IUsableItem usableItem;
IItemIdentifier consumableItemIdentifier;
for (int i = 0; i < itemActions.Length; ++i) {
if (((usableItem = itemActions[i] as IUsableItem) != null) && (consumableItemIdentifier = usableItem.GetConsumableItemIdentifier()) != null) {
usableItem.RemoveConsumableItemIdentifierAmount();
// Any consumable ItemIdentifier should also be removed if there are no more of the same items remaining.
if (GetItemIdentifierAmount(itemIdentifier) == 1) {
RemoveItemIdentifierInternal(consumableItemIdentifier, slotID, amount);
m_AllItemIdentifiers.Remove(consumableItemIdentifier);
}
// Notify those interested of the removed amount.
SendItemIdentifierAdjustmentEvents(consumableItemIdentifier);
}
}
}
}
m_AllItems.Remove(item);
}
// The ItemIdentifier should be removed from the inventory.
RemoveItemIdentifierInternal(itemIdentifier, slotID, amount);
if (GetItemIdentifierAmount(itemIdentifier) == 0) {
m_AllItemIdentifiers.Remove(itemIdentifier);
}
// Notify those interested that the item will be removed.
if (item != null) {
EventHandler.ExecuteEvent(m_GameObject, "OnInventoryRemoveItem", item, slotID);
if (m_OnRemoveItemEvent != null) {
m_OnRemoveItemEvent.Invoke(item, slotID);
}
} else {
SendItemIdentifierAdjustmentEvents(itemIdentifier);
}
}
///
/// Sends the ItemIdentifier adjustment events.
///
/// The ItemIdentifier to remove.
private void SendItemIdentifierAdjustmentEvents(IItemIdentifier itemIdentifier)
{
// Notify those interested of the removed amount.
var amount = GetItemIdentifierAmount(itemIdentifier);
EventHandler.ExecuteEvent(m_GameObject, "OnInventoryAdjustItemIdentifierAmount", itemIdentifier, amount);
if (m_OnAdjustItemIdentifierAmountEvent != null) {
m_OnAdjustItemIdentifierAmountEvent.Invoke(itemIdentifier, amount);
}
if (amount == 0) {
m_AllItemIdentifiers.Remove(itemIdentifier);
}
}
///
/// Internal method which removes the ItemIdentifier from the inventory.
///
/// The ItemIdentifier to remove.
/// The ID of the slot.
/// The amount of the ItemIdentifier that should be removed.
protected abstract void RemoveItemIdentifierInternal(IItemIdentifier itemIdentifier, int slotID, int amount);
///
/// The character has died.
///
/// The position of the force.
/// The amount of force which killed the character.
/// The GameObject that killed the character.
private void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
{
enabled = false;
// The item's drop method will call RemoveItem within the inventory.
if (m_RemoveAllOnDeath) {
RemoveAllItems(true);
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.RemoveAllItems();
}
#endif
}
}
///
/// Removes all of the items from the inventory.
///
/// Should the item be dropped when removed?
public void RemoveAllItems(bool drop)
{
var allItems = GetAllItems();
for (int i = allItems.Count - 1; i >= 0; --i) {
// Multiple items may be dropped at the same time.
if (allItems.Count <= i) {
continue;
}
var itemIdentifier = allItems[i].ItemIdentifier;
var slotID = allItems[i].SlotID;
while (GetItemIdentifierAmount(itemIdentifier) > 0) {
RemoveItem(itemIdentifier, slotID, 1, drop);
}
}
}
///
/// The character has respawned.
///
private void OnRespawn()
{
enabled = true;
if (m_LoadDefaultLoadoutOnRespawn) {
LoadDefaultLoadout();
}
// Notify others that the inventory has respawned - allows EquipUnequip to equip any previously equipped items.
EventHandler.ExecuteEvent(m_GameObject, "OnInventoryRespawned");
}
///
/// The object has been destroyed.
///
private void OnDestroy()
{
EventHandler.UnregisterEvent(m_GameObject, "OnDeath", OnDeath);
EventHandler.UnregisterEvent(m_GameObject, "OnRespawn", OnRespawn);
}
}
}