6
\$\begingroup\$

I'm sure that my intentions are highly erroneous, and I really just want to see what you guys would suggest I do instead. I have a class I made for my game which helps store various values regarding resistances, aversions, attribute modifications for buffs and debuffs, and damage mitigation and damage amplification vs creatures.

using System;
using System.Collections.Generic;

namespace GrimoireEngine.Framework.Collections
{

    public enum ModificationType
    {
        Increase,
        Decrease
    }

    public class ModificationTable<TEnum> where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        private static TEnum[] _keys;
        private int[] _values;
        public const int DefaultModification = MinimumModification;
        public const int MinimumModification = 0;
        public const int MaximumModification = 100;
        private static IEqualityComparer<TEnum> _comparer;

        static ModificationTable()
        {
            _keys = (TEnum[])Enum.GetValues(typeof(TEnum));
            _comparer = EqualityComparer<TEnum>.Default;
        }

        public TEnum[] Keys
        {
            get
            {
                return _keys;
            }
        }

        public int this[TEnum enumType]
        {
            get
            {
                return _values[GetIndex(enumType)];
            }
            set
            {
                _values[GetIndex(enumType)] = value;
            }
        }

        private int GetIndex(TEnum enumType)
        {
            return _comparer.GetHashCode(enumType);
        }

        public ModificationTable(int defaultValue = DefaultModification)
        {
            if (_values == null)
            {
                _values = new int[this.Keys.Length];
            }
            if (defaultValue != DefaultModification)
            {
                SetAll(defaultValue);
            }
        }

        public void SetAll(int value)
        {
            for (int i = 0; i < _keys.Length; i++)
            {
                Set(_keys[i], value);
            }
        }

        public void Increase(TEnum type, int value)
        {
            Set(type, Get(type) + value);
        }

        public void Decrease(TEnum type, int value)
        {
            Set(type, Get(type) - value);
        }

        public void Clear(TEnum type)
        {
            Set(type, DefaultModification);
        }

        public void Clear()
        {
            SetAll(DefaultModification);
        }

        public int Get(TEnum type)
        {
            return this[type];
        }

        public void Set(TEnum type, int value)
        {
            this[type] = value;
        }

        public int Compute(ModificationType modificationType, TEnum type, int value)
        {
            switch (modificationType)
            {
                case ModificationType.Increase:
                    return (int)(value * (1.0f + (Get(type) / 100f)));
                case ModificationType.Decrease:
                    return (int)(value - ((value * (Get(type) / 100f))));
                default:
                    throw new ArgumentOutOfRangeException(nameof(modificationType), modificationType, null);
            }
        }

        private int Clamp(int value)
        {
            return (value < MinimumModification) ? MinimumModification : (value > MaximumModification) ? MaximumModification : value;
        }

        public ModificationTable<TEnum> Clone()
        {
            ModificationTable<TEnum> table = new ModificationTable<TEnum>();
            for (int i = 0; i < Keys.Length; i++)
            {
                table.Set(Keys[i], Get(Keys[i]));
            }
            return table;
        }

        public void Copy(ModificationTable<TEnum> table)
        {

            for (int i = 0; i < Keys.Length; i++)
            {
                this.Set(Keys[i], table.Get(Keys[i]));
            }
        }

        public void Add(ModificationTable<TEnum> other)
        {
            for (int i = 0; i < Keys.Length; i++)
            {
                TEnum key = Keys[i];
                this[key] += other[key];
            }
        }

        public void Subtract(ModificationTable<TEnum> other)
        {
            for (int i = 0; i < Keys.Length; i++)
            {
                TEnum key = Keys[i];
                this[key] -= other[key];
            }
        }

        public static ModificationTable<TEnum> operator +(ModificationTable<TEnum> table1, ModificationTable<TEnum> table2)
        {
            ModificationTable<TEnum> combined = new ModificationTable<TEnum>();
            combined.Add(table1);
            combined.Add(table2);
            return combined;
        }

        public static ModificationTable<TEnum> operator -(ModificationTable<TEnum> table1, ModificationTable<TEnum> table2)
        {
            ModificationTable<TEnum> combined = new ModificationTable<TEnum>();
            combined.Copy(table1);
            combined.Subtract(table2);
            return combined;
        }
    }
}

For this class I use various enumerations, which are automatically allocated and used as keys for the internal storage mechanism.

namespace GrimoireEngine.Framework.Logic
{
    public enum CreatureType
    {
        Humanoid = 0,
        Beast = 1,
        Elemental = 2,
        Undead = 3,
        Demon = 4,
        Dragonkin = 6,
        Aberration = 7,
        Construct = 8,
        Insect = 9
    }
}

namespace GrimoireEngine.Framework.Logic
{
    public enum AttributeType
    {
        Strength,
        Agility,
        Stamina,
        Intellect,
        Wisdom,
        Spirit,
        Defense,
        Willpower
    }
}

namespace GrimoireEngine.Framework.Logic
{

    /**
     * Possible Elements:
     * Kinetic
     */

    public enum ElementType
    {
        /// <summary>
        /// Represents a Typeless-based Element.
        /// </summary>
        Normal,

        /// <summary>
        /// Represents a Fire-based Element.
        /// </summary>
        Fire,

        /// <summary>
        /// Represents a Frost-based Element.
        /// </summary>
        Frost,

        /// <summary>
        /// Represents an Energy-based Element.
        /// </summary>
        Energy,

        /// <summary>
        /// Represents a Sound-based Element.
        /// </summary>
        Sonic,

        /// <summary>
        /// Represents a Nature-based Element.
        /// </summary>
        Nature,

        /// <summary>
        /// Represents an Arcane/Magic-based Element.
        /// </summary>
        Arcane,

        /// <summary>
        /// Represents a Holy-based Element.
        /// </summary>
        Holy,

        /// <summary>
        /// Represents a Shadow-based Element.
        /// </summary>
        Shadow

    }
}

A simple example usage which shows how powerful the class can be:

using System;
using System.Windows;
using GrimoireEngine.Framework.Collections;
using GrimoireEngine.Framework.Logic;

namespace GrimoireDevelopmentKit
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            ModificationTable<CreatureType> creatureDamagePotency = new ModificationTable<CreatureType>();
            ModificationTable<CreatureType> creatureDamageImpotency = new ModificationTable<CreatureType>();

            ModificationTable<AttributeType> buffs = new ModificationTable<AttributeType>();
            ModificationTable<AttributeType> debuffs = new ModificationTable<AttributeType>();

            ModificationTable<ElementType> resistances = new ModificationTable<ElementType>();
            ModificationTable<ElementType> aversions = new ModificationTable<ElementType>();

        }
    }
}

Now onto my intentions. You see, I wanted to pre-declare a few classes which would automatically inherit/extend the ModifcationTable with the enums that I mentioned above. So essentially I would have a class like:

using GrimoireEngine.Framework.Logic;

namespace GrimoireEngine.Framework.Collections
{
    public class ElementalTable: ModificationTable<ElementType>
    {
    }
}

The problem with this, however, is that the class is practically bare-bones nothing. It's just me passing in the desired enum. My question is, is this ok to do? What kind of common conventions adhere to situations like this? Is there an alternative solution, such as making ModificationTable an abstract class? And then, what is the best way to implement something like that?

\$\endgroup\$
3
\$\begingroup\$

The problem with this, however, is that the class is practically bare-bones nothing. It's just me passing in the desired enum. My question is, is this ok to do?

There is no problem. The implementation is perfect - at least I cannot complain about anything specific right now.

This is exactly like the Dictionary<,> works. It's not abstract because there is nothing to implement. You can use it inline like

var myDic = new Dictionary<string, string>();

or for creating custom types by deriving from it:

class MyDic : Dictionar<string, string>() { }

var myDic = new MyDic();

Your class works the same way. It implements everything you need so you can either use it inline or to define new types. I find it's perfectly fine.

\$\endgroup\$
6
\$\begingroup\$

I might be missing something but why do you need your ModificationType at all? Let's look at your maths here:

value * (1.0f + (Get(type) / 100f))
value - ((value * (Get(type) / 100f)))

Let's define value as v and (Get(type) / 100f) as r.

v*(1+r) // Increase
v-(v*r) // Decrease

Rearranging your top formula gives you:

v+(v*r) // Increase

Which means your increase and decrease functions are the same apart from the sign (which is what you'd expect).

I think you can drop the ModificationType and instead have a range of -100 to 100 for the modification value.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.