UNPKG

lootastic

Version:
122 lines (93 loc) 3.42 kB
import { select } from 'weighted-randomly-select'; import * as random from 'lodash.random'; export enum LootFunction { WithReplacement = 'chooseWithReplacement', WithoutReplacement = 'chooseWithoutReplacement', EachItem = 'tryToDropEachItem' } export interface LootRollerConfig { table: LootTable; func: LootFunction; args: number; } export interface LootTableOptions { rollModifier: number; seed?: number } export interface Rollable { result: string; chance: number; maxChance?: number; } export class LootRoller { static rollTables(lootTableConfigs: LootRollerConfig[]) { const items = []; lootTableConfigs.forEach(({ table, func, args }) => { items.push(...table[func](args)); }); return items; } static rollTable(lootTableConfig: LootRollerConfig) { return this.rollTables([lootTableConfig]); } }; export class LootTable { private choices: Rollable[]; private rollModifier: number; constructor(choices: string[]|Rollable[], { rollModifier }: LootTableOptions = { rollModifier: 0 }) { this.choices = this.configureChoices(choices); this.rollModifier = rollModifier || 0; } private getFormattedChoices(): { alwaysDrop: string[], choices: Rollable[] } { const alwaysDrop = []; const choices = this.choices.map(x => { // if the chance < -1, it always drops if(x.chance <= 0) { alwaysDrop.push(x.result); return null; } // if there is for some reason a negative total roll, this item can never drop const totalChance = x.chance + this.rollModifier; if(totalChance <= 0) { return null; } return { result: x.result, chance: totalChance, maxChance: x.maxChance }; }).filter(Boolean); return { alwaysDrop, choices }; } private configureChoices(choices: string[]|Rollable[]): Rollable[] { // pre-formatted with weights if((choices[0] as Rollable).chance) return choices as Rollable[]; // assign a weight of 1 to everything return (choices as string[]).map(item => ({ result: item, chance: 1 })); } public chooseWithReplacement(numItems = 1): string[] { const { choices, alwaysDrop } = this.getFormattedChoices(); return alwaysDrop.concat( Array(numItems).fill(null).map(() => select(choices)) ); } public chooseWithoutReplacement(numItems = 1): string[] { const { choices, alwaysDrop } = this.getFormattedChoices(); if(numItems > choices.length) throw new Error(`Cannot choose ${numItems} without replacement when array is only ${choices.length} choices.`); return alwaysDrop.concat( Array(numItems).fill(null).map(() => { // choose something const choice = select(choices); // remove old choice choices.splice(choices.indexOf(choice), 1); return choice; }) ); } public tryToDropEachItem(comparisonValue: number): string[] { const { choices, alwaysDrop } = this.getFormattedChoices(); return alwaysDrop.concat( choices.map(({ result, chance, maxChance }) => { const randomValue = random(0, maxChance || comparisonValue || 1); if(chance >= randomValue) return result; return null; }).filter(Boolean) ); } };