ARPG Loot Generation
Prerequisites
This guide assumes you have a basic knowledge of the plugin:
- How to create a new Loot Table Template.
- How to use the Loot Registry Subsystem.
- How to use float masks.
Example
See LV_UseCases in the example map for an implementation. All loot tables use the Loot Registry system; to see the loot tables, click on the
STLootRegistrationComponent
on each trigger actor.
Overview
Action Role Playing Game (ARPG) Loot Generation involves generating items which have multiple parts:
- The Base Item Type, e.g., a piece of armor like 'Leather Hood'.
- The Rarity, e.g., 'Normal', 'Magical', or 'Rare'. Many ARPGs also include 'Unique', but we won't cover that in this guide.
- The Affixes, e.g., '30% increased power'.
We can generate each of these parts using a separate loot table for each one.
The items generated by this example do not affect gameplay, nor do they have a fully backing item system. They are only for demonstrating Stoch's loot tables.
For the purposes of this demo, we assume a specific ARPG Loot System used by several popular ARPGs. However, Stoch can be used for nearly any loot system.
Preparing Data
First we'll prepare all of the data we need for the generation process.
Base Item Type
For the base item type, we have created the data table named DT_Armor. This includes a set of demo item types:
And the corresponding loot table, named LT_Armor. This Loot Table Template uses an Unlimited distribution type.
Masks
For this table, we will want to limit which items can drop based on the area's level. To prepare for this, we will add a Copy Data
Mask Generator; this will copy the Item Level
property from each data table row:
Rarity
Rarity determines the number of affixes an item can have. The demo assumes affixes are split into two types: prefixes and suffixes. This Loot Table Template also uses an Unlimited distribution type.
Rarity | Prefix/Suffix Count | Total affixes |
---|---|---|
Normal | 0 / 0 | 0 |
Magical | 1 / 1 | 2 |
Rare | 3 / 3 | 6 |
Masks
Many ARPG games offer 'Increased Magic Find', or increasing the chances for rolling magical and rare items. To prepare for this, we will add a mask generator for MagicFindEffect
that stores how increases to magic find should affect each row's weight:
Affixes
For the affixes, we first create a data table for each type of affix:
We create a loot table template for each 'affix group'. Affix groups are affixes that have the same effect (e.g., 'Increased Power'), but different tiers (e.g., +11-20%, +21-30%, +31-40%). Each of these tiers are mutually exclusive. Here's one of the loot tables for an affix group:
Then we will include all of the groups in a loot table template:
Organizing the affixes into smaller loot table templates helps when we have multiple loot tables that use them. For example, suppose we want to have different affix possibilities for Swords and Maces. But these two weapon types share most of the same affixes (e.g., they both have 'Health', 'Strength', etc., but maybe swords have 'Increased Slashing Damage' and maces have 'Increased Smashing Damage'). Instead of referencing the affixes data table directly from each loot table, we create smaller loot tables so we can mix-and-match which affix groups we want for swords and maces.
Masks
We will use float masks to ensure:
- We limit the number of prefixes and suffixes.
- Affixes within groups are mutually exclusive.
To prepare for this, we will add a Bool Comparison
mask generator to evaluate whether each row is a prefix or a suffix:
We will also add a Gameplay Tag Enumerate
mask generator to create masks for each affix group:
Generation
Now that we have all of the data tables and loot tables created, we can write the generation process.
The process for each item will go like this:
- Roll a base item type, limiting based on the item level.
- Roll a random rarity, affected by magic find.
- Create a float mask (
AffixesMask
) which represents changes to affix weights. It will start with 1.0 for each row (all default weights). - Optionally, scale the weights of specific affixes (see section below).
- Roll
AffixCount
betweenMinAffixCount
andMaxAffixCount
based on our rolled rarity. - Roll affixes up to
AffixCount
:- Masked sample from the affixes loot table by multiplying
AffixesMask
. - Update how many prefixes/suffixes we have based on the sampled affix. If we reach our limit, remove them from
AffixesMask
. - Remove all affixes in our sampled affix's group from
AffixesMask
.
- Masked sample from the affixes loot table by multiplying
This process is fully implemented in the BP_SampleAffixedItems
blueprint in the example map.
Roll Base Item Type
We get the ItemLevel
float mask from the LT_Armor
loot table, then call Filter <=
to filter out all rows with item level greater than our area's item level. For example:
Index | Mask (Item Level) | Mask (Filter <= 20) |
---|---|---|
0 | 1 | 1.0 |
1 | 10 | 1.0 |
2 | 20 | 1.0 |
3 | 40 | 0.0 |
Multiplying the loot table's weights by this mask effectively filters out rows with item level greater than 20.
Roll Rarity
We get the MagicFindEffect
float mask from the LT_Rarity
loot table, then:
- Multiply by the player's magic find (negative to reduce magic find, positive to increase magic find, 0 has no effect).
- Add 1.0 so it can be multiplied to the loot table's weights.
- Masked sample and
Multiply
the magic find mask.
Here's how the mask changes:
Zero Magic Find (+0%)
Rarity | Original Weight | Mask (Magic Find Effect) | Mask (After Multiply) | Mask (After Adding 1) | Final Weight | Probability |
---|---|---|---|---|---|---|
Normal | 10,000 | -0.3 | 0.0 | 1.0 | 10,000 | 50.0% |
Magical | 7,000 | 0.7 | 0.0 | 1.0 | 7,000 | 35.0% |
Rare | 3,000 | 1.3 | 0.0 | 1.0 | 3,000 | 15.0% |
Increased Magic Find (+50%)
Rarity | Original Weight | Mask (Magic Find Effect) | Mask (After Multiply) | Mask (After Adding 1) | Final Weight | Probability |
---|---|---|---|---|---|---|
Normal | 10,000 | -0.3 | -0.15 | 0.85 | 8,500 | 38.0% |
Magical | 7,000 | 0.7 | 0.35 | 1.35 | 9,550 | 41.5% |
Rare | 3,000 | 1.3 | 0.65 | 1.65 | 4,950 | 21.5% |
Reduced Magic Find (-50%)
Rarity | Original Weight | Mask (Magic Find Effect) | Mask (After Multiply) | Mask (After Adding 1) | Final Weight | Probability |
---|---|---|---|---|---|---|
Normal | 10,000 | -0.3 | 0.15 | 1.15 | 11,500 | 67.3% |
Magical | 7,000 | 0.7 | -0.35 | 0.35 | 4,550 | 26.6% |
Rare | 3,000 | 1.3 | -0.65 | 0.65 | 1,050 | 6.1% |
You can tune the Magic Find Effect
to whatever values you want. We opted for these values because they behave similarly to how most players would expect.
Roll Affixes
First, we create a AffixesMask
which will be reused when rolling all affixes for the item. We initialize it to 1.0 for all rows; since we're multiplying the mask, this doesn't change weights at all:
Next, using the rarity we rolled earlier, we roll the affix count for this item; it should be between Rarity:MinAffixCount
and Rarity:MaxAffixCount
. The maximum number of allowed prefixes/suffixes will be half of Rarity:MaxAffixCount
:
Then, we roll each affix:
Masked Sample
by multiplying ourAffixesMask
. Remember, ourAffixesMask
holds the cumulative filters for this item's rolled affixes.- Roll a random magnitude within our affix's range (e.g., for the +11-20% tier, we find a random value in the range: 11%, 15%, etc.):
- Update our prefix and suffix counts for this item:
- If we reached the prefix or suffix limit, filter them from our
AffixesMask
:
- Filter all affixes in our sampled affix's group:
For example, if we rolled Power3
, our tag would be Affix.Group.Power
. We find the mask for Affix.Group.Power
, which includes all rows in this group. We invert the filter, so it excludes all affixes in this group instead. Then we multiply it to our AffixesMask
to ensure this group isn't eligible for subsequent affix rolls:
Affix | Original Weight | Mask (Affix.Group.Power) | Mask (Inverted) | Final Weight |
---|---|---|---|---|
Power3 | 300 | 1.0 | 0.0 | 0 |
Int3 | 300 | 0.0 | 1.0 | 300 |
Dex3 | 300 | 0.0 | 1.0 | 300 |
Addendum: Scaling Affix Weights
Some games want to modify affix weights for specific crafting methods. You can do this by scaling the initial AffixMask
:
We convert the filtering mask for an affix group (e.g., Affix.Group.Power
) to a scaling mask for those rows. This modifies filtered rows' weights, but keeps the other rows the same. It looks like this:
Affix | Original Weight | Mask (Affix.Group.Power) | Mask (ScaleFilter 1.5) | Final Weight |
---|---|---|---|---|
Power3 | 300 | 1.0 | 1.5 | 450 |
Int3 | 300 | 0.0 | 1.0 | 300 |
Dex3 | 300 | 0.0 | 1.0 | 300 |
Addendum: Boss Affixes
Some games may want to add special affixes to the pool for items dropped by bosses, or for other reasons. In this case, we recommend composing loot table templates and their masks, then using the same generation method above. For example, our item's basic loot table contains the default affixes, then we can append the boss's affix loot table to the end.
Results
When displayed in a widget, here is what a random item might look like: