feat(qol & backup): add wave progress bar, hover collection, area explosion preview, hotkeys, auto-sync and json save export/import

This commit is contained in:
2026-09-17 13:20:29 +00:00
parent 7cd0792799
commit 23918dab1f
7 changed files with 710 additions and 38 deletions
+96 -1
View File
@@ -677,12 +677,20 @@ window.GameEngine = class GameEngine {
}
checkTouchCollect(pos) {
if (!this.isRunning) return;
for (let i = this.economy.suns.length - 1; i >= 0; i--) {
const s = this.economy.suns[i];
if (!s.collecting && Math.hypot(pos.x - s.x, pos.y - s.y) <= s.radius + 28) {
s.collecting = true;
this.triggerHaptic(12);
if (window.PvZAudio) window.PvZAudio.playSun();
if (this.economy.workshop) {
this.economy.sunCount = (this.economy.sunCount || 0) + 1;
if (this.economy.sunCount >= 8) {
this.economy.sunCount = 0;
this.economy.workshop.addMaterial('sunEssence', 1);
}
}
}
}
for (let i = this.economy.coins.length - 1; i >= 0; i--) {
@@ -699,6 +707,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.isRunning) {
this.checkTouchCollect(pos);
}
});
this.canvas.addEventListener('mouseleave', () => {
@@ -920,10 +931,11 @@ window.GameEngine = class GameEngine {
document.addEventListener('fullscreenchange', updateFullscreenBtn);
document.addEventListener('webkitfullscreenchange', updateFullscreenBtn);
// Global Hotkeys (Space, KeyP for Pause; KeyF for Fullscreen)
// Global Hotkeys (Space/KeyP: Pause; KeyF: Fullscreen; Digit1-Digit6: Deck slots 1-6; KeyS: Shovel)
window.addEventListener('keydown', (e) => {
// Do not intercept hotkeys if user is typing in an input or textarea
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName)) return;
if (e.code === 'Space' || e.code === 'KeyP') {
e.preventDefault();
if (this.isRunning) {
@@ -932,6 +944,29 @@ window.GameEngine = class GameEngine {
} else if (e.code === 'KeyF') {
e.preventDefault();
toggleFullscreen();
} else if (e.code === 'KeyS' || e.key === 's' || e.key === 'S' || e.key === 'ы' || e.key === 'Ы') {
e.preventDefault();
if (this.isRunning && !this.isPaused) {
this.economy.selectedCard = (this.economy.selectedCard === 'shovel') ? null : 'shovel';
if (window.PvZAudio) window.PvZAudio.playButtonClick();
this.triggerHaptic(15);
}
} else if (['Digit1', 'Digit2', 'Digit3', 'Digit4', 'Digit5', 'Digit6', 'Numpad1', 'Numpad2', 'Numpad3', 'Numpad4', 'Numpad5', 'Numpad6'].includes(e.code) || (e.key >= '1' && e.key <= '6')) {
const num = parseInt(e.key, 10) || parseInt(e.code.replace(/\D/g, ''), 10);
const slotIdx = num - 1;
if (this.isRunning && !this.isPaused && this.economy.cards && slotIdx >= 0 && slotIdx < this.economy.cards.length) {
e.preventDefault();
const card = this.economy.cards[slotIdx];
if (card) {
if (this.economy.selectedCard === card.id) {
this.economy.selectedCard = null;
} else if (this.economy.canAfford(card.cost) && card.timer <= 0) {
this.economy.selectedCard = card.id;
if (window.PvZAudio) window.PvZAudio.playButtonClick();
this.triggerHaptic(15);
}
}
}
}
});
@@ -1182,6 +1217,66 @@ window.GameEngine = class GameEngine {
});
}
// Export / Import Save File Handlers
const btnExportSave = document.getElementById('btn-export-save');
const btnExportSaveProfile = document.getElementById('btn-export-save-profile');
const btnImportSave = document.getElementById('btn-import-save');
const btnImportSaveProfile = document.getElementById('btn-import-save-profile');
const inputImportSave = document.getElementById('input-import-save');
const handleExportSave = () => {
this.saveUserData();
if (window.PvZAuth && window.PvZAuth.exportSaveFile) {
window.PvZAuth.exportSaveFile();
}
};
if (btnExportSave) btnExportSave.addEventListener('click', handleExportSave);
if (btnExportSaveProfile) btnExportSaveProfile.addEventListener('click', handleExportSave);
const handleTriggerImport = () => {
if (inputImportSave) {
inputImportSave.value = '';
inputImportSave.click();
}
};
if (btnImportSave) btnImportSave.addEventListener('click', handleTriggerImport);
if (btnImportSaveProfile) btnImportSaveProfile.addEventListener('click', handleTriggerImport);
if (inputImportSave) {
inputImportSave.addEventListener('change', async (e) => {
const file = e.target.files && e.target.files[0];
if (!file) return;
if (window.PvZAuth && window.PvZAuth.importSaveFile) {
const res = await window.PvZAuth.importSaveFile(file);
if (res && res.success) {
this.loadUserData();
this.updateProfileUI();
this.updateRecordDisplay();
this.updateShopUI();
}
}
inputImportSave.value = '';
});
}
// Auto-save on page hide / tab switch / page unload
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
this.saveUserData();
if (window.PvZAuth && window.PvZAuth.flushSave) {
window.PvZAuth.flushSave();
}
}
});
window.addEventListener('beforeunload', () => {
this.saveUserData();
if (window.PvZAuth && window.PvZAuth.flushSave) {
window.PvZAuth.flushSave(true);
}
});
// Inventory / Deck Builder Buttons
const btnInvStart = document.getElementById('btn-inventory-start');