/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var AreaTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('areaTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); // Add explosion indicator // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -50; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -50; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; var explosionIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); explosionIndicator.x = 0; explosionIndicator.y = 0; explosionIndicator.tint = 0xff0000; // Red explosion symbol explosionIndicator.scaleX = 1.3; explosionIndicator.scaleY = 1.3; self.towerType = 'area'; self.health = 140; self.maxHealth = 140; self.range = 180; self.damage = 25; // Base damage per unit self.areaRadius = 80; // Area effect radius self.shootCooldown = 0; self.target = null; self.cost = 130; self.canUpgrade = function () { return false; // Area towers cannot be upgraded }; self.getUpgradeCost = function () { return 0; // Area towers cannot be upgraded }; self.update = function () { // Find target (center of enemy group) if (!self.target || !self.target.parent) { self.target = null; var bestTarget = null; var maxUnitsInArea = 0; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.range) { // Count how many units would be hit if we target this unit var unitsInArea = 0; for (var j = 0; j < playerUnits.length; j++) { var otherUnit = playerUnits[j]; var areaDistance = Math.sqrt(Math.pow(otherUnit.x - unit.x, 2) + Math.pow(otherUnit.y - unit.y, 2)); if (areaDistance <= self.areaRadius) { unitsInArea++; } } if (unitsInArea > maxUnitsInArea) { maxUnitsInArea = unitsInArea; bestTarget = unit; } } } self.target = bestTarget; } // Shoot area effect if (self.target && self.shootCooldown <= 0) { // Create area explosion at target location var targetX = self.target.x; var targetY = self.target.y; // Damage all units in area var unitsHit = 0; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - targetX, 2) + Math.pow(unit.y - targetY, 2)); if (distance <= self.areaRadius) { unit.takeDamage(self.damage); LK.effects.flashObject(unit, 0xff6600, 300); unitsHit++; } } if (unitsHit > 0) { // Visual explosion effect tween(explosionIndicator, { scaleX: 3.0, scaleY: 3.0, alpha: 0.3 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(explosionIndicator, { scaleX: 1.3, scaleY: 1.3, alpha: 1 }, { duration: 200, easing: tween.easeIn }); } }); LK.getSound('shoot').play(); } self.shootCooldown = 150; // 2.5 second cooldown } if (self.shootCooldown > 0) { self.shootCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var AttackUnit = Container.expand(function () { var self = Container.call(this); var unitGraphics = self.attachAsset('basicUnitAsset', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -40; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -40; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; self.health = 100; self.maxHealth = 100; self.speed = 2; self.damage = 25; self.range = 150; self.shootCooldown = 0; self.target = null; self.lastX = 0; self.lastY = 0; self.currentWaypoint = 0; self.isMovingToWaypoint = false; // Create path through tower grid var pathWaypoints = []; pathWaypoints.push({ x: 400, y: self.y }); // Tower grid parameters var gridStartX = 800; var gridStartY = 600; var gridSpacingX = 200; var gridSpacingY = 200; var gridRows = 8; var gridCols = 4; // Serpentine path through towers for (var col = 0; col < gridCols; col++) { if (col % 2 === 0) { for (var row = 0; row < gridRows; row++) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } else { for (var row = gridRows - 1; row >= 0; row--) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } } pathWaypoints.push({ x: 1600, y: 1000 }); self.pathWaypoints = pathWaypoints; self.followPath = function () { if (self.isMovingToWaypoint) return; if (self.currentWaypoint >= self.pathWaypoints.length) { // Reached end of path, move directly to base self.x += self.speed; return; } var waypoint = self.pathWaypoints[self.currentWaypoint]; var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2)); var duration = distance / self.speed * 16.67; // Convert speed to ms (60fps = 16.67ms per frame) self.isMovingToWaypoint = true; tween(self, { x: waypoint.x, y: waypoint.y }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { self.currentWaypoint++; self.isMovingToWaypoint = false; } }); }; self.update = function () { self.lastX = self.x; self.lastY = self.y; // Find target (prioritize base, then towers) if (!self.target || !self.target.parent) { self.target = null; var closestTarget = null; var closestDistance = Infinity; // Check distance to enemy base first if (enemyBase && enemyBase.parent) { var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2)); if (baseDistance <= self.range) { closestTarget = enemyBase; closestDistance = baseDistance; } } // Check towers if no base target or if tower is closer for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower && tower.parent) { var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2)); if (towerDistance <= self.range && towerDistance < closestDistance) { closestDistance = towerDistance; closestTarget = tower; } } } self.target = closestTarget; } // Attack target if in range if (self.target && self.shootCooldown <= 0) { var bullet = new UnitBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = self.target; bullet.damage = self.damage; unitBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.shootCooldown = 60; // 1 second cooldown at 60fps } if (self.shootCooldown > 0) { self.shootCooldown--; } // Move towards enemy base using waypoints only if no target in range if (!self.target) { self.followPath(); } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); if (self.health <= 0) { return true; // Unit is dead } return false; }; return self; }); var BasicTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('basicTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -50; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -50; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; self.towerType = 'basic'; self.health = 100; self.maxHealth = 100; self.range = 150; self.damage = 20; self.shootCooldown = 0; self.target = null; self.cost = 75; // Upgrade indicator (initially hidden) var upgradeIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); upgradeIndicator.x = 30; upgradeIndicator.y = -30; upgradeIndicator.visible = false; self.level = 1; self.maxLevel = 3; self.baseRange = 150; self.baseDamage = 20; self.range = self.baseRange; self.damage = self.baseDamage; self.updateStats = function () { var multiplier = 1 + (self.level - 1) * 0.6; // 60% increase per level self.range = Math.floor(self.baseRange * multiplier); self.damage = Math.floor(self.baseDamage * multiplier); self.maxHealth = Math.floor(100 * multiplier); self.health = Math.min(self.health, self.maxHealth); // Special abilities for max level if (self.level === 3) { self.shieldActive = false; self.shieldCooldown = 0; self.shieldDuration = 0; } // Update visual appearance based on level if (self.level === 2) { towerGraphics.tint = 0x4169E1; // Royal blue upgradeIndicator.visible = true; upgradeIndicator.tint = 0xffd700; // Gold } else if (self.level === 3) { towerGraphics.tint = 0x0000FF; // Pure blue upgradeIndicator.visible = true; upgradeIndicator.tint = 0xff4500; // Orange red } }; self.canUpgrade = function () { return self.level < self.maxLevel; }; self.getUpgradeCost = function () { return 50 + (self.level - 1) * 25; // Cost increases with level }; self.upgrade = function () { if (self.canUpgrade()) { self.level++; self.updateStats(); LK.effects.flashObject(self, 0xffd700, 500); return true; } return false; }; self.update = function () { // Shield ability for level 3 if (self.level === 3) { // Activate shield when health is low and not on cooldown if (self.health < self.maxHealth * 0.3 && !self.shieldActive && self.shieldCooldown <= 0) { self.shieldActive = true; self.shieldDuration = 300; // 5 seconds of invulnerability towerGraphics.tint = 0xFFD700; // Golden shield color LK.effects.flashObject(self, 0xFFD700, 300); } // Handle shield duration if (self.shieldActive) { self.shieldDuration--; if (self.shieldDuration <= 0) { self.shieldActive = false; self.shieldCooldown = 1200; // 20 second cooldown towerGraphics.tint = 0x0000FF; // Back to normal level 3 color } } // Update cooldown if (self.shieldCooldown > 0) { self.shieldCooldown--; } } // Find target if (!self.target || !self.target.parent) { self.target = null; var closestUnit = null; var closestDistance = Infinity; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestUnit = unit; } } self.target = closestUnit; } // Shoot at target (if not frozen) if (self.target && self.shootCooldown <= 0 && !self.frozenUntil) { var bullet = new TowerBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = self.target; bullet.damage = self.damage; towerBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.shootCooldown = 90; // 1.5 second cooldown } if (self.shootCooldown > 0) { self.shootCooldown--; } }; self.takeDamage = function (damage) { // Shield ability blocks damage at level 3 if (self.level === 3 && self.shieldActive) { LK.effects.flashObject(self, 0xFFD700, 200); // Gold flash for blocked damage return false; // No damage taken } self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var BasicUnit = Container.expand(function () { var self = Container.call(this); var unitGraphics = self.attachAsset('basicUnitAsset', { anchorX: 0.5, anchorY: 0.5 }); unitGraphics.tint = 0x87CEEB; // Sky blue color for basic unit unitGraphics.scaleX = 0.9; unitGraphics.scaleY = 0.9; // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -35; healthBarBg.scaleX = 0.8; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -35; healthBar.scaleX = 0.8; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent * 0.8; if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; } else { healthBar.tint = 0xff0000; } }; self.unitType = 'basic'; self.health = 60; self.maxHealth = 60; self.speed = 1.8; self.damage = 15; self.range = 140; self.cost = 40; self.shootCooldown = 0; self.target = null; self.lastX = 0; self.lastY = 0; self.currentWaypoint = 0; self.isMovingToWaypoint = false; // Create path through tower grid var pathWaypoints = []; pathWaypoints.push({ x: 400, y: self.y }); // Tower grid parameters var gridStartX = 800; var gridStartY = 600; var gridSpacingX = 200; var gridSpacingY = 200; var gridRows = 8; var gridCols = 4; // Serpentine path through towers for (var col = 0; col < gridCols; col++) { if (col % 2 === 0) { for (var row = 0; row < gridRows; row++) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } else { for (var row = gridRows - 1; row >= 0; row--) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } } pathWaypoints.push({ x: 1600, y: 1000 }); self.pathWaypoints = pathWaypoints; self.followPath = function () { if (self.isMovingToWaypoint) return; if (self.currentWaypoint >= self.pathWaypoints.length) { // Check for walls blocking movement var canMove = true; var nextX = self.x + self.speed; for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower.towerType === 'wall') { var distance = Math.sqrt(Math.pow(tower.x - nextX, 2) + Math.pow(tower.y - self.y, 2)); if (distance <= tower.blockRadius) { canMove = false; break; } } } if (canMove) { self.x += self.speed; } return; } var waypoint = self.pathWaypoints[self.currentWaypoint]; // Check if path to waypoint is blocked by walls var pathBlocked = false; for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower.towerType === 'wall') { var wallDistance = Math.sqrt(Math.pow(tower.x - waypoint.x, 2) + Math.pow(tower.y - waypoint.y, 2)); if (wallDistance <= tower.blockRadius) { pathBlocked = true; break; } } } if (pathBlocked) { // Skip this waypoint if blocked self.currentWaypoint++; return; } var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2)); var duration = distance / self.speed * 16.67; self.isMovingToWaypoint = true; tween(self, { x: waypoint.x, y: waypoint.y }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { self.currentWaypoint++; self.isMovingToWaypoint = false; } }); }; self.update = function () { self.lastX = self.x; self.lastY = self.y; if (!self.target || !self.target.parent) { self.target = null; var closestTarget = null; var closestDistance = Infinity; if (enemyBase && enemyBase.parent) { var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2)); if (baseDistance <= self.range) { closestTarget = enemyBase; closestDistance = baseDistance; } } for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower && tower.parent) { var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2)); if (towerDistance <= self.range && towerDistance < closestDistance) { closestDistance = towerDistance; closestTarget = tower; } } } self.target = closestTarget; } if (self.target && self.shootCooldown <= 0) { var bullet = new UnitBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = self.target; bullet.damage = self.damage; unitBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.shootCooldown = 75; } if (self.shootCooldown > 0) { self.shootCooldown--; } if (!self.target) { self.followPath(); } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); if (self.health <= 0) { return true; } return false; }; return self; }); var EnemyBase = Container.expand(function () { var self = Container.call(this); var baseGraphics = self.attachAsset('enemyBase', { anchorX: 0.5, anchorY: 0.5 }); self.health = 800; self.maxHealth = 800; self.range = 300; // Base shooting range self.damage = 60; // Base damage per shot self.shootCooldown = 0; self.target = null; self.update = function () { // Find target within range if (!self.target || !self.target.parent) { self.target = null; var closestUnit = null; var closestDistance = Infinity; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestUnit = unit; } } self.target = closestUnit; } // Shoot at target (if not frozen) if (self.target && self.shootCooldown <= 0 && !self.frozenUntil) { var bullet = new TowerBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = self.target; bullet.damage = self.damage; towerBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.shootCooldown = 90; // 1.5 second cooldown } if (self.shootCooldown > 0) { self.shootCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xff0000, 300); if (self.health <= 0) { return true; // Base destroyed - player wins! } return false; }; return self; }); // Game arrays to track entities var EnemyTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('enemyTower', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -50; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -50; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; // Upgrade indicator (initially hidden) var upgradeIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); upgradeIndicator.x = 30; upgradeIndicator.y = -30; upgradeIndicator.visible = false; self.level = 1; self.maxLevel = 3; self.health = 250; self.maxHealth = 250; self.baseRange = 220; self.baseDamage = 45; self.range = self.baseRange; self.damage = self.baseDamage; self.shootCooldown = 0; self.target = null; self.updateStats = function () { var multiplier = 1 + (self.level - 1) * 0.5; // 50% increase per level self.range = Math.floor(self.baseRange * multiplier); self.damage = Math.floor(self.baseDamage * multiplier); self.maxHealth = Math.floor(150 * multiplier); self.health = Math.min(self.health, self.maxHealth); // Update visual appearance based on level if (self.level === 2) { towerGraphics.tint = 0xc0392b; // Darker red upgradeIndicator.visible = true; upgradeIndicator.tint = 0xffd700; // Gold } else if (self.level === 3) { towerGraphics.tint = 0x8b0000; // Dark red upgradeIndicator.visible = true; upgradeIndicator.tint = 0xff4500; // Orange red } }; self.canUpgrade = function () { return self.level < self.maxLevel; }; self.getUpgradeCost = function () { return 75 * self.level; // Cost increases with level }; self.upgrade = function () { if (self.canUpgrade()) { self.level++; self.updateStats(); LK.effects.flashObject(self, 0xffd700, 500); return true; } return false; }; self.update = function () { // Increase range and damage if base is low on health var baseHealthPercent = enemyBase ? enemyBase.health / enemyBase.maxHealth : 1; var bonusRange = baseHealthPercent < 0.5 ? 100 : 0; var bonusDamage = baseHealthPercent < 0.3 ? 20 : 0; var currentRange = self.range + bonusRange; var currentDamage = self.damage + bonusDamage; // Find target if (!self.target || !self.target.parent) { self.target = null; var closestUnit = null; var closestDistance = Infinity; // Prioritize units closer to base for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); var distanceToBase = Math.sqrt(Math.pow(unit.x - enemyBase.x, 2) + Math.pow(unit.y - enemyBase.y, 2)); if (distance <= currentRange) { // Prioritize units closer to base var priority = distance - distanceToBase * 0.3; if (priority < closestDistance) { closestDistance = priority; closestUnit = unit; } } } self.target = closestUnit; } // Shoot at target if (self.target && self.shootCooldown <= 0) { var bullet = new TowerBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = self.target; bullet.damage = currentDamage; towerBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); // Shoot faster when base is in danger var shootCooldown = baseHealthPercent < 0.5 ? 60 : 90; self.shootCooldown = shootCooldown; } if (self.shootCooldown > 0) { self.shootCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var FastTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('fastTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -40; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -40; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; self.towerType = 'fast'; self.health = 80; self.maxHealth = 80; self.range = 180; self.damage = 15; self.shootCooldown = 0; self.target = null; self.cost = 100; // Upgrade indicator (initially hidden) var upgradeIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); upgradeIndicator.x = 25; upgradeIndicator.y = -25; upgradeIndicator.visible = false; self.level = 1; self.maxLevel = 3; self.baseRange = 180; self.baseDamage = 15; self.baseCooldown = 30; self.multiShot = 1; // Number of bullets per shot self.range = self.baseRange; self.damage = self.baseDamage; self.updateStats = function () { var multiplier = 1 + (self.level - 1) * 0.4; // 40% increase per level self.range = Math.floor(self.baseRange * multiplier); self.damage = Math.floor(self.baseDamage * multiplier); self.maxHealth = Math.floor(80 * multiplier); self.health = Math.min(self.health, self.maxHealth); self.multiShot = self.level; // Shoot multiple bullets at higher levels // Special abilities for max level if (self.level === 3) { self.burstMode = false; self.burstCooldown = 0; self.burstDuration = 0; self.burstTriggerDistance = 100; // Distance to activate burst mode } // Update visual appearance based on level if (self.level === 2) { towerGraphics.tint = 0x00FF7F; // Spring green upgradeIndicator.visible = true; upgradeIndicator.tint = 0xffd700; // Gold } else if (self.level === 3) { towerGraphics.tint = 0x00FF00; // Pure green upgradeIndicator.visible = true; upgradeIndicator.tint = 0xff4500; // Orange red } }; self.canUpgrade = function () { return self.level < self.maxLevel; }; self.getUpgradeCost = function () { return 60 + (self.level - 1) * 30; // Cost increases with level }; self.upgrade = function () { if (self.canUpgrade()) { self.level++; self.updateStats(); LK.effects.flashObject(self, 0xffd700, 500); return true; } return false; }; self.update = function () { // Burst mode ability for level 3 if (self.level === 3) { // Check if any enemies are very close to activate burst mode var hasCloseEnemies = false; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.burstTriggerDistance) { hasCloseEnemies = true; break; } } // Activate burst mode when enemies are close and not on cooldown if (hasCloseEnemies && !self.burstMode && self.burstCooldown <= 0) { self.burstMode = true; self.burstDuration = 180; // 3 seconds of burst fire towerGraphics.tint = 0xFFFF00; // Yellow burst color LK.effects.flashObject(self, 0xFFFF00, 300); } // Handle burst duration if (self.burstMode) { self.burstDuration--; if (self.burstDuration <= 0) { self.burstMode = false; self.burstCooldown = 600; // 10 second cooldown towerGraphics.tint = 0x00FF00; // Back to normal level 3 color } } // Update cooldown if (self.burstCooldown > 0) { self.burstCooldown--; } } // Find target if (!self.target || !self.target.parent) { self.target = null; var closestUnit = null; var closestDistance = Infinity; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestUnit = unit; } } self.target = closestUnit; } // Shoot at target with fast rate and multi-shot if (self.target && self.shootCooldown <= 0) { // Determine shot count - burst mode doubles the shots var shotCount = self.multiShot; if (self.level === 3 && self.burstMode) { shotCount = self.multiShot * 2; // Double shots in burst mode } // Shoot multiple bullets based on level and burst mode for (var shotIndex = 0; shotIndex < shotCount; shotIndex++) { var bullet = new TowerBullet(); bullet.x = self.x; bullet.y = self.y; // For multi-shot, slightly spread the bullets if (shotCount > 1) { var angleOffset = (shotIndex - (shotCount - 1) / 2) * 0.2; // Smaller angle spread for more bullets var dx = self.target.x - self.x; var dy = self.target.y - self.y; var angle = Math.atan2(dy, dx) + angleOffset; var distance = Math.sqrt(dx * dx + dy * dy); // Create a virtual target with slight offset bullet.target = { x: self.x + Math.cos(angle) * distance, y: self.y + Math.sin(angle) * distance, parent: self.target.parent, takeDamage: self.target.takeDamage.bind(self.target) }; } else { bullet.target = self.target; } bullet.damage = self.damage; towerBullets.push(bullet); game.addChild(bullet); } LK.getSound('shoot').play(); // Faster shooting in burst mode var cooldownTime = self.level === 3 && self.burstMode ? 15 : 30; self.shootCooldown = cooldownTime; } if (self.shootCooldown > 0) { self.shootCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var FastUnit = Container.expand(function () { var self = Container.call(this); var unitGraphics = self.attachAsset('fastUnitAsset', { anchorX: 0.5, anchorY: 0.5 }); unitGraphics.tint = 0x32CD32; // Lime green color for fast unit unitGraphics.scaleX = 0.8; unitGraphics.scaleY = 0.8; // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -32; healthBarBg.scaleX = 0.7; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -32; healthBar.scaleX = 0.7; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent * 0.7; if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; } else { healthBar.tint = 0xff0000; } }; self.unitType = 'fast'; self.health = 45; self.maxHealth = 45; self.speed = 3.5; self.damage = 12; self.range = 120; self.cost = 60; self.shootCooldown = 0; self.target = null; self.lastX = 0; self.lastY = 0; self.currentWaypoint = 0; self.isMovingToWaypoint = false; // Create path through tower grid var pathWaypoints = []; pathWaypoints.push({ x: 400, y: self.y }); // Tower grid parameters var gridStartX = 800; var gridStartY = 600; var gridSpacingX = 200; var gridSpacingY = 200; var gridRows = 8; var gridCols = 4; // Serpentine path through towers for (var col = 0; col < gridCols; col++) { if (col % 2 === 0) { for (var row = 0; row < gridRows; row++) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } else { for (var row = gridRows - 1; row >= 0; row--) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } } pathWaypoints.push({ x: 1600, y: 1000 }); self.pathWaypoints = pathWaypoints; self.followPath = function () { if (self.isMovingToWaypoint) return; if (self.currentWaypoint >= self.pathWaypoints.length) { self.x += self.speed; return; } var waypoint = self.pathWaypoints[self.currentWaypoint]; var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2)); var duration = distance / self.speed * 16.67; self.isMovingToWaypoint = true; tween(self, { x: waypoint.x, y: waypoint.y }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { self.currentWaypoint++; self.isMovingToWaypoint = false; } }); }; self.update = function () { self.lastX = self.x; self.lastY = self.y; if (!self.target || !self.target.parent) { self.target = null; var closestTarget = null; var closestDistance = Infinity; if (enemyBase && enemyBase.parent) { var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2)); if (baseDistance <= self.range) { closestTarget = enemyBase; closestDistance = baseDistance; } } for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower && tower.parent) { var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2)); if (towerDistance <= self.range && towerDistance < closestDistance) { closestDistance = towerDistance; closestTarget = tower; } } } self.target = closestTarget; } if (self.target && self.shootCooldown <= 0) { var bullet = new UnitBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = self.target; bullet.damage = self.damage; unitBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.shootCooldown = 45; // Faster shooting } if (self.shootCooldown > 0) { self.shootCooldown--; } if (!self.target) { self.followPath(); } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); if (self.health <= 0) { return true; } return false; }; return self; }); var HealTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('healTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -50; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -50; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; // Add a cross symbol effect var crossIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); crossIndicator.x = 0; crossIndicator.y = 0; crossIndicator.tint = 0xffffff; // White cross crossIndicator.scaleX = 1.5; crossIndicator.scaleY = 1.5; self.towerType = 'heal'; self.health = 120; self.maxHealth = 120; self.range = 200; // Healing range self.healAmount = 15; self.healCooldown = 0; self.target = null; self.cost = 110; self.canUpgrade = function () { return false; // Heal towers cannot be upgraded }; self.getUpgradeCost = function () { return 0; // Heal towers cannot be upgraded }; self.update = function () { // Find damaged towers to heal if (self.healCooldown <= 0) { self.target = null; var bestTarget = null; var lowestHealthPercent = 1; for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower !== self && tower.parent) { var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2)); if (distance <= self.range) { var healthPercent = tower.health / tower.maxHealth; if (healthPercent < 1 && healthPercent < lowestHealthPercent) { lowestHealthPercent = healthPercent; bestTarget = tower; } } } } self.target = bestTarget; } // Heal target if (self.target && self.healCooldown <= 0) { var healingAmount = Math.min(self.healAmount, self.target.maxHealth - self.target.health); if (healingAmount > 0) { self.target.health += healingAmount; // Update target's health bar after healing if (self.target.updateHealthBar) { self.target.updateHealthBar(); } LK.effects.flashObject(self.target, 0x00ff00, 400); // Create healing visual effect tween(crossIndicator, { scaleX: 2.0, scaleY: 2.0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(crossIndicator, { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeIn }); } }); self.healCooldown = 120; // 2 second cooldown } } if (self.healCooldown > 0) { self.healCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var HeavyTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('heavyTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -60; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -60; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; self.towerType = 'heavy'; self.health = 200; self.maxHealth = 200; self.range = 160; self.damage = 50; self.shootCooldown = 0; self.target = null; self.cost = 150; // Upgrade indicator (initially hidden) var upgradeIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); upgradeIndicator.x = 35; upgradeIndicator.y = -35; upgradeIndicator.visible = false; self.level = 1; self.maxLevel = 3; self.baseRange = 160; self.baseDamage = 50; self.splashRadius = 0; // Splash damage radius self.range = self.baseRange; self.damage = self.baseDamage; self.updateStats = function () { var multiplier = 1 + (self.level - 1) * 0.5; // 50% increase per level self.range = Math.floor(self.baseRange * multiplier); self.damage = Math.floor(self.baseDamage * multiplier); self.maxHealth = Math.floor(200 * multiplier); self.health = Math.min(self.health, self.maxHealth); self.splashRadius = (self.level - 1) * 60; // Splash damage at higher levels // Special abilities for max level if (self.level === 3) { self.mineActive = false; self.mineCooldown = 0; self.mineRadius = 100; self.mineDamage = 120; } // Update visual appearance based on level if (self.level === 2) { towerGraphics.tint = 0x8B4513; // Saddle brown upgradeIndicator.visible = true; upgradeIndicator.tint = 0xffd700; // Gold } else if (self.level === 3) { towerGraphics.tint = 0x654321; // Dark brown upgradeIndicator.visible = true; upgradeIndicator.tint = 0xff4500; // Orange red } }; self.canUpgrade = function () { return self.level < self.maxLevel; }; self.getUpgradeCost = function () { return 75 + (self.level - 1) * 40; // Cost increases with level }; self.upgrade = function () { if (self.canUpgrade()) { self.level++; self.updateStats(); LK.effects.flashObject(self, 0xffd700, 500); return true; } return false; }; self.update = function () { // Earthquake ability for level 3 if (self.level === 3 && self.earthquakeCooldown <= 0) { // Count enemies within earthquake range var enemiesInRange = 0; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.earthquakeRadius) { enemiesInRange++; } } // Trigger earthquake if 3+ enemies are nearby if (enemiesInRange >= 3) { // Damage all enemies in range for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.earthquakeRadius) { unit.takeDamage(self.earthquakeDamage); LK.effects.flashObject(unit, 0x8B4513, 400); } } // Visual earthquake effect towerGraphics.tint = 0xFFD700; // Golden earthquake color LK.effects.flashObject(self, 0x8B4513, 600); // Screen shake effect simulation LK.effects.flashScreen(0x8B4513, 300); self.earthquakeCooldown = 900; // 15 second cooldown // Return to normal color after effect var self_ref = self; LK.setTimeout(function () { self_ref.towerGraphics.tint = 0x654321; // Back to level 3 color }, 600); } } // Update earthquake cooldown if (self.earthquakeCooldown > 0) { self.earthquakeCooldown--; } // Find target if (!self.target || !self.target.parent) { self.target = null; var closestUnit = null; var closestDistance = Infinity; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestUnit = unit; } } self.target = closestUnit; } // Shoot at target with heavy damage and potential splash if (self.target && self.shootCooldown <= 0) { var bullet = new TowerBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = self.target; bullet.damage = self.damage; bullet.splashRadius = self.splashRadius; // Add splash capability bullet.splashDamage = Math.floor(self.damage * 0.6); // 60% splash damage towerBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.shootCooldown = 120; // 2 second cooldown (slow) } if (self.shootCooldown > 0) { self.shootCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var HeavyUnit = Container.expand(function () { var self = Container.call(this); var unitGraphics = self.attachAsset('heavyUnitAsset', { anchorX: 0.5, anchorY: 0.5 }); unitGraphics.tint = 0x8B4513; // Brown color for heavy unit unitGraphics.scaleX = 1.2; unitGraphics.scaleY = 1.2; // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -45; healthBarBg.scaleX = 1.0; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -45; healthBar.scaleX = 1.0; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent * 1.0; if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; } else { healthBar.tint = 0xff0000; } }; self.unitType = 'heavy'; self.health = 120; self.maxHealth = 120; self.speed = 0.8; self.damage = 30; self.range = 160; self.cost = 90; self.shootCooldown = 0; self.target = null; self.lastX = 0; self.lastY = 0; self.currentWaypoint = 0; self.isMovingToWaypoint = false; // Create path through tower grid var pathWaypoints = []; pathWaypoints.push({ x: 400, y: self.y }); // Tower grid parameters var gridStartX = 800; var gridStartY = 600; var gridSpacingX = 200; var gridSpacingY = 200; var gridRows = 8; var gridCols = 4; // Serpentine path through towers for (var col = 0; col < gridCols; col++) { if (col % 2 === 0) { for (var row = 0; row < gridRows; row++) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } else { for (var row = gridRows - 1; row >= 0; row--) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } } pathWaypoints.push({ x: 1600, y: 1000 }); self.pathWaypoints = pathWaypoints; self.followPath = function () { if (self.isMovingToWaypoint) return; if (self.currentWaypoint >= self.pathWaypoints.length) { self.x += self.speed; return; } var waypoint = self.pathWaypoints[self.currentWaypoint]; var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2)); var duration = distance / self.speed * 16.67; self.isMovingToWaypoint = true; tween(self, { x: waypoint.x, y: waypoint.y }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { self.currentWaypoint++; self.isMovingToWaypoint = false; } }); }; self.update = function () { self.lastX = self.x; self.lastY = self.y; if (!self.target || !self.target.parent) { self.target = null; var closestTarget = null; var closestDistance = Infinity; if (enemyBase && enemyBase.parent) { var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2)); if (baseDistance <= self.range) { closestTarget = enemyBase; closestDistance = baseDistance; } } for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower && tower.parent) { var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2)); if (towerDistance <= self.range && towerDistance < closestDistance) { closestDistance = towerDistance; closestTarget = tower; } } } self.target = closestTarget; } if (self.target && self.shootCooldown <= 0) { var bullet = new UnitBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = self.target; bullet.damage = self.damage; unitBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.shootCooldown = 100; // Slower shooting } if (self.shootCooldown > 0) { self.shootCooldown--; } if (!self.target) { self.followPath(); } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); if (self.health <= 0) { return true; } return false; }; return self; }); var LaserTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('laserTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); towerGraphics.tint = 0xFF1493; // Deep pink for laser tower towerGraphics.scaleX = 1.2; towerGraphics.scaleY = 1.2; // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -45; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -45; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; // Add laser beam indicator var laserIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); laserIndicator.x = 0; laserIndicator.y = 0; laserIndicator.tint = 0xFF1493; // Pink laser symbol laserIndicator.scaleX = 1.3; laserIndicator.scaleY = 1.3; self.towerType = 'laser'; self.health = 100; self.maxHealth = 100; self.range = 220; self.damage = 8; // Continuous damage per frame self.chargingTime = 60; // 1 second to charge self.maxBeamTime = 180; // 3 seconds max beam self.cooldownTime = 120; // 2 second cooldown self.beamState = 'idle'; // 'idle', 'charging', 'firing', 'cooling' self.stateTimer = 0; self.target = null; self.cost = 140; self.canUpgrade = function () { return false; // Laser towers cannot be upgraded }; self.getUpgradeCost = function () { return 0; // Laser towers cannot be upgraded }; self.update = function () { // Find target when idle if (self.beamState === 'idle') { if (!self.target || !self.target.parent) { self.target = null; var closestUnit = null; var closestDistance = Infinity; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestUnit = unit; } } self.target = closestUnit; } if (self.target) { self.beamState = 'charging'; self.stateTimer = self.chargingTime; // Start charging effect towerGraphics.tint = 0xFF69B4; // Lighter pink while charging } } // Handle beam states if (self.beamState === 'charging') { self.stateTimer--; if (self.stateTimer <= 0) { self.beamState = 'firing'; self.stateTimer = self.maxBeamTime; towerGraphics.tint = 0xFF0000; // Red while firing } } else if (self.beamState === 'firing') { // Continuous damage while firing if (self.target && self.target.parent) { var distance = Math.sqrt(Math.pow(self.target.x - self.x, 2) + Math.pow(self.target.y - self.y, 2)); if (distance <= self.range) { self.target.takeDamage(self.damage); // Visual beam effect if (LK.ticks % 10 === 0) { LK.effects.flashObject(self.target, 0xFF1493, 100); } } else { self.target = null; // Lost target } } // Laser beam visual effect tween(laserIndicator, { scaleX: 1.8, scaleY: 1.8, alpha: 0.7 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(laserIndicator, { scaleX: 1.3, scaleY: 1.3, alpha: 1 }, { duration: 100, easing: tween.easeIn }); } }); self.stateTimer--; if (self.stateTimer <= 0 || !self.target || !self.target.parent) { self.beamState = 'cooling'; self.stateTimer = self.cooldownTime; self.target = null; towerGraphics.tint = 0x808080; // Gray while cooling } } else if (self.beamState === 'cooling') { self.stateTimer--; if (self.stateTimer <= 0) { self.beamState = 'idle'; towerGraphics.tint = 0xFF1493; // Back to normal color } } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var MeleeUnit = Container.expand(function () { var self = Container.call(this); var unitGraphics = self.attachAsset('meleeUnitAsset', { anchorX: 0.5, anchorY: 0.5 }); unitGraphics.tint = 0xFF6347; // Tomato red color for melee unit unitGraphics.scaleX = 1.1; unitGraphics.scaleY = 1.1; // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -40; healthBarBg.scaleX = 0.9; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -40; healthBar.scaleX = 0.9; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent * 0.9; if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; } else { healthBar.tint = 0xff0000; } }; self.unitType = 'melee'; self.health = 90; self.maxHealth = 90; self.speed = 1.3; self.damage = 25; self.range = 50; // Very short range for melee self.cost = 30; self.attackCooldown = 0; self.target = null; self.lastX = 0; self.lastY = 0; self.currentWaypoint = 0; self.isMovingToWaypoint = false; // Create path through tower grid var pathWaypoints = []; pathWaypoints.push({ x: 400, y: self.y }); // Tower grid parameters var gridStartX = 800; var gridStartY = 600; var gridSpacingX = 200; var gridSpacingY = 200; var gridRows = 8; var gridCols = 4; // Serpentine path through towers for (var col = 0; col < gridCols; col++) { if (col % 2 === 0) { for (var row = 0; row < gridRows; row++) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } else { for (var row = gridRows - 1; row >= 0; row--) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } } pathWaypoints.push({ x: 1600, y: 1000 }); self.pathWaypoints = pathWaypoints; self.followPath = function () { if (self.isMovingToWaypoint) return; if (self.currentWaypoint >= self.pathWaypoints.length) { self.x += self.speed; return; } var waypoint = self.pathWaypoints[self.currentWaypoint]; var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2)); var duration = distance / self.speed * 16.67; self.isMovingToWaypoint = true; tween(self, { x: waypoint.x, y: waypoint.y }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { self.currentWaypoint++; self.isMovingToWaypoint = false; } }); }; self.update = function () { self.lastX = self.x; self.lastY = self.y; // Find closest target (prioritize base, then towers) if (!self.target || !self.target.parent) { self.target = null; var closestTarget = null; var closestDistance = Infinity; // Check distance to enemy base first if (enemyBase && enemyBase.parent) { var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2)); if (baseDistance <= self.range) { closestTarget = enemyBase; closestDistance = baseDistance; } } // Check towers if no base target or if tower is closer for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower && tower.parent) { var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2)); if (towerDistance <= self.range && towerDistance < closestDistance) { closestDistance = towerDistance; closestTarget = tower; } } } self.target = closestTarget; } // Melee attack - no bullets, direct damage if (self.target && self.attackCooldown <= 0) { // Flash effect for melee attack LK.effects.flashObject(self, 0xFF6347, 200); LK.effects.flashObject(self.target, 0xFF0000, 200); // Direct damage to target self.target.takeDamage(self.damage); LK.getSound('hit').play(); self.attackCooldown = 90; // 1.5 second cooldown } if (self.attackCooldown > 0) { self.attackCooldown--; } // Move towards target or follow path if (self.target) { // Move directly towards target if one is found and not in attack range if (!self.isMovingToWaypoint) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.range) { // Move towards target only if outside attack range self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } } else { // Follow path if no target self.followPath(); } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); if (self.health <= 0) { return true; } return false; }; return self; }); var PoisonTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('poisonTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); towerGraphics.tint = 0x228B22; // Forest green for poison tower // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -50; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -50; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; // Add poison cloud indicator var poisonIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); poisonIndicator.x = 0; poisonIndicator.y = 0; poisonIndicator.tint = 0x228B22; // Green poison symbol poisonIndicator.scaleX = 1.4; poisonIndicator.scaleY = 1.4; self.towerType = 'poison'; self.health = 110; self.maxHealth = 110; self.range = 160; self.damage = 15; // Initial damage self.poisonDamage = 5; // Damage per tick self.poisonDuration = 480; // 8 seconds at 60fps self.shootCooldown = 0; self.target = null; self.cost = 95; self.canUpgrade = function () { return false; // Poison towers cannot be upgraded }; self.getUpgradeCost = function () { return 0; // Poison towers cannot be upgraded }; self.update = function () { // Find target if (!self.target || !self.target.parent) { self.target = null; var closestUnit = null; var closestDistance = Infinity; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestUnit = unit; } } self.target = closestUnit; } // Apply poison effect if (self.target && self.shootCooldown <= 0) { // Initial damage self.target.takeDamage(self.damage); // Apply poison effect self.target.poisonDamage = self.poisonDamage; self.target.poisonEndTime = LK.ticks + self.poisonDuration; self.target.lastPoisonTick = LK.ticks; // Visual effects LK.effects.flashObject(self.target, 0x228B22, 400); tween(poisonIndicator, { scaleX: 2.2, scaleY: 2.2, alpha: 0.6 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(poisonIndicator, { scaleX: 1.4, scaleY: 1.4, alpha: 1 }, { duration: 200, easing: tween.easeIn }); } }); LK.getSound('shoot').play(); self.shootCooldown = 120; // 2 second cooldown } if (self.shootCooldown > 0) { self.shootCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var ResurrectionTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('resurrectionTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); towerGraphics.tint = 0xFFD700; // Gold color for resurrection tower towerGraphics.scaleX = 1.3; towerGraphics.scaleY = 1.3; // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -60; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -60; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; // Add resurrection symbol effect var resIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); resIndicator.x = 0; resIndicator.y = 0; resIndicator.tint = 0xFFFFFF; // White resurrection symbol resIndicator.scaleX = 2.0; resIndicator.scaleY = 2.0; self.towerType = 'resurrection'; self.health = 150; self.maxHealth = 150; self.range = 250; // Resurrection range self.resurrectionCooldown = 0; self.cost = 200; self.canUpgrade = function () { return false; // Resurrection towers cannot be upgraded }; self.getUpgradeCost = function () { return 0; // Resurrection towers cannot be upgraded }; self.update = function () { // Only attempt resurrection when not on cooldown if (self.resurrectionCooldown <= 0) { // Look for empty slots within range that used to have towers var bestSlot = null; var bestPriority = 0; for (var i = 0; i < towerSlots.length; i++) { var slot = towerSlots[i]; if (!slot.occupied && slot.lastTowerType) { // Check if slot is within resurrection range var distance = Math.sqrt(Math.pow(slot.x - self.x, 2) + Math.pow(slot.y - self.y, 2)); if (distance <= self.range) { // Calculate priority based on tower type and nearby units var priority = 1; var nearbyUnits = 0; for (var j = 0; j < playerUnits.length; j++) { var unit = playerUnits[j]; var unitDistance = Math.sqrt(Math.pow(unit.x - slot.x, 2) + Math.pow(unit.y - slot.y, 2)); if (unitDistance <= 300) { nearbyUnits++; } } // Higher priority for slots with nearby enemies priority += nearbyUnits * 2; // Higher priority for certain tower types if (slot.lastTowerType === 'basic' || slot.lastTowerType === 'fast') { priority += 3; } else if (slot.lastTowerType === 'heavy' || slot.lastTowerType === 'sniper') { priority += 5; } if (priority > bestPriority) { bestPriority = priority; bestSlot = slot; } } } } // Resurrect tower at best slot if (bestSlot) { var newTower = null; // Recreate the tower based on its last type switch (bestSlot.lastTowerType) { case 'basic': newTower = new BasicTower(); break; case 'fast': newTower = new FastTower(); break; case 'heavy': newTower = new HeavyTower(); break; case 'sniper': newTower = new SniperTower(); break; case 'slow': newTower = new SlowTower(); break; case 'poison': newTower = new PoisonTower(); break; case 'laser': newTower = new LaserTower(); break; case 'area': newTower = new AreaTower(); break; case 'heal': newTower = new HealTower(); break; case 'wall': newTower = new Wall(); break; case 'trap': newTower = new Trap(); break; } if (newTower) { // Place the resurrected tower newTower.x = bestSlot.x; newTower.y = bestSlot.y; newTower.slot = bestSlot; // Resurrect at 50% health newTower.health = Math.floor(newTower.maxHealth * 0.5); enemyTowers.push(newTower); game.addChild(newTower); bestSlot.occupied = true; bestSlot.tower = newTower; bestSlot.attachAsset('towerSlot', {}).alpha = 0.1; // Clear the resurrection memory bestSlot.lastTowerType = null; // Initialize health bar if (newTower.updateHealthBar) { newTower.updateHealthBar(); } // Visual resurrection effect tween(resIndicator, { scaleX: 3.5, scaleY: 3.5, alpha: 0.3 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { tween(resIndicator, { scaleX: 2.0, scaleY: 2.0, alpha: 1 }, { duration: 300, easing: tween.easeIn }); } }); LK.effects.flashObject(newTower, 0xFFD700, 800); LK.effects.flashObject(self, 0xFFD700, 400); self.resurrectionCooldown = 1200; // 20 second cooldown } } } if (self.resurrectionCooldown > 0) { self.resurrectionCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var SlowTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('slowTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); towerGraphics.tint = 0x9932CC; // Purple color for slow tower // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -50; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -50; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; // Add slow effect indicator var slowIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); slowIndicator.x = 0; slowIndicator.y = 0; slowIndicator.tint = 0x9932CC; // Purple indicator slowIndicator.scaleX = 1.2; slowIndicator.scaleY = 1.2; self.towerType = 'slow'; self.health = 90; self.maxHealth = 90; self.range = 200; // Large range for slowing self.damage = 10; // Low damage self.slowEffect = 0.5; // Reduces speed by 50% self.slowDuration = 300; // 5 seconds at 60fps self.shootCooldown = 0; self.target = null; self.cost = 85; self.canUpgrade = function () { return false; // Slow towers cannot be upgraded }; self.getUpgradeCost = function () { return 0; // Slow towers cannot be upgraded }; self.update = function () { // Find target if (!self.target || !self.target.parent) { self.target = null; var closestUnit = null; var closestDistance = Infinity; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestUnit = unit; } } self.target = closestUnit; } // Apply slow effect and light damage if (self.target && self.shootCooldown <= 0) { // Apply slow effect if (!self.target.originalSpeed) { self.target.originalSpeed = self.target.speed; } self.target.speed = self.target.originalSpeed * self.slowEffect; self.target.slowEndTime = LK.ticks + self.slowDuration; // Light damage self.target.takeDamage(self.damage); // Visual effects LK.effects.flashObject(self.target, 0x9932CC, 300); tween(slowIndicator, { scaleX: 1.8, scaleY: 1.8 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(slowIndicator, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeIn }); } }); LK.getSound('shoot').play(); self.shootCooldown = 60; // 1 second cooldown } if (self.shootCooldown > 0) { self.shootCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var SniperTower = Container.expand(function () { var self = Container.call(this); var towerGraphics = self.attachAsset('sniperTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -55; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -55; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; self.towerType = 'sniper'; self.health = 120; self.maxHealth = 120; self.range = 300; // Very long range self.damage = 40; self.shootCooldown = 0; self.target = null; self.cost = 125; // Upgrade indicator (initially hidden) var upgradeIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); upgradeIndicator.x = 30; upgradeIndicator.y = -35; upgradeIndicator.visible = false; self.level = 1; self.maxLevel = 3; self.baseRange = 300; self.baseDamage = 40; self.criticalChance = 0; // Chance for critical hits self.pierceCount = 0; // Number of units bullet can pierce through self.range = self.baseRange; self.damage = self.baseDamage; self.updateStats = function () { var multiplier = 1 + (self.level - 1) * 0.7; // 70% increase per level self.range = Math.floor(self.baseRange * multiplier); self.damage = Math.floor(self.baseDamage * multiplier); self.maxHealth = Math.floor(120 * multiplier); self.health = Math.min(self.health, self.maxHealth); self.criticalChance = (self.level - 1) * 0.25; // 25% crit chance per level above 1 self.pierceCount = self.level - 1; // Pierce through multiple enemies // Special abilities for max level if (self.level === 3) { self.executeThreshold = 0.25; // Execute enemies below 25% health } // Update visual appearance based on level if (self.level === 2) { towerGraphics.tint = 0x708090; // Slate gray upgradeIndicator.visible = true; upgradeIndicator.tint = 0xffd700; // Gold } else if (self.level === 3) { towerGraphics.tint = 0x2F4F4F; // Dark slate gray upgradeIndicator.visible = true; upgradeIndicator.tint = 0xff4500; // Orange red } }; self.canUpgrade = function () { return self.level < self.maxLevel; }; self.getUpgradeCost = function () { return 80 + (self.level - 1) * 50; // Cost increases with level }; self.upgrade = function () { if (self.canUpgrade()) { self.level++; self.updateStats(); LK.effects.flashObject(self, 0xffd700, 500); return true; } return false; }; self.update = function () { // Find target (prioritize units furthest away) if (!self.target || !self.target.parent) { self.target = null; var bestUnit = null; var longestDistance = 0; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.range && distance > longestDistance) { longestDistance = distance; bestUnit = unit; } } self.target = bestUnit; } // Shoot at target with precision, critical hits, and piercing if (self.target && self.shootCooldown <= 0) { var bullet = new TowerBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = self.target; // Check for critical hit var isCritical = Math.random() < self.criticalChance; bullet.damage = isCritical ? self.damage * 2 : self.damage; // Execute ability for level 3 - instantly kill low health enemies if (self.level === 3) { var targetHealthPercent = self.target.health / self.target.maxHealth; if (targetHealthPercent <= self.executeThreshold) { bullet.execute = true; // Mark bullet for execution bullet.damage = self.target.health + 1000; // Ensure instant kill LK.effects.flashObject(self, 0xFF0000, 500); // Red flash for execute } } bullet.speed = 10; // Faster bullet bullet.pierceCount = self.pierceCount; // Add pierce capability bullet.pierceRemaining = self.pierceCount; if (isCritical) { // Visual effect for critical hit LK.effects.flashObject(self, 0xFFD700, 300); } towerBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.shootCooldown = 150; // 2.5 second cooldown } if (self.shootCooldown > 0) { self.shootCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Tower is destroyed } return false; }; return self; }); var SniperUnit = Container.expand(function () { var self = Container.call(this); var unitGraphics = self.attachAsset('sniperUnitAsset', { anchorX: 0.5, anchorY: 0.5 }); unitGraphics.tint = 0x4B0082; // Indigo color for sniper unit unitGraphics.scaleX = 1.0; unitGraphics.scaleY = 1.3; // Taller appearance // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -42; healthBarBg.scaleX = 0.9; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -42; healthBar.scaleX = 0.9; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent * 0.9; if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; } else { healthBar.tint = 0xff0000; } }; self.unitType = 'sniper'; self.health = 50; self.maxHealth = 50; self.speed = 1.2; self.damage = 35; self.range = 250; // Very long range self.cost = 80; self.shootCooldown = 0; self.target = null; self.lastX = 0; self.lastY = 0; self.currentWaypoint = 0; self.isMovingToWaypoint = false; // Create path through tower grid var pathWaypoints = []; pathWaypoints.push({ x: 400, y: self.y }); // Tower grid parameters var gridStartX = 800; var gridStartY = 600; var gridSpacingX = 200; var gridSpacingY = 200; var gridRows = 8; var gridCols = 4; // Serpentine path through towers for (var col = 0; col < gridCols; col++) { if (col % 2 === 0) { for (var row = 0; row < gridRows; row++) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } else { for (var row = gridRows - 1; row >= 0; row--) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } } pathWaypoints.push({ x: 1600, y: 1000 }); self.pathWaypoints = pathWaypoints; self.followPath = function () { if (self.isMovingToWaypoint) return; if (self.currentWaypoint >= self.pathWaypoints.length) { self.x += self.speed; return; } var waypoint = self.pathWaypoints[self.currentWaypoint]; var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2)); var duration = distance / self.speed * 16.67; self.isMovingToWaypoint = true; tween(self, { x: waypoint.x, y: waypoint.y }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { self.currentWaypoint++; self.isMovingToWaypoint = false; } }); }; self.update = function () { self.lastX = self.x; self.lastY = self.y; if (!self.target || !self.target.parent) { self.target = null; var closestTarget = null; var closestDistance = Infinity; // Prioritize towers over base due to long range for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower && tower.parent) { var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2)); if (towerDistance <= self.range && towerDistance < closestDistance) { closestDistance = towerDistance; closestTarget = tower; } } } // Check base if no towers in range if (!closestTarget && enemyBase && enemyBase.parent) { var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2)); if (baseDistance <= self.range) { closestTarget = enemyBase; closestDistance = baseDistance; } } self.target = closestTarget; } if (self.target && self.shootCooldown <= 0) { var bullet = new UnitBullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = self.target; bullet.damage = self.damage; bullet.speed = 12; // Faster bullet unitBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.shootCooldown = 120; // Slow shooting rate } if (self.shootCooldown > 0) { self.shootCooldown--; } if (!self.target) { self.followPath(); } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); if (self.health <= 0) { return true; } return false; }; return self; }); var TowerBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('towerBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 6; self.target = null; self.damage = 30; self.update = function () { if (!self.target || !self.target.parent) { // Target destroyed, remove bullet return; } // Move towards target var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { // Hit target if (self.target.takeDamage) { self.target.takeDamage(self.damage); LK.getSound('hit').play(); // Handle splash damage if (self.splashRadius && self.splashRadius > 0) { for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; if (unit !== self.target && unit.parent) { var splashDistance = Math.sqrt(Math.pow(unit.x - self.target.x, 2) + Math.pow(unit.y - self.target.y, 2)); if (splashDistance <= self.splashRadius) { unit.takeDamage(self.splashDamage || Math.floor(self.damage * 0.6)); LK.effects.flashObject(unit, 0xFF6600, 200); } } } // Visual splash effect LK.effects.flashObject(self.target, 0xFF6600, 400); } } // Handle piercing if (self.pierceRemaining && self.pierceRemaining > 0) { self.pierceRemaining--; // Find next target in line var nextTarget = null; var minDistance = Infinity; var bulletDirection = { x: (self.target.x - self.x) / distance, y: (self.target.y - self.y) / distance }; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; if (unit !== self.target && unit.parent) { var unitDistance = Math.sqrt(Math.pow(unit.x - self.target.x, 2) + Math.pow(unit.y - self.target.y, 2)); // Check if unit is roughly in the same direction var unitDirection = { x: (unit.x - self.target.x) / unitDistance, y: (unit.y - self.target.y) / unitDistance }; var dotProduct = bulletDirection.x * unitDirection.x + bulletDirection.y * unitDirection.y; if (dotProduct > 0.7 && unitDistance < minDistance && unitDistance < 150) { // Within piercing range minDistance = unitDistance; nextTarget = unit; } } } if (nextTarget) { self.target = nextTarget; // Continue moving towards new target return; } } return; // Bullet will be removed } // Normalize and move self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; }; return self; }); var TowerSlot = Container.expand(function () { var self = Container.call(this); var slotGraphics = self.attachAsset('towerSlot', { anchorX: 0.5, anchorY: 0.5 }); slotGraphics.alpha = 0.3; self.occupied = false; self.tower = null; self.placeTower = function () { if (!self.occupied) { var tower = new EnemyTower(); tower.x = self.x; tower.y = self.y; tower.slot = self; tower.updateStats(); // Initialize stats properly enemyTowers.push(tower); game.addChild(tower); self.occupied = true; self.tower = tower; slotGraphics.alpha = 0.1; return tower; } return null; }; self.removeTower = function () { if (self.occupied && self.tower) { self.occupied = false; self.tower = null; slotGraphics.alpha = 0.3; } }; return self; }); var Trap = Container.expand(function () { var self = Container.call(this); var trapGraphics = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); trapGraphics.tint = 0x8B4513; // Brown color for trap trapGraphics.scaleX = 0.8; trapGraphics.scaleY = 0.8; trapGraphics.alpha = 0.6; // Semi-transparent to show it's hidden // Upgrade indicator (initially hidden) var upgradeIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); upgradeIndicator.x = 15; upgradeIndicator.y = -15; upgradeIndicator.visible = false; self.towerType = 'trap'; self.level = 1; self.maxLevel = 3; self.health = 1; // Traps are destroyed after one use self.maxHealth = 1; self.baseDamage = 60; self.baseRange = 40; self.damage = self.baseDamage; // High damage when triggered self.range = self.baseRange; // Detection range for triggering self.triggered = false; self.cost = 50; self.updateStats = function () { var multiplier = 1 + (self.level - 1) * 0.8; // 80% increase per level self.damage = Math.floor(self.baseDamage * multiplier); self.range = Math.floor(self.baseRange * multiplier); // Update visual appearance based on level if (self.level === 2) { trapGraphics.tint = 0xA0522D; // Darker brown trapGraphics.scaleX = 1.0; trapGraphics.scaleY = 1.0; upgradeIndicator.visible = true; upgradeIndicator.tint = 0xffd700; // Gold } else if (self.level === 3) { trapGraphics.tint = 0x8B0000; // Dark red trapGraphics.scaleX = 1.2; trapGraphics.scaleY = 1.2; upgradeIndicator.visible = true; upgradeIndicator.tint = 0xff4500; // Orange red } }; self.canUpgrade = function () { return self.level < self.maxLevel && !self.triggered; }; self.getUpgradeCost = function () { return 30 + (self.level - 1) * 20; // Cost increases with level }; self.upgrade = function () { if (self.canUpgrade()) { self.level++; self.updateStats(); LK.effects.flashObject(self, 0xffd700, 500); return true; } return false; }; self.update = function () { if (self.triggered) return; // Already triggered // Check for player units in range for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.range) { // Trigger trap self.triggered = true; unit.takeDamage(self.damage); // Visual explosion effect LK.effects.flashObject(self, 0xFF6600, 400); LK.effects.flashObject(unit, 0xFF0000, 300); // Create explosion visual trapGraphics.tint = 0xFF6600; trapGraphics.alpha = 1.0; tween(trapGraphics, { scaleX: 2.0, scaleY: 2.0, alpha: 0 }, { duration: 400, easing: tween.easeOut }); LK.getSound('hit').play(); return true; // Trap is destroyed } } }; self.takeDamage = function (damage) { // Traps take minimal damage from ranged attacks self.health -= Math.max(1, Math.floor(damage * 0.1)); if (self.health <= 0) { return true; // Trap is destroyed } return false; }; return self; }); var UnitBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('unitBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.target = null; self.damage = 25; self.update = function () { if (!self.target || !self.target.parent) { // Target destroyed, remove bullet return; } // Move towards target var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { // Hit target if (self.target.takeDamage) { self.target.takeDamage(self.damage); LK.getSound('hit').play(); } return; // Bullet will be removed } // Normalize and move self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; }; return self; }); var Wall = Container.expand(function () { var self = Container.call(this); var wallGraphics = self.attachAsset('basicTowerAsset', { anchorX: 0.5, anchorY: 0.5 }); wallGraphics.tint = 0x696969; // Gray color for wall wallGraphics.scaleX = 1.2; wallGraphics.scaleY = 0.8; // Make it look more like a wall // Health bar background var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.x = 0; healthBarBg.y = -40; // Health bar foreground var healthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBar.x = 0; healthBar.y = -40; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBar.scaleX = healthPercent; if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } }; // Upgrade indicator (initially hidden) var upgradeIndicator = self.attachAsset('upgradeIndicator', { anchorX: 0.5, anchorY: 0.5 }); upgradeIndicator.x = 25; upgradeIndicator.y = -25; upgradeIndicator.visible = false; self.towerType = 'wall'; self.level = 1; self.maxLevel = 3; self.baseHealth = 300; self.baseBlockRadius = 60; self.health = self.baseHealth; // High health to absorb damage self.maxHealth = self.baseHealth; self.blockRadius = self.baseBlockRadius; // Radius that blocks unit movement self.cost = 40; self.updateStats = function () { var multiplier = 1 + (self.level - 1) * 0.6; // 60% increase per level self.maxHealth = Math.floor(self.baseHealth * multiplier); self.health = Math.min(self.health, self.maxHealth); self.blockRadius = Math.floor(self.baseBlockRadius * multiplier); // Update visual appearance based on level if (self.level === 2) { wallGraphics.tint = 0x808080; // Darker gray wallGraphics.scaleX = 1.4; wallGraphics.scaleY = 1.0; upgradeIndicator.visible = true; upgradeIndicator.tint = 0xffd700; // Gold } else if (self.level === 3) { wallGraphics.tint = 0x2F4F4F; // Dark slate gray wallGraphics.scaleX = 1.6; wallGraphics.scaleY = 1.2; upgradeIndicator.visible = true; upgradeIndicator.tint = 0xff4500; // Orange red } }; self.canUpgrade = function () { return self.level < self.maxLevel; }; self.getUpgradeCost = function () { return 25 + (self.level - 1) * 15; // Cost increases with level }; self.upgrade = function () { if (self.canUpgrade()) { self.level++; self.updateStats(); LK.effects.flashObject(self, 0xffd700, 500); return true; } return false; }; self.update = function () { // Mine ability for level 3 if (self.level === 3 && self.mineCooldown <= 0) { // Check if any enemies are touching the wall var enemiesInContact = 0; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.blockRadius + 20) { enemiesInContact++; } } // Trigger mine explosion if enemies are touching the wall if (enemiesInContact > 0) { // Damage all enemies within mine radius for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2)); if (distance <= self.mineRadius) { unit.takeDamage(self.mineDamage); LK.effects.flashObject(unit, 0xFF6600, 400); } } // Visual mine explosion effect wallGraphics.tint = 0xFF6600; // Orange explosion color LK.effects.flashObject(self, 0xFF6600, 600); // Screen flash effect for mine explosion LK.effects.flashScreen(0xFF6600, 200); self.mineCooldown = 600; // 10 second cooldown // Return to normal color after effect var self_ref = self; LK.setTimeout(function () { self_ref.wallGraphics.tint = 0x2F4F4F; // Back to level 3 color }, 600); } } // Update mine cooldown if (self.mineCooldown > 0) { self.mineCooldown--; } }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0) { return true; // Wall is destroyed } return false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2d5016 }); /**** * Game Code ****/ // Game arrays to track entities // Initialize game assets var playerUnits = []; var enemyTowers = []; var unitBullets = []; var towerBullets = []; var towerSlots = []; var enemyBase; // Game resources var coins = 100; var unitCost = 250; // Ability system var abilities = { lightning: { name: 'Lightning Strike', cooldown: 900, // 15 seconds currentCooldown: 0, description: 'Strikes all towers with lightning' }, heal: { name: 'Mass Heal', cooldown: 600, // 10 seconds currentCooldown: 0, description: 'Heals all your units to full health' }, freeze: { name: 'Time Freeze', cooldown: 1200, // 20 seconds currentCooldown: 0, description: 'Freezes all towers for 5 seconds' }, boost: { name: 'Unit Boost', cooldown: 480, // 8 seconds currentCooldown: 0, description: 'Doubles unit damage for 8 seconds' } }; // UI Elements var coinsText = new Text2('Coins: ' + coins, { size: 80, fill: 0xFFD700 }); coinsText.anchor.set(1, 0); coinsText.x = -20; // Small margin from right edge coinsText.y = 20; // Small margin from top LK.gui.topRight.addChild(coinsText); var coinIcon = LK.getAsset('coinIcon', { anchorX: 0.5, anchorY: 0.5 }); coinIcon.x = -coinsText.width - 60; // Position to left of text coinIcon.y = 45; // Align with text LK.gui.topRight.addChild(coinIcon); var healthText = new Text2('Base Health: 500', { size: 50, fill: 0xFFFFFF }); healthText.anchor.set(0.5, 0); LK.gui.top.addChild(healthText); // Unit spawn buttons var basicButton = new Text2('Basic (300)', { size: 40, fill: 0x87CEEB }); basicButton.anchor.set(0.5, 1); basicButton.x = -300; basicButton.interactive = true; basicButton.down = function () { if (coins >= 300) { spawnUnit('basic'); } }; LK.gui.bottom.addChild(basicButton); var fastButton = new Text2('Fast (450)', { size: 40, fill: 0x32CD32 }); fastButton.anchor.set(0.5, 1); fastButton.x = -100; fastButton.interactive = true; fastButton.down = function () { if (coins >= 450) { spawnUnit('fast'); } }; LK.gui.bottom.addChild(fastButton); var heavyButton = new Text2('Heavy (650)', { size: 40, fill: 0x8B4513 }); heavyButton.anchor.set(0.5, 1); heavyButton.x = 100; heavyButton.interactive = true; heavyButton.down = function () { if (coins >= 650) { spawnUnit('heavy'); } }; LK.gui.bottom.addChild(heavyButton); var sniperButton = new Text2('Sniper (600)', { size: 40, fill: 0x4B0082 }); sniperButton.anchor.set(0.5, 1); sniperButton.x = 200; sniperButton.interactive = true; sniperButton.down = function () { if (coins >= 600) { spawnUnit('sniper'); } }; LK.gui.bottom.addChild(sniperButton); var meleeButton = new Text2('Melee (250)', { size: 40, fill: 0xFF6347 }); meleeButton.anchor.set(0.5, 1); meleeButton.x = 400; meleeButton.interactive = true; meleeButton.down = function () { if (coins >= 250) { spawnUnit('melee'); } }; LK.gui.bottom.addChild(meleeButton); // Ability buttons var lightningButton = new Text2('Lightning', { size: 35, fill: 0xFFFF00 }); lightningButton.anchor.set(0, 0.5); lightningButton.x = 20; lightningButton.y = -200; lightningButton.interactive = true; lightningButton.down = function () { useAbility('lightning'); }; LK.gui.left.addChild(lightningButton); var healButton = new Text2('Heal All', { size: 35, fill: 0x00FF00 }); healButton.anchor.set(0, 0.5); healButton.x = 20; healButton.y = -100; healButton.interactive = true; healButton.down = function () { useAbility('heal'); }; LK.gui.left.addChild(healButton); var freezeButton = new Text2('Freeze', { size: 35, fill: 0x00FFFF }); freezeButton.anchor.set(0, 0.5); freezeButton.x = 20; freezeButton.y = 0; freezeButton.interactive = true; freezeButton.down = function () { useAbility('freeze'); }; LK.gui.left.addChild(freezeButton); var boostButton = new Text2('Boost', { size: 35, fill: 0xFF8C00 }); boostButton.anchor.set(0, 0.5); boostButton.x = 20; boostButton.y = 100; boostButton.interactive = true; boostButton.down = function () { useAbility('boost'); }; LK.gui.left.addChild(boostButton); // Create enemy base enemyBase = game.addChild(new EnemyBase()); enemyBase.x = 1800; enemyBase.y = 2732 / 2; // Create tower slots in a grid var gridStartX = 800; var gridStartY = 600; var gridSpacingX = 200; var gridSpacingY = 200; var gridRows = 8; var gridCols = 4; for (var row = 0; row < gridRows; row++) { for (var col = 0; col < gridCols; col++) { var slot = new TowerSlot(); slot.x = gridStartX + col * gridSpacingX; slot.y = gridStartY + row * gridSpacingY; towerSlots.push(slot); game.addChild(slot); } } // Initially place some towers in random slots var initialTowerCount = 8; var placedTowers = 0; while (placedTowers < initialTowerCount && placedTowers < towerSlots.length) { var randomSlotIndex = Math.floor(Math.random() * towerSlots.length); var slot = towerSlots[randomSlotIndex]; if (slot.placeTower()) { // Initialize health bar for the newly placed tower if (slot.tower && slot.tower.updateHealthBar) { slot.tower.updateHealthBar(); } placedTowers++; } } function useAbility(abilityType) { var ability = abilities[abilityType]; if (!ability || ability.currentCooldown > 0) { return false; } ability.currentCooldown = ability.cooldown; // Enhanced visual feedback - pulse effect on ability button and coins switch (abilityType) { case 'lightning': // Enhanced lightning visual effects tween(lightningButton, { scaleX: 1.3, scaleY: 1.3, alpha: 0.7 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(lightningButton, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 300, easing: tween.bounceOut }); } }); // Strike all towers with lightning for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; tower.takeDamage(80); LK.effects.flashObject(tower, 0xFFFF00, 500); // Add scaling effect to towers when hit by lightning tween(tower, { scaleX: 1.2, scaleY: 1.2 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(tower, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.elasticOut }); } }); } // Strike the base too if (enemyBase) { enemyBase.takeDamage(120); LK.effects.flashObject(enemyBase, 0xFFFF00, 800); tween(enemyBase, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(enemyBase, { scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.elasticOut }); } }); } LK.effects.flashScreen(0xFFFF00, 300); break; case 'heal': // Enhanced heal visual effects tween(healButton, { scaleX: 1.3, scaleY: 1.3, tint: 0x00FFFF }, { duration: 250, easing: tween.easeOut, onFinish: function onFinish() { tween(healButton, { scaleX: 1.0, scaleY: 1.0, tint: 0x00FF00 }, { duration: 400, easing: tween.bounceOut }); } }); // Heal all units to full health for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; unit.health = unit.maxHealth; unit.updateHealthBar(); LK.effects.flashObject(unit, 0x00FF00, 400); // Add pulsing effect to healed units tween(unit, { scaleX: 1.15, scaleY: 1.15, alpha: 0.8 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(unit, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 300, easing: tween.bounceOut }); } }); } LK.effects.flashScreen(0x00FF00, 200); break; case 'freeze': // Enhanced freeze visual effects tween(freezeButton, { scaleX: 1.4, scaleY: 1.4, rotation: 0.2 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(freezeButton, { scaleX: 1.0, scaleY: 1.0, rotation: 0 }, { duration: 500, easing: tween.elasticOut }); } }); // Freeze all towers for 5 seconds for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; tower.frozenUntil = LK.ticks + 300; // 5 seconds tower.originalTint = tower.tint || 0xFFFFFF; LK.effects.flashObject(tower, 0x00FFFF, 600); // Add shaking effect to frozen towers tween(tower, { x: tower.x + 5, y: tower.y + 5 }, { duration: 100, easing: tween.easeInOut, onFinish: function onFinish() { tween(tower, { x: tower.x - 5, y: tower.y - 5 }, { duration: 100, easing: tween.easeInOut, onFinish: function onFinish() { tween(tower, { x: tower.x, y: tower.y }, { duration: 100, easing: tween.easeOut }); } }); } }); } // Freeze the base too if (enemyBase) { enemyBase.frozenUntil = LK.ticks + 300; LK.effects.flashObject(enemyBase, 0x00FFFF, 600); } LK.effects.flashScreen(0x00FFFF, 400); break; case 'boost': // Enhanced boost visual effects tween(boostButton, { scaleX: 1.5, scaleY: 1.5, tint: 0xFFD700 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(boostButton, { scaleX: 1.0, scaleY: 1.0, tint: 0xFF8C00 }, { duration: 400, easing: tween.bounceOut }); } }); // Double unit damage for 8 seconds for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; if (!unit.boostedUntil) { unit.originalDamage = unit.damage; } unit.damage = unit.originalDamage * 2; unit.boostedUntil = LK.ticks + 480; // 8 seconds LK.effects.flashObject(unit, 0xFF8C00, 500); // Add growing effect to boosted units tween(unit, { scaleX: 1.2, scaleY: 1.2, tint: 0xFFD700 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(unit, { scaleX: 1.0, scaleY: 1.0, tint: 0xFFFFFF }, { duration: 200, easing: tween.easeInOut }); } }); } LK.effects.flashScreen(0xFF8C00, 300); break; } return true; } function spawnUnit(unitType) { if (!unitType) unitType = 'basic'; var unitClass = null; var cost = 40; switch (unitType) { case 'basic': unitClass = BasicUnit; cost = 300; break; case 'fast': unitClass = FastUnit; cost = 450; break; case 'heavy': unitClass = HeavyUnit; cost = 650; break; case 'sniper': unitClass = SniperUnit; cost = 600; break; case 'melee': unitClass = MeleeUnit; cost = 250; break; default: unitClass = BasicUnit; cost = 200; break; } if (coins >= cost && unitClass) { coins -= cost; coinsText.setText('Coins: ' + coins); coinIcon.x = -coinsText.width - 60; // Spawn 5 units for (var unitIndex = 0; unitIndex < 5; unitIndex++) { var unit = new unitClass(); unit.x = 100; unit.y = 500 + (playerUnits.length + unitIndex) % 8 * 100; // Create path that goes through all tower positions var pathWaypoints = []; // Start from spawn position pathWaypoints.push({ x: 400, y: unit.y }); // Add waypoints to pass through tower grid var gridStartX = 800; var gridStartY = 600; var gridSpacingX = 200; var gridSpacingY = 200; var gridRows = 8; var gridCols = 4; // Create serpentine path through tower grid for (var col = 0; col < gridCols; col++) { if (col % 2 === 0) { // Go down for even columns for (var row = 0; row < gridRows; row++) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } else { // Go up for odd columns for (var row = gridRows - 1; row >= 0; row--) { pathWaypoints.push({ x: gridStartX + col * gridSpacingX, y: gridStartY + row * gridSpacingY }); } } } // Final waypoint towards the base pathWaypoints.push({ x: 1600, y: 1366 }); unit.pathWaypoints = pathWaypoints; playerUnits.push(unit); game.addChild(unit); unit.updateHealthBar(); } LK.getSound('unitSpawn').play(); } } // Coin generation timer var coinTimer = 0; // Enemy AI system var enemyAI = { coins: 400, towerCost: 75, lastTowerPlacement: 0, placementCooldown: 180, // 3 seconds at 60fps update: function update() { // Generate coins for enemy AI if (LK.ticks % 120 === 0) { // Every 2 seconds this.coins += 75; } // Force spending if AI has too many coins var forceSpend = this.coins >= 1000; // Check if should place new towers if (this.coins >= this.towerCost && LK.ticks - this.lastTowerPlacement > this.placementCooldown || forceSpend) { this.considerTowerPlacement(); } // Upgrade existing towers (with forced spending) this.considerTowerUpgrades(forceSpend); // Repair damaged towers this.repairTowers(); }, considerTowerPlacement: function considerTowerPlacement() { // First priority: Check if we need heal towers near damaged towers var needsHealTower = false; var healSlot = null; var damagedTowerNeedingHeal = null; // Find damaged towers that need healing for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower.health < tower.maxHealth * 0.6) { // If tower is below 60% health // Check if there's already a heal tower nearby var hasHealerNearby = false; for (var j = 0; j < enemyTowers.length; j++) { var otherTower = enemyTowers[j]; if (otherTower.towerType === 'heal') { var distance = Math.sqrt(Math.pow(tower.x - otherTower.x, 2) + Math.pow(tower.y - otherTower.y, 2)); if (distance <= 200) { // Within healing range hasHealerNearby = true; break; } } } if (!hasHealerNearby) { damagedTowerNeedingHeal = tower; needsHealTower = true; break; } } } // If we need a heal tower and can afford it, find best slot near damaged tower if (needsHealTower && damagedTowerNeedingHeal && this.coins >= 110) { var bestDistance = Infinity; var forceSpendHeal = this.coins >= 1000; for (var i = 0; i < towerSlots.length; i++) { var slot = towerSlots[i]; if (!slot.occupied) { // Check if there are any units too close to this heal slot (within 120 pixels) var hasUnitsNearbyHeal = false; for (var k = 0; k < playerUnits.length; k++) { var unit = playerUnits[k]; var unitDistance = Math.sqrt(Math.pow(unit.x - slot.x, 2) + Math.pow(unit.y - slot.y, 2)); if (unitDistance < 120) { // Don't build heal tower if units are too close hasUnitsNearbyHeal = true; break; } } // Skip this slot if units are too close (unless forced to spend) if (hasUnitsNearbyHeal && !forceSpendHeal) { continue; } var distance = Math.sqrt(Math.pow(damagedTowerNeedingHeal.x - slot.x, 2) + Math.pow(damagedTowerNeedingHeal.y - slot.y, 2)); if (distance < bestDistance && distance <= 200) { // Within heal range bestDistance = distance; healSlot = slot; } } } // Place heal tower if we found a good slot if (healSlot) { var tower = new HealTower(); tower.x = healSlot.x; tower.y = healSlot.y; tower.slot = healSlot; enemyTowers.push(tower); game.addChild(tower); healSlot.occupied = true; healSlot.tower = tower; healSlot.attachAsset('towerSlot', {}).alpha = 0.1; this.coins -= 110; this.lastTowerPlacement = LK.ticks; // Initialize health bar if (tower.updateHealthBar) { tower.updateHealthBar(); } // Visual feedback LK.effects.flashObject(healSlot, 0x00ff00, 500); return; // Exit early after placing heal tower } } // Second priority: Find empty slots closest to player units for combat towers var bestSlot = null; var minDistance = Infinity; var forceSpend = this.coins >= 1000; for (var i = 0; i < towerSlots.length; i++) { var slot = towerSlots[i]; if (!slot.occupied) { // Check if there are any units too close to this slot (within 120 pixels) var hasUnitsNearby = false; for (var k = 0; k < playerUnits.length; k++) { var unit = playerUnits[k]; var unitDistance = Math.sqrt(Math.pow(unit.x - slot.x, 2) + Math.pow(unit.y - slot.y, 2)); if (unitDistance < 120) { // Don't build if units are too close hasUnitsNearby = true; break; } } // Skip this slot if units are too close (unless forced to spend) if (hasUnitsNearby && !forceSpend) { continue; } // Calculate average distance to all player units var totalDistance = 0; var unitCount = 0; for (var j = 0; j < playerUnits.length; j++) { var unit = playerUnits[j]; var distance = Math.sqrt(Math.pow(unit.x - slot.x, 2) + Math.pow(unit.y - slot.y, 2)); totalDistance += distance; unitCount++; } if (unitCount > 0) { var avgDistance = totalDistance / unitCount; // Force placement if we have too many coins, otherwise use distance restriction if (avgDistance < minDistance && (avgDistance < 400 || forceSpend)) { minDistance = avgDistance; bestSlot = slot; } } else if (forceSpend && !bestSlot) { // If forced to spend and no players around, pick any empty slot bestSlot = slot; } } } // Place tower at best location if (bestSlot) { // Strategic placement: Check if we should place walls or traps based on unit density var nearbyUnits = 0; var averageUnitDistance = 0; for (var k = 0; k < playerUnits.length; k++) { var unit = playerUnits[k]; var unitDist = Math.sqrt(Math.pow(unit.x - bestSlot.x, 2) + Math.pow(unit.y - bestSlot.y, 2)); if (unitDist <= 300) { nearbyUnits++; averageUnitDistance += unitDist; } } if (nearbyUnits > 0) { averageUnitDistance /= nearbyUnits; } // Choose tower type based on strategy and available coins var towerTypes = []; // Add defensive structures first if units are close if (nearbyUnits >= 3 && this.coins >= 40) towerTypes.push('wall'); if (nearbyUnits >= 2 && averageUnitDistance < 150 && this.coins >= 50) towerTypes.push('trap'); // Add combat towers if (this.coins >= 75) towerTypes.push('basic'); if (this.coins >= 85) towerTypes.push('slow'); if (this.coins >= 95) towerTypes.push('poison'); if (this.coins >= 100) towerTypes.push('fast'); if (this.coins >= 125) towerTypes.push('sniper'); if (this.coins >= 130) towerTypes.push('area'); if (this.coins >= 140) towerTypes.push('laser'); if (this.coins >= 150) towerTypes.push('heavy'); if (this.coins >= 200) towerTypes.push('resurrection'); if (towerTypes.length > 0) { var randomType = towerTypes[Math.floor(Math.random() * towerTypes.length)]; var tower = null; var cost = 75; switch (randomType) { case 'wall': tower = new Wall(); cost = 40; break; case 'trap': tower = new Trap(); cost = 50; break; case 'basic': tower = new BasicTower(); cost = 75; break; case 'slow': tower = new SlowTower(); cost = 85; break; case 'poison': tower = new PoisonTower(); cost = 95; break; case 'fast': tower = new FastTower(); cost = 100; break; case 'sniper': tower = new SniperTower(); cost = 125; break; case 'area': tower = new AreaTower(); cost = 130; break; case 'laser': tower = new LaserTower(); cost = 140; break; case 'heavy': tower = new HeavyTower(); cost = 150; break; case 'resurrection': tower = new ResurrectionTower(); cost = 200; break; } if (tower) { tower.x = bestSlot.x; tower.y = bestSlot.y; tower.slot = bestSlot; enemyTowers.push(tower); game.addChild(tower); bestSlot.occupied = true; bestSlot.tower = tower; bestSlot.attachAsset('towerSlot', {}).alpha = 0.1; this.coins -= cost; this.lastTowerPlacement = LK.ticks; // Initialize health bar for towers that have them if (tower.updateHealthBar) { tower.updateHealthBar(); } // Visual feedback var feedbackColor = 0xff0000; // Red for combat towers if (randomType === 'wall') feedbackColor = 0x696969; // Gray for walls if (randomType === 'trap') feedbackColor = 0x8B4513; // Brown for traps LK.effects.flashObject(bestSlot, feedbackColor, 500); } } } }, considerTowerUpgrades: function considerTowerUpgrades(forceSpend) { // Find towers that can be upgraded and are in combat zones var upgradeTargets = []; for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower.canUpgrade && tower.canUpgrade()) { var upgradeCost = tower.getUpgradeCost(); if (this.coins >= upgradeCost) { // Check if tower is in an active combat zone (has nearby player units) var nearbyUnits = 0; var priority = 0; for (var j = 0; j < playerUnits.length; j++) { var unit = playerUnits[j]; var distance = Math.sqrt(Math.pow(unit.x - tower.x, 2) + Math.pow(unit.y - tower.y, 2)); // Special range consideration for traps and walls var checkRange = tower.range + 100; if (tower.towerType === 'trap') { checkRange = tower.range + 50; // Shorter range check for traps } else if (tower.towerType === 'wall') { checkRange = tower.blockRadius + 80; // Check block radius for walls } if (distance <= checkRange) { nearbyUnits++; } } // Calculate upgrade priority with special consideration for traps and walls if (nearbyUnits > 0) { priority = nearbyUnits * 10 + (tower.maxLevel - tower.level + 1) * 5; // Higher priority for traps and walls in active zones if (tower.towerType === 'trap' || tower.towerType === 'wall') { priority += nearbyUnits * 5; // Extra priority for defensive structures } } else if (this.coins > upgradeCost * 3 || forceSpend) { priority = 1; // Low priority for non-combat upgrades } if (priority > 0) { upgradeTargets.push({ tower: tower, cost: upgradeCost, priority: priority }); } } } } // Sort by priority and upgrade the best candidate if (upgradeTargets.length > 0) { upgradeTargets.sort(function (a, b) { return b.priority - a.priority; }); var bestTarget = upgradeTargets[0]; if (bestTarget.tower.upgrade && bestTarget.tower.upgrade()) { this.coins -= bestTarget.cost; // Visual feedback LK.effects.flashObject(bestTarget.tower, 0xFFD700, 600); } } }, repairTowers: function repairTowers() { // Find damaged towers and repair them for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower.health < tower.maxHealth * 0.5 && this.coins >= 25) { tower.health = Math.min(tower.health + 25, tower.maxHealth); this.coins -= 25; LK.effects.flashObject(tower, 0x00ff00, 300); break; // Only repair one tower per update } } } }; // Touch controls for spawning units game.down = function (x, y, obj) { if (coins >= 300) { spawnUnit('basic'); } }; game.update = function () { // Update enemy AI enemyAI.update(); // Generate coins over time coinTimer++; if (coinTimer >= 180) { // Every 3 seconds coins += 50; coinsText.setText('Coins: ' + coins); coinIcon.x = -coinsText.width - 60; // Update icon position coinTimer = 0; } // Update unit bullets for (var i = unitBullets.length - 1; i >= 0; i--) { var bullet = unitBullets[i]; if (!bullet.target || !bullet.target.parent) { bullet.destroy(); unitBullets.splice(i, 1); continue; } var dx = bullet.target.x - bullet.x; var dy = bullet.target.y - bullet.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 15) { // Hit target var isDead = bullet.target.takeDamage(bullet.damage); if (isDead) { // Remove from appropriate array if (bullet.target === enemyBase) { // Player wins! LK.effects.flashScreen(0x00ff00, 2000); LK.showYouWin(); return; } else { // Remove tower for (var j = 0; j < enemyTowers.length; j++) { if (enemyTowers[j] === bullet.target) { // Calculate money reward based on tower type and level var moneyReward = 225; // Base reward (75 * 3) if (enemyTowers[j].towerType) { switch (enemyTowers[j].towerType) { case 'wall': moneyReward = 60 + (enemyTowers[j].level ? (enemyTowers[j].level - 1) * 45 : 0); break; case 'trap': moneyReward = 75 + (enemyTowers[j].level ? (enemyTowers[j].level - 1) * 60 : 0); break; case 'slow': moneyReward = 255; break; case 'poison': moneyReward = 285; break; case 'fast': moneyReward = 300; break; case 'heal': moneyReward = 330; break; case 'sniper': moneyReward = 375; break; case 'area': moneyReward = 390; break; case 'laser': moneyReward = 420; break; case 'heavy': moneyReward = 450; break; case 'resurrection': moneyReward = 600; break; default: moneyReward = 225; break; } } else if (enemyTowers[j].level) { // For upgradeable towers, reward based on level moneyReward = 225 + (enemyTowers[j].level - 1) * 150; } // Clean up slot if tower has one if (enemyTowers[j].slot) { // Remember tower type for potential resurrection enemyTowers[j].slot.lastTowerType = enemyTowers[j].towerType; enemyTowers[j].slot.removeTower(); } enemyTowers[j].destroy(); enemyTowers.splice(j, 1); coins += moneyReward; // Money reward for destroying tower coinsText.setText('Coins: ' + coins); coinIcon.x = -coinsText.width - 60; // Update icon position break; } } } } bullet.destroy(); unitBullets.splice(i, 1); } } // Handle unit status effects (slow, poison) for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; // Handle slow effect restoration if (unit.slowEndTime && LK.ticks >= unit.slowEndTime) { if (unit.originalSpeed) { unit.speed = unit.originalSpeed; unit.originalSpeed = null; } unit.slowEndTime = null; } // Handle poison damage over time if (unit.poisonEndTime && LK.ticks >= unit.poisonEndTime) { unit.poisonDamage = null; unit.poisonEndTime = null; unit.lastPoisonTick = null; } else if (unit.poisonDamage && unit.lastPoisonTick && LK.ticks - unit.lastPoisonTick >= 60) { // Apply poison damage every second unit.takeDamage(unit.poisonDamage); unit.lastPoisonTick = LK.ticks; LK.effects.flashObject(unit, 0x228B22, 200); } } // Update tower bullets for (var i = towerBullets.length - 1; i >= 0; i--) { var bullet = towerBullets[i]; if (!bullet.target || !bullet.target.parent) { bullet.destroy(); towerBullets.splice(i, 1); continue; } var dx = bullet.target.x - bullet.x; var dy = bullet.target.y - bullet.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 15) { // Hit target var isDead = bullet.target.takeDamage(bullet.damage); if (isDead) { // Remove unit for (var j = 0; j < playerUnits.length; j++) { if (playerUnits[j] === bullet.target) { playerUnits[j].destroy(); playerUnits.splice(j, 1); break; } } } bullet.destroy(); towerBullets.splice(i, 1); } } // Lose condition removed - player can no longer lose // Process trap damage and cleanup for (var i = enemyTowers.length - 1; i >= 0; i--) { var tower = enemyTowers[i]; if (tower.towerType === 'trap' && tower.triggered) { // Clean up slot if tower has one if (tower.slot) { tower.slot.removeTower(); } tower.destroy(); enemyTowers.splice(i, 1); } } // Update base health display if (enemyBase) { healthText.setText('Base Health: ' + enemyBase.health); } // Update ability cooldowns and button display for (var abilityName in abilities) { var ability = abilities[abilityName]; if (ability.currentCooldown > 0) { ability.currentCooldown--; } } // Update ability button texts and colors var canUseLightning = abilities.lightning.currentCooldown <= 0; lightningButton.fill = canUseLightning ? 0xFFFF00 : 0x666666; lightningButton.setText(abilities.lightning.currentCooldown > 0 ? 'Lightning (' + Math.ceil(abilities.lightning.currentCooldown / 60) + 's)' : 'Lightning'); var canUseHeal = abilities.heal.currentCooldown <= 0; healButton.fill = canUseHeal ? 0x00FF00 : 0x666666; healButton.setText(abilities.heal.currentCooldown > 0 ? 'Heal (' + Math.ceil(abilities.heal.currentCooldown / 60) + 's)' : 'Heal All'); var canUseFreeze = abilities.freeze.currentCooldown <= 0; freezeButton.fill = canUseFreeze ? 0x00FFFF : 0x666666; freezeButton.setText(abilities.freeze.currentCooldown > 0 ? 'Freeze (' + Math.ceil(abilities.freeze.currentCooldown / 60) + 's)' : 'Freeze'); var canUseBoost = abilities.boost.currentCooldown <= 0; boostButton.fill = canUseBoost ? 0xFF8C00 : 0x666666; boostButton.setText(abilities.boost.currentCooldown > 0 ? 'Boost (' + Math.ceil(abilities.boost.currentCooldown / 60) + 's)' : 'Boost'); // Handle frozen towers (disable shooting) for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (tower.frozenUntil && LK.ticks >= tower.frozenUntil) { tower.frozenUntil = null; } } // Handle frozen base if (enemyBase && enemyBase.frozenUntil && LK.ticks >= enemyBase.frozenUntil) { enemyBase.frozenUntil = null; } // Handle unit boost expiration for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; if (unit.boostedUntil && LK.ticks >= unit.boostedUntil) { unit.damage = unit.originalDamage || unit.damage / 2; unit.boostedUntil = null; } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AreaTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('areaTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
// Add explosion indicator
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -50;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -50;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
var explosionIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
explosionIndicator.x = 0;
explosionIndicator.y = 0;
explosionIndicator.tint = 0xff0000; // Red explosion symbol
explosionIndicator.scaleX = 1.3;
explosionIndicator.scaleY = 1.3;
self.towerType = 'area';
self.health = 140;
self.maxHealth = 140;
self.range = 180;
self.damage = 25; // Base damage per unit
self.areaRadius = 80; // Area effect radius
self.shootCooldown = 0;
self.target = null;
self.cost = 130;
self.canUpgrade = function () {
return false; // Area towers cannot be upgraded
};
self.getUpgradeCost = function () {
return 0; // Area towers cannot be upgraded
};
self.update = function () {
// Find target (center of enemy group)
if (!self.target || !self.target.parent) {
self.target = null;
var bestTarget = null;
var maxUnitsInArea = 0;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.range) {
// Count how many units would be hit if we target this unit
var unitsInArea = 0;
for (var j = 0; j < playerUnits.length; j++) {
var otherUnit = playerUnits[j];
var areaDistance = Math.sqrt(Math.pow(otherUnit.x - unit.x, 2) + Math.pow(otherUnit.y - unit.y, 2));
if (areaDistance <= self.areaRadius) {
unitsInArea++;
}
}
if (unitsInArea > maxUnitsInArea) {
maxUnitsInArea = unitsInArea;
bestTarget = unit;
}
}
}
self.target = bestTarget;
}
// Shoot area effect
if (self.target && self.shootCooldown <= 0) {
// Create area explosion at target location
var targetX = self.target.x;
var targetY = self.target.y;
// Damage all units in area
var unitsHit = 0;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - targetX, 2) + Math.pow(unit.y - targetY, 2));
if (distance <= self.areaRadius) {
unit.takeDamage(self.damage);
LK.effects.flashObject(unit, 0xff6600, 300);
unitsHit++;
}
}
if (unitsHit > 0) {
// Visual explosion effect
tween(explosionIndicator, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0.3
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(explosionIndicator, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
LK.getSound('shoot').play();
}
self.shootCooldown = 150; // 2.5 second cooldown
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var AttackUnit = Container.expand(function () {
var self = Container.call(this);
var unitGraphics = self.attachAsset('basicUnitAsset', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -40;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -40;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
self.health = 100;
self.maxHealth = 100;
self.speed = 2;
self.damage = 25;
self.range = 150;
self.shootCooldown = 0;
self.target = null;
self.lastX = 0;
self.lastY = 0;
self.currentWaypoint = 0;
self.isMovingToWaypoint = false;
// Create path through tower grid
var pathWaypoints = [];
pathWaypoints.push({
x: 400,
y: self.y
});
// Tower grid parameters
var gridStartX = 800;
var gridStartY = 600;
var gridSpacingX = 200;
var gridSpacingY = 200;
var gridRows = 8;
var gridCols = 4;
// Serpentine path through towers
for (var col = 0; col < gridCols; col++) {
if (col % 2 === 0) {
for (var row = 0; row < gridRows; row++) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
} else {
for (var row = gridRows - 1; row >= 0; row--) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
}
}
pathWaypoints.push({
x: 1600,
y: 1000
});
self.pathWaypoints = pathWaypoints;
self.followPath = function () {
if (self.isMovingToWaypoint) return;
if (self.currentWaypoint >= self.pathWaypoints.length) {
// Reached end of path, move directly to base
self.x += self.speed;
return;
}
var waypoint = self.pathWaypoints[self.currentWaypoint];
var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2));
var duration = distance / self.speed * 16.67; // Convert speed to ms (60fps = 16.67ms per frame)
self.isMovingToWaypoint = true;
tween(self, {
x: waypoint.x,
y: waypoint.y
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
self.currentWaypoint++;
self.isMovingToWaypoint = false;
}
});
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Find target (prioritize base, then towers)
if (!self.target || !self.target.parent) {
self.target = null;
var closestTarget = null;
var closestDistance = Infinity;
// Check distance to enemy base first
if (enemyBase && enemyBase.parent) {
var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2));
if (baseDistance <= self.range) {
closestTarget = enemyBase;
closestDistance = baseDistance;
}
}
// Check towers if no base target or if tower is closer
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower && tower.parent) {
var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (towerDistance <= self.range && towerDistance < closestDistance) {
closestDistance = towerDistance;
closestTarget = tower;
}
}
}
self.target = closestTarget;
}
// Attack target if in range
if (self.target && self.shootCooldown <= 0) {
var bullet = new UnitBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = self.target;
bullet.damage = self.damage;
unitBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = 60; // 1 second cooldown at 60fps
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Move towards enemy base using waypoints only if no target in range
if (!self.target) {
self.followPath();
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
if (self.health <= 0) {
return true; // Unit is dead
}
return false;
};
return self;
});
var BasicTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('basicTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -50;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -50;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
self.towerType = 'basic';
self.health = 100;
self.maxHealth = 100;
self.range = 150;
self.damage = 20;
self.shootCooldown = 0;
self.target = null;
self.cost = 75;
// Upgrade indicator (initially hidden)
var upgradeIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeIndicator.x = 30;
upgradeIndicator.y = -30;
upgradeIndicator.visible = false;
self.level = 1;
self.maxLevel = 3;
self.baseRange = 150;
self.baseDamage = 20;
self.range = self.baseRange;
self.damage = self.baseDamage;
self.updateStats = function () {
var multiplier = 1 + (self.level - 1) * 0.6; // 60% increase per level
self.range = Math.floor(self.baseRange * multiplier);
self.damage = Math.floor(self.baseDamage * multiplier);
self.maxHealth = Math.floor(100 * multiplier);
self.health = Math.min(self.health, self.maxHealth);
// Special abilities for max level
if (self.level === 3) {
self.shieldActive = false;
self.shieldCooldown = 0;
self.shieldDuration = 0;
}
// Update visual appearance based on level
if (self.level === 2) {
towerGraphics.tint = 0x4169E1; // Royal blue
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xffd700; // Gold
} else if (self.level === 3) {
towerGraphics.tint = 0x0000FF; // Pure blue
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xff4500; // Orange red
}
};
self.canUpgrade = function () {
return self.level < self.maxLevel;
};
self.getUpgradeCost = function () {
return 50 + (self.level - 1) * 25; // Cost increases with level
};
self.upgrade = function () {
if (self.canUpgrade()) {
self.level++;
self.updateStats();
LK.effects.flashObject(self, 0xffd700, 500);
return true;
}
return false;
};
self.update = function () {
// Shield ability for level 3
if (self.level === 3) {
// Activate shield when health is low and not on cooldown
if (self.health < self.maxHealth * 0.3 && !self.shieldActive && self.shieldCooldown <= 0) {
self.shieldActive = true;
self.shieldDuration = 300; // 5 seconds of invulnerability
towerGraphics.tint = 0xFFD700; // Golden shield color
LK.effects.flashObject(self, 0xFFD700, 300);
}
// Handle shield duration
if (self.shieldActive) {
self.shieldDuration--;
if (self.shieldDuration <= 0) {
self.shieldActive = false;
self.shieldCooldown = 1200; // 20 second cooldown
towerGraphics.tint = 0x0000FF; // Back to normal level 3 color
}
}
// Update cooldown
if (self.shieldCooldown > 0) {
self.shieldCooldown--;
}
}
// Find target
if (!self.target || !self.target.parent) {
self.target = null;
var closestUnit = null;
var closestDistance = Infinity;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestUnit = unit;
}
}
self.target = closestUnit;
}
// Shoot at target (if not frozen)
if (self.target && self.shootCooldown <= 0 && !self.frozenUntil) {
var bullet = new TowerBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = self.target;
bullet.damage = self.damage;
towerBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = 90; // 1.5 second cooldown
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
self.takeDamage = function (damage) {
// Shield ability blocks damage at level 3
if (self.level === 3 && self.shieldActive) {
LK.effects.flashObject(self, 0xFFD700, 200); // Gold flash for blocked damage
return false; // No damage taken
}
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var BasicUnit = Container.expand(function () {
var self = Container.call(this);
var unitGraphics = self.attachAsset('basicUnitAsset', {
anchorX: 0.5,
anchorY: 0.5
});
unitGraphics.tint = 0x87CEEB; // Sky blue color for basic unit
unitGraphics.scaleX = 0.9;
unitGraphics.scaleY = 0.9;
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -35;
healthBarBg.scaleX = 0.8;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -35;
healthBar.scaleX = 0.8;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent * 0.8;
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00;
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00;
} else {
healthBar.tint = 0xff0000;
}
};
self.unitType = 'basic';
self.health = 60;
self.maxHealth = 60;
self.speed = 1.8;
self.damage = 15;
self.range = 140;
self.cost = 40;
self.shootCooldown = 0;
self.target = null;
self.lastX = 0;
self.lastY = 0;
self.currentWaypoint = 0;
self.isMovingToWaypoint = false;
// Create path through tower grid
var pathWaypoints = [];
pathWaypoints.push({
x: 400,
y: self.y
});
// Tower grid parameters
var gridStartX = 800;
var gridStartY = 600;
var gridSpacingX = 200;
var gridSpacingY = 200;
var gridRows = 8;
var gridCols = 4;
// Serpentine path through towers
for (var col = 0; col < gridCols; col++) {
if (col % 2 === 0) {
for (var row = 0; row < gridRows; row++) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
} else {
for (var row = gridRows - 1; row >= 0; row--) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
}
}
pathWaypoints.push({
x: 1600,
y: 1000
});
self.pathWaypoints = pathWaypoints;
self.followPath = function () {
if (self.isMovingToWaypoint) return;
if (self.currentWaypoint >= self.pathWaypoints.length) {
// Check for walls blocking movement
var canMove = true;
var nextX = self.x + self.speed;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower.towerType === 'wall') {
var distance = Math.sqrt(Math.pow(tower.x - nextX, 2) + Math.pow(tower.y - self.y, 2));
if (distance <= tower.blockRadius) {
canMove = false;
break;
}
}
}
if (canMove) {
self.x += self.speed;
}
return;
}
var waypoint = self.pathWaypoints[self.currentWaypoint];
// Check if path to waypoint is blocked by walls
var pathBlocked = false;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower.towerType === 'wall') {
var wallDistance = Math.sqrt(Math.pow(tower.x - waypoint.x, 2) + Math.pow(tower.y - waypoint.y, 2));
if (wallDistance <= tower.blockRadius) {
pathBlocked = true;
break;
}
}
}
if (pathBlocked) {
// Skip this waypoint if blocked
self.currentWaypoint++;
return;
}
var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2));
var duration = distance / self.speed * 16.67;
self.isMovingToWaypoint = true;
tween(self, {
x: waypoint.x,
y: waypoint.y
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
self.currentWaypoint++;
self.isMovingToWaypoint = false;
}
});
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (!self.target || !self.target.parent) {
self.target = null;
var closestTarget = null;
var closestDistance = Infinity;
if (enemyBase && enemyBase.parent) {
var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2));
if (baseDistance <= self.range) {
closestTarget = enemyBase;
closestDistance = baseDistance;
}
}
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower && tower.parent) {
var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (towerDistance <= self.range && towerDistance < closestDistance) {
closestDistance = towerDistance;
closestTarget = tower;
}
}
}
self.target = closestTarget;
}
if (self.target && self.shootCooldown <= 0) {
var bullet = new UnitBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = self.target;
bullet.damage = self.damage;
unitBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = 75;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (!self.target) {
self.followPath();
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var EnemyBase = Container.expand(function () {
var self = Container.call(this);
var baseGraphics = self.attachAsset('enemyBase', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 800;
self.maxHealth = 800;
self.range = 300; // Base shooting range
self.damage = 60; // Base damage per shot
self.shootCooldown = 0;
self.target = null;
self.update = function () {
// Find target within range
if (!self.target || !self.target.parent) {
self.target = null;
var closestUnit = null;
var closestDistance = Infinity;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestUnit = unit;
}
}
self.target = closestUnit;
}
// Shoot at target (if not frozen)
if (self.target && self.shootCooldown <= 0 && !self.frozenUntil) {
var bullet = new TowerBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = self.target;
bullet.damage = self.damage;
towerBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = 90; // 1.5 second cooldown
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
return true; // Base destroyed - player wins!
}
return false;
};
return self;
});
// Game arrays to track entities
var EnemyTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('enemyTower', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -50;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -50;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
// Upgrade indicator (initially hidden)
var upgradeIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeIndicator.x = 30;
upgradeIndicator.y = -30;
upgradeIndicator.visible = false;
self.level = 1;
self.maxLevel = 3;
self.health = 250;
self.maxHealth = 250;
self.baseRange = 220;
self.baseDamage = 45;
self.range = self.baseRange;
self.damage = self.baseDamage;
self.shootCooldown = 0;
self.target = null;
self.updateStats = function () {
var multiplier = 1 + (self.level - 1) * 0.5; // 50% increase per level
self.range = Math.floor(self.baseRange * multiplier);
self.damage = Math.floor(self.baseDamage * multiplier);
self.maxHealth = Math.floor(150 * multiplier);
self.health = Math.min(self.health, self.maxHealth);
// Update visual appearance based on level
if (self.level === 2) {
towerGraphics.tint = 0xc0392b; // Darker red
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xffd700; // Gold
} else if (self.level === 3) {
towerGraphics.tint = 0x8b0000; // Dark red
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xff4500; // Orange red
}
};
self.canUpgrade = function () {
return self.level < self.maxLevel;
};
self.getUpgradeCost = function () {
return 75 * self.level; // Cost increases with level
};
self.upgrade = function () {
if (self.canUpgrade()) {
self.level++;
self.updateStats();
LK.effects.flashObject(self, 0xffd700, 500);
return true;
}
return false;
};
self.update = function () {
// Increase range and damage if base is low on health
var baseHealthPercent = enemyBase ? enemyBase.health / enemyBase.maxHealth : 1;
var bonusRange = baseHealthPercent < 0.5 ? 100 : 0;
var bonusDamage = baseHealthPercent < 0.3 ? 20 : 0;
var currentRange = self.range + bonusRange;
var currentDamage = self.damage + bonusDamage;
// Find target
if (!self.target || !self.target.parent) {
self.target = null;
var closestUnit = null;
var closestDistance = Infinity;
// Prioritize units closer to base
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
var distanceToBase = Math.sqrt(Math.pow(unit.x - enemyBase.x, 2) + Math.pow(unit.y - enemyBase.y, 2));
if (distance <= currentRange) {
// Prioritize units closer to base
var priority = distance - distanceToBase * 0.3;
if (priority < closestDistance) {
closestDistance = priority;
closestUnit = unit;
}
}
}
self.target = closestUnit;
}
// Shoot at target
if (self.target && self.shootCooldown <= 0) {
var bullet = new TowerBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = self.target;
bullet.damage = currentDamage;
towerBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
// Shoot faster when base is in danger
var shootCooldown = baseHealthPercent < 0.5 ? 60 : 90;
self.shootCooldown = shootCooldown;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var FastTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('fastTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -40;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -40;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
self.towerType = 'fast';
self.health = 80;
self.maxHealth = 80;
self.range = 180;
self.damage = 15;
self.shootCooldown = 0;
self.target = null;
self.cost = 100;
// Upgrade indicator (initially hidden)
var upgradeIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeIndicator.x = 25;
upgradeIndicator.y = -25;
upgradeIndicator.visible = false;
self.level = 1;
self.maxLevel = 3;
self.baseRange = 180;
self.baseDamage = 15;
self.baseCooldown = 30;
self.multiShot = 1; // Number of bullets per shot
self.range = self.baseRange;
self.damage = self.baseDamage;
self.updateStats = function () {
var multiplier = 1 + (self.level - 1) * 0.4; // 40% increase per level
self.range = Math.floor(self.baseRange * multiplier);
self.damage = Math.floor(self.baseDamage * multiplier);
self.maxHealth = Math.floor(80 * multiplier);
self.health = Math.min(self.health, self.maxHealth);
self.multiShot = self.level; // Shoot multiple bullets at higher levels
// Special abilities for max level
if (self.level === 3) {
self.burstMode = false;
self.burstCooldown = 0;
self.burstDuration = 0;
self.burstTriggerDistance = 100; // Distance to activate burst mode
}
// Update visual appearance based on level
if (self.level === 2) {
towerGraphics.tint = 0x00FF7F; // Spring green
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xffd700; // Gold
} else if (self.level === 3) {
towerGraphics.tint = 0x00FF00; // Pure green
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xff4500; // Orange red
}
};
self.canUpgrade = function () {
return self.level < self.maxLevel;
};
self.getUpgradeCost = function () {
return 60 + (self.level - 1) * 30; // Cost increases with level
};
self.upgrade = function () {
if (self.canUpgrade()) {
self.level++;
self.updateStats();
LK.effects.flashObject(self, 0xffd700, 500);
return true;
}
return false;
};
self.update = function () {
// Burst mode ability for level 3
if (self.level === 3) {
// Check if any enemies are very close to activate burst mode
var hasCloseEnemies = false;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.burstTriggerDistance) {
hasCloseEnemies = true;
break;
}
}
// Activate burst mode when enemies are close and not on cooldown
if (hasCloseEnemies && !self.burstMode && self.burstCooldown <= 0) {
self.burstMode = true;
self.burstDuration = 180; // 3 seconds of burst fire
towerGraphics.tint = 0xFFFF00; // Yellow burst color
LK.effects.flashObject(self, 0xFFFF00, 300);
}
// Handle burst duration
if (self.burstMode) {
self.burstDuration--;
if (self.burstDuration <= 0) {
self.burstMode = false;
self.burstCooldown = 600; // 10 second cooldown
towerGraphics.tint = 0x00FF00; // Back to normal level 3 color
}
}
// Update cooldown
if (self.burstCooldown > 0) {
self.burstCooldown--;
}
}
// Find target
if (!self.target || !self.target.parent) {
self.target = null;
var closestUnit = null;
var closestDistance = Infinity;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestUnit = unit;
}
}
self.target = closestUnit;
}
// Shoot at target with fast rate and multi-shot
if (self.target && self.shootCooldown <= 0) {
// Determine shot count - burst mode doubles the shots
var shotCount = self.multiShot;
if (self.level === 3 && self.burstMode) {
shotCount = self.multiShot * 2; // Double shots in burst mode
}
// Shoot multiple bullets based on level and burst mode
for (var shotIndex = 0; shotIndex < shotCount; shotIndex++) {
var bullet = new TowerBullet();
bullet.x = self.x;
bullet.y = self.y;
// For multi-shot, slightly spread the bullets
if (shotCount > 1) {
var angleOffset = (shotIndex - (shotCount - 1) / 2) * 0.2; // Smaller angle spread for more bullets
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var angle = Math.atan2(dy, dx) + angleOffset;
var distance = Math.sqrt(dx * dx + dy * dy);
// Create a virtual target with slight offset
bullet.target = {
x: self.x + Math.cos(angle) * distance,
y: self.y + Math.sin(angle) * distance,
parent: self.target.parent,
takeDamage: self.target.takeDamage.bind(self.target)
};
} else {
bullet.target = self.target;
}
bullet.damage = self.damage;
towerBullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
// Faster shooting in burst mode
var cooldownTime = self.level === 3 && self.burstMode ? 15 : 30;
self.shootCooldown = cooldownTime;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var FastUnit = Container.expand(function () {
var self = Container.call(this);
var unitGraphics = self.attachAsset('fastUnitAsset', {
anchorX: 0.5,
anchorY: 0.5
});
unitGraphics.tint = 0x32CD32; // Lime green color for fast unit
unitGraphics.scaleX = 0.8;
unitGraphics.scaleY = 0.8;
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -32;
healthBarBg.scaleX = 0.7;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -32;
healthBar.scaleX = 0.7;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent * 0.7;
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00;
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00;
} else {
healthBar.tint = 0xff0000;
}
};
self.unitType = 'fast';
self.health = 45;
self.maxHealth = 45;
self.speed = 3.5;
self.damage = 12;
self.range = 120;
self.cost = 60;
self.shootCooldown = 0;
self.target = null;
self.lastX = 0;
self.lastY = 0;
self.currentWaypoint = 0;
self.isMovingToWaypoint = false;
// Create path through tower grid
var pathWaypoints = [];
pathWaypoints.push({
x: 400,
y: self.y
});
// Tower grid parameters
var gridStartX = 800;
var gridStartY = 600;
var gridSpacingX = 200;
var gridSpacingY = 200;
var gridRows = 8;
var gridCols = 4;
// Serpentine path through towers
for (var col = 0; col < gridCols; col++) {
if (col % 2 === 0) {
for (var row = 0; row < gridRows; row++) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
} else {
for (var row = gridRows - 1; row >= 0; row--) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
}
}
pathWaypoints.push({
x: 1600,
y: 1000
});
self.pathWaypoints = pathWaypoints;
self.followPath = function () {
if (self.isMovingToWaypoint) return;
if (self.currentWaypoint >= self.pathWaypoints.length) {
self.x += self.speed;
return;
}
var waypoint = self.pathWaypoints[self.currentWaypoint];
var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2));
var duration = distance / self.speed * 16.67;
self.isMovingToWaypoint = true;
tween(self, {
x: waypoint.x,
y: waypoint.y
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
self.currentWaypoint++;
self.isMovingToWaypoint = false;
}
});
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (!self.target || !self.target.parent) {
self.target = null;
var closestTarget = null;
var closestDistance = Infinity;
if (enemyBase && enemyBase.parent) {
var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2));
if (baseDistance <= self.range) {
closestTarget = enemyBase;
closestDistance = baseDistance;
}
}
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower && tower.parent) {
var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (towerDistance <= self.range && towerDistance < closestDistance) {
closestDistance = towerDistance;
closestTarget = tower;
}
}
}
self.target = closestTarget;
}
if (self.target && self.shootCooldown <= 0) {
var bullet = new UnitBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = self.target;
bullet.damage = self.damage;
unitBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = 45; // Faster shooting
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (!self.target) {
self.followPath();
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var HealTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('healTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -50;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -50;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
// Add a cross symbol effect
var crossIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
crossIndicator.x = 0;
crossIndicator.y = 0;
crossIndicator.tint = 0xffffff; // White cross
crossIndicator.scaleX = 1.5;
crossIndicator.scaleY = 1.5;
self.towerType = 'heal';
self.health = 120;
self.maxHealth = 120;
self.range = 200; // Healing range
self.healAmount = 15;
self.healCooldown = 0;
self.target = null;
self.cost = 110;
self.canUpgrade = function () {
return false; // Heal towers cannot be upgraded
};
self.getUpgradeCost = function () {
return 0; // Heal towers cannot be upgraded
};
self.update = function () {
// Find damaged towers to heal
if (self.healCooldown <= 0) {
self.target = null;
var bestTarget = null;
var lowestHealthPercent = 1;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower !== self && tower.parent) {
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance <= self.range) {
var healthPercent = tower.health / tower.maxHealth;
if (healthPercent < 1 && healthPercent < lowestHealthPercent) {
lowestHealthPercent = healthPercent;
bestTarget = tower;
}
}
}
}
self.target = bestTarget;
}
// Heal target
if (self.target && self.healCooldown <= 0) {
var healingAmount = Math.min(self.healAmount, self.target.maxHealth - self.target.health);
if (healingAmount > 0) {
self.target.health += healingAmount;
// Update target's health bar after healing
if (self.target.updateHealthBar) {
self.target.updateHealthBar();
}
LK.effects.flashObject(self.target, 0x00ff00, 400);
// Create healing visual effect
tween(crossIndicator, {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(crossIndicator, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeIn
});
}
});
self.healCooldown = 120; // 2 second cooldown
}
}
if (self.healCooldown > 0) {
self.healCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var HeavyTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('heavyTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -60;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -60;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
self.towerType = 'heavy';
self.health = 200;
self.maxHealth = 200;
self.range = 160;
self.damage = 50;
self.shootCooldown = 0;
self.target = null;
self.cost = 150;
// Upgrade indicator (initially hidden)
var upgradeIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeIndicator.x = 35;
upgradeIndicator.y = -35;
upgradeIndicator.visible = false;
self.level = 1;
self.maxLevel = 3;
self.baseRange = 160;
self.baseDamage = 50;
self.splashRadius = 0; // Splash damage radius
self.range = self.baseRange;
self.damage = self.baseDamage;
self.updateStats = function () {
var multiplier = 1 + (self.level - 1) * 0.5; // 50% increase per level
self.range = Math.floor(self.baseRange * multiplier);
self.damage = Math.floor(self.baseDamage * multiplier);
self.maxHealth = Math.floor(200 * multiplier);
self.health = Math.min(self.health, self.maxHealth);
self.splashRadius = (self.level - 1) * 60; // Splash damage at higher levels
// Special abilities for max level
if (self.level === 3) {
self.mineActive = false;
self.mineCooldown = 0;
self.mineRadius = 100;
self.mineDamage = 120;
}
// Update visual appearance based on level
if (self.level === 2) {
towerGraphics.tint = 0x8B4513; // Saddle brown
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xffd700; // Gold
} else if (self.level === 3) {
towerGraphics.tint = 0x654321; // Dark brown
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xff4500; // Orange red
}
};
self.canUpgrade = function () {
return self.level < self.maxLevel;
};
self.getUpgradeCost = function () {
return 75 + (self.level - 1) * 40; // Cost increases with level
};
self.upgrade = function () {
if (self.canUpgrade()) {
self.level++;
self.updateStats();
LK.effects.flashObject(self, 0xffd700, 500);
return true;
}
return false;
};
self.update = function () {
// Earthquake ability for level 3
if (self.level === 3 && self.earthquakeCooldown <= 0) {
// Count enemies within earthquake range
var enemiesInRange = 0;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.earthquakeRadius) {
enemiesInRange++;
}
}
// Trigger earthquake if 3+ enemies are nearby
if (enemiesInRange >= 3) {
// Damage all enemies in range
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.earthquakeRadius) {
unit.takeDamage(self.earthquakeDamage);
LK.effects.flashObject(unit, 0x8B4513, 400);
}
}
// Visual earthquake effect
towerGraphics.tint = 0xFFD700; // Golden earthquake color
LK.effects.flashObject(self, 0x8B4513, 600);
// Screen shake effect simulation
LK.effects.flashScreen(0x8B4513, 300);
self.earthquakeCooldown = 900; // 15 second cooldown
// Return to normal color after effect
var self_ref = self;
LK.setTimeout(function () {
self_ref.towerGraphics.tint = 0x654321; // Back to level 3 color
}, 600);
}
}
// Update earthquake cooldown
if (self.earthquakeCooldown > 0) {
self.earthquakeCooldown--;
}
// Find target
if (!self.target || !self.target.parent) {
self.target = null;
var closestUnit = null;
var closestDistance = Infinity;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestUnit = unit;
}
}
self.target = closestUnit;
}
// Shoot at target with heavy damage and potential splash
if (self.target && self.shootCooldown <= 0) {
var bullet = new TowerBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = self.target;
bullet.damage = self.damage;
bullet.splashRadius = self.splashRadius; // Add splash capability
bullet.splashDamage = Math.floor(self.damage * 0.6); // 60% splash damage
towerBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = 120; // 2 second cooldown (slow)
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var HeavyUnit = Container.expand(function () {
var self = Container.call(this);
var unitGraphics = self.attachAsset('heavyUnitAsset', {
anchorX: 0.5,
anchorY: 0.5
});
unitGraphics.tint = 0x8B4513; // Brown color for heavy unit
unitGraphics.scaleX = 1.2;
unitGraphics.scaleY = 1.2;
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -45;
healthBarBg.scaleX = 1.0;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -45;
healthBar.scaleX = 1.0;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent * 1.0;
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00;
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00;
} else {
healthBar.tint = 0xff0000;
}
};
self.unitType = 'heavy';
self.health = 120;
self.maxHealth = 120;
self.speed = 0.8;
self.damage = 30;
self.range = 160;
self.cost = 90;
self.shootCooldown = 0;
self.target = null;
self.lastX = 0;
self.lastY = 0;
self.currentWaypoint = 0;
self.isMovingToWaypoint = false;
// Create path through tower grid
var pathWaypoints = [];
pathWaypoints.push({
x: 400,
y: self.y
});
// Tower grid parameters
var gridStartX = 800;
var gridStartY = 600;
var gridSpacingX = 200;
var gridSpacingY = 200;
var gridRows = 8;
var gridCols = 4;
// Serpentine path through towers
for (var col = 0; col < gridCols; col++) {
if (col % 2 === 0) {
for (var row = 0; row < gridRows; row++) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
} else {
for (var row = gridRows - 1; row >= 0; row--) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
}
}
pathWaypoints.push({
x: 1600,
y: 1000
});
self.pathWaypoints = pathWaypoints;
self.followPath = function () {
if (self.isMovingToWaypoint) return;
if (self.currentWaypoint >= self.pathWaypoints.length) {
self.x += self.speed;
return;
}
var waypoint = self.pathWaypoints[self.currentWaypoint];
var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2));
var duration = distance / self.speed * 16.67;
self.isMovingToWaypoint = true;
tween(self, {
x: waypoint.x,
y: waypoint.y
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
self.currentWaypoint++;
self.isMovingToWaypoint = false;
}
});
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (!self.target || !self.target.parent) {
self.target = null;
var closestTarget = null;
var closestDistance = Infinity;
if (enemyBase && enemyBase.parent) {
var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2));
if (baseDistance <= self.range) {
closestTarget = enemyBase;
closestDistance = baseDistance;
}
}
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower && tower.parent) {
var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (towerDistance <= self.range && towerDistance < closestDistance) {
closestDistance = towerDistance;
closestTarget = tower;
}
}
}
self.target = closestTarget;
}
if (self.target && self.shootCooldown <= 0) {
var bullet = new UnitBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = self.target;
bullet.damage = self.damage;
unitBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = 100; // Slower shooting
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (!self.target) {
self.followPath();
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var LaserTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('laserTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
towerGraphics.tint = 0xFF1493; // Deep pink for laser tower
towerGraphics.scaleX = 1.2;
towerGraphics.scaleY = 1.2;
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -45;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -45;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
// Add laser beam indicator
var laserIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
laserIndicator.x = 0;
laserIndicator.y = 0;
laserIndicator.tint = 0xFF1493; // Pink laser symbol
laserIndicator.scaleX = 1.3;
laserIndicator.scaleY = 1.3;
self.towerType = 'laser';
self.health = 100;
self.maxHealth = 100;
self.range = 220;
self.damage = 8; // Continuous damage per frame
self.chargingTime = 60; // 1 second to charge
self.maxBeamTime = 180; // 3 seconds max beam
self.cooldownTime = 120; // 2 second cooldown
self.beamState = 'idle'; // 'idle', 'charging', 'firing', 'cooling'
self.stateTimer = 0;
self.target = null;
self.cost = 140;
self.canUpgrade = function () {
return false; // Laser towers cannot be upgraded
};
self.getUpgradeCost = function () {
return 0; // Laser towers cannot be upgraded
};
self.update = function () {
// Find target when idle
if (self.beamState === 'idle') {
if (!self.target || !self.target.parent) {
self.target = null;
var closestUnit = null;
var closestDistance = Infinity;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestUnit = unit;
}
}
self.target = closestUnit;
}
if (self.target) {
self.beamState = 'charging';
self.stateTimer = self.chargingTime;
// Start charging effect
towerGraphics.tint = 0xFF69B4; // Lighter pink while charging
}
}
// Handle beam states
if (self.beamState === 'charging') {
self.stateTimer--;
if (self.stateTimer <= 0) {
self.beamState = 'firing';
self.stateTimer = self.maxBeamTime;
towerGraphics.tint = 0xFF0000; // Red while firing
}
} else if (self.beamState === 'firing') {
// Continuous damage while firing
if (self.target && self.target.parent) {
var distance = Math.sqrt(Math.pow(self.target.x - self.x, 2) + Math.pow(self.target.y - self.y, 2));
if (distance <= self.range) {
self.target.takeDamage(self.damage);
// Visual beam effect
if (LK.ticks % 10 === 0) {
LK.effects.flashObject(self.target, 0xFF1493, 100);
}
} else {
self.target = null; // Lost target
}
}
// Laser beam visual effect
tween(laserIndicator, {
scaleX: 1.8,
scaleY: 1.8,
alpha: 0.7
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(laserIndicator, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 1
}, {
duration: 100,
easing: tween.easeIn
});
}
});
self.stateTimer--;
if (self.stateTimer <= 0 || !self.target || !self.target.parent) {
self.beamState = 'cooling';
self.stateTimer = self.cooldownTime;
self.target = null;
towerGraphics.tint = 0x808080; // Gray while cooling
}
} else if (self.beamState === 'cooling') {
self.stateTimer--;
if (self.stateTimer <= 0) {
self.beamState = 'idle';
towerGraphics.tint = 0xFF1493; // Back to normal color
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var MeleeUnit = Container.expand(function () {
var self = Container.call(this);
var unitGraphics = self.attachAsset('meleeUnitAsset', {
anchorX: 0.5,
anchorY: 0.5
});
unitGraphics.tint = 0xFF6347; // Tomato red color for melee unit
unitGraphics.scaleX = 1.1;
unitGraphics.scaleY = 1.1;
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -40;
healthBarBg.scaleX = 0.9;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -40;
healthBar.scaleX = 0.9;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent * 0.9;
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00;
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00;
} else {
healthBar.tint = 0xff0000;
}
};
self.unitType = 'melee';
self.health = 90;
self.maxHealth = 90;
self.speed = 1.3;
self.damage = 25;
self.range = 50; // Very short range for melee
self.cost = 30;
self.attackCooldown = 0;
self.target = null;
self.lastX = 0;
self.lastY = 0;
self.currentWaypoint = 0;
self.isMovingToWaypoint = false;
// Create path through tower grid
var pathWaypoints = [];
pathWaypoints.push({
x: 400,
y: self.y
});
// Tower grid parameters
var gridStartX = 800;
var gridStartY = 600;
var gridSpacingX = 200;
var gridSpacingY = 200;
var gridRows = 8;
var gridCols = 4;
// Serpentine path through towers
for (var col = 0; col < gridCols; col++) {
if (col % 2 === 0) {
for (var row = 0; row < gridRows; row++) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
} else {
for (var row = gridRows - 1; row >= 0; row--) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
}
}
pathWaypoints.push({
x: 1600,
y: 1000
});
self.pathWaypoints = pathWaypoints;
self.followPath = function () {
if (self.isMovingToWaypoint) return;
if (self.currentWaypoint >= self.pathWaypoints.length) {
self.x += self.speed;
return;
}
var waypoint = self.pathWaypoints[self.currentWaypoint];
var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2));
var duration = distance / self.speed * 16.67;
self.isMovingToWaypoint = true;
tween(self, {
x: waypoint.x,
y: waypoint.y
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
self.currentWaypoint++;
self.isMovingToWaypoint = false;
}
});
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Find closest target (prioritize base, then towers)
if (!self.target || !self.target.parent) {
self.target = null;
var closestTarget = null;
var closestDistance = Infinity;
// Check distance to enemy base first
if (enemyBase && enemyBase.parent) {
var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2));
if (baseDistance <= self.range) {
closestTarget = enemyBase;
closestDistance = baseDistance;
}
}
// Check towers if no base target or if tower is closer
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower && tower.parent) {
var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (towerDistance <= self.range && towerDistance < closestDistance) {
closestDistance = towerDistance;
closestTarget = tower;
}
}
}
self.target = closestTarget;
}
// Melee attack - no bullets, direct damage
if (self.target && self.attackCooldown <= 0) {
// Flash effect for melee attack
LK.effects.flashObject(self, 0xFF6347, 200);
LK.effects.flashObject(self.target, 0xFF0000, 200);
// Direct damage to target
self.target.takeDamage(self.damage);
LK.getSound('hit').play();
self.attackCooldown = 90; // 1.5 second cooldown
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Move towards target or follow path
if (self.target) {
// Move directly towards target if one is found and not in attack range
if (!self.isMovingToWaypoint) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.range) {
// Move towards target only if outside attack range
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
} else {
// Follow path if no target
self.followPath();
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var PoisonTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('poisonTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
towerGraphics.tint = 0x228B22; // Forest green for poison tower
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -50;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -50;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
// Add poison cloud indicator
var poisonIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
poisonIndicator.x = 0;
poisonIndicator.y = 0;
poisonIndicator.tint = 0x228B22; // Green poison symbol
poisonIndicator.scaleX = 1.4;
poisonIndicator.scaleY = 1.4;
self.towerType = 'poison';
self.health = 110;
self.maxHealth = 110;
self.range = 160;
self.damage = 15; // Initial damage
self.poisonDamage = 5; // Damage per tick
self.poisonDuration = 480; // 8 seconds at 60fps
self.shootCooldown = 0;
self.target = null;
self.cost = 95;
self.canUpgrade = function () {
return false; // Poison towers cannot be upgraded
};
self.getUpgradeCost = function () {
return 0; // Poison towers cannot be upgraded
};
self.update = function () {
// Find target
if (!self.target || !self.target.parent) {
self.target = null;
var closestUnit = null;
var closestDistance = Infinity;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestUnit = unit;
}
}
self.target = closestUnit;
}
// Apply poison effect
if (self.target && self.shootCooldown <= 0) {
// Initial damage
self.target.takeDamage(self.damage);
// Apply poison effect
self.target.poisonDamage = self.poisonDamage;
self.target.poisonEndTime = LK.ticks + self.poisonDuration;
self.target.lastPoisonTick = LK.ticks;
// Visual effects
LK.effects.flashObject(self.target, 0x228B22, 400);
tween(poisonIndicator, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0.6
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(poisonIndicator, {
scaleX: 1.4,
scaleY: 1.4,
alpha: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
LK.getSound('shoot').play();
self.shootCooldown = 120; // 2 second cooldown
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var ResurrectionTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('resurrectionTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
towerGraphics.tint = 0xFFD700; // Gold color for resurrection tower
towerGraphics.scaleX = 1.3;
towerGraphics.scaleY = 1.3;
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -60;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -60;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
// Add resurrection symbol effect
var resIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
resIndicator.x = 0;
resIndicator.y = 0;
resIndicator.tint = 0xFFFFFF; // White resurrection symbol
resIndicator.scaleX = 2.0;
resIndicator.scaleY = 2.0;
self.towerType = 'resurrection';
self.health = 150;
self.maxHealth = 150;
self.range = 250; // Resurrection range
self.resurrectionCooldown = 0;
self.cost = 200;
self.canUpgrade = function () {
return false; // Resurrection towers cannot be upgraded
};
self.getUpgradeCost = function () {
return 0; // Resurrection towers cannot be upgraded
};
self.update = function () {
// Only attempt resurrection when not on cooldown
if (self.resurrectionCooldown <= 0) {
// Look for empty slots within range that used to have towers
var bestSlot = null;
var bestPriority = 0;
for (var i = 0; i < towerSlots.length; i++) {
var slot = towerSlots[i];
if (!slot.occupied && slot.lastTowerType) {
// Check if slot is within resurrection range
var distance = Math.sqrt(Math.pow(slot.x - self.x, 2) + Math.pow(slot.y - self.y, 2));
if (distance <= self.range) {
// Calculate priority based on tower type and nearby units
var priority = 1;
var nearbyUnits = 0;
for (var j = 0; j < playerUnits.length; j++) {
var unit = playerUnits[j];
var unitDistance = Math.sqrt(Math.pow(unit.x - slot.x, 2) + Math.pow(unit.y - slot.y, 2));
if (unitDistance <= 300) {
nearbyUnits++;
}
}
// Higher priority for slots with nearby enemies
priority += nearbyUnits * 2;
// Higher priority for certain tower types
if (slot.lastTowerType === 'basic' || slot.lastTowerType === 'fast') {
priority += 3;
} else if (slot.lastTowerType === 'heavy' || slot.lastTowerType === 'sniper') {
priority += 5;
}
if (priority > bestPriority) {
bestPriority = priority;
bestSlot = slot;
}
}
}
}
// Resurrect tower at best slot
if (bestSlot) {
var newTower = null;
// Recreate the tower based on its last type
switch (bestSlot.lastTowerType) {
case 'basic':
newTower = new BasicTower();
break;
case 'fast':
newTower = new FastTower();
break;
case 'heavy':
newTower = new HeavyTower();
break;
case 'sniper':
newTower = new SniperTower();
break;
case 'slow':
newTower = new SlowTower();
break;
case 'poison':
newTower = new PoisonTower();
break;
case 'laser':
newTower = new LaserTower();
break;
case 'area':
newTower = new AreaTower();
break;
case 'heal':
newTower = new HealTower();
break;
case 'wall':
newTower = new Wall();
break;
case 'trap':
newTower = new Trap();
break;
}
if (newTower) {
// Place the resurrected tower
newTower.x = bestSlot.x;
newTower.y = bestSlot.y;
newTower.slot = bestSlot;
// Resurrect at 50% health
newTower.health = Math.floor(newTower.maxHealth * 0.5);
enemyTowers.push(newTower);
game.addChild(newTower);
bestSlot.occupied = true;
bestSlot.tower = newTower;
bestSlot.attachAsset('towerSlot', {}).alpha = 0.1;
// Clear the resurrection memory
bestSlot.lastTowerType = null;
// Initialize health bar
if (newTower.updateHealthBar) {
newTower.updateHealthBar();
}
// Visual resurrection effect
tween(resIndicator, {
scaleX: 3.5,
scaleY: 3.5,
alpha: 0.3
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(resIndicator, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 1
}, {
duration: 300,
easing: tween.easeIn
});
}
});
LK.effects.flashObject(newTower, 0xFFD700, 800);
LK.effects.flashObject(self, 0xFFD700, 400);
self.resurrectionCooldown = 1200; // 20 second cooldown
}
}
}
if (self.resurrectionCooldown > 0) {
self.resurrectionCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var SlowTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('slowTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
towerGraphics.tint = 0x9932CC; // Purple color for slow tower
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -50;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -50;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
// Add slow effect indicator
var slowIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
slowIndicator.x = 0;
slowIndicator.y = 0;
slowIndicator.tint = 0x9932CC; // Purple indicator
slowIndicator.scaleX = 1.2;
slowIndicator.scaleY = 1.2;
self.towerType = 'slow';
self.health = 90;
self.maxHealth = 90;
self.range = 200; // Large range for slowing
self.damage = 10; // Low damage
self.slowEffect = 0.5; // Reduces speed by 50%
self.slowDuration = 300; // 5 seconds at 60fps
self.shootCooldown = 0;
self.target = null;
self.cost = 85;
self.canUpgrade = function () {
return false; // Slow towers cannot be upgraded
};
self.getUpgradeCost = function () {
return 0; // Slow towers cannot be upgraded
};
self.update = function () {
// Find target
if (!self.target || !self.target.parent) {
self.target = null;
var closestUnit = null;
var closestDistance = Infinity;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestUnit = unit;
}
}
self.target = closestUnit;
}
// Apply slow effect and light damage
if (self.target && self.shootCooldown <= 0) {
// Apply slow effect
if (!self.target.originalSpeed) {
self.target.originalSpeed = self.target.speed;
}
self.target.speed = self.target.originalSpeed * self.slowEffect;
self.target.slowEndTime = LK.ticks + self.slowDuration;
// Light damage
self.target.takeDamage(self.damage);
// Visual effects
LK.effects.flashObject(self.target, 0x9932CC, 300);
tween(slowIndicator, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(slowIndicator, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
LK.getSound('shoot').play();
self.shootCooldown = 60; // 1 second cooldown
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var SniperTower = Container.expand(function () {
var self = Container.call(this);
var towerGraphics = self.attachAsset('sniperTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -55;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -55;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
self.towerType = 'sniper';
self.health = 120;
self.maxHealth = 120;
self.range = 300; // Very long range
self.damage = 40;
self.shootCooldown = 0;
self.target = null;
self.cost = 125;
// Upgrade indicator (initially hidden)
var upgradeIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeIndicator.x = 30;
upgradeIndicator.y = -35;
upgradeIndicator.visible = false;
self.level = 1;
self.maxLevel = 3;
self.baseRange = 300;
self.baseDamage = 40;
self.criticalChance = 0; // Chance for critical hits
self.pierceCount = 0; // Number of units bullet can pierce through
self.range = self.baseRange;
self.damage = self.baseDamage;
self.updateStats = function () {
var multiplier = 1 + (self.level - 1) * 0.7; // 70% increase per level
self.range = Math.floor(self.baseRange * multiplier);
self.damage = Math.floor(self.baseDamage * multiplier);
self.maxHealth = Math.floor(120 * multiplier);
self.health = Math.min(self.health, self.maxHealth);
self.criticalChance = (self.level - 1) * 0.25; // 25% crit chance per level above 1
self.pierceCount = self.level - 1; // Pierce through multiple enemies
// Special abilities for max level
if (self.level === 3) {
self.executeThreshold = 0.25; // Execute enemies below 25% health
}
// Update visual appearance based on level
if (self.level === 2) {
towerGraphics.tint = 0x708090; // Slate gray
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xffd700; // Gold
} else if (self.level === 3) {
towerGraphics.tint = 0x2F4F4F; // Dark slate gray
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xff4500; // Orange red
}
};
self.canUpgrade = function () {
return self.level < self.maxLevel;
};
self.getUpgradeCost = function () {
return 80 + (self.level - 1) * 50; // Cost increases with level
};
self.upgrade = function () {
if (self.canUpgrade()) {
self.level++;
self.updateStats();
LK.effects.flashObject(self, 0xffd700, 500);
return true;
}
return false;
};
self.update = function () {
// Find target (prioritize units furthest away)
if (!self.target || !self.target.parent) {
self.target = null;
var bestUnit = null;
var longestDistance = 0;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.range && distance > longestDistance) {
longestDistance = distance;
bestUnit = unit;
}
}
self.target = bestUnit;
}
// Shoot at target with precision, critical hits, and piercing
if (self.target && self.shootCooldown <= 0) {
var bullet = new TowerBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = self.target;
// Check for critical hit
var isCritical = Math.random() < self.criticalChance;
bullet.damage = isCritical ? self.damage * 2 : self.damage;
// Execute ability for level 3 - instantly kill low health enemies
if (self.level === 3) {
var targetHealthPercent = self.target.health / self.target.maxHealth;
if (targetHealthPercent <= self.executeThreshold) {
bullet.execute = true; // Mark bullet for execution
bullet.damage = self.target.health + 1000; // Ensure instant kill
LK.effects.flashObject(self, 0xFF0000, 500); // Red flash for execute
}
}
bullet.speed = 10; // Faster bullet
bullet.pierceCount = self.pierceCount; // Add pierce capability
bullet.pierceRemaining = self.pierceCount;
if (isCritical) {
// Visual effect for critical hit
LK.effects.flashObject(self, 0xFFD700, 300);
}
towerBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = 150; // 2.5 second cooldown
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Tower is destroyed
}
return false;
};
return self;
});
var SniperUnit = Container.expand(function () {
var self = Container.call(this);
var unitGraphics = self.attachAsset('sniperUnitAsset', {
anchorX: 0.5,
anchorY: 0.5
});
unitGraphics.tint = 0x4B0082; // Indigo color for sniper unit
unitGraphics.scaleX = 1.0;
unitGraphics.scaleY = 1.3; // Taller appearance
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -42;
healthBarBg.scaleX = 0.9;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -42;
healthBar.scaleX = 0.9;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent * 0.9;
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00;
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00;
} else {
healthBar.tint = 0xff0000;
}
};
self.unitType = 'sniper';
self.health = 50;
self.maxHealth = 50;
self.speed = 1.2;
self.damage = 35;
self.range = 250; // Very long range
self.cost = 80;
self.shootCooldown = 0;
self.target = null;
self.lastX = 0;
self.lastY = 0;
self.currentWaypoint = 0;
self.isMovingToWaypoint = false;
// Create path through tower grid
var pathWaypoints = [];
pathWaypoints.push({
x: 400,
y: self.y
});
// Tower grid parameters
var gridStartX = 800;
var gridStartY = 600;
var gridSpacingX = 200;
var gridSpacingY = 200;
var gridRows = 8;
var gridCols = 4;
// Serpentine path through towers
for (var col = 0; col < gridCols; col++) {
if (col % 2 === 0) {
for (var row = 0; row < gridRows; row++) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
} else {
for (var row = gridRows - 1; row >= 0; row--) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
}
}
pathWaypoints.push({
x: 1600,
y: 1000
});
self.pathWaypoints = pathWaypoints;
self.followPath = function () {
if (self.isMovingToWaypoint) return;
if (self.currentWaypoint >= self.pathWaypoints.length) {
self.x += self.speed;
return;
}
var waypoint = self.pathWaypoints[self.currentWaypoint];
var distance = Math.sqrt(Math.pow(waypoint.x - self.x, 2) + Math.pow(waypoint.y - self.y, 2));
var duration = distance / self.speed * 16.67;
self.isMovingToWaypoint = true;
tween(self, {
x: waypoint.x,
y: waypoint.y
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
self.currentWaypoint++;
self.isMovingToWaypoint = false;
}
});
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (!self.target || !self.target.parent) {
self.target = null;
var closestTarget = null;
var closestDistance = Infinity;
// Prioritize towers over base due to long range
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower && tower.parent) {
var towerDistance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (towerDistance <= self.range && towerDistance < closestDistance) {
closestDistance = towerDistance;
closestTarget = tower;
}
}
}
// Check base if no towers in range
if (!closestTarget && enemyBase && enemyBase.parent) {
var baseDistance = Math.sqrt(Math.pow(enemyBase.x - self.x, 2) + Math.pow(enemyBase.y - self.y, 2));
if (baseDistance <= self.range) {
closestTarget = enemyBase;
closestDistance = baseDistance;
}
}
self.target = closestTarget;
}
if (self.target && self.shootCooldown <= 0) {
var bullet = new UnitBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = self.target;
bullet.damage = self.damage;
bullet.speed = 12; // Faster bullet
unitBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = 120; // Slow shooting rate
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (!self.target) {
self.followPath();
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var TowerBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('towerBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.target = null;
self.damage = 30;
self.update = function () {
if (!self.target || !self.target.parent) {
// Target destroyed, remove bullet
return;
}
// Move towards target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
// Hit target
if (self.target.takeDamage) {
self.target.takeDamage(self.damage);
LK.getSound('hit').play();
// Handle splash damage
if (self.splashRadius && self.splashRadius > 0) {
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
if (unit !== self.target && unit.parent) {
var splashDistance = Math.sqrt(Math.pow(unit.x - self.target.x, 2) + Math.pow(unit.y - self.target.y, 2));
if (splashDistance <= self.splashRadius) {
unit.takeDamage(self.splashDamage || Math.floor(self.damage * 0.6));
LK.effects.flashObject(unit, 0xFF6600, 200);
}
}
}
// Visual splash effect
LK.effects.flashObject(self.target, 0xFF6600, 400);
}
}
// Handle piercing
if (self.pierceRemaining && self.pierceRemaining > 0) {
self.pierceRemaining--;
// Find next target in line
var nextTarget = null;
var minDistance = Infinity;
var bulletDirection = {
x: (self.target.x - self.x) / distance,
y: (self.target.y - self.y) / distance
};
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
if (unit !== self.target && unit.parent) {
var unitDistance = Math.sqrt(Math.pow(unit.x - self.target.x, 2) + Math.pow(unit.y - self.target.y, 2));
// Check if unit is roughly in the same direction
var unitDirection = {
x: (unit.x - self.target.x) / unitDistance,
y: (unit.y - self.target.y) / unitDistance
};
var dotProduct = bulletDirection.x * unitDirection.x + bulletDirection.y * unitDirection.y;
if (dotProduct > 0.7 && unitDistance < minDistance && unitDistance < 150) {
// Within piercing range
minDistance = unitDistance;
nextTarget = unit;
}
}
}
if (nextTarget) {
self.target = nextTarget;
// Continue moving towards new target
return;
}
}
return; // Bullet will be removed
}
// Normalize and move
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
};
return self;
});
var TowerSlot = Container.expand(function () {
var self = Container.call(this);
var slotGraphics = self.attachAsset('towerSlot', {
anchorX: 0.5,
anchorY: 0.5
});
slotGraphics.alpha = 0.3;
self.occupied = false;
self.tower = null;
self.placeTower = function () {
if (!self.occupied) {
var tower = new EnemyTower();
tower.x = self.x;
tower.y = self.y;
tower.slot = self;
tower.updateStats(); // Initialize stats properly
enemyTowers.push(tower);
game.addChild(tower);
self.occupied = true;
self.tower = tower;
slotGraphics.alpha = 0.1;
return tower;
}
return null;
};
self.removeTower = function () {
if (self.occupied && self.tower) {
self.occupied = false;
self.tower = null;
slotGraphics.alpha = 0.3;
}
};
return self;
});
var Trap = Container.expand(function () {
var self = Container.call(this);
var trapGraphics = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
trapGraphics.tint = 0x8B4513; // Brown color for trap
trapGraphics.scaleX = 0.8;
trapGraphics.scaleY = 0.8;
trapGraphics.alpha = 0.6; // Semi-transparent to show it's hidden
// Upgrade indicator (initially hidden)
var upgradeIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeIndicator.x = 15;
upgradeIndicator.y = -15;
upgradeIndicator.visible = false;
self.towerType = 'trap';
self.level = 1;
self.maxLevel = 3;
self.health = 1; // Traps are destroyed after one use
self.maxHealth = 1;
self.baseDamage = 60;
self.baseRange = 40;
self.damage = self.baseDamage; // High damage when triggered
self.range = self.baseRange; // Detection range for triggering
self.triggered = false;
self.cost = 50;
self.updateStats = function () {
var multiplier = 1 + (self.level - 1) * 0.8; // 80% increase per level
self.damage = Math.floor(self.baseDamage * multiplier);
self.range = Math.floor(self.baseRange * multiplier);
// Update visual appearance based on level
if (self.level === 2) {
trapGraphics.tint = 0xA0522D; // Darker brown
trapGraphics.scaleX = 1.0;
trapGraphics.scaleY = 1.0;
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xffd700; // Gold
} else if (self.level === 3) {
trapGraphics.tint = 0x8B0000; // Dark red
trapGraphics.scaleX = 1.2;
trapGraphics.scaleY = 1.2;
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xff4500; // Orange red
}
};
self.canUpgrade = function () {
return self.level < self.maxLevel && !self.triggered;
};
self.getUpgradeCost = function () {
return 30 + (self.level - 1) * 20; // Cost increases with level
};
self.upgrade = function () {
if (self.canUpgrade()) {
self.level++;
self.updateStats();
LK.effects.flashObject(self, 0xffd700, 500);
return true;
}
return false;
};
self.update = function () {
if (self.triggered) return; // Already triggered
// Check for player units in range
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.range) {
// Trigger trap
self.triggered = true;
unit.takeDamage(self.damage);
// Visual explosion effect
LK.effects.flashObject(self, 0xFF6600, 400);
LK.effects.flashObject(unit, 0xFF0000, 300);
// Create explosion visual
trapGraphics.tint = 0xFF6600;
trapGraphics.alpha = 1.0;
tween(trapGraphics, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut
});
LK.getSound('hit').play();
return true; // Trap is destroyed
}
}
};
self.takeDamage = function (damage) {
// Traps take minimal damage from ranged attacks
self.health -= Math.max(1, Math.floor(damage * 0.1));
if (self.health <= 0) {
return true; // Trap is destroyed
}
return false;
};
return self;
});
var UnitBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('unitBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.target = null;
self.damage = 25;
self.update = function () {
if (!self.target || !self.target.parent) {
// Target destroyed, remove bullet
return;
}
// Move towards target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
// Hit target
if (self.target.takeDamage) {
self.target.takeDamage(self.damage);
LK.getSound('hit').play();
}
return; // Bullet will be removed
}
// Normalize and move
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('basicTowerAsset', {
anchorX: 0.5,
anchorY: 0.5
});
wallGraphics.tint = 0x696969; // Gray color for wall
wallGraphics.scaleX = 1.2;
wallGraphics.scaleY = 0.8; // Make it look more like a wall
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 0;
healthBarBg.y = -40;
// Health bar foreground
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.x = 0;
healthBar.y = -40;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
};
// Upgrade indicator (initially hidden)
var upgradeIndicator = self.attachAsset('upgradeIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeIndicator.x = 25;
upgradeIndicator.y = -25;
upgradeIndicator.visible = false;
self.towerType = 'wall';
self.level = 1;
self.maxLevel = 3;
self.baseHealth = 300;
self.baseBlockRadius = 60;
self.health = self.baseHealth; // High health to absorb damage
self.maxHealth = self.baseHealth;
self.blockRadius = self.baseBlockRadius; // Radius that blocks unit movement
self.cost = 40;
self.updateStats = function () {
var multiplier = 1 + (self.level - 1) * 0.6; // 60% increase per level
self.maxHealth = Math.floor(self.baseHealth * multiplier);
self.health = Math.min(self.health, self.maxHealth);
self.blockRadius = Math.floor(self.baseBlockRadius * multiplier);
// Update visual appearance based on level
if (self.level === 2) {
wallGraphics.tint = 0x808080; // Darker gray
wallGraphics.scaleX = 1.4;
wallGraphics.scaleY = 1.0;
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xffd700; // Gold
} else if (self.level === 3) {
wallGraphics.tint = 0x2F4F4F; // Dark slate gray
wallGraphics.scaleX = 1.6;
wallGraphics.scaleY = 1.2;
upgradeIndicator.visible = true;
upgradeIndicator.tint = 0xff4500; // Orange red
}
};
self.canUpgrade = function () {
return self.level < self.maxLevel;
};
self.getUpgradeCost = function () {
return 25 + (self.level - 1) * 15; // Cost increases with level
};
self.upgrade = function () {
if (self.canUpgrade()) {
self.level++;
self.updateStats();
LK.effects.flashObject(self, 0xffd700, 500);
return true;
}
return false;
};
self.update = function () {
// Mine ability for level 3
if (self.level === 3 && self.mineCooldown <= 0) {
// Check if any enemies are touching the wall
var enemiesInContact = 0;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.blockRadius + 20) {
enemiesInContact++;
}
}
// Trigger mine explosion if enemies are touching the wall
if (enemiesInContact > 0) {
// Damage all enemies within mine radius
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var distance = Math.sqrt(Math.pow(unit.x - self.x, 2) + Math.pow(unit.y - self.y, 2));
if (distance <= self.mineRadius) {
unit.takeDamage(self.mineDamage);
LK.effects.flashObject(unit, 0xFF6600, 400);
}
}
// Visual mine explosion effect
wallGraphics.tint = 0xFF6600; // Orange explosion color
LK.effects.flashObject(self, 0xFF6600, 600);
// Screen flash effect for mine explosion
LK.effects.flashScreen(0xFF6600, 200);
self.mineCooldown = 600; // 10 second cooldown
// Return to normal color after effect
var self_ref = self;
LK.setTimeout(function () {
self_ref.wallGraphics.tint = 0x2F4F4F; // Back to level 3 color
}, 600);
}
}
// Update mine cooldown
if (self.mineCooldown > 0) {
self.mineCooldown--;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true; // Wall is destroyed
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2d5016
});
/****
* Game Code
****/
// Game arrays to track entities
// Initialize game assets
var playerUnits = [];
var enemyTowers = [];
var unitBullets = [];
var towerBullets = [];
var towerSlots = [];
var enemyBase;
// Game resources
var coins = 100;
var unitCost = 250;
// Ability system
var abilities = {
lightning: {
name: 'Lightning Strike',
cooldown: 900,
// 15 seconds
currentCooldown: 0,
description: 'Strikes all towers with lightning'
},
heal: {
name: 'Mass Heal',
cooldown: 600,
// 10 seconds
currentCooldown: 0,
description: 'Heals all your units to full health'
},
freeze: {
name: 'Time Freeze',
cooldown: 1200,
// 20 seconds
currentCooldown: 0,
description: 'Freezes all towers for 5 seconds'
},
boost: {
name: 'Unit Boost',
cooldown: 480,
// 8 seconds
currentCooldown: 0,
description: 'Doubles unit damage for 8 seconds'
}
};
// UI Elements
var coinsText = new Text2('Coins: ' + coins, {
size: 80,
fill: 0xFFD700
});
coinsText.anchor.set(1, 0);
coinsText.x = -20; // Small margin from right edge
coinsText.y = 20; // Small margin from top
LK.gui.topRight.addChild(coinsText);
var coinIcon = LK.getAsset('coinIcon', {
anchorX: 0.5,
anchorY: 0.5
});
coinIcon.x = -coinsText.width - 60; // Position to left of text
coinIcon.y = 45; // Align with text
LK.gui.topRight.addChild(coinIcon);
var healthText = new Text2('Base Health: 500', {
size: 50,
fill: 0xFFFFFF
});
healthText.anchor.set(0.5, 0);
LK.gui.top.addChild(healthText);
// Unit spawn buttons
var basicButton = new Text2('Basic (300)', {
size: 40,
fill: 0x87CEEB
});
basicButton.anchor.set(0.5, 1);
basicButton.x = -300;
basicButton.interactive = true;
basicButton.down = function () {
if (coins >= 300) {
spawnUnit('basic');
}
};
LK.gui.bottom.addChild(basicButton);
var fastButton = new Text2('Fast (450)', {
size: 40,
fill: 0x32CD32
});
fastButton.anchor.set(0.5, 1);
fastButton.x = -100;
fastButton.interactive = true;
fastButton.down = function () {
if (coins >= 450) {
spawnUnit('fast');
}
};
LK.gui.bottom.addChild(fastButton);
var heavyButton = new Text2('Heavy (650)', {
size: 40,
fill: 0x8B4513
});
heavyButton.anchor.set(0.5, 1);
heavyButton.x = 100;
heavyButton.interactive = true;
heavyButton.down = function () {
if (coins >= 650) {
spawnUnit('heavy');
}
};
LK.gui.bottom.addChild(heavyButton);
var sniperButton = new Text2('Sniper (600)', {
size: 40,
fill: 0x4B0082
});
sniperButton.anchor.set(0.5, 1);
sniperButton.x = 200;
sniperButton.interactive = true;
sniperButton.down = function () {
if (coins >= 600) {
spawnUnit('sniper');
}
};
LK.gui.bottom.addChild(sniperButton);
var meleeButton = new Text2('Melee (250)', {
size: 40,
fill: 0xFF6347
});
meleeButton.anchor.set(0.5, 1);
meleeButton.x = 400;
meleeButton.interactive = true;
meleeButton.down = function () {
if (coins >= 250) {
spawnUnit('melee');
}
};
LK.gui.bottom.addChild(meleeButton);
// Ability buttons
var lightningButton = new Text2('Lightning', {
size: 35,
fill: 0xFFFF00
});
lightningButton.anchor.set(0, 0.5);
lightningButton.x = 20;
lightningButton.y = -200;
lightningButton.interactive = true;
lightningButton.down = function () {
useAbility('lightning');
};
LK.gui.left.addChild(lightningButton);
var healButton = new Text2('Heal All', {
size: 35,
fill: 0x00FF00
});
healButton.anchor.set(0, 0.5);
healButton.x = 20;
healButton.y = -100;
healButton.interactive = true;
healButton.down = function () {
useAbility('heal');
};
LK.gui.left.addChild(healButton);
var freezeButton = new Text2('Freeze', {
size: 35,
fill: 0x00FFFF
});
freezeButton.anchor.set(0, 0.5);
freezeButton.x = 20;
freezeButton.y = 0;
freezeButton.interactive = true;
freezeButton.down = function () {
useAbility('freeze');
};
LK.gui.left.addChild(freezeButton);
var boostButton = new Text2('Boost', {
size: 35,
fill: 0xFF8C00
});
boostButton.anchor.set(0, 0.5);
boostButton.x = 20;
boostButton.y = 100;
boostButton.interactive = true;
boostButton.down = function () {
useAbility('boost');
};
LK.gui.left.addChild(boostButton);
// Create enemy base
enemyBase = game.addChild(new EnemyBase());
enemyBase.x = 1800;
enemyBase.y = 2732 / 2;
// Create tower slots in a grid
var gridStartX = 800;
var gridStartY = 600;
var gridSpacingX = 200;
var gridSpacingY = 200;
var gridRows = 8;
var gridCols = 4;
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var slot = new TowerSlot();
slot.x = gridStartX + col * gridSpacingX;
slot.y = gridStartY + row * gridSpacingY;
towerSlots.push(slot);
game.addChild(slot);
}
}
// Initially place some towers in random slots
var initialTowerCount = 8;
var placedTowers = 0;
while (placedTowers < initialTowerCount && placedTowers < towerSlots.length) {
var randomSlotIndex = Math.floor(Math.random() * towerSlots.length);
var slot = towerSlots[randomSlotIndex];
if (slot.placeTower()) {
// Initialize health bar for the newly placed tower
if (slot.tower && slot.tower.updateHealthBar) {
slot.tower.updateHealthBar();
}
placedTowers++;
}
}
function useAbility(abilityType) {
var ability = abilities[abilityType];
if (!ability || ability.currentCooldown > 0) {
return false;
}
ability.currentCooldown = ability.cooldown;
// Enhanced visual feedback - pulse effect on ability button and coins
switch (abilityType) {
case 'lightning':
// Enhanced lightning visual effects
tween(lightningButton, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.7
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(lightningButton, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.bounceOut
});
}
});
// Strike all towers with lightning
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
tower.takeDamage(80);
LK.effects.flashObject(tower, 0xFFFF00, 500);
// Add scaling effect to towers when hit by lightning
tween(tower, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(tower, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.elasticOut
});
}
});
}
// Strike the base too
if (enemyBase) {
enemyBase.takeDamage(120);
LK.effects.flashObject(enemyBase, 0xFFFF00, 800);
tween(enemyBase, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemyBase, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.elasticOut
});
}
});
}
LK.effects.flashScreen(0xFFFF00, 300);
break;
case 'heal':
// Enhanced heal visual effects
tween(healButton, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0x00FFFF
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(healButton, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0x00FF00
}, {
duration: 400,
easing: tween.bounceOut
});
}
});
// Heal all units to full health
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
unit.health = unit.maxHealth;
unit.updateHealthBar();
LK.effects.flashObject(unit, 0x00FF00, 400);
// Add pulsing effect to healed units
tween(unit, {
scaleX: 1.15,
scaleY: 1.15,
alpha: 0.8
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(unit, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.bounceOut
});
}
});
}
LK.effects.flashScreen(0x00FF00, 200);
break;
case 'freeze':
// Enhanced freeze visual effects
tween(freezeButton, {
scaleX: 1.4,
scaleY: 1.4,
rotation: 0.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(freezeButton, {
scaleX: 1.0,
scaleY: 1.0,
rotation: 0
}, {
duration: 500,
easing: tween.elasticOut
});
}
});
// Freeze all towers for 5 seconds
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
tower.frozenUntil = LK.ticks + 300; // 5 seconds
tower.originalTint = tower.tint || 0xFFFFFF;
LK.effects.flashObject(tower, 0x00FFFF, 600);
// Add shaking effect to frozen towers
tween(tower, {
x: tower.x + 5,
y: tower.y + 5
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(tower, {
x: tower.x - 5,
y: tower.y - 5
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(tower, {
x: tower.x,
y: tower.y
}, {
duration: 100,
easing: tween.easeOut
});
}
});
}
});
}
// Freeze the base too
if (enemyBase) {
enemyBase.frozenUntil = LK.ticks + 300;
LK.effects.flashObject(enemyBase, 0x00FFFF, 600);
}
LK.effects.flashScreen(0x00FFFF, 400);
break;
case 'boost':
// Enhanced boost visual effects
tween(boostButton, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFFD700
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boostButton, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFF8C00
}, {
duration: 400,
easing: tween.bounceOut
});
}
});
// Double unit damage for 8 seconds
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
if (!unit.boostedUntil) {
unit.originalDamage = unit.damage;
}
unit.damage = unit.originalDamage * 2;
unit.boostedUntil = LK.ticks + 480; // 8 seconds
LK.effects.flashObject(unit, 0xFF8C00, 500);
// Add growing effect to boosted units
tween(unit, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFFD700
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(unit, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
LK.effects.flashScreen(0xFF8C00, 300);
break;
}
return true;
}
function spawnUnit(unitType) {
if (!unitType) unitType = 'basic';
var unitClass = null;
var cost = 40;
switch (unitType) {
case 'basic':
unitClass = BasicUnit;
cost = 300;
break;
case 'fast':
unitClass = FastUnit;
cost = 450;
break;
case 'heavy':
unitClass = HeavyUnit;
cost = 650;
break;
case 'sniper':
unitClass = SniperUnit;
cost = 600;
break;
case 'melee':
unitClass = MeleeUnit;
cost = 250;
break;
default:
unitClass = BasicUnit;
cost = 200;
break;
}
if (coins >= cost && unitClass) {
coins -= cost;
coinsText.setText('Coins: ' + coins);
coinIcon.x = -coinsText.width - 60;
// Spawn 5 units
for (var unitIndex = 0; unitIndex < 5; unitIndex++) {
var unit = new unitClass();
unit.x = 100;
unit.y = 500 + (playerUnits.length + unitIndex) % 8 * 100;
// Create path that goes through all tower positions
var pathWaypoints = [];
// Start from spawn position
pathWaypoints.push({
x: 400,
y: unit.y
});
// Add waypoints to pass through tower grid
var gridStartX = 800;
var gridStartY = 600;
var gridSpacingX = 200;
var gridSpacingY = 200;
var gridRows = 8;
var gridCols = 4;
// Create serpentine path through tower grid
for (var col = 0; col < gridCols; col++) {
if (col % 2 === 0) {
// Go down for even columns
for (var row = 0; row < gridRows; row++) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
} else {
// Go up for odd columns
for (var row = gridRows - 1; row >= 0; row--) {
pathWaypoints.push({
x: gridStartX + col * gridSpacingX,
y: gridStartY + row * gridSpacingY
});
}
}
}
// Final waypoint towards the base
pathWaypoints.push({
x: 1600,
y: 1366
});
unit.pathWaypoints = pathWaypoints;
playerUnits.push(unit);
game.addChild(unit);
unit.updateHealthBar();
}
LK.getSound('unitSpawn').play();
}
}
// Coin generation timer
var coinTimer = 0;
// Enemy AI system
var enemyAI = {
coins: 400,
towerCost: 75,
lastTowerPlacement: 0,
placementCooldown: 180,
// 3 seconds at 60fps
update: function update() {
// Generate coins for enemy AI
if (LK.ticks % 120 === 0) {
// Every 2 seconds
this.coins += 75;
}
// Force spending if AI has too many coins
var forceSpend = this.coins >= 1000;
// Check if should place new towers
if (this.coins >= this.towerCost && LK.ticks - this.lastTowerPlacement > this.placementCooldown || forceSpend) {
this.considerTowerPlacement();
}
// Upgrade existing towers (with forced spending)
this.considerTowerUpgrades(forceSpend);
// Repair damaged towers
this.repairTowers();
},
considerTowerPlacement: function considerTowerPlacement() {
// First priority: Check if we need heal towers near damaged towers
var needsHealTower = false;
var healSlot = null;
var damagedTowerNeedingHeal = null;
// Find damaged towers that need healing
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower.health < tower.maxHealth * 0.6) {
// If tower is below 60% health
// Check if there's already a heal tower nearby
var hasHealerNearby = false;
for (var j = 0; j < enemyTowers.length; j++) {
var otherTower = enemyTowers[j];
if (otherTower.towerType === 'heal') {
var distance = Math.sqrt(Math.pow(tower.x - otherTower.x, 2) + Math.pow(tower.y - otherTower.y, 2));
if (distance <= 200) {
// Within healing range
hasHealerNearby = true;
break;
}
}
}
if (!hasHealerNearby) {
damagedTowerNeedingHeal = tower;
needsHealTower = true;
break;
}
}
}
// If we need a heal tower and can afford it, find best slot near damaged tower
if (needsHealTower && damagedTowerNeedingHeal && this.coins >= 110) {
var bestDistance = Infinity;
var forceSpendHeal = this.coins >= 1000;
for (var i = 0; i < towerSlots.length; i++) {
var slot = towerSlots[i];
if (!slot.occupied) {
// Check if there are any units too close to this heal slot (within 120 pixels)
var hasUnitsNearbyHeal = false;
for (var k = 0; k < playerUnits.length; k++) {
var unit = playerUnits[k];
var unitDistance = Math.sqrt(Math.pow(unit.x - slot.x, 2) + Math.pow(unit.y - slot.y, 2));
if (unitDistance < 120) {
// Don't build heal tower if units are too close
hasUnitsNearbyHeal = true;
break;
}
}
// Skip this slot if units are too close (unless forced to spend)
if (hasUnitsNearbyHeal && !forceSpendHeal) {
continue;
}
var distance = Math.sqrt(Math.pow(damagedTowerNeedingHeal.x - slot.x, 2) + Math.pow(damagedTowerNeedingHeal.y - slot.y, 2));
if (distance < bestDistance && distance <= 200) {
// Within heal range
bestDistance = distance;
healSlot = slot;
}
}
}
// Place heal tower if we found a good slot
if (healSlot) {
var tower = new HealTower();
tower.x = healSlot.x;
tower.y = healSlot.y;
tower.slot = healSlot;
enemyTowers.push(tower);
game.addChild(tower);
healSlot.occupied = true;
healSlot.tower = tower;
healSlot.attachAsset('towerSlot', {}).alpha = 0.1;
this.coins -= 110;
this.lastTowerPlacement = LK.ticks;
// Initialize health bar
if (tower.updateHealthBar) {
tower.updateHealthBar();
}
// Visual feedback
LK.effects.flashObject(healSlot, 0x00ff00, 500);
return; // Exit early after placing heal tower
}
}
// Second priority: Find empty slots closest to player units for combat towers
var bestSlot = null;
var minDistance = Infinity;
var forceSpend = this.coins >= 1000;
for (var i = 0; i < towerSlots.length; i++) {
var slot = towerSlots[i];
if (!slot.occupied) {
// Check if there are any units too close to this slot (within 120 pixels)
var hasUnitsNearby = false;
for (var k = 0; k < playerUnits.length; k++) {
var unit = playerUnits[k];
var unitDistance = Math.sqrt(Math.pow(unit.x - slot.x, 2) + Math.pow(unit.y - slot.y, 2));
if (unitDistance < 120) {
// Don't build if units are too close
hasUnitsNearby = true;
break;
}
}
// Skip this slot if units are too close (unless forced to spend)
if (hasUnitsNearby && !forceSpend) {
continue;
}
// Calculate average distance to all player units
var totalDistance = 0;
var unitCount = 0;
for (var j = 0; j < playerUnits.length; j++) {
var unit = playerUnits[j];
var distance = Math.sqrt(Math.pow(unit.x - slot.x, 2) + Math.pow(unit.y - slot.y, 2));
totalDistance += distance;
unitCount++;
}
if (unitCount > 0) {
var avgDistance = totalDistance / unitCount;
// Force placement if we have too many coins, otherwise use distance restriction
if (avgDistance < minDistance && (avgDistance < 400 || forceSpend)) {
minDistance = avgDistance;
bestSlot = slot;
}
} else if (forceSpend && !bestSlot) {
// If forced to spend and no players around, pick any empty slot
bestSlot = slot;
}
}
}
// Place tower at best location
if (bestSlot) {
// Strategic placement: Check if we should place walls or traps based on unit density
var nearbyUnits = 0;
var averageUnitDistance = 0;
for (var k = 0; k < playerUnits.length; k++) {
var unit = playerUnits[k];
var unitDist = Math.sqrt(Math.pow(unit.x - bestSlot.x, 2) + Math.pow(unit.y - bestSlot.y, 2));
if (unitDist <= 300) {
nearbyUnits++;
averageUnitDistance += unitDist;
}
}
if (nearbyUnits > 0) {
averageUnitDistance /= nearbyUnits;
}
// Choose tower type based on strategy and available coins
var towerTypes = [];
// Add defensive structures first if units are close
if (nearbyUnits >= 3 && this.coins >= 40) towerTypes.push('wall');
if (nearbyUnits >= 2 && averageUnitDistance < 150 && this.coins >= 50) towerTypes.push('trap');
// Add combat towers
if (this.coins >= 75) towerTypes.push('basic');
if (this.coins >= 85) towerTypes.push('slow');
if (this.coins >= 95) towerTypes.push('poison');
if (this.coins >= 100) towerTypes.push('fast');
if (this.coins >= 125) towerTypes.push('sniper');
if (this.coins >= 130) towerTypes.push('area');
if (this.coins >= 140) towerTypes.push('laser');
if (this.coins >= 150) towerTypes.push('heavy');
if (this.coins >= 200) towerTypes.push('resurrection');
if (towerTypes.length > 0) {
var randomType = towerTypes[Math.floor(Math.random() * towerTypes.length)];
var tower = null;
var cost = 75;
switch (randomType) {
case 'wall':
tower = new Wall();
cost = 40;
break;
case 'trap':
tower = new Trap();
cost = 50;
break;
case 'basic':
tower = new BasicTower();
cost = 75;
break;
case 'slow':
tower = new SlowTower();
cost = 85;
break;
case 'poison':
tower = new PoisonTower();
cost = 95;
break;
case 'fast':
tower = new FastTower();
cost = 100;
break;
case 'sniper':
tower = new SniperTower();
cost = 125;
break;
case 'area':
tower = new AreaTower();
cost = 130;
break;
case 'laser':
tower = new LaserTower();
cost = 140;
break;
case 'heavy':
tower = new HeavyTower();
cost = 150;
break;
case 'resurrection':
tower = new ResurrectionTower();
cost = 200;
break;
}
if (tower) {
tower.x = bestSlot.x;
tower.y = bestSlot.y;
tower.slot = bestSlot;
enemyTowers.push(tower);
game.addChild(tower);
bestSlot.occupied = true;
bestSlot.tower = tower;
bestSlot.attachAsset('towerSlot', {}).alpha = 0.1;
this.coins -= cost;
this.lastTowerPlacement = LK.ticks;
// Initialize health bar for towers that have them
if (tower.updateHealthBar) {
tower.updateHealthBar();
}
// Visual feedback
var feedbackColor = 0xff0000; // Red for combat towers
if (randomType === 'wall') feedbackColor = 0x696969; // Gray for walls
if (randomType === 'trap') feedbackColor = 0x8B4513; // Brown for traps
LK.effects.flashObject(bestSlot, feedbackColor, 500);
}
}
}
},
considerTowerUpgrades: function considerTowerUpgrades(forceSpend) {
// Find towers that can be upgraded and are in combat zones
var upgradeTargets = [];
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower.canUpgrade && tower.canUpgrade()) {
var upgradeCost = tower.getUpgradeCost();
if (this.coins >= upgradeCost) {
// Check if tower is in an active combat zone (has nearby player units)
var nearbyUnits = 0;
var priority = 0;
for (var j = 0; j < playerUnits.length; j++) {
var unit = playerUnits[j];
var distance = Math.sqrt(Math.pow(unit.x - tower.x, 2) + Math.pow(unit.y - tower.y, 2));
// Special range consideration for traps and walls
var checkRange = tower.range + 100;
if (tower.towerType === 'trap') {
checkRange = tower.range + 50; // Shorter range check for traps
} else if (tower.towerType === 'wall') {
checkRange = tower.blockRadius + 80; // Check block radius for walls
}
if (distance <= checkRange) {
nearbyUnits++;
}
}
// Calculate upgrade priority with special consideration for traps and walls
if (nearbyUnits > 0) {
priority = nearbyUnits * 10 + (tower.maxLevel - tower.level + 1) * 5;
// Higher priority for traps and walls in active zones
if (tower.towerType === 'trap' || tower.towerType === 'wall') {
priority += nearbyUnits * 5; // Extra priority for defensive structures
}
} else if (this.coins > upgradeCost * 3 || forceSpend) {
priority = 1; // Low priority for non-combat upgrades
}
if (priority > 0) {
upgradeTargets.push({
tower: tower,
cost: upgradeCost,
priority: priority
});
}
}
}
}
// Sort by priority and upgrade the best candidate
if (upgradeTargets.length > 0) {
upgradeTargets.sort(function (a, b) {
return b.priority - a.priority;
});
var bestTarget = upgradeTargets[0];
if (bestTarget.tower.upgrade && bestTarget.tower.upgrade()) {
this.coins -= bestTarget.cost;
// Visual feedback
LK.effects.flashObject(bestTarget.tower, 0xFFD700, 600);
}
}
},
repairTowers: function repairTowers() {
// Find damaged towers and repair them
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower.health < tower.maxHealth * 0.5 && this.coins >= 25) {
tower.health = Math.min(tower.health + 25, tower.maxHealth);
this.coins -= 25;
LK.effects.flashObject(tower, 0x00ff00, 300);
break; // Only repair one tower per update
}
}
}
};
// Touch controls for spawning units
game.down = function (x, y, obj) {
if (coins >= 300) {
spawnUnit('basic');
}
};
game.update = function () {
// Update enemy AI
enemyAI.update();
// Generate coins over time
coinTimer++;
if (coinTimer >= 180) {
// Every 3 seconds
coins += 50;
coinsText.setText('Coins: ' + coins);
coinIcon.x = -coinsText.width - 60; // Update icon position
coinTimer = 0;
}
// Update unit bullets
for (var i = unitBullets.length - 1; i >= 0; i--) {
var bullet = unitBullets[i];
if (!bullet.target || !bullet.target.parent) {
bullet.destroy();
unitBullets.splice(i, 1);
continue;
}
var dx = bullet.target.x - bullet.x;
var dy = bullet.target.y - bullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 15) {
// Hit target
var isDead = bullet.target.takeDamage(bullet.damage);
if (isDead) {
// Remove from appropriate array
if (bullet.target === enemyBase) {
// Player wins!
LK.effects.flashScreen(0x00ff00, 2000);
LK.showYouWin();
return;
} else {
// Remove tower
for (var j = 0; j < enemyTowers.length; j++) {
if (enemyTowers[j] === bullet.target) {
// Calculate money reward based on tower type and level
var moneyReward = 225; // Base reward (75 * 3)
if (enemyTowers[j].towerType) {
switch (enemyTowers[j].towerType) {
case 'wall':
moneyReward = 60 + (enemyTowers[j].level ? (enemyTowers[j].level - 1) * 45 : 0);
break;
case 'trap':
moneyReward = 75 + (enemyTowers[j].level ? (enemyTowers[j].level - 1) * 60 : 0);
break;
case 'slow':
moneyReward = 255;
break;
case 'poison':
moneyReward = 285;
break;
case 'fast':
moneyReward = 300;
break;
case 'heal':
moneyReward = 330;
break;
case 'sniper':
moneyReward = 375;
break;
case 'area':
moneyReward = 390;
break;
case 'laser':
moneyReward = 420;
break;
case 'heavy':
moneyReward = 450;
break;
case 'resurrection':
moneyReward = 600;
break;
default:
moneyReward = 225;
break;
}
} else if (enemyTowers[j].level) {
// For upgradeable towers, reward based on level
moneyReward = 225 + (enemyTowers[j].level - 1) * 150;
}
// Clean up slot if tower has one
if (enemyTowers[j].slot) {
// Remember tower type for potential resurrection
enemyTowers[j].slot.lastTowerType = enemyTowers[j].towerType;
enemyTowers[j].slot.removeTower();
}
enemyTowers[j].destroy();
enemyTowers.splice(j, 1);
coins += moneyReward; // Money reward for destroying tower
coinsText.setText('Coins: ' + coins);
coinIcon.x = -coinsText.width - 60; // Update icon position
break;
}
}
}
}
bullet.destroy();
unitBullets.splice(i, 1);
}
}
// Handle unit status effects (slow, poison)
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
// Handle slow effect restoration
if (unit.slowEndTime && LK.ticks >= unit.slowEndTime) {
if (unit.originalSpeed) {
unit.speed = unit.originalSpeed;
unit.originalSpeed = null;
}
unit.slowEndTime = null;
}
// Handle poison damage over time
if (unit.poisonEndTime && LK.ticks >= unit.poisonEndTime) {
unit.poisonDamage = null;
unit.poisonEndTime = null;
unit.lastPoisonTick = null;
} else if (unit.poisonDamage && unit.lastPoisonTick && LK.ticks - unit.lastPoisonTick >= 60) {
// Apply poison damage every second
unit.takeDamage(unit.poisonDamage);
unit.lastPoisonTick = LK.ticks;
LK.effects.flashObject(unit, 0x228B22, 200);
}
}
// Update tower bullets
for (var i = towerBullets.length - 1; i >= 0; i--) {
var bullet = towerBullets[i];
if (!bullet.target || !bullet.target.parent) {
bullet.destroy();
towerBullets.splice(i, 1);
continue;
}
var dx = bullet.target.x - bullet.x;
var dy = bullet.target.y - bullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 15) {
// Hit target
var isDead = bullet.target.takeDamage(bullet.damage);
if (isDead) {
// Remove unit
for (var j = 0; j < playerUnits.length; j++) {
if (playerUnits[j] === bullet.target) {
playerUnits[j].destroy();
playerUnits.splice(j, 1);
break;
}
}
}
bullet.destroy();
towerBullets.splice(i, 1);
}
}
// Lose condition removed - player can no longer lose
// Process trap damage and cleanup
for (var i = enemyTowers.length - 1; i >= 0; i--) {
var tower = enemyTowers[i];
if (tower.towerType === 'trap' && tower.triggered) {
// Clean up slot if tower has one
if (tower.slot) {
tower.slot.removeTower();
}
tower.destroy();
enemyTowers.splice(i, 1);
}
}
// Update base health display
if (enemyBase) {
healthText.setText('Base Health: ' + enemyBase.health);
}
// Update ability cooldowns and button display
for (var abilityName in abilities) {
var ability = abilities[abilityName];
if (ability.currentCooldown > 0) {
ability.currentCooldown--;
}
}
// Update ability button texts and colors
var canUseLightning = abilities.lightning.currentCooldown <= 0;
lightningButton.fill = canUseLightning ? 0xFFFF00 : 0x666666;
lightningButton.setText(abilities.lightning.currentCooldown > 0 ? 'Lightning (' + Math.ceil(abilities.lightning.currentCooldown / 60) + 's)' : 'Lightning');
var canUseHeal = abilities.heal.currentCooldown <= 0;
healButton.fill = canUseHeal ? 0x00FF00 : 0x666666;
healButton.setText(abilities.heal.currentCooldown > 0 ? 'Heal (' + Math.ceil(abilities.heal.currentCooldown / 60) + 's)' : 'Heal All');
var canUseFreeze = abilities.freeze.currentCooldown <= 0;
freezeButton.fill = canUseFreeze ? 0x00FFFF : 0x666666;
freezeButton.setText(abilities.freeze.currentCooldown > 0 ? 'Freeze (' + Math.ceil(abilities.freeze.currentCooldown / 60) + 's)' : 'Freeze');
var canUseBoost = abilities.boost.currentCooldown <= 0;
boostButton.fill = canUseBoost ? 0xFF8C00 : 0x666666;
boostButton.setText(abilities.boost.currentCooldown > 0 ? 'Boost (' + Math.ceil(abilities.boost.currentCooldown / 60) + 's)' : 'Boost');
// Handle frozen towers (disable shooting)
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (tower.frozenUntil && LK.ticks >= tower.frozenUntil) {
tower.frozenUntil = null;
}
}
// Handle frozen base
if (enemyBase && enemyBase.frozenUntil && LK.ticks >= enemyBase.frozenUntil) {
enemyBase.frozenUntil = null;
}
// Handle unit boost expiration
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
if (unit.boostedUntil && LK.ticks >= unit.boostedUntil) {
unit.damage = unit.originalDamage || unit.damage / 2;
unit.boostedUntil = null;
}
}
};
coinIcon. In-Game asset. 2d. High contrast. No shadows
enemyBase. In-Game asset. 2d. High contrast. No shadows
enemyTower. In-Game asset. 2d. High contrast. No shadows
towerBullet. In-Game asset. 2d. High contrast. No shadows
towerSlot. In-Game asset. 2d. High contrast. No shadows
upgradeIndicator. In-Game asset. 2d. High contrast. No shadows
heavyTowerAsset. In-Game asset. 2d. High contrast. No shadows
fastTowerAsset. In-Game asset. 2d. High contrast. No shadows
healTowerAsset. In-Game asset. 2d. High contrast. No shadows
sniperTowerAsset. In-Game asset. 2d. High contrast. No shadows
areaTowerAsset. In-Game asset. 2d. High contrast. No shadows
meleeUnitAsset. In-Game asset. 2d. High contrast. No shadows
laserTowerAsset. In-Game asset. 2d. High contrast. No shadows
poisonTowerAsset. In-Game asset. 2d. High contrast. No shadows
slowTowerAsset. In-Game asset. 2d. High contrast. No shadows
resurrectionTowerAsset. In-Game asset. 2d. High contrast. No shadows