User prompt
el sistema de eleccion de zombies funciona correctamente, sin embargo, los zombies que has seleccionado para que no aparezcan siguen apareciendo, debe ser un bug o que que se te ha olvidado configurar que no deben aparecer los que has puesto que no quieres que aparezcan, arréglalo.
User prompt
implementa un sistema en el modo endless para que el jugador elija que zombies quiere que le aparezcan durante la partida, al hacer click en endless le apareceran los zombies que hay y él debe elegir cuales quiere que aparezcan en su partida
User prompt
bajale la velocidad a la que se le acerca el sol en un 300% al zombiesol ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
añadamos un nuevo tipo de zombie, el zombie sol, tendrá el asset llamado "zombieSol", será un zombie totalmente normal, pero con la modalidad de que si hay algún sol que el jugador no haya hecho click para recoger lo atraerá hacia él a la velocidad de 3 veces un zombie, una vez el sol toque al zombie sol, el sol desaparecerá y el jugador ya no podrá conseguir dicho sol. durante el trayecto del sol hacia el zombie sol el jugador podrá hacer click en el sol para poder reclamarlo antes de que el zombie se lo lleve. el zombie sol aparece en las oleadas de numero impar, excepto en las oleadas 1 y 3, en esas no aparece. cuando el zombie sol está atrayendo el sol hacia él se quedará quieto hasta que toque el sol o el jugador reclame el sol. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
la petacereza hace daño en área, es decir, no tiene un numero limitado de zombies a los que daña, daña a todos los que haya en su rango, que era en las 9 casillas de alrededor
User prompt
Añadamos una nueva planta, se llamará "petacereza" y tendrá el asset llamado "petacereza", esta costará 150 soles, y si colocas una tendrás que esperar 15 segundos para volverla a usar con la mecanica del icono en gris incluida, al colocarla se quedará quieta sin hacer nada durante 1,5 segundos y tras ello explotará y desaparecerá, la explosión tendrá el asset de la explosión del minero, y afectará a la casilla donde ha explotado, a la de arriba, a la de abajo, a la de la derecha, izquierda, arriba-derecha, arriba-izquierda, abajo-izquierda y abajo-derecha, causará 325 de daño. durante sus 1,5 segundos de no hacer nada los zombies la ignorarán es como si no existiera la atravesarán y no la atacarán ni las mecanicas especiales de los zombies le harán efecto a ella ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
so do it
User prompt
haz que el zombie rugby en vez de matar de forma instantanea haga 500 puntos de daño. tambien crea una nueva planta que se llame "nuez primitiva" tendrá el asset llamado "nuezPrimitiva" y su funcionamiento será similar al de la nuez normal, costará 125 soles, tendrás que esperar 3 segundos para volverla a colocar una vez coloques una, y al igual que la nuez normal se pondrá en gris cuando no puedas colocarla. Tiene 625 hp y cuando tenga menos de 300 cambiará su asset al de la nuez normal y si tiene menos de 150 al de la wallnut damaged. quiero que el icono de la repetidora se intercambie por el de la nuez, para que el orden de los iconos sea: girasol, lanzaguisantes, repetidora, nuez, nuez primitiva.
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 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.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 snowball = createProjectile('snowball', self.x, self.y, 6.4, 20 * self.level, plantId); if (snowball) { 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 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 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; } // 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.takeDamage(self.damage); 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.originalSpeed = self.speed; 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.update = function () { if (self.slowTimer > 0) { self.slowTimer--; if (self.slowTimer === 0) { self.speed = self.originalSpeed; graphics.tint = 0xFFFFFF; } } 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 ZombieSol = ZombieBase.expand(function () { var self = ZombieBase.call(this, 'zombieSol'); 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 for (var i = 0; i < suns.length; i++) { var sun = suns[i]; 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 / 2.4, // Speed of 3x zombie speed (0.8 * 3 = 2.4) 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 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 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 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 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 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', 'peashooter', 'repetidora', 'wallnut', 'nuezPrimitiva', 'petacereza']; var plantCosts = { 'peashooter': 100, 'sunflower': 50, 'wallnut': 50, 'repetidora': 200, 'nuezPrimitiva': 125, 'petacereza': 150 }; // 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: -200, 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 = -200; 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 = -200; normalModeSubtext.y = 10; modeSelectionContainer.addChild(normalModeSubtext); // Endless mode button var endlessModeButton = LK.getAsset('gridcell', { anchorX: 0.5, anchorY: 0.5, x: 200, y: -50, scaleX: 2, scaleY: 1.5, color: 0xFF9800 }); 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 = 200; 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 = 200; endlessModeSubtext.y = 10; modeSelectionContainer.addChild(endlessModeSubtext); game.addChild(modeSelectionContainer); function selectGameMode() { gameModeSelected = true; modeSelectionContainer.destroy(); updateWaveDisplay(); // Start first wave after mode selection LK.setTimeout(function () { startWave(); }, 3000); } // 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 button = LK.getAsset(plantTypes[i], { anchorX: 0.5, anchorY: 0.5, x: 200 + i * 220, y: uiY, scaleX: 2.25, scaleY: 2.25 }); button.plantType = plantTypes[i]; 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[plantTypes[i]], { size: 40, fill: 0xFFFFFF }); costText.anchor.set(0.5, 0); costText.x = button.x; costText.y = button.y + 80; game.addChild(costText); } 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 { 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 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); } } // 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 'sunflower': plant = new Sunflower(); 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; 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_sol': zombie = new ZombieSol(); 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; 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); } 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 endless mode waves 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 } // 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; // 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 sol (odd waves excluding 1 and 3) in endless mode var shouldSpawnZombieSol = currentWave % 2 === 1 && currentWave > 3 && Math.random() < 0.25; if (shouldSpawnMinerZombie) { zombieType = 'miner_zombie'; minerZombiesSpawnedThisWave++; } else if (shouldSpawnRugbyZombie) { zombieType = 'rugby_zombie'; rugbyZombiesLeft--; } else if (shouldSpawnZombieSol) { zombieType = 'zombie_sol'; } else if (normalZombiesLeft > 0 && coneZombiesLeft > 0 && bucketZombiesLeft > 0) { var rand = Math.random(); if (rand < 0.15) { zombieType = 'zombie'; normalZombiesLeft--; } else if (rand < 0.40) { zombieType = 'cone_zombie'; coneZombiesLeft--; } else { zombieType = 'bucket_zombie'; bucketZombiesLeft--; } } else if (normalZombiesLeft > 0 && coneZombiesLeft > 0) { zombieType = Math.random() < 0.35 ? 'zombie' : 'cone_zombie'; if (zombieType === 'zombie') { normalZombiesLeft--; } else { coneZombiesLeft--; } } else if (normalZombiesLeft > 0 && bucketZombiesLeft > 0) { zombieType = Math.random() < 0.2 ? 'zombie' : 'bucket_zombie'; if (zombieType === 'zombie') { normalZombiesLeft--; } else { bucketZombiesLeft--; } } else if (coneZombiesLeft > 0 && bucketZombiesLeft > 0) { zombieType = Math.random() < 0.3 ? 'cone_zombie' : 'bucket_zombie'; if (zombieType === 'cone_zombie') { coneZombiesLeft--; } else { bucketZombiesLeft--; } } else if (rugbyZombiesLeft > 0) { zombieType = 'rugby_zombie'; rugbyZombiesLeft--; } 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 = 'bucket_zombie'; // fallback to strongest zombie } 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); spawnQueue.push({ type: '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; } 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 = Math.random() < 0.75 ? 1 : 0; var coneCount = 1; var spawnQueue = []; // Add normal zombies to queue for (var i = 0; i < normalCount; i++) { var row = Math.floor(Math.random() * gridRows); spawnQueue.push({ type: 'zombie', row: row, delay: 0 }); } // Add sun zombie if chance succeeds if (sunZombieCount > 0) { var row = Math.floor(Math.random() * gridRows); spawnQueue.push({ type: 'sun_zombie', row: row, delay: 0 }); sunZombieSpawnedThisWave = true; } // Add cone zombie 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 = 1; // Only 1 bucket zombie in wave 3 enemiesInWave = Math.floor(Math.random() * 2) + 3 + Math.floor(Math.random() * 2) + 1 + bucketZombiesLeft; // normal + cone + bucket } else if (currentWave === 4) { bucketZombiesLeft = Math.floor(Math.random() * 2) + 2; // 2-3 bucket zombies enemiesInWave = Math.floor(Math.random() * 2) + 3 + Math.floor(Math.random() * 2) + 1 + bucketZombiesLeft; } else if (currentWave === 5) { bucketZombiesLeft = Math.floor(Math.random() * 2) + 3; // 3-4 bucket zombies 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 = Math.floor((Math.floor(Math.random() * 2) + 4 + (currentWave - 6)) * 0.5); // 50% fewer bucket zombies 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) { rugbyZombiesLeft = 1; } else if (currentWave === 10) { rugbyZombiesLeft = 2; } enemiesInWave += rugbyZombiesLeft; // Spawn Zombiestein in wave 10 (only once per game) if (currentWave === 10 && !zombiesteinSpawned) { 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; // 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; // Check if we should spawn a rugby zombie (waves 8-10) var shouldSpawnRugbyZombie = currentWave >= 8 && currentWave <= 10 && rugbyZombiesLeft > 0 && Math.random() < 0.3; // Check if we should spawn a zombie sol (odd waves excluding 1 and 3) var shouldSpawnZombieSol = currentWave % 2 === 1 && currentWave > 3 && Math.random() < 0.25; 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 (shouldSpawnZombieSol) { zombieType = 'zombie_sol'; } else if (currentWave >= 3) { // From wave 3 onwards, include bucket zombies // 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 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(); // 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); } // 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
@@ -425,18 +425,36 @@
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;
@@ -601,8 +619,98 @@
}
};
return self;
});
+var ZombieSol = ZombieBase.expand(function () {
+ var self = ZombieBase.call(this, 'zombieSol');
+ 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
+ for (var i = 0; i < suns.length; i++) {
+ var sun = suns[i];
+ 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 / 2.4,
+ // Speed of 3x zombie speed (0.8 * 3 = 2.4)
+ 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 Zombie = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'zombie');
self.health = 190;
self.maxHealth = 190;
@@ -1357,8 +1465,11 @@
break;
case 'sun_zombie':
zombie = new SunZombie();
break;
+ case 'zombie_sol':
+ zombie = new ZombieSol();
+ break;
case 'miner_zombie':
zombie = new MinerZombie();
// Special positioning for miner
zombie.initializeMiner(row);
@@ -1444,16 +1555,20 @@
var row = Math.floor(Math.random() * gridRows);
var zombieType;
// Miner zombie spawning (every wave in endless mode)
var shouldSpawnMinerZombie = minerZombiesSpawnedThisWave < maxMinerZombiesPerWave && Math.random() < 0.3;
- // Rugby zombie spawning for endless mode
+ // 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 sol (odd waves excluding 1 and 3) in endless mode
+ var shouldSpawnZombieSol = currentWave % 2 === 1 && currentWave > 3 && Math.random() < 0.25;
if (shouldSpawnMinerZombie) {
zombieType = 'miner_zombie';
minerZombiesSpawnedThisWave++;
} else if (shouldSpawnRugbyZombie) {
zombieType = 'rugby_zombie';
rugbyZombiesLeft--;
+ } else if (shouldSpawnZombieSol) {
+ zombieType = 'zombie_sol';
} else if (normalZombiesLeft > 0 && coneZombiesLeft > 0 && bucketZombiesLeft > 0) {
var rand = Math.random();
if (rand < 0.15) {
zombieType = 'zombie';
@@ -1638,8 +1753,10 @@
// 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;
// Check if we should spawn a rugby zombie (waves 8-10)
var shouldSpawnRugbyZombie = currentWave >= 8 && currentWave <= 10 && rugbyZombiesLeft > 0 && Math.random() < 0.3;
+ // Check if we should spawn a zombie sol (odd waves excluding 1 and 3)
+ var shouldSpawnZombieSol = currentWave % 2 === 1 && currentWave > 3 && Math.random() < 0.25;
if (shouldSpawnSunZombie) {
zombieType = 'sun_zombie';
sunZombieSpawnedThisWave = true; // Mark that sun zombie has been spawned this wave
} else if (shouldSpawnMinerZombie) {
@@ -1647,8 +1764,10 @@
minerZombiesSpawnedThisWave++; // Increment miner count for this wave
} else if (shouldSpawnRugbyZombie) {
zombieType = 'rugby_zombie';
rugbyZombiesLeft--;
+ } else if (shouldSpawnZombieSol) {
+ zombieType = 'zombie_sol';
} else if (currentWave >= 3) {
// From wave 3 onwards, include bucket zombies
// For wave 6+, increase cone and bucket zombie probability by 25%
if (normalZombiesLeft > 0 && coneZombiesLeft > 0 && bucketZombiesLeft > 0) {