Skip to content

Commit

Permalink
feat: Enables custom dice values using Xd[...] syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
valentine195 committed Oct 11, 2023
1 parent 9be1207 commit af5ccf1
Show file tree
Hide file tree
Showing 8 changed files with 1,940 additions and 148 deletions.
1,921 changes: 1,834 additions & 87 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "node esbuild.config.mjs production",
"test": "vitest",
"web:dev": "set NODE_OPTIONS=--openssl-legacy-provider && webpack --config webpack.dev.js -w",
"web:build": "webpack",
"rollup:dev": "rollup --config rollup.config-dev.js -w",
Expand Down Expand Up @@ -53,6 +54,8 @@
"ts-loader": "^9.2.6",
"tslib": "^2.0.3",
"typescript": "^4.0.3",
"vite-tsconfig-paths": "^4.2.0",
"vitest": "^0.33.0",
"webpack": "^5.53.0",
"webpack-cli": "^4.8.0"
},
Expand Down
5 changes: 4 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export default class DiceRollerPlugin extends Plugin {
return Math.pow(a, b);
}
};
parser = new Lexer(this);
parser: Lexer;
persistingFiles: Set<string> = new Set();
renderer: DiceRenderer;

Expand All @@ -233,6 +233,7 @@ export default class DiceRollerPlugin extends Plugin {
this.data = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());

this.renderer = new DiceRenderer(this.getRendererData());
this.parser = new Lexer(this.data.defaultRoll, this.data.defaultFace);

this.addSettingTab(new SettingTab(this.app, this));

Expand Down Expand Up @@ -713,6 +714,7 @@ export default class DiceRollerPlugin extends Plugin {
}
});

this.parser.setInlineFields(this.inline);
this.registerEvent(
this.app.metadataCache.on(
"dataview:metadata-change",
Expand All @@ -732,6 +734,7 @@ export default class DiceRollerPlugin extends Plugin {
continue;
this.inline.set(key, value);
}
this.parser.setInlineFields(this.inline);
}
}
)
Expand Down
28 changes: 19 additions & 9 deletions src/parser/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export const LINE_REGEX =
/(?:\d+[Dd])?(?:\[.*\]\(|\[\[)(?:.+)(?:\)|\]\])\|line/u;
export const MATH_REGEX = /[\(\^\+\-\*\/\)]/u;
export const OMITTED_REGEX =
/(?:\d+|\b)[Dd](?:\[?(?:-?\d+[ \t]?,)?[ \t]?(?:-?\d+|%|F)\]?|\b)/u;
/(?:\d+|\b)[Dd](?:%|F|-?\d+|\[\d+(?:[ \t]*,[ \t]*\d+)+\]|\b)/u;

export const CONDITIONAL_REGEX =
/(?:=|=!|<|>|<=|>=|=<|=>|-=|=-)(?:\d+(?:[Dd](?:\[?(?:-?\d+[ \t]?,)?[ \t]?(?:-?\d+|%|F)\]?|\b))?)/u;
/(?:=|=!|<|>|<=|>=|=<|=>|-=|=-)(?:\d+(?:[Dd](?:%|F|-?\d+|\[\d+(?:[ \t]*,[ \t]*\d+)+\]|\b))?)/u;

export interface LexicalToken extends Partial<moo.Token> {
conditions?: Conditional[];
Expand Down Expand Up @@ -67,10 +67,10 @@ export default class Lexer {
match: OMITTED_REGEX,
value: (match) => {
const {
roll = this.plugin.data.defaultRoll,
faces = this.plugin.data.defaultFace
roll = this.defaultRoll,
faces = this.defaultFace
} = match.match(
/(?<roll>\d+)?[Dd](?<faces>\[?(?:-?\d+[ \t]?,)?[ \t]?(?:-?\d+|%|F)\]?)?/
/(?<roll>\d+)?[Dd](?<faces>%|F|-?\d+|\[\d+(?:[ \t]*,[ \t]*\d+)+\])?/
).groups;
return `${roll}d${faces}`;
}
Expand All @@ -79,8 +79,8 @@ export default class Lexer {
{
match: /\b[A-Za-z][A-Za-z0-9_]+\b/u,
value: (match) => {
if (this.plugin.inline.has(match)) {
return `${this.plugin.inline.get(match)}`;
if (this.inline.has(match)) {
return `${this.inline.get(match)}`;
}
return match;
}
Expand All @@ -95,11 +95,21 @@ export default class Lexer {
math: MATH_REGEX
});
parser: Parser;
inline: Map<string, number> = new Map();
clampInfinite(match: string) {
if (/i$/.test(match)) return "100";
return match.replace(/^\D+/g, "");
}
constructor(public plugin: DiceRollerPlugin) {
public setInlineFields(fields: Map<string, number>) {
this.inline = fields;
}
public setDefaultRoll(roll: number) {
this.defaultRoll = roll;
}
public setDefaultFace(face: number) {
this.defaultFace = face;
}
constructor(public defaultRoll: number, public defaultFace: number) {
const exponent = {
precedence: 3,
associativity: "right"
Expand Down Expand Up @@ -139,7 +149,7 @@ export default class Lexer {
if (!previous.conditions) previous.conditions = [];
const [_, operator, comparer] =
token.value.match(
/(?<operator>=|=!|<|>|<=|>=|=<|=>|-=|=-)(?<comparer>\d+(?:[Dd](?:\[?(?:-?\d+[ \t]?,)?[ \t]?(?:-?\d+|%|F)\]?|\b))?)/
/(?<operator>=|=!|<|>|<=|>=|=<|=>|-=|=-)(?<comparer>\d+(?:[Dd](?:%|F|-?\d+|\[\d+(?:[ \t]*,[ \t]*\d+)+\]|\b))?)/
) ?? [];
const lexemes = this.parse(comparer);
previous.conditions.push({
Expand Down
10 changes: 8 additions & 2 deletions src/renderer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,14 +384,14 @@ export default class DiceRenderer extends Component {
this.event.trigger("throw-finished");
}
#positions: WeakMap<DiceShape, Vec3> = new WeakMap();
static Threshold = 5;
throwFinished() {
let res = true;
if (this.iterations < 10 / this.frame_rate) {
for (const shapes of this.#current) {
let finished = true;
for (const dice of shapes) {
if (dice.stopped === true) continue;

/* const a = dice.body.angularVelocity,
v = dice.body.velocity; */
/* if (
Expand Down Expand Up @@ -421,7 +421,13 @@ export default class DiceRenderer extends Component {
Math.pow(current.y - previous.y, 2) +
Math.pow(current.z - previous.z, 2)
);
if (delta < 0.1) {
const a = dice.body.angularVelocity;
if (
delta < 0.1 &&
Math.abs(a.x) < DiceRenderer.Threshold &&
Math.abs(a.y) < DiceRenderer.Threshold &&
Math.abs(a.z) < DiceRenderer.Threshold
) {
if (dice.stopped) {
if (this.iterations - dice.stopped > 5) {
dice.stopped = true;
Expand Down
80 changes: 52 additions & 28 deletions src/roller/dice.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Notice, setIcon } from "obsidian";
import type DiceRollerPlugin from "src/main";
import Lexer, { LexicalToken } from "src/parser/lexer";
import { LexicalToken } from "src/parser/lexer";
import {
ResultMapInterface,
Conditional,
Expand All @@ -11,7 +11,7 @@ import {
import { _insertIntoMap } from "src/utils/util";
import { GenericRoller, Roller } from "./roller";
import DiceRenderer from "src/renderer/renderer";
import { D6Dice, DiceShape } from "src/renderer/shapes";
import { DiceShape } from "src/renderer/shapes";

interface Modifier {
conditionals: Conditional[];
Expand Down Expand Up @@ -40,41 +40,55 @@ export class DiceRoller {
this.static = true;
this.modifiersAllowed = false;
}
let [, rolls, minStr = null, maxStr = 1] = this.dice.match(
/(\-?\d+)[dD]\[?(?:(-?\d+)\s?,)?\s?(-?\d+|%|F)\]?/
) || [, 1, null, 1];
let [, rolls, maxStr = "1"] = this.dice.match(
/(\-?\d+)[dD](%|F|-?\d+|\[\d+(?:[ \t]*,[ \t]*\d+)+\])/
) || [, 1, "1"];

rolls = Number(rolls);

this.multiplier = rolls < 0 ? -1 : 1;
let min = isNaN(Number(minStr)) ? null : Number(minStr);
let max: number;
let min = 1;
let max = isNaN(Number(maxStr)) ? 1 : Number(maxStr);
this.rolls = Math.abs(Number(rolls)) || 1;

if (maxStr === "%") {
max = 100;
} else if (maxStr === "F") {
max = 1;
min = -1;
this.fudge = true;
//ugly af
if (/\[\d+(?:[ \t]*,[ \t]*\d+)+\]/.test(maxStr)) {
this.possibilities = maxStr
.replace(/[\[\]\s]/g, "")
.split(",")
.map((v) => Number(v));
} else {
max = Number(maxStr);
}
if (Number(max) < 0 && !min) {
min = -1;
}
if (Number(max) < Number(min)) {
[max, min] = [min, max];
}

this.faces = { max: max ? Number(max) : 1, min: min ? Number(min) : 1 };
if (maxStr === "%") {
max = 100;
} else if (maxStr === "F") {
this.possibilities = [-1, 0, 1];
this.fudge = true;
} else {
max = Number(maxStr);
}
if (Number(max) < 0 && !min) {
min = -1;
}
if (Number(max) < Number(min)) {
[max, min] = [min, max];
}

this.possibilities = [...Array(Number(max)).keys()].map(
(k) => k + min
);
}
this.conditions = this.lexeme.conditions ?? [];
}
dice: string;
modifiers: Map<ModifierType, Modifier> = new Map();
rolls: number;
faces: { min: number; max: number };
possibilities: number[] = [];
get faces() {
return {
max: this.possibilities[this.possibilities.length - 1],
min: this.possibilities[0]
};
}
results: ResultMapInterface<number> = new Map();
shapes: Map<number, DiceShape[]> = new Map();
getShapes(index?: number) {
Expand Down Expand Up @@ -318,9 +332,15 @@ export class DiceRoller {
})
);
}

canRender() {
if (this.possibilities.length !== this.faces.max) return false;
const arr = [...Array(this.faces.max).keys()].map(
(k) => this.faces.min + k
);
return arr.every((v) => this.possibilities.includes(v));
}
async getValue(shapes?: DiceShape[]) {
if (this.shouldRender) {
if (this.shouldRender && this.canRender()) {
const temp = shapes ?? this.renderer.getDiceForRoller(this) ?? [];
await this.renderer.addDice(temp);
return this.#resolveShapeValue(temp);
Expand Down Expand Up @@ -566,10 +586,14 @@ export class DiceRoller {
return true;
}
average(): number {
return (this.faces.min + this.faces.max) / 2;
return (
this.possibilities.reduce((a, b) => a + b) /
this.possibilities.length
);
}
getRandomBetween(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
const index = Math.floor(Math.random() * this.possibilities.length);
return this.possibilities[index];
}

shouldRender = false;
Expand Down
3 changes: 3 additions & 0 deletions src/settings/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ export default class SettingTab extends PluginSettingTab {
}

this.plugin.data.defaultFace = Number(t.inputEl.value);
this.plugin.parser.setDefaultFace(
this.plugin.data.defaultFace
);
await this.plugin.saveSettings();
};
});
Expand Down
38 changes: 17 additions & 21 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES2020",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"allowSyntheticDefaultImports": true,
"lib": [
"dom",
"es5",
"scripthost",
"es2020"
]
},
"include": [
"**/*.ts"
],
"compilerOptions": {
"paths": {
"src/*": ["./src/*"]
},
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES2020",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"allowSyntheticDefaultImports": true,
"lib": ["dom", "es5", "scripthost", "es2020"]
},
"include": ["**/*.ts"]
}

0 comments on commit af5ccf1

Please sign in to comment.