User prompt
increase drag and drop area, build turret
User prompt
increase time between Wave
User prompt
Adjust the zombie spawn mechanism, only spawn a certain number, usually 3-4 depending on the difficulty of the game, then wait a random amount of time to spawn the next wave
User prompt
makes SSC spawn sun slower, usually randomly from 10-15s,the turrets cannot be targeted and attacked by zombies
User prompt
add range to turret, when zombie is in range turret will aim at target and shoot otherwise will stop, duo barrel and triple barrel will shoot two, three bullets instead of shooting two, three targets ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Change Laser, freeze and explosive tower to classic turret, double barrel turret, triple barrel turret with price of 50, 75, 100 sun respectively, Zombie only appears after 35s, SSC will spawn 20 sun after a period of time not too long
User prompt
change the price of SSC, only 25 sun instead of 50
User prompt
Progress bar run slower (about 300 seconds to run out of bar and increase 25s each time reset) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
progress bar runs slower ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add progress bar, when the progress bar ends, the game will spawn a boss, when the boss is defeated, the progress bar will reset, the game will be harder, the game will change to another stage (light -> dark; dark -> light),When entering the game for the first time, the game will be easy for players to prepare (zombies will appear less and less frequently), the later, the more difficult the game will be ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Place the text that displays the turret's solar energy needs above the icon that displays that turret
User prompt
Add text to show how much solar energy that turret needs to build
User prompt
When you click on the icon of that turret, you will need to drag that turret to where you want to build, release it to build.
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'toGlobal')' in or related to this line: 'var globalPos = game.toLocal(obj.parent.toGlobal(obj.position));' Line Number: 700
User prompt
create drag and drop system for turrets, when built it will cost some sun
User prompt
Increase the hitbox of the house to avoid zombies from gathering in one place when approaching
User prompt
There is only one house, the house will be as long as the width of the bottom screen
User prompt
S.S.C not attacked and targeted by zombies
User prompt
Zombies will only go from top to bottom aiming at houses, houses will lose health based on zombie damage
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'dayText.style.fill = isDaytime ? "#FFD700" : "#4169E1";' Line Number: 691
Code edit (1 edits merged)
Please save this source code
User prompt
The Zombies Are Coming
Initial prompt
Create a tower defense game name "The zombie are coming",You need to use your knowledge, build Super Solar Collector (SSC) to collect energy for building resources,confront the massive zombie waves
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var AcidTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('acidTurret', { anchorX: 0.5, anchorY: 0.5 }); self.range = 140; self.fireRate = 45; self.fireTimer = 0; self.health = 100; self.maxHealth = 100; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.acidAttack(); } }; self.acidAttack = function () { LK.getSound('laser').play(); for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.range) { // Apply acid effect (doesn't remove freezing) zombies[i].acidDuration = 240; // 4 seconds of acid zombies[i].acidDamage = 2; // Smaller damage per tick } } var acidEffect = self.attachAsset('acidEffect', { anchorX: 0.5, anchorY: 0.5 }); acidEffect.width = self.range * 2; acidEffect.height = self.range * 2; acidEffect.alpha = 0.6; tween(acidEffect, { alpha: 0, scaleX: 1.4, scaleY: 1.4 }, { duration: 600, onFinish: function onFinish() { acidEffect.destroy(); } }); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var Attractor = Container.expand(function () { var self = Container.call(this); var attractorGraphics = self.attachAsset('attractor', { anchorX: 0.5, anchorY: 0.5 }); self.attractionRange = 300; self.health = 80; self.maxHealth = 80; self.attractionTimer = 0; // Pulse animation for attractor self.update = function () { self.attractionTimer++; // Create pulsing effect var pulseScale = 1 + Math.sin(self.attractionTimer * 0.15) * 0.3; attractorGraphics.scaleX = pulseScale; attractorGraphics.scaleY = pulseScale; // Attract zombies in range if (self.attractionTimer % 30 === 0) { self.attractZombies(); } }; self.attractZombies = function () { for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = self.x - zombie.x; var dy = self.y - zombie.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.attractionRange && dist > 30) { // Override zombie's current target to move toward attractor var attractForce = 0.3; zombie.x += dx / dist * attractForce; zombie.y += dy / dist * attractForce; // Visual effect for attraction if (Math.random() < 0.1) { var sparkle = game.addChild(LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5 })); sparkle.x = zombie.x + (Math.random() - 0.5) * 20; sparkle.y = zombie.y + (Math.random() - 0.5) * 20; sparkle.tint = 0xff69b4; sparkle.width = 4; sparkle.height = 4; tween(sparkle, { x: self.x, y: self.y, alpha: 0 }, { duration: 500, onFinish: function onFinish() { sparkle.destroy(); } }); } } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { // Find and mark grid cell as unoccupied when attractor is destroyed var gridX = Math.floor((self.x - GRID_SIZE / 2) / GRID_SIZE); var gridY = Math.floor((self.y - GRID_SIZE / 2 - 200) / GRID_SIZE); if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS) { var cellIndex = gridY * GRID_COLS + gridX; if (cellIndex < gridCells.length) { gridCells[cellIndex].occupied = false; } } self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var AutoTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('autoTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 8; self.range = 400; self.fireRate = 40; self.fireTimer = 0; self.health = 140; self.maxHealth = 140; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { if (dist < minDist) { minDist = dist; target = zombies[i]; } } } if (target) { // Aim turret at target var dx = target.x - self.x; var dy = target.y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; self.children[0].rotation = angle; self.shootHomingBullet(); } else { // No target in range, return to default direction (facing up) self.children[0].rotation = 0; } }; self.shootHomingBullet = function () { LK.getSound('laser').play(); var bullet = new HomingBullet(); bullet.x = self.x; bullet.y = self.y; bullet.damage = self.damage; game.addChild(bullet); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var BallTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('ballTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 40; self.range = 250; self.fireRate = 75; self.fireTimer = 0; self.health = 130; self.maxHealth = 130; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { if (dist < minDist) { minDist = dist; target = zombies[i]; } } } if (target) { self.shootEnergyBall(target); } }; self.shootEnergyBall = function (target) { LK.getSound('laser').play(); var ball = new EnergyBall(); ball.x = self.x; ball.y = self.y; ball.targetX = target.x; ball.targetY = target.y; ball.damage = self.damage; ball.bounces = 3; game.addChild(ball); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var BoxZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('boxZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.3; self.health = 80; self.damage = 15; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { // Handle burning effect if (self.burnDuration > 0) { self.burnDuration--; if (self.burnDuration % 10 === 0) { self.takeDamage(self.burnDamage || 3); } } // Handle acid effect if (self.acidDuration > 0) { self.acidDuration--; if (self.acidDuration % 15 === 0) { self.takeDamage(self.acidDamage || 2); } } if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.15; } else if (self.burnDuration > 0) { zombieGraphics.tint = 0xFF4500; self.speed = 0.3; } else if (self.acidDuration > 0) { zombieGraphics.tint = 0x32CD32; self.speed = 0.3; } else { zombieGraphics.tint = 0x8b4513; self.speed = 0.3; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { // Check for wall collisions before moving var nextX = self.x + dx / dist * self.speed; var nextY = self.y + dy / dist * self.speed; var blocked = false; // Check collision with walls for (var w = 0; w < walls.length; w++) { var wallDx = nextX - walls[w].x; var wallDy = nextY - walls[w].y; var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy); if (wallDist < 80) { self.target = walls[w]; blocked = true; break; } } if (!blocked) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } } else { self.attackTimer++; if (self.attackTimer >= 60) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 20); LK.getSound('zombieDeath').play(); // Spawn three random zombies (excluding CrowZombie) for (var i = 0; i < 3; i++) { self.spawnRandomZombie(); } self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.spawnRandomZombie = function () { var zombieTypes = [function () { return new Zombie(); }, function () { return new ToughZombie(); }, function () { return new RunnerZombie(); }, function () { return new UmbrellaZombie(); }, function () { return new CrowsZombie(); }, function () { return new TankZombie(); }]; var randomIndex = Math.floor(Math.random() * zombieTypes.length); var newZombie = zombieTypes[randomIndex](); newZombie.x = self.x + (Math.random() - 0.5) * 80; newZombie.y = self.y + (Math.random() - 0.5) * 80; zombies.push(newZombie); game.addChild(newZombie); }; self.freeze = function () { self.frozen = 180; }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.damage = 25; self.targetX = 0; self.targetY = 0; self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 5) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { // Hit target area, check for zombie collision for (var i = 0; i < zombies.length; i++) { var zdx = zombies[i].x - self.x; var zdy = zombies[i].y - self.y; var zdist = Math.sqrt(zdx * zdx + zdy * zdy); if (zdist < 30) { zombies[i].takeDamage(self.damage); self.destroy(); return; } } self.destroy(); } }; return self; }); var BuriedZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('buriedZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.8; self.health = 40; self.damage = 12; self.target = null; self.attackTimer = 0; self.frozen = 0; self.emergingTimer = 0; self.isEmerging = true; // Start buried (underground) self.alpha = 0.3; self.emergingTimer = 120; // 2 seconds to emerge self.update = function () { // Handle emerging from ground if (self.isEmerging) { self.emergingTimer--; if (self.emergingTimer <= 0) { self.isEmerging = false; self.alpha = 1; // Flash effect when emerging LK.effects.flashObject(self, 0x8b7355, 300); } return; } // Handle burning effect if (self.burnDuration > 0) { self.burnDuration--; if (self.burnDuration % 10 === 0) { self.takeDamage(self.burnDamage || 3); } } // Handle acid effect if (self.acidDuration > 0) { self.acidDuration--; if (self.acidDuration % 15 === 0) { self.takeDamage(self.acidDamage || 2); } } if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.4; } else if (self.burnDuration > 0) { zombieGraphics.tint = 0xFF4500; self.speed = 0.8; } else if (self.acidDuration > 0) { zombieGraphics.tint = 0x32CD32; self.speed = 0.8; } else { zombieGraphics.tint = 0x8b7355; self.speed = 0.8; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { // Check for wall collisions before moving var nextX = self.x + dx / dist * self.speed; var nextY = self.y + dy / dist * self.speed; var blocked = false; // Check collision with walls for (var w = 0; w < walls.length; w++) { var wallDx = nextX - walls[w].x; var wallDy = nextY - walls[w].y; var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy); if (wallDist < 80) { self.target = walls[w]; blocked = true; break; } } if (!blocked) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } } else { self.attackTimer++; if (self.attackTimer >= 50) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { // Take reduced damage while emerging if (self.isEmerging) { damage = Math.floor(damage * 0.5); } self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 14); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 150; }; return self; }); var CardboardZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('cardboardZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.5; self.health = 35; self.damage = 8; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { // Handle burning effect if (self.burnDuration > 0) { self.burnDuration--; if (self.burnDuration % 10 === 0) { self.takeDamage(self.burnDamage || 3); } } // Handle acid effect if (self.acidDuration > 0) { self.acidDuration--; if (self.acidDuration % 15 === 0) { self.takeDamage(self.acidDamage || 2); } } if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.25; } else if (self.burnDuration > 0) { zombieGraphics.tint = 0xFF4500; self.speed = 0.5; } else if (self.acidDuration > 0) { zombieGraphics.tint = 0x32CD32; self.speed = 0.5; } else { zombieGraphics.tint = 0xd2b48c; self.speed = 0.5; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { // Check for wall collisions before moving var nextX = self.x + dx / dist * self.speed; var nextY = self.y + dy / dist * self.speed; var blocked = false; // Check collision with walls for (var w = 0; w < walls.length; w++) { var wallDx = nextX - walls[w].x; var wallDy = nextY - walls[w].y; var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy); if (wallDist < 80) { self.target = walls[w]; blocked = true; break; } } if (!blocked) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } } else { self.attackTimer++; if (self.attackTimer >= 60) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 12); LK.getSound('zombieDeath').play(); // Spawn a random zombie (excluding CrowZombie) self.spawnRandomZombie(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.spawnRandomZombie = function () { var zombieTypes = [function () { return new Zombie(); }, function () { return new ToughZombie(); }, function () { return new RunnerZombie(); }, function () { return new UmbrellaZombie(); }, function () { return new CrowsZombie(); }, function () { return new TankZombie(); }]; var randomIndex = Math.floor(Math.random() * zombieTypes.length); var newZombie = zombieTypes[randomIndex](); newZombie.x = self.x + (Math.random() - 0.5) * 40; newZombie.y = self.y + (Math.random() - 0.5) * 40; zombies.push(newZombie); game.addChild(newZombie); }; self.freeze = function () { self.frozen = 180; }; return self; }); var ClassicTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('classicTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 25; self.range = 250; self.fireRate = 45; self.fireTimer = 0; self.health = 120; self.maxHealth = 120; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { if (dist < minDist) { minDist = dist; target = zombies[i]; } } } if (target) { // Aim turret at target var dx = target.x - self.x; var dy = target.y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; self.children[0].rotation = angle; self.shootBullet(target); } else { // No target in range, return to default direction (facing up) self.children[0].rotation = 0; } }; self.shootBullet = function (target) { LK.getSound('laser').play(); var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.targetX = target.x; bullet.targetY = target.y; bullet.damage = self.damage; game.addChild(bullet); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var CrazyZombie = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('crazyZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.3; self.health = 500; self.damage = 25; self.target = null; self.attackTimer = 0; self.frozen = 0; self.digTimer = 0; self.buffTimer = 0; self.isUnderground = false; self.update = function () { // Handle burning effect if (self.burnDuration > 0) { self.burnDuration--; if (self.burnDuration % 10 === 0) { self.takeDamage(self.burnDamage || 3); } } // Handle acid effect if (self.acidDuration > 0) { self.acidDuration--; if (self.acidDuration % 15 === 0) { self.takeDamage(self.acidDamage || 2); } } if (self.frozen > 0) { self.frozen--; bossGraphics.tint = 0x87CEEB; self.speed = 0.15; } else if (self.burnDuration > 0) { bossGraphics.tint = 0xFF4500; self.speed = 0.3; } else if (self.acidDuration > 0) { bossGraphics.tint = 0x32CD32; self.speed = 0.3; } else { bossGraphics.tint = 0x8b4513; self.speed = 0.3; } // Dig underground ability self.digTimer++; if (self.digTimer >= 360) { // Every 6 seconds self.digTimer = 0; self.digUnderground(); } // Buff other zombies self.buffTimer++; if (self.buffTimer >= 180) { // Every 3 seconds self.buffTimer = 0; self.buffOtherZombies(); } if (!self.isUnderground) { if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 60) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 45) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives -= 4; self.destroy(); } } } }; self.digUnderground = function () { self.isUnderground = true; self.alpha = 0.3; // Make translucent // Emerge near a random turret or house var targets = towers.concat(houses); if (targets.length > 0) { var randomTarget = targets[Math.floor(Math.random() * targets.length)]; tween(self, { x: randomTarget.x + (Math.random() - 0.5) * 100, y: randomTarget.y + (Math.random() - 0.5) * 100 }, { duration: 1000, onFinish: function onFinish() { self.isUnderground = false; self.alpha = 1; LK.effects.flashObject(self, 0x8b4513, 300); } }); } }; self.buffOtherZombies = function () { for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; if (zombie !== self) { var dx = zombie.x - self.x; var dy = zombie.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 200) { // Buff range zombie.speed *= 1.2; // Increase speed zombie.damage = Math.floor(zombie.damage * 1.1); // Increase damage LK.effects.flashObject(zombie, 0xFFD700, 200); } } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { if (self.isUnderground) { damage = Math.floor(damage * 0.5); // Take less damage underground } self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 200); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 120; }; return self; }); var CrowZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('crowZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 1.8; self.health = 15; self.damage = 5; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.9; } else { zombieGraphics.tint = 0xFFFFFF; self.speed = 1.8; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 30) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 45) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 5); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 120; }; return self; }); var CrowsZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('crowsZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.7; self.health = 60; self.damage = 10; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.35; } else { zombieGraphics.tint = 0xFFFFFF; self.speed = 0.7; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 60) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 15); LK.getSound('zombieDeath').play(); // Spawn 3 crow zombies for (var i = 0; i < 3; i++) { var crow = new CrowZombie(); crow.x = self.x + (Math.random() - 0.5) * 60; crow.y = self.y + (Math.random() - 0.5) * 60; zombies.push(crow); game.addChild(crow); } self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 180; }; return self; }); var DoubleBarrelTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('doubleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 20; self.range = 280; self.fireRate = 35; self.fireTimer = 0; self.health = 140; self.maxHealth = 140; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var targets = []; // Find all zombies in range and sort by distance var zombiesInRange = []; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { zombiesInRange.push({ zombie: zombies[i], distance: dist }); } } // Sort by distance and take closest 2 zombiesInRange.sort(function (a, b) { return a.distance - b.distance; }); for (var i = 0; i < Math.min(2, zombiesInRange.length); i++) { targets.push(zombiesInRange[i].zombie); } if (targets.length > 0) { // Aim at closest target var dx = targets[0].x - self.x; var dy = targets[0].y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; self.children[0].rotation = angle; // Shoot two bullets for (var i = 0; i < targets.length; i++) { self.shootBullet(targets[i]); } } else { // No targets in range, return to default direction (facing up) self.children[0].rotation = 0; } }; self.shootBullet = function (target) { LK.getSound('laser').play(); var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.targetX = target.x; bullet.targetY = target.y; bullet.damage = self.damage; game.addChild(bullet); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var ElectricalTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('electricalTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 35; self.range = 200; self.fireRate = 60; self.fireTimer = 0; self.health = 120; self.maxHealth = 120; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { if (dist < minDist) { minDist = dist; target = zombies[i]; } } } if (target) { self.shootElectricalBolt(target); } }; self.shootElectricalBolt = function (target) { LK.getSound('laser').play(); // Create electrical bolt effect var bolt = self.attachAsset('electricalBolt', { anchorX: 0.5, anchorY: 1 }); var dx = target.x - self.x; var dy = target.y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; bolt.rotation = angle; var dist = Math.sqrt(dx * dx + dy * dy); bolt.height = dist; bolt.alpha = 1; tween(bolt, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { bolt.destroy(); } }); // Damage primary target target.takeDamage(self.damage); // Chain to nearby zombies self.chainLightning(target, 2, self.damage * 0.7); }; self.chainLightning = function (sourceZombie, remainingChains, chainDamage) { if (remainingChains <= 0) return; var chainRange = 120; var nextTarget = null; var minDist = chainRange; for (var i = 0; i < zombies.length; i++) { if (zombies[i] === sourceZombie) continue; var dx = zombies[i].x - sourceZombie.x; var dy = zombies[i].y - sourceZombie.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= chainRange && dist < minDist) { minDist = dist; nextTarget = zombies[i]; } } if (nextTarget) { // Create chain bolt visual var chainBolt = game.addChild(LK.getAsset('electricalBolt', { anchorX: 0.5, anchorY: 1 })); chainBolt.x = sourceZombie.x; chainBolt.y = sourceZombie.y; var dx = nextTarget.x - sourceZombie.x; var dy = nextTarget.y - sourceZombie.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; chainBolt.rotation = angle; var dist = Math.sqrt(dx * dx + dy * dy); chainBolt.height = dist; chainBolt.width = 4; chainBolt.tint = 0x00ffff; tween(chainBolt, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { chainBolt.destroy(); } }); // Damage chain target nextTarget.takeDamage(Math.floor(chainDamage)); // Continue chain self.chainLightning(nextTarget, remainingChains - 1, chainDamage * 0.8); } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var EnergyBall = Container.expand(function () { var self = Container.call(this); var ballGraphics = self.attachAsset('energyBall', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 7; self.damage = 40; self.targetX = 0; self.targetY = 0; self.bounces = 3; self.hasHitTarget = false; self.update = function () { if (!self.hasHitTarget) { // Move toward initial target var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 10) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; // Rotate ball for visual effect ballGraphics.rotation += 0.2; } else { self.hasHitTarget = true; self.findNextTarget(); } } else { // Ball is bouncing between targets if (self.targetX !== 0 && self.targetY !== 0) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 15) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; ballGraphics.rotation += 0.2; } else { self.findNextTarget(); } } else { // No more targets, destroy ball self.destroy(); } } // Check for zombie collision for (var i = 0; i < zombies.length; i++) { var zdx = zombies[i].x - self.x; var zdy = zombies[i].y - self.y; var zdist = Math.sqrt(zdx * zdx + zdy * zdy); if (zdist < 25) { zombies[i].takeDamage(self.damage); self.findNextTarget(); break; } } }; self.findNextTarget = function () { if (self.bounces <= 0) { self.destroy(); return; } self.bounces--; var bounceRange = 150; var nextTarget = null; var minDist = bounceRange; for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= bounceRange && dist < minDist) { minDist = dist; nextTarget = zombies[i]; } } if (nextTarget) { self.targetX = nextTarget.x; self.targetY = nextTarget.y; // Flash effect when bouncing tween(ballGraphics, { scaleX: 1.5, scaleY: 1.5, tint: 0xffffff }, { duration: 100, onFinish: function onFinish() { tween(ballGraphics, { scaleX: 1, scaleY: 1, tint: 0x9370db }, { duration: 100 }); } }); } else { self.targetX = 0; self.targetY = 0; } }; return self; }); var ExplosiveTower = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('explosiveTower', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 75; self.range = 280; self.explosionRange = 100; self.fireRate = 90; self.fireTimer = 0; self.health = 120; self.maxHealth = 120; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndExplode(); } }; self.findAndExplode = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { if (dist < minDist) { minDist = dist; target = zombies[i]; } } } if (target) { self.explode(target.x, target.y); } }; self.explode = function (x, y) { LK.getSound('explode').play(); var explosion = game.addChild(LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5 })); explosion.x = x; explosion.y = y; explosion.alpha = 0.8; tween(explosion, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, onFinish: function onFinish() { explosion.destroy(); } }); for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - x; var dy = zombies[i].y - y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.explosionRange) { zombies[i].takeDamage(self.damage); } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var FakeZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('fakeZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.8; self.health = 10; self.damage = 3; self.target = null; self.attackTimer = 0; self.frozen = 0; self.lifeTimer = 0; self.maxLife = 600; // 10 seconds self.update = function () { self.lifeTimer++; if (self.lifeTimer >= self.maxLife) { // Fade away after 10 seconds tween(self, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); return; } if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.4; } else { zombieGraphics.tint = 0xdda0dd; self.speed = 0.8; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 30) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 60) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 2); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 60; }; return self; }); var FlameThrowerTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('flameThrowerTurret', { anchorX: 0.5, anchorY: 0.5 }); self.range = 150; self.fireRate = 30; self.fireTimer = 0; self.health = 110; self.maxHealth = 110; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.flameAttack(); } }; self.flameAttack = function () { LK.getSound('flame').play(); for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.range) { // Remove freezing effect if zombie is frozen if (zombies[i].frozen > 0) { zombies[i].frozen = 0; } // Apply burning effect zombies[i].burnDuration = 180; // 3 seconds of burning zombies[i].burnDamage = 3; // Damage per tick } } var flameEffect = self.attachAsset('fireEffect', { anchorX: 0.5, anchorY: 0.5 }); flameEffect.width = self.range * 2; flameEffect.height = self.range * 2; flameEffect.alpha = 0.7; tween(flameEffect, { alpha: 0, scaleX: 1.3, scaleY: 1.3 }, { duration: 400, onFinish: function onFinish() { flameEffect.destroy(); } }); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var FootballZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('footballZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.6; self.health = 150; self.damage = 20; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { // Handle burning effect if (self.burnDuration > 0) { self.burnDuration--; if (self.burnDuration % 10 === 0) { self.takeDamage(self.burnDamage || 3); } } // Handle acid effect if (self.acidDuration > 0) { self.acidDuration--; if (self.acidDuration % 15 === 0) { self.takeDamage(self.acidDamage || 2); } } if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.3; } else if (self.burnDuration > 0) { zombieGraphics.tint = 0xFF4500; self.speed = 0.6; } else if (self.acidDuration > 0) { zombieGraphics.tint = 0x32CD32; self.speed = 0.6; } else { zombieGraphics.tint = 0x2e8b57; self.speed = 0.6; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { // Check for wall collisions before moving var nextX = self.x + dx / dist * self.speed; var nextY = self.y + dy / dist * self.speed; var blocked = false; // Check collision with walls for (var w = 0; w < walls.length; w++) { var wallDx = nextX - walls[w].x; var wallDy = nextY - walls[w].y; var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy); if (wallDist < 80) { self.target = walls[w]; blocked = true; break; } } if (!blocked) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } } else { self.attackTimer++; if (self.attackTimer >= 45) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives -= 2; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 25); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 120; }; return self; }); var FreezeTower = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('freezeTower', { anchorX: 0.5, anchorY: 0.5 }); self.range = 150; self.fireRate = 120; self.fireTimer = 0; self.health = 100; self.maxHealth = 100; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.freezeArea(); } }; self.freezeArea = function () { LK.getSound('freeze').play(); for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.range) { zombies[i].freeze(); } } var freezeEffect = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); freezeEffect.tint = 0x00BFFF; freezeEffect.alpha = 0.5; freezeEffect.width = self.range * 2; freezeEffect.height = self.range * 2; tween(freezeEffect, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, onFinish: function onFinish() { freezeEffect.destroy(); } }); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var FrozenTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('frozenTurret', { anchorX: 0.5, anchorY: 0.5 }); self.range = 200; self.fireRate = 90; self.fireTimer = 0; self.health = 120; self.maxHealth = 120; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.freezeArea(); } }; self.freezeArea = function () { LK.getSound('freeze').play(); for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.range) { zombies[i].freeze(); } } var freezeEffect = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); freezeEffect.tint = 0x00FFFF; freezeEffect.alpha = 0.5; freezeEffect.width = self.range * 2; freezeEffect.height = self.range * 2; tween(freezeEffect, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, onFinish: function onFinish() { freezeEffect.destroy(); } }); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var HomingBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('homingBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 6; self.damage = 8; self.target = null; self.turnSpeed = 0.15; self.update = function () { // Find closest target if we don't have one or target is destroyed if (!self.target || self.target.destroyed || self.target.health <= 0) { self.findTarget(); } if (self.target) { // Move towards target with homing behavior var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 10) { // Normalize direction and move var dirX = dx / dist; var dirY = dy / dist; self.x += dirX * self.speed; self.y += dirY * self.speed; // Rotate bullet to face movement direction var angle = Math.atan2(dy, dx); bulletGraphics.rotation = angle; } else { // Hit target self.target.takeDamage(self.damage); self.destroy(); } } else { // No target, move forward self.y += self.speed; if (self.y > 2732) { self.destroy(); } } }; self.findTarget = function () { var minDist = 400; // Max homing range self.target = null; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = zombies[i]; } } }; return self; }); var House = Container.expand(function () { var self = Container.call(this); var houseGraphics = self.attachAsset('house', { anchorX: 0.5, anchorY: 0.5 }); // Scale house to span the width of the screen houseGraphics.width = 2048; // Full screen width houseGraphics.height = 100; // Keep original height self.health = 500; // Increased health since it's the only house self.maxHealth = 500; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { lives -= 10; // Lose more lives when the main house is destroyed self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var LaserTower = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('laserTower', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 30; self.range = 280; self.fireRate = 30; self.fireTimer = 0; self.health = 150; self.maxHealth = 150; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { if (dist < minDist) { minDist = dist; target = zombies[i]; } } } if (target) { self.shootLaser(target); } }; self.shootLaser = function (target) { LK.getSound('laser').play(); var laser = self.attachAsset('laserBeam', { anchorX: 0.5, anchorY: 1 }); var dx = target.x - self.x; var dy = target.y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; laser.rotation = angle; var dist = Math.sqrt(dx * dx + dy * dy); laser.height = dist; tween(laser, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { laser.destroy(); } }); target.takeDamage(self.damage); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var MinigunTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('minigunTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 12; self.range = 220; self.fireRate = 10; // Very fast firing self.fireTimer = 0; self.health = 130; self.maxHealth = 130; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { if (dist < minDist) { minDist = dist; target = zombies[i]; } } } if (target) { // Aim turret at target var dx = target.x - self.x; var dy = target.y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; self.children[0].rotation = angle; self.shootBullet(target); } else { // No target in range, return to default direction (facing up) self.children[0].rotation = 0; } }; self.shootBullet = function (target) { LK.getSound('minigun').play(); var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.targetX = target.x; bullet.targetY = target.y; bullet.damage = self.damage; game.addChild(bullet); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var NinjaZombie = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('ninjaZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.4; self.health = 350; self.damage = 30; self.target = null; self.attackTimer = 0; self.frozen = 0; self.teleportTimer = 0; self.cloneTimer = 0; self.canClone = true; self.update = function () { // Handle burning effect if (self.burnDuration > 0) { self.burnDuration--; if (self.burnDuration % 10 === 0) { self.takeDamage(self.burnDamage || 3); } } // Handle acid effect if (self.acidDuration > 0) { self.acidDuration--; if (self.acidDuration % 15 === 0) { self.takeDamage(self.acidDamage || 2); } } if (self.frozen > 0) { self.frozen--; bossGraphics.tint = 0x87CEEB; self.speed = 0.2; } else if (self.burnDuration > 0) { bossGraphics.tint = 0xFF4500; self.speed = 0.4; } else if (self.acidDuration > 0) { bossGraphics.tint = 0x32CD32; self.speed = 0.4; } else { bossGraphics.tint = 0x2f2f2f; self.speed = 0.4; } // Teleport ability self.teleportTimer++; if (self.teleportTimer >= 240) { // Every 4 seconds self.teleportTimer = 0; self.teleport(); } // Clone ability when health is low if (self.health < 175 && self.canClone) { self.canClone = false; self.createClone(); } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 60) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 30) { // Faster attacks self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives -= 3; self.destroy(); } } }; self.teleport = function () { // Teleport to random location self.x = Math.random() * 2048; self.y = Math.random() * 1000 + 200; LK.effects.flashObject(self, 0x8A2BE2, 300); tween(self, { alpha: 0.5 }, { duration: 100, onFinish: function onFinish() { tween(self, { alpha: 1 }, { duration: 100 }); } }); }; self.createClone = function () { var clone = new NinjaZombie(); clone.x = self.x + (Math.random() - 0.5) * 200; clone.y = self.y + (Math.random() - 0.5) * 200; clone.health = 100; // Clone has less health clone.canClone = false; // Clones can't clone zombies.push(clone); game.addChild(clone); LK.effects.flashObject(self, 0x800080, 500); }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 150); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 120; }; return self; }); var Rocket = Container.expand(function () { var self = Container.call(this); var rocketGraphics = self.attachAsset('rocket', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 10; self.damage = 80; self.explosionRange = 120; self.targetX = 0; self.targetY = 0; self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 8) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; // Rotate rocket to face movement direction var angle = Math.atan2(dy, dx) + Math.PI / 2; rocketGraphics.rotation = angle; } else { // Explode at target location self.explode(); } }; self.explode = function () { LK.getSound('explode').play(); var explosion = game.addChild(LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5 })); explosion.x = self.x; explosion.y = self.y; explosion.alpha = 0.9; explosion.width = self.explosionRange * 2; explosion.height = self.explosionRange * 2; tween(explosion, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 400, onFinish: function onFinish() { explosion.destroy(); } }); // Damage all zombies in explosion range for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.explosionRange) { zombies[i].takeDamage(self.damage); } } self.destroy(); }; return self; }); var RocketTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('rocketTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 80; self.range = 300; self.explosionRange = 120; self.fireRate = 120; // Slow but powerful self.fireTimer = 0; self.health = 150; self.maxHealth = 150; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { if (dist < minDist) { minDist = dist; target = zombies[i]; } } } if (target) { // Aim turret at target var dx = target.x - self.x; var dy = target.y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; self.children[0].rotation = angle; self.shootRocket(target); } else { // No target in range, return to default direction (facing up) self.children[0].rotation = 0; } }; self.shootRocket = function (target) { LK.getSound('rocket').play(); var rocket = new Rocket(); rocket.x = self.x; rocket.y = self.y; rocket.targetX = target.x; rocket.targetY = target.y; rocket.damage = self.damage; rocket.explosionRange = self.explosionRange; game.addChild(rocket); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var RunnerZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('runnerZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 1.4; self.health = 30; self.damage = 8; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.7; } else { zombieGraphics.tint = 0xFFFFFF; self.speed = 1.4; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 60) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 8); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 180; }; return self; }); var ShamanZombie = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('shamanZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.25; self.health = 400; self.damage = 20; self.target = null; self.attackTimer = 0; self.frozen = 0; self.summonTimer = 0; self.healTimer = 0; self.lowHealthHealed = false; self.update = function () { // Handle burning effect if (self.burnDuration > 0) { self.burnDuration--; if (self.burnDuration % 10 === 0) { self.takeDamage(self.burnDamage || 3); } } // Handle acid effect if (self.acidDuration > 0) { self.acidDuration--; if (self.acidDuration % 15 === 0) { self.takeDamage(self.acidDamage || 2); } } if (self.frozen > 0) { self.frozen--; bossGraphics.tint = 0x87CEEB; self.speed = 0.125; } else if (self.burnDuration > 0) { bossGraphics.tint = 0xFF4500; self.speed = 0.25; } else if (self.acidDuration > 0) { bossGraphics.tint = 0x32CD32; self.speed = 0.25; } else { bossGraphics.tint = 0x800080; self.speed = 0.25; } // Healing when low on health if (self.health < 100 && !self.lowHealthHealed) { self.healTimer++; if (self.healTimer >= 120) { // 2 seconds self.health += 150; self.lowHealthHealed = true; LK.effects.flashObject(self, 0x00FF00, 500); } } // Summon fake zombies periodically self.summonTimer++; if (self.summonTimer >= 300) { // Every 5 seconds self.summonTimer = 0; self.summonFakeZombies(); } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 60) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 45) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives -= 3; self.destroy(); } } }; self.summonFakeZombies = function () { for (var i = 0; i < 2; i++) { var fakeZombie = new FakeZombie(); fakeZombie.x = self.x + (Math.random() - 0.5) * 100; fakeZombie.y = self.y + (Math.random() - 0.5) * 100; zombies.push(fakeZombie); game.addChild(fakeZombie); } LK.effects.flashObject(self, 0x800080, 300); }; self.findTarget = function () { var minDist = Infinity; self.target = null; // Prioritize houses first for (var i = 0; i < houses.length; i++) { // Create a virtual target point spread across the house width var houseLeft = houses[i].x - 1024; // Left edge of house var houseRight = houses[i].x + 1024; // Right edge of house var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); // Clamp zombie x to house width var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } // Skip solar collectors and towers - they are not targeted by boss zombies }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 150); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 120; }; return self; }); var SolarCollector = Container.expand(function () { var self = Container.call(this); var collector = self.attachAsset('solarCollector', { anchorX: 0.5, anchorY: 0.5 }); self.energyRate = 1; self.health = 100; self.maxHealth = 100; self.isDaytime = true; self.energyTimer = 0; self.energyGenerationTime = Math.floor(Math.random() * 300) + 600; // Random 10-15 seconds (600-900 frames at 60fps) self.rotationTimer = 0; self.rotationInterval = Math.floor(Math.random() * 600) + 300; // Random 5-15 seconds for rotation self.update = function () { self.energyTimer++; if (self.energyTimer >= self.energyGenerationTime) { self.energyTimer = 0; // Set new random interval for next generation self.energyGenerationTime = Math.floor(Math.random() * 300) + 600; // Random 10-15 seconds var energyGain = 15; energy += energyGain; LK.getSound('collect').play(); var energyText = new Text2('+' + energyGain, { size: 30, fill: 0xFFD700 }); energyText.anchor.set(0.5, 0.5); energyText.x = 0; energyText.y = -40; self.addChild(energyText); tween(energyText, { y: -80, alpha: 0 }, { duration: 1000, onFinish: function onFinish() { energyText.destroy(); } }); } // Handle random rotation self.rotationTimer++; if (self.rotationTimer >= self.rotationInterval) { self.rotationTimer = 0; // Set new random interval for next rotation self.rotationInterval = Math.floor(Math.random() * 600) + 300; // Random 5-15 seconds // Rotate to a random angle with smooth animation var targetRotation = Math.random() * Math.PI * 2; // Random angle between 0 and 2π tween(collector, { rotation: targetRotation }, { duration: 800, easing: tween.easeInOut }); } collector.tint = self.isDaytime ? 0xFFD700 : 0xB8860B; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var SoldierZombie = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('soldierZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.35; self.health = 300; self.damage = 22; self.target = null; self.attackTimer = 0; self.frozen = 0; self.groupSpawned = false; self.update = function () { // Handle burning effect if (self.burnDuration > 0) { self.burnDuration--; if (self.burnDuration % 10 === 0) { self.takeDamage(self.burnDamage || 3); } } // Handle acid effect if (self.acidDuration > 0) { self.acidDuration--; if (self.acidDuration % 15 === 0) { self.takeDamage(self.acidDamage || 2); } } if (self.frozen > 0) { self.frozen--; bossGraphics.tint = 0x87CEEB; self.speed = 0.175; } else if (self.burnDuration > 0) { bossGraphics.tint = 0xFF4500; self.speed = 0.35; } else if (self.acidDuration > 0) { bossGraphics.tint = 0x32CD32; self.speed = 0.35; } else { bossGraphics.tint = 0x556b2f; self.speed = 0.35; } // Spawn group when health gets low if (self.health < 150 && !self.groupSpawned) { self.groupSpawned = true; self.spawnGroup(); } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 60) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 45) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives -= 3; self.destroy(); } } }; self.spawnGroup = function () { // Spawn 4 regular zombies for (var i = 0; i < 4; i++) { var soldier = new Zombie(); soldier.x = self.x + (Math.random() - 0.5) * 150; soldier.y = self.y + (Math.random() - 0.5) * 150; soldier.health = 40; // Slightly stronger soldier.damage = 12; zombies.push(soldier); game.addChild(soldier); } // Spawn 2 tank zombies for (var i = 0; i < 2; i++) { var tank = new TankZombie(); tank.x = self.x + (Math.random() - 0.5) * 200; tank.y = self.y + (Math.random() - 0.5) * 200; zombies.push(tank); game.addChild(tank); } LK.effects.flashObject(self, 0x556b2f, 500); }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 150); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 120; }; return self; }); var SpikeTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('spikeTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 50; self.range = 180; self.fireRate = 90; self.fireTimer = 0; self.health = 100; self.maxHealth = 100; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndSpike(); } }; self.findAndSpike = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { if (dist < minDist) { minDist = dist; target = zombies[i]; } } } if (target) { self.createSpikes(target); } }; self.createSpikes = function (target) { LK.getSound('build').play(); // Create multiple spikes around target area for (var i = 0; i < 5; i++) { var spike = game.addChild(LK.getAsset('spike', { anchorX: 0.5, anchorY: 1 })); spike.x = target.x + (Math.random() - 0.5) * 80; spike.y = target.y + (Math.random() - 0.5) * 40; spike.scaleY = 0; spike.alpha = 0.8; spike.tint = 0x8b4513; // Animate spikes emerging from ground tween(spike, { scaleY: 1 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { // Check for zombie collision after spike emerges for (var j = 0; j < zombies.length; j++) { var dx = zombies[j].x - spike.x; var dy = zombies[j].y - spike.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 40) { zombies[j].takeDamage(self.damage); } } // Retract spikes after 1 second tween(spike, { scaleY: 0, alpha: 0 }, { duration: 300, onFinish: function onFinish() { spike.destroy(); } }); } }); } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var TankZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('tankZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.2; self.health = 100; self.damage = 18; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.1; } else { zombieGraphics.tint = 0x696969; self.speed = 0.2; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 60) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 20); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 120; }; return self; }); var ToughZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('toughZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.4; self.health = 120; self.damage = 15; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.2; } else { zombieGraphics.tint = 0xFFFFFF; self.speed = 0.4; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 60) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 15); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 180; }; return self; }); var TripleBarrelTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('tripleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 15; self.range = 300; self.fireRate = 30; self.fireTimer = 0; self.health = 160; self.maxHealth = 160; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var targets = []; // Find all zombies in range and sort by distance var zombiesInRange = []; for (var i = 0; i < zombies.length; i++) { // Skip destroyed or dead zombies if (zombies[i].destroyed || zombies[i].health <= 0) continue; var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= self.range) { zombiesInRange.push({ zombie: zombies[i], distance: dist }); } } // Sort by distance and take closest 3 zombiesInRange.sort(function (a, b) { return a.distance - b.distance; }); for (var i = 0; i < Math.min(3, zombiesInRange.length); i++) { targets.push(zombiesInRange[i].zombie); } if (targets.length > 0) { // Aim at closest target var dx = targets[0].x - self.x; var dy = targets[0].y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; self.children[0].rotation = angle; // Shoot three bullets for (var i = 0; i < targets.length; i++) { self.shootBullet(targets[i]); } } else { // No targets in range, return to default direction (facing up) self.children[0].rotation = 0; } }; self.shootBullet = function (target) { LK.getSound('laser').play(); var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.targetX = target.x; bullet.targetY = target.y; bullet.damage = self.damage; game.addChild(bullet); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var UmbrellaZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('umbrellaZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.6; self.health = 50; self.damage = 10; self.umbrellaHealth = 40; self.hasUmbrella = true; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.3; } else { zombieGraphics.tint = self.hasUmbrella ? 0x9370DB : 0xFFFFFF; self.speed = 0.6; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 60) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; for (var i = 0; i < houses.length; i++) { var houseLeft = houses[i].x - 1024; var houseRight = houses[i].x + 1024; var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } }; self.takeDamage = function (damage) { if (self.hasUmbrella) { self.umbrellaHealth -= damage; if (self.umbrellaHealth <= 0) { self.hasUmbrella = false; LK.effects.flashObject(self, 0xFF0000, 300); } else { LK.effects.flashObject(self, 0x9370DB, 200); } return false; } else { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 12); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; } }; self.freeze = function () { self.frozen = 180; }; return self; }); var Wall = Container.expand(function () { var self = Container.call(this); var wallGraphics = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); self.health = 200; self.maxHealth = 200; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { // Find and mark grid cell as unoccupied when wall is destroyed var gridX = Math.floor((self.x - GRID_SIZE / 2) / GRID_SIZE); var gridY = Math.floor((self.y - GRID_SIZE / 2 - 200) / GRID_SIZE); if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS) { var cellIndex = gridY * GRID_COLS + gridX; if (cellIndex < gridCells.length) { gridCells[cellIndex].occupied = false; } } self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var Zombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.7; self.health = 50; self.damage = 10; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { // Handle burning effect if (self.burnDuration > 0) { self.burnDuration--; if (self.burnDuration % 10 === 0) { // Damage every 10 frames self.takeDamage(self.burnDamage || 3); } } // Handle acid effect if (self.acidDuration > 0) { self.acidDuration--; if (self.acidDuration % 15 === 0) { // Damage every 15 frames self.takeDamage(self.acidDamage || 2); } } if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.3; } else if (self.burnDuration > 0) { zombieGraphics.tint = 0xFF4500; // Orange tint for burning self.speed = 0.7; } else if (self.acidDuration > 0) { zombieGraphics.tint = 0x32CD32; // Green tint for acid self.speed = 0.7; } else { zombieGraphics.tint = 0xFFFFFF; self.speed = 0.7; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { // Check for wall collisions before moving var nextX = self.x + dx / dist * self.speed; var nextY = self.y + dy / dist * self.speed; var blocked = false; // Check collision with walls for (var w = 0; w < walls.length; w++) { var wallDx = nextX - walls[w].x; var wallDy = nextY - walls[w].y; var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy); if (wallDist < 80) { // Zombie is blocked by wall, attack it instead self.target = walls[w]; blocked = true; break; } } if (!blocked) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } } else { self.attackTimer++; if (self.attackTimer >= 60) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { // Move towards bottom of screen when no target self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; // Prioritize houses first for (var i = 0; i < houses.length; i++) { // Create a virtual target point spread across the house width var houseLeft = houses[i].x - 1024; // Left edge of house var houseRight = houses[i].x + 1024; // Right edge of house var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); // Clamp zombie x to house width var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } // Skip solar collectors and towers - they are not targeted by zombies }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 10); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 180; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x228B22 }); /**** * Game Code ****/ // Add background image var background = game.addChild(LK.getAsset('background', { anchorX: 0, anchorY: 0 })); background.x = 0; background.y = 0; background.width = 2048; background.height = 2732; // Send background to back layer game.setChildIndex(background, 0); var energy = 100; var wave = 0; var lives = 20; var isDaytime = true; var dayTimer = 0; var waveTimer = 0; var zombieSpawnTimer = 0; var zombiesPerWave = 5; var zombiesToSpawn = 0; var bossProgress = 0; var bossProgressMax = 1000; // Will be 300 seconds at 60fps with new increment rate var currentStage = 1; var bossActive = false; var difficultyMultiplier = 1; var easyPeriodTimer = 0; var easyPeriodDuration = 2100; // 35 seconds at 60fps var isEasyPeriod = true; var gameTimer = 0; // Track total game time in frames (60fps) var specialZombiesUnlocked = false; // Track if special zombies are unlocked var currentPage = 0; // Current turret page (0 = basic turrets, 1 = special turrets, 2 = advanced turrets, 3 = experimental turrets) var maxPages = 3; // Total number of pages (0, 1, 2, 3) var solarCollectors = []; var towers = []; var zombies = []; var houses = []; var walls = []; var attractors = []; var gridCells = []; var selectedTower = null; var placementMode = false; var placementCost = 0; var dragging = false; var dragTurret = null; var GRID_SIZE = 120; var GRID_COLS = Math.floor(2048 / GRID_SIZE); var GRID_ROWS = Math.floor(2200 / GRID_SIZE); // Extend to house area boundary var energyText = new Text2('Energy: ' + energy, { size: 60, fill: 0xFFD700 }); energyText.anchor.set(0.5, 0); LK.gui.top.addChild(energyText); var waveText = new Text2('Wave: ' + wave, { size: 50, fill: 0xFFFFFF }); waveText.anchor.set(0, 0); waveText.x = -900; waveText.y = 100; LK.gui.top.addChild(waveText); var livesText = new Text2('Lives: ' + lives, { size: 50, fill: 0xFF0000 }); livesText.anchor.set(1, 0); livesText.x = 900; livesText.y = 100; LK.gui.top.addChild(livesText); var dayText = new Text2('Day', { size: 40, fill: 0xFFD700 }); dayText.anchor.set(0.5, 0); dayText.y = 80; LK.gui.top.addChild(dayText); var progressBarBg = LK.getAsset('progressBarBg', { anchorX: 0.5, anchorY: 0.5 }); progressBarBg.x = 0; progressBarBg.y = 150; LK.gui.top.addChild(progressBarBg); var progressBarFill = LK.getAsset('progressBarFill', { anchorX: 0, anchorY: 0.5 }); progressBarFill.x = -200; progressBarFill.y = 150; progressBarFill.width = 0; LK.gui.top.addChild(progressBarFill); var progressText = new Text2('Boss Progress', { size: 30, fill: 0xFFFFFF }); progressText.anchor.set(0.5, 0.5); progressText.x = 0; progressText.y = 180; LK.gui.top.addChild(progressText); var stageText = new Text2('Stage: 1', { size: 35, fill: 0xFFD700 }); stageText.anchor.set(0, 0); stageText.x = -900; stageText.y = 150; LK.gui.top.addChild(stageText); var solarButton = new Container(); var solarButtonBg = solarButton.attachAsset('solarCollector', { anchorX: 0.5, anchorY: 0.5 }); solarButtonBg.width = 100; solarButtonBg.height = 100; var solarButtonText = new Text2('Solar: 25', { size: 25, fill: 0xFFD700 }); solarButtonText.anchor.set(0.5, 0.5); solarButtonText.y = -60; solarButton.addChild(solarButtonText); solarButton.x = -300; LK.gui.bottom.addChild(solarButton); var classicButton = new Container(); var classicButtonBg = classicButton.attachAsset('classicTurret', { anchorX: 0.5, anchorY: 0.5 }); classicButtonBg.width = 100; classicButtonBg.height = 100; var classicButtonText = new Text2('Solar: 50', { size: 25, fill: 0xFFD700 }); classicButtonText.anchor.set(0.5, 0.5); classicButtonText.y = -60; classicButton.addChild(classicButtonText); classicButton.x = -100; LK.gui.bottom.addChild(classicButton); var doubleButton = new Container(); var doubleButtonBg = doubleButton.attachAsset('doubleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 }); doubleButtonBg.width = 100; doubleButtonBg.height = 100; var doubleButtonText = new Text2('Solar: 75', { size: 25, fill: 0xFFD700 }); doubleButtonText.anchor.set(0.5, 0.5); doubleButtonText.y = -60; doubleButton.addChild(doubleButtonText); doubleButton.x = 100; LK.gui.bottom.addChild(doubleButton); var tripleButton = new Container(); var tripleButtonBg = tripleButton.attachAsset('tripleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 }); tripleButtonBg.width = 100; tripleButtonBg.height = 100; var tripleButtonText = new Text2('Solar: 100', { size: 25, fill: 0xFFD700 }); tripleButtonText.anchor.set(0.5, 0.5); tripleButtonText.y = -60; tripleButton.addChild(tripleButtonText); tripleButton.x = 300; LK.gui.bottom.addChild(tripleButton); var frozenButton = new Container(); var frozenButtonBg = frozenButton.attachAsset('frozenTurret', { anchorX: 0.5, anchorY: 0.5 }); frozenButtonBg.width = 100; frozenButtonBg.height = 100; var frozenButtonText = new Text2('Solar: 60', { size: 25, fill: 0xFFD700 }); frozenButtonText.anchor.set(0.5, 0.5); frozenButtonText.y = -60; frozenButton.addChild(frozenButtonText); frozenButton.x = -200; frozenButton.y = -150; LK.gui.bottom.addChild(frozenButton); var flameButton = new Container(); var flameButtonBg = flameButton.attachAsset('flameThrowerTurret', { anchorX: 0.5, anchorY: 0.5 }); flameButtonBg.width = 100; flameButtonBg.height = 100; var flameButtonText = new Text2('Solar: 80', { size: 25, fill: 0xFFD700 }); flameButtonText.anchor.set(0.5, 0.5); flameButtonText.y = -60; flameButton.addChild(flameButtonText); flameButton.x = 0; flameButton.y = -150; LK.gui.bottom.addChild(flameButton); var acidButton = new Container(); var acidButtonBg = acidButton.attachAsset('acidTurret', { anchorX: 0.5, anchorY: 0.5 }); acidButtonBg.width = 100; acidButtonBg.height = 100; var acidButtonText = new Text2('Solar: 70', { size: 25, fill: 0xFFD700 }); acidButtonText.anchor.set(0.5, 0.5); acidButtonText.y = -60; acidButton.addChild(acidButtonText); acidButton.x = 200; acidButton.y = -150; LK.gui.bottom.addChild(acidButton); var autoButton = new Container(); var autoButtonBg = autoButton.attachAsset('autoTurret', { anchorX: 0.5, anchorY: 0.5 }); autoButtonBg.width = 100; autoButtonBg.height = 100; var autoButtonText = new Text2('Solar: 120', { size: 25, fill: 0xFFD700 }); autoButtonText.anchor.set(0.5, 0.5); autoButtonText.y = -60; autoButton.addChild(autoButtonText); autoButton.x = -100; autoButton.y = -250; LK.gui.bottom.addChild(autoButton); var minigunButton = new Container(); var minigunButtonBg = minigunButton.attachAsset('minigunTurret', { anchorX: 0.5, anchorY: 0.5 }); minigunButtonBg.width = 100; minigunButtonBg.height = 100; var minigunButtonText = new Text2('Solar: 90', { size: 25, fill: 0xFFD700 }); minigunButtonText.anchor.set(0.5, 0.5); minigunButtonText.y = -60; minigunButton.addChild(minigunButtonText); minigunButton.x = 100; minigunButton.y = -250; LK.gui.bottom.addChild(minigunButton); var rocketButton = new Container(); var rocketButtonBg = rocketButton.attachAsset('rocketTurret', { anchorX: 0.5, anchorY: 0.5 }); rocketButtonBg.width = 100; rocketButtonBg.height = 100; var rocketButtonText = new Text2('Solar: 150', { size: 25, fill: 0xFFD700 }); rocketButtonText.anchor.set(0.5, 0.5); rocketButtonText.y = -60; rocketButton.addChild(rocketButtonText); rocketButton.x = 0; rocketButton.y = -250; LK.gui.bottom.addChild(rocketButton); var electricalButton = new Container(); var electricalButtonBg = electricalButton.attachAsset('electricalTurret', { anchorX: 0.5, anchorY: 0.5 }); electricalButtonBg.width = 100; electricalButtonBg.height = 100; var electricalButtonText = new Text2('Solar: 110', { size: 25, fill: 0xFFD700 }); electricalButtonText.anchor.set(0.5, 0.5); electricalButtonText.y = -60; electricalButton.addChild(electricalButtonText); electricalButton.x = -300; electricalButton.y = -350; LK.gui.bottom.addChild(electricalButton); var spikeButton = new Container(); var spikeButtonBg = spikeButton.attachAsset('spikeTurret', { anchorX: 0.5, anchorY: 0.5 }); spikeButtonBg.width = 100; spikeButtonBg.height = 100; var spikeButtonText = new Text2('Solar: 85', { size: 25, fill: 0xFFD700 }); spikeButtonText.anchor.set(0.5, 0.5); spikeButtonText.y = -60; spikeButton.addChild(spikeButtonText); spikeButton.x = -100; spikeButton.y = -350; LK.gui.bottom.addChild(spikeButton); var ballButton = new Container(); var ballButtonBg = ballButton.attachAsset('ballTurret', { anchorX: 0.5, anchorY: 0.5 }); ballButtonBg.width = 100; ballButtonBg.height = 100; var ballButtonText = new Text2('Solar: 95', { size: 25, fill: 0xFFD700 }); ballButtonText.anchor.set(0.5, 0.5); ballButtonText.y = -60; ballButton.addChild(ballButtonText); ballButton.x = 100; ballButton.y = -350; LK.gui.bottom.addChild(ballButton); var wallButton = new Container(); var wallButtonBg = wallButton.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); wallButtonBg.width = 100; wallButtonBg.height = 100; var wallButtonText = new Text2('Solar: 30', { size: 25, fill: 0xFFD700 }); wallButtonText.anchor.set(0.5, 0.5); wallButtonText.y = -60; wallButton.addChild(wallButtonText); wallButton.x = 300; wallButton.y = -350; LK.gui.bottom.addChild(wallButton); var attractorButton = new Container(); var attractorButtonBg = attractorButton.attachAsset('attractor', { anchorX: 0.5, anchorY: 0.5 }); attractorButtonBg.width = 100; attractorButtonBg.height = 100; var attractorButtonText = new Text2('Solar: 50', { size: 25, fill: 0xFFD700 }); attractorButtonText.anchor.set(0.5, 0.5); attractorButtonText.y = -60; attractorButton.addChild(attractorButtonText); attractorButton.x = -300; attractorButton.y = -450; LK.gui.bottom.addChild(attractorButton); // Create navigation arrows var leftArrow = new Container(); var leftArrowBg = leftArrow.attachAsset('gridCell', { anchorX: 0.5, anchorY: 0.5 }); leftArrowBg.width = 80; leftArrowBg.height = 80; leftArrowBg.tint = 0x4169E1; var leftArrowText = new Text2('<', { size: 60, fill: 0xFFFFFF }); leftArrowText.anchor.set(0.5, 0.5); leftArrow.addChild(leftArrowText); leftArrow.x = -450; leftArrow.y = 0; LK.gui.bottom.addChild(leftArrow); var rightArrow = new Container(); var rightArrowBg = rightArrow.attachAsset('gridCell', { anchorX: 0.5, anchorY: 0.5 }); rightArrowBg.width = 80; rightArrowBg.height = 80; rightArrowBg.tint = 0x4169E1; var rightArrowText = new Text2('>', { size: 60, fill: 0xFFFFFF }); rightArrowText.anchor.set(0.5, 0.5); rightArrow.addChild(rightArrowText); rightArrow.x = 450; rightArrow.y = 0; LK.gui.bottom.addChild(rightArrow); // Create page indicator text var pageText = new Text2('Page 1/3', { size: 30, fill: 0xFFFFFF }); pageText.anchor.set(0.5, 0.5); pageText.x = 0; pageText.y = 100; LK.gui.bottom.addChild(pageText); for (var row = 0; row < GRID_ROWS; row++) { for (var col = 0; col < GRID_COLS; col++) { var cell = game.addChild(LK.getAsset('gridCell', { anchorX: 0, anchorY: 0 })); cell.x = col * GRID_SIZE; cell.y = row * GRID_SIZE + 200; cell.alpha = 0.1; cell.gridX = col; cell.gridY = row; cell.occupied = false; gridCells.push(cell); } } // Create single house spanning the bottom of screen var house = new House(); house.x = 1024; // Center of screen width (2048/2) house.y = 2500; houses.push(house); game.addChild(house); // Initialize turret page display updateTurretPage(); solarButton.down = function (x, y, obj) { if (energy < 25) { // Flash button red to indicate insufficient energy LK.effects.flashObject(solarButton, 0xff0000, 300); return; } dragging = true; selectedTower = 'solar'; placementCost = 25; dragTurret = game.addChild(LK.getAsset('solarCollector', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 120; dragTurret.height = 120; dragTurret.alpha = 0.8; // Convert GUI position to game coordinates var globalPos = game.toLocal({ x: solarButton.x, y: solarButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; // Flash button green to indicate drag started LK.effects.flashObject(solarButton, 0x00ff00, 200); }; classicButton.down = function (x, y, obj) { if (energy < 50) return; dragging = true; selectedTower = 'classic'; placementCost = 50; dragTurret = game.addChild(LK.getAsset('classicTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; // Convert GUI position to game coordinates var globalPos = game.toLocal({ x: classicButton.x, y: classicButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; doubleButton.down = function (x, y, obj) { if (energy < 75) return; dragging = true; selectedTower = 'double'; placementCost = 75; dragTurret = game.addChild(LK.getAsset('doubleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; // Convert GUI position to game coordinates var globalPos = game.toLocal({ x: doubleButton.x, y: doubleButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; tripleButton.down = function (x, y, obj) { if (energy < 100) return; dragging = true; selectedTower = 'triple'; placementCost = 100; dragTurret = game.addChild(LK.getAsset('tripleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; // Convert GUI position to game coordinates var globalPos = game.toLocal({ x: tripleButton.x, y: tripleButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; frozenButton.down = function (x, y, obj) { if (energy < 60) return; dragging = true; selectedTower = 'frozen'; placementCost = 60; dragTurret = game.addChild(LK.getAsset('frozenTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: frozenButton.x, y: frozenButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; flameButton.down = function (x, y, obj) { if (energy < 80) return; dragging = true; selectedTower = 'flame'; placementCost = 80; dragTurret = game.addChild(LK.getAsset('flameThrowerTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: flameButton.x, y: flameButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; acidButton.down = function (x, y, obj) { if (energy < 70) return; dragging = true; selectedTower = 'acid'; placementCost = 70; dragTurret = game.addChild(LK.getAsset('acidTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: acidButton.x, y: acidButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; autoButton.down = function (x, y, obj) { if (energy < 120) return; dragging = true; selectedTower = 'auto'; placementCost = 120; dragTurret = game.addChild(LK.getAsset('autoTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: autoButton.x, y: autoButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; minigunButton.down = function (x, y, obj) { if (energy < 90) return; dragging = true; selectedTower = 'minigun'; placementCost = 90; dragTurret = game.addChild(LK.getAsset('minigunTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: minigunButton.x, y: minigunButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; rocketButton.down = function (x, y, obj) { if (energy < 150) return; dragging = true; selectedTower = 'rocket'; placementCost = 150; dragTurret = game.addChild(LK.getAsset('rocketTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: rocketButton.x, y: rocketButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; electricalButton.down = function (x, y, obj) { if (energy < 110) return; dragging = true; selectedTower = 'electrical'; placementCost = 110; dragTurret = game.addChild(LK.getAsset('electricalTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: electricalButton.x, y: electricalButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; spikeButton.down = function (x, y, obj) { if (energy < 85) return; dragging = true; selectedTower = 'spike'; placementCost = 85; dragTurret = game.addChild(LK.getAsset('spikeTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: spikeButton.x, y: spikeButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; ballButton.down = function (x, y, obj) { if (energy < 95) return; dragging = true; selectedTower = 'ball'; placementCost = 95; dragTurret = game.addChild(LK.getAsset('ballTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: ballButton.x, y: ballButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; wallButton.down = function (x, y, obj) { if (energy < 30) return; dragging = true; selectedTower = 'wall'; placementCost = 30; dragTurret = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 120; dragTurret.height = 120; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: wallButton.x, y: wallButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; attractorButton.down = function (x, y, obj) { if (energy < 50) return; dragging = true; selectedTower = 'attractor'; placementCost = 50; dragTurret = game.addChild(LK.getAsset('attractor', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 80; dragTurret.height = 80; dragTurret.alpha = 0.7; var globalPos = game.toLocal({ x: attractorButton.x, y: attractorButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; leftArrow.down = function (x, y, obj) { if (currentPage > 0) { currentPage--; updateTurretPage(); LK.effects.flashObject(leftArrow, 0x00ff00, 200); } else { LK.effects.flashObject(leftArrow, 0xff0000, 200); } }; rightArrow.down = function (x, y, obj) { if (currentPage < maxPages) { currentPage++; updateTurretPage(); LK.effects.flashObject(rightArrow, 0x00ff00, 200); } else { LK.effects.flashObject(rightArrow, 0xff0000, 200); } }; function updateTurretPage() { // Hide all turret buttons first solarButton.visible = false; classicButton.visible = false; doubleButton.visible = false; tripleButton.visible = false; frozenButton.visible = false; flameButton.visible = false; acidButton.visible = false; autoButton.visible = false; minigunButton.visible = false; rocketButton.visible = false; electricalButton.visible = false; spikeButton.visible = false; ballButton.visible = false; wallButton.visible = false; attractorButton.visible = false; // Page 0: Basic turrets (solar, classic, double, triple) if (currentPage === 0) { solarButton.visible = true; classicButton.visible = true; doubleButton.visible = true; tripleButton.visible = true; } // Page 1: Special turrets (frozen, flame, acid) else if (currentPage === 1) { frozenButton.visible = true; flameButton.visible = true; acidButton.visible = true; // Reposition special turrets to bottom row frozenButton.x = -200; frozenButton.y = 0; flameButton.x = 0; flameButton.y = 0; acidButton.x = 200; acidButton.y = 0; } // Page 2: Advanced turrets (auto, minigun, rocket) else if (currentPage === 2) { autoButton.visible = true; minigunButton.visible = true; rocketButton.visible = true; // Reposition advanced turrets to bottom row autoButton.x = -200; autoButton.y = 0; minigunButton.x = 0; minigunButton.y = 0; rocketButton.x = 200; rocketButton.y = 0; } // Page 3: Experimental turrets (electrical, spike, ball, wall, attractor) else if (currentPage === 3) { electricalButton.visible = true; spikeButton.visible = true; ballButton.visible = true; wallButton.visible = true; attractorButton.visible = true; // Reposition experimental turrets to bottom row electricalButton.x = -300; electricalButton.y = 0; spikeButton.x = -100; spikeButton.y = 0; ballButton.x = 100; ballButton.y = 0; wallButton.x = 300; wallButton.y = 0; attractorButton.x = 0; attractorButton.y = -100; } // Update page indicator pageText.setText('Page ' + (currentPage + 1) + '/' + (maxPages + 1)); // Update arrow visibility/opacity leftArrow.alpha = currentPage > 0 ? 1.0 : 0.5; rightArrow.alpha = currentPage < maxPages ? 1.0 : 0.5; } function highlightValidCells(show) { for (var i = 0; i < gridCells.length; i++) { if (!gridCells[i].occupied) { gridCells[i].alpha = show ? 0.3 : 0.1; gridCells[i].tint = show ? 0x00ff00 : 0xffffff; } else { gridCells[i].alpha = show ? 0.2 : 0.1; gridCells[i].tint = show ? 0xff0000 : 0xffffff; } } } game.down = function (x, y, obj) { // Allow starting drag from anywhere in the game area when not already dragging if (!dragging) { // Check if we clicked near any existing turret buttons area var buttonAreaY = 2732 - 200; // Near bottom of screen if (y > buttonAreaY) { // Near button area, let button handlers manage this return; } // Check if we clicked in the house area (exclude from drag zone) var houseAreaY = 2400; // Above house area if (y > houseAreaY) { // In house area, don't allow dragging return; } } }; game.move = function (x, y, obj) { if (dragging && dragTurret) { dragTurret.x = x; dragTurret.y = y; highlightValidCells(true); // Show range circle for turrets (not solar collectors) if (selectedTower !== 'solar' && !dragTurret.rangeCircle) { var range = 250; // Default range if (selectedTower === 'double') range = 280; if (selectedTower === 'triple') range = 300; if (selectedTower === 'explosive') range = 280; if (selectedTower === 'laser') range = 280; if (selectedTower === 'freeze') range = 150; if (selectedTower === 'frozen') range = 200; if (selectedTower === 'flame') range = 150; if (selectedTower === 'acid') range = 140; if (selectedTower === 'auto') range = 400; if (selectedTower === 'minigun') range = 220; if (selectedTower === 'rocket') range = 300; if (selectedTower === 'electrical') range = 200; if (selectedTower === 'spike') range = 180; if (selectedTower === 'ball') range = 250; if (selectedTower === 'attractor') range = 300; dragTurret.rangeCircle = dragTurret.addChild(LK.getAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.rangeCircle.width = range * 2; dragTurret.rangeCircle.height = range * 2; dragTurret.rangeCircle.alpha = 0.2; dragTurret.rangeCircle.tint = 0x00ff00; } // Show visual feedback for valid placement area var gridX = Math.floor(x / GRID_SIZE); var gridY = Math.floor((y - 200) / GRID_SIZE); var houseAreaY = 2400; // House area boundary // Check if in house area (exclude from placement) var inHouseArea = y > houseAreaY; if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS && !inHouseArea) { var cellIndex = gridY * GRID_COLS + gridX; if (cellIndex < gridCells.length && !gridCells[cellIndex].occupied) { dragTurret.tint = 0x00ff00; // Green tint for valid placement dragTurret.alpha = 0.8; } else { dragTurret.tint = 0xff0000; // Red tint for invalid placement dragTurret.alpha = 0.5; } } else { dragTurret.tint = 0xff0000; // Red tint for out of bounds or house area dragTurret.alpha = 0.5; } } }; game.up = function (x, y, obj) { if (dragging && dragTurret) { highlightValidCells(false); var gridX = Math.floor(x / GRID_SIZE); var gridY = Math.floor((y - 200) / GRID_SIZE); var validPlacement = false; var houseAreaY = 2400; // House area boundary var inHouseArea = y > houseAreaY; // Expanded validation - check if within game bounds, valid grid, and not in house area if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS && !inHouseArea) { var cellIndex = gridY * GRID_COLS + gridX; if (cellIndex < gridCells.length && !gridCells[cellIndex].occupied && energy >= placementCost) { validPlacement = true; energy -= placementCost; gridCells[cellIndex].occupied = true; var newX = gridX * GRID_SIZE + GRID_SIZE / 2; var newY = gridY * GRID_SIZE + GRID_SIZE / 2 + 200; if (selectedTower === 'solar') { var collector = new SolarCollector(); collector.x = newX; collector.y = newY; solarCollectors.push(collector); game.addChild(collector); } else if (selectedTower === 'classic') { var tower = new ClassicTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'double') { var tower = new DoubleBarrelTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'triple') { var tower = new TripleBarrelTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'frozen') { var tower = new FrozenTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'flame') { var tower = new FlameThrowerTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'acid') { var tower = new AcidTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'auto') { var tower = new AutoTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'minigun') { var tower = new MinigunTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'rocket') { var tower = new RocketTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'electrical') { var tower = new ElectricalTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'spike') { var tower = new SpikeTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'ball') { var tower = new BallTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'wall') { var wall = new Wall(); wall.x = newX; wall.y = newY; walls.push(wall); game.addChild(wall); } else if (selectedTower === 'attractor') { var attractor = new Attractor(); attractor.x = newX; attractor.y = newY; attractors.push(attractor); game.addChild(attractor); } LK.getSound('build').play(); // Flash effect for successful placement LK.effects.flashObject(dragTurret, 0x00ff00, 300); } else { // Invalid placement - show error feedback LK.effects.flashObject(dragTurret, 0xff0000, 300); } } else { // Out of bounds or house area - show error feedback LK.effects.flashObject(dragTurret, 0xff0000, 300); } // Clean up drag state dragTurret.destroy(); dragTurret = null; dragging = false; selectedTower = null; placementCost = 0; } }; function spawnZombie(isBoss) { var zombie; if (isBoss) { // Randomly select boss type var bossRoll = Math.random(); if (bossRoll < 0.25) { zombie = new ShamanZombie(); } else if (bossRoll < 0.5) { zombie = new NinjaZombie(); } else if (bossRoll < 0.75) { zombie = new CrazyZombie(); } else { zombie = new SoldierZombie(); } zombie.health *= difficultyMultiplier; zombie.damage = Math.floor(zombie.damage * difficultyMultiplier); bossActive = true; } else { // Determine zombie type based on probability and game time var zombieRoll = Math.random(); var stageMultiplier = Math.min(currentStage, 5); // Cap at stage 5 for variety if (!specialZombiesUnlocked) { // First 100 seconds: only basic zombies, tough zombies, and runners if (zombieRoll < 0.6) { // 60% Basic zombie zombie = new Zombie(); } else if (zombieRoll < 0.8) { // 20% Tough zombie zombie = new ToughZombie(); } else { // 20% Runner zombie zombie = new RunnerZombie(); } } else { // After 100 seconds: all zombie types available if (zombieRoll < 0.35) { // 35% Basic zombie zombie = new Zombie(); } else if (zombieRoll < 0.48) { // 13% Tough zombie zombie = new ToughZombie(); } else if (zombieRoll < 0.61) { // 13% Runner zombie zombie = new RunnerZombie(); } else if (zombieRoll < 0.72) { // 11% Umbrella zombie zombie = new UmbrellaZombie(); } else if (zombieRoll < 0.81) { // 9% Crows zombie zombie = new CrowsZombie(); } else if (zombieRoll < 0.88) { // 7% Cardboard zombie zombie = new CardboardZombie(); } else if (zombieRoll < 0.94) { // 6% Box zombie zombie = new BoxZombie(); } else { // 6% Football zombie zombie = new FootballZombie(); } } if (!isEasyPeriod) { zombie.health = Math.floor(zombie.health * difficultyMultiplier); zombie.damage = Math.floor(zombie.damage * difficultyMultiplier); if (zombie.speed) { zombie.speed *= Math.min(difficultyMultiplier * 0.3 + 0.7, 2); } } } // Spawn only from top of screen zombie.x = Math.random() * 2048; zombie.y = 150; zombies.push(zombie); game.addChild(zombie); } function onBossDefeated() { bossActive = false; bossProgress = 0; currentStage++; difficultyMultiplier += 0.5; // Switch day/night cycle isDaytime = !isDaytime; dayText.setText(isDaytime ? 'Day' : 'Night'); dayText.tint = isDaytime ? 0xFFD700 : 0x4169E1; game.setBackgroundColor(isDaytime ? 0x87CEEB : 0x191970); // Update solar collectors for (var i = 0; i < solarCollectors.length; i++) { solarCollectors[i].isDaytime = isDaytime; } // Flash screen effect for stage transition LK.effects.flashScreen(isDaytime ? 0xFFD700 : 0x191970, 1000); // Increase boss progress requirement by 25 seconds (1500 frames at 60fps) bossProgressMax += 84; // 25 seconds * 60fps * 0.056 increment = ~84 progress units } game.update = function () { // Track total game time and unlock special zombies after 100 seconds gameTimer++; if (!specialZombiesUnlocked && gameTimer >= 6000) { // 100 seconds * 60 fps = 6000 frames specialZombiesUnlocked = true; // Flash screen to indicate special zombies are now available LK.effects.flashScreen(0x800080, 500); // Purple flash } // Spawn buried zombies randomly on the map if (specialZombiesUnlocked && Math.random() < 0.001) { // 0.1% chance per frame var buriedZombie = new BuriedZombie(); // Spawn anywhere on the map but not too close to house var minDistanceFromHouse = 300; var validPosition = false; var attempts = 0; while (!validPosition && attempts < 10) { buriedZombie.x = Math.random() * 1800 + 124; // Keep away from edges buriedZombie.y = Math.random() * 1800 + 400; // Keep away from top and house // Check distance from house var distanceFromHouse = Math.sqrt(Math.pow(buriedZombie.x - 1024, 2) + Math.pow(buriedZombie.y - 2500, 2)); if (distanceFromHouse >= minDistanceFromHouse && buriedZombie.y < 2200) { validPosition = true; } attempts++; } if (validPosition) { zombies.push(buriedZombie); game.addChild(buriedZombie); } } // Handle easy period for new players if (isEasyPeriod) { easyPeriodTimer++; if (easyPeriodTimer >= easyPeriodDuration) { isEasyPeriod = false; } } // Update progress bar based on time and zombie kills if (!bossActive) { var progressIncrease = isEasyPeriod ? 0.028 : 0.056; // Reduced to make 300 seconds initially bossProgress += progressIncrease; // Check if boss should spawn if (bossProgress >= bossProgressMax) { spawnZombie(true); } } // Update progress bar visual var progressPercent = Math.min(bossProgress / bossProgressMax, 1); progressBarFill.width = 400 * progressPercent; // Change progress bar color based on proximity to boss if (progressPercent > 0.8) { progressBarFill.tint = 0xff0000; // Red when close to boss } else if (progressPercent > 0.6) { progressBarFill.tint = 0xffaa00; // Orange } else { progressBarFill.tint = 0xff6600; // Default orange } // Animate progress bar when close to boss spawn if (progressPercent > 0.9 && !bossActive) { var pulseScale = 1 + Math.sin(LK.ticks * 0.3) * 0.1; progressBarFill.scaleY = pulseScale; progressBarBg.scaleY = pulseScale; } else { progressBarFill.scaleY = 1; progressBarBg.scaleY = 1; } // Day/night cycle is now only controlled by boss defeats // Remove automatic timer-based day/night switching // Wave-based zombie spawning with limited numbers per wave if (zombiesToSpawn <= 0 && !bossActive) { waveTimer++; // Random delay between waves (10-25 seconds based on difficulty) var minWaveDelay = isEasyPeriod ? 600 : Math.max(600, 900 - Math.floor(difficultyMultiplier * 60)); // 10-15 seconds var maxWaveDelay = isEasyPeriod ? 1500 : Math.max(900, 1500 - Math.floor(difficultyMultiplier * 30)); // 15-25 seconds var randomWaveDelay = Math.floor(Math.random() * (maxWaveDelay - minWaveDelay + 1)) + minWaveDelay; if (waveTimer >= randomWaveDelay) { waveTimer = 0; wave++; // Spawn 3-4 zombies per wave depending on difficulty var baseZombies = isEasyPeriod ? 3 : 3; var maxZombies = isEasyPeriod ? 3 : 4; // Increase chance of 4 zombies as difficulty rises var zombieCount = baseZombies; if (!isEasyPeriod && Math.random() < (difficultyMultiplier - 1) * 0.3) { zombieCount = maxZombies; } zombiesPerWave = zombieCount; zombiesToSpawn = zombiesPerWave; } } else if (zombiesToSpawn > 0) { zombieSpawnTimer++; // Spawn zombies quickly within a wave (every 0.5-1 second) var spawnDelay = isEasyPeriod ? 60 : Math.max(30, 60 - Math.floor(difficultyMultiplier * 5)); if (zombieSpawnTimer >= spawnDelay) { zombieSpawnTimer = 0; zombiesToSpawn--; spawnZombie(false); } } for (var i = solarCollectors.length - 1; i >= 0; i--) { if (solarCollectors[i].destroyed) { solarCollectors.splice(i, 1); } } for (var i = towers.length - 1; i >= 0; i--) { if (towers[i].destroyed) { towers.splice(i, 1); } } for (var i = zombies.length - 1; i >= 0; i--) { if (zombies[i].destroyed) { // Check if destroyed zombie was a boss if ((zombies[i] instanceof ShamanZombie || zombies[i] instanceof NinjaZombie || zombies[i] instanceof CrazyZombie || zombies[i] instanceof SoldierZombie) && bossActive) { // Boss zombies detected by type onBossDefeated(); } else if (!bossActive) { // Regular zombie kill increases boss progress slightly bossProgress += 0.1; // Reduced to match slower progress bar } zombies.splice(i, 1); } } for (var i = houses.length - 1; i >= 0; i--) { if (houses[i].destroyed) { houses.splice(i, 1); } } for (var i = walls.length - 1; i >= 0; i--) { if (walls[i].destroyed) { walls.splice(i, 1); } } for (var i = attractors.length - 1; i >= 0; i--) { if (attractors[i].destroyed) { attractors.splice(i, 1); } } energyText.setText('Energy: ' + energy); waveText.setText('Wave: ' + wave); livesText.setText('Lives: ' + lives); stageText.setText('Stage: ' + currentStage); // Update progress text based on state if (bossActive) { progressText.setText('BOSS ACTIVE!'); progressText.tint = 0xff0000; var pulseAlpha = 0.5 + Math.sin(LK.ticks * 0.5) * 0.5; progressText.alpha = pulseAlpha; } else if (isEasyPeriod) { progressText.setText('Preparation Time'); progressText.tint = 0x00ff00; progressText.alpha = 1; } else { progressText.setText('Boss Progress: ' + Math.floor(progressPercent * 100) + '%'); progressText.tint = 0xFFFFFF; progressText.alpha = 1; } if (lives <= 0) { LK.showGameOver(); } }; LK.playMusic('gameMusic');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AcidTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('acidTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.range = 140;
self.fireRate = 45;
self.fireTimer = 0;
self.health = 100;
self.maxHealth = 100;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.acidAttack();
}
};
self.acidAttack = function () {
LK.getSound('laser').play();
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.range) {
// Apply acid effect (doesn't remove freezing)
zombies[i].acidDuration = 240; // 4 seconds of acid
zombies[i].acidDamage = 2; // Smaller damage per tick
}
}
var acidEffect = self.attachAsset('acidEffect', {
anchorX: 0.5,
anchorY: 0.5
});
acidEffect.width = self.range * 2;
acidEffect.height = self.range * 2;
acidEffect.alpha = 0.6;
tween(acidEffect, {
alpha: 0,
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 600,
onFinish: function onFinish() {
acidEffect.destroy();
}
});
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var Attractor = Container.expand(function () {
var self = Container.call(this);
var attractorGraphics = self.attachAsset('attractor', {
anchorX: 0.5,
anchorY: 0.5
});
self.attractionRange = 300;
self.health = 80;
self.maxHealth = 80;
self.attractionTimer = 0;
// Pulse animation for attractor
self.update = function () {
self.attractionTimer++;
// Create pulsing effect
var pulseScale = 1 + Math.sin(self.attractionTimer * 0.15) * 0.3;
attractorGraphics.scaleX = pulseScale;
attractorGraphics.scaleY = pulseScale;
// Attract zombies in range
if (self.attractionTimer % 30 === 0) {
self.attractZombies();
}
};
self.attractZombies = function () {
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = self.x - zombie.x;
var dy = self.y - zombie.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.attractionRange && dist > 30) {
// Override zombie's current target to move toward attractor
var attractForce = 0.3;
zombie.x += dx / dist * attractForce;
zombie.y += dy / dist * attractForce;
// Visual effect for attraction
if (Math.random() < 0.1) {
var sparkle = game.addChild(LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
}));
sparkle.x = zombie.x + (Math.random() - 0.5) * 20;
sparkle.y = zombie.y + (Math.random() - 0.5) * 20;
sparkle.tint = 0xff69b4;
sparkle.width = 4;
sparkle.height = 4;
tween(sparkle, {
x: self.x,
y: self.y,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
sparkle.destroy();
}
});
}
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
// Find and mark grid cell as unoccupied when attractor is destroyed
var gridX = Math.floor((self.x - GRID_SIZE / 2) / GRID_SIZE);
var gridY = Math.floor((self.y - GRID_SIZE / 2 - 200) / GRID_SIZE);
if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS) {
var cellIndex = gridY * GRID_COLS + gridX;
if (cellIndex < gridCells.length) {
gridCells[cellIndex].occupied = false;
}
}
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var AutoTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('autoTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 8;
self.range = 400;
self.fireRate = 40;
self.fireTimer = 0;
self.health = 140;
self.maxHealth = 140;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
}
if (target) {
// Aim turret at target
var dx = target.x - self.x;
var dy = target.y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
self.children[0].rotation = angle;
self.shootHomingBullet();
} else {
// No target in range, return to default direction (facing up)
self.children[0].rotation = 0;
}
};
self.shootHomingBullet = function () {
LK.getSound('laser').play();
var bullet = new HomingBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.damage = self.damage;
game.addChild(bullet);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var BallTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('ballTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 40;
self.range = 250;
self.fireRate = 75;
self.fireTimer = 0;
self.health = 130;
self.maxHealth = 130;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
}
if (target) {
self.shootEnergyBall(target);
}
};
self.shootEnergyBall = function (target) {
LK.getSound('laser').play();
var ball = new EnergyBall();
ball.x = self.x;
ball.y = self.y;
ball.targetX = target.x;
ball.targetY = target.y;
ball.damage = self.damage;
ball.bounces = 3;
game.addChild(ball);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var BoxZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('boxZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.3;
self.health = 80;
self.damage = 15;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
// Handle burning effect
if (self.burnDuration > 0) {
self.burnDuration--;
if (self.burnDuration % 10 === 0) {
self.takeDamage(self.burnDamage || 3);
}
}
// Handle acid effect
if (self.acidDuration > 0) {
self.acidDuration--;
if (self.acidDuration % 15 === 0) {
self.takeDamage(self.acidDamage || 2);
}
}
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.15;
} else if (self.burnDuration > 0) {
zombieGraphics.tint = 0xFF4500;
self.speed = 0.3;
} else if (self.acidDuration > 0) {
zombieGraphics.tint = 0x32CD32;
self.speed = 0.3;
} else {
zombieGraphics.tint = 0x8b4513;
self.speed = 0.3;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
// Check for wall collisions before moving
var nextX = self.x + dx / dist * self.speed;
var nextY = self.y + dy / dist * self.speed;
var blocked = false;
// Check collision with walls
for (var w = 0; w < walls.length; w++) {
var wallDx = nextX - walls[w].x;
var wallDy = nextY - walls[w].y;
var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy);
if (wallDist < 80) {
self.target = walls[w];
blocked = true;
break;
}
}
if (!blocked) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
} else {
self.attackTimer++;
if (self.attackTimer >= 60) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 20);
LK.getSound('zombieDeath').play();
// Spawn three random zombies (excluding CrowZombie)
for (var i = 0; i < 3; i++) {
self.spawnRandomZombie();
}
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.spawnRandomZombie = function () {
var zombieTypes = [function () {
return new Zombie();
}, function () {
return new ToughZombie();
}, function () {
return new RunnerZombie();
}, function () {
return new UmbrellaZombie();
}, function () {
return new CrowsZombie();
}, function () {
return new TankZombie();
}];
var randomIndex = Math.floor(Math.random() * zombieTypes.length);
var newZombie = zombieTypes[randomIndex]();
newZombie.x = self.x + (Math.random() - 0.5) * 80;
newZombie.y = self.y + (Math.random() - 0.5) * 80;
zombies.push(newZombie);
game.addChild(newZombie);
};
self.freeze = function () {
self.frozen = 180;
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 25;
self.targetX = 0;
self.targetY = 0;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 5) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
// Hit target area, check for zombie collision
for (var i = 0; i < zombies.length; i++) {
var zdx = zombies[i].x - self.x;
var zdy = zombies[i].y - self.y;
var zdist = Math.sqrt(zdx * zdx + zdy * zdy);
if (zdist < 30) {
zombies[i].takeDamage(self.damage);
self.destroy();
return;
}
}
self.destroy();
}
};
return self;
});
var BuriedZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('buriedZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.8;
self.health = 40;
self.damage = 12;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.emergingTimer = 0;
self.isEmerging = true;
// Start buried (underground)
self.alpha = 0.3;
self.emergingTimer = 120; // 2 seconds to emerge
self.update = function () {
// Handle emerging from ground
if (self.isEmerging) {
self.emergingTimer--;
if (self.emergingTimer <= 0) {
self.isEmerging = false;
self.alpha = 1;
// Flash effect when emerging
LK.effects.flashObject(self, 0x8b7355, 300);
}
return;
}
// Handle burning effect
if (self.burnDuration > 0) {
self.burnDuration--;
if (self.burnDuration % 10 === 0) {
self.takeDamage(self.burnDamage || 3);
}
}
// Handle acid effect
if (self.acidDuration > 0) {
self.acidDuration--;
if (self.acidDuration % 15 === 0) {
self.takeDamage(self.acidDamage || 2);
}
}
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.4;
} else if (self.burnDuration > 0) {
zombieGraphics.tint = 0xFF4500;
self.speed = 0.8;
} else if (self.acidDuration > 0) {
zombieGraphics.tint = 0x32CD32;
self.speed = 0.8;
} else {
zombieGraphics.tint = 0x8b7355;
self.speed = 0.8;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
// Check for wall collisions before moving
var nextX = self.x + dx / dist * self.speed;
var nextY = self.y + dy / dist * self.speed;
var blocked = false;
// Check collision with walls
for (var w = 0; w < walls.length; w++) {
var wallDx = nextX - walls[w].x;
var wallDy = nextY - walls[w].y;
var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy);
if (wallDist < 80) {
self.target = walls[w];
blocked = true;
break;
}
}
if (!blocked) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
} else {
self.attackTimer++;
if (self.attackTimer >= 50) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
// Take reduced damage while emerging
if (self.isEmerging) {
damage = Math.floor(damage * 0.5);
}
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 14);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 150;
};
return self;
});
var CardboardZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('cardboardZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.5;
self.health = 35;
self.damage = 8;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
// Handle burning effect
if (self.burnDuration > 0) {
self.burnDuration--;
if (self.burnDuration % 10 === 0) {
self.takeDamage(self.burnDamage || 3);
}
}
// Handle acid effect
if (self.acidDuration > 0) {
self.acidDuration--;
if (self.acidDuration % 15 === 0) {
self.takeDamage(self.acidDamage || 2);
}
}
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.25;
} else if (self.burnDuration > 0) {
zombieGraphics.tint = 0xFF4500;
self.speed = 0.5;
} else if (self.acidDuration > 0) {
zombieGraphics.tint = 0x32CD32;
self.speed = 0.5;
} else {
zombieGraphics.tint = 0xd2b48c;
self.speed = 0.5;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
// Check for wall collisions before moving
var nextX = self.x + dx / dist * self.speed;
var nextY = self.y + dy / dist * self.speed;
var blocked = false;
// Check collision with walls
for (var w = 0; w < walls.length; w++) {
var wallDx = nextX - walls[w].x;
var wallDy = nextY - walls[w].y;
var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy);
if (wallDist < 80) {
self.target = walls[w];
blocked = true;
break;
}
}
if (!blocked) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
} else {
self.attackTimer++;
if (self.attackTimer >= 60) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 12);
LK.getSound('zombieDeath').play();
// Spawn a random zombie (excluding CrowZombie)
self.spawnRandomZombie();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.spawnRandomZombie = function () {
var zombieTypes = [function () {
return new Zombie();
}, function () {
return new ToughZombie();
}, function () {
return new RunnerZombie();
}, function () {
return new UmbrellaZombie();
}, function () {
return new CrowsZombie();
}, function () {
return new TankZombie();
}];
var randomIndex = Math.floor(Math.random() * zombieTypes.length);
var newZombie = zombieTypes[randomIndex]();
newZombie.x = self.x + (Math.random() - 0.5) * 40;
newZombie.y = self.y + (Math.random() - 0.5) * 40;
zombies.push(newZombie);
game.addChild(newZombie);
};
self.freeze = function () {
self.frozen = 180;
};
return self;
});
var ClassicTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('classicTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 25;
self.range = 250;
self.fireRate = 45;
self.fireTimer = 0;
self.health = 120;
self.maxHealth = 120;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
}
if (target) {
// Aim turret at target
var dx = target.x - self.x;
var dy = target.y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
self.children[0].rotation = angle;
self.shootBullet(target);
} else {
// No target in range, return to default direction (facing up)
self.children[0].rotation = 0;
}
};
self.shootBullet = function (target) {
LK.getSound('laser').play();
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
game.addChild(bullet);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var CrazyZombie = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('crazyZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.3;
self.health = 500;
self.damage = 25;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.digTimer = 0;
self.buffTimer = 0;
self.isUnderground = false;
self.update = function () {
// Handle burning effect
if (self.burnDuration > 0) {
self.burnDuration--;
if (self.burnDuration % 10 === 0) {
self.takeDamage(self.burnDamage || 3);
}
}
// Handle acid effect
if (self.acidDuration > 0) {
self.acidDuration--;
if (self.acidDuration % 15 === 0) {
self.takeDamage(self.acidDamage || 2);
}
}
if (self.frozen > 0) {
self.frozen--;
bossGraphics.tint = 0x87CEEB;
self.speed = 0.15;
} else if (self.burnDuration > 0) {
bossGraphics.tint = 0xFF4500;
self.speed = 0.3;
} else if (self.acidDuration > 0) {
bossGraphics.tint = 0x32CD32;
self.speed = 0.3;
} else {
bossGraphics.tint = 0x8b4513;
self.speed = 0.3;
}
// Dig underground ability
self.digTimer++;
if (self.digTimer >= 360) {
// Every 6 seconds
self.digTimer = 0;
self.digUnderground();
}
// Buff other zombies
self.buffTimer++;
if (self.buffTimer >= 180) {
// Every 3 seconds
self.buffTimer = 0;
self.buffOtherZombies();
}
if (!self.isUnderground) {
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 60) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 45) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives -= 4;
self.destroy();
}
}
}
};
self.digUnderground = function () {
self.isUnderground = true;
self.alpha = 0.3; // Make translucent
// Emerge near a random turret or house
var targets = towers.concat(houses);
if (targets.length > 0) {
var randomTarget = targets[Math.floor(Math.random() * targets.length)];
tween(self, {
x: randomTarget.x + (Math.random() - 0.5) * 100,
y: randomTarget.y + (Math.random() - 0.5) * 100
}, {
duration: 1000,
onFinish: function onFinish() {
self.isUnderground = false;
self.alpha = 1;
LK.effects.flashObject(self, 0x8b4513, 300);
}
});
}
};
self.buffOtherZombies = function () {
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie !== self) {
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 200) {
// Buff range
zombie.speed *= 1.2; // Increase speed
zombie.damage = Math.floor(zombie.damage * 1.1); // Increase damage
LK.effects.flashObject(zombie, 0xFFD700, 200);
}
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
if (self.isUnderground) {
damage = Math.floor(damage * 0.5); // Take less damage underground
}
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 200);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 120;
};
return self;
});
var CrowZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('crowZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.8;
self.health = 15;
self.damage = 5;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.9;
} else {
zombieGraphics.tint = 0xFFFFFF;
self.speed = 1.8;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 30) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 45) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 5);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 120;
};
return self;
});
var CrowsZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('crowsZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.7;
self.health = 60;
self.damage = 10;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.35;
} else {
zombieGraphics.tint = 0xFFFFFF;
self.speed = 0.7;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 60) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 15);
LK.getSound('zombieDeath').play();
// Spawn 3 crow zombies
for (var i = 0; i < 3; i++) {
var crow = new CrowZombie();
crow.x = self.x + (Math.random() - 0.5) * 60;
crow.y = self.y + (Math.random() - 0.5) * 60;
zombies.push(crow);
game.addChild(crow);
}
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 180;
};
return self;
});
var DoubleBarrelTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('doubleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 20;
self.range = 280;
self.fireRate = 35;
self.fireTimer = 0;
self.health = 140;
self.maxHealth = 140;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var targets = [];
// Find all zombies in range and sort by distance
var zombiesInRange = [];
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
zombiesInRange.push({
zombie: zombies[i],
distance: dist
});
}
}
// Sort by distance and take closest 2
zombiesInRange.sort(function (a, b) {
return a.distance - b.distance;
});
for (var i = 0; i < Math.min(2, zombiesInRange.length); i++) {
targets.push(zombiesInRange[i].zombie);
}
if (targets.length > 0) {
// Aim at closest target
var dx = targets[0].x - self.x;
var dy = targets[0].y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
self.children[0].rotation = angle;
// Shoot two bullets
for (var i = 0; i < targets.length; i++) {
self.shootBullet(targets[i]);
}
} else {
// No targets in range, return to default direction (facing up)
self.children[0].rotation = 0;
}
};
self.shootBullet = function (target) {
LK.getSound('laser').play();
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
game.addChild(bullet);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var ElectricalTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('electricalTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 35;
self.range = 200;
self.fireRate = 60;
self.fireTimer = 0;
self.health = 120;
self.maxHealth = 120;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
}
if (target) {
self.shootElectricalBolt(target);
}
};
self.shootElectricalBolt = function (target) {
LK.getSound('laser').play();
// Create electrical bolt effect
var bolt = self.attachAsset('electricalBolt', {
anchorX: 0.5,
anchorY: 1
});
var dx = target.x - self.x;
var dy = target.y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
bolt.rotation = angle;
var dist = Math.sqrt(dx * dx + dy * dy);
bolt.height = dist;
bolt.alpha = 1;
tween(bolt, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
bolt.destroy();
}
});
// Damage primary target
target.takeDamage(self.damage);
// Chain to nearby zombies
self.chainLightning(target, 2, self.damage * 0.7);
};
self.chainLightning = function (sourceZombie, remainingChains, chainDamage) {
if (remainingChains <= 0) return;
var chainRange = 120;
var nextTarget = null;
var minDist = chainRange;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === sourceZombie) continue;
var dx = zombies[i].x - sourceZombie.x;
var dy = zombies[i].y - sourceZombie.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= chainRange && dist < minDist) {
minDist = dist;
nextTarget = zombies[i];
}
}
if (nextTarget) {
// Create chain bolt visual
var chainBolt = game.addChild(LK.getAsset('electricalBolt', {
anchorX: 0.5,
anchorY: 1
}));
chainBolt.x = sourceZombie.x;
chainBolt.y = sourceZombie.y;
var dx = nextTarget.x - sourceZombie.x;
var dy = nextTarget.y - sourceZombie.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
chainBolt.rotation = angle;
var dist = Math.sqrt(dx * dx + dy * dy);
chainBolt.height = dist;
chainBolt.width = 4;
chainBolt.tint = 0x00ffff;
tween(chainBolt, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
chainBolt.destroy();
}
});
// Damage chain target
nextTarget.takeDamage(Math.floor(chainDamage));
// Continue chain
self.chainLightning(nextTarget, remainingChains - 1, chainDamage * 0.8);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var EnergyBall = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('energyBall', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 7;
self.damage = 40;
self.targetX = 0;
self.targetY = 0;
self.bounces = 3;
self.hasHitTarget = false;
self.update = function () {
if (!self.hasHitTarget) {
// Move toward initial target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 10) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
// Rotate ball for visual effect
ballGraphics.rotation += 0.2;
} else {
self.hasHitTarget = true;
self.findNextTarget();
}
} else {
// Ball is bouncing between targets
if (self.targetX !== 0 && self.targetY !== 0) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 15) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
ballGraphics.rotation += 0.2;
} else {
self.findNextTarget();
}
} else {
// No more targets, destroy ball
self.destroy();
}
}
// Check for zombie collision
for (var i = 0; i < zombies.length; i++) {
var zdx = zombies[i].x - self.x;
var zdy = zombies[i].y - self.y;
var zdist = Math.sqrt(zdx * zdx + zdy * zdy);
if (zdist < 25) {
zombies[i].takeDamage(self.damage);
self.findNextTarget();
break;
}
}
};
self.findNextTarget = function () {
if (self.bounces <= 0) {
self.destroy();
return;
}
self.bounces--;
var bounceRange = 150;
var nextTarget = null;
var minDist = bounceRange;
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= bounceRange && dist < minDist) {
minDist = dist;
nextTarget = zombies[i];
}
}
if (nextTarget) {
self.targetX = nextTarget.x;
self.targetY = nextTarget.y;
// Flash effect when bouncing
tween(ballGraphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(ballGraphics, {
scaleX: 1,
scaleY: 1,
tint: 0x9370db
}, {
duration: 100
});
}
});
} else {
self.targetX = 0;
self.targetY = 0;
}
};
return self;
});
var ExplosiveTower = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('explosiveTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 75;
self.range = 280;
self.explosionRange = 100;
self.fireRate = 90;
self.fireTimer = 0;
self.health = 120;
self.maxHealth = 120;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndExplode();
}
};
self.findAndExplode = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
}
if (target) {
self.explode(target.x, target.y);
}
};
self.explode = function (x, y) {
LK.getSound('explode').play();
var explosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
}));
explosion.x = x;
explosion.y = y;
explosion.alpha = 0.8;
tween(explosion, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
explosion.destroy();
}
});
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - x;
var dy = zombies[i].y - y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.explosionRange) {
zombies[i].takeDamage(self.damage);
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var FakeZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('fakeZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.8;
self.health = 10;
self.damage = 3;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.lifeTimer = 0;
self.maxLife = 600; // 10 seconds
self.update = function () {
self.lifeTimer++;
if (self.lifeTimer >= self.maxLife) {
// Fade away after 10 seconds
tween(self, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
return;
}
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.4;
} else {
zombieGraphics.tint = 0xdda0dd;
self.speed = 0.8;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 30) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 60) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 2);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 60;
};
return self;
});
var FlameThrowerTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('flameThrowerTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.range = 150;
self.fireRate = 30;
self.fireTimer = 0;
self.health = 110;
self.maxHealth = 110;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.flameAttack();
}
};
self.flameAttack = function () {
LK.getSound('flame').play();
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.range) {
// Remove freezing effect if zombie is frozen
if (zombies[i].frozen > 0) {
zombies[i].frozen = 0;
}
// Apply burning effect
zombies[i].burnDuration = 180; // 3 seconds of burning
zombies[i].burnDamage = 3; // Damage per tick
}
}
var flameEffect = self.attachAsset('fireEffect', {
anchorX: 0.5,
anchorY: 0.5
});
flameEffect.width = self.range * 2;
flameEffect.height = self.range * 2;
flameEffect.alpha = 0.7;
tween(flameEffect, {
alpha: 0,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 400,
onFinish: function onFinish() {
flameEffect.destroy();
}
});
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var FootballZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('footballZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.6;
self.health = 150;
self.damage = 20;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
// Handle burning effect
if (self.burnDuration > 0) {
self.burnDuration--;
if (self.burnDuration % 10 === 0) {
self.takeDamage(self.burnDamage || 3);
}
}
// Handle acid effect
if (self.acidDuration > 0) {
self.acidDuration--;
if (self.acidDuration % 15 === 0) {
self.takeDamage(self.acidDamage || 2);
}
}
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.3;
} else if (self.burnDuration > 0) {
zombieGraphics.tint = 0xFF4500;
self.speed = 0.6;
} else if (self.acidDuration > 0) {
zombieGraphics.tint = 0x32CD32;
self.speed = 0.6;
} else {
zombieGraphics.tint = 0x2e8b57;
self.speed = 0.6;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
// Check for wall collisions before moving
var nextX = self.x + dx / dist * self.speed;
var nextY = self.y + dy / dist * self.speed;
var blocked = false;
// Check collision with walls
for (var w = 0; w < walls.length; w++) {
var wallDx = nextX - walls[w].x;
var wallDy = nextY - walls[w].y;
var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy);
if (wallDist < 80) {
self.target = walls[w];
blocked = true;
break;
}
}
if (!blocked) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
} else {
self.attackTimer++;
if (self.attackTimer >= 45) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives -= 2;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 25);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 120;
};
return self;
});
var FreezeTower = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('freezeTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.range = 150;
self.fireRate = 120;
self.fireTimer = 0;
self.health = 100;
self.maxHealth = 100;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.freezeArea();
}
};
self.freezeArea = function () {
LK.getSound('freeze').play();
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.range) {
zombies[i].freeze();
}
}
var freezeEffect = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
freezeEffect.tint = 0x00BFFF;
freezeEffect.alpha = 0.5;
freezeEffect.width = self.range * 2;
freezeEffect.height = self.range * 2;
tween(freezeEffect, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
onFinish: function onFinish() {
freezeEffect.destroy();
}
});
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var FrozenTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('frozenTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.range = 200;
self.fireRate = 90;
self.fireTimer = 0;
self.health = 120;
self.maxHealth = 120;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.freezeArea();
}
};
self.freezeArea = function () {
LK.getSound('freeze').play();
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.range) {
zombies[i].freeze();
}
}
var freezeEffect = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
freezeEffect.tint = 0x00FFFF;
freezeEffect.alpha = 0.5;
freezeEffect.width = self.range * 2;
freezeEffect.height = self.range * 2;
tween(freezeEffect, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
onFinish: function onFinish() {
freezeEffect.destroy();
}
});
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var HomingBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('homingBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.damage = 8;
self.target = null;
self.turnSpeed = 0.15;
self.update = function () {
// Find closest target if we don't have one or target is destroyed
if (!self.target || self.target.destroyed || self.target.health <= 0) {
self.findTarget();
}
if (self.target) {
// Move towards target with homing behavior
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 10) {
// Normalize direction and move
var dirX = dx / dist;
var dirY = dy / dist;
self.x += dirX * self.speed;
self.y += dirY * self.speed;
// Rotate bullet to face movement direction
var angle = Math.atan2(dy, dx);
bulletGraphics.rotation = angle;
} else {
// Hit target
self.target.takeDamage(self.damage);
self.destroy();
}
} else {
// No target, move forward
self.y += self.speed;
if (self.y > 2732) {
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = 400; // Max homing range
self.target = null;
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = zombies[i];
}
}
};
return self;
});
var House = Container.expand(function () {
var self = Container.call(this);
var houseGraphics = self.attachAsset('house', {
anchorX: 0.5,
anchorY: 0.5
});
// Scale house to span the width of the screen
houseGraphics.width = 2048; // Full screen width
houseGraphics.height = 100; // Keep original height
self.health = 500; // Increased health since it's the only house
self.maxHealth = 500;
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
lives -= 10; // Lose more lives when the main house is destroyed
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var LaserTower = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('laserTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 30;
self.range = 280;
self.fireRate = 30;
self.fireTimer = 0;
self.health = 150;
self.maxHealth = 150;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
}
if (target) {
self.shootLaser(target);
}
};
self.shootLaser = function (target) {
LK.getSound('laser').play();
var laser = self.attachAsset('laserBeam', {
anchorX: 0.5,
anchorY: 1
});
var dx = target.x - self.x;
var dy = target.y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
laser.rotation = angle;
var dist = Math.sqrt(dx * dx + dy * dy);
laser.height = dist;
tween(laser, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
laser.destroy();
}
});
target.takeDamage(self.damage);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var MinigunTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('minigunTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 12;
self.range = 220;
self.fireRate = 10; // Very fast firing
self.fireTimer = 0;
self.health = 130;
self.maxHealth = 130;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
}
if (target) {
// Aim turret at target
var dx = target.x - self.x;
var dy = target.y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
self.children[0].rotation = angle;
self.shootBullet(target);
} else {
// No target in range, return to default direction (facing up)
self.children[0].rotation = 0;
}
};
self.shootBullet = function (target) {
LK.getSound('minigun').play();
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
game.addChild(bullet);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var NinjaZombie = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('ninjaZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.4;
self.health = 350;
self.damage = 30;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.teleportTimer = 0;
self.cloneTimer = 0;
self.canClone = true;
self.update = function () {
// Handle burning effect
if (self.burnDuration > 0) {
self.burnDuration--;
if (self.burnDuration % 10 === 0) {
self.takeDamage(self.burnDamage || 3);
}
}
// Handle acid effect
if (self.acidDuration > 0) {
self.acidDuration--;
if (self.acidDuration % 15 === 0) {
self.takeDamage(self.acidDamage || 2);
}
}
if (self.frozen > 0) {
self.frozen--;
bossGraphics.tint = 0x87CEEB;
self.speed = 0.2;
} else if (self.burnDuration > 0) {
bossGraphics.tint = 0xFF4500;
self.speed = 0.4;
} else if (self.acidDuration > 0) {
bossGraphics.tint = 0x32CD32;
self.speed = 0.4;
} else {
bossGraphics.tint = 0x2f2f2f;
self.speed = 0.4;
}
// Teleport ability
self.teleportTimer++;
if (self.teleportTimer >= 240) {
// Every 4 seconds
self.teleportTimer = 0;
self.teleport();
}
// Clone ability when health is low
if (self.health < 175 && self.canClone) {
self.canClone = false;
self.createClone();
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 60) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 30) {
// Faster attacks
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives -= 3;
self.destroy();
}
}
};
self.teleport = function () {
// Teleport to random location
self.x = Math.random() * 2048;
self.y = Math.random() * 1000 + 200;
LK.effects.flashObject(self, 0x8A2BE2, 300);
tween(self, {
alpha: 0.5
}, {
duration: 100,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 100
});
}
});
};
self.createClone = function () {
var clone = new NinjaZombie();
clone.x = self.x + (Math.random() - 0.5) * 200;
clone.y = self.y + (Math.random() - 0.5) * 200;
clone.health = 100; // Clone has less health
clone.canClone = false; // Clones can't clone
zombies.push(clone);
game.addChild(clone);
LK.effects.flashObject(self, 0x800080, 500);
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 150);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 120;
};
return self;
});
var Rocket = Container.expand(function () {
var self = Container.call(this);
var rocketGraphics = self.attachAsset('rocket', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 10;
self.damage = 80;
self.explosionRange = 120;
self.targetX = 0;
self.targetY = 0;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 8) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
// Rotate rocket to face movement direction
var angle = Math.atan2(dy, dx) + Math.PI / 2;
rocketGraphics.rotation = angle;
} else {
// Explode at target location
self.explode();
}
};
self.explode = function () {
LK.getSound('explode').play();
var explosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
}));
explosion.x = self.x;
explosion.y = self.y;
explosion.alpha = 0.9;
explosion.width = self.explosionRange * 2;
explosion.height = self.explosionRange * 2;
tween(explosion, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 400,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Damage all zombies in explosion range
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.explosionRange) {
zombies[i].takeDamage(self.damage);
}
}
self.destroy();
};
return self;
});
var RocketTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('rocketTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 80;
self.range = 300;
self.explosionRange = 120;
self.fireRate = 120; // Slow but powerful
self.fireTimer = 0;
self.health = 150;
self.maxHealth = 150;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
}
if (target) {
// Aim turret at target
var dx = target.x - self.x;
var dy = target.y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
self.children[0].rotation = angle;
self.shootRocket(target);
} else {
// No target in range, return to default direction (facing up)
self.children[0].rotation = 0;
}
};
self.shootRocket = function (target) {
LK.getSound('rocket').play();
var rocket = new Rocket();
rocket.x = self.x;
rocket.y = self.y;
rocket.targetX = target.x;
rocket.targetY = target.y;
rocket.damage = self.damage;
rocket.explosionRange = self.explosionRange;
game.addChild(rocket);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var RunnerZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('runnerZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.4;
self.health = 30;
self.damage = 8;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.7;
} else {
zombieGraphics.tint = 0xFFFFFF;
self.speed = 1.4;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 60) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 8);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 180;
};
return self;
});
var ShamanZombie = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('shamanZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.25;
self.health = 400;
self.damage = 20;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.summonTimer = 0;
self.healTimer = 0;
self.lowHealthHealed = false;
self.update = function () {
// Handle burning effect
if (self.burnDuration > 0) {
self.burnDuration--;
if (self.burnDuration % 10 === 0) {
self.takeDamage(self.burnDamage || 3);
}
}
// Handle acid effect
if (self.acidDuration > 0) {
self.acidDuration--;
if (self.acidDuration % 15 === 0) {
self.takeDamage(self.acidDamage || 2);
}
}
if (self.frozen > 0) {
self.frozen--;
bossGraphics.tint = 0x87CEEB;
self.speed = 0.125;
} else if (self.burnDuration > 0) {
bossGraphics.tint = 0xFF4500;
self.speed = 0.25;
} else if (self.acidDuration > 0) {
bossGraphics.tint = 0x32CD32;
self.speed = 0.25;
} else {
bossGraphics.tint = 0x800080;
self.speed = 0.25;
}
// Healing when low on health
if (self.health < 100 && !self.lowHealthHealed) {
self.healTimer++;
if (self.healTimer >= 120) {
// 2 seconds
self.health += 150;
self.lowHealthHealed = true;
LK.effects.flashObject(self, 0x00FF00, 500);
}
}
// Summon fake zombies periodically
self.summonTimer++;
if (self.summonTimer >= 300) {
// Every 5 seconds
self.summonTimer = 0;
self.summonFakeZombies();
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 60) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 45) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives -= 3;
self.destroy();
}
}
};
self.summonFakeZombies = function () {
for (var i = 0; i < 2; i++) {
var fakeZombie = new FakeZombie();
fakeZombie.x = self.x + (Math.random() - 0.5) * 100;
fakeZombie.y = self.y + (Math.random() - 0.5) * 100;
zombies.push(fakeZombie);
game.addChild(fakeZombie);
}
LK.effects.flashObject(self, 0x800080, 300);
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
// Prioritize houses first
for (var i = 0; i < houses.length; i++) {
// Create a virtual target point spread across the house width
var houseLeft = houses[i].x - 1024; // Left edge of house
var houseRight = houses[i].x + 1024; // Right edge of house
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); // Clamp zombie x to house width
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
// Skip solar collectors and towers - they are not targeted by boss zombies
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 150);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 120;
};
return self;
});
var SolarCollector = Container.expand(function () {
var self = Container.call(this);
var collector = self.attachAsset('solarCollector', {
anchorX: 0.5,
anchorY: 0.5
});
self.energyRate = 1;
self.health = 100;
self.maxHealth = 100;
self.isDaytime = true;
self.energyTimer = 0;
self.energyGenerationTime = Math.floor(Math.random() * 300) + 600; // Random 10-15 seconds (600-900 frames at 60fps)
self.rotationTimer = 0;
self.rotationInterval = Math.floor(Math.random() * 600) + 300; // Random 5-15 seconds for rotation
self.update = function () {
self.energyTimer++;
if (self.energyTimer >= self.energyGenerationTime) {
self.energyTimer = 0;
// Set new random interval for next generation
self.energyGenerationTime = Math.floor(Math.random() * 300) + 600; // Random 10-15 seconds
var energyGain = 15;
energy += energyGain;
LK.getSound('collect').play();
var energyText = new Text2('+' + energyGain, {
size: 30,
fill: 0xFFD700
});
energyText.anchor.set(0.5, 0.5);
energyText.x = 0;
energyText.y = -40;
self.addChild(energyText);
tween(energyText, {
y: -80,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
energyText.destroy();
}
});
}
// Handle random rotation
self.rotationTimer++;
if (self.rotationTimer >= self.rotationInterval) {
self.rotationTimer = 0;
// Set new random interval for next rotation
self.rotationInterval = Math.floor(Math.random() * 600) + 300; // Random 5-15 seconds
// Rotate to a random angle with smooth animation
var targetRotation = Math.random() * Math.PI * 2; // Random angle between 0 and 2π
tween(collector, {
rotation: targetRotation
}, {
duration: 800,
easing: tween.easeInOut
});
}
collector.tint = self.isDaytime ? 0xFFD700 : 0xB8860B;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var SoldierZombie = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('soldierZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.35;
self.health = 300;
self.damage = 22;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.groupSpawned = false;
self.update = function () {
// Handle burning effect
if (self.burnDuration > 0) {
self.burnDuration--;
if (self.burnDuration % 10 === 0) {
self.takeDamage(self.burnDamage || 3);
}
}
// Handle acid effect
if (self.acidDuration > 0) {
self.acidDuration--;
if (self.acidDuration % 15 === 0) {
self.takeDamage(self.acidDamage || 2);
}
}
if (self.frozen > 0) {
self.frozen--;
bossGraphics.tint = 0x87CEEB;
self.speed = 0.175;
} else if (self.burnDuration > 0) {
bossGraphics.tint = 0xFF4500;
self.speed = 0.35;
} else if (self.acidDuration > 0) {
bossGraphics.tint = 0x32CD32;
self.speed = 0.35;
} else {
bossGraphics.tint = 0x556b2f;
self.speed = 0.35;
}
// Spawn group when health gets low
if (self.health < 150 && !self.groupSpawned) {
self.groupSpawned = true;
self.spawnGroup();
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 60) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 45) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives -= 3;
self.destroy();
}
}
};
self.spawnGroup = function () {
// Spawn 4 regular zombies
for (var i = 0; i < 4; i++) {
var soldier = new Zombie();
soldier.x = self.x + (Math.random() - 0.5) * 150;
soldier.y = self.y + (Math.random() - 0.5) * 150;
soldier.health = 40; // Slightly stronger
soldier.damage = 12;
zombies.push(soldier);
game.addChild(soldier);
}
// Spawn 2 tank zombies
for (var i = 0; i < 2; i++) {
var tank = new TankZombie();
tank.x = self.x + (Math.random() - 0.5) * 200;
tank.y = self.y + (Math.random() - 0.5) * 200;
zombies.push(tank);
game.addChild(tank);
}
LK.effects.flashObject(self, 0x556b2f, 500);
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 150);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 120;
};
return self;
});
var SpikeTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('spikeTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 50;
self.range = 180;
self.fireRate = 90;
self.fireTimer = 0;
self.health = 100;
self.maxHealth = 100;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndSpike();
}
};
self.findAndSpike = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
}
if (target) {
self.createSpikes(target);
}
};
self.createSpikes = function (target) {
LK.getSound('build').play();
// Create multiple spikes around target area
for (var i = 0; i < 5; i++) {
var spike = game.addChild(LK.getAsset('spike', {
anchorX: 0.5,
anchorY: 1
}));
spike.x = target.x + (Math.random() - 0.5) * 80;
spike.y = target.y + (Math.random() - 0.5) * 40;
spike.scaleY = 0;
spike.alpha = 0.8;
spike.tint = 0x8b4513;
// Animate spikes emerging from ground
tween(spike, {
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Check for zombie collision after spike emerges
for (var j = 0; j < zombies.length; j++) {
var dx = zombies[j].x - spike.x;
var dy = zombies[j].y - spike.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 40) {
zombies[j].takeDamage(self.damage);
}
}
// Retract spikes after 1 second
tween(spike, {
scaleY: 0,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
spike.destroy();
}
});
}
});
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var TankZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('tankZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.2;
self.health = 100;
self.damage = 18;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.1;
} else {
zombieGraphics.tint = 0x696969;
self.speed = 0.2;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 60) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 20);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 120;
};
return self;
});
var ToughZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('toughZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.4;
self.health = 120;
self.damage = 15;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.2;
} else {
zombieGraphics.tint = 0xFFFFFF;
self.speed = 0.4;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 60) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 15);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 180;
};
return self;
});
var TripleBarrelTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('tripleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 15;
self.range = 300;
self.fireRate = 30;
self.fireTimer = 0;
self.health = 160;
self.maxHealth = 160;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var targets = [];
// Find all zombies in range and sort by distance
var zombiesInRange = [];
for (var i = 0; i < zombies.length; i++) {
// Skip destroyed or dead zombies
if (zombies[i].destroyed || zombies[i].health <= 0) continue;
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= self.range) {
zombiesInRange.push({
zombie: zombies[i],
distance: dist
});
}
}
// Sort by distance and take closest 3
zombiesInRange.sort(function (a, b) {
return a.distance - b.distance;
});
for (var i = 0; i < Math.min(3, zombiesInRange.length); i++) {
targets.push(zombiesInRange[i].zombie);
}
if (targets.length > 0) {
// Aim at closest target
var dx = targets[0].x - self.x;
var dy = targets[0].y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
self.children[0].rotation = angle;
// Shoot three bullets
for (var i = 0; i < targets.length; i++) {
self.shootBullet(targets[i]);
}
} else {
// No targets in range, return to default direction (facing up)
self.children[0].rotation = 0;
}
};
self.shootBullet = function (target) {
LK.getSound('laser').play();
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
game.addChild(bullet);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var UmbrellaZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('umbrellaZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.6;
self.health = 50;
self.damage = 10;
self.umbrellaHealth = 40;
self.hasUmbrella = true;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.3;
} else {
zombieGraphics.tint = self.hasUmbrella ? 0x9370DB : 0xFFFFFF;
self.speed = 0.6;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 60) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
for (var i = 0; i < houses.length; i++) {
var houseLeft = houses[i].x - 1024;
var houseRight = houses[i].x + 1024;
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x));
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
};
self.takeDamage = function (damage) {
if (self.hasUmbrella) {
self.umbrellaHealth -= damage;
if (self.umbrellaHealth <= 0) {
self.hasUmbrella = false;
LK.effects.flashObject(self, 0xFF0000, 300);
} else {
LK.effects.flashObject(self, 0x9370DB, 200);
}
return false;
} else {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 12);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
}
};
self.freeze = function () {
self.frozen = 180;
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 200;
self.maxHealth = 200;
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
// Find and mark grid cell as unoccupied when wall is destroyed
var gridX = Math.floor((self.x - GRID_SIZE / 2) / GRID_SIZE);
var gridY = Math.floor((self.y - GRID_SIZE / 2 - 200) / GRID_SIZE);
if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS) {
var cellIndex = gridY * GRID_COLS + gridX;
if (cellIndex < gridCells.length) {
gridCells[cellIndex].occupied = false;
}
}
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.7;
self.health = 50;
self.damage = 10;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
// Handle burning effect
if (self.burnDuration > 0) {
self.burnDuration--;
if (self.burnDuration % 10 === 0) {
// Damage every 10 frames
self.takeDamage(self.burnDamage || 3);
}
}
// Handle acid effect
if (self.acidDuration > 0) {
self.acidDuration--;
if (self.acidDuration % 15 === 0) {
// Damage every 15 frames
self.takeDamage(self.acidDamage || 2);
}
}
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.3;
} else if (self.burnDuration > 0) {
zombieGraphics.tint = 0xFF4500; // Orange tint for burning
self.speed = 0.7;
} else if (self.acidDuration > 0) {
zombieGraphics.tint = 0x32CD32; // Green tint for acid
self.speed = 0.7;
} else {
zombieGraphics.tint = 0xFFFFFF;
self.speed = 0.7;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
// Check for wall collisions before moving
var nextX = self.x + dx / dist * self.speed;
var nextY = self.y + dy / dist * self.speed;
var blocked = false;
// Check collision with walls
for (var w = 0; w < walls.length; w++) {
var wallDx = nextX - walls[w].x;
var wallDy = nextY - walls[w].y;
var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy);
if (wallDist < 80) {
// Zombie is blocked by wall, attack it instead
self.target = walls[w];
blocked = true;
break;
}
}
if (!blocked) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
} else {
self.attackTimer++;
if (self.attackTimer >= 60) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
// Move towards bottom of screen when no target
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
// Prioritize houses first
for (var i = 0; i < houses.length; i++) {
// Create a virtual target point spread across the house width
var houseLeft = houses[i].x - 1024; // Left edge of house
var houseRight = houses[i].x + 1024; // Right edge of house
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); // Clamp zombie x to house width
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
// Skip solar collectors and towers - they are not targeted by zombies
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 10);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 180;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Add background image
var background = game.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 0
}));
background.x = 0;
background.y = 0;
background.width = 2048;
background.height = 2732;
// Send background to back layer
game.setChildIndex(background, 0);
var energy = 100;
var wave = 0;
var lives = 20;
var isDaytime = true;
var dayTimer = 0;
var waveTimer = 0;
var zombieSpawnTimer = 0;
var zombiesPerWave = 5;
var zombiesToSpawn = 0;
var bossProgress = 0;
var bossProgressMax = 1000; // Will be 300 seconds at 60fps with new increment rate
var currentStage = 1;
var bossActive = false;
var difficultyMultiplier = 1;
var easyPeriodTimer = 0;
var easyPeriodDuration = 2100; // 35 seconds at 60fps
var isEasyPeriod = true;
var gameTimer = 0; // Track total game time in frames (60fps)
var specialZombiesUnlocked = false; // Track if special zombies are unlocked
var currentPage = 0; // Current turret page (0 = basic turrets, 1 = special turrets, 2 = advanced turrets, 3 = experimental turrets)
var maxPages = 3; // Total number of pages (0, 1, 2, 3)
var solarCollectors = [];
var towers = [];
var zombies = [];
var houses = [];
var walls = [];
var attractors = [];
var gridCells = [];
var selectedTower = null;
var placementMode = false;
var placementCost = 0;
var dragging = false;
var dragTurret = null;
var GRID_SIZE = 120;
var GRID_COLS = Math.floor(2048 / GRID_SIZE);
var GRID_ROWS = Math.floor(2200 / GRID_SIZE); // Extend to house area boundary
var energyText = new Text2('Energy: ' + energy, {
size: 60,
fill: 0xFFD700
});
energyText.anchor.set(0.5, 0);
LK.gui.top.addChild(energyText);
var waveText = new Text2('Wave: ' + wave, {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(0, 0);
waveText.x = -900;
waveText.y = 100;
LK.gui.top.addChild(waveText);
var livesText = new Text2('Lives: ' + lives, {
size: 50,
fill: 0xFF0000
});
livesText.anchor.set(1, 0);
livesText.x = 900;
livesText.y = 100;
LK.gui.top.addChild(livesText);
var dayText = new Text2('Day', {
size: 40,
fill: 0xFFD700
});
dayText.anchor.set(0.5, 0);
dayText.y = 80;
LK.gui.top.addChild(dayText);
var progressBarBg = LK.getAsset('progressBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
progressBarBg.x = 0;
progressBarBg.y = 150;
LK.gui.top.addChild(progressBarBg);
var progressBarFill = LK.getAsset('progressBarFill', {
anchorX: 0,
anchorY: 0.5
});
progressBarFill.x = -200;
progressBarFill.y = 150;
progressBarFill.width = 0;
LK.gui.top.addChild(progressBarFill);
var progressText = new Text2('Boss Progress', {
size: 30,
fill: 0xFFFFFF
});
progressText.anchor.set(0.5, 0.5);
progressText.x = 0;
progressText.y = 180;
LK.gui.top.addChild(progressText);
var stageText = new Text2('Stage: 1', {
size: 35,
fill: 0xFFD700
});
stageText.anchor.set(0, 0);
stageText.x = -900;
stageText.y = 150;
LK.gui.top.addChild(stageText);
var solarButton = new Container();
var solarButtonBg = solarButton.attachAsset('solarCollector', {
anchorX: 0.5,
anchorY: 0.5
});
solarButtonBg.width = 100;
solarButtonBg.height = 100;
var solarButtonText = new Text2('Solar: 25', {
size: 25,
fill: 0xFFD700
});
solarButtonText.anchor.set(0.5, 0.5);
solarButtonText.y = -60;
solarButton.addChild(solarButtonText);
solarButton.x = -300;
LK.gui.bottom.addChild(solarButton);
var classicButton = new Container();
var classicButtonBg = classicButton.attachAsset('classicTurret', {
anchorX: 0.5,
anchorY: 0.5
});
classicButtonBg.width = 100;
classicButtonBg.height = 100;
var classicButtonText = new Text2('Solar: 50', {
size: 25,
fill: 0xFFD700
});
classicButtonText.anchor.set(0.5, 0.5);
classicButtonText.y = -60;
classicButton.addChild(classicButtonText);
classicButton.x = -100;
LK.gui.bottom.addChild(classicButton);
var doubleButton = new Container();
var doubleButtonBg = doubleButton.attachAsset('doubleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
});
doubleButtonBg.width = 100;
doubleButtonBg.height = 100;
var doubleButtonText = new Text2('Solar: 75', {
size: 25,
fill: 0xFFD700
});
doubleButtonText.anchor.set(0.5, 0.5);
doubleButtonText.y = -60;
doubleButton.addChild(doubleButtonText);
doubleButton.x = 100;
LK.gui.bottom.addChild(doubleButton);
var tripleButton = new Container();
var tripleButtonBg = tripleButton.attachAsset('tripleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
});
tripleButtonBg.width = 100;
tripleButtonBg.height = 100;
var tripleButtonText = new Text2('Solar: 100', {
size: 25,
fill: 0xFFD700
});
tripleButtonText.anchor.set(0.5, 0.5);
tripleButtonText.y = -60;
tripleButton.addChild(tripleButtonText);
tripleButton.x = 300;
LK.gui.bottom.addChild(tripleButton);
var frozenButton = new Container();
var frozenButtonBg = frozenButton.attachAsset('frozenTurret', {
anchorX: 0.5,
anchorY: 0.5
});
frozenButtonBg.width = 100;
frozenButtonBg.height = 100;
var frozenButtonText = new Text2('Solar: 60', {
size: 25,
fill: 0xFFD700
});
frozenButtonText.anchor.set(0.5, 0.5);
frozenButtonText.y = -60;
frozenButton.addChild(frozenButtonText);
frozenButton.x = -200;
frozenButton.y = -150;
LK.gui.bottom.addChild(frozenButton);
var flameButton = new Container();
var flameButtonBg = flameButton.attachAsset('flameThrowerTurret', {
anchorX: 0.5,
anchorY: 0.5
});
flameButtonBg.width = 100;
flameButtonBg.height = 100;
var flameButtonText = new Text2('Solar: 80', {
size: 25,
fill: 0xFFD700
});
flameButtonText.anchor.set(0.5, 0.5);
flameButtonText.y = -60;
flameButton.addChild(flameButtonText);
flameButton.x = 0;
flameButton.y = -150;
LK.gui.bottom.addChild(flameButton);
var acidButton = new Container();
var acidButtonBg = acidButton.attachAsset('acidTurret', {
anchorX: 0.5,
anchorY: 0.5
});
acidButtonBg.width = 100;
acidButtonBg.height = 100;
var acidButtonText = new Text2('Solar: 70', {
size: 25,
fill: 0xFFD700
});
acidButtonText.anchor.set(0.5, 0.5);
acidButtonText.y = -60;
acidButton.addChild(acidButtonText);
acidButton.x = 200;
acidButton.y = -150;
LK.gui.bottom.addChild(acidButton);
var autoButton = new Container();
var autoButtonBg = autoButton.attachAsset('autoTurret', {
anchorX: 0.5,
anchorY: 0.5
});
autoButtonBg.width = 100;
autoButtonBg.height = 100;
var autoButtonText = new Text2('Solar: 120', {
size: 25,
fill: 0xFFD700
});
autoButtonText.anchor.set(0.5, 0.5);
autoButtonText.y = -60;
autoButton.addChild(autoButtonText);
autoButton.x = -100;
autoButton.y = -250;
LK.gui.bottom.addChild(autoButton);
var minigunButton = new Container();
var minigunButtonBg = minigunButton.attachAsset('minigunTurret', {
anchorX: 0.5,
anchorY: 0.5
});
minigunButtonBg.width = 100;
minigunButtonBg.height = 100;
var minigunButtonText = new Text2('Solar: 90', {
size: 25,
fill: 0xFFD700
});
minigunButtonText.anchor.set(0.5, 0.5);
minigunButtonText.y = -60;
minigunButton.addChild(minigunButtonText);
minigunButton.x = 100;
minigunButton.y = -250;
LK.gui.bottom.addChild(minigunButton);
var rocketButton = new Container();
var rocketButtonBg = rocketButton.attachAsset('rocketTurret', {
anchorX: 0.5,
anchorY: 0.5
});
rocketButtonBg.width = 100;
rocketButtonBg.height = 100;
var rocketButtonText = new Text2('Solar: 150', {
size: 25,
fill: 0xFFD700
});
rocketButtonText.anchor.set(0.5, 0.5);
rocketButtonText.y = -60;
rocketButton.addChild(rocketButtonText);
rocketButton.x = 0;
rocketButton.y = -250;
LK.gui.bottom.addChild(rocketButton);
var electricalButton = new Container();
var electricalButtonBg = electricalButton.attachAsset('electricalTurret', {
anchorX: 0.5,
anchorY: 0.5
});
electricalButtonBg.width = 100;
electricalButtonBg.height = 100;
var electricalButtonText = new Text2('Solar: 110', {
size: 25,
fill: 0xFFD700
});
electricalButtonText.anchor.set(0.5, 0.5);
electricalButtonText.y = -60;
electricalButton.addChild(electricalButtonText);
electricalButton.x = -300;
electricalButton.y = -350;
LK.gui.bottom.addChild(electricalButton);
var spikeButton = new Container();
var spikeButtonBg = spikeButton.attachAsset('spikeTurret', {
anchorX: 0.5,
anchorY: 0.5
});
spikeButtonBg.width = 100;
spikeButtonBg.height = 100;
var spikeButtonText = new Text2('Solar: 85', {
size: 25,
fill: 0xFFD700
});
spikeButtonText.anchor.set(0.5, 0.5);
spikeButtonText.y = -60;
spikeButton.addChild(spikeButtonText);
spikeButton.x = -100;
spikeButton.y = -350;
LK.gui.bottom.addChild(spikeButton);
var ballButton = new Container();
var ballButtonBg = ballButton.attachAsset('ballTurret', {
anchorX: 0.5,
anchorY: 0.5
});
ballButtonBg.width = 100;
ballButtonBg.height = 100;
var ballButtonText = new Text2('Solar: 95', {
size: 25,
fill: 0xFFD700
});
ballButtonText.anchor.set(0.5, 0.5);
ballButtonText.y = -60;
ballButton.addChild(ballButtonText);
ballButton.x = 100;
ballButton.y = -350;
LK.gui.bottom.addChild(ballButton);
var wallButton = new Container();
var wallButtonBg = wallButton.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
wallButtonBg.width = 100;
wallButtonBg.height = 100;
var wallButtonText = new Text2('Solar: 30', {
size: 25,
fill: 0xFFD700
});
wallButtonText.anchor.set(0.5, 0.5);
wallButtonText.y = -60;
wallButton.addChild(wallButtonText);
wallButton.x = 300;
wallButton.y = -350;
LK.gui.bottom.addChild(wallButton);
var attractorButton = new Container();
var attractorButtonBg = attractorButton.attachAsset('attractor', {
anchorX: 0.5,
anchorY: 0.5
});
attractorButtonBg.width = 100;
attractorButtonBg.height = 100;
var attractorButtonText = new Text2('Solar: 50', {
size: 25,
fill: 0xFFD700
});
attractorButtonText.anchor.set(0.5, 0.5);
attractorButtonText.y = -60;
attractorButton.addChild(attractorButtonText);
attractorButton.x = -300;
attractorButton.y = -450;
LK.gui.bottom.addChild(attractorButton);
// Create navigation arrows
var leftArrow = new Container();
var leftArrowBg = leftArrow.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5
});
leftArrowBg.width = 80;
leftArrowBg.height = 80;
leftArrowBg.tint = 0x4169E1;
var leftArrowText = new Text2('<', {
size: 60,
fill: 0xFFFFFF
});
leftArrowText.anchor.set(0.5, 0.5);
leftArrow.addChild(leftArrowText);
leftArrow.x = -450;
leftArrow.y = 0;
LK.gui.bottom.addChild(leftArrow);
var rightArrow = new Container();
var rightArrowBg = rightArrow.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5
});
rightArrowBg.width = 80;
rightArrowBg.height = 80;
rightArrowBg.tint = 0x4169E1;
var rightArrowText = new Text2('>', {
size: 60,
fill: 0xFFFFFF
});
rightArrowText.anchor.set(0.5, 0.5);
rightArrow.addChild(rightArrowText);
rightArrow.x = 450;
rightArrow.y = 0;
LK.gui.bottom.addChild(rightArrow);
// Create page indicator text
var pageText = new Text2('Page 1/3', {
size: 30,
fill: 0xFFFFFF
});
pageText.anchor.set(0.5, 0.5);
pageText.x = 0;
pageText.y = 100;
LK.gui.bottom.addChild(pageText);
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
var cell = game.addChild(LK.getAsset('gridCell', {
anchorX: 0,
anchorY: 0
}));
cell.x = col * GRID_SIZE;
cell.y = row * GRID_SIZE + 200;
cell.alpha = 0.1;
cell.gridX = col;
cell.gridY = row;
cell.occupied = false;
gridCells.push(cell);
}
}
// Create single house spanning the bottom of screen
var house = new House();
house.x = 1024; // Center of screen width (2048/2)
house.y = 2500;
houses.push(house);
game.addChild(house);
// Initialize turret page display
updateTurretPage();
solarButton.down = function (x, y, obj) {
if (energy < 25) {
// Flash button red to indicate insufficient energy
LK.effects.flashObject(solarButton, 0xff0000, 300);
return;
}
dragging = true;
selectedTower = 'solar';
placementCost = 25;
dragTurret = game.addChild(LK.getAsset('solarCollector', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 120;
dragTurret.height = 120;
dragTurret.alpha = 0.8;
// Convert GUI position to game coordinates
var globalPos = game.toLocal({
x: solarButton.x,
y: solarButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
// Flash button green to indicate drag started
LK.effects.flashObject(solarButton, 0x00ff00, 200);
};
classicButton.down = function (x, y, obj) {
if (energy < 50) return;
dragging = true;
selectedTower = 'classic';
placementCost = 50;
dragTurret = game.addChild(LK.getAsset('classicTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
// Convert GUI position to game coordinates
var globalPos = game.toLocal({
x: classicButton.x,
y: classicButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
doubleButton.down = function (x, y, obj) {
if (energy < 75) return;
dragging = true;
selectedTower = 'double';
placementCost = 75;
dragTurret = game.addChild(LK.getAsset('doubleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
// Convert GUI position to game coordinates
var globalPos = game.toLocal({
x: doubleButton.x,
y: doubleButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
tripleButton.down = function (x, y, obj) {
if (energy < 100) return;
dragging = true;
selectedTower = 'triple';
placementCost = 100;
dragTurret = game.addChild(LK.getAsset('tripleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
// Convert GUI position to game coordinates
var globalPos = game.toLocal({
x: tripleButton.x,
y: tripleButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
frozenButton.down = function (x, y, obj) {
if (energy < 60) return;
dragging = true;
selectedTower = 'frozen';
placementCost = 60;
dragTurret = game.addChild(LK.getAsset('frozenTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: frozenButton.x,
y: frozenButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
flameButton.down = function (x, y, obj) {
if (energy < 80) return;
dragging = true;
selectedTower = 'flame';
placementCost = 80;
dragTurret = game.addChild(LK.getAsset('flameThrowerTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: flameButton.x,
y: flameButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
acidButton.down = function (x, y, obj) {
if (energy < 70) return;
dragging = true;
selectedTower = 'acid';
placementCost = 70;
dragTurret = game.addChild(LK.getAsset('acidTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: acidButton.x,
y: acidButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
autoButton.down = function (x, y, obj) {
if (energy < 120) return;
dragging = true;
selectedTower = 'auto';
placementCost = 120;
dragTurret = game.addChild(LK.getAsset('autoTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: autoButton.x,
y: autoButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
minigunButton.down = function (x, y, obj) {
if (energy < 90) return;
dragging = true;
selectedTower = 'minigun';
placementCost = 90;
dragTurret = game.addChild(LK.getAsset('minigunTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: minigunButton.x,
y: minigunButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
rocketButton.down = function (x, y, obj) {
if (energy < 150) return;
dragging = true;
selectedTower = 'rocket';
placementCost = 150;
dragTurret = game.addChild(LK.getAsset('rocketTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: rocketButton.x,
y: rocketButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
electricalButton.down = function (x, y, obj) {
if (energy < 110) return;
dragging = true;
selectedTower = 'electrical';
placementCost = 110;
dragTurret = game.addChild(LK.getAsset('electricalTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: electricalButton.x,
y: electricalButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
spikeButton.down = function (x, y, obj) {
if (energy < 85) return;
dragging = true;
selectedTower = 'spike';
placementCost = 85;
dragTurret = game.addChild(LK.getAsset('spikeTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: spikeButton.x,
y: spikeButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
ballButton.down = function (x, y, obj) {
if (energy < 95) return;
dragging = true;
selectedTower = 'ball';
placementCost = 95;
dragTurret = game.addChild(LK.getAsset('ballTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: ballButton.x,
y: ballButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
wallButton.down = function (x, y, obj) {
if (energy < 30) return;
dragging = true;
selectedTower = 'wall';
placementCost = 30;
dragTurret = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 120;
dragTurret.height = 120;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: wallButton.x,
y: wallButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
attractorButton.down = function (x, y, obj) {
if (energy < 50) return;
dragging = true;
selectedTower = 'attractor';
placementCost = 50;
dragTurret = game.addChild(LK.getAsset('attractor', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 80;
dragTurret.height = 80;
dragTurret.alpha = 0.7;
var globalPos = game.toLocal({
x: attractorButton.x,
y: attractorButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
leftArrow.down = function (x, y, obj) {
if (currentPage > 0) {
currentPage--;
updateTurretPage();
LK.effects.flashObject(leftArrow, 0x00ff00, 200);
} else {
LK.effects.flashObject(leftArrow, 0xff0000, 200);
}
};
rightArrow.down = function (x, y, obj) {
if (currentPage < maxPages) {
currentPage++;
updateTurretPage();
LK.effects.flashObject(rightArrow, 0x00ff00, 200);
} else {
LK.effects.flashObject(rightArrow, 0xff0000, 200);
}
};
function updateTurretPage() {
// Hide all turret buttons first
solarButton.visible = false;
classicButton.visible = false;
doubleButton.visible = false;
tripleButton.visible = false;
frozenButton.visible = false;
flameButton.visible = false;
acidButton.visible = false;
autoButton.visible = false;
minigunButton.visible = false;
rocketButton.visible = false;
electricalButton.visible = false;
spikeButton.visible = false;
ballButton.visible = false;
wallButton.visible = false;
attractorButton.visible = false;
// Page 0: Basic turrets (solar, classic, double, triple)
if (currentPage === 0) {
solarButton.visible = true;
classicButton.visible = true;
doubleButton.visible = true;
tripleButton.visible = true;
}
// Page 1: Special turrets (frozen, flame, acid)
else if (currentPage === 1) {
frozenButton.visible = true;
flameButton.visible = true;
acidButton.visible = true;
// Reposition special turrets to bottom row
frozenButton.x = -200;
frozenButton.y = 0;
flameButton.x = 0;
flameButton.y = 0;
acidButton.x = 200;
acidButton.y = 0;
}
// Page 2: Advanced turrets (auto, minigun, rocket)
else if (currentPage === 2) {
autoButton.visible = true;
minigunButton.visible = true;
rocketButton.visible = true;
// Reposition advanced turrets to bottom row
autoButton.x = -200;
autoButton.y = 0;
minigunButton.x = 0;
minigunButton.y = 0;
rocketButton.x = 200;
rocketButton.y = 0;
}
// Page 3: Experimental turrets (electrical, spike, ball, wall, attractor)
else if (currentPage === 3) {
electricalButton.visible = true;
spikeButton.visible = true;
ballButton.visible = true;
wallButton.visible = true;
attractorButton.visible = true;
// Reposition experimental turrets to bottom row
electricalButton.x = -300;
electricalButton.y = 0;
spikeButton.x = -100;
spikeButton.y = 0;
ballButton.x = 100;
ballButton.y = 0;
wallButton.x = 300;
wallButton.y = 0;
attractorButton.x = 0;
attractorButton.y = -100;
}
// Update page indicator
pageText.setText('Page ' + (currentPage + 1) + '/' + (maxPages + 1));
// Update arrow visibility/opacity
leftArrow.alpha = currentPage > 0 ? 1.0 : 0.5;
rightArrow.alpha = currentPage < maxPages ? 1.0 : 0.5;
}
function highlightValidCells(show) {
for (var i = 0; i < gridCells.length; i++) {
if (!gridCells[i].occupied) {
gridCells[i].alpha = show ? 0.3 : 0.1;
gridCells[i].tint = show ? 0x00ff00 : 0xffffff;
} else {
gridCells[i].alpha = show ? 0.2 : 0.1;
gridCells[i].tint = show ? 0xff0000 : 0xffffff;
}
}
}
game.down = function (x, y, obj) {
// Allow starting drag from anywhere in the game area when not already dragging
if (!dragging) {
// Check if we clicked near any existing turret buttons area
var buttonAreaY = 2732 - 200; // Near bottom of screen
if (y > buttonAreaY) {
// Near button area, let button handlers manage this
return;
}
// Check if we clicked in the house area (exclude from drag zone)
var houseAreaY = 2400; // Above house area
if (y > houseAreaY) {
// In house area, don't allow dragging
return;
}
}
};
game.move = function (x, y, obj) {
if (dragging && dragTurret) {
dragTurret.x = x;
dragTurret.y = y;
highlightValidCells(true);
// Show range circle for turrets (not solar collectors)
if (selectedTower !== 'solar' && !dragTurret.rangeCircle) {
var range = 250; // Default range
if (selectedTower === 'double') range = 280;
if (selectedTower === 'triple') range = 300;
if (selectedTower === 'explosive') range = 280;
if (selectedTower === 'laser') range = 280;
if (selectedTower === 'freeze') range = 150;
if (selectedTower === 'frozen') range = 200;
if (selectedTower === 'flame') range = 150;
if (selectedTower === 'acid') range = 140;
if (selectedTower === 'auto') range = 400;
if (selectedTower === 'minigun') range = 220;
if (selectedTower === 'rocket') range = 300;
if (selectedTower === 'electrical') range = 200;
if (selectedTower === 'spike') range = 180;
if (selectedTower === 'ball') range = 250;
if (selectedTower === 'attractor') range = 300;
dragTurret.rangeCircle = dragTurret.addChild(LK.getAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.rangeCircle.width = range * 2;
dragTurret.rangeCircle.height = range * 2;
dragTurret.rangeCircle.alpha = 0.2;
dragTurret.rangeCircle.tint = 0x00ff00;
}
// Show visual feedback for valid placement area
var gridX = Math.floor(x / GRID_SIZE);
var gridY = Math.floor((y - 200) / GRID_SIZE);
var houseAreaY = 2400; // House area boundary
// Check if in house area (exclude from placement)
var inHouseArea = y > houseAreaY;
if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS && !inHouseArea) {
var cellIndex = gridY * GRID_COLS + gridX;
if (cellIndex < gridCells.length && !gridCells[cellIndex].occupied) {
dragTurret.tint = 0x00ff00; // Green tint for valid placement
dragTurret.alpha = 0.8;
} else {
dragTurret.tint = 0xff0000; // Red tint for invalid placement
dragTurret.alpha = 0.5;
}
} else {
dragTurret.tint = 0xff0000; // Red tint for out of bounds or house area
dragTurret.alpha = 0.5;
}
}
};
game.up = function (x, y, obj) {
if (dragging && dragTurret) {
highlightValidCells(false);
var gridX = Math.floor(x / GRID_SIZE);
var gridY = Math.floor((y - 200) / GRID_SIZE);
var validPlacement = false;
var houseAreaY = 2400; // House area boundary
var inHouseArea = y > houseAreaY;
// Expanded validation - check if within game bounds, valid grid, and not in house area
if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS && !inHouseArea) {
var cellIndex = gridY * GRID_COLS + gridX;
if (cellIndex < gridCells.length && !gridCells[cellIndex].occupied && energy >= placementCost) {
validPlacement = true;
energy -= placementCost;
gridCells[cellIndex].occupied = true;
var newX = gridX * GRID_SIZE + GRID_SIZE / 2;
var newY = gridY * GRID_SIZE + GRID_SIZE / 2 + 200;
if (selectedTower === 'solar') {
var collector = new SolarCollector();
collector.x = newX;
collector.y = newY;
solarCollectors.push(collector);
game.addChild(collector);
} else if (selectedTower === 'classic') {
var tower = new ClassicTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'double') {
var tower = new DoubleBarrelTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'triple') {
var tower = new TripleBarrelTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'frozen') {
var tower = new FrozenTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'flame') {
var tower = new FlameThrowerTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'acid') {
var tower = new AcidTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'auto') {
var tower = new AutoTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'minigun') {
var tower = new MinigunTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'rocket') {
var tower = new RocketTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'electrical') {
var tower = new ElectricalTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'spike') {
var tower = new SpikeTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'ball') {
var tower = new BallTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'wall') {
var wall = new Wall();
wall.x = newX;
wall.y = newY;
walls.push(wall);
game.addChild(wall);
} else if (selectedTower === 'attractor') {
var attractor = new Attractor();
attractor.x = newX;
attractor.y = newY;
attractors.push(attractor);
game.addChild(attractor);
}
LK.getSound('build').play();
// Flash effect for successful placement
LK.effects.flashObject(dragTurret, 0x00ff00, 300);
} else {
// Invalid placement - show error feedback
LK.effects.flashObject(dragTurret, 0xff0000, 300);
}
} else {
// Out of bounds or house area - show error feedback
LK.effects.flashObject(dragTurret, 0xff0000, 300);
}
// Clean up drag state
dragTurret.destroy();
dragTurret = null;
dragging = false;
selectedTower = null;
placementCost = 0;
}
};
function spawnZombie(isBoss) {
var zombie;
if (isBoss) {
// Randomly select boss type
var bossRoll = Math.random();
if (bossRoll < 0.25) {
zombie = new ShamanZombie();
} else if (bossRoll < 0.5) {
zombie = new NinjaZombie();
} else if (bossRoll < 0.75) {
zombie = new CrazyZombie();
} else {
zombie = new SoldierZombie();
}
zombie.health *= difficultyMultiplier;
zombie.damage = Math.floor(zombie.damage * difficultyMultiplier);
bossActive = true;
} else {
// Determine zombie type based on probability and game time
var zombieRoll = Math.random();
var stageMultiplier = Math.min(currentStage, 5); // Cap at stage 5 for variety
if (!specialZombiesUnlocked) {
// First 100 seconds: only basic zombies, tough zombies, and runners
if (zombieRoll < 0.6) {
// 60% Basic zombie
zombie = new Zombie();
} else if (zombieRoll < 0.8) {
// 20% Tough zombie
zombie = new ToughZombie();
} else {
// 20% Runner zombie
zombie = new RunnerZombie();
}
} else {
// After 100 seconds: all zombie types available
if (zombieRoll < 0.35) {
// 35% Basic zombie
zombie = new Zombie();
} else if (zombieRoll < 0.48) {
// 13% Tough zombie
zombie = new ToughZombie();
} else if (zombieRoll < 0.61) {
// 13% Runner zombie
zombie = new RunnerZombie();
} else if (zombieRoll < 0.72) {
// 11% Umbrella zombie
zombie = new UmbrellaZombie();
} else if (zombieRoll < 0.81) {
// 9% Crows zombie
zombie = new CrowsZombie();
} else if (zombieRoll < 0.88) {
// 7% Cardboard zombie
zombie = new CardboardZombie();
} else if (zombieRoll < 0.94) {
// 6% Box zombie
zombie = new BoxZombie();
} else {
// 6% Football zombie
zombie = new FootballZombie();
}
}
if (!isEasyPeriod) {
zombie.health = Math.floor(zombie.health * difficultyMultiplier);
zombie.damage = Math.floor(zombie.damage * difficultyMultiplier);
if (zombie.speed) {
zombie.speed *= Math.min(difficultyMultiplier * 0.3 + 0.7, 2);
}
}
}
// Spawn only from top of screen
zombie.x = Math.random() * 2048;
zombie.y = 150;
zombies.push(zombie);
game.addChild(zombie);
}
function onBossDefeated() {
bossActive = false;
bossProgress = 0;
currentStage++;
difficultyMultiplier += 0.5;
// Switch day/night cycle
isDaytime = !isDaytime;
dayText.setText(isDaytime ? 'Day' : 'Night');
dayText.tint = isDaytime ? 0xFFD700 : 0x4169E1;
game.setBackgroundColor(isDaytime ? 0x87CEEB : 0x191970);
// Update solar collectors
for (var i = 0; i < solarCollectors.length; i++) {
solarCollectors[i].isDaytime = isDaytime;
}
// Flash screen effect for stage transition
LK.effects.flashScreen(isDaytime ? 0xFFD700 : 0x191970, 1000);
// Increase boss progress requirement by 25 seconds (1500 frames at 60fps)
bossProgressMax += 84; // 25 seconds * 60fps * 0.056 increment = ~84 progress units
}
game.update = function () {
// Track total game time and unlock special zombies after 100 seconds
gameTimer++;
if (!specialZombiesUnlocked && gameTimer >= 6000) {
// 100 seconds * 60 fps = 6000 frames
specialZombiesUnlocked = true;
// Flash screen to indicate special zombies are now available
LK.effects.flashScreen(0x800080, 500); // Purple flash
}
// Spawn buried zombies randomly on the map
if (specialZombiesUnlocked && Math.random() < 0.001) {
// 0.1% chance per frame
var buriedZombie = new BuriedZombie();
// Spawn anywhere on the map but not too close to house
var minDistanceFromHouse = 300;
var validPosition = false;
var attempts = 0;
while (!validPosition && attempts < 10) {
buriedZombie.x = Math.random() * 1800 + 124; // Keep away from edges
buriedZombie.y = Math.random() * 1800 + 400; // Keep away from top and house
// Check distance from house
var distanceFromHouse = Math.sqrt(Math.pow(buriedZombie.x - 1024, 2) + Math.pow(buriedZombie.y - 2500, 2));
if (distanceFromHouse >= minDistanceFromHouse && buriedZombie.y < 2200) {
validPosition = true;
}
attempts++;
}
if (validPosition) {
zombies.push(buriedZombie);
game.addChild(buriedZombie);
}
}
// Handle easy period for new players
if (isEasyPeriod) {
easyPeriodTimer++;
if (easyPeriodTimer >= easyPeriodDuration) {
isEasyPeriod = false;
}
}
// Update progress bar based on time and zombie kills
if (!bossActive) {
var progressIncrease = isEasyPeriod ? 0.028 : 0.056; // Reduced to make 300 seconds initially
bossProgress += progressIncrease;
// Check if boss should spawn
if (bossProgress >= bossProgressMax) {
spawnZombie(true);
}
}
// Update progress bar visual
var progressPercent = Math.min(bossProgress / bossProgressMax, 1);
progressBarFill.width = 400 * progressPercent;
// Change progress bar color based on proximity to boss
if (progressPercent > 0.8) {
progressBarFill.tint = 0xff0000; // Red when close to boss
} else if (progressPercent > 0.6) {
progressBarFill.tint = 0xffaa00; // Orange
} else {
progressBarFill.tint = 0xff6600; // Default orange
}
// Animate progress bar when close to boss spawn
if (progressPercent > 0.9 && !bossActive) {
var pulseScale = 1 + Math.sin(LK.ticks * 0.3) * 0.1;
progressBarFill.scaleY = pulseScale;
progressBarBg.scaleY = pulseScale;
} else {
progressBarFill.scaleY = 1;
progressBarBg.scaleY = 1;
}
// Day/night cycle is now only controlled by boss defeats
// Remove automatic timer-based day/night switching
// Wave-based zombie spawning with limited numbers per wave
if (zombiesToSpawn <= 0 && !bossActive) {
waveTimer++;
// Random delay between waves (10-25 seconds based on difficulty)
var minWaveDelay = isEasyPeriod ? 600 : Math.max(600, 900 - Math.floor(difficultyMultiplier * 60)); // 10-15 seconds
var maxWaveDelay = isEasyPeriod ? 1500 : Math.max(900, 1500 - Math.floor(difficultyMultiplier * 30)); // 15-25 seconds
var randomWaveDelay = Math.floor(Math.random() * (maxWaveDelay - minWaveDelay + 1)) + minWaveDelay;
if (waveTimer >= randomWaveDelay) {
waveTimer = 0;
wave++;
// Spawn 3-4 zombies per wave depending on difficulty
var baseZombies = isEasyPeriod ? 3 : 3;
var maxZombies = isEasyPeriod ? 3 : 4;
// Increase chance of 4 zombies as difficulty rises
var zombieCount = baseZombies;
if (!isEasyPeriod && Math.random() < (difficultyMultiplier - 1) * 0.3) {
zombieCount = maxZombies;
}
zombiesPerWave = zombieCount;
zombiesToSpawn = zombiesPerWave;
}
} else if (zombiesToSpawn > 0) {
zombieSpawnTimer++;
// Spawn zombies quickly within a wave (every 0.5-1 second)
var spawnDelay = isEasyPeriod ? 60 : Math.max(30, 60 - Math.floor(difficultyMultiplier * 5));
if (zombieSpawnTimer >= spawnDelay) {
zombieSpawnTimer = 0;
zombiesToSpawn--;
spawnZombie(false);
}
}
for (var i = solarCollectors.length - 1; i >= 0; i--) {
if (solarCollectors[i].destroyed) {
solarCollectors.splice(i, 1);
}
}
for (var i = towers.length - 1; i >= 0; i--) {
if (towers[i].destroyed) {
towers.splice(i, 1);
}
}
for (var i = zombies.length - 1; i >= 0; i--) {
if (zombies[i].destroyed) {
// Check if destroyed zombie was a boss
if ((zombies[i] instanceof ShamanZombie || zombies[i] instanceof NinjaZombie || zombies[i] instanceof CrazyZombie || zombies[i] instanceof SoldierZombie) && bossActive) {
// Boss zombies detected by type
onBossDefeated();
} else if (!bossActive) {
// Regular zombie kill increases boss progress slightly
bossProgress += 0.1; // Reduced to match slower progress bar
}
zombies.splice(i, 1);
}
}
for (var i = houses.length - 1; i >= 0; i--) {
if (houses[i].destroyed) {
houses.splice(i, 1);
}
}
for (var i = walls.length - 1; i >= 0; i--) {
if (walls[i].destroyed) {
walls.splice(i, 1);
}
}
for (var i = attractors.length - 1; i >= 0; i--) {
if (attractors[i].destroyed) {
attractors.splice(i, 1);
}
}
energyText.setText('Energy: ' + energy);
waveText.setText('Wave: ' + wave);
livesText.setText('Lives: ' + lives);
stageText.setText('Stage: ' + currentStage);
// Update progress text based on state
if (bossActive) {
progressText.setText('BOSS ACTIVE!');
progressText.tint = 0xff0000;
var pulseAlpha = 0.5 + Math.sin(LK.ticks * 0.5) * 0.5;
progressText.alpha = pulseAlpha;
} else if (isEasyPeriod) {
progressText.setText('Preparation Time');
progressText.tint = 0x00ff00;
progressText.alpha = 1;
} else {
progressText.setText('Boss Progress: ' + Math.floor(progressPercent * 100) + '%');
progressText.tint = 0xFFFFFF;
progressText.alpha = 1;
}
if (lives <= 0) {
LK.showGameOver();
}
};
LK.playMusic('gameMusic');
top view of a garden. 2d. No shadows. In-game assetmodern, simple
top view of a grassy area. In-Game asset. 2d. High contrast. No shadows
Top view of a house. In-Game asset. 2d. High contrast. No shadows
Acid turret. In-Game asset. 2d. High contrast. No shadows
A zombie. In-Game asset. 2d. High contrast. No shadows
solar collector. In-Game asset. 2d. High contrast. No shadows
A turret. In-Game asset. 2d. High contrast. No shadows
A duo barrel turret. In-Game asset. 2d. High contrast. No shadows
Triple barrel turret. In-Game asset. 2d. High contrast. No shadows
Tough zombie. In-Game asset. 2d. High contrast. No shadows
Light-skin runner zombie. In-Game asset. 2d. High contrast. No shadows
a zombie raises an umbrella forward. In-Game asset. 2d. High contrast. No shadows
Zombie dressed as a crow. In-Game asset. 2d. High contrast. No shadows
Zombie crow. In-Game asset. 2d. High contrast. No shadows
Ice turret. In-Game asset. 2d. High contrast. No shadows
Flame thrower turret. In-Game asset. 2d. High contrast. No shadows
Homing bullet turret. In-Game asset. 2d. High contrast. No shadows
Minigun turret. In-Game asset. 2d. High contrast. No shadows
Rocket launcher turret. In-Game asset. 2d. High contrast. No shadows
Missile. In-Game asset. 2d. High contrast. No shadows
Old abandoned, broken tank. In-Game asset. 2d. High contrast. No shadows
Ninja zombie. In-Game asset. 2d. High contrast. No shadows
Grey zombie. In-Game asset. 2d. High contrast. No shadows
Soldier zombie. In-Game asset. 2d. High contrast. No shadows
Very crazy zombie. In-Game asset. 2d. High contrast. No shadows
A shaman zombie,head wearing a cow skull. In-Game asset. 2d. High contrast. No shadows
Spike turret. In-Game asset. 2d. High contrast. No shadows
Ball turret. In-Game asset. 2d. High contrast. No shadows
Electrical turret. In-Game asset. 2d. High contrast. No shadows
Sci-fi wall. In-Game asset. 2d. High contrast. No shadows
A shield with radar. In-Game asset. 2d. High contrast. No shadows
Football zombie. In-Game asset. 2d. High contrast. No shadows
a zombie covered in mud. In-Game asset. 2d. High contrast. No shadows
A cardboard. In-Game asset. 2d. High contrast. No shadows
A wooden crate. In-Game asset. 2d. High contrast. No shadows