feat(polish & stats): add wallnut degradation states, hit flash, decapitation particle, frost crystals, dave dialogues and lifetime player stats
This commit is contained in:
1 parent
23918dab1f
commit
643b0287c1
10 files changed
+1260
-65
No files matched your search
+135
-20
@@ -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();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user