import { DAILY_REWARDS, DAILY_TASK_POOLS, WEEKLY_REWARDS, } from "@/dailyTaskConfig"; import type { DailyTask, DailyWeeklyTaskData } from "@/dailyTaskConfig"; const DAY_MS = 86400000; const SHANGHAI_OFFSET_MS = 8 * 3600000; export function getDayKey(nowMs = Date.now()): string { return new Date(nowMs + SHANGHAI_OFFSET_MS).toISOString().slice(0, 10); } export function getWeekKey(nowMs = Date.now()): string { const shifted = new Date(nowMs + SHANGHAI_OFFSET_MS); const day = shifted.getUTCDay() || 7; return new Date(shifted.getTime() - (day - 1) * DAY_MS).toISOString().slice(0, 10); } export function getWeekEndsAt(nowMs = Date.now()): number { const weekKey = getWeekKey(nowMs); return Date.parse(`${weekKey}T00:00:00+08:00`) + 7 * DAY_MS; } function hash(input: string): number { let value = 2166136261; for (let i = 0; i < input.length; i++) { value ^= input.charCodeAt(i); value = Math.imul(value, 16777619); } return value >>> 0; } function selectDailyTasks(uid: string, dayKey: string, level: number, previousTaskIds: string[]): DailyTask[] { return [2, 3, 4, 5, 6].map((stars, slot) => { const pool = DAILY_TASK_POOLS[stars].filter(candidate => (candidate.minLevel || 1) <= level); const withoutPrevious = pool.filter(candidate => previousTaskIds[slot] !== candidate.taskId); const candidates = withoutPrevious.length > 0 ? withoutPrevious : pool; const selected = candidates[hash(`${uid}:${dayKey}:${stars}`) % candidates.length]; return { slot, taskId: selected.taskId, metric: selected.metric, target: selected.target, value: selected.metric === "login" ? 1 : 0, stars, state: selected.metric === "login" ? 1 : 0, locked: slot !== 0, rewards: DAILY_REWARDS[stars].map(reward => ({ ...reward })), ...(selected.params ? { params: { ...selected.params } } : {}), } as DailyTask; }); } export function refreshTaskData( stored: unknown, uid: string, level: number, nowMs = Date.now(), ): { data: DailyWeeklyTaskData; changed: boolean } { const dayKey = getDayKey(nowMs); const weekKey = getWeekKey(nowMs); const weekEndsAt = getWeekEndsAt(nowMs); const parsed = typeof stored === "string" ? safeParse(stored) : stored; const valid = parsed && (parsed as DailyWeeklyTaskData).version === 2 && Array.isArray((parsed as DailyWeeklyTaskData).dailyTasks) && (parsed as DailyWeeklyTaskData).dailyTasks.length === 5 && Array.isArray((parsed as DailyWeeklyTaskData).weeklyRewards); const old = valid ? parsed as DailyWeeklyTaskData : null; const newWeek = !old || old.weekKey !== weekKey; const newDay = !old || old.dayKey !== dayKey; if (!newDay && !newWeek) { const changed = old.weekEndsAt !== weekEndsAt; old.weekEndsAt = weekEndsAt; old.serverNow = nowMs; applyLocks(old.dailyTasks); applyWeeklyStates(old); return { data: old, changed }; } const previousTaskIds = old ? old.dailyTasks.map(task => task.taskId) : []; const data: DailyWeeklyTaskData = { version: 2, dayKey, weekKey, weekEndsAt, serverNow: nowMs, dailyTasks: selectDailyTasks(uid, dayKey, Math.max(1, level), previousTaskIds), weeklyStars: newWeek ? 0 : Number(old?.weeklyStars) || 0, weeklyRewards: newWeek ? WEEKLY_REWARDS.map(reward => ({ ...reward, rewards: reward.rewards.map(item => ({ ...item })) })) : old!.weeklyRewards, previousTaskIds, }; applyWeeklyStates(data); return { data, changed: true }; } export function mergeClientProgress(data: DailyWeeklyTaskData, submitted: unknown): void { const parsed = typeof submitted === "string" ? safeParse(submitted) : submitted; const submittedTasks = Array.isArray((parsed as any)?.dailyTasks) ? (parsed as any).dailyTasks : []; const byId = new Map(submittedTasks.map((task: any) => [task.taskId, task])); data.dailyTasks.forEach(task => { const incoming: any = byId.get(task.taskId); if (!incoming) return; const value = Number(incoming.value); if (Number.isFinite(value)) task.value = Math.max(0, Math.min(task.target, value)); if (task.state !== 2) task.state = task.value >= task.target ? 1 : 0; }); applyLocks(data.dailyTasks); } export function claimDaily(data: DailyWeeklyTaskData, taskId: string) { const task = data.dailyTasks.find(item => item.taskId === taskId); if (!task) return { ok: false, msg: "任务不存在" }; if (task.locked) return { ok: false, msg: "请先领取上一阶段奖励" }; if (task.state === 2) return { ok: false, msg: "不能重复领取奖励" }; if (task.value < task.target || task.state !== 1) return { ok: false, msg: "请先完成任务" }; task.state = 2; data.weeklyStars = Math.min(140, data.weeklyStars + task.stars); applyLocks(data.dailyTasks); applyWeeklyStates(data); return { ok: true, rewards: task.rewards }; } export function claimWeekly(data: DailyWeeklyTaskData, rewardId: string) { const reward = data.weeklyRewards.find(item => item.rewardId === rewardId); if (!reward) return { ok: false, msg: "周奖励不存在" }; if (reward.state === 2) return { ok: false, msg: "不能重复领取奖励" }; if (data.weeklyStars < reward.target) return { ok: false, msg: "星星数量不足" }; reward.state = 2; return { ok: true, rewards: reward.rewards }; } function applyLocks(tasks: DailyTask[]): void { tasks.forEach((task, index) => { task.locked = index > 0 && tasks[index - 1].state !== 2; if (task.state !== 2 && task.value >= task.target) task.state = 1; }); } function applyWeeklyStates(data: DailyWeeklyTaskData): void { data.weeklyRewards.forEach(reward => { if (reward.state !== 2) reward.state = data.weeklyStars >= reward.target ? 1 : 0; }); } function safeParse(value: string): unknown { try { return JSON.parse(value); } catch (_) { return null; } }