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) {
if (self.target && !self.target.destroyed) {
self.target.takeDamage(self.damage);
}
// If this is an explosive bullet, create area damage
if (self.isExplosive) {
// Create explosion effect
var explosionEffect = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.8
});
explosionEffect.x = self.target.x;
explosionEffect.y = self.target.y;
explosionEffect.tint = 0xff4444;
game.addChild(explosionEffect);
// Animate explosion
tween(explosionEffect, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
// Damage nearby enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy !== self.target) {
var expDistance = Math.sqrt(Math.pow(enemy.x - self.target.x, 2) + Math.pow(enemy.y - self.target.y, 2));
if (expDistance <= 80) {
// Explosion radius
enemy.takeDamage(Math.floor(self.damage * 0.6)); // 60% damage to nearby enemies
}
}
}
}
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;
// Track bomb throws - soldiers can throw 1 bomb initially, then 2 more after 1 second
self.bombsThrown = 0;
self.maxBombs = 1;
self.bombDelayTimer = 0;
self.hasUnlockedExtraBombs = false;
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') {
// Update bomb delay timer to unlock additional bombs after 0.5 second
if (!self.hasUnlockedExtraBombs) {
self.bombDelayTimer++;
if (self.bombDelayTimer >= 30) {
// 0.5 second at 60 FPS
self.maxBombs = 3; // Allow 2 more bombs (3 total)
self.hasUnlockedExtraBombs = true;
}
}
self.throwBomb();
} 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;
}
// Apply damage to shield first if active
if (tower.shieldProtection && tower.shieldHp > 0) {
var shieldDamage = Math.min(damage, tower.shieldHp);
tower.shieldHp -= shieldDamage;
damage -= shieldDamage;
// Flash shield when taking damage
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
tint: 0xff0000
}, {
duration: 150,
onFinish: function onFinish() {
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
tint: 0xffffff
}, {
duration: 150
});
}
}
});
}
// Remove shield when depleted
if (tower.shieldHp <= 0) {
tower.shieldProtection = false;
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
if (tower.shieldGraphics) {
tower.shieldGraphics.destroy();
tower.shieldGraphics = null;
}
}
});
}
}
}
// Apply remaining damage to tower HP
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) {
var damage = blastDamage;
// Apply damage to shield first if active
if (tower.shieldProtection && tower.shieldHp > 0) {
var shieldDamage = Math.min(damage, tower.shieldHp);
tower.shieldHp -= shieldDamage;
damage -= shieldDamage;
// Flash shield when taking damage
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
tint: 0xff0000
}, {
duration: 150,
onFinish: function onFinish() {
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
tint: 0xffffff
}, {
duration: 150
});
}
}
});
}
// Remove shield when depleted
if (tower.shieldHp <= 0) {
tower.shieldProtection = false;
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
if (tower.shieldGraphics) {
tower.shieldGraphics.destroy();
tower.shieldGraphics = null;
}
}
});
}
}
}
// Apply remaining damage to tower HP
tower.hp -= damage;
// 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('bombProjectile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Add continuous round pulsing effect to make bomb more visible
var _bombPulse = function bombPulse() {
if (specialItem && !specialItem.destroyed) {
tween(specialItem, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.7
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (specialItem && !specialItem.destroyed) {
tween(specialItem, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 1
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: _bombPulse
});
}
}
});
}
};
_bombPulse();
} else if (itemType === 'shield') {
specialItem = LK.getAsset('soldierShield', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
specialItem.tint = 0x4a90e2;
// Add continuous glowing effect to make shield more visible
var _shieldGlow = function shieldGlow() {
if (specialItem && !specialItem.destroyed) {
tween(specialItem, {
tint: 0x00ccff,
alpha: 0.8,
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (specialItem && !specialItem.destroyed) {
tween(specialItem, {
tint: 0x4a90e2,
alpha: 1,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: _shieldGlow
});
}
}
});
}
};
_shieldGlow();
} else if (itemType === 'missile') {
// Create round missile base
specialItem = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
specialItem.tint = 0xff4444; // Red missile base
// Add missile tip (smaller round element)
var tip = specialItem.attachAsset('runner', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
x: 0,
y: -20
});
tip.tint = 0xffaa00; // Orange tip color
// Add continuous missile glow effect
var _missileGlow = function missileGlow() {
if (specialItem && !specialItem.destroyed) {
tween(specialItem, {
tint: 0xff0000,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.9
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (specialItem && !specialItem.destroyed) {
tween(specialItem, {
tint: 0xff4444,
scaleX: 0.7,
scaleY: 0.7,
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: _missileGlow
});
}
}
});
}
};
_missileGlow();
}
// 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 name text below special item
var nameText = new Text2(itemType.charAt(0).toUpperCase() + itemType.slice(1), {
size: 25,
fill: '#ffffff'
});
nameText.anchor.set(0.5, 0.5);
nameText.x = 0;
nameText.y = 45;
specialItem.addChild(nameText);
// Store item type and dragging state
specialItem.itemType = itemType;
specialItem.isDragging = false;
specialItem.startDragX = 0;
specialItem.startDragY = 0;
// Add to special items array for tracking
specialItems.push(specialItem);
game.addChild(specialItem);
// Add down handler for dragging special items
specialItem.down = function (localX, localY, obj) {
this.isDragging = true;
this.startDragX = localX;
this.startDragY = localY;
this.alpha = 0.7; // Make slightly transparent while dragging
};
// 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;
// Check if soldier has already thrown maximum bombs
if (self.bombsThrown >= self.maxBombs) 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) {
// Increment bomb throw counter
self.bombsThrown++;
// Create bomb projectile
var bomb = LK.getAsset('bombProjectile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
bomb.x = self.x;
bomb.y = self.y;
// Add pulsing effect to flying bomb
tween(bomb, {
scaleX: 0.6,
scaleY: 0.6
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bomb, {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
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
var damage = 30;
// Apply damage to shield first if active
if (tower.shieldProtection && tower.shieldHp > 0) {
var shieldDamage = Math.min(damage, tower.shieldHp);
tower.shieldHp -= shieldDamage;
damage -= shieldDamage;
// Flash shield when taking damage
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
tint: 0xff0000
}, {
duration: 150,
onFinish: function onFinish() {
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
tint: 0xffffff
}, {
duration: 150
});
}
}
});
}
// Remove shield when depleted
if (tower.shieldHp <= 0) {
tower.shieldProtection = false;
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
if (tower.shieldGraphics) {
tower.shieldGraphics.destroy();
tower.shieldGraphics = null;
}
}
});
}
}
}
// Apply remaining damage to tower HP
tower.hp -= damage;
// 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
var damage = 40;
// Apply damage to shield first if active
if (nearestTower.shieldProtection && nearestTower.shieldHp > 0) {
var shieldDamage = Math.min(damage, nearestTower.shieldHp);
nearestTower.shieldHp -= shieldDamage;
damage -= shieldDamage;
// Flash shield when taking damage
if (nearestTower.shieldGraphics) {
tween(nearestTower.shieldGraphics, {
tint: 0xff0000
}, {
duration: 150,
onFinish: function onFinish() {
if (nearestTower.shieldGraphics) {
tween(nearestTower.shieldGraphics, {
tint: 0xffffff
}, {
duration: 150
});
}
}
});
}
// Remove shield when depleted
if (nearestTower.shieldHp <= 0) {
nearestTower.shieldProtection = false;
if (nearestTower.shieldGraphics) {
tween(nearestTower.shieldGraphics, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
if (nearestTower.shieldGraphics) {
nearestTower.shieldGraphics.destroy();
nearestTower.shieldGraphics = null;
}
}
});
}
}
}
// Apply remaining damage to tower HP
nearestTower.hp -= damage;
// 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
var damage = 50;
// Apply damage to shield first if active
if (tower.shieldProtection && tower.shieldHp > 0) {
var shieldDamage = Math.min(damage, tower.shieldHp);
tower.shieldHp -= shieldDamage;
damage -= shieldDamage;
// Flash shield when taking damage
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
tint: 0xff0000
}, {
duration: 150,
onFinish: function onFinish() {
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
tint: 0xffffff
}, {
duration: 150
});
}
}
});
}
// Remove shield when depleted
if (tower.shieldHp <= 0) {
tower.shieldProtection = false;
if (tower.shieldGraphics) {
tween(tower.shieldGraphics, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
if (tower.shieldGraphics) {
tower.shieldGraphics.destroy();
tower.shieldGraphics = null;
}
}
});
}
}
}
// Apply remaining damage to tower HP
tower.hp -= damage;
// 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 = 250;
self.hp = 190;
self.maxHp = 190;
// Missile usage tracking
self.missileUsed = false;
self.missileSecondTime = false;
self.missileTimer = 0;
} 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
// Special item enhancement properties
self.bombEnhanced = false;
self.bombEnhancedTimer = 0;
self.shieldEnhanced = false;
self.shieldEnhancedTimer = 0;
self.shieldProtection = false;
self.shieldGraphics = null;
self.shieldHp = 0;
self.shieldMaxHp = 70;
self.missileEnhanced = false;
self.missileEnhancedTimer = 0;
// Bomb usage tracking - towers can throw only one bomb initially
self.bombUsed = false;
self.bombUsageTimer = 0;
self.bombSecondTime = false;
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 missile timer
if (self.missileTimer > 0) {
self.missileTimer--;
if (self.missileTimer <= 0 && self.missileUsed && !self.missileSecondTime) {
self.missileSecondTime = true; // Unlock second missile usage
}
}
// Update bomb usage timer
if (self.bombUsageTimer > 0) {
self.bombUsageTimer--;
if (self.bombUsageTimer <= 0 && self.bombUsed && !self.bombSecondTime) {
self.bombSecondTime = true; // Unlock second bomb usage
}
}
// Update HP display
if (self.shieldProtection && self.shieldHp > 0) {
self.hpText.setText(self.hp + ' HP + ' + self.shieldHp + ' Shield');
} else {
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 finalDamage = self.damage;
// Check if tower has special item enhancements active
if (self.bombEnhanced && self.bombEnhancedTimer > 0) {
finalDamage = Math.floor(finalDamage * 1.5); // 50% damage boost from bomb
// Create bomb projectile that flies to enemy
var bomb = LK.getAsset('bombProjectile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
bomb.x = self.x;
bomb.y = self.y;
game.addChild(bomb);
// Add pulsing effect to flying bomb
tween(bomb, {
scaleX: 0.7,
scaleY: 0.7
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bomb, {
scaleX: 0.6,
scaleY: 0.6
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
// Animate bomb flying to target enemy
tween(bomb, {
x: self.target.x,
y: self.target.y
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
// Apply damage to target
if (self.target && !self.target.destroyed) {
self.target.takeDamage(finalDamage);
}
// Create explosion effect at target location
var explosionEffect = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.8
});
explosionEffect.x = bomb.x;
explosionEffect.y = bomb.y;
explosionEffect.tint = 0xff8800;
game.addChild(explosionEffect);
// Animate explosion
tween(explosionEffect, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
bomb.destroy();
}
});
self.bombEnhancedTimer--;
if (self.bombEnhancedTimer <= 0) {
self.bombEnhanced = false;
}
} else if (self.missileEnhanced && self.missileEnhancedTimer > 0) {
// Missile enhancement: chance for explosive bullets
if (Math.random() < 0.3) {
// 30% chance for explosive shot
finalDamage = Math.floor(finalDamage * 2); // Double damage for explosive shot
// Visual effect for explosive bullet
var explosiveBullet = new Bullet(self.x, self.y, self.target, finalDamage);
explosiveBullet.isExplosive = true;
bullets.push(explosiveBullet);
game.addChild(explosiveBullet);
} else {
var bullet = new Bullet(self.x, self.y, self.target, finalDamage);
bullets.push(bullet);
game.addChild(bullet);
}
self.missileEnhancedTimer--;
if (self.missileEnhancedTimer <= 0) {
self.missileEnhanced = false;
}
} else if (self.type === 'cannon') {
// Cannon missile usage logic: 1 missile initially, then after 1 second cooldown, normal damage
if (!self.missileUsed) {
// First missile - use immediately
self.launchMissile();
self.missileUsed = true;
self.missileTimer = 60; // 1 second cooldown at 60 FPS
} else if (self.missileSecondTime && self.missileTimer <= 0) {
// Second time - can use missile again but then revert to normal damage
self.launchMissile();
self.missileSecondTime = false;
} else {
var bullet = new Bullet(self.x, self.y, self.target, finalDamage);
bullets.push(bullet);
game.addChild(bullet);
}
} else {
// Bomb usage logic: 1 bomb initially, then after 1 second cooldown, normal shooting
if (!self.bombUsed) {
// First bomb - use immediately
self.throwBomb();
self.bombUsed = true;
self.bombUsageTimer = 60; // 1 second cooldown at 60 FPS
} else if (self.bombSecondTime && self.bombUsageTimer <= 0) {
// Second time - can use bomb again but then revert to normal shooting
self.throwBomb();
self.bombSecondTime = false;
} else {
var bullet = new Bullet(self.x, self.y, self.target, finalDamage);
bullets.push(bullet);
game.addChild(bullet);
}
}
LK.getSound('shoot').play();
}
};
self.updateFireArrow = function () {
// Arrow display disabled
};
self.launchMissile = function () {
if (self.target) {
var finalDamage = self.damage; // Missiles do normal damage
// Create missile projectile
var missile = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
missile.x = self.x;
missile.y = self.y;
missile.tint = 0xff4444; // Red missile color
game.addChild(missile);
// Add missile trail effect
var trail = LK.getAsset('runner', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0.7
});
trail.x = self.x;
trail.y = self.y;
trail.tint = 0xffaa00; // Orange trail
game.addChild(trail);
// Animate missile flying to target
tween(missile, {
x: self.target.x,
y: self.target.y
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create large explosion effect
var explosionEffect = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.9
});
explosionEffect.x = missile.x;
explosionEffect.y = missile.y;
explosionEffect.tint = 0xff0000;
game.addChild(explosionEffect);
// Animate explosion
tween(explosionEffect, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
// Apply damage to target
if (self.target && !self.target.destroyed) {
self.target.takeDamage(finalDamage);
}
// Area damage to nearby enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy !== self.target) {
var distance = Math.sqrt(Math.pow(enemy.x - missile.x, 2) + Math.pow(enemy.y - missile.y, 2));
if (distance <= 100) {
// Missile blast radius
enemy.takeDamage(Math.floor(finalDamage * 0.5)); // 50% damage to nearby enemies
}
}
}
missile.destroy();
}
});
// Animate trail following missile
tween(trail, {
x: self.target.x,
y: self.target.y,
alpha: 0
}, {
duration: 900,
easing: tween.linear,
onFinish: function onFinish() {
trail.destroy();
}
});
}
};
self.throwBomb = function () {
if (self.target) {
var finalDamage = Math.floor(self.damage * 1.2); // Bombs do 20% more damage
// Create bomb projectile
var bomb = LK.getAsset('bombProjectile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
bomb.x = self.x;
bomb.y = self.y;
bomb.tint = 0xff8800; // Orange bomb color
game.addChild(bomb);
// Add pulsing effect to flying bomb
tween(bomb, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.8
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bomb, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
// Animate bomb flying to target with arc trajectory
tween(bomb, {
x: self.target.x,
y: self.target.y
}, {
duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create large explosion effect
var explosionEffect = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
alpha: 0.9
});
explosionEffect.x = bomb.x;
explosionEffect.y = bomb.y;
explosionEffect.tint = 0xff4444;
game.addChild(explosionEffect);
// Animate explosion
tween(explosionEffect, {
scaleX: 2.8,
scaleY: 2.8,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
// Apply damage to target
if (self.target && !self.target.destroyed) {
self.target.takeDamage(finalDamage);
}
// Area damage to nearby enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy !== self.target) {
var distance = Math.sqrt(Math.pow(enemy.x - bomb.x, 2) + Math.pow(enemy.y - bomb.y, 2));
if (distance <= 120) {
// Bomb blast radius
enemy.takeDamage(Math.floor(finalDamage * 0.6)); // 60% damage to nearby enemies
}
}
}
bomb.destroy();
}
});
}
};
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;
}
}
// Handle special item dragging
for (var i = 0; i < specialItems.length; i++) {
var item = specialItems[i];
if (item.isDragging) {
item.x = x;
item.y = y;
break;
}
}
};
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: 250 / 50,
// cannon range
scaleY: 250 / 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;
}
}
// Handle special item placement on towers
for (var i = 0; i < specialItems.length; i++) {
var item = specialItems[i];
if (item.isDragging) {
var targetTower = null;
// Check if dropped on a tower
for (var j = 0; j < towers.length; j++) {
var tower = towers[j];
var distance = Math.sqrt(Math.pow(tower.x - x, 2) + Math.pow(tower.y - y, 2));
if (distance <= 60) {
// Tower placement radius
targetTower = tower;
break;
}
}
if (targetTower) {
// Apply special item effect to the tower
applySpecialItemToTower(item.itemType, targetTower);
// Remove the special item
specialItems.splice(i, 1);
item.destroy();
// Reposition remaining special items
repositionSpecialItems();
} else {
// Return item to original position if not dropped on tower
var cannonX = 500;
var baseY = 2400;
item.x = cannonX - 150;
item.y = baseY - i * 80;
}
item.isDragging = false;
item.alpha = 1; // Restore full opacity
break;
}
}
};
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 - only run when game is started
if (gameStarted) {
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();
};
// Add down handlers for special items
for (var i = 0; i < specialItems.length; i++) {
var item = specialItems[i];
item.down = function (localX, localY, obj) {
useSpecialItem(this);
};
}
function useSpecialItem(item) {
// Use stored itemType property instead of tint detection
var itemType = item.itemType;
// Apply special item effects
if (itemType === 'bomb') {
// Bomb: Damages all enemies on screen
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
enemy.takeDamage(100);
// Visual explosion effect on each enemy
var explosionEffect = LK.getAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.8
});
explosionEffect.x = enemy.x;
explosionEffect.y = enemy.y;
explosionEffect.tint = 0xff4444;
game.addChild(explosionEffect);
tween(explosionEffect, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
}
} else if (itemType === 'shield') {
// Shield: Heals all towers to full HP
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
tower.hp = tower.maxHp;
// Visual healing effect
tween(tower, {
tint: 0x00ff00
}, {
duration: 300,
onFinish: function onFinish() {
tween(tower, {
tint: 0xffffff
}, {
duration: 300
});
}
});
}
} else if (itemType === 'missile') {
var _shake = function shake() {
if (shakeTimer < shakeDuration) {
game.x = originalX + (Math.random() - 0.5) * shakeAmount;
game.y = originalY + (Math.random() - 0.5) * shakeAmount;
shakeTimer += 16; // Approximate frame time
LK.setTimeout(_shake, 16);
} else {
game.x = originalX;
game.y = originalY;
}
};
// Missile: Massive damage to all enemies and screen shake effect
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
enemy.takeDamage(120);
// Large missile explosion effect
var missileEffect = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.9
});
missileEffect.x = enemy.x;
missileEffect.y = enemy.y;
missileEffect.tint = 0xff0000;
game.addChild(missileEffect);
tween(missileEffect, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
missileEffect.destroy();
}
});
}
// Screen shake effect
var originalX = game.x;
var originalY = game.y;
var shakeAmount = 10;
var shakeDuration = 500;
var shakeTimer = 0;
_shake();
}
// Remove used special item
for (var i = specialItems.length - 1; i >= 0; i--) {
if (specialItems[i] === item) {
specialItems.splice(i, 1);
break;
}
}
item.destroy();
// Reposition remaining special items
for (var i = 0; i < specialItems.length; i++) {
var cannonX = 500;
var baseY = 2400;
specialItems[i].y = baseY - i * 80;
}
}
function applySpecialItemToTower(itemType, tower) {
if (itemType === 'bomb') {
// Bomb: Enhanced damage for battle effectiveness
tower.bombEnhanced = true;
tower.bombEnhancedTimer = 600; // 10 seconds at 60 FPS
// Visual effect
tween(tower, {
tint: 0xff4444
}, {
duration: 300,
onFinish: function onFinish() {
tween(tower, {
tint: 0xffffff
}, {
duration: 300
});
}
});
} else if (itemType === 'shield') {
// Shield: Protect tower with 70 HP shield
tower.shieldProtection = true;
tower.shieldHp = tower.shieldMaxHp; // 70 HP shield
// Create visual shield graphics - blue circle around tower
if (!tower.shieldGraphics) {
tower.shieldGraphics = tower.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
scaleX: 1.2,
scaleY: 1.2
});
tower.shieldGraphics.tint = 0x0000ff; // Blue color for shield
}
// Animate shield with pulsing blue effect
tween(tower.shieldGraphics, {
alpha: 0.3,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(tower.shieldGraphics, {
alpha: 0.6,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
// Visual healing effect
tween(tower, {
tint: 0x00ff00
}, {
duration: 300,
onFinish: function onFinish() {
tween(tower, {
tint: 0xffffff
}, {
duration: 300
});
}
});
} else if (itemType === 'missile') {
// Missile: Only works with cannon towers
if (tower.type === 'cannon') {
// Missile: Enhanced explosive shots for battle
tower.missileEnhanced = true;
tower.missileEnhancedTimer = 900; // 15 seconds at 60 FPS
tower.fireRate = Math.floor(tower.fireRate * 0.8); // Slightly faster firing
// Visual effect
tween(tower, {
tint: 0xff0000
}, {
duration: 300,
onFinish: function onFinish() {
tween(tower, {
tint: 0xffffff
}, {
duration: 300
});
}
});
// Reset fire rate after enhancement ends
LK.setTimeout(function () {
if (!tower.missileEnhanced) {
tower.fireRate = Math.floor(tower.fireRate / 0.8); // Reset to original
}
}, 15000);
}
}
}
function repositionSpecialItems() {
for (var i = 0; i < specialItems.length; i++) {
var cannonX = 500;
var baseY = 2400;
specialItems[i].x = cannonX - 150;
specialItems[i].y = baseY - i * 80;
}
}
// Game state management
var gameStarted = false;
var gameStartButton = null;
// Create start button
gameStartButton = LK.getAsset('towerSpot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
gameStartButton.x = 1024; // Center of screen
gameStartButton.y = 1366; // Center of screen
gameStartButton.tint = 0x00ff00; // Green color
game.addChild(gameStartButton);
// Add start button text
var startButtonText = new Text2('TAP TO START', {
size: 80,
fill: '#ffffff'
});
startButtonText.anchor.set(0.5, 0.5);
startButtonText.x = 0;
startButtonText.y = 0;
gameStartButton.addChild(startButtonText);
// Add pulsing animation to start button
var _startButtonPulse = function startButtonPulse() {
if (gameStartButton && !gameStarted) {
tween(gameStartButton, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0.8
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (gameStartButton && !gameStarted) {
tween(gameStartButton, {
scaleX: 2,
scaleY: 2,
alpha: 1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: _startButtonPulse
});
}
}
});
}
};
_startButtonPulse();
// Add click handler for start button
gameStartButton.down = function (localX, localY, obj) {
if (!gameStarted) {
gameStarted = true;
// Remove start button
gameStartButton.destroy();
gameStartButton = null;
// Start first wave
startWave();
}
};
// Don't start waves automatically anymore - wait for player to tap start ===================================================================
--- original.js
+++ change.js
@@ -1341,8 +1341,12 @@
self.shieldHp = 0;
self.shieldMaxHp = 70;
self.missileEnhanced = false;
self.missileEnhancedTimer = 0;
+ // Bomb usage tracking - towers can throw only one bomb initially
+ self.bombUsed = false;
+ self.bombUsageTimer = 0;
+ self.bombSecondTime = false;
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
@@ -1378,8 +1382,15 @@
if (self.missileTimer <= 0 && self.missileUsed && !self.missileSecondTime) {
self.missileSecondTime = true; // Unlock second missile usage
}
}
+ // Update bomb usage timer
+ if (self.bombUsageTimer > 0) {
+ self.bombUsageTimer--;
+ if (self.bombUsageTimer <= 0 && self.bombUsed && !self.bombSecondTime) {
+ self.bombSecondTime = true; // Unlock second bomb usage
+ }
+ }
// Update HP display
if (self.shieldProtection && self.shieldHp > 0) {
self.hpText.setText(self.hp + ' HP + ' + self.shieldHp + ' Shield');
} else {
@@ -1510,12 +1521,18 @@
bullets.push(bullet);
game.addChild(bullet);
}
} else {
- // Random chance for towers to throw bombs instead of shooting bullets
- if (Math.random() < 0.15) {
- // 15% chance to throw bomb
+ // Bomb usage logic: 1 bomb initially, then after 1 second cooldown, normal shooting
+ if (!self.bombUsed) {
+ // First bomb - use immediately
self.throwBomb();
+ self.bombUsed = true;
+ self.bombUsageTimer = 60; // 1 second cooldown at 60 FPS
+ } else if (self.bombSecondTime && self.bombUsageTimer <= 0) {
+ // Second time - can use bomb again but then revert to normal shooting
+ self.throwBomb();
+ self.bombSecondTime = false;
} else {
var bullet = new Bullet(self.x, self.y, self.target, finalDamage);
bullets.push(bullet);
game.addChild(bullet);