From e5f6407f1e3f043fcab1736dfd9c182895af172c Mon Sep 17 00:00:00 2001 From: gc <30398469+gc@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:05:30 +1000 Subject: [PATCH 1/3] changes --- bench/LootTable.bench.ts | 46 ++++++++++++++ scripts/enum.ts | 15 +++-- src/meta/types.ts | 3 + src/structures/Bank.ts | 123 +++++++++++++++++++----------------- src/structures/LootTable.ts | 38 +++++++++-- test/Bank.test.ts | 117 +++++++++++++++++----------------- test/BankClass.test.ts | 7 +- vitest.config.mts | 3 + 8 files changed, 220 insertions(+), 132 deletions(-) create mode 100644 bench/LootTable.bench.ts diff --git a/bench/LootTable.bench.ts b/bench/LootTable.bench.ts new file mode 100644 index 000000000..4c63b90a5 --- /dev/null +++ b/bench/LootTable.bench.ts @@ -0,0 +1,46 @@ +import { bench, describe } from "vitest"; +import { LootTable } from "../src"; + +const New = new LootTable() + .add("Spirit shield", 1, 8) + .add("Holy elixir", 1, 3) + .oneIn(585, new LootTable().add("Spectral sigil", 1, 3).add("Arcane sigil", 1, 3).add("Elysian sigil", 1, 1)) + .add("Mystic robe top", 1, 18) + .add("Mystic robe bottom", 1, 18) + .add("Mystic air staff", 1, 12) + .add("Mystic water staff", 1, 12) + .add("Mystic earth staff", 1, 12) + .add("Mystic fire staff", 1, 12) + .add("Soul rune", 250, 32) + .add("Runite bolts", 250, 24) + .add("Death rune", 300, 22) + .add("Onyx bolts (e)", 175, 20) + .add("Cannonball", 2000, 17) + .add("Adamant arrow", 750, 17) + .add("Law rune", 250, 17) + .add("Cosmic rune", 500, 17) + .add("Raw shark", 70, 21) + .add("Pure essence", 2500, 21) + .add("Adamantite bar", 35, 18) + .add("Green dragonhide", 100, 18) + .add("Adamantite ore", 125, 17) + .add("Runite ore", 20, 12) + .add("Teak plank", 100, 12) + .add("Mahogany logs", 150, 12) + .add("Magic logs", 75, 12) + .add("Tuna potato", 30, 20) + .add("White berries", 120, 17) + .add("Desert goat horn", 120, 17) + .add("Watermelon seed", 24, 15) + .add("Coins", [20_000, 50_000], 12) + .add("Antidote++(4)", 40, 10) + .add("Ranarr seed", 10, 5) + .tertiary(200, "Clue scroll (elite)") + .tertiary(1000, "Jar of spirits") + .tertiary(5000, "Pet dark core"); + +describe("LootTable Implementations", () => { + bench("1", () => { + New.roll(); + }); +}); diff --git a/scripts/enum.ts b/scripts/enum.ts index 85fae1a26..db810028b 100644 --- a/scripts/enum.ts +++ b/scripts/enum.ts @@ -4,6 +4,14 @@ import { Items, Monsters } from "../src"; import { USELESS_ITEMS } from "../src/structures/Items"; import { moidLink } from "./prepareItems"; +export function safeItemName(itemName: string) { + let key = itemName; + key = key.replace("3rd", "third"); + key = key.replace(/[^\w\s]|_/g, ""); + key = key.replace(/\s+/g, "_"); + key = key.toUpperCase(); + return key; +} const exitingKeys = new Set(); const duplicates = new Set(); let str = "export enum EItem {"; @@ -32,11 +40,8 @@ outer: for (const item of Items.values()) { if (USELESS_ITEMS.includes(item.id)) { continue; } - let key = item.wiki_name ?? item.name; - key = key.replace("3rd", "third"); - key = key.replace(/[^\w\s]|_/g, ""); - key = key.replace(/\s+/g, "_"); - key = key.toUpperCase(); + const key = safeItemName(item.wiki_name ?? item.name); + if (exitingKeys.has(key)) { duplicates.add(item.id); continue; diff --git a/src/meta/types.ts b/src/meta/types.ts index e9ace2773..a95b0444b 100644 --- a/src/meta/types.ts +++ b/src/meta/types.ts @@ -275,6 +275,9 @@ export interface WikiPage { }[]; } +export interface IntKeyBank { + [key: number]: number; +} export interface ItemBank { [key: string]: number; } diff --git a/src/structures/Bank.ts b/src/structures/Bank.ts index e04a53c6e..2bb2fa5c5 100644 --- a/src/structures/Bank.ts +++ b/src/structures/Bank.ts @@ -1,55 +1,74 @@ import { randArrItem } from "e"; -import type { BankItem, Item, ItemBank, ReturnedLootItem } from "../meta/types"; -import { fasterResolveBank, resolveNameBank } from "../util/bank"; +import type { BankItem, IntKeyBank, Item, ItemBank, ReturnedLootItem } from "../meta/types"; import itemID from "../util/itemID"; import Items from "./Items"; const frozenErrorStr = "Tried to mutate a frozen Bank."; +const isValidInteger = (str: string): boolean => /^-?\d+$/.test(str); + +function makeFromInitialBankX(initialBank?: IntKeyBank | ItemBank | Bank) { + if (!initialBank) return new Map(); + if (initialBank instanceof Bank) { + return new Map(initialBank.map.entries()); + } + const entries = Object.entries(initialBank); + if (entries.length === 0) return new Map(); + if (isValidInteger(entries[0][0])) { + return new Map(entries.map(([k, v]) => [Number(k), v])); + } else { + return new Map(entries.map(([k, v]) => [Items.get(k)!.id, v])); + } +} + export default class Bank { - public bank: ItemBank = {}; + public map: Map; public frozen = false; - constructor(initialBank?: ItemBank | Bank) { - if (initialBank) { - this.bank = JSON.parse( - JSON.stringify(initialBank instanceof Bank ? initialBank.bank : fasterResolveBank(initialBank)), - ); - } + constructor(initialBank?: IntKeyBank | ItemBank | Bank) { + this.map = makeFromInitialBankX(initialBank); + } + + get bank() { + return Object.fromEntries(this.map); } public freeze(): this { this.frozen = true; - Object.freeze(this.bank); + Object.freeze(this.map); return this; } public amount(item: string | number): number { - return this.bank[typeof item === "string" ? itemID(item) : item] ?? 0; + const itemIDNum = typeof item === "string" ? itemID(item) : item; + return this.map.get(itemIDNum) ?? 0; } public addItem(item: number, quantity = 1): this { + if (this.frozen) throw new Error(frozenErrorStr); if (quantity < 1) return this; - if (this.bank[item]) this.bank[item] += quantity; - else this.bank[item] = quantity; + const current = this.map.get(item) ?? 0; + this.map.set(item, current + quantity); return this; } public removeItem(item: number | string, quantity = 1): this { - const currentValue = this.bank[item]; + if (this.frozen) throw new Error(frozenErrorStr); + const itemIDNum = typeof item === "string" ? itemID(item) : item; + const currentValue = this.map.get(itemIDNum); - if (typeof currentValue === "undefined") return this; + if (currentValue === undefined) return this; if (currentValue - quantity <= 0) { - delete this.bank[item]; + this.map.delete(itemIDNum); } else { - this.bank[item] = currentValue - quantity; + this.map.set(itemIDNum, currentValue - quantity); } return this; } - public add(item: string | number | ReturnedLootItem[] | ItemBank | Bank | Item | undefined, quantity = 1): Bank { + public add(item: string | number | ReturnedLootItem[] | IntKeyBank | Bank | Item | undefined, quantity = 1): Bank { if (this.frozen) throw new Error(frozenErrorStr); // Bank.add(123); @@ -64,7 +83,10 @@ export default class Bank { } if (item instanceof Bank) { - return this.add(item.bank); + for (const [itemID, qty] of item.map.entries()) { + this.addItem(itemID, qty); + } + return this; } if (!item) { @@ -81,17 +103,15 @@ export default class Bank { return this.addItem(_item.id, quantity); } - const firstKey: string | undefined = Object.keys(item)[0]; - if (firstKey === undefined) { - return this; - } - - if (Number.isNaN(Number(firstKey))) { - this.add(resolveNameBank(item)); - } else { - for (const [itemID, quantity] of Object.entries(item)) { - this.addItem(Number.parseInt(itemID), quantity); + for (const [itemID, qty] of Object.entries(item)) { + let int: number | undefined = Number.parseInt(itemID); + if (Number.isNaN(int)) { + int = Items.get(itemID)?.id; } + if (!int) { + throw new Error(`${itemID} is not a valid name or id`); + } + this.addItem(int, qty); } return this; @@ -112,44 +132,34 @@ export default class Bank { } if (item instanceof Bank) { - for (const [key, value] of Object.entries(item.bank)) { - this.removeItem(key, value); + for (const [itemID, qty] of item.map.entries()) { + this.removeItem(itemID, qty); if (this.length === 0) break; } return this; } - const firstKey = Object.keys(item)[0]; - if (firstKey === undefined) { - return this; - } - if (Array.isArray(item)) { for (const _item of item) this.remove(_item.item, _item.quantity); return this; } - if (Number.isNaN(Number(firstKey))) { - this.remove(resolveNameBank(item)); - } else { - return this.remove(new Bank(item)); - } - + this.remove(new Bank(item)); return this; } public random(): BankItem | null { - const entries = Object.entries(this.bank); + const entries = Array.from(this.map.entries()); if (entries.length === 0) return null; const randomEntry = randArrItem(entries); - return { id: Number(randomEntry[0]), qty: randomEntry[1] }; + return { id: randomEntry[0], qty: randomEntry[1] }; } public multiply(multiplier: number, itemsToNotMultiply?: number[]): this { if (this.frozen) throw new Error(frozenErrorStr); - for (const itemID of Object.keys(this.bank).map(Number)) { + for (const [itemID, quantity] of this.map.entries()) { if (itemsToNotMultiply?.includes(itemID)) continue; - this.bank[itemID] *= multiplier; + this.map.set(itemID, quantity * multiplier); } return this; } @@ -176,8 +186,8 @@ export default class Bank { public items(): [Item, number][] { const arr: [Item, number][] = []; - for (const [key, val] of Object.entries(this.bank)) { - arr.push([Items.get(Number.parseInt(key))!, val]); + for (const [key, val] of this.map.entries()) { + arr.push([Items.get(key)!, val]); } return arr; } @@ -189,7 +199,7 @@ export default class Bank { } public clone(): Bank { - return new Bank({ ...this.bank }); + return new Bank(this); } public fits(bank: Bank): number { @@ -198,36 +208,31 @@ export default class Bank { return divisions[0] ?? 0; } - public filter(fn: (item: Item, quantity: number) => boolean, mutate = false): Bank { + public filter(fn: (item: Item, quantity: number) => boolean): Bank { const result = new Bank(); for (const item of this.items()) { if (fn(...item)) { result.add(item[0].id, item[1]); } } - if (mutate) { - if (this.frozen) throw new Error(frozenErrorStr); - this.bank = result.bank; - return this; - } return result; } public toString(): string { - const entries = Object.entries(this.bank); + const entries = Array.from(this.map.entries()); if (entries.length === 0) { return "No items"; } const res = []; for (const [id, qty] of entries.sort((a, b) => b[1] - a[1])) { - res.push(`${qty.toLocaleString()}x ${Items.get(Number(id))?.name ?? "Unknown item"}`); + res.push(`${qty.toLocaleString()}x ${Items.get(id)?.name ?? "Unknown item"}`); } return res.join(", "); } public get length(): number { - return Object.keys(this.bank).length; + return this.map.size; } public value(): number { @@ -243,7 +248,7 @@ export default class Bank { for (const [item, quantity] of this.items()) { if (otherBank.amount(item.id) !== quantity) return false; } - return JSON.stringify(this.bank) === JSON.stringify(otherBank.bank); + return JSON.stringify([...this.map]) === JSON.stringify([...otherBank.map]); } public difference(otherBank: Bank): Bank { diff --git a/src/structures/LootTable.ts b/src/structures/LootTable.ts index 876e7e6f6..32418094f 100644 --- a/src/structures/LootTable.ts +++ b/src/structures/LootTable.ts @@ -1,10 +1,38 @@ -import { randFloat, randInt, reduceNumByPercent, roll } from "e"; - -import type { LootTableItem, LootTableMoreOptions, LootTableOptions, OneInItems } from "../meta/types"; +import type { LootTableOptions } from "../meta/types"; import itemID from "../util/itemID"; import Bank from "./Bank"; import Items from "./Items"; +export function reduceNumByPercent(value: number, percent: number): number { + if (percent <= 0) return value; + return value - value * (percent / 100); +} +export function randInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1) + min); +} +export function randFloat(min: number, max: number): number { + return Math.random() * (max - min) + min; +} + +export function roll(upperLimit: number): boolean { + return randInt(1, upperLimit) === 1; +} + +export interface LootTableMoreOptions { + multiply?: boolean; + freeze?: boolean; +} + +export interface LootTableItem { + item: number | LootTable | LootTableItem[]; + weight?: number; + quantity: number | number[]; + options?: LootTableMoreOptions; +} + +export interface OneInItems extends LootTableItem { + chance: number; +} export function isArrayOfItemTuples(x: readonly unknown[]): x is [string, (number | number[])?][] { return Array.isArray(x[0]); } @@ -178,9 +206,7 @@ export default class LootTable { return this; } - public roll(quantity = 1, options?: LootTableRollOptions): Bank { - const loot = new Bank(); - + public roll(quantity = 1, options?: LootTableRollOptions, loot = new Bank()): Bank { const effectiveTertiaryItems = options?.tertiaryItemPercentageChanges ? this.tertiaryItems.map(i => { if (typeof i.item !== "number") return i; diff --git a/test/Bank.test.ts b/test/Bank.test.ts index 6b4d346cf..4fba556ce 100644 --- a/test/Bank.test.ts +++ b/test/Bank.test.ts @@ -22,24 +22,30 @@ describe("Bank", () => { test("bank has all items", () => { expect.assertions(2); - const bankToHave = new Bank({ - "Fire rune": 1000, - "Air rune": 1, - "Chaos rune": 101_010, - }); + const bankToHave = new Bank( + resolveNameBank({ + "Fire rune": 1000, + "Air rune": 1, + "Chaos rune": 101_010, + }), + ); - const bankThatShouldntHave = new Bank({ - "Fire rune": 1000, - "Air rune": 1, - "Chaos rune": 1, - }); + const bankThatShouldntHave = new Bank( + resolveNameBank({ + "Fire rune": 1000, + "Air rune": 1, + "Chaos rune": 1, + }), + ); - const bankThatShouldHave = new Bank({ - "Fire rune": 104_200, - "Air rune": 43_432, - "Chaos rune": 121_010, - "Death rune": 121_010, - }); + const bankThatShouldHave = new Bank( + resolveNameBank({ + "Fire rune": 104_200, + "Air rune": 43_432, + "Chaos rune": 121_010, + "Death rune": 121_010, + }), + ); expect(bankThatShouldHave.has(bankToHave)).toBeTruthy(); expect(bankThatShouldntHave.has(bankToHave)).toBeFalsy(); @@ -69,19 +75,25 @@ describe("Bank", () => { test("remove bank from bank", () => { expect.assertions(1); - const sourceBank = new Bank({ - "Fire rune": 100, - "Air rune": 50, - }); + const sourceBank = new Bank( + resolveNameBank({ + "Fire rune": 100, + "Air rune": 50, + }), + ); - const bankToRemove = new Bank({ - "Fire rune": 50, - "Air rune": 50, - }); + const bankToRemove = new Bank( + resolveNameBank({ + "Fire rune": 50, + "Air rune": 50, + }), + ); - const expectedBank = new Bank({ - "Fire rune": 50, - }); + const expectedBank = new Bank( + resolveNameBank({ + "Fire rune": 50, + }), + ); sourceBank.remove(bankToRemove); expect(sourceBank.equals(expectedBank)).toBeTruthy(); @@ -147,38 +159,30 @@ describe("Bank", () => { expect(bank.amount("Egg")).toEqual(100); }); - test("mutate filter", () => { - const bank = new Bank({ - Toolkit: 2, - "Ammo Mould": 4, - Candle: 1, - }); - expect(bank.length).toEqual(3); - const empty = bank.filter(() => false); - expect(bank.length).toEqual(3); - expect(empty.length).toEqual(0); - bank.filter(item => item.name === "Candle", true); - expect(bank.length).toEqual(1); - }); - test("value", () => { - const bank = new Bank({ - Toolkit: 2, - }); + const bank = new Bank( + resolveNameBank({ + Toolkit: 2, + }), + ); expect(bank.value()).toEqual(0); const runePlatebody = Items.get("Rune platebody")!; - const bank2 = new Bank({ - "Rune platebody": 10, - }); + const bank2 = new Bank( + resolveNameBank({ + "Rune platebody": 10, + }), + ); expect(runePlatebody.price).toBeGreaterThan(25_000); expect(bank2.value()).toEqual(runePlatebody.price * 10); - const bank3 = new Bank({ - "Rune platebody": 10, - "Rune platelegs": 10, - "Rune boots": 10, - Toolkit: 1, - "Abyssal book": 10_000, - }); + const bank3 = new Bank( + resolveNameBank({ + "Rune platebody": 10, + "Rune platelegs": 10, + "Rune boots": 10, + Toolkit: 1, + "Abyssal book": 10_000, + }), + ); expect(runePlatebody.price).toBeGreaterThan(25_000); expect(bank3.value()).toEqual( runePlatebody.price * 10 + Items.get("Rune platelegs")!.price * 10 + Items.get("Rune boots")!.price * 10, @@ -194,7 +198,7 @@ describe("Bank", () => { start[2] = 1; bank.bank[2] = 1; bank = bank.multiply(100); - bank.bank = {}; + bank.map.clear(); expect(bankToTest.amount(1)).toEqual(1); expect(bankToTest.length).toEqual(1); }); @@ -219,9 +223,6 @@ describe("Bank", () => { try { bank.multiply(5); } catch {} - try { - bank.filter(() => true, true); - } catch {} expect(bank.amount("Twisted bow")).toEqual(73); }); diff --git a/test/BankClass.test.ts b/test/BankClass.test.ts index 5af3be133..08a671912 100644 --- a/test/BankClass.test.ts +++ b/test/BankClass.test.ts @@ -75,10 +75,10 @@ describe("Bank Class", () => { bank.remove({ 1: 4 }); expect(bank.amount(1)).toBe(0); - bank.add({ Toolkit: 4 }); + bank.add(resolveNameBank({ Toolkit: 4 })); expect(bank.amount(1)).toBe(4); - bank.remove({ Toolkit: 4 }); + bank.remove(resolveNameBank({ Toolkit: 4 })); expect(bank.amount(1)).toBe(0); bank.add(TestLootTable.roll()); @@ -281,8 +281,7 @@ describe("Bank Class", () => { const bank = new Bank().add("Twisted bow").freeze(); expect(() => bank.add("Coal")).toThrow(); - expect(() => (bank.bank[itemID("Coal")] = 1)).toThrow(); - expect(() => delete bank.bank[itemID("Twisted bow")]).toThrow(); + expect(() => bank.remove("Twisted bow")).toThrow(); expect(bank.bank).toEqual({ [itemID("Twisted bow")]: 1 }); }); diff --git a/vitest.config.mts b/vitest.config.mts index 572e2a43e..ef11a5287 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -3,6 +3,9 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { name: "OldschoolJS", + benchmark: { + include: ["bench/**/*.bench.ts"], + }, include: ["test/**/*.test.ts"], coverage: { provider: "v8", From a3db900f3a6678ca126842fa40a19b3c6f12c171 Mon Sep 17 00:00:00 2001 From: gc <30398469+gc@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:13:04 +1000 Subject: [PATCH 2/3] change --- src/simulation/clues/Master.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/simulation/clues/Master.ts b/src/simulation/clues/Master.ts index d05a39b75..b4a5be2eb 100644 --- a/src/simulation/clues/Master.ts +++ b/src/simulation/clues/Master.ts @@ -167,12 +167,7 @@ export class MasterCasket extends Clue { for (let i = 0; i < quantity; i++) { if (roll(1000)) loot.add("Bloodhound"); - - const numberOfRolls = randInt(5, 7); - - for (let i = 0; i < numberOfRolls; i++) { - loot.add(MasterClueTable.roll()); - } + MasterClueTable.roll(randInt(5, 7), { targetBank: loot }); } return loot; From e01e307ee4032a224bdbc4b3399054afa1dcbc46 Mon Sep 17 00:00:00 2001 From: gc <30398469+gc@users.noreply.github.com> Date: Wed, 11 Sep 2024 00:23:29 +1000 Subject: [PATCH 3/3] changes --- src/simulation/clues/Beginner.ts | 18 ++++---- src/simulation/clues/Easy.ts | 21 +++------- src/simulation/clues/Elite.ts | 21 +++------- src/simulation/clues/Hard.ts | 35 +++++++--------- src/simulation/clues/Master.ts | 17 ++++---- src/simulation/clues/Medium.ts | 22 ++++------ src/simulation/monsters/bosses/Bryophyta.ts | 5 ++- .../monsters/bosses/CommanderZilyana.ts | 9 ++-- src/simulation/monsters/bosses/Kreearra.ts | 5 ++- .../monsters/bosses/KrilTsutsaroth.ts | 9 ++-- src/simulation/monsters/bosses/Obor.ts | 5 ++- .../monsters/bosses/slayer/AlchemicalHydra.ts | 13 +++--- .../bosses/slayer/GrotesqueGuardians.ts | 5 ++- .../monsters/bosses/wildy/ChaosFanatic.ts | 9 ++-- .../bosses/wildy/CrazyArchaeologist.ts | 5 ++- src/simulation/monsters/low/a-f/Bloodveld.ts | 9 ++-- .../monsters/low/g-m/JungleHorror.ts | 9 ++-- src/simulation/openables/CrystalChest.ts | 17 ++++---- src/simulation/openables/ElvenCrystalChest.ts | 41 ++++++++++--------- src/simulation/openables/GrubbyChest.ts | 13 +++--- src/structures/Bank.ts | 17 ++++---- src/structures/LootTable.ts | 35 +++------------- src/util/smallUtils.ts | 28 +++++++++++++ src/util/util.ts | 39 +++++------------- test/BankClass.test.ts | 6 +-- test/Monsters.test.ts | 8 +++- 26 files changed, 194 insertions(+), 227 deletions(-) create mode 100644 src/util/smallUtils.ts diff --git a/src/simulation/clues/Beginner.ts b/src/simulation/clues/Beginner.ts index 9c2b3e593..10fbd9b84 100644 --- a/src/simulation/clues/Beginner.ts +++ b/src/simulation/clues/Beginner.ts @@ -1,5 +1,3 @@ -import { randInt } from "e"; - import Bank from "../../structures/Bank"; import Clue from "../../structures/Clue"; import LootTable from "../../structures/LootTable"; @@ -88,16 +86,14 @@ export const StandardTable = new LootTable() export const BeginnerClueTable = new LootTable().add(StandardTable, 1, 11).add(UniqueTable, 1, 1); -export class BeginnerCasket extends Clue { - public open(quantity = 1): Bank { - const loot = new Bank(); - for (let i = 0; i < quantity; i++) { - const numberOfRolls = randInt(1, 3); +const MainTable = new LootTable().add(BeginnerClueTable, [1, 3]); - for (let i = 0; i < numberOfRolls; i++) { - loot.add(BeginnerClueTable.roll()); - } - } +export class BeginnerCasket extends Clue { + open(quantity: number, targetBank?: undefined): Bank; + open(quantity: number, targetBank: Bank): null; + public open(quantity: number, targetBank?: Bank): Bank | null { + const loot = targetBank ?? new Bank(); + MainTable.roll(quantity, { targetBank: loot }); return loot; } } diff --git a/src/simulation/clues/Easy.ts b/src/simulation/clues/Easy.ts index 0fe3d2270..cb696744f 100644 --- a/src/simulation/clues/Easy.ts +++ b/src/simulation/clues/Easy.ts @@ -1,5 +1,3 @@ -import { randInt, roll } from "e"; - import Bank from "../../structures/Bank"; import Clue from "../../structures/Clue"; import LootTable from "../../structures/LootTable"; @@ -189,20 +187,13 @@ export const EasyStandardTable = new LootTable() export const EasyClueTable = new LootTable().add(EasyStandardTable, 1, 11).add(EasyRareTable, 1, 1); +const MainTable = new LootTable().add(EasyClueTable, [2, 4]).tertiary(50, "Clue scroll (master)"); export class EasyCasket extends Clue { - public open(quantity = 1): Bank { - const loot = new Bank(); - - for (let i = 0; i < quantity; i++) { - const numberOfRolls = randInt(2, 4); - - if (roll(50)) loot.add("Clue scroll (master)"); - - for (let i = 0; i < numberOfRolls; i++) { - loot.add(EasyClueTable.roll()); - } - } - + open(quantity: number, targetBank?: undefined): Bank; + open(quantity: number, targetBank: Bank): null; + public open(quantity: number, targetBank?: Bank): Bank | null { + const loot = targetBank ?? new Bank(); + MainTable.roll(quantity, { targetBank: loot }); return loot; } } diff --git a/src/simulation/clues/Elite.ts b/src/simulation/clues/Elite.ts index 64a5e3cdf..6dba696be 100644 --- a/src/simulation/clues/Elite.ts +++ b/src/simulation/clues/Elite.ts @@ -1,5 +1,3 @@ -import { randInt, roll } from "e"; - import Bank from "../../structures/Bank"; import Clue from "../../structures/Clue"; import LootTable from "../../structures/LootTable"; @@ -152,21 +150,14 @@ export const EliteStandardTable = new LootTable() .add(BlessingTable); export const EliteClueTable = new LootTable().add(EliteStandardTable, 1, 24).add(EliteRareTable, 1, 1); +const MainTable = new LootTable().add(EliteClueTable, [4, 6]).tertiary(5, "Clue scroll (master)"); export class EliteCasket extends Clue { - public open(quantity = 1): Bank { - const loot = new Bank(); - - for (let i = 0; i < quantity; i++) { - const numberOfRolls = randInt(4, 6); - - if (roll(5)) loot.add("Clue scroll (master)"); - - for (let i = 0; i < numberOfRolls; i++) { - loot.add(EliteClueTable.roll()); - } - } - + open(quantity: number, targetBank?: undefined): Bank; + open(quantity: number, targetBank: Bank): null; + public open(quantity: number, targetBank?: Bank): Bank | null { + const loot = targetBank ?? new Bank(); + MainTable.roll(quantity, { targetBank: loot }); return loot; } } diff --git a/src/simulation/clues/Hard.ts b/src/simulation/clues/Hard.ts index be39caf7b..df496ba14 100644 --- a/src/simulation/clues/Hard.ts +++ b/src/simulation/clues/Hard.ts @@ -1,9 +1,7 @@ -import { randInt, roll } from "e"; - import Bank from "../../structures/Bank"; import Clue from "../../structures/Clue"; import LootTable from "../../structures/LootTable"; -import { itemID } from "../../util"; +import { itemID, itemTupleToTable } from "../../util"; import { BlessingTable, FirelighterTable, GildedTable, PrayerPageTable, TeleportScrollTable } from "./General"; export const Hard3rdageTable = new LootTable() @@ -25,11 +23,13 @@ export const HardMegaRareTable = new LootTable() .add("Super energy(4)", 15) .add("Super restore(4)", 15) .add("Antifire potion(4)", 15) - .add([ - ["Super attack(4)", 5], - ["Super strength(4)", 5], - ["Super defence(4)", 5], - ]) + .add( + itemTupleToTable([ + ["Super attack(4)", 5], + ["Super strength(4)", 5], + ["Super defence(4)", 5], + ]), + ) .add(Hard3rdageTable) .add(GildedTable, 1, 5); @@ -206,21 +206,14 @@ export const HardStandardTable = new LootTable() .add(HardBowTable); export const HardClueTable = new LootTable().add(HardStandardTable, 1, 12).add(HardRareTable, 1, 1); +const MainTable = new LootTable().add(HardClueTable, [4, 6]).tertiary(15, "Clue scroll (master)"); export class HardCasket extends Clue { - public open(quantity = 1): Bank { - const loot = new Bank(); - - for (let i = 0; i < quantity; i++) { - const numberOfRolls = randInt(4, 6); - - if (roll(15)) loot.add("Clue scroll (master)"); - - for (let i = 0; i < numberOfRolls; i++) { - loot.add(HardClueTable.roll()); - } - } - + open(quantity: number, targetBank?: undefined): Bank; + open(quantity: number, targetBank: Bank): null; + public open(quantity: number, targetBank?: Bank): Bank | null { + const loot = targetBank ?? new Bank(); + MainTable.roll(quantity, { targetBank: loot }); return loot; } } diff --git a/src/simulation/clues/Master.ts b/src/simulation/clues/Master.ts index b4a5be2eb..95f016184 100644 --- a/src/simulation/clues/Master.ts +++ b/src/simulation/clues/Master.ts @@ -1,5 +1,3 @@ -import { randInt, roll } from "e"; - import Bank from "../../structures/Bank"; import Clue from "../../structures/Clue"; import LootTable from "../../structures/LootTable"; @@ -161,15 +159,14 @@ export const MasterStandardTable = new LootTable() export const MasterClueTable = new LootTable().add(MasterStandardTable, 1, 22).add(MasterRareTable, 1, 1); -export class MasterCasket extends Clue { - public open(quantity = 1): Bank { - const loot = new Bank(); - - for (let i = 0; i < quantity; i++) { - if (roll(1000)) loot.add("Bloodhound"); - MasterClueTable.roll(randInt(5, 7), { targetBank: loot }); - } +const MainTable = new LootTable().add(MasterClueTable, [5, 7]).tertiary(1000, "Bloodhound"); +export class MasterCasket extends Clue { + open(quantity: number, targetBank?: undefined): Bank; + open(quantity: number, targetBank: Bank): null; + public open(quantity: number, targetBank?: Bank): Bank | null { + const loot = targetBank ?? new Bank(); + MainTable.roll(quantity, { targetBank: loot }); return loot; } } diff --git a/src/simulation/clues/Medium.ts b/src/simulation/clues/Medium.ts index 49999be96..a0050beee 100644 --- a/src/simulation/clues/Medium.ts +++ b/src/simulation/clues/Medium.ts @@ -1,5 +1,3 @@ -import { randInt, roll } from "e"; - import Bank from "../../structures/Bank"; import Clue from "../../structures/Clue"; import LootTable from "../../structures/LootTable"; @@ -173,20 +171,14 @@ export const MediumStandardTable = new LootTable() export const MediumClueTable = new LootTable().add(MediumStandardTable, 1, 10).add(MediumRareTable, 1, 1); -export class MediumCasket extends Clue { - public open(quantity = 1): Bank { - const loot = new Bank(); - - for (let i = 0; i < quantity; i++) { - const numberOfRolls = randInt(3, 5); - - if (roll(30)) loot.add("Clue scroll (master)"); - - for (let i = 0; i < numberOfRolls; i++) { - loot.add(MediumClueTable.roll()); - } - } +const MainTable = new LootTable().add(MediumClueTable, [3, 5]).tertiary(30, "Clue scroll (master)"); +export class MediumCasket extends Clue { + open(quantity: number, targetBank?: undefined): Bank; + open(quantity: number, targetBank: Bank): null; + public open(quantity: number, targetBank?: Bank): Bank | null { + const loot = targetBank ?? new Bank(); + MainTable.roll(quantity, { targetBank: loot }); return loot; } } diff --git a/src/simulation/monsters/bosses/Bryophyta.ts b/src/simulation/monsters/bosses/Bryophyta.ts index 9f64cbb3b..9e3100641 100644 --- a/src/simulation/monsters/bosses/Bryophyta.ts +++ b/src/simulation/monsters/bosses/Bryophyta.ts @@ -1,5 +1,6 @@ import LootTable from "../../../structures/LootTable"; import SimpleMonster from "../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../util"; import HerbDropTable from "../../subtables/HerbDropTable"; import UncommonSeedDropTable from "../../subtables/UncommonSeedDropTable"; @@ -39,10 +40,10 @@ const BryophytaTable = new LootTable() /* Materials */ .add("Runite bar", 2, 6) .add( - [ + itemTupleToTable([ ["Uncut ruby", 5], ["Uncut diamond", 5], - ], + ]), 1, 4, ) diff --git a/src/simulation/monsters/bosses/CommanderZilyana.ts b/src/simulation/monsters/bosses/CommanderZilyana.ts index fce5aa450..d0bb3c010 100644 --- a/src/simulation/monsters/bosses/CommanderZilyana.ts +++ b/src/simulation/monsters/bosses/CommanderZilyana.ts @@ -1,5 +1,6 @@ import LootTable from "../../../structures/LootTable"; import SimpleMonster from "../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../util"; import GWRareDropTable, { GWGemTable, ShardTable } from "../../subtables/GWRareDropTable"; const MinionUniqueTable = new LootTable().add("Coins", [1400, 1500], 124).add("Saradomin sword", 1, 3); @@ -45,18 +46,18 @@ const CommanderZilyanaTable = new LootTable() /* Potions */ .add("Prayer potion(4)", 3, 8) .add( - [ + itemTupleToTable([ ["Super defence(3)", 3], ["Magic potion(3)", 3], - ], + ]), 1, 8, ) .add( - [ + itemTupleToTable([ ["Saradomin brew(3)", 3], ["Super restore(4)", 3], - ], + ]), 1, 6, ) diff --git a/src/simulation/monsters/bosses/Kreearra.ts b/src/simulation/monsters/bosses/Kreearra.ts index fc791ca0d..7b01bed86 100644 --- a/src/simulation/monsters/bosses/Kreearra.ts +++ b/src/simulation/monsters/bosses/Kreearra.ts @@ -1,5 +1,6 @@ import LootTable from "../../../structures/LootTable"; import SimpleMonster from "../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../util"; import GWRareDropTable, { GWGemTable, ShardTable } from "../../subtables/GWRareDropTable"; const KreearraArmorTable = new LootTable().add("Armadyl helmet").add("Armadyl chestplate").add("Armadyl chainskirt"); @@ -52,10 +53,10 @@ const KreearraTable = new LootTable() /* Other */ .add("Coins", [19_500, 20_000], 40) .add( - [ + itemTupleToTable([ ["Ranging potion(3)", 3], ["Super defence(3)", 3], - ], + ]), 1, 8, ) diff --git a/src/simulation/monsters/bosses/KrilTsutsaroth.ts b/src/simulation/monsters/bosses/KrilTsutsaroth.ts index 939051847..97762ea3a 100644 --- a/src/simulation/monsters/bosses/KrilTsutsaroth.ts +++ b/src/simulation/monsters/bosses/KrilTsutsaroth.ts @@ -1,5 +1,6 @@ import LootTable from "../../../structures/LootTable"; import SimpleMonster from "../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../util"; import GWRareDropTable, { GWGemTable, ShardTable } from "../../subtables/GWRareDropTable"; const MinionUniqueTable = new LootTable().add("Coins", [1300, 1400], 124).add("Zamorakian spear", 1, 3); @@ -50,18 +51,18 @@ const KrilTsutsarothTable = new LootTable() /* Potions */ .add( - [ + itemTupleToTable([ ["Super attack(3)", 3], ["Super strength(3)", 3], - ], + ]), 1, 8, ) .add( - [ + itemTupleToTable([ ["Super restore(3)", 3], ["Zamorak brew(3)", 3], - ], + ]), 1, 8, ) diff --git a/src/simulation/monsters/bosses/Obor.ts b/src/simulation/monsters/bosses/Obor.ts index 03cdc2c60..96dc52120 100644 --- a/src/simulation/monsters/bosses/Obor.ts +++ b/src/simulation/monsters/bosses/Obor.ts @@ -1,5 +1,6 @@ import LootTable from "../../../structures/LootTable"; import SimpleMonster from "../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../util"; const OborTable = new LootTable({ limit: 118 }) .every("Big Bones") @@ -34,10 +35,10 @@ const OborTable = new LootTable({ limit: 118 }) .add("Limpwurt root", 20, 8) .add("Big bones", 50, 8) .add( - [ + itemTupleToTable([ ["Uncut diamond", 5], ["Uncut ruby", 5], - ], + ]), 1, 5, ); diff --git a/src/simulation/monsters/bosses/slayer/AlchemicalHydra.ts b/src/simulation/monsters/bosses/slayer/AlchemicalHydra.ts index effb548e7..049fefdcf 100644 --- a/src/simulation/monsters/bosses/slayer/AlchemicalHydra.ts +++ b/src/simulation/monsters/bosses/slayer/AlchemicalHydra.ts @@ -1,5 +1,6 @@ import LootTable from "../../../../structures/LootTable"; import SimpleMonster from "../../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../../util"; import RareDropTable from "../../../subtables/RareDropTable"; import TreeHerbSeedTable from "../../../subtables/TreeHerbSeedTable"; @@ -22,10 +23,10 @@ const NormalTable = new LootTable() /* Weapons and armour */ .add( - [ + itemTupleToTable([ ["Mystic fire staff", 1], ["Mystic water staff", 1], - ], + ]), 1, 8, ) @@ -36,10 +37,10 @@ const NormalTable = new LootTable() .add("Dragon med helm", 1, 3) .add("Dragon battleaxe", 1, 2) .add( - [ + itemTupleToTable([ ["Mystic robe top (light)", 1], ["Mystic robe bottom (light)", 1], - ], + ]), 1, 1, ) @@ -62,10 +63,10 @@ const NormalTable = new LootTable() .add("Coins", [40_000, 60_000], 10) .add("Shark", [2, 4], 7) .add( - [ + itemTupleToTable([ ["Ranging potion(3)", 1], ["Super restore(3)", 2], - ], + ]), 1, 7, ) diff --git a/src/simulation/monsters/bosses/slayer/GrotesqueGuardians.ts b/src/simulation/monsters/bosses/slayer/GrotesqueGuardians.ts index 006155941..2f254cd6a 100644 --- a/src/simulation/monsters/bosses/slayer/GrotesqueGuardians.ts +++ b/src/simulation/monsters/bosses/slayer/GrotesqueGuardians.ts @@ -1,5 +1,6 @@ import LootTable from "../../../../structures/LootTable"; import SimpleMonster from "../../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../../util"; const NormalUniqueTable = new LootTable() /* Unique */ @@ -23,11 +24,11 @@ const NormalUniqueTable = new LootTable() .add("Mushroom potato", [4, 6], 10) .add("Saradomin brew(4)", 2, 8) .add( - [ + itemTupleToTable([ ["Magic potion(2)", 1], ["Ranging potion(2)", 1], ["Super combat potion(2)", 1], - ], + ]), 1, 6, ) diff --git a/src/simulation/monsters/bosses/wildy/ChaosFanatic.ts b/src/simulation/monsters/bosses/wildy/ChaosFanatic.ts index 4c841d3f2..23d5aa371 100644 --- a/src/simulation/monsters/bosses/wildy/ChaosFanatic.ts +++ b/src/simulation/monsters/bosses/wildy/ChaosFanatic.ts @@ -1,5 +1,6 @@ import LootTable from "../../../../structures/LootTable"; import SimpleMonster from "../../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../../util"; import RareDropTable, { GemTable } from "../../../subtables/RareDropTable"; const ChaosFanaticUniqueTable = new LootTable().add("Odium shard 1").add("Malediction shard 1"); @@ -15,10 +16,10 @@ const ChaosFanaticTable = new LootTable() .add("Splitbark body", 1, 5) .add("Splitbark legs", 1, 5) .add( - [ + itemTupleToTable([ ["Zamorak monk top", 1], ["Zamorak monk bottom", 1], - ], + ]), 1, 4, ) @@ -43,10 +44,10 @@ const ChaosFanaticTable = new LootTable() .add("Chaos talisman", 1, 6) .add("Wine of zamorak", 10, 6) .add( - [ + itemTupleToTable([ ["Uncut emerald", 6], ["Uncut sapphire", 4], - ], + ]), 1, 5, ) diff --git a/src/simulation/monsters/bosses/wildy/CrazyArchaeologist.ts b/src/simulation/monsters/bosses/wildy/CrazyArchaeologist.ts index 061a65aca..c251d0126 100644 --- a/src/simulation/monsters/bosses/wildy/CrazyArchaeologist.ts +++ b/src/simulation/monsters/bosses/wildy/CrazyArchaeologist.ts @@ -1,5 +1,6 @@ import LootTable from "../../../../structures/LootTable"; import SimpleMonster from "../../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../../util"; import RareDropTable, { GemTable } from "../../../subtables/RareDropTable"; const CrazyArchaeologistUniqueTable = new LootTable().add("Odium shard 2").add("Malediction shard 2"); @@ -33,10 +34,10 @@ const CrazyArchaeologistTable = new LootTable() .add("White berries", 10, 6) .add("Silver ore", 40, 6) .add( - [ + itemTupleToTable([ ["Uncut emerald", 6], ["Uncut sapphire", 4], - ], + ]), 1, 5, ) diff --git a/src/simulation/monsters/low/a-f/Bloodveld.ts b/src/simulation/monsters/low/a-f/Bloodveld.ts index cb6abcb31..7af62b8e9 100644 --- a/src/simulation/monsters/low/a-f/Bloodveld.ts +++ b/src/simulation/monsters/low/a-f/Bloodveld.ts @@ -1,5 +1,6 @@ import LootTable from "../../../../structures/LootTable"; import SimpleMonster from "../../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../../util"; import HerbDropTable from "../../../subtables/HerbDropTable"; import { GemTable } from "../../../subtables/RareDropTable"; @@ -31,18 +32,18 @@ export const BloodveldPreTable = new LootTable() /* Other */ .add( - [ + itemTupleToTable([ ["Big bones", 1], ["Bones", 1], - ], + ]), 1, 7, ) .add( - [ + itemTupleToTable([ ["Big bones", 3], ["Bones", 1], - ], + ]), 1, 3, ) diff --git a/src/simulation/monsters/low/g-m/JungleHorror.ts b/src/simulation/monsters/low/g-m/JungleHorror.ts index 84c7d1514..69e657e8f 100644 --- a/src/simulation/monsters/low/g-m/JungleHorror.ts +++ b/src/simulation/monsters/low/g-m/JungleHorror.ts @@ -1,5 +1,6 @@ import LootTable from "../../../../structures/LootTable"; import SimpleMonster from "../../../../structures/SimpleMonster"; +import { itemTupleToTable } from "../../../../util"; import HerbDropTable from "../../../subtables/HerbDropTable"; import { GemTable } from "../../../subtables/RareDropTable"; import VariableAllotmentSeedTable from "../../../subtables/VariableAllotmentSeedTable"; @@ -32,18 +33,18 @@ const JungleHorrorTable = new LootTable({ limit: 129 }) /* Other */ .add("Pineapple", 1, 8) .add( - [ + itemTupleToTable([ ["Big bones", 1], ["Bones", 1], - ], + ]), 1, 3, ) .add( - [ + itemTupleToTable([ ["Big bones", 3], ["Bones", 1], - ], + ]), 1, 2, ) diff --git a/src/simulation/openables/CrystalChest.ts b/src/simulation/openables/CrystalChest.ts index 155f6b6ba..8877c7dd5 100644 --- a/src/simulation/openables/CrystalChest.ts +++ b/src/simulation/openables/CrystalChest.ts @@ -1,5 +1,6 @@ import LootTable from "../../structures/LootTable"; import SimpleOpenable from "../../structures/SimpleOpenable"; +import { itemTupleToTable } from "../../util"; const runeArmorTable = new LootTable().add("Rune platelegs", 1, 1).add("Rune plateskirt", 1, 1); @@ -11,15 +12,15 @@ const coinsKeyHalfTable = new LootTable() const CrystalChestTable = new LootTable({ limit: 128 }) .every("Uncut dragonstone") .add( - [ + itemTupleToTable([ ["Spinach roll", 1], ["Coins", 2000], - ], + ]), 1, 34, ) .add( - [ + itemTupleToTable([ ["Air rune", 50], ["Water rune", 50], ["Earth rune", 50], @@ -31,15 +32,15 @@ const CrystalChestTable = new LootTable({ limit: 128 }) ["Cosmic rune", 10], ["Nature rune", 10], ["Law rune", 10], - ], + ]), 1, 12, ) .add( - [ + itemTupleToTable([ ["Ruby", 2], ["Diamond", 2], - ], + ]), 1, 12, ) @@ -48,10 +49,10 @@ const CrystalChestTable = new LootTable({ limit: 128 }) .add("Iron ore", 150, 10) .add("Coal", 100, 10) .add( - [ + itemTupleToTable([ ["Raw swordfish", 5], ["Coins", 1000], - ], + ]), 1, 8, ) diff --git a/src/simulation/openables/ElvenCrystalChest.ts b/src/simulation/openables/ElvenCrystalChest.ts index 7b9f2434c..60a25d1d2 100644 --- a/src/simulation/openables/ElvenCrystalChest.ts +++ b/src/simulation/openables/ElvenCrystalChest.ts @@ -1,5 +1,6 @@ import LootTable from "../../structures/LootTable"; import SimpleOpenable from "../../structures/SimpleOpenable"; +import { itemTupleToTable } from "../../util"; /* Dragonstone armour roll */ const DragonStoneArmorTable = new LootTable() @@ -35,89 +36,89 @@ const ElvenCrystalChestTable = new LootTable() .oneIn(500, DragonStoneArmorTable) .add(coinsKeyHalfTable, 1, 64) .add( - [ + itemTupleToTable([ ["Uncut dragonstone", 1], ["Uncut ruby", [10, 13]], ["Uncut diamond", [5, 8]], - ], + ]), 1, 32, ) .add( - [ + itemTupleToTable([ ["Uncut dragonstone", 1], ["Crystal key", 1], - ], + ]), 1, 24, ) .add( - [ + itemTupleToTable([ ["Uncut dragonstone", 1], ["Coins", [30_000, 50_000]], ["Crystal shard", [8, 13]], - ], + ]), 1, 20, ) .add( - [ + itemTupleToTable([ ["Uncut dragonstone", 1], ["Crystal shard", [20, 30]], - ], + ]), 1, 17, ) .add(runeArmorTable, 1, 17) .add( - [ + itemTupleToTable([ ["Uncut dragonstone", 1], ["Cosmic rune", [50, 100]], ["Chaos rune", [50, 100]], ["Nature rune", [50, 100]], ["Law rune", [50, 100]], ["Death rune", [50, 100]], - ], + ]), 1, 17, ) .add( - [ + itemTupleToTable([ ["Uncut dragonstone", 1], ["Yew seed", 1], - ], + ]), 1, 17, ) .add( - [ + itemTupleToTable([ ["Uncut dragonstone", 1], ["Raw shark", [50, 100]], - ], + ]), 1, 17, ) .add( - [ + itemTupleToTable([ ["Uncut dragonstone", 1], ["Gold ore", [350, 500]], - ], + ]), 1, 12, ) .add( - [ + itemTupleToTable([ ["Uncut dragonstone", 1], ["Runite ore", [7, 10]], - ], + ]), 1, 9, ) .add( - [ + itemTupleToTable([ ["Uncut dragonstone", 1], ["Crystal acorn", [1, 2]], - ], + ]), 1, 7, ) diff --git a/src/simulation/openables/GrubbyChest.ts b/src/simulation/openables/GrubbyChest.ts index 4f2330b3f..c198bad37 100644 --- a/src/simulation/openables/GrubbyChest.ts +++ b/src/simulation/openables/GrubbyChest.ts @@ -1,33 +1,34 @@ import LootTable from "../../structures/LootTable"; import SimpleOpenable from "../../structures/SimpleOpenable"; +import { itemTupleToTable } from "../../util"; const FoodTable = new LootTable() .add("Egg potato", 4, 12) .add("Shark", 4, 7) .add( - [ + itemTupleToTable([ ["Saradomin brew(2)", 3], ["Super restore(2)", 1], - ], + ]), 1, 1, ); const PotionTable = new LootTable() .add( - [ + itemTupleToTable([ ["Super attack(2)", 1], ["Super strength(2)", 1], ["Super defence(2)", 1], - ], + ]), 1, 8, ) .add( - [ + itemTupleToTable([ ["Super defence(2)", 1], ["Ranging potion(2)", 1], - ], + ]), 1, 8, ) diff --git a/src/structures/Bank.ts b/src/structures/Bank.ts index d632c0d56..f5df06a58 100644 --- a/src/structures/Bank.ts +++ b/src/structures/Bank.ts @@ -2,6 +2,7 @@ import { randArrItem } from "e"; import type { BankItem, IntKeyBank, Item, ItemBank } from "../meta/types"; import itemID from "../util/itemID"; +import { toKMB } from "../util/smallUtils"; import Items from "./Items"; const frozenErrorStr = "Tried to mutate a frozen Bank."; @@ -221,16 +222,14 @@ export default class Bank { } public toString(): string { - const entries = Array.from(this.map.entries()); - if (entries.length === 0) { + const items = this.items(); + if (items.length === 0) { return "No items"; } - const res = []; - for (const [id, qty] of entries.sort((a, b) => b[1] - a[1])) { - res.push(`${qty.toLocaleString()}x ${Items.get(id)?.name ?? "Unknown item"}`); - } - - return res.join(", "); + return items + .sort((a, b) => a[0].name.localeCompare(b[0].name)) + .map(([item, qty]) => `${qty < 1000 ? `${qty}x` : toKMB(qty)} ${item?.name ?? "Unknown item"}`) + .join(", "); } public get length(): number { @@ -250,7 +249,7 @@ export default class Bank { for (const [item, quantity] of this.items()) { if (otherBank.amount(item.id) !== quantity) return false; } - return JSON.stringify([...this.map]) === JSON.stringify([...otherBank.map]); + return true; } public difference(otherBank: Bank): Bank { diff --git a/src/structures/LootTable.ts b/src/structures/LootTable.ts index 4a4b08567..db39e4512 100644 --- a/src/structures/LootTable.ts +++ b/src/structures/LootTable.ts @@ -27,7 +27,7 @@ export interface LootTableMoreOptions { } export interface LootTableItem { - item: number | LootTable | LootTableItem[]; + item: number | LootTable; weight?: number; quantity: number | number[]; options?: LootTableMoreOptions; @@ -166,7 +166,7 @@ export default class LootTable { } public add( - item: LootTable | number | string | [string, (number | number[])?][] | LootTableItem[], + item: LootTable | number | string, quantity: number[] | number = 1, weight = 1, options?: LootTableMoreOptions, @@ -178,23 +178,6 @@ export default class LootTable { return this.add(this.resolveName(item), quantity, weight, options); } - // If its an array, but not a LootTableItem[] array. - // i.e, if its directly from the user, and not being internally added. - if (Array.isArray(item) && isArrayOfItemTuples(item)) { - const newItems = []; - const _item = item as [string, (number | number[])?][]; - for (const itemToAdd of _item) { - const resolvedId = this.resolveName(itemToAdd[0]); - this.addToAllItems(resolvedId); - newItems.push({ - item: resolvedId, - quantity: this.determineQuantity(itemToAdd[1]!) || 1, - }); - } - - return this.add(newItems, quantity, weight, options); - } - this.length += 1; this.totalWeight += weight; @@ -257,7 +240,6 @@ export default class LootTable { for (let i = 0; i < this.table.length; i++) { const item = this.table[i]!; - weight += item.weight!; if (randomWeight <= weight) { result = i; @@ -266,14 +248,16 @@ export default class LootTable { } const chosenItem = this.table[result]; - this.addResultToLoot(chosenItem, loot); + if (chosenItem) { + this.addResultToLoot(chosenItem, loot); + } } if (options.targetBank) return null; return loot; } - private addResultToLoot(result: LootTableItem | undefined, loot: Bank): void { + private addResultToLoot(result: LootTableItem, loot: Bank): void { if (!result) return; const { item, quantity, options } = result; @@ -288,13 +272,6 @@ export default class LootTable { else item.roll(qty, { targetBank: loot }); return; } - - if (Array.isArray(item)) { - for (const singleItem of item) { - this.addResultToLoot(singleItem, loot); - } - return; - } } protected determineQuantity(quantity: number | number[]): number { diff --git a/src/util/smallUtils.ts b/src/util/smallUtils.ts new file mode 100644 index 000000000..89f5b4360 --- /dev/null +++ b/src/util/smallUtils.ts @@ -0,0 +1,28 @@ +import { round } from "e"; + +export function toKMB(number: number): string { + if (number > 999_999_999 || number < -999_999_999) { + return `${round(number / 1_000_000_000)}b`; + } else if (number > 999_999 || number < -999_999) { + return `${round(number / 1_000_000)}m`; + } else if (number > 999 || number < -999) { + return `${round(number / 1000)}k`; + } + return round(number).toString(); +} + +export function fromKMB(number: string): number { + number = number.toLowerCase().replace(/,/g, ""); + const [numberBefore, numberAfter] = number.split(/[.kmb]/g); + + let newNum = numberBefore; + if (number.includes("b")) { + newNum += numberAfter + "0".repeat(9).slice(numberAfter.length); + } else if (number.includes("m")) { + newNum += numberAfter + "0".repeat(6).slice(numberAfter.length); + } else if (number.includes("k")) { + newNum += numberAfter + "0".repeat(3).slice(numberAfter.length); + } + + return Number.parseInt(newNum); +} diff --git a/src/util/util.ts b/src/util/util.ts index c0270f000..86f0a57a4 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -1,4 +1,4 @@ -import { randFloat, randInt, roll, round } from "e"; +import { randFloat, randInt, roll } from "e"; import { CLUES, MINIGAMES, SKILLS, type hiscoreURLs, mappedBossNames } from "../constants"; import type { CustomKillLogic, Item, MonsterKillOptions } from "../meta/types"; @@ -112,33 +112,6 @@ export function convertXPtoLVL(xp: number, cap = 99): number { return cap; } -export function toKMB(number: number): string { - if (number > 999_999_999 || number < -999_999_999) { - return `${round(number / 1_000_000_000)}b`; - } else if (number > 999_999 || number < -999_999) { - return `${round(number / 1_000_000)}m`; - } else if (number > 999 || number < -999) { - return `${round(number / 1000)}k`; - } - return round(number).toString(); -} - -export function fromKMB(number: string): number { - number = number.toLowerCase().replace(/,/g, ""); - const [numberBefore, numberAfter] = number.split(/[.kmb]/g); - - let newNum = numberBefore; - if (number.includes("b")) { - newNum += numberAfter + "0".repeat(9).slice(numberAfter.length); - } else if (number.includes("m")) { - newNum += numberAfter + "0".repeat(6).slice(numberAfter.length); - } else if (number.includes("k")) { - newNum += numberAfter + "0".repeat(3).slice(numberAfter.length); - } - - return Number.parseInt(newNum); -} - export function getBrimKeyChanceFromCBLevel(combatLevel: number): number { // https://twitter.com/JagexKieren/status/1083781544135847936 if (combatLevel < 100) { @@ -309,3 +282,13 @@ export function deepResolveItems(itemArray: ArrayItemsResolvable): ArrayItemsRes return newArray; } + +export function itemTupleToTable(items: [string, number | [number, number]][]): LootTable { + const table = new LootTable(); + for (const [item, quantity] of items) { + table.every(item, quantity ?? 1); + } + return table; +} + +export * from "./smallUtils"; diff --git a/test/BankClass.test.ts b/test/BankClass.test.ts index e7dc2f3e5..f485349cf 100644 --- a/test/BankClass.test.ts +++ b/test/BankClass.test.ts @@ -113,10 +113,10 @@ describe("Bank Class", () => { test("toString", () => { const bank = new Bank(resolveNameBank({ Coal: 20, Egg: 5000, Emerald: 1, Ruby: 20_000 })); bank.add("Twisted bow", 0); - expect(bank.toString()).toEqual("20,000x Ruby, 5,000x Egg, 20x Coal, 1x Emerald"); + expect(bank.toString()).toEqual("20x Coal, 5k Egg, 1x Emerald, 20k Ruby"); expect(bank.length).toEqual(4); bank.add("3rd age platebody", 2); - expect(bank.toString()).toEqual("20,000x Ruby, 5,000x Egg, 20x Coal, 2x 3rd age platebody, 1x Emerald"); + expect(bank.toString()).toEqual("2x 3rd age platebody, 20x Coal, 5k Egg, 1x Emerald, 20k Ruby"); expect(bank.length).toEqual(5); expect(new Bank().toString()).toEqual("No items"); expect(new Bank({ 111231231: 1 }).toString()).toEqual("1x Unknown item"); @@ -238,7 +238,7 @@ describe("Bank Class", () => { const idVersion = resolveNameBank(baseBank); const bank = new Bank(baseBank); expect(bank.amount("Coal")).toEqual(20); - expect(new Bank(idVersion).equals(new Bank(bank))).toBeTruthy(); + expect(new Bank(idVersion).toString()).toEqual(new Bank(bank).toString()); expect(bank.has(idVersion)).toBeTruthy(); const otherBank = new Bank(idVersion); diff --git a/test/Monsters.test.ts b/test/Monsters.test.ts index 619955c2f..1a6032368 100644 --- a/test/Monsters.test.ts +++ b/test/Monsters.test.ts @@ -4,6 +4,7 @@ import { Monsters } from "../src"; import Bank from "../src/structures/Bank"; import LootTable from "../src/structures/LootTable"; import Monster from "../src/structures/Monster"; +import { itemTupleToTable } from "../src/util"; import { checkThreshold } from "./testUtil"; describe("Monsters", () => { @@ -18,7 +19,12 @@ describe("Monsters", () => { .add("Needle") .add("Amethyst") .add("Knife") - .add([["Iron bar"], ["Steel bar"]]) + .add( + itemTupleToTable([ + ["Iron bar", 1], + ["Steel bar", 1], + ]), + ) .add(subSubTable); beforeAll(async () => {