/**** * 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