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
+140 -1
View File
@@ -313,7 +313,12 @@ window.GridSystem = class GridSystem {
this.drawGravestone(ctx, bounds.centerX, bounds.centerY);
}
// 3. Hover effect
// 2.5 Area / Range Preview Highlight (Cherry Bomb 3x3, Jalapeno row, Potato Mine, Doom-shroom 5x5)
if (hoverCell && selectedPlantType) {
this.drawAreaPreview(ctx, hoverCell, selectedPlantType);
}
// 3. Hover placement indicator
if (hoverCell && selectedPlantType) {
const bounds = this.getCellBounds(hoverCell.col, hoverCell.row);
const isWater = this.isWaterCell(hoverCell.row);
@@ -560,4 +565,138 @@ window.GridSystem = class GridSystem {
ctx.restore();
}
drawAreaPreview(ctx, hoverCell, selectedPlantType) {
if (!hoverCell || !selectedPlantType) return;
ctx.save();
// 1. Cherry Bomb / Bowling Cherry: 3x3 explosion area
if (selectedPlantType === 'cherrybomb' || selectedPlantType === 'bowling_cherry') {
const minCol = Math.max(0, hoverCell.col - 1);
const maxCol = Math.min(this.cols - 1, hoverCell.col + 1);
const minRow = Math.max(0, hoverCell.row - 1);
const maxRow = Math.min(this.rows - 1, hoverCell.row + 1);
// Fill affected tiles
for (let r = minRow; r <= maxRow; r++) {
for (let c = minCol; c <= maxCol; c++) {
const b = this.getCellBounds(c, r);
ctx.fillStyle = 'rgba(255, 87, 34, 0.28)';
ctx.fillRect(b.x, b.y, b.w, b.h);
ctx.strokeStyle = 'rgba(255, 112, 67, 0.45)';
ctx.lineWidth = 1;
ctx.strokeRect(b.x + 1, b.y + 1, b.w - 2, b.h - 2);
}
}
// Enclosing blast perimeter
const tl = this.getCellBounds(minCol, minRow);
const br = this.getCellBounds(maxCol, maxRow);
const areaW = (br.x + br.w) - tl.x;
const areaH = (br.y + br.h) - tl.y;
ctx.strokeStyle = '#ff3d00';
ctx.lineWidth = 2.5;
ctx.setLineDash([8, 4]);
ctx.strokeRect(tl.x, tl.y, areaW, areaH);
ctx.setLineDash([]);
ctx.fillStyle = '#ff1744';
ctx.font = 'bold 11px sans-serif';
ctx.textAlign = 'left';
ctx.shadowColor = '#000000';
ctx.shadowBlur = 4;
ctx.fillText('💥 ЗОНА ВЗРЫВА 3х3', tl.x + 6, tl.y - 4);
}
// 2. Jalapeno: Entire Row Lane
else if (selectedPlantType === 'jalapeno') {
const r = hoverCell.row;
const startX = this.startX;
const totalW = this.cols * this.cellW;
const y = this.startY + r * this.cellH;
const h = this.cellH;
ctx.fillStyle = 'rgba(244, 67, 54, 0.28)';
ctx.fillRect(startX, y, totalW, h);
// Fiery boundary lines
ctx.strokeStyle = '#ff5722';
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.moveTo(startX, y);
ctx.lineTo(startX + totalW, y);
ctx.moveTo(startX, y + h);
ctx.lineTo(startX + totalW, y + h);
ctx.stroke();
ctx.fillStyle = '#ff3d00';
ctx.font = 'bold 11px sans-serif';
ctx.textAlign = 'left';
ctx.shadowColor = '#000000';
ctx.shadowBlur = 4;
ctx.fillText('🔥 ОГНЕННАЯ ЛИНИЯ ПО ВСЕЙ ПОЛОСЕ', startX + 6, y - 4);
}
// 3. Potato Mine: Single tile armed trigger zone
else if (selectedPlantType === 'potatomine') {
const b = this.getCellBounds(hoverCell.col, hoverCell.row);
ctx.fillStyle = 'rgba(255, 193, 7, 0.25)';
ctx.fillRect(b.x, b.y, b.w, b.h);
ctx.strokeStyle = '#ffb300';
ctx.lineWidth = 2;
ctx.setLineDash([4, 4]);
ctx.strokeRect(b.x + 3, b.y + 3, b.w - 6, b.h - 6);
ctx.setLineDash([]);
// Crosshair / target marker in tile center
ctx.strokeStyle = 'rgba(255, 179, 0, 0.6)';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.arc(b.centerX, b.centerY, 18, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = '#ffa000';
ctx.font = 'bold 10px sans-serif';
ctx.textAlign = 'center';
ctx.shadowColor = '#000000';
ctx.shadowBlur = 3;
ctx.fillText('💣 МИНА', b.centerX, b.y - 3);
}
// 4. Doom-shroom: Large 5x5 blast area
else if (selectedPlantType === 'doomshroom') {
const minCol = Math.max(0, hoverCell.col - 2);
const maxCol = Math.min(this.cols - 1, hoverCell.col + 2);
const minRow = Math.max(0, hoverCell.row - 2);
const maxRow = Math.min(this.rows - 1, hoverCell.row + 2);
for (let r = minRow; r <= maxRow; r++) {
for (let c = minCol; c <= maxCol; c++) {
const b = this.getCellBounds(c, r);
ctx.fillStyle = 'rgba(156, 39, 176, 0.28)';
ctx.fillRect(b.x, b.y, b.w, b.h);
}
}
const tl = this.getCellBounds(minCol, minRow);
const br = this.getCellBounds(maxCol, maxRow);
const areaW = (br.x + br.w) - tl.x;
const areaH = (br.y + br.h) - tl.y;
ctx.strokeStyle = '#ab47bc';
ctx.lineWidth = 2.5;
ctx.setLineDash([8, 4]);
ctx.strokeRect(tl.x, tl.y, areaW, areaH);
ctx.setLineDash([]);
ctx.fillStyle = '#ba68c8';
ctx.font = 'bold 11px sans-serif';
ctx.textAlign = 'left';
ctx.shadowColor = '#000000';
ctx.shadowBlur = 4;
ctx.fillText('☢️ ЯДЕРНЫЙ ВЗРЫВ 5х5', tl.x + 6, tl.y - 4);
}
ctx.restore();
}
};