forked from xiangwang25/whale-town-end-v2
190 lines
4.9 KiB
TypeScript
190 lines
4.9 KiB
TypeScript
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;
|
|
}
|