In this lesson, we’ll move the extension methods from InventoryService into the Inventory class and make them regular functions.
Step 1: Move the Engine\Services\InventoryService.cs functions
Cut-and-paste all the functions from InventoryService into the Inventory class.
Convert the extension methods to regular methods.
- Remove the “static” from these functions
- Remove the “this Inventory inventory” parameter
- Remove references to the old “inventory” parameter in these functions. Since these functions are inside the Inventory class, they can directly reference the properties.
Inventory.cs
using System.Collections.Generic;
using System.Linq;
using Engine.Shared;
using Newtonsoft.Json;
namespace Engine.Models
{
public class Inventory
{
#region Backing variables
private readonly List<GameItem> _backingInventory =
new List<GameItem>();
private readonly List<GroupedInventoryItem> _backingGroupedInventoryItems =
new List<GroupedInventoryItem>();
#endregion
#region Properties
public IReadOnlyList<GameItem> Items => _backingInventory.AsReadOnly();
[JsonIgnore]
public IReadOnlyList<GroupedInventoryItem> GroupedInventory =>
_backingGroupedInventoryItems.AsReadOnly();
[JsonIgnore]
public IReadOnlyList<GameItem> Weapons =>
_backingInventory.ItemsThatAre(GameItem.ItemCategory.Weapon).AsReadOnly();
[JsonIgnore]
public IReadOnlyList<GameItem> Consumables =>
_backingInventory.ItemsThatAre(GameItem.ItemCategory.Consumable).AsReadOnly();
[JsonIgnore]
public bool HasConsumable => Consumables.Any();
#endregion
#region Constructors
public Inventory(IEnumerable<GameItem> items = null)
{
if(items == null)
{
return;
}
foreach(GameItem item in items)
{
_backingInventory.Add(item);
AddItemToGroupedInventory(item);
}
}
#endregion
#region Public functions
public bool HasAllTheseItems(IEnumerable<ItemQuantity> items)
{
return items.All(item => Items.Count(i => i.ItemTypeID == item.ItemID) >= item.Quantity);
}
public Inventory AddItem(GameItem item)
{
return AddItems(new List<GameItem> { item });
}
public Inventory AddItems(IEnumerable<GameItem> items)
{
return new Inventory(Items.Concat(items));
}
public Inventory RemoveItem(GameItem item)
{
return RemoveItems(new List<GameItem> { item });
}
public Inventory RemoveItems(IEnumerable<GameItem> items)
{
// REFACTOR: Look for a cleaner solution, with fewer temporary variables.
List<GameItem> workingInventory = Items.ToList();
IEnumerable<GameItem> itemsToRemove = items.ToList();
foreach (GameItem item in itemsToRemove)
{
workingInventory.Remove(item);
}
return new Inventory(workingInventory);
}
public Inventory RemoveItems(IEnumerable<ItemQuantity> itemQuantities)
{
// REFACTOR
Inventory workingInventory = new Inventory(Items);
foreach (ItemQuantity itemQuantity in itemQuantities)
{
for (int i = 0; i < itemQuantity.Quantity; i++)
{
workingInventory =
workingInventory
.RemoveItem(workingInventory
.Items
.First(item => item.ItemTypeID == itemQuantity.ItemID));
}
}
return workingInventory;
}
#endregion
#region Private functions
// REFACTOR: Look for a better way to do this (extension method?)
private void AddItemToGroupedInventory(GameItem item)
{
if(item.IsUnique)
{
_backingGroupedInventoryItems.Add(new GroupedInventoryItem(item, 1));
}
else
{
if(_backingGroupedInventoryItems.All(gi => gi.Item.ItemTypeID != item.ItemTypeID))
{
_backingGroupedInventoryItems.Add(new GroupedInventoryItem(item, 0));
}
_backingGroupedInventoryItems.First(gi => gi.Item.ItemTypeID == item.ItemTypeID).Quantity++;
}
}
#endregion
}
}
Step 2: Delete Engine\Services\InventoryService.cs
Now that all its functions have been moved, we can delete this class.
Step 3: Clean Engine\Models\LivingEntity.cs
Remove the using directive for “Engine.Services”
LivingEntity.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Newtonsoft.Json;
namespace Engine.Models
{
public abstract class LivingEntity : INotifyPropertyChanged
{
#region Properties
private GameItem _currentWeapon;
private GameItem _currentConsumable;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<PlayerAttribute> Attributes { get; } =
new ObservableCollection<PlayerAttribute>();
public string Name { get; }
public int CurrentHitPoints { get; private set; }
public int MaximumHitPoints { get; protected set; }
public int Gold { get; private set; }
public int Level { get; protected set; }
public Inventory Inventory { get; private set; }
public GameItem CurrentWeapon
{
get => _currentWeapon;
set
{
if (_currentWeapon != null)
{
_currentWeapon.Action.OnActionPerformed -= RaiseActionPerformedEvent;
}
_currentWeapon = value;
if (_currentWeapon != null)
{
_currentWeapon.Action.OnActionPerformed += RaiseActionPerformedEvent;
}
}
}
public GameItem CurrentConsumable
{
get => _currentConsumable;
set
{
if(_currentConsumable != null)
{
_currentConsumable.Action.OnActionPerformed -= RaiseActionPerformedEvent;
}
_currentConsumable = value;
if (_currentConsumable != null)
{
_currentConsumable.Action.OnActionPerformed += RaiseActionPerformedEvent;
}
}
}
[JsonIgnore]
public bool IsAlive => CurrentHitPoints > 0;
[JsonIgnore]
public bool IsDead => !IsAlive;
#endregion
public event EventHandler<string> OnActionPerformed;
public event EventHandler OnKilled;
protected LivingEntity(string name, int maximumHitPoints, int currentHitPoints,
IEnumerable<PlayerAttribute> attributes, int gold, int level = 1)
{
Name = name;
MaximumHitPoints = maximumHitPoints;
CurrentHitPoints = currentHitPoints;
Gold = gold;
Level = level;
foreach (PlayerAttribute attribute in attributes)
{
Attributes.Add(attribute);
}
Inventory = new Inventory();
}
public void UseCurrentWeaponOn(LivingEntity target)
{
CurrentWeapon.PerformAction(this, target);
}
public void UseCurrentConsumable()
{
CurrentConsumable.PerformAction(this, this);
RemoveItemFromInventory(CurrentConsumable);
}
public void TakeDamage(int hitPointsOfDamage)
{
CurrentHitPoints -= hitPointsOfDamage;
if(IsDead)
{
CurrentHitPoints = 0;
RaiseOnKilledEvent();
}
}
public void Heal(int hitPointsToHeal)
{
CurrentHitPoints += hitPointsToHeal;
if(CurrentHitPoints > MaximumHitPoints)
{
CurrentHitPoints = MaximumHitPoints;
}
}
public void CompletelyHeal()
{
CurrentHitPoints = MaximumHitPoints;
}
public void ReceiveGold(int amountOfGold)
{
Gold += amountOfGold;
}
public void SpendGold(int amountOfGold)
{
if(amountOfGold > Gold)
{
throw new ArgumentOutOfRangeException($"{Name} only has {Gold} gold, and cannot spend {amountOfGold} gold");
}
Gold -= amountOfGold;
}
public void AddItemToInventory(GameItem item)
{
Inventory = Inventory.AddItem(item);
}
public void RemoveItemFromInventory(GameItem item)
{
Inventory = Inventory.RemoveItem(item);
}
public void RemoveItemsFromInventory(IEnumerable<ItemQuantity> itemQuantities)
{
Inventory = Inventory.RemoveItems(itemQuantities);
}
#region Private functions
private void RaiseOnKilledEvent()
{
OnKilled?.Invoke(this, new System.EventArgs());
}
private void RaiseActionPerformedEvent(object sender, string result)
{
OnActionPerformed?.Invoke(this, result);
}
#endregion
}
}
Step 4: Test the game
NEXT LESSON: Lesson 19.13: Remove CombatService dependency from model classes
PREVIOUS LESSON: Lesson 19.11: Move common functions to SOSCSRPG.Core