User prompt
When a Zombie dies, the turret still facing the dead Zombie and continues to shoot.
User prompt
Add background asset
User prompt
Add background
User prompt
make SSC rotate randomly after some time on a regular basis ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add cardboard zombie (when dead will spawn a random zombie (except Crowzombie)),box zombies (same as Cardboard zombies but spawns three),football zombies,Buried zombies: appear randomly on the map (not too close to home) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add wall(block zombies and get attacked by zombies) and attractor(attract all zombies) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add Electrical turret,Spike turret,ball turret ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
S.S.C spawn 15 sun instead of 10 sun
User prompt
change boss zombie, divided into three types: Shaman zombie: summon fake zombie, heal when low on health, Ninja zombie: teleport, clone, Crazy zombie: dig the ground, buff other zombies, Soldier zombie: go in groups with 4 other and two tanks ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
S.S.C only spawn 10sun instead of 20sun
User prompt
Runners and Tough only appear after 45s
User prompt
added cooldown for building turrets
User prompt
change the zombie spawn mechanism, only when there are 1-2 zombies left, then the next wave will be born, SSC only spawns 10 suns in about 15-25 seconds
User prompt
When zombies are not in range, turrets will turn back to default direction and stop shooting.
User prompt
Add arrows, when pressed will switch to pages containing turrets to make the game UI more compact
User prompt
Add turret: Frozen turret (causes freezing, slows down), Flame thrower (causes fire damage over time, if the zombie is frozen, slowing down will end that effect), Acid turret: is a flame thrower but the damage over time is smaller but will not affect the freezing effect), Automatic bullet turret: very large range, bullets will automatically chase the enemy, in return the damage is small, Minigun turret, rocket launcher ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Special zombies only appear if the player defends the house for 100s
User prompt
added new zombies: Basic zombies now include Tough zombies and Runners, special zombies: Umbrella zombie (umbrella works like a shield), Crows Zombie (when dead, it will release 3 crow zombies)
User prompt
Increases turret damage and slows zombies down a bit
User prompt
Day only turns Night when progress bar runs out
User prompt
increase the range of turrets
User prompt
Make gridcell full screen,except house area
User prompt
make the drag and drop area and build turret full screen, except the house area
User prompt
When not in range, the turrets will stop firing and wait until another zombie enters, increasing the turret's firing range further, when dragging and dropping the turret, the player can see how far the turret's firing range is.
User prompt
increase gridcell size
/****
* 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 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++) {
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 BossZombie = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('bossZombie', {
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.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 = 0xFFFFFF;
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 > 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.x += self.speed;
if (self.x > 2048) {
lives -= 5;
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 boss zombies
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 100);
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 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 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++) {
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 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++) {
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 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++) {
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 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 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++) {
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++) {
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++) {
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 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++) {
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 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.sunCount = 0; // Track how many suns have been generated
self.maxSuns = 10; // Maximum of 10 suns per collector
self.energyGenerationTime = Math.floor(Math.random() * 600) + 900; // Random 15-25 seconds (900-1500 frames at 60fps)
self.update = function () {
// Only generate energy if we haven't reached the sun limit
if (self.sunCount < self.maxSuns) {
self.energyTimer++;
if (self.energyTimer >= self.energyGenerationTime) {
self.energyTimer = 0;
self.sunCount++; // Increment sun count
// Set new random interval for next generation (15-25 seconds)
self.energyGenerationTime = Math.floor(Math.random() * 600) + 900; // Random 15-25 seconds
var energyGain = 20;
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();
}
});
// Visual indication when collector is exhausted
if (self.sunCount >= self.maxSuns) {
collector.tint = 0x666666; // Dim the collector when exhausted
}
}
}
collector.tint = self.isDaytime && self.sunCount < self.maxSuns ? 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 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++) {
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 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) {
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: 0x87CEEB
});
/****
* Game Code
****/
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)
var maxPages = 2; // Total number of pages (0, 1, 2)
var solarCollectors = [];
var towers = [];
var zombies = [];
var houses = [];
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);
// 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;
};
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;
// 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;
}
// 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;
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);
}
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) {
zombie = new BossZombie();
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.5) {
// 50% Basic zombie
zombie = new Zombie();
} else if (zombieRoll < 0.65) {
// 15% Tough zombie
zombie = new ToughZombie();
} else if (zombieRoll < 0.8) {
// 15% Runner zombie
zombie = new RunnerZombie();
} else if (zombieRoll < 0.9) {
// 10% Umbrella zombie
zombie = new UmbrellaZombie();
} else {
// 10% Crows zombie
zombie = new CrowsZombie();
}
}
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
}
// 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 - only trigger new wave when 1-2 zombies remain
if (zombiesToSpawn <= 0 && !bossActive) {
// Check if we should spawn next wave based on remaining zombies
var aliveZombies = zombies.length;
if (aliveZombies <= 2) {
// Only spawn when 1-2 zombies left
waveTimer++;
// Random delay between waves (2-5 seconds after condition is met)
var minWaveDelay = 120; // 2 seconds
var maxWaveDelay = 300; // 5 seconds
var randomWaveDelay = Math.floor(Math.random() * (maxWaveDelay - minWaveDelay + 1)) + minWaveDelay;
if (waveTimer >= randomWaveDelay) {
waveTimer = 0;
wave++;
// Spawn 5-8 zombies per wave depending on difficulty
var baseZombies = isEasyPeriod ? 5 : 6;
var maxZombies = isEasyPeriod ? 6 : 8;
// Increase chance of more zombies as difficulty rises
var zombieCount = baseZombies;
if (!isEasyPeriod && Math.random() < (difficultyMultiplier - 1) * 0.4) {
zombieCount = maxZombies;
}
zombiesPerWave = zombieCount;
zombiesToSpawn = zombiesPerWave;
}
} else {
// Reset wave timer if too many zombies still alive
waveTimer = 0;
}
} 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].health > 100 && bossActive) {
// Boss zombies have much higher health
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);
}
}
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'); ===================================================================
--- original.js
+++ change.js
@@ -1297,37 +1297,47 @@
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.sunCount = 0; // Track how many suns have been generated
+ self.maxSuns = 10; // Maximum of 10 suns per collector
+ self.energyGenerationTime = Math.floor(Math.random() * 600) + 900; // Random 15-25 seconds (900-1500 frames at 60fps)
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 = 20;
- 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();
+ // Only generate energy if we haven't reached the sun limit
+ if (self.sunCount < self.maxSuns) {
+ self.energyTimer++;
+ if (self.energyTimer >= self.energyGenerationTime) {
+ self.energyTimer = 0;
+ self.sunCount++; // Increment sun count
+ // Set new random interval for next generation (15-25 seconds)
+ self.energyGenerationTime = Math.floor(Math.random() * 600) + 900; // Random 15-25 seconds
+ var energyGain = 20;
+ 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();
+ }
+ });
+ // Visual indication when collector is exhausted
+ if (self.sunCount >= self.maxSuns) {
+ collector.tint = 0x666666; // Dim the collector when exhausted
}
- });
+ }
}
- collector.tint = self.isDaytime ? 0xFFD700 : 0xB8860B;
+ collector.tint = self.isDaytime && self.sunCount < self.maxSuns ? 0xFFD700 : 0xB8860B;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
@@ -2618,28 +2628,36 @@
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
+ // Wave-based zombie spawning - only trigger new wave when 1-2 zombies remain
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;
+ // Check if we should spawn next wave based on remaining zombies
+ var aliveZombies = zombies.length;
+ if (aliveZombies <= 2) {
+ // Only spawn when 1-2 zombies left
+ waveTimer++;
+ // Random delay between waves (2-5 seconds after condition is met)
+ var minWaveDelay = 120; // 2 seconds
+ var maxWaveDelay = 300; // 5 seconds
+ var randomWaveDelay = Math.floor(Math.random() * (maxWaveDelay - minWaveDelay + 1)) + minWaveDelay;
+ if (waveTimer >= randomWaveDelay) {
+ waveTimer = 0;
+ wave++;
+ // Spawn 5-8 zombies per wave depending on difficulty
+ var baseZombies = isEasyPeriod ? 5 : 6;
+ var maxZombies = isEasyPeriod ? 6 : 8;
+ // Increase chance of more zombies as difficulty rises
+ var zombieCount = baseZombies;
+ if (!isEasyPeriod && Math.random() < (difficultyMultiplier - 1) * 0.4) {
+ zombieCount = maxZombies;
+ }
+ zombiesPerWave = zombieCount;
+ zombiesToSpawn = zombiesPerWave;
}
- zombiesPerWave = zombieCount;
- zombiesToSpawn = zombiesPerWave;
+ } else {
+ // Reset wave timer if too many zombies still alive
+ waveTimer = 0;
}
} else if (zombiesToSpawn > 0) {
zombieSpawnTimer++;
// Spawn zombies quickly within a wave (every 0.5-1 second)
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