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