@mcptoolshop/roll 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/README.md +105 -137
- package/dist/analyze/distribution.d.ts.map +1 -1
- package/dist/analyze/distribution.js +249 -33
- package/dist/analyze/distribution.js.map +1 -1
- package/dist/bridge/handler.d.ts +14 -0
- package/dist/bridge/handler.d.ts.map +1 -0
- package/dist/bridge/handler.js +163 -0
- package/dist/bridge/handler.js.map +1 -0
- package/dist/bridge/protocol.d.ts +53 -0
- package/dist/bridge/protocol.d.ts.map +1 -0
- package/dist/bridge/protocol.js +8 -0
- package/dist/bridge/protocol.js.map +1 -0
- package/dist/bridge/server.d.ts +3 -0
- package/dist/bridge/server.d.ts.map +1 -0
- package/dist/bridge/server.js +71 -0
- package/dist/bridge/server.js.map +1 -0
- package/dist/display/format.d.ts.map +1 -1
- package/dist/display/format.js +28 -3
- package/dist/display/format.js.map +1 -1
- package/dist/engine/pipeline.d.ts +27 -0
- package/dist/engine/pipeline.d.ts.map +1 -0
- package/dist/engine/pipeline.js +304 -0
- package/dist/engine/pipeline.js.map +1 -0
- package/dist/engine/roller.d.ts +3 -0
- package/dist/engine/roller.d.ts.map +1 -1
- package/dist/engine/roller.js +82 -125
- package/dist/engine/roller.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +182 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +12 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +106 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/parser/ast.d.ts +7 -1
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/lexer.d.ts.map +1 -1
- package/dist/parser/lexer.js +138 -45
- package/dist/parser/lexer.js.map +1 -1
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +107 -15
- package/dist/parser/parser.js.map +1 -1
- package/dist/parser/tokens.d.ts +14 -0
- package/dist/parser/tokens.d.ts.map +1 -1
- package/dist/parser/tokens.js +22 -0
- package/dist/parser/tokens.js.map +1 -1
- package/dist/tables/conditions.d.ts +7 -0
- package/dist/tables/conditions.d.ts.map +1 -0
- package/dist/tables/conditions.js +65 -0
- package/dist/tables/conditions.js.map +1 -0
- package/dist/tables/engine.d.ts +10 -0
- package/dist/tables/engine.d.ts.map +1 -0
- package/dist/tables/engine.js +147 -0
- package/dist/tables/engine.js.map +1 -0
- package/dist/tables/schema.d.ts +89 -0
- package/dist/tables/schema.d.ts.map +1 -0
- package/dist/tables/schema.js +3 -0
- package/dist/tables/schema.js.map +1 -0
- package/dist/tables/validate.d.ts +4 -0
- package/dist/tables/validate.d.ts.map +1 -0
- package/dist/tables/validate.js +98 -0
- package/dist/tables/validate.js.map +1 -0
- package/package.json +12 -2
package/dist/parser/tokens.js
CHANGED
|
@@ -8,12 +8,34 @@ export var TokenType;
|
|
|
8
8
|
TokenType["SLASH"] = "SLASH";
|
|
9
9
|
TokenType["LPAREN"] = "LPAREN";
|
|
10
10
|
TokenType["RPAREN"] = "RPAREN";
|
|
11
|
+
// Keep/drop
|
|
11
12
|
TokenType["KH"] = "KH";
|
|
12
13
|
TokenType["KL"] = "KL";
|
|
13
14
|
TokenType["DH"] = "DH";
|
|
14
15
|
TokenType["DL"] = "DL";
|
|
16
|
+
// Explosion variants
|
|
15
17
|
TokenType["BANG"] = "BANG";
|
|
18
|
+
TokenType["BANG_BANG"] = "BANG_BANG";
|
|
19
|
+
TokenType["BANG_P"] = "BANG_P";
|
|
20
|
+
// Comparison operators
|
|
16
21
|
TokenType["GT"] = "GT";
|
|
22
|
+
TokenType["GTE"] = "GTE";
|
|
23
|
+
TokenType["LT"] = "LT";
|
|
24
|
+
TokenType["LTE"] = "LTE";
|
|
25
|
+
TokenType["EQ"] = "EQ";
|
|
26
|
+
// Reroll
|
|
27
|
+
TokenType["R"] = "R";
|
|
28
|
+
TokenType["RO"] = "RO";
|
|
29
|
+
// Success/failure counting and marking
|
|
30
|
+
TokenType["CS"] = "CS";
|
|
31
|
+
TokenType["CF"] = "CF";
|
|
32
|
+
// Sorting
|
|
33
|
+
TokenType["SA"] = "SA";
|
|
34
|
+
TokenType["SD"] = "SD";
|
|
35
|
+
// Min/max clamping
|
|
36
|
+
TokenType["MIN"] = "MIN";
|
|
37
|
+
TokenType["MAX"] = "MAX";
|
|
38
|
+
// Special die types
|
|
17
39
|
TokenType["PERCENT"] = "PERCENT";
|
|
18
40
|
TokenType["F"] = "F";
|
|
19
41
|
TokenType["EOF"] = "EOF";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/parser/tokens.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/parser/tokens.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,SAwCX;AAxCD,WAAY,SAAS;IACnB,8BAAiB,CAAA;IACjB,oBAAO,CAAA;IACP,0BAAa,CAAA;IACb,4BAAe,CAAA;IACf,0BAAa,CAAA;IACb,4BAAe,CAAA;IACf,8BAAiB,CAAA;IACjB,8BAAiB,CAAA;IACjB,YAAY;IACZ,sBAAS,CAAA;IACT,sBAAS,CAAA;IACT,sBAAS,CAAA;IACT,sBAAS,CAAA;IACT,qBAAqB;IACrB,0BAAa,CAAA;IACb,oCAAuB,CAAA;IACvB,8BAAiB,CAAA;IACjB,uBAAuB;IACvB,sBAAS,CAAA;IACT,wBAAW,CAAA;IACX,sBAAS,CAAA;IACT,wBAAW,CAAA;IACX,sBAAS,CAAA;IACT,SAAS;IACT,oBAAO,CAAA;IACP,sBAAS,CAAA;IACT,uCAAuC;IACvC,sBAAS,CAAA;IACT,sBAAS,CAAA;IACT,UAAU;IACV,sBAAS,CAAA;IACT,sBAAS,CAAA;IACT,mBAAmB;IACnB,wBAAW,CAAA;IACX,wBAAW,CAAA;IACX,oBAAoB;IACpB,gCAAmB,CAAA;IACnB,oBAAO,CAAA;IACP,wBAAW,CAAA;AACb,CAAC,EAxCW,SAAS,KAAT,SAAS,QAwCpB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TableCondition, TableContext, TableEntry } from "./schema.js";
|
|
2
|
+
/** Test a single condition against context. */
|
|
3
|
+
export declare function evaluateCondition(condition: TableCondition, context: TableContext): boolean;
|
|
4
|
+
/** Filter entries to only those whose ALL conditions pass.
|
|
5
|
+
* Also filters by level range if context.level is set. */
|
|
6
|
+
export declare function filterEligibleEntries(entries: TableEntry[], context: TableContext): TableEntry[];
|
|
7
|
+
//# sourceMappingURL=conditions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditions.d.ts","sourceRoot":"","sources":["../../src/tables/conditions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5E,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,cAAc,EACzB,OAAO,EAAE,YAAY,GACpB,OAAO,CA8BT;AAED;2DAC2D;AAC3D,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,YAAY,GACpB,UAAU,EAAE,CAed"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Test a single condition against context. */
|
|
2
|
+
export function evaluateCondition(condition, context) {
|
|
3
|
+
switch (condition.type) {
|
|
4
|
+
case "compare": {
|
|
5
|
+
if (context.triggerRoll === undefined)
|
|
6
|
+
return false;
|
|
7
|
+
return compareNumeric(context.triggerRoll, condition.operator, condition.value);
|
|
8
|
+
}
|
|
9
|
+
case "nat": {
|
|
10
|
+
if (context.triggerNat === undefined)
|
|
11
|
+
return false;
|
|
12
|
+
return compareNumeric(context.triggerNat, condition.operator, condition.value);
|
|
13
|
+
}
|
|
14
|
+
case "tag": {
|
|
15
|
+
if (!context.tags)
|
|
16
|
+
return false;
|
|
17
|
+
return context.tags.includes(condition.tag);
|
|
18
|
+
}
|
|
19
|
+
case "context": {
|
|
20
|
+
if (!context.variables)
|
|
21
|
+
return false;
|
|
22
|
+
const actual = context.variables[condition.variable];
|
|
23
|
+
if (actual === undefined)
|
|
24
|
+
return false;
|
|
25
|
+
if (typeof condition.value === "string") {
|
|
26
|
+
// String comparison: only equality
|
|
27
|
+
return String(actual) === condition.value;
|
|
28
|
+
}
|
|
29
|
+
return compareNumeric(Number(actual), condition.operator, condition.value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Filter entries to only those whose ALL conditions pass.
|
|
34
|
+
* Also filters by level range if context.level is set. */
|
|
35
|
+
export function filterEligibleEntries(entries, context) {
|
|
36
|
+
return entries.filter((entry) => {
|
|
37
|
+
// Level range check
|
|
38
|
+
if (context.level !== undefined) {
|
|
39
|
+
if (entry.minLevel !== undefined && context.level < entry.minLevel)
|
|
40
|
+
return false;
|
|
41
|
+
if (entry.maxLevel !== undefined && context.level > entry.maxLevel)
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
// All conditions must pass
|
|
45
|
+
if (entry.conditions && entry.conditions.length > 0) {
|
|
46
|
+
return entry.conditions.every((c) => evaluateCondition(c, context));
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function compareNumeric(actual, operator, expected) {
|
|
52
|
+
switch (operator) {
|
|
53
|
+
case "=":
|
|
54
|
+
return actual === expected;
|
|
55
|
+
case ">":
|
|
56
|
+
return actual > expected;
|
|
57
|
+
case ">=":
|
|
58
|
+
return actual >= expected;
|
|
59
|
+
case "<":
|
|
60
|
+
return actual < expected;
|
|
61
|
+
case "<=":
|
|
62
|
+
return actual <= expected;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=conditions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditions.js","sourceRoot":"","sources":["../../src/tables/conditions.ts"],"names":[],"mappings":"AAEA,+CAA+C;AAC/C,MAAM,UAAU,iBAAiB,CAC/B,SAAyB,EACzB,OAAqB;IAErB,QAAQ,SAAS,CAAC,IAAI,EAAE,CAAC;QACvB,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS;gBAAE,OAAO,KAAK,CAAC;YACpD,OAAO,cAAc,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QAClF,CAAC;QAED,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;gBAAE,OAAO,KAAK,CAAC;YACnD,OAAO,cAAc,CAAC,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QACjF,CAAC;QAED,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,IAAI,CAAC,OAAO,CAAC,IAAI;gBAAE,OAAO,KAAK,CAAC;YAChC,OAAO,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC9C,CAAC;QAED,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,SAAS;gBAAE,OAAO,KAAK,CAAC;YACrC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YACrD,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,KAAK,CAAC;YAEvC,IAAI,OAAO,SAAS,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACxC,mCAAmC;gBACnC,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,KAAK,CAAC;YAC5C,CAAC;YAED,OAAO,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;AACH,CAAC;AAED;2DAC2D;AAC3D,MAAM,UAAU,qBAAqB,CACnC,OAAqB,EACrB,OAAqB;IAErB,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9B,oBAAoB;QACpB,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAC;YACjF,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAC;QACnF,CAAC;QAED,2BAA2B;QAC3B,IAAI,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpD,OAAO,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QACtE,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CACrB,MAAc,EACd,QAAuC,EACvC,QAAgB;IAEhB,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,GAAG;YACN,OAAO,MAAM,KAAK,QAAQ,CAAC;QAC7B,KAAK,GAAG;YACN,OAAO,MAAM,GAAG,QAAQ,CAAC;QAC3B,KAAK,IAAI;YACP,OAAO,MAAM,IAAI,QAAQ,CAAC;QAC5B,KAAK,GAAG;YACN,OAAO,MAAM,GAAG,QAAQ,CAAC;QAC3B,KAAK,IAAI;YACP,OAAO,MAAM,IAAI,QAAQ,CAAC;IAC9B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GameTable, GameTableCollection, TableContext, TableResult } from "./schema.js";
|
|
2
|
+
import type { LootTable } from "../loot/table.js";
|
|
3
|
+
import { type RngFn } from "../engine/random.js";
|
|
4
|
+
/** Roll on a game table, resolving nested tables, chains, and dice expressions. */
|
|
5
|
+
export declare function rollGameTable(collection: GameTableCollection, tableName: string, context?: TableContext, rng?: RngFn, depth?: number): TableResult[];
|
|
6
|
+
/** Roll on a table multiple times, optionally excluding duplicates. */
|
|
7
|
+
export declare function rollMultiple(collection: GameTableCollection, tableName: string, count: number, context?: TableContext, rng?: RngFn): TableResult[];
|
|
8
|
+
/** Convert a V1 LootTable to a V2 GameTable. */
|
|
9
|
+
export declare function convertLootToGameTable(loot: LootTable): GameTable;
|
|
10
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/tables/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,mBAAmB,EACnB,YAAY,EAEZ,WAAW,EACZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIlD,OAAO,EAAa,KAAK,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAM5D,mFAAmF;AACnF,wBAAgB,aAAa,CAC3B,UAAU,EAAE,mBAAmB,EAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,YAAiB,EAC1B,GAAG,GAAE,KAAiB,EACtB,KAAK,SAAI,GACR,WAAW,EAAE,CAqCf;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAC1B,UAAU,EAAE,mBAAmB,EAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,YAAiB,EAC1B,GAAG,GAAE,KAAiB,GACrB,WAAW,EAAE,CAsCf;AAID,gDAAgD;AAChD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAYjE"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { filterEligibleEntries } from "./conditions.js";
|
|
2
|
+
import { parse } from "../parser/parser.js";
|
|
3
|
+
import { evaluate } from "../engine/roller.js";
|
|
4
|
+
import { cryptoRng } from "../engine/random.js";
|
|
5
|
+
const MAX_DEPTH = 10;
|
|
6
|
+
// ─── Core engine ─────────────────────────────────────────────────────────────
|
|
7
|
+
/** Roll on a game table, resolving nested tables, chains, and dice expressions. */
|
|
8
|
+
export function rollGameTable(collection, tableName, context = {}, rng = cryptoRng, depth = 0) {
|
|
9
|
+
if (depth > MAX_DEPTH) {
|
|
10
|
+
throw new Error("Table recursion depth exceeded (circular reference?)");
|
|
11
|
+
}
|
|
12
|
+
const table = collection.tables.find((t) => t.table === tableName);
|
|
13
|
+
if (!table) {
|
|
14
|
+
throw new Error(`Table not found: ${tableName}`);
|
|
15
|
+
}
|
|
16
|
+
// Filter entries by conditions and level
|
|
17
|
+
const eligible = filterEligibleEntries(table.entries, context);
|
|
18
|
+
if (eligible.length === 0) {
|
|
19
|
+
throw new Error(`No eligible entries in table "${tableName}" for given context`);
|
|
20
|
+
}
|
|
21
|
+
// Weighted random selection
|
|
22
|
+
const selected = weightedSelect(eligible, rng);
|
|
23
|
+
// Handle nested table reference
|
|
24
|
+
if (selected.table) {
|
|
25
|
+
return rollGameTable(collection, selected.table, context, rng, depth + 1);
|
|
26
|
+
}
|
|
27
|
+
// Resolve the entry
|
|
28
|
+
const result = resolveEntry(selected, tableName, rng);
|
|
29
|
+
// Handle chain: roll on additional tables
|
|
30
|
+
if (selected.chain && selected.chain.length > 0) {
|
|
31
|
+
result.chainResults = [];
|
|
32
|
+
for (const chainTable of selected.chain) {
|
|
33
|
+
const chainResult = rollGameTable(collection, chainTable, context, rng, depth + 1);
|
|
34
|
+
result.chainResults.push(...chainResult);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return [result];
|
|
38
|
+
}
|
|
39
|
+
/** Roll on a table multiple times, optionally excluding duplicates. */
|
|
40
|
+
export function rollMultiple(collection, tableName, count, context = {}, rng = cryptoRng) {
|
|
41
|
+
const table = collection.tables.find((t) => t.table === tableName);
|
|
42
|
+
if (!table) {
|
|
43
|
+
throw new Error(`Table not found: ${tableName}`);
|
|
44
|
+
}
|
|
45
|
+
const results = [];
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
for (let i = 0; i < count; i++) {
|
|
48
|
+
const result = rollGameTable(collection, tableName, context, rng);
|
|
49
|
+
const entry = result[0];
|
|
50
|
+
if (!table.allowDuplicates && seen.has(entry.entry)) {
|
|
51
|
+
// Skip duplicates — try again (up to 3x attempts per slot)
|
|
52
|
+
let retries = 0;
|
|
53
|
+
let unique = false;
|
|
54
|
+
while (retries < count * 3 && !unique) {
|
|
55
|
+
const retry = rollGameTable(collection, tableName, context, rng);
|
|
56
|
+
if (!seen.has(retry[0].entry)) {
|
|
57
|
+
results.push(retry[0]);
|
|
58
|
+
seen.add(retry[0].entry);
|
|
59
|
+
unique = true;
|
|
60
|
+
}
|
|
61
|
+
retries++;
|
|
62
|
+
}
|
|
63
|
+
if (!unique) {
|
|
64
|
+
// Exhausted retries, add the duplicate anyway
|
|
65
|
+
results.push(entry);
|
|
66
|
+
seen.add(entry.entry);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
results.push(entry);
|
|
71
|
+
seen.add(entry.entry);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return results;
|
|
75
|
+
}
|
|
76
|
+
// ─── V1 Bridge ───────────────────────────────────────────────────────────────
|
|
77
|
+
/** Convert a V1 LootTable to a V2 GameTable. */
|
|
78
|
+
export function convertLootToGameTable(loot) {
|
|
79
|
+
return {
|
|
80
|
+
table: loot.table,
|
|
81
|
+
kind: "loot",
|
|
82
|
+
entries: loot.items.map((item) => ({
|
|
83
|
+
name: item.name,
|
|
84
|
+
weight: item.weight,
|
|
85
|
+
roll: item.roll,
|
|
86
|
+
quantity: item.quantity,
|
|
87
|
+
table: item.table,
|
|
88
|
+
})),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// ─── Internal helpers ────────────────────────────────────────────────────────
|
|
92
|
+
function weightedSelect(entries, rng) {
|
|
93
|
+
const totalWeight = entries.reduce((sum, e) => sum + e.weight, 0);
|
|
94
|
+
const roll = rng(1, totalWeight);
|
|
95
|
+
let cumulative = 0;
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
cumulative += entry.weight;
|
|
98
|
+
if (roll <= cumulative)
|
|
99
|
+
return entry;
|
|
100
|
+
}
|
|
101
|
+
return entries[entries.length - 1];
|
|
102
|
+
}
|
|
103
|
+
function resolveEntry(entry, tableName, rng) {
|
|
104
|
+
// Resolve quantity dice
|
|
105
|
+
let quantity = 1;
|
|
106
|
+
if (entry.quantity) {
|
|
107
|
+
const ast = parse(entry.quantity);
|
|
108
|
+
quantity = Math.max(1, evaluate(ast, rng).total);
|
|
109
|
+
}
|
|
110
|
+
// Resolve roll value dice
|
|
111
|
+
let rollValue;
|
|
112
|
+
let rollExpression;
|
|
113
|
+
if (entry.roll) {
|
|
114
|
+
const ast = parse(entry.roll);
|
|
115
|
+
rollValue = evaluate(ast, rng).total;
|
|
116
|
+
rollExpression = entry.roll;
|
|
117
|
+
}
|
|
118
|
+
// Resolve duration dice
|
|
119
|
+
let duration;
|
|
120
|
+
let durationExpression;
|
|
121
|
+
if (entry.duration) {
|
|
122
|
+
const ast = parse(entry.duration);
|
|
123
|
+
duration = Math.max(1, evaluate(ast, rng).total);
|
|
124
|
+
durationExpression = entry.duration;
|
|
125
|
+
}
|
|
126
|
+
const result = {
|
|
127
|
+
entry: entry.name,
|
|
128
|
+
quantity,
|
|
129
|
+
fromTable: tableName,
|
|
130
|
+
};
|
|
131
|
+
if (entry.description)
|
|
132
|
+
result.description = entry.description;
|
|
133
|
+
if (rollValue !== undefined)
|
|
134
|
+
result.rollValue = rollValue;
|
|
135
|
+
if (rollExpression)
|
|
136
|
+
result.rollExpression = rollExpression;
|
|
137
|
+
if (duration !== undefined)
|
|
138
|
+
result.duration = duration;
|
|
139
|
+
if (durationExpression)
|
|
140
|
+
result.durationExpression = durationExpression;
|
|
141
|
+
if (entry.rarity)
|
|
142
|
+
result.rarity = entry.rarity;
|
|
143
|
+
if (entry.tags && entry.tags.length > 0)
|
|
144
|
+
result.tags = entry.tags;
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/tables/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export type TableKind = "loot" | "encounter" | "critical" | "fumble" | "reward" | "status" | "event" | "custom";
|
|
2
|
+
export type ConditionType = "compare" | "nat" | "tag" | "context";
|
|
3
|
+
export interface CompareCondition {
|
|
4
|
+
type: "compare";
|
|
5
|
+
/** Compare the triggering roll total against a threshold. */
|
|
6
|
+
operator: "=" | ">" | ">=" | "<" | "<=";
|
|
7
|
+
value: number;
|
|
8
|
+
}
|
|
9
|
+
export interface NatCondition {
|
|
10
|
+
type: "nat";
|
|
11
|
+
/** Compare a specific natural die value (e.g., nat 20). */
|
|
12
|
+
operator: "=" | ">" | ">=" | "<" | "<=";
|
|
13
|
+
value: number;
|
|
14
|
+
}
|
|
15
|
+
export interface TagCondition {
|
|
16
|
+
type: "tag";
|
|
17
|
+
/** Entry is eligible only if this tag is present in context. */
|
|
18
|
+
tag: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ContextCondition {
|
|
21
|
+
type: "context";
|
|
22
|
+
/** Test a named context variable. */
|
|
23
|
+
variable: string;
|
|
24
|
+
operator: "=" | ">" | ">=" | "<" | "<=";
|
|
25
|
+
value: number | string;
|
|
26
|
+
}
|
|
27
|
+
export type TableCondition = CompareCondition | NatCondition | TagCondition | ContextCondition;
|
|
28
|
+
export interface TableEntry {
|
|
29
|
+
name: string;
|
|
30
|
+
weight: number;
|
|
31
|
+
description?: string;
|
|
32
|
+
/** Dice expression evaluated on selection (e.g., "2d6*10" for gold). */
|
|
33
|
+
roll?: string;
|
|
34
|
+
/** Dice expression for quantity (e.g., "1d4"). */
|
|
35
|
+
quantity?: string;
|
|
36
|
+
/** Dice expression for duration in turns/rounds (e.g., "1d4+1"). */
|
|
37
|
+
duration?: string;
|
|
38
|
+
/** Nested table reference — recurses into another table. */
|
|
39
|
+
table?: string;
|
|
40
|
+
/** Chain: after this entry resolves, also roll on these tables. */
|
|
41
|
+
chain?: string[];
|
|
42
|
+
/** Conditions that must ALL pass for this entry to be eligible. */
|
|
43
|
+
conditions?: TableCondition[];
|
|
44
|
+
/** Metadata */
|
|
45
|
+
tags?: string[];
|
|
46
|
+
rarity?: "common" | "uncommon" | "rare" | "epic" | "legendary";
|
|
47
|
+
minLevel?: number;
|
|
48
|
+
maxLevel?: number;
|
|
49
|
+
}
|
|
50
|
+
export interface GameTable {
|
|
51
|
+
table: string;
|
|
52
|
+
kind: TableKind;
|
|
53
|
+
entries: TableEntry[];
|
|
54
|
+
/** Optional dice expression to roll for index-based lookup (e.g., "1d100"). */
|
|
55
|
+
rollExpression?: string;
|
|
56
|
+
/** Allow the same entry to be selected multiple times in multi-roll. */
|
|
57
|
+
allowDuplicates?: boolean;
|
|
58
|
+
/** Tags for table filtering/grouping. */
|
|
59
|
+
tags?: string[];
|
|
60
|
+
}
|
|
61
|
+
export interface TableContext {
|
|
62
|
+
level?: number;
|
|
63
|
+
tags?: string[];
|
|
64
|
+
variables?: Record<string, number | string>;
|
|
65
|
+
/** The result of the triggering dice roll (if any). */
|
|
66
|
+
triggerRoll?: number;
|
|
67
|
+
/** Natural die value from trigger (for nat conditions). */
|
|
68
|
+
triggerNat?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface TableResult {
|
|
71
|
+
entry: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
quantity: number;
|
|
74
|
+
rollValue?: number;
|
|
75
|
+
rollExpression?: string;
|
|
76
|
+
duration?: number;
|
|
77
|
+
durationExpression?: string;
|
|
78
|
+
fromTable: string;
|
|
79
|
+
rarity?: string;
|
|
80
|
+
tags?: string[];
|
|
81
|
+
/** Results from chained tables. */
|
|
82
|
+
chainResults?: TableResult[];
|
|
83
|
+
}
|
|
84
|
+
export interface GameTableCollection {
|
|
85
|
+
version: "2.0";
|
|
86
|
+
tables: GameTable[];
|
|
87
|
+
metadata?: Record<string, unknown>;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/tables/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/tables/schema.ts"],"names":[],"mappings":"AAAA,gFAAgF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/tables/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGvD,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,mBAAmB,GAAG,MAAM,EAAE,CAsE5E"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { parse } from "../parser/parser.js";
|
|
2
|
+
/** Validate a game table collection. Returns error strings or empty array. */
|
|
3
|
+
export function validateGameTables(collection) {
|
|
4
|
+
const errors = [];
|
|
5
|
+
const tableNames = new Set(collection.tables.map((t) => t.table));
|
|
6
|
+
for (const table of collection.tables) {
|
|
7
|
+
if (!table.table) {
|
|
8
|
+
errors.push("Table missing name");
|
|
9
|
+
}
|
|
10
|
+
if (!table.entries || table.entries.length === 0) {
|
|
11
|
+
errors.push(`Table "${table.table}" has no entries`);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
for (const entry of table.entries) {
|
|
15
|
+
// Must have a name or a table reference
|
|
16
|
+
if (!entry.name && !entry.table) {
|
|
17
|
+
errors.push(`Entry in "${table.table}" has no name or table reference`);
|
|
18
|
+
}
|
|
19
|
+
// Weight must be positive
|
|
20
|
+
if (entry.weight <= 0) {
|
|
21
|
+
errors.push(`Entry "${entry.name}" in "${table.table}" has invalid weight: ${entry.weight}`);
|
|
22
|
+
}
|
|
23
|
+
// Nested table reference must exist
|
|
24
|
+
if (entry.table && !tableNames.has(entry.table)) {
|
|
25
|
+
errors.push(`Table reference "${entry.table}" not found (in "${table.table}")`);
|
|
26
|
+
}
|
|
27
|
+
// Chain references must exist
|
|
28
|
+
if (entry.chain) {
|
|
29
|
+
for (const ref of entry.chain) {
|
|
30
|
+
if (!tableNames.has(ref)) {
|
|
31
|
+
errors.push(`Chain reference "${ref}" not found (in "${table.table}", entry "${entry.name}")`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Validate dice expressions
|
|
36
|
+
for (const [field, expr] of [
|
|
37
|
+
["roll", entry.roll],
|
|
38
|
+
["quantity", entry.quantity],
|
|
39
|
+
["duration", entry.duration],
|
|
40
|
+
]) {
|
|
41
|
+
if (expr) {
|
|
42
|
+
try {
|
|
43
|
+
parse(expr);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
errors.push(`Invalid dice expression "${expr}" for ${field} in "${table.table}", entry "${entry.name}"`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Level range sanity
|
|
51
|
+
if (entry.minLevel !== undefined &&
|
|
52
|
+
entry.maxLevel !== undefined &&
|
|
53
|
+
entry.minLevel > entry.maxLevel) {
|
|
54
|
+
errors.push(`Entry "${entry.name}" in "${table.table}" has minLevel (${entry.minLevel}) > maxLevel (${entry.maxLevel})`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Circular reference detection via DFS
|
|
59
|
+
const circularErrors = detectCircularRefs(collection);
|
|
60
|
+
errors.push(...circularErrors);
|
|
61
|
+
return errors;
|
|
62
|
+
}
|
|
63
|
+
function detectCircularRefs(collection) {
|
|
64
|
+
const errors = [];
|
|
65
|
+
const tableMap = new Map(collection.tables.map((t) => [t.table, t]));
|
|
66
|
+
for (const table of collection.tables) {
|
|
67
|
+
const visited = new Set();
|
|
68
|
+
const stack = new Set();
|
|
69
|
+
function dfs(name) {
|
|
70
|
+
if (stack.has(name)) {
|
|
71
|
+
errors.push(`Circular reference detected: ${[...stack, name].join(" → ")}`);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (visited.has(name))
|
|
75
|
+
return false;
|
|
76
|
+
visited.add(name);
|
|
77
|
+
stack.add(name);
|
|
78
|
+
const t = tableMap.get(name);
|
|
79
|
+
if (t) {
|
|
80
|
+
for (const entry of t.entries) {
|
|
81
|
+
if (entry.table && dfs(entry.table))
|
|
82
|
+
return true;
|
|
83
|
+
if (entry.chain) {
|
|
84
|
+
for (const ref of entry.chain) {
|
|
85
|
+
if (dfs(ref))
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
stack.delete(name);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
dfs(table.table);
|
|
95
|
+
}
|
|
96
|
+
return errors;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/tables/validate.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcptoolshop/roll",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "RPG dice engine — expression parser, probability analyzer, loot tables, beautiful terminal output",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"roll": "./dist/bin.js"
|
|
9
|
+
"roll": "./dist/bin.js",
|
|
10
|
+
"roll-bridge": "./dist/bridge/server.js",
|
|
11
|
+
"roll-mcp": "./dist/mcp/server.js"
|
|
10
12
|
},
|
|
11
13
|
"exports": {
|
|
12
14
|
".": {
|
|
13
15
|
"types": "./dist/index.d.ts",
|
|
14
16
|
"import": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./tables": {
|
|
19
|
+
"types": "./dist/tables/schema.d.ts",
|
|
20
|
+
"import": "./dist/tables/engine.js"
|
|
21
|
+
},
|
|
22
|
+
"./bridge": {
|
|
23
|
+
"types": "./dist/bridge/protocol.d.ts",
|
|
24
|
+
"import": "./dist/bridge/handler.js"
|
|
15
25
|
}
|
|
16
26
|
},
|
|
17
27
|
"files": [
|