@mythxengine/engine 0.1.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/LICENSE +21 -0
- package/dist/__tests__/advantage.test.d.ts +5 -0
- package/dist/__tests__/advantage.test.d.ts.map +1 -0
- package/dist/__tests__/advantage.test.js +126 -0
- package/dist/__tests__/advantage.test.js.map +1 -0
- package/dist/__tests__/dice.test.d.ts +2 -0
- package/dist/__tests__/dice.test.d.ts.map +1 -0
- package/dist/__tests__/dice.test.js +150 -0
- package/dist/__tests__/dice.test.js.map +1 -0
- package/dist/__tests__/resolution.test.d.ts +5 -0
- package/dist/__tests__/resolution.test.d.ts.map +1 -0
- package/dist/__tests__/resolution.test.js +362 -0
- package/dist/__tests__/resolution.test.js.map +1 -0
- package/dist/__tests__/rng.test.d.ts +2 -0
- package/dist/__tests__/rng.test.d.ts.map +1 -0
- package/dist/__tests__/rng.test.js +93 -0
- package/dist/__tests__/rng.test.js.map +1 -0
- package/dist/dice/advantage.d.ts +45 -0
- package/dist/dice/advantage.d.ts.map +1 -0
- package/dist/dice/advantage.js +118 -0
- package/dist/dice/advantage.js.map +1 -0
- package/dist/dice/index.d.ts +7 -0
- package/dist/dice/index.d.ts.map +1 -0
- package/dist/dice/index.js +7 -0
- package/dist/dice/index.js.map +1 -0
- package/dist/dice/parser.d.ts +21 -0
- package/dist/dice/parser.d.ts.map +1 -0
- package/dist/dice/parser.js +61 -0
- package/dist/dice/parser.js.map +1 -0
- package/dist/dice/roller.d.ts +23 -0
- package/dist/dice/roller.d.ts.map +1 -0
- package/dist/dice/roller.js +62 -0
- package/dist/dice/roller.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/resolution/combat.d.ts +39 -0
- package/dist/resolution/combat.d.ts.map +1 -0
- package/dist/resolution/combat.js +214 -0
- package/dist/resolution/combat.js.map +1 -0
- package/dist/resolution/damage.d.ts +27 -0
- package/dist/resolution/damage.d.ts.map +1 -0
- package/dist/resolution/damage.js +44 -0
- package/dist/resolution/damage.js.map +1 -0
- package/dist/resolution/index.d.ts +8 -0
- package/dist/resolution/index.d.ts.map +1 -0
- package/dist/resolution/index.js +8 -0
- package/dist/resolution/index.js.map +1 -0
- package/dist/resolution/initiative.d.ts +19 -0
- package/dist/resolution/initiative.d.ts.map +1 -0
- package/dist/resolution/initiative.js +55 -0
- package/dist/resolution/initiative.js.map +1 -0
- package/dist/resolution/test.d.ts +34 -0
- package/dist/resolution/test.d.ts.map +1 -0
- package/dist/resolution/test.js +143 -0
- package/dist/resolution/test.js.map +1 -0
- package/dist/rng/index.d.ts +6 -0
- package/dist/rng/index.d.ts.map +1 -0
- package/dist/rng/index.js +6 -0
- package/dist/rng/index.js.map +1 -0
- package/dist/rng/mulberry32.d.ts +13 -0
- package/dist/rng/mulberry32.d.ts.map +1 -0
- package/dist/rng/mulberry32.js +23 -0
- package/dist/rng/mulberry32.js.map +1 -0
- package/dist/rng/rng.d.ts +26 -0
- package/dist/rng/rng.d.ts.map +1 -0
- package/dist/rng/rng.js +51 -0
- package/dist/rng/rng.js.map +1 -0
- package/dist/rules/context.d.ts +86 -0
- package/dist/rules/context.d.ts.map +1 -0
- package/dist/rules/context.js +137 -0
- package/dist/rules/context.js.map +1 -0
- package/dist/rules/custom-test.d.ts +33 -0
- package/dist/rules/custom-test.d.ts.map +1 -0
- package/dist/rules/custom-test.js +164 -0
- package/dist/rules/custom-test.js.map +1 -0
- package/dist/rules/index.d.ts +6 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +6 -0
- package/dist/rules/index.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dice exports
|
|
3
|
+
*/
|
|
4
|
+
export { parseDice, isValidDiceExpression } from "./parser.js";
|
|
5
|
+
export { rollDice, rollDie, rollNd } from "./roller.js";
|
|
6
|
+
export { calculateNetAdvantage, rollD20WithAdvantage, rollWithAdvantage, } from "./advantage.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dice/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dice exports
|
|
3
|
+
*/
|
|
4
|
+
export { parseDice, isValidDiceExpression } from "./parser.js";
|
|
5
|
+
export { rollDice, rollDie, rollNd } from "./roller.js";
|
|
6
|
+
export { calculateNetAdvantage, rollD20WithAdvantage, rollWithAdvantage, } from "./advantage.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dice/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dice expression parser
|
|
3
|
+
*
|
|
4
|
+
* Supports formats:
|
|
5
|
+
* - "d20" -> 1d20+0
|
|
6
|
+
* - "2d6" -> 2d6+0
|
|
7
|
+
* - "1d8+3" -> 1d8+3
|
|
8
|
+
* - "d6-1" -> 1d6-1
|
|
9
|
+
* - "d20+STR" -> 1d20+0 with ability: "STR"
|
|
10
|
+
*/
|
|
11
|
+
import type { ParsedDice } from "@mythxengine/types";
|
|
12
|
+
/**
|
|
13
|
+
* Parse a dice expression string
|
|
14
|
+
* @throws Error if expression is invalid
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseDice(expression: string): ParsedDice;
|
|
17
|
+
/**
|
|
18
|
+
* Check if a string is a valid dice expression
|
|
19
|
+
*/
|
|
20
|
+
export declare function isValidDiceExpression(expression: string): boolean;
|
|
21
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/dice/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,oBAAoB,CAAC;AAMlE;;;GAGG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAsCxD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAOjE"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dice expression parser
|
|
3
|
+
*
|
|
4
|
+
* Supports formats:
|
|
5
|
+
* - "d20" -> 1d20+0
|
|
6
|
+
* - "2d6" -> 2d6+0
|
|
7
|
+
* - "1d8+3" -> 1d8+3
|
|
8
|
+
* - "d6-1" -> 1d6-1
|
|
9
|
+
* - "d20+STR" -> 1d20+0 with ability: "STR"
|
|
10
|
+
*/
|
|
11
|
+
const DICE_REGEX = /^(\d*)d(\d+)(?:([+-])(\d+|STR|AGI|WIT|CON))?$/i;
|
|
12
|
+
const ABILITY_NAMES = new Set(["STR", "AGI", "WIT", "CON"]);
|
|
13
|
+
/**
|
|
14
|
+
* Parse a dice expression string
|
|
15
|
+
* @throws Error if expression is invalid
|
|
16
|
+
*/
|
|
17
|
+
export function parseDice(expression) {
|
|
18
|
+
const trimmed = expression.trim();
|
|
19
|
+
const match = trimmed.match(DICE_REGEX);
|
|
20
|
+
if (!match) {
|
|
21
|
+
throw new Error(`Invalid dice expression: ${expression}`);
|
|
22
|
+
}
|
|
23
|
+
const [, countStr, sidesStr, sign, modifierStr] = match;
|
|
24
|
+
const count = countStr ? parseInt(countStr, 10) : 1;
|
|
25
|
+
const sides = parseInt(sidesStr, 10);
|
|
26
|
+
if (count < 1 || count > 100) {
|
|
27
|
+
throw new Error(`Invalid dice count: ${count}`);
|
|
28
|
+
}
|
|
29
|
+
if (sides < 1 || sides > 100) {
|
|
30
|
+
throw new Error(`Invalid dice sides: ${sides}`);
|
|
31
|
+
}
|
|
32
|
+
// Parse modifier
|
|
33
|
+
let modifier = 0;
|
|
34
|
+
let ability;
|
|
35
|
+
if (modifierStr) {
|
|
36
|
+
const upperMod = modifierStr.toUpperCase();
|
|
37
|
+
if (ABILITY_NAMES.has(upperMod)) {
|
|
38
|
+
ability = upperMod;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
modifier = parseInt(modifierStr, 10);
|
|
42
|
+
if (sign === "-") {
|
|
43
|
+
modifier = -modifier;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { count, sides, modifier, ability };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if a string is a valid dice expression
|
|
51
|
+
*/
|
|
52
|
+
export function isValidDiceExpression(expression) {
|
|
53
|
+
try {
|
|
54
|
+
parseDice(expression);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/dice/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,UAAU,GAAG,gDAAgD,CAAC;AAEpE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAE5D;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,UAAkB;IAC1C,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAExC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,4BAA4B,UAAU,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC;IAExD,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAErC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,uBAAuB,KAAK,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,uBAAuB,KAAK,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,iBAAiB;IACjB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,OAAgC,CAAC;IAErC,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;QAC3C,IAAI,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChC,OAAO,GAAG,QAAuB,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YACrC,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjB,QAAQ,GAAG,CAAC,QAAQ,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC7C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAAkB;IACtD,IAAI,CAAC;QACH,SAAS,CAAC,UAAU,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dice roller
|
|
3
|
+
*/
|
|
4
|
+
import type { DiceResult, Abilities } from "@mythxengine/types";
|
|
5
|
+
import type { RNG } from "../rng/rng.js";
|
|
6
|
+
/**
|
|
7
|
+
* Roll a dice expression
|
|
8
|
+
*
|
|
9
|
+
* @param expression - Dice expression (e.g., "2d6+3")
|
|
10
|
+
* @param rng - Random number generator
|
|
11
|
+
* @param abilities - Optional abilities for ability modifiers
|
|
12
|
+
* @returns Dice result with all roll details
|
|
13
|
+
*/
|
|
14
|
+
export declare function rollDice(expression: string, rng: RNG, abilities?: Abilities): DiceResult;
|
|
15
|
+
/**
|
|
16
|
+
* Roll a simple die (e.g., d20, d6)
|
|
17
|
+
*/
|
|
18
|
+
export declare function rollDie(sides: number, rng: RNG): number;
|
|
19
|
+
/**
|
|
20
|
+
* Roll multiple dice and sum
|
|
21
|
+
*/
|
|
22
|
+
export declare function rollNd(count: number, sides: number, rng: RNG): number;
|
|
23
|
+
//# sourceMappingURL=roller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roller.d.ts","sourceRoot":"","sources":["../../src/dice/roller.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,GAAG,EACR,SAAS,CAAC,EAAE,SAAS,GACpB,UAAU,CAoCZ;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,CAEvD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,CAMrE"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dice roller
|
|
3
|
+
*/
|
|
4
|
+
import { parseDice } from "./parser.js";
|
|
5
|
+
/**
|
|
6
|
+
* Roll a dice expression
|
|
7
|
+
*
|
|
8
|
+
* @param expression - Dice expression (e.g., "2d6+3")
|
|
9
|
+
* @param rng - Random number generator
|
|
10
|
+
* @param abilities - Optional abilities for ability modifiers
|
|
11
|
+
* @returns Dice result with all roll details
|
|
12
|
+
*/
|
|
13
|
+
export function rollDice(expression, rng, abilities) {
|
|
14
|
+
const parsed = parseDice(expression);
|
|
15
|
+
// Roll each die
|
|
16
|
+
const rolls = [];
|
|
17
|
+
for (let i = 0; i < parsed.count; i++) {
|
|
18
|
+
rolls.push(rng.nextInt(1, parsed.sides));
|
|
19
|
+
}
|
|
20
|
+
// Calculate natural total (sum of dice)
|
|
21
|
+
const natural = rolls.reduce((sum, roll) => sum + roll, 0);
|
|
22
|
+
// Calculate modifier (including ability if specified)
|
|
23
|
+
let modifier = parsed.modifier;
|
|
24
|
+
if (parsed.ability && abilities) {
|
|
25
|
+
modifier += abilities[parsed.ability];
|
|
26
|
+
}
|
|
27
|
+
// Calculate total
|
|
28
|
+
const total = natural + modifier;
|
|
29
|
+
// Check for critical (only on d20)
|
|
30
|
+
let critical;
|
|
31
|
+
if (parsed.count === 1 && parsed.sides === 20) {
|
|
32
|
+
if (natural === 20)
|
|
33
|
+
critical = "success";
|
|
34
|
+
if (natural === 1)
|
|
35
|
+
critical = "failure";
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
expression,
|
|
39
|
+
rolls,
|
|
40
|
+
modifier,
|
|
41
|
+
total,
|
|
42
|
+
natural,
|
|
43
|
+
critical,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Roll a simple die (e.g., d20, d6)
|
|
48
|
+
*/
|
|
49
|
+
export function rollDie(sides, rng) {
|
|
50
|
+
return rng.nextInt(1, sides);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Roll multiple dice and sum
|
|
54
|
+
*/
|
|
55
|
+
export function rollNd(count, sides, rng) {
|
|
56
|
+
let total = 0;
|
|
57
|
+
for (let i = 0; i < count; i++) {
|
|
58
|
+
total += rng.nextInt(1, sides);
|
|
59
|
+
}
|
|
60
|
+
return total;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=roller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roller.js","sourceRoot":"","sources":["../../src/dice/roller.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CACtB,UAAkB,EAClB,GAAQ,EACR,SAAqB;IAErB,MAAM,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IAErC,gBAAgB;IAChB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,wCAAwC;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;IAE3D,sDAAsD;IACtD,IAAI,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC/B,IAAI,MAAM,CAAC,OAAO,IAAI,SAAS,EAAE,CAAC;QAChC,QAAQ,IAAI,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,kBAAkB;IAClB,MAAM,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IAEjC,mCAAmC;IACnC,IAAI,QAA2C,CAAC;IAChD,IAAI,MAAM,CAAC,KAAK,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE,EAAE,CAAC;QAC9C,IAAI,OAAO,KAAK,EAAE;YAAE,QAAQ,GAAG,SAAS,CAAC;QACzC,IAAI,OAAO,KAAK,CAAC;YAAE,QAAQ,GAAG,SAAS,CAAC;IAC1C,CAAC;IAED,OAAO;QACL,UAAU;QACV,KAAK;QACL,QAAQ;QACR,KAAK;QACL,OAAO;QACP,QAAQ;KACT,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,GAAQ;IAC7C,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,KAAa,EAAE,GAAQ;IAC3D,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mythxengine/engine
|
|
3
|
+
*
|
|
4
|
+
* Pure game engine with deterministic mechanics
|
|
5
|
+
*/
|
|
6
|
+
export * from "./rng/index.js";
|
|
7
|
+
export * from "./dice/index.js";
|
|
8
|
+
export * from "./resolution/index.js";
|
|
9
|
+
export * from "./rules/index.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,gBAAgB,CAAC;AAG/B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,uBAAuB,CAAC;AAGtC,cAAc,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mythxengine/engine
|
|
3
|
+
*
|
|
4
|
+
* Pure game engine with deterministic mechanics
|
|
5
|
+
*/
|
|
6
|
+
// RNG
|
|
7
|
+
export * from "./rng/index.js";
|
|
8
|
+
// Dice
|
|
9
|
+
export * from "./dice/index.js";
|
|
10
|
+
// Resolution
|
|
11
|
+
export * from "./resolution/index.js";
|
|
12
|
+
// Rules
|
|
13
|
+
export * from "./rules/index.js";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM;AACN,cAAc,gBAAgB,CAAC;AAE/B,OAAO;AACP,cAAc,iBAAiB,CAAC;AAEhC,aAAa;AACb,cAAc,uBAAuB,CAAC;AAEtC,QAAQ;AACR,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combat resolution
|
|
3
|
+
*/
|
|
4
|
+
import type { Character, Enemy, Weapon, Modifier, AttackResult } from "@mythxengine/types";
|
|
5
|
+
import type { RNG } from "../rng/rng.js";
|
|
6
|
+
import type { RulesContext } from "../rules/context.js";
|
|
7
|
+
export interface AttackOptions {
|
|
8
|
+
attacker: Character | Enemy;
|
|
9
|
+
defender: Character | Enemy;
|
|
10
|
+
weapon: Weapon;
|
|
11
|
+
modifiers?: Modifier[];
|
|
12
|
+
rng: RNG;
|
|
13
|
+
/** Explicit sources of advantage (e.g., "flanking", "high ground") */
|
|
14
|
+
advantageSources?: string[];
|
|
15
|
+
/** Explicit sources of disadvantage (e.g., "darkness", "cover") */
|
|
16
|
+
disadvantageSources?: string[];
|
|
17
|
+
/** Damage type for resistance/vulnerability (default: "physical") */
|
|
18
|
+
damageType?: string;
|
|
19
|
+
/** Rules context (uses defaults if not provided) */
|
|
20
|
+
rules?: RulesContext;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve an attack
|
|
24
|
+
*
|
|
25
|
+
* Attack roll: d20 + ability + skill vs 10 + defender AGI
|
|
26
|
+
* On hit: roll damage - armor
|
|
27
|
+
*
|
|
28
|
+
* Supports advantage/disadvantage:
|
|
29
|
+
* - Explicit sources passed via advantageSources/disadvantageSources
|
|
30
|
+
* - Conditions with GRANT_ADVANTAGE/GRANT_DISADVANTAGE effects
|
|
31
|
+
* - Advantage + disadvantage cancel to normal roll
|
|
32
|
+
*
|
|
33
|
+
* Supports resistance/vulnerability:
|
|
34
|
+
* - Defender conditions with RESISTANCE/VULNERABILITY effects
|
|
35
|
+
* - Resistance halves damage, vulnerability doubles it
|
|
36
|
+
* - Resistance + vulnerability cancel out
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveAttack(options: AttackOptions): AttackResult;
|
|
39
|
+
//# sourceMappingURL=combat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combat.d.ts","sourceRoot":"","sources":["../../src/resolution/combat.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,KAAK,EACL,MAAM,EACN,QAAQ,EACR,YAAY,EAGb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAUxD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,SAAS,GAAG,KAAK,CAAC;IAC5B,QAAQ,EAAE,SAAS,GAAG,KAAK,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,GAAG,EAAE,GAAG,CAAC;IACT,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AA2ID;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CA6GlE"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combat resolution
|
|
3
|
+
*/
|
|
4
|
+
import { rollWithAdvantage, calculateNetAdvantage } from "../dice/advantage.js";
|
|
5
|
+
import { calculateDamage } from "./damage.js";
|
|
6
|
+
import { getDefaultRulesContext, checkCriticalSuccess, checkCriticalFailure, calculateDefenseTarget, getResistanceMultiplier, getVulnerabilityMultiplier, } from "../rules/context.js";
|
|
7
|
+
/**
|
|
8
|
+
* Check if combatant is a Character (not an Enemy)
|
|
9
|
+
*/
|
|
10
|
+
function isCharacter(combatant) {
|
|
11
|
+
return "archetypeId" in combatant;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get skill bonus for combat
|
|
15
|
+
*/
|
|
16
|
+
function getCombatSkillBonus(combatant, skillName) {
|
|
17
|
+
if (!skillName)
|
|
18
|
+
return 0;
|
|
19
|
+
if (!isCharacter(combatant))
|
|
20
|
+
return 0; // Enemies don't have skills
|
|
21
|
+
const skill = combatant.skills.find((s) => s.name.toLowerCase() === skillName.toLowerCase());
|
|
22
|
+
return skill?.bonus ?? 0;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get condition modifiers for attack
|
|
26
|
+
*/
|
|
27
|
+
function getAttackConditionMod(combatant) {
|
|
28
|
+
let total = 0;
|
|
29
|
+
for (const condition of combatant.conditions) {
|
|
30
|
+
for (const effect of condition.effects) {
|
|
31
|
+
if (effect.type === "MODIFY_SKILL" && effect.skillId === "combat") {
|
|
32
|
+
total += effect.amount;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return total;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get advantage/disadvantage sources from attacker conditions
|
|
40
|
+
*/
|
|
41
|
+
function getAttackAdvantage(attacker) {
|
|
42
|
+
const advantageSources = [];
|
|
43
|
+
const disadvantageSources = [];
|
|
44
|
+
for (const condition of attacker.conditions) {
|
|
45
|
+
for (const effect of condition.effects) {
|
|
46
|
+
if (effect.type === "GRANT_ADVANTAGE") {
|
|
47
|
+
if (effect.scope === "all" || effect.scope === "attacks") {
|
|
48
|
+
advantageSources.push(`condition:${condition.name}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (effect.type === "GRANT_DISADVANTAGE") {
|
|
52
|
+
if (effect.scope === "all" || effect.scope === "attacks") {
|
|
53
|
+
disadvantageSources.push(`condition:${condition.name}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { advantageSources, disadvantageSources };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Calculate damage multiplier from defender's resistance/vulnerability
|
|
62
|
+
*
|
|
63
|
+
* @param defender - The defender
|
|
64
|
+
* @param damageType - The damage type being dealt
|
|
65
|
+
* @param rules - Rules context for default multipliers
|
|
66
|
+
* @returns Object with multiplier and reason
|
|
67
|
+
*/
|
|
68
|
+
function getDamageMultiplier(defender, damageType, rules) {
|
|
69
|
+
let hasResistance = false;
|
|
70
|
+
let hasVulnerability = false;
|
|
71
|
+
// Use rules context for default multipliers
|
|
72
|
+
let resistanceMultiplier = getResistanceMultiplier(rules);
|
|
73
|
+
let vulnerabilityMultiplier = getVulnerabilityMultiplier(rules);
|
|
74
|
+
for (const condition of defender.conditions) {
|
|
75
|
+
for (const effect of condition.effects) {
|
|
76
|
+
if (effect.type === "RESISTANCE" &&
|
|
77
|
+
effect.damageType.toLowerCase() === damageType.toLowerCase()) {
|
|
78
|
+
hasResistance = true;
|
|
79
|
+
if (effect.multiplier !== undefined) {
|
|
80
|
+
resistanceMultiplier = effect.multiplier;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (effect.type === "VULNERABILITY" &&
|
|
84
|
+
effect.damageType.toLowerCase() === damageType.toLowerCase()) {
|
|
85
|
+
hasVulnerability = true;
|
|
86
|
+
if (effect.multiplier !== undefined) {
|
|
87
|
+
vulnerabilityMultiplier = effect.multiplier;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// If both, they cancel out (like advantage/disadvantage)
|
|
93
|
+
if (hasResistance && hasVulnerability) {
|
|
94
|
+
return { multiplier: 1 };
|
|
95
|
+
}
|
|
96
|
+
if (hasResistance) {
|
|
97
|
+
return { multiplier: resistanceMultiplier, reason: "resistance" };
|
|
98
|
+
}
|
|
99
|
+
if (hasVulnerability) {
|
|
100
|
+
return { multiplier: vulnerabilityMultiplier, reason: "vulnerability" };
|
|
101
|
+
}
|
|
102
|
+
return { multiplier: 1 };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get armor value for a defender
|
|
106
|
+
*/
|
|
107
|
+
function getArmorValue(defender) {
|
|
108
|
+
if ("armor" in defender && typeof defender.armor === "number") {
|
|
109
|
+
return defender.armor;
|
|
110
|
+
}
|
|
111
|
+
// Parse armor from equipment description if character
|
|
112
|
+
const armorStr = defender.equipment?.armor;
|
|
113
|
+
if (!armorStr)
|
|
114
|
+
return 0;
|
|
115
|
+
// Try to extract number from description like "Light armor (+1 defense)"
|
|
116
|
+
const match = armorStr.match(/\+(\d+)/);
|
|
117
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolve an attack
|
|
121
|
+
*
|
|
122
|
+
* Attack roll: d20 + ability + skill vs 10 + defender AGI
|
|
123
|
+
* On hit: roll damage - armor
|
|
124
|
+
*
|
|
125
|
+
* Supports advantage/disadvantage:
|
|
126
|
+
* - Explicit sources passed via advantageSources/disadvantageSources
|
|
127
|
+
* - Conditions with GRANT_ADVANTAGE/GRANT_DISADVANTAGE effects
|
|
128
|
+
* - Advantage + disadvantage cancel to normal roll
|
|
129
|
+
*
|
|
130
|
+
* Supports resistance/vulnerability:
|
|
131
|
+
* - Defender conditions with RESISTANCE/VULNERABILITY effects
|
|
132
|
+
* - Resistance halves damage, vulnerability doubles it
|
|
133
|
+
* - Resistance + vulnerability cancel out
|
|
134
|
+
*/
|
|
135
|
+
export function resolveAttack(options) {
|
|
136
|
+
const { attacker, defender, weapon, modifiers = [], rng, advantageSources = [], disadvantageSources = [], damageType = "physical", rules = getDefaultRulesContext(), } = options;
|
|
137
|
+
// Determine attack ability
|
|
138
|
+
const attackAbility = weapon.ability ?? "STR";
|
|
139
|
+
const attackMod = attacker.abilities[attackAbility];
|
|
140
|
+
// Get combat skill bonus
|
|
141
|
+
const skillBonus = getCombatSkillBonus(attacker, weapon.skill);
|
|
142
|
+
// Condition modifiers
|
|
143
|
+
const conditionMods = getAttackConditionMod(attacker);
|
|
144
|
+
// Other modifiers
|
|
145
|
+
const otherMods = modifiers.reduce((sum, m) => sum + m.amount, 0);
|
|
146
|
+
// Total attack bonus
|
|
147
|
+
const totalAttackMod = attackMod + skillBonus + conditionMods + otherMods;
|
|
148
|
+
// Defense target from rules context (default: 10 + defender AGI)
|
|
149
|
+
const defenseTarget = calculateDefenseTarget(rules, defender.abilities);
|
|
150
|
+
// Get advantage/disadvantage from attacker conditions
|
|
151
|
+
const conditionAdvantage = getAttackAdvantage(attacker);
|
|
152
|
+
// Combine explicit and condition-based advantage sources
|
|
153
|
+
const allAdvantageSources = [
|
|
154
|
+
...advantageSources,
|
|
155
|
+
...conditionAdvantage.advantageSources,
|
|
156
|
+
];
|
|
157
|
+
const allDisadvantageSources = [
|
|
158
|
+
...disadvantageSources,
|
|
159
|
+
...conditionAdvantage.disadvantageSources,
|
|
160
|
+
];
|
|
161
|
+
// Calculate net advantage state
|
|
162
|
+
const advantageState = calculateNetAdvantage(allAdvantageSources, allDisadvantageSources);
|
|
163
|
+
// Roll attack with advantage/disadvantage
|
|
164
|
+
const attackRoll = rollWithAdvantage("d20", rng, advantageState);
|
|
165
|
+
const attackTotal = attackRoll.natural + totalAttackMod;
|
|
166
|
+
// Check for critical using rules context
|
|
167
|
+
const criticalHit = checkCriticalSuccess(rules, attackRoll.natural);
|
|
168
|
+
const criticalMiss = checkCriticalFailure(rules, attackRoll.natural);
|
|
169
|
+
// Determine hit (respects autoSuccess/autoFailure from rules)
|
|
170
|
+
const { criticals } = rules.rules.mechanics;
|
|
171
|
+
const autoHit = criticalHit && criticals.autoSuccess;
|
|
172
|
+
const autoMiss = criticalMiss && criticals.autoFailure;
|
|
173
|
+
const hit = autoHit || (!autoMiss && attackTotal >= defenseTarget);
|
|
174
|
+
if (!hit) {
|
|
175
|
+
return {
|
|
176
|
+
hit: false,
|
|
177
|
+
roll: { ...attackRoll, total: attackTotal },
|
|
178
|
+
critical: criticalMiss ? "miss" : undefined,
|
|
179
|
+
advantageState,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// Calculate base damage using rules context for damage config
|
|
183
|
+
const armor = getArmorValue(defender);
|
|
184
|
+
const damageResult = calculateDamage(weapon, attackMod, armor, rng, rules);
|
|
185
|
+
// Apply critical damage multiplier from rules context
|
|
186
|
+
const critMultiplier = criticalHit ? criticals.damageMultiplier : 1;
|
|
187
|
+
let finalDamage = Math.floor(damageResult.damage * critMultiplier);
|
|
188
|
+
// Apply resistance/vulnerability using rules context for default multipliers
|
|
189
|
+
const damageMultiplierInfo = getDamageMultiplier(defender, damageType, rules);
|
|
190
|
+
const originalDamage = finalDamage;
|
|
191
|
+
finalDamage = Math.floor(finalDamage * damageMultiplierInfo.multiplier);
|
|
192
|
+
// Build damage modification info if applicable
|
|
193
|
+
let damageModification;
|
|
194
|
+
if (damageMultiplierInfo.reason) {
|
|
195
|
+
damageModification = {
|
|
196
|
+
originalDamage,
|
|
197
|
+
finalDamage,
|
|
198
|
+
reason: damageMultiplierInfo.reason,
|
|
199
|
+
damageType,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// Calculate remaining HP
|
|
203
|
+
const defenderHpRemaining = defender.hp.current - finalDamage;
|
|
204
|
+
return {
|
|
205
|
+
hit: true,
|
|
206
|
+
roll: { ...attackRoll, total: attackTotal },
|
|
207
|
+
damage: finalDamage,
|
|
208
|
+
critical: criticalHit ? "hit" : undefined,
|
|
209
|
+
defenderHpRemaining,
|
|
210
|
+
advantageState,
|
|
211
|
+
damageModification,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=combat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combat.js","sourceRoot":"","sources":["../../src/resolution/combat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Damage calculation
|
|
3
|
+
*/
|
|
4
|
+
import type { Weapon, DiceResult } from "@mythxengine/types";
|
|
5
|
+
import type { RNG } from "../rng/rng.js";
|
|
6
|
+
import type { RulesContext } from "../rules/context.js";
|
|
7
|
+
export interface DamageResult {
|
|
8
|
+
damage: number;
|
|
9
|
+
roll: DiceResult;
|
|
10
|
+
rawDamage: number;
|
|
11
|
+
armorReduction: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Calculate damage from an attack
|
|
15
|
+
*
|
|
16
|
+
* Default: Damage = weapon die + ability mod - armor (minimum 0)
|
|
17
|
+
* Respects rules context for addAbility, subtractArmor, minimumDamage
|
|
18
|
+
*/
|
|
19
|
+
export declare function calculateDamage(weapon: Weapon, abilityMod: number, armor: number, rng: RNG, rules?: RulesContext): DamageResult;
|
|
20
|
+
/**
|
|
21
|
+
* Calculate healing amount
|
|
22
|
+
*/
|
|
23
|
+
export declare function calculateHealing(expression: string, rng: RNG, maxHp: number, currentHp: number): {
|
|
24
|
+
healing: number;
|
|
25
|
+
roll: DiceResult;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=damage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"damage.d.ts","sourceRoot":"","sources":["../../src/resolution/damage.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,GAAG,EACR,KAAK,GAAE,YAAuC,GAC7C,YAAY,CA2Bd;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAMvC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Damage calculation
|
|
3
|
+
*/
|
|
4
|
+
import { rollDice } from "../dice/roller.js";
|
|
5
|
+
import { getDefaultRulesContext, getDamageConfig } from "../rules/context.js";
|
|
6
|
+
/**
|
|
7
|
+
* Calculate damage from an attack
|
|
8
|
+
*
|
|
9
|
+
* Default: Damage = weapon die + ability mod - armor (minimum 0)
|
|
10
|
+
* Respects rules context for addAbility, subtractArmor, minimumDamage
|
|
11
|
+
*/
|
|
12
|
+
export function calculateDamage(weapon, abilityMod, armor, rng, rules = getDefaultRulesContext()) {
|
|
13
|
+
const damageConfig = getDamageConfig(rules);
|
|
14
|
+
// Roll weapon damage
|
|
15
|
+
const roll = rollDice(weapon.damage, rng);
|
|
16
|
+
// Raw damage = roll + ability mod (if configured)
|
|
17
|
+
const abilityBonus = damageConfig.addAbility ? abilityMod : 0;
|
|
18
|
+
const rawDamage = roll.total + abilityBonus;
|
|
19
|
+
// Apply armor reduction (if configured)
|
|
20
|
+
let damage = rawDamage;
|
|
21
|
+
let armorReduction = 0;
|
|
22
|
+
if (damageConfig.subtractArmor) {
|
|
23
|
+
armorReduction = Math.min(armor, rawDamage);
|
|
24
|
+
damage = rawDamage - armorReduction;
|
|
25
|
+
}
|
|
26
|
+
// Apply minimum damage
|
|
27
|
+
damage = Math.max(damageConfig.minimumDamage, damage);
|
|
28
|
+
return {
|
|
29
|
+
damage,
|
|
30
|
+
roll,
|
|
31
|
+
rawDamage,
|
|
32
|
+
armorReduction,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Calculate healing amount
|
|
37
|
+
*/
|
|
38
|
+
export function calculateHealing(expression, rng, maxHp, currentHp) {
|
|
39
|
+
const roll = rollDice(expression, rng);
|
|
40
|
+
const potential = roll.total;
|
|
41
|
+
const healing = Math.min(potential, maxHp - currentHp);
|
|
42
|
+
return { healing, roll };
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=damage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"damage.js","sourceRoot":"","sources":["../../src/resolution/damage.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAS9E;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,UAAkB,EAClB,KAAa,EACb,GAAQ,EACR,QAAsB,sBAAsB,EAAE;IAE9C,MAAM,YAAY,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IAE5C,qBAAqB;IACrB,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE1C,kDAAkD;IAClD,MAAM,YAAY,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC;IAE5C,wCAAwC;IACxC,IAAI,MAAM,GAAG,SAAS,CAAC;IACvB,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,YAAY,CAAC,aAAa,EAAE,CAAC;QAC/B,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAC5C,MAAM,GAAG,SAAS,GAAG,cAAc,CAAC;IACtC,CAAC;IAED,uBAAuB;IACvB,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAEtD,OAAO;QACL,MAAM;QACN,IAAI;QACJ,SAAS;QACT,cAAc;KACf,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,UAAkB,EAClB,GAAQ,EACR,KAAa,EACb,SAAiB;IAEjB,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,GAAG,SAAS,CAAC,CAAC;IAEvD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolution exports
|
|
3
|
+
*/
|
|
4
|
+
export { resolveTest, type TestOptions } from "./test.js";
|
|
5
|
+
export { resolveAttack, type AttackOptions } from "./combat.js";
|
|
6
|
+
export { calculateDamage, calculateHealing, type DamageResult } from "./damage.js";
|
|
7
|
+
export { rollInitiative, rollInitiativeDetailed } from "./initiative.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resolution/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolution exports
|
|
3
|
+
*/
|
|
4
|
+
export { resolveTest } from "./test.js";
|
|
5
|
+
export { resolveAttack } from "./combat.js";
|
|
6
|
+
export { calculateDamage, calculateHealing } from "./damage.js";
|
|
7
|
+
export { rollInitiative, rollInitiativeDetailed } from "./initiative.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resolution/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAoB,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAqB,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initiative resolution
|
|
3
|
+
*/
|
|
4
|
+
import type { Character, Enemy, InitiativeResult } from "@mythxengine/types";
|
|
5
|
+
import type { RNG } from "../rng/rng.js";
|
|
6
|
+
type Combatant = Character | Enemy;
|
|
7
|
+
/**
|
|
8
|
+
* Roll initiative for all combatants
|
|
9
|
+
*
|
|
10
|
+
* Initiative = d20 + AGI
|
|
11
|
+
* Returns array of IDs in order (highest first)
|
|
12
|
+
*/
|
|
13
|
+
export declare function rollInitiative(combatants: readonly Combatant[], rng: RNG): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Get initiative results with full details
|
|
16
|
+
*/
|
|
17
|
+
export declare function rollInitiativeDetailed(combatants: readonly Combatant[], rng: RNG): InitiativeResult[];
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=initiative.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initiative.d.ts","sourceRoot":"","sources":["../../src/resolution/initiative.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,KAAK,SAAS,GAAG,SAAS,GAAG,KAAK,CAAC;AAEnC;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,GAAG,EAAE,GAAG,GACP,MAAM,EAAE,CA6BV;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,GAAG,EAAE,GAAG,GACP,gBAAgB,EAAE,CAmBpB"}
|