@swrpg-online/dice 0.0.0-development

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/dist/dice.js ADDED
@@ -0,0 +1,695 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.roll = exports.DEFAULT_MAX_TOTAL_DICE = exports.DEFAULT_MAX_DICE_PER_TYPE = void 0;
4
+ const hints_1 = require("./hints");
5
+ // Default dice limits for performance and security
6
+ exports.DEFAULT_MAX_DICE_PER_TYPE = 100;
7
+ exports.DEFAULT_MAX_TOTAL_DICE = 500;
8
+ const rollDie = (sides) => Math.floor(Math.random() * sides) + 1;
9
+ const boostDieResult = (roll) => {
10
+ switch (roll) {
11
+ case 3:
12
+ return {
13
+ successes: 1,
14
+ failures: 0,
15
+ advantages: 0,
16
+ threats: 0,
17
+ triumphs: 0,
18
+ despair: 0,
19
+ lightSide: 0,
20
+ darkSide: 0,
21
+ };
22
+ case 4:
23
+ return {
24
+ successes: 1,
25
+ failures: 0,
26
+ advantages: 1,
27
+ threats: 0,
28
+ triumphs: 0,
29
+ despair: 0,
30
+ lightSide: 0,
31
+ darkSide: 0,
32
+ };
33
+ case 5:
34
+ return {
35
+ successes: 0,
36
+ failures: 0,
37
+ advantages: 2,
38
+ threats: 0,
39
+ triumphs: 0,
40
+ despair: 0,
41
+ lightSide: 0,
42
+ darkSide: 0,
43
+ };
44
+ case 6:
45
+ return {
46
+ successes: 0,
47
+ failures: 0,
48
+ advantages: 1,
49
+ threats: 0,
50
+ triumphs: 0,
51
+ despair: 0,
52
+ lightSide: 0,
53
+ darkSide: 0,
54
+ };
55
+ default:
56
+ return {
57
+ successes: 0,
58
+ failures: 0,
59
+ advantages: 0,
60
+ threats: 0,
61
+ triumphs: 0,
62
+ despair: 0,
63
+ lightSide: 0,
64
+ darkSide: 0,
65
+ };
66
+ }
67
+ };
68
+ const setBackDieResult = (roll) => {
69
+ switch (roll) {
70
+ case 3:
71
+ case 4:
72
+ return {
73
+ successes: 0,
74
+ failures: 1,
75
+ advantages: 0,
76
+ threats: 0,
77
+ triumphs: 0,
78
+ despair: 0,
79
+ lightSide: 0,
80
+ darkSide: 0,
81
+ };
82
+ case 5:
83
+ case 6:
84
+ return {
85
+ successes: 0,
86
+ failures: 0,
87
+ advantages: 0,
88
+ threats: 1,
89
+ triumphs: 0,
90
+ despair: 0,
91
+ lightSide: 0,
92
+ darkSide: 0,
93
+ };
94
+ default:
95
+ return {
96
+ successes: 0,
97
+ failures: 0,
98
+ advantages: 0,
99
+ threats: 0,
100
+ triumphs: 0,
101
+ despair: 0,
102
+ lightSide: 0,
103
+ darkSide: 0,
104
+ };
105
+ }
106
+ };
107
+ const abilityDieResult = (roll) => {
108
+ switch (roll) {
109
+ case 2:
110
+ case 3:
111
+ return {
112
+ successes: 1,
113
+ failures: 0,
114
+ advantages: 0,
115
+ threats: 0,
116
+ triumphs: 0,
117
+ despair: 0,
118
+ lightSide: 0,
119
+ darkSide: 0,
120
+ };
121
+ case 4:
122
+ return {
123
+ successes: 2,
124
+ failures: 0,
125
+ advantages: 0,
126
+ threats: 0,
127
+ triumphs: 0,
128
+ despair: 0,
129
+ lightSide: 0,
130
+ darkSide: 0,
131
+ };
132
+ case 5:
133
+ case 6:
134
+ return {
135
+ successes: 0,
136
+ failures: 0,
137
+ advantages: 1,
138
+ threats: 0,
139
+ triumphs: 0,
140
+ despair: 0,
141
+ lightSide: 0,
142
+ darkSide: 0,
143
+ };
144
+ case 7:
145
+ return {
146
+ successes: 1,
147
+ failures: 0,
148
+ advantages: 1,
149
+ threats: 0,
150
+ triumphs: 0,
151
+ despair: 0,
152
+ lightSide: 0,
153
+ darkSide: 0,
154
+ };
155
+ case 8:
156
+ return {
157
+ successes: 0,
158
+ failures: 0,
159
+ advantages: 2,
160
+ threats: 0,
161
+ triumphs: 0,
162
+ despair: 0,
163
+ lightSide: 0,
164
+ darkSide: 0,
165
+ };
166
+ default:
167
+ return {
168
+ successes: 0,
169
+ failures: 0,
170
+ advantages: 0,
171
+ threats: 0,
172
+ triumphs: 0,
173
+ despair: 0,
174
+ lightSide: 0,
175
+ darkSide: 0,
176
+ };
177
+ }
178
+ };
179
+ const difficultyDieResult = (roll) => {
180
+ switch (roll) {
181
+ case 2:
182
+ return {
183
+ successes: 0,
184
+ failures: 1,
185
+ advantages: 0,
186
+ threats: 0,
187
+ triumphs: 0,
188
+ despair: 0,
189
+ lightSide: 0,
190
+ darkSide: 0,
191
+ };
192
+ case 3:
193
+ return {
194
+ successes: 0,
195
+ failures: 2,
196
+ advantages: 0,
197
+ threats: 0,
198
+ triumphs: 0,
199
+ despair: 0,
200
+ lightSide: 0,
201
+ darkSide: 0,
202
+ };
203
+ case 4:
204
+ case 5:
205
+ case 6:
206
+ return {
207
+ successes: 0,
208
+ failures: 0,
209
+ advantages: 0,
210
+ threats: 1,
211
+ triumphs: 0,
212
+ despair: 0,
213
+ lightSide: 0,
214
+ darkSide: 0,
215
+ };
216
+ case 7:
217
+ return {
218
+ successes: 0,
219
+ failures: 0,
220
+ advantages: 0,
221
+ threats: 2,
222
+ triumphs: 0,
223
+ despair: 0,
224
+ lightSide: 0,
225
+ darkSide: 0,
226
+ };
227
+ case 8:
228
+ return {
229
+ successes: 0,
230
+ failures: 1,
231
+ advantages: 0,
232
+ threats: 1,
233
+ triumphs: 0,
234
+ despair: 0,
235
+ lightSide: 0,
236
+ darkSide: 0,
237
+ };
238
+ default:
239
+ return {
240
+ successes: 0,
241
+ failures: 0,
242
+ advantages: 0,
243
+ threats: 0,
244
+ triumphs: 0,
245
+ despair: 0,
246
+ lightSide: 0,
247
+ darkSide: 0,
248
+ };
249
+ }
250
+ };
251
+ const proficiencyDieResult = (roll) => {
252
+ switch (roll) {
253
+ case 2:
254
+ case 3:
255
+ return {
256
+ successes: 1,
257
+ failures: 0,
258
+ advantages: 0,
259
+ threats: 0,
260
+ triumphs: 0,
261
+ despair: 0,
262
+ lightSide: 0,
263
+ darkSide: 0,
264
+ };
265
+ case 4:
266
+ case 5:
267
+ return {
268
+ successes: 2,
269
+ failures: 0,
270
+ advantages: 0,
271
+ threats: 0,
272
+ triumphs: 0,
273
+ despair: 0,
274
+ lightSide: 0,
275
+ darkSide: 0,
276
+ };
277
+ case 6:
278
+ return {
279
+ successes: 0,
280
+ failures: 0,
281
+ advantages: 1,
282
+ threats: 0,
283
+ triumphs: 0,
284
+ despair: 0,
285
+ lightSide: 0,
286
+ darkSide: 0,
287
+ };
288
+ case 7:
289
+ case 8:
290
+ case 9:
291
+ return {
292
+ successes: 1,
293
+ failures: 0,
294
+ advantages: 1,
295
+ threats: 0,
296
+ triumphs: 0,
297
+ despair: 0,
298
+ lightSide: 0,
299
+ darkSide: 0,
300
+ };
301
+ case 10:
302
+ case 11:
303
+ return {
304
+ successes: 0,
305
+ failures: 0,
306
+ advantages: 2,
307
+ threats: 0,
308
+ triumphs: 0,
309
+ despair: 0,
310
+ lightSide: 0,
311
+ darkSide: 0,
312
+ };
313
+ case 12:
314
+ return {
315
+ successes: 0,
316
+ failures: 0,
317
+ advantages: 0,
318
+ threats: 0,
319
+ triumphs: 1,
320
+ despair: 0,
321
+ lightSide: 0,
322
+ darkSide: 0,
323
+ };
324
+ default:
325
+ return {
326
+ successes: 0,
327
+ failures: 0,
328
+ advantages: 0,
329
+ threats: 0,
330
+ triumphs: 0,
331
+ despair: 0,
332
+ lightSide: 0,
333
+ darkSide: 0,
334
+ };
335
+ }
336
+ };
337
+ const challengeDieResult = (roll) => {
338
+ switch (roll) {
339
+ case 2:
340
+ case 3:
341
+ return {
342
+ successes: 0,
343
+ failures: 1,
344
+ advantages: 0,
345
+ threats: 0,
346
+ triumphs: 0,
347
+ despair: 0,
348
+ lightSide: 0,
349
+ darkSide: 0,
350
+ };
351
+ case 4:
352
+ case 5:
353
+ return {
354
+ successes: 0,
355
+ failures: 2,
356
+ advantages: 0,
357
+ threats: 0,
358
+ triumphs: 0,
359
+ despair: 0,
360
+ lightSide: 0,
361
+ darkSide: 0,
362
+ };
363
+ case 6:
364
+ case 7:
365
+ return {
366
+ successes: 0,
367
+ failures: 0,
368
+ advantages: 0,
369
+ threats: 1,
370
+ triumphs: 0,
371
+ despair: 0,
372
+ lightSide: 0,
373
+ darkSide: 0,
374
+ };
375
+ case 8:
376
+ case 9:
377
+ return {
378
+ successes: 0,
379
+ failures: 1,
380
+ advantages: 0,
381
+ threats: 1,
382
+ triumphs: 0,
383
+ despair: 0,
384
+ lightSide: 0,
385
+ darkSide: 0,
386
+ };
387
+ case 10:
388
+ case 11:
389
+ return {
390
+ successes: 0,
391
+ failures: 0,
392
+ advantages: 0,
393
+ threats: 2,
394
+ triumphs: 0,
395
+ despair: 0,
396
+ lightSide: 0,
397
+ darkSide: 0,
398
+ };
399
+ case 12:
400
+ return {
401
+ successes: 0,
402
+ failures: 0,
403
+ advantages: 0,
404
+ threats: 0,
405
+ triumphs: 0,
406
+ despair: 1,
407
+ lightSide: 0,
408
+ darkSide: 0,
409
+ };
410
+ default:
411
+ return {
412
+ successes: 0,
413
+ failures: 0,
414
+ advantages: 0,
415
+ threats: 0,
416
+ triumphs: 0,
417
+ despair: 0,
418
+ lightSide: 0,
419
+ darkSide: 0,
420
+ };
421
+ }
422
+ };
423
+ const forceDieResult = (roll) => {
424
+ switch (roll) {
425
+ case 1:
426
+ case 2:
427
+ case 3:
428
+ case 4:
429
+ case 5:
430
+ return {
431
+ successes: 0,
432
+ failures: 0,
433
+ advantages: 0,
434
+ threats: 0,
435
+ triumphs: 0,
436
+ despair: 0,
437
+ lightSide: 1,
438
+ darkSide: 0,
439
+ };
440
+ case 6:
441
+ case 7:
442
+ return {
443
+ successes: 0,
444
+ failures: 0,
445
+ advantages: 0,
446
+ threats: 0,
447
+ triumphs: 0,
448
+ despair: 0,
449
+ lightSide: 2,
450
+ darkSide: 0,
451
+ };
452
+ case 8:
453
+ case 9:
454
+ case 10:
455
+ case 11:
456
+ return {
457
+ successes: 0,
458
+ failures: 0,
459
+ advantages: 0,
460
+ threats: 0,
461
+ triumphs: 0,
462
+ despair: 0,
463
+ lightSide: 0,
464
+ darkSide: 1,
465
+ };
466
+ case 12:
467
+ return {
468
+ successes: 0,
469
+ failures: 0,
470
+ advantages: 0,
471
+ threats: 0,
472
+ triumphs: 0,
473
+ despair: 0,
474
+ lightSide: 0,
475
+ darkSide: 2,
476
+ };
477
+ default:
478
+ return {
479
+ successes: 0,
480
+ failures: 0,
481
+ advantages: 0,
482
+ threats: 0,
483
+ triumphs: 0,
484
+ despair: 0,
485
+ lightSide: 0,
486
+ darkSide: 0,
487
+ };
488
+ }
489
+ };
490
+ const sumResults = (results, options) => {
491
+ const sums = results.reduce((acc, curr) => ({
492
+ successes: acc.successes + curr.successes,
493
+ failures: acc.failures + curr.failures,
494
+ advantages: acc.advantages + curr.advantages,
495
+ threats: acc.threats + curr.threats,
496
+ triumphs: acc.triumphs + curr.triumphs,
497
+ despair: acc.despair + curr.despair,
498
+ lightSide: acc.lightSide + (curr.lightSide || 0),
499
+ darkSide: acc.darkSide + (curr.darkSide || 0),
500
+ }), {
501
+ successes: 0,
502
+ failures: 0,
503
+ advantages: 0,
504
+ threats: 0,
505
+ triumphs: 0,
506
+ despair: 0,
507
+ lightSide: 0,
508
+ darkSide: 0,
509
+ });
510
+ let netSuccesses = 0;
511
+ let netFailures = 0;
512
+ if (sums.successes === sums.failures) {
513
+ netSuccesses = 0;
514
+ netFailures = 0;
515
+ }
516
+ else if (sums.successes > sums.failures) {
517
+ netSuccesses = sums.successes - sums.failures;
518
+ }
519
+ else {
520
+ netFailures = sums.failures - sums.successes;
521
+ }
522
+ const result = {
523
+ successes: netSuccesses,
524
+ failures: netFailures,
525
+ advantages: sums.advantages,
526
+ threats: sums.threats,
527
+ triumphs: sums.triumphs,
528
+ despair: sums.despair,
529
+ lightSide: sums.lightSide,
530
+ darkSide: sums.darkSide,
531
+ };
532
+ return result;
533
+ };
534
+ /**
535
+ * Rolls a dice pool and returns the results.
536
+ *
537
+ * @param pool - The dice pool to roll
538
+ * @param options - Optional roll configuration including dice limits
539
+ * @returns The roll results with detailed die information and summary
540
+ * @throws {Error} If dice counts exceed configured limits
541
+ *
542
+ * Default limits:
543
+ * - Max dice per type: 100 (configurable via options.maxDicePerType)
544
+ * - Max total dice: 500 (configurable via options.maxTotalDice)
545
+ */
546
+ const roll = (pool, options) => {
547
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
548
+ const boostCount = (_a = pool.boostDice) !== null && _a !== void 0 ? _a : 0;
549
+ const abilityCount = (_b = pool.abilityDice) !== null && _b !== void 0 ? _b : 0;
550
+ const proficiencyCount = (_c = pool.proficiencyDice) !== null && _c !== void 0 ? _c : 0;
551
+ const setBackCount = (_d = pool.setBackDice) !== null && _d !== void 0 ? _d : 0;
552
+ const difficultyCount = (_e = pool.difficultyDice) !== null && _e !== void 0 ? _e : 0;
553
+ const challengeCount = (_f = pool.challengeDice) !== null && _f !== void 0 ? _f : 0;
554
+ const forceCount = (_g = pool.forceDice) !== null && _g !== void 0 ? _g : 0;
555
+ // Get limits from options or use defaults
556
+ const maxDicePerType = (_h = options === null || options === void 0 ? void 0 : options.maxDicePerType) !== null && _h !== void 0 ? _h : exports.DEFAULT_MAX_DICE_PER_TYPE;
557
+ const maxTotalDice = (_j = options === null || options === void 0 ? void 0 : options.maxTotalDice) !== null && _j !== void 0 ? _j : exports.DEFAULT_MAX_TOTAL_DICE;
558
+ // Ensure all dice counts are non-negative and apply per-type limits
559
+ const sanitizedPool = {
560
+ boostDice: Math.max(0, Math.min(boostCount, maxDicePerType)),
561
+ abilityDice: Math.max(0, Math.min(abilityCount, maxDicePerType)),
562
+ proficiencyDice: Math.max(0, Math.min(proficiencyCount, maxDicePerType)),
563
+ setBackDice: Math.max(0, Math.min(setBackCount, maxDicePerType)),
564
+ difficultyDice: Math.max(0, Math.min(difficultyCount, maxDicePerType)),
565
+ challengeDice: Math.max(0, Math.min(challengeCount, maxDicePerType)),
566
+ forceDice: Math.max(0, Math.min(forceCount, maxDicePerType)),
567
+ };
568
+ // Check if any dice counts exceeded the per-type limit
569
+ const exceedsPerTypeLimit = boostCount > maxDicePerType ||
570
+ abilityCount > maxDicePerType ||
571
+ proficiencyCount > maxDicePerType ||
572
+ setBackCount > maxDicePerType ||
573
+ difficultyCount > maxDicePerType ||
574
+ challengeCount > maxDicePerType ||
575
+ forceCount > maxDicePerType;
576
+ // Calculate total dice count
577
+ const totalDice = sanitizedPool.boostDice +
578
+ sanitizedPool.abilityDice +
579
+ sanitizedPool.proficiencyDice +
580
+ sanitizedPool.setBackDice +
581
+ sanitizedPool.difficultyDice +
582
+ sanitizedPool.challengeDice +
583
+ sanitizedPool.forceDice;
584
+ // Check total dice limit
585
+ if (totalDice > maxTotalDice) {
586
+ throw new Error(`Total dice count (${totalDice}) exceeds maximum allowed (${maxTotalDice}). ` +
587
+ `Please reduce the number of dice in your pool.`);
588
+ }
589
+ // Warn if per-type limits were exceeded (but continue with capped values)
590
+ if (exceedsPerTypeLimit && (options === null || options === void 0 ? void 0 : options.throwOnLimitExceeded)) {
591
+ const exceeded = [];
592
+ if (boostCount > maxDicePerType)
593
+ exceeded.push(`boost: ${boostCount}`);
594
+ if (abilityCount > maxDicePerType)
595
+ exceeded.push(`ability: ${abilityCount}`);
596
+ if (proficiencyCount > maxDicePerType)
597
+ exceeded.push(`proficiency: ${proficiencyCount}`);
598
+ if (setBackCount > maxDicePerType)
599
+ exceeded.push(`setback: ${setBackCount}`);
600
+ if (difficultyCount > maxDicePerType)
601
+ exceeded.push(`difficulty: ${difficultyCount}`);
602
+ if (challengeCount > maxDicePerType)
603
+ exceeded.push(`challenge: ${challengeCount}`);
604
+ if (forceCount > maxDicePerType)
605
+ exceeded.push(`force: ${forceCount}`);
606
+ throw new Error(`Dice counts exceed per-type limit (${maxDicePerType}): ${exceeded.join(", ")}. ` +
607
+ `Dice counts have been capped to the maximum.`);
608
+ }
609
+ const detailedResults = [];
610
+ // Roll boost dice
611
+ for (let i = 0; i < sanitizedPool.boostDice; i++) {
612
+ const roll = rollDie(6);
613
+ detailedResults.push({
614
+ type: "boost",
615
+ roll,
616
+ result: boostDieResult(roll),
617
+ });
618
+ }
619
+ // Roll ability dice
620
+ for (let i = 0; i < sanitizedPool.abilityDice; i++) {
621
+ const roll = rollDie(8);
622
+ detailedResults.push({
623
+ type: "ability",
624
+ roll,
625
+ result: abilityDieResult(roll),
626
+ });
627
+ }
628
+ // Roll proficiency dice
629
+ for (let i = 0; i < sanitizedPool.proficiencyDice; i++) {
630
+ const roll = rollDie(12);
631
+ detailedResults.push({
632
+ type: "proficiency",
633
+ roll,
634
+ result: proficiencyDieResult(roll),
635
+ });
636
+ }
637
+ // Roll setback dice
638
+ for (let i = 0; i < sanitizedPool.setBackDice; i++) {
639
+ const roll = rollDie(6);
640
+ detailedResults.push({
641
+ type: "setback",
642
+ roll,
643
+ result: setBackDieResult(roll),
644
+ });
645
+ }
646
+ // Roll difficulty dice
647
+ for (let i = 0; i < sanitizedPool.difficultyDice; i++) {
648
+ const roll = rollDie(8);
649
+ detailedResults.push({
650
+ type: "difficulty",
651
+ roll,
652
+ result: difficultyDieResult(roll),
653
+ });
654
+ }
655
+ // Roll challenge dice
656
+ for (let i = 0; i < sanitizedPool.challengeDice; i++) {
657
+ const roll = rollDie(12);
658
+ detailedResults.push({
659
+ type: "challenge",
660
+ roll,
661
+ result: challengeDieResult(roll),
662
+ });
663
+ }
664
+ // Roll force dice
665
+ for (let i = 0; i < sanitizedPool.forceDice; i++) {
666
+ const roll = rollDie(12);
667
+ detailedResults.push({
668
+ type: "force",
669
+ roll,
670
+ result: forceDieResult(roll),
671
+ });
672
+ }
673
+ const summary = sumResults(detailedResults.map((r) => r.result));
674
+ if (options === null || options === void 0 ? void 0 : options.hints) {
675
+ const applicableHints = hints_1.hints.filter((hint) => {
676
+ const { cost } = hint;
677
+ // For OR conditions: at least one option must be fully satisfied
678
+ // Each entry in cost represents an alternative way to pay for the hint
679
+ return Object.entries(cost).some(([symbol, required]) => {
680
+ const summaryKey = (symbol.toLowerCase() + "s");
681
+ const value = summary[summaryKey];
682
+ if (typeof value !== "number")
683
+ return false;
684
+ // Check if we have enough of this symbol type to afford the hint
685
+ return required !== undefined && required > 0 && value >= required;
686
+ });
687
+ });
688
+ summary.hints = applicableHints.map((hint) => `${(0, hints_1.hintCostDisplayText)(hint)} - ${hint.description}`);
689
+ }
690
+ return {
691
+ results: detailedResults,
692
+ summary: summary,
693
+ };
694
+ };
695
+ 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 {};