Skip to content

Commit

Permalink
Unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Cory Leach committed Sep 7, 2022
1 parent ea1c716 commit 1b07312
Show file tree
Hide file tree
Showing 15 changed files with 405 additions and 151 deletions.
22 changes: 22 additions & 0 deletions Runtime/INotifyStatModifierSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Gameframe.StatSheet
{
public enum StatModifierSetActionType
{
Add,
Set
}

public struct StatModifierSetChangedArgs<TKey>
{
public StatModifierSetActionType Action;
public StatModifier<TKey> Modifier;
public StatModifier<TKey> Previous;
}

public delegate void StatModifierSetChangedEventHandler<TKey>(StatModifierSet<TKey> modifierSet, StatModifierSetChangedArgs<TKey> args);

public interface INotifyStatModifierSet<TKey> : IStatModifierSet<TKey>
{
event StatModifierSetChangedEventHandler<TKey> ModifiersChanged;
}
}
3 changes: 3 additions & 0 deletions Runtime/INotifyStatModifierSet.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Runtime/IStatModifierSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Gameframe.StatSheet
/// <typeparam name="TKey">Stat key type (Usually an enum)</typeparam>
public interface IStatModifierSet<TKey> : IEnumerable<StatModifier<TKey>>
{
float Modify(TKey statType, float inValue);
StatModifier<TKey> Get(TKey statName, StatMode mode);

IEnumerable<StatModifier<TKey>> Get(StatMode mode);
}
}
2 changes: 2 additions & 0 deletions Runtime/ListStatSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public struct StatValue : IStatValue<TKey>
/// Duplicate stat names should only be possible if manually added via the Unity inspector
/// </summary>
[SerializeField]
// ReSharper disable once FieldCanBeMadeReadOnly.Global
// If we make this readonly it seems to become unserializable so don't do that pls
protected List<StatValue> stats = new List<StatValue>();

/// <summary>
Expand Down
60 changes: 50 additions & 10 deletions Runtime/StatModel.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Gameframe.StatSheet
{
public class StatModel : StatModel<string> { }
/// <summary>
///
/// </summary>
public class StatModel : StatModel<string>
{
}

public class StatModel<TKey> : IReadOnlyStatSet<TKey>
{
protected IStatSet<TKey> _baseStats;

public IStatSet<TKey> BaseStats
{
get => _baseStats;
set => _baseStats = value;
}

public bool IsDirty { get; private set; }

protected ListStatSet<TKey> _statTotals = new ListStatSet<TKey>();
public IStatSet<TKey> StatTotals => _statTotals;

Expand All @@ -22,30 +31,63 @@ public IStatSet<TKey> BaseStats
public virtual void AddModifierSet(IStatModifierSet<TKey> modifierSet)
{
_modifiers.Add(modifierSet);
//If this modifier set is a notify set then subscribe for changes
if (modifierSet is INotifyStatModifierSet<TKey> notifySet)
{
notifySet.ModifiersChanged += NotifySetOnModifiersChanged;
}

IsDirty = true;
}

public virtual void RemoveModifier(IStatModifierSet<TKey> modifierSet)
{
_modifiers.Remove(modifierSet);
if (modifierSet is INotifyStatModifierSet<TKey> notifySet)
{
notifySet.ModifiersChanged -= NotifySetOnModifiersChanged;
}

IsDirty = true;
}

private void NotifySetOnModifiersChanged(StatModifierSet<TKey> set, StatModifierSetChangedArgs<TKey> args)
{
IsDirty = true;
}

public void UpdateTotals()
/// <summary>
/// Update all stat totals
/// This method must be called to ensure all stats affected by modifiers are up to date
/// </summary>
/// <param name="checkDirty">When true totals will update only if IsDirty property is true</param>
public virtual void UpdateTotals(bool checkDirty = false)
{
if (checkDirty && !IsDirty)
{
return;
}

//Add Base Stats
_statTotals.Clear();
if (_baseStats != null)
{
_statTotals.Add(_baseStats);
}

//
foreach (var modifierSet in _modifiers)
//Apply Adds
foreach (var mod in _modifiers.SelectMany(modifierSet => modifierSet.Get(StatMode.Add)))
{
foreach (var mod in modifierSet)
{
_statTotals[mod.statType] = mod.Modify(_statTotals[mod.statType]);
}
_statTotals[mod.statType] = mod.Modify(_statTotals[mod.statType]);
}

//Apply Multipliers
foreach (var mod in _modifiers.SelectMany(modifierSet => modifierSet.Get(StatMode.Multiply)))
{
_statTotals[mod.statType] = mod.Modify(_statTotals[mod.statType]);
}

IsDirty = false;
}

#region IReadonlyStatSet Implementation
Expand All @@ -63,7 +105,5 @@ IEnumerator IEnumerable.GetEnumerator()
}

#endregion

}

}
109 changes: 76 additions & 33 deletions Runtime/StatModifierSet.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace Gameframe.StatSheet
{
Expand All @@ -8,77 +10,106 @@ namespace Gameframe.StatSheet
/// Stat modifiers are adds or multipliers that can be applied to a stat sheet
/// </summary>
/// <typeparam name="TKey"></typeparam>
public class StatModifierSet<TKey> : IStatModifierSetIndexed<TKey>
public class StatModifierSet<TKey> : INotifyStatModifierSet<TKey>
{
private readonly List<StatModifier<TKey>> _mods = new List<StatModifier<TKey>>();

/// <summary>
/// Set a modifier
/// </summary>
/// <param name="modifier">Stat modifier value</param>
public void Set(StatModifier<TKey> modifier)
{
Set(modifier.statType,modifier.value, modifier.mode);
}

/// <summary>
/// Set a modifier
/// </summary>
/// <param name="statType">Stat type the modifier will affect</param>
/// <param name="value">value of the stat modifier</param>
/// <param name="mode">how the value will be applied to the stat</param>
public void Set(TKey statType, float value, StatMode mode)
{
for (int i = 0; i < _mods.Count; i++)
{
if (_mods[i].statType.Equals(statType) && _mods[i].mode == mode)
{
var val = _mods[i];
val.value = value;
_mods[i] = val;
//If there is no change in value then just return and do nothing
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (_mods[i].value == value)
{
return;
}

var prevValue = _mods[i];
var newVal = prevValue;
newVal.value = value;
_mods[i] = newVal;
NotifyChanged(StatModifierSetActionType.Set, newVal, prevValue);
return;
}
}
//Adding a new modifier
_mods.Add(new StatModifier<TKey>()
var modifier = new StatModifier<TKey>()
{
statType = statType,
value = value,
mode = mode
});
};
_mods.Add(modifier);
NotifyChanged(StatModifierSetActionType.Add, modifier);
}

public StatModifier<TKey> Get(TKey statName, StatMode mode)
/// <summary>
/// Get modifier for a given stat type and mode
/// </summary>
/// <param name="statType">Stat that is being affected</param>
/// <param name="mode">how the stat is applied: Add or Multiply</param>
/// <returns>StatModifier value</returns>
public StatModifier<TKey> Get(TKey statType, StatMode mode)
{
for (var i = 0; i < _mods.Count; i++)
{
if (_mods[i].statType.Equals(statName) && _mods[i].mode == mode)
if (_mods[i].statType.Equals(statType) && _mods[i].mode == mode)
{
return _mods[i];
}
}

if (mode == StatMode.Multiply)
switch (mode)
{
return new StatModifier<TKey>
{
statType = statName,
value = 1,
mode = StatMode.Multiply
};
}
else
{
return new StatModifier<TKey>
{
statType = statName,
value = 0,
mode = StatMode.Add
};
case StatMode.Multiply:
return new StatModifier<TKey>
{
statType = statType,
value = 1,
mode = StatMode.Multiply
};
case StatMode.Add:
return new StatModifier<TKey>
{
statType = statType,
value = 0,
mode = StatMode.Add
};
default:
throw new InvalidEnumArgumentException($"StatMode {mode} not implemented");
}
}

public IEnumerable<StatModifier<TKey>> Get(StatMode mode)
{
return _mods.Where(x => x.mode == mode);
}

public int Count => _mods.Count;

public StatModifier<TKey> GetIndex(int index)
{
return _mods[index];
}

public float Modify(TKey statType, float value)
{
var adds = Get(statType, StatMode.Add);
var mul = Get(statType, StatMode.Multiply);
value += adds.value;
value *= mul.value;
return value;
}

public IEnumerator<StatModifier<TKey>> GetEnumerator()
{
return _mods.GetEnumerator();
Expand All @@ -88,5 +119,17 @@ IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

public event StatModifierSetChangedEventHandler<TKey> ModifiersChanged;

private void NotifyChanged(StatModifierSetActionType action, StatModifier<TKey> modifier, StatModifier<TKey> previous = new StatModifier<TKey>())
{
ModifiersChanged?.Invoke(this, new StatModifierSetChangedArgs<TKey>
{
Action = action,
Modifier = modifier,
Previous = previous
});
}
}
}
14 changes: 3 additions & 11 deletions Runtime/StatSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@

namespace Gameframe.StatSheet
{
// List of base stats
// List of adds from n sources
// List of multipliers from n sources
// Generate/Calculate List of final stats
// Need to Serialize just base stats
// Equipment is just a list of modifiers
// Need to be able to show stat delta (unequip modifier set A, equip set B and show how stats change)

/// <summary>
/// Abstract implementation of IStatSet<TKey>
/// Provides Add and Subtract methods
Expand All @@ -20,7 +12,7 @@ public abstract class StatSet<TKey> : IStatSet<TKey>
{
protected StatSet() { }

protected StatSet(IStatSet<TKey> set)
protected StatSet(IReadOnlyStatSet<TKey> set)
{
foreach (var statValue in set)
{
Expand All @@ -38,7 +30,7 @@ protected StatSet(IStatSet<TKey> set)
/// Loops the given set and adds the value to this set.
/// </summary>
/// <param name="statSet">a stat set</param>
public void Add(IStatSet<TKey> statSet)
public void Add(IReadOnlyStatSet<TKey> statSet)
{
foreach (var stat in statSet)
{
Expand All @@ -50,7 +42,7 @@ public void Add(IStatSet<TKey> statSet)
/// Loops over the given set and subtracts the values from the stats in this set
/// </summary>
/// <param name="statSet">a stat set</param>
public void Subtract(IStatSet<TKey> statSet)
public void Subtract(IReadOnlyStatSet<TKey> statSet)
{
foreach (var stat in statSet)
{
Expand Down
2 changes: 1 addition & 1 deletion Runtime/StatSetUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static class StatSetUtility
/// <param name="sets"></param>
/// <typeparam name="TKey">The type of the Stat. Usually an enum.</typeparam>
/// <returns>IStatSetIndexed<TKey></returns>
public static IStatSetIndexed<TKey> Sum<TKey>(params IStatSet<TKey>[] sets)
public static IStatSetIndexed<TKey> Sum<TKey>(params IReadOnlyStatSet<TKey>[] sets)
{
var newSet = new ListStatSet<TKey>();
for (var i = 0; i < sets.Length; i++)
Expand Down
Loading

0 comments on commit 1b07312

Please sign in to comment.