From 97b2766c899a7f1df5af9a14f9b6453048ba24cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Tue, 11 Mar 2025 14:14:32 -0400 Subject: [PATCH] monitor initialization --- .../src/tools/achievements/event-processor.ts | 83 +++++++++++++------ libs/common/src/tools/achievements/meta.ts | 11 +++ 2 files changed, 68 insertions(+), 26 deletions(-) create mode 100644 libs/common/src/tools/achievements/meta.ts diff --git a/libs/common/src/tools/achievements/event-processor.ts b/libs/common/src/tools/achievements/event-processor.ts index b706bef2324..e9df09e6615 100644 --- a/libs/common/src/tools/achievements/event-processor.ts +++ b/libs/common/src/tools/achievements/event-processor.ts @@ -1,34 +1,65 @@ -import { - Observable, - OperatorFunction, - concatMap, - filter, - from, - map, - pipe, - withLatestFrom, -} from "rxjs"; +import { Observable, OperatorFunction, concatMap, from, map, pipe, withLatestFrom } from "rxjs"; import { EventFormat } from "../log/ecs-format"; -import { achievementMonitors$, achievementsLocal$, userActionIn$ } from "./inputs"; -import { AchievementFormat, AchievementWatch } from "./types"; +import { + achievementMonitors$, + achievementsLocal$ as achievementsLog$, + userActionIn$, +} from "./inputs"; +import { isProgress } from "./meta"; +import { AchievementFormat, AchievementWatch, Earned, Progress } from "./types"; -// the formal even processor -function validate( - achievements$: Observable, +// OPTIMIZATION: compute the list of active monitors from trigger criteria +function active( status$: Observable, -): OperatorFunction { - // compute list of active monitors - const monitors$ = achievements$.pipe( - withLatestFrom(achievementsLocal$), - filter(([monitors, local]) => { - // 🧩 TODO: filter out inactive monitors by reviewing local store - // and interpreting triggers. - return true; +): OperatorFunction { + return pipe( + withLatestFrom(status$), + map(([monitors, log]) => { + // partition the log into progress and earned achievements + const progress: Progress[] = []; + const earned: Earned[] = []; + for (const l of log) { + if (isProgress(l.achievement)) { + progress.push(l.achievement); + } else { + earned.push(l.achievement); + } + } + + const progressByName = new Map(progress.map((a) => [a.name, a.value])); + const earnedByName = new Set(earned.map((e) => e.name)); + + // compute list of active achievements + const active = monitors.filter((m) => { + if (m.trigger === "once") { + // monitor disabled if already achieved + return !earnedByName.has(m.achievement); + } + + // monitor disabled if outside of threshold + const progress = progressByName.get(m.achievement) ?? 0; + if (m.trigger.high ?? Number.POSITIVE_INFINITY < progress) { + return false; + } else if (m.trigger.low ?? 0 > progress) { + return false; + } + + // otherwise you're within the threshold, so the monitor is active + return true; + }); + + return active; }), ); +} +// the formal event processor +function validate( + monitors$: Observable, + status$: Observable, +): OperatorFunction { // analyze the incoming event stream to identify achievements const processor = pipe( withLatestFrom(monitors$), @@ -48,10 +79,10 @@ function validate( return processor; } +const liveMonitors$ = achievementMonitors$.pipe(active(achievementsLog$)); + // pre-wired achievement stream; this is the prototype's host, and // in the full version is wired by the application -const validatedAchievements$ = userActionIn$.pipe( - validate(achievementMonitors$, achievementsLocal$), -); +const validatedAchievements$ = userActionIn$.pipe(validate(liveMonitors$, achievementsLog$)); export { validate, validatedAchievements$ }; diff --git a/libs/common/src/tools/achievements/meta.ts b/libs/common/src/tools/achievements/meta.ts new file mode 100644 index 00000000000..54556be4cd0 --- /dev/null +++ b/libs/common/src/tools/achievements/meta.ts @@ -0,0 +1,11 @@ +import { Earned, Progress } from "./types"; + +function isProgress(achievement: any): achievement is Progress { + return achievement.type === "progress" && "value" in achievement; +} + +function isEarned(achievement: any): achievement is Earned { + return !isProgress(achievement); +} + +export { isProgress, isEarned };