User prompt
crea un nuevo tipo de planta, la recogesoles. cuesta 200 soles, y no ataca ni produce soles, lo unico que hace es lo mismo que el zombie ra pero para el jugador, atrae los soles y los recoge automaticamente, tiene la vida de un girasol. en caso de que haya un zombie ra y un recogesoles a la vez simplemente no ocurrira nada, cada uno anula el efecto del otro, asi que el zombie ra ignora el sol y sigue caminando y el recogesoles simplemente no lo recoge. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que el cuando le das a personalizado y sale el icono de la excavadora, que su icono sea el del asset "excabadora_surface"
User prompt
haz que la imagen de el zombie es la del otro asset. y bajale 100 de hp
User prompt
haz que cuando aparece, en vez de llegar a la casilla 9, llegue a la 7
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'call')' in or related to this line: 'ZombieBase.prototype.takeDamage.call(self, damage);' Line Number: 1736
User prompt
ups, con la primera casilla me referia a la primera casilla desde la derecha, o lo que es para ti, la casilla 9. cabe recalcar que la excavadora va en bucle, es decir, es incubadora, 3 segundos, genera zombie, 10 segundos, genera zombie, 3 segundos excava y cambia de modo avanzando 2 casillas y vuelve al modo incuvadora y asi sucesivamente. aumentale la velocidad en el modo excabada un 40% ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
en el segundo modo se llama, "incubadora": en este modo está estatica, no se mueve, cuando pasa a este modo tarda 3 segundos y genera un zombie normal (tiene el asset de un zombie normal y funciona como un zombie normal, pero en realidad tiene un 20% menos de vida y 35% menos de velocidad y 10% menos de daño) despues de esos 3 segundos tarda 10 segundos en generar al siguiente zombie, despues de generar al segundo espera 3 segundos y cambia al modo excabada, el modo excabada avanzará unicamente 2 casillas y volverá al modo incubadora. la vida general de una excabadora es la misma que la de un cañon pero con 100 de hp menos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
hagamos un nuevo tipo de zombie, se llamara "excabadora". tiene dos assets diferentes, y tiene dos formas diferentes, llega en la primera forma, la forma excabada: va a la velocidad de un zombie multiplicado por 1.5 y atraviesa las balas, no puede ser absorvido, no puede ser golpeado, está como en un modo que ignora todo. y va a en ese modo hasta la primera casilla donde se cambia de modo.
User prompt
haz que la rapaz cueste 300 soles. cuanta vida tiene la rapaz? dimelo, y sumale 20 de hp a lo que tenga. sumale los 20hp a la carnivora tambien.
User prompt
haz que la zona donde seleccionas cuantos soles iniciales tendrá el jugador en el modo personalizado aparezca arriba derecha, no en el medio derecha
User prompt
haz que en el modo personalizado se puedan elegir los soles iniciales
User prompt
en el modo endless por alguna razon solo aparecen los zombistein, haz que aparezcan tambien los otros zombies del juego y de forma progresiva
User prompt
haz que solo pueda golpear a 1 zombie a la vez, si hay mas de 1 en su rango solo golpeará a 1
User prompt
hagamosle un ataque al boxeador, su rango es, su casilla, las dos casillas de enfrente y su casilla de atrás. golpea a los zombis haciendoles 40 hp por segundo. haz que cueste 150 soles
User prompt
desplaza los iconos de el guisante de hielo, guisante de fuego, repetidora, nuez y nuez primitiva un 100% a la izquierda
User prompt
haz que el captus cueste 175 soles. vamos a crear una nueva planta, se llamará "boxeador", y su icono estará a la derecha del captus. tendrá 300 hp y por ahora no tendrá ataque
Remix started
Copy pvz a new awaken III
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BackpackDropped = Container.expand(function (x, y) {
var self = Container.call(this);
self.health = 300;
self.maxHealth = 300;
self.x = x;
self.y = y;
self.gridY = Math.floor((y - gridStartY + cellSize / 2) / cellSize);
self.spawnTimer = 300; // First zombie spawns after 5 seconds (300 ticks at 60fps)
self.regularSpawnInterval = 900; // Regular spawns every 15 seconds (900 ticks at 60fps)
self.isFirstSpawn = true;
var graphics = self.attachAsset('zombie_mochila_backpack', {
anchorX: 0.5,
anchorY: 0.5
});
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFF0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
};
self.spawnZombie = function () {
// Create a new normal zombie at backpack position
var zombie = new Zombie();
zombie.gridY = self.gridY;
zombie.x = self.x;
zombie.y = self.y;
enemies.push(zombie);
game.addChild(zombie);
};
// Backpack spawns zombies on a timer
self.update = function () {
self.spawnTimer--;
if (self.spawnTimer <= 0) {
// Spawn zombie
self.spawnZombie();
// Reset timer for next spawn
if (self.isFirstSpawn) {
self.isFirstSpawn = false;
self.spawnTimer = self.regularSpawnInterval; // 15 seconds for subsequent spawns
} else {
self.spawnTimer = self.regularSpawnInterval; // 15 seconds
}
}
};
return self;
});
var CanonShell = Container.expand(function () {
var self = Container.call(this);
self.speed = 3.2; // 4x normal zombie speed (0.8 * 4)
self.damage = 20;
self.hasExploded = false;
self.hasReachedColumn6 = false;
self.gridY = 0;
var graphics = self.attachAsset('canonShell', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// Move left towards plants
self.x -= self.speed;
// Check if reached column 3
var cellX = Math.floor((self.x - gridStartX) / cellSize);
if (cellX <= 2 && !self.hasReachedColumn6) {
self.hasReachedColumn6 = true;
self.explode();
return;
}
// Check collision with plants
var cellX = Math.floor((self.x - gridStartX) / cellSize);
if (cellX >= 0 && cellX < gridCols && plants[self.gridY] && plants[self.gridY][cellX]) {
var plant = plants[self.gridY][cellX];
if (self.intersects(plant)) {
plant.takeDamage(self.damage);
self.explode();
return;
}
}
// Remove if goes off screen
if (self.x < -100) {
self.removeFromGame();
}
};
self.explode = function () {
if (self.hasExploded) {
return;
}
self.hasExploded = true;
// Create explosion effect
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 1.5,
scaleY: 1.5
});
game.addChild(explosion);
// Animate explosion and remove it
tween(explosion, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Transform shell into normal zombidito at current position
var zombidito = new Zombidito();
zombidito.x = self.x;
zombidito.y = self.y;
zombidito.gridY = self.gridY;
enemies.push(zombidito);
game.addChild(zombidito);
// Remove shell
self.removeFromGame();
};
self.removeFromGame = function () {
for (var i = 0; i < canonShells.length; i++) {
if (canonShells[i] === self) {
canonShells.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Lawnmower = Container.expand(function (row) {
var self = Container.call(this);
self.gridY = row;
self.speed = 0;
self.isActivated = false;
self.x = gridStartX - 100;
self.y = gridStartY + row * cellSize;
var graphics = self.attachAsset('lawnmower', {
anchorX: 0.5,
anchorY: 0.5
});
self.activate = function () {
if (!self.isActivated) {
self.isActivated = true;
self.speed = 6.4;
}
};
self.update = function () {
if (self.isActivated) {
self.x += self.speed;
// Check collision with zombies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && self.intersects(enemy)) {
enemy.die();
// Count as player kill for score tracking
enemiesKilled++;
}
}
// Remove lawnmower when it reaches right edge
if (self.x > 2148) {
self.removeFromGame();
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < lawnmowers.length; i++) {
if (lawnmowers[i] === self) {
lawnmowers.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var PlantBase = Container.expand(function (plantType) {
var self = Container.call(this);
self.plantType = plantType;
self.health = 100;
self.maxHealth = 100;
self.cost = 50;
self.shootTimer = 0;
self.shootDelay = 72;
self.gridX = 0;
self.gridY = 0;
self.level = 1;
var graphics = self.attachAsset(plantType, {
anchorX: 0.5,
anchorY: 0.5
});
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFF0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Clean up projectile tracking for this plant
var plantId = self.gridX + '_' + self.gridY;
if (plantProjectileCount[plantId]) {
delete plantProjectileCount[plantId];
}
plants[self.gridY][self.gridX] = null;
self.destroy();
};
return self;
});
var Wallnut = PlantBase.expand(function () {
var self = PlantBase.call(this, 'wallnut');
self.cost = 50;
self.health = 300;
self.maxHealth = 300;
self.isDamaged = false;
var originalTakeDamage = self.takeDamage;
self.takeDamage = function (damage) {
originalTakeDamage(damage);
// Check if wallnut should switch to damaged appearance
if (self.health < 150 && !self.isDamaged) {
self.isDamaged = true;
// Remove current graphics and add damaged version
self.removeChild(self.children[0]);
var damagedGraphics = self.attachAsset('wallnut_damaged', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
return self;
});
var Sunflower = PlantBase.expand(function () {
var self = PlantBase.call(this, 'sunflower');
self.cost = 50;
self.sunTimer = 0;
self.update = function () {
self.sunTimer++;
if (self.sunTimer >= 1080) {
// 18 seconds
var sun = new Sun(self.x, self.y - 60);
suns.push(sun);
game.addChild(sun);
self.sunTimer = 0;
LK.effects.flashObject(self, 0xFFFF00, 300);
}
};
return self;
});
var SnowPea = PlantBase.expand(function () {
var self = PlantBase.call(this, 'snowpea');
self.cost = 175;
self.health = 120;
self.maxHealth = 120;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.shootTimer = 0;
}
}
};
self.findTarget = function () {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && enemy.x > self.x) {
return enemy;
}
}
return null;
};
self.shoot = function (target) {
var plantId = self.gridX + '_' + self.gridY; // Unique plant identifier
var pea = createProjectile('snowshot', self.x, self.y, 5.12, 25 * self.level, plantId); // 6.4 * 0.8 = 5.12 (20% slower)
if (pea) {
LK.getSound('shoot').play();
}
};
return self;
});
var Repetidora = PlantBase.expand(function () {
var self = PlantBase.call(this, 'repetidora');
self.cost = 200;
self.isTrackingFirstPea = false;
self.firstPea = null;
self.firstPeaStartX = 0;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
var target = self.findTarget();
if (target && !self.isTrackingFirstPea) {
var firstPea = self.shoot(target);
if (firstPea) {
self.firstPea = firstPea;
self.firstPeaStartX = firstPea.x;
self.isTrackingFirstPea = true;
}
self.shootTimer = 0;
}
}
// Handle second pea timing based on first pea position
if (self.isTrackingFirstPea && self.firstPea) {
// Check if first pea has advanced half a cell (72 pixels)
if (self.firstPea.x >= self.firstPeaStartX + 72) {
var target = self.findTarget();
if (target) {
self.shoot(target);
}
// Stop tracking to prevent lag
self.isTrackingFirstPea = false;
self.firstPea = null;
}
// Also stop tracking if first pea no longer exists
else if (!self.firstPea.parent) {
self.isTrackingFirstPea = false;
self.firstPea = null;
}
}
};
self.findTarget = function () {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && enemy.x > self.x) {
return enemy;
}
}
return null;
};
self.shoot = function (target) {
var plantId = self.gridX + '_' + self.gridY; // Unique plant identifier
var pea = createProjectile('pea', self.x, self.y, 8.64, 20 * self.level, plantId);
if (pea) {
LK.getSound('shoot').play();
}
return pea; // Return the pea for tracking
};
return self;
});
var Recogesoles = PlantBase.expand(function () {
var self = PlantBase.call(this, 'recogesoles');
self.cost = 200;
self.health = 100; // Same as sunflower
self.maxHealth = 100;
self.isAttractingSun = false;
self.targetSun = null;
// Override update to handle sun attraction
self.update = function () {
// Look for uncollected suns to attract
if (!self.isAttractingSun && !self.targetSun) {
var closestSun = null;
var closestDistance = Infinity;
// Find the closest sun that isn't already being attracted
for (var i = 0; i < suns.length; i++) {
var sun = suns[i];
if (!sun.isBeingAttracted) {
// Check if there's a zombie ra that would conflict
var hasConflictingZombieRa = false;
for (var j = 0; j < enemies.length; j++) {
var enemy = enemies[j];
if (enemy.zombieType === 'zombieRa' && !enemy.isAttractingSun) {
// If zombie ra and recogesoles would target the same sun, cancel each other
var zombieDistance = Math.abs(sun.x - enemy.x) + Math.abs(sun.y - enemy.y);
var plantDistance = Math.abs(sun.x - self.x) + Math.abs(sun.y - self.y);
if (Math.abs(zombieDistance - plantDistance) < 100) {
hasConflictingZombieRa = true;
break;
}
}
}
if (!hasConflictingZombieRa) {
var distance = Math.abs(sun.x - self.x) + Math.abs(sun.y - self.y);
if (distance < closestDistance) {
closestDistance = distance;
closestSun = sun;
}
}
}
}
// If found a sun, start attracting it
if (closestSun) {
self.targetSun = closestSun;
self.isAttractingSun = true;
// Mark sun as being attracted so other recogesoles don't target it
closestSun.isBeingAttracted = true;
closestSun.attractedBy = self;
// Start tweening sun towards plant
tween(closestSun, {
x: self.x,
y: self.y
}, {
duration: closestDistance / 0.6,
// Same speed as zombie ra
easing: tween.linear,
onFinish: function onFinish() {
// Sun reached plant - consume it for player
if (self.targetSun && self.targetSun.parent) {
self.consumeSun();
}
}
});
}
}
// Check if sun reached plant (collision detection)
if (self.isAttractingSun && self.targetSun && self.targetSun.parent) {
var distance = Math.abs(self.targetSun.x - self.x) + Math.abs(self.targetSun.y - self.y);
if (distance < 50) {
// Close enough to consume
self.consumeSun();
}
}
};
self.consumeSun = function () {
if (self.targetSun && self.targetSun.parent) {
// Add sun points to player (unlike zombie ra)
sunPoints += self.targetSun.value;
updateSunDisplay();
// Remove sun from game
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self.targetSun) {
suns.splice(i, 1);
break;
}
}
self.targetSun.destroy();
self.targetSun = null;
self.isAttractingSun = false;
}
};
// Override die to stop any sun attraction
var originalDie = self.die;
self.die = function () {
// If attracting a sun, stop the tween and release the sun
if (self.targetSun && self.targetSun.parent) {
tween.stop(self.targetSun);
self.targetSun.isBeingAttracted = false;
self.targetSun.attractedBy = null;
}
originalDie();
};
return self;
});
var Rapaz = PlantBase.expand(function () {
var self = PlantBase.call(this, 'rapaz');
self.cost = 325;
self.health = 445;
self.maxHealth = 445;
self.damageTimer = 0; // Timer for dealing damage to high-HP zombies
self.cooldownTimer = 0; // Timer for cooldown after killing zombie <400 HP
self.isOnCooldown = false; // Track if rapaz is on cooldown
self.targetZombie = null; // Track zombie being consumed
self.targetingHighHPZombie = false; // Track if currently attacking high-HP zombie
var graphics = self.children[0]; // Reference to graphics for color changes
self.update = function () {
// Handle cooldown timer
if (self.isOnCooldown && self.cooldownTimer > 0) {
self.cooldownTimer--;
if (self.cooldownTimer === 0) {
// End cooldown - restore normal color
self.isOnCooldown = false;
self.targetingHighHPZombie = false;
self.targetZombie = null;
tween(graphics, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
}
return; // Don't attack while on cooldown
}
// Check for zombies in current cell and cell in front for immediate kill
var targetsFound = self.findImmediateTargets();
if (targetsFound.length > 0) {
var zombie = targetsFound[0];
if (zombie.health > 500) {
// High-HP zombie: deal gradual damage instead of instant kill
if (!self.targetingHighHPZombie) {
self.targetingHighHPZombie = true;
self.targetZombie = zombie;
self.damageTimer = 0; // Reset damage timer
}
self.damageTimer++;
// Deal 35 damage every 60 ticks (1 second)
if (self.damageTimer >= 60) {
zombie.takeDamage(35);
// Add flashing effect to zombie when taking damage from rapaz
tween(zombie, {
tint: 0xFF0000
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(zombie, {
tint: 0xFFFFFF
}, {
duration: 100,
easing: tween.easeOut
});
}
});
self.damageTimer = 0;
// Check if zombie now has <= 500 HP and kill it instantly
if (zombie.health <= 500 && zombie.parent) {
zombie.die();
enemiesKilled++;
self.targetingHighHPZombie = false;
self.targetZombie = null;
// Start 10 second cooldown and gray out the plant
self.isOnCooldown = true;
self.cooldownTimer = 600; // 10 seconds at 60fps
tween(graphics, {
tint: 0x888888 // Gray tint
}, {
duration: 200,
easing: tween.easeOut
});
}
}
return; // Exit early while dealing gradual damage
} else {
// Low-HP zombie: kill instantly
zombie.die();
enemiesKilled++;
// Start 10 second cooldown and gray out the plant
self.isOnCooldown = true;
self.cooldownTimer = 600; // 10 seconds at 60fps
tween(graphics, {
tint: 0x888888 // Gray tint
}, {
duration: 200,
easing: tween.easeOut
});
return; // Exit early after immediate kill
}
}
// If no immediate targets, check for distant zombies to absorb
var distantTargets = self.findTargetZombies();
if (distantTargets.length > 0) {
// Absorb the first zombie found in the 7-cell range
var zombieToAbsorb = distantTargets[0];
zombieToAbsorb.beingConsumed = true;
zombieToAbsorb.consumingPlant = self;
// Set 4x speed and rotation effect while being absorbed
zombieToAbsorb.speed = 0.8 * 4; // 4x normal zombie speed
zombieToAbsorb.rotationSpeed = 0.1; // Rotation speed per frame
}
};
self.findImmediateTargets = function () {
var targets = [];
// Check current cell and cell in front
for (var checkX = self.gridX; checkX <= self.gridX + 1 && checkX < gridCols; checkX++) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyGridX = Math.floor((enemy.x - gridStartX + cellSize / 2) / cellSize);
// Check if zombie is in the target cell
if (enemy.gridY === self.gridY && enemyGridX === checkX) {
targets.push(enemy);
}
}
}
return targets;
};
self.findTargetZombies = function () {
var targets = [];
// Check if rapaz is already consuming a zombie
var isAlreadyConsuming = false;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.beingConsumed && enemy.consumingPlant === self) {
isAlreadyConsuming = true;
break;
}
}
// If already consuming a zombie, don't absorb another one
if (isAlreadyConsuming) {
return targets; // Return empty array
}
// Check 7 cells ahead (excluding current and next cell)
for (var checkX = self.gridX + 2; checkX <= self.gridX + 8 && checkX < gridCols; checkX++) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyGridX = Math.floor((enemy.x - gridStartX + cellSize / 2) / cellSize);
// Check if zombie is in the target cell, not already being consumed, and has <= 500 HP
if (enemy.gridY === self.gridY && enemyGridX === checkX && !enemy.beingConsumed && enemy.health <= 500) {
targets.push(enemy);
}
}
}
return targets;
};
// Override die to clean up projectile tracking for this plant
var originalDie = self.die;
self.die = function () {
originalDie();
};
return self;
});
var Plantorcha = PlantBase.expand(function () {
var self = PlantBase.call(this, 'plantorcha');
self.cost = 175;
self.health = 300;
self.maxHealth = 300;
// Plantorcha doesn't shoot - it only converts projectiles
self.update = function () {
// No shooting behavior - just exists to convert projectiles
};
return self;
});
var Petacereza = PlantBase.expand(function () {
var self = PlantBase.call(this, 'petacereza');
self.cost = 150;
self.health = 1; // Very low health since it explodes
self.maxHealth = 1;
self.isArmed = false; // Zombies ignore it until armed
self.armTimer = 90; // 1.5 seconds at 60fps
self.update = function () {
if (self.armTimer > 0) {
self.armTimer--;
if (self.armTimer === 0) {
// Arm the petacereza and explode
self.isArmed = true;
self.explode();
}
}
};
self.explode = function () {
// Create explosion effect
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 3,
// Larger explosion for 3x3 area
scaleY: 3
});
game.addChild(explosion);
// Animate explosion and remove it
tween(explosion, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Damage all zombies in 3x3 grid area around petacereza
var centerGridX = self.gridX;
var centerGridY = self.gridY;
var damagedEnemies = []; // Track enemies to damage
// First pass: identify all enemies in range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyGridX = Math.floor((enemy.x - gridStartX + cellSize / 2) / cellSize);
var enemyGridY = enemy.gridY;
// Check if enemy is within 3x3 area (center ± 1 in both directions)
var deltaX = Math.abs(enemyGridX - centerGridX);
var deltaY = Math.abs(enemyGridY - centerGridY);
if (deltaX <= 1 && deltaY <= 1) {
damagedEnemies.push(enemy);
}
}
// Second pass: damage all identified enemies
for (var j = 0; j < damagedEnemies.length; j++) {
damagedEnemies[j].takeDamage(325);
}
// Remove petacereza after explosion
self.die();
};
// Override getPlantInFront for zombies to ignore unarmed petacereza
self.shouldBeIgnoredByZombies = function () {
return !self.isArmed;
};
return self;
});
var Peashooter = PlantBase.expand(function () {
var self = PlantBase.call(this, 'peashooter');
self.cost = 100;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.shootTimer = 0;
}
}
};
self.findTarget = function () {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && enemy.x > self.x) {
return enemy;
}
}
return null;
};
self.shoot = function (target) {
var plantId = self.gridX + '_' + self.gridY; // Unique plant identifier
var pea = createProjectile('pea', self.x, self.y, 6.4, 20 * self.level, plantId);
if (pea) {
LK.getSound('shoot').play();
}
};
return self;
});
var NuezPrimitiva = PlantBase.expand(function () {
var self = PlantBase.call(this, 'nuezPrimitiva');
self.cost = 125;
self.health = 625;
self.maxHealth = 625;
self.hasChangedToWallnut = false;
self.hasChangedToDamaged = false;
var originalTakeDamage = self.takeDamage;
self.takeDamage = function (damage) {
originalTakeDamage(damage);
// Check if should switch to normal wallnut appearance (< 300 hp)
if (self.health < 300 && !self.hasChangedToWallnut) {
self.hasChangedToWallnut = true;
// Remove current graphics and add wallnut version
self.removeChild(self.children[0]);
var wallnutGraphics = self.attachAsset('wallnut', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Check if should switch to damaged wallnut appearance (< 150 hp)
else if (self.health < 150 && !self.hasChangedToDamaged) {
self.hasChangedToDamaged = true;
// Remove current graphics and add damaged version
self.removeChild(self.children[0]);
var damagedGraphics = self.attachAsset('wallnut_damaged', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
return self;
});
var Fireshot = PlantBase.expand(function () {
var self = PlantBase.call(this, 'fireshot');
self.cost = 100;
self.health = 150;
self.maxHealth = 150;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.shootTimer = 0;
}
}
};
self.findTarget = function () {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && enemy.x > self.x) {
return enemy;
}
}
return null;
};
self.shoot = function (target) {
var plantId = self.gridX + '_' + self.gridY; // Unique plant identifier
// Calculate damage based on target type
var damage = 35 * self.level;
if (target.zombieType === 'cone_zombie' || target.zombieType === 'bucket_zombie') {
damage = 50 * self.level;
}
var pea = createProjectile('shotfire', self.x, self.y, 6.4, damage, plantId);
if (pea) {
LK.getSound('shoot').play();
}
};
return self;
});
var Carnivora = PlantBase.expand(function () {
var self = PlantBase.call(this, 'carnivora');
self.cost = 25;
self.health = 320;
self.maxHealth = 320;
self.damageTimer = 0; // Timer for dealing damage to high-HP zombies
self.cooldownTimer = 0; // Timer for cooldown after killing zombie <400 HP
self.isOnCooldown = false; // Track if carnivora is on cooldown
var graphics = self.children[0]; // Reference to graphics for color changes
self.update = function () {
// Handle cooldown timer
if (self.isOnCooldown && self.cooldownTimer > 0) {
self.cooldownTimer--;
if (self.cooldownTimer === 0) {
// End cooldown - restore normal color
self.isOnCooldown = false;
tween(graphics, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
}
return; // Don't attack while on cooldown
}
self.damageTimer++;
// Check every 60 ticks (1 second) for zombies to attack
if (self.damageTimer >= 60) {
var targetZombies = self.findTargetZombies();
for (var i = 0; i < targetZombies.length; i++) {
var zombie = targetZombies[i];
if (zombie.health < 400) {
// Instantly kill zombie with <400 HP
zombie.die();
enemiesKilled++;
// Start 15 second cooldown and gray out the plant
self.isOnCooldown = true;
self.cooldownTimer = 900; // 15 seconds at 60fps
tween(graphics, {
tint: 0x888888 // Gray tint
}, {
duration: 200,
easing: tween.easeOut
});
break; // Only kill one zombie per cycle
} else {
// Deal 25 damage to zombie with ≥400 HP
zombie.takeDamage(25);
// Add flashing effect to zombie when taking damage from carnivora
tween(zombie, {
tint: 0xFF0000
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(zombie, {
tint: 0xFFFFFF
}, {
duration: 100,
easing: tween.easeOut
});
}
});
// Check if zombie now has <400 HP and kill it
if (zombie.health < 400 && zombie.parent) {
zombie.die();
enemiesKilled++;
// Start 15 second cooldown and gray out the plant
self.isOnCooldown = true;
self.cooldownTimer = 900; // 15 seconds at 60fps
tween(graphics, {
tint: 0x888888 // Gray tint
}, {
duration: 200,
easing: tween.easeOut
});
break; // Only kill one zombie per cycle
}
}
}
self.damageTimer = 0;
}
};
self.findTargetZombies = function () {
var targets = [];
// Check current cell and cell in front
for (var checkX = self.gridX; checkX <= self.gridX + 1 && checkX < gridCols; checkX++) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyGridX = Math.floor((enemy.x - gridStartX + cellSize / 2) / cellSize);
// Check if zombie is in the target cell
if (enemy.gridY === self.gridY && enemyGridX === checkX) {
targets.push(enemy);
}
}
}
return targets;
};
return self;
});
var Captus = PlantBase.expand(function () {
var self = PlantBase.call(this, 'captus');
// Use captus graphics for captus plant
self.cost = 175;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.shootTimer = 0;
}
}
};
self.findTarget = function () {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && enemy.x > self.x) {
return enemy;
}
}
return null;
};
self.shoot = function (target) {
var plantId = self.gridX + '_' + self.gridY; // Unique plant identifier
var pea = createCaptusProjectile('captus_pea', self.x, self.y, 6.4, 20 * self.level, plantId);
if (pea) {
LK.getSound('shoot').play();
}
};
return self;
});
var Boxeador = PlantBase.expand(function () {
var self = PlantBase.call(this, 'boxeador');
self.cost = 150;
self.health = 300;
self.maxHealth = 300;
self.damageTimer = 0; // Timer for dealing damage every second
self.update = function () {
self.damageTimer++;
// Deal damage every 60 ticks (1 second at 60fps)
if (self.damageTimer >= 60) {
var targetZombies = self.findTargetZombies();
for (var i = 0; i < targetZombies.length; i++) {
var zombie = targetZombies[i];
zombie.takeDamage(40); // Deal 40 HP damage per second
}
self.damageTimer = 0;
}
};
self.findTargetZombies = function () {
// Check boxeador's range: its cell, two cells in front, and one cell behind
for (var checkX = self.gridX - 1; checkX <= self.gridX + 2 && checkX < gridCols; checkX++) {
// Only check valid grid positions
if (checkX >= 0) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyGridX = Math.floor((enemy.x - gridStartX + cellSize / 2) / cellSize);
// Check if zombie is in the target cell and same row
if (enemy.gridY === self.gridY && enemyGridX === checkX) {
return [enemy]; // Return only the first zombie found
}
}
}
}
return []; // Return empty array if no zombie found
};
return self;
});
var Birasol = PlantBase.expand(function () {
var self = PlantBase.call(this, 'birasol');
self.cost = 125;
self.health = 125;
self.maxHealth = 125;
self.sunTimer = 0;
self.update = function () {
self.sunTimer++;
if (self.sunTimer >= 900) {
// 15 seconds (900 ticks at 60fps)
var sun1 = new Sun(self.x - 30, self.y - 60);
var sun2 = new Sun(self.x + 30, self.y - 60);
suns.push(sun1);
suns.push(sun2);
game.addChild(sun1);
game.addChild(sun2);
self.sunTimer = 0;
LK.effects.flashObject(self, 0xFFFF00, 300);
}
};
return self;
});
var Projectile = Container.expand(function (type, startX, startY, speed, damage) {
var self = Container.call(this);
self.speed = speed;
self.damage = damage;
self.x = startX;
self.y = startY;
self.type = type;
self.gridY = Math.floor((startY - gridStartY + cellSize / 2) / cellSize);
self.ownerId = null; // Plant that owns this projectile
var graphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.x += self.speed;
if (self.x > 2048) {
self.removeFromGame();
return;
}
// Check collision with plantorcha to convert pea to fire projectile
if (self.type === 'pea') {
var projectileGridX = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
var projectileGridY = self.gridY;
if (projectileGridX >= 0 && projectileGridX < gridCols && projectileGridY >= 0 && projectileGridY < gridRows) {
var plantInCell = plants[projectileGridY][projectileGridX];
if (plantInCell && plantInCell.plantType === 'plantorcha' && self.intersects(plantInCell)) {
// Convert pea to shotfire and add 10 damage
self.removeChild(self.children[0]);
var fireGraphics = self.attachAsset('shotfire', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'shotfire';
self.damage += 10;
// Continue with normal movement
}
}
}
// Only check collisions with zombies in same row and within 100px range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && Math.abs(enemy.x - self.x) <= 100 && self.intersects(enemy)) {
// Skip collision entirely if zombie is being consumed by rapaz (immune to all projectiles)
if (enemy.beingConsumed) {
continue; // Skip this zombie completely - projectile passes through
}
enemy.takeDamage(self.damage);
// Apply ice effect if this is a snowshot
if (self.type === 'snowshot') {
enemy.applyIceEffect();
}
self.removeFromGame();
break;
}
}
};
self.removeFromGame = function () {
// Decrease projectile count for the plant that owns this projectile
if (self.ownerId && plantProjectileCount[self.ownerId]) {
plantProjectileCount[self.ownerId]--;
}
for (var i = 0; i < projectiles.length; i++) {
if (projectiles[i] === self) {
projectiles.splice(i, 1);
break;
}
}
// Return to pool instead of destroying
self.returnToPool();
};
self.returnToPool = function () {
if (projectilePool.length < maxPoolSize) {
self.removeChildren();
if (self.parent) {
self.parent.removeChild(self);
}
projectilePool.push(self);
} else {
self.destroy();
}
};
return self;
});
var SlowProjectile = Projectile.expand(function (type, startX, startY, speed, damage) {
var self = Projectile.call(this, type, startX, startY, speed, damage);
var originalUpdate = self.update;
self.update = function () {
originalUpdate();
// Only check collisions with zombies in same row and within 100px range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && Math.abs(enemy.x - self.x) <= 100 && self.intersects(enemy)) {
enemy.applySlowEffect();
break;
}
}
};
return self;
});
var Sun = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.value = 25;
self.collectTimer = 0;
self.isBeingAttracted = false;
self.attractedBy = null;
var graphics = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5
});
self.down = function (x, y, obj) {
// If being attracted by zombie sol, stop the attraction
if (self.isBeingAttracted && self.attractedBy) {
tween.stop(self);
self.attractedBy.targetSun = null;
self.attractedBy.isAttractingSun = false;
self.attractedBy.speed = self.attractedBy.originalSpeed;
self.isBeingAttracted = false;
self.attractedBy = null;
}
sunPoints += self.value;
updateSunDisplay();
self.collect();
};
self.collect = function () {
// Clean up attraction if being collected
if (self.isBeingAttracted && self.attractedBy) {
tween.stop(self);
self.attractedBy.targetSun = null;
self.attractedBy.isAttractingSun = false;
self.attractedBy.speed = self.attractedBy.originalSpeed;
}
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self) {
suns.splice(i, 1);
break;
}
}
self.destroy();
};
self.update = function () {
self.collectTimer++;
if (self.collectTimer > 720) {
// 12 seconds timeout
self.collect();
}
};
return self;
});
var ZombieBase = Container.expand(function (zombieType) {
var self = Container.call(this);
self.zombieType = zombieType;
self.health = 190;
self.maxHealth = 190;
self.speed = 0.8;
self.damage = 25;
self.gridY = 0;
self.attackTimer = 0;
self.slowTimer = 0;
self.iceTimer = 0;
self.isIced = false;
self.originalSpeed = self.speed;
self.beingConsumed = false; // Track if zombie is being consumed by rapaz
self.consumingPlant = null; // Reference to the plant consuming this zombie
var graphics = self.attachAsset(zombieType, {
anchorX: 0.5,
anchorY: 0.5
});
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFF0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Check if this is Zombiestein being killed
if (self.zombieType === 'zombiestein') {
zombiesteinAlive = false;
}
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
};
self.applySlowEffect = function () {
self.slowTimer = 180;
self.speed = self.originalSpeed * 0.5;
graphics.tint = 0x81C784;
};
self.applyIceEffect = function () {
// Set ice effect timer for 3 seconds if not already active or extend it
self.iceTimer = 180; // 3 seconds at 60fps
self.speed = self.originalSpeed * 0.8; // 20% slower
// Only start the visual tween if not already iced
if (!self.isIced) {
self.isIced = true;
// Calculate 35% blue tint (mix current color with blue)
var blueColor = 0x5555FF; // Blue color
// Apply blue tint
tween(graphics, {
tint: blueColor
}, {
duration: 100,
easing: tween.easeOut
});
}
};
self.update = function () {
// Handle being consumed by rapaz - move toward the consuming plant
if (self.beingConsumed && self.consumingPlant) {
// Apply continuous rotation while moving
if (self.rotationSpeed && self.children[0]) {
self.children[0].rotation += self.rotationSpeed;
}
// Move toward the rapaz plant position at 4x normal zombie speed
var targetX = self.consumingPlant.x;
var consumptionSpeed = 0.8 * 4; // 4x normal zombie speed
if (self.x > targetX + 10) {
// Add small buffer to prevent overshooting
self.x -= consumptionSpeed; // Move left toward rapaz at 4x speed
} else {
// Reached rapaz position - end consumption state and let rapaz handle killing
self.beingConsumed = false;
self.consumingPlant = null;
self.rotationSpeed = 0; // Stop rotating
if (self.children[0]) {
self.children[0].rotation = 0; // Reset rotation
}
// The rapaz will detect this zombie is in its cell and kill it with immediate attack
}
return; // Skip normal update logic when being consumed
}
if (self.slowTimer > 0) {
self.slowTimer--;
if (self.slowTimer === 0) {
self.speed = self.originalSpeed;
graphics.tint = 0xFFFFFF;
}
}
// Handle ice effect timer
if (self.iceTimer > 0) {
self.iceTimer--;
if (self.iceTimer === 0) {
// Ice effect ends
self.isIced = false;
self.speed = self.originalSpeed;
// Return to normal color
tween(graphics, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
}
}
var plantInFront = self.getPlantInFront();
if (plantInFront) {
self.attackTimer++;
if (self.attackTimer >= 72) {
plantInFront.takeDamage(self.damage);
self.attackTimer = 0;
}
} else {
self.x -= self.speed;
// Check if zombie reached left screen edge
if (self.x < 0) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
// Check collision with lawnmower
for (var i = 0; i < lawnmowers.length; i++) {
var lawnmower = lawnmowers[i];
if (lawnmower.gridY === self.gridY && self.x <= lawnmower.x + 60 && !lawnmower.isActivated) {
lawnmower.activate();
}
}
}
};
self.getPlantInFront = function () {
var cellX = Math.floor((self.x - gridStartX) / cellSize);
if (cellX >= 0 && cellX < gridCols && plants[self.gridY] && plants[self.gridY][cellX]) {
var plant = plants[self.gridY][cellX];
// Check if this is an unarmed petacereza that should be ignored
if (plant.shouldBeIgnoredByZombies && plant.shouldBeIgnoredByZombies()) {
return null; // Ignore unarmed petacereza
}
return plant;
}
return null;
};
return self;
});
var Zombiestein = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'zombiestein');
self.health = 2250;
self.maxHealth = 2250;
self.speed = 0.414; // 10% slower than 0.46
self.originalSpeed = 0.414;
self.damage = 500; // 500 damage per second
self.isSummoning = false;
self.hasSpawnedZombidito = false;
self.originalUpdate = self.update;
self.update = function () {
// Check if health is below 1500 and hasn't spawned Zombidito yet
if (self.health < 1500 && !self.hasSpawnedZombidito && !self.isSummoning) {
self.isSummoning = true;
self.speed = 0; // Stop moving completely
// Wait 1.5 seconds then spawn Zombidito
LK.setTimeout(function () {
// Resume movement
self.speed = self.originalSpeed;
self.isSummoning = false;
self.hasSpawnedZombidito = true;
// Calculate position 3 cells ahead (in front of Zombiestein)
var spawnX = self.x - 3 * cellSize;
var spawnY = self.y;
// Create Zombidito with explosion effect
var zombidito = new Zombidito();
zombidito.x = spawnX;
zombidito.y = spawnY;
zombidito.gridY = self.gridY;
enemies.push(zombidito);
game.addChild(zombidito);
// Create explosion effect like miner zombie
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: spawnX,
y: spawnY,
scaleX: 2,
scaleY: 2
});
game.addChild(explosion);
// Animate explosion and remove it
tween(explosion, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
}, 1500);
}
// Call original update only if not summoning (stopped)
if (!self.isSummoning) {
self.originalUpdate();
}
};
return self;
});
var ZombieSarcofago = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'zombie_sarcofago');
self.health = 500;
self.maxHealth = 500;
self.speed = 0.6; // Slightly slower than normal zombie
self.originalSpeed = 0.6;
// Override die function to spawn normal zombie at death location
var originalDie = self.die;
self.die = function () {
// Create spawned zombie at current position with 2x speed and 225 HP
var spawnedZombie = new SpawnedZombie();
spawnedZombie.x = self.x;
spawnedZombie.y = self.y;
spawnedZombie.gridY = self.gridY;
enemies.push(spawnedZombie);
game.addChild(spawnedZombie);
// Call original die function
originalDie();
};
return self;
});
var ZombieRa = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'zombieRa');
self.health = 190;
self.maxHealth = 190;
self.speed = 0.8;
self.originalSpeed = 0.8;
self.isAttractingSun = false;
self.targetSun = null;
// Override update to handle sun attraction
var originalUpdate = self.update;
self.update = function () {
// Look for uncollected suns to attract
if (!self.isAttractingSun && !self.targetSun) {
var closestSun = null;
var closestDistance = Infinity;
// Find the closest sun that isn't already being attracted
for (var i = 0; i < suns.length; i++) {
var sun = suns[i];
if (!sun.isBeingAttracted) {
// Check if there's a recogesoles that would conflict
var hasConflictingRecogesoles = false;
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var plant = plants[row][col];
if (plant && plant.plantType === 'recogesoles' && !plant.isAttractingSun) {
// If zombie ra and recogesoles would target the same sun, zombie ra ignores it
var plantDistance = Math.abs(sun.x - plant.x) + Math.abs(sun.y - plant.y);
var zombieDistance = Math.abs(sun.x - self.x) + Math.abs(sun.y - self.y);
if (Math.abs(plantDistance - zombieDistance) < 100) {
hasConflictingRecogesoles = true;
break;
}
}
}
if (hasConflictingRecogesoles) break;
}
if (!hasConflictingRecogesoles) {
var distance = Math.abs(sun.x - self.x) + Math.abs(sun.y - self.y);
if (distance < closestDistance) {
closestDistance = distance;
closestSun = sun;
}
}
}
}
// If found a sun, start attracting it
if (closestSun) {
self.targetSun = closestSun;
self.isAttractingSun = true;
self.speed = 0; // Stop moving while attracting
// Mark sun as being attracted so other zombie sols don't target it
closestSun.isBeingAttracted = true;
closestSun.attractedBy = self;
// Start tweening sun towards zombie
tween(closestSun, {
x: self.x,
y: self.y
}, {
duration: closestDistance / 0.6,
// Speed reduced by 300% (was 2.4, now 0.6 = 4x slower)
easing: tween.linear,
onFinish: function onFinish() {
// Sun reached zombie - consume it
if (self.targetSun && self.targetSun.parent) {
self.consumeSun();
}
}
});
}
}
// Check if sun reached zombie (collision detection)
if (self.isAttractingSun && self.targetSun && self.targetSun.parent) {
var distance = Math.abs(self.targetSun.x - self.x) + Math.abs(self.targetSun.y - self.y);
if (distance < 50) {
// Close enough to consume
self.consumeSun();
}
}
// Call original update only if not attracting sun
if (!self.isAttractingSun) {
originalUpdate();
}
};
self.consumeSun = function () {
if (self.targetSun && self.targetSun.parent) {
// Remove sun from game without giving points to player
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self.targetSun) {
suns.splice(i, 1);
break;
}
}
self.targetSun.destroy();
self.targetSun = null;
self.isAttractingSun = false;
self.speed = self.originalSpeed; // Resume movement
}
};
// Override die to stop any sun attraction
var originalDie = self.die;
self.die = function () {
// If attracting a sun, stop the tween and release the sun
if (self.targetSun && self.targetSun.parent) {
tween.stop(self.targetSun);
self.targetSun.isBeingAttracted = false;
self.targetSun.attractedBy = null;
}
originalDie();
};
return self;
});
var ZombieMochila = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'zombie_mochila_body');
self.health = 190;
self.maxHealth = 190;
self.speed = 0.8;
self.originalSpeed = 0.8;
self.backpack = null; // Reference to the backpack object
// Create and attach backpack behind the zombie
self.createBackpack = function () {
if (!self.backpack) {
self.backpack = LK.getAsset('zombie_mochila_backpack', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x + 30,
// Position behind zombie
y: self.y
});
game.addChild(self.backpack);
}
};
// Override update to make backpack follow zombie
var originalUpdate = self.update;
self.update = function () {
// Update backpack position to follow zombie
if (self.backpack && self.backpack.parent) {
self.backpack.x = self.x + 30; // Stay behind zombie
self.backpack.y = self.y;
}
// Call original update
originalUpdate();
};
// Override die to leave backpack behind
var originalDie = self.die;
self.die = function () {
// Leave backpack at current position with 300 HP
if (self.backpack && self.backpack.parent) {
var backpackObj = new BackpackDropped(self.backpack.x, self.backpack.y);
enemies.push(backpackObj);
game.addChild(backpackObj);
// Remove the original backpack graphic
self.backpack.destroy();
self.backpack = null;
}
// Call original die function
originalDie();
};
return self;
});
var ZombieCascanueces = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'cascanueces');
self.health = 1000;
self.maxHealth = 1000;
self.speed = 0.96; // Normal zombie speed (0.8) * 1.2
self.originalSpeed = 0.96;
self.damage = 35; // 35 damage per second
self.isParalyzed = false;
self.paralysisTimer = 0;
self.paralysisTime = 600; // 10 seconds at 60fps
self.hasReachedColumn6 = false;
self.hasReachedColumn4 = false;
var originalUpdate = self.update;
self.update = function () {
// Handle paralysis timer
if (self.isParalyzed) {
self.paralysisTimer--;
if (self.paralysisTimer <= 0) {
// Wake up from paralysis
self.isParalyzed = false;
self.speed = self.originalSpeed;
// Flash animation when waking up
tween(self, {
alpha: 0.3
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
alpha: 1.0
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Second flash
tween(self, {
alpha: 0.3
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
alpha: 1.0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
}
});
}
return; // Don't move while paralyzed
}
// Check current column position
var cellX = Math.floor((self.x - gridStartX) / cellSize);
// Check if reached column 6 and hasn't been paralyzed there yet
if (cellX <= 6 && !self.hasReachedColumn6 && !self.isParalyzed) {
self.hasReachedColumn6 = true;
self.isParalyzed = true;
self.paralysisTimer = self.paralysisTime;
self.speed = 0; // Stop moving
return; // Don't move this frame
}
// Check if reached column 4 and hasn't been paralyzed there yet (and already passed column 6)
if (cellX <= 4 && !self.hasReachedColumn4 && self.hasReachedColumn6 && !self.isParalyzed) {
self.hasReachedColumn4 = true;
self.isParalyzed = true;
self.paralysisTimer = self.paralysisTime;
self.speed = 0; // Stop moving
return; // Don't move this frame
}
// Call original update only if not paralyzed
if (!self.isParalyzed) {
originalUpdate();
}
};
return self;
});
var ZombieCanon = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'canon');
self.health = 500;
self.maxHealth = 500;
self.speed = 0.8;
self.originalSpeed = 0.8;
self.hasReachedPosition = false;
self.shootTimer = 0;
self.shootDelay = 600; // 10 seconds at 60fps
self.firstShot = true; // Track if this is the first shot
self.damage = 0; // Canon doesn't do regular damage
var originalUpdate = self.update;
self.update = function () {
// Move until reaching ninth column
if (!self.hasReachedPosition) {
var cellX = Math.floor((self.x - gridStartX) / cellSize);
if (cellX <= 8) {
self.hasReachedPosition = true;
self.speed = 0; // Stop moving
// Position exactly at ninth column
self.x = gridStartX + 8 * cellSize;
}
}
// If reached position, handle shooting
if (self.hasReachedPosition) {
self.shootTimer++;
var currentDelay = self.firstShot ? 180 : self.shootDelay; // 3 seconds for first shot, 10 seconds for subsequent
if (self.shootTimer >= currentDelay) {
self.shootShell();
self.shootTimer = 0;
self.firstShot = false; // After first shot, use normal delay
}
} else {
// Call original update to move normally
originalUpdate();
}
};
self.shootShell = function () {
// Create zombidito shell
var shell = new CanonShell();
shell.x = self.x;
shell.y = self.y;
shell.gridY = self.gridY;
canonShells.push(shell);
game.addChild(shell);
};
return self;
});
var Zombie = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'zombie');
self.health = 190;
self.maxHealth = 190;
self.speed = 0.8;
self.originalSpeed = 0.8;
return self;
});
var Zombidito = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'zombidito');
self.health = 250;
self.maxHealth = 250;
self.speed = 0.8;
self.originalSpeed = 0.8;
self.damage = 50; // 50 damage per second
// Scale down the graphics to make it smaller
var graphics = self.children[0];
if (graphics) {
graphics.scaleX = 0.7;
graphics.scaleY = 0.7;
}
return self;
});
var SunZombie = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'sun_zombie');
self.health = 190;
self.maxHealth = 190;
self.speed = 0.8;
self.originalSpeed = 0.8;
// Override die function to drop a sun when killed
var originalDie = self.die;
self.die = function () {
// Create a sun at zombie's position
var droppedSun = new Sun(self.x, self.y);
suns.push(droppedSun);
game.addChild(droppedSun);
// Call original die function
originalDie();
};
return self;
});
var SpawnedZombie = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'zombie');
self.health = 225;
self.maxHealth = 225;
self.speed = 1.6; // 2x normal zombie speed (0.8 * 2)
self.originalSpeed = 1.6;
return self;
});
var RugbyZombie = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'rugby');
self.health = 300;
self.maxHealth = 300;
self.speed = 2.4; // 3x normal zombie speed
self.originalSpeed = 2.4;
self.hasContactedPlant = false;
self.damage = 0; // No regular damage since it kills instantly
// Override getPlantInFront to handle instant kill
self.getPlantInFront = function () {
var cellX = Math.floor((self.x - gridStartX) / cellSize);
if (cellX >= 0 && cellX < gridCols && plants[self.gridY] && plants[self.gridY][cellX]) {
return plants[self.gridY][cellX];
}
return null;
};
// Override update to handle 500 damage to plants
var originalUpdate = self.update;
self.update = function () {
var plantInFront = self.getPlantInFront();
if (plantInFront && !self.hasContactedPlant) {
// Deal 500 damage to the plant
plantInFront.takeDamage(500);
// Reduce speed to normal zombie speed
self.hasContactedPlant = true;
self.speed = 0; // Stop for 2 seconds
self.originalSpeed = 0.8;
// Flash effect to show speed change
LK.effects.flashObject(self, 0xFFFF00, 500);
// Resume normal speed after 2 seconds
LK.setTimeout(function () {
self.speed = 0.8;
}, 2000);
}
// Call original update logic
originalUpdate();
};
return self;
});
var MinerZombie = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'miner_zombie');
self.health = 100;
self.maxHealth = 100;
self.speed = 0.8;
self.originalSpeed = 0.8;
self.damage = 15;
// Override the initialization to spawn at 5th column and show explosion
self.initializeMiner = function (row) {
self.gridY = row;
// Position at 5th column (index 4)
self.x = gridStartX + 4 * cellSize;
self.y = gridStartY + row * cellSize;
// Create explosion effect
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 2,
scaleY: 2
});
game.addChild(explosion);
// Animate explosion and remove it
tween(explosion, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
};
return self;
});
var Excabadora = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'excabadora_underground');
self.health = 300; // Canon (500) minus 200 HP total
self.maxHealth = 300;
self.speed = 1.68; // 1.5x normal zombie speed (0.8 * 1.5) increased by 40% (1.2 * 1.4)
self.originalSpeed = 1.68;
self.isUnderground = true;
self.isIncubadora = false;
self.hasTransformed = false;
self.damage = 22.5; // 10% less damage than normal zombie (25 * 0.9)
self.zombiesSpawned = 0;
self.maxZombiesPerIncubation = 2;
self.incubationTimer = 0;
self.movementCellsLeft = 2; // Can only move 2 cells in underground mode after incubation
// Store reference to original takeDamage method
var originalTakeDamage = self.takeDamage;
// Override takeDamage to make underground form immune
self.takeDamage = function (damage) {
if (self.isUnderground) {
return; // Ignore all damage when underground
}
// Call original takeDamage only when surfaced
originalTakeDamage(damage);
};
// Override intersects to make underground form non-collidable
var originalIntersects = self.intersects;
self.intersects = function (other) {
if (self.isUnderground) {
return false; // Cannot be hit by anything when underground
}
return originalIntersects.call(self, other);
};
// Override update to handle transformation and incubation cycle
var originalUpdate = self.update;
self.update = function () {
// Check if reached column 7 and still underground
var cellX = Math.floor((self.x - gridStartX) / cellSize);
if (self.isUnderground && cellX <= 7 && !self.hasTransformed && !self.isIncubadora) {
self.transformToIncubadora();
return;
}
// Handle incubadora mode behavior
if (self.isIncubadora) {
self.incubationTimer++;
// First zombie spawns after 3 seconds (180 ticks)
if (self.zombiesSpawned === 0 && self.incubationTimer >= 180) {
self.spawnZombie();
self.incubationTimer = 0; // Reset timer for next spawn
}
// Second zombie spawns after 10 more seconds (600 ticks)
else if (self.zombiesSpawned === 1 && self.incubationTimer >= 600) {
self.spawnZombie();
self.incubationTimer = 0; // Reset timer for transformation delay
}
// After second zombie, wait 3 seconds then transform back to underground
else if (self.zombiesSpawned >= self.maxZombiesPerIncubation && self.incubationTimer >= 180) {
self.transformToUnderground();
return;
}
// Stay stationary during incubation
return;
}
// Handle underground movement after incubation (limited to 2 cells)
if (self.isUnderground && self.hasTransformed && self.movementCellsLeft > 0) {
var currentCellX = Math.floor((self.x - gridStartX) / cellSize);
var targetX = self.x - self.speed;
var targetCellX = Math.floor((targetX - gridStartX) / cellSize);
// Check if moving to a new cell
if (targetCellX < currentCellX) {
self.movementCellsLeft--;
if (self.movementCellsLeft <= 0) {
// Stop and transform back to incubadora after moving 2 cells
self.transformToIncubadora();
return;
}
}
}
// Call original update
originalUpdate();
};
self.transformToIncubadora = function () {
self.isUnderground = false;
self.isIncubadora = true;
self.speed = 0; // Stationary
self.zombiesSpawned = 0;
self.incubationTimer = 0;
// Change graphics to surface form
self.removeChild(self.children[0]);
var surfaceGraphics = self.attachAsset('excabadora_surface', {
anchorX: 0.5,
anchorY: 0.5
});
// Create emergence effect
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 1.5,
scaleY: 1.5
});
game.addChild(explosion);
// Animate explosion and remove it
tween(explosion, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
};
self.transformToUnderground = function () {
self.isIncubadora = false;
self.isUnderground = true;
self.hasTransformed = true;
self.speed = 1.68; // Return to underground speed (40% faster than original 1.2)
self.movementCellsLeft = 2; // Reset movement limit
self.zombiesSpawned = 0;
self.incubationTimer = 0;
// Change graphics back to underground form
self.removeChild(self.children[0]);
var undergroundGraphics = self.attachAsset('excabadora_underground', {
anchorX: 0.5,
anchorY: 0.5
});
// Create submersion effect
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 1.2,
scaleY: 1.2
});
game.addChild(explosion);
// Animate explosion and remove it
tween(explosion, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
};
self.spawnZombie = function () {
// Create a modified normal zombie with normal zombie asset
var spawnedZombie = new Zombie();
// Remove the original graphics and use normal zombie asset
spawnedZombie.removeChild(spawnedZombie.children[0]);
var normalGraphics = spawnedZombie.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
spawnedZombie.x = self.x;
spawnedZombie.y = self.y;
spawnedZombie.gridY = self.gridY;
// Apply modifications: 20% less health, 35% less speed, 10% less damage
spawnedZombie.health = Math.floor(190 * 0.8); // 152 HP (20% less)
spawnedZombie.maxHealth = Math.floor(190 * 0.8);
spawnedZombie.speed = 0.8 * 0.65; // 0.52 speed (35% less)
spawnedZombie.originalSpeed = 0.8 * 0.65;
spawnedZombie.damage = Math.floor(25 * 0.9); // 22.5 damage (10% less)
enemies.push(spawnedZombie);
game.addChild(spawnedZombie);
self.zombiesSpawned++;
// Create spawn effect
var spawnEffect = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 1.0,
scaleY: 1.0
});
game.addChild(spawnEffect);
tween(spawnEffect, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
spawnEffect.destroy();
}
});
};
return self;
});
var ConeZombie = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'cone_zombie');
self.health = 300;
self.maxHealth = 300;
self.speed = 0.8;
self.originalSpeed = 0.8;
self.hasChangedAsset = false;
var originalTakeDamage = self.takeDamage;
self.takeDamage = function (damage) {
originalTakeDamage(damage);
// Check if cone zombie should switch to normal zombie appearance
if (self.health < 191 && !self.hasChangedAsset) {
self.hasChangedAsset = true;
// Remove current graphics and add normal zombie version
self.removeChild(self.children[0]);
var normalGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
return self;
});
var BucketZombie = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'bucket_zombie');
self.health = 375;
self.maxHealth = 375;
self.speed = 0.8;
self.originalSpeed = 0.8;
self.hasChangedAsset = false;
var originalTakeDamage = self.takeDamage;
self.takeDamage = function (damage) {
originalTakeDamage(damage);
// Check if bucket zombie should switch to normal zombie appearance
if (self.health < 191 && !self.hasChangedAsset) {
self.hasChangedAsset = true;
// Remove current graphics and add normal zombie version
self.removeChild(self.children[0]);
var normalGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1B5E20
});
/****
* Game Code
****/
// Grid setup - bigger grid centered on screen
// Plant defenders
// Projectiles
// Undead enemies
// UI and grid
// Sounds
var gridRows = 5;
var gridCols = 9;
var cellSize = 144;
var gridStartX = (2048 - gridCols * cellSize) / 2;
var gridStartY = (2732 - gridRows * cellSize) / 2;
// Game state
var plants = [];
var enemies = [];
var projectiles = [];
var suns = [];
var canonShells = [];
var lawnmowers = [];
var sunPoints = 500;
var lives = 3;
var currentWave = 1;
var wavesCompleted = 0;
var totalWaves = 10;
var gameMode = null; // 'normal' or 'endless'
var gameModeSelected = false;
var zombieSelectionActive = false;
var selectedZombieTypes = [];
var customWaveConfigs = {}; // Store custom wave configurations
var currentCustomizingWave = 1;
var availableZombieTypes = [{
type: 'zombie',
name: 'Normal Zombie',
asset: 'zombie'
}, {
type: 'cone_zombie',
name: 'Cone Zombie',
asset: 'cone_zombie'
}, {
type: 'bucket_zombie',
name: 'Bucket Zombie',
asset: 'bucket_zombie'
}, {
type: 'sun_zombie',
name: 'Sun Zombie',
asset: 'sun_zombie'
}, {
type: 'zombie_ra',
name: 'Zombie Ra',
asset: 'zombieRa'
}, {
type: 'zombie_canon',
name: 'Zombie Canon',
asset: 'canon'
}, {
type: 'miner_zombie',
name: 'Miner Zombie',
asset: 'miner_zombie'
}, {
type: 'rugby_zombie',
name: 'Rugby Zombie',
asset: 'rugby'
}, {
type: 'zombiestein',
name: 'Zombiestein',
asset: 'zombiestein'
}, {
type: 'zombie_cascanueces',
name: 'Zombie Cascanueces',
asset: 'cascanueces'
}, {
type: 'zombie_sarcofago',
name: 'Zombie Sarcófago',
asset: 'zombie_sarcofago'
}, {
type: 'zombie_mochila',
name: 'Zombie Mochila',
asset: 'zombie_mochila_body'
}, {
type: 'excabadora',
name: 'Excabadora',
asset: 'excabadora_surface'
}];
var baseEnemiesInWave = 0; // Store base enemy count for endless mode scaling
var waveInProgress = false;
var enemiesInWave = 0;
var enemiesKilled = 0;
var sunTimer = 0;
var waveStartTick = 0;
var nextWaveScheduled = false;
var sunZombieSpawnedThisWave = false;
// Spawn collision tracking for waves 1-2
var lastSpawnTime = {};
var lastSpawnRow = {};
// Miner zombie tracking
var minerZombiesSpawnedThisWave = 0;
var maxMinerZombiesPerWave = 1;
// Zombiestein tracking
var zombiesteinSpawned = false;
var zombiesteinAlive = false;
// Performance optimization variables
var maxObjectsPerType = 50;
var cleanupCounter = 0;
var lagOptimizationActive = false;
// Projectile pool for object reuse
var projectilePool = [];
var maxPoolSize = 20;
// Track active projectiles per plant
var plantProjectileCount = {};
// Selected plant type
var selectedPlantType = null;
var plantTypes = ['sunflower', 'birasol', 'peashooter', 'captus', 'boxeador', 'snowpea', 'fireshot', 'repetidora', 'wallnut', 'nuezPrimitiva', 'petacereza', 'carnivora', 'rapaz', 'recogesoles'];
var plantCosts = {
'peashooter': 100,
'captus': 175,
'sunflower': 50,
'birasol': 225,
'snowpea': 175,
'fireshot': 175,
'plantorcha': 175,
'wallnut': 50,
'repetidora': 200,
'nuezPrimitiva': 125,
'petacereza': 150,
'carnivora': 200,
'rapaz': 300,
'boxeador': 150,
'recogesoles': 200
};
// Wallnut cooldown system
var wallnutCooldown = 0;
var wallnutCooldownTime = 720; // 12 seconds at 60fps
// Nuez primitiva cooldown system
var nuezPrimitivaCooldown = 0;
var nuezPrimitivaCooldownTime = 180; // 3 seconds at 60fps
// Petacereza cooldown system
var petacerezaCooldown = 0;
var petacerezaCooldownTime = 900; // 15 seconds at 60fps
// Ghost plant for preview
var ghostPlant = null;
// Initialize grid cells array
var gridCells = [];
// Initialize grid
for (var row = 0; row < gridRows; row++) {
plants[row] = [];
gridCells[row] = [];
for (var col = 0; col < gridCols; col++) {
plants[row][col] = null;
var cell = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
x: gridStartX + col * cellSize,
y: gridStartY + row * cellSize
});
gridCells[row][col] = cell;
game.addChild(cell);
}
}
// Initialize lawnmowers
for (var row = 0; row < gridRows; row++) {
var lawnmower = new Lawnmower(row);
lawnmowers.push(lawnmower);
game.addChild(lawnmower);
}
// Game mode selection UI
var modeSelectionContainer = new Container();
modeSelectionContainer.x = 2048 / 2;
modeSelectionContainer.y = 2732 / 2;
var modeTitle = new Text2('Select Game Mode', {
size: 80,
fill: 0xFFFFFF
});
modeTitle.anchor.set(0.5, 0.5);
modeTitle.y = -200;
modeSelectionContainer.addChild(modeTitle);
// Normal mode button
var normalModeButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: -240,
y: -50,
scaleX: 2,
scaleY: 1.5,
color: 0x4CAF50
});
normalModeButton.interactive = true;
normalModeButton.buttonMode = true;
normalModeButton.down = function (x, y, obj) {
gameMode = 'normal';
totalWaves = 10;
selectGameMode();
};
modeSelectionContainer.addChild(normalModeButton);
var normalModeText = new Text2('Normal', {
size: 60,
fill: 0xFFFFFF
});
normalModeText.anchor.set(0.5, 0.5);
normalModeText.x = -320;
normalModeText.y = -50;
modeSelectionContainer.addChild(normalModeText);
var normalModeSubtext = new Text2('(10 waves)', {
size: 40,
fill: 0xCCCCCC
});
normalModeSubtext.anchor.set(0.5, 0.5);
normalModeSubtext.x = -240;
normalModeSubtext.y = 10;
modeSelectionContainer.addChild(normalModeSubtext);
// Personalize mode button
var personalizeModeButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50,
scaleX: 2,
scaleY: 1.5,
color: 0xFF9800
});
personalizeModeButton.interactive = true;
personalizeModeButton.buttonMode = true;
personalizeModeButton.down = function (x, y, obj) {
gameMode = 'personalize';
totalWaves = 20;
showZombieSelection();
};
modeSelectionContainer.addChild(personalizeModeButton);
var personalizeModeText = new Text2('Personalize', {
size: 60,
fill: 0xFFFFFF
});
personalizeModeText.anchor.set(0.5, 0.5);
personalizeModeText.x = 0;
personalizeModeText.y = -50;
modeSelectionContainer.addChild(personalizeModeText);
var personalizeModeSubtext = new Text2('(20 waves)', {
size: 40,
fill: 0xCCCCCC
});
personalizeModeSubtext.anchor.set(0.5, 0.5);
personalizeModeSubtext.x = 0;
personalizeModeSubtext.y = 10;
modeSelectionContainer.addChild(personalizeModeSubtext);
// Endless mode button
var endlessModeButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: 360,
y: -50,
scaleX: 2,
scaleY: 1.5,
color: 0x9C27B0
});
endlessModeButton.interactive = true;
endlessModeButton.buttonMode = true;
endlessModeButton.down = function (x, y, obj) {
gameMode = 'endless';
totalWaves = 999; // Large number for display purposes
selectGameMode();
};
modeSelectionContainer.addChild(endlessModeButton);
var endlessModeText = new Text2('Endless', {
size: 60,
fill: 0xFFFFFF
});
endlessModeText.anchor.set(0.5, 0.5);
endlessModeText.x = 360;
endlessModeText.y = -50;
modeSelectionContainer.addChild(endlessModeText);
var endlessModeSubtext = new Text2('(infinite waves)', {
size: 40,
fill: 0xCCCCCC
});
endlessModeSubtext.anchor.set(0.5, 0.5);
endlessModeSubtext.x = 360;
endlessModeSubtext.y = 10;
modeSelectionContainer.addChild(endlessModeSubtext);
game.addChild(modeSelectionContainer);
function selectGameMode() {
gameModeSelected = true;
modeSelectionContainer.destroy();
// Apply custom starting suns for personalize mode
if (gameMode === 'personalize' && game.customStartingSuns) {
sunPoints = game.customStartingSuns;
updateSunDisplay();
}
updateWaveDisplay();
// Create skip wave button only for endless mode
if (gameMode === 'endless') {
createSkipWaveButton();
}
// Start first wave after mode selection
LK.setTimeout(function () {
startWave();
}, 3000);
}
function showZombieSelection() {
zombieSelectionActive = true;
modeSelectionContainer.destroy();
// Create zombie selection container
var zombieSelectionContainer = new Container();
zombieSelectionContainer.x = 2048 / 2;
zombieSelectionContainer.y = 2732 / 2;
game.addChild(zombieSelectionContainer);
// Title
var selectionTitle = new Text2('Select Zombies for Endless Mode', {
size: 60,
fill: 0xFFFFFF
});
selectionTitle.anchor.set(0.5, 0.5);
selectionTitle.y = -350;
zombieSelectionContainer.addChild(selectionTitle);
// Instructions
var instructions = new Text2('Click zombies to toggle selection (min 3 required)', {
size: 40,
fill: 0xCCCCCC
});
instructions.anchor.set(0.5, 0.5);
instructions.y = -300;
zombieSelectionContainer.addChild(instructions);
// Create zombie selection buttons
var zombieButtons = [];
var cols = 4;
var rows = Math.ceil(availableZombieTypes.length / cols);
for (var i = 0; i < availableZombieTypes.length; i++) {
var zombieType = availableZombieTypes[i];
var col = i % cols;
var row = Math.floor(i / cols);
// Zombie button
var zombieButton = LK.getAsset(zombieType.asset, {
anchorX: 0.5,
anchorY: 0.5,
x: (col - (cols - 1) / 2) * 200,
y: (row - (rows - 1) / 2) * 160 - 50,
scaleX: 1.5,
scaleY: 1.5
});
zombieButton.zombieType = zombieType.type;
zombieButton.isSelected = false;
zombieButton.interactive = true;
zombieButton.buttonMode = true;
zombieButton.alpha = 0.5; // Start unselected
zombieButton.down = function (x, y, obj) {
toggleZombieSelection(this);
};
zombieSelectionContainer.addChild(zombieButton);
zombieButtons.push(zombieButton);
// Zombie name
var nameText = new Text2(zombieType.name, {
size: 30,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0.5);
nameText.x = zombieButton.x;
nameText.y = zombieButton.y + 80;
zombieSelectionContainer.addChild(nameText);
}
// Start button
var startButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 250,
scaleX: 3,
scaleY: 1.5,
color: 0x4CAF50
});
startButton.interactive = true;
startButton.buttonMode = true;
startButton.alpha = 0.5; // Start disabled
startButton.down = function (x, y, obj) {
if (selectedZombieTypes.length >= 3) {
zombieSelectionContainer.destroy();
showWaveCustomization();
}
};
zombieSelectionContainer.addChild(startButton);
var startButtonText = new Text2('Customize Waves', {
size: 50,
fill: 0xFFFFFF
});
startButtonText.anchor.set(0.5, 0.5);
startButtonText.y = 250;
zombieSelectionContainer.addChild(startButtonText);
// Store references for updates
zombieSelectionContainer.zombieButtons = zombieButtons;
zombieSelectionContainer.startButton = startButton;
zombieSelectionContainer.startButtonText = startButtonText;
game.zombieSelectionContainer = zombieSelectionContainer;
}
function toggleZombieSelection(button) {
if (button.isSelected) {
// Deselect
button.isSelected = false;
button.alpha = 0.5;
button.tint = 0xFFFFFF;
// Remove from selected array
for (var i = 0; i < selectedZombieTypes.length; i++) {
if (selectedZombieTypes[i] === button.zombieType) {
selectedZombieTypes.splice(i, 1);
break;
}
}
} else {
// Select
button.isSelected = true;
button.alpha = 1.0;
button.tint = 0x00FF00;
selectedZombieTypes.push(button.zombieType);
}
updateStartButton();
}
function updateStartButton() {
var container = game.zombieSelectionContainer;
if (!container) {
return;
}
if (selectedZombieTypes.length >= 3) {
container.startButton.alpha = 1.0;
container.startButton.tint = 0x4CAF50;
container.startButtonText.setText('Customize Waves (' + selectedZombieTypes.length + ' selected)');
} else {
container.startButton.alpha = 0.5;
container.startButton.tint = 0x888888;
container.startButtonText.setText('Select at least 3 zombies (' + selectedZombieTypes.length + '/3)');
}
}
function showWaveCustomization() {
// Initialize default configurations for all 20 waves
for (var wave = 1; wave <= 20; wave++) {
if (!customWaveConfigs[wave]) {
customWaveConfigs[wave] = {};
for (var i = 0; i < selectedZombieTypes.length; i++) {
customWaveConfigs[wave][selectedZombieTypes[i]] = wave <= 2 ? 2 : Math.min(wave, 8); // Default quantities
}
}
}
currentCustomizingWave = 1;
showWaveConfigUI();
}
function showWaveConfigUI() {
// Remove existing wave config container if any
if (game.waveConfigContainer) {
game.waveConfigContainer.destroy();
}
// Create wave customization container
var waveConfigContainer = new Container();
waveConfigContainer.x = 2048 / 2;
waveConfigContainer.y = 2732 / 2;
game.addChild(waveConfigContainer);
game.waveConfigContainer = waveConfigContainer;
// Initialize custom starting suns if not set
if (!game.customStartingSuns) {
game.customStartingSuns = 500; // Default starting suns
}
// Title
var configTitle = new Text2('Wave ' + currentCustomizingWave + ' Configuration', {
size: 60,
fill: 0xFFFFFF
});
configTitle.anchor.set(0.5, 0.5);
configTitle.y = -350;
waveConfigContainer.addChild(configTitle);
// Instructions
var configInstructions = new Text2('Set zombie quantities for Wave ' + currentCustomizingWave + ' (1-20)', {
size: 40,
fill: 0xCCCCCC
});
configInstructions.anchor.set(0.5, 0.5);
configInstructions.y = -300;
waveConfigContainer.addChild(configInstructions);
// Initial suns selection (only show on wave 1)
if (currentCustomizingWave === 1) {
var sunsTitle = new Text2('Starting Suns:', {
size: 45,
fill: 0xFFEB3B
});
sunsTitle.anchor.set(0.5, 0.5);
sunsTitle.x = 650;
sunsTitle.y = -200;
waveConfigContainer.addChild(sunsTitle);
// Sun decrease button
var sunDecreaseButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: 600,
y: -150,
scaleX: 0.8,
scaleY: 0.8,
color: 0xF44336
});
sunDecreaseButton.interactive = true;
sunDecreaseButton.buttonMode = true;
sunDecreaseButton.down = function () {
if (game.customStartingSuns > 0) {
game.customStartingSuns = Math.max(0, game.customStartingSuns - 50);
showWaveConfigUI(); // Refresh UI
}
};
waveConfigContainer.addChild(sunDecreaseButton);
var sunDecreaseText = new Text2('-50', {
size: 35,
fill: 0xFFFFFF
});
sunDecreaseText.anchor.set(0.5, 0.5);
sunDecreaseText.x = 600;
sunDecreaseText.y = -150;
waveConfigContainer.addChild(sunDecreaseText);
// Sun display
var sunAmountText = new Text2(game.customStartingSuns.toString(), {
size: 50,
fill: 0xFFEB3B
});
sunAmountText.anchor.set(0.5, 0.5);
sunAmountText.x = 700;
sunAmountText.y = -150;
waveConfigContainer.addChild(sunAmountText);
// Sun increase button
var sunIncreaseButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: 800,
y: -150,
scaleX: 0.8,
scaleY: 0.8,
color: 0x4CAF50
});
sunIncreaseButton.interactive = true;
sunIncreaseButton.buttonMode = true;
sunIncreaseButton.down = function () {
if (game.customStartingSuns < 9999) {
game.customStartingSuns = Math.min(9999, game.customStartingSuns + 50);
showWaveConfigUI(); // Refresh UI
}
};
waveConfigContainer.addChild(sunIncreaseButton);
var sunIncreaseText = new Text2('+50', {
size: 35,
fill: 0xFFFFFF
});
sunIncreaseText.anchor.set(0.5, 0.5);
sunIncreaseText.x = 800;
sunIncreaseText.y = -150;
waveConfigContainer.addChild(sunIncreaseText);
// Preset buttons for common sun amounts
var presetAmounts = [250, 500, 750, 1000, 2000];
for (var p = 0; p < presetAmounts.length; p++) {
var presetButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: 650 + p * 90,
y: -100,
scaleX: 0.6,
scaleY: 0.6,
color: game.customStartingSuns === presetAmounts[p] ? 0x4CAF50 : 0x2196F3
});
presetButton.presetAmount = presetAmounts[p];
presetButton.interactive = true;
presetButton.buttonMode = true;
presetButton.down = function () {
game.customStartingSuns = this.presetAmount;
showWaveConfigUI(); // Refresh UI
};
waveConfigContainer.addChild(presetButton);
var presetText = new Text2(presetAmounts[p].toString(), {
size: 25,
fill: 0xFFFFFF
});
presetText.anchor.set(0.5, 0.5);
presetText.x = 650 + p * 90;
presetText.y = -100;
waveConfigContainer.addChild(presetText);
}
}
// Wave navigation buttons - positioned slightly higher
var prevWaveButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: -400,
y: -250,
scaleX: 1.5,
scaleY: 1.0,
color: currentCustomizingWave > 1 ? 0x2196F3 : 0x666666
});
prevWaveButton.interactive = currentCustomizingWave > 1;
prevWaveButton.buttonMode = currentCustomizingWave > 1;
if (currentCustomizingWave > 1) {
prevWaveButton.down = function () {
currentCustomizingWave--;
showWaveConfigUI();
};
}
waveConfigContainer.addChild(prevWaveButton);
var prevWaveText = new Text2('◀ Previous', {
size: 40,
fill: 0xFFFFFF
});
prevWaveText.anchor.set(0.5, 0.5);
prevWaveText.x = -400;
prevWaveText.y = -250;
waveConfigContainer.addChild(prevWaveText);
var nextWaveButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: 400,
y: -250,
scaleX: 1.5,
scaleY: 1.0,
color: currentCustomizingWave < 20 ? 0x2196F3 : 0x666666
});
nextWaveButton.interactive = currentCustomizingWave < 20;
nextWaveButton.buttonMode = currentCustomizingWave < 20;
if (currentCustomizingWave < 20) {
nextWaveButton.down = function () {
currentCustomizingWave++;
showWaveConfigUI();
};
}
waveConfigContainer.addChild(nextWaveButton);
var nextWaveText = new Text2('Next ▶', {
size: 40,
fill: 0xFFFFFF
});
nextWaveText.anchor.set(0.5, 0.5);
nextWaveText.x = 400;
nextWaveText.y = -250;
waveConfigContainer.addChild(nextWaveText);
// Add "All to 0" and "All to 1" buttons on the left side
var allToZeroButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: -700,
y: -50,
scaleX: 1.5,
scaleY: 1.0,
color: 0xF44336
});
allToZeroButton.interactive = true;
allToZeroButton.buttonMode = true;
allToZeroButton.down = function () {
// Set all zombie quantities to 0 for current wave
for (var i = 0; i < selectedZombieTypes.length; i++) {
customWaveConfigs[currentCustomizingWave][selectedZombieTypes[i]] = 0;
}
showWaveConfigUI(); // Refresh UI
};
waveConfigContainer.addChild(allToZeroButton);
var allToZeroText = new Text2('All to 0', {
size: 35,
fill: 0xFFFFFF
});
allToZeroText.anchor.set(0.5, 0.5);
allToZeroText.x = -700;
allToZeroText.y = -50;
waveConfigContainer.addChild(allToZeroText);
var allToOneButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: -700,
y: 20,
scaleX: 1.5,
scaleY: 1.0,
color: 0x4CAF50
});
allToOneButton.interactive = true;
allToOneButton.buttonMode = true;
allToOneButton.down = function () {
// Set all zombie quantities to 1 for current wave
for (var i = 0; i < selectedZombieTypes.length; i++) {
customWaveConfigs[currentCustomizingWave][selectedZombieTypes[i]] = 1;
}
showWaveConfigUI(); // Refresh UI
};
waveConfigContainer.addChild(allToOneButton);
var allToOneText = new Text2('All to 1', {
size: 35,
fill: 0xFFFFFF
});
allToOneText.anchor.set(0.5, 0.5);
allToOneText.x = -700;
allToOneText.y = 20;
waveConfigContainer.addChild(allToOneText);
// Zombie quantity controls
var yOffset = -100;
var zombieControls = [];
for (var i = 0; i < selectedZombieTypes.length; i++) {
var zombieType = selectedZombieTypes[i];
var zombieAsset = null;
for (var j = 0; j < availableZombieTypes.length; j++) {
if (availableZombieTypes[j].type === zombieType) {
zombieAsset = availableZombieTypes[j].asset;
break;
}
}
if (zombieAsset) {
// Zombie icon
var zombieIcon = LK.getAsset(zombieAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: -300,
y: yOffset,
scaleX: 1.2,
scaleY: 1.2
});
waveConfigContainer.addChild(zombieIcon);
// Decrease button
var decreaseButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: -100,
y: yOffset,
scaleX: 0.8,
scaleY: 0.8,
color: 0xF44336
});
decreaseButton.zombieType = zombieType;
decreaseButton.interactive = true;
decreaseButton.buttonMode = true;
decreaseButton.down = function () {
var currentQty = customWaveConfigs[currentCustomizingWave][this.zombieType] || 0;
if (currentQty > 0) {
customWaveConfigs[currentCustomizingWave][this.zombieType] = currentQty - 1;
showWaveConfigUI();
}
};
waveConfigContainer.addChild(decreaseButton);
var decreaseText = new Text2('-', {
size: 50,
fill: 0xFFFFFF
});
decreaseText.anchor.set(0.5, 0.5);
decreaseText.x = -100;
decreaseText.y = yOffset;
waveConfigContainer.addChild(decreaseText);
// Quantity display
var quantity = customWaveConfigs[currentCustomizingWave][zombieType] || 0;
var quantityText = new Text2(quantity.toString(), {
size: 45,
fill: 0xFFFFFF
});
quantityText.anchor.set(0.5, 0.5);
quantityText.x = 0;
quantityText.y = yOffset;
waveConfigContainer.addChild(quantityText);
// Increase button
var increaseButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: yOffset,
scaleX: 0.8,
scaleY: 0.8,
color: 0x4CAF50
});
increaseButton.zombieType = zombieType;
increaseButton.interactive = true;
increaseButton.buttonMode = true;
increaseButton.down = function () {
var currentQty = customWaveConfigs[currentCustomizingWave][this.zombieType] || 0;
if (currentQty < 15) {
// Max 15 zombies per type per wave
customWaveConfigs[currentCustomizingWave][this.zombieType] = currentQty + 1;
showWaveConfigUI();
}
};
waveConfigContainer.addChild(increaseButton);
var increaseText = new Text2('+', {
size: 40,
fill: 0xFFFFFF
});
increaseText.anchor.set(0.5, 0.5);
increaseText.x = 100;
increaseText.y = yOffset;
waveConfigContainer.addChild(increaseText);
// Zombie name
var zombieName = null;
for (var k = 0; k < availableZombieTypes.length; k++) {
if (availableZombieTypes[k].type === zombieType) {
zombieName = availableZombieTypes[k].name;
break;
}
}
var nameText = new Text2(zombieName || zombieType, {
size: 30,
fill: 0xCCCCCC
});
nameText.anchor.set(0.5, 0.5);
nameText.x = 300;
nameText.y = yOffset;
waveConfigContainer.addChild(nameText);
yOffset += 70;
}
}
// Finish customization button - positioned at bottom right
var finishButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: 600,
y: 350,
scaleX: 3,
scaleY: 1.5,
color: 0x4CAF50
});
finishButton.interactive = true;
finishButton.buttonMode = true;
finishButton.down = function () {
waveConfigContainer.destroy();
selectGameMode();
};
waveConfigContainer.addChild(finishButton);
var finishButtonText = new Text2('Start Game', {
size: 50,
fill: 0xFFFFFF
});
finishButtonText.anchor.set(0.5, 0.5);
finishButtonText.x = 600;
finishButtonText.y = 350;
waveConfigContainer.addChild(finishButtonText);
}
// Shovel tool for removing plants
var selectedTool = null; // 'shovel' or null
var shovelButton = LK.getAsset('shovel', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 100,
scaleX: 1.5,
scaleY: 1.5
});
shovelButton.interactive = true;
shovelButton.buttonMode = true;
shovelButton.down = function (x, y, obj) {
console.log("Shovel button clicked");
if (selectedTool === 'shovel') {
selectedTool = null;
shovelButton.alpha = 0.8;
shovelButton.scaleX = shovelButton.scaleY = 1.5;
} else {
selectedTool = 'shovel';
selectedPlantType = null;
shovelButton.alpha = 1.0;
shovelButton.scaleX = shovelButton.scaleY = 1.8;
updatePlantSelection();
}
};
shovelButton.alpha = 0.8;
game.addChild(shovelButton);
// UI Elements
var sunDisplay = new Text2('Sun: ' + sunPoints, {
size: 60,
fill: 0xFFEB3B
});
sunDisplay.anchor.set(0, 0);
sunDisplay.x = 120; // Offset from left edge to avoid menu icon
LK.gui.topLeft.addChild(sunDisplay);
var livesDisplay = new Text2('Lives: ' + lives, {
size: 60,
fill: 0xF44336
});
livesDisplay.anchor.set(0, 0);
livesDisplay.y = 80;
LK.gui.topRight.addChild(livesDisplay);
var waveDisplay = new Text2('Wave: ' + currentWave + '/' + totalWaves, {
size: 50,
fill: 0x4CAF50
});
waveDisplay.anchor.set(0.5, 0);
LK.gui.top.addChild(waveDisplay);
// Plant selection UI at bottom
var plantButtons = [];
var uiY = 2732 - 150;
for (var i = 0; i < plantTypes.length; i++) {
var plantType = plantTypes[i];
var yPos = uiY;
var xPos = 200 + i * 220;
// Special positioning for carnivora - place it above peashooter
if (plantType === 'carnivora') {
xPos = 860; // 100% to the right from peashooter position (640 + 220)
yPos = uiY - 266; // Above peashooter
}
// Special positioning for petacereza - place it above birasol
else if (plantType === 'petacereza') {
xPos = 420; // Same x position as birasol
yPos = uiY - 266; // Above birasol, 10% higher than previous position (242 * 1.1 = 266)
}
// Special positioning for rapaz - place it above snowpea
else if (plantType === 'rapaz') {
xPos = 640; // Same x position as snowpea (moved 100% left)
yPos = uiY - 266; // Above snowpea
}
// Special positioning for captus - place it to the right of rapaz and above fireshot
else if (plantType === 'captus') {
xPos = 1080; // 100% to the right from previous position (860 + 220)
yPos = uiY - 266; // Above fireshot, same height as rapaz
}
// Special positioning for boxeador - place it to the right of captus
else if (plantType === 'boxeador') {
xPos = 1300; // 100% to the right from captus position (1080 + 220)
yPos = uiY - 266; // Same height as captus and rapaz
}
// Move specific plant icons 100% to the left (220 pixels)
if (plantType === 'snowpea' || plantType === 'fireshot' || plantType === 'repetidora' || plantType === 'wallnut' || plantType === 'nuezPrimitiva') {
xPos -= 440; // Move 200% to the left (two button widths total)
}
var button = LK.getAsset(plantType, {
anchorX: 0.5,
anchorY: 0.5,
x: xPos,
y: yPos,
scaleX: 2.25,
scaleY: 2.25
});
button.plantType = plantType;
button.interactive = true; // Enable button interactivity
button.buttonMode = true; // Make it behave like a button
button.down = function (x, y, obj) {
console.log("Plant button clicked:", this.plantType, "Cost:", plantCosts[this.plantType], "Current suns:", sunPoints);
// Check wallnut cooldown
if (this.plantType === 'wallnut' && wallnutCooldown > 0) {
return; // Don't allow selection during cooldown
}
// Check nuez primitiva cooldown
if (this.plantType === 'nuezPrimitiva' && nuezPrimitivaCooldown > 0) {
return; // Don't allow selection during cooldown
}
// Check petacereza cooldown
if (this.plantType === 'petacereza' && petacerezaCooldown > 0) {
return; // Don't allow selection during cooldown
}
if (sunPoints >= plantCosts[this.plantType]) {
selectedPlantType = this.plantType;
selectedTool = null;
shovelButton.alpha = 0.8;
shovelButton.scaleX = shovelButton.scaleY = 1.5;
updatePlantSelection();
}
};
game.addChild(button);
plantButtons.push(button);
// Add cost text
var costText = new Text2(plantCosts[plantType], {
size: 40,
fill: 0xFFFFFF
});
costText.anchor.set(0.5, 0);
costText.x = button.x;
costText.y = button.y + 80;
game.addChild(costText);
}
// Add plantorcha button above sunflower
var plantorchaButton = LK.getAsset('plantorcha', {
anchorX: 0.5,
anchorY: 0.5,
x: 150,
// 25% more to the left from position 200 (200 - 50 = 150)
y: uiY - 351,
// Above sunflower with 10% more space above previous position (319 * 1.1 = 351)
scaleX: 2.25,
scaleY: 2.25
});
plantorchaButton.plantType = 'plantorcha';
plantorchaButton.interactive = true;
plantorchaButton.buttonMode = true;
plantorchaButton.down = function (x, y, obj) {
console.log("Plant button clicked:", this.plantType, "Cost:", plantCosts[this.plantType], "Current suns:", sunPoints);
if (sunPoints >= plantCosts[this.plantType]) {
selectedPlantType = this.plantType;
selectedTool = null;
shovelButton.alpha = 0.8;
shovelButton.scaleX = shovelButton.scaleY = 1.5;
updatePlantSelection();
}
};
game.addChild(plantorchaButton);
plantButtons.push(plantorchaButton);
// Add plantorcha cost text
var plantorchaCostText = new Text2(plantCosts['plantorcha'], {
size: 40,
fill: 0xFFFFFF
});
plantorchaCostText.anchor.set(0.5, 0);
plantorchaCostText.x = plantorchaButton.x;
plantorchaCostText.y = plantorchaButton.y + 80;
game.addChild(plantorchaCostText);
function updatePlantSelection() {
for (var i = 0; i < plantButtons.length; i++) {
if (plantButtons[i].plantType === selectedPlantType) {
plantButtons[i].alpha = 1.0;
plantButtons[i].scaleX = plantButtons[i].scaleY = 2.55;
} else if (plantButtons[i].plantType === 'wallnut' && wallnutCooldown > 0) {
// Gray out wallnut during cooldown
plantButtons[i].alpha = 0.3;
plantButtons[i].scaleX = plantButtons[i].scaleY = 2.25;
plantButtons[i].tint = 0x888888;
} else if (plantButtons[i].plantType === 'nuezPrimitiva' && nuezPrimitivaCooldown > 0) {
// Gray out nuez primitiva during cooldown
plantButtons[i].alpha = 0.3;
plantButtons[i].scaleX = plantButtons[i].scaleY = 2.25;
plantButtons[i].tint = 0x888888;
} else if (plantButtons[i].plantType === 'petacereza' && petacerezaCooldown > 0) {
// Gray out petacereza during cooldown
plantButtons[i].alpha = 0.3;
plantButtons[i].scaleX = plantButtons[i].scaleY = 2.25;
plantButtons[i].tint = 0x888888;
} else {
plantButtons[i].alpha = 0.6;
plantButtons[i].scaleX = plantButtons[i].scaleY = 2.25;
plantButtons[i].tint = 0xFFFFFF;
}
}
// Remove existing ghost plant
if (ghostPlant) {
ghostPlant.destroy();
ghostPlant = null;
}
// Create new ghost plant if a type is selected
if (selectedPlantType) {
ghostPlant = LK.getAsset(selectedPlantType, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0x88FF88
});
game.addChild(ghostPlant);
}
// Highlight valid placement squares
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
if (selectedPlantType && canPlacePlant(col, row, selectedPlantType)) {
// Green highlight for valid empty squares
gridCells[row][col].tint = 0x00FF00;
gridCells[row][col].alpha = 0.7;
} else if (selectedPlantType && plants[row][col] !== null) {
// Red highlight for occupied squares when plant is selected
gridCells[row][col].tint = 0xFF0000;
gridCells[row][col].alpha = 0.5;
} else if (selectedTool === 'shovel' && plants[row][col] !== null) {
// Yellow highlight for plants that can be removed with shovel
gridCells[row][col].tint = 0xFFFF00;
gridCells[row][col].alpha = 0.7;
} else {
// Normal appearance for unselected or invalid squares
gridCells[row][col].tint = 0xFFFFFF;
gridCells[row][col].alpha = 0.3;
}
}
}
}
function updateSunDisplay() {
sunDisplay.setText('Sun: ' + sunPoints);
}
function updateLivesDisplay() {
livesDisplay.setText('Lives: ' + lives);
}
function updateWaveDisplay() {
if (gameMode === 'endless') {
waveDisplay.setText('Wave: ' + currentWave + ' / ∞');
} else if (gameMode === 'personalize') {
waveDisplay.setText('Wave: ' + currentWave + '/' + totalWaves);
} else {
waveDisplay.setText('Wave: ' + currentWave + '/' + totalWaves);
}
}
function createProjectile(type, startX, startY, speed, damage, plantId) {
// Check if plant already has 2 active projectiles
if (!plantProjectileCount[plantId]) {
plantProjectileCount[plantId] = 0;
}
if (plantProjectileCount[plantId] >= 2) {
return null; // Don't create projectile if plant already has 2
}
var projectile;
if (projectilePool.length > 0) {
// Reuse from pool
projectile = projectilePool.pop();
projectile.speed = speed;
projectile.damage = damage;
projectile.x = startX;
projectile.y = startY;
projectile.type = type;
projectile.gridY = Math.floor((startY - gridStartY + cellSize / 2) / cellSize);
projectile.ownerId = plantId;
// Re-add graphics
var graphics = projectile.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
} else {
// Create new projectile
projectile = new Projectile(type, startX, startY, speed, damage);
projectile.ownerId = plantId;
}
plantProjectileCount[plantId]++;
projectiles.push(projectile);
game.addChild(projectile);
return projectile;
}
function createCaptusProjectile(type, startX, startY, speed, damage, plantId) {
// Check if plant already has 2 active projectiles
if (!plantProjectileCount[plantId]) {
plantProjectileCount[plantId] = 0;
}
if (plantProjectileCount[plantId] >= 2) {
return null; // Don't create projectile if plant already has 2
}
var projectile;
if (projectilePool.length > 0) {
// Reuse from pool
projectile = projectilePool.pop();
projectile.speed = speed;
projectile.damage = damage;
projectile.x = startX;
projectile.y = startY;
projectile.type = type;
projectile.gridY = Math.floor((startY - gridStartY + cellSize / 2) / cellSize);
projectile.ownerId = plantId;
projectile.hitCount = 0; // Initialize hit counter for captus projectiles
projectile.maxHits = 2; // Captus projectiles must hit 2 zombies
projectile.hitZombies = []; // Track zombies that have already been hit
// Re-add graphics
var graphics = projectile.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
// Override update method for captus projectiles
projectile.update = function () {
projectile.x += projectile.speed;
if (projectile.x > 2048) {
projectile.removeFromGame();
return;
}
// Check collision with plantorcha to convert pea to fire projectile
if (projectile.type === 'captus_pea') {
var projectileGridX = Math.floor((projectile.x - gridStartX + cellSize / 2) / cellSize);
var projectileGridY = projectile.gridY;
if (projectileGridX >= 0 && projectileGridX < gridCols && projectileGridY >= 0 && projectileGridY < gridRows) {
var plantInCell = plants[projectileGridY][projectileGridX];
if (plantInCell && plantInCell.plantType === 'plantorcha' && projectile.intersects(plantInCell)) {
// Convert captus_pea to shotfire and add 10 damage
projectile.removeChild(projectile.children[0]);
var fireGraphics = projectile.attachAsset('shotfire', {
anchorX: 0.5,
anchorY: 0.5
});
projectile.type = 'shotfire';
projectile.damage += 10;
// Continue with normal movement
}
}
}
// Only check collisions with zombies in same row and within 100px range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === projectile.gridY && Math.abs(enemy.x - projectile.x) <= 100 && projectile.intersects(enemy)) {
// Skip collision entirely if zombie is being consumed by rapaz (immune to all projectiles)
if (enemy.beingConsumed) {
continue; // Skip this zombie completely - projectile passes through
}
// Skip if this zombie has already been hit by this projectile
var alreadyHit = false;
for (var j = 0; j < projectile.hitZombies.length; j++) {
if (projectile.hitZombies[j] === enemy) {
alreadyHit = true;
break;
}
}
if (alreadyHit) {
continue; // Skip this zombie - already hit
}
enemy.takeDamage(projectile.damage);
// Apply ice effect if this is a snowshot
if (projectile.type === 'snowshot') {
enemy.applyIceEffect();
}
// Add this zombie to the hit list
projectile.hitZombies.push(enemy);
// Increment hit count for captus projectiles
projectile.hitCount++;
// Only remove projectile if it has hit the required number of zombies (2)
if (projectile.hitCount >= projectile.maxHits) {
projectile.removeFromGame();
break; // Exit the loop after removing projectile
} else {
// Continue moving after hitting zombie but before reaching max hits
// Don't break here so projectile continues
}
}
}
};
} else {
// Create new captus projectile
projectile = new Projectile(type, startX, startY, speed, damage);
projectile.ownerId = plantId;
projectile.hitCount = 0; // Initialize hit counter for captus projectiles
projectile.maxHits = 2; // Captus projectiles must hit 2 zombies
projectile.hitZombies = []; // Track zombies that have already been hit
// Override update method for captus projectiles
projectile.update = function () {
projectile.x += projectile.speed;
if (projectile.x > 2048) {
projectile.removeFromGame();
return;
}
// Check collision with plantorcha to convert pea to fire projectile
if (projectile.type === 'captus_pea') {
var projectileGridX = Math.floor((projectile.x - gridStartX + cellSize / 2) / cellSize);
var projectileGridY = projectile.gridY;
if (projectileGridX >= 0 && projectileGridX < gridCols && projectileGridY >= 0 && projectileGridY < gridRows) {
var plantInCell = plants[projectileGridY][projectileGridX];
if (plantInCell && plantInCell.plantType === 'plantorcha' && projectile.intersects(plantInCell)) {
// Convert captus_pea to shotfire and add 10 damage
projectile.removeChild(projectile.children[0]);
var fireGraphics = projectile.attachAsset('shotfire', {
anchorX: 0.5,
anchorY: 0.5
});
projectile.type = 'shotfire';
projectile.damage += 10;
// Continue with normal movement
}
}
}
// Only check collisions with zombies in same row and within 100px range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === projectile.gridY && Math.abs(enemy.x - projectile.x) <= 100 && projectile.intersects(enemy)) {
// Skip collision entirely if zombie is being consumed by rapaz (immune to all projectiles)
if (enemy.beingConsumed) {
continue; // Skip this zombie completely - projectile passes through
}
// Skip if this zombie has already been hit by this projectile
var alreadyHit = false;
for (var j = 0; j < projectile.hitZombies.length; j++) {
if (projectile.hitZombies[j] === enemy) {
alreadyHit = true;
break;
}
}
if (alreadyHit) {
continue; // Skip this zombie - already hit
}
enemy.takeDamage(projectile.damage);
// Apply ice effect if this is a snowshot
if (projectile.type === 'snowshot') {
enemy.applyIceEffect();
}
// Add this zombie to the hit list
projectile.hitZombies.push(enemy);
// Increment hit count for captus projectiles
projectile.hitCount++;
// Only remove projectile if it has hit the required number of zombies (2)
if (projectile.hitCount >= projectile.maxHits) {
projectile.removeFromGame();
break; // Exit the loop after removing projectile
} else {
// Continue moving after hitting zombie but before reaching max hits
// Don't break here so projectile continues
}
}
}
};
}
plantProjectileCount[plantId]++;
projectiles.push(projectile);
game.addChild(projectile);
return projectile;
}
function performanceCleanup() {
// Clean up destroyed objects that might still be in arrays
for (var i = enemies.length - 1; i >= 0; i--) {
if (!enemies[i].parent) {
enemies.splice(i, 1);
}
}
for (var i = projectiles.length - 1; i >= 0; i--) {
if (!projectiles[i].parent) {
projectiles.splice(i, 1);
}
}
for (var i = suns.length - 1; i >= 0; i--) {
if (!suns[i].parent) {
suns.splice(i, 1);
}
}
for (var i = canonShells.length - 1; i >= 0; i--) {
if (!canonShells[i].parent) {
canonShells.splice(i, 1);
}
}
// Clean up projectile tracking for destroyed plants
for (var plantId in plantProjectileCount) {
var coords = plantId.split('_');
var x = parseInt(coords[0]);
var y = parseInt(coords[1]);
if (y >= 0 && y < gridRows && x >= 0 && x < gridCols && !plants[y][x]) {
delete plantProjectileCount[plantId];
}
}
// Limit maximum objects if we have too many
if (enemies.length > maxObjectsPerType) {
for (var i = 0; i < enemies.length - maxObjectsPerType; i++) {
if (enemies[i]) {
enemies[i].destroy();
}
}
}
if (projectiles.length > maxObjectsPerType) {
for (var i = 0; i < projectiles.length - maxObjectsPerType; i++) {
if (projectiles[i]) {
projectiles[i].removeFromGame();
}
}
}
if (suns.length > 20) {
for (var i = 0; i < suns.length - 20; i++) {
if (suns[i]) {
suns[i].destroy();
}
}
}
// Limit projectile pool size
if (projectilePool.length > maxPoolSize) {
for (var i = maxPoolSize; i < projectilePool.length; i++) {
if (projectilePool[i]) {
projectilePool[i].destroy();
}
}
projectilePool.length = maxPoolSize;
}
}
function getGridPosition(x, y) {
var gridX = Math.floor((x - gridStartX + cellSize / 2) / cellSize);
var gridY = Math.floor((y - gridStartY + cellSize / 2) / cellSize);
if (gridX >= 0 && gridX < gridCols && gridY >= 0 && gridY < gridRows) {
return {
x: gridX,
y: gridY
};
}
return null;
}
function canPlacePlant(gridX, gridY, plantType) {
// Check if grid position is valid
if (gridX < 0 || gridX >= gridCols || gridY < 0 || gridY >= gridRows) {
return false;
}
// Check wallnut cooldown
if (plantType === 'wallnut' && wallnutCooldown > 0) {
return false;
}
// Check nuez primitiva cooldown
if (plantType === 'nuezPrimitiva' && nuezPrimitivaCooldown > 0) {
return false;
}
// Check petacereza cooldown
if (plantType === 'petacereza' && petacerezaCooldown > 0) {
return false;
}
// Check if square is empty and player has enough suns
return plants[gridY][gridX] === null && sunPoints >= plantCosts[plantType];
}
function placePlant(gridX, gridY, plantType) {
// Double-check that placement is valid before proceeding
if (!canPlacePlant(gridX, gridY, plantType)) {
return false;
}
var plant;
switch (plantType) {
case 'peashooter':
plant = new Peashooter();
break;
case 'captus':
plant = new Captus();
break;
case 'sunflower':
plant = new Sunflower();
break;
case 'birasol':
plant = new Birasol();
break;
case 'snowpea':
plant = new SnowPea();
break;
case 'fireshot':
plant = new Fireshot();
break;
case 'plantorcha':
plant = new Plantorcha();
break;
case 'wallnut':
plant = new Wallnut();
break;
case 'snowpea':
plant = new SnowPea();
break;
case 'repetidora':
plant = new Repetidora();
break;
case 'nuezPrimitiva':
plant = new NuezPrimitiva();
break;
case 'petacereza':
plant = new Petacereza();
break;
case 'carnivora':
plant = new Carnivora();
break;
case 'rapaz':
plant = new Rapaz();
break;
case 'boxeador':
plant = new Boxeador();
break;
case 'recogesoles':
plant = new Recogesoles();
break;
default:
return false;
}
// Set plant position and grid reference
plant.gridX = gridX;
plant.gridY = gridY;
plant.x = gridStartX + gridX * cellSize;
plant.y = gridStartY + gridY * cellSize;
// Place plant in grid and add to game
plants[gridY][gridX] = plant;
game.addChild(plant);
// Deduct cost and update UI
sunPoints -= plantCosts[plantType];
updateSunDisplay();
LK.getSound('plant').play();
// Start wallnut cooldown if placing wallnut
if (plantType === 'wallnut') {
wallnutCooldown = wallnutCooldownTime;
selectedPlantType = null; // Deselect wallnut after placing
updatePlantSelection();
}
// Start nuez primitiva cooldown if placing nuez primitiva
if (plantType === 'nuezPrimitiva') {
nuezPrimitivaCooldown = nuezPrimitivaCooldownTime;
selectedPlantType = null; // Deselect nuez primitiva after placing
updatePlantSelection();
}
// Start petacereza cooldown if placing petacereza
if (plantType === 'petacereza') {
petacerezaCooldown = petacerezaCooldownTime;
selectedPlantType = null; // Deselect petacereza after placing
updatePlantSelection();
}
// Flash the grid cell to show successful placement
LK.effects.flashObject(gridCells[gridY][gridX], 0x00FF00, 300);
return true;
}
function canSpawnWithoutCollision(row) {
// Only apply collision detection for waves 1-2
if (currentWave > 2) {
return true;
}
var currentTime = LK.ticks;
var oneSecond = 60; // 60 ticks = 1 second at 60fps
// Check if same row was used in last second
if (lastSpawnRow[row] && currentTime - lastSpawnRow[row] < oneSecond) {
return false;
}
// Check if any zombie was spawned in last second
for (var checkRow in lastSpawnTime) {
if (currentTime - lastSpawnTime[checkRow] < oneSecond) {
return false;
}
}
return true;
}
function spawnZombie(type, row) {
var zombie;
switch (type) {
case 'zombie':
zombie = new Zombie();
break;
case 'cone_zombie':
zombie = new ConeZombie();
break;
case 'bucket_zombie':
zombie = new BucketZombie();
break;
case 'sun_zombie':
zombie = new SunZombie();
break;
case 'zombie_ra':
zombie = new ZombieRa();
break;
case 'zombie_canon':
zombie = new ZombieCanon();
break;
case 'miner_zombie':
zombie = new MinerZombie();
// Special positioning for miner
zombie.initializeMiner(row);
// Apply health bonus for endless mode after wave 10
if (gameMode === 'endless' && currentWave > 10) {
var healthMultiplier = 1 + (currentWave - 10) * 0.05;
zombie.health = Math.floor(zombie.health * healthMultiplier);
zombie.maxHealth = Math.floor(zombie.maxHealth * healthMultiplier);
}
enemies.push(zombie);
game.addChild(zombie);
return;
// Return early since positioning is handled
case 'zombiestein':
zombie = new Zombiestein();
break;
case 'rugby_zombie':
zombie = new RugbyZombie();
break;
case 'zombie_cascanueces':
zombie = new ZombieCascanueces();
break;
case 'zombie_sarcofago':
zombie = new ZombieSarcofago();
break;
case 'zombie_mochila':
zombie = new ZombieMochila();
break;
case 'excabadora':
zombie = new Excabadora();
break;
default:
return;
}
zombie.gridY = row;
zombie.x = 2100;
zombie.y = gridStartY + row * cellSize;
// Apply health bonus for endless mode after wave 10
if (gameMode === 'endless' && currentWave > 10) {
var healthMultiplier = 1 + (currentWave - 10) * 0.05;
zombie.health = Math.floor(zombie.health * healthMultiplier);
zombie.maxHealth = Math.floor(zombie.maxHealth * healthMultiplier);
}
enemies.push(zombie);
game.addChild(zombie);
// Create backpack for zombie mochila after positioning
if (zombie.zombieType === 'zombie_mochila_body') {
zombie.createBackpack();
}
}
function startWave() {
if (waveInProgress) {
return;
}
waveInProgress = true;
waveStartTick = LK.ticks; // Record when wave started
sunZombieSpawnedThisWave = false; // Reset sun zombie spawn flag for this wave
// Reset and update miner zombie tracking for this wave
minerZombiesSpawnedThisWave = 0;
if (currentWave >= 8) {
maxMinerZombiesPerWave = 2;
} else {
maxMinerZombiesPerWave = 1;
}
var bucketZombiesLeft = 0;
// Handle custom wave configurations for waves 1-20 in personalize mode
if (gameMode === 'personalize' && currentWave <= 20 && customWaveConfigs[currentWave]) {
var waveConfig = customWaveConfigs[currentWave];
var totalZombies = 0;
var spawnQueue = [];
// Build spawn queue from custom configuration
for (var zombieType in waveConfig) {
var quantity = waveConfig[zombieType];
for (var i = 0; i < quantity; i++) {
spawnQueue.push(zombieType);
totalZombies++;
}
}
enemiesInWave = totalZombies;
enemiesKilled = 0;
// Spawn zombies from custom configuration
if (spawnQueue.length > 0) {
var spawnIndex = 0;
var customSpawnTimer = LK.setInterval(function () {
if (spawnIndex < spawnQueue.length) {
var zombieType = spawnQueue[spawnIndex];
var row = Math.floor(Math.random() * gridRows);
spawnZombie(zombieType, row);
spawnIndex++;
} else {
LK.clearInterval(customSpawnTimer);
}
}, 800); // Spawn every 800ms
}
return; // Exit early for custom waves
}
// Handle endless mode waves (but not personalize mode)
if (gameMode === 'endless' && currentWave > 10) {
// For endless mode waves 11+, spawn Zombiestein every 5 waves
if (currentWave % 5 === 0) {
// Calculate number of Zombiesteins to spawn based on wave
var zombiesteinCount = Math.floor((currentWave - 10) / 5) + 1;
for (var z = 0; z < zombiesteinCount; z++) {
var zombiesteinRow = Math.floor(Math.random() * gridRows);
spawnZombie('zombiestein', zombiesteinRow);
}
zombiesteinSpawned = true;
zombiesteinAlive = true;
enemiesInWave += zombiesteinCount - 1; // Adjust enemy count for additional Zombiesteins
}
// Zombie Sarcófago spawning logic for endless mode waves 15+
if (currentWave >= 15) {
// Count bucket zombies in this wave to determine if we can spawn sarcófago
var bucketZombieCount = Math.floor(scaledEnemyCount * 0.6);
// 50% chance to spawn if at least 2 bucket zombies
if (bucketZombieCount >= 2 && Math.random() < 0.5) {
// Calculate max sarcófagos: 1 + (waves past 15) / 5
var maxSarcofagos = 1 + Math.floor((currentWave - 15) / 5);
var sarcofagosToSpawn = Math.min(maxSarcofagos, 1); // Start with 1, increase every 5 waves
for (var s = 0; s < sarcofagosToSpawn; s++) {
var sarcofagoRow = Math.floor(Math.random() * gridRows);
spawnZombie('zombie_sarcofago', sarcofagoRow);
enemiesInWave++; // Add to enemy count
}
}
}
// Calculate enemy count with 5% increase per wave after wave 10
var waveMultiplier = 1 + (currentWave - 10) * 0.05;
var baseCount = 7; // Base count similar to wave 10
var scaledEnemyCount = Math.floor(baseCount * waveMultiplier);
bucketZombiesLeft = Math.floor(scaledEnemyCount * 0.6); // 60% bucket zombies
// Calculate rugby zombies for endless mode (every 5 waves adds 1 more)
var rugbyZombiesLeft = 0;
if (currentWave >= 11) {
var rugbyBaseCount = 3; // Base count for waves 11-15
var additionalRugby = Math.floor((currentWave - 11) / 5); // +1 every 5 waves
rugbyZombiesLeft = rugbyBaseCount + additionalRugby;
}
enemiesInWave = scaledEnemyCount + rugbyZombiesLeft + (currentWave % 5 === 0 ? 1 : 0); // Add rugby and Zombiestein if applicable
enemiesKilled = 0;
// Set up spawn timer for endless waves
var normalZombiesLeft = Math.floor(scaledEnemyCount * 0.15); // 15% normal
var coneZombiesLeft = Math.floor(scaledEnemyCount * 0.25); // 25% cone
var spawnTimer = LK.setInterval(function () {
if (enemiesInWave > 0) {
var row = Math.floor(Math.random() * gridRows);
var zombieType = null;
// Miner zombie spawning (every wave in endless mode)
var shouldSpawnMinerZombie = minerZombiesSpawnedThisWave < maxMinerZombiesPerWave && Math.random() < 0.3;
// Check if we should spawn a rugby zombie for endless mode
var shouldSpawnRugbyZombie = rugbyZombiesLeft > 0 && Math.random() < 0.2;
// Check if we should spawn a zombie ra (odd waves excluding 1 and 3) in endless mode
var shouldSpawnZombieRa = currentWave % 2 === 1 && currentWave > 3 && Math.random() < 0.25;
// Check if we should spawn a zombie canon (wave 9+) in endless mode
var shouldSpawnZombieCanon = currentWave >= 9 && Math.random() < 0.3;
// Check if we should spawn zombie cascanueces (waves 12+)
var shouldSpawnZombieCascanueces = currentWave >= 12 && Math.random() < 0.2;
// Check if we should spawn zombie mochila (waves 13+)
var shouldSpawnZombieMochila = currentWave >= 13 && Math.random() < 0.15;
// Check if we should spawn excabadora (waves 7+)
var shouldSpawnExcabadora = currentWave >= 7 && Math.random() < 0.2;
if (shouldSpawnMinerZombie) {
zombieType = 'miner_zombie';
minerZombiesSpawnedThisWave++;
} else if (shouldSpawnZombieCanon) {
zombieType = 'zombie_canon';
} else if (shouldSpawnRugbyZombie) {
zombieType = 'rugby_zombie';
rugbyZombiesLeft--;
} else if (shouldSpawnZombieRa) {
zombieType = 'zombie_ra';
} else if (shouldSpawnZombieCascanueces) {
zombieType = 'zombie_cascanueces';
} else if (shouldSpawnZombieMochila) {
zombieType = 'zombie_mochila';
} else if (shouldSpawnExcabadora) {
zombieType = 'excabadora';
} else {
// Pick random zombie type from all available types based on wave progression
var availableTypes = [];
// Always available zombie types
availableTypes.push('zombie');
availableTypes.push('cone_zombie');
availableTypes.push('bucket_zombie');
// Progressive unlock based on wave number
if (currentWave >= 3) {
availableTypes.push('sun_zombie');
}
if (currentWave >= 9) {
availableTypes.push('zombie_canon');
}
if (currentWave >= 12) {
availableTypes.push('zombie_cascanueces');
}
if (currentWave >= 13) {
availableTypes.push('zombie_mochila');
}
if (currentWave >= 15) {
availableTypes.push('zombie_sarcofago');
}
if (currentWave >= 7) {
availableTypes.push('excabadora');
}
if (availableTypes.length > 0) {
var randomIndex = Math.floor(Math.random() * availableTypes.length);
zombieType = availableTypes[randomIndex];
}
}
// Only spawn if we have a valid zombie type
if (zombieType) {
spawnZombie(zombieType, row);
}
enemiesInWave--;
} else {
LK.clearInterval(spawnTimer);
}
}, 800); // Faster spawn rate for endless mode
return; // Exit function early for endless mode
}
if (currentWave === 1) {
// Wave 1: 3-4 normal zombies with collision detection
var normalCount = Math.floor(Math.random() * 2) + 3;
var spawnQueue = [];
for (var i = 0; i < normalCount; i++) {
var row = Math.floor(Math.random() * gridRows);
// For personalize mode, check if zombie type is selected, otherwise use normal zombie
var zombieType = 'zombie';
if (gameMode === 'personalize' && selectedZombieTypes.indexOf('zombie') === -1) {
// If normal zombie not selected, pick a random selected type
if (selectedZombieTypes.length > 0) {
var availableEndlessTypes = selectedZombieTypes.filter(function (type) {
return type !== 'miner_zombie' && type !== 'rugby_zombie' && type !== 'zombiestein' && type !== 'zombie_sol' && type !== 'zombie_canon';
});
if (availableEndlessTypes.length > 0) {
var randomIndex = Math.floor(Math.random() * availableEndlessTypes.length);
zombieType = availableEndlessTypes[randomIndex];
} else {
zombieType = selectedZombieTypes[Math.floor(Math.random() * selectedZombieTypes.length)];
}
}
}
spawnQueue.push({
type: zombieType,
row: row,
delay: 0
});
}
// Process spawn queue with collision detection
var spawnIndex = 0;
var spawnProcessor = LK.setInterval(function () {
if (spawnIndex < spawnQueue.length) {
var spawn = spawnQueue[spawnIndex];
if (canSpawnWithoutCollision(spawn.row)) {
spawnZombie(spawn.type, spawn.row);
lastSpawnTime[spawn.row] = LK.ticks;
lastSpawnRow[spawn.row] = LK.ticks;
spawnIndex++;
} else {
// Add 1 second delay
spawn.delay += 60;
}
} else {
LK.clearInterval(spawnProcessor);
}
}, 60); // Check every second
enemiesInWave = normalCount;
} else if (currentWave === 2) {
// Wave 2: 3-4 normal zombies, 75% chance for 1 sun zombie, 1 cone zombie with collision detection
var normalCount = Math.floor(Math.random() * 2) + 3;
var sunZombieCount = gameMode === 'endless' && selectedZombieTypes.indexOf('sun_zombie') === -1 ? 0 : Math.random() < 0.75 ? 1 : 0;
var coneCount = gameMode === 'endless' && selectedZombieTypes.indexOf('cone_zombie') === -1 ? 0 : 1;
var spawnQueue = [];
// Add normal zombies to queue
for (var i = 0; i < normalCount; i++) {
var row = Math.floor(Math.random() * gridRows);
// For endless mode, check if zombie type is selected
var zombieType = 'zombie';
if (gameMode === 'endless' && selectedZombieTypes.indexOf('zombie') === -1) {
// If normal zombie not selected, pick a random selected type
if (selectedZombieTypes.length > 0) {
var randomIndex = Math.floor(Math.random() * selectedZombieTypes.length);
zombieType = selectedZombieTypes[randomIndex];
}
}
spawnQueue.push({
type: zombieType,
row: row,
delay: 0
});
}
// Add sun zombie if chance succeeds and selected in endless mode
if (sunZombieCount > 0) {
var row = Math.floor(Math.random() * gridRows);
spawnQueue.push({
type: 'sun_zombie',
row: row,
delay: 0
});
sunZombieSpawnedThisWave = true;
}
// Add cone zombie if selected in endless mode
if (coneCount > 0) {
var row = Math.floor(Math.random() * gridRows);
spawnQueue.push({
type: 'cone_zombie',
row: row,
delay: 0
});
}
// Process spawn queue with collision detection
var spawnIndex = 0;
var spawnProcessor = LK.setInterval(function () {
if (spawnIndex < spawnQueue.length) {
var spawn = spawnQueue[spawnIndex];
if (canSpawnWithoutCollision(spawn.row)) {
spawnZombie(spawn.type, spawn.row);
lastSpawnTime[spawn.row] = LK.ticks;
lastSpawnRow[spawn.row] = LK.ticks;
spawnIndex++;
} else {
// Add 1 second delay
spawn.delay += 60;
}
} else {
LK.clearInterval(spawnProcessor);
}
}, 60); // Check every second
enemiesInWave = normalCount + sunZombieCount + coneCount;
} else if (currentWave === 3) {
bucketZombiesLeft = gameMode === 'personalize' && selectedZombieTypes.indexOf('bucket_zombie') === -1 ? 0 : 1; // Only 1 bucket zombie in wave 3 if selected
enemiesInWave = Math.floor(Math.random() * 2) + 3 + Math.floor(Math.random() * 2) + 1 + bucketZombiesLeft; // normal + cone + bucket
} else if (currentWave === 4) {
bucketZombiesLeft = gameMode === 'personalize' && selectedZombieTypes.indexOf('bucket_zombie') === -1 ? 0 : Math.floor(Math.random() * 2) + 2; // 2-3 bucket zombies if selected
enemiesInWave = Math.floor(Math.random() * 2) + 3 + Math.floor(Math.random() * 2) + 1 + bucketZombiesLeft;
} else if (currentWave === 5) {
bucketZombiesLeft = gameMode === 'personalize' && selectedZombieTypes.indexOf('bucket_zombie') === -1 ? 0 : Math.floor(Math.random() * 2) + 3; // 3-4 bucket zombies if selected
enemiesInWave = Math.floor(Math.random() * 2) + 3 + Math.floor(Math.random() * 2) + 1 + bucketZombiesLeft;
} else {
// From wave 6+: spawn 50% fewer zombies but increase cone/bucket probability by 25%
bucketZombiesLeft = gameMode === 'personalize' && selectedZombieTypes.indexOf('bucket_zombie') === -1 ? 0 : Math.floor((Math.floor(Math.random() * 2) + 4 + (currentWave - 6)) * 0.5); // 50% fewer bucket zombies if selected
enemiesInWave = Math.floor((5 + currentWave * 2) * 0.5); // 50% fewer total enemies
// Add rugby zombies for waves 8-10
var rugbyZombiesLeft = 0;
if (currentWave >= 8 && currentWave <= 9 && (gameMode === 'normal' || selectedZombieTypes.indexOf('rugby_zombie') !== -1)) {
rugbyZombiesLeft = 1;
} else if (currentWave === 10 && (gameMode === 'normal' || selectedZombieTypes.indexOf('rugby_zombie') !== -1)) {
rugbyZombiesLeft = 2;
}
enemiesInWave += rugbyZombiesLeft;
// Spawn Zombiestein in wave 10 (only once per game)
if (currentWave === 10 && !zombiesteinSpawned && (gameMode === 'normal' || selectedZombieTypes.indexOf('zombiestein') !== -1)) {
var zombiesteinRow = Math.floor(Math.random() * gridRows);
spawnZombie('zombiestein', zombiesteinRow);
zombiesteinSpawned = true;
zombiesteinAlive = true;
enemiesInWave++; // Add Zombiestein to enemy count
}
}
enemiesKilled = 0;
// Only set up spawn timer for waves 3 and above (waves 1-2 spawn immediately)
if (currentWave >= 3) {
var normalZombiesLeft = Math.floor(Math.random() * 2) + 3;
var coneZombiesLeft = Math.floor(Math.random() * 2) + 1;
var spawnTimer = LK.setInterval(function () {
if (enemiesInWave > 0) {
var row = Math.floor(Math.random() * gridRows);
var zombieType;
// Determine sun zombie probability based on wave
var sunZombieProbability = 0;
if (currentWave === 3) {
sunZombieProbability = 0.50;
} else if (currentWave === 4) {
sunZombieProbability = 0.25;
}
// Check if we should spawn a sun zombie (only waves 3-4, max 1 per wave)
var shouldSpawnSunZombie = currentWave >= 3 && currentWave <= 4 && !sunZombieSpawnedThisWave && Math.random() < sunZombieProbability && (gameMode === 'normal' || selectedZombieTypes.indexOf('sun_zombie') !== -1);
// Check if we should spawn a miner zombie (waves 6+, 100% chance, max per wave limit)
var shouldSpawnMinerZombie = currentWave >= 6 && minerZombiesSpawnedThisWave < maxMinerZombiesPerWave && Math.random() < 1.0 && (gameMode === 'normal' || selectedZombieTypes.indexOf('miner_zombie') !== -1);
// Check if we should spawn a rugby zombie (waves 8-10)
var shouldSpawnRugbyZombie = currentWave >= 8 && currentWave <= 10 && rugbyZombiesLeft > 0 && Math.random() < 0.3 && (gameMode === 'normal' || selectedZombieTypes.indexOf('rugby_zombie') !== -1);
// Check if we should spawn a zombie ra (odd waves excluding 1 and 3)
var shouldSpawnZombieRa = currentWave % 2 === 1 && currentWave > 3 && Math.random() < 0.25 && (gameMode === 'normal' || selectedZombieTypes.indexOf('zombie_ra') !== -1);
// Check if we should spawn a zombie canon (wave 9+, before zombiestein wave)
var shouldSpawnZombieCanon = currentWave >= 9 && currentWave < (zombiesteinSpawned ? currentWave : 10) && Math.random() < 0.3 && (gameMode === 'normal' || selectedZombieTypes.indexOf('zombie_canon') !== -1);
// Check if we should spawn a zombie cascanueces (waves 12, 17, 22, 27, 32, etc.)
var shouldSpawnZombieCascanueces = (currentWave - 12) % 5 === 0 && currentWave >= 12 && Math.random() < 0.3 && (gameMode === 'normal' || selectedZombieTypes.indexOf('zombie_cascanueces') !== -1);
// Check if we should spawn excabadora (waves 7+)
var shouldSpawnExcabadora = currentWave >= 7 && Math.random() < 0.2 && (gameMode === 'normal' || selectedZombieTypes.indexOf('excabadora') !== -1);
if (shouldSpawnSunZombie) {
zombieType = 'sun_zombie';
sunZombieSpawnedThisWave = true; // Mark that sun zombie has been spawned this wave
} else if (shouldSpawnMinerZombie) {
zombieType = 'miner_zombie';
minerZombiesSpawnedThisWave++; // Increment miner count for this wave
} else if (shouldSpawnRugbyZombie) {
zombieType = 'rugby_zombie';
rugbyZombiesLeft--;
} else if (shouldSpawnZombieRa) {
zombieType = 'zombie_ra';
} else if (shouldSpawnZombieCanon) {
zombieType = 'zombie_canon';
} else if (shouldSpawnZombieCascanueces) {
zombieType = 'zombie_cascanueces';
} else if (shouldSpawnExcabadora) {
zombieType = 'excabadora';
} else if (currentWave >= 3) {
// From wave 3 onwards, include bucket zombies
// For personalize mode, only spawn selected zombie types
if (gameMode === 'personalize') {
// Pick random zombie type from selected types (excluding special zombies)
var availableTypes = [];
if (selectedZombieTypes.indexOf('zombie') !== -1 && normalZombiesLeft > 0) {
availableTypes.push('zombie');
}
if (selectedZombieTypes.indexOf('cone_zombie') !== -1 && coneZombiesLeft > 0) {
availableTypes.push('cone_zombie');
}
if (selectedZombieTypes.indexOf('bucket_zombie') !== -1 && bucketZombiesLeft > 0) {
availableTypes.push('bucket_zombie');
}
if (selectedZombieTypes.indexOf('sun_zombie') !== -1) {
availableTypes.push('sun_zombie');
}
if (selectedZombieTypes.indexOf('zombie_canon') !== -1) {
availableTypes.push('zombie_canon');
}
if (selectedZombieTypes.indexOf('zombie_cascanueces') !== -1) {
availableTypes.push('zombie_cascanueces');
}
if (selectedZombieTypes.indexOf('excabadora') !== -1) {
availableTypes.push('excabadora');
}
if (availableTypes.length > 0) {
var randomIndex = Math.floor(Math.random() * availableTypes.length);
zombieType = availableTypes[randomIndex];
// Decrement counters
if (zombieType === 'zombie') {
normalZombiesLeft--;
} else if (zombieType === 'cone_zombie') {
coneZombiesLeft--;
} else if (zombieType === 'bucket_zombie') {
bucketZombiesLeft--;
}
} else {
zombieType = 'zombie'; // fallback
}
} else {
// Original logic for normal mode
// For wave 6+, increase cone and bucket zombie probability by 25%
if (normalZombiesLeft > 0 && coneZombiesLeft > 0 && bucketZombiesLeft > 0) {
var rand = Math.random();
if (currentWave >= 6) {
// Wave 6+: 25% normal, 37.5% cone, 37.5% bucket (25% more cone/bucket)
if (rand < 0.25) {
zombieType = 'zombie';
normalZombiesLeft--;
} else if (rand < 0.625) {
zombieType = 'cone_zombie';
coneZombiesLeft--;
} else {
zombieType = 'bucket_zombie';
bucketZombiesLeft--;
}
} else {
// Wave 3-5: original probabilities
if (rand < 0.4) {
zombieType = 'zombie';
normalZombiesLeft--;
} else if (rand < 0.7) {
zombieType = 'cone_zombie';
coneZombiesLeft--;
} else {
zombieType = 'bucket_zombie';
bucketZombiesLeft--;
}
}
} else if (normalZombiesLeft > 0 && coneZombiesLeft > 0) {
if (currentWave >= 6) {
// Wave 6+: 35% normal, 65% cone (25% more cone)
zombieType = Math.random() < 0.35 ? 'zombie' : 'cone_zombie';
} else {
// Wave 3-5: original 60/40 split
zombieType = Math.random() < 0.6 ? 'zombie' : 'cone_zombie';
}
if (zombieType === 'zombie') {
normalZombiesLeft--;
} else {
coneZombiesLeft--;
}
} else if (normalZombiesLeft > 0 && bucketZombiesLeft > 0) {
if (currentWave >= 6) {
// Wave 6+: 35% normal, 65% bucket (25% more bucket)
zombieType = Math.random() < 0.35 ? 'zombie' : 'bucket_zombie';
} else {
// Wave 3-5: original 60/40 split
zombieType = Math.random() < 0.6 ? 'zombie' : 'bucket_zombie';
}
if (zombieType === 'zombie') {
normalZombiesLeft--;
} else {
bucketZombiesLeft--;
}
} else if (coneZombiesLeft > 0 && bucketZombiesLeft > 0) {
zombieType = Math.random() < 0.5 ? 'cone_zombie' : 'bucket_zombie';
if (zombieType === 'cone_zombie') {
coneZombiesLeft--;
} else {
bucketZombiesLeft--;
}
} else if (normalZombiesLeft > 0) {
zombieType = 'zombie';
normalZombiesLeft--;
} else if (coneZombiesLeft > 0) {
zombieType = 'cone_zombie';
coneZombiesLeft--;
} else if (bucketZombiesLeft > 0) {
zombieType = 'bucket_zombie';
bucketZombiesLeft--;
} else {
zombieType = 'zombie'; // fallback
}
}
} else {
// Fallback for other waves
if (gameMode === 'personalize') {
// Pick from selected types
var availableTypes = [];
if (selectedZombieTypes.indexOf('zombie') !== -1) {
availableTypes.push('zombie');
}
if (selectedZombieTypes.indexOf('cone_zombie') !== -1) {
availableTypes.push('cone_zombie');
}
if (availableTypes.length > 0) {
var randomIndex = Math.floor(Math.random() * availableTypes.length);
zombieType = availableTypes[randomIndex];
} else {
zombieType = 'zombie'; // fallback
}
} else {
zombieType = Math.random() < 0.5 ? 'zombie' : 'cone_zombie';
}
}
spawnZombie(zombieType, row);
enemiesInWave--;
} else {
LK.clearInterval(spawnTimer);
}
}, 1200);
} // Close the if (currentWave >= 3) block
}
// Mouse move handler to update ghost plant position
game.move = function (x, y, obj) {
if (ghostPlant && selectedPlantType) {
var gridPos = getGridPosition(x, y);
if (gridPos) {
// Snap to grid position
ghostPlant.x = gridStartX + gridPos.x * cellSize;
ghostPlant.y = gridStartY + gridPos.y * cellSize;
// Change color based on validity
if (canPlacePlant(gridPos.x, gridPos.y, selectedPlantType)) {
ghostPlant.tint = 0x88FF88; // Green tint for valid placement
} else {
ghostPlant.tint = 0xFF8888; // Red tint for invalid placement
}
} else {
// Follow cursor if outside grid
ghostPlant.x = x;
ghostPlant.y = y;
ghostPlant.tint = 0xFF8888; // Red tint when outside grid
}
}
};
// Game input handling
game.down = function (x, y, obj) {
// Check if clicking on a sun
for (var i = 0; i < suns.length; i++) {
if (suns[i].intersects({
x: x,
y: y,
width: 1,
height: 1
})) {
return; // Let sun handle its own click
}
}
// Check if using shovel to remove plant
if (selectedTool === 'shovel') {
var gridPos = getGridPosition(x, y);
if (gridPos && plants[gridPos.y][gridPos.x] !== null) {
// Remove the plant
var plant = plants[gridPos.y][gridPos.x];
plant.destroy();
plants[gridPos.y][gridPos.x] = null;
LK.effects.flashObject(gridCells[gridPos.y][gridPos.x], 0xFFFF00, 300);
updatePlantSelection();
return;
} else if (gridPos) {
// Flash red if trying to remove from empty square
LK.effects.flashObject(gridCells[gridPos.y][gridPos.x], 0xFF0000, 300);
} else {
// Clicking outside grid cancels shovel selection
selectedTool = null;
shovelButton.alpha = 0.8;
shovelButton.scaleX = shovelButton.scaleY = 1.5;
updatePlantSelection();
}
}
// Check if placing a plant
else if (selectedPlantType) {
var gridPos = getGridPosition(x, y);
if (gridPos) {
if (canPlacePlant(gridPos.x, gridPos.y, selectedPlantType)) {
// Successfully place the plant
if (placePlant(gridPos.x, gridPos.y, selectedPlantType)) {
selectedPlantType = null;
updatePlantSelection();
return;
}
} else if (plants[gridPos.y][gridPos.x] !== null) {
// Flash red if trying to place on occupied square
LK.effects.flashObject(gridCells[gridPos.y][gridPos.x], 0xFF0000, 300);
} else if (sunPoints < plantCosts[selectedPlantType]) {
// Flash yellow if not enough suns
LK.effects.flashObject(gridCells[gridPos.y][gridPos.x], 0xFFFF00, 300);
}
} else {
// Clicking outside grid cancels selection
selectedPlantType = null;
updatePlantSelection();
}
}
};
// Initialize UI
updatePlantSelection();
updateSunDisplay();
updateLivesDisplay();
updateWaveDisplay();
// Skip wave button for endless mode
var skipWaveButton = null;
function createSkipWaveButton() {
if (gameMode === 'endless' && !skipWaveButton) {
skipWaveButton = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
x: 1800,
y: 100,
scaleX: 2,
scaleY: 1,
color: 0x888888 // Start gray
});
skipWaveButton.interactive = true;
skipWaveButton.buttonMode = true;
skipWaveButton.alpha = 0.5; // Start disabled
skipWaveButton.down = function (x, y, obj) {
if (waveInProgress) {
// Force end current wave and start next one
waveInProgress = false;
wavesCompleted++;
currentWave++;
updateWaveDisplay();
updateSkipWaveButton();
// Start next wave immediately
LK.setTimeout(function () {
startWave();
}, 500);
}
};
game.addChild(skipWaveButton);
var skipWaveText = new Text2('Skip Wave', {
size: 40,
fill: 0xFFFFFF
});
skipWaveText.anchor.set(0.5, 0.5);
skipWaveText.x = 1800;
skipWaveText.y = 100;
game.addChild(skipWaveText);
skipWaveButton.textElement = skipWaveText;
}
}
function canSkipWave() {
// Check if all zombies for current wave have been spawned
return waveInProgress && enemiesInWave <= 0;
}
function updateSkipWaveButton() {
if (skipWaveButton && gameMode === 'endless') {
if (waveInProgress) {
// Enable button - wave is in progress
skipWaveButton.alpha = 1.0;
skipWaveButton.tint = 0x4CAF50; // Green
skipWaveButton.interactive = true;
} else {
// Disable button - no wave in progress
skipWaveButton.alpha = 0.5;
skipWaveButton.tint = 0x888888; // Gray
skipWaveButton.interactive = false;
}
}
}
// Game will start after mode selection
// Main game loop
game.update = function () {
// Enable lag optimization from wave 8 onwards
if (currentWave >= 8 && !lagOptimizationActive) {
lagOptimizationActive = true;
console.log("Lag optimization activated for wave", currentWave);
}
// Performance cleanup every 3 seconds when lag optimization is active
if (lagOptimizationActive) {
cleanupCounter++;
if (cleanupCounter >= 180) {
// Every 3 seconds at 60fps
performanceCleanup();
cleanupCounter = 0;
}
}
// Update wallnut cooldown
if (wallnutCooldown > 0) {
wallnutCooldown--;
if (wallnutCooldown === 0) {
updatePlantSelection(); // Refresh button appearance when cooldown ends
}
}
// Update nuez primitiva cooldown
if (nuezPrimitivaCooldown > 0) {
nuezPrimitivaCooldown--;
if (nuezPrimitivaCooldown === 0) {
updatePlantSelection(); // Refresh button appearance when cooldown ends
}
}
// Update petacereza cooldown
if (petacerezaCooldown > 0) {
petacerezaCooldown--;
if (petacerezaCooldown === 0) {
updatePlantSelection(); // Refresh button appearance when cooldown ends
}
}
// Falling sun system - every 10 seconds (optimized for high waves)
sunTimer++;
var sunInterval = lagOptimizationActive ? 1080 : 720; // 18 seconds when optimized, 12 seconds normally
if (sunTimer >= sunInterval) {
// Only spawn sun if we don't have too many already
if (suns.length < (lagOptimizationActive ? 8 : 15)) {
var fallingSun = new Sun(Math.random() * (gridStartX + gridCols * cellSize - gridStartX) + gridStartX, -50);
suns.push(fallingSun);
game.addChild(fallingSun);
// Animate falling sun
tween(fallingSun, {
y: Math.random() * 200 + 300
}, {
duration: 2400,
easing: tween.easeOut
});
}
sunTimer = 0;
}
// Check if current wave should end (22 seconds timer-based)
if (waveInProgress && LK.ticks - waveStartTick >= 1584) {
// 26.4 seconds at 60fps
waveInProgress = false;
wavesCompleted++;
if (gameMode === 'normal' && wavesCompleted >= totalWaves) {
// Check if Zombiestein was spawned and is still alive
if (zombiesteinSpawned && zombiesteinAlive) {
// Don't end game until Zombiestein is defeated
console.log("All waves completed but Zombiestein still alive!");
return;
}
LK.effects.flashScreen(0x4CAF50, 1000);
LK.showYouWin();
return;
} else if (gameMode === 'endless') {
// For endless mode, just continue to next wave
// Reset Zombiestein spawn flag for next potential spawn
if (currentWave % 5 === 0) {
zombiesteinSpawned = false;
zombiesteinAlive = false;
}
}
currentWave++;
updateWaveDisplay();
// Start next wave after delay
LK.setTimeout(function () {
startWave();
}, 3000);
}
// Update skip wave button state
if (gameMode === 'endless') {
updateSkipWaveButton();
}
// Auto-start next wave if no wave in progress and we haven't completed all waves (and mode is selected)
if (gameModeSelected && !waveInProgress && (gameMode === 'endless' || wavesCompleted < totalWaves) && !nextWaveScheduled) {
nextWaveScheduled = true;
LK.setTimeout(function () {
nextWaveScheduled = false;
startWave();
}, 1000);
}
}; ===================================================================
--- original.js
+++ change.js
@@ -356,8 +356,111 @@
return pea; // Return the pea for tracking
};
return self;
});
+var Recogesoles = PlantBase.expand(function () {
+ var self = PlantBase.call(this, 'recogesoles');
+ self.cost = 200;
+ self.health = 100; // Same as sunflower
+ self.maxHealth = 100;
+ self.isAttractingSun = false;
+ self.targetSun = null;
+ // Override update to handle sun attraction
+ self.update = function () {
+ // Look for uncollected suns to attract
+ if (!self.isAttractingSun && !self.targetSun) {
+ var closestSun = null;
+ var closestDistance = Infinity;
+ // Find the closest sun that isn't already being attracted
+ for (var i = 0; i < suns.length; i++) {
+ var sun = suns[i];
+ if (!sun.isBeingAttracted) {
+ // Check if there's a zombie ra that would conflict
+ var hasConflictingZombieRa = false;
+ for (var j = 0; j < enemies.length; j++) {
+ var enemy = enemies[j];
+ if (enemy.zombieType === 'zombieRa' && !enemy.isAttractingSun) {
+ // If zombie ra and recogesoles would target the same sun, cancel each other
+ var zombieDistance = Math.abs(sun.x - enemy.x) + Math.abs(sun.y - enemy.y);
+ var plantDistance = Math.abs(sun.x - self.x) + Math.abs(sun.y - self.y);
+ if (Math.abs(zombieDistance - plantDistance) < 100) {
+ hasConflictingZombieRa = true;
+ break;
+ }
+ }
+ }
+ if (!hasConflictingZombieRa) {
+ var distance = Math.abs(sun.x - self.x) + Math.abs(sun.y - self.y);
+ if (distance < closestDistance) {
+ closestDistance = distance;
+ closestSun = sun;
+ }
+ }
+ }
+ }
+ // If found a sun, start attracting it
+ if (closestSun) {
+ self.targetSun = closestSun;
+ self.isAttractingSun = true;
+ // Mark sun as being attracted so other recogesoles don't target it
+ closestSun.isBeingAttracted = true;
+ closestSun.attractedBy = self;
+ // Start tweening sun towards plant
+ tween(closestSun, {
+ x: self.x,
+ y: self.y
+ }, {
+ duration: closestDistance / 0.6,
+ // Same speed as zombie ra
+ easing: tween.linear,
+ onFinish: function onFinish() {
+ // Sun reached plant - consume it for player
+ if (self.targetSun && self.targetSun.parent) {
+ self.consumeSun();
+ }
+ }
+ });
+ }
+ }
+ // Check if sun reached plant (collision detection)
+ if (self.isAttractingSun && self.targetSun && self.targetSun.parent) {
+ var distance = Math.abs(self.targetSun.x - self.x) + Math.abs(self.targetSun.y - self.y);
+ if (distance < 50) {
+ // Close enough to consume
+ self.consumeSun();
+ }
+ }
+ };
+ self.consumeSun = function () {
+ if (self.targetSun && self.targetSun.parent) {
+ // Add sun points to player (unlike zombie ra)
+ sunPoints += self.targetSun.value;
+ updateSunDisplay();
+ // Remove sun from game
+ for (var i = 0; i < suns.length; i++) {
+ if (suns[i] === self.targetSun) {
+ suns.splice(i, 1);
+ break;
+ }
+ }
+ self.targetSun.destroy();
+ self.targetSun = null;
+ self.isAttractingSun = false;
+ }
+ };
+ // Override die to stop any sun attraction
+ var originalDie = self.die;
+ self.die = function () {
+ // If attracting a sun, stop the tween and release the sun
+ if (self.targetSun && self.targetSun.parent) {
+ tween.stop(self.targetSun);
+ self.targetSun.isBeingAttracted = false;
+ self.targetSun.attractedBy = null;
+ }
+ originalDie();
+ };
+ return self;
+});
var Rapaz = PlantBase.expand(function () {
var self = PlantBase.call(this, 'rapaz');
self.cost = 325;
self.health = 445;
@@ -1287,13 +1390,32 @@
// Find the closest sun that isn't already being attracted
for (var i = 0; i < suns.length; i++) {
var sun = suns[i];
if (!sun.isBeingAttracted) {
- var distance = Math.abs(sun.x - self.x) + Math.abs(sun.y - self.y);
- if (distance < closestDistance) {
- closestDistance = distance;
- closestSun = sun;
+ // Check if there's a recogesoles that would conflict
+ var hasConflictingRecogesoles = false;
+ for (var row = 0; row < gridRows; row++) {
+ for (var col = 0; col < gridCols; col++) {
+ var plant = plants[row][col];
+ if (plant && plant.plantType === 'recogesoles' && !plant.isAttractingSun) {
+ // If zombie ra and recogesoles would target the same sun, zombie ra ignores it
+ var plantDistance = Math.abs(sun.x - plant.x) + Math.abs(sun.y - plant.y);
+ var zombieDistance = Math.abs(sun.x - self.x) + Math.abs(sun.y - self.y);
+ if (Math.abs(plantDistance - zombieDistance) < 100) {
+ hasConflictingRecogesoles = true;
+ break;
+ }
+ }
+ }
+ if (hasConflictingRecogesoles) break;
}
+ if (!hasConflictingRecogesoles) {
+ var distance = Math.abs(sun.x - self.x) + Math.abs(sun.y - self.y);
+ if (distance < closestDistance) {
+ closestDistance = distance;
+ closestSun = sun;
+ }
+ }
}
}
// If found a sun, start attracting it
if (closestSun) {
@@ -2027,9 +2149,9 @@
// Track active projectiles per plant
var plantProjectileCount = {};
// Selected plant type
var selectedPlantType = null;
-var plantTypes = ['sunflower', 'birasol', 'peashooter', 'captus', 'boxeador', 'snowpea', 'fireshot', 'repetidora', 'wallnut', 'nuezPrimitiva', 'petacereza', 'carnivora', 'rapaz'];
+var plantTypes = ['sunflower', 'birasol', 'peashooter', 'captus', 'boxeador', 'snowpea', 'fireshot', 'repetidora', 'wallnut', 'nuezPrimitiva', 'petacereza', 'carnivora', 'rapaz', 'recogesoles'];
var plantCosts = {
'peashooter': 100,
'captus': 175,
'sunflower': 50,
@@ -2042,9 +2164,10 @@
'nuezPrimitiva': 125,
'petacereza': 150,
'carnivora': 200,
'rapaz': 300,
- 'boxeador': 150
+ 'boxeador': 150,
+ 'recogesoles': 200
};
// Wallnut cooldown system
var wallnutCooldown = 0;
var wallnutCooldownTime = 720; // 12 seconds at 60fps
@@ -3329,8 +3452,11 @@
break;
case 'boxeador':
plant = new Boxeador();
break;
+ case 'recogesoles':
+ plant = new Recogesoles();
+ break;
default:
return false;
}
// Set plant position and grid reference