feat: Initial commit Plants vs Zombies Web Edition v2.4
- 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
This commit is contained in:
commit
6457a8904e
50 files changed
+14486
No files matched your search
@@ -0,0 +1,271 @@
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user