'use strict'; /* ============================================================ NEON SURVIVORS — achievements.js Declarative achievement list + evaluator. Names are embedded bilingual strings (kept out of i18n dicts on purpose). cond(save, G) -> boolean against persisted progress only. ============================================================ */ const ACHS = [ // ---- combat milestones ---- { id: 'kill100', icon: '⚔️', vi: 'Thợ Săn Mới', en: 'Rookie Hunter', dvi: 'Diệt 100 quái', den: 'Kill 100 enemies', cond: s => s.totals.kills >= 100 }, { id: 'kill500', icon: '🗡️', vi: 'Kẻ Trừng Phạt', en: 'Executioner', dvi: 'Diệt 500 quái', den: 'Kill 500 enemies', cond: s => s.totals.kills >= 500 }, { id: 'kill2500', icon: '💀', vi: 'Tử Thần Nhỏ', en: 'Petite Reaper', dvi: 'Diệt 2.500 quái', den: 'Kill 2,500 enemies', cond: s => s.totals.kills >= 2500 }, { id: 'kill10k', icon: '☠️', vi: 'Tai Họa Đi Bộ', en: 'Walking Calamity', dvi: 'Diệt 10.000 quái', den: 'Kill 10,000 enemies', cond: s => s.totals.kills >= 10000 }, // ---- wealth ---- { id: 'gold1k', icon: '🪙', vi: 'Giàu Có', en: 'Coin Purse', dvi: 'Kiếm 1.000 vàng lifetime', den: 'Earn 1,000 gold lifetime', cond: s => s.totals.gold >= 1000 }, { id: 'gold10k', icon: '💰', vi: 'Đại Gia Vàng', en: 'Gold Baron', dvi: 'Kiếm 10.000 vàng lifetime', den: 'Earn 10,000 gold lifetime', cond: s => s.totals.gold >= 10000 }, // ---- survival time ---- { id: 'time5', icon: '⏱️', vi: 'Làm quen', en: 'Warmed Up', dvi: 'Sống sót 5 phút', den: 'Survive 5 minutes', cond: s => s.totals.best >= 300 }, { id: 'time10', icon: '⏳', vi: 'Bền Bỉ', en: 'Tenacious', dvi: 'Sống sót 10 phút', den: 'Survive 10 minutes', cond: s => s.totals.best >= 600 }, { id: 'time15', icon: '🌙', vi: 'Dòng Chảy', en: 'In The Zone', dvi: 'Sống sót 15 phút', den: 'Survive 15 minutes', cond: s => s.totals.best >= 900 }, { id: 'time20', icon: '🌅', vi: 'Bình Minh', en: 'Daybreak', dvi: 'Sống sót đủ 20 phút (thắng)', den: 'Survive the full 20 minutes (win)', cond: s => s.totals.best >= 1200 || s.totals.wins >= 1 }, // ---- victories & difficulty ---- { id: 'win1', icon: '👑', vi: 'Kết Thúc Cuộc Ngủ', en: 'Wake The Dead', dvi: 'Hạ Tử Thần — thắng 1 ván', den: 'Slay Death — win a run', cond: s => s.totals.wins >= 1 }, { id: 'win5', icon: '🏆', vi: 'Vua Sống Sót', en: 'Survivor King', dvi: 'Thắng 5 ván', den: 'Win 5 runs', cond: s => s.totals.wins >= 5 }, { id: 'grade3', icon: '💀', vi: 'Vô Cầu Vị', en: 'No Mercy', dvi: 'Thắng ở Độ khó 💀 (III+)', den: 'Win on Grade 💀 (III+)', cond: s => (s.progress.gradeWins[2] || 0) + (s.progress.gradeWins[3] || 0) >= 1 }, // ---- stages & endless ---- { id: 'st_frost', icon: '❄️', vi: 'Phá Băng', en: 'Ice Breaker', dvi: 'Thắng Hang Băng', den: 'Win Frozen Hollow', cond: s => !!s.progress.stageWins.frostcave }, { id: 'st_magma', icon: '🌋', vi: 'Chúa Tể Núi Lửa', en: 'Volcano Lord', dvi: 'Thắng Vực Magma', den: 'Win Magma Rift', cond: s => !!s.progress.stageWins.magmalands }, { id: 'endless25', icon: '∞', vi: 'Không Điểm Dừng', en: 'No Stop Sign', dvi: 'Endless đạt 25 phút', den: 'Reach minute 25 in Endless', cond: s => (s.progress.endlessBest || 0) >= 1500 }, // ---- collection ---- { id: 'evo1', icon: '🌟', vi: 'Tiến Hóa!', en: 'Evolve!', dvi: 'Tiến hóa 1 vũ khí', den: 'Evolve a weapon', cond: s => (s.totals.evos || 0) >= 1 }, { id: 'evo6', icon: '✨', vi: 'Nhà Sưu Tầm', en: 'Collector', dvi: 'Tiến hóa 6 lần (tổng mọi ván)', den: 'Evolve 6 times (across runs)', cond: s => (s.totals.evos || 0) >= 6 }, { id: 'chars_all', icon: '🎭', vi: 'Full Dàn Sao', en: 'Full Roster', dvi: 'Mở khóa tất cả nhân vật', den: 'Unlock every character', cond: s => (s.chars || []).length >= Object.keys(CHARS).length }, { id: 'meta10', icon: '🔧', vi: 'Nâng Cấp Giỏi', en: 'Well Oiled', dvi: 'Mua tổng cộng 10 cấp cửa hàng', den: 'Buy 10 total meta-shop ranks', cond: s => Object.values(s.meta || {}).reduce((a, b) => a + b, 0) >= 10 }, // ---- misc ---- { id: 'props50', icon: '💎', vi: 'Phá Phách', en: 'Smasher', dvi: 'Phá 50 đèn pha lê', den: 'Break 50 crystal lamps', cond: s => (s.totals.props || 0) >= 50 }, { id: 'daily1', icon: '⚡', vi: 'Người Của Hôm Nay', en: 'Daily Daredevil', dvi: 'Hoàn thành 1 Thử thách hôm nay', den: 'Finish a Daily Challenge', cond: s => (s.daily.best || 0) > 0 } ]; /** Evaluate all; unlock new ones into save.ach and return their defs. */ function checkAchievements() { const save = Store.data; const newly = []; for (const a of ACHS) { if (!save.ach[a.id] && _safe(a.cond, save)) { save.ach[a.id] = Date.now(); newly.push(a); } } if (newly.length) Store.save(); return newly; } function _safe(fn, save) { try { return !!fn(save); } catch (e) { return false; } } /** Achievement count helper for menu/codex badges. */ function achProgress() { let n = 0; for (const a of ACHS) if (Store.data.ach[a.id]) n++; return { n, total: ACHS.length }; }