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