I wanted to create a loot table mechanic for any future game projects, and came up with what I am about to show you guys. I was wondering if someone could give it a glance and see if there could be any errors, or if it could be improved in any way.
using System;
using System.Collections.Generic;
namespace NovaEngineFramework.Framework.Probability
{
/// <summary>
/// A String-based loot table.
/// The table is based upon a pie-chart design. In other words,
/// each item is added with a desired rarity; that rarity counts
/// towards a total maximum of 100. Example:
/// Item1 = 10
/// Item2 = 40
/// Item3 = 50
/// Total = 100.
/// Item1 is the rarest item in the table, while Item3
/// is the most common.
/// </summary>
public class StringBasedLootTable
{
private readonly List<string> _loot;
private readonly Dictionary<string , int> _cachedItems;
private int _remainingStorage = 100;
private readonly Random _rngInstance;
public StringBasedLootTable()
{
_cachedItems = new Dictionary<string , int>();
_loot = new List<string>();
_rngInstance = new Random();
}
/// <summary>
/// Adds an item to the Loot Table with it's own specified rarity.
/// An error may be thrown if the storage capacity(maximum 100) is
/// exceeded by a previously added item.
/// </summary>
/// <param name="rarity">How rare the item will be.</param>
/// <param name="itemName">The string name representation of the item to be added.</param>
/// <exception cref="InvalidOperationException">Thrown if the capacity has already been exceeded, or is about to be exceeded.</exception>
public void Store( int rarity , string itemName )
{
int test = ( _remainingStorage - rarity );
if( test < 0 )
{
throw new Exception( "Storage Capacity Was full" );
}
_remainingStorage = _remainingStorage - rarity;
_cachedItems.Add( itemName , rarity );
}
/// <summary>
/// Shows the remaining storage of this loot table.
/// </summary>
/// <returns></returns>
public int CheckRemainingStorage()
{
return _remainingStorage;
}
/// <summary>
/// Rebuilds the loot table. An error will be thrown if
/// all of the available space has not been properly utilized.
/// This function should only be called once all the items have
/// been added to the table.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the capacity is not full.</exception>
public void Rebuild()
{
if( _remainingStorage > 0 )
{
throw new Exception( "The Capacity was not completely full." );
}
_loot.Clear();
foreach( KeyValuePair<string , int> pair in _cachedItems )
{
for( int i = 0; i < pair.Value; i++ )
{
_loot.Add( pair.Key );
}
}
}
/// <summary>
/// Finds a random item(string) within the loot table.
/// </summary>
/// <returns></returns>
public string GetRandomItem()
{
return _loot[ _rngInstance.Next( _loot.Count ) ];
}
}
}
And here is a brief implementation:
StringBasedLootTable swordTable = new StringBasedLootTable();
swordTable.Store( 40 , "Rusty Sword" );
swordTable.Store( 20 , "Steel Sword" );
swordTable.Store( 10 , "Titanium Sword" );
swordTable.Store( 30 , "Iron Sword" );
swordTable.Rebuild();
Console.WriteLine( swordTable.GetRandomItem() );
And the general concept of how it works:
It is based upon a pie-graph, in a way. Each item you add takes up a slice of the pie, and all the item probabilities must add up to exactly 100. A visual rendition would likely facillitate better understanding, so I went ahead and made this:
As you can see, the most probable item for one to recieve from the above implementation is a "rusty sword", being that it has the largest section of the pie graph. The least common item would be a Titanium sword, etc.
This post is not meant to fuel an argument about the use of var
. Please do not offer it as an example of something that could be used towards fixing the code. The use of var
is based upon purely personal, arbitrary taste; I have no intentions of using it.