feat: add task reward economy APIs

This commit is contained in:
ANG-Server
2026-07-22 00:45:59 +08:00
parent 9119737f11
commit fe52ab1696
14 changed files with 824 additions and 2 deletions

View File

@@ -0,0 +1,189 @@
export const NEWBIE_CYCLE_KEY = 'newbie';
export const TASK_ACTIVITY_TYPES = [
'guide_opened',
'notice_viewed',
'map_visited',
'course_board_opened',
'facility_interacted',
'public_message_sent',
'skin_purchased',
] as const;
export type TaskActivityType = typeof TASK_ACTIVITY_TYPES[number];
export type TaskGroup = 'newbie' | 'weekly';
export type TaskProgressMode = 'count' | 'unique_target';
export interface TaskDefinition {
id: string;
group: TaskGroup;
title: string;
description: string;
reward: number;
target: number;
activity?: TaskActivityType;
progress_mode?: TaskProgressMode;
allowed_targets?: string[];
optional?: boolean;
bonus?: boolean;
sort_order: number;
}
export interface WeeklyCycle {
key: string;
starts_at: string;
ends_at: string;
}
export interface TaskProgressState {
targets?: string[];
}
export const NEWBIE_TASKS: TaskDefinition[] = [
{
id: 'newbie_guide',
group: 'newbie',
title: '翻阅新人手册',
description: '打开新人引导,了解鲸镇的基本操作。',
reward: 40,
target: 1,
activity: 'guide_opened',
sort_order: 10,
},
{
id: 'newbie_notice',
group: 'newbie',
title: '查看镇务公告',
description: '在广场查看一次公告栏。',
reward: 60,
target: 1,
activity: 'notice_viewed',
sort_order: 20,
},
{
id: 'newbie_work_zone',
group: 'newbie',
title: '探索打工区',
description: '前往打工区,看看小镇的工作与学习入口。',
reward: 80,
target: 1,
activity: 'map_visited',
allowed_targets: ['work_zone'],
sort_order: 30,
},
{
id: 'newbie_course_board',
group: 'newbie',
title: '浏览课程板',
description: '在打工区打开 Datawhale 课程看板。',
reward: 120,
target: 1,
activity: 'course_board_opened',
sort_order: 40,
},
{
id: 'newbie_first_skin',
group: 'newbie',
title: '选择你的形象',
description: '在鲸鱼商城购买任意一款皮肤。此任务可跳过。',
reward: 100,
target: 1,
activity: 'skin_purchased',
optional: true,
sort_order: 50,
},
];
export const WEEKLY_TASKS: TaskDefinition[] = [
{
id: 'weekly_explore',
group: 'weekly',
title: '海风巡游',
description: '探索两个不同的开放地图。',
reward: 100,
target: 2,
activity: 'map_visited',
progress_mode: 'unique_target',
allowed_targets: ['square', 'work_zone', 'whale_cafe'],
sort_order: 10,
},
{
id: 'weekly_course',
group: 'weekly',
title: '本周学习计划',
description: '打开一次 Datawhale 课程看板。',
reward: 100,
target: 1,
activity: 'course_board_opened',
sort_order: 20,
},
{
id: 'weekly_interact',
group: 'weekly',
title: '和小镇打招呼',
description: '与两个不同的公共设施或 NPC 互动。',
reward: 100,
target: 2,
activity: 'facility_interacted',
progress_mode: 'unique_target',
allowed_targets: ['welcome_board', 'notice_board', 'npc'],
sort_order: 30,
},
{
id: 'weekly_public_message',
group: 'weekly',
title: '分享此刻',
description: '在公共频道成功发送一条消息。',
reward: 100,
target: 1,
activity: 'public_message_sent',
sort_order: 40,
},
];
export const WEEKLY_COMPLETION_BONUS: TaskDefinition = {
id: 'weekly_completion_bonus',
group: 'weekly',
title: '本周任务书结算',
description: '完成本周全部四项任务后领取额外奖励。',
reward: 200,
target: 1,
bonus: true,
sort_order: 90,
};
export function getCurrentWeeklyCycle(now: Date = new Date()): WeeklyCycle {
const formatter = new Intl.DateTimeFormat('en-CA', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
const parts = Object.fromEntries(formatter.formatToParts(now)
.filter((part) => part.type !== 'literal')
.map((part) => [part.type, part.value]));
const year = Number(parts.year);
const month = Number(parts.month);
const day = Number(parts.day);
const chinaDateAsUtc = Date.UTC(year, month - 1, day);
const weekday = new Date(chinaDateAsUtc).getUTCDay();
const daysSinceMonday = (weekday + 6) % 7;
const mondayAsUtc = chinaDateAsUtc - daysSinceMonday * 24 * 60 * 60 * 1000;
const monday = new Date(mondayAsUtc);
const cycleDate = monday.toISOString().slice(0, 10);
const startsAt = new Date(mondayAsUtc - 8 * 60 * 60 * 1000);
const endsAt = new Date(startsAt.getTime() + 7 * 24 * 60 * 60 * 1000);
return {
key: `weekly:${cycleDate}`,
starts_at: startsAt.toISOString(),
ends_at: endsAt.toISOString(),
};
}
export function getTaskDefinitions(): TaskDefinition[] {
return [...NEWBIE_TASKS, ...WEEKLY_TASKS, WEEKLY_COMPLETION_BONUS];
}
export function getTaskCycleKey(definition: TaskDefinition, cycle: WeeklyCycle): string {
return definition.group === 'weekly' ? cycle.key : NEWBIE_CYCLE_KEY;
}