Skip to content

Commit

Permalink
Support custom meters in take/suffer
Browse files Browse the repository at this point in the history
  • Loading branch information
cwegrzyn committed May 17, 2024
1 parent 23f298e commit 16af6a3
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 139 deletions.
98 changes: 81 additions & 17 deletions src/characters/action-context.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,77 @@
import { Move } from "@datasworn/core";
import { App } from "obsidian";
import { ConditionMeterDefinition } from "rules/ruleset";
import { vaultProcess } from "utils/obsidian";
import { CharacterContext } from "../character-tracker";
import { movesReader, rollablesReader } from "../characters/lens";
import {
CharReader,
MOMENTUM_METER_DEFINITION,
MeterWithLens,
MeterWithoutLens,
ValidatedCharacter,
meterLenses,
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 {
export interface IActionContext {
readonly kind: "no_character" | "character";
readonly moves: Move[];
readonly rollables: {
key: string;
value?: number | undefined;
definition: MeterCommon;
}[];
readonly rollables: (MeterWithLens | MeterWithoutLens)[];
readonly conditionMeters: (
| MeterWithLens<ConditionMeterDefinition>
| MeterWithoutLens<ConditionMeterDefinition>
)[];

readonly momentum?: number;

getWithLens<T>(lens: CharReader<T>): T | undefined;
}

export class NoCharacterActionConext implements ActionContext {
export class NoCharacterActionConext implements IActionContext {
readonly kind = "no_character";
readonly momentum: undefined = undefined;

constructor(public readonly datastore: Datastore) {}

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

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

get momentum() {
getWithLens<T>(_lens: CharReader<T>): undefined {
return undefined;
}

get conditionMeters(): MeterWithoutLens<ConditionMeterDefinition>[] {
return [
...Object.entries(this.datastore.ruleset.condition_meters).map(
([key, definition]) => ({
key,
definition,
lens: undefined,
value: undefined,
}),
),
MOMENTUM_METER_DEFINITION,
];
}
}

export class CharacterActionContext implements ActionContext {
export class CharacterActionContext implements IActionContext {
readonly kind = "character";

constructor(
public readonly datastore: Datastore,
public readonly characterPath: string,
Expand All @@ -57,7 +89,7 @@ export class CharacterActionContext implements ActionContext {
return this.datastore.moves.concat(characterMoves);
}

get rollables(): { key: string; value?: number; definition: MeterCommon }[] {
get rollables(): MeterWithLens[] {
return rollablesReader(
this.characterContext.lens,
this.datastore.index,
Expand All @@ -69,7 +101,39 @@ export class CharacterActionContext implements ActionContext {
this.characterContext.character,
);
}

getWithLens<T>(lens: CharReader<T>): T {
return lens.get(this.characterContext.character);
}

get conditionMeters(): MeterWithLens<ConditionMeterDefinition>[] {
const { character, lens } = this.characterContext;
return Object.values(
meterLenses(lens, character, this.datastore.index),
).map(({ key, definition, lens }) => ({
key,
definition,
lens,
value: lens.get(character),
}));
}

async update(
app: App,
updater: (
obj: ValidatedCharacter,
context: CharacterContext,
) => ValidatedCharacter,
) {
return await this.characterContext.updater(
vaultProcess(app, this.characterPath),
updater,
);
}
}

export type ActionContext = CharacterActionContext | NoCharacterActionConext;

export async function determineCharacterActionContext(
plugin: ForgedPlugin,
): Promise<ActionContext | undefined> {
Expand Down
13 changes: 5 additions & 8 deletions src/characters/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Asset } from "@datasworn/core";
import ForgedPlugin from "index";
import { Editor, FuzzyMatch, MarkdownView } from "obsidian";
import { vaultProcess } from "utils/obsidian";
import { titleCase } from "utils/strings";
import { CustomSuggestModal } from "utils/suggest";
import { PromptModal } from "utils/ui/prompt";
import {
Expand All @@ -12,14 +13,10 @@ import {
updateAssetWithOptions,
} from "./assets";

export function titleCase(str: string): string {
return str.slice(0, 1).toUpperCase() + str.slice(1);
}

export async function addAssetToCharacter(
plugin: ForgedPlugin,
editor: Editor,
view: MarkdownView,
_editor: Editor,
_view: MarkdownView,
): Promise<void> {
const [path, context] = plugin.characters.activeCharacter();
const { character, lens } = context;
Expand All @@ -37,7 +34,7 @@ export async function addAssetToCharacter(
plugin.app,
availableAssets,
(asset) => asset.name,
({ item: asset, match }: FuzzyMatch<Asset>, el: HTMLElement) => {
({ item: asset }: FuzzyMatch<Asset>, el: HTMLElement) => {
el.createEl("small", {
text:
asset.category +
Expand All @@ -61,7 +58,7 @@ export async function addAssetToCharacter(
const choice = await CustomSuggestModal.select(
plugin.app,
Object.entries(optionControl.choices),
([choiceKey, choice]) => choice.label,
([_choiceKey, choice]) => choice.label,
undefined,
titleCase(optionControl.label),
);
Expand Down
51 changes: 35 additions & 16 deletions src/characters/lens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,14 +340,24 @@ export function conditionMetersReader(
});
}

export const MOMENTUM_METER_DEFINITION: MeterWithoutLens<ConditionMeterDefinition> =
{
key: "momentum",
lens: undefined,
value: undefined,
definition: new ConditionMeterDefinition({
label: "momentum",
min: -6,
max: 10,
rollable: true,
}),
};

export function meterLenses(
charLens: CharacterLens,
character: ValidatedCharacter,
dataIndex: DataIndex,
): Record<
string,
{ key: string; definition: ConditionMeterDefinition; lens: CharLens<number> }
> {
): Record<string, MeterWithLens<ConditionMeterDefinition>> {
const baseMeters = Object.fromEntries(
Object.entries(charLens.condition_meters).map(([key, lens]) => [
key,
Expand Down Expand Up @@ -377,37 +387,46 @@ export function meterLenses(
.map((val) => [val.key, val]);
return {
...baseMeters,
momentum: {
key: "momentum",
lens: charLens.momentum,
definition: new ConditionMeterDefinition({
label: "momentum",
min: -6,
max: 10,
rollable: true,
}),
},
momentum: { ...MOMENTUM_METER_DEFINITION, lens: charLens.momentum },
...Object.fromEntries(allAssetMeters),
};
}

export type KeyWithDefinition<T> = { key: string; definition: T };

export type WithCharLens<Base, T> = Base & { lens: CharLens<T>; value: T };
export type WithoutCharLens<Base> = Base & {
lens: undefined;
value: undefined;
};

export type MeterWithoutLens<T extends MeterCommon = MeterCommon> =
WithoutCharLens<KeyWithDefinition<T>>;

export type MeterWithLens<T extends MeterCommon = MeterCommon> = WithCharLens<
KeyWithDefinition<T>,
number
>;

export function rollablesReader(
charLens: CharacterLens,
dataIndex: DataIndex,
): CharReader<{ key: string; value: number; definition: MeterCommon }[]> {
): CharReader<MeterWithLens[]> {
return reader((character) => {
return [
...Object.values(meterLenses(charLens, character, dataIndex)).map(
({ key, definition, lens }) => ({
key,
definition,
lens,
value: lens.get(character),
}),
),
...Object.entries(charLens.stats).map(([key, lens]) => ({
key,
value: lens.get(character),
definition: charLens.ruleset.stats[key],
lens,
value: lens.get(character),
})),
];
});
Expand Down
Loading

0 comments on commit 16af6a3

Please sign in to comment.