Skip to content

Commit

Permalink
Change oracle modal and entity generators to allow choosing results f…
Browse files Browse the repository at this point in the history
…rom dropdowns (#470)
  • Loading branch information
cwegrzyn authored Nov 7, 2024
1 parent b5364ee commit 18a24a8
Show file tree
Hide file tree
Showing 11 changed files with 965 additions and 36 deletions.
25 changes: 25 additions & 0 deletions src/datastore/parsers/datasworn/id.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
extractDataswornLinkParts,
matchDataswornLink,
parseDataswornLinks,
ParsedDataswornId,
} from "./id";

Expand Down Expand Up @@ -63,3 +64,27 @@ describe("matchDataswornLink", () => {
},
);
});

describe("parseDataswornLinks", () => {
it("parses datasworn links", () => {
expect(
parseDataswornLinks(
"before[text](datasworn:oracle_rollable:foo/bar)after[text2](datasworn:oracle_rollable:foo/baz) end",
),
).toEqual([
"before",
{
match: "[text](datasworn:oracle_rollable:foo/bar)",
label: "text",
id: "oracle_rollable:foo/bar",
},
"after",
{
match: "[text2](datasworn:oracle_rollable:foo/baz)",
label: "text2",
id: "oracle_rollable:foo/baz",
},
" end",
]);
});
});
19 changes: 19 additions & 0 deletions src/datastore/parsers/datasworn/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,25 @@ export function matchDataswornLink(
return { label: match.groups!.label, id: match.groups!.id };
}

export function parseDataswornLinks(
text: string,
): (string | { match: string; label: string; id: string })[] {
const results: (string | { match: string; label: string; id: string })[] = [];
let lastIndex = 0;
for (const match of text.matchAll(new RegExp(DATASWORN_LINK_REGEX, "g"))) {
if (match.index > lastIndex) {
results.push(text.slice(lastIndex, match.index));
}
const { label, id } = match.groups!;
results.push({ match: match[0], label, id });
lastIndex = match.index + match[0].length;
}
if (lastIndex < text.length) {
results.push(text.slice(lastIndex));
}
return results;
}

/** Render a markdown link for a given datasworn ID. */
export function createDataswornMarkdownLink(label: string, id: string): string {
if (id.startsWith("datasworn:"))
Expand Down
37 changes: 28 additions & 9 deletions src/datastore/parsers/datasworn/oracles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export class DataswornOracle implements Oracle {
const roll = await diceRoller.rollAsync(group);

return {
...(await this.evaluate(context, roll[0].value)),
...this.evaluate(context, roll[0].value),
cursedRoll: roll[1].value,
cursedTableId: cursed.id,
};
Expand All @@ -131,7 +131,29 @@ export class DataswornOracle implements Oracle {
);
}

async evaluate(context: RollContext, roll: number): Promise<Roll> {
rollDirect(context: RollContext): Roll {
const diceRoller = context.diceRoller();

const cursed = this.cursedBy(context);
const cursedDice = context.cursedDice(); // non-null if cursed die is enabled

if (cursed && cursedDice) {
const group = DiceGroup.of(this.dice, cursedDice);
const roll = diceRoller.roll(group);

return {
...this.evaluate(context, roll[0].value),
cursedRoll: roll[1].value,
cursedTableId: cursed.id,
};
}
return this.evaluate(
context,
diceRoller.roll(new DiceGroup([this.dice]))[0].value,
);
}

evaluate(context: RollContext, roll: number): Roll {
const row = this.rowFor(roll);

const subrolls: Record<string, Subroll<Roll>> = {};
Expand All @@ -153,7 +175,7 @@ export class DataswornOracle implements Oracle {
throw new NoSuchOracleError(id, `missing subtable in ${this.id}`);
}
subrolls[id] = {
rolls: [await subTable.roll(context)],
rolls: [subTable.rollDirect(context)],
inTemplate: true,
};
}
Expand Down Expand Up @@ -211,7 +233,7 @@ export class DataswornOracle implements Oracle {
);
throw new Error("too many iterations");
}
const roll = await subrollable.roll(context);
const roll = subrollable.rollDirect(context);
switch (subOracle.duplicates) {
case "reroll":
if (
Expand Down Expand Up @@ -250,12 +272,9 @@ export class DataswornOracle implements Oracle {
};
}

async variants(
context: RollContext,
roll: Roll,
): Promise<Record<string, Roll>> {
variants(context: RollContext, roll: Roll): Record<string, Roll> {
return {
flip: await this.evaluate(context, this.dice.flip(roll.roll)),
flip: this.evaluate(context, this.dice.flip(roll.roll)),
};
}
}
5 changes: 3 additions & 2 deletions src/model/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@ export interface Oracle {
row(value: number): OracleRow;

roll(context: RollContext): Promise<Roll>;
rollDirect(context: RollContext): Roll;
// TODO: with variants, can we eliminate this? or is there a better way to deal with the
// specificity of the randomizer ("value")?
evaluate(context: RollContext, value: number): Promise<Roll>;
evaluate(context: RollContext, value: number): Roll;
// TODO: this feels kludgey
variants(context: RollContext, roll: Roll): Promise<Record<string, Roll>>;
variants(context: RollContext, roll: Roll): Record<string, Roll>;
}

// TODO: template currently relies on re-exporting datasworn
Expand Down
52 changes: 52 additions & 0 deletions src/model/rolls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export class RollWrapper {
this.row = oracle.row(roll.roll);
}

get diceValue(): number {
return this.roll.roll;
}

async variants(): Promise<Readonly<Record<string, RollWrapper>>> {
return Object.fromEntries(
Object.entries(await this.oracle.variants(this.context, this.roll)).map(
Expand Down Expand Up @@ -212,6 +216,39 @@ export class RollWrapper {
return this._subrolls;
}

replacingSubroll(subrollId: string, newValues: RollWrapper[]): RollWrapper {
const existingSubroll = this.roll.subrolls?.[subrollId];
if (!existingSubroll) {
throw new Error(
`Attempted to replace non-existent subroll oracle id ${subrollId}`,
);
}
const updatedRoll = {
...this.roll,
subrolls: {
...this.roll.subrolls,
[subrollId]: {
...existingSubroll,
rolls: newValues.map((wrap) => wrap.roll),
},
},
};
return new RollWrapper(this.oracle, this.context, updatedRoll);
}

replacingSubrolls(subrolls: [string, Subroll<RollWrapper>][]): RollWrapper {
const updatedRoll = {
...this.roll,
subrolls: Object.fromEntries(
subrolls.map(([k, s]) => [
k,
{ ...s, rolls: s.rolls.map((w) => w.roll) },
]),
),
};
return new RollWrapper(this.oracle, this.context, updatedRoll);
}

get selfRolls(): RollWrapper[] {
return this.subrolls[this.roll.tableId]?.rolls ?? [];
}
Expand Down Expand Up @@ -277,6 +314,21 @@ export class RollWrapper {
}
}
}

withinRange(range?: NumberRange): boolean {
const { roll } = this.roll;
return range ? range.min <= roll && roll <= range.max : false;
}

isSameRowAs(rollWrapper: RollWrapper): boolean {
return (
rollWrapper.row.range != null &&
this.row.range != null &&
rollWrapper.oracle.id === this.oracle.id &&
rollWrapper.row.range.min == this.row.range.min &&
rollWrapper.row.range.max == this.row.range.max
);
}
}

export interface NumberRange {
Expand Down
45 changes: 22 additions & 23 deletions src/oracles/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import {
OracleGrouping,
OracleGroupingType,
} from "../model/oracle";
import { Roll, RollWrapper } from "../model/rolls";
import { Roll } from "../model/rolls";
import { CustomSuggestModal } from "../utils/suggest";
import { OracleRollerModal } from "./modal";
import { NewOracleRollerModal } from "./new-modal";
import { oracleNameWithParents } from "./render";
import { OracleRoller } from "./roller";

Expand Down Expand Up @@ -114,31 +115,29 @@ export async function runOracleCommand(
`Roll your oracle dice (${oracle.dice}) and enter the value`,
);
if (typeof diceValue === "number") {
initialRoll = await oracle.evaluate(rollContext, diceValue);
initialRoll = oracle.evaluate(rollContext, diceValue);
}
}

new OracleRollerModal(
const modal = plugin.settings.useOldRoller
? OracleRollerModal
: NewOracleRollerModal;
const { roll, cursedRoll } = await modal.forRoll(
plugin,
oracle,
new RollWrapper(
oracle,
rollContext,
initialRoll || (await oracle.roll(rollContext)),
),
(roll, cursedRoll?) => {
// Delete the prompt and then inject the oracle node to a mechanics block
editor.setSelection(replaceSelection.anchor, replaceSelection.head);
editor.replaceSelection("");
const oracleNode = createOracleNode(roll, prompt);
const oracleNodes = [oracleNode];
if (cursedRoll) {
oracleNode.children.push(createOracleNode(cursedRoll));
oracleNode.properties.replaced =
cursedRoll.oracle.curseBehavior === CurseBehavior.ReplaceResult;
}
createOrAppendMechanics(editor, oracleNodes);
},
() => {},
).open();
rollContext,
initialRoll || (await oracle.roll(rollContext)),
);

// Delete the prompt and then inject the oracle node to a mechanics block
editor.setSelection(replaceSelection.anchor, replaceSelection.head);
editor.replaceSelection("");
const oracleNode = createOracleNode(roll, prompt);
const oracleNodes = [oracleNode];
if (cursedRoll) {
oracleNode.children.push(createOracleNode(cursedRoll));
oracleNode.properties.replaced =
cursedRoll.oracle.curseBehavior === CurseBehavior.ReplaceResult;
}
createOrAppendMechanics(editor, oracleNodes);
}
30 changes: 30 additions & 0 deletions src/oracles/css/oracles.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@
}
}

.iron-vault-oracle-table-container {
overflow-y: auto;
min-height: 3em;
max-height: calc(var(--modal-max-height) * 0.5);
}

table.iron-vault-oracle-table {
width: 100%;
border-collapse: collapse;

box-shadow: var(--shadow);
margin-top: 1em;
& thead {
position: sticky;
top: 0;
z-index: 1;
}
& thead tr {
background-color: var(--background-primary);
}
Expand All @@ -27,6 +39,12 @@ table.iron-vault-oracle-table {
&:last-child {
border-bottom: none;
}
&:hover {
background-color: var(--interactive-accent-hover);
& a {
color: var(--text-on-accent);
}
}
& td {
& > p {
margin: 0;
Expand All @@ -38,4 +56,16 @@ table.iron-vault-oracle-table {
}
}
}

& tbody tr.selected {
background-color: var(--interactive-accent);

&:hover {
background-color: var(--interactive-accent-hover);
}

& a {
color: var(--text-on-accent);
}
}
}
25 changes: 23 additions & 2 deletions src/oracles/modal.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
import IronVaultPlugin from "index";
import { CurseBehavior, Oracle } from "model/oracle";
import { RollWrapper } from "model/rolls";
import { CurseBehavior, Oracle, RollContext } from "model/oracle";
import { Roll, RollWrapper } from "model/rolls";
import { Modal, Setting } from "obsidian";
import { stripMarkdown } from "utils/strip-markdown";

export class OracleRollerModal extends Modal {
public accepted: boolean = false;
public cursedRoll?: RollWrapper;

static async forRoll(
plugin: IronVaultPlugin,
oracle: Oracle,
context: RollContext,
initialRoll: Roll,
): Promise<{ roll: RollWrapper; cursedRoll?: RollWrapper }> {
return new Promise((resolve, reject) => {
try {
new this(
plugin,
oracle,
new RollWrapper(oracle, context, initialRoll),
(roll, cursedRoll) => resolve({ roll, cursedRoll }),
reject,
).open();
} catch (e) {
reject(e);
}
});
}

constructor(
private plugin: IronVaultPlugin,
protected oracle: Oracle,
Expand Down
Loading

0 comments on commit 18a24a8

Please sign in to comment.