Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust loottables #397

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions dist/cjs/index.cjs

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/cjs/index.cjs.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/esm/index.mjs

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/esm/index.mjs.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/types/structures/Bank.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default class Bank {
private map;
frozen: boolean;
static withSanitizedValues(source: ItemBank | IntKeyBank): Bank;
constructor(initialBank?: IntKeyBank | ItemBank | Bank);
constructor(initialBank?: IntKeyBank | ItemBank | Bank | Map<number, number>);
removeInvalidValues(): Bank;
private resolveItemID;
clear(item?: Item | string | number): this;
Expand Down
2 changes: 1 addition & 1 deletion dist/types/structures/Bank.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion dist/types/structures/LootTable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export default class LootTable {
tertiary(chance: number, item: LootTable | number | string, quantity?: number | number[], options?: LootTableMoreOptions): this;
every(item: LootTable | number | string, quantity?: number | number[], options?: LootTableMoreOptions): this;
add(item: LootTable | number | string, quantity?: number[] | number, weight?: number, options?: LootTableMoreOptions): this;
private cachedOptimizedTable;
roll(quantity?: number): Bank;
roll(quantity: number, options: {
targetBank?: undefined;
Expand Down
2 changes: 1 addition & 1 deletion dist/types/structures/LootTable.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion esbuild.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const baseConfig = {
keepNames: true,
minify: true,
plugins: [minifyJsonPlugin],
external: ['node-fetch'],
external: ['node-fetch', "@gc/rust-walker"],
loader: {
'.json': 'copy',
},
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@
"prepare": "tsx scripts/prepare",
"dev": "yarn prepare && yarn lint && yarn build && yarn test",
"lint": "biome check --write --diagnostic-level=error",
"test:lint": "biome check --diagnostic-level=error"
"test:lint": "biome check --diagnostic-level=error",
"snapshots": "concurrently \"tsx scripts/cluesnapshots.ts\" \"tsx scripts/monstersnapshots.ts 0\" \"tsx scripts/monstersnapshots.ts 1\" \"tsx scripts/monstersnapshots.ts 2\" \"tsx scripts/monstersnapshots.ts 3\""
},
"dependencies": {
"@gc/rust-walker": "^0.0.3",
"e": "^0.2.33",
"node-fetch": "2.6.7"
},
Expand All @@ -71,6 +73,7 @@
"node": "20.15.0"
},
"resolutions": {
"esbuild": "0.24.0"
"esbuild": "0.24.0",
"@gc/rust-walker": "portal:/C:/dev/rust-walker"
}
}
20 changes: 20 additions & 0 deletions scripts/cluesnapshots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { appendFile, writeFile } from "node:fs/promises";

import { Clues } from "../src";
import { bankToString } from "./scriptUtil";

const SAMPLES = 100_000_000;

async function main() {
await writeFile("./snapshots/clues.snapshot.tsv", "");
const allClues = Object.entries(Clues);
for (const [name, table] of allClues) {
await appendFile(
"./snapshots/clues.snapshot.tsv",
`${name}
${bankToString(SAMPLES, table.roll(SAMPLES, {}))}`,
);
}
}

main();
32 changes: 32 additions & 0 deletions scripts/monstersnapshots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { appendFile, writeFile } from "node:fs/promises";

import { chunk } from "e";
import { Monsters } from "../src";
import { bankToString } from "./scriptUtil";

const SAMPLES = 20_000_000;
const index = Number(process.argv.slice(2)[0]);

async function main() {
const fileName = `./snapshots/monsters-${index}.snapshot.tsv`;
await writeFile(fileName, "");
const allMonsters = Array.from(Monsters.values()).sort((a, b) => a.name.localeCompare(b.name));
const chunked = chunk(allMonsters, Math.ceil(allMonsters.length / 4))[index];
for (let i = 0; i < chunked.length; i++) {
try {
const monster = chunked[i];
console.log(`Processing monster ${monster.name}`);
await appendFile(
fileName,
`${monster.name}
${bankToString(SAMPLES, monster.kill(SAMPLES, {}))}
`,
);
console.log(`Monsters remaining: ${chunked.length - i}`);
} catch (err) {
console.log(`Error on monster ${chunked[i].name}: ${err}`);
}
}
}

main();
16 changes: 16 additions & 0 deletions scripts/scriptUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Bank } from "../src";

export function round(int: number) {
return Math.round(int / 2) * 2;
}

export function bankToString(samples: number, bank: Bank) {
return bank
.items()
.sort((a, b) => a[0].name.localeCompare(b[0].name))
.map(([item, qty]) => {
const rate = round(samples / qty);
return ` ${item.name}[1 in ${rate}]`;
})
.join("\n");
}
36 changes: 36 additions & 0 deletions scripts/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { type Bank, LootTable } from "../src";

const TestTable = new LootTable().add("Coal", 5);
function bankToString(samples: number, bank: Bank) {
return bank
.items()
.sort((a, b) => a[0].name.localeCompare(b[0].name))
.map(([item, qty]) => {
if (item.name !== "Coal") return "";
const rate = (samples / qty).toFixed(1);
return `${item.name}[${(qty / samples).toFixed(1)}]`;
})
.filter(i => i.length)
.join("\n");
}

const tables = [
new LootTable().add(new LootTable().add(new LootTable().add(new LootTable().add(TestTable)))),
new LootTable().add(new LootTable().add(TestTable)),
new LootTable().add(new LootTable().add("Coal", 10).add("Egg", 5)),
new LootTable().add(new LootTable().add("Coal", 15).add("Egg", 5).add("Egg", 5)),
new LootTable().add(new LootTable().add("Coal", 15).add("Egg", 5).add("Egg", 5), [1, 10]),
new LootTable().add(new LootTable().add("Coal", [1, 30]).add("Egg", 5).add("Egg", 5), [1, 10]),
new LootTable().add(new LootTable().every(TestTable)),
new LootTable().every(new LootTable().every(TestTable)),
new LootTable().every(new LootTable().every(new LootTable().add(TestTable))),
new LootTable().every(new LootTable().every(new LootTable().every(TestTable))),
new LootTable().every(new LootTable().add(new LootTable().every(TestTable))),
new LootTable().every(new LootTable().add(new LootTable().add(TestTable).add(TestTable))),
];

for (let i = 0; i < tables.length; i++) {
const table = tables[i];
const loot = table.roll(100_000_000);
console.log(i, bankToString(100_000_000, loot));
}
150 changes: 150 additions & 0 deletions scripts/wiki.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { readFileSync, writeFileSync } from "node:fs";
import { omitBy } from "remeda";
import wtf from "wtf_wikipedia";
import { Monsters } from "../src";

// const constructBody = (pages: string[]): string => {
// const encodedPages = pages.map(page => encodeURIComponent(page)).join("\n");
// const formData = new URLSearchParams({
// title: "Special:Export",
// catname: "",
// pages: encodedPages,
// curonly: "1",
// wpDownload: "1",
// });
// return formData.toString();
// };
// const fetchWikiExport = async (pages: string[]) => {
// const url = "https://oldschool.runescape.wiki/w/Special:Export";
// const body = constructBody(pages);
// const options = {
// headers: {
// "content-type": "application/x-www-form-urlencoded",
// accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
// "user-agent": "discord:magnaboy",
// },
// body,
// method: "POST",
// };
// const xml = await fetch(url, options).then(response => response.text());
// const doc = wtf(xml);
// const json = doc.json();
// return { json, doc };
// };
const transformData = (data: any): any => {
let {
id,
members,
combat,
hitpoints,
"attack style": attackStyle,
"attack speed": attackSpeed,
aggressive,
poisonous,
immunepoison,
immunevenom,
cat,
examine,
smwname,
name,
slaylvl,
slayxp,
assignedby,
att,
str,
def,
mage,
range,
amagic,
arange,
dstab,
dslash,
dcrush,
dmagic,
drange,
attributes,
} = data;
attributes ??= [];
if (!Array.isArray(attributes)) {
attributes = [attributes];
}
return {
members: members?.toLowerCase() === "yes",
combatLevel: combat,
hitpoints,
attackType: attackStyle?.toLowerCase().split(", "),
attackSpeed,
aggressive: aggressive?.toLowerCase() === "yes",
poisonous: poisonous?.toLowerCase().includes("yes"),
immuneToPoison: immunepoison?.toLowerCase() === "yes",
immuneToVenom: immunevenom?.toLowerCase() === "yes",
attributes: attributes ?? [],
category: cat?.toLowerCase().split(", "),
examineText: examine,
wikiName: name,
wikiURL: `https://oldschool.runescape.wiki/w/${name}`,
attackLevel: att,
strengthLevel: str,
defenceLevel: def,
magicLevel: mage,
rangedLevel: range,
attackStab: 0,
attackSlash: 0,
attackCrush: 0,
attackMagic: amagic,
attackRanged: arange,
defenceStab: dstab,
defenceSlash: Number(dslash),
defenceCrush: Number(dcrush),
defenceMagic: Number(dmagic),
defenceRanged: Number(drange),
attackAccuracy: 0,
meleeStrength: 0,
rangedStrength: 0,
magicDamage: 0,
isSlayerMonster: !!slaylvl,
slayerLevelRequired: slaylvl,
slayerXP: slayxp,
assignableSlayerMasters: assignedby?.split(",").map(master => master.trim().toLowerCase()),
};
};

async function main() {
// const { json, doc }: any = await fetchWikiExport([Monsters.map(m => [m.data?.wikiName, m.name])].flat(222));
const json = wtf(readFileSync("wiki.xml", "utf-8")).json();
const sections = json.sections
.map(s => s.infoboxes)
.flat(100)
.filter(s => s && Boolean(s.name) && Boolean(s.examine) && Boolean(s.hitpoints))
.map(s =>
omitBy(s, (value, key) =>
["version", "image", "release", "examine", "update"].some(str => key.startsWith(str)),
),
);
for (let i = 0; i < sections.length; i++) {
const section = sections[i];
const allIDs: any[] = [];
for (const [key, val] of Object.entries(section) as any[]) {
if (key.startsWith("id") && key.length !== 2) {
allIDs.push(val.text);
continue;
}
section[key] = val.number ?? val.text ?? val;
}
section.allIDs = allIDs
.map(idOrIdArr => (idOrIdArr.includes(",") ? idOrIdArr.split(",") : idOrIdArr))
.flat(100)
.map(id => Number(id.trim()))
.sort((a, b) => a - b);
}

const obj = {};
// const obj: any = JSON.parse(readFileSync("./src/data/monsters_data.json", "utf-8"));
for (const section of sections) {
const existingMonster = Monsters.find(m => m.name === section.name);
obj[existingMonster?.id ?? section.id] = transformData(section);
}
writeFileSync("test.json", JSON.stringify(obj, null, 4));
}

main();
1 change: 1 addition & 0 deletions snapshots/clues.snapshot.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Beginner
Loading