User prompt
towers throw bomb only one time when we use bomb after towers is normal ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
towers also throw bomb on enemy ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'takeDamage')' in or related to this line: 'self.target.takeDamage(finalDamage);' Line Number: 1483
User prompt
add start switch when i tap one switch game was start and enemy is coming
User prompt
missile use only one short after seconded time we use then missile can shot and after cannon damage is normal ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
missile can damage enemy hp 100
User prompt
cannon can launch missile ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Solider can throw bomb after 0.5 sec ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Solider can throw 1 bomb in 1 time and after 1 sec throw 2 bomb ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Solider can throw 1 bomb ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Bomb color is white ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Enemy throw bomb infinite time ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
special item more visible like round ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: Cannot use 'in' operator to search for 'tint' in null' in or related to this line: 'tween(tower.shieldGraphics, {' Line Number: 560 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
bomb is throw on enemy when tower used it ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
protect shield when used it make a blue circle around the tower ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
cannon range is bigger than laser and smaller than sniper
User prompt
missile only shoot with cannon
User prompt
bomb make more visible missile look like calendar with corn on top
User prompt
shield is protect towers and it hp is 70
User prompt
bomb killed runner and missile damage is 120
User prompt
towers used special item to damage enemy
User prompt
show name under special item and it also used with dragged and place on tower
User prompt
special item used in bettle
User prompt
every wave special item dropped and when boss kill every special item dropped 2 times and special item see in vertical in side of cannon
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (startX, startY, target, damage) {
var self = Container.call(this);
self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 8;
self.update = function () {
if (!self.target || self.target.destroyed) {
self.removeBullet();
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
self.target.takeDamage(self.damage);
self.removeBullet();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.removeBullet = function () {
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.pathIndex = 0;
self.x = path[0].x;
self.y = path[0].y;
self.lastPathIndex = 0;
if (type === 'soldier') {
self.attachAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5
});
var soldierEyeLeft = self.attachAsset('soldierEyeLeft', {
anchorX: 0.5,
anchorY: 0.5,
x: -12,
y: -8
});
var soldierEyeRight = self.attachAsset('soldierEyeRight', {
anchorX: 0.5,
anchorY: 0.5,
x: 12,
y: -8
});
var soldierMouth = self.attachAsset('soldierMouth', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 8
});
self.health = 100;
self.maxHealth = 100;
self.speed = 2.5;
self.reward = 10;
// Add shield for soldiers after 2 bosses killed
self.hasShield = false;
self.shieldGraphics = null;
if (bossKillCount >= 2 && Math.random() < 0.6) {
// 60% chance for shield after 2 bosses
self.hasShield = true;
self.shieldGraphics = self.attachAsset('soldierShield', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
alpha: 0.8
});
self.health = 150; // Increased health with shield
self.maxHealth = 150;
}
} else if (type === 'tank') {
self.attachAsset('tank', {
anchorX: 0.5,
anchorY: 0.5
});
var tankNozzle = self.attachAsset('tankNozzle', {
anchorX: 0,
anchorY: 0.5,
x: 40,
y: 0
});
self.health = 250;
self.maxHealth = 250;
self.speed = 2;
self.reward = 15;
} else if (type === 'runner') {
var runnerWingLeft = self.attachAsset('runnerWingLeft', {
anchorX: 0.5,
anchorY: 0.5,
x: -28,
y: 0,
rotation: 0
});
var runnerWingRight = self.attachAsset('runnerWingRight', {
anchorX: 0.5,
anchorY: 0.5,
x: 28,
y: 0,
rotation: 0
});
self.attachAsset('runner', {
anchorX: 0.5,
anchorY: 0.5
});
// Add wing flapping animation to make wings more visible
self.wingFlapTimer = 0;
tween(runnerWingLeft, {
rotation: -0.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(runnerWingLeft, {
rotation: 0.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(runnerWingLeft, {
rotation: 0
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
});
tween(runnerWingRight, {
rotation: 0.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(runnerWingRight, {
rotation: -0.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(runnerWingRight, {
rotation: 0
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
});
var runnerEyeLeft = self.attachAsset('runnerEyeLeft', {
anchorX: 0.5,
anchorY: 0.5,
x: -8,
y: -6
});
var runnerEyeRight = self.attachAsset('runnerEyeRight', {
anchorX: 0.5,
anchorY: 0.5,
x: 8,
y: -6
});
self.health = 50;
self.maxHealth = 50;
self.speed = 4;
self.reward = 5;
} else if (type === 'boss') {
self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
var bossEyeLeft = self.attachAsset('bossEyeLeft', {
anchorX: 0.5,
anchorY: 0.5,
x: -18,
y: -12
});
var bossEyeRight = self.attachAsset('bossEyeRight', {
anchorX: 0.5,
anchorY: 0.5,
x: 18,
y: -12
});
var bossMouth = self.attachAsset('bossMouth', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 12
});
var bossHealth = 500 + bossKillCount * 200;
self.health = bossHealth;
self.maxHealth = bossHealth;
self.speed = 1;
self.reward = 200;
}
// Create HP display text for enemy
self.hpText = new Text2(self.health + ' HP', {
size: 20,
fill: '#ff0000'
});
self.hpText.anchor.set(0.5, 0.5);
self.hpText.x = 0;
self.hpText.y = -40;
self.addChild(self.hpText);
// Add attack cooldown and special abilities
self.attackCooldown = 0;
self.specialAbilityCooldown = 0;
self.update = function () {
self.move();
// Update HP display
self.hpText.setText(self.health + ' HP');
// Reduce cooldowns
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (self.specialAbilityCooldown > 0) {
self.specialAbilityCooldown--;
}
// Special abilities for different enemy types
if (self.type === 'soldier' && self.specialAbilityCooldown <= 0) {
self.throwBomb();
self.specialAbilityCooldown = 180; // 3 seconds
} else if (self.type === 'tank' && self.attackCooldown <= 0) {
self.fireTower();
self.attackCooldown = 120; // 2 seconds
} else if (self.type === 'boss' && self.attackCooldown <= 0) {
self.fireMissile();
self.attackCooldown = 90; // 1.5 seconds
}
// Continuous wing flapping for runners to make wings more visible
if (self.type === 'runner') {
self.wingFlapTimer++;
if (self.wingFlapTimer >= 90) {
// Every 1.5 seconds
var wingLeft = self.children[0]; // runnerWingLeft
var wingRight = self.children[1]; // runnerWingRight
tween(wingLeft, {
rotation: -0.4
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wingLeft, {
rotation: 0.4
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wingLeft, {
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
tween(wingRight, {
rotation: 0.4
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wingRight, {
rotation: -0.4
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wingRight, {
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
self.wingFlapTimer = 0;
}
}
};
self.move = function () {
if (self.pathIndex >= path.length - 1) {
self.reachedEnd();
return;
}
// Check for tower collisions and damage them
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < 50) {
// Collision distance
var damage = 0;
if (self.type === 'runner') {
damage = 20;
} else if (self.type === 'soldier') {
damage = 35;
} else if (self.type === 'tank') {
damage = 45;
} else if (self.type === 'boss') {
damage = 70;
}
tower.hp -= damage;
if (tower.hp <= 0) {
// Remove tower from game
for (var j = towers.length - 1; j >= 0; j--) {
if (towers[j] === tower) {
towers.splice(j, 1);
break;
}
}
tower.destroy();
}
// Enemy dies after attacking tower
self.die();
return;
}
}
var targetPoint = path[self.pathIndex + 1];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
self.pathIndex++;
self.lastPathIndex = self.pathIndex;
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.takeDamage = function (damage) {
// Shield absorbs damage for soldiers
if (self.type === 'soldier' && self.hasShield && self.shieldGraphics) {
// Shield absorbs 50% of damage
var actualDamage = Math.ceil(damage * 0.5);
self.health -= actualDamage;
// Flash shield when taking damage
tween(self.shieldGraphics, {
tint: 0xff0000
}, {
duration: 150,
onFinish: function onFinish() {
tween(self.shieldGraphics, {
tint: 0xffffff
}, {
duration: 150
});
}
});
// Remove shield when health gets low
if (self.health <= 50 && self.shieldGraphics) {
// Shield breaks animation
tween(self.shieldGraphics, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
if (self.shieldGraphics) {
self.shieldGraphics.destroy();
self.shieldGraphics = null;
}
self.hasShield = false;
}
});
}
} else {
self.health -= damage;
}
if (self.health <= 0) {
self.die();
}
};
self.blastTowers = function (blastRadius, blastDamage) {
// Create visual blast effect
var blastEffect = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.8
});
blastEffect.x = self.x;
blastEffect.y = self.y;
blastEffect.tint = 0xff4444;
game.addChild(blastEffect);
// Animate blast expanding
tween(blastEffect, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
blastEffect.destroy();
}
});
// Damage towers within blast radius
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance <= blastRadius) {
tower.hp -= blastDamage;
// Flash tower red to show damage
tween(tower, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(tower, {
tint: 0xffffff
}, {
duration: 200
});
}
});
if (tower.hp <= 0) {
// Remove tower from game
for (var j = towers.length - 1; j >= 0; j--) {
if (towers[j] === tower) {
towers.splice(j, 1);
break;
}
}
tower.destroy();
}
}
}
};
self.die = function () {
if (self.type === 'boss') {
bossKillCount++;
}
// Drop chances for different enemy types
var dropChance = Math.random();
var shouldDrop = false;
var dropMultiplier = 1;
// Every wave special item drops guaranteed
if (waveSpecialItemDropped === false) {
shouldDrop = true;
waveSpecialItemDropped = true;
}
// Boss kills drop 2 special items
if (self.type === 'boss') {
shouldDrop = true;
dropMultiplier = 2;
}
// Regular drop chances (if not already dropping from wave/boss)
if (!shouldDrop) {
if (self.type === 'runner' && dropChance < 0.008) {
shouldDrop = true;
} else if (self.type === 'soldier' && dropChance < 0.002) {
shouldDrop = true;
} else if (self.type === 'tank' && dropChance < 0.0004) {
shouldDrop = true;
}
}
if (shouldDrop) {
for (var dropCount = 0; dropCount < dropMultiplier; dropCount++) {
var itemType = ['bomb', 'shield', 'missile'][Math.floor(Math.random() * 3)];
var specialItem;
if (itemType === 'bomb') {
specialItem = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
specialItem.tint = 0x000000;
} else if (itemType === 'shield') {
specialItem = LK.getAsset('soldierShield', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
specialItem.tint = 0xc0c0c0;
} else if (itemType === 'missile') {
specialItem = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
specialItem.tint = 0xff0000;
}
// Position item vertically beside cannon (left side)
var cannonX = 500; // Cannon button X position
var baseY = 2400; // Above cannon button
specialItem.x = cannonX - 150; // Left side of cannon
specialItem.y = baseY - specialItems.length * 80; // Stack vertically
// Add to special items array for tracking
specialItems.push(specialItem);
game.addChild(specialItem);
// Animate special item with gentle floating effect
tween(specialItem, {
y: specialItem.y - 10,
alpha: 0.9
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(specialItem, {
y: specialItem.y + 10,
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Loop the floating animation
if (specialItem && !specialItem.destroyed) {
tween(specialItem, {
y: specialItem.y - 10,
alpha: 0.9
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
});
}
});
}
}
// Special death effects for different enemy types
if (self.type === 'runner') {
// Runner blasts with smaller radius and damage
self.blastTowers(120, 15);
} else if (self.type === 'soldier') {
// Soldier blasts with larger radius and damage
self.blastTowers(150, 25);
} else if (self.type === 'boss') {
// Boss blasts with massive radius and damage
self.blastTowers(200, 60);
}
money += self.reward;
moneyText.setText('Money: ' + money);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
LK.getSound('enemyHit').play();
};
self.throwBomb = function () {
if (self.type !== 'soldier') return;
// Find nearest tower to throw bomb at
var nearestTower = null;
var nearestDistance = Infinity;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < nearestDistance && distance <= 300) {
// 300 pixel throw range
nearestTower = tower;
nearestDistance = distance;
}
}
if (nearestTower) {
// Create bomb projectile
var bomb = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
bomb.x = self.x;
bomb.y = self.y;
bomb.tint = 0x000000;
game.addChild(bomb);
// Animate bomb flying to tower
tween(bomb, {
x: nearestTower.x,
y: nearestTower.y
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Explode at tower location
var blastEffect = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.8
});
blastEffect.x = bomb.x;
blastEffect.y = bomb.y;
blastEffect.tint = 0xff8800;
game.addChild(blastEffect);
// Animate explosion
tween(blastEffect, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
blastEffect.destroy();
}
});
// Damage towers in blast radius
for (var j = 0; j < towers.length; j++) {
var tower = towers[j];
var distance = Math.sqrt(Math.pow(tower.x - bomb.x, 2) + Math.pow(tower.y - bomb.y, 2));
if (distance <= 100) {
// Bomb blast radius
tower.hp -= 30;
// Flash tower red
tween(tower, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(tower, {
tint: 0xffffff
}, {
duration: 200
});
}
});
if (tower.hp <= 0) {
// Remove tower from game
for (var k = towers.length - 1; k >= 0; k--) {
if (towers[k] === tower) {
towers.splice(k, 1);
break;
}
}
tower.destroy();
}
}
}
bomb.destroy();
}
});
}
};
self.fireTower = function () {
if (self.type !== 'tank') return;
// Find nearest tower to fire at
var nearestTower = null;
var nearestDistance = Infinity;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < nearestDistance && distance <= 400) {
// 400 pixel firing range
nearestTower = tower;
nearestDistance = distance;
}
}
if (nearestTower) {
// Create tank projectile
var tankBullet = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
tankBullet.x = self.x;
tankBullet.y = self.y;
tankBullet.tint = 0x8b572a;
game.addChild(tankBullet);
// Animate bullet flying to tower
tween(tankBullet, {
x: nearestTower.x,
y: nearestTower.y
}, {
duration: 600,
easing: tween.linear,
onFinish: function onFinish() {
// Damage the tower
nearestTower.hp -= 40;
// Flash tower red
tween(nearestTower, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(nearestTower, {
tint: 0xffffff
}, {
duration: 200
});
}
});
if (nearestTower.hp <= 0) {
// Remove tower from game
for (var j = towers.length - 1; j >= 0; j--) {
if (towers[j] === nearestTower) {
towers.splice(j, 1);
break;
}
}
nearestTower.destroy();
}
tankBullet.destroy();
}
});
}
};
self.fireMissile = function () {
if (self.type !== 'boss') return;
// Find nearest tower to fire missile at
var nearestTower = null;
var nearestDistance = Infinity;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < nearestDistance && distance <= 500) {
// 500 pixel missile range
nearestTower = tower;
nearestDistance = distance;
}
}
if (nearestTower) {
// Create boss missile
var missile = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
missile.x = self.x;
missile.y = self.y;
missile.tint = 0xff0000;
game.addChild(missile);
// Animate missile flying to tower
tween(missile, {
x: nearestTower.x,
y: nearestTower.y
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create explosion effect
var explosionEffect = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
alpha: 0.9
});
explosionEffect.x = missile.x;
explosionEffect.y = missile.y;
explosionEffect.tint = 0xffaa00;
game.addChild(explosionEffect);
// Animate explosion
tween(explosionEffect, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
// Damage towers in missile blast radius
for (var j = 0; j < towers.length; j++) {
var tower = towers[j];
var distance = Math.sqrt(Math.pow(tower.x - missile.x, 2) + Math.pow(tower.y - missile.y, 2));
if (distance <= 120) {
// Missile blast radius
tower.hp -= 50;
// Flash tower red
tween(tower, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(tower, {
tint: 0xffffff
}, {
duration: 200
});
}
});
if (tower.hp <= 0) {
// Remove tower from game
for (var k = towers.length - 1; k >= 0; k--) {
if (towers[k] === tower) {
towers.splice(k, 1);
break;
}
}
tower.destroy();
}
}
}
missile.destroy();
}
});
}
};
self.reachedEnd = function () {
lives--;
livesText.setText('Lives: ' + lives);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
if (lives <= 0) {
LK.showGameOver();
}
};
return self;
});
var Tower = Container.expand(function (type, x, y) {
var self = Container.call(this);
self.type = type;
self.x = x;
self.y = y;
self.range = 200;
self.shootCooldown = 0;
self.target = null;
if (type === 'cannon') {
self.attachAsset('cannon', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 30;
self.fireRate = 60;
self.range = 120;
self.hp = 190;
self.maxHp = 190;
} else if (type === 'sniper') {
self.attachAsset('sniper', {
anchorX: 0.5,
anchorY: 0.5
});
var sniperTriangle1 = self.attachAsset('sniperTriangle1', {
anchorX: 0.5,
anchorY: 0.5,
x: -15,
y: -10,
rotation: Math.PI / 4
});
var sniperTriangle2 = self.attachAsset('sniperTriangle2', {
anchorX: 0.5,
anchorY: 0.5,
x: 15,
y: -10,
rotation: -Math.PI / 4
});
self.damage = 80;
self.fireRate = 120;
self.range = 350;
self.hp = 240;
self.maxHp = 240;
} else if (type === 'laser') {
self.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 15;
self.fireRate = 20;
self.hp = 150;
self.maxHp = 150;
}
// Create HP display text
self.hpText = new Text2(self.hp + ' HP', {
size: 25,
fill: '#ffffff'
});
self.hpText.anchor.set(0.5, 0.5);
self.hpText.x = 0;
self.hpText.y = -60;
self.addChild(self.hpText);
// Add HP regeneration timer
self.hpRegenTimer = 0;
if (type === 'laser') {
self.hpRegenRate = 120; // 2 seconds at 60 FPS
} else if (type === 'cannon') {
self.hpRegenRate = 165; // 2.75 seconds at 60 FPS
} else if (type === 'sniper') {
self.hpRegenRate = 225; // 3.75 seconds at 60 FPS
}
self.hpRegenAmount = Math.ceil(self.maxHp * 0.1); // Regenerate 10% of max HP
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
self.findTarget();
if (self.target && self.shootCooldown <= 0) {
self.shoot();
self.shootCooldown = self.fireRate;
}
// HP regeneration logic
if (self.hp < self.maxHp) {
self.hpRegenTimer++;
if (self.hpRegenTimer >= self.hpRegenRate) {
self.hp = Math.min(self.hp + self.hpRegenAmount, self.maxHp);
self.hpRegenTimer = 0;
// Visual regeneration effect
tween(self, {
tint: 0x00ff00
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 200
});
}
});
}
}
// Update HP display
self.hpText.setText(self.hp + ' HP');
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestEnemy = enemy;
closestDistance = distance;
}
}
self.target = closestEnemy;
};
self.shoot = function () {
if (self.target) {
var bullet = new Bullet(self.x, self.y, self.target, self.damage);
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
}
};
self.updateFireArrow = function () {
// Arrow display disabled
};
return self;
});
var TowerSpot = Container.expand(function (x, y) {
var self = Container.call(this);
self.attachAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = x;
self.y = y;
self.occupied = false;
self.tower = null;
self.down = function (localX, localY, obj) {
if (!self.occupied && selectedTowerType && canAffordTower(selectedTowerType)) {
self.placeTower(selectedTowerType);
}
};
self.placeTower = function (type) {
var cost = getTowerCost(type);
if (money >= cost) {
money -= cost;
moneyText.setText('Money: ' + money);
var tower = new Tower(type, self.x, self.y);
towers.push(tower);
game.addChild(tower);
self.occupied = true;
self.tower = tower;
self.alpha = 0.3;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
var money = 350;
var lives = 20;
var wave = 1;
var waveTimer = 0;
var spawnTimer = 0;
var enemiesInWave = 0;
var enemiesSpawned = 0;
var selectedTowerType = null;
var selectedTower = null;
var rangeIndicator = null;
var bossKillCount = 0;
var draggedTower = null;
var draggedTowerType = null;
var isDragging = false;
var towers = [];
var enemies = [];
var bullets = [];
var towerSpots = [];
var specialItems = [];
var waveSpecialItemDropped = false;
var path = [{
x: 1024,
y: 0
}, {
x: 1024,
y: 200
}, {
x: 1600,
y: 200
}, {
x: 1600,
y: 400
}, {
x: 400,
y: 400
}, {
x: 400,
y: 600
}, {
x: 1700,
y: 600
}, {
x: 1700,
y: 800
}, {
x: 300,
y: 800
}, {
x: 300,
y: 1000
}, {
x: 1500,
y: 1000
}, {
x: 1500,
y: 1200
}, {
x: 500,
y: 1200
}, {
x: 500,
y: 1400
}, {
x: 1600,
y: 1400
}, {
x: 1600,
y: 1600
}, {
x: 200,
y: 1600
}, {
x: 200,
y: 1800
}, {
x: 1700,
y: 1800
}, {
x: 1700,
y: 2000
}, {
x: 600,
y: 2000
}, {
x: 600,
y: 2200
}, {
x: 1400,
y: 2200
}, {
x: 1400,
y: 2400
}, {
x: 1024,
y: 2400
}, {
x: 1024,
y: 2600
}];
// Create path visualization
for (var i = 0; i < path.length - 1; i++) {
var pathSegment = LK.getAsset('path', {
anchorX: 0.5,
anchorY: 0.5
});
pathSegment.x = (path[i].x + path[i + 1].x) / 2;
pathSegment.y = (path[i].y + path[i + 1].y) / 2;
var dx = path[i + 1].x - path[i].x;
var dy = path[i + 1].y - path[i].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (Math.abs(dx) > Math.abs(dy)) {
pathSegment.width = distance;
pathSegment.height = 120;
} else {
pathSegment.width = 120;
pathSegment.height = distance;
}
game.addChild(pathSegment);
}
// UI Elements
var moneyText = new Text2('Money: ' + money, {
size: 60,
fill: '#ffffff'
});
moneyText.anchor.set(0, 0);
LK.gui.topLeft.addChild(moneyText);
moneyText.x = 120;
moneyText.y = 20;
var livesText = new Text2('Lives: ' + lives, {
size: 60,
fill: '#ffffff'
});
livesText.anchor.set(0, 0);
LK.gui.topLeft.addChild(livesText);
livesText.x = 120;
livesText.y = 100;
var waveText = new Text2('Wave: ' + wave, {
size: 60,
fill: '#ffffff'
});
waveText.anchor.set(0, 0);
LK.gui.topLeft.addChild(waveText);
waveText.x = 120;
waveText.y = 180;
// Tower selection buttons
var cannonButton = LK.getAsset('cannon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
cannonButton.x = 500;
cannonButton.y = 2500;
game.addChild(cannonButton);
var sniperButton = LK.getAsset('sniper', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
sniperButton.x = 900;
sniperButton.y = 2500;
game.addChild(sniperButton);
var laserButton = LK.getAsset('laser', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
laserButton.x = 1300;
laserButton.y = 2500;
game.addChild(laserButton);
var cannonNameText = new Text2('Cannon', {
size: 35,
fill: '#ffffff'
});
cannonNameText.anchor.set(0.5, 0.5);
cannonNameText.x = 500;
cannonNameText.y = 2660;
game.addChild(cannonNameText);
var sniperNameText = new Text2('Sniper', {
size: 35,
fill: '#ffffff'
});
sniperNameText.anchor.set(0.5, 0.5);
sniperNameText.x = 900;
sniperNameText.y = 2660;
game.addChild(sniperNameText);
var laserNameText = new Text2('Laser', {
size: 35,
fill: '#ffffff'
});
laserNameText.anchor.set(0.5, 0.5);
laserNameText.x = 1300;
laserNameText.y = 2660;
game.addChild(laserNameText);
var cannonCostText = new Text2('50', {
size: 40,
fill: '#ffffff'
});
cannonCostText.anchor.set(0.5, 0.5);
cannonCostText.x = 500;
cannonCostText.y = 2620;
game.addChild(cannonCostText);
var sniperCostText = new Text2('100', {
size: 40,
fill: '#ffffff'
});
sniperCostText.anchor.set(0.5, 0.5);
sniperCostText.x = 900;
sniperCostText.y = 2620;
game.addChild(sniperCostText);
var laserCostText = new Text2('30', {
size: 40,
fill: '#ffffff'
});
laserCostText.anchor.set(0.5, 0.5);
laserCostText.x = 1300;
laserCostText.y = 2620;
game.addChild(laserCostText);
function getTowerCost(type) {
if (type === 'cannon') return 50;
if (type === 'sniper') return 100;
if (type === 'laser') return 30;
return 0;
}
function canAffordTower(type) {
return money >= getTowerCost(type);
}
function updateButtonColors() {
cannonButton.tint = canAffordTower('cannon') ? 0xffffff : 0x666666;
sniperButton.tint = canAffordTower('sniper') ? 0xffffff : 0x666666;
laserButton.tint = canAffordTower('laser') ? 0xffffff : 0x666666;
}
function spawnEnemy() {
var enemyType;
if (wave >= 6 && enemiesSpawned === 0) {
// Spawn boss as first enemy after wave 6
enemyType = 'boss';
} else {
var rand = Math.random();
if (rand < 0.4) {
enemyType = 'soldier';
} else if (rand < 0.6) {
enemyType = 'tank';
} else {
enemyType = 'runner';
}
}
var enemy = new Enemy(enemyType);
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
function startWave() {
enemiesInWave = 5 + wave * 2;
enemiesSpawned = 0;
spawnTimer = 0;
waveSpecialItemDropped = false; // Reset special item drop flag for new wave
}
game.move = function (x, y, obj) {
if (isDragging && draggedTowerType) {
// Update range indicator position during drag
if (rangeIndicator) {
rangeIndicator.x = x;
rangeIndicator.y = y;
}
}
};
game.down = function (x, y, obj) {
// Clear previous selection
if (rangeIndicator && !isDragging) {
rangeIndicator.destroy();
rangeIndicator = null;
}
selectedTower = null;
// Check tower button clicks
var buttonSize = 80;
if (y >= 2500 - buttonSize / 2 && y <= 2500 + buttonSize / 2) {
if (x >= 500 - buttonSize / 2 && x <= 500 + buttonSize / 2 && canAffordTower('cannon')) {
draggedTowerType = 'cannon';
isDragging = true;
// Show range indicator for dragged tower
rangeIndicator = LK.getAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 120 / 50,
// cannon range
scaleY: 120 / 50,
alpha: 0.4
});
rangeIndicator.x = x;
rangeIndicator.y = y;
rangeIndicator.tint = 0x4a90e2;
game.addChild(rangeIndicator);
} else if (x >= 900 - buttonSize / 2 && x <= 900 + buttonSize / 2 && canAffordTower('sniper')) {
draggedTowerType = 'sniper';
isDragging = true;
// Show range indicator for dragged tower
rangeIndicator = LK.getAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 350 / 50,
// sniper range
scaleY: 350 / 50,
alpha: 0.4
});
rangeIndicator.x = x;
rangeIndicator.y = y;
rangeIndicator.tint = 0x7ed321;
game.addChild(rangeIndicator);
} else if (x >= 1300 - buttonSize / 2 && x <= 1300 + buttonSize / 2 && canAffordTower('laser')) {
draggedTowerType = 'laser';
isDragging = true;
// Show range indicator for dragged tower
rangeIndicator = LK.getAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 200 / 50,
// laser range
scaleY: 200 / 50,
alpha: 0.4
});
rangeIndicator.x = x;
rangeIndicator.y = y;
rangeIndicator.tint = 0xf5a623;
game.addChild(rangeIndicator);
}
} else {
// Check if clicking on existing tower
var clickedTower = null;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - x, 2) + Math.pow(tower.y - y, 2));
if (distance <= 40) {
// Tower click radius
clickedTower = tower;
break;
}
}
if (clickedTower) {
// Select tower and show range
selectedTower = clickedTower;
rangeIndicator = LK.getAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: selectedTower.range / 50,
scaleY: selectedTower.range / 50,
alpha: 0.3
});
rangeIndicator.x = selectedTower.x;
rangeIndicator.y = selectedTower.y;
rangeIndicator.tint = 0x00ff00;
game.addChild(rangeIndicator);
}
}
};
game.up = function (x, y, obj) {
if (isDragging && draggedTowerType) {
// Place tower at release position if affordable
var cost = getTowerCost(draggedTowerType);
if (money >= cost) {
money -= cost;
moneyText.setText('Money: ' + money);
var tower = new Tower(draggedTowerType, x, y);
towers.push(tower);
game.addChild(tower);
}
// Clean up dragging state
isDragging = false;
draggedTowerType = null;
if (rangeIndicator) {
rangeIndicator.destroy();
rangeIndicator = null;
}
}
};
game.update = function () {
// Update all towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update all enemies
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
// Update all bullets
for (var i = 0; i < bullets.length; i++) {
bullets[i].update();
}
// Wave management
if (enemies.length === 0 && enemiesSpawned >= enemiesInWave) {
waveTimer++;
if (waveTimer >= 180) {
// 3 seconds between waves
wave++;
waveText.setText('Wave: ' + wave);
startWave();
waveTimer = 0;
}
}
// Enemy spawning
if (enemiesSpawned < enemiesInWave) {
spawnTimer++;
if (spawnTimer >= 45) {
// Spawn every 0.75 seconds
spawnEnemy();
spawnTimer = 0;
}
}
// Update button colors based on affordability
updateButtonColors();
};
// Start first wave
startWave(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (startX, startY, target, damage) {
var self = Container.call(this);
self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 8;
self.update = function () {
if (!self.target || self.target.destroyed) {
self.removeBullet();
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
self.target.takeDamage(self.damage);
self.removeBullet();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.removeBullet = function () {
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.pathIndex = 0;
self.x = path[0].x;
self.y = path[0].y;
self.lastPathIndex = 0;
if (type === 'soldier') {
self.attachAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5
});
var soldierEyeLeft = self.attachAsset('soldierEyeLeft', {
anchorX: 0.5,
anchorY: 0.5,
x: -12,
y: -8
});
var soldierEyeRight = self.attachAsset('soldierEyeRight', {
anchorX: 0.5,
anchorY: 0.5,
x: 12,
y: -8
});
var soldierMouth = self.attachAsset('soldierMouth', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 8
});
self.health = 100;
self.maxHealth = 100;
self.speed = 2.5;
self.reward = 10;
// Add shield for soldiers after 2 bosses killed
self.hasShield = false;
self.shieldGraphics = null;
if (bossKillCount >= 2 && Math.random() < 0.6) {
// 60% chance for shield after 2 bosses
self.hasShield = true;
self.shieldGraphics = self.attachAsset('soldierShield', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
alpha: 0.8
});
self.health = 150; // Increased health with shield
self.maxHealth = 150;
}
} else if (type === 'tank') {
self.attachAsset('tank', {
anchorX: 0.5,
anchorY: 0.5
});
var tankNozzle = self.attachAsset('tankNozzle', {
anchorX: 0,
anchorY: 0.5,
x: 40,
y: 0
});
self.health = 250;
self.maxHealth = 250;
self.speed = 2;
self.reward = 15;
} else if (type === 'runner') {
var runnerWingLeft = self.attachAsset('runnerWingLeft', {
anchorX: 0.5,
anchorY: 0.5,
x: -28,
y: 0,
rotation: 0
});
var runnerWingRight = self.attachAsset('runnerWingRight', {
anchorX: 0.5,
anchorY: 0.5,
x: 28,
y: 0,
rotation: 0
});
self.attachAsset('runner', {
anchorX: 0.5,
anchorY: 0.5
});
// Add wing flapping animation to make wings more visible
self.wingFlapTimer = 0;
tween(runnerWingLeft, {
rotation: -0.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(runnerWingLeft, {
rotation: 0.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(runnerWingLeft, {
rotation: 0
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
});
tween(runnerWingRight, {
rotation: 0.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(runnerWingRight, {
rotation: -0.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(runnerWingRight, {
rotation: 0
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
});
var runnerEyeLeft = self.attachAsset('runnerEyeLeft', {
anchorX: 0.5,
anchorY: 0.5,
x: -8,
y: -6
});
var runnerEyeRight = self.attachAsset('runnerEyeRight', {
anchorX: 0.5,
anchorY: 0.5,
x: 8,
y: -6
});
self.health = 50;
self.maxHealth = 50;
self.speed = 4;
self.reward = 5;
} else if (type === 'boss') {
self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
var bossEyeLeft = self.attachAsset('bossEyeLeft', {
anchorX: 0.5,
anchorY: 0.5,
x: -18,
y: -12
});
var bossEyeRight = self.attachAsset('bossEyeRight', {
anchorX: 0.5,
anchorY: 0.5,
x: 18,
y: -12
});
var bossMouth = self.attachAsset('bossMouth', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 12
});
var bossHealth = 500 + bossKillCount * 200;
self.health = bossHealth;
self.maxHealth = bossHealth;
self.speed = 1;
self.reward = 200;
}
// Create HP display text for enemy
self.hpText = new Text2(self.health + ' HP', {
size: 20,
fill: '#ff0000'
});
self.hpText.anchor.set(0.5, 0.5);
self.hpText.x = 0;
self.hpText.y = -40;
self.addChild(self.hpText);
// Add attack cooldown and special abilities
self.attackCooldown = 0;
self.specialAbilityCooldown = 0;
self.update = function () {
self.move();
// Update HP display
self.hpText.setText(self.health + ' HP');
// Reduce cooldowns
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (self.specialAbilityCooldown > 0) {
self.specialAbilityCooldown--;
}
// Special abilities for different enemy types
if (self.type === 'soldier' && self.specialAbilityCooldown <= 0) {
self.throwBomb();
self.specialAbilityCooldown = 180; // 3 seconds
} else if (self.type === 'tank' && self.attackCooldown <= 0) {
self.fireTower();
self.attackCooldown = 120; // 2 seconds
} else if (self.type === 'boss' && self.attackCooldown <= 0) {
self.fireMissile();
self.attackCooldown = 90; // 1.5 seconds
}
// Continuous wing flapping for runners to make wings more visible
if (self.type === 'runner') {
self.wingFlapTimer++;
if (self.wingFlapTimer >= 90) {
// Every 1.5 seconds
var wingLeft = self.children[0]; // runnerWingLeft
var wingRight = self.children[1]; // runnerWingRight
tween(wingLeft, {
rotation: -0.4
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wingLeft, {
rotation: 0.4
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wingLeft, {
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
tween(wingRight, {
rotation: 0.4
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wingRight, {
rotation: -0.4
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wingRight, {
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
self.wingFlapTimer = 0;
}
}
};
self.move = function () {
if (self.pathIndex >= path.length - 1) {
self.reachedEnd();
return;
}
// Check for tower collisions and damage them
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < 50) {
// Collision distance
var damage = 0;
if (self.type === 'runner') {
damage = 20;
} else if (self.type === 'soldier') {
damage = 35;
} else if (self.type === 'tank') {
damage = 45;
} else if (self.type === 'boss') {
damage = 70;
}
tower.hp -= damage;
if (tower.hp <= 0) {
// Remove tower from game
for (var j = towers.length - 1; j >= 0; j--) {
if (towers[j] === tower) {
towers.splice(j, 1);
break;
}
}
tower.destroy();
}
// Enemy dies after attacking tower
self.die();
return;
}
}
var targetPoint = path[self.pathIndex + 1];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
self.pathIndex++;
self.lastPathIndex = self.pathIndex;
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.takeDamage = function (damage) {
// Shield absorbs damage for soldiers
if (self.type === 'soldier' && self.hasShield && self.shieldGraphics) {
// Shield absorbs 50% of damage
var actualDamage = Math.ceil(damage * 0.5);
self.health -= actualDamage;
// Flash shield when taking damage
tween(self.shieldGraphics, {
tint: 0xff0000
}, {
duration: 150,
onFinish: function onFinish() {
tween(self.shieldGraphics, {
tint: 0xffffff
}, {
duration: 150
});
}
});
// Remove shield when health gets low
if (self.health <= 50 && self.shieldGraphics) {
// Shield breaks animation
tween(self.shieldGraphics, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
if (self.shieldGraphics) {
self.shieldGraphics.destroy();
self.shieldGraphics = null;
}
self.hasShield = false;
}
});
}
} else {
self.health -= damage;
}
if (self.health <= 0) {
self.die();
}
};
self.blastTowers = function (blastRadius, blastDamage) {
// Create visual blast effect
var blastEffect = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.8
});
blastEffect.x = self.x;
blastEffect.y = self.y;
blastEffect.tint = 0xff4444;
game.addChild(blastEffect);
// Animate blast expanding
tween(blastEffect, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
blastEffect.destroy();
}
});
// Damage towers within blast radius
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance <= blastRadius) {
tower.hp -= blastDamage;
// Flash tower red to show damage
tween(tower, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(tower, {
tint: 0xffffff
}, {
duration: 200
});
}
});
if (tower.hp <= 0) {
// Remove tower from game
for (var j = towers.length - 1; j >= 0; j--) {
if (towers[j] === tower) {
towers.splice(j, 1);
break;
}
}
tower.destroy();
}
}
}
};
self.die = function () {
if (self.type === 'boss') {
bossKillCount++;
}
// Drop chances for different enemy types
var dropChance = Math.random();
var shouldDrop = false;
var dropMultiplier = 1;
// Every wave special item drops guaranteed
if (waveSpecialItemDropped === false) {
shouldDrop = true;
waveSpecialItemDropped = true;
}
// Boss kills drop 2 special items
if (self.type === 'boss') {
shouldDrop = true;
dropMultiplier = 2;
}
// Regular drop chances (if not already dropping from wave/boss)
if (!shouldDrop) {
if (self.type === 'runner' && dropChance < 0.008) {
shouldDrop = true;
} else if (self.type === 'soldier' && dropChance < 0.002) {
shouldDrop = true;
} else if (self.type === 'tank' && dropChance < 0.0004) {
shouldDrop = true;
}
}
if (shouldDrop) {
for (var dropCount = 0; dropCount < dropMultiplier; dropCount++) {
var itemType = ['bomb', 'shield', 'missile'][Math.floor(Math.random() * 3)];
var specialItem;
if (itemType === 'bomb') {
specialItem = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
specialItem.tint = 0x000000;
} else if (itemType === 'shield') {
specialItem = LK.getAsset('soldierShield', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
specialItem.tint = 0xc0c0c0;
} else if (itemType === 'missile') {
specialItem = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
specialItem.tint = 0xff0000;
}
// Position item vertically beside cannon (left side)
var cannonX = 500; // Cannon button X position
var baseY = 2400; // Above cannon button
specialItem.x = cannonX - 150; // Left side of cannon
specialItem.y = baseY - specialItems.length * 80; // Stack vertically
// Add to special items array for tracking
specialItems.push(specialItem);
game.addChild(specialItem);
// Animate special item with gentle floating effect
tween(specialItem, {
y: specialItem.y - 10,
alpha: 0.9
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(specialItem, {
y: specialItem.y + 10,
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Loop the floating animation
if (specialItem && !specialItem.destroyed) {
tween(specialItem, {
y: specialItem.y - 10,
alpha: 0.9
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
});
}
});
}
}
// Special death effects for different enemy types
if (self.type === 'runner') {
// Runner blasts with smaller radius and damage
self.blastTowers(120, 15);
} else if (self.type === 'soldier') {
// Soldier blasts with larger radius and damage
self.blastTowers(150, 25);
} else if (self.type === 'boss') {
// Boss blasts with massive radius and damage
self.blastTowers(200, 60);
}
money += self.reward;
moneyText.setText('Money: ' + money);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
LK.getSound('enemyHit').play();
};
self.throwBomb = function () {
if (self.type !== 'soldier') return;
// Find nearest tower to throw bomb at
var nearestTower = null;
var nearestDistance = Infinity;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < nearestDistance && distance <= 300) {
// 300 pixel throw range
nearestTower = tower;
nearestDistance = distance;
}
}
if (nearestTower) {
// Create bomb projectile
var bomb = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
bomb.x = self.x;
bomb.y = self.y;
bomb.tint = 0x000000;
game.addChild(bomb);
// Animate bomb flying to tower
tween(bomb, {
x: nearestTower.x,
y: nearestTower.y
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Explode at tower location
var blastEffect = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.8
});
blastEffect.x = bomb.x;
blastEffect.y = bomb.y;
blastEffect.tint = 0xff8800;
game.addChild(blastEffect);
// Animate explosion
tween(blastEffect, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
blastEffect.destroy();
}
});
// Damage towers in blast radius
for (var j = 0; j < towers.length; j++) {
var tower = towers[j];
var distance = Math.sqrt(Math.pow(tower.x - bomb.x, 2) + Math.pow(tower.y - bomb.y, 2));
if (distance <= 100) {
// Bomb blast radius
tower.hp -= 30;
// Flash tower red
tween(tower, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(tower, {
tint: 0xffffff
}, {
duration: 200
});
}
});
if (tower.hp <= 0) {
// Remove tower from game
for (var k = towers.length - 1; k >= 0; k--) {
if (towers[k] === tower) {
towers.splice(k, 1);
break;
}
}
tower.destroy();
}
}
}
bomb.destroy();
}
});
}
};
self.fireTower = function () {
if (self.type !== 'tank') return;
// Find nearest tower to fire at
var nearestTower = null;
var nearestDistance = Infinity;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < nearestDistance && distance <= 400) {
// 400 pixel firing range
nearestTower = tower;
nearestDistance = distance;
}
}
if (nearestTower) {
// Create tank projectile
var tankBullet = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
tankBullet.x = self.x;
tankBullet.y = self.y;
tankBullet.tint = 0x8b572a;
game.addChild(tankBullet);
// Animate bullet flying to tower
tween(tankBullet, {
x: nearestTower.x,
y: nearestTower.y
}, {
duration: 600,
easing: tween.linear,
onFinish: function onFinish() {
// Damage the tower
nearestTower.hp -= 40;
// Flash tower red
tween(nearestTower, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(nearestTower, {
tint: 0xffffff
}, {
duration: 200
});
}
});
if (nearestTower.hp <= 0) {
// Remove tower from game
for (var j = towers.length - 1; j >= 0; j--) {
if (towers[j] === nearestTower) {
towers.splice(j, 1);
break;
}
}
nearestTower.destroy();
}
tankBullet.destroy();
}
});
}
};
self.fireMissile = function () {
if (self.type !== 'boss') return;
// Find nearest tower to fire missile at
var nearestTower = null;
var nearestDistance = Infinity;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < nearestDistance && distance <= 500) {
// 500 pixel missile range
nearestTower = tower;
nearestDistance = distance;
}
}
if (nearestTower) {
// Create boss missile
var missile = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
missile.x = self.x;
missile.y = self.y;
missile.tint = 0xff0000;
game.addChild(missile);
// Animate missile flying to tower
tween(missile, {
x: nearestTower.x,
y: nearestTower.y
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create explosion effect
var explosionEffect = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
alpha: 0.9
});
explosionEffect.x = missile.x;
explosionEffect.y = missile.y;
explosionEffect.tint = 0xffaa00;
game.addChild(explosionEffect);
// Animate explosion
tween(explosionEffect, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
// Damage towers in missile blast radius
for (var j = 0; j < towers.length; j++) {
var tower = towers[j];
var distance = Math.sqrt(Math.pow(tower.x - missile.x, 2) + Math.pow(tower.y - missile.y, 2));
if (distance <= 120) {
// Missile blast radius
tower.hp -= 50;
// Flash tower red
tween(tower, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(tower, {
tint: 0xffffff
}, {
duration: 200
});
}
});
if (tower.hp <= 0) {
// Remove tower from game
for (var k = towers.length - 1; k >= 0; k--) {
if (towers[k] === tower) {
towers.splice(k, 1);
break;
}
}
tower.destroy();
}
}
}
missile.destroy();
}
});
}
};
self.reachedEnd = function () {
lives--;
livesText.setText('Lives: ' + lives);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
if (lives <= 0) {
LK.showGameOver();
}
};
return self;
});
var Tower = Container.expand(function (type, x, y) {
var self = Container.call(this);
self.type = type;
self.x = x;
self.y = y;
self.range = 200;
self.shootCooldown = 0;
self.target = null;
if (type === 'cannon') {
self.attachAsset('cannon', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 30;
self.fireRate = 60;
self.range = 120;
self.hp = 190;
self.maxHp = 190;
} else if (type === 'sniper') {
self.attachAsset('sniper', {
anchorX: 0.5,
anchorY: 0.5
});
var sniperTriangle1 = self.attachAsset('sniperTriangle1', {
anchorX: 0.5,
anchorY: 0.5,
x: -15,
y: -10,
rotation: Math.PI / 4
});
var sniperTriangle2 = self.attachAsset('sniperTriangle2', {
anchorX: 0.5,
anchorY: 0.5,
x: 15,
y: -10,
rotation: -Math.PI / 4
});
self.damage = 80;
self.fireRate = 120;
self.range = 350;
self.hp = 240;
self.maxHp = 240;
} else if (type === 'laser') {
self.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 15;
self.fireRate = 20;
self.hp = 150;
self.maxHp = 150;
}
// Create HP display text
self.hpText = new Text2(self.hp + ' HP', {
size: 25,
fill: '#ffffff'
});
self.hpText.anchor.set(0.5, 0.5);
self.hpText.x = 0;
self.hpText.y = -60;
self.addChild(self.hpText);
// Add HP regeneration timer
self.hpRegenTimer = 0;
if (type === 'laser') {
self.hpRegenRate = 120; // 2 seconds at 60 FPS
} else if (type === 'cannon') {
self.hpRegenRate = 165; // 2.75 seconds at 60 FPS
} else if (type === 'sniper') {
self.hpRegenRate = 225; // 3.75 seconds at 60 FPS
}
self.hpRegenAmount = Math.ceil(self.maxHp * 0.1); // Regenerate 10% of max HP
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
self.findTarget();
if (self.target && self.shootCooldown <= 0) {
self.shoot();
self.shootCooldown = self.fireRate;
}
// HP regeneration logic
if (self.hp < self.maxHp) {
self.hpRegenTimer++;
if (self.hpRegenTimer >= self.hpRegenRate) {
self.hp = Math.min(self.hp + self.hpRegenAmount, self.maxHp);
self.hpRegenTimer = 0;
// Visual regeneration effect
tween(self, {
tint: 0x00ff00
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 200
});
}
});
}
}
// Update HP display
self.hpText.setText(self.hp + ' HP');
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestEnemy = enemy;
closestDistance = distance;
}
}
self.target = closestEnemy;
};
self.shoot = function () {
if (self.target) {
var bullet = new Bullet(self.x, self.y, self.target, self.damage);
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
}
};
self.updateFireArrow = function () {
// Arrow display disabled
};
return self;
});
var TowerSpot = Container.expand(function (x, y) {
var self = Container.call(this);
self.attachAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = x;
self.y = y;
self.occupied = false;
self.tower = null;
self.down = function (localX, localY, obj) {
if (!self.occupied && selectedTowerType && canAffordTower(selectedTowerType)) {
self.placeTower(selectedTowerType);
}
};
self.placeTower = function (type) {
var cost = getTowerCost(type);
if (money >= cost) {
money -= cost;
moneyText.setText('Money: ' + money);
var tower = new Tower(type, self.x, self.y);
towers.push(tower);
game.addChild(tower);
self.occupied = true;
self.tower = tower;
self.alpha = 0.3;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
var money = 350;
var lives = 20;
var wave = 1;
var waveTimer = 0;
var spawnTimer = 0;
var enemiesInWave = 0;
var enemiesSpawned = 0;
var selectedTowerType = null;
var selectedTower = null;
var rangeIndicator = null;
var bossKillCount = 0;
var draggedTower = null;
var draggedTowerType = null;
var isDragging = false;
var towers = [];
var enemies = [];
var bullets = [];
var towerSpots = [];
var specialItems = [];
var waveSpecialItemDropped = false;
var path = [{
x: 1024,
y: 0
}, {
x: 1024,
y: 200
}, {
x: 1600,
y: 200
}, {
x: 1600,
y: 400
}, {
x: 400,
y: 400
}, {
x: 400,
y: 600
}, {
x: 1700,
y: 600
}, {
x: 1700,
y: 800
}, {
x: 300,
y: 800
}, {
x: 300,
y: 1000
}, {
x: 1500,
y: 1000
}, {
x: 1500,
y: 1200
}, {
x: 500,
y: 1200
}, {
x: 500,
y: 1400
}, {
x: 1600,
y: 1400
}, {
x: 1600,
y: 1600
}, {
x: 200,
y: 1600
}, {
x: 200,
y: 1800
}, {
x: 1700,
y: 1800
}, {
x: 1700,
y: 2000
}, {
x: 600,
y: 2000
}, {
x: 600,
y: 2200
}, {
x: 1400,
y: 2200
}, {
x: 1400,
y: 2400
}, {
x: 1024,
y: 2400
}, {
x: 1024,
y: 2600
}];
// Create path visualization
for (var i = 0; i < path.length - 1; i++) {
var pathSegment = LK.getAsset('path', {
anchorX: 0.5,
anchorY: 0.5
});
pathSegment.x = (path[i].x + path[i + 1].x) / 2;
pathSegment.y = (path[i].y + path[i + 1].y) / 2;
var dx = path[i + 1].x - path[i].x;
var dy = path[i + 1].y - path[i].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (Math.abs(dx) > Math.abs(dy)) {
pathSegment.width = distance;
pathSegment.height = 120;
} else {
pathSegment.width = 120;
pathSegment.height = distance;
}
game.addChild(pathSegment);
}
// UI Elements
var moneyText = new Text2('Money: ' + money, {
size: 60,
fill: '#ffffff'
});
moneyText.anchor.set(0, 0);
LK.gui.topLeft.addChild(moneyText);
moneyText.x = 120;
moneyText.y = 20;
var livesText = new Text2('Lives: ' + lives, {
size: 60,
fill: '#ffffff'
});
livesText.anchor.set(0, 0);
LK.gui.topLeft.addChild(livesText);
livesText.x = 120;
livesText.y = 100;
var waveText = new Text2('Wave: ' + wave, {
size: 60,
fill: '#ffffff'
});
waveText.anchor.set(0, 0);
LK.gui.topLeft.addChild(waveText);
waveText.x = 120;
waveText.y = 180;
// Tower selection buttons
var cannonButton = LK.getAsset('cannon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
cannonButton.x = 500;
cannonButton.y = 2500;
game.addChild(cannonButton);
var sniperButton = LK.getAsset('sniper', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
sniperButton.x = 900;
sniperButton.y = 2500;
game.addChild(sniperButton);
var laserButton = LK.getAsset('laser', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
laserButton.x = 1300;
laserButton.y = 2500;
game.addChild(laserButton);
var cannonNameText = new Text2('Cannon', {
size: 35,
fill: '#ffffff'
});
cannonNameText.anchor.set(0.5, 0.5);
cannonNameText.x = 500;
cannonNameText.y = 2660;
game.addChild(cannonNameText);
var sniperNameText = new Text2('Sniper', {
size: 35,
fill: '#ffffff'
});
sniperNameText.anchor.set(0.5, 0.5);
sniperNameText.x = 900;
sniperNameText.y = 2660;
game.addChild(sniperNameText);
var laserNameText = new Text2('Laser', {
size: 35,
fill: '#ffffff'
});
laserNameText.anchor.set(0.5, 0.5);
laserNameText.x = 1300;
laserNameText.y = 2660;
game.addChild(laserNameText);
var cannonCostText = new Text2('50', {
size: 40,
fill: '#ffffff'
});
cannonCostText.anchor.set(0.5, 0.5);
cannonCostText.x = 500;
cannonCostText.y = 2620;
game.addChild(cannonCostText);
var sniperCostText = new Text2('100', {
size: 40,
fill: '#ffffff'
});
sniperCostText.anchor.set(0.5, 0.5);
sniperCostText.x = 900;
sniperCostText.y = 2620;
game.addChild(sniperCostText);
var laserCostText = new Text2('30', {
size: 40,
fill: '#ffffff'
});
laserCostText.anchor.set(0.5, 0.5);
laserCostText.x = 1300;
laserCostText.y = 2620;
game.addChild(laserCostText);
function getTowerCost(type) {
if (type === 'cannon') return 50;
if (type === 'sniper') return 100;
if (type === 'laser') return 30;
return 0;
}
function canAffordTower(type) {
return money >= getTowerCost(type);
}
function updateButtonColors() {
cannonButton.tint = canAffordTower('cannon') ? 0xffffff : 0x666666;
sniperButton.tint = canAffordTower('sniper') ? 0xffffff : 0x666666;
laserButton.tint = canAffordTower('laser') ? 0xffffff : 0x666666;
}
function spawnEnemy() {
var enemyType;
if (wave >= 6 && enemiesSpawned === 0) {
// Spawn boss as first enemy after wave 6
enemyType = 'boss';
} else {
var rand = Math.random();
if (rand < 0.4) {
enemyType = 'soldier';
} else if (rand < 0.6) {
enemyType = 'tank';
} else {
enemyType = 'runner';
}
}
var enemy = new Enemy(enemyType);
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
function startWave() {
enemiesInWave = 5 + wave * 2;
enemiesSpawned = 0;
spawnTimer = 0;
waveSpecialItemDropped = false; // Reset special item drop flag for new wave
}
game.move = function (x, y, obj) {
if (isDragging && draggedTowerType) {
// Update range indicator position during drag
if (rangeIndicator) {
rangeIndicator.x = x;
rangeIndicator.y = y;
}
}
};
game.down = function (x, y, obj) {
// Clear previous selection
if (rangeIndicator && !isDragging) {
rangeIndicator.destroy();
rangeIndicator = null;
}
selectedTower = null;
// Check tower button clicks
var buttonSize = 80;
if (y >= 2500 - buttonSize / 2 && y <= 2500 + buttonSize / 2) {
if (x >= 500 - buttonSize / 2 && x <= 500 + buttonSize / 2 && canAffordTower('cannon')) {
draggedTowerType = 'cannon';
isDragging = true;
// Show range indicator for dragged tower
rangeIndicator = LK.getAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 120 / 50,
// cannon range
scaleY: 120 / 50,
alpha: 0.4
});
rangeIndicator.x = x;
rangeIndicator.y = y;
rangeIndicator.tint = 0x4a90e2;
game.addChild(rangeIndicator);
} else if (x >= 900 - buttonSize / 2 && x <= 900 + buttonSize / 2 && canAffordTower('sniper')) {
draggedTowerType = 'sniper';
isDragging = true;
// Show range indicator for dragged tower
rangeIndicator = LK.getAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 350 / 50,
// sniper range
scaleY: 350 / 50,
alpha: 0.4
});
rangeIndicator.x = x;
rangeIndicator.y = y;
rangeIndicator.tint = 0x7ed321;
game.addChild(rangeIndicator);
} else if (x >= 1300 - buttonSize / 2 && x <= 1300 + buttonSize / 2 && canAffordTower('laser')) {
draggedTowerType = 'laser';
isDragging = true;
// Show range indicator for dragged tower
rangeIndicator = LK.getAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 200 / 50,
// laser range
scaleY: 200 / 50,
alpha: 0.4
});
rangeIndicator.x = x;
rangeIndicator.y = y;
rangeIndicator.tint = 0xf5a623;
game.addChild(rangeIndicator);
}
} else {
// Check if clicking on existing tower
var clickedTower = null;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt(Math.pow(tower.x - x, 2) + Math.pow(tower.y - y, 2));
if (distance <= 40) {
// Tower click radius
clickedTower = tower;
break;
}
}
if (clickedTower) {
// Select tower and show range
selectedTower = clickedTower;
rangeIndicator = LK.getAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: selectedTower.range / 50,
scaleY: selectedTower.range / 50,
alpha: 0.3
});
rangeIndicator.x = selectedTower.x;
rangeIndicator.y = selectedTower.y;
rangeIndicator.tint = 0x00ff00;
game.addChild(rangeIndicator);
}
}
};
game.up = function (x, y, obj) {
if (isDragging && draggedTowerType) {
// Place tower at release position if affordable
var cost = getTowerCost(draggedTowerType);
if (money >= cost) {
money -= cost;
moneyText.setText('Money: ' + money);
var tower = new Tower(draggedTowerType, x, y);
towers.push(tower);
game.addChild(tower);
}
// Clean up dragging state
isDragging = false;
draggedTowerType = null;
if (rangeIndicator) {
rangeIndicator.destroy();
rangeIndicator = null;
}
}
};
game.update = function () {
// Update all towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update all enemies
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
// Update all bullets
for (var i = 0; i < bullets.length; i++) {
bullets[i].update();
}
// Wave management
if (enemies.length === 0 && enemiesSpawned >= enemiesInWave) {
waveTimer++;
if (waveTimer >= 180) {
// 3 seconds between waves
wave++;
waveText.setText('Wave: ' + wave);
startWave();
waveTimer = 0;
}
}
// Enemy spawning
if (enemiesSpawned < enemiesInWave) {
spawnTimer++;
if (spawnTimer >= 45) {
// Spawn every 0.75 seconds
spawnEnemy();
spawnTimer = 0;
}
}
// Update button colors based on affordability
updateButtonColors();
};
// Start first wave
startWave();