First, allow me to apologize, because this is going to be a fairly long post. I've given this topic a lot of thought, and even experimented with concepts and previous implementations.
When I imagine a loot table, I always think of something a little like how World of Warcraft does theirs. Here is a picture example:
Now, just using visual cues, when can pretty much dissect the way the loot system works, but let's also go ahead and use some Wowhead statistics to glean a better understanding for what we're dealing with. Here's a quick mock up:
As you can see, the loot system that World of Warcraft uses is quite dynamic. It's not exactly a roulette, but can, at it's base level, resemble one in ways. My first implementation of a Loot Table avoided the concept of a roulette, and even a weighing mechanic(which gives certain items a greater probability of being taken from the table, and leave others). I did not like the idea of weighing items in this nature. So I made something sort of like this:
This Loot Table was more of a "List". Basically, I would loop through each item, test it's probability, and if it was succesful, I would add it to a list of drops. This worked well in theory, but I was quick to notice a fatal flaw in the system.
Consider this output:
Ore_Copper x1
Nugget_Copper x2
Gem_Diamond x1
Gem_Emerald x1
Gem_Topaz x1
That's an exgaggeration, but if you look at the basic structure it's entirely possible...but what if we didnt want to drop 4 types of gem? And also, the collective probability of the gems also makes you practically guaranteed to recieve at least one, because you have a 10%*5 chances to get one. Now, this may be ok for a game, but it's not what I had intended. I wanted the ability to drop gems, but with only one chance. I wanted the probability of recieving a gem to be exactly 10%. So...how do we fix this? Here's what I came up with:
So what's happening now? We have essentially built a roulette system. Instead of a direct collection of LootTableItem
s, we now defer items into independent collections. LootTableList
s. Each list has it's own probability, much like my original LootTable
, but now we can successfully organize items into their own "pool" within a single LootTable
. If we want an item to be less rare than another, we just add extra instances to the desired LootTableList
. Then, when my LootTable
is running it's algorithm, it selects one item from the LootTableList
. This is great, but it also brought up another issue. What if I wanted all items in a LootTableList
to be dropped, or have a chance of dropping? Well, I went ahead and added an extra field, called CollectAll
. When this field is checked, it avoids picking one item, and instead picks all items in the LootTableList
.
After planning all this, and doing my homework on the subject, I came to creating this kind of LootTable. I'm also fairly certain that this is also the type of LootTable that World of Warcraft is using, or at least conceptually they're very close. Here's the code:
using System;
using System.Collections.Generic;
using System.Linq;
using GrimoireDevelopmentKit.Framework.Interfaces;
using GrimoireDevelopmentKit.Framework.Maths;
using GrimoireDevelopmentKit.Framework.Utilities;
namespace GrimoireDevelopmentKit.Framework.Collections
{
[Serializable]
public class LootTable : Dictionary<string, LootTableList>, ICopy<LootTable>
{
public string Identifier { get; set; }
public AdvancedRandom Random { get; set; }
public int TotalItems
{
get
{
int total = 0;
foreach (LootTableList list in this.Values)
{
total += list.Values.Count;
}
return total;
}
}
public LootTable(string identifier = null)
: this(identifier, new AdvancedRandom())
{
}
public LootTable(string identifier, AdvancedRandom random)
{
Identifier = identifier;
Random = random;
}
public void Add(LootTableList list)
{
if (!Contains(list))
{
Add(list.Identifier, list);
}
else
{
throw new ArgumentException("Key: '" + list.Identifier + "' already exists!");
}
}
public bool Remove(LootTableList list)
{
return Remove(list.Identifier);
}
public bool Contains(string listIdentifier)
{
return ContainsKey(listIdentifier);
}
public bool Contains(LootTableList list)
{
return Contains(list.Identifier);
}
public List<LootTableResult> Next()
{
Range probabilityScope = new Range(0, 100);
List<LootTableResult> drops = new List<LootTableResult>(TotalItems);
foreach (LootTableList list in Values)
{
bool success = Random.NextRange(probabilityScope) <= list.Probability;
if (success)
{
if (list.CollectAll)
{
foreach (LootTableItem item in list.Values)
{
int quantity = Random.NextRange(item.Quantity);
if(quantity > 0)
{
drops.Add(new LootTableResult(item.Identifier, quantity));
}
}
}
else
{
LootTableItem item = list.ElementAt(Random.NextInteger(0, list.Count)).Value;
int quantity = Random.NextRange(item.Quantity);
if(quantity > 0)
{
drops.Add(new LootTableResult(item.Identifier,quantity));
}
}
}
}
return drops;
}
public void Copy(LootTable other)
{
this.Clear();
this.Identifier = other.Identifier;
this.Random = other.Random;
foreach (LootTableList list in other.Values)
{
this.Add(list.MakeCopy());
}
}
public LootTable MakeCopy()
{
LootTable copy = new LootTable();
copy.Copy(this);
return copy;
}
}
}
My Question:
Is there a better and more modern conceptual foundation that I could use to achieve this kind of loot table?
Are there any game developers on this site that have created Loot Tables of this nature? I just want to know if I could improve my concept, not just the code, but the concept itself. I'm certain there is more that can be done to improve upon my work. I'm just looking for advice from people experienced in this subject.
I can also post my implementation into a separate project if it would help people help me. In fact, I think I'm going to work on that right now actually.
EDIT
Just realized I forgot to code the Quantity count in the LootTableLists, which if coded, would allow a LootTableList to be collected from multiple times during the same drop.