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:
Antigravity committed 2026-09-17 09:30:52 +00:00
commit 6457a8904e
50 files changed
+14486

No files matched your search

+271
View File
@@ -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);
}
};