diff --git a/README.md b/README.md index 705c6939..638a766d 100644 --- a/README.md +++ b/README.md @@ -265,19 +265,19 @@ Box/tick amounts can be given either as a single `from` argument, or with - `from-boxes` (optional): the starting value of the progress track, in boxes. - `from-ticks` (optional): the starting value of the progress track, in ticks filled into the last unfilled box. -- `level` - the difficulty level of the progress track (e.g. `"formidable"`, `"epic"`, etc). +- `rank` - the challenge rank of the progress track (e.g. `"formidable"`, `"epic"`, etc). - `steps` (optional, default: 1) - number of times to mark progress. ##### Example ```kdl -progress "My Background Vow" from-boxes=3 from-ticks=2 level="formidable" steps=2 +progress "My Background Vow" from-boxes=3 from-ticks=2 rank="formidable" steps=2 ``` #### `track` Marks progress on a progress track. Can be used interchangeably with -`progress`, but doesn't encode the track difficulty level or the number of +`progress`, but doesn't encode the track challenge rank or the number of times progress was marked. Most often, this node would be used for moves that say something like "erase two ticks from TKTK". diff --git a/src/characters/lens.test.ts b/src/characters/lens.test.ts index 4cd3bd68..c0a3f149 100644 --- a/src/characters/lens.test.ts +++ b/src/characters/lens.test.ts @@ -506,7 +506,7 @@ describe("Special Tracks", () => { progress: 4, unbounded: true, complete: false, - difficulty: ChallengeRanks.Epic, + rank: ChallengeRanks.Epic, }); }); diff --git a/src/characters/lens.ts b/src/characters/lens.ts index 0ac853b9..9544407d 100644 --- a/src/characters/lens.ts +++ b/src/characters/lens.ts @@ -97,7 +97,7 @@ function legacyTrack( lens: { get(source) { return ProgressTrack.create_({ - difficulty: ChallengeRanks.Epic, + rank: ChallengeRanks.Epic, // SAFE: a validated character will satisfy the above schema progress: source[progressKey] as number, complete: false, diff --git a/src/mechanics/css/dlist-progress.css b/src/mechanics/css/dlist-progress.css index 35bb61c0..3caaa2d1 100644 --- a/src/mechanics/css/dlist-progress.css +++ b/src/mechanics/css/dlist-progress.css @@ -29,7 +29,7 @@ } } - &.level { + &.rank { text-transform: capitalize; color: var(--text-faint); &:before { diff --git a/src/mechanics/mechanics-blocks.ts b/src/mechanics/mechanics-blocks.ts index cbbc38a3..d14f1940 100644 --- a/src/mechanics/mechanics-blocks.ts +++ b/src/mechanics/mechanics-blocks.ts @@ -312,7 +312,7 @@ export class MechanicsRenderer { node.properties.from ?? ((node.properties["from-boxes"] as number) ?? 0) * 4 + ((node.properties["from-ticks"] as number) ?? 0), - difficulty: node.properties.level, + rank: node.properties.rank ?? node.properties.level, unbounded: (node.properties.unbounded ?? false) as boolean, complete: false, }); @@ -327,7 +327,7 @@ export class MechanicsRenderer { const startTrack = result.value; const [fromBoxes, fromTicks] = startTrack.boxesAndTicks(); - const level = startTrack.difficulty; + const rank = startTrack.rank; const steps = (node.properties.steps ?? node.values[3] ?? 1) as number; const endTrack = startTrack.advanced(steps); @@ -338,7 +338,7 @@ export class MechanicsRenderer { cls: "steps " + (steps < 0 ? "negative" : "positive"), value: steps, }, - Level: { cls: "level", value: level }, + Rank: { cls: "rank", value: rank }, "From Boxes": { cls: "from-boxes", value: fromBoxes }, "From Ticks": { cls: "from-ticks", value: fromTicks }, "To Boxes": { cls: "to-boxes", value: toBoxes }, diff --git a/src/mechanics/node-builders.ts b/src/mechanics/node-builders.ts index bb2ec952..e97feda3 100644 --- a/src/mechanics/node-builders.ts +++ b/src/mechanics/node-builders.ts @@ -12,7 +12,7 @@ export function createProgressNode( properties: { name: `[[${trackContext.location}|${trackContext.name}]]`, from: trackContext.track.progress, - level: trackContext.track.difficulty, + rank: trackContext.track.rank, steps, }, }); diff --git a/src/tracks/progress-create.ts b/src/tracks/progress-create.ts index aa3318ea..fad35ad3 100644 --- a/src/tracks/progress-create.ts +++ b/src/tracks/progress-create.ts @@ -67,7 +67,7 @@ function generateTrackName(name: string): string { export class ProgressTrackCreateModal extends Modal { public result = { - difficulty: ChallengeRanks.Dangerous, + rank: ChallengeRanks.Dangerous, progress: 0, name: "", tracktype: "", @@ -114,13 +114,13 @@ export class ProgressTrackCreateModal extends Modal { // TODO: since the string value equals the display string, i don't actually know if this // is working as intended with the options - new Setting(contentEl).setName("Difficulty").addDropdown((dropdown) => + new Setting(contentEl).setName("Rank").addDropdown((dropdown) => dropdown .addOptions(ChallengeRanks) .onChange((value) => { - this.result.difficulty = value as ChallengeRanks; + this.result.rank = value as ChallengeRanks; }) - .setValue(this.result.difficulty), + .setValue(this.result.rank), ); new Setting(contentEl).setName("Type").addSearch((search) => { @@ -166,7 +166,7 @@ export class ProgressTrackCreateModal extends Modal { tracktype: this.result.tracktype, fileName: this.result.fileName, track: ProgressTrack.create_({ - difficulty: this.result.difficulty, + rank: this.result.rank, progress: this.result.progress, complete: false, unbounded: false, diff --git a/src/tracks/progress.test.ts b/src/tracks/progress.test.ts index 21b4dac8..4f1b9c14 100644 --- a/src/tracks/progress.test.ts +++ b/src/tracks/progress.test.ts @@ -12,7 +12,7 @@ import { describe("ProgressTrack", () => { const TEST_DATA: ProgressTrackSchema = { - difficulty: ChallengeRanks.Dangerous, + rank: ChallengeRanks.Dangerous, progress: 10, complete: false, unbounded: false, @@ -27,22 +27,22 @@ describe("ProgressTrack", () => { expect(result.isRight()).toBeTruthy(); const track = result.unwrap(); expect(track).toMatchObject({ - difficulty: ChallengeRanks.Dangerous, + rank: ChallengeRanks.Dangerous, progress: 10, complete: false, unbounded: false, }); }); - it("interprets the difficulty case insensitively", () => { + it("interprets the rank case insensitively", () => { const result = ProgressTrack.create({ ...TEST_DATA, - difficulty: "DANGERous", + rank: "DANGERous", }); expect(result.isRight()).toBeTruthy(); const track = result.unwrap(); expect(track).toMatchObject({ - difficulty: ChallengeRanks.Dangerous, + rank: ChallengeRanks.Dangerous, progress: 10, complete: false, unbounded: false, @@ -61,7 +61,7 @@ describe("ProgressTrack", () => { { rank: ChallengeRanks.Extreme, steps: 1 }, { rank: ChallengeRanks.Extreme, steps: 2 }, ])("advances $rank track by $steps steps", ({ rank, steps }) => { - const start = make({ difficulty: rank }); + const start = make({ rank: rank }); const startingProgress = start.progress; expect(start.advanced(steps).progress).toBe( startingProgress + CHALLENGE_STEPS[rank] * steps, @@ -106,9 +106,7 @@ describe("ProgressTrack", () => { ])( "#stepsRemaining calculates $steps steps from $ticks ticks for $rank", ({ ticks, steps, rank }) => { - expect(make({ progress: ticks, difficulty: rank }).stepsRemaining).toBe( - steps, - ); + expect(make({ progress: ticks, rank: rank }).stepsRemaining).toBe(steps); }, ); }); @@ -116,7 +114,7 @@ describe("ProgressTrack", () => { describe("ProgressTrackFileAdapter", () => { const TEST_DATA = { Name: "Test", - Difficulty: "Dangerous", + rank: "Dangerous", Progress: 10, tags: "incomplete", TrackImage: "[[progress-track-10.svg]]", @@ -124,8 +122,8 @@ describe("ProgressTrackFileAdapter", () => { }; function make( - overrides: Omit, "Difficulty"> & { - Difficulty?: ChallengeRanks | string; + overrides: Omit, "rank"> & { + rank?: ChallengeRanks | string; } = {}, ): Either { return ProgressTrackFileAdapter.create( @@ -141,8 +139,8 @@ describe("ProgressTrackFileAdapter", () => { } function make_( - overrides: Omit, "Difficulty"> & { - Difficulty?: ChallengeRanks | string; + overrides: Omit, "rank"> & { + Rank?: ChallengeRanks | string; } = {}, ): ProgressTrackFileAdapter { return make(overrides).unwrap(); @@ -151,7 +149,7 @@ describe("ProgressTrackFileAdapter", () => { it("#track extracts the progress track data", () => { expect(make_().track).toEqual( ProgressTrack.create_({ - difficulty: ChallengeRanks.Dangerous, + rank: ChallengeRanks.Dangerous, progress: 10, complete: false, unbounded: false, @@ -159,6 +157,13 @@ describe("ProgressTrackFileAdapter", () => { ); }); + it("parses a track with Difficulty instead of rank", () => { + expect( + make_({ rank: undefined, Difficulty: ChallengeRanks.Troublesome }).track + .rank, + ).toEqual(ChallengeRanks.Troublesome); + }); + it("requires a completion tag", () => { expect(make({ tags: ["missing_completion"] })).toEqual( Left.create( @@ -255,7 +260,7 @@ describe("legacyTrackXpEarned", () => { expect( legacyTrackXpEarned( ProgressTrack.create_({ - difficulty: ChallengeRanks.Epic, + rank: ChallengeRanks.Epic, progress: boxes * 4, complete: false, unbounded: true, diff --git a/src/tracks/progress.ts b/src/tracks/progress.ts index 06aea376..4a8728ba 100644 --- a/src/tracks/progress.ts +++ b/src/tracks/progress.ts @@ -36,10 +36,11 @@ export const challengeRankSchema = z.preprocess( z.nativeEnum(ChallengeRanks), ); -export const progressTrackerSchema = z +/** Schema for progress track files. */ +export const baseProgressTrackerSchema = z .object({ Name: z.string(), - Difficulty: challengeRankSchema, + rank: challengeRankSchema, Progress: z.number().int().nonnegative().default(0), tags: z .union([z.string().transform((arg) => [arg]), z.array(z.string())]) @@ -61,12 +62,26 @@ export const progressTrackerSchema = z }) .passthrough(); +export const progressTrackerSchema = z.union([ + baseProgressTrackerSchema, + baseProgressTrackerSchema + .omit({ rank: true }) + .merge( + z.object({ + Difficulty: baseProgressTrackerSchema.shape.rank, + }), + ) + .passthrough() + .transform(({ Difficulty, ...rest }) => ({ ...rest, rank: Difficulty })), +]); + export type ProgressTrackerInputSchema = z.input; export type ProgressTrackerSchema = z.infer; +/** Validation for progress track domain model object. */ export const progressTrackSchema = z .object({ - difficulty: challengeRankSchema, + rank: challengeRankSchema, progress: z.number().int().nonnegative(), complete: z.boolean(), unbounded: z.boolean(), @@ -82,7 +97,7 @@ export class ProgressTrack { /** * Challenge rank for this track */ - readonly difficulty: ChallengeRanks; + readonly rank: ChallengeRanks; /** * Current progress (in ticks) @@ -117,14 +132,14 @@ export class ProgressTrack { } private constructor(data: ProgressTrackSchema) { - this.difficulty = data.difficulty; + this.rank = data.rank; this.progress = data.progress; this.complete = data.complete; this.unbounded = data.unbounded; } get ticksPerStep(): number { - return CHALLENGE_STEPS[this.difficulty]; + return CHALLENGE_STEPS[this.rank]; } get boxesFilled(): number { @@ -173,7 +188,7 @@ export class ProgressTrack { return ( this.progress === other.progress && this.complete === other.complete && - this.difficulty === other.difficulty && + this.rank === other.rank && this.unbounded === other.unbounded ); } @@ -218,7 +233,7 @@ export class ProgressTrackFileAdapter implements ProgressTrackInfo { return this.create( { Name: name, - Difficulty: track.difficulty, + rank: track.rank, Progress: track.progress, tags: track.complete ? ["complete"] : ["incomplete"], TrackImage: settings.generateTrackImage(track), @@ -237,7 +252,7 @@ export class ProgressTrackFileAdapter implements ProgressTrackInfo { if (result.success) { const raw = result.data; return ProgressTrack.create({ - difficulty: raw.Difficulty, + rank: raw.rank, progress: raw.Progress, complete: raw.tags.includes("complete"), unbounded: false, @@ -259,7 +274,7 @@ export class ProgressTrackFileAdapter implements ProgressTrackInfo { produce(this.raw, (data) => { data.Progress = other.progress; data.TrackImage = this.settings.generateTrackImage(other); - data.Difficulty = other.difficulty; + data.rank = other.rank; const [tagToRemove, tagToAdd] = other.complete ? ["incomplete", "complete"] : ["complete", "incomplete"];