Implement dice matching option

This commit is contained in:
Ean Milligan 2025-06-28 04:34:46 -04:00
parent 791dd3a626
commit 0e009441ca
8 changed files with 135 additions and 19 deletions

View File

@ -106,6 +106,10 @@ The Artificer comes with a few supplemental commands to the main rolling command
| !!=u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u, but adds the resulting explosion to the die that caused this explosion |
| !!>u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u or greater, but adds the resulting explosion to the die that caused this explosion |
| !!<u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u or under, but adds the resulting explosion to the die that caused this explosion |
| m | Optional | No | matching dice, adds labels to any dice that match |
| mz | Optional | No | matching dice, adds labels to any dice that have z or more matches |
| mt | Optional | No | matching dice, adds labels to any dice that match, changes result to be the count of labels added |
| mtz | Optional | No | matching dice, adds labels to any dice that have z or more matches, changes result to be the count of labels added |
* If the parameter is Required, it must be provided at all times.
* If the parameter is Repeatable, it may occur multiple times in the roll configuration.

View File

@ -15,6 +15,7 @@ export interface RollSet {
critHit: boolean;
critFail: boolean;
isComplex: boolean;
matchLabel: string;
}
// CountDetails is the object holding the count data for creating the Count Embed
@ -102,4 +103,14 @@ export interface RollConf {
penetrating: boolean;
nums: number[];
};
match: {
on: boolean;
minCount: number;
returnTotal: boolean;
};
}
export interface SumOverride {
on: boolean;
value: number;
}

View File

@ -1,6 +1,6 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { RollModifiers, RollSet } from 'artigen/dice/dice.d.ts';
import { RollModifiers, RollSet, SumOverride } from 'artigen/dice/dice.d.ts';
import { genFateRoll, genRoll } from 'artigen/dice/randomRoll.ts';
import { getRollConf } from 'artigen/dice/getRollConf.ts';
@ -8,10 +8,11 @@ import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { compareOrigIdx, compareRolls } from 'artigen/utils/sortFuncs.ts';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { generateRollVals } from 'artigen/utils/rollValCounter.ts';
// roll(rollStr, modifiers) returns RollSet
// roll parses and executes the rollStr
export const executeRoll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet[], SumOverride] => {
/* Roll Capabilities
* Deciphers and rolls a single dice roll set
*
@ -67,6 +68,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): RollSet[
rollConf.critScore.on ||
rollConf.critFail.on ||
rollConf.exploding.on,
matchLabel: '',
});
// Initial rolling, not handling reroll or exploding here
@ -307,18 +309,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): RollSet[
// Handle OVA dropping/keeping
if (rollConf.type === 'ova') {
// Make "empty" vals array to easily sum up which die value is the greatest
const rollVals: Array<number> = new Array(rollConf.dieSize).fill(0);
// Sum up all rolls
for (const ovaRoll of rollSet) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | incrementing rollVals for ${JSON.stringify(ovaRoll)}`);
if (!ovaRoll.dropped && !ovaRoll.rerolled) {
rollVals[ovaRoll.roll - 1] += ovaRoll.roll;
}
}
const rollVals: Array<number> = generateRollVals(rollConf, rollSet, rollStr, false);
// Find max value, using lastIndexOf to use the greatest die size max in case of duplicate maximums
const maxRoll = rollVals.lastIndexOf(Math.max(...rollVals)) + 1;
@ -337,5 +328,47 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): RollSet[
}
}
return rollSet;
const sumOverride: SumOverride = {
on: rollConf.match.returnTotal,
value: 0,
};
if (rollConf.match.on) {
const rollVals: Array<number> = generateRollVals(rollConf, rollSet, rollStr, true).map((count) => (count >= rollConf.match.minCount ? count : 0));
const labels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let labelIdx = 0;
const rollLabels: Array<string> = rollVals.map((count) => {
loopCountCheck();
if (labelIdx >= labels.length) {
throw new Error(`TooManyLabels_${labels.length}`);
}
if (count) {
return labels[labelIdx++];
}
return '';
});
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | current match state: ${rollVals} | ${rollLabels}`);
// Apply labels
for (const roll of rollSet) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | trying to add a label to ${JSON.stringify(roll)}`);
if (rollLabels[roll.roll - 1]) {
roll.matchLabel = rollLabels[roll.roll - 1];
} else if (rollConf.match.returnTotal) {
roll.dropped = true;
}
}
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | labels added: ${JSON.stringify(rollSet)}`);
if (rollConf.match.returnTotal) {
sumOverride.value = rollVals.filter((count) => count !== 0).length;
}
}
return [rollSet, sumOverride];
};

View File

@ -19,7 +19,7 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers
let tempComplex = false;
// Generate the roll, passing flags thru
const tempRollSet = executeRoll(rollConf, modifiers);
const [tempRollSet, sumOverride] = executeRoll(rollConf, modifiers);
// Loop thru all parts of the roll to document everything that was done to create the total roll
tempRollSet.forEach((e) => {
@ -72,8 +72,13 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers
postFormat = `!${postFormat}`;
}
let rollLabel = '';
if (e.matchLabel) {
rollLabel = `${e.matchLabel}:`;
}
// Finally add this to the roll's details
tempDetails += `${preFormat}${e.roll}${postFormat} + `;
tempDetails += `${preFormat}${rollLabel}${e.roll}${postFormat} + `;
});
// After the looping is done, remove the extra " + " from the details and cap it with the closing ]
tempDetails = tempDetails.substring(0, tempDetails.length - 3);
@ -86,7 +91,7 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers
return {
solvedStep: {
total: tempTotal,
total: sumOverride.on ? sumOverride.value : tempTotal,
details: tempDetails,
containsCrit: tempCrit,
containsFail: tempFail,

View File

@ -61,6 +61,11 @@ export const getRollConf = (rollStr: string): RollConf => {
penetrating: false,
nums: [],
},
match: {
on: false,
minCount: 2,
returnTotal: false,
},
};
// If the dPts is not long enough, throw error
@ -380,12 +385,23 @@ export const getRollConf = (rollStr: string): RollConf => {
!rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i);
}
break;
case 'm':
case 'mt':
rollConf.match.on = true;
if (afterNumIdx > 0) {
// User gave a number to explode on, save it
rollConf.match.minCount = tNum;
} else {
// User did not give number, use cs
afterNumIdx = 1;
}
break;
default:
// Throw error immediately if unknown op is encountered
throw new Error(`UnknownOperation_${tSep}`);
}
// Exploding flags get set in their own switch statement to avoid weird duplicated code
// Followup switch to avoid weird duplicated code
switch (tSep) {
case '!o':
case '!o=':
@ -405,6 +421,9 @@ export const getRollConf = (rollStr: string): RollConf => {
case '!!<':
rollConf.exploding.compounding = true;
break;
case 'mt':
rollConf.match.returnTotal = true;
break;
}
// Finally slice off everything else parsed this loop

View File

@ -0,0 +1,25 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { RollConf, RollSet } from 'artigen/dice/dice.d.ts';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
// Can either count or sum each die
export const generateRollVals = (rollConf: RollConf, rollSet: RollSet[], rollStr: string, count: boolean): Array<number> => {
const rollVals = new Array(rollConf.dieSize).fill(0);
// Count up all rolls
for (const ovaRoll of rollSet) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | incrementing rollVals for ${JSON.stringify(ovaRoll)}`);
if (!ovaRoll.dropped && !ovaRoll.rerolled) {
rollVals[ovaRoll.roll - 1] += count ? 1 : ovaRoll.roll;
}
}
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | rollVals ${rollVals}`);
return rollVals;
};

View File

@ -112,6 +112,9 @@ export const translateError = (solverError: Error): [string, string] => {
case 'IllegalVariable':
errorMsg = `Error: \`${errorDetails}\` is not a valid variable`;
break;
case 'TooManyLabels':
errorMsg = `Error: ${config.name} can only support a maximum of \`${errorDetails}\` labels when using the dice matching options (\`m\` or \`mt\`)`;
break;
default:
log(LT.ERROR, `Unhandled Parser Error: ${errorName}, ${errorDetails}`);
errorMsg = `Unhandled Error: ${solverError.message}\nCheck input and try again, if issue persists, please use \`${config.prefix}report\` to alert the devs of the issue`;

View File

@ -230,6 +230,22 @@ Any time a Compounding Explosion happens, the formatting on the die will still s
],
},
],
[
'dice-matching',
{
name: 'Dice Matching',
description: `**Usage:** \`xdym\`, \`xdymz\`, \`xdymt\`, or \`xdymtz\`
\`z\` - Minimum count of matches for a label to be added
The basic \`m\` option will only add labels without modifying the results, whereas the \`mt\` options will add labels and will change the result of the roll to be equal to the number of labels that have been added.`,
example: [
'`[[10d6m]]` => [**C:6** + B:2 + 4 + __C:1__ + __C:1__ + B:2 + **C:6** + B:2 + **C:6** + **C:6**] = 36',
'`[[10d6m4]]` => [**A:6** + 2 + 4 + __1__ + __1__ + 2 + **A:6** + 2 + **A:6** + **A:6**] = 36',
'`[[10d6mt]]` => [**C:6** + B:2 + ~~4~~ + __C:1__ + __C:1__ + B:2 + **C:6** + B:2 + **C:6** + **C:6**] = 3',
'`[[10d6mt4]]` => [**A:6** + ~~2~~ + ~~4~~ + ~~__1__~~ + ~~__1__~~ + ~~2~~ + **A:6** + ~~2~~ + **A:6** + **A:6**] = 1',
],
},
],
]);
export const DiceOptionsHelpPages: HelpPage = {