feat(polish & stats): add wallnut degradation states, hit flash, decapitation particle, frost crystals, dave dialogues and lifetime player stats

This commit is contained in:
Antigravity committed 2026-09-17 13:28:33 +00:00
1 parent 23918dab1f
commit 643b0287c1
10 files changed
+1260 -65

No files matched your search

+135 -20
View File
@@ -80,6 +80,13 @@ window.GameEngine = class GameEngine {
// Score & Persistence
this.score = 0;
this.zombiesKilled = 0;
this.stats = {
zombiesKilled: 0,
sunProduced: 0,
plantsPlanted: 0,
levelsCompleted: 0,
coinsEarned: 0
};
this.loadUserData();
this.updateRecordDisplay();
this.updateProfileUI();
@@ -110,6 +117,13 @@ window.GameEngine = class GameEngine {
this.maxUnlockedLevel = uData.maxUnlockedLevel || Math.max(this.currentLevel, 1);
this.highScore = uData.highScore || 0;
this.currentTheme = uData.theme || 'classic';
this.stats = uData.stats || {
zombiesKilled: 0,
sunProduced: 0,
plantsPlanted: 0,
levelsCompleted: 0,
coinsEarned: 0
};
this.applyTheme(this.currentTheme);
if (this.economy) this.economy.reloadProfileData();
if (this.workshop) this.workshop.loadFromAuth();
@@ -122,10 +136,30 @@ window.GameEngine = class GameEngine {
this.maxUnlockedLevel = parseInt(localStorage.getItem('pvz_max_level') || this.currentLevel.toString(), 10);
this.highScore = parseInt(localStorage.getItem('pvz_highscore') || '0', 10);
this.currentTheme = localStorage.getItem('pvz_theme') || 'classic';
try {
const rawStats = localStorage.getItem('pvz_stats');
this.stats = rawStats ? JSON.parse(rawStats) : {
zombiesKilled: 0,
sunProduced: 0,
plantsPlanted: 0,
levelsCompleted: 0,
coinsEarned: 0
};
} catch (_) {
this.stats = { zombiesKilled: 0, sunProduced: 0, plantsPlanted: 0, levelsCompleted: 0, coinsEarned: 0 };
}
this.applyTheme(this.currentTheme);
}
}
recordStat(key, amount = 1) {
if (!this.stats) {
this.stats = { zombiesKilled: 0, sunProduced: 0, plantsPlanted: 0, levelsCompleted: 0, coinsEarned: 0 };
}
this.stats[key] = (this.stats[key] || 0) + amount;
this.saveUserData();
}
saveUserData() {
if (window.PvZAuth) {
if (this.achievements) this.achievements.saveToAuth();
@@ -137,6 +171,7 @@ window.GameEngine = class GameEngine {
maxUnlockedLevel: this.maxUnlockedLevel,
highScore: this.highScore,
theme: this.currentTheme,
stats: this.stats,
upgrades: this.economy ? this.economy.upgrades : undefined,
materials: this.workshop ? this.workshop.materials : undefined,
plantLevels: this.workshop ? this.workshop.plantLevels : undefined,
@@ -154,6 +189,7 @@ window.GameEngine = class GameEngine {
localStorage.setItem('pvz_max_level', this.maxUnlockedLevel.toString());
localStorage.setItem('pvz_highscore', this.highScore.toString());
localStorage.setItem('pvz_theme', this.currentTheme);
localStorage.setItem('pvz_stats', JSON.stringify(this.stats));
if (this.economy) {
localStorage.setItem('pvz_coins', this.economy.totalCoins.toString());
}
@@ -269,6 +305,20 @@ window.GameEngine = class GameEngine {
const totalMats = (mats.bioHumus || 0) + (mats.scrapMetal || 0) + (mats.sunEssence || 0) + (mats.crystals || 0);
statMaterials.textContent = `${totalMats} шт.`;
}
// Extended lifetime player stats
const stats = uData.stats || this.stats || {};
const statZombies = document.getElementById('profile-stat-zombies');
const statSun = document.getElementById('profile-stat-sun');
const statPlants = document.getElementById('profile-stat-plants');
const statLevelsWon = document.getElementById('profile-stat-levels-won');
const statCoinsEarned = document.getElementById('profile-stat-coins-earned');
if (statZombies) statZombies.textContent = (stats.zombiesKilled || 0).toLocaleString('ru-RU');
if (statSun) statSun.textContent = (stats.sunProduced || 0).toLocaleString('ru-RU');
if (statPlants) statPlants.textContent = (stats.plantsPlanted || 0).toLocaleString('ru-RU');
if (statLevelsWon) statLevelsWon.textContent = (stats.levelsCompleted || 0).toLocaleString('ru-RU');
if (statCoinsEarned) statCoinsEarned.textContent = (stats.coinsEarned || 0).toLocaleString('ru-RU');
}
}
}
@@ -438,6 +488,39 @@ window.GameEngine = class GameEngine {
this.plants.push(p43);
}
showDaveDialogue(level, mode, onContinue) {
const dialogue = window.CampaignSystem ? window.CampaignSystem.getDaveDialogue(level, mode) : null;
if (!dialogue) {
if (typeof onContinue === 'function') onContinue();
return;
}
const modal = document.getElementById('dave-modal');
const speakerEl = document.getElementById('dave-modal-speaker');
const subEl = document.getElementById('dave-modal-subtitle');
const textEl = document.getElementById('dave-modal-text');
const btnContinue = document.getElementById('btn-dave-continue');
if (speakerEl) speakerEl.textContent = dialogue.title || 'Безумный Дэйв';
if (subEl) subEl.textContent = 'Твой чудаковатый сосед';
if (textEl) textEl.textContent = `«${dialogue.text}»`;
if (btnContinue && dialogue.btnText) btnContinue.textContent = dialogue.btnText;
if (modal) modal.classList.remove('hidden');
const handleContinue = () => {
if (modal) modal.classList.add('hidden');
if (window.PvZAudio) window.PvZAudio.playButtonClick();
if (typeof onContinue === 'function') onContinue();
};
if (btnContinue) {
const newBtn = btnContinue.cloneNode(true);
btnContinue.parentNode.replaceChild(newBtn, btnContinue);
newBtn.addEventListener('click', handleContinue);
}
}
init() {
let startLvl = 1;
if (this.currentMode === 'adventure') {
@@ -446,17 +529,19 @@ window.GameEngine = class GameEngine {
startLvl = this.roguelike ? this.roguelike.currentFloor : 1;
}
// Fixed-conveyor mini-games launch immediately, standard modes open deck builder
if (this.currentMode === 'bowling' || this.currentMode === 'izombie' || this.currentMode === 'boss' || this.currentMode === 'bossrush') {
this.startLevel(startLvl, this.currentMode);
} else {
this.openSeedSelectionModal(startLvl, this.currentMode);
}
this.showDaveDialogue(startLvl, this.currentMode, () => {
// Fixed-conveyor mini-games launch immediately, standard modes open deck builder
if (this.currentMode === 'bowling' || this.currentMode === 'izombie' || this.currentMode === 'boss' || this.currentMode === 'bossrush') {
this.startLevel(startLvl, this.currentMode);
} else {
this.openSeedSelectionModal(startLvl, this.currentMode);
}
if (!this.loopStarted) {
this.loopStarted = true;
requestAnimationFrame(this.loop.bind(this));
}
if (!this.loopStarted) {
this.loopStarted = true;
requestAnimationFrame(this.loop.bind(this));
}
});
}
reset() {
@@ -494,6 +579,9 @@ window.GameEngine = class GameEngine {
if (this.roguelike && this.currentMode === 'roguelike' && this.roguelike.hasRelic('relic_sun_resonance') && !isFalling) {
finalValue += 15;
}
if (!isFalling) {
this.recordStat('sunProduced', finalValue);
}
this.economy.spawnSun(x, y, finalValue, isFalling, targetY);
}
@@ -524,6 +612,7 @@ window.GameEngine = class GameEngine {
if (z.health <= 0) {
this.addScore(250);
this.zombiesKilled++;
this.recordStat('zombiesKilled', 1);
if (Math.random() < 0.4) this.spawnCoin(z.x, z.y, 'gold');
}
}
@@ -567,6 +656,7 @@ window.GameEngine = class GameEngine {
if (this.isVictory) return;
this.isVictory = true;
this.isRunning = false;
this.recordStat('levelsCompleted', 1);
this.addScore(1000);
this.economy.addCoins(100); // 100 coin bonus for level completion!
@@ -707,6 +797,9 @@ window.GameEngine = class GameEngine {
this.canvas.addEventListener('mousemove', (e) => {
const pos = this.getEventPos(e);
this.hoverCell = this.grid.getCellFromCoords(pos.x, pos.y);
if (this.economy) {
this.economy.handleMouseMove(pos.x, pos.y);
}
if (this.isRunning) {
this.checkTouchCollect(pos);
}
@@ -714,6 +807,9 @@ window.GameEngine = class GameEngine {
this.canvas.addEventListener('mouseleave', () => {
this.hoverCell = null;
if (this.economy) {
this.economy.handleMouseMove(-1, -1);
}
});
this.canvas.addEventListener('click', (e) => {
@@ -736,6 +832,9 @@ window.GameEngine = class GameEngine {
// Mobile touch with Drag & Drop and continuous swipe-collect
this.canvas.addEventListener('touchstart', (e) => {
if (!this.isRunning) return;
if (this.economy) {
this.economy.handleMouseMove(-1, -1);
}
const pos = this.getEventPos(e);
this.checkTouchCollect(pos);
@@ -1000,7 +1099,15 @@ window.GameEngine = class GameEngine {
const btnRestartWin = document.getElementById('btn-restart-win');
if (btnRestartWin) {
btnRestartWin.addEventListener('click', () => {
this.startLevel(this.currentLevel, this.currentMode);
const victoryModal = document.getElementById('victory-modal');
if (victoryModal) victoryModal.classList.add('hidden');
this.showDaveDialogue(this.currentLevel, this.currentMode, () => {
if (this.currentMode === 'bowling' || this.currentMode === 'izombie' || this.currentMode === 'boss' || this.currentMode === 'bossrush' || (this.currentMode === 'adventure' && this.currentLevel === 25)) {
this.startLevel(this.currentLevel, this.currentMode);
} else {
this.openSeedSelectionModal(this.currentLevel, this.currentMode);
}
});
});
}
@@ -1810,15 +1917,17 @@ window.GameEngine = class GameEngine {
if (startModal) startModal.classList.add('hidden');
if (window.PvZAudio) window.PvZAudio.init();
if (targetLevel === 25) {
this.startLevel(targetLevel, 'adventure');
} else {
this.openSeedSelectionModal(targetLevel, 'adventure');
}
if (!this.loopStarted) {
this.loopStarted = true;
requestAnimationFrame(this.loop.bind(this));
}
this.showDaveDialogue(targetLevel, 'adventure', () => {
if (targetLevel === 25) {
this.startLevel(targetLevel, 'adventure');
} else {
this.openSeedSelectionModal(targetLevel, 'adventure');
}
if (!this.loopStarted) {
this.loopStarted = true;
requestAnimationFrame(this.loop.bind(this));
}
});
}
});
});
@@ -2817,6 +2926,7 @@ window.GameEngine = class GameEngine {
const plant = this.createPlant(card.id, col, row, bounds);
this.grid.setPlant(col, row, plant);
this.plants.push(plant);
this.recordStat('plantsPlanted', 1);
this.economy.selectedCard = null;
if (window.PvZAudio) window.PvZAudio.playPlant();
}
@@ -2838,6 +2948,7 @@ window.GameEngine = class GameEngine {
const plant = this.createPlant('lilypad', col, row, bounds);
this.grid.setPlant(col, row, plant);
this.plants.push(plant);
this.recordStat('plantsPlanted', 1);
this.economy.selectedCard = null;
if (window.PvZAudio) window.PvZAudio.playPlant();
}
@@ -2851,6 +2962,7 @@ window.GameEngine = class GameEngine {
const plant = this.createPlant(card.id, col, row, bounds);
this.grid.setPlant(col, row, plant);
this.plants.push(plant);
this.recordStat('plantsPlanted', 1);
this.economy.selectedCard = null;
if (window.PvZAudio) window.PvZAudio.playPlant();
}
@@ -2871,6 +2983,7 @@ window.GameEngine = class GameEngine {
const plant = this.createPlant('flowerpot', col, row, bounds);
this.grid.setPlant(col, row, plant);
this.plants.push(plant);
this.recordStat('plantsPlanted', 1);
this.economy.selectedCard = null;
if (window.PvZAudio) window.PvZAudio.playPlant();
}
@@ -2885,6 +2998,7 @@ window.GameEngine = class GameEngine {
const plant = this.createPlant(card.id, col, row, bounds);
this.grid.setPlant(col, row, plant);
this.plants.push(plant);
this.recordStat('plantsPlanted', 1);
this.economy.selectedCard = null;
if (window.PvZAudio) window.PvZAudio.playPlant();
}
@@ -2905,6 +3019,7 @@ window.GameEngine = class GameEngine {
const plant = this.createPlant(card.id, col, row, bounds);
this.grid.setPlant(col, row, plant);
this.plants.push(plant);
this.recordStat('plantsPlanted', 1);
this.economy.selectedCard = null;
if (window.PvZAudio) window.PvZAudio.playPlant();
}