SkillAgentSearch skills...

Ultraloot

Highly functional loot table implementation in Javascript. Extendable, inheritance based tables with multiple pools and a flexible workflow.

Install / Use

/learn @manticorp/Ultraloot
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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:    
View on GitHub
GitHub Stars39
CategoryDevelopment
Updated4mo ago
Forks2

Languages

TypeScript

Security Score

87/100

Audited on Nov 10, 2025

No findings