@swrpg-online/dice 1.0.5 → 1.2.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/README.md CHANGED
@@ -7,25 +7,71 @@
7
7
 
8
8
  A TypeScript library that creates dice rolls using the [narrative dice system](https://star-wars-rpg-ffg.fandom.com/wiki/Narrative_Dice) for the Star Wars Role-Playing Game by [Fantasy Flight Games](https://www.fantasyflightgames.com/en/starwarsrpg/) and [Edge Studio](https://www.edge-studio.net/categories-games/starwarsrpg/).
9
9
 
10
+ ## Features
11
+
12
+ - Complete narrative dice system implementation
13
+ - Detailed roll breakdown for each die
14
+ - Action hints to suggest possible uses for advantages, triumphs, etc.
15
+ - Roll results include:
16
+ - Successes / Failures
17
+ - Advantages / Threats
18
+ - Triumphs / Despairs
19
+ - Light / Dark Side Points (Force dice)
20
+ - Comprehensive Test Coverage
21
+ - The safety of TypeScript
22
+ - CLI Support
23
+
10
24
  ## Installation
11
25
 
26
+ ### As a CLI Tool
27
+
28
+ To use the dice roller from the command line:
29
+
30
+ ```bash
31
+ npm i -g @swrpg-online/dice
32
+ ```
33
+
34
+ ### As a project dependency
35
+
12
36
  ```bash
13
37
  npm i @swrpg-online/dice
14
38
  ```
15
39
 
16
- or optionally to use as a CLI command you can install globally with `npm i -g @swrpg-online/dice`
40
+ ## CLI Usage
17
41
 
18
- ## Features
42
+ ```
43
+ swrpg-dice <dice-notation> [options]
44
+ ```
19
45
 
20
- - Complete narrative dice system implementation
21
- - Detailed roll breakdown for each die
22
- - Comprehensive test coverage
23
- - TypeScript type safety
24
- - roll from a CLI
46
+ Example:
25
47
 
26
- ## Usage
48
+ ```
49
+ swrpg-dice 2y 1g 1p 1b 1sb --hints
50
+ ```
51
+
52
+ Output:
27
53
 
28
- via code:
54
+ ```
55
+ 1 Success(es), 4 Advantage(s), 1 Threat(s)
56
+
57
+ Possible actions:
58
+ • 1 Advantage or 1 Triumph - Recover one strain (may be applied more than once).
59
+ • 1 Advantage or 1 Triumph - Add a boost die to the next allied active character's check.
60
+ • 1 Advantage or 1 Triumph - Notice a single important point in the ongoing conflict, such as the location of a blast door's control panel or
61
+ ...
62
+ ```
63
+
64
+ Dice Options:
65
+
66
+ - y/pro = Yellow / Proficiency
67
+ - g/a = Green / Ability
68
+ - b/boo = Blue / Boost
69
+ - r/c = Red / Challenge
70
+ - p/diff = Purple / Difficulty
71
+ - blk/k/sb/s = Black / Setback
72
+ - w/f = White / Force
73
+
74
+ ## Programmatic Usage
29
75
 
30
76
  ```typescript
31
77
  import { roll, DicePool } from '@swrpg-online/dice';
@@ -39,9 +85,8 @@ const pool: DicePool = {
39
85
 
40
86
  const result = roll(pool);
41
87
 
42
- // Access detailed results
43
- console.log(result.results); // Array of individual die results
44
- console.log(result.summary); // Summary of total successes, advantages, etc.
88
+ console.log(result.results);
89
+ console.log(result.summary);
45
90
 
46
91
  => {
47
92
  "results": [
@@ -57,18 +102,6 @@ console.log(result.summary); // Summary of total successes, advantages, etc.
57
102
  "despair": 0
58
103
  }
59
104
  },
60
- {
61
- "type": "ability",
62
- "roll": 3,
63
- "result": {
64
- "successes": 1,
65
- "failures": 0,
66
- "advantages": 0,
67
- "threats": 0,
68
- "triumphs": 0,
69
- "despair": 0
70
- }
71
- },
72
105
  {
73
106
  "type": "proficiency",
74
107
  "roll": 10,
@@ -81,30 +114,7 @@ console.log(result.summary); // Summary of total successes, advantages, etc.
81
114
  "despair": 0
82
115
  }
83
116
  },
84
- {
85
- "type": "difficulty",
86
- "roll": 2,
87
- "result": {
88
- "successes": 0,
89
- "failures": 1,
90
- "advantages": 0,
91
- "threats": 0,
92
- "triumphs": 0,
93
- "despair": 0
94
- }
95
- },
96
- {
97
- "type": "challenge",
98
- "roll": 11,
99
- "result": {
100
- "successes": 0,
101
- "failures": 0,
102
- "advantages": 0,
103
- "threats": 2,
104
- "triumphs": 0,
105
- "despair": 0
106
- }
107
- }
117
+ ...
108
118
  ],
109
119
  "summary": {
110
120
  "successes": 0,
@@ -117,21 +127,9 @@ console.log(result.summary); // Summary of total successes, advantages, etc.
117
127
  }
118
128
  ```
119
129
 
120
- Each roll result includes:
121
-
122
- - Detailed breakdown of each die roll
123
- - Die type identification
124
- - Individual results per die
125
- - Overall summary of the roll
126
-
127
- # Roadmap or Under Review
130
+ # License
128
131
 
129
- - implement the Force die
130
- - implement ability to add success, failure, and so on to dice pools
131
- - ship combat?
132
- - crits?
133
- - polyhedral dice for convenience?
134
- - anything else?
132
+ This project is licensed under the MIT License.
135
133
 
136
134
  # Contribution
137
135
 
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { DicePool } from "./types";
2
+ import { DicePool, RollResult } from "./types";
3
3
  export declare function parseDiceNotation(input: string): DicePool;
4
- export declare function formatResult(result: any): string;
4
+ export declare const formatResult: (result: RollResult) => string;
5
5
  export declare const main: () => void;
package/dist/cli.js CHANGED
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.main = void 0;
4
+ exports.main = exports.formatResult = void 0;
5
5
  exports.parseDiceNotation = parseDiceNotation;
6
- exports.formatResult = formatResult;
7
6
  const dice_1 = require("./dice");
8
7
  // import * as path from 'path';
9
8
  function parseDiceNotation(input) {
@@ -14,67 +13,49 @@ function parseDiceNotation(input) {
14
13
  setBackDice: 0,
15
14
  difficultyDice: 0,
16
15
  challengeDice: 0,
16
+ forceDice: 0,
17
17
  };
18
- // function getImagePath(type: string): string {
19
- // const basePath = path.join(__dirname, 'images');
20
- // switch (type) {
21
- // case 'successes':
22
- // return path.join(basePath, 'success.svg'); // Adjust path and extension as needed
23
- // case 'failures':
24
- // return path.join(basePath, 'failure.svg');
25
- // case 'advantages':
26
- // return path.join(basePath, 'advantage.svg');
27
- // case 'threats':
28
- // return path.join(basePath, 'threat.svg');
29
- // case 'triumphs':
30
- // return path.join(basePath, 'triumph.svg');
31
- // case 'despair':
32
- // return path.join(basePath, 'despair.svg');
33
- // default:
34
- // return '';
35
- // }
36
- // }
37
18
  const parts = input.toLowerCase().trim().split(" ");
38
19
  for (const part of parts) {
39
20
  const count = parseInt(part);
40
- const color = part.slice(String(count).length);
21
+ const color = part.slice(String(count).length).toLowerCase();
41
22
  switch (color) {
42
- // y/pro = Yellow/Proficiency
23
+ // y/pro = Yellow / Proficiency
43
24
  case "y":
44
25
  pool.proficiencyDice = count;
45
26
  break;
46
27
  case "pro":
47
28
  pool.proficiencyDice = count;
48
29
  break;
49
- // g/a = Green/Ability
30
+ // g/a = Green / Ability
50
31
  case "g":
51
32
  pool.abilityDice = count;
52
33
  break;
53
34
  case "a":
54
35
  pool.abilityDice = count;
55
36
  break;
56
- // b/boo = Blue/Boost
37
+ // b/boo = Blue / Boost
57
38
  case "b":
58
39
  pool.boostDice = count;
59
40
  break;
60
41
  case "boo":
61
42
  pool.boostDice = count;
62
43
  break;
63
- // r/c = Red/ Challenge
44
+ // r/c = Red / Challenge
64
45
  case "r":
65
46
  pool.challengeDice = count;
66
47
  break;
67
48
  case "c":
68
49
  pool.challengeDice = count;
69
50
  break;
70
- // p/diff = Purple/ Difficulty
51
+ // p/diff = Purple / Difficulty
71
52
  case "p":
72
53
  pool.difficultyDice = count;
73
54
  break;
74
55
  case "diff":
75
56
  pool.difficultyDice = count;
76
57
  break;
77
- // blk/k/sb/s = Black/Setback
58
+ // blk/k/sb/s = Black / Setback
78
59
  case "blk":
79
60
  pool.setBackDice = count;
80
61
  break;
@@ -87,44 +68,63 @@ function parseDiceNotation(input) {
87
68
  case "s":
88
69
  pool.setBackDice = count;
89
70
  break;
90
- // w/f = White/Force
91
- // TODO
71
+ // w/f = White / Force
72
+ case "w":
73
+ pool.forceDice = count;
74
+ break;
75
+ case "f":
76
+ pool.forceDice = count;
77
+ break;
92
78
  }
93
79
  }
94
80
  return pool;
95
81
  }
96
- function formatResult(result) {
97
- const parts = [];
82
+ const formatResult = (result) => {
83
+ const effects = [];
98
84
  if (result.summary.successes > 0)
99
- parts.push(`${result.summary.successes} Success(es)`);
85
+ effects.push(`${result.summary.successes} Success(es)`);
100
86
  if (result.summary.failures > 0)
101
- parts.push(`${result.summary.failures} Failure(s)`);
87
+ effects.push(`${result.summary.failures} Failure(s)`);
102
88
  if (result.summary.advantages > 0)
103
- parts.push(`${result.summary.advantages} Advantage(s)`);
89
+ effects.push(`${result.summary.advantages} Advantage(s)`);
104
90
  if (result.summary.threats > 0)
105
- parts.push(`${result.summary.threats} Threat(s)`);
91
+ effects.push(`${result.summary.threats} Threat(s)`);
106
92
  if (result.summary.triumphs > 0)
107
- parts.push(`${result.summary.triumphs} Triumph(s)`);
93
+ effects.push(`${result.summary.triumphs} Triumph(s)`);
108
94
  if (result.summary.despair > 0)
109
- parts.push(`${result.summary.despair} Despair(s)`);
110
- return parts.join(", ") || "No effects";
111
- }
95
+ effects.push(`${result.summary.despair} Despair(s)`);
96
+ const resultText = effects.length > 0 ? effects.join(", ") : "No effects";
97
+ if (result.summary.hints && result.summary.hints.length > 0) {
98
+ return `${resultText}\n\nPossible actions:\n${result.summary.hints.map((hint) => " • " + hint).join("\n")}`;
99
+ }
100
+ return resultText;
101
+ };
102
+ exports.formatResult = formatResult;
112
103
  const main = () => {
113
- const input = process.argv.slice(2).join(" ");
114
- if (!input) {
115
- console.log(`Usage: > swrpg-dice 2y 1g 2p 1r
104
+ const args = process.argv.slice(2);
105
+ const hintsIndex = args.indexOf("--hints");
106
+ const showHints = hintsIndex !== -1;
107
+ const diceNotation = hintsIndex !== -1
108
+ ? args.filter((_, index) => index !== hintsIndex).join(" ")
109
+ : args.join(" ");
110
+ if (!diceNotation.trim()) {
111
+ console.log(`Usage: swrpg-dice <dice-notation> <dice-options>
112
+ Example: swrpg-dice 2y 1g 1p 1b 1sb --hints
113
+ Dice Options:
116
114
  - y/pro = Yellow / Proficiency
117
115
  - g/a = Green / Ability
118
116
  - b/boo = Blue / Boost
119
117
  - r/c = Red / Challenge
120
118
  - p/diff = Purple / Difficulty
121
119
  - blk/k/sb/s = Black / Setback
122
- `);
120
+ - w/f = White/Force
121
+ Options:
122
+ --hints Show possible actions based on roll results`);
123
123
  process.exit(1);
124
124
  }
125
- const pool = parseDiceNotation(input);
126
- const result = (0, dice_1.roll)(pool);
127
- console.log(formatResult(result));
125
+ const pool = parseDiceNotation(diceNotation);
126
+ const result = (0, dice_1.roll)(pool, { hints: showHints });
127
+ console.log((0, exports.formatResult)(result));
128
128
  };
129
129
  exports.main = main;
130
130
  if (require.main === module) {
package/dist/dice.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { DicePool, RollResult } from "./types";
2
- export declare const roll: (pool: DicePool) => RollResult;
1
+ import { DicePool, RollResult, RollOptions } from "./types";
2
+ export declare const roll: (pool: DicePool, options?: RollOptions) => RollResult;
package/dist/dice.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.roll = void 0;
4
+ const hints_1 = require("./hints");
4
5
  const rollDie = (sides) => Math.floor(Math.random() * sides) + 1;
5
6
  const boostDieResult = (roll) => {
6
7
  switch (roll) {
@@ -12,6 +13,8 @@ const boostDieResult = (roll) => {
12
13
  threats: 0,
13
14
  triumphs: 0,
14
15
  despair: 0,
16
+ lightSide: 0,
17
+ darkSide: 0,
15
18
  };
16
19
  case 4:
17
20
  return {
@@ -21,6 +24,8 @@ const boostDieResult = (roll) => {
21
24
  threats: 0,
22
25
  triumphs: 0,
23
26
  despair: 0,
27
+ lightSide: 0,
28
+ darkSide: 0,
24
29
  };
25
30
  case 5:
26
31
  return {
@@ -30,6 +35,8 @@ const boostDieResult = (roll) => {
30
35
  threats: 0,
31
36
  triumphs: 0,
32
37
  despair: 0,
38
+ lightSide: 0,
39
+ darkSide: 0,
33
40
  };
34
41
  case 6:
35
42
  return {
@@ -39,6 +46,8 @@ const boostDieResult = (roll) => {
39
46
  threats: 0,
40
47
  triumphs: 0,
41
48
  despair: 0,
49
+ lightSide: 0,
50
+ darkSide: 0,
42
51
  };
43
52
  default:
44
53
  return {
@@ -48,6 +57,8 @@ const boostDieResult = (roll) => {
48
57
  threats: 0,
49
58
  triumphs: 0,
50
59
  despair: 0,
60
+ lightSide: 0,
61
+ darkSide: 0,
51
62
  };
52
63
  }
53
64
  };
@@ -62,6 +73,8 @@ const setBackDieResult = (roll) => {
62
73
  threats: 0,
63
74
  triumphs: 0,
64
75
  despair: 0,
76
+ lightSide: 0,
77
+ darkSide: 0,
65
78
  };
66
79
  case 5:
67
80
  case 6:
@@ -72,6 +85,8 @@ const setBackDieResult = (roll) => {
72
85
  threats: 1,
73
86
  triumphs: 0,
74
87
  despair: 0,
88
+ lightSide: 0,
89
+ darkSide: 0,
75
90
  };
76
91
  default:
77
92
  return {
@@ -81,6 +96,8 @@ const setBackDieResult = (roll) => {
81
96
  threats: 0,
82
97
  triumphs: 0,
83
98
  despair: 0,
99
+ lightSide: 0,
100
+ darkSide: 0,
84
101
  };
85
102
  }
86
103
  };
@@ -95,6 +112,8 @@ const abilityDieResult = (roll) => {
95
112
  threats: 0,
96
113
  triumphs: 0,
97
114
  despair: 0,
115
+ lightSide: 0,
116
+ darkSide: 0,
98
117
  };
99
118
  case 4:
100
119
  return {
@@ -104,6 +123,8 @@ const abilityDieResult = (roll) => {
104
123
  threats: 0,
105
124
  triumphs: 0,
106
125
  despair: 0,
126
+ lightSide: 0,
127
+ darkSide: 0,
107
128
  };
108
129
  case 5:
109
130
  case 6:
@@ -114,6 +135,8 @@ const abilityDieResult = (roll) => {
114
135
  threats: 0,
115
136
  triumphs: 0,
116
137
  despair: 0,
138
+ lightSide: 0,
139
+ darkSide: 0,
117
140
  };
118
141
  case 7:
119
142
  return {
@@ -123,6 +146,8 @@ const abilityDieResult = (roll) => {
123
146
  threats: 0,
124
147
  triumphs: 0,
125
148
  despair: 0,
149
+ lightSide: 0,
150
+ darkSide: 0,
126
151
  };
127
152
  case 8:
128
153
  return {
@@ -132,6 +157,8 @@ const abilityDieResult = (roll) => {
132
157
  threats: 0,
133
158
  triumphs: 0,
134
159
  despair: 0,
160
+ lightSide: 0,
161
+ darkSide: 0,
135
162
  };
136
163
  default:
137
164
  return {
@@ -141,6 +168,8 @@ const abilityDieResult = (roll) => {
141
168
  threats: 0,
142
169
  triumphs: 0,
143
170
  despair: 0,
171
+ lightSide: 0,
172
+ darkSide: 0,
144
173
  };
145
174
  }
146
175
  };
@@ -154,6 +183,8 @@ const difficultyDieResult = (roll) => {
154
183
  threats: 0,
155
184
  triumphs: 0,
156
185
  despair: 0,
186
+ lightSide: 0,
187
+ darkSide: 0,
157
188
  };
158
189
  case 3:
159
190
  return {
@@ -163,6 +194,8 @@ const difficultyDieResult = (roll) => {
163
194
  threats: 0,
164
195
  triumphs: 0,
165
196
  despair: 0,
197
+ lightSide: 0,
198
+ darkSide: 0,
166
199
  };
167
200
  case 4:
168
201
  case 5:
@@ -174,6 +207,8 @@ const difficultyDieResult = (roll) => {
174
207
  threats: 1,
175
208
  triumphs: 0,
176
209
  despair: 0,
210
+ lightSide: 0,
211
+ darkSide: 0,
177
212
  };
178
213
  case 7:
179
214
  return {
@@ -183,6 +218,8 @@ const difficultyDieResult = (roll) => {
183
218
  threats: 2,
184
219
  triumphs: 0,
185
220
  despair: 0,
221
+ lightSide: 0,
222
+ darkSide: 0,
186
223
  };
187
224
  case 8:
188
225
  return {
@@ -192,6 +229,8 @@ const difficultyDieResult = (roll) => {
192
229
  threats: 1,
193
230
  triumphs: 0,
194
231
  despair: 0,
232
+ lightSide: 0,
233
+ darkSide: 0,
195
234
  };
196
235
  default:
197
236
  return {
@@ -201,6 +240,8 @@ const difficultyDieResult = (roll) => {
201
240
  threats: 0,
202
241
  triumphs: 0,
203
242
  despair: 0,
243
+ lightSide: 0,
244
+ darkSide: 0,
204
245
  };
205
246
  }
206
247
  };
@@ -215,6 +256,8 @@ const proficiencyDieResult = (roll) => {
215
256
  threats: 0,
216
257
  triumphs: 0,
217
258
  despair: 0,
259
+ lightSide: 0,
260
+ darkSide: 0,
218
261
  };
219
262
  case 4:
220
263
  case 5:
@@ -225,6 +268,8 @@ const proficiencyDieResult = (roll) => {
225
268
  threats: 0,
226
269
  triumphs: 0,
227
270
  despair: 0,
271
+ lightSide: 0,
272
+ darkSide: 0,
228
273
  };
229
274
  case 6:
230
275
  return {
@@ -234,6 +279,8 @@ const proficiencyDieResult = (roll) => {
234
279
  threats: 0,
235
280
  triumphs: 0,
236
281
  despair: 0,
282
+ lightSide: 0,
283
+ darkSide: 0,
237
284
  };
238
285
  case 7:
239
286
  case 8:
@@ -245,6 +292,8 @@ const proficiencyDieResult = (roll) => {
245
292
  threats: 0,
246
293
  triumphs: 0,
247
294
  despair: 0,
295
+ lightSide: 0,
296
+ darkSide: 0,
248
297
  };
249
298
  case 10:
250
299
  case 11:
@@ -255,6 +304,8 @@ const proficiencyDieResult = (roll) => {
255
304
  threats: 0,
256
305
  triumphs: 0,
257
306
  despair: 0,
307
+ lightSide: 0,
308
+ darkSide: 0,
258
309
  };
259
310
  case 12:
260
311
  return {
@@ -264,6 +315,8 @@ const proficiencyDieResult = (roll) => {
264
315
  threats: 0,
265
316
  triumphs: 1,
266
317
  despair: 0,
318
+ lightSide: 0,
319
+ darkSide: 0,
267
320
  };
268
321
  default:
269
322
  return {
@@ -273,6 +326,8 @@ const proficiencyDieResult = (roll) => {
273
326
  threats: 0,
274
327
  triumphs: 0,
275
328
  despair: 0,
329
+ lightSide: 0,
330
+ darkSide: 0,
276
331
  };
277
332
  }
278
333
  };
@@ -287,6 +342,8 @@ const challengeDieResult = (roll) => {
287
342
  threats: 0,
288
343
  triumphs: 0,
289
344
  despair: 0,
345
+ lightSide: 0,
346
+ darkSide: 0,
290
347
  };
291
348
  case 4:
292
349
  case 5:
@@ -297,6 +354,8 @@ const challengeDieResult = (roll) => {
297
354
  threats: 0,
298
355
  triumphs: 0,
299
356
  despair: 0,
357
+ lightSide: 0,
358
+ darkSide: 0,
300
359
  };
301
360
  case 6:
302
361
  case 7:
@@ -307,6 +366,8 @@ const challengeDieResult = (roll) => {
307
366
  threats: 1,
308
367
  triumphs: 0,
309
368
  despair: 0,
369
+ lightSide: 0,
370
+ darkSide: 0,
310
371
  };
311
372
  case 8:
312
373
  case 9:
@@ -317,6 +378,8 @@ const challengeDieResult = (roll) => {
317
378
  threats: 1,
318
379
  triumphs: 0,
319
380
  despair: 0,
381
+ lightSide: 0,
382
+ darkSide: 0,
320
383
  };
321
384
  case 10:
322
385
  case 11:
@@ -327,6 +390,8 @@ const challengeDieResult = (roll) => {
327
390
  threats: 2,
328
391
  triumphs: 0,
329
392
  despair: 0,
393
+ lightSide: 0,
394
+ darkSide: 0,
330
395
  };
331
396
  case 12:
332
397
  return {
@@ -336,6 +401,8 @@ const challengeDieResult = (roll) => {
336
401
  threats: 0,
337
402
  triumphs: 0,
338
403
  despair: 1,
404
+ lightSide: 0,
405
+ darkSide: 0,
339
406
  };
340
407
  default:
341
408
  return {
@@ -345,10 +412,79 @@ const challengeDieResult = (roll) => {
345
412
  threats: 0,
346
413
  triumphs: 0,
347
414
  despair: 0,
415
+ lightSide: 0,
416
+ darkSide: 0,
348
417
  };
349
418
  }
350
419
  };
351
- const sumResults = (results) => {
420
+ const forceDieResult = (roll) => {
421
+ switch (roll) {
422
+ case 1:
423
+ case 2:
424
+ case 3:
425
+ case 4:
426
+ case 5:
427
+ return {
428
+ successes: 0,
429
+ failures: 0,
430
+ advantages: 0,
431
+ threats: 0,
432
+ triumphs: 0,
433
+ despair: 0,
434
+ lightSide: 1,
435
+ darkSide: 0,
436
+ };
437
+ case 6:
438
+ case 7:
439
+ return {
440
+ successes: 0,
441
+ failures: 0,
442
+ advantages: 0,
443
+ threats: 0,
444
+ triumphs: 0,
445
+ despair: 0,
446
+ lightSide: 2,
447
+ darkSide: 0,
448
+ };
449
+ case 8:
450
+ case 9:
451
+ case 10:
452
+ case 11:
453
+ return {
454
+ successes: 0,
455
+ failures: 0,
456
+ advantages: 0,
457
+ threats: 0,
458
+ triumphs: 0,
459
+ despair: 0,
460
+ lightSide: 0,
461
+ darkSide: 1,
462
+ };
463
+ case 12:
464
+ return {
465
+ successes: 0,
466
+ failures: 0,
467
+ advantages: 0,
468
+ threats: 0,
469
+ triumphs: 0,
470
+ despair: 0,
471
+ lightSide: 0,
472
+ darkSide: 2,
473
+ };
474
+ default:
475
+ return {
476
+ successes: 0,
477
+ failures: 0,
478
+ advantages: 0,
479
+ threats: 0,
480
+ triumphs: 0,
481
+ despair: 0,
482
+ lightSide: 0,
483
+ darkSide: 0,
484
+ };
485
+ }
486
+ };
487
+ const sumResults = (results, options) => {
352
488
  const sums = results.reduce((acc, curr) => ({
353
489
  successes: acc.successes + curr.successes,
354
490
  failures: acc.failures + curr.failures,
@@ -356,6 +492,8 @@ const sumResults = (results) => {
356
492
  threats: acc.threats + curr.threats,
357
493
  triumphs: acc.triumphs + curr.triumphs,
358
494
  despair: acc.despair + curr.despair,
495
+ lightSide: acc.lightSide + (curr.lightSide || 0),
496
+ darkSide: acc.darkSide + (curr.darkSide || 0),
359
497
  }), {
360
498
  successes: 0,
361
499
  failures: 0,
@@ -363,8 +501,9 @@ const sumResults = (results) => {
363
501
  threats: 0,
364
502
  triumphs: 0,
365
503
  despair: 0,
504
+ lightSide: 0,
505
+ darkSide: 0,
366
506
  });
367
- // Calculate net successes/failures
368
507
  let netSuccesses = 0;
369
508
  let netFailures = 0;
370
509
  if (sums.successes === sums.failures) {
@@ -377,24 +516,27 @@ const sumResults = (results) => {
377
516
  else {
378
517
  netFailures = sums.failures - sums.successes;
379
518
  }
380
- return {
519
+ const result = {
381
520
  successes: netSuccesses,
382
521
  failures: netFailures,
383
522
  advantages: sums.advantages,
384
523
  threats: sums.threats,
385
524
  triumphs: sums.triumphs,
386
525
  despair: sums.despair,
526
+ lightSide: sums.lightSide,
527
+ darkSide: sums.darkSide,
387
528
  };
529
+ return result;
388
530
  };
389
- const roll = (pool) => {
390
- var _a, _b, _c, _d, _e, _f;
391
- // Initialize all dice counts to 0 if undefined
531
+ const roll = (pool, options) => {
532
+ var _a, _b, _c, _d, _e, _f, _g;
392
533
  const boostCount = (_a = pool.boostDice) !== null && _a !== void 0 ? _a : 0;
393
534
  const abilityCount = (_b = pool.abilityDice) !== null && _b !== void 0 ? _b : 0;
394
535
  const proficiencyCount = (_c = pool.proficiencyDice) !== null && _c !== void 0 ? _c : 0;
395
536
  const setBackCount = (_d = pool.setBackDice) !== null && _d !== void 0 ? _d : 0;
396
537
  const difficultyCount = (_e = pool.difficultyDice) !== null && _e !== void 0 ? _e : 0;
397
538
  const challengeCount = (_f = pool.challengeDice) !== null && _f !== void 0 ? _f : 0;
539
+ const forceCount = (_g = pool.forceDice) !== null && _g !== void 0 ? _g : 0;
398
540
  // Ensure all dice counts are non-negative
399
541
  const sanitizedPool = {
400
542
  boostDice: Math.max(0, boostCount),
@@ -403,6 +545,7 @@ const roll = (pool) => {
403
545
  setBackDice: Math.max(0, setBackCount),
404
546
  difficultyDice: Math.max(0, difficultyCount),
405
547
  challengeDice: Math.max(0, challengeCount),
548
+ forceDice: Math.max(0, forceCount),
406
549
  };
407
550
  const detailedResults = [];
408
551
  // Roll boost dice
@@ -459,9 +602,32 @@ const roll = (pool) => {
459
602
  result: challengeDieResult(roll),
460
603
  });
461
604
  }
605
+ // Roll force dice
606
+ for (let i = 0; i < sanitizedPool.forceDice; i++) {
607
+ const roll = rollDie(12);
608
+ detailedResults.push({
609
+ type: "force",
610
+ roll,
611
+ result: forceDieResult(roll),
612
+ });
613
+ }
614
+ const summary = sumResults(detailedResults.map((r) => r.result));
615
+ if (options === null || options === void 0 ? void 0 : options.hints) {
616
+ const applicableHints = hints_1.hints.filter((hint) => {
617
+ const { cost } = hint;
618
+ return Object.entries(cost).some(([symbol, required]) => {
619
+ const summaryKey = (symbol.toLowerCase() + "s");
620
+ const value = summary[summaryKey];
621
+ if (typeof value !== "number")
622
+ return false;
623
+ return required <= value;
624
+ });
625
+ });
626
+ summary.hints = applicableHints.map((hint) => `${(0, hints_1.hintCostDisplayText)(hint)} - ${hint.description}`);
627
+ }
462
628
  return {
463
629
  results: detailedResults,
464
- summary: sumResults(detailedResults.map((r) => r.result)),
630
+ summary: summary,
465
631
  };
466
632
  };
467
633
  exports.roll = roll;
@@ -0,0 +1,11 @@
1
+ import { type Symbol } from "./types";
2
+ export type CostType = {
3
+ [key in Symbol]?: number;
4
+ };
5
+ type Hint = {
6
+ description: string;
7
+ cost: CostType;
8
+ };
9
+ export declare const hints: Hint[];
10
+ export declare function hintCostDisplayText(hint: Hint): string;
11
+ export {};
package/dist/hints.js ADDED
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hints = void 0;
4
+ exports.hintCostDisplayText = hintCostDisplayText;
5
+ const types_1 = require("./types");
6
+ // 1 advantage or 1 triumph
7
+ const recoverOneStrain = "Recover one strain (may be applied more than once).";
8
+ const addBoostDieToActiveAlly = "Add a boost die to the next allied active character's check.";
9
+ const noticeImportantPoint = "Notice a single important point in the ongoing conflict, such as the location of a blast door's control panel or a weak point on an attack speeder.";
10
+ const inflictCriticalInjury = "Inflict a Critical Injury with a successful attack that deals damage past soak (Advantage cost may vary).";
11
+ const activateWeaponQuality = "Activate a weapon quality (Advantage cost may vary).";
12
+ // 2 advantage or 1 triumph
13
+ const performManeuver = "Perform an immediate free maneuver that does not exceed the two maneuver per turn limit.";
14
+ const addSetbackDie = "Add a setback die to the targeted character's next check.";
15
+ const addBoostDieToAnyAlly = "Add a boost die to any allied character's next check, including that of the active character.";
16
+ // 3 advantage or 1 triumph
17
+ const negateEnemy = "Negate the targeted enemy's defensive bonuses (such as the defense gained from cover, equipment, or performing the Guarded Stance maneuver) util the end of the current round.";
18
+ const ignoreEnvironment = "Ignore penalizing environmental effects such as inclement weather, zero gravity, or similar circumstances until the end of the active character's next turn.";
19
+ const disableOpponent = "When dealing damage to a target, have the attack disable the opponent or one piece of gear rather than dealing wounds or strain. This could include hobbling them temporarily with a shot to the leg, or disabling their comlink. This should be agreed upon by the player and the GM, and the effects are up to the GM (although Table 6-10: Critical Injury Result is a god resource to consult for possible effects). The effects should be temporary and not too excessive.";
20
+ const gainDefense = "Gain + 1 melee or ranged defense until the end of the active character's next turn.";
21
+ const dropWeapon = "Force the target to drop a melee or ranged weapon they are wielding.";
22
+ // 1 triumph
23
+ const upgradeDifficultyTargetedCharacter = "Upgrade the difficulty of the targeted character's next check.";
24
+ const doSomethingVital = "Do something vital, such as shooting the controls to the nearby blast doors to seal them shut.";
25
+ const upgradeAnyAllyCheck = "Upgrade any allied character's next check, including that of the current active character.";
26
+ // 2 triumph
27
+ const destroyEquipment = "When dealing damage to a target, have the attack destroy a piece of equipment the target is using, such as blowing up his blaster or destroying a personal shield generator.";
28
+ // 1 threat or 1 despair
29
+ const sufferStrain = "The active character suffers 1 strain.";
30
+ const loseManeuverBenefit = "The active character loses the benefits of a prior maneuver (such as from taking cover or assuming a Guarded Stance) until they perform the maneuver again.";
31
+ // 2 threat or 1 despair
32
+ const freeManeuver = "An opponent may immediately perform one free maneuver in response to the active character's check.";
33
+ const addBoostDieToTargetedCharacter = "Add a boost die to the targeted character's next check.";
34
+ const sufferSetback = "The active character or an allied character suffers a setback die on their next action.";
35
+ // 3 threat or 1 despair
36
+ const fallProne = "The active character falls prone.";
37
+ const gainSignificantAdvantage = "The active character grants the enemy a significant advantage in the ongoing encounter, such as accidentally blasting the controls to a bridge the active character was planning to use for their escape.";
38
+ // 1 despair
39
+ const outOfAmmo = "The character's ranged weapon imediately runs out of ammunition and may not be used for the remainder of the encounter.";
40
+ const upgradeDifficultyAlliedCharacter = "Upgrade the difficulty of an allied character's next check, including that of the current active character.";
41
+ const damagedItem = "The tool or melee weapon the character is using becomes damaged.";
42
+ exports.hints = [
43
+ // 1 advantage or 1 triumph
44
+ {
45
+ description: recoverOneStrain,
46
+ cost: {
47
+ [types_1.SYMBOLS.ADVANTAGE]: 1,
48
+ [types_1.SYMBOLS.TRIUMPH]: 1,
49
+ },
50
+ },
51
+ {
52
+ description: addBoostDieToActiveAlly,
53
+ cost: {
54
+ [types_1.SYMBOLS.ADVANTAGE]: 1,
55
+ [types_1.SYMBOLS.TRIUMPH]: 1,
56
+ },
57
+ },
58
+ {
59
+ description: noticeImportantPoint,
60
+ cost: {
61
+ [types_1.SYMBOLS.ADVANTAGE]: 1,
62
+ [types_1.SYMBOLS.TRIUMPH]: 1,
63
+ },
64
+ },
65
+ {
66
+ description: inflictCriticalInjury,
67
+ cost: {
68
+ [types_1.SYMBOLS.ADVANTAGE]: 1,
69
+ [types_1.SYMBOLS.TRIUMPH]: 1,
70
+ },
71
+ },
72
+ {
73
+ description: activateWeaponQuality,
74
+ cost: {
75
+ [types_1.SYMBOLS.ADVANTAGE]: 1,
76
+ [types_1.SYMBOLS.TRIUMPH]: 1,
77
+ },
78
+ },
79
+ // 2 advantage or 1 triumph
80
+ {
81
+ description: performManeuver,
82
+ cost: {
83
+ [types_1.SYMBOLS.ADVANTAGE]: 2,
84
+ [types_1.SYMBOLS.TRIUMPH]: 1,
85
+ },
86
+ },
87
+ {
88
+ description: addSetbackDie,
89
+ cost: {
90
+ [types_1.SYMBOLS.ADVANTAGE]: 2,
91
+ [types_1.SYMBOLS.TRIUMPH]: 1,
92
+ },
93
+ },
94
+ {
95
+ description: addBoostDieToAnyAlly,
96
+ cost: {
97
+ [types_1.SYMBOLS.ADVANTAGE]: 2,
98
+ [types_1.SYMBOLS.TRIUMPH]: 1,
99
+ },
100
+ },
101
+ // 3 advantage or 1 triumph
102
+ {
103
+ description: negateEnemy,
104
+ cost: {
105
+ [types_1.SYMBOLS.ADVANTAGE]: 3,
106
+ [types_1.SYMBOLS.TRIUMPH]: 1,
107
+ },
108
+ },
109
+ {
110
+ description: ignoreEnvironment,
111
+ cost: {
112
+ [types_1.SYMBOLS.ADVANTAGE]: 3,
113
+ [types_1.SYMBOLS.TRIUMPH]: 1,
114
+ },
115
+ },
116
+ {
117
+ description: disableOpponent,
118
+ cost: {
119
+ [types_1.SYMBOLS.ADVANTAGE]: 3,
120
+ [types_1.SYMBOLS.TRIUMPH]: 1,
121
+ },
122
+ },
123
+ {
124
+ description: gainDefense,
125
+ cost: {
126
+ [types_1.SYMBOLS.ADVANTAGE]: 3,
127
+ [types_1.SYMBOLS.TRIUMPH]: 1,
128
+ },
129
+ },
130
+ {
131
+ description: dropWeapon,
132
+ cost: {
133
+ [types_1.SYMBOLS.ADVANTAGE]: 3,
134
+ [types_1.SYMBOLS.TRIUMPH]: 1,
135
+ },
136
+ },
137
+ // 1 triumph
138
+ {
139
+ description: upgradeDifficultyTargetedCharacter,
140
+ cost: {
141
+ [types_1.SYMBOLS.TRIUMPH]: 1,
142
+ },
143
+ },
144
+ {
145
+ description: doSomethingVital,
146
+ cost: {
147
+ [types_1.SYMBOLS.TRIUMPH]: 1,
148
+ },
149
+ },
150
+ {
151
+ description: upgradeAnyAllyCheck,
152
+ cost: {
153
+ [types_1.SYMBOLS.TRIUMPH]: 1,
154
+ },
155
+ },
156
+ // 2 triumph
157
+ {
158
+ description: destroyEquipment,
159
+ cost: {
160
+ [types_1.SYMBOLS.TRIUMPH]: 2,
161
+ },
162
+ },
163
+ // 1 threat or 1 despair
164
+ {
165
+ description: sufferStrain,
166
+ cost: {
167
+ [types_1.SYMBOLS.THREAT]: 1,
168
+ [types_1.SYMBOLS.DESPAIR]: 1,
169
+ },
170
+ },
171
+ {
172
+ description: loseManeuverBenefit,
173
+ cost: {
174
+ [types_1.SYMBOLS.THREAT]: 1,
175
+ [types_1.SYMBOLS.DESPAIR]: 1,
176
+ },
177
+ },
178
+ // 2 threat or 1 despair
179
+ {
180
+ description: freeManeuver,
181
+ cost: {
182
+ [types_1.SYMBOLS.THREAT]: 2,
183
+ [types_1.SYMBOLS.DESPAIR]: 1,
184
+ },
185
+ },
186
+ {
187
+ description: addBoostDieToTargetedCharacter,
188
+ cost: {
189
+ [types_1.SYMBOLS.THREAT]: 1,
190
+ [types_1.SYMBOLS.DESPAIR]: 1,
191
+ },
192
+ },
193
+ {
194
+ description: sufferSetback,
195
+ cost: {
196
+ [types_1.SYMBOLS.THREAT]: 2,
197
+ [types_1.SYMBOLS.DESPAIR]: 1,
198
+ },
199
+ },
200
+ // 3 threat or 1 despair
201
+ {
202
+ description: fallProne,
203
+ cost: {
204
+ [types_1.SYMBOLS.THREAT]: 3,
205
+ [types_1.SYMBOLS.DESPAIR]: 1,
206
+ },
207
+ },
208
+ {
209
+ description: gainSignificantAdvantage,
210
+ cost: {
211
+ [types_1.SYMBOLS.THREAT]: 3,
212
+ [types_1.SYMBOLS.DESPAIR]: 1,
213
+ },
214
+ },
215
+ // 1 despair
216
+ {
217
+ description: outOfAmmo,
218
+ cost: {
219
+ [types_1.SYMBOLS.DESPAIR]: 1,
220
+ },
221
+ },
222
+ {
223
+ description: upgradeDifficultyAlliedCharacter,
224
+ cost: {
225
+ [types_1.SYMBOLS.DESPAIR]: 1,
226
+ },
227
+ },
228
+ {
229
+ description: damagedItem,
230
+ cost: {
231
+ [types_1.SYMBOLS.DESPAIR]: 1,
232
+ },
233
+ },
234
+ ];
235
+ function hintCostDisplayText(hint) {
236
+ if (!hint.cost || Object.keys(hint.cost).length === 0) {
237
+ return "No cost";
238
+ }
239
+ const parts = Object.entries(hint.cost)
240
+ .filter(([_, count]) => count && count > 0)
241
+ .map(([symbol, count]) => {
242
+ const symbolName = symbol.charAt(0) + symbol.toLowerCase().slice(1);
243
+ return `${count} ${symbolName}${count > 1 ? "(s)" : ""}`;
244
+ });
245
+ return parts.length > 0 ? parts.join(" or ") : "No cost";
246
+ }
package/dist/types.d.ts CHANGED
@@ -5,6 +5,7 @@ export type DicePool = {
5
5
  setBackDice?: number;
6
6
  difficultyDice?: number;
7
7
  challengeDice?: number;
8
+ forceDice?: number;
8
9
  };
9
10
  export type DiceResult = {
10
11
  successes: number;
@@ -13,8 +14,11 @@ export type DiceResult = {
13
14
  threats: number;
14
15
  triumphs: number;
15
16
  despair: number;
17
+ lightSide: number;
18
+ darkSide: number;
19
+ hints?: string[];
16
20
  };
17
- export type DieType = "boost" | "ability" | "proficiency" | "setback" | "difficulty" | "challenge";
21
+ export type DieType = "boost" | "ability" | "proficiency" | "setback" | "difficulty" | "challenge" | "force";
18
22
  export type DetailedDieResult = {
19
23
  type: DieType;
20
24
  roll: number;
@@ -24,3 +28,17 @@ export type RollResult = {
24
28
  results: DetailedDieResult[];
25
29
  summary: DiceResult;
26
30
  };
31
+ export declare const SYMBOLS: {
32
+ readonly SUCCESS: "SUCCESS";
33
+ readonly FAILURE: "FAILURE";
34
+ readonly ADVANTAGE: "ADVANTAGE";
35
+ readonly THREAT: "THREAT";
36
+ readonly TRIUMPH: "TRIUMPH";
37
+ readonly DESPAIR: "DESPAIR";
38
+ readonly LIGHT: "LIGHT";
39
+ readonly DARK: "DARK";
40
+ };
41
+ export type Symbol = keyof typeof SYMBOLS;
42
+ export type RollOptions = {
43
+ hints?: boolean;
44
+ };
package/dist/types.js CHANGED
@@ -1,2 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SYMBOLS = void 0;
4
+ exports.SYMBOLS = {
5
+ SUCCESS: "SUCCESS",
6
+ FAILURE: "FAILURE",
7
+ ADVANTAGE: "ADVANTAGE",
8
+ THREAT: "THREAT",
9
+ TRIUMPH: "TRIUMPH",
10
+ DESPAIR: "DESPAIR",
11
+ LIGHT: "LIGHT",
12
+ DARK: "DARK",
13
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swrpg-online/dice",
3
- "version": "1.0.5",
3
+ "version": "1.2.0",
4
4
  "description": "A TypeScript library for simulating Star Wars RPG narrative dice rolls.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {