/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BuildSpot = Container.expand(function () {
var self = Container.call(this);
var spotGraphics = self.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.isOccupied = false;
self.turret = null;
self.previewRange = null;
self.move = function (x, y, obj) {
if (self.isOccupied) return;
if (!self.previewRange) {
// Create preview range indicator
self.previewRange = self.addChild(LK.getAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.15,
tint: selectedTowerType === 'laser' ? 0x4444FF : selectedTowerType === 'machineGun' ? 0xFF6600 : selectedTowerType === 'missile' ? 0xFF4444 : selectedTowerType === 'fire' ? 0xFF8800 : selectedTowerType === 'fireMachine' ? 0xFF2200 : selectedTowerType === 'fireTrap' ? 0xFF4400 : 0xFFFF00,
scaleX: selectedTowerType === 'laser' ? 3.0 : selectedTowerType === 'machineGun' ? 4.0 : selectedTowerType === 'missile' ? 3.4 : selectedTowerType === 'fire' ? 3.6 : selectedTowerType === 'fireMachine' ? 5.0 : selectedTowerType === 'fireTrap' ? 4.0 : 4.4,
scaleY: selectedTowerType === 'laser' ? 3.0 : selectedTowerType === 'machineGun' ? 4.0 : selectedTowerType === 'missile' ? 3.4 : selectedTowerType === 'fire' ? 3.6 : selectedTowerType === 'fireMachine' ? 5.0 : selectedTowerType === 'fireTrap' ? 4.0 : 4.4
}));
}
};
self.up = function (x, y, obj) {
if (self.previewRange) {
self.previewRange.destroy();
self.previewRange = null;
}
};
self.down = function (x, y, obj) {
if (self.isOccupied) return;
if (currency < selectedTowerCost) return;
// Build turret
var turret = new Turret();
turret.x = self.x;
turret.y = self.y;
turret.towerType = selectedTowerType;
turret.cost = selectedTowerCost;
turret.updateTowerProperties(); // Update properties based on tower type
self.turret = turret;
self.isOccupied = true;
currency -= selectedTowerCost;
updateCurrencyDisplay();
turrets.push(turret);
game.addChild(turret);
// Hide build spot
spotGraphics.alpha = 0;
// Flash green when built with tower type color
var flashColor = selectedTowerType === 'laser' ? 0x4444FF : selectedTowerType === 'machineGun' ? 0xFF6600 : selectedTowerType === 'missile' ? 0xFF4444 : selectedTowerType === 'fire' ? 0xFF8800 : selectedTowerType === 'fireMachine' ? 0xFF2200 : selectedTowerType === 'fireTrap' ? 0xFF4400 : 0xFFFF00;
LK.effects.flashObject(turret, flashColor, 500);
};
return self;
});
var FireTrap = Container.expand(function () {
var self = Container.call(this);
var fireGraphics = self.attachAsset('fireTrap', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.damage = 80; // Increased immediate damage
self.burnDuration = 3000; // 3 seconds
self.lastDamage = 0;
self.damageRate = 20; // Faster damage rate - hits more frequently
// Add range indicator
self.rangeIndicator = self.addChild(LK.getAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.15,
tint: 0xFF4400,
scaleX: 8.0,
// 400 pixel range
scaleY: 8.0
}));
// Create fire effect animation
tween(fireGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(fireGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart animation loop
if (self.parent) {
tween(fireGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
}
});
}
});
self.update = function () {
// Check for zombies within firing range
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.isDead) continue;
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 400) {
// Within firing range - deal immediate damage
self.lastDamage += speedMultiplier;
if (self.lastDamage >= self.damageRate) {
// Deal immediate damage every damage rate interval
zombie.takeDamage(self.damage);
self.lastDamage = 0;
// Create fire explosion effect at zombie location
var fireExplosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: zombie.x,
y: zombie.y,
tint: 0xFF4400,
scaleX: 1.5,
scaleY: 1.5
}));
tween(fireExplosion, {
alpha: 0,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 400,
onFinish: function onFinish() {
fireExplosion.destroy();
}
});
LK.getSound('explosion').play();
}
// Apply powerful burning effect for 5 seconds
if (!zombie.isBurning) {
zombie.isBurning = true;
zombie.burnTimer = 0;
zombie.burnDuration = 300; // 5 seconds at 60fps
zombie.burnDamage = 40; // Increased burn damage
// More intense visual burning effect
tween(zombie.children[0], {
tint: 0xFF2200
}, {
duration: 150,
onFinish: function onFinish() {
tween(zombie.children[0], {
tint: 0xFFFFFF
}, {
duration: 150,
onFinish: function onFinish() {
if (zombie.isBurning && zombie.parent) {
tween(zombie.children[0], {
tint: 0xFF2200
}, {
duration: 150,
onFinish: arguments.callee
});
}
}
});
}
});
// Add fire aura effect around burning zombie
var fireAura = zombie.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0xFF4400,
scaleX: 1.2,
scaleY: 1.2
}));
tween(fireAura, {
scaleX: 1.8,
scaleY: 1.8,
alpha: 0.2
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (fireAura.parent) {
tween(fireAura, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.6
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
}
});
}
}
}
};
return self;
});
var Turret = Container.expand(function () {
var self = Container.call(this);
var turretGraphics = self.attachAsset('turret', {
anchorX: 0.5,
anchorY: 0.5
});
// Add range indicator (will be updated based on tower type)
self.rangeIndicator = self.addChild(LK.getAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.25,
tint: 0x4444FF,
scaleX: 4,
scaleY: 4
}));
// Add cross indicator (hidden by default)
self.crossIndicator = self.addChild(LK.getAsset('cross', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
rotation: Math.PI / 4 // 45 degrees to make it look like an X
}));
self.damage = 75;
self.range = 200;
self.fireRate = 15; // frames between shots
self.lastShot = 0;
self.target = null;
self.cost = 50;
self.sellPrice = 25; // 50% of cost back when sold
self.towerType = 'laser'; // Default type
self.upgradeLevel = 1; // Track upgrade level
self.upgradeCost = 50; // Cost to upgrade
self.updateTowerProperties = function () {
var upgradeMultiplier = 1 + (self.upgradeLevel - 1) * 0.5; // 50% increase per level
if (self.towerType === 'laser') {
self.range = 150 * upgradeMultiplier;
self.damage = 75 * upgradeMultiplier;
self.fireRate = Math.max(5, 15 - (self.upgradeLevel - 1) * 2); // Faster fire rate
self.rangeIndicator.tint = 0x4444FF;
self.rangeIndicator.scaleX = 3.0; // Keep visual size constant
self.rangeIndicator.scaleY = 3.0; // Keep visual size constant
self.sellPrice = Math.floor((50 + (self.upgradeLevel - 1) * 50) * 0.5); // 50% of total cost back
self.upgradeCost = 50 * self.upgradeLevel; // Increasing upgrade cost
} else if (self.towerType === 'missile') {
self.range = 170 * upgradeMultiplier;
self.damage = 120 * upgradeMultiplier;
self.fireRate = Math.max(8, 20 - (self.upgradeLevel - 1) * 3);
self.rangeIndicator.tint = 0xFF4444;
self.rangeIndicator.scaleX = 3.4; // Keep visual size constant
self.rangeIndicator.scaleY = 3.4; // Keep visual size constant
self.sellPrice = Math.floor((100 + (self.upgradeLevel - 1) * 100) * 0.5); // 50% of total cost back
self.upgradeCost = 100 * self.upgradeLevel;
} else if (self.towerType === 'fire') {
self.range = 180 * upgradeMultiplier;
self.damage = 90 * upgradeMultiplier;
self.fireRate = Math.max(4, 10 - (self.upgradeLevel - 1) * 2);
self.rangeIndicator.tint = 0xFF8800;
self.rangeIndicator.scaleX = 3.6; // Keep visual size constant
self.rangeIndicator.scaleY = 3.6; // Keep visual size constant
self.sellPrice = Math.floor((200 + (self.upgradeLevel - 1) * 200) * 0.5); // 50% of total cost back
self.upgradeCost = 200 * self.upgradeLevel;
} else if (self.towerType === 'electric') {
self.range = 220 * upgradeMultiplier;
self.damage = 60 * upgradeMultiplier;
self.fireRate = Math.max(6, 12 - (self.upgradeLevel - 1) * 2);
self.rangeIndicator.tint = 0xFFFF00;
self.rangeIndicator.scaleX = 4.4; // Keep visual size constant
self.rangeIndicator.scaleY = 4.4; // Keep visual size constant
self.sellPrice = Math.floor((300 + (self.upgradeLevel - 1) * 300) * 0.5); // 50% of total cost back
self.upgradeCost = 300 * self.upgradeLevel;
} else if (self.towerType === 'fireMachine') {
self.range = 250 * upgradeMultiplier;
self.damage = 200 * upgradeMultiplier;
self.fireRate = Math.max(3, 8 - (self.upgradeLevel - 1) * 2);
self.rangeIndicator.tint = 0xFF2200;
self.rangeIndicator.scaleX = 5.0; // Keep visual size constant
self.rangeIndicator.scaleY = 5.0; // Keep visual size constant
self.sellPrice = Math.floor((500 + (self.upgradeLevel - 1) * 500) * 0.5); // 50% of total cost back
self.upgradeCost = 500 * self.upgradeLevel;
} else if (self.towerType === 'machineGun') {
self.range = 200 * upgradeMultiplier;
self.damage = 300 * upgradeMultiplier;
self.fireRate = Math.max(2, 5 - (self.upgradeLevel - 1) * 1);
self.rangeIndicator.tint = 0xFF6600;
self.rangeIndicator.scaleX = 4.0; // Keep visual size constant
self.rangeIndicator.scaleY = 4.0; // Keep visual size constant
self.sellPrice = Math.floor((1000 + (self.upgradeLevel - 1) * 1000) * 0.5); // 50% of total cost back
self.upgradeCost = 1000 * self.upgradeLevel;
} else if (self.towerType === 'fireTrap') {
self.range = 400 * upgradeMultiplier;
self.damage = 50 * upgradeMultiplier;
self.fireRate = Math.max(10, 30 - (self.upgradeLevel - 1) * 5);
self.rangeIndicator.tint = 0xFF4400;
self.rangeIndicator.scaleX = 4.0; // Keep visual size constant
self.rangeIndicator.scaleY = 4.0; // Keep visual size constant
self.sellPrice = Math.floor((700 + (self.upgradeLevel - 1) * 700) * 0.5); // 50% of total cost back
self.upgradeCost = 700 * self.upgradeLevel;
}
};
self.towerType = 'laser'; // Default type
self.findTarget = function () {
var closestZombie = null;
var closestDistance = self.range;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.isDead) continue;
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestZombie = zombie;
}
}
return closestZombie;
};
self.shoot = function (target) {
// Direct damage to target without bullets
if (target && !target.isDead) {
target.takeDamage(self.damage);
// Create explosion effect at target location
var explosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: target.x,
y: target.y
}));
tween(explosion, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
explosion.destroy();
}
});
LK.getSound('explosion').play();
}
LK.getSound('shoot').play();
// Flash blue when shooting
LK.effects.flashObject(self, 0x4444ff, 200);
};
self.down = function (x, y, obj) {
if (placementMode) return; // Don't select turrets in placement mode
selectTurret(self);
}; //{1j_new}
self.update = function () {
// Skip shooting if turret is frozen
if (self.isFrozen) return;
self.lastShot += speedMultiplier;
if (self.lastShot >= self.fireRate) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.lastShot = 0;
}
}
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.speed = 1;
self.reward = 10;
self.pathIndex = 0;
self.targetX = 0;
self.targetY = 0;
self.isDead = false;
// Create health bar
self.healthBar = self.addChild(LK.getAsset('buildSpot', {
anchorX: 0,
anchorY: 0.5,
x: -25,
y: -40,
width: 50,
height: 6,
color: 0x44FF44
}));
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
// Directly add currency without visual money drop
currency += self.reward;
updateCurrencyDisplay();
LK.getSound('zombieHit').play();
}
};
self.update = function () {
if (self.isDead) return;
// Move towards target continuously
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Always move towards target, no stopping threshold
if (distance > 0.1) {
var moveX = dx / distance * self.speed * speedMultiplier;
var moveY = dy / distance * self.speed * speedMultiplier;
self.x += moveX;
self.y += moveY;
}
// Check if we've reached or passed the current target
if (distance <= self.speed * speedMultiplier + 1) {
// Move to next path point immediately
self.pathIndex++;
if (self.pathIndex >= gamePath.length) {
// Reached end - damage base
lives--;
updateLivesDisplay();
if (lives <= 0) {
LK.showGameOver();
}
self.isDead = true;
} else {
self.targetX = gamePath[self.pathIndex].x;
self.targetY = gamePath[self.pathIndex].y;
}
}
// Handle burning effect
if (self.isBurning) {
self.burnTimer += speedMultiplier;
if (self.burnTimer >= 60) {
// Burn damage every second
self.takeDamage(self.burnDamage);
self.burnTimer = 0;
}
// Stop burning after 5 seconds
if (self.burnTimer >= self.burnDuration) {
self.isBurning = false;
tween.stop(self.children[0], {
tint: true
});
self.children[0].tint = 0xFFFFFF;
}
}
// Handle freeze effect for levels 11 and 15
if (self.hasFreezeEffect && !self.freezeEffectUsed) {
self.freezeEffectCooldown += speedMultiplier;
// Use freeze effect when zombie is at half health or after 5 seconds
var healthPercent = self.health / self.maxHealth;
if (healthPercent <= 0.5 || self.freezeEffectCooldown >= 300) {
self.freezeEffectUsed = true;
activateZombieFreezeEffect();
// Add visual indication that zombie used freeze effect
var freezeIndicator = self.addChild(LK.getAsset('cross', {
anchorX: 0.5,
anchorY: 0.5,
y: -50,
tint: 0x00CCFF,
scaleX: 1.5,
scaleY: 1.5
}));
tween(freezeIndicator, {
alpha: 0,
y: -80,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 1000,
onFinish: function onFinish() {
if (freezeIndicator.parent) {
freezeIndicator.destroy();
}
}
});
}
}
// Update health bar if it exists
if (self.healthBar) {
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent;
self.healthBar.tint = healthPercent > 0.5 ? 0x44FF44 : healthPercent > 0.25 ? 0xFFFF44 : 0xFF4444;
}
};
return self;
});
var FlyingZombie = Zombie.expand(function () {
var self = Zombie.call(this);
// Mark as flying type
self.isFlying = true;
// Add flying visual indicator - wings or glow effect
var flyingIndicator = self.addChild(LK.getAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.4,
tint: 0x00FFFF,
scaleX: 1.5,
scaleY: 0.8,
y: -10
}));
// Start floating animation
function startFloatingAnimation() {
tween(self, {
y: self.y - 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
y: self.y + 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent && !self.isDead) {
startFloatingAnimation();
}
}
});
}
});
}
// Store the original update method from parent Zombie class
var originalUpdate = self.update;
self.update = function () {
if (self.isDead) return;
// Initialize flying zombie movement properties if not set
if (!self.flyingInitialized) {
self.flyingInitialized = true;
// Set initial target as the final destination
self.finalTargetX = gamePath[gamePath.length - 1].x;
self.finalTargetY = gamePath[gamePath.length - 1].y;
// Flying zombies move more directly but with some wandering
self.wanderAngle = Math.random() * Math.PI * 2;
self.wanderTimer = 0;
self.wanderChangeRate = 180; // Change direction every 3 seconds
}
// Flying zombie free movement logic
var dx = self.finalTargetX - self.x;
var dy = self.finalTargetY - self.y;
var distanceToTarget = Math.sqrt(dx * dx + dy * dy);
// Add wandering behavior to make movement more interesting
self.wanderTimer += speedMultiplier;
if (self.wanderTimer >= self.wanderChangeRate) {
self.wanderAngle += (Math.random() - 0.5) * 0.8; // Random direction change
self.wanderTimer = 0;
}
// Calculate movement with wandering
var wanderStrength = 0.3; // How much the zombie wanders vs goes direct
var directX = dx / distanceToTarget;
var directY = dy / distanceToTarget;
var wanderX = Math.cos(self.wanderAngle) * wanderStrength;
var wanderY = Math.sin(self.wanderAngle) * wanderStrength;
// Combine direct movement with wandering
var moveX = (directX * (1 - wanderStrength) + wanderX) * self.speed * speedMultiplier;
var moveY = (directY * (1 - wanderStrength) + wanderY) * self.speed * speedMultiplier;
// Apply movement
self.x += moveX;
self.y += moveY;
// Keep flying zombies within game bounds
if (self.x < 50) self.x = 50;
if (self.x > 1998) self.x = 1998;
if (self.y < 50) self.y = 50;
if (self.y > 2050) self.y = 2050;
// Check if reached the end
if (distanceToTarget <= 100) {
// Reached end - damage base
lives--;
updateLivesDisplay();
if (lives <= 0) {
LK.showGameOver();
}
self.isDead = true;
}
// Handle burning effect (same as regular zombies)
if (self.isBurning) {
self.burnTimer += speedMultiplier;
if (self.burnTimer >= 60) {
// Burn damage every second
self.takeDamage(self.burnDamage);
self.burnTimer = 0;
}
// Stop burning after 5 seconds
if (self.burnTimer >= self.burnDuration) {
self.isBurning = false;
tween.stop(self.children[0], {
tint: true
});
self.children[0].tint = 0xFFFFFF;
}
}
// Handle freeze effect for levels 11 and 15 (same as regular zombies)
if (self.hasFreezeEffect && !self.freezeEffectUsed) {
self.freezeEffectCooldown += speedMultiplier;
var healthPercent = self.health / self.maxHealth;
if (healthPercent <= 0.5 || self.freezeEffectCooldown >= 300) {
self.freezeEffectUsed = true;
activateZombieFreezeEffect();
var freezeIndicator = self.addChild(LK.getAsset('cross', {
anchorX: 0.5,
anchorY: 0.5,
y: -50,
tint: 0x00CCFF,
scaleX: 1.5,
scaleY: 1.5
}));
tween(freezeIndicator, {
alpha: 0,
y: -80,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 1000,
onFinish: function onFinish() {
if (freezeIndicator.parent) {
freezeIndicator.destroy();
}
}
});
}
}
// Update health bar if it exists
if (self.healthBar) {
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent;
self.healthBar.tint = healthPercent > 0.5 ? 0x44FF44 : healthPercent > 0.25 ? 0xFFFF44 : 0xFF4444;
}
// Start floating animation if not already started
if (!self.floatingStarted && self.parent) {
self.floatingStarted = true;
startFloatingAnimation();
}
// Add gentle rotation for flying effect
if (self.children[0]) {
self.children[0].rotation += 0.02 * speedMultiplier;
}
// Pulsing glow effect for flying indicator
if (flyingIndicator && flyingIndicator.parent) {
flyingIndicator.alpha = 0.4 + Math.sin(LK.ticks * 0.1) * 0.2;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
var zombies = [];
var turrets = [];
var bullets = [];
var buildSpots = [];
var fireTraps = [];
var gamePath = [];
var currency = 100;
var lives = 10;
var wave = 1;
var zombiesSpawned = 0;
var zombiesToSpawn = 5;
var spawnTimer = 0;
var spawnRate = 120; // frames between spawns
var baseSpawnRate = 120; // Store base spawn rate
var waveActive = false;
var waveCompleted = false;
var selectedTowerType = 'laser';
var selectedTowerCost = 50;
// Create path points for zombies to follow - Shorter strategic path
gamePath = [{
x: 0,
y: 1366
}, {
x: 400,
y: 1366
}, {
x: 400,
y: 1000
}, {
x: 800,
y: 1000
}, {
x: 800,
y: 1500
}, {
x: 1200,
y: 1500
}, {
x: 1200,
y: 800
}, {
x: 1600,
y: 800
}, {
x: 1600,
y: 400
}, {
x: 1000,
y: 400
}, {
x: 1000,
y: 1800
}, {
x: 1400,
y: 1800
}, {
x: 2048,
y: 1800
}];
// Create visual path
for (var i = 0; i < gamePath.length - 1; i++) {
var start = gamePath[i];
var end = gamePath[i + 1];
var steps = Math.floor(Math.sqrt((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / 120);
for (var j = 0; j <= steps; j++) {
var t = j / steps;
var pathX = start.x + (end.x - start.x) * t;
var pathY = start.y + (end.y - start.y) * t;
var pathTile = game.addChild(LK.getAsset('path', {
anchorX: 0.5,
anchorY: 0.5,
x: pathX,
y: pathY,
alpha: 0.3
}));
}
}
// Build spots are no longer needed - turrets can be placed anywhere
// buildSpots array is kept for compatibility but not used
// UI Elements
var currencyTxt = new Text2('Money: $' + currency, {
size: 72,
fill: 0x00FFFF
});
currencyTxt.anchor.set(0, 0);
currencyTxt.x = 120; // Offset from left edge to avoid platform menu
currencyTxt.y = 100;
LK.gui.topLeft.addChild(currencyTxt);
// Add glowing effect to currency
LK.effects.flashObject(currencyTxt, 0x00FFFF, 2000);
var livesTxt = new Text2('Lives: ' + lives, {
size: 60,
fill: 0xFF4444
});
livesTxt.anchor.set(0, 0);
livesTxt.x = 120; // Offset from left edge to avoid platform menu
livesTxt.y = 20;
LK.gui.topLeft.addChild(livesTxt);
// Tower purchase menu at bottom left - vertical layout with consistent alignment
var towerMenuX = 120; // Left side position for tower menu
var towerMenuStartY = 1550; // Starting Y position - moved up to be above sell turret text
var towerMenuSpacing = 140; // Further increased spacing between buttons to prevent interference
var laserTurretBtn = new Text2('Laser Turret\n$50', {
size: 48,
fill: 0x4444FF
});
laserTurretBtn.anchor.set(0, 0.5); // Left-aligned anchor
laserTurretBtn.x = towerMenuX;
laserTurretBtn.y = towerMenuStartY;
game.addChild(laserTurretBtn);
var missileLauncherBtn = new Text2('Missile Tower\n$100', {
size: 48,
fill: 0xFF4444
});
missileLauncherBtn.anchor.set(0, 0.5); // Left-aligned anchor
missileLauncherBtn.x = towerMenuX;
missileLauncherBtn.y = towerMenuStartY + towerMenuSpacing;
game.addChild(missileLauncherBtn);
var fireDroneBtn = new Text2('Fire Blaster\n$200', {
size: 48,
fill: 0xFF8800
});
fireDroneBtn.anchor.set(0, 0.5); // Left-aligned anchor
fireDroneBtn.x = towerMenuX;
fireDroneBtn.y = towerMenuStartY + towerMenuSpacing * 2;
game.addChild(fireDroneBtn);
var electricTrapBtn = new Text2('Electric Field\n$300', {
size: 48,
fill: 0xFFFF00
});
electricTrapBtn.anchor.set(0, 0.5); // Left-aligned anchor
electricTrapBtn.x = towerMenuX;
electricTrapBtn.y = towerMenuStartY + towerMenuSpacing * 3;
game.addChild(electricTrapBtn);
var fireMachineBtn = new Text2('Fire Machine\n$500', {
size: 48,
fill: 0xFF2200
});
fireMachineBtn.anchor.set(0, 0.5); // Left-aligned anchor
fireMachineBtn.x = towerMenuX + 400; // Move to the right of laser turret
fireMachineBtn.y = towerMenuStartY;
game.addChild(fireMachineBtn);
var machineGunBtn = new Text2('Machine Gun\n$1000', {
size: 48,
fill: 0xFF6600
});
machineGunBtn.anchor.set(0, 0.5); // Left-aligned anchor
machineGunBtn.x = towerMenuX + 400; // Move to the right of laser turret
machineGunBtn.y = towerMenuStartY + towerMenuSpacing;
game.addChild(machineGunBtn);
var fireTrapBtn = new Text2('Fire Trap\n$700', {
size: 48,
fill: 0xFF4400
});
fireTrapBtn.anchor.set(0, 0.5); // Left-aligned anchor
fireTrapBtn.x = towerMenuX + 400; // Move to the right of laser turret
fireTrapBtn.y = towerMenuStartY + towerMenuSpacing * 2;
game.addChild(fireTrapBtn);
var waveTxt = new Text2('Level ' + wave, {
size: 80,
fill: 0x44FF44
});
waveTxt.anchor.set(0.5, 0);
waveTxt.y = 20;
LK.gui.top.addChild(waveTxt);
// Bomb system
var bombsLeft = 3;
var bombTxt = new Text2('Bombs: ' + bombsLeft, {
size: 72,
fill: 0xFF0000
});
bombTxt.anchor.set(1, 0);
bombTxt.x = -20;
bombTxt.y = -80;
LK.gui.topRight.addChild(bombTxt);
var bombBtn = new Text2('💣 BOMB', {
size: 80,
fill: 0xFF4444
});
bombBtn.anchor.set(1, 1);
bombBtn.x = -20;
bombBtn.y = -20;
LK.gui.bottomRight.addChild(bombBtn);
var startWaveBtn = new Text2('START WAVE', {
size: 80,
fill: 0xFFFFFF
});
startWaveBtn.anchor.set(0.5, 0.5);
startWaveBtn.x = 500;
startWaveBtn.y = 2550;
game.addChild(startWaveBtn);
var sellBtn = new Text2('SELL\nTURRET\n$0', {
size: 64,
fill: 0xFFAA00
});
sellBtn.anchor.set(0.5, 0.5);
sellBtn.x = 300; // Position to the left
sellBtn.y = 2400; // Move down
sellBtn.alpha = 0.5; // Start dimmed
game.addChild(sellBtn);
var upgradeBtn = new Text2('UPGRADE\nTURRET\n$0', {
size: 64,
fill: 0x00FF00
});
upgradeBtn.anchor.set(0.5, 0.5);
upgradeBtn.x = 700; // Position to the right
upgradeBtn.y = 2400; // Same height as sell button - moved down
upgradeBtn.alpha = 0.5; // Start dimmed
game.addChild(upgradeBtn);
// Speed acceleration system
var gameSpeed = 1.0;
var speedMultiplier = 1.0;
// Speed acceleration buttons
var speed1xBtn = new Text2('1x Speed', {
size: 48,
fill: 0x00FF00
});
speed1xBtn.anchor.set(0.5, 0.5);
speed1xBtn.x = 1400;
speed1xBtn.y = 2180;
game.addChild(speed1xBtn);
var speed2xBtn = new Text2('2x Speed', {
size: 48,
fill: 0xFFFF00
});
speed2xBtn.anchor.set(0.5, 0.5);
speed2xBtn.x = 1400;
speed2xBtn.y = 2280;
game.addChild(speed2xBtn);
var speed3xBtn = new Text2('3x Speed', {
size: 48,
fill: 0xFF8800
});
speed3xBtn.anchor.set(0.5, 0.5);
speed3xBtn.x = 1400;
speed3xBtn.y = 2380;
game.addChild(speed3xBtn);
function distanceToLineSegment(px, py, x1, y1, x2, y2) {
var dx = x2 - x1;
var dy = y2 - y1;
var length = Math.sqrt(dx * dx + dy * dy);
if (length === 0) return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
var t = ((px - x1) * dx + (py - y1) * dy) / (length * length);
t = Math.max(0, Math.min(1, t));
var projX = x1 + t * dx;
var projY = y1 + t * dy;
return Math.sqrt((px - projX) * (px - projX) + (py - projY) * (py - projY));
}
function updateCurrencyDisplay() {
currencyTxt.setText('Money: $' + currency);
// Add glowing effect when currency changes
LK.effects.flashObject(currencyTxt, 0x00FFFF, 500);
}
function updateBombDisplay() {
bombTxt.setText('Bombs: ' + bombsLeft);
}
function useBomb() {
if (bombsLeft <= 0) return;
bombsLeft--;
updateBombDisplay();
// Create massive explosion effect
var bombExplosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 10,
scaleY: 10,
alpha: 0.8
}));
// Damage all zombies significantly
for (var i = 0; i < zombies.length; i++) {
if (!zombies[i].isDead) {
zombies[i].takeDamage(150);
LK.effects.flashObject(zombies[i], 0xFF0000, 500);
}
}
// Animate explosion
tween(bombExplosion, {
alpha: 0,
scaleX: 15,
scaleY: 15
}, {
duration: 1000,
onFinish: function onFinish() {
bombExplosion.destroy();
}
});
LK.getSound('explosion').play();
LK.effects.flashScreen(0xFF4444, 800);
}
function updateLivesDisplay() {
livesTxt.setText('Lives: ' + lives);
}
function updateWaveDisplay() {
waveTxt.setText('Level ' + wave);
}
function startWave() {
if (waveActive) return;
waveActive = true;
waveCompleted = false;
zombiesSpawned = 0;
// Make first level easier with fewer zombies
if (wave === 1) {
zombiesToSpawn = 3; // Only 3 zombies for first wave
} else if (wave >= 9 && wave <= 10) {
zombiesToSpawn = 10; // Only 10 enemies for levels 9 and 10
} else if (wave >= 11) {
zombiesToSpawn = 12 + (wave - 11) * 2; // More enemies for levels 11-15
} else {
zombiesToSpawn = 5 + (wave - 1) * 2; // Adjusted formula for subsequent waves
}
spawnTimer = 0;
startWaveBtn.alpha = 0.3;
// Increase zombie health each wave, but start lower for first level
if (wave === 1) {
Zombie.prototype.baseHealth = 60; // Weaker zombies for first level
} else if (wave <= 5) {
Zombie.prototype.baseHealth = 100 + (wave - 1) * 35; // Adjusted for waves 2-5
} else if (wave <= 10) {
// After level 5, base health becomes extremely high for boss-level enemies
var levelBonus = wave - 5;
Zombie.prototype.baseHealth = 2000 + levelBonus * 800; // Extremely high base health after level 5 - boss-level
} else {
// Levels 11-15: Ultimate boss enemies with exponential health scaling
var levelBonus = wave - 10;
Zombie.prototype.baseHealth = 8000 + levelBonus * 2000; // Exponential health scaling for levels 11-15
}
}
function spawnZombie() {
if (zombiesSpawned >= zombiesToSpawn) return;
// Determine if this should be a flying zombie (30% chance for waves 10+)
var shouldBeFlyingZombie = wave >= 10 && Math.random() < 0.3;
var zombie = shouldBeFlyingZombie ? new FlyingZombie() : new Zombie();
zombie.x = gamePath[0].x;
zombie.y = gamePath[0].y;
zombie.targetX = gamePath[1].x;
zombie.targetY = gamePath[1].y;
// Make first level zombies weaker and slower
if (wave === 1) {
zombie.health = 60; // Much weaker for first level
zombie.speed = 1.2; // Faster for first level
zombie.reward = 15; // Higher reward for first level to help progression
} else if (wave <= 5) {
zombie.health = 100 + (wave - 1) * 35; // Adjusted for waves 2-5
zombie.speed = 1.5 + (wave - 1) * 0.4; // Faster for waves 2-5
zombie.reward = 10 + wave * 5;
} else {
// After level 5, enemies become extremely strong bosses
var levelBonus = wave - 5;
// Levels 9 and 10 have nearly indestructible enemies
if (wave >= 9 && wave <= 10) {
zombie.health = 15000 + levelBonus * 5000; // Massive health for levels 9-10
} else if (wave >= 11) {
// Levels 11-15: Ultimate boss enemies with exponential health
var ultimateBonus = wave - 10;
zombie.health = 25000 + ultimateBonus * 10000; // Exponential health for levels 11-15
// Add freeze effect for levels 11 and 15
if (wave === 11 || wave === 15) {
zombie.hasFreezeEffect = true;
zombie.freezeEffectUsed = false;
zombie.freezeEffectCooldown = 0;
}
} else {
zombie.health = 2000 + levelBonus * 800; // Extremely high health after level 5 - boss-level
}
zombie.speed = 2.5 + levelBonus * 0.3; // Reduced speed after level 5 - not too fast
zombie.reward = 100 + levelBonus * 50; // Much higher rewards for boss-level enemies
// After level 6, some enemies get speed boost
if (wave > 6) {
var shouldGetSpeedBoost = Math.random() < 0.4; // 40% chance for speed boost
if (shouldGetSpeedBoost) {
zombie.speed *= 1.8; // 80% speed increase for boosted enemies
zombie.hasSpeedBoost = true;
// Flying zombies get even more speed boost
if (zombie.isFlying) {
zombie.speed *= 1.3; // Additional 30% speed for flying zombies
zombie.children[0].tint = 0x00FFAA; // Turquoise tint for fast flying enemies
} else {
zombie.children[0].tint = 0xFF8800; // Orange tint for speed boost
}
// Add speed boost aura effect
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.4,
tint: 0xFF8800,
scaleX: 1.8,
scaleY: 1.8
});
}
}
}
zombie.maxHealth = zombie.health;
// Add visual effects for higher levels but keep first level size
if (wave >= 5) {
// Add continuous glowing effect for boss-level enemies
LK.effects.flashObject(zombie, 0xFF0000, 2000);
// Keep zombie size same as first level - no scaling
zombie.children[0].scaleX = 1.0;
zombie.children[0].scaleY = 1.0;
// Different tint for flying zombies
if (zombie.isFlying) {
zombie.children[0].tint = 0x00FFFF; // Cyan tint for flying boss enemies
} else {
zombie.children[0].tint = 0xFF0000; // Deep red tint for boss enemies
}
// Add boss aura effect
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0xFF0000,
scaleX: 2.5,
scaleY: 2.5
});
// Add second layer boss effect for very high levels
if (wave >= 8) {
// Level 8+ enemies are EXTREMELY strong with maximum boss effects
if (wave >= 11) {
zombie.health *= 5.0; // 5x health for levels 11-15 - ultimate bosses
zombie.reward *= 10; // 10x rewards for these ultimate bosses
} else if (wave >= 9) {
zombie.health *= 3.0; // Triple health for levels 9-10 - nearly indestructible
zombie.reward *= 5; // 5x rewards for these ultimate bosses
} else {
zombie.health *= 1.5; // 50% more health on top of base boss health
zombie.reward *= 2; // Double rewards for these ultra-bosses
}
zombie.maxHealth = zombie.health;
// Maximum boss visual effects
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0xFFFF00,
scaleX: 4.0,
scaleY: 4.0
});
// Add third layer ultra-boss effect for level 8+
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.4,
tint: 0xFF00FF,
scaleX: 3.5,
scaleY: 3.5
});
// Levels 9-15 get additional intimidating effects
if (wave >= 11) {
// Levels 11-15: Ultimate boss effects with rainbow aura
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
tint: 0xFF00FF,
scaleX: 6.0,
scaleY: 6.0
});
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
tint: 0x00FFFF,
scaleX: 5.5,
scaleY: 5.5
});
zombie.children[0].scaleX = 1.0; // Keep same size as first level
zombie.children[0].scaleY = 1.0; // Keep same size as first level
// Different tint for flying ultimate bosses
if (zombie.isFlying) {
zombie.children[0].tint = 0x00FFFF; // Cyan tint for flying ultimate bosses
// Add extra flying effects for ultimate flying bosses
LK.effects.flashObject(zombie, 0x00FFFF, 600);
} else {
zombie.children[0].tint = 0xFF00FF; // Magenta tint for ultimate bosses
}
// Multiple rainbow effects for ultimate bosses
LK.effects.flashObject(zombie, 0xFF00FF, 800);
LK.effects.flashObject(zombie, 0x00FFFF, 1000);
LK.effects.flashObject(zombie, 0xFFFF00, 1200);
} else if (wave >= 9) {
// Add fourth layer ultimate boss effect for levels 9-10
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
tint: 0x000000,
scaleX: 5.0,
scaleY: 5.0
});
// Add fifth layer ultimate boss effect for levels 9-10
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7,
tint: 0xFFFFFF,
scaleX: 4.5,
scaleY: 4.5
});
// Keep them same size as first level
zombie.children[0].scaleX = 1.0;
zombie.children[0].scaleY = 1.0;
// Different tint for flying ultimate bosses
if (zombie.isFlying) {
zombie.children[0].tint = 0x00CCFF; // Light blue tint for flying ultimate bosses
} else {
zombie.children[0].tint = 0x000000; // Black tint for ultimate bosses
}
// Multiple continuous pulsing effects for ultimate bosses
LK.effects.flashObject(zombie, 0x000000, 1000);
LK.effects.flashObject(zombie, 0xFFFFFF, 1200);
} else {
// Keep them same size as first level
zombie.children[0].scaleX = 1.0;
zombie.children[0].scaleY = 1.0;
zombie.children[0].tint = 0x8B0000; // Dark red tint for ultra bosses
// Continuous pulsing effect for ultra bosses
LK.effects.flashObject(zombie, 0xFF00FF, 1500);
}
}
}
zombies.push(zombie);
game.addChild(zombie);
zombiesSpawned++;
}
function checkWaveComplete() {
if (!waveActive) return;
var aliveZombies = 0;
for (var i = 0; i < zombies.length; i++) {
if (!zombies[i].isDead) {
aliveZombies++;
}
}
if (zombiesSpawned >= zombiesToSpawn && aliveZombies === 0) {
waveActive = false;
waveCompleted = true;
wave++;
updateWaveDisplay();
currency += 50; // Wave completion bonus
updateCurrencyDisplay();
startWaveBtn.alpha = 1;
// Flash screen green for wave completion
LK.effects.flashScreen(0x44ff44, 1000);
// Check win condition
if (wave > 15) {
LK.showYouWin();
}
}
}
// Start wave button interaction
startWaveBtn.down = function (x, y, obj) {
startWave();
};
// Bomb button interaction
bombBtn.down = function (x, y, obj) {
useBomb();
};
// Sell button interaction
sellBtn.down = function (x, y, obj) {
sellTurret();
};
// Upgrade button interaction
upgradeBtn.down = function (x, y, obj) {
upgradeTurret();
};
// Tower selection highlighting
var selectedTowerBtn = laserTurretBtn;
function highlightSelectedTower() {
// Reset all tower buttons
laserTurretBtn.alpha = selectedTowerType === 'laser' ? 1.0 : 0.7;
machineGunBtn.alpha = selectedTowerType === 'machineGun' ? 1.0 : 0.7;
missileLauncherBtn.alpha = selectedTowerType === 'missile' ? 1.0 : 0.7;
fireDroneBtn.alpha = selectedTowerType === 'fire' ? 1.0 : 0.7;
electricTrapBtn.alpha = selectedTowerType === 'electric' ? 1.0 : 0.7;
fireMachineBtn.alpha = selectedTowerType === 'fireMachine' ? 1.0 : 0.7;
fireTrapBtn.alpha = selectedTowerType === 'fireTrap' ? 1.0 : 0.7;
// Add glow effect to selected tower
if (selectedTowerType === 'laser') {
LK.effects.flashObject(laserTurretBtn, 0x4444FF, 500);
} else if (selectedTowerType === 'machineGun') {
LK.effects.flashObject(machineGunBtn, 0xFF6600, 500);
} else if (selectedTowerType === 'missile') {
LK.effects.flashObject(missileLauncherBtn, 0xFF4444, 500);
} else if (selectedTowerType === 'fire') {
LK.effects.flashObject(fireDroneBtn, 0xFF8800, 500);
} else if (selectedTowerType === 'electric') {
LK.effects.flashObject(electricTrapBtn, 0xFFFF00, 500);
} else if (selectedTowerType === 'fireMachine') {
LK.effects.flashObject(fireMachineBtn, 0xFF2200, 500);
} else if (selectedTowerType === 'fireTrap') {
LK.effects.flashObject(fireTrapBtn, 0xFF4400, 500);
}
}
// Initial highlight
highlightSelectedTower();
// Speed button interactions
speed1xBtn.down = function (x, y, obj) {
speedMultiplier = 1.0;
highlightSelectedSpeed();
};
speed2xBtn.down = function (x, y, obj) {
speedMultiplier = 2.0;
highlightSelectedSpeed();
};
speed3xBtn.down = function (x, y, obj) {
speedMultiplier = 3.0;
highlightSelectedSpeed();
};
function highlightSelectedSpeed() {
// Reset all speed buttons
speed1xBtn.alpha = speedMultiplier === 1.0 ? 1.0 : 0.7;
speed2xBtn.alpha = speedMultiplier === 2.0 ? 1.0 : 0.7;
speed3xBtn.alpha = speedMultiplier === 3.0 ? 1.0 : 0.7;
// Add glow effect to selected speed
if (speedMultiplier === 1.0) {
LK.effects.flashObject(speed1xBtn, 0x00FF00, 500);
} else if (speedMultiplier === 2.0) {
LK.effects.flashObject(speed2xBtn, 0xFFFF00, 500);
} else if (speedMultiplier === 3.0) {
LK.effects.flashObject(speed3xBtn, 0xFF8800, 500);
}
}
// Initial speed highlight
highlightSelectedSpeed();
// Tower menu interactions
laserTurretBtn.down = function (x, y, obj) {
if (currency < 50) return; // Not enough money
selectedTowerType = 'laser';
selectedTowerCost = 50;
placementMode = true;
turretToPlace = {
type: 'laser',
cost: 50
};
highlightSelectedTower();
};
machineGunBtn.down = function (x, y, obj) {
if (currency < 1000) return; // Not enough money
selectedTowerType = 'machineGun';
selectedTowerCost = 1000;
placementMode = true;
turretToPlace = {
type: 'machineGun',
cost: 1000
};
highlightSelectedTower();
};
missileLauncherBtn.down = function (x, y, obj) {
if (currency < 100) return; // Not enough money
selectedTowerType = 'missile';
selectedTowerCost = 100;
placementMode = true;
turretToPlace = {
type: 'missile',
cost: 100
};
highlightSelectedTower();
};
fireDroneBtn.down = function (x, y, obj) {
if (currency < 200) return; // Not enough money
selectedTowerType = 'fire';
selectedTowerCost = 200;
placementMode = true;
turretToPlace = {
type: 'fire',
cost: 200
};
highlightSelectedTower();
};
electricTrapBtn.down = function (x, y, obj) {
if (currency < 300) return; // Not enough money
selectedTowerType = 'electric';
selectedTowerCost = 300;
placementMode = true;
turretToPlace = {
type: 'electric',
cost: 300
};
highlightSelectedTower();
};
fireMachineBtn.down = function (x, y, obj) {
if (currency < 500) return; // Not enough money
selectedTowerType = 'fireMachine';
selectedTowerCost = 500;
placementMode = true;
turretToPlace = {
type: 'fireMachine',
cost: 500
};
highlightSelectedTower();
};
fireTrapBtn.down = function (x, y, obj) {
if (currency < 700) return; // Not enough money
selectedTowerType = 'fireTrap';
selectedTowerCost = 700;
placementMode = true;
turretToPlace = {
type: 'fireTrap',
cost: 700
};
highlightSelectedTower();
};
var selectedTurret = null;
var placementMode = false;
var turretToPlace = null;
var frozenTurrets = [];
var freezeEffectActive = false;
var freezeEffectTimer = 0;
var freezeEffectDuration = 120; // 2 seconds at 60fps
function selectTurret(turret) {
// Deselect previous turret
if (selectedTurret) {
selectedTurret.rangeIndicator.alpha = 0.25;
// Hide cross indicator on previous turret
selectedTurret.crossIndicator.alpha = 0;
}
selectedTurret = turret;
// Highlight selected turret
selectedTurret.rangeIndicator.alpha = 0.5;
// Show cross indicator
selectedTurret.crossIndicator.alpha = 1.0;
sellBtn.alpha = 1.0; // Enable sell button
upgradeBtn.alpha = 1.0; // Enable upgrade button
// Update button texts with prices
sellBtn.setText('SELL\nTURRET\n$' + selectedTurret.sellPrice);
if (selectedTurret.upgradeLevel >= 5) {
upgradeBtn.setText('UPGRADE\nMax'); //{ap_new}
} else {
//{ap_new2}
upgradeBtn.setText('UPGRADE\nLv.' + selectedTurret.upgradeLevel + ' → ' + (selectedTurret.upgradeLevel + 1) + '\n$' + selectedTurret.upgradeCost);
} //{ap_new3}
LK.effects.flashObject(selectedTurret, 0x00FF00, 300);
}
function deselectTurret() {
if (selectedTurret) {
selectedTurret.rangeIndicator.alpha = 0.25;
// Hide cross indicator
selectedTurret.crossIndicator.alpha = 0;
}
selectedTurret = null;
sellBtn.alpha = 0.5; // Disable sell button
upgradeBtn.alpha = 0.5; // Disable upgrade button
sellBtn.setText('SELL\nTURRET\n$0'); // Reset text
upgradeBtn.setText('UPGRADE\nTURRET\n$0'); // Reset text
}
function sellTurret() {
if (!selectedTurret) return;
// Give money back
currency += selectedTurret.sellPrice;
updateCurrencyDisplay();
// Remove turret from game
for (var i = turrets.length - 1; i >= 0; i--) {
if (turrets[i] === selectedTurret) {
turrets.splice(i, 1);
break;
}
}
selectedTurret.destroy();
// Flash green effect
LK.effects.flashScreen(0x00FF00, 300);
deselectTurret();
}
function upgradeTurret() {
if (!selectedTurret) return;
if (selectedTurret.upgradeLevel >= 5) return; // Max upgrade level
if (currency < selectedTurret.upgradeCost) return; // Not enough money
// Deduct cost
currency -= selectedTurret.upgradeCost;
updateCurrencyDisplay();
// Upgrade turret
selectedTurret.upgradeLevel++;
selectedTurret.updateTowerProperties();
// Update button texts with new prices
sellBtn.setText('SELL\nTURRET\n$' + selectedTurret.sellPrice);
if (selectedTurret.upgradeLevel >= 5) {
upgradeBtn.setText('UPGRADE\nMax'); //{aG_new}
} else {
//{aG_new2}
upgradeBtn.setText('UPGRADE\nLv.' + selectedTurret.upgradeLevel + ' → ' + (selectedTurret.upgradeLevel + 1) + '\n$' + selectedTurret.upgradeCost);
} //{aG_new3}
// Visual effects
LK.effects.flashObject(selectedTurret, 0x00FFFF, 500);
// Change turret color based on upgrade level
if (selectedTurret.upgradeLevel >= 3) {
selectedTurret.children[0].tint = 0xFFFFFF; // White for high level
} else if (selectedTurret.upgradeLevel >= 2) {
selectedTurret.children[0].tint = 0xFFFF00; // Yellow for mid level
}
}
function activateZombieFreezeEffect() {
if (freezeEffectActive) return; // Already active
if (turrets.length === 0) return; // No turrets to freeze
// Randomly select up to 2 turrets to freeze
var availableTurrets = turrets.slice(); // Copy array
var turretsToFreeze = Math.min(2, availableTurrets.length);
frozenTurrets = [];
for (var i = 0; i < turretsToFreeze; i++) {
var randomIndex = Math.floor(Math.random() * availableTurrets.length);
var turretToFreeze = availableTurrets[randomIndex];
availableTurrets.splice(randomIndex, 1); // Remove from available list
frozenTurrets.push(turretToFreeze);
// Add freeze visual effect
turretToFreeze.isFrozen = true;
turretToFreeze.children[0].tint = 0x00CCFF; // Ice blue tint
// Add ice effect around turret
var iceEffect = turretToFreeze.addChild(LK.getAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0x00CCFF,
scaleX: 1.5,
scaleY: 1.5
}));
turretToFreeze.iceEffect = iceEffect;
// Flash effect
LK.effects.flashObject(turretToFreeze, 0x00CCFF, 300);
}
// Activate freeze effect
freezeEffectActive = true;
freezeEffectTimer = 0;
// Screen flash to indicate freeze effect
LK.effects.flashScreen(0x00CCFF, 500);
}
function updateFreezeEffect() {
if (!freezeEffectActive) return;
freezeEffectTimer += speedMultiplier;
if (freezeEffectTimer >= freezeEffectDuration) {
// Remove freeze effect
for (var i = 0; i < frozenTurrets.length; i++) {
var turret = frozenTurrets[i];
if (turret.parent) {
// Check if turret still exists
turret.isFrozen = false;
turret.children[0].tint = 0xFFFFFF; // Reset tint
if (turret.iceEffect) {
turret.iceEffect.destroy();
turret.iceEffect = null;
}
}
}
frozenTurrets = [];
freezeEffectActive = false;
freezeEffectTimer = 0;
}
}
// Game-wide click handler - enable turret placement when in placement mode
game.down = function (x, y, obj) {
// Check if click is in game area (not on UI elements)
if (y > 2300) return; // Don't place on bottom UI
if (placementMode && turretToPlace) {
// Check if position is too close to path
var tooCloseToPath = false;
for (var i = 0; i < gamePath.length - 1; i++) {
var start = gamePath[i];
var end = gamePath[i + 1];
var distance = distanceToLineSegment(x, y, start.x, start.y, end.x, end.y);
if (distance < 80) {
// Minimum distance from path
tooCloseToPath = true;
break;
}
}
if (tooCloseToPath) {
LK.effects.flashScreen(0xFF0000, 200); // Red flash for invalid placement
return;
}
// Check if position overlaps with existing turrets
var tooCloseToTurret = false;
for (var i = 0; i < turrets.length; i++) {
var turret = turrets[i];
var dx = turret.x - x;
var dy = turret.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
// Minimum distance between turrets
tooCloseToTurret = true;
break;
}
}
if (tooCloseToTurret) {
LK.effects.flashScreen(0xFF0000, 200); // Red flash for invalid placement
return;
}
if (turretToPlace.type === 'fireTrap') {
// Place fire trap (can be placed anywhere)
var fireTrap = new FireTrap();
fireTrap.x = x;
fireTrap.y = y;
currency -= turretToPlace.cost;
updateCurrencyDisplay();
fireTraps.push(fireTrap);
game.addChild(fireTrap);
// Flash orange when built
LK.effects.flashObject(fireTrap, 0xFF4400, 500);
} else {
// Place turret
var turret = new Turret();
turret.x = x;
turret.y = y;
turret.towerType = turretToPlace.type;
turret.cost = turretToPlace.cost;
turret.updateTowerProperties();
currency -= turretToPlace.cost;
updateCurrencyDisplay();
turrets.push(turret);
game.addChild(turret);
// Flash green when built
var flashColor = turretToPlace.type === 'laser' ? 0x4444FF : turretToPlace.type === 'machineGun' ? 0xFF6600 : turretToPlace.type === 'missile' ? 0xFF4444 : turretToPlace.type === 'fire' ? 0xFF8800 : turretToPlace.type === 'fireMachine' ? 0xFF2200 : 0xFFFF00;
LK.effects.flashObject(turret, flashColor, 500);
}
// Exit placement mode
placementMode = false;
turretToPlace = null;
} else {
// Deselect turret when clicking empty area
deselectTurret();
}
};
game.update = function () {
// Spawn zombies
if (waveActive && zombiesSpawned < zombiesToSpawn) {
spawnTimer += speedMultiplier;
// Adjust spawn rate based on wave - slower spawning for first level
var currentSpawnRate = wave === 1 ? baseSpawnRate * 1.5 : baseSpawnRate;
if (spawnTimer >= currentSpawnRate) {
spawnZombie();
spawnTimer = 0;
}
}
// Clean up dead zombies
for (var i = zombies.length - 1; i >= 0; i--) {
if (zombies[i].isDead) {
zombies[i].destroy();
zombies.splice(i, 1);
}
}
// Update freeze effect
updateFreezeEffect();
checkWaveComplete();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BuildSpot = Container.expand(function () {
var self = Container.call(this);
var spotGraphics = self.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.isOccupied = false;
self.turret = null;
self.previewRange = null;
self.move = function (x, y, obj) {
if (self.isOccupied) return;
if (!self.previewRange) {
// Create preview range indicator
self.previewRange = self.addChild(LK.getAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.15,
tint: selectedTowerType === 'laser' ? 0x4444FF : selectedTowerType === 'machineGun' ? 0xFF6600 : selectedTowerType === 'missile' ? 0xFF4444 : selectedTowerType === 'fire' ? 0xFF8800 : selectedTowerType === 'fireMachine' ? 0xFF2200 : selectedTowerType === 'fireTrap' ? 0xFF4400 : 0xFFFF00,
scaleX: selectedTowerType === 'laser' ? 3.0 : selectedTowerType === 'machineGun' ? 4.0 : selectedTowerType === 'missile' ? 3.4 : selectedTowerType === 'fire' ? 3.6 : selectedTowerType === 'fireMachine' ? 5.0 : selectedTowerType === 'fireTrap' ? 4.0 : 4.4,
scaleY: selectedTowerType === 'laser' ? 3.0 : selectedTowerType === 'machineGun' ? 4.0 : selectedTowerType === 'missile' ? 3.4 : selectedTowerType === 'fire' ? 3.6 : selectedTowerType === 'fireMachine' ? 5.0 : selectedTowerType === 'fireTrap' ? 4.0 : 4.4
}));
}
};
self.up = function (x, y, obj) {
if (self.previewRange) {
self.previewRange.destroy();
self.previewRange = null;
}
};
self.down = function (x, y, obj) {
if (self.isOccupied) return;
if (currency < selectedTowerCost) return;
// Build turret
var turret = new Turret();
turret.x = self.x;
turret.y = self.y;
turret.towerType = selectedTowerType;
turret.cost = selectedTowerCost;
turret.updateTowerProperties(); // Update properties based on tower type
self.turret = turret;
self.isOccupied = true;
currency -= selectedTowerCost;
updateCurrencyDisplay();
turrets.push(turret);
game.addChild(turret);
// Hide build spot
spotGraphics.alpha = 0;
// Flash green when built with tower type color
var flashColor = selectedTowerType === 'laser' ? 0x4444FF : selectedTowerType === 'machineGun' ? 0xFF6600 : selectedTowerType === 'missile' ? 0xFF4444 : selectedTowerType === 'fire' ? 0xFF8800 : selectedTowerType === 'fireMachine' ? 0xFF2200 : selectedTowerType === 'fireTrap' ? 0xFF4400 : 0xFFFF00;
LK.effects.flashObject(turret, flashColor, 500);
};
return self;
});
var FireTrap = Container.expand(function () {
var self = Container.call(this);
var fireGraphics = self.attachAsset('fireTrap', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.damage = 80; // Increased immediate damage
self.burnDuration = 3000; // 3 seconds
self.lastDamage = 0;
self.damageRate = 20; // Faster damage rate - hits more frequently
// Add range indicator
self.rangeIndicator = self.addChild(LK.getAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.15,
tint: 0xFF4400,
scaleX: 8.0,
// 400 pixel range
scaleY: 8.0
}));
// Create fire effect animation
tween(fireGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(fireGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart animation loop
if (self.parent) {
tween(fireGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
}
});
}
});
self.update = function () {
// Check for zombies within firing range
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.isDead) continue;
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 400) {
// Within firing range - deal immediate damage
self.lastDamage += speedMultiplier;
if (self.lastDamage >= self.damageRate) {
// Deal immediate damage every damage rate interval
zombie.takeDamage(self.damage);
self.lastDamage = 0;
// Create fire explosion effect at zombie location
var fireExplosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: zombie.x,
y: zombie.y,
tint: 0xFF4400,
scaleX: 1.5,
scaleY: 1.5
}));
tween(fireExplosion, {
alpha: 0,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 400,
onFinish: function onFinish() {
fireExplosion.destroy();
}
});
LK.getSound('explosion').play();
}
// Apply powerful burning effect for 5 seconds
if (!zombie.isBurning) {
zombie.isBurning = true;
zombie.burnTimer = 0;
zombie.burnDuration = 300; // 5 seconds at 60fps
zombie.burnDamage = 40; // Increased burn damage
// More intense visual burning effect
tween(zombie.children[0], {
tint: 0xFF2200
}, {
duration: 150,
onFinish: function onFinish() {
tween(zombie.children[0], {
tint: 0xFFFFFF
}, {
duration: 150,
onFinish: function onFinish() {
if (zombie.isBurning && zombie.parent) {
tween(zombie.children[0], {
tint: 0xFF2200
}, {
duration: 150,
onFinish: arguments.callee
});
}
}
});
}
});
// Add fire aura effect around burning zombie
var fireAura = zombie.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0xFF4400,
scaleX: 1.2,
scaleY: 1.2
}));
tween(fireAura, {
scaleX: 1.8,
scaleY: 1.8,
alpha: 0.2
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (fireAura.parent) {
tween(fireAura, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.6
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
}
});
}
}
}
};
return self;
});
var Turret = Container.expand(function () {
var self = Container.call(this);
var turretGraphics = self.attachAsset('turret', {
anchorX: 0.5,
anchorY: 0.5
});
// Add range indicator (will be updated based on tower type)
self.rangeIndicator = self.addChild(LK.getAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.25,
tint: 0x4444FF,
scaleX: 4,
scaleY: 4
}));
// Add cross indicator (hidden by default)
self.crossIndicator = self.addChild(LK.getAsset('cross', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
rotation: Math.PI / 4 // 45 degrees to make it look like an X
}));
self.damage = 75;
self.range = 200;
self.fireRate = 15; // frames between shots
self.lastShot = 0;
self.target = null;
self.cost = 50;
self.sellPrice = 25; // 50% of cost back when sold
self.towerType = 'laser'; // Default type
self.upgradeLevel = 1; // Track upgrade level
self.upgradeCost = 50; // Cost to upgrade
self.updateTowerProperties = function () {
var upgradeMultiplier = 1 + (self.upgradeLevel - 1) * 0.5; // 50% increase per level
if (self.towerType === 'laser') {
self.range = 150 * upgradeMultiplier;
self.damage = 75 * upgradeMultiplier;
self.fireRate = Math.max(5, 15 - (self.upgradeLevel - 1) * 2); // Faster fire rate
self.rangeIndicator.tint = 0x4444FF;
self.rangeIndicator.scaleX = 3.0; // Keep visual size constant
self.rangeIndicator.scaleY = 3.0; // Keep visual size constant
self.sellPrice = Math.floor((50 + (self.upgradeLevel - 1) * 50) * 0.5); // 50% of total cost back
self.upgradeCost = 50 * self.upgradeLevel; // Increasing upgrade cost
} else if (self.towerType === 'missile') {
self.range = 170 * upgradeMultiplier;
self.damage = 120 * upgradeMultiplier;
self.fireRate = Math.max(8, 20 - (self.upgradeLevel - 1) * 3);
self.rangeIndicator.tint = 0xFF4444;
self.rangeIndicator.scaleX = 3.4; // Keep visual size constant
self.rangeIndicator.scaleY = 3.4; // Keep visual size constant
self.sellPrice = Math.floor((100 + (self.upgradeLevel - 1) * 100) * 0.5); // 50% of total cost back
self.upgradeCost = 100 * self.upgradeLevel;
} else if (self.towerType === 'fire') {
self.range = 180 * upgradeMultiplier;
self.damage = 90 * upgradeMultiplier;
self.fireRate = Math.max(4, 10 - (self.upgradeLevel - 1) * 2);
self.rangeIndicator.tint = 0xFF8800;
self.rangeIndicator.scaleX = 3.6; // Keep visual size constant
self.rangeIndicator.scaleY = 3.6; // Keep visual size constant
self.sellPrice = Math.floor((200 + (self.upgradeLevel - 1) * 200) * 0.5); // 50% of total cost back
self.upgradeCost = 200 * self.upgradeLevel;
} else if (self.towerType === 'electric') {
self.range = 220 * upgradeMultiplier;
self.damage = 60 * upgradeMultiplier;
self.fireRate = Math.max(6, 12 - (self.upgradeLevel - 1) * 2);
self.rangeIndicator.tint = 0xFFFF00;
self.rangeIndicator.scaleX = 4.4; // Keep visual size constant
self.rangeIndicator.scaleY = 4.4; // Keep visual size constant
self.sellPrice = Math.floor((300 + (self.upgradeLevel - 1) * 300) * 0.5); // 50% of total cost back
self.upgradeCost = 300 * self.upgradeLevel;
} else if (self.towerType === 'fireMachine') {
self.range = 250 * upgradeMultiplier;
self.damage = 200 * upgradeMultiplier;
self.fireRate = Math.max(3, 8 - (self.upgradeLevel - 1) * 2);
self.rangeIndicator.tint = 0xFF2200;
self.rangeIndicator.scaleX = 5.0; // Keep visual size constant
self.rangeIndicator.scaleY = 5.0; // Keep visual size constant
self.sellPrice = Math.floor((500 + (self.upgradeLevel - 1) * 500) * 0.5); // 50% of total cost back
self.upgradeCost = 500 * self.upgradeLevel;
} else if (self.towerType === 'machineGun') {
self.range = 200 * upgradeMultiplier;
self.damage = 300 * upgradeMultiplier;
self.fireRate = Math.max(2, 5 - (self.upgradeLevel - 1) * 1);
self.rangeIndicator.tint = 0xFF6600;
self.rangeIndicator.scaleX = 4.0; // Keep visual size constant
self.rangeIndicator.scaleY = 4.0; // Keep visual size constant
self.sellPrice = Math.floor((1000 + (self.upgradeLevel - 1) * 1000) * 0.5); // 50% of total cost back
self.upgradeCost = 1000 * self.upgradeLevel;
} else if (self.towerType === 'fireTrap') {
self.range = 400 * upgradeMultiplier;
self.damage = 50 * upgradeMultiplier;
self.fireRate = Math.max(10, 30 - (self.upgradeLevel - 1) * 5);
self.rangeIndicator.tint = 0xFF4400;
self.rangeIndicator.scaleX = 4.0; // Keep visual size constant
self.rangeIndicator.scaleY = 4.0; // Keep visual size constant
self.sellPrice = Math.floor((700 + (self.upgradeLevel - 1) * 700) * 0.5); // 50% of total cost back
self.upgradeCost = 700 * self.upgradeLevel;
}
};
self.towerType = 'laser'; // Default type
self.findTarget = function () {
var closestZombie = null;
var closestDistance = self.range;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.isDead) continue;
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestZombie = zombie;
}
}
return closestZombie;
};
self.shoot = function (target) {
// Direct damage to target without bullets
if (target && !target.isDead) {
target.takeDamage(self.damage);
// Create explosion effect at target location
var explosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: target.x,
y: target.y
}));
tween(explosion, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
explosion.destroy();
}
});
LK.getSound('explosion').play();
}
LK.getSound('shoot').play();
// Flash blue when shooting
LK.effects.flashObject(self, 0x4444ff, 200);
};
self.down = function (x, y, obj) {
if (placementMode) return; // Don't select turrets in placement mode
selectTurret(self);
}; //{1j_new}
self.update = function () {
// Skip shooting if turret is frozen
if (self.isFrozen) return;
self.lastShot += speedMultiplier;
if (self.lastShot >= self.fireRate) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.lastShot = 0;
}
}
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.speed = 1;
self.reward = 10;
self.pathIndex = 0;
self.targetX = 0;
self.targetY = 0;
self.isDead = false;
// Create health bar
self.healthBar = self.addChild(LK.getAsset('buildSpot', {
anchorX: 0,
anchorY: 0.5,
x: -25,
y: -40,
width: 50,
height: 6,
color: 0x44FF44
}));
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
// Directly add currency without visual money drop
currency += self.reward;
updateCurrencyDisplay();
LK.getSound('zombieHit').play();
}
};
self.update = function () {
if (self.isDead) return;
// Move towards target continuously
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Always move towards target, no stopping threshold
if (distance > 0.1) {
var moveX = dx / distance * self.speed * speedMultiplier;
var moveY = dy / distance * self.speed * speedMultiplier;
self.x += moveX;
self.y += moveY;
}
// Check if we've reached or passed the current target
if (distance <= self.speed * speedMultiplier + 1) {
// Move to next path point immediately
self.pathIndex++;
if (self.pathIndex >= gamePath.length) {
// Reached end - damage base
lives--;
updateLivesDisplay();
if (lives <= 0) {
LK.showGameOver();
}
self.isDead = true;
} else {
self.targetX = gamePath[self.pathIndex].x;
self.targetY = gamePath[self.pathIndex].y;
}
}
// Handle burning effect
if (self.isBurning) {
self.burnTimer += speedMultiplier;
if (self.burnTimer >= 60) {
// Burn damage every second
self.takeDamage(self.burnDamage);
self.burnTimer = 0;
}
// Stop burning after 5 seconds
if (self.burnTimer >= self.burnDuration) {
self.isBurning = false;
tween.stop(self.children[0], {
tint: true
});
self.children[0].tint = 0xFFFFFF;
}
}
// Handle freeze effect for levels 11 and 15
if (self.hasFreezeEffect && !self.freezeEffectUsed) {
self.freezeEffectCooldown += speedMultiplier;
// Use freeze effect when zombie is at half health or after 5 seconds
var healthPercent = self.health / self.maxHealth;
if (healthPercent <= 0.5 || self.freezeEffectCooldown >= 300) {
self.freezeEffectUsed = true;
activateZombieFreezeEffect();
// Add visual indication that zombie used freeze effect
var freezeIndicator = self.addChild(LK.getAsset('cross', {
anchorX: 0.5,
anchorY: 0.5,
y: -50,
tint: 0x00CCFF,
scaleX: 1.5,
scaleY: 1.5
}));
tween(freezeIndicator, {
alpha: 0,
y: -80,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 1000,
onFinish: function onFinish() {
if (freezeIndicator.parent) {
freezeIndicator.destroy();
}
}
});
}
}
// Update health bar if it exists
if (self.healthBar) {
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent;
self.healthBar.tint = healthPercent > 0.5 ? 0x44FF44 : healthPercent > 0.25 ? 0xFFFF44 : 0xFF4444;
}
};
return self;
});
var FlyingZombie = Zombie.expand(function () {
var self = Zombie.call(this);
// Mark as flying type
self.isFlying = true;
// Add flying visual indicator - wings or glow effect
var flyingIndicator = self.addChild(LK.getAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.4,
tint: 0x00FFFF,
scaleX: 1.5,
scaleY: 0.8,
y: -10
}));
// Start floating animation
function startFloatingAnimation() {
tween(self, {
y: self.y - 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
y: self.y + 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent && !self.isDead) {
startFloatingAnimation();
}
}
});
}
});
}
// Store the original update method from parent Zombie class
var originalUpdate = self.update;
self.update = function () {
if (self.isDead) return;
// Initialize flying zombie movement properties if not set
if (!self.flyingInitialized) {
self.flyingInitialized = true;
// Set initial target as the final destination
self.finalTargetX = gamePath[gamePath.length - 1].x;
self.finalTargetY = gamePath[gamePath.length - 1].y;
// Flying zombies move more directly but with some wandering
self.wanderAngle = Math.random() * Math.PI * 2;
self.wanderTimer = 0;
self.wanderChangeRate = 180; // Change direction every 3 seconds
}
// Flying zombie free movement logic
var dx = self.finalTargetX - self.x;
var dy = self.finalTargetY - self.y;
var distanceToTarget = Math.sqrt(dx * dx + dy * dy);
// Add wandering behavior to make movement more interesting
self.wanderTimer += speedMultiplier;
if (self.wanderTimer >= self.wanderChangeRate) {
self.wanderAngle += (Math.random() - 0.5) * 0.8; // Random direction change
self.wanderTimer = 0;
}
// Calculate movement with wandering
var wanderStrength = 0.3; // How much the zombie wanders vs goes direct
var directX = dx / distanceToTarget;
var directY = dy / distanceToTarget;
var wanderX = Math.cos(self.wanderAngle) * wanderStrength;
var wanderY = Math.sin(self.wanderAngle) * wanderStrength;
// Combine direct movement with wandering
var moveX = (directX * (1 - wanderStrength) + wanderX) * self.speed * speedMultiplier;
var moveY = (directY * (1 - wanderStrength) + wanderY) * self.speed * speedMultiplier;
// Apply movement
self.x += moveX;
self.y += moveY;
// Keep flying zombies within game bounds
if (self.x < 50) self.x = 50;
if (self.x > 1998) self.x = 1998;
if (self.y < 50) self.y = 50;
if (self.y > 2050) self.y = 2050;
// Check if reached the end
if (distanceToTarget <= 100) {
// Reached end - damage base
lives--;
updateLivesDisplay();
if (lives <= 0) {
LK.showGameOver();
}
self.isDead = true;
}
// Handle burning effect (same as regular zombies)
if (self.isBurning) {
self.burnTimer += speedMultiplier;
if (self.burnTimer >= 60) {
// Burn damage every second
self.takeDamage(self.burnDamage);
self.burnTimer = 0;
}
// Stop burning after 5 seconds
if (self.burnTimer >= self.burnDuration) {
self.isBurning = false;
tween.stop(self.children[0], {
tint: true
});
self.children[0].tint = 0xFFFFFF;
}
}
// Handle freeze effect for levels 11 and 15 (same as regular zombies)
if (self.hasFreezeEffect && !self.freezeEffectUsed) {
self.freezeEffectCooldown += speedMultiplier;
var healthPercent = self.health / self.maxHealth;
if (healthPercent <= 0.5 || self.freezeEffectCooldown >= 300) {
self.freezeEffectUsed = true;
activateZombieFreezeEffect();
var freezeIndicator = self.addChild(LK.getAsset('cross', {
anchorX: 0.5,
anchorY: 0.5,
y: -50,
tint: 0x00CCFF,
scaleX: 1.5,
scaleY: 1.5
}));
tween(freezeIndicator, {
alpha: 0,
y: -80,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 1000,
onFinish: function onFinish() {
if (freezeIndicator.parent) {
freezeIndicator.destroy();
}
}
});
}
}
// Update health bar if it exists
if (self.healthBar) {
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent;
self.healthBar.tint = healthPercent > 0.5 ? 0x44FF44 : healthPercent > 0.25 ? 0xFFFF44 : 0xFF4444;
}
// Start floating animation if not already started
if (!self.floatingStarted && self.parent) {
self.floatingStarted = true;
startFloatingAnimation();
}
// Add gentle rotation for flying effect
if (self.children[0]) {
self.children[0].rotation += 0.02 * speedMultiplier;
}
// Pulsing glow effect for flying indicator
if (flyingIndicator && flyingIndicator.parent) {
flyingIndicator.alpha = 0.4 + Math.sin(LK.ticks * 0.1) * 0.2;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
var zombies = [];
var turrets = [];
var bullets = [];
var buildSpots = [];
var fireTraps = [];
var gamePath = [];
var currency = 100;
var lives = 10;
var wave = 1;
var zombiesSpawned = 0;
var zombiesToSpawn = 5;
var spawnTimer = 0;
var spawnRate = 120; // frames between spawns
var baseSpawnRate = 120; // Store base spawn rate
var waveActive = false;
var waveCompleted = false;
var selectedTowerType = 'laser';
var selectedTowerCost = 50;
// Create path points for zombies to follow - Shorter strategic path
gamePath = [{
x: 0,
y: 1366
}, {
x: 400,
y: 1366
}, {
x: 400,
y: 1000
}, {
x: 800,
y: 1000
}, {
x: 800,
y: 1500
}, {
x: 1200,
y: 1500
}, {
x: 1200,
y: 800
}, {
x: 1600,
y: 800
}, {
x: 1600,
y: 400
}, {
x: 1000,
y: 400
}, {
x: 1000,
y: 1800
}, {
x: 1400,
y: 1800
}, {
x: 2048,
y: 1800
}];
// Create visual path
for (var i = 0; i < gamePath.length - 1; i++) {
var start = gamePath[i];
var end = gamePath[i + 1];
var steps = Math.floor(Math.sqrt((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / 120);
for (var j = 0; j <= steps; j++) {
var t = j / steps;
var pathX = start.x + (end.x - start.x) * t;
var pathY = start.y + (end.y - start.y) * t;
var pathTile = game.addChild(LK.getAsset('path', {
anchorX: 0.5,
anchorY: 0.5,
x: pathX,
y: pathY,
alpha: 0.3
}));
}
}
// Build spots are no longer needed - turrets can be placed anywhere
// buildSpots array is kept for compatibility but not used
// UI Elements
var currencyTxt = new Text2('Money: $' + currency, {
size: 72,
fill: 0x00FFFF
});
currencyTxt.anchor.set(0, 0);
currencyTxt.x = 120; // Offset from left edge to avoid platform menu
currencyTxt.y = 100;
LK.gui.topLeft.addChild(currencyTxt);
// Add glowing effect to currency
LK.effects.flashObject(currencyTxt, 0x00FFFF, 2000);
var livesTxt = new Text2('Lives: ' + lives, {
size: 60,
fill: 0xFF4444
});
livesTxt.anchor.set(0, 0);
livesTxt.x = 120; // Offset from left edge to avoid platform menu
livesTxt.y = 20;
LK.gui.topLeft.addChild(livesTxt);
// Tower purchase menu at bottom left - vertical layout with consistent alignment
var towerMenuX = 120; // Left side position for tower menu
var towerMenuStartY = 1550; // Starting Y position - moved up to be above sell turret text
var towerMenuSpacing = 140; // Further increased spacing between buttons to prevent interference
var laserTurretBtn = new Text2('Laser Turret\n$50', {
size: 48,
fill: 0x4444FF
});
laserTurretBtn.anchor.set(0, 0.5); // Left-aligned anchor
laserTurretBtn.x = towerMenuX;
laserTurretBtn.y = towerMenuStartY;
game.addChild(laserTurretBtn);
var missileLauncherBtn = new Text2('Missile Tower\n$100', {
size: 48,
fill: 0xFF4444
});
missileLauncherBtn.anchor.set(0, 0.5); // Left-aligned anchor
missileLauncherBtn.x = towerMenuX;
missileLauncherBtn.y = towerMenuStartY + towerMenuSpacing;
game.addChild(missileLauncherBtn);
var fireDroneBtn = new Text2('Fire Blaster\n$200', {
size: 48,
fill: 0xFF8800
});
fireDroneBtn.anchor.set(0, 0.5); // Left-aligned anchor
fireDroneBtn.x = towerMenuX;
fireDroneBtn.y = towerMenuStartY + towerMenuSpacing * 2;
game.addChild(fireDroneBtn);
var electricTrapBtn = new Text2('Electric Field\n$300', {
size: 48,
fill: 0xFFFF00
});
electricTrapBtn.anchor.set(0, 0.5); // Left-aligned anchor
electricTrapBtn.x = towerMenuX;
electricTrapBtn.y = towerMenuStartY + towerMenuSpacing * 3;
game.addChild(electricTrapBtn);
var fireMachineBtn = new Text2('Fire Machine\n$500', {
size: 48,
fill: 0xFF2200
});
fireMachineBtn.anchor.set(0, 0.5); // Left-aligned anchor
fireMachineBtn.x = towerMenuX + 400; // Move to the right of laser turret
fireMachineBtn.y = towerMenuStartY;
game.addChild(fireMachineBtn);
var machineGunBtn = new Text2('Machine Gun\n$1000', {
size: 48,
fill: 0xFF6600
});
machineGunBtn.anchor.set(0, 0.5); // Left-aligned anchor
machineGunBtn.x = towerMenuX + 400; // Move to the right of laser turret
machineGunBtn.y = towerMenuStartY + towerMenuSpacing;
game.addChild(machineGunBtn);
var fireTrapBtn = new Text2('Fire Trap\n$700', {
size: 48,
fill: 0xFF4400
});
fireTrapBtn.anchor.set(0, 0.5); // Left-aligned anchor
fireTrapBtn.x = towerMenuX + 400; // Move to the right of laser turret
fireTrapBtn.y = towerMenuStartY + towerMenuSpacing * 2;
game.addChild(fireTrapBtn);
var waveTxt = new Text2('Level ' + wave, {
size: 80,
fill: 0x44FF44
});
waveTxt.anchor.set(0.5, 0);
waveTxt.y = 20;
LK.gui.top.addChild(waveTxt);
// Bomb system
var bombsLeft = 3;
var bombTxt = new Text2('Bombs: ' + bombsLeft, {
size: 72,
fill: 0xFF0000
});
bombTxt.anchor.set(1, 0);
bombTxt.x = -20;
bombTxt.y = -80;
LK.gui.topRight.addChild(bombTxt);
var bombBtn = new Text2('💣 BOMB', {
size: 80,
fill: 0xFF4444
});
bombBtn.anchor.set(1, 1);
bombBtn.x = -20;
bombBtn.y = -20;
LK.gui.bottomRight.addChild(bombBtn);
var startWaveBtn = new Text2('START WAVE', {
size: 80,
fill: 0xFFFFFF
});
startWaveBtn.anchor.set(0.5, 0.5);
startWaveBtn.x = 500;
startWaveBtn.y = 2550;
game.addChild(startWaveBtn);
var sellBtn = new Text2('SELL\nTURRET\n$0', {
size: 64,
fill: 0xFFAA00
});
sellBtn.anchor.set(0.5, 0.5);
sellBtn.x = 300; // Position to the left
sellBtn.y = 2400; // Move down
sellBtn.alpha = 0.5; // Start dimmed
game.addChild(sellBtn);
var upgradeBtn = new Text2('UPGRADE\nTURRET\n$0', {
size: 64,
fill: 0x00FF00
});
upgradeBtn.anchor.set(0.5, 0.5);
upgradeBtn.x = 700; // Position to the right
upgradeBtn.y = 2400; // Same height as sell button - moved down
upgradeBtn.alpha = 0.5; // Start dimmed
game.addChild(upgradeBtn);
// Speed acceleration system
var gameSpeed = 1.0;
var speedMultiplier = 1.0;
// Speed acceleration buttons
var speed1xBtn = new Text2('1x Speed', {
size: 48,
fill: 0x00FF00
});
speed1xBtn.anchor.set(0.5, 0.5);
speed1xBtn.x = 1400;
speed1xBtn.y = 2180;
game.addChild(speed1xBtn);
var speed2xBtn = new Text2('2x Speed', {
size: 48,
fill: 0xFFFF00
});
speed2xBtn.anchor.set(0.5, 0.5);
speed2xBtn.x = 1400;
speed2xBtn.y = 2280;
game.addChild(speed2xBtn);
var speed3xBtn = new Text2('3x Speed', {
size: 48,
fill: 0xFF8800
});
speed3xBtn.anchor.set(0.5, 0.5);
speed3xBtn.x = 1400;
speed3xBtn.y = 2380;
game.addChild(speed3xBtn);
function distanceToLineSegment(px, py, x1, y1, x2, y2) {
var dx = x2 - x1;
var dy = y2 - y1;
var length = Math.sqrt(dx * dx + dy * dy);
if (length === 0) return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
var t = ((px - x1) * dx + (py - y1) * dy) / (length * length);
t = Math.max(0, Math.min(1, t));
var projX = x1 + t * dx;
var projY = y1 + t * dy;
return Math.sqrt((px - projX) * (px - projX) + (py - projY) * (py - projY));
}
function updateCurrencyDisplay() {
currencyTxt.setText('Money: $' + currency);
// Add glowing effect when currency changes
LK.effects.flashObject(currencyTxt, 0x00FFFF, 500);
}
function updateBombDisplay() {
bombTxt.setText('Bombs: ' + bombsLeft);
}
function useBomb() {
if (bombsLeft <= 0) return;
bombsLeft--;
updateBombDisplay();
// Create massive explosion effect
var bombExplosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 10,
scaleY: 10,
alpha: 0.8
}));
// Damage all zombies significantly
for (var i = 0; i < zombies.length; i++) {
if (!zombies[i].isDead) {
zombies[i].takeDamage(150);
LK.effects.flashObject(zombies[i], 0xFF0000, 500);
}
}
// Animate explosion
tween(bombExplosion, {
alpha: 0,
scaleX: 15,
scaleY: 15
}, {
duration: 1000,
onFinish: function onFinish() {
bombExplosion.destroy();
}
});
LK.getSound('explosion').play();
LK.effects.flashScreen(0xFF4444, 800);
}
function updateLivesDisplay() {
livesTxt.setText('Lives: ' + lives);
}
function updateWaveDisplay() {
waveTxt.setText('Level ' + wave);
}
function startWave() {
if (waveActive) return;
waveActive = true;
waveCompleted = false;
zombiesSpawned = 0;
// Make first level easier with fewer zombies
if (wave === 1) {
zombiesToSpawn = 3; // Only 3 zombies for first wave
} else if (wave >= 9 && wave <= 10) {
zombiesToSpawn = 10; // Only 10 enemies for levels 9 and 10
} else if (wave >= 11) {
zombiesToSpawn = 12 + (wave - 11) * 2; // More enemies for levels 11-15
} else {
zombiesToSpawn = 5 + (wave - 1) * 2; // Adjusted formula for subsequent waves
}
spawnTimer = 0;
startWaveBtn.alpha = 0.3;
// Increase zombie health each wave, but start lower for first level
if (wave === 1) {
Zombie.prototype.baseHealth = 60; // Weaker zombies for first level
} else if (wave <= 5) {
Zombie.prototype.baseHealth = 100 + (wave - 1) * 35; // Adjusted for waves 2-5
} else if (wave <= 10) {
// After level 5, base health becomes extremely high for boss-level enemies
var levelBonus = wave - 5;
Zombie.prototype.baseHealth = 2000 + levelBonus * 800; // Extremely high base health after level 5 - boss-level
} else {
// Levels 11-15: Ultimate boss enemies with exponential health scaling
var levelBonus = wave - 10;
Zombie.prototype.baseHealth = 8000 + levelBonus * 2000; // Exponential health scaling for levels 11-15
}
}
function spawnZombie() {
if (zombiesSpawned >= zombiesToSpawn) return;
// Determine if this should be a flying zombie (30% chance for waves 10+)
var shouldBeFlyingZombie = wave >= 10 && Math.random() < 0.3;
var zombie = shouldBeFlyingZombie ? new FlyingZombie() : new Zombie();
zombie.x = gamePath[0].x;
zombie.y = gamePath[0].y;
zombie.targetX = gamePath[1].x;
zombie.targetY = gamePath[1].y;
// Make first level zombies weaker and slower
if (wave === 1) {
zombie.health = 60; // Much weaker for first level
zombie.speed = 1.2; // Faster for first level
zombie.reward = 15; // Higher reward for first level to help progression
} else if (wave <= 5) {
zombie.health = 100 + (wave - 1) * 35; // Adjusted for waves 2-5
zombie.speed = 1.5 + (wave - 1) * 0.4; // Faster for waves 2-5
zombie.reward = 10 + wave * 5;
} else {
// After level 5, enemies become extremely strong bosses
var levelBonus = wave - 5;
// Levels 9 and 10 have nearly indestructible enemies
if (wave >= 9 && wave <= 10) {
zombie.health = 15000 + levelBonus * 5000; // Massive health for levels 9-10
} else if (wave >= 11) {
// Levels 11-15: Ultimate boss enemies with exponential health
var ultimateBonus = wave - 10;
zombie.health = 25000 + ultimateBonus * 10000; // Exponential health for levels 11-15
// Add freeze effect for levels 11 and 15
if (wave === 11 || wave === 15) {
zombie.hasFreezeEffect = true;
zombie.freezeEffectUsed = false;
zombie.freezeEffectCooldown = 0;
}
} else {
zombie.health = 2000 + levelBonus * 800; // Extremely high health after level 5 - boss-level
}
zombie.speed = 2.5 + levelBonus * 0.3; // Reduced speed after level 5 - not too fast
zombie.reward = 100 + levelBonus * 50; // Much higher rewards for boss-level enemies
// After level 6, some enemies get speed boost
if (wave > 6) {
var shouldGetSpeedBoost = Math.random() < 0.4; // 40% chance for speed boost
if (shouldGetSpeedBoost) {
zombie.speed *= 1.8; // 80% speed increase for boosted enemies
zombie.hasSpeedBoost = true;
// Flying zombies get even more speed boost
if (zombie.isFlying) {
zombie.speed *= 1.3; // Additional 30% speed for flying zombies
zombie.children[0].tint = 0x00FFAA; // Turquoise tint for fast flying enemies
} else {
zombie.children[0].tint = 0xFF8800; // Orange tint for speed boost
}
// Add speed boost aura effect
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.4,
tint: 0xFF8800,
scaleX: 1.8,
scaleY: 1.8
});
}
}
}
zombie.maxHealth = zombie.health;
// Add visual effects for higher levels but keep first level size
if (wave >= 5) {
// Add continuous glowing effect for boss-level enemies
LK.effects.flashObject(zombie, 0xFF0000, 2000);
// Keep zombie size same as first level - no scaling
zombie.children[0].scaleX = 1.0;
zombie.children[0].scaleY = 1.0;
// Different tint for flying zombies
if (zombie.isFlying) {
zombie.children[0].tint = 0x00FFFF; // Cyan tint for flying boss enemies
} else {
zombie.children[0].tint = 0xFF0000; // Deep red tint for boss enemies
}
// Add boss aura effect
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0xFF0000,
scaleX: 2.5,
scaleY: 2.5
});
// Add second layer boss effect for very high levels
if (wave >= 8) {
// Level 8+ enemies are EXTREMELY strong with maximum boss effects
if (wave >= 11) {
zombie.health *= 5.0; // 5x health for levels 11-15 - ultimate bosses
zombie.reward *= 10; // 10x rewards for these ultimate bosses
} else if (wave >= 9) {
zombie.health *= 3.0; // Triple health for levels 9-10 - nearly indestructible
zombie.reward *= 5; // 5x rewards for these ultimate bosses
} else {
zombie.health *= 1.5; // 50% more health on top of base boss health
zombie.reward *= 2; // Double rewards for these ultra-bosses
}
zombie.maxHealth = zombie.health;
// Maximum boss visual effects
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0xFFFF00,
scaleX: 4.0,
scaleY: 4.0
});
// Add third layer ultra-boss effect for level 8+
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.4,
tint: 0xFF00FF,
scaleX: 3.5,
scaleY: 3.5
});
// Levels 9-15 get additional intimidating effects
if (wave >= 11) {
// Levels 11-15: Ultimate boss effects with rainbow aura
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
tint: 0xFF00FF,
scaleX: 6.0,
scaleY: 6.0
});
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
tint: 0x00FFFF,
scaleX: 5.5,
scaleY: 5.5
});
zombie.children[0].scaleX = 1.0; // Keep same size as first level
zombie.children[0].scaleY = 1.0; // Keep same size as first level
// Different tint for flying ultimate bosses
if (zombie.isFlying) {
zombie.children[0].tint = 0x00FFFF; // Cyan tint for flying ultimate bosses
// Add extra flying effects for ultimate flying bosses
LK.effects.flashObject(zombie, 0x00FFFF, 600);
} else {
zombie.children[0].tint = 0xFF00FF; // Magenta tint for ultimate bosses
}
// Multiple rainbow effects for ultimate bosses
LK.effects.flashObject(zombie, 0xFF00FF, 800);
LK.effects.flashObject(zombie, 0x00FFFF, 1000);
LK.effects.flashObject(zombie, 0xFFFF00, 1200);
} else if (wave >= 9) {
// Add fourth layer ultimate boss effect for levels 9-10
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
tint: 0x000000,
scaleX: 5.0,
scaleY: 5.0
});
// Add fifth layer ultimate boss effect for levels 9-10
zombie.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7,
tint: 0xFFFFFF,
scaleX: 4.5,
scaleY: 4.5
});
// Keep them same size as first level
zombie.children[0].scaleX = 1.0;
zombie.children[0].scaleY = 1.0;
// Different tint for flying ultimate bosses
if (zombie.isFlying) {
zombie.children[0].tint = 0x00CCFF; // Light blue tint for flying ultimate bosses
} else {
zombie.children[0].tint = 0x000000; // Black tint for ultimate bosses
}
// Multiple continuous pulsing effects for ultimate bosses
LK.effects.flashObject(zombie, 0x000000, 1000);
LK.effects.flashObject(zombie, 0xFFFFFF, 1200);
} else {
// Keep them same size as first level
zombie.children[0].scaleX = 1.0;
zombie.children[0].scaleY = 1.0;
zombie.children[0].tint = 0x8B0000; // Dark red tint for ultra bosses
// Continuous pulsing effect for ultra bosses
LK.effects.flashObject(zombie, 0xFF00FF, 1500);
}
}
}
zombies.push(zombie);
game.addChild(zombie);
zombiesSpawned++;
}
function checkWaveComplete() {
if (!waveActive) return;
var aliveZombies = 0;
for (var i = 0; i < zombies.length; i++) {
if (!zombies[i].isDead) {
aliveZombies++;
}
}
if (zombiesSpawned >= zombiesToSpawn && aliveZombies === 0) {
waveActive = false;
waveCompleted = true;
wave++;
updateWaveDisplay();
currency += 50; // Wave completion bonus
updateCurrencyDisplay();
startWaveBtn.alpha = 1;
// Flash screen green for wave completion
LK.effects.flashScreen(0x44ff44, 1000);
// Check win condition
if (wave > 15) {
LK.showYouWin();
}
}
}
// Start wave button interaction
startWaveBtn.down = function (x, y, obj) {
startWave();
};
// Bomb button interaction
bombBtn.down = function (x, y, obj) {
useBomb();
};
// Sell button interaction
sellBtn.down = function (x, y, obj) {
sellTurret();
};
// Upgrade button interaction
upgradeBtn.down = function (x, y, obj) {
upgradeTurret();
};
// Tower selection highlighting
var selectedTowerBtn = laserTurretBtn;
function highlightSelectedTower() {
// Reset all tower buttons
laserTurretBtn.alpha = selectedTowerType === 'laser' ? 1.0 : 0.7;
machineGunBtn.alpha = selectedTowerType === 'machineGun' ? 1.0 : 0.7;
missileLauncherBtn.alpha = selectedTowerType === 'missile' ? 1.0 : 0.7;
fireDroneBtn.alpha = selectedTowerType === 'fire' ? 1.0 : 0.7;
electricTrapBtn.alpha = selectedTowerType === 'electric' ? 1.0 : 0.7;
fireMachineBtn.alpha = selectedTowerType === 'fireMachine' ? 1.0 : 0.7;
fireTrapBtn.alpha = selectedTowerType === 'fireTrap' ? 1.0 : 0.7;
// Add glow effect to selected tower
if (selectedTowerType === 'laser') {
LK.effects.flashObject(laserTurretBtn, 0x4444FF, 500);
} else if (selectedTowerType === 'machineGun') {
LK.effects.flashObject(machineGunBtn, 0xFF6600, 500);
} else if (selectedTowerType === 'missile') {
LK.effects.flashObject(missileLauncherBtn, 0xFF4444, 500);
} else if (selectedTowerType === 'fire') {
LK.effects.flashObject(fireDroneBtn, 0xFF8800, 500);
} else if (selectedTowerType === 'electric') {
LK.effects.flashObject(electricTrapBtn, 0xFFFF00, 500);
} else if (selectedTowerType === 'fireMachine') {
LK.effects.flashObject(fireMachineBtn, 0xFF2200, 500);
} else if (selectedTowerType === 'fireTrap') {
LK.effects.flashObject(fireTrapBtn, 0xFF4400, 500);
}
}
// Initial highlight
highlightSelectedTower();
// Speed button interactions
speed1xBtn.down = function (x, y, obj) {
speedMultiplier = 1.0;
highlightSelectedSpeed();
};
speed2xBtn.down = function (x, y, obj) {
speedMultiplier = 2.0;
highlightSelectedSpeed();
};
speed3xBtn.down = function (x, y, obj) {
speedMultiplier = 3.0;
highlightSelectedSpeed();
};
function highlightSelectedSpeed() {
// Reset all speed buttons
speed1xBtn.alpha = speedMultiplier === 1.0 ? 1.0 : 0.7;
speed2xBtn.alpha = speedMultiplier === 2.0 ? 1.0 : 0.7;
speed3xBtn.alpha = speedMultiplier === 3.0 ? 1.0 : 0.7;
// Add glow effect to selected speed
if (speedMultiplier === 1.0) {
LK.effects.flashObject(speed1xBtn, 0x00FF00, 500);
} else if (speedMultiplier === 2.0) {
LK.effects.flashObject(speed2xBtn, 0xFFFF00, 500);
} else if (speedMultiplier === 3.0) {
LK.effects.flashObject(speed3xBtn, 0xFF8800, 500);
}
}
// Initial speed highlight
highlightSelectedSpeed();
// Tower menu interactions
laserTurretBtn.down = function (x, y, obj) {
if (currency < 50) return; // Not enough money
selectedTowerType = 'laser';
selectedTowerCost = 50;
placementMode = true;
turretToPlace = {
type: 'laser',
cost: 50
};
highlightSelectedTower();
};
machineGunBtn.down = function (x, y, obj) {
if (currency < 1000) return; // Not enough money
selectedTowerType = 'machineGun';
selectedTowerCost = 1000;
placementMode = true;
turretToPlace = {
type: 'machineGun',
cost: 1000
};
highlightSelectedTower();
};
missileLauncherBtn.down = function (x, y, obj) {
if (currency < 100) return; // Not enough money
selectedTowerType = 'missile';
selectedTowerCost = 100;
placementMode = true;
turretToPlace = {
type: 'missile',
cost: 100
};
highlightSelectedTower();
};
fireDroneBtn.down = function (x, y, obj) {
if (currency < 200) return; // Not enough money
selectedTowerType = 'fire';
selectedTowerCost = 200;
placementMode = true;
turretToPlace = {
type: 'fire',
cost: 200
};
highlightSelectedTower();
};
electricTrapBtn.down = function (x, y, obj) {
if (currency < 300) return; // Not enough money
selectedTowerType = 'electric';
selectedTowerCost = 300;
placementMode = true;
turretToPlace = {
type: 'electric',
cost: 300
};
highlightSelectedTower();
};
fireMachineBtn.down = function (x, y, obj) {
if (currency < 500) return; // Not enough money
selectedTowerType = 'fireMachine';
selectedTowerCost = 500;
placementMode = true;
turretToPlace = {
type: 'fireMachine',
cost: 500
};
highlightSelectedTower();
};
fireTrapBtn.down = function (x, y, obj) {
if (currency < 700) return; // Not enough money
selectedTowerType = 'fireTrap';
selectedTowerCost = 700;
placementMode = true;
turretToPlace = {
type: 'fireTrap',
cost: 700
};
highlightSelectedTower();
};
var selectedTurret = null;
var placementMode = false;
var turretToPlace = null;
var frozenTurrets = [];
var freezeEffectActive = false;
var freezeEffectTimer = 0;
var freezeEffectDuration = 120; // 2 seconds at 60fps
function selectTurret(turret) {
// Deselect previous turret
if (selectedTurret) {
selectedTurret.rangeIndicator.alpha = 0.25;
// Hide cross indicator on previous turret
selectedTurret.crossIndicator.alpha = 0;
}
selectedTurret = turret;
// Highlight selected turret
selectedTurret.rangeIndicator.alpha = 0.5;
// Show cross indicator
selectedTurret.crossIndicator.alpha = 1.0;
sellBtn.alpha = 1.0; // Enable sell button
upgradeBtn.alpha = 1.0; // Enable upgrade button
// Update button texts with prices
sellBtn.setText('SELL\nTURRET\n$' + selectedTurret.sellPrice);
if (selectedTurret.upgradeLevel >= 5) {
upgradeBtn.setText('UPGRADE\nMax'); //{ap_new}
} else {
//{ap_new2}
upgradeBtn.setText('UPGRADE\nLv.' + selectedTurret.upgradeLevel + ' → ' + (selectedTurret.upgradeLevel + 1) + '\n$' + selectedTurret.upgradeCost);
} //{ap_new3}
LK.effects.flashObject(selectedTurret, 0x00FF00, 300);
}
function deselectTurret() {
if (selectedTurret) {
selectedTurret.rangeIndicator.alpha = 0.25;
// Hide cross indicator
selectedTurret.crossIndicator.alpha = 0;
}
selectedTurret = null;
sellBtn.alpha = 0.5; // Disable sell button
upgradeBtn.alpha = 0.5; // Disable upgrade button
sellBtn.setText('SELL\nTURRET\n$0'); // Reset text
upgradeBtn.setText('UPGRADE\nTURRET\n$0'); // Reset text
}
function sellTurret() {
if (!selectedTurret) return;
// Give money back
currency += selectedTurret.sellPrice;
updateCurrencyDisplay();
// Remove turret from game
for (var i = turrets.length - 1; i >= 0; i--) {
if (turrets[i] === selectedTurret) {
turrets.splice(i, 1);
break;
}
}
selectedTurret.destroy();
// Flash green effect
LK.effects.flashScreen(0x00FF00, 300);
deselectTurret();
}
function upgradeTurret() {
if (!selectedTurret) return;
if (selectedTurret.upgradeLevel >= 5) return; // Max upgrade level
if (currency < selectedTurret.upgradeCost) return; // Not enough money
// Deduct cost
currency -= selectedTurret.upgradeCost;
updateCurrencyDisplay();
// Upgrade turret
selectedTurret.upgradeLevel++;
selectedTurret.updateTowerProperties();
// Update button texts with new prices
sellBtn.setText('SELL\nTURRET\n$' + selectedTurret.sellPrice);
if (selectedTurret.upgradeLevel >= 5) {
upgradeBtn.setText('UPGRADE\nMax'); //{aG_new}
} else {
//{aG_new2}
upgradeBtn.setText('UPGRADE\nLv.' + selectedTurret.upgradeLevel + ' → ' + (selectedTurret.upgradeLevel + 1) + '\n$' + selectedTurret.upgradeCost);
} //{aG_new3}
// Visual effects
LK.effects.flashObject(selectedTurret, 0x00FFFF, 500);
// Change turret color based on upgrade level
if (selectedTurret.upgradeLevel >= 3) {
selectedTurret.children[0].tint = 0xFFFFFF; // White for high level
} else if (selectedTurret.upgradeLevel >= 2) {
selectedTurret.children[0].tint = 0xFFFF00; // Yellow for mid level
}
}
function activateZombieFreezeEffect() {
if (freezeEffectActive) return; // Already active
if (turrets.length === 0) return; // No turrets to freeze
// Randomly select up to 2 turrets to freeze
var availableTurrets = turrets.slice(); // Copy array
var turretsToFreeze = Math.min(2, availableTurrets.length);
frozenTurrets = [];
for (var i = 0; i < turretsToFreeze; i++) {
var randomIndex = Math.floor(Math.random() * availableTurrets.length);
var turretToFreeze = availableTurrets[randomIndex];
availableTurrets.splice(randomIndex, 1); // Remove from available list
frozenTurrets.push(turretToFreeze);
// Add freeze visual effect
turretToFreeze.isFrozen = true;
turretToFreeze.children[0].tint = 0x00CCFF; // Ice blue tint
// Add ice effect around turret
var iceEffect = turretToFreeze.addChild(LK.getAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0x00CCFF,
scaleX: 1.5,
scaleY: 1.5
}));
turretToFreeze.iceEffect = iceEffect;
// Flash effect
LK.effects.flashObject(turretToFreeze, 0x00CCFF, 300);
}
// Activate freeze effect
freezeEffectActive = true;
freezeEffectTimer = 0;
// Screen flash to indicate freeze effect
LK.effects.flashScreen(0x00CCFF, 500);
}
function updateFreezeEffect() {
if (!freezeEffectActive) return;
freezeEffectTimer += speedMultiplier;
if (freezeEffectTimer >= freezeEffectDuration) {
// Remove freeze effect
for (var i = 0; i < frozenTurrets.length; i++) {
var turret = frozenTurrets[i];
if (turret.parent) {
// Check if turret still exists
turret.isFrozen = false;
turret.children[0].tint = 0xFFFFFF; // Reset tint
if (turret.iceEffect) {
turret.iceEffect.destroy();
turret.iceEffect = null;
}
}
}
frozenTurrets = [];
freezeEffectActive = false;
freezeEffectTimer = 0;
}
}
// Game-wide click handler - enable turret placement when in placement mode
game.down = function (x, y, obj) {
// Check if click is in game area (not on UI elements)
if (y > 2300) return; // Don't place on bottom UI
if (placementMode && turretToPlace) {
// Check if position is too close to path
var tooCloseToPath = false;
for (var i = 0; i < gamePath.length - 1; i++) {
var start = gamePath[i];
var end = gamePath[i + 1];
var distance = distanceToLineSegment(x, y, start.x, start.y, end.x, end.y);
if (distance < 80) {
// Minimum distance from path
tooCloseToPath = true;
break;
}
}
if (tooCloseToPath) {
LK.effects.flashScreen(0xFF0000, 200); // Red flash for invalid placement
return;
}
// Check if position overlaps with existing turrets
var tooCloseToTurret = false;
for (var i = 0; i < turrets.length; i++) {
var turret = turrets[i];
var dx = turret.x - x;
var dy = turret.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
// Minimum distance between turrets
tooCloseToTurret = true;
break;
}
}
if (tooCloseToTurret) {
LK.effects.flashScreen(0xFF0000, 200); // Red flash for invalid placement
return;
}
if (turretToPlace.type === 'fireTrap') {
// Place fire trap (can be placed anywhere)
var fireTrap = new FireTrap();
fireTrap.x = x;
fireTrap.y = y;
currency -= turretToPlace.cost;
updateCurrencyDisplay();
fireTraps.push(fireTrap);
game.addChild(fireTrap);
// Flash orange when built
LK.effects.flashObject(fireTrap, 0xFF4400, 500);
} else {
// Place turret
var turret = new Turret();
turret.x = x;
turret.y = y;
turret.towerType = turretToPlace.type;
turret.cost = turretToPlace.cost;
turret.updateTowerProperties();
currency -= turretToPlace.cost;
updateCurrencyDisplay();
turrets.push(turret);
game.addChild(turret);
// Flash green when built
var flashColor = turretToPlace.type === 'laser' ? 0x4444FF : turretToPlace.type === 'machineGun' ? 0xFF6600 : turretToPlace.type === 'missile' ? 0xFF4444 : turretToPlace.type === 'fire' ? 0xFF8800 : turretToPlace.type === 'fireMachine' ? 0xFF2200 : 0xFFFF00;
LK.effects.flashObject(turret, flashColor, 500);
}
// Exit placement mode
placementMode = false;
turretToPlace = null;
} else {
// Deselect turret when clicking empty area
deselectTurret();
}
};
game.update = function () {
// Spawn zombies
if (waveActive && zombiesSpawned < zombiesToSpawn) {
spawnTimer += speedMultiplier;
// Adjust spawn rate based on wave - slower spawning for first level
var currentSpawnRate = wave === 1 ? baseSpawnRate * 1.5 : baseSpawnRate;
if (spawnTimer >= currentSpawnRate) {
spawnZombie();
spawnTimer = 0;
}
}
// Clean up dead zombies
for (var i = zombies.length - 1; i >= 0; i--) {
if (zombies[i].isDead) {
zombies[i].destroy();
zombies.splice(i, 1);
}
}
// Update freeze effect
updateFreezeEffect();
checkWaveComplete();
};