"use server"; import config from "@payload-config"; import { getPayload } from "payload"; import { OPORD_DIFFICULTIES, QUIZ_TIME_GRACE_MS, calculateOpordAccuracy, calculateOpordScore, calculateOpordXp, isOpordDifficulty, minOpordRunTimeMs, type OpordDifficulty, } from "@/lib/opord-recall"; import { emitGameEvent } from "@/utils/event-log/emit"; import { EventTypes } from "@/utils/event-log/eventTypes"; import { broadcastToUser } from "@/lib/realtime/bus"; interface ActionResult { success: boolean; error?: string; data?: T; } export interface OpordStats { gamesPlayed: number; bestScore: number; bestAccuracy: number; recruitBestScore: number; veteranBestScore: number; eliteBestScore: number; opordRecallXp: number; xpEarned: number; score: number; accuracy: number; } export type OpordRunInput = { difficulty: OpordDifficulty; correct: number; timeMs: number; }; async function authenticate() { const payloadConfig = await config; const payload = await getPayload({ config: payloadConfig }); const { headers } = await import("next/headers"); const hdrs = await headers(); const { user } = await payload.auth({ headers: hdrs, canSetHeaders: false, }); if (!user) { throw new Error("Unauthorized"); } return { payload, user }; } export async function submitOpordRecallResult( input: OpordRunInput, ): Promise> { try { const { payload, user } = await authenticate(); if (!isOpordDifficulty(input.difficulty)) { return { success: false, error: "Invalid difficulty." }; } const tier = OPORD_DIFFICULTIES[input.difficulty]; const { correct, timeMs } = input; const minTimeMs = minOpordRunTimeMs(input.difficulty); const maxTimeMs = tier.quizSeconds * 1000 + QUIZ_TIME_GRACE_MS; if ( !Number.isInteger(correct) || correct < 0 || correct > tier.questionCount || typeof timeMs !== "number" || !Number.isFinite(timeMs) || timeMs < minTimeMs || timeMs > maxTimeMs ) { return { success: false, error: "Invalid result." }; } const profileRes = await payload.find({ collection: "profiles", where: { user: { equals: user.id as number } }, limit: 1, depth: 0, overrideAccess: true, }); const profile = profileRes.docs[0]; if (!profile) { return { success: false, error: "No profile found for this user." }; } const progression = (profile.progression ?? {}) as { xp?: unknown; minigames?: { opordRecall?: { gamesPlayed?: number; bestScore?: number; bestAccuracy?: number; recruitBestScore?: number; veteranBestScore?: number; eliteBestScore?: number; xpEarned?: number; }; }; }; const current = progression.minigames?.opordRecall ?? {}; const score = calculateOpordScore(input.difficulty, correct, timeMs); const accuracy = calculateOpordAccuracy(correct, tier.questionCount); const tierBestScoreKey = `${input.difficulty}BestScore` as | "recruitBestScore" | "veteranBestScore" | "eliteBestScore"; const isNewTierBest = score > (current[tierBestScoreKey] ?? 0); const xp = calculateOpordXp(score, isNewTierBest); const gamesPlayed = (current.gamesPlayed ?? 0) + 1; const bestScore = Math.max(current.bestScore ?? 0, score); const bestAccuracy = Math.round(Math.max(current.bestAccuracy ?? 0, accuracy) * 10000) / 10000; const tierBestScore = Math.max(current[tierBestScoreKey] ?? 0, score); const opordRecallXp = (current.xpEarned ?? 0) + xp; const recruitBestScore = input.difficulty === "recruit" ? tierBestScore : (current.recruitBestScore ?? 0); const veteranBestScore = input.difficulty === "veteran" ? tierBestScore : (current.veteranBestScore ?? 0); const eliteBestScore = input.difficulty === "elite" ? tierBestScore : (current.eliteBestScore ?? 0); const { xp: _xp, ...progressionRest } = progression; await payload.update({ collection: "profiles", id: profile.id, data: { progression: { ...progressionRest, minigames: { ...(progressionRest.minigames ?? {}), opordRecall: { gamesPlayed, bestScore, bestAccuracy, recruitBestScore, veteranBestScore, eliteBestScore, xpEarned: opordRecallXp, }, }, }, }, overrideAccess: true, depth: 0, }); broadcastToUser("profile-update", Number(user.id), { reason: "xp", xpGained: xp, profileId: profile.id, }); if (score > 0) { const pct = Math.round(accuracy * 100); await emitGameEvent(payload, { type: EventTypes.MinigameOpordRecall, message: `recalled an ${input.difficulty} briefing with ${correct}/${tier.questionCount} details correct (${pct}% accuracy, +${xp} XP)`, actor: user.id as number, targetCollection: "users", targetId: user.id as number, data: { difficulty: input.difficulty, correct, total: tier.questionCount, score, accuracy, timeMs: Math.round(timeMs), xpEarned: xp, bestScore, tierBestScore, opordRecallXp, }, }); } return { success: true, data: { gamesPlayed, bestScore, bestAccuracy, recruitBestScore, veteranBestScore, eliteBestScore, opordRecallXp, xpEarned: xp, score, accuracy, }, }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; if (message === "Unauthorized") { return { success: false, error: "You must be logged in." }; } return { success: false, error: message }; } }