User prompt
Que los miners te den menos dinero
User prompt
Pone un contador de canto dinero tenes
User prompt
Que el botón de el miner este arriba de los ortos botones
User prompt
Crea una tropa qué te da dinero (100) coste:35
User prompt
Más separados
User prompt
Pone todos los botones separados
User prompt
Crea una tropa qué rispare muy rápido pero sus balas no asen mucho daño (0.5)
User prompt
Crea una tropa qué lanza bombas
User prompt
Pone un botón para cada uno
User prompt
Crea una tropa qué rispare a los zombies
User prompt
Pone una topa que tega resistencia muy grande y si los zombies lo tocan les ace daño
User prompt
Quiero que las towers puedan dispara
Code edit (1 edits merged)
Please save this source code
User prompt
Zombie City Defense
Initial prompt
Taber difen de zombies en una ciudad
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Assassin = Container.expand(function () { var self = Container.call(this); var assassinGraphics = self.attachAsset('assassin', { anchorX: 0.5, anchorY: 0.5 }); assassinGraphics.tint = 0x2F4F4F; // Dark slate gray tint for assassins self.range = 80; // Short range self.attackCooldown = 0; self.attackRate = 180; // Slow but deadly attacks self.speed = 1.5; // Fast movement self.patrolTarget = null; self.mode = 'patrol'; self.isStealthed = false; self.stealthTimer = 0; self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.speed = self.originalSpeed; } } // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } // Stealth mechanics if (self.isStealthed) { self.stealthTimer--; if (self.stealthTimer <= 0) { self.isStealthed = false; assassinGraphics.alpha = 1.0; } } // Decrease attack cooldown if (self.attackCooldown > 0) { self.attackCooldown--; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && self.attackCooldown === 0) { self.mode = 'combat'; self.assassinate(nearestZombie); self.attackCooldown = self.attackRate; } else { self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } }; self.assassinate = function (target) { // Enter stealth after attack self.isStealthed = true; self.stealthTimer = 120; // 2 seconds of stealth assassinGraphics.alpha = 0.3; // Deal massive damage var damage = 8; if (self.isDamageWeakened) { damage = damage * 0.5; } target.takeDamage(damage); // Visual effect LK.effects.flashObject(self, 0x2F4F4F, 300); LK.effects.flashObject(target, 0xFF0000, 500); LK.getSound('shoot').play(); }; self.setNewPatrolTarget = function () { var angle = Math.random() * Math.PI * 2; var radius = 150 + Math.random() * 250; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var Barrier = Container.expand(function () { var self = Container.call(this); var barrierGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); barrierGraphics.tint = 0x8B4513; // Brown tint for barriers self.health = 100; // Very high resistance self.maxHealth = 100; self.lastIntersecting = {}; self.update = function () { // Check collision with zombies for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var zombieId = zombies.indexOf(zombie); // Initialize last intersecting state if not exists if (self.lastIntersecting[zombieId] === undefined) { self.lastIntersecting[zombieId] = false; } var currentIntersecting = self.intersects(zombie); // Check if zombie just started intersecting if (!self.lastIntersecting[zombieId] && currentIntersecting) { // Damage zombie zombie.takeDamage(); // Take damage to barrier self.health -= 5; // Update barrier appearance based on health var healthPercent = self.health / self.maxHealth; if (healthPercent > 0.7) { barrierGraphics.tint = 0x8B4513; // Brown } else if (healthPercent > 0.4) { barrierGraphics.tint = 0xFF8C00; // Orange } else { barrierGraphics.tint = 0xFF0000; // Red } // Destroy barrier if health reaches 0 if (self.health <= 0) { self.destroy(); var index = barriers.indexOf(self); if (index > -1) { barriers.splice(index, 1); } return; } } // Update last intersecting state self.lastIntersecting[zombieId] = currentIntersecting; } }; return self; }); var Bomb = Container.expand(function () { var self = Container.call(this); var bombGraphics = self.attachAsset('bomb', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 4; self.target = null; self.exploded = false; self.update = function () { if (self.exploded) return; if (!self.target || zombies.indexOf(self.target) === -1) { self.explode(); return; } // Move toward target var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 15) { // Hit target area self.explode(); } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.explode = function () { if (self.exploded) return; self.exploded = true; // Play explosion sound LK.getSound('bombExplode').play(); // Damage all zombies in explosion radius var explosionRadius = 80; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRadius) { var damage = 1; // Check if any bomber that could have fired this bomb is weakened for (var j = 0; j < bombers.length; j++) { var bomber = bombers[j]; if (bomber.isDamageWeakened) { damage = 0.5; // Reduced damage break; } } zombie.takeDamage(damage); zombie.takeDamage(damage); // Bombs do double damage } } // Create explosion effect LK.effects.flashObject(self, 0xff4444, 200); // Remove bomb self.destroy(); var index = bombs.indexOf(self); if (index > -1) { bombs.splice(index, 1); } }; return self; }); var Bomber = Container.expand(function () { var self = Container.call(this); var bomberGraphics = self.attachAsset('bomber', { anchorX: 0.5, anchorY: 0.5 }); bomberGraphics.tint = 0xff6600; // Orange tint for bombers self.range = 180; self.shootCooldown = 0; self.shootRate = 90; // slower than soldiers (more frames between shots) self.speed = 0.6; self.patrolTarget = null; self.mode = 'patrol'; self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.shootRate = self.originalShootRate; } } // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } // Decrease shoot cooldown if (self.shootCooldown > 0) { self.shootCooldown--; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && self.shootCooldown === 0) { // Combat mode - launch bomb at zombie self.mode = 'combat'; self.launchBomb(nearestZombie); self.shootCooldown = self.shootRate; } else { // Patrol mode - move around self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } }; self.launchBomb = function (target) { var bomb = new Bomb(); bomb.x = self.x; bomb.y = self.y; bomb.target = target; bombs.push(bomb); game.addChild(bomb); }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 250 + Math.random() * 350; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var BoosterZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('boosterZombie', { anchorX: 0.5, anchorY: 0.5 }); zombieGraphics.tint = 0xFFD700; // Gold tint for booster zombies self.speed = 0.9; self.health = 20; self.maxHealth = 20; self.targetX = cityCenter.x; self.targetY = cityCenter.y; self.boostTimer = 0; self.boostRate = 180; // Boost zombies every 3 seconds (180 frames) self.boostRadius = 120; // Radius to boost other zombies // Create health bar var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -50 }); var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5, x: -29, y: -50 }); self.update = function () { // Handle frozen state if (self.isFrozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.isFrozen = false; self.speed = self.originalSpeed; tween(zombieGraphics, { tint: 0xFFD700 }, { duration: 500, easing: tween.easeOut }); } } // Handle burning state if (self.isBurning) { self.burnTimer--; self.burnDamageTimer++; // Deal burn damage every 30 frames (0.5 seconds) if (self.burnDamageTimer >= 30) { self.takeDamage(0.5); // Burn damage self.burnDamageTimer = 0; LK.effects.flashObject(self, 0xFF4500, 100); } if (self.burnTimer <= 0) { self.isBurning = false; tween(zombieGraphics, { tint: 0xFFD700 }, { duration: 500, easing: tween.easeOut }); } } // Move toward city center (only if not frozen) if (!self.isFrozen) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } // Update visual based on health var healthPercent = self.health / self.maxHealth; if (healthPercent > 0.7) { zombieGraphics.tint = 0xFFD700; // Gold } else if (healthPercent > 0.4) { zombieGraphics.tint = 0xFF8C00; // Orange } else { zombieGraphics.tint = 0xFF0000; // Red } // Update health bar healthBarFill.width = 58 * healthPercent; if (healthPercent > 0.7) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } // Boost nearby zombies periodically self.boostTimer++; if (self.boostTimer >= self.boostRate) { self.boostNearbyZombies(); self.boostTimer = 0; } }; self.boostNearbyZombies = function () { for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; if (zombie === self) continue; // Don't boost self var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.boostRadius) { // Increase zombie speed by 30% zombie.speed *= 1.3; // Visual effect when boosting LK.effects.flashObject(zombie, 0xFFD700, 300); } } // Visual effect when boosting others LK.effects.flashObject(self, 0xFFD700, 500); }; self.takeDamage = function (damage) { if (damage === undefined) damage = 1; self.health -= damage; if (self.health <= 0) { self.die(); } }; self.die = function () { LK.getSound('zombieHit').play(); gamePoints += 12; // Moderate points for killing booster zombies updateUI(); // Remove from zombies array var index = zombies.indexOf(self); if (index > -1) { zombies.splice(index, 1); } self.destroy(); }; 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.target = null; self.update = function () { if (!self.target || zombies.indexOf(self.target) === -1) { self.destroy(); var index = bullets.indexOf(self); if (index > -1) { bullets.splice(index, 1); } return; } // Move toward target var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { // Hit target with reduced damage if weakened var damage = 1; // Check if any tower that could have fired this bullet is weakened for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.isDamageWeakened) { damage = 0.5; // Reduced damage break; } } // Check if any soldier that could have fired this bullet is weakened for (var i = 0; i < soldiers.length; i++) { var soldier = soldiers[i]; if (soldier.isDamageWeakened) { damage = 0.5; // Reduced damage break; } } self.target.takeDamage(damage); self.destroy(); var index = bullets.indexOf(self); if (index > -1) { bullets.splice(index, 1); } } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; return self; }); var Burner = Container.expand(function () { var self = Container.call(this); var burnerGraphics = self.attachAsset('burner', { anchorX: 0.5, anchorY: 0.5 }); burnerGraphics.tint = 0xFF4500; // Orange red tint for burners self.range = 120; self.explodeRange = 999999; // Global burn effect self.speed = 1.0; self.patrolTarget = null; self.mode = 'patrol'; self.hasExploded = false; self.explodeTimer = 0; self.explodeDelay = 60; // 1 second delay before explosion self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.speed = self.originalSpeed; } } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && !self.hasExploded) { // Start explosion sequence self.mode = 'exploding'; self.hasExploded = true; self.explodeTimer = self.explodeDelay; // Visual indication of incoming explosion tween(burnerGraphics, { tint: 0xFF0000, scaleX: 1.5, scaleY: 1.5 }, { duration: 1000, easing: tween.easeOut }); LK.effects.flashObject(self, 0xFF4500, 200); } else if (!self.hasExploded) { // Patrol mode - move around self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } // Handle explosion countdown if (self.hasExploded) { self.explodeTimer--; if (self.explodeTimer <= 0) { self.explode(); } } }; self.explode = function () { // Burn ALL zombies - deal continuous damage over time for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; // Skip immune zombies if (zombie.isImmune) continue; // Mark zombie as burning zombie.isBurning = true; zombie.burnTimer = 300; // Burn for 5 seconds (300 frames at 60fps) zombie.burnDamageTimer = 0; // Initialize burn damage timer // Visual effect when burning tween(zombie, { tint: 0xFF4500 }, { duration: 300, easing: tween.easeOut }); LK.effects.flashObject(zombie, 0xFF4500, 300); } // Global burn visual effect LK.effects.flashScreen(0xFF4500, 500); LK.getSound('bombExplode').play(); // Remove burner self.destroy(); var index = burners.indexOf(self); if (index > -1) { burners.splice(index, 1); } }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 180 + Math.random() * 270; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var Catcher = Container.expand(function () { var self = Container.call(this); var catcherGraphics = self.attachAsset('catcher', { anchorX: 0.5, anchorY: 0.5 }); catcherGraphics.tint = 0x00BFFF; // Deep sky blue tint for catchers self.range = 140; self.freezeCooldown = 0; self.freezeRate = 120; // Freeze every 2 seconds (120 frames) self.speed = 0.9; self.patrolTarget = null; self.mode = 'patrol'; self.update = function () { // Decrease freeze cooldown if (self.freezeCooldown > 0) { self.freezeCooldown--; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance && !zombie.isFrozen) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && self.freezeCooldown === 0) { // Combat mode - freeze zombie self.mode = 'combat'; self.freezeZombie(nearestZombie); self.freezeCooldown = self.freezeRate; } else { // Patrol mode - move around self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } }; self.freezeZombie = function (zombie) { if (zombie.isFrozen || zombie.isImmune || zombie.cannotBeFrozen) return; // Mark zombie as frozen zombie.isFrozen = true; zombie.frozenTimer = 180; // Freeze for 3 seconds (180 frames) zombie.originalSpeed = zombie.speed; zombie.speed = 0; // Stop movement // Visual effect when freezing LK.getSound('freeze').play(); tween(zombie, { tint: 0x87CEEB }, { duration: 300, easing: tween.easeOut }); LK.effects.flashObject(zombie, 0x00BFFF, 300); LK.effects.flashObject(self, 0x00BFFF, 200); }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 200 + Math.random() * 300; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var EliteSniper = Container.expand(function () { var self = Container.call(this); var sniperGraphics = self.attachAsset('eliteSniper', { anchorX: 0.5, anchorY: 0.5 }); sniperGraphics.tint = 0x8B0000; // Dark red tint for elite snipers self.range = 500; // Very long range self.shootCooldown = 0; self.shootRate = 120; // Slow but powerful shots self.speed = 0.6; // Slow movement self.patrolTarget = null; self.mode = 'patrol'; self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.shootRate = self.originalShootRate; } } // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } // Decrease shoot cooldown if (self.shootCooldown > 0) { self.shootCooldown--; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && self.shootCooldown === 0) { // Combat mode - snipe zombie self.mode = 'combat'; self.snipe(nearestZombie); self.shootCooldown = self.shootRate; } else { // Patrol mode - move around self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } }; self.snipe = function (target) { // Instant hit with high damage var damage = 5; // Very high damage if (self.isDamageWeakened) { damage = damage * 0.5; // Reduced damage } target.takeDamage(damage); // Visual effect tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.easeOut }); } }); LK.getSound('shoot').play(); LK.effects.flashObject(self, 0x8B0000, 300); }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 250 + Math.random() * 350; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var Engineer = Container.expand(function () { var self = Container.call(this); var engineerGraphics = self.attachAsset('engineer', { anchorX: 0.5, anchorY: 0.5 }); engineerGraphics.tint = 0xFFD700; // Gold tint for engineers self.range = 100; self.buildCooldown = 0; self.buildRate = 240; // Build every 4 seconds self.speed = 0.7; self.patrolTarget = null; self.mode = 'patrol'; self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.speed = self.originalSpeed; } } // Decrease build cooldown if (self.buildCooldown > 0) { self.buildCooldown--; } // Repair nearby barriers and towers if (self.buildCooldown === 0) { self.repairNearbyStructures(); self.buildCooldown = self.buildRate; } // Patrol behavior self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); }; self.repairNearbyStructures = function () { var repairedAny = false; // Repair barriers for (var i = 0; i < barriers.length; i++) { var barrier = barriers[i]; if (barrier.health >= barrier.maxHealth) continue; var dx = barrier.x - self.x; var dy = barrier.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range) { barrier.health = Math.min(barrier.maxHealth, barrier.health + 25); LK.effects.flashObject(barrier, 0xFFD700, 300); repairedAny = true; } } // Repair heavy tanks for (var i = 0; i < heavyTanks.length; i++) { var tank = heavyTanks[i]; if (tank.health >= tank.maxHealth) continue; var dx = tank.x - self.x; var dy = tank.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range) { tank.health = Math.min(tank.maxHealth, tank.health + 20); LK.effects.flashObject(tank, 0xFFD700, 300); repairedAny = true; } } if (repairedAny) { LK.effects.flashObject(self, 0xFFD700, 500); } }; self.setNewPatrolTarget = function () { var angle = Math.random() * Math.PI * 2; var radius = 200 + Math.random() * 300; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var Exploder = Container.expand(function () { var self = Container.call(this); var exploderGraphics = self.attachAsset('exploder', { anchorX: 0.5, anchorY: 0.5 }); exploderGraphics.tint = 0xff6600; // Orange-red tint for exploders self.range = 100; self.explodeRange = 120; // Range for explosion damage self.speed = 1.2; self.patrolTarget = null; self.mode = 'patrol'; self.hasExploded = false; self.explodeTimer = 0; self.explodeDelay = 60; // 1 second delay before explosion self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.speed = self.originalSpeed; } } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && !self.hasExploded) { // Start explosion sequence self.mode = 'exploding'; self.hasExploded = true; self.explodeTimer = self.explodeDelay; // Visual indication of incoming explosion tween(exploderGraphics, { tint: 0xff0000, scaleX: 1.3, scaleY: 1.3 }, { duration: 1000, easing: tween.easeOut }); LK.effects.flashObject(self, 0xff6600, 200); } else if (!self.hasExploded) { // Patrol mode - move around self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } // Handle explosion countdown if (self.hasExploded) { self.explodeTimer--; if (self.explodeTimer <= 0) { self.explode(); } } }; self.explode = function () { // Damage all zombies in explosion radius for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.explodeRange) { zombie.takeDamage(3); // High explosion damage } } // Create fire tile at explosion location var fireTile = new FireTile(); fireTile.x = self.x; fireTile.y = self.y; fireTiles.push(fireTile); game.addChild(fireTile); // Explosion visual effect LK.effects.flashScreen(0xff4400, 300); LK.getSound('bombExplode').play(); // Remove exploder self.destroy(); var index = exploders.indexOf(self); if (index > -1) { exploders.splice(index, 1); } }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 150 + Math.random() * 250; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var FastBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); bulletGraphics.tint = 0xff9900; // Orange tint for fast bullets self.speed = 10; self.target = null; self.damage = 0.5; // Low damage self.update = function () { if (!self.target || zombies.indexOf(self.target) === -1) { self.destroy(); var index = fastBullets.indexOf(self); if (index > -1) { fastBullets.splice(index, 1); } return; } // Move toward target var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { // Hit target with reduced damage var damage = self.damage; // Check if any gunner that could have fired this bullet is weakened for (var i = 0; i < gunners.length; i++) { var gunner = gunners[i]; if (gunner.isDamageWeakened) { damage = damage * 0.5; // Reduced damage break; } } self.target.takeDamage(damage); self.destroy(); var index = fastBullets.indexOf(self); if (index > -1) { fastBullets.splice(index, 1); } } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; return self; }); var FireTile = Container.expand(function () { var self = Container.call(this); var fireGraphics = self.attachAsset('fireTile', { anchorX: 0.5, anchorY: 0.5 }); fireGraphics.alpha = 0.8; self.duration = 600; // Fire lasts for 10 seconds (600 frames) self.damageTimer = 0; self.damageRate = 30; // Deal damage every 0.5 seconds (30 frames) self.lastIntersecting = {}; self.update = function () { // Decrease duration self.duration--; if (self.duration <= 0) { self.destroy(); var index = fireTiles.indexOf(self); if (index > -1) { fireTiles.splice(index, 1); } return; } // Fade out over time var fadePercent = self.duration / 600; fireGraphics.alpha = 0.8 * fadePercent; // Damage timer self.damageTimer++; if (self.damageTimer >= self.damageRate) { self.damageTimer = 0; // Check collision with zombies for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var zombieId = zombies.indexOf(zombie); // Initialize last intersecting state if not exists if (self.lastIntersecting[zombieId] === undefined) { self.lastIntersecting[zombieId] = false; } var currentIntersecting = self.intersects(zombie); // Deal damage if zombie is on fire tile if (currentIntersecting) { zombie.takeDamage(0.5); // Fire deals moderate damage // Visual effect when burning LK.effects.flashObject(zombie, 0xff4400, 200); } // Update last intersecting state self.lastIntersecting[zombieId] = currentIntersecting; } } // Flickering fire effect if (LK.ticks % 10 === 0) { tween(fireGraphics, { scaleX: 0.9 + Math.random() * 0.2, scaleY: 0.9 + Math.random() * 0.2 }, { duration: 200, easing: tween.easeOut }); } }; return self; }); var Flamethrower = Container.expand(function () { var self = Container.call(this); var flamethrowerGraphics = self.attachAsset('flamethrower', { anchorX: 0.5, anchorY: 0.5 }); flamethrowerGraphics.tint = 0xFF6600; // Orange tint for flamethrowers self.range = 140; self.burnCooldown = 0; self.burnRate = 60; // Burn every 1 second (60 frames) self.speed = 0.8; self.patrolTarget = null; self.mode = 'patrol'; self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.burnRate = self.originalBurnRate; } } // Decrease burn cooldown if (self.burnCooldown > 0) { self.burnCooldown--; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance && !zombie.isBurning) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && self.burnCooldown === 0) { // Combat mode - burn zombie self.mode = 'combat'; self.burnZombie(nearestZombie); self.burnCooldown = self.burnRate; } else { // Patrol mode - move around self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } }; self.burnZombie = function (zombie) { if (zombie.isBurning || zombie.isImmune) return; // Mark zombie as burning zombie.isBurning = true; zombie.burnTimer = 300; // Burn for 5 seconds (300 frames) zombie.burnDamageTimer = 0; // Initialize burn damage timer // Visual effect when burning tween(zombie, { tint: 0xFF4500 }, { duration: 300, easing: tween.easeOut }); LK.effects.flashObject(zombie, 0xFF6600, 300); LK.effects.flashObject(self, 0xFF6600, 200); }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 200 + Math.random() * 300; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var Freezer = Container.expand(function () { var self = Container.call(this); var freezerGraphics = self.attachAsset('freezer', { anchorX: 0.5, anchorY: 0.5 }); freezerGraphics.tint = 0x00FFFF; // Cyan tint for freezers self.range = 120; self.explodeRange = 999999; // Global freeze effect self.speed = 1.0; self.patrolTarget = null; self.mode = 'patrol'; self.hasExploded = false; self.explodeTimer = 0; self.explodeDelay = 60; // 1 second delay before explosion self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.speed = self.originalSpeed; } } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && !self.hasExploded) { // Start explosion sequence self.mode = 'exploding'; self.hasExploded = true; self.explodeTimer = self.explodeDelay; // Visual indication of incoming explosion tween(freezerGraphics, { tint: 0x00FFFF, scaleX: 1.5, scaleY: 1.5 }, { duration: 1000, easing: tween.easeOut }); LK.effects.flashObject(self, 0x00FFFF, 200); } else if (!self.hasExploded) { // Patrol mode - move around self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } // Handle explosion countdown if (self.hasExploded) { self.explodeTimer--; if (self.explodeTimer <= 0) { self.explode(); } } }; self.explode = function () { // Freeze ALL zombies for 6.8 seconds (408 frames at 60fps) for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; // Skip immune zombies and speedy zombies if (zombie.isImmune || zombie.cannotBeFrozen) continue; // Mark zombie as frozen zombie.isFrozen = true; zombie.frozenTimer = 408; // 6.8 seconds at 60fps if (!zombie.originalSpeed) { zombie.originalSpeed = zombie.speed; } zombie.speed = 0; // Stop movement // Visual effect when freezing tween(zombie, { tint: 0x87CEEB }, { duration: 300, easing: tween.easeOut }); LK.effects.flashObject(zombie, 0x00FFFF, 300); } // Global freeze visual effect LK.effects.flashScreen(0x00FFFF, 500); LK.getSound('freeze').play(); // Remove freezer self.destroy(); var index = freezers.indexOf(self); if (index > -1) { freezers.splice(index, 1); } }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 180 + Math.random() * 270; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var FreezerTroop = Container.expand(function () { var self = Container.call(this); var freezerTroopGraphics = self.attachAsset('freezerTroop', { anchorX: 0.5, anchorY: 0.5 }); freezerTroopGraphics.tint = 0x87CEEB; // Sky blue tint for freezer troops self.range = 160; // Breathing range self.freezeCooldown = 0; self.freezeRate = 30; // Continuous freezing every 0.5 seconds self.speed = 1.0; self.patrolTarget = null; self.mode = 'patrol'; self.breathingScale = 1.0; self.breathingDirection = 1; self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.speed = self.originalSpeed; } } // Breathing animation self.breathingScale += self.breathingDirection * 0.01; if (self.breathingScale >= 1.1) { self.breathingDirection = -1; } else if (self.breathingScale <= 0.9) { self.breathingDirection = 1; } freezerTroopGraphics.scaleX = self.breathingScale; freezerTroopGraphics.scaleY = self.breathingScale; // Decrease freeze cooldown if (self.freezeCooldown > 0) { self.freezeCooldown--; } // Continuous freezing with breathing if (self.freezeCooldown === 0) { self.freezeZombiesWithBreathing(); self.freezeCooldown = self.freezeRate; } // Patrol behavior self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); }; self.freezeZombiesWithBreathing = function () { var frozenAny = false; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && !zombie.isFrozen && !zombie.isImmune && !zombie.cannotBeFrozen) { // Freeze zombie with breathing zombie.isFrozen = true; zombie.frozenTimer = 120; // Freeze for 2 seconds zombie.originalSpeed = zombie.speed; zombie.speed = 0; // Stop movement // Visual effect when freezing tween(zombie, { tint: 0x87CEEB }, { duration: 300, easing: tween.easeOut }); LK.effects.flashObject(zombie, 0x87CEEB, 300); frozenAny = true; } } // Visual breathing effect when freezing if (frozenAny) { LK.getSound('freeze').play(); tween(freezerTroopGraphics, { tint: 0x00FFFF }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(freezerTroopGraphics, { tint: 0x87CEEB }, { duration: 200, easing: tween.easeOut }); } }); } }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 200 + Math.random() * 300; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var Gunner = Container.expand(function () { var self = Container.call(this); var gunnerGraphics = self.attachAsset('gunner', { anchorX: 0.5, anchorY: 0.5 }); gunnerGraphics.tint = 0xffa500; // Orange tint for gunners self.range = 100; self.shootCooldown = 0; self.shootRate = 8; // Very fast shooting (much faster than soldiers) self.speed = 1.0; self.patrolTarget = null; self.mode = 'patrol'; self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.shootRate = self.originalShootRate; } } // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } // Decrease shoot cooldown if (self.shootCooldown > 0) { self.shootCooldown--; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && self.shootCooldown === 0) { // Combat mode - shoot at zombie self.mode = 'combat'; self.shoot(nearestZombie); self.shootCooldown = self.shootRate; } else { // Patrol mode - move around self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } }; self.shoot = function (target) { var bullet = new FastBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = target; fastBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 180 + Math.random() * 280; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var HeavyTank = Container.expand(function () { var self = Container.call(this); var tankGraphics = self.attachAsset('heavyTank', { anchorX: 0.5, anchorY: 0.5 }); tankGraphics.tint = 0x556B2F; // Dark olive green tint for heavy tanks self.range = 200; self.shootCooldown = 0; self.shootRate = 90; // Moderate shooting rate self.speed = 0.4; // Very slow movement self.patrolTarget = null; self.mode = 'patrol'; self.health = 150; // Very high health self.maxHealth = 150; self.armor = 0.5; // 50% damage reduction // Create health bar var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -50 }); var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5, x: -29, y: -50 }); self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.shootRate = self.originalShootRate; } } // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } // Decrease shoot cooldown if (self.shootCooldown > 0) { self.shootCooldown--; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && self.shootCooldown === 0) { // Combat mode - shoot at zombie self.mode = 'combat'; self.shoot(nearestZombie); self.shootCooldown = self.shootRate; } else { // Patrol mode - move around self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } // Update health bar var healthPercent = self.health / self.maxHealth; healthBarFill.width = 58 * healthPercent; if (healthPercent > 0.7) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } }; self.shoot = function (target) { var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = target; bullet.damage = 2; // Higher damage bullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); }; self.takeDamage = function (damage) { if (damage === undefined) damage = 1; // Apply armor reduction damage = damage * self.armor; self.health -= damage; if (self.health <= 0) { self.die(); } }; self.die = function () { // Remove from array var index = heavyTanks.indexOf(self); if (index > -1) { heavyTanks.splice(index, 1); } self.destroy(); }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 200 + Math.random() * 300; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var ImmunityZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('immunityZombie', { anchorX: 0.5, anchorY: 0.5 }); zombieGraphics.tint = 0x9932CC; // Dark orchid tint for immunity zombies self.speed = 0.7; self.health = 28; self.maxHealth = 28; self.targetX = cityCenter.x; self.targetY = cityCenter.y; self.immunityTimer = 0; self.immunityRate = 60; // Provide immunity every 1 second (60 frames) self.immunityRadius = 200; // Radius to provide immunity to other zombies // Create health bar var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -50 }); var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5, x: -29, y: -50 }); self.update = function () { // Handle frozen state if (self.isFrozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.isFrozen = false; self.speed = self.originalSpeed; tween(zombieGraphics, { tint: 0x9932CC }, { duration: 500, easing: tween.easeOut }); } } // Handle burning state if (self.isBurning) { self.burnTimer--; self.burnDamageTimer++; // Deal burn damage every 30 frames (0.5 seconds) if (self.burnDamageTimer >= 30) { self.takeDamage(0.5); // Burn damage self.burnDamageTimer = 0; LK.effects.flashObject(self, 0xFF4500, 100); } if (self.burnTimer <= 0) { self.isBurning = false; tween(zombieGraphics, { tint: 0x9932CC }, { duration: 500, easing: tween.easeOut }); } } // Move toward city center (only if not frozen) if (!self.isFrozen) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } // Update visual based on health var healthPercent = self.health / self.maxHealth; if (healthPercent > 0.7) { zombieGraphics.tint = 0x9932CC; // Dark orchid } else if (healthPercent > 0.4) { zombieGraphics.tint = 0xFF8C00; // Orange } else { zombieGraphics.tint = 0xFF0000; // Red } // Update health bar healthBarFill.width = 58 * healthPercent; if (healthPercent > 0.7) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } // Provide immunity to nearby zombies periodically self.immunityTimer++; if (self.immunityTimer >= self.immunityRate) { self.grantImmunityToNearbyZombies(); self.immunityTimer = 0; } }; self.grantImmunityToNearbyZombies = function () { for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; if (zombie === self) continue; // Don't grant immunity to self var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.immunityRadius) { // Grant immunity - clear any freeze or burn effects if (zombie.isFrozen) { zombie.isFrozen = false; zombie.frozenTimer = 0; if (zombie.originalSpeed) { zombie.speed = zombie.originalSpeed; } } if (zombie.isBurning) { zombie.isBurning = false; zombie.burnTimer = 0; zombie.burnDamageTimer = 0; } // Mark zombie as immune for a period zombie.isImmune = true; zombie.immunityTimer = 300; // Immune for 5 seconds (300 frames) // Visual effect when granting immunity LK.effects.flashObject(zombie, 0x9932CC, 300); } } // Visual effect when granting immunity to others LK.effects.flashObject(self, 0x9932CC, 500); }; self.takeDamage = function (damage) { if (damage === undefined) damage = 1; self.health -= damage; if (self.health <= 0) { self.die(); } }; self.die = function () { LK.getSound('zombieHit').play(); gamePoints += 15; // Moderate points for killing immunity zombies updateUI(); // Remove from zombies array var index = zombies.indexOf(self); if (index > -1) { zombies.splice(index, 1); } self.destroy(); }; return self; }); var LaserTurret = Container.expand(function () { var self = Container.call(this); var turretGraphics = self.attachAsset('laserTurret', { anchorX: 0.5, anchorY: 0.5 }); turretGraphics.tint = 0x00FF00; // Green tint for laser turrets self.range = 250; self.currentTarget = null; self.laserBeam = null; self.damageTimer = 0; self.damageRate = 30; // Deal damage every 0.5 seconds self.update = function () { // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && nearestZombie !== self.currentTarget) { // Switch target self.currentTarget = nearestZombie; self.createLaserBeam(); } else if (!nearestZombie && self.currentTarget) { // No target, stop laser self.currentTarget = null; self.removeLaserBeam(); } // Update laser beam and deal damage if (self.currentTarget && self.laserBeam) { self.updateLaserBeam(); self.damageTimer++; if (self.damageTimer >= self.damageRate) { var damage = 2; // Continuous damage if (self.isDamageWeakened) { damage = damage * 0.5; // Reduced damage } self.currentTarget.takeDamage(damage); self.damageTimer = 0; } } }; self.createLaserBeam = function () { if (self.laserBeam) { self.laserBeam.destroy(); } self.laserBeam = self.attachAsset('laserBeam', { anchorX: 0, anchorY: 0.5 }); self.laserBeam.alpha = 0.8; }; self.updateLaserBeam = function () { if (!self.laserBeam || !self.currentTarget) return; var dx = self.currentTarget.x - self.x; var dy = self.currentTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); self.laserBeam.width = distance; self.laserBeam.rotation = Math.atan2(dy, dx); // Flickering effect self.laserBeam.alpha = 0.6 + Math.random() * 0.4; }; self.removeLaserBeam = function () { if (self.laserBeam) { self.laserBeam.destroy(); self.laserBeam = null; } }; return self; }); var Miner = Container.expand(function () { var self = Container.call(this); var minerGraphics = self.attachAsset('miner', { anchorX: 0.5, anchorY: 0.5 }); minerGraphics.tint = 0xffd700; // Gold tint for miners self.moneyTimer = 0; self.moneyRate = 180; // Generate money every 3 seconds (180 frames) self.lastIntersecting = {}; self.update = function () { // Check collision with zombies first for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var zombieId = zombies.indexOf(zombie); // Initialize last intersecting state if not exists if (self.lastIntersecting[zombieId] === undefined) { self.lastIntersecting[zombieId] = false; } var currentIntersecting = self.intersects(zombie); // Check if zombie just started intersecting if (!self.lastIntersecting[zombieId] && currentIntersecting) { // Transform miner into zombie var newZombie = new Zombie(); newZombie.x = self.x; newZombie.y = self.y; zombies.push(newZombie); game.addChild(newZombie); // Remove miner self.destroy(); var minerIndex = miners.indexOf(self); if (minerIndex > -1) { miners.splice(minerIndex, 1); } return; } // Update last intersecting state self.lastIntersecting[zombieId] = currentIntersecting; } self.moneyTimer++; if (self.moneyTimer >= self.moneyRate) { // Generate money gamePoints += 25; updateUI(); // Visual effect when generating money LK.effects.flashObject(self, 0xffd700, 500); self.moneyTimer = 0; } }; return self; }); var Mortar = Container.expand(function () { var self = Container.call(this); var mortarGraphics = self.attachAsset('mortar', { anchorX: 0.5, anchorY: 0.5 }); mortarGraphics.tint = 0x696969; // Dim gray tint for mortars self.range = 400; // Very long range self.shootCooldown = 0; self.shootRate = 180; // Slow but powerful self.update = function () { // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } if (self.shootCooldown > 0) { self.shootCooldown--; return; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie) { self.fireMortar(nearestZombie); self.shootCooldown = self.shootRate; } }; self.fireMortar = function (target) { // Create delayed explosion at target location var explosionX = target.x; var explosionY = target.y; // Visual warning LK.effects.flashObject(target, 0xFF0000, 1000); // Delayed explosion LK.setTimeout(function () { var explosionRadius = 120; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - explosionX; var dy = zombie.y - explosionY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRadius) { var damage = 4; if (self.isDamageWeakened) { damage = damage * 0.5; } zombie.takeDamage(damage); } } // Explosion effect LK.effects.flashScreen(0xFF4400, 300); LK.getSound('bombExplode').play(); }, 1000); // Recoil effect tween(self, { scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.easeOut }); } }); }; 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 = 6; self.target = null; self.launcher = null; self.exploded = false; self.update = function () { if (self.exploded) return; if (!self.target || zombies.indexOf(self.target) === -1) { self.explode(); return; } // Move toward target var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 20) { // Hit target area self.explode(); } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.explode = function () { if (self.exploded) return; self.exploded = true; // Play explosion sound LK.getSound('bombExplode').play(); // Damage all zombies in explosion radius var explosionRadius = 100; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRadius) { var damage = 3; // High damage if (self.launcher && self.launcher.isDamageWeakened) { damage = damage * 0.5; // Reduced damage } zombie.takeDamage(damage); } } // Create explosion effect LK.effects.flashObject(self, 0xff4444, 300); // Remove rocket self.destroy(); var index = rockets.indexOf(self); if (index > -1) { rockets.splice(index, 1); } }; return self; }); var RocketLauncher = Container.expand(function () { var self = Container.call(this); var launcherGraphics = self.attachAsset('rocketLauncher', { anchorX: 0.5, anchorY: 0.5 }); launcherGraphics.tint = 0x8B4513; // Brown tint for rocket launchers self.range = 280; self.shootCooldown = 0; self.shootRate = 150; // Slow but powerful self.update = function () { // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } if (self.shootCooldown > 0) { self.shootCooldown--; return; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie) { self.launchRocket(nearestZombie); self.shootCooldown = self.shootRate; } }; self.launchRocket = function (target) { var rocket = new Rocket(); rocket.x = self.x; rocket.y = self.y; rocket.target = target; rocket.launcher = self; rockets.push(rocket); game.addChild(rocket); LK.getSound('shoot').play(); }; return self; }); var ShieldGenerator = Container.expand(function () { var self = Container.call(this); var generatorGraphics = self.attachAsset('shieldGenerator', { anchorX: 0.5, anchorY: 0.5 }); generatorGraphics.tint = 0x00AAFF; // Blue tint for shield generators self.range = 150; self.shieldTimer = 0; self.shieldRate = 120; // Generate shields every 2 seconds self.shields = []; self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; } } self.shieldTimer++; if (self.shieldTimer >= self.shieldRate) { self.generateShields(); self.shieldTimer = 0; } // Update existing shields for (var i = self.shields.length - 1; i >= 0; i--) { var shield = self.shields[i]; shield.duration--; if (shield.duration <= 0) { shield.destroy(); self.shields.splice(i, 1); } } }; self.generateShields = function () { // Find nearby friendly units var nearbyUnits = []; // Check soldiers for (var i = 0; i < soldiers.length; i++) { var soldier = soldiers[i]; var dx = soldier.x - self.x; var dy = soldier.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range) { nearbyUnits.push(soldier); } } // Check gunners for (var i = 0; i < gunners.length; i++) { var gunner = gunners[i]; var dx = gunner.x - self.x; var dy = gunner.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range) { nearbyUnits.push(gunner); } } // Check heavy tanks for (var i = 0; i < heavyTanks.length; i++) { var tank = heavyTanks[i]; var dx = tank.x - self.x; var dy = tank.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range) { nearbyUnits.push(tank); } } // Generate shields for nearby units for (var i = 0; i < nearbyUnits.length; i++) { var unit = nearbyUnits[i]; if (!unit.hasShield) { var shield = unit.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5 }); shield.alpha = 0.3; shield.duration = 300; // 5 seconds unit.hasShield = true; unit.shieldHealth = 50; self.shields.push(shield); // Visual effect LK.effects.flashObject(unit, 0x00AAFF, 300); } } // Visual effect for generator LK.effects.flashObject(self, 0x00AAFF, 500); }; return self; }); var ShockTrooper = Container.expand(function () { var self = Container.call(this); var shockGraphics = self.attachAsset('shockTrooper', { anchorX: 0.5, anchorY: 0.5 }); shockGraphics.tint = 0x00CED1; // Dark turquoise tint for shock troopers self.range = 160; self.shockCooldown = 0; self.shockRate = 120; // Shock every 2 seconds self.speed = 1.0; self.patrolTarget = null; self.mode = 'patrol'; self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.speed = self.originalSpeed; } } // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } // Decrease shock cooldown if (self.shockCooldown > 0) { self.shockCooldown--; } // Find zombies in range for shock attack var zombiesInRange = []; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range) { zombiesInRange.push(zombie); } } if (zombiesInRange.length > 0 && self.shockCooldown === 0) { self.mode = 'combat'; self.electricShock(zombiesInRange); self.shockCooldown = self.shockRate; } else { self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } }; self.electricShock = function (zombiesInRange) { var damage = 2; if (self.isDamageWeakened) { damage = damage * 0.5; } for (var i = 0; i < zombiesInRange.length; i++) { var zombie = zombiesInRange[i]; zombie.takeDamage(damage); // Stun effect - slow zombie temporarily zombie.isStunned = true; zombie.stunnedTimer = 60; // 1 second stun if (!zombie.originalSpeed) { zombie.originalSpeed = zombie.speed; } zombie.speed = zombie.speed * 0.3; // 70% speed reduction // Visual effect tween(zombie, { tint: 0x00CED1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(zombie, { tint: zombie.originalTint || 0xFFFFFF }, { duration: 300, easing: tween.easeOut }); } }); } // Shock visual effect LK.effects.flashObject(self, 0x00CED1, 500); LK.getSound('shoot').play(); }; self.setNewPatrolTarget = function () { var angle = Math.random() * Math.PI * 2; var radius = 200 + Math.random() * 300; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var Soldier = Container.expand(function () { var self = Container.call(this); var soldierGraphics = self.attachAsset('soldier', { anchorX: 0.5, anchorY: 0.5 }); soldierGraphics.tint = 0x4CAF50; // Green tint for soldiers self.range = 120; self.shootCooldown = 0; self.shootRate = 45; // frames between shots self.speed = 0.8; self.patrolTarget = null; self.mode = 'patrol'; // 'patrol' or 'combat' self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.shootRate = self.originalShootRate; } } // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } // Decrease shoot cooldown if (self.shootCooldown > 0) { self.shootCooldown--; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie && self.shootCooldown === 0) { // Combat mode - shoot at zombie self.mode = 'combat'; self.shoot(nearestZombie); self.shootCooldown = self.shootRate; } else { // Patrol mode - move around self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); } }; self.shoot = function (target) { var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = target; bullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); }; self.setNewPatrolTarget = function () { // Set random patrol target around city center var angle = Math.random() * Math.PI * 2; var radius = 200 + Math.random() * 300; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; // Keep patrol target within bounds self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var SpawnerZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('spawnerZombie', { anchorX: 0.5, anchorY: 0.5 }); zombieGraphics.tint = 0x800080; // Purple tint for spawner zombies self.speed = 0.8; self.health = 25; self.maxHealth = 25; self.targetX = cityCenter.x; self.targetY = cityCenter.y; self.spawnTimer = 0; self.spawnRate = 300; // Spawn tank zombie every 5 seconds (300 frames) // Create health bar var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -50 }); var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5, x: -29, y: -50 }); self.update = function () { // Handle frozen state if (self.isFrozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.isFrozen = false; self.speed = self.originalSpeed; tween(zombieGraphics, { tint: 0x800080 }, { duration: 500, easing: tween.easeOut }); } } // Handle burning state if (self.isBurning) { self.burnTimer--; self.burnDamageTimer++; // Deal burn damage every 30 frames (0.5 seconds) if (self.burnDamageTimer >= 30) { self.takeDamage(0.5); // Burn damage self.burnDamageTimer = 0; LK.effects.flashObject(self, 0xFF4500, 100); } if (self.burnTimer <= 0) { self.isBurning = false; tween(zombieGraphics, { tint: 0x800080 }, { duration: 500, easing: tween.easeOut }); } } // Move toward city center (only if not frozen) if (!self.isFrozen) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } // Update visual based on health var healthPercent = self.health / self.maxHealth; if (healthPercent > 0.7) { zombieGraphics.tint = 0x800080; // Purple } else if (healthPercent > 0.4) { zombieGraphics.tint = 0xFF4500; // Orange } else { zombieGraphics.tint = 0xFF0000; // Red } // Update health bar healthBarFill.width = 58 * healthPercent; if (healthPercent > 0.7) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } // Spawn tank zombies periodically self.spawnTimer++; if (self.spawnTimer >= self.spawnRate) { self.spawnTankZombie(); self.spawnTimer = 0; } }; self.spawnTankZombie = function () { var tankZombie = new TankZombie(); // Spawn near the spawner zombie var angle = Math.random() * Math.PI * 2; var radius = 50; tankZombie.x = self.x + Math.cos(angle) * radius; tankZombie.y = self.y + Math.sin(angle) * radius; // Keep within bounds tankZombie.x = Math.max(50, Math.min(1998, tankZombie.x)); tankZombie.y = Math.max(50, Math.min(2682, tankZombie.y)); zombies.push(tankZombie); game.addChild(tankZombie); // Visual effect when spawning LK.effects.flashObject(self, 0x800080, 500); }; self.takeDamage = function (damage) { if (damage === undefined) damage = 1; self.health -= damage; if (self.health <= 0) { self.die(); } }; self.die = function () { LK.getSound('zombieHit').play(); gamePoints += 15; // More points than regular zombie, less than tank updateUI(); // Remove from zombies array var index = zombies.indexOf(self); if (index > -1) { zombies.splice(index, 1); } self.destroy(); }; return self; }); var SpeedyZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('speedyZombie', { anchorX: 0.5, anchorY: 0.5 }); zombieGraphics.tint = 0xFF6B35; // Orange tint for speedy zombies self.speed = 2.5; // Very fast self.health = 15; self.maxHealth = 15; self.targetX = cityCenter.x; self.targetY = cityCenter.y; self.cannotBeFrozen = true; // Cannot be frozen // Create health bar var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -40 }); var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5, x: -29, y: -40 }); self.update = function () { // Handle immunity timer if (self.isImmune) { self.immunityTimer--; if (self.immunityTimer <= 0) { self.isImmune = false; } } // Speedy zombies CANNOT be frozen - ignore freeze effects if (self.isFrozen) { self.isFrozen = false; // Immediately clear frozen state self.frozenTimer = 0; self.speed = 2.5; // Maintain high speed } // Handle burning state if (self.isBurning) { self.burnTimer--; self.burnDamageTimer++; // Deal burn damage every 30 frames (0.5 seconds) if (self.burnDamageTimer >= 30) { self.takeDamage(0.5); // Burn damage self.burnDamageTimer = 0; LK.effects.flashObject(self, 0xFF4500, 100); } if (self.burnTimer <= 0) { self.isBurning = false; tween(zombieGraphics, { tint: 0xFF6B35 }, { duration: 500, easing: tween.easeOut }); } } // Move toward city center - always fast var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } // Update visual based on health var healthPercent = self.health / self.maxHealth; if (healthPercent > 0.7) { zombieGraphics.tint = 0xFF6B35; // Orange } else if (healthPercent > 0.4) { zombieGraphics.tint = 0xFF8C00; // Dark orange } else { zombieGraphics.tint = 0xFF0000; // Red } // Update health bar healthBarFill.width = 58 * healthPercent; if (healthPercent > 0.7) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } }; self.takeDamage = function (damage) { if (damage === undefined) damage = 1; self.health -= damage; if (self.health <= 0) { self.die(); } }; self.die = function () { LK.getSound('zombieHit').play(); gamePoints += 8; // Moderate points for killing speedy zombies updateUI(); // Remove from zombies array var index = zombies.indexOf(self); if (index > -1) { zombies.splice(index, 1); } self.destroy(); }; return self; }); var SupportMedic = Container.expand(function () { var self = Container.call(this); var medicGraphics = self.attachAsset('supportMedic', { anchorX: 0.5, anchorY: 0.5 }); medicGraphics.tint = 0x00FF7F; // Spring green tint for support medics self.range = 140; self.healCooldown = 0; self.healRate = 90; // Heal every 1.5 seconds self.speed = 0.9; self.patrolTarget = null; self.mode = 'patrol'; self.update = function () { // Handle weakening effect if (self.isWeakened) { self.weakenTimer--; if (self.weakenTimer <= 0) { self.isWeakened = false; self.speed = self.originalSpeed; } } // Decrease heal cooldown if (self.healCooldown > 0) { self.healCooldown--; } // Heal nearby troops if (self.healCooldown === 0) { self.healNearbyTroops(); self.healCooldown = self.healRate; } // Patrol behavior self.mode = 'patrol'; if (!self.patrolTarget || self.reachedTarget()) { self.setNewPatrolTarget(); } self.moveTowardTarget(); }; self.healNearbyTroops = function () { var healedAny = false; var allTroops = [].concat(soldiers, gunners, bombers, heavyTanks, eliteSnipers); for (var i = 0; i < allTroops.length; i++) { var troop = allTroops[i]; if (!troop.health || troop.health >= troop.maxHealth) continue; var dx = troop.x - self.x; var dy = troop.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range) { troop.health = Math.min(troop.maxHealth, troop.health + 15); LK.effects.flashObject(troop, 0x00FF7F, 300); healedAny = true; } } if (healedAny) { LK.effects.flashObject(self, 0x00FF7F, 500); } }; self.setNewPatrolTarget = function () { var angle = Math.random() * Math.PI * 2; var radius = 200 + Math.random() * 300; self.patrolTarget = { x: cityCenter.x + Math.cos(angle) * radius, y: cityCenter.y + Math.sin(angle) * radius }; self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x)); self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y)); }; self.moveTowardTarget = function () { if (!self.patrolTarget) return; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.reachedTarget = function () { if (!self.patrolTarget) return true; var dx = self.patrolTarget.x - self.x; var dy = self.patrolTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < 20; }; return self; }); var TankZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('tankZombie', { anchorX: 0.5, anchorY: 0.5 }); zombieGraphics.tint = 0x8B0000; // Dark red tint for tank zombies self.speed = 0.6; // Slower than regular zombies self.health = 35; // Much higher health self.maxHealth = 35; self.targetX = cityCenter.x; self.targetY = cityCenter.y; // Create health bar var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -50 }); var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5, x: -29, y: -50 }); self.update = function () { // Handle frozen state if (self.isFrozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.isFrozen = false; self.speed = self.originalSpeed; tween(zombieGraphics, { tint: 0x8B0000 }, { duration: 500, easing: tween.easeOut }); } } // Handle burning state if (self.isBurning) { self.burnTimer--; self.burnDamageTimer++; // Deal burn damage every 30 frames (0.5 seconds) if (self.burnDamageTimer >= 30) { self.takeDamage(0.5); // Burn damage self.burnDamageTimer = 0; LK.effects.flashObject(self, 0xFF4500, 100); } if (self.burnTimer <= 0) { self.isBurning = false; tween(zombieGraphics, { tint: 0x8B0000 }, { duration: 500, easing: tween.easeOut }); } } // Move toward city center (only if not frozen) if (!self.isFrozen) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } // Update visual based on health var healthPercent = self.health / self.maxHealth; if (healthPercent > 0.7) { zombieGraphics.tint = 0x8B0000; // Dark red } else if (healthPercent > 0.4) { zombieGraphics.tint = 0xFF4500; // Orange red } else { zombieGraphics.tint = 0xFF0000; // Bright red } // Update health bar healthBarFill.width = 58 * healthPercent; if (healthPercent > 0.7) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } }; self.takeDamage = function (damage) { if (damage === undefined) damage = 1; // Default damage is 1 self.health -= damage; if (self.health <= 0) { self.die(); } }; self.die = function () { LK.getSound('zombieHit').play(); gamePoints += 20; // More points for killing tank zombies updateUI(); // Remove from zombies array var index = zombies.indexOf(self); if (index > -1) { zombies.splice(index, 1); } self.destroy(); }; return self; }); var Tower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); self.range = 150; self.shootCooldown = 0; self.shootRate = 30; // frames between shots self.level = 1; self.update = function () { // Check collision with zombies first for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var zombieId = zombies.indexOf(zombie); // Initialize last intersecting state if not exists if (self.lastIntersecting === undefined) { self.lastIntersecting = {}; } if (self.lastIntersecting[zombieId] === undefined) { self.lastIntersecting[zombieId] = false; } var currentIntersecting = self.intersects(zombie); // Check if zombie just started intersecting if (!self.lastIntersecting[zombieId] && currentIntersecting) { // Zombie breaks tower self.destroy(); var index = towers.indexOf(self); if (index > -1) { towers.splice(index, 1); } return; } // Update last intersecting state self.lastIntersecting[zombieId] = currentIntersecting; } // Handle damage weakening effect if (self.isDamageWeakened) { self.damageWeakenTimer--; if (self.damageWeakenTimer <= 0) { self.isDamageWeakened = false; } } if (self.shootCooldown > 0) { self.shootCooldown--; return; } // Find nearest zombie in range var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } if (nearestZombie) { self.shoot(nearestZombie); self.shootCooldown = self.shootRate; } }; self.shoot = function (target) { var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = target; bullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); }; self.upgrade = function () { if (gamePoints >= self.getUpgradeCost()) { gamePoints -= self.getUpgradeCost(); self.level++; self.range += 20; self.shootRate = Math.max(10, self.shootRate - 5); towerGraphics.tint = 0x2e7d32; // Green tint for upgraded towers updateUI(); } }; self.getUpgradeCost = function () { return self.level * 15; }; return self; }); var Trap = Container.expand(function () { var self = Container.call(this); var trapGraphics = self.attachAsset('trap', { anchorX: 0.5, anchorY: 0.5 }); trapGraphics.tint = 0x8B4513; // Saddle brown tint for traps self.isArmed = true; self.lastIntersecting = {}; trapGraphics.alpha = 0.7; // Slightly transparent self.update = function () { if (!self.isArmed) return; // Check collision with zombies for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var zombieId = zombies.indexOf(zombie); // Initialize last intersecting state if not exists if (self.lastIntersecting[zombieId] === undefined) { self.lastIntersecting[zombieId] = false; } var currentIntersecting = self.intersects(zombie); // Check if zombie just stepped on trap if (!self.lastIntersecting[zombieId] && currentIntersecting) { self.triggerTrap(zombie); return; } // Update last intersecting state self.lastIntersecting[zombieId] = currentIntersecting; } }; self.triggerTrap = function (zombie) { // Trap zombie for 3 seconds zombie.isTrapped = true; zombie.trappedTimer = 180; // 3 seconds if (!zombie.originalSpeed) { zombie.originalSpeed = zombie.speed; } zombie.speed = 0; // Stop movement // Deal damage over time var trapDamage = 0; var trapDamageInterval = LK.setInterval(function () { if (zombie.isTrapped) { zombie.takeDamage(0.5); trapDamage++; if (trapDamage >= 6) { // Stop after 6 ticks LK.clearInterval(trapDamageInterval); } } else { LK.clearInterval(trapDamageInterval); } }, 30); // Visual effects LK.effects.flashObject(zombie, 0x8B4513, 300); LK.effects.flashObject(self, 0xFF0000, 500); // Disarm trap self.isArmed = false; trapGraphics.alpha = 0.3; trapGraphics.tint = 0x696969; // Gray when used // Remove trap after use LK.setTimeout(function () { self.destroy(); var index = traps.indexOf(self); if (index > -1) { traps.splice(index, 1); } }, 3000); }; return self; }); var WeakeningZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('weakeningZombie', { anchorX: 0.5, anchorY: 0.5 }); zombieGraphics.tint = 0x4B0082; // Dark purple tint for weakening zombies self.speed = 0.9; self.health = 25; self.maxHealth = 25; self.targetX = cityCenter.x; self.targetY = cityCenter.y; self.weakenTimer = 0; self.weakenRate = 120; // Weaken troops every 2 seconds (120 frames) self.weakenRadius = 180; // Radius to weaken troops // Create health bar var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -50 }); var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5, x: -29, y: -50 }); self.update = function () { // Handle immunity timer if (self.isImmune) { self.immunityTimer--; if (self.immunityTimer <= 0) { self.isImmune = false; } } // Handle frozen state if (self.isFrozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.isFrozen = false; self.speed = self.originalSpeed; tween(zombieGraphics, { tint: 0x4B0082 }, { duration: 500, easing: tween.easeOut }); } } // Handle burning state if (self.isBurning) { self.burnTimer--; self.burnDamageTimer++; // Deal burn damage every 30 frames (0.5 seconds) if (self.burnDamageTimer >= 30) { self.takeDamage(0.5); // Burn damage self.burnDamageTimer = 0; LK.effects.flashObject(self, 0xFF4500, 100); } if (self.burnTimer <= 0) { self.isBurning = false; tween(zombieGraphics, { tint: 0x4B0082 }, { duration: 500, easing: tween.easeOut }); } } // Move toward city center (only if not frozen) if (!self.isFrozen) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } // Update visual based on health var healthPercent = self.health / self.maxHealth; if (healthPercent > 0.7) { zombieGraphics.tint = 0x4B0082; // Dark purple } else if (healthPercent > 0.4) { zombieGraphics.tint = 0xFF8C00; // Orange } else { zombieGraphics.tint = 0xFF0000; // Red } // Update health bar healthBarFill.width = 58 * healthPercent; if (healthPercent > 0.7) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } // Weaken nearby troops periodically self.weakenTimer++; if (self.weakenTimer >= self.weakenRate) { self.weakenNearbyTroops(); self.weakenTimer = 0; } }; self.weakenNearbyTroops = function () { // Weaken soldiers - reduce damage for (var i = 0; i < soldiers.length; i++) { var soldier = soldiers[i]; var dx = soldier.x - self.x; var dy = soldier.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark soldier as damage weakened if (!soldier.isDamageWeakened) { soldier.isDamageWeakened = true; // Visual effect when weakening LK.effects.flashObject(soldier, 0x4B0082, 300); } soldier.damageWeakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Weaken gunners - reduce damage for (var i = 0; i < gunners.length; i++) { var gunner = gunners[i]; var dx = gunner.x - self.x; var dy = gunner.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark gunner as damage weakened if (!gunner.isDamageWeakened) { gunner.isDamageWeakened = true; // Visual effect when weakening LK.effects.flashObject(gunner, 0x4B0082, 300); } gunner.damageWeakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Weaken bombers - reduce damage for (var i = 0; i < bombers.length; i++) { var bomber = bombers[i]; var dx = bomber.x - self.x; var dy = bomber.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark bomber as damage weakened if (!bomber.isDamageWeakened) { bomber.isDamageWeakened = true; // Visual effect when weakening LK.effects.flashObject(bomber, 0x4B0082, 300); } bomber.damageWeakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Weaken towers - reduce damage for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx = tower.x - self.x; var dy = tower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark tower as damage weakened if (!tower.isDamageWeakened) { tower.isDamageWeakened = true; // Visual effect when weakening LK.effects.flashObject(tower, 0x4B0082, 300); } tower.damageWeakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Visual effect when weakening others LK.effects.flashObject(self, 0x4B0082, 500); }; self.takeDamage = function (damage) { if (damage === undefined) damage = 1; self.health -= damage; if (self.health <= 0) { self.die(); } }; self.die = function () { LK.getSound('zombieHit').play(); gamePoints += 12; // Moderate points for killing weakening zombies updateUI(); // Remove from zombies array var index = zombies.indexOf(self); if (index > -1) { zombies.splice(index, 1); } self.destroy(); }; return self; }); var WeakerZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('weakerZombie', { anchorX: 0.5, anchorY: 0.5 }); zombieGraphics.tint = 0x8A2BE2; // Blue violet tint for weaker zombies self.speed = 0.8; self.health = 22; self.maxHealth = 22; self.targetX = cityCenter.x; self.targetY = cityCenter.y; self.weakenTimer = 0; self.weakenRate = 120; // Weaken troops every 2 seconds (120 frames) self.weakenRadius = 150; // Radius to weaken troops // Create health bar var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -50 }); var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5, x: -29, y: -50 }); self.update = function () { // Handle frozen state if (self.isFrozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.isFrozen = false; self.speed = self.originalSpeed; tween(zombieGraphics, { tint: 0x8A2BE2 }, { duration: 500, easing: tween.easeOut }); } } // Handle burning state if (self.isBurning) { self.burnTimer--; self.burnDamageTimer++; // Deal burn damage every 30 frames (0.5 seconds) if (self.burnDamageTimer >= 30) { self.takeDamage(0.5); // Burn damage self.burnDamageTimer = 0; LK.effects.flashObject(self, 0xFF4500, 100); } if (self.burnTimer <= 0) { self.isBurning = false; tween(zombieGraphics, { tint: 0x8A2BE2 }, { duration: 500, easing: tween.easeOut }); } } // Move toward city center (only if not frozen) if (!self.isFrozen) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } // Update visual based on health var healthPercent = self.health / self.maxHealth; if (healthPercent > 0.7) { zombieGraphics.tint = 0x8A2BE2; // Blue violet } else if (healthPercent > 0.4) { zombieGraphics.tint = 0xFF8C00; // Orange } else { zombieGraphics.tint = 0xFF0000; // Red } // Update health bar healthBarFill.width = 58 * healthPercent; if (healthPercent > 0.7) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } // Weaken nearby troops periodically self.weakenTimer++; if (self.weakenTimer >= self.weakenRate) { self.weakenNearbyTroops(); self.weakenTimer = 0; } }; self.weakenNearbyTroops = function () { // Weaken soldiers for (var i = 0; i < soldiers.length; i++) { var soldier = soldiers[i]; var dx = soldier.x - self.x; var dy = soldier.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark soldier as weakened if (!soldier.isWeakened) { soldier.isWeakened = true; soldier.originalShootRate = soldier.shootRate; soldier.shootRate = Math.floor(soldier.shootRate * 1.5); // 50% slower shooting // Visual effect when weakening LK.effects.flashObject(soldier, 0x8A2BE2, 300); } soldier.weakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Weaken gunners for (var i = 0; i < gunners.length; i++) { var gunner = gunners[i]; var dx = gunner.x - self.x; var dy = gunner.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark gunner as weakened if (!gunner.isWeakened) { gunner.isWeakened = true; gunner.originalShootRate = gunner.shootRate; gunner.shootRate = Math.floor(gunner.shootRate * 1.5); // 50% slower shooting // Visual effect when weakening LK.effects.flashObject(gunner, 0x8A2BE2, 300); } gunner.weakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Weaken bombers for (var i = 0; i < bombers.length; i++) { var bomber = bombers[i]; var dx = bomber.x - self.x; var dy = bomber.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark bomber as weakened if (!bomber.isWeakened) { bomber.isWeakened = true; bomber.originalShootRate = bomber.shootRate; bomber.shootRate = Math.floor(bomber.shootRate * 1.5); // 50% slower shooting // Visual effect when weakening LK.effects.flashObject(bomber, 0x8A2BE2, 300); } bomber.weakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Weaken exploders for (var i = 0; i < exploders.length; i++) { var exploder = exploders[i]; var dx = exploder.x - self.x; var dy = exploder.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark exploder as weakened if (!exploder.isWeakened) { exploder.isWeakened = true; exploder.originalSpeed = exploder.speed; exploder.speed = exploder.speed * 0.7; // 30% slower movement // Visual effect when weakening LK.effects.flashObject(exploder, 0x8A2BE2, 300); } exploder.weakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Weaken freezers for (var i = 0; i < freezers.length; i++) { var freezer = freezers[i]; var dx = freezer.x - self.x; var dy = freezer.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark freezer as weakened if (!freezer.isWeakened) { freezer.isWeakened = true; freezer.originalSpeed = freezer.speed; freezer.speed = freezer.speed * 0.7; // 30% slower movement // Visual effect when weakening LK.effects.flashObject(freezer, 0x8A2BE2, 300); } freezer.weakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Weaken burners for (var i = 0; i < burners.length; i++) { var burner = burners[i]; var dx = burner.x - self.x; var dy = burner.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark burner as weakened if (!burner.isWeakened) { burner.isWeakened = true; burner.originalSpeed = burner.speed; burner.speed = burner.speed * 0.7; // 30% slower movement // Visual effect when weakening LK.effects.flashObject(burner, 0x8A2BE2, 300); } burner.weakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Weaken flamethrowers for (var i = 0; i < flamethrowers.length; i++) { var flamethrower = flamethrowers[i]; var dx = flamethrower.x - self.x; var dy = flamethrower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark flamethrower as weakened if (!flamethrower.isWeakened) { flamethrower.isWeakened = true; flamethrower.originalBurnRate = flamethrower.burnRate; flamethrower.burnRate = Math.floor(flamethrower.burnRate * 1.5); // 50% slower burning // Visual effect when weakening LK.effects.flashObject(flamethrower, 0x8A2BE2, 300); } flamethrower.weakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Weaken freezer troops for (var i = 0; i < freezerTroops.length; i++) { var freezerTroop = freezerTroops[i]; var dx = freezerTroop.x - self.x; var dy = freezerTroop.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.weakenRadius) { // Mark freezer troop as weakened if (!freezerTroop.isWeakened) { freezerTroop.isWeakened = true; freezerTroop.originalSpeed = freezerTroop.speed; freezerTroop.speed = freezerTroop.speed * 0.7; // 30% slower movement // Visual effect when weakening LK.effects.flashObject(freezerTroop, 0x8A2BE2, 300); } freezerTroop.weakenTimer = 600; // Weakened for 10 seconds (600 frames) } } // Visual effect when weakening others LK.effects.flashObject(self, 0x8A2BE2, 500); }; self.takeDamage = function (damage) { if (damage === undefined) damage = 1; self.health -= damage; if (self.health <= 0) { self.die(); } }; self.die = function () { LK.getSound('zombieHit').play(); gamePoints += 10; // Moderate points for killing weaker zombies updateUI(); // Remove from zombies array var index = zombies.indexOf(self); if (index > -1) { zombies.splice(index, 1); } self.destroy(); }; 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 = 1; self.health = 3; self.maxHealth = 3; self.targetX = cityCenter.x; self.targetY = cityCenter.y; // Create health bar var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -40 }); var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5, x: -29, y: -40 }); self.update = function () { // Handle immunity timer if (self.isImmune) { self.immunityTimer--; if (self.immunityTimer <= 0) { self.isImmune = false; } } // Handle stunned state if (self.isStunned) { self.stunnedTimer--; if (self.stunnedTimer <= 0) { self.isStunned = false; self.speed = self.originalSpeed; } } // Handle trapped state if (self.isTrapped) { self.trappedTimer--; if (self.trappedTimer <= 0) { self.isTrapped = false; self.speed = self.originalSpeed; } } // Handle frozen state if (self.isFrozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.isFrozen = false; self.speed = self.originalSpeed; tween(zombieGraphics, { tint: 0xFFFFFF }, { duration: 500, easing: tween.easeOut }); } } // Handle burning state if (self.isBurning) { self.burnTimer--; self.burnDamageTimer++; // Deal burn damage every 30 frames (0.5 seconds) if (self.burnDamageTimer >= 30) { self.takeDamage(0.5); // Burn damage self.burnDamageTimer = 0; LK.effects.flashObject(self, 0xFF4500, 100); } if (self.burnTimer <= 0) { self.isBurning = false; tween(zombieGraphics, { tint: 0xFFFFFF }, { duration: 500, easing: tween.easeOut }); } } // Move toward city center (only if not frozen) if (!self.isFrozen) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } // Update health bar var healthPercent = self.health / self.maxHealth; healthBarFill.width = 58 * healthPercent; if (healthPercent > 0.7) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } }; self.takeDamage = function (damage) { if (damage === undefined) damage = 1; // Default damage is 1 self.health -= damage; if (self.health <= 0) { self.die(); } }; self.die = function () { LK.getSound('zombieHit').play(); gamePoints += 5; updateUI(); // Remove from zombies array var index = zombies.indexOf(self); if (index > -1) { zombies.splice(index, 1); } self.destroy(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2e2e2e }); /**** * Game Code ****/ // Game variables var towers = []; var zombies = []; var bullets = []; var fastBullets = []; var barriers = []; var soldiers = []; var gunners = []; var bombers = []; var bombs = []; var miners = []; var catchers = []; var exploders = []; var freezers = []; var burners = []; var flamethrowers = []; var freezerTroops = []; var fireTiles = []; var eliteSnipers = []; var heavyTanks = []; var laserTurrets = []; var rocketLaunchers = []; var rockets = []; var shieldGenerators = []; var supportMedics = []; var assassins = []; var engineers = []; var mortars = []; var shockTroopers = []; var traps = []; var gamePoints = 50; var wave = 1; var zombiesSpawned = 0; var zombiesToSpawn = 5; var spawnTimer = 0; var waveComplete = true; var gridSize = 80; var towerCost = 10; var barrierCost = 15; var soldierCost = 20; var gunnerCost = 18; var bomberCost = 30; var minerCost = 18; var catcherCost = 22; var exploderCost = 25; var freezerCost = 35; var burnerCost = 40; var flamethrowerCost = 28; var freezerTroopCost = 25; var eliteSniperCost = 50; var heavyTankCost = 60; var laserTurretCost = 45; var rocketLauncherCost = 55; var shieldGeneratorCost = 40; var supportMedicCost = 32; var assassinCost = 42; var engineerCost = 35; var mortarCost = 65; var shockTrooperCost = 38; var trapCost = 12; // Create city center var cityCenter = game.addChild(LK.getAsset('cityCenter', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 })); // Create UI elements var pointsText = new Text2('Points: 50', { size: 60, fill: 0xFFFFFF }); pointsText.anchor.set(0, 0); LK.gui.topRight.addChild(pointsText); var moneyText = new Text2('Money: 50', { size: 60, fill: 0xffd700 }); moneyText.anchor.set(1, 0); moneyText.x = -20; LK.gui.topRight.addChild(moneyText); var waveText = new Text2('Wave: 1', { size: 60, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); LK.gui.top.addChild(waveText); // Create GUI buttons organized in rows // Row 1: Basic defensive structures var towerButton = new Text2('Tower (10)', { size: 30, fill: 0xFFFFFF }); towerButton.anchor.set(0, 1); towerButton.x = 20; towerButton.y = -20; LK.gui.bottomLeft.addChild(towerButton); var barrierButton = new Text2('Barrier (15)', { size: 30, fill: 0xFFFFFF }); barrierButton.anchor.set(0, 1); barrierButton.x = 280; barrierButton.y = -20; LK.gui.bottomLeft.addChild(barrierButton); var laserTurretButton = new Text2('LaserTurret (45)', { size: 30, fill: 0xFFFFFF }); laserTurretButton.anchor.set(0, 1); laserTurretButton.x = 540; laserTurretButton.y = -20; LK.gui.bottomLeft.addChild(laserTurretButton); var rocketLauncherButton = new Text2('RocketLauncher (55)', { size: 30, fill: 0xFFFFFF }); rocketLauncherButton.anchor.set(0, 1); rocketLauncherButton.x = 800; rocketLauncherButton.y = -20; LK.gui.bottomLeft.addChild(rocketLauncherButton); var mortarButton = new Text2('Mortar (65)', { size: 30, fill: 0xFFFFFF }); mortarButton.anchor.set(0, 1); mortarButton.x = 1060; mortarButton.y = -20; LK.gui.bottomLeft.addChild(mortarButton); // Row 2: Basic troops var soldierButton = new Text2('Soldier (20)', { size: 30, fill: 0xFFFFFF }); soldierButton.anchor.set(0, 1); soldierButton.x = 20; soldierButton.y = -70; LK.gui.bottomLeft.addChild(soldierButton); var gunnerButton = new Text2('Gunner (18)', { size: 30, fill: 0xFFFFFF }); gunnerButton.anchor.set(0, 1); gunnerButton.x = 280; gunnerButton.y = -70; LK.gui.bottomLeft.addChild(gunnerButton); var bomberButton = new Text2('Bomber (30)', { size: 30, fill: 0xFFFFFF }); bomberButton.anchor.set(0, 1); bomberButton.x = 540; bomberButton.y = -70; LK.gui.bottomLeft.addChild(bomberButton); var eliteSniperButton = new Text2('EliteSniper (50)', { size: 30, fill: 0xFFFFFF }); eliteSniperButton.anchor.set(0, 1); eliteSniperButton.x = 800; eliteSniperButton.y = -70; LK.gui.bottomLeft.addChild(eliteSniperButton); var heavyTankButton = new Text2('HeavyTank (60)', { size: 30, fill: 0xFFFFFF }); heavyTankButton.anchor.set(0, 1); heavyTankButton.x = 1060; heavyTankButton.y = -70; LK.gui.bottomLeft.addChild(heavyTankButton); // Row 3: Special troops var catcherButton = new Text2('Catcher (22)', { size: 30, fill: 0xFFFFFF }); catcherButton.anchor.set(0, 1); catcherButton.x = 20; catcherButton.y = -120; LK.gui.bottomLeft.addChild(catcherButton); var exploderButton = new Text2('Exploder (25)', { size: 30, fill: 0xFFFFFF }); exploderButton.anchor.set(0, 1); exploderButton.x = 280; exploderButton.y = -120; LK.gui.bottomLeft.addChild(exploderButton); var freezerButton = new Text2('Freezer (35)', { size: 30, fill: 0xFFFFFF }); freezerButton.anchor.set(0, 1); freezerButton.x = 540; freezerButton.y = -120; LK.gui.bottomLeft.addChild(freezerButton); var burnerButton = new Text2('Burner (40)', { size: 30, fill: 0xFFFFFF }); burnerButton.anchor.set(0, 1); burnerButton.x = 800; burnerButton.y = -120; LK.gui.bottomLeft.addChild(burnerButton); var flamethrowerButton = new Text2('Flamethrower (28)', { size: 30, fill: 0xFFFFFF }); flamethrowerButton.anchor.set(0, 1); flamethrowerButton.x = 1060; flamethrowerButton.y = -120; LK.gui.bottomLeft.addChild(flamethrowerButton); // Row 4: Advanced troops var freezerTroopButton = new Text2('FreezerTroop (25)', { size: 30, fill: 0xFFFFFF }); freezerTroopButton.anchor.set(0, 1); freezerTroopButton.x = 20; freezerTroopButton.y = -170; LK.gui.bottomLeft.addChild(freezerTroopButton); var shieldGeneratorButton = new Text2('ShieldGenerator (40)', { size: 30, fill: 0xFFFFFF }); shieldGeneratorButton.anchor.set(0, 1); shieldGeneratorButton.x = 330; shieldGeneratorButton.y = -170; LK.gui.bottomLeft.addChild(shieldGeneratorButton); var supportMedicButton = new Text2('SupportMedic (32)', { size: 30, fill: 0xFFFFFF }); supportMedicButton.anchor.set(0, 1); supportMedicButton.x = 640; supportMedicButton.y = -170; LK.gui.bottomLeft.addChild(supportMedicButton); var assassinButton = new Text2('Assassin (42)', { size: 30, fill: 0xFFFFFF }); assassinButton.anchor.set(0, 1); assassinButton.x = 950; assassinButton.y = -170; LK.gui.bottomLeft.addChild(assassinButton); var engineerButton = new Text2('Engineer (35)', { size: 30, fill: 0xFFFFFF }); engineerButton.anchor.set(0, 1); engineerButton.x = 1260; engineerButton.y = -170; LK.gui.bottomLeft.addChild(engineerButton); // Row 5: Utility troops var minerButton = new Text2('Miner (18)', { size: 30, fill: 0xFFFFFF }); minerButton.anchor.set(0, 1); minerButton.x = 20; minerButton.y = -220; LK.gui.bottomLeft.addChild(minerButton); var shockTrooperButton = new Text2('ShockTrooper (38)', { size: 30, fill: 0xFFFFFF }); shockTrooperButton.anchor.set(0, 1); shockTrooperButton.x = 320; shockTrooperButton.y = -220; LK.gui.bottomLeft.addChild(shockTrooperButton); var trapButton = new Text2('Trap (12)', { size: 30, fill: 0xFFFFFF }); trapButton.anchor.set(0, 1); trapButton.x = 620; trapButton.y = -220; LK.gui.bottomLeft.addChild(trapButton); function updateUI() { pointsText.setText('Points: ' + gamePoints); moneyText.setText('Money: ' + gamePoints); waveText.setText('Wave: ' + wave); } function snapToGrid(value) { return Math.floor(value / gridSize) * gridSize + gridSize / 2; } function canPlaceTower(x, y) { // Check if position is too close to city center var dx = x - cityCenter.x; var dy = y - cityCenter.y; var distanceToCenter = Math.sqrt(dx * dx + dy * dy); if (distanceToCenter < 150) { return false; } // Check if there's already a tower at this position for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (Math.abs(tower.x - x) < gridSize / 2 && Math.abs(tower.y - y) < gridSize / 2) { return false; } } // Check if there's already a barrier at this position for (var i = 0; i < barriers.length; i++) { var barrier = barriers[i]; if (Math.abs(barrier.x - x) < gridSize / 2 && Math.abs(barrier.y - y) < gridSize / 2) { return false; } } return true; } function placeTower(x, y) { if (gamePoints >= towerCost && canPlaceTower(x, y)) { var tower = new Tower(); tower.x = x; tower.y = y; towers.push(tower); game.addChild(tower); gamePoints -= towerCost; updateUI(); } } function placeBarrier(x, y) { if (gamePoints >= barrierCost && canPlaceTower(x, y)) { var barrier = new Barrier(); barrier.x = x; barrier.y = y; barriers.push(barrier); game.addChild(barrier); gamePoints -= barrierCost; updateUI(); } } function placeSoldier(x, y) { if (gamePoints >= soldierCost) { var soldier = new Soldier(); soldier.x = x; soldier.y = y; soldiers.push(soldier); game.addChild(soldier); gamePoints -= soldierCost; updateUI(); } } function placeGunner(x, y) { if (gamePoints >= gunnerCost) { var gunner = new Gunner(); gunner.x = x; gunner.y = y; gunners.push(gunner); game.addChild(gunner); gamePoints -= gunnerCost; updateUI(); } } function placeBomber(x, y) { if (gamePoints >= bomberCost) { var bomber = new Bomber(); bomber.x = x; bomber.y = y; bombers.push(bomber); game.addChild(bomber); gamePoints -= bomberCost; updateUI(); } } function placeMiner(x, y) { if (gamePoints >= minerCost) { var miner = new Miner(); miner.x = x; miner.y = y; miners.push(miner); game.addChild(miner); gamePoints -= minerCost; updateUI(); } } function placeCatcher(x, y) { if (gamePoints >= catcherCost) { var catcher = new Catcher(); catcher.x = x; catcher.y = y; catchers.push(catcher); game.addChild(catcher); gamePoints -= catcherCost; updateUI(); } } function placeExploder(x, y) { if (gamePoints >= exploderCost) { var exploder = new Exploder(); exploder.x = x; exploder.y = y; exploders.push(exploder); game.addChild(exploder); gamePoints -= exploderCost; updateUI(); } } function placeFreezer(x, y) { if (gamePoints >= freezerCost) { var freezer = new Freezer(); freezer.x = x; freezer.y = y; freezers.push(freezer); game.addChild(freezer); gamePoints -= freezerCost; updateUI(); } } function placeBurner(x, y) { if (gamePoints >= burnerCost) { var burner = new Burner(); burner.x = x; burner.y = y; burners.push(burner); game.addChild(burner); gamePoints -= burnerCost; updateUI(); } } function placeFlamethrower(x, y) { if (gamePoints >= flamethrowerCost) { var flamethrower = new Flamethrower(); flamethrower.x = x; flamethrower.y = y; flamethrowers.push(flamethrower); game.addChild(flamethrower); gamePoints -= flamethrowerCost; updateUI(); } } function placeFreezerTroop(x, y) { if (gamePoints >= freezerTroopCost) { var freezerTroop = new FreezerTroop(); freezerTroop.x = x; freezerTroop.y = y; freezerTroops.push(freezerTroop); game.addChild(freezerTroop); gamePoints -= freezerTroopCost; updateUI(); } } function placeEliteSniper(x, y) { if (gamePoints >= eliteSniperCost) { var eliteSniper = new EliteSniper(); eliteSniper.x = x; eliteSniper.y = y; eliteSnipers.push(eliteSniper); game.addChild(eliteSniper); gamePoints -= eliteSniperCost; updateUI(); } } function placeHeavyTank(x, y) { if (gamePoints >= heavyTankCost) { var heavyTank = new HeavyTank(); heavyTank.x = x; heavyTank.y = y; heavyTanks.push(heavyTank); game.addChild(heavyTank); gamePoints -= heavyTankCost; updateUI(); } } function placeLaserTurret(x, y) { if (gamePoints >= laserTurretCost && canPlaceTower(x, y)) { var laserTurret = new LaserTurret(); laserTurret.x = x; laserTurret.y = y; laserTurrets.push(laserTurret); game.addChild(laserTurret); gamePoints -= laserTurretCost; updateUI(); } } function placeRocketLauncher(x, y) { if (gamePoints >= rocketLauncherCost && canPlaceTower(x, y)) { var rocketLauncher = new RocketLauncher(); rocketLauncher.x = x; rocketLauncher.y = y; rocketLaunchers.push(rocketLauncher); game.addChild(rocketLauncher); gamePoints -= rocketLauncherCost; updateUI(); } } function placeShieldGenerator(x, y) { if (gamePoints >= shieldGeneratorCost && canPlaceTower(x, y)) { var shieldGenerator = new ShieldGenerator(); shieldGenerator.x = x; shieldGenerator.y = y; shieldGenerators.push(shieldGenerator); game.addChild(shieldGenerator); gamePoints -= shieldGeneratorCost; updateUI(); } } function placeSupportMedic(x, y) { if (gamePoints >= supportMedicCost) { var supportMedic = new SupportMedic(); supportMedic.x = x; supportMedic.y = y; supportMedics.push(supportMedic); game.addChild(supportMedic); gamePoints -= supportMedicCost; updateUI(); } } function placeAssassin(x, y) { if (gamePoints >= assassinCost) { var assassin = new Assassin(); assassin.x = x; assassin.y = y; assassins.push(assassin); game.addChild(assassin); gamePoints -= assassinCost; updateUI(); } } function placeEngineer(x, y) { if (gamePoints >= engineerCost) { var engineer = new Engineer(); engineer.x = x; engineer.y = y; engineers.push(engineer); game.addChild(engineer); gamePoints -= engineerCost; updateUI(); } } function placeMortar(x, y) { if (gamePoints >= mortarCost && canPlaceTower(x, y)) { var mortar = new Mortar(); mortar.x = x; mortar.y = y; mortars.push(mortar); game.addChild(mortar); gamePoints -= mortarCost; updateUI(); } } function placeShockTrooper(x, y) { if (gamePoints >= shockTrooperCost) { var shockTrooper = new ShockTrooper(); shockTrooper.x = x; shockTrooper.y = y; shockTroopers.push(shockTrooper); game.addChild(shockTrooper); gamePoints -= shockTrooperCost; updateUI(); } } function placeTrap(x, y) { if (gamePoints >= trapCost) { var trap = new Trap(); trap.x = x; trap.y = y; traps.push(trap); game.addChild(trap); gamePoints -= trapCost; updateUI(); } } function getTowerAt(x, y) { for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx = Math.abs(tower.x - x); var dy = Math.abs(tower.y - y); if (dx < gridSize / 2 && dy < gridSize / 2) { return tower; } } return null; } function updateButtonColors() { // Reset all button colors towerButton.tint = selectedMode === 'tower' ? 0x4CAF50 : 0xFFFFFF; barrierButton.tint = selectedMode === 'barrier' ? 0x4CAF50 : 0xFFFFFF; soldierButton.tint = selectedMode === 'soldier' ? 0x4CAF50 : 0xFFFFFF; gunnerButton.tint = selectedMode === 'gunner' ? 0x4CAF50 : 0xFFFFFF; bomberButton.tint = selectedMode === 'bomber' ? 0x4CAF50 : 0xFFFFFF; minerButton.tint = selectedMode === 'miner' ? 0x4CAF50 : 0xFFFFFF; catcherButton.tint = selectedMode === 'catcher' ? 0x4CAF50 : 0xFFFFFF; exploderButton.tint = selectedMode === 'exploder' ? 0x4CAF50 : 0xFFFFFF; freezerButton.tint = selectedMode === 'freezer' ? 0x4CAF50 : 0xFFFFFF; burnerButton.tint = selectedMode === 'burner' ? 0x4CAF50 : 0xFFFFFF; flamethrowerButton.tint = selectedMode === 'flamethrower' ? 0x4CAF50 : 0xFFFFFF; freezerTroopButton.tint = selectedMode === 'freezerTroop' ? 0x4CAF50 : 0xFFFFFF; eliteSniperButton.tint = selectedMode === 'eliteSniper' ? 0x4CAF50 : 0xFFFFFF; heavyTankButton.tint = selectedMode === 'heavyTank' ? 0x4CAF50 : 0xFFFFFF; laserTurretButton.tint = selectedMode === 'laserTurret' ? 0x4CAF50 : 0xFFFFFF; rocketLauncherButton.tint = selectedMode === 'rocketLauncher' ? 0x4CAF50 : 0xFFFFFF; shieldGeneratorButton.tint = selectedMode === 'shieldGenerator' ? 0x4CAF50 : 0xFFFFFF; supportMedicButton.tint = selectedMode === 'supportMedic' ? 0x4CAF50 : 0xFFFFFF; assassinButton.tint = selectedMode === 'assassin' ? 0x4CAF50 : 0xFFFFFF; engineerButton.tint = selectedMode === 'engineer' ? 0x4CAF50 : 0xFFFFFF; mortarButton.tint = selectedMode === 'mortar' ? 0x4CAF50 : 0xFFFFFF; shockTrooperButton.tint = selectedMode === 'shockTrooper' ? 0x4CAF50 : 0xFFFFFF; trapButton.tint = selectedMode === 'trap' ? 0x4CAF50 : 0xFFFFFF; } // Button event handlers towerButton.down = function () { selectedMode = 'tower'; updateButtonColors(); }; barrierButton.down = function () { selectedMode = 'barrier'; updateButtonColors(); }; soldierButton.down = function () { selectedMode = 'soldier'; updateButtonColors(); }; gunnerButton.down = function () { selectedMode = 'gunner'; updateButtonColors(); }; bomberButton.down = function () { selectedMode = 'bomber'; updateButtonColors(); }; minerButton.down = function () { selectedMode = 'miner'; updateButtonColors(); }; catcherButton.down = function () { selectedMode = 'catcher'; updateButtonColors(); }; exploderButton.down = function () { selectedMode = 'exploder'; updateButtonColors(); }; freezerButton.down = function () { selectedMode = 'freezer'; updateButtonColors(); }; burnerButton.down = function () { selectedMode = 'burner'; updateButtonColors(); }; flamethrowerButton.down = function () { selectedMode = 'flamethrower'; updateButtonColors(); }; freezerTroopButton.down = function () { selectedMode = 'freezerTroop'; updateButtonColors(); }; eliteSniperButton.down = function () { selectedMode = 'eliteSniper'; updateButtonColors(); }; heavyTankButton.down = function () { selectedMode = 'heavyTank'; updateButtonColors(); }; laserTurretButton.down = function () { selectedMode = 'laserTurret'; updateButtonColors(); }; rocketLauncherButton.down = function () { selectedMode = 'rocketLauncher'; updateButtonColors(); }; shieldGeneratorButton.down = function () { selectedMode = 'shieldGenerator'; updateButtonColors(); }; supportMedicButton.down = function () { selectedMode = 'supportMedic'; updateButtonColors(); }; assassinButton.down = function () { selectedMode = 'assassin'; updateButtonColors(); }; engineerButton.down = function () { selectedMode = 'engineer'; updateButtonColors(); }; mortarButton.down = function () { selectedMode = 'mortar'; updateButtonColors(); }; shockTrooperButton.down = function () { selectedMode = 'shockTrooper'; updateButtonColors(); }; trapButton.down = function () { selectedMode = 'trap'; updateButtonColors(); }; // Initialize button colors updateButtonColors(); function spawnZombie() { var zombie; // 6% chance to spawn a weakening zombie after wave 4 if (wave >= 4 && Math.random() < 0.06) { zombie = new WeakeningZombie(); } else if (wave >= 3 && Math.random() < 0.05) { // 5% chance to spawn an immunity zombie after wave 3 zombie = new ImmunityZombie(); } else if (wave >= 2 && Math.random() < 0.08) { // 8% chance to spawn a spawner zombie after wave 2 zombie = new SpawnerZombie(); } else if (wave >= 2 && Math.random() < 0.12) { // 12% chance to spawn a weaker zombie after wave 2 zombie = new WeakerZombie(); } else if (wave >= 2 && Math.random() < 0.15) { // 15% chance to spawn a booster zombie after wave 2 zombie = new BoosterZombie(); } else if (wave >= 1 && Math.random() < 0.25) { // 25% chance to spawn a speedy zombie from wave 1 zombie = new SpeedyZombie(); } else if (wave >= 1 && Math.random() < 0.55) { // 55% chance to spawn a tank zombie from wave 1 zombie = new TankZombie(); } else { zombie = new Zombie(); } // Spawn from random edge var edge = Math.floor(Math.random() * 4); switch (edge) { case 0: // Top zombie.x = Math.random() * 2048; zombie.y = 0; break; case 1: // Right zombie.x = 2048; zombie.y = Math.random() * 2732; break; case 2: // Bottom zombie.x = Math.random() * 2048; zombie.y = 2732; break; case 3: // Left zombie.x = 0; zombie.y = Math.random() * 2732; break; } // Increase speed and health with waves if (zombie instanceof WeakeningZombie) { zombie.speed = 0.9 + (wave - 1) * 0.07; // Weakening zombies moderate scaling zombie.health = 25 + Math.floor((wave - 1) / 2); zombie.maxHealth = zombie.health; zombie.weakenRate = Math.max(90, 120 - (wave - 1) * 3); // Weaken more frequently over time } else if (zombie instanceof ImmunityZombie) { zombie.speed = 0.7 + (wave - 1) * 0.06; // Immunity zombies moderate scaling zombie.health = 28 + Math.floor((wave - 1) / 2); zombie.maxHealth = zombie.health; zombie.immunityRate = Math.max(45, 60 - (wave - 1) * 2); // Grant immunity more frequently over time } else if (zombie instanceof SpawnerZombie) { zombie.speed = 0.8 + (wave - 1) * 0.05; // Spawner zombies scale slowest zombie.health = 25 + Math.floor((wave - 1) / 2); zombie.maxHealth = zombie.health; zombie.spawnRate = Math.max(180, 300 - (wave - 1) * 10); // Spawn faster over time } else if (zombie instanceof WeakerZombie) { zombie.speed = 0.8 + (wave - 1) * 0.07; // Weaker zombies moderate scaling zombie.health = 22 + Math.floor((wave - 1) / 2); zombie.maxHealth = zombie.health; zombie.weakenRate = Math.max(90, 120 - (wave - 1) * 3); // Weaken more frequently over time } else if (zombie instanceof BoosterZombie) { zombie.speed = 0.9 + (wave - 1) * 0.08; // Booster zombies moderate scaling zombie.health = 35 + Math.floor((wave - 1) / 2); zombie.maxHealth = zombie.health; zombie.boostRate = Math.max(120, 180 - (wave - 1) * 5); // Boost more frequently over time } else if (zombie instanceof SpeedyZombie) { zombie.speed = 2.5 + (wave - 1) * 0.15; // Speedy zombies get even faster zombie.health = 15 + Math.floor((wave - 1) / 2); zombie.maxHealth = zombie.health; } else if (zombie instanceof TankZombie) { zombie.speed = 0.6 + (wave - 1) * 0.1; // Tank zombies scale slower zombie.health = 20 + Math.floor((wave - 1) / 2); zombie.maxHealth = zombie.health; } else { zombie.speed = 1 + (wave - 1) * 0.2; zombie.health = 3 + Math.floor((wave - 1) / 3); zombie.maxHealth = zombie.health; } zombies.push(zombie); game.addChild(zombie); } function startNextWave() { wave++; zombiesToSpawn = 8 + wave * 4; zombiesSpawned = 0; waveComplete = false; spawnTimer = 0; updateUI(); } var holdTimer = 0; var isHolding = false; var holdPosition = { x: 0, y: 0 }; var lastTapTime = 0; var doubleTapDelay = 300; // milliseconds var selectedMode = 'tower'; // 'tower', 'barrier', 'soldier', or 'bomber' game.down = function (x, y, obj) { var gridX = snapToGrid(x); var gridY = snapToGrid(y); var existingTower = getTowerAt(gridX, gridY); if (existingTower) { // Try to upgrade tower existingTower.upgrade(); return; } // Place unit based on selected mode if (selectedMode === 'tower') { placeTower(gridX, gridY); } else if (selectedMode === 'barrier') { placeBarrier(gridX, gridY); } else if (selectedMode === 'soldier') { placeSoldier(x, y); } else if (selectedMode === 'gunner') { placeGunner(x, y); } else if (selectedMode === 'bomber') { placeBomber(x, y); } else if (selectedMode === 'miner') { placeMiner(x, y); } else if (selectedMode === 'catcher') { placeCatcher(x, y); } else if (selectedMode === 'exploder') { placeExploder(x, y); } else if (selectedMode === 'freezer') { placeFreezer(x, y); } else if (selectedMode === 'burner') { placeBurner(x, y); } else if (selectedMode === 'flamethrower') { placeFlamethrower(x, y); } else if (selectedMode === 'freezerTroop') { placeFreezerTroop(x, y); } else if (selectedMode === 'eliteSniper') { placeEliteSniper(x, y); } else if (selectedMode === 'heavyTank') { placeHeavyTank(x, y); } else if (selectedMode === 'laserTurret') { placeLaserTurret(gridX, gridY); } else if (selectedMode === 'rocketLauncher') { placeRocketLauncher(gridX, gridY); } else if (selectedMode === 'shieldGenerator') { placeShieldGenerator(gridX, gridY); } else if (selectedMode === 'supportMedic') { placeSupportMedic(x, y); } else if (selectedMode === 'assassin') { placeAssassin(x, y); } else if (selectedMode === 'engineer') { placeEngineer(x, y); } else if (selectedMode === 'mortar') { placeMortar(gridX, gridY); } else if (selectedMode === 'shockTrooper') { placeShockTrooper(x, y); } else if (selectedMode === 'trap') { placeTrap(x, y); } }; game.update = function () { // Spawn zombies for current wave if (!waveComplete) { spawnTimer++; if (spawnTimer >= 30 && zombiesSpawned < zombiesToSpawn) { // Spawn 1-2 zombies per cycle for reduced intensity var zombiesToSpawnNow = Math.min(1 + Math.floor(wave / 5), zombiesToSpawn - zombiesSpawned); for (var spawnCount = 0; spawnCount < zombiesToSpawnNow; spawnCount++) { spawnZombie(); zombiesSpawned++; if (zombiesSpawned >= zombiesToSpawn) break; } spawnTimer = 0; } if (zombiesSpawned >= zombiesToSpawn && zombies.length === 0) { waveComplete = true; LK.setTimeout(startNextWave, 2000); // Start next wave after 2 seconds } } // Check if zombies reached city center for (var i = zombies.length - 1; i >= 0; i--) { var zombie = zombies[i]; var dx = zombie.x - cityCenter.x; var dy = zombie.y - cityCenter.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 100) { // Game over LK.showGameOver(); return; } } // Update all towers (this enables shooting) for (var i = 0; i < towers.length; i++) { towers[i].update(); } // Update all barriers (this enables damage dealing) for (var i = 0; i < barriers.length; i++) { barriers[i].update(); } // Update all soldiers (this enables movement and shooting) for (var i = 0; i < soldiers.length; i++) { soldiers[i].update(); } // Update all gunners (this enables movement and fast shooting) for (var i = 0; i < gunners.length; i++) { gunners[i].update(); } // Update all bombers (this enables movement and bomb launching) for (var i = 0; i < bombers.length; i++) { bombers[i].update(); } // Update all bombs (this enables movement and explosion) for (var i = 0; i < bombs.length; i++) { bombs[i].update(); } // Update all miners (this enables money generation) for (var i = 0; i < miners.length; i++) { miners[i].update(); } // Update all catchers (this enables zombie freezing) for (var i = 0; i < catchers.length; i++) { catchers[i].update(); } // Update all exploders (this enables explosion mechanics) for (var i = 0; i < exploders.length; i++) { exploders[i].update(); } // Update all freezers (this enables global freeze mechanics) for (var i = 0; i < freezers.length; i++) { freezers[i].update(); } // Update all burners (this enables global burn mechanics) for (var i = 0; i < burners.length; i++) { burners[i].update(); } // Update all flamethrowers (this enables continuous burn mechanics) for (var i = 0; i < flamethrowers.length; i++) { flamethrowers[i].update(); } // Update all freezer troops (this enables continuous freeze mechanics) for (var i = 0; i < freezerTroops.length; i++) { freezerTroops[i].update(); } // Update all elite snipers (this enables high-damage sniping) for (var i = 0; i < eliteSnipers.length; i++) { eliteSnipers[i].update(); } // Update all heavy tanks (this enables armored combat) for (var i = 0; i < heavyTanks.length; i++) { heavyTanks[i].update(); } // Update all laser turrets (this enables continuous beam damage) for (var i = 0; i < laserTurrets.length; i++) { laserTurrets[i].update(); } // Update all rocket launchers (this enables area damage rockets) for (var i = 0; i < rocketLaunchers.length; i++) { rocketLaunchers[i].update(); } // Update all rockets (this enables rocket movement and explosion) for (var i = 0; i < rockets.length; i++) { rockets[i].update(); } // Update all shield generators (this enables defensive support) for (var i = 0; i < shieldGenerators.length; i++) { shieldGenerators[i].update(); } // Update all support medics (this enables healing) for (var i = 0; i < supportMedics.length; i++) { supportMedics[i].update(); } // Update all assassins (this enables stealth attacks) for (var i = 0; i < assassins.length; i++) { assassins[i].update(); } // Update all engineers (this enables repair mechanics) for (var i = 0; i < engineers.length; i++) { engineers[i].update(); } // Update all mortars (this enables artillery strikes) for (var i = 0; i < mortars.length; i++) { mortars[i].update(); } // Update all shock troopers (this enables electrical attacks) for (var i = 0; i < shockTroopers.length; i++) { shockTroopers[i].update(); } // Update all traps (this enables zombie trapping) for (var i = 0; i < traps.length; i++) { traps[i].update(); } // Update all fire tiles (this enables fire damage) for (var i = 0; i < fireTiles.length; i++) { fireTiles[i].update(); } // Update score LK.setScore(gamePoints + (wave - 1) * 100); }; // Pause information text var pauseInfoText = new Text2('', { size: 40, fill: 0xFFFFFF }); pauseInfoText.anchor.set(0.5, 0.5); pauseInfoText.x = 1024; pauseInfoText.y = 1366; pauseInfoText.alpha = 0; game.addChild(pauseInfoText); // Show troop information when game is paused LK.on('pause', function () { var infoText = 'TROOP ABILITIES:\n\n'; infoText += 'TOWER (20): Shoots bullets at zombies\n'; infoText += 'BARRIER (30): Blocks zombies, takes damage\n'; infoText += 'SOLDIER (40): Patrols and shoots at zombies\n'; infoText += 'GUNNER (35): Fast shooting, lower damage\n'; infoText += 'BOMBER (60): Launches explosive bombs\n'; infoText += 'MINER (35): Generates money over time\n'; infoText += 'CATCHER (45): Freezes zombies in range\n'; infoText += 'EXPLODER (50): Explodes near zombies, creates fire\n'; infoText += 'FREEZER (70): Global freeze explosion\n'; infoText += 'BURNER (80): Global burn explosion\n'; infoText += 'FLAMETHROWER (55): Burns zombies continuously\n'; infoText += 'FREEZER TROOP (50): Freezes with breathing\n'; infoText += 'ELITE SNIPER (100): High damage, long range\n'; infoText += 'HEAVY TANK (120): Armored, high health\n'; infoText += 'LASER TURRET (90): Continuous beam damage\n'; infoText += 'ROCKET LAUNCHER (110): Area damage rockets\n'; infoText += 'SHIELD GENERATOR (80): Protects nearby troops\n'; infoText += 'SUPPORT MEDIC (65): Heals nearby troops\n'; infoText += 'ASSASSIN (85): Stealth high-damage attacks\n'; infoText += 'ENGINEER (70): Repairs structures and tanks\n'; infoText += 'MORTAR (130): Long-range artillery strikes\n'; infoText += 'SHOCK TROOPER (75): Electrical area damage\n'; infoText += 'TRAP (25): Single-use zombie trap'; pauseInfoText.setText(infoText); pauseInfoText.alpha = 1; }); // Hide troop information when game is resumed LK.on('resume', function () { pauseInfoText.alpha = 0; }); // Start first wave after 3 seconds LK.setTimeout(startNextWave, 3000); ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Assassin = Container.expand(function () {
var self = Container.call(this);
var assassinGraphics = self.attachAsset('assassin', {
anchorX: 0.5,
anchorY: 0.5
});
assassinGraphics.tint = 0x2F4F4F; // Dark slate gray tint for assassins
self.range = 80; // Short range
self.attackCooldown = 0;
self.attackRate = 180; // Slow but deadly attacks
self.speed = 1.5; // Fast movement
self.patrolTarget = null;
self.mode = 'patrol';
self.isStealthed = false;
self.stealthTimer = 0;
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.speed = self.originalSpeed;
}
}
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
// Stealth mechanics
if (self.isStealthed) {
self.stealthTimer--;
if (self.stealthTimer <= 0) {
self.isStealthed = false;
assassinGraphics.alpha = 1.0;
}
}
// Decrease attack cooldown
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && self.attackCooldown === 0) {
self.mode = 'combat';
self.assassinate(nearestZombie);
self.attackCooldown = self.attackRate;
} else {
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
};
self.assassinate = function (target) {
// Enter stealth after attack
self.isStealthed = true;
self.stealthTimer = 120; // 2 seconds of stealth
assassinGraphics.alpha = 0.3;
// Deal massive damage
var damage = 8;
if (self.isDamageWeakened) {
damage = damage * 0.5;
}
target.takeDamage(damage);
// Visual effect
LK.effects.flashObject(self, 0x2F4F4F, 300);
LK.effects.flashObject(target, 0xFF0000, 500);
LK.getSound('shoot').play();
};
self.setNewPatrolTarget = function () {
var angle = Math.random() * Math.PI * 2;
var radius = 150 + Math.random() * 250;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var Barrier = Container.expand(function () {
var self = Container.call(this);
var barrierGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
barrierGraphics.tint = 0x8B4513; // Brown tint for barriers
self.health = 100; // Very high resistance
self.maxHealth = 100;
self.lastIntersecting = {};
self.update = function () {
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var zombieId = zombies.indexOf(zombie);
// Initialize last intersecting state if not exists
if (self.lastIntersecting[zombieId] === undefined) {
self.lastIntersecting[zombieId] = false;
}
var currentIntersecting = self.intersects(zombie);
// Check if zombie just started intersecting
if (!self.lastIntersecting[zombieId] && currentIntersecting) {
// Damage zombie
zombie.takeDamage();
// Take damage to barrier
self.health -= 5;
// Update barrier appearance based on health
var healthPercent = self.health / self.maxHealth;
if (healthPercent > 0.7) {
barrierGraphics.tint = 0x8B4513; // Brown
} else if (healthPercent > 0.4) {
barrierGraphics.tint = 0xFF8C00; // Orange
} else {
barrierGraphics.tint = 0xFF0000; // Red
}
// Destroy barrier if health reaches 0
if (self.health <= 0) {
self.destroy();
var index = barriers.indexOf(self);
if (index > -1) {
barriers.splice(index, 1);
}
return;
}
}
// Update last intersecting state
self.lastIntersecting[zombieId] = currentIntersecting;
}
};
return self;
});
var Bomb = Container.expand(function () {
var self = Container.call(this);
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.target = null;
self.exploded = false;
self.update = function () {
if (self.exploded) return;
if (!self.target || zombies.indexOf(self.target) === -1) {
self.explode();
return;
}
// Move toward target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 15) {
// Hit target area
self.explode();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.explode = function () {
if (self.exploded) return;
self.exploded = true;
// Play explosion sound
LK.getSound('bombExplode').play();
// Damage all zombies in explosion radius
var explosionRadius = 80;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRadius) {
var damage = 1;
// Check if any bomber that could have fired this bomb is weakened
for (var j = 0; j < bombers.length; j++) {
var bomber = bombers[j];
if (bomber.isDamageWeakened) {
damage = 0.5; // Reduced damage
break;
}
}
zombie.takeDamage(damage);
zombie.takeDamage(damage); // Bombs do double damage
}
}
// Create explosion effect
LK.effects.flashObject(self, 0xff4444, 200);
// Remove bomb
self.destroy();
var index = bombs.indexOf(self);
if (index > -1) {
bombs.splice(index, 1);
}
};
return self;
});
var Bomber = Container.expand(function () {
var self = Container.call(this);
var bomberGraphics = self.attachAsset('bomber', {
anchorX: 0.5,
anchorY: 0.5
});
bomberGraphics.tint = 0xff6600; // Orange tint for bombers
self.range = 180;
self.shootCooldown = 0;
self.shootRate = 90; // slower than soldiers (more frames between shots)
self.speed = 0.6;
self.patrolTarget = null;
self.mode = 'patrol';
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.shootRate = self.originalShootRate;
}
}
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
// Decrease shoot cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && self.shootCooldown === 0) {
// Combat mode - launch bomb at zombie
self.mode = 'combat';
self.launchBomb(nearestZombie);
self.shootCooldown = self.shootRate;
} else {
// Patrol mode - move around
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
};
self.launchBomb = function (target) {
var bomb = new Bomb();
bomb.x = self.x;
bomb.y = self.y;
bomb.target = target;
bombs.push(bomb);
game.addChild(bomb);
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 250 + Math.random() * 350;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var BoosterZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('boosterZombie', {
anchorX: 0.5,
anchorY: 0.5
});
zombieGraphics.tint = 0xFFD700; // Gold tint for booster zombies
self.speed = 0.9;
self.health = 20;
self.maxHealth = 20;
self.targetX = cityCenter.x;
self.targetY = cityCenter.y;
self.boostTimer = 0;
self.boostRate = 180; // Boost zombies every 3 seconds (180 frames)
self.boostRadius = 120; // Radius to boost other zombies
// Create health bar
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5,
x: -29,
y: -50
});
self.update = function () {
// Handle frozen state
if (self.isFrozen) {
self.frozenTimer--;
if (self.frozenTimer <= 0) {
self.isFrozen = false;
self.speed = self.originalSpeed;
tween(zombieGraphics, {
tint: 0xFFD700
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Handle burning state
if (self.isBurning) {
self.burnTimer--;
self.burnDamageTimer++;
// Deal burn damage every 30 frames (0.5 seconds)
if (self.burnDamageTimer >= 30) {
self.takeDamage(0.5); // Burn damage
self.burnDamageTimer = 0;
LK.effects.flashObject(self, 0xFF4500, 100);
}
if (self.burnTimer <= 0) {
self.isBurning = false;
tween(zombieGraphics, {
tint: 0xFFD700
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Move toward city center (only if not frozen)
if (!self.isFrozen) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
// Update visual based on health
var healthPercent = self.health / self.maxHealth;
if (healthPercent > 0.7) {
zombieGraphics.tint = 0xFFD700; // Gold
} else if (healthPercent > 0.4) {
zombieGraphics.tint = 0xFF8C00; // Orange
} else {
zombieGraphics.tint = 0xFF0000; // Red
}
// Update health bar
healthBarFill.width = 58 * healthPercent;
if (healthPercent > 0.7) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
// Boost nearby zombies periodically
self.boostTimer++;
if (self.boostTimer >= self.boostRate) {
self.boostNearbyZombies();
self.boostTimer = 0;
}
};
self.boostNearbyZombies = function () {
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie === self) continue; // Don't boost self
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.boostRadius) {
// Increase zombie speed by 30%
zombie.speed *= 1.3;
// Visual effect when boosting
LK.effects.flashObject(zombie, 0xFFD700, 300);
}
}
// Visual effect when boosting others
LK.effects.flashObject(self, 0xFFD700, 500);
};
self.takeDamage = function (damage) {
if (damage === undefined) damage = 1;
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('zombieHit').play();
gamePoints += 12; // Moderate points for killing booster zombies
updateUI();
// Remove from zombies array
var index = zombies.indexOf(self);
if (index > -1) {
zombies.splice(index, 1);
}
self.destroy();
};
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.target = null;
self.update = function () {
if (!self.target || zombies.indexOf(self.target) === -1) {
self.destroy();
var index = bullets.indexOf(self);
if (index > -1) {
bullets.splice(index, 1);
}
return;
}
// Move toward target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
// Hit target with reduced damage if weakened
var damage = 1;
// Check if any tower that could have fired this bullet is weakened
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.isDamageWeakened) {
damage = 0.5; // Reduced damage
break;
}
}
// Check if any soldier that could have fired this bullet is weakened
for (var i = 0; i < soldiers.length; i++) {
var soldier = soldiers[i];
if (soldier.isDamageWeakened) {
damage = 0.5; // Reduced damage
break;
}
}
self.target.takeDamage(damage);
self.destroy();
var index = bullets.indexOf(self);
if (index > -1) {
bullets.splice(index, 1);
}
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
return self;
});
var Burner = Container.expand(function () {
var self = Container.call(this);
var burnerGraphics = self.attachAsset('burner', {
anchorX: 0.5,
anchorY: 0.5
});
burnerGraphics.tint = 0xFF4500; // Orange red tint for burners
self.range = 120;
self.explodeRange = 999999; // Global burn effect
self.speed = 1.0;
self.patrolTarget = null;
self.mode = 'patrol';
self.hasExploded = false;
self.explodeTimer = 0;
self.explodeDelay = 60; // 1 second delay before explosion
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.speed = self.originalSpeed;
}
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && !self.hasExploded) {
// Start explosion sequence
self.mode = 'exploding';
self.hasExploded = true;
self.explodeTimer = self.explodeDelay;
// Visual indication of incoming explosion
tween(burnerGraphics, {
tint: 0xFF0000,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeOut
});
LK.effects.flashObject(self, 0xFF4500, 200);
} else if (!self.hasExploded) {
// Patrol mode - move around
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
// Handle explosion countdown
if (self.hasExploded) {
self.explodeTimer--;
if (self.explodeTimer <= 0) {
self.explode();
}
}
};
self.explode = function () {
// Burn ALL zombies - deal continuous damage over time
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
// Skip immune zombies
if (zombie.isImmune) continue;
// Mark zombie as burning
zombie.isBurning = true;
zombie.burnTimer = 300; // Burn for 5 seconds (300 frames at 60fps)
zombie.burnDamageTimer = 0; // Initialize burn damage timer
// Visual effect when burning
tween(zombie, {
tint: 0xFF4500
}, {
duration: 300,
easing: tween.easeOut
});
LK.effects.flashObject(zombie, 0xFF4500, 300);
}
// Global burn visual effect
LK.effects.flashScreen(0xFF4500, 500);
LK.getSound('bombExplode').play();
// Remove burner
self.destroy();
var index = burners.indexOf(self);
if (index > -1) {
burners.splice(index, 1);
}
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 180 + Math.random() * 270;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var Catcher = Container.expand(function () {
var self = Container.call(this);
var catcherGraphics = self.attachAsset('catcher', {
anchorX: 0.5,
anchorY: 0.5
});
catcherGraphics.tint = 0x00BFFF; // Deep sky blue tint for catchers
self.range = 140;
self.freezeCooldown = 0;
self.freezeRate = 120; // Freeze every 2 seconds (120 frames)
self.speed = 0.9;
self.patrolTarget = null;
self.mode = 'patrol';
self.update = function () {
// Decrease freeze cooldown
if (self.freezeCooldown > 0) {
self.freezeCooldown--;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance && !zombie.isFrozen) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && self.freezeCooldown === 0) {
// Combat mode - freeze zombie
self.mode = 'combat';
self.freezeZombie(nearestZombie);
self.freezeCooldown = self.freezeRate;
} else {
// Patrol mode - move around
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
};
self.freezeZombie = function (zombie) {
if (zombie.isFrozen || zombie.isImmune || zombie.cannotBeFrozen) return;
// Mark zombie as frozen
zombie.isFrozen = true;
zombie.frozenTimer = 180; // Freeze for 3 seconds (180 frames)
zombie.originalSpeed = zombie.speed;
zombie.speed = 0; // Stop movement
// Visual effect when freezing
LK.getSound('freeze').play();
tween(zombie, {
tint: 0x87CEEB
}, {
duration: 300,
easing: tween.easeOut
});
LK.effects.flashObject(zombie, 0x00BFFF, 300);
LK.effects.flashObject(self, 0x00BFFF, 200);
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 200 + Math.random() * 300;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var EliteSniper = Container.expand(function () {
var self = Container.call(this);
var sniperGraphics = self.attachAsset('eliteSniper', {
anchorX: 0.5,
anchorY: 0.5
});
sniperGraphics.tint = 0x8B0000; // Dark red tint for elite snipers
self.range = 500; // Very long range
self.shootCooldown = 0;
self.shootRate = 120; // Slow but powerful shots
self.speed = 0.6; // Slow movement
self.patrolTarget = null;
self.mode = 'patrol';
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.shootRate = self.originalShootRate;
}
}
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
// Decrease shoot cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && self.shootCooldown === 0) {
// Combat mode - snipe zombie
self.mode = 'combat';
self.snipe(nearestZombie);
self.shootCooldown = self.shootRate;
} else {
// Patrol mode - move around
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
};
self.snipe = function (target) {
// Instant hit with high damage
var damage = 5; // Very high damage
if (self.isDamageWeakened) {
damage = damage * 0.5; // Reduced damage
}
target.takeDamage(damage);
// Visual effect
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
}
});
LK.getSound('shoot').play();
LK.effects.flashObject(self, 0x8B0000, 300);
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 250 + Math.random() * 350;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var Engineer = Container.expand(function () {
var self = Container.call(this);
var engineerGraphics = self.attachAsset('engineer', {
anchorX: 0.5,
anchorY: 0.5
});
engineerGraphics.tint = 0xFFD700; // Gold tint for engineers
self.range = 100;
self.buildCooldown = 0;
self.buildRate = 240; // Build every 4 seconds
self.speed = 0.7;
self.patrolTarget = null;
self.mode = 'patrol';
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.speed = self.originalSpeed;
}
}
// Decrease build cooldown
if (self.buildCooldown > 0) {
self.buildCooldown--;
}
// Repair nearby barriers and towers
if (self.buildCooldown === 0) {
self.repairNearbyStructures();
self.buildCooldown = self.buildRate;
}
// Patrol behavior
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
};
self.repairNearbyStructures = function () {
var repairedAny = false;
// Repair barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
if (barrier.health >= barrier.maxHealth) continue;
var dx = barrier.x - self.x;
var dy = barrier.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
barrier.health = Math.min(barrier.maxHealth, barrier.health + 25);
LK.effects.flashObject(barrier, 0xFFD700, 300);
repairedAny = true;
}
}
// Repair heavy tanks
for (var i = 0; i < heavyTanks.length; i++) {
var tank = heavyTanks[i];
if (tank.health >= tank.maxHealth) continue;
var dx = tank.x - self.x;
var dy = tank.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
tank.health = Math.min(tank.maxHealth, tank.health + 20);
LK.effects.flashObject(tank, 0xFFD700, 300);
repairedAny = true;
}
}
if (repairedAny) {
LK.effects.flashObject(self, 0xFFD700, 500);
}
};
self.setNewPatrolTarget = function () {
var angle = Math.random() * Math.PI * 2;
var radius = 200 + Math.random() * 300;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var Exploder = Container.expand(function () {
var self = Container.call(this);
var exploderGraphics = self.attachAsset('exploder', {
anchorX: 0.5,
anchorY: 0.5
});
exploderGraphics.tint = 0xff6600; // Orange-red tint for exploders
self.range = 100;
self.explodeRange = 120; // Range for explosion damage
self.speed = 1.2;
self.patrolTarget = null;
self.mode = 'patrol';
self.hasExploded = false;
self.explodeTimer = 0;
self.explodeDelay = 60; // 1 second delay before explosion
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.speed = self.originalSpeed;
}
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && !self.hasExploded) {
// Start explosion sequence
self.mode = 'exploding';
self.hasExploded = true;
self.explodeTimer = self.explodeDelay;
// Visual indication of incoming explosion
tween(exploderGraphics, {
tint: 0xff0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 1000,
easing: tween.easeOut
});
LK.effects.flashObject(self, 0xff6600, 200);
} else if (!self.hasExploded) {
// Patrol mode - move around
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
// Handle explosion countdown
if (self.hasExploded) {
self.explodeTimer--;
if (self.explodeTimer <= 0) {
self.explode();
}
}
};
self.explode = function () {
// Damage all zombies in explosion radius
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.explodeRange) {
zombie.takeDamage(3); // High explosion damage
}
}
// Create fire tile at explosion location
var fireTile = new FireTile();
fireTile.x = self.x;
fireTile.y = self.y;
fireTiles.push(fireTile);
game.addChild(fireTile);
// Explosion visual effect
LK.effects.flashScreen(0xff4400, 300);
LK.getSound('bombExplode').play();
// Remove exploder
self.destroy();
var index = exploders.indexOf(self);
if (index > -1) {
exploders.splice(index, 1);
}
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 150 + Math.random() * 250;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var FastBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.tint = 0xff9900; // Orange tint for fast bullets
self.speed = 10;
self.target = null;
self.damage = 0.5; // Low damage
self.update = function () {
if (!self.target || zombies.indexOf(self.target) === -1) {
self.destroy();
var index = fastBullets.indexOf(self);
if (index > -1) {
fastBullets.splice(index, 1);
}
return;
}
// Move toward target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
// Hit target with reduced damage
var damage = self.damage;
// Check if any gunner that could have fired this bullet is weakened
for (var i = 0; i < gunners.length; i++) {
var gunner = gunners[i];
if (gunner.isDamageWeakened) {
damage = damage * 0.5; // Reduced damage
break;
}
}
self.target.takeDamage(damage);
self.destroy();
var index = fastBullets.indexOf(self);
if (index > -1) {
fastBullets.splice(index, 1);
}
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
return self;
});
var FireTile = Container.expand(function () {
var self = Container.call(this);
var fireGraphics = self.attachAsset('fireTile', {
anchorX: 0.5,
anchorY: 0.5
});
fireGraphics.alpha = 0.8;
self.duration = 600; // Fire lasts for 10 seconds (600 frames)
self.damageTimer = 0;
self.damageRate = 30; // Deal damage every 0.5 seconds (30 frames)
self.lastIntersecting = {};
self.update = function () {
// Decrease duration
self.duration--;
if (self.duration <= 0) {
self.destroy();
var index = fireTiles.indexOf(self);
if (index > -1) {
fireTiles.splice(index, 1);
}
return;
}
// Fade out over time
var fadePercent = self.duration / 600;
fireGraphics.alpha = 0.8 * fadePercent;
// Damage timer
self.damageTimer++;
if (self.damageTimer >= self.damageRate) {
self.damageTimer = 0;
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var zombieId = zombies.indexOf(zombie);
// Initialize last intersecting state if not exists
if (self.lastIntersecting[zombieId] === undefined) {
self.lastIntersecting[zombieId] = false;
}
var currentIntersecting = self.intersects(zombie);
// Deal damage if zombie is on fire tile
if (currentIntersecting) {
zombie.takeDamage(0.5); // Fire deals moderate damage
// Visual effect when burning
LK.effects.flashObject(zombie, 0xff4400, 200);
}
// Update last intersecting state
self.lastIntersecting[zombieId] = currentIntersecting;
}
}
// Flickering fire effect
if (LK.ticks % 10 === 0) {
tween(fireGraphics, {
scaleX: 0.9 + Math.random() * 0.2,
scaleY: 0.9 + Math.random() * 0.2
}, {
duration: 200,
easing: tween.easeOut
});
}
};
return self;
});
var Flamethrower = Container.expand(function () {
var self = Container.call(this);
var flamethrowerGraphics = self.attachAsset('flamethrower', {
anchorX: 0.5,
anchorY: 0.5
});
flamethrowerGraphics.tint = 0xFF6600; // Orange tint for flamethrowers
self.range = 140;
self.burnCooldown = 0;
self.burnRate = 60; // Burn every 1 second (60 frames)
self.speed = 0.8;
self.patrolTarget = null;
self.mode = 'patrol';
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.burnRate = self.originalBurnRate;
}
}
// Decrease burn cooldown
if (self.burnCooldown > 0) {
self.burnCooldown--;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance && !zombie.isBurning) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && self.burnCooldown === 0) {
// Combat mode - burn zombie
self.mode = 'combat';
self.burnZombie(nearestZombie);
self.burnCooldown = self.burnRate;
} else {
// Patrol mode - move around
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
};
self.burnZombie = function (zombie) {
if (zombie.isBurning || zombie.isImmune) return;
// Mark zombie as burning
zombie.isBurning = true;
zombie.burnTimer = 300; // Burn for 5 seconds (300 frames)
zombie.burnDamageTimer = 0; // Initialize burn damage timer
// Visual effect when burning
tween(zombie, {
tint: 0xFF4500
}, {
duration: 300,
easing: tween.easeOut
});
LK.effects.flashObject(zombie, 0xFF6600, 300);
LK.effects.flashObject(self, 0xFF6600, 200);
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 200 + Math.random() * 300;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var Freezer = Container.expand(function () {
var self = Container.call(this);
var freezerGraphics = self.attachAsset('freezer', {
anchorX: 0.5,
anchorY: 0.5
});
freezerGraphics.tint = 0x00FFFF; // Cyan tint for freezers
self.range = 120;
self.explodeRange = 999999; // Global freeze effect
self.speed = 1.0;
self.patrolTarget = null;
self.mode = 'patrol';
self.hasExploded = false;
self.explodeTimer = 0;
self.explodeDelay = 60; // 1 second delay before explosion
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.speed = self.originalSpeed;
}
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && !self.hasExploded) {
// Start explosion sequence
self.mode = 'exploding';
self.hasExploded = true;
self.explodeTimer = self.explodeDelay;
// Visual indication of incoming explosion
tween(freezerGraphics, {
tint: 0x00FFFF,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeOut
});
LK.effects.flashObject(self, 0x00FFFF, 200);
} else if (!self.hasExploded) {
// Patrol mode - move around
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
// Handle explosion countdown
if (self.hasExploded) {
self.explodeTimer--;
if (self.explodeTimer <= 0) {
self.explode();
}
}
};
self.explode = function () {
// Freeze ALL zombies for 6.8 seconds (408 frames at 60fps)
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
// Skip immune zombies and speedy zombies
if (zombie.isImmune || zombie.cannotBeFrozen) continue;
// Mark zombie as frozen
zombie.isFrozen = true;
zombie.frozenTimer = 408; // 6.8 seconds at 60fps
if (!zombie.originalSpeed) {
zombie.originalSpeed = zombie.speed;
}
zombie.speed = 0; // Stop movement
// Visual effect when freezing
tween(zombie, {
tint: 0x87CEEB
}, {
duration: 300,
easing: tween.easeOut
});
LK.effects.flashObject(zombie, 0x00FFFF, 300);
}
// Global freeze visual effect
LK.effects.flashScreen(0x00FFFF, 500);
LK.getSound('freeze').play();
// Remove freezer
self.destroy();
var index = freezers.indexOf(self);
if (index > -1) {
freezers.splice(index, 1);
}
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 180 + Math.random() * 270;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var FreezerTroop = Container.expand(function () {
var self = Container.call(this);
var freezerTroopGraphics = self.attachAsset('freezerTroop', {
anchorX: 0.5,
anchorY: 0.5
});
freezerTroopGraphics.tint = 0x87CEEB; // Sky blue tint for freezer troops
self.range = 160; // Breathing range
self.freezeCooldown = 0;
self.freezeRate = 30; // Continuous freezing every 0.5 seconds
self.speed = 1.0;
self.patrolTarget = null;
self.mode = 'patrol';
self.breathingScale = 1.0;
self.breathingDirection = 1;
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.speed = self.originalSpeed;
}
}
// Breathing animation
self.breathingScale += self.breathingDirection * 0.01;
if (self.breathingScale >= 1.1) {
self.breathingDirection = -1;
} else if (self.breathingScale <= 0.9) {
self.breathingDirection = 1;
}
freezerTroopGraphics.scaleX = self.breathingScale;
freezerTroopGraphics.scaleY = self.breathingScale;
// Decrease freeze cooldown
if (self.freezeCooldown > 0) {
self.freezeCooldown--;
}
// Continuous freezing with breathing
if (self.freezeCooldown === 0) {
self.freezeZombiesWithBreathing();
self.freezeCooldown = self.freezeRate;
}
// Patrol behavior
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
};
self.freezeZombiesWithBreathing = function () {
var frozenAny = false;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && !zombie.isFrozen && !zombie.isImmune && !zombie.cannotBeFrozen) {
// Freeze zombie with breathing
zombie.isFrozen = true;
zombie.frozenTimer = 120; // Freeze for 2 seconds
zombie.originalSpeed = zombie.speed;
zombie.speed = 0; // Stop movement
// Visual effect when freezing
tween(zombie, {
tint: 0x87CEEB
}, {
duration: 300,
easing: tween.easeOut
});
LK.effects.flashObject(zombie, 0x87CEEB, 300);
frozenAny = true;
}
}
// Visual breathing effect when freezing
if (frozenAny) {
LK.getSound('freeze').play();
tween(freezerTroopGraphics, {
tint: 0x00FFFF
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(freezerTroopGraphics, {
tint: 0x87CEEB
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 200 + Math.random() * 300;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var Gunner = Container.expand(function () {
var self = Container.call(this);
var gunnerGraphics = self.attachAsset('gunner', {
anchorX: 0.5,
anchorY: 0.5
});
gunnerGraphics.tint = 0xffa500; // Orange tint for gunners
self.range = 100;
self.shootCooldown = 0;
self.shootRate = 8; // Very fast shooting (much faster than soldiers)
self.speed = 1.0;
self.patrolTarget = null;
self.mode = 'patrol';
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.shootRate = self.originalShootRate;
}
}
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
// Decrease shoot cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && self.shootCooldown === 0) {
// Combat mode - shoot at zombie
self.mode = 'combat';
self.shoot(nearestZombie);
self.shootCooldown = self.shootRate;
} else {
// Patrol mode - move around
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
};
self.shoot = function (target) {
var bullet = new FastBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = target;
fastBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 180 + Math.random() * 280;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var HeavyTank = Container.expand(function () {
var self = Container.call(this);
var tankGraphics = self.attachAsset('heavyTank', {
anchorX: 0.5,
anchorY: 0.5
});
tankGraphics.tint = 0x556B2F; // Dark olive green tint for heavy tanks
self.range = 200;
self.shootCooldown = 0;
self.shootRate = 90; // Moderate shooting rate
self.speed = 0.4; // Very slow movement
self.patrolTarget = null;
self.mode = 'patrol';
self.health = 150; // Very high health
self.maxHealth = 150;
self.armor = 0.5; // 50% damage reduction
// Create health bar
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5,
x: -29,
y: -50
});
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.shootRate = self.originalShootRate;
}
}
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
// Decrease shoot cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && self.shootCooldown === 0) {
// Combat mode - shoot at zombie
self.mode = 'combat';
self.shoot(nearestZombie);
self.shootCooldown = self.shootRate;
} else {
// Patrol mode - move around
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
// Update health bar
var healthPercent = self.health / self.maxHealth;
healthBarFill.width = 58 * healthPercent;
if (healthPercent > 0.7) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
};
self.shoot = function (target) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = target;
bullet.damage = 2; // Higher damage
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.takeDamage = function (damage) {
if (damage === undefined) damage = 1;
// Apply armor reduction
damage = damage * self.armor;
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Remove from array
var index = heavyTanks.indexOf(self);
if (index > -1) {
heavyTanks.splice(index, 1);
}
self.destroy();
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 200 + Math.random() * 300;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var ImmunityZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('immunityZombie', {
anchorX: 0.5,
anchorY: 0.5
});
zombieGraphics.tint = 0x9932CC; // Dark orchid tint for immunity zombies
self.speed = 0.7;
self.health = 28;
self.maxHealth = 28;
self.targetX = cityCenter.x;
self.targetY = cityCenter.y;
self.immunityTimer = 0;
self.immunityRate = 60; // Provide immunity every 1 second (60 frames)
self.immunityRadius = 200; // Radius to provide immunity to other zombies
// Create health bar
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5,
x: -29,
y: -50
});
self.update = function () {
// Handle frozen state
if (self.isFrozen) {
self.frozenTimer--;
if (self.frozenTimer <= 0) {
self.isFrozen = false;
self.speed = self.originalSpeed;
tween(zombieGraphics, {
tint: 0x9932CC
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Handle burning state
if (self.isBurning) {
self.burnTimer--;
self.burnDamageTimer++;
// Deal burn damage every 30 frames (0.5 seconds)
if (self.burnDamageTimer >= 30) {
self.takeDamage(0.5); // Burn damage
self.burnDamageTimer = 0;
LK.effects.flashObject(self, 0xFF4500, 100);
}
if (self.burnTimer <= 0) {
self.isBurning = false;
tween(zombieGraphics, {
tint: 0x9932CC
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Move toward city center (only if not frozen)
if (!self.isFrozen) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
// Update visual based on health
var healthPercent = self.health / self.maxHealth;
if (healthPercent > 0.7) {
zombieGraphics.tint = 0x9932CC; // Dark orchid
} else if (healthPercent > 0.4) {
zombieGraphics.tint = 0xFF8C00; // Orange
} else {
zombieGraphics.tint = 0xFF0000; // Red
}
// Update health bar
healthBarFill.width = 58 * healthPercent;
if (healthPercent > 0.7) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
// Provide immunity to nearby zombies periodically
self.immunityTimer++;
if (self.immunityTimer >= self.immunityRate) {
self.grantImmunityToNearbyZombies();
self.immunityTimer = 0;
}
};
self.grantImmunityToNearbyZombies = function () {
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie === self) continue; // Don't grant immunity to self
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.immunityRadius) {
// Grant immunity - clear any freeze or burn effects
if (zombie.isFrozen) {
zombie.isFrozen = false;
zombie.frozenTimer = 0;
if (zombie.originalSpeed) {
zombie.speed = zombie.originalSpeed;
}
}
if (zombie.isBurning) {
zombie.isBurning = false;
zombie.burnTimer = 0;
zombie.burnDamageTimer = 0;
}
// Mark zombie as immune for a period
zombie.isImmune = true;
zombie.immunityTimer = 300; // Immune for 5 seconds (300 frames)
// Visual effect when granting immunity
LK.effects.flashObject(zombie, 0x9932CC, 300);
}
}
// Visual effect when granting immunity to others
LK.effects.flashObject(self, 0x9932CC, 500);
};
self.takeDamage = function (damage) {
if (damage === undefined) damage = 1;
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('zombieHit').play();
gamePoints += 15; // Moderate points for killing immunity zombies
updateUI();
// Remove from zombies array
var index = zombies.indexOf(self);
if (index > -1) {
zombies.splice(index, 1);
}
self.destroy();
};
return self;
});
var LaserTurret = Container.expand(function () {
var self = Container.call(this);
var turretGraphics = self.attachAsset('laserTurret', {
anchorX: 0.5,
anchorY: 0.5
});
turretGraphics.tint = 0x00FF00; // Green tint for laser turrets
self.range = 250;
self.currentTarget = null;
self.laserBeam = null;
self.damageTimer = 0;
self.damageRate = 30; // Deal damage every 0.5 seconds
self.update = function () {
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && nearestZombie !== self.currentTarget) {
// Switch target
self.currentTarget = nearestZombie;
self.createLaserBeam();
} else if (!nearestZombie && self.currentTarget) {
// No target, stop laser
self.currentTarget = null;
self.removeLaserBeam();
}
// Update laser beam and deal damage
if (self.currentTarget && self.laserBeam) {
self.updateLaserBeam();
self.damageTimer++;
if (self.damageTimer >= self.damageRate) {
var damage = 2; // Continuous damage
if (self.isDamageWeakened) {
damage = damage * 0.5; // Reduced damage
}
self.currentTarget.takeDamage(damage);
self.damageTimer = 0;
}
}
};
self.createLaserBeam = function () {
if (self.laserBeam) {
self.laserBeam.destroy();
}
self.laserBeam = self.attachAsset('laserBeam', {
anchorX: 0,
anchorY: 0.5
});
self.laserBeam.alpha = 0.8;
};
self.updateLaserBeam = function () {
if (!self.laserBeam || !self.currentTarget) return;
var dx = self.currentTarget.x - self.x;
var dy = self.currentTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
self.laserBeam.width = distance;
self.laserBeam.rotation = Math.atan2(dy, dx);
// Flickering effect
self.laserBeam.alpha = 0.6 + Math.random() * 0.4;
};
self.removeLaserBeam = function () {
if (self.laserBeam) {
self.laserBeam.destroy();
self.laserBeam = null;
}
};
return self;
});
var Miner = Container.expand(function () {
var self = Container.call(this);
var minerGraphics = self.attachAsset('miner', {
anchorX: 0.5,
anchorY: 0.5
});
minerGraphics.tint = 0xffd700; // Gold tint for miners
self.moneyTimer = 0;
self.moneyRate = 180; // Generate money every 3 seconds (180 frames)
self.lastIntersecting = {};
self.update = function () {
// Check collision with zombies first
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var zombieId = zombies.indexOf(zombie);
// Initialize last intersecting state if not exists
if (self.lastIntersecting[zombieId] === undefined) {
self.lastIntersecting[zombieId] = false;
}
var currentIntersecting = self.intersects(zombie);
// Check if zombie just started intersecting
if (!self.lastIntersecting[zombieId] && currentIntersecting) {
// Transform miner into zombie
var newZombie = new Zombie();
newZombie.x = self.x;
newZombie.y = self.y;
zombies.push(newZombie);
game.addChild(newZombie);
// Remove miner
self.destroy();
var minerIndex = miners.indexOf(self);
if (minerIndex > -1) {
miners.splice(minerIndex, 1);
}
return;
}
// Update last intersecting state
self.lastIntersecting[zombieId] = currentIntersecting;
}
self.moneyTimer++;
if (self.moneyTimer >= self.moneyRate) {
// Generate money
gamePoints += 25;
updateUI();
// Visual effect when generating money
LK.effects.flashObject(self, 0xffd700, 500);
self.moneyTimer = 0;
}
};
return self;
});
var Mortar = Container.expand(function () {
var self = Container.call(this);
var mortarGraphics = self.attachAsset('mortar', {
anchorX: 0.5,
anchorY: 0.5
});
mortarGraphics.tint = 0x696969; // Dim gray tint for mortars
self.range = 400; // Very long range
self.shootCooldown = 0;
self.shootRate = 180; // Slow but powerful
self.update = function () {
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
return;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie) {
self.fireMortar(nearestZombie);
self.shootCooldown = self.shootRate;
}
};
self.fireMortar = function (target) {
// Create delayed explosion at target location
var explosionX = target.x;
var explosionY = target.y;
// Visual warning
LK.effects.flashObject(target, 0xFF0000, 1000);
// Delayed explosion
LK.setTimeout(function () {
var explosionRadius = 120;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - explosionX;
var dy = zombie.y - explosionY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRadius) {
var damage = 4;
if (self.isDamageWeakened) {
damage = damage * 0.5;
}
zombie.takeDamage(damage);
}
}
// Explosion effect
LK.effects.flashScreen(0xFF4400, 300);
LK.getSound('bombExplode').play();
}, 1000);
// Recoil effect
tween(self, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
}
});
};
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 = 6;
self.target = null;
self.launcher = null;
self.exploded = false;
self.update = function () {
if (self.exploded) return;
if (!self.target || zombies.indexOf(self.target) === -1) {
self.explode();
return;
}
// Move toward target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
// Hit target area
self.explode();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.explode = function () {
if (self.exploded) return;
self.exploded = true;
// Play explosion sound
LK.getSound('bombExplode').play();
// Damage all zombies in explosion radius
var explosionRadius = 100;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRadius) {
var damage = 3; // High damage
if (self.launcher && self.launcher.isDamageWeakened) {
damage = damage * 0.5; // Reduced damage
}
zombie.takeDamage(damage);
}
}
// Create explosion effect
LK.effects.flashObject(self, 0xff4444, 300);
// Remove rocket
self.destroy();
var index = rockets.indexOf(self);
if (index > -1) {
rockets.splice(index, 1);
}
};
return self;
});
var RocketLauncher = Container.expand(function () {
var self = Container.call(this);
var launcherGraphics = self.attachAsset('rocketLauncher', {
anchorX: 0.5,
anchorY: 0.5
});
launcherGraphics.tint = 0x8B4513; // Brown tint for rocket launchers
self.range = 280;
self.shootCooldown = 0;
self.shootRate = 150; // Slow but powerful
self.update = function () {
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
return;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie) {
self.launchRocket(nearestZombie);
self.shootCooldown = self.shootRate;
}
};
self.launchRocket = function (target) {
var rocket = new Rocket();
rocket.x = self.x;
rocket.y = self.y;
rocket.target = target;
rocket.launcher = self;
rockets.push(rocket);
game.addChild(rocket);
LK.getSound('shoot').play();
};
return self;
});
var ShieldGenerator = Container.expand(function () {
var self = Container.call(this);
var generatorGraphics = self.attachAsset('shieldGenerator', {
anchorX: 0.5,
anchorY: 0.5
});
generatorGraphics.tint = 0x00AAFF; // Blue tint for shield generators
self.range = 150;
self.shieldTimer = 0;
self.shieldRate = 120; // Generate shields every 2 seconds
self.shields = [];
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
}
}
self.shieldTimer++;
if (self.shieldTimer >= self.shieldRate) {
self.generateShields();
self.shieldTimer = 0;
}
// Update existing shields
for (var i = self.shields.length - 1; i >= 0; i--) {
var shield = self.shields[i];
shield.duration--;
if (shield.duration <= 0) {
shield.destroy();
self.shields.splice(i, 1);
}
}
};
self.generateShields = function () {
// Find nearby friendly units
var nearbyUnits = [];
// Check soldiers
for (var i = 0; i < soldiers.length; i++) {
var soldier = soldiers[i];
var dx = soldier.x - self.x;
var dy = soldier.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
nearbyUnits.push(soldier);
}
}
// Check gunners
for (var i = 0; i < gunners.length; i++) {
var gunner = gunners[i];
var dx = gunner.x - self.x;
var dy = gunner.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
nearbyUnits.push(gunner);
}
}
// Check heavy tanks
for (var i = 0; i < heavyTanks.length; i++) {
var tank = heavyTanks[i];
var dx = tank.x - self.x;
var dy = tank.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
nearbyUnits.push(tank);
}
}
// Generate shields for nearby units
for (var i = 0; i < nearbyUnits.length; i++) {
var unit = nearbyUnits[i];
if (!unit.hasShield) {
var shield = unit.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5
});
shield.alpha = 0.3;
shield.duration = 300; // 5 seconds
unit.hasShield = true;
unit.shieldHealth = 50;
self.shields.push(shield);
// Visual effect
LK.effects.flashObject(unit, 0x00AAFF, 300);
}
}
// Visual effect for generator
LK.effects.flashObject(self, 0x00AAFF, 500);
};
return self;
});
var ShockTrooper = Container.expand(function () {
var self = Container.call(this);
var shockGraphics = self.attachAsset('shockTrooper', {
anchorX: 0.5,
anchorY: 0.5
});
shockGraphics.tint = 0x00CED1; // Dark turquoise tint for shock troopers
self.range = 160;
self.shockCooldown = 0;
self.shockRate = 120; // Shock every 2 seconds
self.speed = 1.0;
self.patrolTarget = null;
self.mode = 'patrol';
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.speed = self.originalSpeed;
}
}
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
// Decrease shock cooldown
if (self.shockCooldown > 0) {
self.shockCooldown--;
}
// Find zombies in range for shock attack
var zombiesInRange = [];
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
zombiesInRange.push(zombie);
}
}
if (zombiesInRange.length > 0 && self.shockCooldown === 0) {
self.mode = 'combat';
self.electricShock(zombiesInRange);
self.shockCooldown = self.shockRate;
} else {
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
};
self.electricShock = function (zombiesInRange) {
var damage = 2;
if (self.isDamageWeakened) {
damage = damage * 0.5;
}
for (var i = 0; i < zombiesInRange.length; i++) {
var zombie = zombiesInRange[i];
zombie.takeDamage(damage);
// Stun effect - slow zombie temporarily
zombie.isStunned = true;
zombie.stunnedTimer = 60; // 1 second stun
if (!zombie.originalSpeed) {
zombie.originalSpeed = zombie.speed;
}
zombie.speed = zombie.speed * 0.3; // 70% speed reduction
// Visual effect
tween(zombie, {
tint: 0x00CED1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(zombie, {
tint: zombie.originalTint || 0xFFFFFF
}, {
duration: 300,
easing: tween.easeOut
});
}
});
}
// Shock visual effect
LK.effects.flashObject(self, 0x00CED1, 500);
LK.getSound('shoot').play();
};
self.setNewPatrolTarget = function () {
var angle = Math.random() * Math.PI * 2;
var radius = 200 + Math.random() * 300;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var Soldier = Container.expand(function () {
var self = Container.call(this);
var soldierGraphics = self.attachAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5
});
soldierGraphics.tint = 0x4CAF50; // Green tint for soldiers
self.range = 120;
self.shootCooldown = 0;
self.shootRate = 45; // frames between shots
self.speed = 0.8;
self.patrolTarget = null;
self.mode = 'patrol'; // 'patrol' or 'combat'
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.shootRate = self.originalShootRate;
}
}
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
// Decrease shoot cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie && self.shootCooldown === 0) {
// Combat mode - shoot at zombie
self.mode = 'combat';
self.shoot(nearestZombie);
self.shootCooldown = self.shootRate;
} else {
// Patrol mode - move around
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
}
};
self.shoot = function (target) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = target;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.setNewPatrolTarget = function () {
// Set random patrol target around city center
var angle = Math.random() * Math.PI * 2;
var radius = 200 + Math.random() * 300;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
// Keep patrol target within bounds
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var SpawnerZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('spawnerZombie', {
anchorX: 0.5,
anchorY: 0.5
});
zombieGraphics.tint = 0x800080; // Purple tint for spawner zombies
self.speed = 0.8;
self.health = 25;
self.maxHealth = 25;
self.targetX = cityCenter.x;
self.targetY = cityCenter.y;
self.spawnTimer = 0;
self.spawnRate = 300; // Spawn tank zombie every 5 seconds (300 frames)
// Create health bar
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5,
x: -29,
y: -50
});
self.update = function () {
// Handle frozen state
if (self.isFrozen) {
self.frozenTimer--;
if (self.frozenTimer <= 0) {
self.isFrozen = false;
self.speed = self.originalSpeed;
tween(zombieGraphics, {
tint: 0x800080
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Handle burning state
if (self.isBurning) {
self.burnTimer--;
self.burnDamageTimer++;
// Deal burn damage every 30 frames (0.5 seconds)
if (self.burnDamageTimer >= 30) {
self.takeDamage(0.5); // Burn damage
self.burnDamageTimer = 0;
LK.effects.flashObject(self, 0xFF4500, 100);
}
if (self.burnTimer <= 0) {
self.isBurning = false;
tween(zombieGraphics, {
tint: 0x800080
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Move toward city center (only if not frozen)
if (!self.isFrozen) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
// Update visual based on health
var healthPercent = self.health / self.maxHealth;
if (healthPercent > 0.7) {
zombieGraphics.tint = 0x800080; // Purple
} else if (healthPercent > 0.4) {
zombieGraphics.tint = 0xFF4500; // Orange
} else {
zombieGraphics.tint = 0xFF0000; // Red
}
// Update health bar
healthBarFill.width = 58 * healthPercent;
if (healthPercent > 0.7) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
// Spawn tank zombies periodically
self.spawnTimer++;
if (self.spawnTimer >= self.spawnRate) {
self.spawnTankZombie();
self.spawnTimer = 0;
}
};
self.spawnTankZombie = function () {
var tankZombie = new TankZombie();
// Spawn near the spawner zombie
var angle = Math.random() * Math.PI * 2;
var radius = 50;
tankZombie.x = self.x + Math.cos(angle) * radius;
tankZombie.y = self.y + Math.sin(angle) * radius;
// Keep within bounds
tankZombie.x = Math.max(50, Math.min(1998, tankZombie.x));
tankZombie.y = Math.max(50, Math.min(2682, tankZombie.y));
zombies.push(tankZombie);
game.addChild(tankZombie);
// Visual effect when spawning
LK.effects.flashObject(self, 0x800080, 500);
};
self.takeDamage = function (damage) {
if (damage === undefined) damage = 1;
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('zombieHit').play();
gamePoints += 15; // More points than regular zombie, less than tank
updateUI();
// Remove from zombies array
var index = zombies.indexOf(self);
if (index > -1) {
zombies.splice(index, 1);
}
self.destroy();
};
return self;
});
var SpeedyZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('speedyZombie', {
anchorX: 0.5,
anchorY: 0.5
});
zombieGraphics.tint = 0xFF6B35; // Orange tint for speedy zombies
self.speed = 2.5; // Very fast
self.health = 15;
self.maxHealth = 15;
self.targetX = cityCenter.x;
self.targetY = cityCenter.y;
self.cannotBeFrozen = true; // Cannot be frozen
// Create health bar
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -40
});
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5,
x: -29,
y: -40
});
self.update = function () {
// Handle immunity timer
if (self.isImmune) {
self.immunityTimer--;
if (self.immunityTimer <= 0) {
self.isImmune = false;
}
}
// Speedy zombies CANNOT be frozen - ignore freeze effects
if (self.isFrozen) {
self.isFrozen = false; // Immediately clear frozen state
self.frozenTimer = 0;
self.speed = 2.5; // Maintain high speed
}
// Handle burning state
if (self.isBurning) {
self.burnTimer--;
self.burnDamageTimer++;
// Deal burn damage every 30 frames (0.5 seconds)
if (self.burnDamageTimer >= 30) {
self.takeDamage(0.5); // Burn damage
self.burnDamageTimer = 0;
LK.effects.flashObject(self, 0xFF4500, 100);
}
if (self.burnTimer <= 0) {
self.isBurning = false;
tween(zombieGraphics, {
tint: 0xFF6B35
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Move toward city center - always fast
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Update visual based on health
var healthPercent = self.health / self.maxHealth;
if (healthPercent > 0.7) {
zombieGraphics.tint = 0xFF6B35; // Orange
} else if (healthPercent > 0.4) {
zombieGraphics.tint = 0xFF8C00; // Dark orange
} else {
zombieGraphics.tint = 0xFF0000; // Red
}
// Update health bar
healthBarFill.width = 58 * healthPercent;
if (healthPercent > 0.7) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
};
self.takeDamage = function (damage) {
if (damage === undefined) damage = 1;
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('zombieHit').play();
gamePoints += 8; // Moderate points for killing speedy zombies
updateUI();
// Remove from zombies array
var index = zombies.indexOf(self);
if (index > -1) {
zombies.splice(index, 1);
}
self.destroy();
};
return self;
});
var SupportMedic = Container.expand(function () {
var self = Container.call(this);
var medicGraphics = self.attachAsset('supportMedic', {
anchorX: 0.5,
anchorY: 0.5
});
medicGraphics.tint = 0x00FF7F; // Spring green tint for support medics
self.range = 140;
self.healCooldown = 0;
self.healRate = 90; // Heal every 1.5 seconds
self.speed = 0.9;
self.patrolTarget = null;
self.mode = 'patrol';
self.update = function () {
// Handle weakening effect
if (self.isWeakened) {
self.weakenTimer--;
if (self.weakenTimer <= 0) {
self.isWeakened = false;
self.speed = self.originalSpeed;
}
}
// Decrease heal cooldown
if (self.healCooldown > 0) {
self.healCooldown--;
}
// Heal nearby troops
if (self.healCooldown === 0) {
self.healNearbyTroops();
self.healCooldown = self.healRate;
}
// Patrol behavior
self.mode = 'patrol';
if (!self.patrolTarget || self.reachedTarget()) {
self.setNewPatrolTarget();
}
self.moveTowardTarget();
};
self.healNearbyTroops = function () {
var healedAny = false;
var allTroops = [].concat(soldiers, gunners, bombers, heavyTanks, eliteSnipers);
for (var i = 0; i < allTroops.length; i++) {
var troop = allTroops[i];
if (!troop.health || troop.health >= troop.maxHealth) continue;
var dx = troop.x - self.x;
var dy = troop.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
troop.health = Math.min(troop.maxHealth, troop.health + 15);
LK.effects.flashObject(troop, 0x00FF7F, 300);
healedAny = true;
}
}
if (healedAny) {
LK.effects.flashObject(self, 0x00FF7F, 500);
}
};
self.setNewPatrolTarget = function () {
var angle = Math.random() * Math.PI * 2;
var radius = 200 + Math.random() * 300;
self.patrolTarget = {
x: cityCenter.x + Math.cos(angle) * radius,
y: cityCenter.y + Math.sin(angle) * radius
};
self.patrolTarget.x = Math.max(100, Math.min(1948, self.patrolTarget.x));
self.patrolTarget.y = Math.max(100, Math.min(2632, self.patrolTarget.y));
};
self.moveTowardTarget = function () {
if (!self.patrolTarget) return;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.reachedTarget = function () {
if (!self.patrolTarget) return true;
var dx = self.patrolTarget.x - self.x;
var dy = self.patrolTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < 20;
};
return self;
});
var TankZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('tankZombie', {
anchorX: 0.5,
anchorY: 0.5
});
zombieGraphics.tint = 0x8B0000; // Dark red tint for tank zombies
self.speed = 0.6; // Slower than regular zombies
self.health = 35; // Much higher health
self.maxHealth = 35;
self.targetX = cityCenter.x;
self.targetY = cityCenter.y;
// Create health bar
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5,
x: -29,
y: -50
});
self.update = function () {
// Handle frozen state
if (self.isFrozen) {
self.frozenTimer--;
if (self.frozenTimer <= 0) {
self.isFrozen = false;
self.speed = self.originalSpeed;
tween(zombieGraphics, {
tint: 0x8B0000
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Handle burning state
if (self.isBurning) {
self.burnTimer--;
self.burnDamageTimer++;
// Deal burn damage every 30 frames (0.5 seconds)
if (self.burnDamageTimer >= 30) {
self.takeDamage(0.5); // Burn damage
self.burnDamageTimer = 0;
LK.effects.flashObject(self, 0xFF4500, 100);
}
if (self.burnTimer <= 0) {
self.isBurning = false;
tween(zombieGraphics, {
tint: 0x8B0000
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Move toward city center (only if not frozen)
if (!self.isFrozen) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
// Update visual based on health
var healthPercent = self.health / self.maxHealth;
if (healthPercent > 0.7) {
zombieGraphics.tint = 0x8B0000; // Dark red
} else if (healthPercent > 0.4) {
zombieGraphics.tint = 0xFF4500; // Orange red
} else {
zombieGraphics.tint = 0xFF0000; // Bright red
}
// Update health bar
healthBarFill.width = 58 * healthPercent;
if (healthPercent > 0.7) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
};
self.takeDamage = function (damage) {
if (damage === undefined) damage = 1; // Default damage is 1
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('zombieHit').play();
gamePoints += 20; // More points for killing tank zombies
updateUI();
// Remove from zombies array
var index = zombies.indexOf(self);
if (index > -1) {
zombies.splice(index, 1);
}
self.destroy();
};
return self;
});
var Tower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
self.range = 150;
self.shootCooldown = 0;
self.shootRate = 30; // frames between shots
self.level = 1;
self.update = function () {
// Check collision with zombies first
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var zombieId = zombies.indexOf(zombie);
// Initialize last intersecting state if not exists
if (self.lastIntersecting === undefined) {
self.lastIntersecting = {};
}
if (self.lastIntersecting[zombieId] === undefined) {
self.lastIntersecting[zombieId] = false;
}
var currentIntersecting = self.intersects(zombie);
// Check if zombie just started intersecting
if (!self.lastIntersecting[zombieId] && currentIntersecting) {
// Zombie breaks tower
self.destroy();
var index = towers.indexOf(self);
if (index > -1) {
towers.splice(index, 1);
}
return;
}
// Update last intersecting state
self.lastIntersecting[zombieId] = currentIntersecting;
}
// Handle damage weakening effect
if (self.isDamageWeakened) {
self.damageWeakenTimer--;
if (self.damageWeakenTimer <= 0) {
self.isDamageWeakened = false;
}
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
return;
}
// Find nearest zombie in range
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
if (nearestZombie) {
self.shoot(nearestZombie);
self.shootCooldown = self.shootRate;
}
};
self.shoot = function (target) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = target;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.upgrade = function () {
if (gamePoints >= self.getUpgradeCost()) {
gamePoints -= self.getUpgradeCost();
self.level++;
self.range += 20;
self.shootRate = Math.max(10, self.shootRate - 5);
towerGraphics.tint = 0x2e7d32; // Green tint for upgraded towers
updateUI();
}
};
self.getUpgradeCost = function () {
return self.level * 15;
};
return self;
});
var Trap = Container.expand(function () {
var self = Container.call(this);
var trapGraphics = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5
});
trapGraphics.tint = 0x8B4513; // Saddle brown tint for traps
self.isArmed = true;
self.lastIntersecting = {};
trapGraphics.alpha = 0.7; // Slightly transparent
self.update = function () {
if (!self.isArmed) return;
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var zombieId = zombies.indexOf(zombie);
// Initialize last intersecting state if not exists
if (self.lastIntersecting[zombieId] === undefined) {
self.lastIntersecting[zombieId] = false;
}
var currentIntersecting = self.intersects(zombie);
// Check if zombie just stepped on trap
if (!self.lastIntersecting[zombieId] && currentIntersecting) {
self.triggerTrap(zombie);
return;
}
// Update last intersecting state
self.lastIntersecting[zombieId] = currentIntersecting;
}
};
self.triggerTrap = function (zombie) {
// Trap zombie for 3 seconds
zombie.isTrapped = true;
zombie.trappedTimer = 180; // 3 seconds
if (!zombie.originalSpeed) {
zombie.originalSpeed = zombie.speed;
}
zombie.speed = 0; // Stop movement
// Deal damage over time
var trapDamage = 0;
var trapDamageInterval = LK.setInterval(function () {
if (zombie.isTrapped) {
zombie.takeDamage(0.5);
trapDamage++;
if (trapDamage >= 6) {
// Stop after 6 ticks
LK.clearInterval(trapDamageInterval);
}
} else {
LK.clearInterval(trapDamageInterval);
}
}, 30);
// Visual effects
LK.effects.flashObject(zombie, 0x8B4513, 300);
LK.effects.flashObject(self, 0xFF0000, 500);
// Disarm trap
self.isArmed = false;
trapGraphics.alpha = 0.3;
trapGraphics.tint = 0x696969; // Gray when used
// Remove trap after use
LK.setTimeout(function () {
self.destroy();
var index = traps.indexOf(self);
if (index > -1) {
traps.splice(index, 1);
}
}, 3000);
};
return self;
});
var WeakeningZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('weakeningZombie', {
anchorX: 0.5,
anchorY: 0.5
});
zombieGraphics.tint = 0x4B0082; // Dark purple tint for weakening zombies
self.speed = 0.9;
self.health = 25;
self.maxHealth = 25;
self.targetX = cityCenter.x;
self.targetY = cityCenter.y;
self.weakenTimer = 0;
self.weakenRate = 120; // Weaken troops every 2 seconds (120 frames)
self.weakenRadius = 180; // Radius to weaken troops
// Create health bar
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5,
x: -29,
y: -50
});
self.update = function () {
// Handle immunity timer
if (self.isImmune) {
self.immunityTimer--;
if (self.immunityTimer <= 0) {
self.isImmune = false;
}
}
// Handle frozen state
if (self.isFrozen) {
self.frozenTimer--;
if (self.frozenTimer <= 0) {
self.isFrozen = false;
self.speed = self.originalSpeed;
tween(zombieGraphics, {
tint: 0x4B0082
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Handle burning state
if (self.isBurning) {
self.burnTimer--;
self.burnDamageTimer++;
// Deal burn damage every 30 frames (0.5 seconds)
if (self.burnDamageTimer >= 30) {
self.takeDamage(0.5); // Burn damage
self.burnDamageTimer = 0;
LK.effects.flashObject(self, 0xFF4500, 100);
}
if (self.burnTimer <= 0) {
self.isBurning = false;
tween(zombieGraphics, {
tint: 0x4B0082
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Move toward city center (only if not frozen)
if (!self.isFrozen) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
// Update visual based on health
var healthPercent = self.health / self.maxHealth;
if (healthPercent > 0.7) {
zombieGraphics.tint = 0x4B0082; // Dark purple
} else if (healthPercent > 0.4) {
zombieGraphics.tint = 0xFF8C00; // Orange
} else {
zombieGraphics.tint = 0xFF0000; // Red
}
// Update health bar
healthBarFill.width = 58 * healthPercent;
if (healthPercent > 0.7) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
// Weaken nearby troops periodically
self.weakenTimer++;
if (self.weakenTimer >= self.weakenRate) {
self.weakenNearbyTroops();
self.weakenTimer = 0;
}
};
self.weakenNearbyTroops = function () {
// Weaken soldiers - reduce damage
for (var i = 0; i < soldiers.length; i++) {
var soldier = soldiers[i];
var dx = soldier.x - self.x;
var dy = soldier.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark soldier as damage weakened
if (!soldier.isDamageWeakened) {
soldier.isDamageWeakened = true;
// Visual effect when weakening
LK.effects.flashObject(soldier, 0x4B0082, 300);
}
soldier.damageWeakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Weaken gunners - reduce damage
for (var i = 0; i < gunners.length; i++) {
var gunner = gunners[i];
var dx = gunner.x - self.x;
var dy = gunner.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark gunner as damage weakened
if (!gunner.isDamageWeakened) {
gunner.isDamageWeakened = true;
// Visual effect when weakening
LK.effects.flashObject(gunner, 0x4B0082, 300);
}
gunner.damageWeakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Weaken bombers - reduce damage
for (var i = 0; i < bombers.length; i++) {
var bomber = bombers[i];
var dx = bomber.x - self.x;
var dy = bomber.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark bomber as damage weakened
if (!bomber.isDamageWeakened) {
bomber.isDamageWeakened = true;
// Visual effect when weakening
LK.effects.flashObject(bomber, 0x4B0082, 300);
}
bomber.damageWeakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Weaken towers - reduce damage
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx = tower.x - self.x;
var dy = tower.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark tower as damage weakened
if (!tower.isDamageWeakened) {
tower.isDamageWeakened = true;
// Visual effect when weakening
LK.effects.flashObject(tower, 0x4B0082, 300);
}
tower.damageWeakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Visual effect when weakening others
LK.effects.flashObject(self, 0x4B0082, 500);
};
self.takeDamage = function (damage) {
if (damage === undefined) damage = 1;
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('zombieHit').play();
gamePoints += 12; // Moderate points for killing weakening zombies
updateUI();
// Remove from zombies array
var index = zombies.indexOf(self);
if (index > -1) {
zombies.splice(index, 1);
}
self.destroy();
};
return self;
});
var WeakerZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('weakerZombie', {
anchorX: 0.5,
anchorY: 0.5
});
zombieGraphics.tint = 0x8A2BE2; // Blue violet tint for weaker zombies
self.speed = 0.8;
self.health = 22;
self.maxHealth = 22;
self.targetX = cityCenter.x;
self.targetY = cityCenter.y;
self.weakenTimer = 0;
self.weakenRate = 120; // Weaken troops every 2 seconds (120 frames)
self.weakenRadius = 150; // Radius to weaken troops
// Create health bar
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5,
x: -29,
y: -50
});
self.update = function () {
// Handle frozen state
if (self.isFrozen) {
self.frozenTimer--;
if (self.frozenTimer <= 0) {
self.isFrozen = false;
self.speed = self.originalSpeed;
tween(zombieGraphics, {
tint: 0x8A2BE2
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Handle burning state
if (self.isBurning) {
self.burnTimer--;
self.burnDamageTimer++;
// Deal burn damage every 30 frames (0.5 seconds)
if (self.burnDamageTimer >= 30) {
self.takeDamage(0.5); // Burn damage
self.burnDamageTimer = 0;
LK.effects.flashObject(self, 0xFF4500, 100);
}
if (self.burnTimer <= 0) {
self.isBurning = false;
tween(zombieGraphics, {
tint: 0x8A2BE2
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Move toward city center (only if not frozen)
if (!self.isFrozen) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
// Update visual based on health
var healthPercent = self.health / self.maxHealth;
if (healthPercent > 0.7) {
zombieGraphics.tint = 0x8A2BE2; // Blue violet
} else if (healthPercent > 0.4) {
zombieGraphics.tint = 0xFF8C00; // Orange
} else {
zombieGraphics.tint = 0xFF0000; // Red
}
// Update health bar
healthBarFill.width = 58 * healthPercent;
if (healthPercent > 0.7) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
// Weaken nearby troops periodically
self.weakenTimer++;
if (self.weakenTimer >= self.weakenRate) {
self.weakenNearbyTroops();
self.weakenTimer = 0;
}
};
self.weakenNearbyTroops = function () {
// Weaken soldiers
for (var i = 0; i < soldiers.length; i++) {
var soldier = soldiers[i];
var dx = soldier.x - self.x;
var dy = soldier.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark soldier as weakened
if (!soldier.isWeakened) {
soldier.isWeakened = true;
soldier.originalShootRate = soldier.shootRate;
soldier.shootRate = Math.floor(soldier.shootRate * 1.5); // 50% slower shooting
// Visual effect when weakening
LK.effects.flashObject(soldier, 0x8A2BE2, 300);
}
soldier.weakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Weaken gunners
for (var i = 0; i < gunners.length; i++) {
var gunner = gunners[i];
var dx = gunner.x - self.x;
var dy = gunner.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark gunner as weakened
if (!gunner.isWeakened) {
gunner.isWeakened = true;
gunner.originalShootRate = gunner.shootRate;
gunner.shootRate = Math.floor(gunner.shootRate * 1.5); // 50% slower shooting
// Visual effect when weakening
LK.effects.flashObject(gunner, 0x8A2BE2, 300);
}
gunner.weakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Weaken bombers
for (var i = 0; i < bombers.length; i++) {
var bomber = bombers[i];
var dx = bomber.x - self.x;
var dy = bomber.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark bomber as weakened
if (!bomber.isWeakened) {
bomber.isWeakened = true;
bomber.originalShootRate = bomber.shootRate;
bomber.shootRate = Math.floor(bomber.shootRate * 1.5); // 50% slower shooting
// Visual effect when weakening
LK.effects.flashObject(bomber, 0x8A2BE2, 300);
}
bomber.weakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Weaken exploders
for (var i = 0; i < exploders.length; i++) {
var exploder = exploders[i];
var dx = exploder.x - self.x;
var dy = exploder.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark exploder as weakened
if (!exploder.isWeakened) {
exploder.isWeakened = true;
exploder.originalSpeed = exploder.speed;
exploder.speed = exploder.speed * 0.7; // 30% slower movement
// Visual effect when weakening
LK.effects.flashObject(exploder, 0x8A2BE2, 300);
}
exploder.weakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Weaken freezers
for (var i = 0; i < freezers.length; i++) {
var freezer = freezers[i];
var dx = freezer.x - self.x;
var dy = freezer.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark freezer as weakened
if (!freezer.isWeakened) {
freezer.isWeakened = true;
freezer.originalSpeed = freezer.speed;
freezer.speed = freezer.speed * 0.7; // 30% slower movement
// Visual effect when weakening
LK.effects.flashObject(freezer, 0x8A2BE2, 300);
}
freezer.weakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Weaken burners
for (var i = 0; i < burners.length; i++) {
var burner = burners[i];
var dx = burner.x - self.x;
var dy = burner.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark burner as weakened
if (!burner.isWeakened) {
burner.isWeakened = true;
burner.originalSpeed = burner.speed;
burner.speed = burner.speed * 0.7; // 30% slower movement
// Visual effect when weakening
LK.effects.flashObject(burner, 0x8A2BE2, 300);
}
burner.weakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Weaken flamethrowers
for (var i = 0; i < flamethrowers.length; i++) {
var flamethrower = flamethrowers[i];
var dx = flamethrower.x - self.x;
var dy = flamethrower.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark flamethrower as weakened
if (!flamethrower.isWeakened) {
flamethrower.isWeakened = true;
flamethrower.originalBurnRate = flamethrower.burnRate;
flamethrower.burnRate = Math.floor(flamethrower.burnRate * 1.5); // 50% slower burning
// Visual effect when weakening
LK.effects.flashObject(flamethrower, 0x8A2BE2, 300);
}
flamethrower.weakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Weaken freezer troops
for (var i = 0; i < freezerTroops.length; i++) {
var freezerTroop = freezerTroops[i];
var dx = freezerTroop.x - self.x;
var dy = freezerTroop.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.weakenRadius) {
// Mark freezer troop as weakened
if (!freezerTroop.isWeakened) {
freezerTroop.isWeakened = true;
freezerTroop.originalSpeed = freezerTroop.speed;
freezerTroop.speed = freezerTroop.speed * 0.7; // 30% slower movement
// Visual effect when weakening
LK.effects.flashObject(freezerTroop, 0x8A2BE2, 300);
}
freezerTroop.weakenTimer = 600; // Weakened for 10 seconds (600 frames)
}
}
// Visual effect when weakening others
LK.effects.flashObject(self, 0x8A2BE2, 500);
};
self.takeDamage = function (damage) {
if (damage === undefined) damage = 1;
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('zombieHit').play();
gamePoints += 10; // Moderate points for killing weaker zombies
updateUI();
// Remove from zombies array
var index = zombies.indexOf(self);
if (index > -1) {
zombies.splice(index, 1);
}
self.destroy();
};
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 = 1;
self.health = 3;
self.maxHealth = 3;
self.targetX = cityCenter.x;
self.targetY = cityCenter.y;
// Create health bar
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -40
});
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5,
x: -29,
y: -40
});
self.update = function () {
// Handle immunity timer
if (self.isImmune) {
self.immunityTimer--;
if (self.immunityTimer <= 0) {
self.isImmune = false;
}
}
// Handle stunned state
if (self.isStunned) {
self.stunnedTimer--;
if (self.stunnedTimer <= 0) {
self.isStunned = false;
self.speed = self.originalSpeed;
}
}
// Handle trapped state
if (self.isTrapped) {
self.trappedTimer--;
if (self.trappedTimer <= 0) {
self.isTrapped = false;
self.speed = self.originalSpeed;
}
}
// Handle frozen state
if (self.isFrozen) {
self.frozenTimer--;
if (self.frozenTimer <= 0) {
self.isFrozen = false;
self.speed = self.originalSpeed;
tween(zombieGraphics, {
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Handle burning state
if (self.isBurning) {
self.burnTimer--;
self.burnDamageTimer++;
// Deal burn damage every 30 frames (0.5 seconds)
if (self.burnDamageTimer >= 30) {
self.takeDamage(0.5); // Burn damage
self.burnDamageTimer = 0;
LK.effects.flashObject(self, 0xFF4500, 100);
}
if (self.burnTimer <= 0) {
self.isBurning = false;
tween(zombieGraphics, {
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Move toward city center (only if not frozen)
if (!self.isFrozen) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
// Update health bar
var healthPercent = self.health / self.maxHealth;
healthBarFill.width = 58 * healthPercent;
if (healthPercent > 0.7) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
};
self.takeDamage = function (damage) {
if (damage === undefined) damage = 1; // Default damage is 1
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('zombieHit').play();
gamePoints += 5;
updateUI();
// Remove from zombies array
var index = zombies.indexOf(self);
if (index > -1) {
zombies.splice(index, 1);
}
self.destroy();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2e2e2e
});
/****
* Game Code
****/
// Game variables
var towers = [];
var zombies = [];
var bullets = [];
var fastBullets = [];
var barriers = [];
var soldiers = [];
var gunners = [];
var bombers = [];
var bombs = [];
var miners = [];
var catchers = [];
var exploders = [];
var freezers = [];
var burners = [];
var flamethrowers = [];
var freezerTroops = [];
var fireTiles = [];
var eliteSnipers = [];
var heavyTanks = [];
var laserTurrets = [];
var rocketLaunchers = [];
var rockets = [];
var shieldGenerators = [];
var supportMedics = [];
var assassins = [];
var engineers = [];
var mortars = [];
var shockTroopers = [];
var traps = [];
var gamePoints = 50;
var wave = 1;
var zombiesSpawned = 0;
var zombiesToSpawn = 5;
var spawnTimer = 0;
var waveComplete = true;
var gridSize = 80;
var towerCost = 10;
var barrierCost = 15;
var soldierCost = 20;
var gunnerCost = 18;
var bomberCost = 30;
var minerCost = 18;
var catcherCost = 22;
var exploderCost = 25;
var freezerCost = 35;
var burnerCost = 40;
var flamethrowerCost = 28;
var freezerTroopCost = 25;
var eliteSniperCost = 50;
var heavyTankCost = 60;
var laserTurretCost = 45;
var rocketLauncherCost = 55;
var shieldGeneratorCost = 40;
var supportMedicCost = 32;
var assassinCost = 42;
var engineerCost = 35;
var mortarCost = 65;
var shockTrooperCost = 38;
var trapCost = 12;
// Create city center
var cityCenter = game.addChild(LK.getAsset('cityCenter', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
// Create UI elements
var pointsText = new Text2('Points: 50', {
size: 60,
fill: 0xFFFFFF
});
pointsText.anchor.set(0, 0);
LK.gui.topRight.addChild(pointsText);
var moneyText = new Text2('Money: 50', {
size: 60,
fill: 0xffd700
});
moneyText.anchor.set(1, 0);
moneyText.x = -20;
LK.gui.topRight.addChild(moneyText);
var waveText = new Text2('Wave: 1', {
size: 60,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
// Create GUI buttons organized in rows
// Row 1: Basic defensive structures
var towerButton = new Text2('Tower (10)', {
size: 30,
fill: 0xFFFFFF
});
towerButton.anchor.set(0, 1);
towerButton.x = 20;
towerButton.y = -20;
LK.gui.bottomLeft.addChild(towerButton);
var barrierButton = new Text2('Barrier (15)', {
size: 30,
fill: 0xFFFFFF
});
barrierButton.anchor.set(0, 1);
barrierButton.x = 280;
barrierButton.y = -20;
LK.gui.bottomLeft.addChild(barrierButton);
var laserTurretButton = new Text2('LaserTurret (45)', {
size: 30,
fill: 0xFFFFFF
});
laserTurretButton.anchor.set(0, 1);
laserTurretButton.x = 540;
laserTurretButton.y = -20;
LK.gui.bottomLeft.addChild(laserTurretButton);
var rocketLauncherButton = new Text2('RocketLauncher (55)', {
size: 30,
fill: 0xFFFFFF
});
rocketLauncherButton.anchor.set(0, 1);
rocketLauncherButton.x = 800;
rocketLauncherButton.y = -20;
LK.gui.bottomLeft.addChild(rocketLauncherButton);
var mortarButton = new Text2('Mortar (65)', {
size: 30,
fill: 0xFFFFFF
});
mortarButton.anchor.set(0, 1);
mortarButton.x = 1060;
mortarButton.y = -20;
LK.gui.bottomLeft.addChild(mortarButton);
// Row 2: Basic troops
var soldierButton = new Text2('Soldier (20)', {
size: 30,
fill: 0xFFFFFF
});
soldierButton.anchor.set(0, 1);
soldierButton.x = 20;
soldierButton.y = -70;
LK.gui.bottomLeft.addChild(soldierButton);
var gunnerButton = new Text2('Gunner (18)', {
size: 30,
fill: 0xFFFFFF
});
gunnerButton.anchor.set(0, 1);
gunnerButton.x = 280;
gunnerButton.y = -70;
LK.gui.bottomLeft.addChild(gunnerButton);
var bomberButton = new Text2('Bomber (30)', {
size: 30,
fill: 0xFFFFFF
});
bomberButton.anchor.set(0, 1);
bomberButton.x = 540;
bomberButton.y = -70;
LK.gui.bottomLeft.addChild(bomberButton);
var eliteSniperButton = new Text2('EliteSniper (50)', {
size: 30,
fill: 0xFFFFFF
});
eliteSniperButton.anchor.set(0, 1);
eliteSniperButton.x = 800;
eliteSniperButton.y = -70;
LK.gui.bottomLeft.addChild(eliteSniperButton);
var heavyTankButton = new Text2('HeavyTank (60)', {
size: 30,
fill: 0xFFFFFF
});
heavyTankButton.anchor.set(0, 1);
heavyTankButton.x = 1060;
heavyTankButton.y = -70;
LK.gui.bottomLeft.addChild(heavyTankButton);
// Row 3: Special troops
var catcherButton = new Text2('Catcher (22)', {
size: 30,
fill: 0xFFFFFF
});
catcherButton.anchor.set(0, 1);
catcherButton.x = 20;
catcherButton.y = -120;
LK.gui.bottomLeft.addChild(catcherButton);
var exploderButton = new Text2('Exploder (25)', {
size: 30,
fill: 0xFFFFFF
});
exploderButton.anchor.set(0, 1);
exploderButton.x = 280;
exploderButton.y = -120;
LK.gui.bottomLeft.addChild(exploderButton);
var freezerButton = new Text2('Freezer (35)', {
size: 30,
fill: 0xFFFFFF
});
freezerButton.anchor.set(0, 1);
freezerButton.x = 540;
freezerButton.y = -120;
LK.gui.bottomLeft.addChild(freezerButton);
var burnerButton = new Text2('Burner (40)', {
size: 30,
fill: 0xFFFFFF
});
burnerButton.anchor.set(0, 1);
burnerButton.x = 800;
burnerButton.y = -120;
LK.gui.bottomLeft.addChild(burnerButton);
var flamethrowerButton = new Text2('Flamethrower (28)', {
size: 30,
fill: 0xFFFFFF
});
flamethrowerButton.anchor.set(0, 1);
flamethrowerButton.x = 1060;
flamethrowerButton.y = -120;
LK.gui.bottomLeft.addChild(flamethrowerButton);
// Row 4: Advanced troops
var freezerTroopButton = new Text2('FreezerTroop (25)', {
size: 30,
fill: 0xFFFFFF
});
freezerTroopButton.anchor.set(0, 1);
freezerTroopButton.x = 20;
freezerTroopButton.y = -170;
LK.gui.bottomLeft.addChild(freezerTroopButton);
var shieldGeneratorButton = new Text2('ShieldGenerator (40)', {
size: 30,
fill: 0xFFFFFF
});
shieldGeneratorButton.anchor.set(0, 1);
shieldGeneratorButton.x = 330;
shieldGeneratorButton.y = -170;
LK.gui.bottomLeft.addChild(shieldGeneratorButton);
var supportMedicButton = new Text2('SupportMedic (32)', {
size: 30,
fill: 0xFFFFFF
});
supportMedicButton.anchor.set(0, 1);
supportMedicButton.x = 640;
supportMedicButton.y = -170;
LK.gui.bottomLeft.addChild(supportMedicButton);
var assassinButton = new Text2('Assassin (42)', {
size: 30,
fill: 0xFFFFFF
});
assassinButton.anchor.set(0, 1);
assassinButton.x = 950;
assassinButton.y = -170;
LK.gui.bottomLeft.addChild(assassinButton);
var engineerButton = new Text2('Engineer (35)', {
size: 30,
fill: 0xFFFFFF
});
engineerButton.anchor.set(0, 1);
engineerButton.x = 1260;
engineerButton.y = -170;
LK.gui.bottomLeft.addChild(engineerButton);
// Row 5: Utility troops
var minerButton = new Text2('Miner (18)', {
size: 30,
fill: 0xFFFFFF
});
minerButton.anchor.set(0, 1);
minerButton.x = 20;
minerButton.y = -220;
LK.gui.bottomLeft.addChild(minerButton);
var shockTrooperButton = new Text2('ShockTrooper (38)', {
size: 30,
fill: 0xFFFFFF
});
shockTrooperButton.anchor.set(0, 1);
shockTrooperButton.x = 320;
shockTrooperButton.y = -220;
LK.gui.bottomLeft.addChild(shockTrooperButton);
var trapButton = new Text2('Trap (12)', {
size: 30,
fill: 0xFFFFFF
});
trapButton.anchor.set(0, 1);
trapButton.x = 620;
trapButton.y = -220;
LK.gui.bottomLeft.addChild(trapButton);
function updateUI() {
pointsText.setText('Points: ' + gamePoints);
moneyText.setText('Money: ' + gamePoints);
waveText.setText('Wave: ' + wave);
}
function snapToGrid(value) {
return Math.floor(value / gridSize) * gridSize + gridSize / 2;
}
function canPlaceTower(x, y) {
// Check if position is too close to city center
var dx = x - cityCenter.x;
var dy = y - cityCenter.y;
var distanceToCenter = Math.sqrt(dx * dx + dy * dy);
if (distanceToCenter < 150) {
return false;
}
// Check if there's already a tower at this position
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (Math.abs(tower.x - x) < gridSize / 2 && Math.abs(tower.y - y) < gridSize / 2) {
return false;
}
}
// Check if there's already a barrier at this position
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
if (Math.abs(barrier.x - x) < gridSize / 2 && Math.abs(barrier.y - y) < gridSize / 2) {
return false;
}
}
return true;
}
function placeTower(x, y) {
if (gamePoints >= towerCost && canPlaceTower(x, y)) {
var tower = new Tower();
tower.x = x;
tower.y = y;
towers.push(tower);
game.addChild(tower);
gamePoints -= towerCost;
updateUI();
}
}
function placeBarrier(x, y) {
if (gamePoints >= barrierCost && canPlaceTower(x, y)) {
var barrier = new Barrier();
barrier.x = x;
barrier.y = y;
barriers.push(barrier);
game.addChild(barrier);
gamePoints -= barrierCost;
updateUI();
}
}
function placeSoldier(x, y) {
if (gamePoints >= soldierCost) {
var soldier = new Soldier();
soldier.x = x;
soldier.y = y;
soldiers.push(soldier);
game.addChild(soldier);
gamePoints -= soldierCost;
updateUI();
}
}
function placeGunner(x, y) {
if (gamePoints >= gunnerCost) {
var gunner = new Gunner();
gunner.x = x;
gunner.y = y;
gunners.push(gunner);
game.addChild(gunner);
gamePoints -= gunnerCost;
updateUI();
}
}
function placeBomber(x, y) {
if (gamePoints >= bomberCost) {
var bomber = new Bomber();
bomber.x = x;
bomber.y = y;
bombers.push(bomber);
game.addChild(bomber);
gamePoints -= bomberCost;
updateUI();
}
}
function placeMiner(x, y) {
if (gamePoints >= minerCost) {
var miner = new Miner();
miner.x = x;
miner.y = y;
miners.push(miner);
game.addChild(miner);
gamePoints -= minerCost;
updateUI();
}
}
function placeCatcher(x, y) {
if (gamePoints >= catcherCost) {
var catcher = new Catcher();
catcher.x = x;
catcher.y = y;
catchers.push(catcher);
game.addChild(catcher);
gamePoints -= catcherCost;
updateUI();
}
}
function placeExploder(x, y) {
if (gamePoints >= exploderCost) {
var exploder = new Exploder();
exploder.x = x;
exploder.y = y;
exploders.push(exploder);
game.addChild(exploder);
gamePoints -= exploderCost;
updateUI();
}
}
function placeFreezer(x, y) {
if (gamePoints >= freezerCost) {
var freezer = new Freezer();
freezer.x = x;
freezer.y = y;
freezers.push(freezer);
game.addChild(freezer);
gamePoints -= freezerCost;
updateUI();
}
}
function placeBurner(x, y) {
if (gamePoints >= burnerCost) {
var burner = new Burner();
burner.x = x;
burner.y = y;
burners.push(burner);
game.addChild(burner);
gamePoints -= burnerCost;
updateUI();
}
}
function placeFlamethrower(x, y) {
if (gamePoints >= flamethrowerCost) {
var flamethrower = new Flamethrower();
flamethrower.x = x;
flamethrower.y = y;
flamethrowers.push(flamethrower);
game.addChild(flamethrower);
gamePoints -= flamethrowerCost;
updateUI();
}
}
function placeFreezerTroop(x, y) {
if (gamePoints >= freezerTroopCost) {
var freezerTroop = new FreezerTroop();
freezerTroop.x = x;
freezerTroop.y = y;
freezerTroops.push(freezerTroop);
game.addChild(freezerTroop);
gamePoints -= freezerTroopCost;
updateUI();
}
}
function placeEliteSniper(x, y) {
if (gamePoints >= eliteSniperCost) {
var eliteSniper = new EliteSniper();
eliteSniper.x = x;
eliteSniper.y = y;
eliteSnipers.push(eliteSniper);
game.addChild(eliteSniper);
gamePoints -= eliteSniperCost;
updateUI();
}
}
function placeHeavyTank(x, y) {
if (gamePoints >= heavyTankCost) {
var heavyTank = new HeavyTank();
heavyTank.x = x;
heavyTank.y = y;
heavyTanks.push(heavyTank);
game.addChild(heavyTank);
gamePoints -= heavyTankCost;
updateUI();
}
}
function placeLaserTurret(x, y) {
if (gamePoints >= laserTurretCost && canPlaceTower(x, y)) {
var laserTurret = new LaserTurret();
laserTurret.x = x;
laserTurret.y = y;
laserTurrets.push(laserTurret);
game.addChild(laserTurret);
gamePoints -= laserTurretCost;
updateUI();
}
}
function placeRocketLauncher(x, y) {
if (gamePoints >= rocketLauncherCost && canPlaceTower(x, y)) {
var rocketLauncher = new RocketLauncher();
rocketLauncher.x = x;
rocketLauncher.y = y;
rocketLaunchers.push(rocketLauncher);
game.addChild(rocketLauncher);
gamePoints -= rocketLauncherCost;
updateUI();
}
}
function placeShieldGenerator(x, y) {
if (gamePoints >= shieldGeneratorCost && canPlaceTower(x, y)) {
var shieldGenerator = new ShieldGenerator();
shieldGenerator.x = x;
shieldGenerator.y = y;
shieldGenerators.push(shieldGenerator);
game.addChild(shieldGenerator);
gamePoints -= shieldGeneratorCost;
updateUI();
}
}
function placeSupportMedic(x, y) {
if (gamePoints >= supportMedicCost) {
var supportMedic = new SupportMedic();
supportMedic.x = x;
supportMedic.y = y;
supportMedics.push(supportMedic);
game.addChild(supportMedic);
gamePoints -= supportMedicCost;
updateUI();
}
}
function placeAssassin(x, y) {
if (gamePoints >= assassinCost) {
var assassin = new Assassin();
assassin.x = x;
assassin.y = y;
assassins.push(assassin);
game.addChild(assassin);
gamePoints -= assassinCost;
updateUI();
}
}
function placeEngineer(x, y) {
if (gamePoints >= engineerCost) {
var engineer = new Engineer();
engineer.x = x;
engineer.y = y;
engineers.push(engineer);
game.addChild(engineer);
gamePoints -= engineerCost;
updateUI();
}
}
function placeMortar(x, y) {
if (gamePoints >= mortarCost && canPlaceTower(x, y)) {
var mortar = new Mortar();
mortar.x = x;
mortar.y = y;
mortars.push(mortar);
game.addChild(mortar);
gamePoints -= mortarCost;
updateUI();
}
}
function placeShockTrooper(x, y) {
if (gamePoints >= shockTrooperCost) {
var shockTrooper = new ShockTrooper();
shockTrooper.x = x;
shockTrooper.y = y;
shockTroopers.push(shockTrooper);
game.addChild(shockTrooper);
gamePoints -= shockTrooperCost;
updateUI();
}
}
function placeTrap(x, y) {
if (gamePoints >= trapCost) {
var trap = new Trap();
trap.x = x;
trap.y = y;
traps.push(trap);
game.addChild(trap);
gamePoints -= trapCost;
updateUI();
}
}
function getTowerAt(x, y) {
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx = Math.abs(tower.x - x);
var dy = Math.abs(tower.y - y);
if (dx < gridSize / 2 && dy < gridSize / 2) {
return tower;
}
}
return null;
}
function updateButtonColors() {
// Reset all button colors
towerButton.tint = selectedMode === 'tower' ? 0x4CAF50 : 0xFFFFFF;
barrierButton.tint = selectedMode === 'barrier' ? 0x4CAF50 : 0xFFFFFF;
soldierButton.tint = selectedMode === 'soldier' ? 0x4CAF50 : 0xFFFFFF;
gunnerButton.tint = selectedMode === 'gunner' ? 0x4CAF50 : 0xFFFFFF;
bomberButton.tint = selectedMode === 'bomber' ? 0x4CAF50 : 0xFFFFFF;
minerButton.tint = selectedMode === 'miner' ? 0x4CAF50 : 0xFFFFFF;
catcherButton.tint = selectedMode === 'catcher' ? 0x4CAF50 : 0xFFFFFF;
exploderButton.tint = selectedMode === 'exploder' ? 0x4CAF50 : 0xFFFFFF;
freezerButton.tint = selectedMode === 'freezer' ? 0x4CAF50 : 0xFFFFFF;
burnerButton.tint = selectedMode === 'burner' ? 0x4CAF50 : 0xFFFFFF;
flamethrowerButton.tint = selectedMode === 'flamethrower' ? 0x4CAF50 : 0xFFFFFF;
freezerTroopButton.tint = selectedMode === 'freezerTroop' ? 0x4CAF50 : 0xFFFFFF;
eliteSniperButton.tint = selectedMode === 'eliteSniper' ? 0x4CAF50 : 0xFFFFFF;
heavyTankButton.tint = selectedMode === 'heavyTank' ? 0x4CAF50 : 0xFFFFFF;
laserTurretButton.tint = selectedMode === 'laserTurret' ? 0x4CAF50 : 0xFFFFFF;
rocketLauncherButton.tint = selectedMode === 'rocketLauncher' ? 0x4CAF50 : 0xFFFFFF;
shieldGeneratorButton.tint = selectedMode === 'shieldGenerator' ? 0x4CAF50 : 0xFFFFFF;
supportMedicButton.tint = selectedMode === 'supportMedic' ? 0x4CAF50 : 0xFFFFFF;
assassinButton.tint = selectedMode === 'assassin' ? 0x4CAF50 : 0xFFFFFF;
engineerButton.tint = selectedMode === 'engineer' ? 0x4CAF50 : 0xFFFFFF;
mortarButton.tint = selectedMode === 'mortar' ? 0x4CAF50 : 0xFFFFFF;
shockTrooperButton.tint = selectedMode === 'shockTrooper' ? 0x4CAF50 : 0xFFFFFF;
trapButton.tint = selectedMode === 'trap' ? 0x4CAF50 : 0xFFFFFF;
}
// Button event handlers
towerButton.down = function () {
selectedMode = 'tower';
updateButtonColors();
};
barrierButton.down = function () {
selectedMode = 'barrier';
updateButtonColors();
};
soldierButton.down = function () {
selectedMode = 'soldier';
updateButtonColors();
};
gunnerButton.down = function () {
selectedMode = 'gunner';
updateButtonColors();
};
bomberButton.down = function () {
selectedMode = 'bomber';
updateButtonColors();
};
minerButton.down = function () {
selectedMode = 'miner';
updateButtonColors();
};
catcherButton.down = function () {
selectedMode = 'catcher';
updateButtonColors();
};
exploderButton.down = function () {
selectedMode = 'exploder';
updateButtonColors();
};
freezerButton.down = function () {
selectedMode = 'freezer';
updateButtonColors();
};
burnerButton.down = function () {
selectedMode = 'burner';
updateButtonColors();
};
flamethrowerButton.down = function () {
selectedMode = 'flamethrower';
updateButtonColors();
};
freezerTroopButton.down = function () {
selectedMode = 'freezerTroop';
updateButtonColors();
};
eliteSniperButton.down = function () {
selectedMode = 'eliteSniper';
updateButtonColors();
};
heavyTankButton.down = function () {
selectedMode = 'heavyTank';
updateButtonColors();
};
laserTurretButton.down = function () {
selectedMode = 'laserTurret';
updateButtonColors();
};
rocketLauncherButton.down = function () {
selectedMode = 'rocketLauncher';
updateButtonColors();
};
shieldGeneratorButton.down = function () {
selectedMode = 'shieldGenerator';
updateButtonColors();
};
supportMedicButton.down = function () {
selectedMode = 'supportMedic';
updateButtonColors();
};
assassinButton.down = function () {
selectedMode = 'assassin';
updateButtonColors();
};
engineerButton.down = function () {
selectedMode = 'engineer';
updateButtonColors();
};
mortarButton.down = function () {
selectedMode = 'mortar';
updateButtonColors();
};
shockTrooperButton.down = function () {
selectedMode = 'shockTrooper';
updateButtonColors();
};
trapButton.down = function () {
selectedMode = 'trap';
updateButtonColors();
};
// Initialize button colors
updateButtonColors();
function spawnZombie() {
var zombie;
// 6% chance to spawn a weakening zombie after wave 4
if (wave >= 4 && Math.random() < 0.06) {
zombie = new WeakeningZombie();
} else if (wave >= 3 && Math.random() < 0.05) {
// 5% chance to spawn an immunity zombie after wave 3
zombie = new ImmunityZombie();
} else if (wave >= 2 && Math.random() < 0.08) {
// 8% chance to spawn a spawner zombie after wave 2
zombie = new SpawnerZombie();
} else if (wave >= 2 && Math.random() < 0.12) {
// 12% chance to spawn a weaker zombie after wave 2
zombie = new WeakerZombie();
} else if (wave >= 2 && Math.random() < 0.15) {
// 15% chance to spawn a booster zombie after wave 2
zombie = new BoosterZombie();
} else if (wave >= 1 && Math.random() < 0.25) {
// 25% chance to spawn a speedy zombie from wave 1
zombie = new SpeedyZombie();
} else if (wave >= 1 && Math.random() < 0.55) {
// 55% chance to spawn a tank zombie from wave 1
zombie = new TankZombie();
} else {
zombie = new Zombie();
}
// Spawn from random edge
var edge = Math.floor(Math.random() * 4);
switch (edge) {
case 0:
// Top
zombie.x = Math.random() * 2048;
zombie.y = 0;
break;
case 1:
// Right
zombie.x = 2048;
zombie.y = Math.random() * 2732;
break;
case 2:
// Bottom
zombie.x = Math.random() * 2048;
zombie.y = 2732;
break;
case 3:
// Left
zombie.x = 0;
zombie.y = Math.random() * 2732;
break;
}
// Increase speed and health with waves
if (zombie instanceof WeakeningZombie) {
zombie.speed = 0.9 + (wave - 1) * 0.07; // Weakening zombies moderate scaling
zombie.health = 25 + Math.floor((wave - 1) / 2);
zombie.maxHealth = zombie.health;
zombie.weakenRate = Math.max(90, 120 - (wave - 1) * 3); // Weaken more frequently over time
} else if (zombie instanceof ImmunityZombie) {
zombie.speed = 0.7 + (wave - 1) * 0.06; // Immunity zombies moderate scaling
zombie.health = 28 + Math.floor((wave - 1) / 2);
zombie.maxHealth = zombie.health;
zombie.immunityRate = Math.max(45, 60 - (wave - 1) * 2); // Grant immunity more frequently over time
} else if (zombie instanceof SpawnerZombie) {
zombie.speed = 0.8 + (wave - 1) * 0.05; // Spawner zombies scale slowest
zombie.health = 25 + Math.floor((wave - 1) / 2);
zombie.maxHealth = zombie.health;
zombie.spawnRate = Math.max(180, 300 - (wave - 1) * 10); // Spawn faster over time
} else if (zombie instanceof WeakerZombie) {
zombie.speed = 0.8 + (wave - 1) * 0.07; // Weaker zombies moderate scaling
zombie.health = 22 + Math.floor((wave - 1) / 2);
zombie.maxHealth = zombie.health;
zombie.weakenRate = Math.max(90, 120 - (wave - 1) * 3); // Weaken more frequently over time
} else if (zombie instanceof BoosterZombie) {
zombie.speed = 0.9 + (wave - 1) * 0.08; // Booster zombies moderate scaling
zombie.health = 35 + Math.floor((wave - 1) / 2);
zombie.maxHealth = zombie.health;
zombie.boostRate = Math.max(120, 180 - (wave - 1) * 5); // Boost more frequently over time
} else if (zombie instanceof SpeedyZombie) {
zombie.speed = 2.5 + (wave - 1) * 0.15; // Speedy zombies get even faster
zombie.health = 15 + Math.floor((wave - 1) / 2);
zombie.maxHealth = zombie.health;
} else if (zombie instanceof TankZombie) {
zombie.speed = 0.6 + (wave - 1) * 0.1; // Tank zombies scale slower
zombie.health = 20 + Math.floor((wave - 1) / 2);
zombie.maxHealth = zombie.health;
} else {
zombie.speed = 1 + (wave - 1) * 0.2;
zombie.health = 3 + Math.floor((wave - 1) / 3);
zombie.maxHealth = zombie.health;
}
zombies.push(zombie);
game.addChild(zombie);
}
function startNextWave() {
wave++;
zombiesToSpawn = 8 + wave * 4;
zombiesSpawned = 0;
waveComplete = false;
spawnTimer = 0;
updateUI();
}
var holdTimer = 0;
var isHolding = false;
var holdPosition = {
x: 0,
y: 0
};
var lastTapTime = 0;
var doubleTapDelay = 300; // milliseconds
var selectedMode = 'tower'; // 'tower', 'barrier', 'soldier', or 'bomber'
game.down = function (x, y, obj) {
var gridX = snapToGrid(x);
var gridY = snapToGrid(y);
var existingTower = getTowerAt(gridX, gridY);
if (existingTower) {
// Try to upgrade tower
existingTower.upgrade();
return;
}
// Place unit based on selected mode
if (selectedMode === 'tower') {
placeTower(gridX, gridY);
} else if (selectedMode === 'barrier') {
placeBarrier(gridX, gridY);
} else if (selectedMode === 'soldier') {
placeSoldier(x, y);
} else if (selectedMode === 'gunner') {
placeGunner(x, y);
} else if (selectedMode === 'bomber') {
placeBomber(x, y);
} else if (selectedMode === 'miner') {
placeMiner(x, y);
} else if (selectedMode === 'catcher') {
placeCatcher(x, y);
} else if (selectedMode === 'exploder') {
placeExploder(x, y);
} else if (selectedMode === 'freezer') {
placeFreezer(x, y);
} else if (selectedMode === 'burner') {
placeBurner(x, y);
} else if (selectedMode === 'flamethrower') {
placeFlamethrower(x, y);
} else if (selectedMode === 'freezerTroop') {
placeFreezerTroop(x, y);
} else if (selectedMode === 'eliteSniper') {
placeEliteSniper(x, y);
} else if (selectedMode === 'heavyTank') {
placeHeavyTank(x, y);
} else if (selectedMode === 'laserTurret') {
placeLaserTurret(gridX, gridY);
} else if (selectedMode === 'rocketLauncher') {
placeRocketLauncher(gridX, gridY);
} else if (selectedMode === 'shieldGenerator') {
placeShieldGenerator(gridX, gridY);
} else if (selectedMode === 'supportMedic') {
placeSupportMedic(x, y);
} else if (selectedMode === 'assassin') {
placeAssassin(x, y);
} else if (selectedMode === 'engineer') {
placeEngineer(x, y);
} else if (selectedMode === 'mortar') {
placeMortar(gridX, gridY);
} else if (selectedMode === 'shockTrooper') {
placeShockTrooper(x, y);
} else if (selectedMode === 'trap') {
placeTrap(x, y);
}
};
game.update = function () {
// Spawn zombies for current wave
if (!waveComplete) {
spawnTimer++;
if (spawnTimer >= 30 && zombiesSpawned < zombiesToSpawn) {
// Spawn 1-2 zombies per cycle for reduced intensity
var zombiesToSpawnNow = Math.min(1 + Math.floor(wave / 5), zombiesToSpawn - zombiesSpawned);
for (var spawnCount = 0; spawnCount < zombiesToSpawnNow; spawnCount++) {
spawnZombie();
zombiesSpawned++;
if (zombiesSpawned >= zombiesToSpawn) break;
}
spawnTimer = 0;
}
if (zombiesSpawned >= zombiesToSpawn && zombies.length === 0) {
waveComplete = true;
LK.setTimeout(startNextWave, 2000); // Start next wave after 2 seconds
}
}
// Check if zombies reached city center
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
var dx = zombie.x - cityCenter.x;
var dy = zombie.y - cityCenter.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
// Game over
LK.showGameOver();
return;
}
}
// Update all towers (this enables shooting)
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update all barriers (this enables damage dealing)
for (var i = 0; i < barriers.length; i++) {
barriers[i].update();
}
// Update all soldiers (this enables movement and shooting)
for (var i = 0; i < soldiers.length; i++) {
soldiers[i].update();
}
// Update all gunners (this enables movement and fast shooting)
for (var i = 0; i < gunners.length; i++) {
gunners[i].update();
}
// Update all bombers (this enables movement and bomb launching)
for (var i = 0; i < bombers.length; i++) {
bombers[i].update();
}
// Update all bombs (this enables movement and explosion)
for (var i = 0; i < bombs.length; i++) {
bombs[i].update();
}
// Update all miners (this enables money generation)
for (var i = 0; i < miners.length; i++) {
miners[i].update();
}
// Update all catchers (this enables zombie freezing)
for (var i = 0; i < catchers.length; i++) {
catchers[i].update();
}
// Update all exploders (this enables explosion mechanics)
for (var i = 0; i < exploders.length; i++) {
exploders[i].update();
}
// Update all freezers (this enables global freeze mechanics)
for (var i = 0; i < freezers.length; i++) {
freezers[i].update();
}
// Update all burners (this enables global burn mechanics)
for (var i = 0; i < burners.length; i++) {
burners[i].update();
}
// Update all flamethrowers (this enables continuous burn mechanics)
for (var i = 0; i < flamethrowers.length; i++) {
flamethrowers[i].update();
}
// Update all freezer troops (this enables continuous freeze mechanics)
for (var i = 0; i < freezerTroops.length; i++) {
freezerTroops[i].update();
}
// Update all elite snipers (this enables high-damage sniping)
for (var i = 0; i < eliteSnipers.length; i++) {
eliteSnipers[i].update();
}
// Update all heavy tanks (this enables armored combat)
for (var i = 0; i < heavyTanks.length; i++) {
heavyTanks[i].update();
}
// Update all laser turrets (this enables continuous beam damage)
for (var i = 0; i < laserTurrets.length; i++) {
laserTurrets[i].update();
}
// Update all rocket launchers (this enables area damage rockets)
for (var i = 0; i < rocketLaunchers.length; i++) {
rocketLaunchers[i].update();
}
// Update all rockets (this enables rocket movement and explosion)
for (var i = 0; i < rockets.length; i++) {
rockets[i].update();
}
// Update all shield generators (this enables defensive support)
for (var i = 0; i < shieldGenerators.length; i++) {
shieldGenerators[i].update();
}
// Update all support medics (this enables healing)
for (var i = 0; i < supportMedics.length; i++) {
supportMedics[i].update();
}
// Update all assassins (this enables stealth attacks)
for (var i = 0; i < assassins.length; i++) {
assassins[i].update();
}
// Update all engineers (this enables repair mechanics)
for (var i = 0; i < engineers.length; i++) {
engineers[i].update();
}
// Update all mortars (this enables artillery strikes)
for (var i = 0; i < mortars.length; i++) {
mortars[i].update();
}
// Update all shock troopers (this enables electrical attacks)
for (var i = 0; i < shockTroopers.length; i++) {
shockTroopers[i].update();
}
// Update all traps (this enables zombie trapping)
for (var i = 0; i < traps.length; i++) {
traps[i].update();
}
// Update all fire tiles (this enables fire damage)
for (var i = 0; i < fireTiles.length; i++) {
fireTiles[i].update();
}
// Update score
LK.setScore(gamePoints + (wave - 1) * 100);
};
// Pause information text
var pauseInfoText = new Text2('', {
size: 40,
fill: 0xFFFFFF
});
pauseInfoText.anchor.set(0.5, 0.5);
pauseInfoText.x = 1024;
pauseInfoText.y = 1366;
pauseInfoText.alpha = 0;
game.addChild(pauseInfoText);
// Show troop information when game is paused
LK.on('pause', function () {
var infoText = 'TROOP ABILITIES:\n\n';
infoText += 'TOWER (20): Shoots bullets at zombies\n';
infoText += 'BARRIER (30): Blocks zombies, takes damage\n';
infoText += 'SOLDIER (40): Patrols and shoots at zombies\n';
infoText += 'GUNNER (35): Fast shooting, lower damage\n';
infoText += 'BOMBER (60): Launches explosive bombs\n';
infoText += 'MINER (35): Generates money over time\n';
infoText += 'CATCHER (45): Freezes zombies in range\n';
infoText += 'EXPLODER (50): Explodes near zombies, creates fire\n';
infoText += 'FREEZER (70): Global freeze explosion\n';
infoText += 'BURNER (80): Global burn explosion\n';
infoText += 'FLAMETHROWER (55): Burns zombies continuously\n';
infoText += 'FREEZER TROOP (50): Freezes with breathing\n';
infoText += 'ELITE SNIPER (100): High damage, long range\n';
infoText += 'HEAVY TANK (120): Armored, high health\n';
infoText += 'LASER TURRET (90): Continuous beam damage\n';
infoText += 'ROCKET LAUNCHER (110): Area damage rockets\n';
infoText += 'SHIELD GENERATOR (80): Protects nearby troops\n';
infoText += 'SUPPORT MEDIC (65): Heals nearby troops\n';
infoText += 'ASSASSIN (85): Stealth high-damage attacks\n';
infoText += 'ENGINEER (70): Repairs structures and tanks\n';
infoText += 'MORTAR (130): Long-range artillery strikes\n';
infoText += 'SHOCK TROOPER (75): Electrical area damage\n';
infoText += 'TRAP (25): Single-use zombie trap';
pauseInfoText.setText(infoText);
pauseInfoText.alpha = 1;
});
// Hide troop information when game is resumed
LK.on('resume', function () {
pauseInfoText.alpha = 0;
});
// Start first wave after 3 seconds
LK.setTimeout(startNextWave, 3000);
;
Citycenter. In-Game asset. 2d. High contrast. No shadows
Un zombie. In-Game asset. 2d. High contrast. No shadows
Una casilla. In-Game asset. 2d. High contrast. No shadows
Toreta. In-Game asset. 2d. High contrast. No shadows
Un cañón. In-Game asset. 2d. High contrast. No shadows
Que tenga bombas
Una bomba. In-Game asset. 2d. High contrast. No shadows
Crea una torreta. In-Game asset. 2d. High contrast. No shadows
Un minero. In-Game asset. 2d. High contrast. No shadows
Un zombie gigante. In-Game asset. 2d. High contrast. No shadows
Fuego. In-Game asset. 2d. High contrast. No shadows
Un congelador. In-Game asset. 2d. High contrast. No shadows
Un zombie con un traje de limpieza. In-Game asset. 2d. High contrast. No shadows
Crea un zombie con un coche. In-Game asset. 2d. High contrast. No shadows
Un tanque. In-Game asset. 2d. High contrast. No shadows
Laserturret. In-Game asset. 2d. High contrast. No shadows
Un misile. In-Game asset. 2d. High contrast. No shadows