feat: Add scripts for mob dependency management and server setup

- Implemented `findMobDependencies.ts` to identify foreign key constraints referencing the Mob table and log dependent rows.
- Created `fullServerSetup.ts` for idempotent server setup, including economy items, item recipes, game areas, mobs, and optional demo mob attacks.
- Developed `removeInvalidMobsWithDeps.ts` to delete invalid mobs and their dependencies, backing up affected scheduled mob attacks.
- Added unit tests in `testMobUnit.ts` and `mob.test.ts` for mob functionality, including stats computation and instance retrieval.
- Introduced reward modification tests in `testRewardMods.ts` and `rewardMods.unit.ts` to validate drop selection and coin multiplier behavior.
- Enhanced command handling for mob deletion in `mobDelete.ts` and setup examples in `setup.ts`, ensuring proper permissions and feedback.
- Created utility functions in `testHelpers.ts` for deterministic drop selection from mob definitions.
This commit is contained in:
Shni
2025-10-14 14:58:38 -05:00
parent f36fa24e46
commit 852b1d02a2
24 changed files with 2158 additions and 177 deletions

View File

@@ -0,0 +1,59 @@
import { test } from "uvu";
import * as assert from "uvu/assert";
import { pickDropFromDef } from "../../src/game/minigames/testHelpers";
// deterministic randomness helper
function seedRandom(seed: number) {
let s = seed % 2147483647;
if (s <= 0) s += 2147483646;
return () => (s = (s * 16807) % 2147483647) / 2147483647;
}
// Patch Math.random for deterministic tests
const realRandom = Math.random;
test.before(() => {
(Math as any).random = seedRandom(42) as any;
});
test.after(() => {
(Math as any).random = realRandom;
});
test("pickDropFromDef chooses weighted item", () => {
const def = {
drops: [
{ itemKey: "ore.iron", qty: 1, weight: 8 },
{ itemKey: "ore.gold", qty: 1, weight: 2 },
],
} as any;
const picks = new Set<string>();
for (let i = 0; i < 10; i++) {
const p = pickDropFromDef(def);
assert.ok(p && (p.itemKey === "ore.iron" || p.itemKey === "ore.gold"));
picks.add(p!.itemKey);
}
// with seeded RNG both options should appear
assert.ok(picks.size >= 1);
});
test("pickDropFromDef chooses from map", () => {
const def = { drops: { "ore.iron": 1, "ore.gold": 2 } } as any;
const p = pickDropFromDef(def);
assert.ok(p && (p.itemKey === "ore.iron" || p.itemKey === "ore.gold"));
});
// coinMultiplier behavior is multiplicative in current design; test small scenario
test("coin multiplier aggregation (product)", () => {
const mobs = [
{ rewardMods: { coinMultiplier: 1.1 } },
{ rewardMods: { coinMultiplier: 1.2 } },
];
const product = mobs.reduce(
(acc, m) => acc * ((m.rewardMods?.coinMultiplier as number) || 1),
1
);
assert.equal(Math.round(product * 100) / 100, Math.round(1.32 * 100) / 100);
});
test.run();

View File

@@ -0,0 +1,57 @@
import * as assert from "assert";
import { pickDropFromDef } from "../../src/game/minigames/testHelpers";
// deterministic RNG
function seedRandom(seed: number) {
let s = seed % 2147483647;
if (s <= 0) s += 2147483646;
return () => (s = (s * 16807) % 2147483647) / 2147483647;
}
const rand = seedRandom(42);
const realRandom = Math.random;
(Math as any).random = rand;
try {
// weighted
const def1 = {
drops: [
{ itemKey: "ore.iron", qty: 1, weight: 8 },
{ itemKey: "ore.gold", qty: 1, weight: 2 },
],
} as any;
const picks = new Set<string>();
for (let i = 0; i < 20; i++) {
const p = pickDropFromDef(def1);
if (!p) throw new Error("expected pick");
picks.add(p.itemKey);
}
assert.ok(picks.size >= 1, "expected at least 1 picked key");
// map
const def2 = { drops: { "ore.iron": 1, "ore.gold": 2 } } as any;
const p2 = pickDropFromDef(def2);
assert.ok(p2 && (p2.itemKey === "ore.iron" || p2.itemKey === "ore.gold"));
// coin multiplier product
const mobs = [
{ rewardMods: { coinMultiplier: 1.1 } },
{ rewardMods: { coinMultiplier: 1.2 } },
];
const product = mobs.reduce(
(acc, m) => acc * ((m.rewardMods?.coinMultiplier as number) || 1),
1
);
assert.strictEqual(
Math.round(product * 100) / 100,
Math.round(1.32 * 100) / 100
);
console.log("All unit tests passed");
(Math as any).random = realRandom;
process.exit(0);
} catch (e) {
(Math as any).random = realRandom;
console.error(e);
process.exit(1);
}