Migrating to Weighted Randomness

Published November 04, 2019
Advertisement

When we started working on this project we had the mindset of "Random is good enough". Most of our systems were running off of pure randomness, which for our very small data set was perfectly fine. If you had a list of craftable items to pick from, and you had 3 or 4 items, just doing at Random.Range() over the list would be perfectly acceptable. I noticed this would become a big issue when we introduced randomized quest requirements. Of course, randomizing over the full set of items made the requirements total nonsense. Even when we narrowed down the blacksmith, he could be asking you to bring him 1 copper ore, or 1 Epic Adamantine Mace, he didn't care. One of the easiest and most useful solutions for the problem were: loot tables (and other tables which I'll get to later). In the future I'm sure I'll increase the following logic to allow for more than 100 percentage points.



public class LootTable
{
    List<LootTableItem> items = new List<LootTableItem>();

    public LootTable(List<LootTableItem> _i)
    {
        BuildListFromPartials(_i);

    }

    public void BuildListFromPartials(List<LootTableItem> _i)
    {
        int previous = 0;
        foreach(LootTableItem item in _i)
        {
            item.LowEnd = previous;
            item.HighEnd = previous + item.PercentChance;
            previous += item.PercentChance;
        }
        items = _i;
    }

    public Item RollForItem()
    {
        int roll = Random.Range(0, 101);

        foreach (LootTableItem item in items)
        {
            if(roll == 0 && item.LowEnd == 0)
            {
                return item.GetAnItem();
            }

            if(item.LowEnd < roll && roll <= item.HighEnd)
            {
                return item.GetAnItem();
            }
        }

        return null;
    }
}

And the supporting class:


public class LootTableItem
{
    //The chance that this item will be rolled (out of 100).
    public int PercentChance;
    public int LowEnd;
    public int HighEnd;

    public List<Item> PossibleItems;

    public LootTableItem(string item, int percentChance, int quality = 0)
    {
        PercentChance = percentChance;
        PossibleItems = new List<Item>();
        PossibleItems.Add(ItemManager.GetItem(item, quality));
    }

    public Item GetAnItem()
    {
        return PossibleItems[Random.Range(0, PossibleItems.Count)];
    }
}

The declaration of a loot table can look a little gnarly, but it's pretty easy to understand. Before long I was creating loot tables for everything, including items you'd get from harvesting:


new LootTable(new List<LootTableItem>(){ new LootTableItem("Copper Ore", 60), new LootTableItem("Iron Ore", 40)});

You can call lootTable.RollForItem() as many times as you want, which is why you can easily augment this strategy by adding rolls. I also stripped down the loot table to simply support weighted chance, allowing me to give the player multiple rolls on harvesting, so the better their equipment the better their chance of getting good items. The difference in this example is that instead of just saying "if the user has an +2 axe equiped, give them Random.Range(0,2) extra rolls", you can weight the outcomes. So you wouldn't have a 1/3 chance to get a 0, 1 or 2. You could have a 50% to get 0, a 30% chance to get 1 and a 20% chance to get 2. With this concept you can easily tweak your item collection outcomes to allow your players to have a significantly more fair experience. You can even tweak the rates if you keep getting really bad results.

0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement