Skip to content

Commit

Permalink
changes
Browse files Browse the repository at this point in the history
  • Loading branch information
gc committed Sep 9, 2024
1 parent 227dddb commit e5f6407
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 132 deletions.
46 changes: 46 additions & 0 deletions bench/LootTable.bench.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
15 changes: 10 additions & 5 deletions scripts/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>();
const duplicates = new Set<number>();
let str = "export enum EItem {";
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/meta/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ export interface WikiPage {
}[];
}

export interface IntKeyBank {
[key: number]: number;
}
export interface ItemBank {
[key: string]: number;
}
Expand Down
123 changes: 64 additions & 59 deletions src/structures/Bank.ts
Original file line number Diff line number Diff line change
@@ -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<number, number>;
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);
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
38 changes: 32 additions & 6 deletions src/structures/LootTable.ts
Original file line number Diff line number Diff line change
@@ -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]);
}
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit e5f6407

Please sign in to comment.