Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make characters optional for moves #8

Merged
merged 7 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,10 @@
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {}
"rules": {
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
]
}
}
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ module.exports = {
},
testEnvironment: "jsdom",
rootDir: "src",
modulePaths: ["<rootDir>"],
};
96 changes: 96 additions & 0 deletions src/characters/action-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Move } from "@datasworn/core";
import { CharacterContext } from "../character-tracker";
import { movesReader, rollablesReader } from "../characters/lens";
import { type Datastore } from "../datastore";
import ForgedPlugin from "../index";
import { MeterCommon } from "../rules/ruleset";
import { InfoModal } from "../utils/ui/info";

export interface ActionContext {
readonly moves: Move[];
readonly rollables: {
key: string;
value?: number | undefined;
definition: MeterCommon;
}[];
readonly momentum?: number;
}

export class NoCharacterActionConext implements ActionContext {
constructor(public readonly datastore: Datastore) {}

get moves(): Move[] {
return this.datastore.moves;
}

get rollables(): {
key: string;
value?: number | undefined;
definition: MeterCommon;
}[] {
return Object.entries(this.datastore.ruleset.stats).map(([key, stat]) => ({
key,
definition: stat,
}));
}

get momentum() {
return undefined;
}
}

export class CharacterActionContext implements ActionContext {
constructor(
public readonly datastore: Datastore,
public readonly characterPath: string,
public readonly characterContext: CharacterContext,
) {}

get moves() {
const characterMoves = movesReader(
this.characterContext.lens,
this.datastore.index,
)
.get(this.characterContext.character)
.expect("unexpected failure finding assets for moves");

return this.datastore.moves.concat(characterMoves);
}

get rollables(): { key: string; value?: number; definition: MeterCommon }[] {
return rollablesReader(
this.characterContext.lens,
this.datastore.index,
).get(this.characterContext.character);
}

get momentum() {
return this.characterContext.lens.momentum.get(
this.characterContext.character,
);
}
}
export async function determineCharacterActionContext(
plugin: ForgedPlugin,
): Promise<ActionContext | undefined> {
if (plugin.settings.useCharacterSystem) {
try {
const [characterPath, characterContext] =
plugin.characters.activeCharacter();
return new CharacterActionContext(
plugin.datastore,
characterPath,
characterContext,
);
} catch (e) {
// TODO: probably want to show character parse errors in full glory
await InfoModal.show(
plugin.app,
`An error occurred while finding your active character.\n\n${e}`,
);
return undefined;
}
} else {
return new NoCharacterActionConext(plugin.datastore);
}
}
2 changes: 1 addition & 1 deletion src/characters/meter-commands.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { updatePreviousMoveOrCreateBlock } from "mechanics/editor";
import { Editor } from "obsidian";
import { ConditionMeterDefinition } from "rules/ruleset";
import { MoveBlockFormat } from "settings/ui";
import { MoveBlockFormat } from "settings";
import { node } from "utils/kdl";
import { updating } from "utils/lens";
import { vaultProcess } from "utils/obsidian";
Expand Down
37 changes: 9 additions & 28 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
import { addAssetToCharacter } from "characters/commands";
import { generateEntityCommand } from "entity/command";
import { IndexManager } from "indexer/manager";
import { runMoveCommand } from "moves/action";
import {
Plugin,
type Editor,
type MarkdownFileInfo,
type MarkdownView,
} from "obsidian";
import { DEFAULT_SETTINGS, ForgedPluginSettings } from "settings";
import { ProgressContext } from "tracks/context";
import { ForgedAPI } from "./api";
import { CharacterIndexer, CharacterTracker } from "./character-tracker";
import * as meterCommands from "./characters/meter-commands";
import { Datastore } from "./datastore";
import registerMechanicsBlock from "./mechanics/mechanics-blocks";
import { runMoveCommand } from "./moves/action";
import { registerMoveBlock } from "./moves/block";
import { runOracleCommand } from "./oracles/command";
import { registerOracleBlock } from "./oracles/render";
import {
DEFAULT_SETTINGS,
ForgedPluginSettings,
ForgedSettingTab,
} from "./settings/ui";
import { ForgedSettingTab } from "./settings/ui";
import { ClockIndex, ClockIndexer } from "./tracks/clock-file";
import {
advanceClock,
Expand Down Expand Up @@ -95,38 +92,22 @@ export default class ForgedPlugin extends Plugin {
id: "make-a-move",
name: "Make a Move",
icon: "zap",
editorCallback: async (
editor: Editor,
view: MarkdownView | MarkdownFileInfo,
) => {
// TODO: what if it is just a fileinfo?
await runMoveCommand(
this.app,
this.datastore,
new ProgressContext(this),
this.characters,
editor,
view as MarkdownView,
this.settings,
);
},
editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) =>
// TODO: what if view is just a fileinfo?
runMoveCommand(this, editor, view as MarkdownView),
});

this.addCommand({
id: "ask-the-oracle",
name: "Ask the Oracle",
icon: "help-circle",
editorCallback: async (
editor: Editor,
view: MarkdownView | MarkdownFileInfo,
) => {
await runOracleCommand(
editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) =>
runOracleCommand(
this.app,
this.datastore,
editor,
view as MarkdownView,
);
},
),
});

this.addCommand({
Expand Down
14 changes: 8 additions & 6 deletions src/moves/action-modal.ts → src/moves/action/action-modal.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { MoveActionRoll } from "@datasworn/core";
import { App, Modal, Setting } from "obsidian";
import { CharacterContext } from "../character-tracker";
import { MomentumTracker, momentumTrackerReader } from "../characters/lens";
import { ActionMoveDescription } from "./desc";
import { ActionMoveWrapper, formatRollResult } from "./wrapper";
import { CharacterContext } from "../../character-tracker";
import { MomentumTracker, momentumTrackerReader } from "../../characters/lens";
import { ActionMoveDescription } from "../desc";
import { ActionMoveWrapper, formatRollResult } from "../wrapper";

export async function checkForMomentumBurn(
app: App,
Expand All @@ -19,8 +19,10 @@ export async function checkForMomentumBurn(
new ActionModal(app, move, roll, momentumTracker, resolve, reject).open();
});
if (shouldBurn) {
// TODO: if this is true, maybe I should use momentumTracker.reset or something like that?
// or do the reset and then look at the new values?
// Instead of generating this value here, an alternative would be for this function
// to return its _intent_ to burn momentum. And then it could use the actual
// character lens command to reset it and then record the results. That _should_
// yield the same result, but would eliminate one possible source of divergence.
return Object.assign({}, roll.move, {
burn: {
orig: momentumTracker.momentum,
Expand Down
File renamed without changes.
105 changes: 105 additions & 0 deletions src/moves/action/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Document, Node } from "kdljs";
import { Editor, stringifyYaml } from "obsidian";
import { MoveBlockFormat } from "settings";
import { createOrAppendMechanics } from "../../mechanics/editor";
import { node } from "../../utils/kdl";
import { MoveDescription, moveIsAction, moveIsProgress } from "../desc";
import { generateMoveLine } from "../move-line-parser";

function generateMechanicsNode(move: MoveDescription): Document {
const children: Node[] = [];
if (moveIsAction(move)) {
const adds = (move.adds ?? []).reduce((acc, { amount }) => acc + amount, 0);

// Add "add" nodes for each non-zero add
children.push(
...(move.adds ?? [])
.filter(({ amount }) => amount != 0)
.map(({ amount, desc }) =>
node("add", { values: [amount, ...(desc ? [desc] : [])] }),
),
);

// Main roll node
children.push(
node("roll", {
values: [move.stat],
properties: {
action: move.action,
stat: move.statVal,
adds,
vs1: move.challenge1,
vs2: move.challenge2,
},
}),
);

// Momentum burn
if (move.burn) {
children.push(
node("burn", {
properties: { from: move.burn.orig, to: move.burn.reset },
}),
);
}
} else if (moveIsProgress(move)) {
children.push(
node("progress-roll", {
properties: {
// TODO: what about progress track id?
// TODO: use a ticks prop instead... or at least use a helper to get this
score: Math.floor(move.progressTicks / 4),
vs1: move.challenge1,
vs2: move.challenge2,
},
}),
);
} else {
throw new Error("what kind of move is this?");
}

// TODO: move name vs move id
const doc: Document = [
node("move", {
values: [move.name],
children,
}),
];
return doc;
}
function mechanicsMoveRenderer(
editor: Editor,
): (move: MoveDescription) => void {
return (move) => createOrAppendMechanics(editor, generateMechanicsNode(move));
}

export function getMoveRenderer(
format: MoveBlockFormat,
editor: Editor,
): (move: MoveDescription) => void {
switch (format) {
case MoveBlockFormat.MoveLine:
return moveLineMoveRenderer(editor);
case MoveBlockFormat.YAML:
return yamlMoveRenderer(editor);
case MoveBlockFormat.Mechanics:
return mechanicsMoveRenderer(editor);
}
}
export function yamlMoveRenderer(
editor: Editor,
): (move: MoveDescription) => void {
return (move) => {
editor.replaceSelection(`\`\`\`move\n${stringifyYaml(move)}\n\`\`\`\n\n`);
};
}

export function moveLineMoveRenderer(
editor: Editor,
): (move: MoveDescription) => void {
return (move) => {
editor.replaceSelection(
`\`\`\`move\n${generateMoveLine(move)}\n\`\`\`\n\n`,
);
};
}
Loading
Loading