I finally found some time to expand on this idea, though it doesn’t fully deal with things like multiple weapons…
First, made some changes to Equipment.cs. The backing field was changed to a Dictionary<int, EquipableItem>
and nearly all of the methods were changed to accomodate this shift in the field.
Note that if you’ve not completed Shops and Abilities or Dialogues and Quests,then some lines won’t compile. I’ll comment those
Equipment.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using GameDevTV.Saving;
using GameDevTV.Utils;
namespace GameDevTV.Inventories
{
/// <summary>
/// Provides a store for the items equipped to a player. Items are stored by
/// their equip locations.
///
/// This component should be placed on the GameObject tagged "Player".
/// </summary>
public class Equipment : MonoBehaviour, ISaveable, IPredicateEvaluator
{
// STATE
Dictionary<int, EquipableItem> equippedItems = new Dictionary<int, EquipableItem>();
// PUBLIC
/// <summary>
/// Broadcasts when the items in the slots are added/removed.
/// </summary>
public event Action equipmentUpdated;
/// <summary>
/// Return the item in the given slot
/// </summary>
public EquipableItem GetItemInSlot(int slot)
{
if (!equippedItems.ContainsKey(slot))
{
return null;
}
return equippedItems[slot];
}
/// <summary>
/// Add an item to the given slot.
/// </summary>
public void AddItem(int slot, EquipableItem item)
{
//This line won't compile without Shops and Abilities course code
Debug.Assert(item.CanEquip(item.GetAllowedEquipLocation(), this));
equippedItems[slot] = item;
if (equipmentUpdated != null)
{
equipmentUpdated();
}
}
/// <summary>
/// Remove the item for the given slot.
/// </summary>
public void RemoveItem(int slot)
{
equippedItems.Remove(slot);
if (equipmentUpdated != null)
{
equipmentUpdated();
}
}
/// <summary>
/// Enumerate through all the slots that currently contain items.
/// </summary>
public IEnumerable<int> GetAllPopulatedSlots()
{
return equippedItems.Keys;
}
// PRIVATE
object ISaveable.CaptureState()
{
var equippedItemsForSerialization = new Dictionary<int, string>();
foreach (var pair in equippedItems)
{
equippedItemsForSerialization[pair.Key] = pair.Value.GetItemID();
}
return equippedItemsForSerialization;
}
void ISaveable.RestoreState(object state)
{
equippedItems = new Dictionary<int, EquipableItem>();
var equippedItemsForSerialization = (Dictionary<int, string>)state;
foreach (var pair in equippedItemsForSerialization)
{
var item = (EquipableItem)InventoryItem.GetFromID(pair.Value);
if (item != null)
{
equippedItems[pair.Key] = item;
}
}
equipmentUpdated?.Invoke();
}
//This method won't compile without Dialogues and Quests code
public bool? Evaluate(string predicate, string[] parameters)
{
if (predicate == "HasItemEquiped")
{
foreach (var item in equippedItems.Values)
{
if (item.GetItemID() == parameters[0])
{
return true;
}
}
return false;
}
return null;
}
}
}
Next, I modified the EquipmentSlotUI.cs. Here, each slot gets a list of allowed equip locations, as well as an int representing the physical slot. Only an item with an AllowedEquipLocation within the list can be equipped.
EquipmentSlotUI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using GameDevTV.Core.UI.Dragging;
using GameDevTV.Inventories;
namespace GameDevTV.UI.Inventories
{
/// <summary>
/// An slot for the players equipment.
/// </summary>
public class EquipmentSlotUI : MonoBehaviour, IItemHolder, IDragContainer<InventoryItem>
{
// CONFIG DATA
[SerializeField] InventoryItemIcon icon = null;
[SerializeField] int slot = -1;
[SerializeField] private List<EquipLocation> allowedEquipLocations = new List<EquipLocation>();
// CACHE
Equipment playerEquipment;
// LIFECYCLE METHODS
private void Awake()
{
var player = GameObject.FindGameObjectWithTag("Player");
playerEquipment = player.GetComponent<Equipment>();
playerEquipment.equipmentUpdated += RedrawUI;
}
private void Start()
{
RedrawUI();
}
// PUBLIC
public int MaxAcceptable(InventoryItem item)
{
EquipableItem equipableItem = item as EquipableItem;
if (equipableItem == null) return 0;
if (!allowedEquipLocations.Contains(equipableItem.GetAllowedEquipLocation())) return 0;
if (!equipableItem.CanEquip(equipableItem.GetAllowedEquipLocation(), playerEquipment)) return 0;
if (GetItem() != null) return 0;
return 1;
}
public void AddItems(InventoryItem item, int number)
{
playerEquipment.AddItem(slot, (EquipableItem) item);
}
public InventoryItem GetItem()
{
return playerEquipment.GetItemInSlot(slot);
}
public int GetNumber()
{
if (GetItem() != null)
{
return 1;
}
else
{
return 0;
}
}
public void RemoveItems(int number)
{
playerEquipment.RemoveItem(slot);
}
// PRIVATE
void RedrawUI()
{
icon.SetItem(playerEquipment.GetItemInSlot(slot));
}
}
}
One final change was in Fighter’s UpdateWeapon method. Since the slots are ints instead of Equiplocations, we have to go through each slot to determine if one is a weapon. Note that this isn’t at all set up to handle multiple weapons, and will only handle the first weapon encountered.
private void UpdateWeapon()
{
WeaponConfig foundWeapon = null;
foreach (int slot in equipment.GetAllPopulatedSlots())
{
if (equipment.GetItemInSlot(slot) is WeaponConfig weaponConfig)
{
foundWeapon = weaponConfig;
break;
}
}
if (foundWeapon == null)
{
EquipWeapon(defaultWeapon);
}
else
{
EquipWeapon(foundWeapon);
}
}