feat: add battle pass handler with quests, rewards, XP

This commit is contained in:
achmad
2026-05-29 16:36:05 +07:00
parent 44784682ae
commit 62611a3685
+200
View File
@@ -0,0 +1,200 @@
import { route, HandlerContext, HttpError } from '@/lib/router';
import { getDb } from '@/lib/db';
const QUEST_DEFS = [
{ quest_id: 'kill_zombies_1', type: 'kill_zombies', name: 'Zombie Slayer I', description: 'Kill 100 zombies', target: 100, reward_exp: 50, reward_free_currency: 100 },
{ quest_id: 'kill_zombies_2', type: 'kill_zombies', name: 'Zombie Slayer II', description: 'Kill 500 zombies', target: 500, reward_exp: 100, reward_free_currency: 250 },
{ quest_id: 'survive_time_1', type: 'survive_time', name: 'Survivor I', description: 'Survive for 600 seconds', target: 600, reward_exp: 30, reward_free_currency: 50 },
{ quest_id: 'survive_waves_1', type: 'survive_waves', name: 'Wave Breaker I', description: 'Survive 10 waves', target: 10, reward_exp: 40, reward_free_currency: 75 },
{ quest_id: 'buy_black_shop_1', type: 'buy_black_shop', name: 'Black Shopper I', description: 'Buy 5 items from Black Shop', target: 5, reward_exp: 25, reward_free_currency: 50 },
{ quest_id: 'complete_npc_quest_1', type: 'complete_npc_quest', name: 'Helper I', description: 'Complete 3 NPC quests', target: 3, reward_exp: 35, reward_free_currency: 60 },
{ quest_id: 'earn_gold_1', type: 'earn_gold', name: 'Gold Rush I', description: 'Earn 5000 gold', target: 5000, reward_exp: 45, reward_free_currency: 100 },
{ quest_id: 'hero_level_1', type: 'hero_level', name: 'Stronger I', description: 'Reach level 10', target: 10, reward_exp: 30, reward_free_currency: 50 },
{ quest_id: 'cook_grilled_meat_1', type: 'cook_grilled_meat', name: 'Chef I', description: 'Cook grilled meat', target: 1, reward_exp: 20, reward_free_currency: 25 },
{ quest_id: 'use_campfire_1', type: 'use_campfire', name: 'Camper I', description: 'Use campfire 5 times', target: 5, reward_exp: 15, reward_free_currency: 25 },
{ quest_id: 'tip_teammate_1', type: 'tip_teammate', name: 'Friendly I', description: 'Tip teammates 3 times', target: 3, reward_exp: 20, reward_free_currency: 30 },
{ quest_id: 'deal_damage_1', type: 'deal_damage', name: 'Berserker I', description: 'Deal 50000 damage', target: 50000, reward_exp: 60, reward_free_currency: 150 },
{ quest_id: 'collect_item_1', type: 'collect_item', name: 'Collector I', description: 'Collect a rare item', target: 1, reward_exp: 40, reward_free_currency: 80, target_item: 'rare' },
];
// POST /battlepass — Create or ensure BP exists for a player, assign default quests
route('battlepass', ['POST'], (ctx: HandlerContext) => {
const { steam_id } = ctx.body as any;
if (!steam_id) throw new HttpError(400, 'steam_id required');
const db = getDb();
db.prepare('INSERT OR IGNORE INTO battle_passes (steam_id, level, experience) VALUES (?, 0, 0)').run(steam_id);
const existing = db.prepare('SELECT COUNT(*) as c FROM battle_pass_quests WHERE steam_id = ?').get(steam_id) as any;
if (existing.c === 0) {
const insert = db.prepare(`
INSERT INTO battle_pass_quests (steam_id, quest_id, type, name, description, target, reward_exp, reward_free_currency)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
for (const q of QUEST_DEFS) {
insert.run(steam_id, q.quest_id, q.type, q.name, q.description, q.target, q.reward_exp, q.reward_free_currency);
}
}
return { success: true };
});
// GET /battlepass/:steamId — Get BP data
route('battlepass/:steamId', ['GET'], (ctx: HandlerContext) => {
const db = getDb();
let bp = db.prepare('SELECT * FROM battle_passes WHERE steam_id = ?').get(ctx.params.steamId) as any;
if (!bp) {
db.prepare('INSERT OR IGNORE INTO battle_passes (steam_id) VALUES (?)').run(ctx.params.steamId);
bp = db.prepare('SELECT * FROM battle_passes WHERE steam_id = ?').get(ctx.params.steamId);
}
return {
level: bp.level,
experience: bp.experience,
has_premium: bp.has_premium === 1,
claimed_rewards: JSON.parse(bp.claimed_rewards || '[]'),
claimed_premium_rewards: JSON.parse(bp.claimed_premium_rewards || '[]'),
};
});
// GET /battlepass/:steamId/quests — Get quests for a player
route('battlepass/:steamId/quests', ['GET'], (ctx: HandlerContext) => {
const db = getDb();
const quests = db.prepare('SELECT * FROM battle_pass_quests WHERE steam_id = ? ORDER BY id').all(ctx.params.steamId);
return { quests };
});
// POST /battlepass/:steamId/quests/progress — Sync quest progress
route('battlepass/:steamId/quests/progress', ['POST'], (ctx: HandlerContext) => {
const { quest_id, progress } = ctx.body as any;
if (!quest_id) throw new HttpError(400, 'quest_id required');
const db = getDb();
const quest = db.prepare('SELECT * FROM battle_pass_quests WHERE steam_id = ? AND quest_id = ?').get(ctx.params.steamId, quest_id) as any;
if (!quest) {
db.prepare(`INSERT INTO battle_pass_quests (steam_id, quest_id, type, name, target, progress) VALUES (?, ?, 'custom', ?, 1, ?)`)
.run(ctx.params.steamId, quest_id, quest_id, progress || 0);
return { success: true, completed: false, progress: progress || 0 };
}
const newProgress = Math.min(progress ?? quest.progress, quest.target);
const completed = newProgress >= quest.target ? 1 : 0;
db.prepare('UPDATE battle_pass_quests SET progress = ?, completed = ?, updated_at = datetime(\'now\') WHERE id = ?')
.run(newProgress, completed, quest.id);
return { success: true, completed: completed === 1, progress: newProgress };
});
// POST /battlepass/:steamId/quests/claim — Claim a quest reward
route('battlepass/:steamId/quests/claim', ['POST'], (ctx: HandlerContext) => {
const { quest_id } = ctx.body as any;
if (!quest_id) throw new HttpError(400, 'quest_id required');
const db = getDb();
const quest = db.prepare('SELECT * FROM battle_pass_quests WHERE steam_id = ? AND quest_id = ?').get(ctx.params.steamId, quest_id) as any;
if (!quest) throw new HttpError(404, 'Quest not found');
if (!quest.completed) throw new HttpError(400, 'Quest not completed');
if (quest.claimed) throw new HttpError(400, 'Already claimed');
db.prepare('UPDATE battle_pass_quests SET claimed = 1, updated_at = datetime(\'now\') WHERE id = ?').run(quest.id);
db.prepare('UPDATE players SET free_currency = free_currency + ?, updated_at = datetime(\'now\') WHERE steam_id = ?')
.run(quest.reward_free_currency, ctx.params.steamId);
db.prepare('UPDATE battle_passes SET experience = experience + ?, updated_at = datetime(\'now\') WHERE steam_id = ?')
.run(quest.reward_exp, ctx.params.steamId);
const bp = db.prepare('SELECT level, experience FROM battle_passes WHERE steam_id = ?').get(ctx.params.steamId) as any;
return {
success: true,
reward_exp: quest.reward_exp,
reward_free_currency: quest.reward_free_currency,
new_level: bp.level,
new_experience: bp.experience,
};
});
// POST /battlepass/:steamId/hero-played — Record a hero being played (fire-and-forget)
route('battlepass/:steamId/hero-played', ['POST'], (ctx: HandlerContext) => {
return { success: true };
});
// POST /battlepass/:steamId/claim — Claim a free BP level reward
route('battlepass/:steamId/claim', ['POST'], (ctx: HandlerContext) => {
const { steam_id, level } = ctx.body as any;
const db = getDb();
const bp = db.prepare('SELECT * FROM battle_passes WHERE steam_id = ?').get(ctx.params.steamId) as any;
if (!bp) throw new HttpError(404, 'BP not found');
let claimed = JSON.parse(bp.claimed_rewards || '[]');
if (!claimed.includes(level)) {
claimed.push(level);
db.prepare("UPDATE battle_passes SET claimed_rewards = ?, updated_at = datetime('now') WHERE steam_id = ?")
.run(JSON.stringify(claimed), ctx.params.steamId);
}
return { success: true, level, currency_granted: { free_currency: level * 250, donate_currency: 0 } };
});
// POST /battlepass/:steamId/claim-premium — Claim a premium BP level reward
route('battlepass/:steamId/claim-premium', ['POST'], (ctx: HandlerContext) => {
const { steam_id, level } = ctx.body as any;
const db = getDb();
const bp = db.prepare('SELECT * FROM battle_passes WHERE steam_id = ?').get(ctx.params.steamId) as any;
if (!bp) throw new HttpError(404, 'BP not found');
let claimed = JSON.parse(bp.claimed_premium_rewards || '[]');
if (!claimed.includes(level)) {
claimed.push(level);
db.prepare("UPDATE battle_passes SET claimed_premium_rewards = ?, updated_at = datetime('now') WHERE steam_id = ?")
.run(JSON.stringify(claimed), ctx.params.steamId);
}
return { success: true, level, currency_granted: { free_currency: level * 250, donate_currency: level * 100 } };
});
// POST /battlepass/:steamId/claim-all — Claim all rewards up to current level
route('battlepass/:steamId/claim-all', ['POST'], (ctx: HandlerContext) => {
const { steam_id } = ctx.body as any;
const db = getDb();
const bp = db.prepare('SELECT * FROM battle_passes WHERE steam_id = ?').get(ctx.params.steamId) as any;
if (!bp) throw new HttpError(404, 'BP not found');
const unclaimedFree: number[] = [];
const unclaimedPremium: number[] = [];
const claimedFree = JSON.parse(bp.claimed_rewards || '[]') as number[];
const claimedPremium = JSON.parse(bp.claimed_premium_rewards || '[]') as number[];
for (let lvl = 1; lvl <= bp.level; lvl++) {
if (!claimedFree.includes(lvl)) unclaimedFree.push(lvl);
if (bp.has_premium && !claimedPremium.includes(lvl)) unclaimedPremium.push(lvl);
}
db.prepare("UPDATE battle_passes SET claimed_rewards = ?, claimed_premium_rewards = ?, updated_at = datetime('now') WHERE steam_id = ?")
.run(JSON.stringify([...claimedFree, ...unclaimedFree]),
JSON.stringify([...claimedPremium, ...unclaimedPremium]),
ctx.params.steamId);
return {
success: true,
free_levels: unclaimedFree,
premium_levels: unclaimedPremium,
currency_granted: {
free_currency: unclaimedFree.length * 250 + unclaimedPremium.length * 250,
donate_currency: unclaimedPremium.length * 100,
},
};
});
// POST /battlepass/:steamId/buy-premium — Activate premium BP
route('battlepass/:steamId/buy-premium', ['POST'], (ctx: HandlerContext) => {
const db = getDb();
db.prepare("UPDATE battle_passes SET has_premium = 1, updated_at = datetime('now') WHERE steam_id = ?")
.run(ctx.params.steamId);
return { success: true };
});
// POST /battlepass/:steamId/addexp — Add experience to BP
route('battlepass/:steamId/addexp', ['POST'], (ctx: HandlerContext) => {
const { experience } = ctx.body as any;
const db = getDb();
const bp = db.prepare('SELECT * FROM battle_passes WHERE steam_id = ?').get(ctx.params.steamId) as any;
if (!bp) throw new HttpError(404, 'BP not found');
const newExp = bp.experience + (experience || 0);
const levelUp = Math.floor(newExp / 1000);
const newLevel = bp.level + levelUp;
const remainder = newExp % 1000;
db.prepare('UPDATE battle_passes SET experience = ?, level = ?, updated_at = datetime(\'now\') WHERE steam_id = ?')
.run(remainder, newLevel, ctx.params.steamId);
return { level: newLevel, experience: remainder, level_up: levelUp > 0 };
});