Ultraloot
Highly functional loot table implementation in Javascript. Extendable, inheritance based tables with multiple pools and a flexible workflow.
Install / Use
/learn @manticorp/UltralootREADME
UltraLoot
An easy to use, extendable, serialisable loot table module
✅ Use in browser
✅ Use in node project
✅ Plug and play
✅ Tables can be stored as JSON file(s)
✅ Flexible inheritance structure - great for large games
✅ Seedable random number generator
✅ Testable (RNG is injectable for predictable results)
✅ Pluggable Functions and Conditions
✅ Looter / Container context functions - allows you to modify loot depending on who looted from where
🏎️ Fast
📝 Written in typescript
📈 Jest unit tests with high coverage
📫 Sync and Async versions (async default, to allow for async conditions/functions)
🏠 Many examples
Installation
Run npm install @manticorp/ultraloot or include the dist file in browser.
Browser
Download release and include ultraloot in browser:
<script src="dist/ultraloot.js"></script>
<script>
const ul = new UltraLoot();
</script>
Node
const { UltraLoot } = require('@manticorp/ultraLoot');
ES Module
import UltraLoot from '@manticorp/ultraLoot';
Use cases
✅ Loot boxes
✅ Mob drops
✅ Random events
✅ Mob spawning
✅ Quest rewards
✅ And more!
Preface
The aim of this module is to provide a highly flexible loot table implementation that allows for inheritance and modifying results based on context (e.g. the looter, where the loot is coming from, and the other results of rolling the table).
Imagine you're building a mining game.
The loot generated depends on what tool the player was mining with. If they mine with a stone axe, they can only mine stone. If they mine with a diamond axe, they might get diamonds.
// Prep the tables
const ul = new UltraLoot();
const miningTable = ul.loadTable('mining');
// Looters in your app
const weakLooter = new Player().equip({id: 'axe', level: 'stone'});
const strongLooter = new Player().equip({id: 'axe', level: 'diamond'});
// Do some mining/rolling
miningTable.roll({looter: weakLooter}).then(results => weakLooter.addInventory(results));
// [{id: 'stone', qty: 1}]
miningTable.roll({looter: strongLooter}).then(results => strongLooter.addInventory(results));
// [{id: 'stone', qty: 3}, {id: 'ruby', qty: 2}, {id: 'diamond', qty: 1}]
The way the tables are structured allows for easy inheritence and storage in JSON files as well, with a default loading mechanism already in place.
Structure
There is a hierarchy:
- Loot Tables contain multiple Pools
- Pools contain multiple Entries
- Each Entry can in turn be another Loot Table, or a plain Item
Items marked Chancy have a special meaning.
Here is a full table definition for illustration, although please view type information for LootTableEasyDefinition for more information:
const tableDefinition = {
name: 'Table Name', // optional - for your convenience, not required
id: 'table_id', // optional - for your convenience, not required
fn: 'filename', // optional - used to let ul know what the filename for this table is -
// used in un/serialization and loading/saving
rng: RngInterface, // optional - RNG to be used by this loot table
ul: ul, // optional - ul instance - only needed when creating loot tables
// directly via new LootTable() and not ul.createTable().
// This allows inheritance of functions and conditions
pools: [{
name: 'Pool Name', // optional - for your convenience, not required
id: 'pool_id', // optional - for your convenience, not required
rolls: 1, // optional - Chancy - default 1 - number of rolls this pool gets when the table is rolled once
nulls: 0, // optional - Chancy - default 0 - alongside entries, null result will appear with this weight.
conditions: [ // optional - an array of conditions that should be called when this pool is rolled.
// If any condition returns false, no result is returned for that pool (all rolls).
{
function: 'condition_name', // required - the name of the condition
args: { // optional - arguments passed to the "args" named parameter of the function.
foo: 'bar' // This can be anything you like.
}
}
],
functions: [ // optional - functions that are applied to every entry result.
{
function: 'function_name', // required - the name of the function
args: { // optional - arguments passed to the "args" named parameter of the function.
foo: 'bar' // This can be anything you like.
}
}
],
template: { // optional - a template entry from which all entries will inherit. Useful for very samey pools
// has the same structure as entries, below
},
entries: [
{
name: 'Entry Name', // optional - for your convenience, not required
id: 'entry_id', // optional - for your convenience, not required (though highly recommended)
stackable: true, // optional - default true - whether this item stacks (multiple results get added together) or not
unique: false, // optional - default false - if true, this item will only appear once when this pool is rolled
weight: 1, // optional - Chancy - default 1 - the relative weight of this entry compared to others
item: {}, // optional - anything, used for your convenience. If this is a loot table, then it will be
// rolled and the results merged with the parent table results.
qty: 1, // optional - Chancy - default 1 - the qty of this item to return
conditions: [ // optional - an array of conditions that should be called when this entry is rolled.
// If any condition returns false, no result is returned for that entry.
{
function: 'condition_name', // required - the name of the condition
args: { // optional - arguments passed to the "args" named parameter of the function.
foo: 'bar' // This can be anything you like.
}
}
],
functions: [ // optional - functions that are applied to every entry result.
{
function: 'function_name', // required - the name of the function
args: { // optional - arguments passed to the "args" named parameter of the function.
foo: 'bar' // This can be anything you like.
}
}
]
}
]
}]
};
const ul = new UltraLoot();
const table = ul.createTable(tableDefinition);
The absolute most basic example you could possibly have is:
const emptyTable = u.createTable({});
const tableWithOneEmptyPool = u.createTable({
pools: [{}]
});
const tableWithOnePoolWithTwoEmptyEntries = u.createTable({
pools: [
{
entries: [
{},
{}
]
}
]
});
See UltraLoot.createTable for more information.
Chancy
Anything marked as "Chancy" is special, and can take one of the following as an argument:
- Number
- Dice string / spec
- Configuration object
const ul = new UltraLoot();
const rng = ul.getRng();
const randomNumbers = [];
// Returns a simluated roll of 2 x 6 sided dice and adds 1
rng.chancy('2d6 + 1');
rng.chancy({n: 2, d: 6, plus: 1});
// Always returns 5
rng.chancy(5);
// A random float between 1 and 10
rng.chancy({min: 1, max: 10});
// A random integer between 1 and 10
rng.chancy({min: 1, max: 10, type: 'integer'});
// A random normal number between 1 and 10
rng.chancy({min: 1, max: 10, type: 'normal'});
// A random normal number centered at 0.5
rng.chancy({mean: 0.5, type: 'normal'});
// A random normal number centered at 10 with a standard deviation of 5
rng.chancy({mean: 10, stddev: 5, type: 'normal'});
// A random normal number centered at 10 with a standard deviation of 5, skewed a bit to the left
rng.chancy({mean: 10, stddev: 5, skew: -1, type: 'normal'});
// A random normal number centered at 10 with a standard deviation of 5, skewed a bit to the right
rng.chancy({mean: 10, stddev: 5, skew: 1, type: 'normal'});
// A random normal number centered at 10 with a standard deviation of 5, rounded down to an integer (also accepts skew)
rng.chancy({mean: 10, stddev: 5, type: 'normal_integer'});
As you can see, it's quite flexible, and allows for many ways of specifying chances in your loot tables.
The rng object is also passed to any functions or conditions on tables, so you can use chancy stuff there as well.
You can find more information about Chancy here.
Usage
You can find the documentation for rolling here. Particularly, the arguments to the roll function, which are passed as ab object.
Here is a simple example:
const ul = new UltraLoot();
const definition = {
name: 'Precious Metals',
id: 'precious_metals',
pools: [
{
rolls: 1,
nulls: 0,
entries: [
{
name: 'Gold',
id:
