User prompt
añadamos un nuevo tipo de zombie, el zombie "rugby", este zombie tiene el asset de nombre "rugby" y aparece a partir de la oleada 7, en la oleada 7 y 8 aparece 1, y en la 9 y 10 aparecen 2. tiene 300 hp, tiene una velocidad 3 veces mas rapida que los zombies normales y va a esa velocidad hasta que se muere o entra en contacto con una planta, al entrar en contacto con una planta la mata de forma instanea pero su velocidad pasa a ser la de un zombie normal. la aparicion en el modo endless es similar al del normal, pero cada 5 oleadas se añade uno extra a cada oleada, de forma que, aparecen en la 11-15: 3 zombies rugby, en la 16-20: aparecen 4, en la 21-25: aparecen 5 y asi de forma sucesiva
User prompt
haz que en el modo endless por cada oleada que pasa todo los zombie van obteniendo un 5% de vida extra, es decir en la 11 todos los zombies tienen un 5% de vida mas de lo normal, en la 12 un 10% en la 13 un 15% y asi sucesivamente. tambien la cantidad de zombistein va sumandose de forma progresiva, en la 10 aparece 1 zombistein, en la oleada 15 2 zombiestein, en la 20 3 zombistein y asi de forma sucesiva
User prompt
bajale la velocidad al zombistein un 10% y bajale la vida a 2250
User prompt
vamos a crear el modo de juego "endless". al empezar una partida al jugador se le preguntará si quiere jugar al modo normal (abajo de normal pondrá entre parentesis "10 waves") o si quiere jugar al endless (abajo de endless pondrá "infinite waves"). el normal seguirá como siempre no modificaremos nada de él aún, el endless será como el modo normal pero no tiene fin, es decir, es infinito, cuando pasan 10 oleadas la partida no termina, sino que empieza la oleada 11. el funcionamiento de las oleadas 1-10 será como el del modo normal (exceptuando que al matar al zombistein esta vez no ganas) y a partir de la 11 continuara similar a la oleada 10 pero con en cada oleada apareceran un 5% mas de zombies de forma progresiva, ademas de que los zombiestein aparecen cada 5 oleadas ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
haz al zombistein un 50% mas lento, y haz que si muere debido a la cortadora de cesped cuente como que el jugador lo ha matado
User prompt
haz que los guisantes de la repetidora sean un 35% mas rapido que los guisantes normales
User prompt
remodifica la repetidora, en vez de lanzar el segundo guisante cuando pasen 0.3 segundos, haz que lo lanze cuando el primero haya avanzado media casilla. tambien haz que una vez se lanze el 2 guisante deje de rastrear al primer guisante para no provocar lag. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que el zombidito tenga el asset de nombre "zombidito"
User prompt
haz que la repetidora tenga el asset de nombre "repetidora"
Remix started
Copy pvz a new awaken II
/**** * Plugins ****/ var tween = LK.import("@upit/tween.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(); } } // 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, 6.4, 20 * self.level, plantId); if (pea) { LK.getSound('shoot').play(); } return pea; // Return the pea for tracking }; 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 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; var graphics = self.attachAsset('sun', { anchorX: 0.5, anchorY: 0.5 }); self.down = function (x, y, obj) { sunPoints += self.value; updateSunDisplay(); self.collect(); }; self.collect = function () { 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]) { return plants[self.gridY][cellX]; } return null; }; return self; }); var Zombiestein = ZombieBase.expand(function () { var self = ZombieBase.call(this, 'zombiestein'); self.health = 3000; self.maxHealth = 3000; self.speed = 0.92; // 15% faster than original 0.8 speed self.originalSpeed = 0.92; 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 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 MinerZombie = ZombieBase.expand(function () { var self = ZombieBase.call(this, 'miner_zombie'); self.health = 190; self.maxHealth = 190; self.speed = 0.8; self.originalSpeed = 0.8; // 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 waveInProgress = false; var enemiesInWave = 0; var enemiesKilled = 0; var sunTimer = 0; var waveStartTick = 0; var nextWaveScheduled = false; var sunZombieSpawnedThisWave = false; // 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', 'wallnut', 'repetidora']; var plantCosts = { 'peashooter': 100, 'sunflower': 50, 'wallnut': 50, 'repetidora': 200 }; // Wallnut cooldown system var wallnutCooldown = 0; var wallnutCooldownTime = 720; // 12 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); } // 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 } 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 { 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() { 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 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; 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(); } // Flash the grid cell to show successful placement LK.effects.flashObject(gridCells[gridY][gridX], 0x00FF00, 300); 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 'miner_zombie': zombie = new MinerZombie(); // Special positioning for miner zombie.initializeMiner(row); enemies.push(zombie); game.addChild(zombie); return; // Return early since positioning is handled case 'zombiestein': zombie = new Zombiestein(); break; default: return; } zombie.gridY = row; zombie.x = 2100; zombie.y = gridStartY + row * cellSize; 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; if (currentWave === 1) { // Wave 1: 3-4 normal zombies var normalCount = Math.floor(Math.random() * 2) + 3; for (var i = 0; i < normalCount; i++) { var row = Math.floor(Math.random() * gridRows); spawnZombie('zombie', row); } enemiesInWave = normalCount; } else if (currentWave === 2) { // Wave 2: 3-4 normal zombies, 75% chance for 1 sun zombie, 1 cone zombie var normalCount = Math.floor(Math.random() * 2) + 3; var sunZombieCount = Math.random() < 0.75 ? 1 : 0; var coneCount = 1; // Spawn normal zombies for (var i = 0; i < normalCount; i++) { var row = Math.floor(Math.random() * gridRows); spawnZombie('zombie', row); } // Spawn sun zombie if chance succeeds if (sunZombieCount > 0) { var row = Math.floor(Math.random() * gridRows); spawnZombie('sun_zombie', row); sunZombieSpawnedThisWave = true; } // Spawn cone zombie var row = Math.floor(Math.random() * gridRows); spawnZombie('cone_zombie', row); 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 // 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; 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 (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(); // Start first wave after a delay LK.setTimeout(function () { startWave(); }, 10000); // 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 } } // 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 (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; } 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 if (!waveInProgress && wavesCompleted < totalWaves && !nextWaveScheduled) { nextWaveScheduled = true; LK.setTimeout(function () { nextWaveScheduled = false; startWave(); }, 1000); } };
===================================================================
--- original.js
+++ change.js
@@ -155,31 +155,42 @@
});
var Repetidora = PlantBase.expand(function () {
var self = PlantBase.call(this, 'repetidora');
self.cost = 200;
- self.isShootingSecondPea = false;
- self.secondPeaTimer = 0;
+ 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.isShootingSecondPea) {
- self.shoot(target);
+ if (target && !self.isTrackingFirstPea) {
+ var firstPea = self.shoot(target);
+ if (firstPea) {
+ self.firstPea = firstPea;
+ self.firstPeaStartX = firstPea.x;
+ self.isTrackingFirstPea = true;
+ }
self.shootTimer = 0;
- self.isShootingSecondPea = true;
- self.secondPeaTimer = 18; // 0.3 seconds at 60fps
}
}
- // Handle second pea timing
- if (self.isShootingSecondPea) {
- self.secondPeaTimer--;
- if (self.secondPeaTimer <= 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);
}
- self.isShootingSecondPea = false;
+ // 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++) {
@@ -195,8 +206,9 @@
var pea = createProjectile('pea', self.x, self.y, 6.4, 20 * self.level, plantId);
if (pea) {
LK.getSound('shoot').play();
}
+ return pea; // Return the pea for tracking
};
return self;
});
var Peashooter = PlantBase.expand(function () {