feat(gameplay): add crazy dave casino and bank, and zombie mutation lab

This commit is contained in:
2026-09-22 09:25:08 +00:00
parent d8be8f4366
commit b8db5a123d
16 changed files with 2365 additions and 135 deletions
+179 -4
View File
@@ -21,6 +21,13 @@ window.Plant = class Plant {
this.recoilDps = 0;
switch (type) {
case 'mega_sunflower':
this.maxHealth = 5000;
this.sunInterval = 5;
this.sunAmount = 100;
this.sunTimer = 2;
this.isRaidBoss = true;
break;
case 'sunflower':
this.sunInterval = this.level === 3 ? 9 : (this.level === 2 ? 10 : 13);
this.sunAmount = this.level >= 2 ? 35 : 25;
@@ -108,6 +115,17 @@ window.Plant = class Plant {
this.shootTimer = 1.0;
break;
}
if (window.PvZConfig) {
const hpMult = window.PvZConfig.getPlantHpMult();
const dmgMult = window.PvZConfig.getPlantDamageMult();
const sunIntMult = window.PvZConfig.getSunIntervalMult();
this.maxHealth = Math.round(this.maxHealth * hpMult);
this.damage = Math.round(this.damage * dmgMult);
if (this.sunInterval) this.sunInterval = this.sunInterval * sunIntMult;
}
this.health = this.maxHealth;
}
@@ -119,15 +137,25 @@ window.Plant = class Plant {
return;
}
// 1. Sunflower & Sun-shroom
if (this.type === 'sunflower') {
// 1. Sunflower, Mega Sunflower & Sun-shroom
if (this.type === 'sunflower' || this.type === 'mega_sunflower') {
this.sunTimer += dt;
if (this.sunTimer >= this.sunInterval) {
this.sunTimer = 0;
gameState.spawnSun(this.x + (Math.random() * 20 - 10), this.y + (Math.random() * 20 - 10), this.sunAmount || 25, false);
// Tier 3: Heal neighboring plants!
if (this.level === 3) {
if (this.type === 'mega_sunflower') {
// Raid Boss solar pulse: heal all plants on the lawn
for (const p of gameState.plants) {
if (p !== this && p.health > 0) {
p.health = Math.min(p.maxHealth, p.health + 25);
if (gameState.combat && gameState.combat.createSplash) {
gameState.combat.createSplash(p.x, p.y, '#ffd54f', 4);
}
}
}
} else if (this.level === 3) {
// Tier 3: Heal neighboring plants!
for (const p of gameState.plants) {
if (p !== this && Math.abs(p.col - this.col) <= 1 && Math.abs(p.row - this.row) <= 1 && p.health > 0) {
p.health = Math.min(p.maxHealth, p.health + 30);
@@ -426,6 +454,9 @@ window.Plant = class Plant {
case 'melonpult':
this.drawMelonpult(ctx);
break;
case 'mega_sunflower':
this.drawMegaSunflower(ctx);
break;
}
// Tier Stars (Level 2 & 3)
@@ -494,6 +525,150 @@ window.Plant = class Plant {
ctx.restore();
}
drawMegaSunflower(ctx) {
// Grand glowing aura
const glowGradient = ctx.createRadialGradient(0, -10, 10, 0, -10, 60);
glowGradient.addColorStop(0, 'rgba(255, 215, 0, 0.6)');
glowGradient.addColorStop(0.7, 'rgba(255, 152, 0, 0.3)');
glowGradient.addColorStop(1, 'rgba(255, 100, 0, 0)');
ctx.fillStyle = glowGradient;
ctx.beginPath();
ctx.arc(0, -10, 60, 0, Math.PI * 2);
ctx.fill();
// Leaves / base
ctx.fillStyle = '#2e7d32';
ctx.beginPath();
ctx.ellipse(-26, 22, 22, 10, -0.4, 0, Math.PI * 2);
ctx.ellipse(26, 22, 22, 10, 0.4, 0, Math.PI * 2);
ctx.fill();
// Big stem
ctx.strokeStyle = '#1b5e20';
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(0, 32);
ctx.quadraticCurveTo(-5, 12, 0, -5);
ctx.stroke();
// 16 Petals with pulse and slow rotation
const petalCount = 16;
const pulse = 1 + Math.sin(this.animTime * 3) * 0.08;
ctx.save();
ctx.translate(0, -10);
ctx.scale(pulse, pulse);
for (let i = 0; i < petalCount; i++) {
const angle = (i * (Math.PI * 2) / petalCount) + this.animTime * 0.3;
ctx.save();
ctx.rotate(angle);
// Outer petal
ctx.fillStyle = i % 2 === 0 ? '#ffca28' : '#ffa000';
ctx.beginPath();
ctx.ellipse(0, -32, 11, 20, 0, 0, Math.PI * 2);
ctx.fill();
// Inner petal highlight
ctx.fillStyle = '#fff59d';
ctx.beginPath();
ctx.ellipse(0, -30, 5, 12, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// Flower Center
ctx.fillStyle = '#5d4037';
ctx.beginPath();
ctx.arc(0, 0, 24, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#8d6e63';
ctx.lineWidth = 3;
ctx.stroke();
// Smiling Face & Eyes
ctx.fillStyle = '#212121';
ctx.beginPath();
ctx.arc(-8, -4, 4, 0, Math.PI * 2);
ctx.arc(8, -4, 4, 0, Math.PI * 2);
ctx.fill();
// Eye sparkles
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-9, -6, 1.5, 0, Math.PI * 2);
ctx.arc(7, -6, 1.5, 0, Math.PI * 2);
ctx.fill();
// Big Royal Smile
ctx.strokeStyle = '#3e2723';
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.arc(0, 5, 9, 0.2, Math.PI - 0.2);
ctx.stroke();
// Royal Crown
ctx.fillStyle = '#ffd700';
ctx.strokeStyle = '#e65100';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(-16, -22);
ctx.lineTo(-14, -36);
ctx.lineTo(-6, -26);
ctx.lineTo(0, -40);
ctx.lineTo(6, -26);
ctx.lineTo(14, -36);
ctx.lineTo(16, -22);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Crown rubies
ctx.fillStyle = '#e91e63';
ctx.beginPath();
ctx.arc(0, -32, 2.5, 0, Math.PI * 2);
ctx.arc(-10, -28, 2, 0, Math.PI * 2);
ctx.arc(10, -28, 2, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// Raid Boss Overhead Health Bar & Label
const barWidth = 68;
const barHeight = 8;
const barX = -barWidth / 2;
const barY = -54;
const hpRatio = Math.max(0, this.health / this.maxHealth);
// Dark background
ctx.fillStyle = 'rgba(0, 0, 0, 0.75)';
ctx.fillRect(barX - 1, barY - 1, barWidth + 2, barHeight + 2);
// Health bar with gradient
const hpGrad = ctx.createLinearGradient(barX, barY, barX + barWidth, barY);
hpGrad.addColorStop(0, '#ffd700');
hpGrad.addColorStop(1, '#ff3d00');
ctx.fillStyle = hpGrad;
ctx.fillRect(barX, barY, barWidth * hpRatio, barHeight);
// Border
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.strokeRect(barX - 1, barY - 1, barWidth + 2, barHeight + 2);
// Boss Title
ctx.font = 'bold 9px sans-serif';
ctx.textAlign = 'center';
ctx.fillStyle = '#ffe082';
ctx.shadowColor = 'rgba(0, 0, 0, 0.8)';
ctx.shadowBlur = 3;
ctx.fillText(`👑 MEGA SUNFLOWER [${Math.ceil(this.health)}]`, 0, barY - 4);
ctx.shadowBlur = 0;
}
drawPeashooter(ctx, isSnow) {
const mainColor = isSnow ? '#29b6f6' : '#4caf50';
const darkColor = isSnow ? '#0288d1' : '#2e7d32';
+54 -10
View File
@@ -58,15 +58,47 @@ window.Zombie = class Zombie {
this.baseSpeed = 24;
break;
case 'flag':
this.maxHealth = 110;
this.maxHealth = 140;
this.baseSpeed = 26;
break;
case 'normal':
default:
this.maxHealth = 100;
this.helmetHealth = 0;
break;
}
// Apply Zombie Lab Mutations (if in I, Zombie mode or if mutation level passed)
this.mutationLevel = 1;
if (window._pvzEngine && window._pvzEngine.zombieLab && window._pvzEngine.currentMode === 'izombie') {
this.mutationLevel = window._pvzEngine.zombieLab.getZombieLevel(type) || 1;
}
if (this.mutationLevel >= 2) {
if (type === 'normal') this.baseSpeed *= 1.35;
if (type === 'conehead') this.helmetHealth += 150;
if (type === 'polevaulter') this.maxVaults = 2;
if (type === 'buckethead') this.helmetHealth += 250;
if (type === 'football') this.helmetHealth += 250;
}
if (this.mutationLevel >= 3) {
if (type === 'normal') this.isVampire = true;
if (type === 'conehead') { this.helmetHealth += 150; this.reflectDmg = 0.25; }
if (type === 'polevaulter') { this.maxVaults = 2; this.turboSprint = true; }
if (type === 'buckethead') { this.helmetHealth += 300; this.iceImmune = true; }
if (type === 'football') { this.helmetHealth += 300; this.mineImmune = true; }
}
if (window.PvZConfig) {
const hpMult = window.PvZConfig.getZombieHpMult();
const speedMult = window.PvZConfig.getZombieSpeedMult();
const dmgMult = window.PvZConfig.getZombieDamageMult();
this.maxHealth = Math.round(this.maxHealth * hpMult);
if (this.helmetHealth) this.helmetHealth = Math.round(this.helmetHealth * hpMult);
this.baseSpeed = this.baseSpeed * speedMult;
this.eatingDps = this.eatingDps * dmgMult;
}
this.speed = this.baseSpeed;
this.health = this.maxHealth;
}
@@ -128,8 +160,13 @@ window.Zombie = class Zombie {
this.x -= 150 * dt;
if (this.vaultTimer >= 0.6) {
this.isVaulting = false;
this.baseSpeed = 22; // Normal speed after jumping
this.speed = 22;
if (this.maxVaults && this.maxVaults > 1) {
this.maxVaults--;
this.hasPole = true; // Can vault second time!
}
const postSpeed = this.turboSprint ? 35 : 22;
this.baseSpeed = postSpeed;
this.speed = postSpeed;
}
return;
}
@@ -156,6 +193,9 @@ window.Zombie = class Zombie {
if (this.eatTimer >= 0.35) {
this.eatTimer = 0;
this.targetPlant.takeDamage(this.eatingDps * 0.35);
if (this.isVampire) {
this.health = Math.min(this.maxHealth, this.health + 45);
}
if (window.PvZAudio) window.PvZAudio.playChomp();
if (this.targetPlant && this.targetPlant.type === 'wallnut' && gameState && gameState.achievements) {
gameState.achievements.trigger('nut_wall');
@@ -176,11 +216,14 @@ window.Zombie = class Zombie {
}
}
takeDamage(amount, slows = false) {
takeDamage(amount, slows = false, source = null) {
this.hitFlashTimer = 0.08;
if (slows) {
if (slows && !this.iceImmune) {
this.slowTimer = 4.0;
}
if (this.reflectDmg && source && source.takeDamage) {
source.takeDamage(amount * this.reflectDmg);
}
// Newspaper mechanics
if (this.type === 'newspaper' && this.hasPaper) {
@@ -511,8 +554,6 @@ window.Zombie = class Zombie {
ctx.fill();
}
ctx.restore();
// Frost Crystals visual effect around slowed / frozen zombie
if (isSlow && !isHit) {
this.drawFrostCrystals(ctx);
@@ -530,13 +571,16 @@ window.Zombie = class Zombie {
ctx.restore();
}
// Health bar if damaged
// Health bar if damaged (drawn directly above zombie head)
if (this.health < this.maxHealth) {
const pct = Math.max(0, this.health / this.maxHealth);
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillStyle = 'rgba(0, 0, 0, 0.65)';
ctx.fillRect(-20, -56, 40, 5);
ctx.fillStyle = pct > 0.4 ? '#8bc34a' : '#f44336';
ctx.fillRect(-20, -56, 40 * pct, 5);
ctx.strokeStyle = '#222';
ctx.lineWidth = 0.8;
ctx.strokeRect(-20, -56, 40, 5);
}
ctx.restore();