For the game that I am creating I came across the need for an Elemental-based Resistance system. By that I mean, a system which would allow the player to mitigate damage or take more damage based upon their Resistance rating towards an element of a given type. With a good Fire Resistance they would take less fire damage, and with a bad Fire Resistance (an Aversion as I call it) they would take more damage.
For my system I decided to go with integers instead of floats, which would have been clamped to a 0-2.0f range. I decided against this simply because it is easier and faster to work with integers than with floats (both IO serialization and actual usage). This was strictly my opinion and overall design choice, so I am not looking for that sort of correction; i'm aware that road could have been taken, I just chose not to do it.
The system itself works in a kind of inverted fashion. As an example, a Fire Resistance of 150 is actually bad. That means you have an Aversion to fire, and will take 50% more damage if hit by it. On the other end of the spectrum, a Fire Resistance of 50 is really good; you would take 50% less Fire damage!
I guess what I am really wanting here is for someone to look over my implementation and see if they can make any positive improvements.
This code can be easily plugged into any project and tested:
using System.Collections.Generic;
using System.Linq;
namespace GrimoireEngine.Framework.Game.AttributeSheets
{
public enum ElementType
{
/// <summary>
/// Represents a Typeless-based Element.
/// </summary>
Normal = 0,
/// <summary>
/// Represents a Fire-based Element.
/// </summary>
Fire = 1,
/// <summary>
/// Represents a Frost-based Element.
/// </summary>
Frost = 2,
/// <summary>
/// Represents an Energy-based Element.
/// </summary>
Energy = 3,
/// <summary>
/// Represents a Sound-based Element.
/// </summary>
Sonic = 4,
/// <summary>
/// Represents a Nature-based Element.
/// </summary>
Nature = 5,
/// <summary>
/// Represents an Arcane/Magic-based Element.
/// </summary>
Arcane = 6,
/// <summary>
/// Represents a Holy-based Element.
/// </summary>
Holy = 7,
/// <summary>
/// Represents a Shadow-based Element.
/// </summary>
Shadow = 8
}
public enum ElementalDamageType
{
Default,
Resistance,
Aversion,
Critical,
Immune
}
public class ResistanceTable
{
private Dictionary<ElementType, int> _resistanceTable;
public string Name { get; set; }
/// <summary>
///
/// </summary>
public ElementType[] Elements
{
get
{
return this._resistanceTable.Keys.ToArray();
}
}
/// <summary>
///
/// </summary>
public KeyValuePair<ElementType, int>[] Resistances
{
get
{
return this._resistanceTable.ToArray();
}
}
public int this[ElementType type]
{
get
{
return GetResistanceVersus(type);
}
set
{
SetResistanceVersus(type, value);
}
}
/// <summary>
///
/// </summary>
public int Normal
{
get
{
return GetResistanceVersus(ElementType.Normal);
}
set
{
SetResistanceVersus(ElementType.Normal, value);
}
}
/// <summary>
///
/// </summary>
public int Fire
{
get
{
return GetResistanceVersus(ElementType.Fire);
}
set
{
SetResistanceVersus(ElementType.Fire, value);
}
}
/// <summary>
///
/// </summary>
public int Frost
{
get
{
return GetResistanceVersus(ElementType.Frost);
}
set
{
SetResistanceVersus(ElementType.Frost, value);
}
}
/// <summary>
///
/// </summary>
public int Energy
{
get
{
return GetResistanceVersus(ElementType.Energy);
}
set
{
SetResistanceVersus(ElementType.Energy, value);
}
}
/// <summary>
///
/// </summary>
public int Sonic
{
get
{
return GetResistanceVersus(ElementType.Sonic);
}
set
{
SetResistanceVersus(ElementType.Sonic, value);
}
}
/// <summary>
///
/// </summary>
public int Nature
{
get
{
return GetResistanceVersus(ElementType.Nature);
}
set
{
SetResistanceVersus(ElementType.Nature, value);
}
}
/// <summary>
///
/// </summary>
public int Arcane
{
get
{
return GetResistanceVersus(ElementType.Arcane);
}
set
{
SetResistanceVersus(ElementType.Arcane, value);
}
}
/// <summary>
///
/// </summary>
public int Holy
{
get
{
return GetResistanceVersus(ElementType.Holy);
}
set
{
SetResistanceVersus(ElementType.Holy, value);
}
}
/// <summary>
///
/// </summary>
public int Shadow
{
get
{
return GetResistanceVersus(ElementType.Shadow);
}
set
{
SetResistanceVersus(ElementType.Shadow, value);
}
}
public const int DefaultResistance = 100;
public const int MinimumResistance = 0;
public const int MaximumResistance = 200;
public ResistanceTable(
int normal = DefaultResistance,
int fire = DefaultResistance,
int frost = DefaultResistance,
int energy = DefaultResistance,
int sonic = DefaultResistance,
int nature = DefaultResistance,
int arcane = DefaultResistance,
int holy = DefaultResistance,
int shadow = DefaultResistance)
{
Initialize(normal,fire,frost,energy,sonic,nature,arcane,holy,shadow);
}
/// <summary>
/// Sets all Resistances to the given value(s).
/// if this is the first pass during initialization,
/// the internal Dictionary will also be created by this
/// method.
/// </summary>
/// <param name="normal"></param>
/// <param name="fire"></param>
/// <param name="frost"></param>
/// <param name="energy"></param>
/// <param name="sonic"></param>
/// <param name="nature"></param>
/// <param name="arcane"></param>
/// <param name="holy"></param>
/// <param name="shadow"></param>
public void Initialize(
int normal = DefaultResistance,
int fire = DefaultResistance,
int frost = DefaultResistance,
int energy = DefaultResistance,
int sonic = DefaultResistance,
int nature = DefaultResistance,
int arcane = DefaultResistance,
int holy = DefaultResistance,
int shadow = DefaultResistance)
{
if (this._resistanceTable == null)
{
/**
* This will serve as the primary entry point for the class
* initialization. All contructors lead to this branch. We take
* this time to not only create the dictionary, but to also fill
* it with the default data, or the data presented by the user.
*/
this._resistanceTable = new Dictionary<ElementType, int>
{
{ElementType.Normal, normal},
{ElementType.Fire, fire},
{ElementType.Frost, frost},
{ElementType.Energy, energy},
{ElementType.Sonic, sonic},
{ElementType.Nature, nature},
{ElementType.Arcane, arcane},
{ElementType.Holy, holy},
{ElementType.Shadow, shadow}
};
}
else
{
/**
* This instance has already been initialized previously;
* the user simply wants to reset the values. This means we
* can avoid having to recreate the internal dictionary and
* just set the values.
*/
Normal = normal;
Fire = fire;
Frost = frost;
Energy = energy;
Sonic = sonic;
Nature = nature;
Arcane = arcane;
Holy = holy;
Shadow = shadow;
}
}
/// <summary>
/// Gets the type of <see cref="ElementalDamageType"/> that will result from the given <see cref="ElementType"/>
/// and this instances Resistance towards it.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public ElementalDamageType GetResistanceDamageTypeVersus(ElementType type)
{
int mastery = GetResistanceVersus(type);
// We intentionally check for min and max first.
if (mastery == MaximumResistance)
{
return ElementalDamageType.Critical;
}
// We intentionally check for min and max first.
if (mastery == MinimumResistance)
{
return ElementalDamageType.Immune;
}
if (mastery > DefaultResistance)
{
return ElementalDamageType.Aversion;
}
if (mastery < DefaultResistance)
{
return ElementalDamageType.Resistance;
}
return ElementalDamageType.Default;
}
/// <summary>
/// Determines if the desired <see cref="ElementType"/> will be
/// inflicted with decreased damage.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public bool HasResistanceVersus(ElementType type)
{
ElementalDamageType damageType = GetResistanceDamageTypeVersus(type);
return damageType == ElementalDamageType.Resistance || damageType == ElementalDamageType.Immune;
}
/// <summary>
/// Determines if the desired <see cref="ElementType"/> will result in increased damage.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public bool HasAversionVersus(ElementType type)
{
ElementalDamageType damageType = GetResistanceDamageTypeVersus(type);
return damageType == ElementalDamageType.Aversion || damageType == ElementalDamageType.Critical;
}
/// <summary>
/// Calculates the resulting damage given the damage, the
/// desired <see cref="ElementType"/>, and this instances Resistance
/// versus that element.
/// </summary>
/// <param name="damage"></param>
/// <param name="type"></param>
/// <returns></returns>
public int ComputeResistanceVersus(int damage, ElementType type)
{
return (int)(damage * ((float)GetResistanceVersus(type) / DefaultResistance));
}
/// <summary>
/// Increases the Resistance against the desired <see cref="ElementType"/>. This
/// method works in an inverted fashion. So increasing is actually
/// decreasing the internal variable.
/// </summary>
/// <param name="type"></param>
/// <param name="value"></param>
public void IncreaseResistanceVersus(ElementType type, int value)
{
// This is inverted
SetResistanceVersus(type, GetResistanceVersus(type) - value);
}
/// <summary>
/// Decreases the Resistance against the desired <see cref="ElementType"/>. This
/// method works in an inverted fashion. So decreasing is actually
/// increasing the internal variable.
/// </summary>
/// <param name="type"></param>
/// <param name="value"></param>
public void DecreaseResistanceVersus(ElementType type, int value)
{
// This is inverted
SetResistanceVersus(type, GetResistanceVersus(type) + value);
}
/// <summary>
/// Sets the desired Resistance of the given <see cref="ElementType"/> to
/// its default value.
/// </summary>
/// <param name="type"></param>
public void SetResistanceToDefault(ElementType type)
{
SetResistanceVersus(type, DefaultResistance);
}
/// <summary>
/// Sets the desired Resistance of the given <see cref="ElementType"/> to the
/// given value.
/// </summary>
/// <param name="type"></param>
/// <param name="mastery"></param>
public void SetResistanceVersus(ElementType type, int mastery)
{
this._resistanceTable[type] = Clamp(mastery);
}
/// <summary>
/// used internally to clamp the resistances to a 0-200 range.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static int Clamp(int value)
{
return (value < MinimumResistance) ? MinimumResistance : (value > MaximumResistance) ? MaximumResistance : value;
}
/// <summary>
/// Retrieves a collection of <see cref="ElementType"/> that will result in
/// decreased damage when attacked by them.
/// </summary>
/// <returns></returns>
public KeyValuePair<ElementType, int>[] GetResistances()
{
List<KeyValuePair<ElementType, int>> resistances = new List<KeyValuePair<ElementType, int>>(9);
foreach (KeyValuePair<ElementType, int> resistancePair in _resistanceTable)
{
if (HasResistanceVersus(resistancePair.Key))
{
resistances.Add(resistancePair);
}
}
return resistances.ToArray();
}
/// <summary>
/// Retrieves a collection of <see cref="ElementType"/> that will result in
/// increased damage when attacked by them.
/// </summary>
/// <returns></returns>
public KeyValuePair<ElementType, int>[] GetAversions()
{
List<KeyValuePair<ElementType, int>> aversions = new List<KeyValuePair<ElementType, int>>(9);
foreach (KeyValuePair<ElementType, int> aversionPair in _resistanceTable)
{
if (HasAversionVersus(aversionPair.Key))
{
aversions.Add(aversionPair);
}
}
return aversions.ToArray();
}
/// <summary>
/// Retrieves the current Resistance value of the specified
/// <see cref="ElementType"/>.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public int GetResistanceVersus(ElementType type)
{
return _resistanceTable[type];
}
public override string ToString()
{
return $"Normal={Normal}," +
$"Fire={Fire}," +
$"Frost={Frost}," +
$"Energy={Energy}," +
$"Sonic={Sonic}," +
$"Nature={Nature}," +
$"Arcane={Arcane}," +
$"Holy={Holy}," +
$"Shadow={Shadow}";
}
}
}