Add support for d% dice
This commit is contained in:
		
							parent
							
								
									f44014c22a
								
							
						
					
					
						commit
						3864cb91fc
					
				| 
						 | 
					@ -57,7 +57,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
 | 
				
			||||||
      const [tempConf, tempFormat] = sepRoll.split(config.postfix);
 | 
					      const [tempConf, tempFormat] = sepRoll.split(config.postfix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Remove all spaces from the operation config and split it by any operator (keeping the operator in mathConf for fullSolver to do math on)
 | 
					      // Remove all spaces from the operation config and split it by any operator (keeping the operator in mathConf for fullSolver to do math on)
 | 
				
			||||||
      const mathConf: (string | number | SolvedStep)[] = <(string | number | SolvedStep)[]> tempConf.replace(/ /g, '').split(/([-+()*/%^])/g);
 | 
					      const mathConf: (string | number | SolvedStep)[] = <(string | number | SolvedStep)[]> tempConf.replace(/ /g, '').split(/([-+()*/^]|(?<![d%])%)/g);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Verify there are equal numbers of opening and closing parenthesis by adding 1 for opening parens and subtracting 1 for closing parens
 | 
					      // Verify there are equal numbers of opening and closing parenthesis by adding 1 for opening parens and subtracting 1 for closing parens
 | 
				
			||||||
      let parenCnt = 0;
 | 
					      let parenCnt = 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ import {
 | 
				
			||||||
} from '../../deps.ts';
 | 
					} from '../../deps.ts';
 | 
				
			||||||
import { RollModifiers } from '../mod.d.ts';
 | 
					import { RollModifiers } from '../mod.d.ts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { ReturnData, RollSet } from './solver.d.ts';
 | 
					import { DPercentConf, ReturnData, RollSet } from './solver.d.ts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const loggingEnabled = false;
 | 
					export const loggingEnabled = false;
 | 
				
			||||||
export const legalMath = [Math.abs, Math.ceil, Math.floor, Math.round, Math.sqrt, Math.cbrt];
 | 
					export const legalMath = [Math.abs, Math.ceil, Math.floor, Math.round, Math.sqrt, Math.cbrt];
 | 
				
			||||||
| 
						 | 
					@ -13,15 +13,17 @@ export const legalMathOperators = legalMath.map((oper) => oper.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// genRoll(size) returns number
 | 
					// genRoll(size) returns number
 | 
				
			||||||
// genRoll rolls a die of size size and returns the result
 | 
					// genRoll rolls a die of size size and returns the result
 | 
				
			||||||
export const genRoll = (size: number, modifiers: RollModifiers): number => {
 | 
					export const genRoll = (size: number, modifiers: RollModifiers, dPercent: DPercentConf): number => {
 | 
				
			||||||
 | 
					  let result;
 | 
				
			||||||
  if (modifiers.maxRoll) {
 | 
					  if (modifiers.maxRoll) {
 | 
				
			||||||
    return size;
 | 
					    result = size;
 | 
				
			||||||
  } else if (modifiers.minRoll) {
 | 
					  } else if (modifiers.minRoll) {
 | 
				
			||||||
    return 1;
 | 
					    result = 1;
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    // Math.random * size will return a decimal number between 0 and size (excluding size), so add 1 and floor the result to not get 0 as a result
 | 
					    // Math.random * size will return a decimal number between 0 and size (excluding size), so add 1 and floor the result to not get 0 as a result
 | 
				
			||||||
    return modifiers.nominalRoll ? size / 2 + 0.5 : Math.floor(Math.random() * size + 1);
 | 
					    result = modifiers.nominalRoll ? size / 2 + 0.5 : Math.floor(Math.random() * size + 1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  return dPercent.on ? (result - 1) * dPercent.sizeAdjustment : result;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// genFateRoll returns -1|0|1
 | 
					// genFateRoll returns -1|0|1
 | 
				
			||||||
| 
						 | 
					@ -31,7 +33,7 @@ export const genFateRoll = (modifiers: RollModifiers): number => {
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    const sides = [-1, -1, 0, 0, 1, 1];
 | 
					    const sides = [-1, -1, 0, 0, 1, 1];
 | 
				
			||||||
    return sides[genRoll(6, modifiers) - 1];
 | 
					    return sides[genRoll(6, modifiers, <DPercentConf> { on: false }) - 1];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,6 +44,11 @@ export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
 | 
				
			||||||
  const rollConf: RollConf = {
 | 
					  const rollConf: RollConf = {
 | 
				
			||||||
    dieCount: 0,
 | 
					    dieCount: 0,
 | 
				
			||||||
    dieSize: 0,
 | 
					    dieSize: 0,
 | 
				
			||||||
 | 
					    dPercent: {
 | 
				
			||||||
 | 
					      on: false,
 | 
				
			||||||
 | 
					      sizeAdjustment: 0,
 | 
				
			||||||
 | 
					      critVal: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    drop: {
 | 
					    drop: {
 | 
				
			||||||
      on: false,
 | 
					      on: false,
 | 
				
			||||||
      count: 0,
 | 
					      count: 0,
 | 
				
			||||||
| 
						 | 
					@ -147,15 +152,26 @@ export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
 | 
				
			||||||
    rollConf.dieCount = parseInt(tempDC);
 | 
					    rollConf.dieCount = parseInt(tempDC);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Finds the end of the die size/beginning of the additional options
 | 
					    // Finds the end of the die size/beginning of the additional options
 | 
				
			||||||
    let afterDieIdx = dPts[0].search(/\D/);
 | 
					    let afterDieIdx = dPts[0].search(/[^%\d]/);
 | 
				
			||||||
    if (afterDieIdx === -1) {
 | 
					    if (afterDieIdx === -1) {
 | 
				
			||||||
      afterDieIdx = dPts[0].length;
 | 
					      afterDieIdx = dPts[0].length;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Get the die size out of the remains and into the rollConf
 | 
					    // Get the die size out of the remains and into the rollConf
 | 
				
			||||||
    rollConf.dieSize = parseInt(remains.slice(0, afterDieIdx));
 | 
					    const rawDS = remains.slice(0, afterDieIdx);
 | 
				
			||||||
    remains = remains.slice(afterDieIdx);
 | 
					    remains = remains.slice(afterDieIdx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (rawDS.startsWith('%')) {
 | 
				
			||||||
 | 
					      rollConf.dieSize = 10;
 | 
				
			||||||
 | 
					      rollConf.dPercent.on = true;
 | 
				
			||||||
 | 
					      const percentCount = rawDS.match(/%/g)?.length ?? 1;
 | 
				
			||||||
 | 
					      rollConf.dPercent.sizeAdjustment = Math.pow(10, percentCount - 1);
 | 
				
			||||||
 | 
					      rollConf.dPercent.critVal = Math.pow(10, percentCount) - rollConf.dPercent.sizeAdjustment;
 | 
				
			||||||
 | 
					      console.log(percentCount, rollConf.dPercent);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      rollConf.dieSize = parseInt(rawDS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (remains.search(/\.\d/) === 0) {
 | 
					    if (remains.search(/\.\d/) === 0) {
 | 
				
			||||||
      throw new Error('WholeDieCountSizeOnly');
 | 
					      throw new Error('WholeDieCountSizeOnly');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -436,12 +452,12 @@ export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
 | 
				
			||||||
  if (rollConf.keepLow.on && rollConf.keepLow.count === 0) {
 | 
					  if (rollConf.keepLow.on && rollConf.keepLow.count === 0) {
 | 
				
			||||||
    throw new Error('NoZerosAllowed_keepLow');
 | 
					    throw new Error('NoZerosAllowed_keepLow');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (rollConf.reroll.on && rollConf.reroll.nums.includes(0)) {
 | 
					  if (rollConf.reroll.on && !rollConf.dPercent.on && rollConf.reroll.nums.includes(0)) {
 | 
				
			||||||
    throw new Error('NoZerosAllowed_reroll');
 | 
					    throw new Error('NoZerosAllowed_reroll');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Filter rollConf num lists to only include valid numbers
 | 
					  // Filter rollConf num lists to only include valid numbers
 | 
				
			||||||
  const validNumFilter = (curNum: number) => curNum <= rollConf.dieSize && curNum > 0;
 | 
					  const validNumFilter = (curNum: number) => curNum <= rollConf.dieSize && curNum > (rollConf.dPercent.on ? -1 : 0);
 | 
				
			||||||
  rollConf.reroll.nums = rollConf.reroll.nums.filter(validNumFilter);
 | 
					  rollConf.reroll.nums = rollConf.reroll.nums.filter(validNumFilter);
 | 
				
			||||||
  rollConf.critScore.range = rollConf.critScore.range.filter(validNumFilter);
 | 
					  rollConf.critScore.range = rollConf.critScore.range.filter(validNumFilter);
 | 
				
			||||||
  rollConf.critFail.range = rollConf.critFail.range.filter(validNumFilter);
 | 
					  rollConf.critFail.range = rollConf.critFail.range.filter(validNumFilter);
 | 
				
			||||||
| 
						 | 
					@ -497,7 +513,7 @@ export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
 | 
				
			||||||
    // Copy the template to fill out for this iteration
 | 
					    // Copy the template to fill out for this iteration
 | 
				
			||||||
    const rolling = getTemplateRoll();
 | 
					    const rolling = getTemplateRoll();
 | 
				
			||||||
    // If maximizeRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll
 | 
					    // If maximizeRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll
 | 
				
			||||||
    rolling.roll = rollType === 'fate' ? genFateRoll(modifiers) : genRoll(rollConf.dieSize, modifiers);
 | 
					    rolling.roll = rollType === 'fate' ? genFateRoll(modifiers) : genRoll(rollConf.dieSize, modifiers, rollConf.dPercent);
 | 
				
			||||||
    // Set origIdx of roll
 | 
					    // Set origIdx of roll
 | 
				
			||||||
    rolling.origIdx = i;
 | 
					    rolling.origIdx = i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -505,7 +521,7 @@ export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
 | 
				
			||||||
    if (rollConf.critScore.on && rollConf.critScore.range.includes(rolling.roll)) {
 | 
					    if (rollConf.critScore.on && rollConf.critScore.range.includes(rolling.roll)) {
 | 
				
			||||||
      rolling.critHit = true;
 | 
					      rolling.critHit = true;
 | 
				
			||||||
    } else if (!rollConf.critScore.on) {
 | 
					    } else if (!rollConf.critScore.on) {
 | 
				
			||||||
      rolling.critHit = rolling.roll === rollConf.dieSize;
 | 
					      rolling.critHit = rolling.roll === (rollConf.dPercent.on ? rollConf.dPercent.critVal : rollConf.dieSize);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
 | 
					    // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
 | 
				
			||||||
    if (rollConf.critFail.on && rollConf.critFail.range.includes(rolling.roll)) {
 | 
					    if (rollConf.critFail.on && rollConf.critFail.range.includes(rolling.roll)) {
 | 
				
			||||||
| 
						 | 
					@ -514,7 +530,7 @@ export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
 | 
				
			||||||
      if (rollType === 'fate') {
 | 
					      if (rollType === 'fate') {
 | 
				
			||||||
        rolling.critFail = rolling.roll === -1;
 | 
					        rolling.critFail = rolling.roll === -1;
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        rolling.critFail = rolling.roll === 1;
 | 
					        rolling.critFail = rolling.roll === (rollConf.dPercent.on ? 0 : 1);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -549,7 +565,7 @@ export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else if (modifiers.minRoll && !minMaxOverride) {
 | 
					        } else if (modifiers.minRoll && !minMaxOverride) {
 | 
				
			||||||
          // If minimizeRoll is on and we've entered the reroll code, 1 is not allowed, determine the next best option and always return that
 | 
					          // If minimizeRoll is on and we've entered the reroll code, 1 is not allowed, determine the next best option and always return that
 | 
				
			||||||
          mmMinLoop: for (let m = 2; m <= rollConf.dieSize; m++) {
 | 
					          mmMinLoop: for (let m = rollConf.dPercent.on ? 1 : 2; m <= rollConf.dieSize; m++) {
 | 
				
			||||||
            loopCountCheck(++loopCount);
 | 
					            loopCountCheck(++loopCount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!rollConf.reroll.nums.includes(m)) {
 | 
					            if (!rollConf.reroll.nums.includes(m)) {
 | 
				
			||||||
| 
						 | 
					@ -563,20 +579,20 @@ export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
 | 
				
			||||||
          newReroll.roll = minMaxOverride;
 | 
					          newReroll.roll = minMaxOverride;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          // If nominalRoll is on, set the roll to the average roll of dieSize, otherwise generate a new random roll
 | 
					          // If nominalRoll is on, set the roll to the average roll of dieSize, otherwise generate a new random roll
 | 
				
			||||||
          newReroll.roll = genRoll(rollConf.dieSize, modifiers);
 | 
					          newReroll.roll = genRoll(rollConf.dieSize, modifiers, rollConf.dPercent);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size
 | 
					        // If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size
 | 
				
			||||||
        if (rollConf.critScore.on && rollConf.critScore.range.includes(newReroll.roll)) {
 | 
					        if (rollConf.critScore.on && rollConf.critScore.range.includes(newReroll.roll)) {
 | 
				
			||||||
          newReroll.critHit = true;
 | 
					          newReroll.critHit = true;
 | 
				
			||||||
        } else if (!rollConf.critScore.on) {
 | 
					        } else if (!rollConf.critScore.on) {
 | 
				
			||||||
          newReroll.critHit = newReroll.roll === rollConf.dieSize;
 | 
					          newReroll.critHit = newReroll.roll === (rollConf.dPercent.on ? rollConf.dPercent.critVal : rollConf.dieSize);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
 | 
					        // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
 | 
				
			||||||
        if (rollConf.critFail.on && rollConf.critFail.range.includes(newReroll.roll)) {
 | 
					        if (rollConf.critFail.on && rollConf.critFail.range.includes(newReroll.roll)) {
 | 
				
			||||||
          newReroll.critFail = true;
 | 
					          newReroll.critFail = true;
 | 
				
			||||||
        } else if (!rollConf.critFail.on) {
 | 
					        } else if (!rollConf.critFail.on) {
 | 
				
			||||||
          newReroll.critFail = newReroll.roll === 1;
 | 
					          newReroll.critFail = newReroll.roll === (rollConf.dPercent.on ? 0 : 1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Slot this new roll in after the current iteration so it can be processed in the next loop
 | 
					        // Slot this new roll in after the current iteration so it can be processed in the next loop
 | 
				
			||||||
| 
						 | 
					@ -593,7 +609,7 @@ export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
 | 
				
			||||||
        // Copy the template to fill out for this iteration
 | 
					        // Copy the template to fill out for this iteration
 | 
				
			||||||
        const newExplodingRoll = getTemplateRoll();
 | 
					        const newExplodingRoll = getTemplateRoll();
 | 
				
			||||||
        // If maximizeRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll
 | 
					        // If maximizeRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll
 | 
				
			||||||
        newExplodingRoll.roll = genRoll(rollConf.dieSize, modifiers);
 | 
					        newExplodingRoll.roll = genRoll(rollConf.dieSize, modifiers, rollConf.dPercent);
 | 
				
			||||||
        // Always mark this roll as exploding
 | 
					        // Always mark this roll as exploding
 | 
				
			||||||
        newExplodingRoll.exploding = true;
 | 
					        newExplodingRoll.exploding = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -601,13 +617,13 @@ export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => {
 | 
				
			||||||
        if (rollConf.critScore.on && rollConf.critScore.range.includes(newExplodingRoll.roll)) {
 | 
					        if (rollConf.critScore.on && rollConf.critScore.range.includes(newExplodingRoll.roll)) {
 | 
				
			||||||
          newExplodingRoll.critHit = true;
 | 
					          newExplodingRoll.critHit = true;
 | 
				
			||||||
        } else if (!rollConf.critScore.on) {
 | 
					        } else if (!rollConf.critScore.on) {
 | 
				
			||||||
          newExplodingRoll.critHit = newExplodingRoll.roll === rollConf.dieSize;
 | 
					          newExplodingRoll.critHit = newExplodingRoll.roll === (rollConf.dPercent.on ? rollConf.dPercent.critVal : rollConf.dieSize);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
 | 
					        // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
 | 
				
			||||||
        if (rollConf.critFail.on && rollConf.critFail.range.includes(newExplodingRoll.roll)) {
 | 
					        if (rollConf.critFail.on && rollConf.critFail.range.includes(newExplodingRoll.roll)) {
 | 
				
			||||||
          newExplodingRoll.critFail = true;
 | 
					          newExplodingRoll.critFail = true;
 | 
				
			||||||
        } else if (!rollConf.critFail.on) {
 | 
					        } else if (!rollConf.critFail.on) {
 | 
				
			||||||
          newExplodingRoll.critFail = newExplodingRoll.roll === 1;
 | 
					          newExplodingRoll.critFail = newExplodingRoll.roll === (rollConf.dPercent.on ? 0 : 1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Slot this new roll in after the current iteration so it can be processed in the next loop
 | 
					        // Slot this new roll in after the current iteration so it can be processed in the next loop
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,10 +59,17 @@ export type SolvedRoll = {
 | 
				
			||||||
  counts: CountDetails;
 | 
					  counts: CountDetails;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DPercentConf = {
 | 
				
			||||||
 | 
					  on: boolean;
 | 
				
			||||||
 | 
					  sizeAdjustment: number;
 | 
				
			||||||
 | 
					  critVal: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RollConf is used by the roll20 setup
 | 
					// RollConf is used by the roll20 setup
 | 
				
			||||||
export type RollConf = {
 | 
					export type RollConf = {
 | 
				
			||||||
  dieCount: number;
 | 
					  dieCount: number;
 | 
				
			||||||
  dieSize: number;
 | 
					  dieSize: number;
 | 
				
			||||||
 | 
					  dPercent: DPercentConf;
 | 
				
			||||||
  drop: {
 | 
					  drop: {
 | 
				
			||||||
    on: boolean;
 | 
					    on: boolean;
 | 
				
			||||||
    count: number;
 | 
					    count: number;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue