- 5 Biomes: Day, Night, Pool, Fog, Roof (25 Campaign levels) - Minigames: Wall-nut Bowling, I Zombie, Roguelike, Boss Rush & Dr. Zomboss - Systems: Zen Garden, Workshop Crafting, Almanac, Tree of Wisdom, Achievements - Backend: REST API + SQLite Accounts & Save Synchronization - Infrastructure: Nginx HTTPS & Docker deployment
272 lines
7.7 KiB
JavaScript
272 lines
7.7 KiB
JavaScript
// Achievements & Trophy System for Plants vs. Zombies
|
|
window.AchievementSystem = class AchievementSystem {
|
|
constructor(gameEngine) {
|
|
this.engine = gameEngine;
|
|
|
|
this.achievements = [
|
|
{
|
|
id: 'first_plant',
|
|
title: 'Первый росток',
|
|
desc: 'Посадите любое растение на газоне',
|
|
icon: '🌱',
|
|
reward: { coins: 50 },
|
|
progress: 0,
|
|
max: 1,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'sun_tycoon',
|
|
title: 'Солнечный магнат',
|
|
desc: 'Накопите 1000 солнца в одном матче',
|
|
icon: '☀️',
|
|
reward: { coins: 150 },
|
|
progress: 0,
|
|
max: 1000,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'nut_wall',
|
|
title: 'Ореховая стена',
|
|
desc: 'Выдержите 30 укусов зомби стеноорехом',
|
|
icon: '🌰',
|
|
reward: { coins: 100 },
|
|
progress: 0,
|
|
max: 30,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'spudow_master',
|
|
title: 'SPUDOW!',
|
|
desc: 'Уничтожьте зомби картофельной миной',
|
|
icon: '🥔',
|
|
reward: { coins: 120 },
|
|
progress: 0,
|
|
max: 1,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'ice_age',
|
|
title: 'Ледниковый период',
|
|
desc: 'Замедлите 25 зомби ледяным горохом',
|
|
icon: '❄️',
|
|
reward: { coins: 150 },
|
|
progress: 0,
|
|
max: 25,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'fire_jalapeno',
|
|
title: 'Огненный шквал',
|
|
desc: 'Испепелите дорожку зомби Халапеньо',
|
|
icon: '🌶️',
|
|
reward: { coins: 120 },
|
|
progress: 0,
|
|
max: 1,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'bowling_strike',
|
|
title: 'Мастер боулинга',
|
|
desc: 'Сделайте рикошет орехом в режиме Боулинг',
|
|
icon: '🎳',
|
|
reward: { coins: 150 },
|
|
progress: 0,
|
|
max: 1,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'izombie_win',
|
|
title: 'Я, Гений!',
|
|
desc: 'Одержите победу в режиме «Я, Зомби»',
|
|
icon: '🧠',
|
|
reward: { coins: 200 },
|
|
progress: 0,
|
|
max: 1,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'tier3_unlocked',
|
|
title: 'Зеленый инженер',
|
|
desc: 'Прокачайте любое растение до 3★ в Мастерской',
|
|
icon: '🧪',
|
|
reward: { coins: 300, crystals: 1 },
|
|
progress: 0,
|
|
max: 1,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'zen_blooming',
|
|
title: 'Магнат Дзен-Сада',
|
|
desc: 'Вырастите взрослое цветущее растение в горшке',
|
|
icon: '🪴',
|
|
reward: { coins: 200 },
|
|
progress: 0,
|
|
max: 1,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'snail_feed',
|
|
title: 'Сытая улитка',
|
|
desc: 'Покормите улитку Стинки шоколадом',
|
|
icon: '🐌',
|
|
reward: { coins: 100 },
|
|
progress: 0,
|
|
max: 1,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'zombie_slayer',
|
|
title: 'Истребитель нежити',
|
|
desc: 'Уничтожьте 100 зомби в любых битвах',
|
|
icon: '⚔️',
|
|
reward: { coins: 300, crystals: 1 },
|
|
progress: 0,
|
|
max: 100,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'clean_lawn',
|
|
title: 'Идеальная оборона',
|
|
desc: 'Пройдите уровень без срабатывания газонокосилок',
|
|
icon: '✨',
|
|
reward: { coins: 250 },
|
|
progress: 0,
|
|
max: 1,
|
|
completed: false,
|
|
claimed: false
|
|
},
|
|
{
|
|
id: 'roguelike_relic',
|
|
title: 'Искатель реликвий',
|
|
desc: 'Выберите реликвию в режиме «Садовый Рогалик»',
|
|
icon: '🎲',
|
|
reward: { coins: 200 },
|
|
progress: 0,
|
|
max: 1,
|
|
completed: false,
|
|
claimed: false
|
|
}
|
|
];
|
|
|
|
this.loadFromAuth();
|
|
}
|
|
|
|
loadFromAuth() {
|
|
for (const a of this.achievements) {
|
|
a.progress = 0;
|
|
a.completed = false;
|
|
a.claimed = false;
|
|
}
|
|
let loadedData = null;
|
|
if (window.PvZAuth && !window.PvZAuth.isGuest()) {
|
|
const uData = window.PvZAuth.getUserData();
|
|
if (uData.achievements) loadedData = uData.achievements;
|
|
}
|
|
if (!loadedData) {
|
|
try {
|
|
loadedData = JSON.parse(localStorage.getItem('pvz_achievements') || 'null');
|
|
} catch (e) {}
|
|
}
|
|
if (loadedData) {
|
|
for (const [id, saved] of Object.entries(loadedData)) {
|
|
const ach = this.achievements.find(a => a.id === id);
|
|
if (ach) {
|
|
ach.progress = saved.progress || 0;
|
|
ach.completed = saved.completed || false;
|
|
ach.claimed = saved.claimed || false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
saveToAuth() {
|
|
const map = {};
|
|
for (const a of this.achievements) {
|
|
map[a.id] = { progress: a.progress, completed: a.completed, claimed: a.claimed };
|
|
}
|
|
try {
|
|
localStorage.setItem('pvz_achievements', JSON.stringify(map));
|
|
} catch (e) {}
|
|
if (window.PvZAuth && !window.PvZAuth.isGuest()) {
|
|
window.PvZAuth.saveUserData({ achievements: map });
|
|
}
|
|
}
|
|
|
|
trigger(id, amount = 1) {
|
|
const ach = this.achievements.find(a => a.id === id);
|
|
if (!ach || ach.completed) return;
|
|
|
|
ach.progress = Math.min(ach.progress + amount, ach.max);
|
|
if (ach.progress >= ach.max) {
|
|
ach.completed = true;
|
|
this.showToast(ach);
|
|
if (window.PvZAudio) window.PvZAudio.playWin();
|
|
}
|
|
this.saveToAuth();
|
|
}
|
|
|
|
setProgress(id, val) {
|
|
const ach = this.achievements.find(a => a.id === id);
|
|
if (!ach || ach.completed) return;
|
|
|
|
ach.progress = Math.max(ach.progress, Math.min(val, ach.max));
|
|
if (ach.progress >= ach.max) {
|
|
ach.completed = true;
|
|
this.showToast(ach);
|
|
if (window.PvZAudio) window.PvZAudio.playWin();
|
|
}
|
|
this.saveToAuth();
|
|
}
|
|
|
|
claimReward(id) {
|
|
const ach = this.achievements.find(a => a.id === id);
|
|
if (!ach || !ach.completed || ach.claimed) return false;
|
|
|
|
ach.claimed = true;
|
|
if (ach.reward.coins && this.engine && this.engine.economy) {
|
|
this.engine.economy.addCoins(ach.reward.coins);
|
|
}
|
|
if (ach.reward.crystals && this.engine && this.engine.workshop) {
|
|
this.engine.workshop.addMaterial('crystals', ach.reward.crystals);
|
|
}
|
|
if (window.PvZAudio) window.PvZAudio.playCoin();
|
|
this.saveToAuth();
|
|
return true;
|
|
}
|
|
|
|
showToast(ach) {
|
|
let toast = document.getElementById('achievement-toast');
|
|
if (!toast) {
|
|
toast = document.createElement('div');
|
|
toast.id = 'achievement-toast';
|
|
toast.className = 'achievement-toast';
|
|
document.body.appendChild(toast);
|
|
}
|
|
|
|
toast.innerHTML = `
|
|
<div class="ach-toast-icon">${ach.icon}</div>
|
|
<div class="ach-toast-text">
|
|
<div class="ach-toast-subtitle">ДОСТИЖЕНИЕ РАЗБЛОКИРОВАНО!</div>
|
|
<div class="ach-toast-title">${ach.title}</div>
|
|
</div>
|
|
`;
|
|
|
|
toast.classList.add('visible');
|
|
setTimeout(() => {
|
|
toast.classList.remove('visible');
|
|
}, 4000);
|
|
}
|
|
};
|