/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { scrapMetal: 0, fireRateLevel: 1, movementSpeedLevel: 1, healthLevel: 1, damageLevel: 1, bulletCountLevel: 1, shieldLevel: 1, shockwaveLevel: 1, maxUpgradeTier: 10, fireRateBaseCost: 50, movementSpeedBaseCost: 75, healthBaseCost: 100, damageBaseCost: 80, bulletCountBaseCost: 120, shieldBaseCost: 150, shockwaveBaseCost: 200, highScore: 0, bestWave: 1, longestSurvivalTime: 0, totalGamesPlayed: 0, totalEnemiesDefeated: 0, achievementUnlocked: false }); /**** * Classes ****/ var AdaptiveEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); enemyGraphics.tint = 0x8A2BE2; // Purple for adaptive enemy self.health = 4; self.maxHealth = 4; self.speed = 1.2; self.damage = 12; self.adaptationLevel = 0; self.adaptationTimer = 0; self.adaptationInterval = 300; // Adapt every 5 seconds self.lastX = 0; self.lastY = 0; self.lastCastleDistanceSquared = Infinity; // Create health bar for this enemy self.healthBar = new EnemyHealthBar(); self.healthBar.y = -50; // Position above enemy enemyHealthBars.push(self.healthBar); self.addChild(self.healthBar); self.update = function () { // Adaptation logic - enemy becomes stronger over time self.adaptationTimer++; if (self.adaptationTimer >= self.adaptationInterval && self.adaptationLevel < 3) { self.adaptationLevel++; self.adaptationTimer = 0; // Increase stats based on adaptation level self.speed += 0.3; self.damage += 3; self.maxHealth += 2; self.health = Math.min(self.maxHealth, self.health + 2); // Visual feedback for adaptation LK.effects.flashObject(self, 0x8A2BE2, 500); tween(self, { scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } }); } // Move toward castle var dx = castle.x - self.x; var dy = castle.y - self.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared > 3600) { var distance = Math.sqrt(distanceSquared); var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } // Check if reached castle if (self.lastCastleDistanceSquared > 3600 && distanceSquared <= 3600) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistanceSquared = distanceSquared; }; self.takeDamage = function (damage) { // Adaptive enemy takes less damage as it adapts var damagereduction = self.adaptationLevel * 0.1; var actualDamage = damage * (1 - damagereduction); self.health -= actualDamage; LK.effects.flashObject(self, 0xFFFFFF, 200); // Update health bar if (self.healthBar) { self.healthBar.updateHealth(self.health, self.maxHealth); } if (self.health <= 0) { LK.setScore(LK.getScore() + 25 + self.adaptationLevel * 10); // More points for adapted enemies scoreText.setText(LK.getScore()); LK.getSound('hit').play(); // Drop more scrap metal based on adaptation level if (Math.random() < 0.7 + self.adaptationLevel * 0.1) { var scrap = new ScrapMetalDrop(); scrap.x = self.x; scrap.y = self.y; scrap.baseY = scrap.y; scrap.value = 5 + self.adaptationLevel * 2; // More valuable based on adaptation scrapMetalDrops.push(scrap); game.addChild(scrap); } self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var Bomb = Container.expand(function () { var self = Container.call(this); var bombGraphics = self.attachAsset('bomb', { anchorX: 0.5, anchorY: 0.5 }); bombGraphics.tint = 0xFF0000; self.explosionRadius = 1000; self.fuseTime = 120; // 2 seconds at 60fps self.blinkSpeed = 10; // Start with small scale self.scaleX = 0.3; self.scaleY = 0.3; self.update = function () { self.fuseTime--; // Blinking effect that gets faster as explosion approaches var blinkInterval = Math.max(5, Math.floor(self.fuseTime / 10)); if (LK.ticks % blinkInterval === 0) { bombGraphics.alpha = bombGraphics.alpha === 1 ? 0.3 : 1; } // Scale pulsing effect var pulseScale = 1 + Math.sin(LK.ticks * 0.3) * 0.1; self.scaleX = pulseScale; self.scaleY = pulseScale; // Explode when fuse runs out if (self.fuseTime <= 0) { self.explode(); } }; self.explode = function () { // Visual explosion effect LK.effects.flashScreen(0xFF4400, 600); // Scale up rapidly during explosion tween(self, { scaleX: 3, scaleY: 3 }, { duration: 300, easing: tween.easeOut }); // Damage enemies in radius for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.explosionRadius) { // Instant kill for enemies in blast radius enemy.takeDamage(999); } } // Remove bomb from game after explosion animation LK.setTimeout(function () { self.destroy(); for (var i = bombs.length - 1; i >= 0; i--) { if (bombs[i] === self) { bombs.splice(i, 1); break; } } }, 300); }; self.activateShield = function () { if (self.shieldCooldown <= 0 && !self.shield) { // Create shield self.shield = new Shield(); self.shield.x = self.x; self.shield.y = self.y; game.addChild(self.shield); // Start cooldown var cooldownReduction = getUpgradeBonus('shield', storage.shieldLevel); self.shieldCooldown = Math.floor(self.shieldMaxCooldown * (1 - cooldownReduction)); // Visual feedback LK.effects.flashScreen(0x0088FF, 300); } }; self.activateShockwave = function () { if (self.shockwaveCooldown <= 0) { // Create shockwave var shockwave = new Shockwave(); shockwave.x = self.x; shockwave.y = self.y; // Increase damage based on upgrade level shockwave.damage = 5 + getUpgradeBonus('shockwave', storage.shockwaveLevel) * 20; // +2 damage per level (0.1 * 20 = 2) shockwaves.push(shockwave); game.addChild(shockwave); // Start cooldown var cooldownReduction = getUpgradeBonus('shockwave', storage.shockwaveLevel); self.shockwaveCooldown = Math.floor(self.shockwaveMaxCooldown * (1 - cooldownReduction)); // Visual feedback LK.effects.flashScreen(0xFF4400, 500); } }; self.update = function () { // Update ability cooldowns if (self.shieldCooldown > 0) { self.shieldCooldown--; } if (self.shockwaveCooldown > 0) { self.shockwaveCooldown--; } // Update shield position if active if (self.shield) { self.shield.x = self.x; self.shield.y = self.y; } if (self.shootCooldown > 0) { self.shootCooldown--; } // Auto-fire with priority targeting system if (self.shootCooldown <= 0 && enemies.length > 0) { var targetEnemy = null; var bestPriority = -1; var bestDistance = Infinity; // Priority levels: Higher number = higher priority var priorityMap = { 'SniperEnemy': 5, // Highest priority - long range damage 'BomberEnemy': 4, // High priority - explosive threat 'BossEnemy': 4, // High priority - powerful enemy 'FastEnemy': 3, // Medium-high priority - quick threat 'HealerEnemy': 3, // Medium-high priority - supports others 'TeleporterEnemy': 2, // Medium priority - unpredictable 'SplitterEnemy': 2, // Medium priority - creates more enemies 'TankEnemy': 1, // Low priority - slow but tough 'ShieldEnemy': 1, // Low priority - tough but manageable 'Enemy': 0 // Lowest priority - basic enemy }; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Determine enemy type by checking constructor name var enemyType = 'Enemy'; // Default if (enemy.constructor === SniperEnemy) enemyType = 'SniperEnemy';else if (enemy.constructor === BomberEnemy) enemyType = 'BomberEnemy';else if (enemy.constructor === BossEnemy) enemyType = 'BossEnemy';else if (enemy.constructor === FastEnemy) enemyType = 'FastEnemy';else if (enemy.constructor === HealerEnemy) enemyType = 'HealerEnemy';else if (enemy.constructor === TeleporterEnemy) enemyType = 'TeleporterEnemy';else if (enemy.constructor === SplitterEnemy) enemyType = 'SplitterEnemy';else if (enemy.constructor === TankEnemy) enemyType = 'TankEnemy';else if (enemy.constructor === ShieldEnemy) enemyType = 'ShieldEnemy'; var priority = priorityMap[enemyType] || 0; // Select target based on priority first, then distance if (priority > bestPriority || priority === bestPriority && distance < bestDistance) { bestPriority = priority; bestDistance = distance; targetEnemy = enemy; } } if (targetEnemy) { self.fireAtTarget(targetEnemy); self.shootCooldown = self.shootInterval; } } }; return self; }); var BombReward = Container.expand(function () { var self = Container.call(this); var rewardGraphics = self.attachAsset('bombReward', { anchorX: 0.5, anchorY: 0.5 }); // Make it visually distinct with orange tint rewardGraphics.tint = 0xFF8800; self.bobOffset = Math.random() * Math.PI * 2; self.baseY = 0; self.lifetime = 420; // 7 seconds at 60fps self.update = function () { // Gentle bobbing animation self.y = self.baseY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5; // Decrease lifetime self.lifetime--; // Start fading when close to expiring if (self.lifetime <= 120) { var alpha = self.lifetime / 120; rewardGraphics.alpha = alpha; // Add flashing animation when close to expiring var flashInterval = Math.max(5, Math.floor(self.lifetime / 20)); if (LK.ticks % flashInterval === 0) { tween(rewardGraphics, { scaleX: 1.3, scaleY: 1.3 }, { duration: flashInterval * 8, easing: tween.easeOut, onFinish: function onFinish() { tween(rewardGraphics, { scaleX: 1, scaleY: 1 }, { duration: flashInterval * 8, easing: tween.easeIn }); } }); } } // Remove when lifetime expires if (self.lifetime <= 0) { tween(self, { scaleX: 0, scaleY: 0 }, { duration: 200, onFinish: function onFinish() { self.destroy(); for (var i = bombRewards.length - 1; i >= 0; i--) { if (bombRewards[i] === self) { bombRewards.splice(i, 1); break; } } } }); } }; self.down = function (x, y, obj) { // Schedule bomb to appear at character position after 3 seconds var bombX = castle.x; var bombY = castle.y; LK.setTimeout(function () { var bomb = new Bomb(); bomb.x = bombX; bomb.y = bombY; bombs.push(bomb); game.addChild(bomb); }, 3000); LK.getSound('award').play(); LK.effects.flashObject(castle, 0xFF8800, 500); self.destroy(); for (var i = bombRewards.length - 1; i >= 0; i--) { if (bombRewards[i] === self) { bombRewards.splice(i, 1); break; } } }; return self; }); var BomberEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('bomberEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 3; self.maxHealth = 3; self.speed = 1.2; self.damage = 12; self.explosionRadius = 100; self.lastX = 0; self.lastY = 0; self.lastCastleDistance = Infinity; self.update = function () { var dx = castle.x - self.x; var dy = castle.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 60) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } var currentDistance = Math.sqrt(dx * dx + dy * dy); if (self.lastCastleDistance > 60 && currentDistance <= 60) { castle.takeDamage(self.damage); self.explode(); } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistance = currentDistance; }; self.explode = function () { LK.effects.flashScreen(0xFF8800, 300); // Damage nearby enemies for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy !== self) { var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.explosionRadius) { enemy.takeDamage(2); } } } self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xFFFFFF, 200); if (self.health <= 0) { LK.setScore(LK.getScore() + 30); scoreText.setText(LK.getScore()); LK.getSound('hit').play(); self.explode(); } }; return self; }); var BossEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('bossEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 15; self.maxHealth = 15; self.speed = 0.6; self.damage = 30; self.spawnCooldown = 0; self.spawnInterval = 300; // Spawns minions every 5 seconds self.lastX = 0; self.lastY = 0; self.lastCastleDistance = Infinity; self.update = function () { var dx = castle.x - self.x; var dy = castle.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 60) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } // Spawn minion enemies if (self.spawnCooldown > 0) { self.spawnCooldown--; } if (self.spawnCooldown <= 0 && enemies.length < 15) { var minion = new Enemy(); var angle = Math.random() * Math.PI * 2; minion.x = self.x + Math.cos(angle) * 60; minion.y = self.y + Math.sin(angle) * 60; minion.lastX = minion.x; minion.lastY = minion.y; var dx2 = castle.x - minion.x; var dy2 = castle.y - minion.y; minion.lastCastleDistance = Math.sqrt(dx2 * dx2 + dy2 * dy2); enemies.push(minion); game.addChild(minion); self.spawnCooldown = self.spawnInterval; } var currentDistance = Math.sqrt(dx * dx + dy * dy); if (self.lastCastleDistance > 60 && currentDistance <= 60) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistance = currentDistance; }; self.takeDamage = function (damage) { var reducedDamage = damage * 0.7; // Boss takes 30% less damage self.health -= reducedDamage; LK.effects.flashObject(self, 0xFFFFFF, 200); if (self.health <= 0) { LK.setScore(LK.getScore() + 100); scoreText.setText(LK.getScore()); LK.getSound('hit').play(); LK.effects.flashScreen(0x440044, 1000); // Drop multiple scrap metal (guaranteed) for (var c = 0; c < 3; c++) { var scrap = new ScrapMetalDrop(); var angle = Math.random() * Math.PI * 2; var distance = 20 + Math.random() * 40; scrap.x = self.x + Math.cos(angle) * distance; scrap.y = self.y + Math.sin(angle) * distance; scrap.baseY = scrap.y; scrap.value = 15; // High value for boss scrapMetalDrops.push(scrap); game.addChild(scrap); } self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 50; self.velocityX = 0; self.velocityY = 0; self.damage = 1.01; self.lastX = 0; self.lastY = 0; self.update = function () { self.lastX = self.x; self.lastY = self.y; self.x += self.velocityX; self.y += self.velocityY; // Add spinning animation self.rotation += 0.2; // Remove if off screen if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { self.destroy(); for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i] === self) { bullets.splice(i, 1); break; } } } }; return self; }); var Castle = Container.expand(function () { var self = Container.call(this); var castleGraphics = self.attachAsset('castle', { anchorX: 0.5, anchorY: 0.5 }); // Set physical diameter to match image dimensions self.width = castleGraphics.width; self.height = castleGraphics.height; self.health = 100; self.maxHealth = 100; self.bulletsPerShot = 1; self.shootCooldown = 0; self.shootInterval = 60; // Fire every second at 60fps // Special abilities self.shield = null; self.shieldCooldown = 0; self.shieldMaxCooldown = 900; // 15 seconds at 60fps self.shockwaveCooldown = 0; self.shockwaveMaxCooldown = 1200; // 20 seconds at 60fps self.update = function () { if (self.shootCooldown > 0) { self.shootCooldown--; } // Auto-fire with priority targeting system if (self.shootCooldown <= 0 && enemies.length > 0) { var targetEnemy = null; var bestPriority = -1; var bestDistance = Infinity; // Priority levels: Higher number = higher priority var priorityMap = { 'EliteEnemy': 6, // Highest priority - extremely dangerous 'SniperEnemy': 5, // Highest priority - long range damage 'AdaptiveEnemy': 5, // Highest priority - becomes stronger over time 'BomberEnemy': 4, // High priority - explosive threat 'BossEnemy': 4, // High priority - powerful enemy 'RegenEnemy': 4, // High priority - heals over time 'FastEnemy': 3, // Medium-high priority - quick threat 'HealerEnemy': 3, // Medium-high priority - supports others 'TeleporterEnemy': 2, // Medium priority - unpredictable 'SplitterEnemy': 2, // Medium priority - creates more enemies 'TankEnemy': 1, // Low priority - slow but tough 'ShieldEnemy': 1, // Low priority - tough but manageable 'Enemy': 0 // Lowest priority - basic enemy }; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Determine enemy type by checking constructor name var enemyType = 'Enemy'; // Default if (enemy.constructor === SniperEnemy) enemyType = 'SniperEnemy';else if (enemy.constructor === BomberEnemy) enemyType = 'BomberEnemy';else if (enemy.constructor === BossEnemy) enemyType = 'BossEnemy';else if (enemy.constructor === FastEnemy) enemyType = 'FastEnemy';else if (enemy.constructor === HealerEnemy) enemyType = 'HealerEnemy';else if (enemy.constructor === TeleporterEnemy) enemyType = 'TeleporterEnemy';else if (enemy.constructor === SplitterEnemy) enemyType = 'SplitterEnemy';else if (enemy.constructor === TankEnemy) enemyType = 'TankEnemy';else if (enemy.constructor === ShieldEnemy) enemyType = 'ShieldEnemy';else if (enemy.constructor === AdaptiveEnemy) enemyType = 'AdaptiveEnemy';else if (enemy.constructor === RegenEnemy) enemyType = 'RegenEnemy';else if (enemy.constructor === EliteEnemy) enemyType = 'EliteEnemy'; var priority = priorityMap[enemyType] || 0; // Select target based on priority first, then distance if (priority > bestPriority || priority === bestPriority && distance < bestDistance) { bestPriority = priority; bestDistance = distance; targetEnemy = enemy; } } if (targetEnemy) { self.fireAtTarget(targetEnemy); self.shootCooldown = self.shootInterval; } } }; self.fireAtTarget = function (target) { var baseAngle = Math.atan2(target.y - self.y, target.x - self.x); var spreadAngle = Math.PI / 6; // 30 degrees spread for (var i = 0; i < self.bulletsPerShot; i++) { var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; // Apply upgraded damage (always use upgraded damage) bullet.damage = self.upgradedBulletDamage || bullet.damage; var offset = 0; if (self.bulletsPerShot > 1) { offset = (i - (self.bulletsPerShot - 1) / 2) * (spreadAngle / Math.max(1, self.bulletsPerShot - 1)); } var angle = baseAngle + offset; bullet.velocityX = Math.cos(angle) * bullet.speed; bullet.velocityY = Math.sin(angle) * bullet.speed; // Set bullet rotation to match direction bullet.rotation = angle; bullets.push(bullet); game.addChild(bullet); } LK.getSound('shoot').play(); }; self.takeDamage = function (damage) { // Check if shield is active if (self.shield) { // Shield blocks damage and flashes LK.effects.flashObject(self.shield, 0x0088FF, 200); return; // No damage taken } self.health -= damage; LK.effects.flashObject(self, 0xFF0000, 300); // Update health bar if (castleHealthBar) { castleHealthBar.updateHealth(self.health, self.maxHealth); } if (self.health <= 0) { // Save collected scrap metal to persistent storage storage.scrapMetal += currentScrapMetal; // Check and update high scores var currentScore = LK.getScore(); var isNewHighScore = false; var isNewWaveRecord = false; var isNewTimeRecord = false; if (currentScore > storage.highScore) { storage.highScore = currentScore; isNewHighScore = true; } if (waveNumber > storage.bestWave) { storage.bestWave = waveNumber; isNewWaveRecord = true; } if (survivalTime > storage.longestSurvivalTime) { storage.longestSurvivalTime = survivalTime; isNewTimeRecord = true; } // Show achievement notifications if (isNewHighScore || isNewWaveRecord || isNewTimeRecord) { var achievementText = 'NEW RECORD!\n'; if (isNewHighScore) achievementText += 'HIGH SCORE: ' + currentScore + '\n'; if (isNewWaveRecord) achievementText += 'BEST WAVE: ' + waveNumber + '\n'; if (isNewTimeRecord) { var minutes = Math.floor(survivalTime / 3600); var seconds = Math.floor(survivalTime % 3600 / 60); achievementText += 'LONGEST TIME: ' + minutes + ':' + (seconds < 10 ? '0' : '') + seconds; } var recordText = new Text2(achievementText, { size: 55, fill: 0xFF00FF }); recordText.anchor.set(0.5, 0.5); recordText.x = 1024; recordText.y = 800; game.addChild(recordText); LK.effects.flashScreen(0xFF00FF, 1500); tween(recordText, { scaleX: 1.3, scaleY: 1.3 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { tween(recordText, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { recordText.destroy(); } }); } }); } LK.showGameOver(); } }; return self; }); var EliteEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('tankEnemy', { anchorX: 0.5, anchorY: 0.5 }); enemyGraphics.tint = 0xFFD700; // Gold for elite enemy self.health = 12; self.maxHealth = 12; self.speed = 1.1; self.damage = 25; self.armor = 0.3; // Takes 30% less damage self.specialAbilityCooldown = 0; self.specialAbilityInterval = 240; // Special ability every 4 seconds self.lastX = 0; self.lastY = 0; self.lastCastleDistanceSquared = Infinity; // Create health bar for this enemy self.healthBar = new EnemyHealthBar(); self.healthBar.y = -50; // Position above enemy enemyHealthBars.push(self.healthBar); self.addChild(self.healthBar); self.update = function () { // Special ability - damage reduction boost self.specialAbilityCooldown++; if (self.specialAbilityCooldown >= self.specialAbilityInterval) { // Temporary damage reduction boost self.armor = Math.min(0.6, self.armor + 0.1); self.specialAbilityCooldown = 0; LK.effects.flashObject(self, 0xFFD700, 400); // Create temporary shield effect tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeIn }); } }); } // Move toward castle var dx = castle.x - self.x; var dy = castle.y - self.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared > 3600) { var distance = Math.sqrt(distanceSquared); var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } // Check if reached castle if (self.lastCastleDistanceSquared > 3600 && distanceSquared <= 3600) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistanceSquared = distanceSquared; }; self.takeDamage = function (damage) { var reducedDamage = damage * (1 - self.armor); self.health -= reducedDamage; LK.effects.flashObject(self, 0xFFFFFF, 200); // Update health bar if (self.healthBar) { self.healthBar.updateHealth(self.health, self.maxHealth); } if (self.health <= 0) { LK.setScore(LK.getScore() + 75); // High score reward scoreText.setText(LK.getScore()); LK.getSound('hit').play(); // Drop multiple scrap metal (guaranteed) for (var c = 0; c < 2; c++) { var scrap = new ScrapMetalDrop(); var angle = Math.random() * Math.PI * 2; var distance = 20 + Math.random() * 40; scrap.x = self.x + Math.cos(angle) * distance; scrap.y = self.y + Math.sin(angle) * distance; scrap.baseY = scrap.y; scrap.value = 12; // High value for elite enemy scrapMetalDrops.push(scrap); game.addChild(scrap); } self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 3; self.maxHealth = 3; self.speed = 1; self.damage = 10; self.lastX = 0; self.lastY = 0; self.lastCastleDistanceSquared = Infinity; // Create health bar for this enemy self.healthBar = new EnemyHealthBar(); self.healthBar.y = -50; // Position above enemy enemyHealthBars.push(self.healthBar); self.addChild(self.healthBar); self.update = function () { // Move toward castle var dx = castle.x - self.x; var dy = castle.y - self.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared > 3600) { // 60^2 = 3600 // Don't overlap with castle var distance = Math.sqrt(distanceSquared); var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } // Check if reached castle if (self.lastCastleDistanceSquared > 3600 && distanceSquared <= 3600) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistanceSquared = distanceSquared; }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xFFFFFF, 200); // Update health bar if (self.healthBar) { self.healthBar.updateHealth(self.health, self.maxHealth); } if (self.health <= 0) { LK.setScore(LK.getScore() + 10); storage.totalEnemiesDefeated++; scoreText.setText(LK.getScore()); LK.getSound('hit').play(); // Drop scrap metal (80% chance - increased for better collection) if (Math.random() < 0.8) { var scrap = new ScrapMetalDrop(); scrap.x = self.x; scrap.y = self.y; scrap.baseY = scrap.y; scrap.value = 5; // Basic enemy drops 5 scrap metal scrapMetalDrops.push(scrap); game.addChild(scrap); } self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var EnemyHealthBar = Container.expand(function () { var self = Container.call(this); // Create background bar (dark red) var backgroundBar = LK.getAsset('bgTile', { anchorX: 0.5, anchorY: 0.5 }); backgroundBar.tint = 0x330000; backgroundBar.scaleX = 0.8; backgroundBar.scaleY = 0.15; self.addChild(backgroundBar); // Create health bar (red) var healthBar = LK.getAsset('bgTile', { anchorX: 0, anchorY: 0.5 }); healthBar.tint = 0xFF0000; healthBar.scaleX = 0.8; healthBar.scaleY = 0.15; healthBar.x = -backgroundBar.width * backgroundBar.scaleX / 2; self.addChild(healthBar); self.backgroundBar = backgroundBar; self.healthBar = healthBar; self.updateHealth = function (currentHealth, maxHealth) { var healthPercentage = Math.max(0, currentHealth / maxHealth); self.healthBar.scaleX = 0.8 * healthPercentage; // Hide if at full health to reduce clutter self.visible = healthPercentage < 1.0; }; return self; }); var FastEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('fastEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 1; self.maxHealth = 1; self.speed = 3; self.damage = 5; self.lastX = 0; self.lastY = 0; self.lastCastleDistance = Infinity; self.update = function () { // Move toward castle with higher speed var dx = castle.x - self.x; var dy = castle.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 60) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } // Check if reached castle var currentDistance = Math.sqrt(dx * dx + dy * dy); if (self.lastCastleDistance > 60 && currentDistance <= 60) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xFFFFFF, 200); if (self.health <= 0) { LK.setScore(LK.getScore() + 15); scoreText.setText(LK.getScore()); LK.getSound('hit').play(); // Drop scrap metal (50% chance, less than basic enemy) if (Math.random() < 0.5) { var scrap = new ScrapMetalDrop(); scrap.x = self.x; scrap.y = self.y; scrap.baseY = scrap.y; scrap.value = 3; // Less value than basic enemy scrapMetalDrops.push(scrap); game.addChild(scrap); } self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var HealerEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('healerEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 4; self.maxHealth = 4; self.speed = 0.8; self.damage = 8; self.healCooldown = 0; self.healInterval = 180; // Heals every 3 seconds self.healRadius = 150; self.lastX = 0; self.lastY = 0; self.lastCastleDistance = Infinity; self.update = function () { var dx = castle.x - self.x; var dy = castle.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 60) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } // Heal nearby enemies if (self.healCooldown > 0) { self.healCooldown--; } if (self.healCooldown <= 0) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy !== self && enemy.health < enemy.maxHealth) { var dx2 = enemy.x - self.x; var dy2 = enemy.y - self.y; var healDistance = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (healDistance <= self.healRadius) { enemy.health = Math.min(enemy.maxHealth, enemy.health + 1); LK.effects.flashObject(enemy, 0x88FF00, 200); } } } self.healCooldown = self.healInterval; } var currentDistance = Math.sqrt(dx * dx + dy * dy); if (self.lastCastleDistance > 60 && currentDistance <= 60) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xFFFFFF, 200); if (self.health <= 0) { LK.setScore(LK.getScore() + 35); scoreText.setText(LK.getScore()); LK.getSound('hit').play(); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var HealthBar = Container.expand(function () { var self = Container.call(this); // Create background bar (red) var backgroundBar = LK.getAsset('bgTile', { anchorX: 0, anchorY: 0.5 }); backgroundBar.tint = 0x660000; backgroundBar.scaleX = 2; backgroundBar.scaleY = 0.3; self.addChild(backgroundBar); // Create health bar (green) var healthBar = LK.getAsset('bgTile', { anchorX: 0, anchorY: 0.5 }); healthBar.tint = 0x00AA00; healthBar.scaleX = 2; healthBar.scaleY = 0.3; self.addChild(healthBar); self.backgroundBar = backgroundBar; self.healthBar = healthBar; self.maxWidth = backgroundBar.width * backgroundBar.scaleX; self.updateHealth = function (currentHealth, maxHealth) { var healthPercentage = Math.max(0, currentHealth / maxHealth); self.healthBar.scaleX = 2 * healthPercentage; // Change color based on health percentage if (healthPercentage > 0.6) { self.healthBar.tint = 0x00AA00; // Green } else if (healthPercentage > 0.3) { self.healthBar.tint = 0xFFAA00; // Orange } else { self.healthBar.tint = 0xFF0000; // Red } }; return self; }); var HealthPack = Container.expand(function () { var self = Container.call(this); var healthGraphics = self.attachAsset('reward', { anchorX: 0.5, anchorY: 0.5 }); // Make it red for health healthGraphics.tint = 0xFF4444; healthGraphics.scaleX = 0.8; healthGraphics.scaleY = 0.8; self.bobOffset = Math.random() * Math.PI * 2; self.baseY = 0; self.lifetime = 420; // 7 seconds at 60fps self.healAmount = 25; // Restore 25 health self.update = function () { // Gentle bobbing animation self.y = self.baseY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5; // Gentle pulsing effect var pulseScale = 0.8 + Math.sin(LK.ticks * 0.2) * 0.1; healthGraphics.scaleX = pulseScale; healthGraphics.scaleY = pulseScale; // Decrease lifetime self.lifetime--; // Start fading when close to expiring if (self.lifetime <= 120) { var alpha = self.lifetime / 120; healthGraphics.alpha = alpha; } // Remove when lifetime expires if (self.lifetime <= 0) { self.destroy(); for (var i = healthPacks.length - 1; i >= 0; i--) { if (healthPacks[i] === self) { healthPacks.splice(i, 1); break; } } } }; self.down = function (x, y, obj) { // Heal castle var healAmount = Math.min(self.healAmount, castle.maxHealth - castle.health); if (healAmount > 0) { castle.health += healAmount; LK.getSound('award').play(); LK.effects.flashObject(castle, 0x44FF44, 500); // Show heal amount var healText = new Text2('+' + healAmount + ' HEALTH', { size: 40, fill: 0x44FF44 }); healText.anchor.set(0.5, 0.5); healText.x = castle.x; healText.y = castle.y - 60; game.addChild(healText); // Animate heal text tween(healText, { y: healText.y - 50, alpha: 0 }, { duration: 1500, onFinish: function onFinish() { healText.destroy(); } }); } self.destroy(); for (var i = healthPacks.length - 1; i >= 0; i--) { if (healthPacks[i] === self) { healthPacks.splice(i, 1); break; } } }; return self; }); var RegenEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('healerEnemy', { anchorX: 0.5, anchorY: 0.5 }); enemyGraphics.tint = 0x32CD32; // Lime green for regen enemy self.health = 6; self.maxHealth = 6; self.speed = 0.9; self.damage = 14; self.regenRate = 1; self.regenTimer = 0; self.regenInterval = 120; // Regenerate every 2 seconds self.lastX = 0; self.lastY = 0; self.lastCastleDistanceSquared = Infinity; // Create health bar for this enemy self.healthBar = new EnemyHealthBar(); self.healthBar.y = -50; // Position above enemy enemyHealthBars.push(self.healthBar); self.addChild(self.healthBar); self.update = function () { // Regeneration logic self.regenTimer++; if (self.regenTimer >= self.regenInterval && self.health < self.maxHealth) { self.health = Math.min(self.maxHealth, self.health + self.regenRate); self.regenTimer = 0; // Visual feedback for regeneration LK.effects.flashObject(self, 0x32CD32, 300); if (self.healthBar) { self.healthBar.updateHealth(self.health, self.maxHealth); } } // Move toward castle var dx = castle.x - self.x; var dy = castle.y - self.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared > 3600) { var distance = Math.sqrt(distanceSquared); var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } // Check if reached castle if (self.lastCastleDistanceSquared > 3600 && distanceSquared <= 3600) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistanceSquared = distanceSquared; }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xFFFFFF, 200); // Update health bar if (self.healthBar) { self.healthBar.updateHealth(self.health, self.maxHealth); } if (self.health <= 0) { LK.setScore(LK.getScore() + 30); scoreText.setText(LK.getScore()); LK.getSound('hit').play(); // Drop guaranteed scrap metal var scrap = new ScrapMetalDrop(); scrap.x = self.x; scrap.y = self.y; scrap.baseY = scrap.y; scrap.value = 8; // Higher value for tougher enemy scrapMetalDrops.push(scrap); game.addChild(scrap); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var Reward = Container.expand(function () { var self = Container.call(this); var rewardGraphics = self.attachAsset('reward', { anchorX: 0.5, anchorY: 0.5 }); self.bobOffset = Math.random() * Math.PI * 2; self.baseY = 0; self.lifetime = 420; // 7 seconds at 60fps self.update = function () { // Gentle bobbing animation self.y = self.baseY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5; // Decrease lifetime self.lifetime--; // Start fading when close to expiring if (self.lifetime <= 120) { var alpha = self.lifetime / 120; rewardGraphics.alpha = alpha; // Add flashing animation when close to expiring var flashInterval = Math.max(5, Math.floor(self.lifetime / 20)); if (LK.ticks % flashInterval === 0) { tween(rewardGraphics, { scaleX: 1.3, scaleY: 1.3 }, { duration: flashInterval * 8, easing: tween.easeOut, onFinish: function onFinish() { tween(rewardGraphics, { scaleX: 1, scaleY: 1 }, { duration: flashInterval * 8, easing: tween.easeIn }); } }); } } // Remove when lifetime expires if (self.lifetime <= 0) { tween(self, { scaleX: 0, scaleY: 0 }, { duration: 200, onFinish: function onFinish() { self.destroy(); for (var i = rewards.length - 1; i >= 0; i--) { if (rewards[i] === self) { rewards.splice(i, 1); break; } } } }); } }; self.down = function (x, y, obj) { // Define maximum bullet limit to prevent excessive stacking var maxBullets = 8; if (castle.bulletsPerShot < maxBullets) { // Add 1 bullet per reward (consistent increment) castle.bulletsPerShot++; bulletsText.setText('Bullets: ' + castle.bulletsPerShot); LK.getSound('award').play(); LK.effects.flashObject(castle, 0x00FF00, 500); // Visual feedback for bullet upgrade with scaling effect tween(castle, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(castle, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } }); } else { // At maximum bullets - give score bonus instead LK.setScore(LK.getScore() + 100); scoreText.setText(LK.getScore()); bulletsText.setText('Bullets: ' + castle.bulletsPerShot + ' (MAX)'); LK.getSound('award').play(); LK.effects.flashObject(castle, 0xFFD700, 500); // Gold flash for max bonus // Brief text notification var maxText = new Text2('+100 SCORE!', { size: 40, fill: 0xFFD700 }); maxText.anchor.set(0.5, 0.5); maxText.x = castle.x; maxText.y = castle.y - 60; game.addChild(maxText); // Animate and remove notification tween(maxText, { y: maxText.y - 50, alpha: 0 }, { duration: 1500, onFinish: function onFinish() { maxText.destroy(); } }); } self.destroy(); for (var i = rewards.length - 1; i >= 0; i--) { if (rewards[i] === self) { rewards.splice(i, 1); break; } } }; return self; }); var ScrapCollectionIndicator = Container.expand(function () { var self = Container.call(this); // Create collection range visualization var rangeCircle = LK.getAsset('bgCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeCircle.tint = 0xC0C0C0; rangeCircle.alpha = 0.1; rangeCircle.scaleX = 0.6; // 180px radius visualization rangeCircle.scaleY = 0.6; self.addChild(rangeCircle); self.rangeCircle = rangeCircle; self.pulseDirection = 1; self.update = function () { // Gentle pulsing effect to show collection range var pulseSpeed = 0.02; self.alpha += self.pulseDirection * pulseSpeed; if (self.alpha >= 0.3) { self.pulseDirection = -1; } else if (self.alpha <= 0.1) { self.pulseDirection = 1; } // Only show when scrap metal is nearby var nearbyScrap = false; for (var i = 0; i < scrapMetalDrops.length; i++) { var scrap = scrapMetalDrops[i]; var dx = scrap.x - castle.x; var dy = scrap.y - castle.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= 250) { // Show range when scrap is within extended range nearbyScrap = true; break; } } self.visible = nearbyScrap; }; return self; }); var ScrapMetalDrop = Container.expand(function () { var self = Container.call(this); var scrapGraphics = self.attachAsset('reward', { anchorX: 0.5, anchorY: 0.5 }); // Make it metallic silver for scrap metal scrapGraphics.tint = 0xC0C0C0; scrapGraphics.scaleX = 0.6; scrapGraphics.scaleY = 0.6; self.value = 5; // Base scrap metal value self.bobOffset = Math.random() * Math.PI * 2; self.baseY = 0; self.lifetime = 600; // 10 seconds at 60fps self.magnetRange = 180; // Increased range for easier collection self.collectDistance = 80; // Distance at which scrap is automatically collected self.update = function () { // Gentle bobbing animation self.y = self.baseY + Math.sin(LK.ticks * 0.15 + self.bobOffset) * 3; // Decrease lifetime self.lifetime--; // Distance calculation var dx = castle.x - self.x; var dy = castle.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Auto-collect when very close if (distance <= self.collectDistance) { currentScrapMetal += self.value; scrapMetalText.setText('Scrap Metal: ' + (storage.scrapMetal + currentScrapMetal)); LK.getSound('award').play(); // Visual collection effect LK.effects.flashObject(castle, 0xC0C0C0, 300); // Show collection amount var collectText = new Text2('+' + self.value + ' SCRAP', { size: 35, fill: 0xC0C0C0 }); collectText.anchor.set(0.5, 0.5); collectText.x = self.x; collectText.y = self.y - 30; game.addChild(collectText); // Animate collection text towards castle tween(collectText, { x: castle.x, y: castle.y - 50, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { collectText.destroy(); } }); self.destroy(); for (var i = scrapMetalDrops.length - 1; i >= 0; i--) { if (scrapMetalDrops[i] === self) { scrapMetalDrops.splice(i, 1); break; } } return; } // Magnet effect when castle is nearby if (distance <= self.magnetRange) { var magnetSpeed = Math.min(5, distance * 0.05); // Speed increases as it gets closer var moveX = dx / distance * magnetSpeed; var moveY = dy / distance * magnetSpeed; self.x += moveX; self.y += moveY; // Add sparkle effect during magnet pull if (LK.ticks % 10 === 0) { LK.effects.flashObject(self, 0xFFFFFF, 100); } } // Start fading when close to expiring if (self.lifetime <= 120) { var alpha = self.lifetime / 120; scrapGraphics.alpha = alpha; } // Remove when lifetime expires if (self.lifetime <= 0) { self.destroy(); for (var i = scrapMetalDrops.length - 1; i >= 0; i--) { if (scrapMetalDrops[i] === self) { scrapMetalDrops.splice(i, 1); break; } } } }; return self; }); var Shield = Container.expand(function () { var self = Container.call(this); var shieldGraphics = self.attachAsset('castle', { anchorX: 0.5, anchorY: 0.5 }); shieldGraphics.tint = 0x0088FF; shieldGraphics.alpha = 0.3; self.scaleX = 1.5; self.scaleY = 1.5; self.duration = 300; // 5 seconds at 60fps self.pulseDirection = 1; self.update = function () { // Pulsing effect var pulseSpeed = 0.1; self.alpha += self.pulseDirection * pulseSpeed; if (self.alpha >= 0.6) { self.pulseDirection = -1; } else if (self.alpha <= 0.2) { self.pulseDirection = 1; } // Decrease duration self.duration--; if (self.duration <= 0) { castle.shield = null; self.destroy(); } }; return self; }); var ShieldEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('shieldEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 6; self.maxHealth = 6; self.speed = 0.7; self.damage = 15; self.shieldRadius = 80; self.lastX = 0; self.lastY = 0; self.lastCastleDistance = Infinity; self.update = function () { var dx = castle.x - self.x; var dy = castle.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 60) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } var currentDistance = Math.sqrt(dx * dx + dy * dy); if (self.lastCastleDistance > 60 && currentDistance <= 60) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xFFFFFF, 200); if (self.health <= 0) { LK.setScore(LK.getScore() + 40); scoreText.setText(LK.getScore()); LK.getSound('hit').play(); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var Shockwave = Container.expand(function () { var self = Container.call(this); var shockwaveGraphics = self.attachAsset('castle', { anchorX: 0.5, anchorY: 0.5 }); shockwaveGraphics.tint = 0xFF4400; shockwaveGraphics.alpha = 0.8; self.maxRadius = 400; self.currentRadius = 50; self.damage = 5; self.speed = 15; self.hasDealtDamage = []; self.update = function () { // Expand shockwave self.currentRadius += self.speed; var scale = self.currentRadius / 100; self.scaleX = scale; self.scaleY = scale; // Fade out as it expands self.alpha = Math.max(0, 0.8 - self.currentRadius / self.maxRadius * 0.8); // Damage enemies for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if enemy is within shockwave range and hasn't been damaged yet if (distance <= self.currentRadius && self.hasDealtDamage.indexOf(enemy) === -1) { enemy.takeDamage(self.damage); self.hasDealtDamage.push(enemy); LK.effects.flashObject(enemy, 0xFF4400, 200); } } // Remove when fully expanded if (self.currentRadius >= self.maxRadius) { self.destroy(); for (var j = shockwaves.length - 1; j >= 0; j--) { if (shockwaves[j] === self) { shockwaves.splice(j, 1); break; } } } }; return self; }); var SniperEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('sniperEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 2; self.maxHealth = 2; self.speed = 1.5; self.damage = 15; self.shootCooldown = 0; self.shootInterval = 120; // Shoots every 2 seconds self.lastX = 0; self.lastY = 0; self.lastCastleDistance = Infinity; self.update = function () { var dx = castle.x - self.x; var dy = castle.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Move toward castle until in range (300 pixels) if (distance > 300) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } else { // In range, shoot at castle if (self.shootCooldown > 0) { self.shootCooldown--; } if (self.shootCooldown <= 0) { castle.takeDamage(self.damage); LK.effects.flashObject(self, 0x00FFFF, 300); self.shootCooldown = self.shootInterval; } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistance = distance; }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xFFFFFF, 200); if (self.health <= 0) { LK.setScore(LK.getScore() + 20); scoreText.setText(LK.getScore()); LK.getSound('hit').play(); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var SplitterEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('splitterEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 5; self.maxHealth = 5; self.speed = 0.9; self.damage = 18; self.splitCount = 2; self.lastX = 0; self.lastY = 0; self.lastCastleDistance = Infinity; self.update = function () { var dx = castle.x - self.x; var dy = castle.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 60) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } var currentDistance = Math.sqrt(dx * dx + dy * dy); if (self.lastCastleDistance > 60 && currentDistance <= 60) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistance = currentDistance; }; self.split = function () { for (var i = 0; i < self.splitCount; i++) { var smallEnemy = new FastEnemy(); var angle = Math.PI * 2 / self.splitCount * i; smallEnemy.x = self.x + Math.cos(angle) * 30; smallEnemy.y = self.y + Math.sin(angle) * 30; smallEnemy.lastX = smallEnemy.x; smallEnemy.lastY = smallEnemy.y; var dx = castle.x - smallEnemy.x; var dy = castle.y - smallEnemy.y; smallEnemy.lastCastleDistance = Math.sqrt(dx * dx + dy * dy); enemies.push(smallEnemy); game.addChild(smallEnemy); } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xFFFFFF, 200); if (self.health <= 0) { LK.setScore(LK.getScore() + 50); scoreText.setText(LK.getScore()); LK.getSound('hit').play(); self.split(); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var TankEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('tankEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 8; self.maxHealth = 8; self.speed = 0.5; self.damage = 20; self.armor = 0.5; // Takes 50% less damage self.lastX = 0; self.lastY = 0; self.lastCastleDistance = Infinity; self.update = function () { // Move toward castle slowly var dx = castle.x - self.x; var dy = castle.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 60) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } // Check if reached castle var currentDistance = Math.sqrt(dx * dx + dy * dy); if (self.lastCastleDistance > 60 && currentDistance <= 60) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistance = currentDistance; }; self.takeDamage = function (damage) { var reducedDamage = damage * (1 - self.armor); self.health -= reducedDamage; LK.effects.flashObject(self, 0xFFFFFF, 200); if (self.health <= 0) { LK.setScore(LK.getScore() + 25); scoreText.setText(LK.getScore()); LK.getSound('hit').play(); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var TeleporterEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('teleporterEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 2; self.maxHealth = 2; self.speed = 1; self.damage = 12; self.teleportCooldown = 0; self.teleportInterval = 240; // Teleports every 4 seconds self.lastX = 0; self.lastY = 0; self.lastCastleDistance = Infinity; self.update = function () { var dx = castle.x - self.x; var dy = castle.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 60) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } // Teleport randomly if (self.teleportCooldown > 0) { self.teleportCooldown--; } if (self.teleportCooldown <= 0) { var angle = Math.random() * Math.PI * 2; var teleportDistance = 200 + Math.random() * 200; self.x = castle.x + Math.cos(angle) * teleportDistance; self.y = castle.y + Math.sin(angle) * teleportDistance; // Keep within bounds self.x = Math.max(50, Math.min(1998, self.x)); self.y = Math.max(50, Math.min(2682, self.y)); LK.effects.flashObject(self, 0xFF00FF, 400); self.teleportCooldown = self.teleportInterval; } var currentDistance = Math.sqrt((castle.x - self.x) * (castle.x - self.x) + (castle.y - self.y) * (castle.y - self.y)); if (self.lastCastleDistance > 60 && currentDistance <= 60) { castle.takeDamage(self.damage); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } self.lastX = self.x; self.lastY = self.y; self.lastCastleDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xFFFFFF, 200); if (self.health <= 0) { LK.setScore(LK.getScore() + 25); scoreText.setText(LK.getScore()); LK.getSound('hit').play(); self.destroy(); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } } }; return self; }); var ThreatIndicator = Container.expand(function () { var self = Container.call(this); var indicator = LK.getAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); indicator.tint = 0xFF4400; indicator.scaleX = 0.5; indicator.scaleY = 0.5; indicator.alpha = 0.8; self.addChild(indicator); self.indicator = indicator; self.pulseDirection = 1; self.update = function () { // Pulsing animation var pulseSpeed = 0.05; self.alpha += self.pulseDirection * pulseSpeed; if (self.alpha >= 1) { self.pulseDirection = -1; } else if (self.alpha <= 0.5) { self.pulseDirection = 1; } }; return self; }); var UpgradeMenu = Container.expand(function () { var self = Container.call(this); // Create semi-transparent background var background = LK.getAsset('bgTile', { anchorX: 0, anchorY: 0 }); background.tint = 0x000000; background.alpha = 0.8; background.scaleX = 10.24; // Cover full screen width background.scaleY = 13.66; // Cover full screen height self.addChild(background); // Menu title var titleText = new Text2('CASTLE UPGRADES', { size: 80, fill: 0xFFD700 }); titleText.anchor.set(0.5, 0); titleText.x = 1024; titleText.y = 200; self.addChild(titleText); // Close button var closeButton = new Text2('β CLOSE', { size: 60, fill: 0xFF4444 }); closeButton.anchor.set(1, 0); closeButton.x = 1900; closeButton.y = 250; self.addChild(closeButton); // Upgrade buttons array self.upgradeButtons = []; // Create upgrade buttons for each type var upgradeTypes = [{ type: 'fireRate', name: 'FIRE RATE', color: 0xFF8800, description: '+15% faster shooting' }, { type: 'damage', name: 'DAMAGE', color: 0xFF0000, description: '+20% bullet damage' }, { type: 'health', name: 'HEALTH', color: 0x00FF00, description: '+20 max health' }, { type: 'bulletCount', name: 'BULLETS', color: 0x00AAFF, description: '+1 bullet per shot' }, { type: 'movementSpeed', name: 'MOVEMENT', color: 0xFFFF00, description: '+25% movement speed' }, { type: 'shield', name: 'SHIELD', color: 0x0088FF, description: '10% shorter cooldown' }, { type: 'shockwave', name: 'SHOCKWAVE', color: 0xFF4400, description: '10% shorter cooldown' }]; for (var i = 0; i < upgradeTypes.length; i++) { var upgrade = upgradeTypes[i]; var buttonContainer = new Container(); // Button background var buttonBg = LK.getAsset('bgTile', { anchorX: 0, anchorY: 0 }); buttonBg.tint = upgrade.color; buttonBg.alpha = 0.7; buttonBg.scaleX = 4; buttonBg.scaleY = 0.8; buttonContainer.addChild(buttonBg); // Button text var buttonText = new Text2('', { size: 45, fill: 0xFFFFFF }); buttonText.anchor.set(0, 0.5); buttonText.x = 20; buttonText.y = buttonBg.height * buttonBg.scaleY / 2; buttonContainer.addChild(buttonText); // Position button var col = i % 2; var row = Math.floor(i / 2); buttonContainer.x = 300 + col * 800; buttonContainer.y = 400 + row * 200; // Store references buttonContainer.upgradeType = upgrade.type; buttonContainer.buttonText = buttonText; buttonContainer.buttonBg = buttonBg; buttonContainer.originalColor = upgrade.color; buttonContainer.upgradeName = upgrade.name; buttonContainer.description = upgrade.description; self.upgradeButtons.push(buttonContainer); self.addChild(buttonContainer); } // Current scrap display var scrapDisplay = new Text2('', { size: 55, fill: 0xC0C0C0 }); scrapDisplay.anchor.set(0.5, 0); scrapDisplay.x = 1024; scrapDisplay.y = 320; self.addChild(scrapDisplay); self.scrapDisplay = scrapDisplay; // Update button states and text self.updateButtons = function () { var totalScrap = storage.scrapMetal + currentScrapMetal; self.scrapDisplay.setText('Available Scrap Metal: ' + totalScrap); for (var i = 0; i < self.upgradeButtons.length; i++) { var button = self.upgradeButtons[i]; var currentLevel = storage[button.upgradeType + 'Level'] || 1; var cost = getUpgradeCost(button.upgradeType, currentLevel); var canAfford = canAffordUpgrade(button.upgradeType, currentLevel); var isMaxLevel = currentLevel >= storage.maxUpgradeTier; if (isMaxLevel) { button.buttonText.setText(button.upgradeName + ' - MAX LEVEL'); button.buttonBg.tint = 0x666666; button.buttonBg.alpha = 0.5; } else { button.buttonText.setText(button.upgradeName + ' T' + currentLevel + 'βT' + (currentLevel + 1) + '\nCost: ' + cost + ' | ' + button.description); button.buttonBg.tint = canAfford ? button.originalColor : 0x666666; button.buttonBg.alpha = canAfford ? 0.7 : 0.3; } } }; // Handle button clicks self.down = function (x, y, obj) { // Check close button if (x >= 1700 && x <= 1900 && y >= 250 && y <= 320) { upgradeMenuVisible = false; self.visible = false; return; } // Check upgrade buttons for (var i = 0; i < self.upgradeButtons.length; i++) { var button = self.upgradeButtons[i]; var buttonX = button.x; var buttonY = button.y; var buttonWidth = button.buttonBg.width * button.buttonBg.scaleX; var buttonHeight = button.buttonBg.height * button.buttonBg.scaleY; if (x >= buttonX && x <= buttonX + buttonWidth && y >= buttonY && y <= buttonY + buttonHeight) { var currentLevel = storage[button.upgradeType + 'Level'] || 1; if (currentLevel < storage.maxUpgradeTier && canAffordUpgrade(button.upgradeType, currentLevel)) { if (purchaseUpgrade(button.upgradeType)) { // Apply upgrade immediately self.applyUpgrade(button.upgradeType); // Update button states self.updateButtons(); // Visual feedback LK.effects.flashObject(button, 0x00FF00, 500); LK.getSound('award').play(); } } break; } } }; // Apply upgrades to castle immediately self.applyUpgrade = function (upgradeType) { var newLevel = storage[upgradeType + 'Level']; switch (upgradeType) { case 'fireRate': var fireRateBonus = getUpgradeBonus('fireRate', newLevel); castle.shootInterval = Math.max(20, Math.floor(60 * (1 - fireRateBonus))); break; case 'health': var healthBonus = getUpgradeBonus('health', newLevel); var newMaxHealth = 100 + healthBonus; var healthIncrease = newMaxHealth - castle.maxHealth; castle.maxHealth = newMaxHealth; castle.health += healthIncrease; // Add the health increase castleHealthBar.updateHealth(castle.health, castle.maxHealth); break; case 'damage': var damageBonus = getUpgradeBonus('damage', newLevel); castle.upgradedBulletDamage = 1.01 * (1 + damageBonus); break; case 'bulletCount': var bulletCountBonus = getUpgradeBonus('bulletCount', newLevel); castle.bulletsPerShot = 1 + bulletCountBonus; bulletsText.setText('Bullets: ' + castle.bulletsPerShot); break; case 'movementSpeed': var movementSpeedBonus = getUpgradeBonus('movementSpeed', newLevel); castleMovementSpeed = 400 * (1 + movementSpeedBonus); break; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2F4F2F }); /**** * Game Code ****/ // Game variables var castle; var enemies = []; var bullets = []; var rewards = []; var bombRewards = []; var bombs = []; var scrapMetalDrops = []; var shockwaves = []; var healthPacks = []; var waveTimer = 0; var enemySpawnTimer = 0; var rewardSpawnTimer = 0; var waveNumber = 1; var isMiniBossWave = false; var miniBossSpawned = false; var survivalTime = 0; // Track total survival time var lastMilestoneWave = 0; // Track last milestone for rewards // Initialize persistent storage with defaults // Current game scrap metal (resets each game) var currentScrapMetal = 0; // Upgrade tier system functions function getUpgradeCost(upgradeType, currentLevel) { var baseCosts = { fireRate: storage.fireRateBaseCost, movementSpeed: storage.movementSpeedBaseCost, health: storage.healthBaseCost, damage: storage.damageBaseCost, bulletCount: storage.bulletCountBaseCost, shield: storage.shieldBaseCost, shockwave: storage.shockwaveBaseCost }; var baseCost = baseCosts[upgradeType] || 100; // Progressive cost increase: base * (1.5^currentLevel) return Math.floor(baseCost * Math.pow(1.5, currentLevel - 1)); } function canAffordUpgrade(upgradeType, currentLevel) { if (currentLevel >= storage.maxUpgradeTier) return false; var cost = getUpgradeCost(upgradeType, currentLevel); return storage.scrapMetal + currentScrapMetal >= cost; } function purchaseUpgrade(upgradeType) { var currentLevel = storage[upgradeType + 'Level'] || 1; if (!canAffordUpgrade(upgradeType, currentLevel)) return false; var cost = getUpgradeCost(upgradeType, currentLevel); var totalScrap = storage.scrapMetal + currentScrapMetal; // Deduct cost and update storage if (currentScrapMetal >= cost) { currentScrapMetal -= cost; } else { var remainingCost = cost - currentScrapMetal; currentScrapMetal = 0; storage.scrapMetal -= remainingCost; } // Increase upgrade level storage[upgradeType + 'Level'] = currentLevel + 1; // Update scrap metal display scrapMetalText.setText('Scrap Metal: ' + (storage.scrapMetal + currentScrapMetal)); return true; } function getUpgradeBonus(upgradeType, level) { var bonuses = { fireRate: (level - 1) * 0.15, // 15% faster per level movementSpeed: (level - 1) * 0.25, // 25% faster per level health: (level - 1) * 20, // +20 health per level damage: (level - 1) * 0.2, // +20% damage per level bulletCount: level - 1, // +1 bullet per level shield: (level - 1) * 0.1, // 10% cooldown reduction per level shockwave: (level - 1) * 0.1 // 10% cooldown reduction per level }; return bonuses[upgradeType] || 0; } // Create background pattern function createBackground() { var bgContainer = new Container(); // Create simpler grid pattern with fewer random calculations for (var x = 0; x <= 2048; x += 240) { for (var y = 0; y <= 2732; y += 240) { // Main background tiles with minimal randomization var tile = LK.getAsset('bgTile', { anchorX: 0.5, anchorY: 0.5 }); tile.x = x; tile.y = y; bgContainer.addChild(tile); // Reduce accent frequency for better performance if ((x + y) % 960 === 0) { // Every 4th tile instead of random var accent = LK.getAsset('bgAccent', { anchorX: 0.5, anchorY: 0.5 }); accent.x = x; accent.y = y; accent.alpha = 0.5; bgContainer.addChild(accent); } } } // Set background container to lowest z-index game.addChildAt(bgContainer, 0); } // Initialize background createBackground(); // UI elements var scoreText = new Text2('0', { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); var bulletsText = new Text2('Bullets: 1', { size: 50, fill: 0x00FF00 }); bulletsText.anchor.set(0, 0); bulletsText.x = 50; bulletsText.y = 50; LK.gui.topLeft.addChild(bulletsText); var healthText = new Text2('Health: 100', { size: 50, fill: 0xFF0000 }); healthText.anchor.set(1, 0); LK.gui.topRight.addChild(healthText); var scrapMetalText = new Text2('Scrap Metal: 0', { size: 45, fill: 0xC0C0C0 }); scrapMetalText.anchor.set(0, 0); scrapMetalText.x = 50; scrapMetalText.y = 120; LK.gui.topLeft.addChild(scrapMetalText); // Wave counter var waveText = new Text2('Wave: 1', { size: 55, fill: 0xFFD700 }); waveText.anchor.set(1, 0); waveText.x = -50; waveText.y = 50; LK.gui.topRight.addChild(waveText); // Survival time counter var survivalTimeText = new Text2('Time: 0:00', { size: 45, fill: 0x88DDFF }); survivalTimeText.anchor.set(1, 0); survivalTimeText.x = -50; survivalTimeText.y = 120; LK.gui.topRight.addChild(survivalTimeText); // High score display var highScoreText = new Text2('Best: ' + storage.highScore + ' (Wave ' + storage.bestWave + ')', { size: 40, fill: 0xFFD700 }); highScoreText.anchor.set(0.5, 0); highScoreText.x = 0; highScoreText.y = 150; LK.gui.top.addChild(highScoreText); // Stats display var statsText = new Text2('Games: ' + storage.totalGamesPlayed + ' | Enemies: ' + storage.totalEnemiesDefeated, { size: 35, fill: 0xCCCCCC }); statsText.anchor.set(0.5, 0); statsText.x = 0; statsText.y = 200; LK.gui.top.addChild(statsText); // Castle health bar var castleHealthBar = new HealthBar(); castleHealthBar.x = 50; castleHealthBar.y = 300; LK.gui.topLeft.addChild(castleHealthBar); // Arrays for UI elements var enemyHealthBars = []; var threatIndicators = []; // Power-up status display var powerUpText = new Text2('', { size: 40, fill: 0x00FF88 }); powerUpText.anchor.set(0.5, 0); powerUpText.x = 0; powerUpText.y = 100; LK.gui.top.addChild(powerUpText); // Track active power-ups var activePowerUps = { shield: 0, extraBullets: 0 }; // Ability UI buttons var shieldButton = new Text2('SHIELD', { size: 45, fill: 0x0088FF }); shieldButton.anchor.set(0, 0); shieldButton.x = 50; shieldButton.y = 180; LK.gui.topLeft.addChild(shieldButton); var shockwaveButton = new Text2('SHOCKWAVE', { size: 45, fill: 0xFF4400 }); shockwaveButton.anchor.set(0, 0); shockwaveButton.x = 50; shockwaveButton.y = 240; LK.gui.topLeft.addChild(shockwaveButton); // Upgrade menu button var upgradeButton = new Text2('β‘ UPGRADES', { size: 50, fill: 0xFFD700 }); upgradeButton.anchor.set(0, 0); upgradeButton.x = 50; upgradeButton.y = 300; LK.gui.topLeft.addChild(upgradeButton); // Initialize castle castle = game.addChild(new Castle()); castle.x = 1024; castle.y = 1366; // Add scrap collection range indicator to castle var scrapIndicator = new ScrapCollectionIndicator(); scrapIndicator.x = castle.x; scrapIndicator.y = castle.y; game.addChild(scrapIndicator); // Apply permanent upgrades from storage var fireRateBonus = getUpgradeBonus('fireRate', storage.fireRateLevel); castle.shootInterval = Math.max(20, Math.floor(castle.shootInterval * (1 - fireRateBonus))); var healthBonus = getUpgradeBonus('health', storage.healthLevel); castle.maxHealth += healthBonus; castle.health = castle.maxHealth; // Start with full health var damageBonus = getUpgradeBonus('damage', storage.damageLevel); var baseBulletDamage = 1.01; // Store upgraded damage for bullets castle.upgradedBulletDamage = baseBulletDamage * (1 + damageBonus); // Apply permanent bullet count bonus (separate from temporary power-ups) var bulletCountBonus = getUpgradeBonus('bulletCount', storage.bulletCountLevel); castle.bulletsPerShot += bulletCountBonus; // Update bullets text with correct value after applying bonus bulletsText.setText('Bullets: ' + castle.bulletsPerShot); // Initialize castle health bar castleHealthBar.updateHealth(castle.health, castle.maxHealth); // Create upgrade menu var upgradeMenu = new UpgradeMenu(); upgradeMenu.visible = false; game.addChild(upgradeMenu); var upgradeMenuVisible = false; // Movement speed will be applied in the movement system var movementSpeedBonus = getUpgradeBonus('movementSpeed', storage.movementSpeedLevel); var baseCastleSpeed = 400; castleMovementSpeed = baseCastleSpeed * (1 + movementSpeedBonus); // Start background music LK.playMusic('bgmusic'); // Update game counter at start storage.totalGamesPlayed++; // Spawn functions function spawnEnemy() { var enemy; // Mini-boss wave logic - every 5th wave if (isMiniBossWave) { if (!miniBossSpawned) { // Always spawn a boss enemy as the first enemy of mini-boss wave enemy = new BossEnemy(); miniBossSpawned = true; } else { // Higher chance for tank/shield enemies in mini-boss waves var enemyType = Math.random(); if (enemyType < 0.4) { enemy = new TankEnemy(); } else if (enemyType < 0.7) { enemy = new ShieldEnemy(); } else if (enemyType < 0.85) { enemy = new BossEnemy(); } else { enemy = new BomberEnemy(); } } } else { // Normal wave enemy distribution with adaptive scaling var enemyType = Math.random(); // Increase chance of advanced enemies in later waves var advancedEnemyChance = Math.min(0.4, waveNumber * 0.02); // Up to 40% chance by wave 20 if (enemyType < 0.25) { enemy = new Enemy(); } else if (enemyType < 0.35) { enemy = new FastEnemy(); } else if (enemyType < 0.45) { enemy = new TankEnemy(); } else if (enemyType < 0.55) { enemy = new SniperEnemy(); } else if (enemyType < 0.63) { enemy = new BomberEnemy(); } else if (enemyType < 0.7) { enemy = new HealerEnemy(); } else if (enemyType < 0.76) { enemy = new ShieldEnemy(); } else if (enemyType < 0.82) { enemy = new TeleporterEnemy(); } else if (enemyType < 0.87) { enemy = new SplitterEnemy(); } else if (enemyType < 0.87 + advancedEnemyChance * 0.3) { // Adaptive enemies become more common in later waves enemy = new AdaptiveEnemy(); } else if (enemyType < 0.87 + advancedEnemyChance * 0.6) { // Regenerating enemies for sustained threat enemy = new RegenEnemy(); } else if (enemyType < 0.87 + advancedEnemyChance * 0.8) { // Elite enemies for high-value targets enemy = new EliteEnemy(); } else if (enemyType < 0.95) { enemy = new BossEnemy(); } else { // Super rare - spawn multiple elite enemies for extreme challenge enemy = new EliteEnemy(); var secondElite = new EliteEnemy(); secondElite.x = -40; // Will be positioned properly below secondElite.y = Math.random() * 2732; secondElite.lastX = secondElite.x; secondElite.lastY = secondElite.y; var dx2 = castle.x - secondElite.x; var dy2 = castle.y - secondElite.y; secondElite.lastCastleDistanceSquared = dx2 * dx2 + dy2 * dy2; // Apply scaling to second elite var healthMultiplier = 1 + (waveNumber - 1) * 0.05; var speedMultiplier = 1 + (waveNumber - 1) * 0.02; var damageMultiplier = 1 + (waveNumber - 1) * 0.05; secondElite.health = Math.ceil(secondElite.health * healthMultiplier); secondElite.maxHealth = secondElite.health; secondElite.speed = secondElite.speed * speedMultiplier; secondElite.damage = Math.ceil(secondElite.damage * damageMultiplier); if (isMiniBossWave) { secondElite.health = Math.ceil(secondElite.health * 1.25); secondElite.maxHealth = secondElite.health; secondElite.damage = Math.ceil(secondElite.damage * 1.15); } enemies.push(secondElite); game.addChild(secondElite); } } // Spawn from random edge var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top enemy.x = Math.random() * 2048; enemy.y = -20; break; case 1: // Right enemy.x = 2068; enemy.y = Math.random() * 2732; break; case 2: // Bottom enemy.x = Math.random() * 2048; enemy.y = 2752; break; case 3: // Left enemy.x = -20; enemy.y = Math.random() * 2732; break; } // Quantified enemy scaling: +5% health and +2% speed per wave var healthMultiplier = 1 + (waveNumber - 1) * 0.05; var speedMultiplier = 1 + (waveNumber - 1) * 0.02; var damageMultiplier = 1 + (waveNumber - 1) * 0.05; // Apply scaling with proper rounding enemy.health = Math.ceil(enemy.health * healthMultiplier); enemy.maxHealth = enemy.health; enemy.speed = enemy.speed * speedMultiplier; enemy.damage = Math.ceil(enemy.damage * damageMultiplier); // Extra scaling for mini-boss waves if (isMiniBossWave) { enemy.health = Math.ceil(enemy.health * 1.25); // 25% more health enemy.maxHealth = enemy.health; enemy.damage = Math.ceil(enemy.damage * 1.15); // 15% more damage } enemy.lastX = enemy.x; enemy.lastY = enemy.y; var dx = castle.x - enemy.x; var dy = castle.y - enemy.y; enemy.lastCastleDistanceSquared = dx * dx + dy * dy; enemies.push(enemy); game.addChild(enemy); } function spawnReward() { var rewardType = Math.random(); var reward; // Increased chance for health packs when castle health is low var healthPercentage = castle.health / castle.maxHealth; var healthPackChance = healthPercentage < 0.3 ? 0.25 : healthPercentage < 0.6 ? 0.15 : 0.1; // 60% regular reward, 25% bomb reward, 15% health pack (adjusted based on health) if (rewardType < 0.6) { reward = new Reward(); rewards.push(reward); } else if (rewardType < 0.85) { reward = new BombReward(); bombRewards.push(reward); } else { reward = new HealthPack(); healthPacks.push(reward); } // Spawn in accessible areas further away from the castle var angle = Math.random() * Math.PI * 2; var distance = 400 + Math.random() * 500; reward.x = castle.x + Math.cos(angle) * distance; reward.y = castle.y + Math.sin(angle) * distance; // Keep within bounds reward.x = Math.max(100, Math.min(1948, reward.x)); reward.y = Math.max(100, Math.min(2632, reward.y)); reward.baseY = reward.y; game.addChild(reward); // Add spawn animation with tween reward.scaleX = 0; reward.scaleY = 0; tween(reward, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeOut }); } // Castle movement system with configurable speed // Note: castleMovementSpeed is set above after applying movement speed bonus var targetX = 1024; var targetY = 1366; var isMoving = false; game.down = function (x, y, obj) { // If upgrade menu is visible, let it handle the input if (upgradeMenuVisible) { upgradeMenu.down(x, y, obj); return; } // Check if clicking on ability buttons var buttonClicked = false; // Check shield button area (approximate) if (x >= 50 && x <= 200 && y >= 180 && y <= 220) { castle.activateShield(); buttonClicked = true; } // Check shockwave button area (approximate) else if (x >= 50 && x <= 250 && y >= 240 && y <= 280) { castle.activateShockwave(); buttonClicked = true; } // Check upgrade button area else if (x >= 50 && x <= 280 && y >= 300 && y <= 350) { upgradeMenuVisible = !upgradeMenuVisible; upgradeMenu.visible = upgradeMenuVisible; if (upgradeMenuVisible) { upgradeMenu.updateButtons(); } buttonClicked = true; } // Only move castle if not clicking on ability buttons if (!buttonClicked) { // Set new target position targetX = x; targetY = y; // Calculate distance to determine movement duration var dx = targetX - castle.x; var dy = targetY - castle.y; var distance = Math.sqrt(dx * dx + dy * dy); // Calculate duration based on configurable movement speed var duration = Math.max(150, distance / castleMovementSpeed * 1000); // minimum 150ms for responsiveness // Stop any existing movement tween tween.stop(castle, { x: true, y: true }); isMoving = true; // Add walking animation with slight bobbing effect var originalY = castle.y; // Start smooth walking animation with subtle bounce tween(castle, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { isMoving = false; // Subtle landing effect tween(castle, { scaleX: 1.1, scaleY: 0.9 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(castle, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeOut }); } }); } }); // Add walking bobbing animation during movement if (duration > 300) { var bobDuration = duration / 3; tween(castle, { scaleY: 1.05 }, { duration: bobDuration, easing: tween.easeInOut, onFinish: function onFinish() { tween(castle, { scaleY: 1 }, { duration: bobDuration, easing: tween.easeInOut }); } }); } } }; game.move = function (x, y, obj) { // Update target while moving (allows for path correction) if (isMoving) { targetX = x; targetY = y; // Calculate new distance and duration var dx = targetX - castle.x; var dy = targetY - castle.y; var distance = Math.sqrt(dx * dx + dy * dy); // Only update if the new target is significantly different if (distance > 40) { var duration = Math.max(150, distance / castleMovementSpeed * 1000); // Stop current tween and start new one tween.stop(castle, { x: true, y: true }); tween(castle, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { isMoving = false; } }); } } }; game.up = function (x, y, obj) { // Optional: Could add final position adjustment here if needed }; // Game update loop game.update = function () { // Update wave timer waveTimer++; // Spawn enemies enemySpawnTimer++; var spawnRate = Math.max(30, 120 - waveNumber * 5); // Faster spawning each wave // Increase spawn rate for mini-boss waves if (isMiniBossWave) { spawnRate = Math.max(20, spawnRate * 0.7); // 30% faster spawning in mini-boss waves } if (enemySpawnTimer >= spawnRate) { spawnEnemy(); enemySpawnTimer = 0; } // Spawn rewards occasionally - tied to enemy defeats for better balance rewardSpawnTimer++; var enemiesDefeated = Math.floor(LK.getScore() / 10); // Approximate enemies defeated based on score var rewardThreshold = 600 - Math.min(300, enemiesDefeated * 5); // Faster spawning as more enemies defeated if (rewardSpawnTimer >= rewardThreshold && rewards.length + bombRewards.length < 3) { // Spawn rate increases with progress, max 3 rewards total spawnReward(); rewardSpawnTimer = 0; } // Advance wave every 30 seconds if (waveTimer >= 1800) { // 30 seconds at 60fps // Wave completion bonus before advancing var waveBonus = waveNumber * 50; // 50 points per wave number var scrapBonus = Math.floor(waveNumber / 2) + 1; // 1-2 scrap metal per wave LK.setScore(LK.getScore() + waveBonus); currentScrapMetal += scrapBonus; // Show wave completion bonus var bonusText = new Text2('WAVE ' + waveNumber + ' COMPLETE!\n+' + waveBonus + ' SCORE +' + scrapBonus + ' SCRAP', { size: 60, fill: 0x00FF88 }); bonusText.anchor.set(0.5, 0.5); bonusText.x = 1024; bonusText.y = 600; game.addChild(bonusText); // Animate wave completion bonus tween(bonusText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { tween(bonusText, { alpha: 0, y: bonusText.y - 100 }, { duration: 1500, onFinish: function onFinish() { bonusText.destroy(); } }); } }); waveNumber++; waveTimer = 0; miniBossSpawned = false; // Reset mini-boss spawn flag // Check if this is a mini-boss wave (every 5th wave) isMiniBossWave = waveNumber % 5 === 0; if (isMiniBossWave) { // Special visual effect for mini-boss waves LK.effects.flashScreen(0xFF4400, 1000); // Orange flash for mini-boss // Bigger bonus for mini-boss waves var miniBossBonus = waveNumber * 100; var miniBossScrapBonus = Math.floor(waveNumber / 3) + 3; LK.setScore(LK.getScore() + miniBossBonus); currentScrapMetal += miniBossScrapBonus; // Show wave announcement var waveAnnouncementText = new Text2('MINI-BOSS WAVE ' + waveNumber + '\nBONUS: +' + miniBossBonus + ' SCORE +' + miniBossScrapBonus + ' SCRAP', { size: 70, fill: 0xFF4400 }); waveAnnouncementText.anchor.set(0.5, 0.5); waveAnnouncementText.x = 1024; waveAnnouncementText.y = 800; game.addChild(waveAnnouncementText); // Fade out wave text after 3 seconds for mini-boss tween(waveAnnouncementText, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { waveAnnouncementText.destroy(); } }); } else if (waveNumber >= 10 && waveNumber % 10 === 0) { // Show special threat warning every 10 waves var threatWarningText = new Text2('THREAT LEVEL INCREASED!\nWAVE ' + waveNumber + ' - ADAPTIVE ENEMIES INCOMING', { size: 65, fill: 0x8A2BE2 }); threatWarningText.anchor.set(0.5, 0.5); threatWarningText.x = 1024; threatWarningText.y = 800; game.addChild(threatWarningText); LK.effects.flashScreen(0x8A2BE2, 800); tween(threatWarningText, { alpha: 0 }, { duration: 2500, onFinish: function onFinish() { threatWarningText.destroy(); } }); } else { // Normal wave flash LK.effects.flashScreen(0x0066FF, 500); } } // Check bullet-enemy collisions for (var b = bullets.length - 1; b >= 0; b--) { var bullet = bullets[b]; var bulletHit = false; // Early exit if bullet is off-screen if (bullet.x < -100 || bullet.x > 2148 || bullet.y < -100 || bullet.y > 2832) { bullet.destroy(); bullets.splice(b, 1); continue; } for (var e = enemies.length - 1; e >= 0; e--) { var enemy = enemies[e]; if (bullet.intersects(enemy)) { enemy.takeDamage(bullet.damage); bullet.destroy(); bullets.splice(b, 1); bulletHit = true; break; } } if (bulletHit) continue; } // Check castle-reward collisions for pickup for (var r = rewards.length - 1; r >= 0; r--) { var reward = rewards[r]; if (castle.intersects(reward)) { // Define maximum bullet limit to prevent excessive stacking var maxBullets = 8; if (castle.bulletsPerShot < maxBullets) { // Add 1 bullet per reward (consistent increment) castle.bulletsPerShot++; bulletsText.setText('Bullets: ' + castle.bulletsPerShot); LK.getSound('award').play(); LK.effects.flashObject(castle, 0x00FF00, 500); // Visual feedback for bullet upgrade with scaling effect tween(castle, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(castle, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } }); } else { // At maximum bullets - give score bonus instead LK.setScore(LK.getScore() + 100); scoreText.setText(LK.getScore()); bulletsText.setText('Bullets: ' + castle.bulletsPerShot + ' (MAX)'); LK.getSound('award').play(); LK.effects.flashObject(castle, 0xFFD700, 500); // Gold flash for max bonus // Brief text notification var maxText = new Text2('+100 SCORE!', { size: 40, fill: 0xFFD700 }); maxText.anchor.set(0.5, 0.5); maxText.x = castle.x; maxText.y = castle.y - 60; game.addChild(maxText); // Animate and remove notification tween(maxText, { y: maxText.y - 50, alpha: 0 }, { duration: 1500, onFinish: function onFinish() { maxText.destroy(); } }); } reward.destroy(); rewards.splice(r, 1); } } // Check castle-bomb reward collisions for pickup for (var br = bombRewards.length - 1; br >= 0; br--) { var bombReward = bombRewards[br]; if (castle.intersects(bombReward)) { // Schedule bomb to appear at character position after 3 seconds var bombX = castle.x; var bombY = castle.y; LK.setTimeout(function () { var bomb = new Bomb(); bomb.x = bombX; bomb.y = bombY; bombs.push(bomb); game.addChild(bomb); }, 3000); LK.getSound('award').play(); LK.effects.flashObject(castle, 0xFF8800, 500); bombReward.destroy(); bombRewards.splice(br, 1); } } // Scrap metal collection is now handled automatically in ScrapMetalDrop class // Check castle-health pack collisions for pickup for (var hp = healthPacks.length - 1; hp >= 0; hp--) { var healthPack = healthPacks[hp]; if (castle.intersects(healthPack)) { // Heal castle var healAmount = Math.min(healthPack.healAmount, castle.maxHealth - castle.health); if (healAmount > 0) { castle.health += healAmount; LK.getSound('award').play(); LK.effects.flashObject(castle, 0x44FF44, 500); // Update health bar if (castleHealthBar) { castleHealthBar.updateHealth(castle.health, castle.maxHealth); } // Show heal amount var healText = new Text2('+' + healAmount + ' HEALTH', { size: 40, fill: 0x44FF44 }); healText.anchor.set(0.5, 0.5); healText.x = castle.x; healText.y = castle.y - 60; game.addChild(healText); // Animate heal text tween(healText, { y: healText.y - 50, alpha: 0 }, { duration: 1500, onFinish: function onFinish() { healText.destroy(); } }); } healthPack.destroy(); healthPacks.splice(hp, 1); } } // Update ability button text with cooldown status if (castle.shieldCooldown > 0) { var shieldSeconds = Math.ceil(castle.shieldCooldown / 60); shieldButton.setText('SHIELD (' + shieldSeconds + 's)'); shieldButton.fill = 0x666666; // Grayed out } else { shieldButton.setText('SHIELD'); shieldButton.fill = 0x0088FF; // Active color } if (castle.shockwaveCooldown > 0) { var shockwaveSeconds = Math.ceil(castle.shockwaveCooldown / 60); shockwaveButton.setText('SHOCKWAVE (' + shockwaveSeconds + 's)'); shockwaveButton.fill = 0x666666; // Grayed out } else { shockwaveButton.setText('SHOCKWAVE'); shockwaveButton.fill = 0xFF4400; // Active color } // Update survival time survivalTime++; var minutes = Math.floor(survivalTime / 3600); // 60 seconds * 60 fps var seconds = Math.floor(survivalTime % 3600 / 60); var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds; // Update wave counter and survival time waveText.setText('Wave: ' + waveNumber); survivalTimeText.setText('Time: ' + timeString); // Check for survival milestones (every 10 waves) if (waveNumber >= lastMilestoneWave + 10) { lastMilestoneWave = waveNumber; // Milestone rewards var milestoneScoreBonus = waveNumber * 200; var milestoneScrapBonus = Math.floor(waveNumber / 5) + 5; LK.setScore(LK.getScore() + milestoneScoreBonus); currentScrapMetal += milestoneScrapBonus; // Show milestone achievement var milestoneText = new Text2('SURVIVAL MILESTONE!\nWAVE ' + waveNumber + ' REACHED\n+' + milestoneScoreBonus + ' SCORE +' + milestoneScrapBonus + ' SCRAP', { size: 55, fill: 0xFFD700 }); milestoneText.anchor.set(0.5, 0.5); milestoneText.x = 1024; milestoneText.y = 1000; game.addChild(milestoneText); // Special visual effects for milestone LK.effects.flashScreen(0xFFD700, 1200); tween(milestoneText, { scaleX: 1.3, scaleY: 1.3 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { tween(milestoneText, { alpha: 0, y: milestoneText.y - 150 }, { duration: 2000, onFinish: function onFinish() { milestoneText.destroy(); } }); } }); } // Update power-up display var powerUpDisplay = ''; if (castle.shield) { var shieldTime = Math.ceil(castle.shield.duration / 60); powerUpDisplay += 'SHIELD: ' + shieldTime + 's '; } var permanentBullets = getUpgradeBonus('bulletCount', storage.bulletCountLevel) + 1; var tempBullets = castle.bulletsPerShot - permanentBullets; if (tempBullets > 0) { powerUpDisplay += '+' + tempBullets + ' BULLETS '; } powerUpText.setText(powerUpDisplay); // Update threat indicators // Clear old indicators for (var ti = threatIndicators.length - 1; ti >= 0; ti--) { threatIndicators[ti].destroy(); threatIndicators.splice(ti, 1); } // Create threat indicators for enemies near screen edges for (var ei = 0; ei < enemies.length; ei++) { var enemy = enemies[ei]; var indicator = null; // Check if enemy is near edges and create indicators if (enemy.x < 100) { // Left edge indicator = new ThreatIndicator(); indicator.x = 50; indicator.y = Math.max(100, Math.min(2632, enemy.y)); indicator.rotation = Math.PI; // Point right } else if (enemy.x > 1948) { // Right edge indicator = new ThreatIndicator(); indicator.x = 1998; indicator.y = Math.max(100, Math.min(2632, enemy.y)); indicator.rotation = 0; // Point left } else if (enemy.y < 100) { // Top edge indicator = new ThreatIndicator(); indicator.x = Math.max(100, Math.min(1948, enemy.x)); indicator.y = 50; indicator.rotation = Math.PI / 2; // Point down } else if (enemy.y > 2632) { // Bottom edge indicator = new ThreatIndicator(); indicator.x = Math.max(100, Math.min(1948, enemy.x)); indicator.y = 2682; indicator.rotation = -Math.PI / 2; // Point up } if (indicator) { threatIndicators.push(indicator); game.addChild(indicator); } } // Update scrap collection indicator position if (scrapIndicator) { scrapIndicator.x = castle.x; scrapIndicator.y = castle.y; } // Update UI with tier information var totalScrap = storage.scrapMetal + currentScrapMetal; var scrapDisplay = 'Scrap: ' + totalScrap; // Show next upgrade cost for fire rate as example if (storage.fireRateLevel < storage.maxUpgradeTier) { var nextCost = getUpgradeCost('fireRate', storage.fireRateLevel); scrapDisplay += ' | Fire Rate T' + storage.fireRateLevel + 'βT' + (storage.fireRateLevel + 1) + ': ' + nextCost; } else { scrapDisplay += ' | Fire Rate: MAX'; } scrapMetalText.setText(scrapDisplay); healthText.setText('Health: ' + castle.health + '/' + castle.maxHealth); scoreText.setText(LK.getScore()); // Update high score display with current vs best comparison var currentScore = LK.getScore(); if (currentScore > storage.highScore) { highScoreText.setText('BEST: ' + currentScore + ' (Wave ' + waveNumber + ') NEW!'); highScoreText.fill = 0x00FF00; // Green for new record } else { highScoreText.setText('Best: ' + storage.highScore + ' (Wave ' + storage.bestWave + ') | Current: ' + currentScore); highScoreText.fill = 0xFFD700; // Gold for normal display } // Update stats display statsText.setText('Games: ' + storage.totalGamesPlayed + ' | Enemies: ' + storage.totalEnemiesDefeated + ' | Best Time: ' + Math.floor(storage.longestSurvivalTime / 3600) + ':' + (Math.floor(storage.longestSurvivalTime % 3600 / 60) < 10 ? '0' : '') + Math.floor(storage.longestSurvivalTime % 3600 / 60)); // Update upgrade menu if visible if (upgradeMenuVisible && upgradeMenu.visible) { upgradeMenu.updateButtons(); } // Update upgrade button text based on available scrap var upgradeButtonText = 'β‘ UPGRADES'; var totalScrap = storage.scrapMetal + currentScrapMetal; if (totalScrap > 0) { upgradeButtonText += ' (' + totalScrap + ')'; } upgradeButton.setText(upgradeButtonText); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
scrapMetal: 0,
fireRateLevel: 1,
movementSpeedLevel: 1,
healthLevel: 1,
damageLevel: 1,
bulletCountLevel: 1,
shieldLevel: 1,
shockwaveLevel: 1,
maxUpgradeTier: 10,
fireRateBaseCost: 50,
movementSpeedBaseCost: 75,
healthBaseCost: 100,
damageBaseCost: 80,
bulletCountBaseCost: 120,
shieldBaseCost: 150,
shockwaveBaseCost: 200,
highScore: 0,
bestWave: 1,
longestSurvivalTime: 0,
totalGamesPlayed: 0,
totalEnemiesDefeated: 0,
achievementUnlocked: false
});
/****
* Classes
****/
var AdaptiveEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGraphics.tint = 0x8A2BE2; // Purple for adaptive enemy
self.health = 4;
self.maxHealth = 4;
self.speed = 1.2;
self.damage = 12;
self.adaptationLevel = 0;
self.adaptationTimer = 0;
self.adaptationInterval = 300; // Adapt every 5 seconds
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistanceSquared = Infinity;
// Create health bar for this enemy
self.healthBar = new EnemyHealthBar();
self.healthBar.y = -50; // Position above enemy
enemyHealthBars.push(self.healthBar);
self.addChild(self.healthBar);
self.update = function () {
// Adaptation logic - enemy becomes stronger over time
self.adaptationTimer++;
if (self.adaptationTimer >= self.adaptationInterval && self.adaptationLevel < 3) {
self.adaptationLevel++;
self.adaptationTimer = 0;
// Increase stats based on adaptation level
self.speed += 0.3;
self.damage += 3;
self.maxHealth += 2;
self.health = Math.min(self.maxHealth, self.health + 2);
// Visual feedback for adaptation
LK.effects.flashObject(self, 0x8A2BE2, 500);
tween(self, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
// Move toward castle
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared > 3600) {
var distance = Math.sqrt(distanceSquared);
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Check if reached castle
if (self.lastCastleDistanceSquared > 3600 && distanceSquared <= 3600) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistanceSquared = distanceSquared;
};
self.takeDamage = function (damage) {
// Adaptive enemy takes less damage as it adapts
var damagereduction = self.adaptationLevel * 0.1;
var actualDamage = damage * (1 - damagereduction);
self.health -= actualDamage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
// Update health bar
if (self.healthBar) {
self.healthBar.updateHealth(self.health, self.maxHealth);
}
if (self.health <= 0) {
LK.setScore(LK.getScore() + 25 + self.adaptationLevel * 10); // More points for adapted enemies
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
// Drop more scrap metal based on adaptation level
if (Math.random() < 0.7 + self.adaptationLevel * 0.1) {
var scrap = new ScrapMetalDrop();
scrap.x = self.x;
scrap.y = self.y;
scrap.baseY = scrap.y;
scrap.value = 5 + self.adaptationLevel * 2; // More valuable based on adaptation
scrapMetalDrops.push(scrap);
game.addChild(scrap);
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var Bomb = Container.expand(function () {
var self = Container.call(this);
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
bombGraphics.tint = 0xFF0000;
self.explosionRadius = 1000;
self.fuseTime = 120; // 2 seconds at 60fps
self.blinkSpeed = 10;
// Start with small scale
self.scaleX = 0.3;
self.scaleY = 0.3;
self.update = function () {
self.fuseTime--;
// Blinking effect that gets faster as explosion approaches
var blinkInterval = Math.max(5, Math.floor(self.fuseTime / 10));
if (LK.ticks % blinkInterval === 0) {
bombGraphics.alpha = bombGraphics.alpha === 1 ? 0.3 : 1;
}
// Scale pulsing effect
var pulseScale = 1 + Math.sin(LK.ticks * 0.3) * 0.1;
self.scaleX = pulseScale;
self.scaleY = pulseScale;
// Explode when fuse runs out
if (self.fuseTime <= 0) {
self.explode();
}
};
self.explode = function () {
// Visual explosion effect
LK.effects.flashScreen(0xFF4400, 600);
// Scale up rapidly during explosion
tween(self, {
scaleX: 3,
scaleY: 3
}, {
duration: 300,
easing: tween.easeOut
});
// Damage enemies in radius
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.explosionRadius) {
// Instant kill for enemies in blast radius
enemy.takeDamage(999);
}
}
// Remove bomb from game after explosion animation
LK.setTimeout(function () {
self.destroy();
for (var i = bombs.length - 1; i >= 0; i--) {
if (bombs[i] === self) {
bombs.splice(i, 1);
break;
}
}
}, 300);
};
self.activateShield = function () {
if (self.shieldCooldown <= 0 && !self.shield) {
// Create shield
self.shield = new Shield();
self.shield.x = self.x;
self.shield.y = self.y;
game.addChild(self.shield);
// Start cooldown
var cooldownReduction = getUpgradeBonus('shield', storage.shieldLevel);
self.shieldCooldown = Math.floor(self.shieldMaxCooldown * (1 - cooldownReduction));
// Visual feedback
LK.effects.flashScreen(0x0088FF, 300);
}
};
self.activateShockwave = function () {
if (self.shockwaveCooldown <= 0) {
// Create shockwave
var shockwave = new Shockwave();
shockwave.x = self.x;
shockwave.y = self.y;
// Increase damage based on upgrade level
shockwave.damage = 5 + getUpgradeBonus('shockwave', storage.shockwaveLevel) * 20; // +2 damage per level (0.1 * 20 = 2)
shockwaves.push(shockwave);
game.addChild(shockwave);
// Start cooldown
var cooldownReduction = getUpgradeBonus('shockwave', storage.shockwaveLevel);
self.shockwaveCooldown = Math.floor(self.shockwaveMaxCooldown * (1 - cooldownReduction));
// Visual feedback
LK.effects.flashScreen(0xFF4400, 500);
}
};
self.update = function () {
// Update ability cooldowns
if (self.shieldCooldown > 0) {
self.shieldCooldown--;
}
if (self.shockwaveCooldown > 0) {
self.shockwaveCooldown--;
}
// Update shield position if active
if (self.shield) {
self.shield.x = self.x;
self.shield.y = self.y;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Auto-fire with priority targeting system
if (self.shootCooldown <= 0 && enemies.length > 0) {
var targetEnemy = null;
var bestPriority = -1;
var bestDistance = Infinity;
// Priority levels: Higher number = higher priority
var priorityMap = {
'SniperEnemy': 5,
// Highest priority - long range damage
'BomberEnemy': 4,
// High priority - explosive threat
'BossEnemy': 4,
// High priority - powerful enemy
'FastEnemy': 3,
// Medium-high priority - quick threat
'HealerEnemy': 3,
// Medium-high priority - supports others
'TeleporterEnemy': 2,
// Medium priority - unpredictable
'SplitterEnemy': 2,
// Medium priority - creates more enemies
'TankEnemy': 1,
// Low priority - slow but tough
'ShieldEnemy': 1,
// Low priority - tough but manageable
'Enemy': 0 // Lowest priority - basic enemy
};
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Determine enemy type by checking constructor name
var enemyType = 'Enemy'; // Default
if (enemy.constructor === SniperEnemy) enemyType = 'SniperEnemy';else if (enemy.constructor === BomberEnemy) enemyType = 'BomberEnemy';else if (enemy.constructor === BossEnemy) enemyType = 'BossEnemy';else if (enemy.constructor === FastEnemy) enemyType = 'FastEnemy';else if (enemy.constructor === HealerEnemy) enemyType = 'HealerEnemy';else if (enemy.constructor === TeleporterEnemy) enemyType = 'TeleporterEnemy';else if (enemy.constructor === SplitterEnemy) enemyType = 'SplitterEnemy';else if (enemy.constructor === TankEnemy) enemyType = 'TankEnemy';else if (enemy.constructor === ShieldEnemy) enemyType = 'ShieldEnemy';
var priority = priorityMap[enemyType] || 0;
// Select target based on priority first, then distance
if (priority > bestPriority || priority === bestPriority && distance < bestDistance) {
bestPriority = priority;
bestDistance = distance;
targetEnemy = enemy;
}
}
if (targetEnemy) {
self.fireAtTarget(targetEnemy);
self.shootCooldown = self.shootInterval;
}
}
};
return self;
});
var BombReward = Container.expand(function () {
var self = Container.call(this);
var rewardGraphics = self.attachAsset('bombReward', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually distinct with orange tint
rewardGraphics.tint = 0xFF8800;
self.bobOffset = Math.random() * Math.PI * 2;
self.baseY = 0;
self.lifetime = 420; // 7 seconds at 60fps
self.update = function () {
// Gentle bobbing animation
self.y = self.baseY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5;
// Decrease lifetime
self.lifetime--;
// Start fading when close to expiring
if (self.lifetime <= 120) {
var alpha = self.lifetime / 120;
rewardGraphics.alpha = alpha;
// Add flashing animation when close to expiring
var flashInterval = Math.max(5, Math.floor(self.lifetime / 20));
if (LK.ticks % flashInterval === 0) {
tween(rewardGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: flashInterval * 8,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(rewardGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: flashInterval * 8,
easing: tween.easeIn
});
}
});
}
}
// Remove when lifetime expires
if (self.lifetime <= 0) {
tween(self, {
scaleX: 0,
scaleY: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
for (var i = bombRewards.length - 1; i >= 0; i--) {
if (bombRewards[i] === self) {
bombRewards.splice(i, 1);
break;
}
}
}
});
}
};
self.down = function (x, y, obj) {
// Schedule bomb to appear at character position after 3 seconds
var bombX = castle.x;
var bombY = castle.y;
LK.setTimeout(function () {
var bomb = new Bomb();
bomb.x = bombX;
bomb.y = bombY;
bombs.push(bomb);
game.addChild(bomb);
}, 3000);
LK.getSound('award').play();
LK.effects.flashObject(castle, 0xFF8800, 500);
self.destroy();
for (var i = bombRewards.length - 1; i >= 0; i--) {
if (bombRewards[i] === self) {
bombRewards.splice(i, 1);
break;
}
}
};
return self;
});
var BomberEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('bomberEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.speed = 1.2;
self.damage = 12;
self.explosionRadius = 100;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.explode();
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.explode = function () {
LK.effects.flashScreen(0xFF8800, 300);
// Damage nearby enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy !== self) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.explosionRadius) {
enemy.takeDamage(2);
}
}
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 30);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.explode();
}
};
return self;
});
var BossEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('bossEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 15;
self.maxHealth = 15;
self.speed = 0.6;
self.damage = 30;
self.spawnCooldown = 0;
self.spawnInterval = 300; // Spawns minions every 5 seconds
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Spawn minion enemies
if (self.spawnCooldown > 0) {
self.spawnCooldown--;
}
if (self.spawnCooldown <= 0 && enemies.length < 15) {
var minion = new Enemy();
var angle = Math.random() * Math.PI * 2;
minion.x = self.x + Math.cos(angle) * 60;
minion.y = self.y + Math.sin(angle) * 60;
minion.lastX = minion.x;
minion.lastY = minion.y;
var dx2 = castle.x - minion.x;
var dy2 = castle.y - minion.y;
minion.lastCastleDistance = Math.sqrt(dx2 * dx2 + dy2 * dy2);
enemies.push(minion);
game.addChild(minion);
self.spawnCooldown = self.spawnInterval;
}
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
var reducedDamage = damage * 0.7; // Boss takes 30% less damage
self.health -= reducedDamage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 100);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
LK.effects.flashScreen(0x440044, 1000);
// Drop multiple scrap metal (guaranteed)
for (var c = 0; c < 3; c++) {
var scrap = new ScrapMetalDrop();
var angle = Math.random() * Math.PI * 2;
var distance = 20 + Math.random() * 40;
scrap.x = self.x + Math.cos(angle) * distance;
scrap.y = self.y + Math.sin(angle) * distance;
scrap.baseY = scrap.y;
scrap.value = 15; // High value for boss
scrapMetalDrops.push(scrap);
game.addChild(scrap);
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 50;
self.velocityX = 0;
self.velocityY = 0;
self.damage = 1.01;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += self.velocityX;
self.y += self.velocityY;
// Add spinning animation
self.rotation += 0.2;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
}
};
return self;
});
var Castle = Container.expand(function () {
var self = Container.call(this);
var castleGraphics = self.attachAsset('castle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set physical diameter to match image dimensions
self.width = castleGraphics.width;
self.height = castleGraphics.height;
self.health = 100;
self.maxHealth = 100;
self.bulletsPerShot = 1;
self.shootCooldown = 0;
self.shootInterval = 60; // Fire every second at 60fps
// Special abilities
self.shield = null;
self.shieldCooldown = 0;
self.shieldMaxCooldown = 900; // 15 seconds at 60fps
self.shockwaveCooldown = 0;
self.shockwaveMaxCooldown = 1200; // 20 seconds at 60fps
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Auto-fire with priority targeting system
if (self.shootCooldown <= 0 && enemies.length > 0) {
var targetEnemy = null;
var bestPriority = -1;
var bestDistance = Infinity;
// Priority levels: Higher number = higher priority
var priorityMap = {
'EliteEnemy': 6,
// Highest priority - extremely dangerous
'SniperEnemy': 5,
// Highest priority - long range damage
'AdaptiveEnemy': 5,
// Highest priority - becomes stronger over time
'BomberEnemy': 4,
// High priority - explosive threat
'BossEnemy': 4,
// High priority - powerful enemy
'RegenEnemy': 4,
// High priority - heals over time
'FastEnemy': 3,
// Medium-high priority - quick threat
'HealerEnemy': 3,
// Medium-high priority - supports others
'TeleporterEnemy': 2,
// Medium priority - unpredictable
'SplitterEnemy': 2,
// Medium priority - creates more enemies
'TankEnemy': 1,
// Low priority - slow but tough
'ShieldEnemy': 1,
// Low priority - tough but manageable
'Enemy': 0 // Lowest priority - basic enemy
};
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Determine enemy type by checking constructor name
var enemyType = 'Enemy'; // Default
if (enemy.constructor === SniperEnemy) enemyType = 'SniperEnemy';else if (enemy.constructor === BomberEnemy) enemyType = 'BomberEnemy';else if (enemy.constructor === BossEnemy) enemyType = 'BossEnemy';else if (enemy.constructor === FastEnemy) enemyType = 'FastEnemy';else if (enemy.constructor === HealerEnemy) enemyType = 'HealerEnemy';else if (enemy.constructor === TeleporterEnemy) enemyType = 'TeleporterEnemy';else if (enemy.constructor === SplitterEnemy) enemyType = 'SplitterEnemy';else if (enemy.constructor === TankEnemy) enemyType = 'TankEnemy';else if (enemy.constructor === ShieldEnemy) enemyType = 'ShieldEnemy';else if (enemy.constructor === AdaptiveEnemy) enemyType = 'AdaptiveEnemy';else if (enemy.constructor === RegenEnemy) enemyType = 'RegenEnemy';else if (enemy.constructor === EliteEnemy) enemyType = 'EliteEnemy';
var priority = priorityMap[enemyType] || 0;
// Select target based on priority first, then distance
if (priority > bestPriority || priority === bestPriority && distance < bestDistance) {
bestPriority = priority;
bestDistance = distance;
targetEnemy = enemy;
}
}
if (targetEnemy) {
self.fireAtTarget(targetEnemy);
self.shootCooldown = self.shootInterval;
}
}
};
self.fireAtTarget = function (target) {
var baseAngle = Math.atan2(target.y - self.y, target.x - self.x);
var spreadAngle = Math.PI / 6; // 30 degrees spread
for (var i = 0; i < self.bulletsPerShot; i++) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
// Apply upgraded damage (always use upgraded damage)
bullet.damage = self.upgradedBulletDamage || bullet.damage;
var offset = 0;
if (self.bulletsPerShot > 1) {
offset = (i - (self.bulletsPerShot - 1) / 2) * (spreadAngle / Math.max(1, self.bulletsPerShot - 1));
}
var angle = baseAngle + offset;
bullet.velocityX = Math.cos(angle) * bullet.speed;
bullet.velocityY = Math.sin(angle) * bullet.speed;
// Set bullet rotation to match direction
bullet.rotation = angle;
bullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
};
self.takeDamage = function (damage) {
// Check if shield is active
if (self.shield) {
// Shield blocks damage and flashes
LK.effects.flashObject(self.shield, 0x0088FF, 200);
return; // No damage taken
}
self.health -= damage;
LK.effects.flashObject(self, 0xFF0000, 300);
// Update health bar
if (castleHealthBar) {
castleHealthBar.updateHealth(self.health, self.maxHealth);
}
if (self.health <= 0) {
// Save collected scrap metal to persistent storage
storage.scrapMetal += currentScrapMetal;
// Check and update high scores
var currentScore = LK.getScore();
var isNewHighScore = false;
var isNewWaveRecord = false;
var isNewTimeRecord = false;
if (currentScore > storage.highScore) {
storage.highScore = currentScore;
isNewHighScore = true;
}
if (waveNumber > storage.bestWave) {
storage.bestWave = waveNumber;
isNewWaveRecord = true;
}
if (survivalTime > storage.longestSurvivalTime) {
storage.longestSurvivalTime = survivalTime;
isNewTimeRecord = true;
}
// Show achievement notifications
if (isNewHighScore || isNewWaveRecord || isNewTimeRecord) {
var achievementText = 'NEW RECORD!\n';
if (isNewHighScore) achievementText += 'HIGH SCORE: ' + currentScore + '\n';
if (isNewWaveRecord) achievementText += 'BEST WAVE: ' + waveNumber + '\n';
if (isNewTimeRecord) {
var minutes = Math.floor(survivalTime / 3600);
var seconds = Math.floor(survivalTime % 3600 / 60);
achievementText += 'LONGEST TIME: ' + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
}
var recordText = new Text2(achievementText, {
size: 55,
fill: 0xFF00FF
});
recordText.anchor.set(0.5, 0.5);
recordText.x = 1024;
recordText.y = 800;
game.addChild(recordText);
LK.effects.flashScreen(0xFF00FF, 1500);
tween(recordText, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(recordText, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
recordText.destroy();
}
});
}
});
}
LK.showGameOver();
}
};
return self;
});
var EliteEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('tankEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGraphics.tint = 0xFFD700; // Gold for elite enemy
self.health = 12;
self.maxHealth = 12;
self.speed = 1.1;
self.damage = 25;
self.armor = 0.3; // Takes 30% less damage
self.specialAbilityCooldown = 0;
self.specialAbilityInterval = 240; // Special ability every 4 seconds
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistanceSquared = Infinity;
// Create health bar for this enemy
self.healthBar = new EnemyHealthBar();
self.healthBar.y = -50; // Position above enemy
enemyHealthBars.push(self.healthBar);
self.addChild(self.healthBar);
self.update = function () {
// Special ability - damage reduction boost
self.specialAbilityCooldown++;
if (self.specialAbilityCooldown >= self.specialAbilityInterval) {
// Temporary damage reduction boost
self.armor = Math.min(0.6, self.armor + 0.1);
self.specialAbilityCooldown = 0;
LK.effects.flashObject(self, 0xFFD700, 400);
// Create temporary shield effect
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeIn
});
}
});
}
// Move toward castle
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared > 3600) {
var distance = Math.sqrt(distanceSquared);
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Check if reached castle
if (self.lastCastleDistanceSquared > 3600 && distanceSquared <= 3600) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistanceSquared = distanceSquared;
};
self.takeDamage = function (damage) {
var reducedDamage = damage * (1 - self.armor);
self.health -= reducedDamage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
// Update health bar
if (self.healthBar) {
self.healthBar.updateHealth(self.health, self.maxHealth);
}
if (self.health <= 0) {
LK.setScore(LK.getScore() + 75); // High score reward
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
// Drop multiple scrap metal (guaranteed)
for (var c = 0; c < 2; c++) {
var scrap = new ScrapMetalDrop();
var angle = Math.random() * Math.PI * 2;
var distance = 20 + Math.random() * 40;
scrap.x = self.x + Math.cos(angle) * distance;
scrap.y = self.y + Math.sin(angle) * distance;
scrap.baseY = scrap.y;
scrap.value = 12; // High value for elite enemy
scrapMetalDrops.push(scrap);
game.addChild(scrap);
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.speed = 1;
self.damage = 10;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistanceSquared = Infinity;
// Create health bar for this enemy
self.healthBar = new EnemyHealthBar();
self.healthBar.y = -50; // Position above enemy
enemyHealthBars.push(self.healthBar);
self.addChild(self.healthBar);
self.update = function () {
// Move toward castle
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared > 3600) {
// 60^2 = 3600
// Don't overlap with castle
var distance = Math.sqrt(distanceSquared);
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Check if reached castle
if (self.lastCastleDistanceSquared > 3600 && distanceSquared <= 3600) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistanceSquared = distanceSquared;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
// Update health bar
if (self.healthBar) {
self.healthBar.updateHealth(self.health, self.maxHealth);
}
if (self.health <= 0) {
LK.setScore(LK.getScore() + 10);
storage.totalEnemiesDefeated++;
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
// Drop scrap metal (80% chance - increased for better collection)
if (Math.random() < 0.8) {
var scrap = new ScrapMetalDrop();
scrap.x = self.x;
scrap.y = self.y;
scrap.baseY = scrap.y;
scrap.value = 5; // Basic enemy drops 5 scrap metal
scrapMetalDrops.push(scrap);
game.addChild(scrap);
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var EnemyHealthBar = Container.expand(function () {
var self = Container.call(this);
// Create background bar (dark red)
var backgroundBar = LK.getAsset('bgTile', {
anchorX: 0.5,
anchorY: 0.5
});
backgroundBar.tint = 0x330000;
backgroundBar.scaleX = 0.8;
backgroundBar.scaleY = 0.15;
self.addChild(backgroundBar);
// Create health bar (red)
var healthBar = LK.getAsset('bgTile', {
anchorX: 0,
anchorY: 0.5
});
healthBar.tint = 0xFF0000;
healthBar.scaleX = 0.8;
healthBar.scaleY = 0.15;
healthBar.x = -backgroundBar.width * backgroundBar.scaleX / 2;
self.addChild(healthBar);
self.backgroundBar = backgroundBar;
self.healthBar = healthBar;
self.updateHealth = function (currentHealth, maxHealth) {
var healthPercentage = Math.max(0, currentHealth / maxHealth);
self.healthBar.scaleX = 0.8 * healthPercentage;
// Hide if at full health to reduce clutter
self.visible = healthPercentage < 1.0;
};
return self;
});
var FastEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('fastEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1;
self.maxHealth = 1;
self.speed = 3;
self.damage = 5;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
// Move toward castle with higher speed
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Check if reached castle
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 15);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
// Drop scrap metal (50% chance, less than basic enemy)
if (Math.random() < 0.5) {
var scrap = new ScrapMetalDrop();
scrap.x = self.x;
scrap.y = self.y;
scrap.baseY = scrap.y;
scrap.value = 3; // Less value than basic enemy
scrapMetalDrops.push(scrap);
game.addChild(scrap);
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var HealerEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('healerEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 4;
self.maxHealth = 4;
self.speed = 0.8;
self.damage = 8;
self.healCooldown = 0;
self.healInterval = 180; // Heals every 3 seconds
self.healRadius = 150;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Heal nearby enemies
if (self.healCooldown > 0) {
self.healCooldown--;
}
if (self.healCooldown <= 0) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy !== self && enemy.health < enemy.maxHealth) {
var dx2 = enemy.x - self.x;
var dy2 = enemy.y - self.y;
var healDistance = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (healDistance <= self.healRadius) {
enemy.health = Math.min(enemy.maxHealth, enemy.health + 1);
LK.effects.flashObject(enemy, 0x88FF00, 200);
}
}
}
self.healCooldown = self.healInterval;
}
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 35);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var HealthBar = Container.expand(function () {
var self = Container.call(this);
// Create background bar (red)
var backgroundBar = LK.getAsset('bgTile', {
anchorX: 0,
anchorY: 0.5
});
backgroundBar.tint = 0x660000;
backgroundBar.scaleX = 2;
backgroundBar.scaleY = 0.3;
self.addChild(backgroundBar);
// Create health bar (green)
var healthBar = LK.getAsset('bgTile', {
anchorX: 0,
anchorY: 0.5
});
healthBar.tint = 0x00AA00;
healthBar.scaleX = 2;
healthBar.scaleY = 0.3;
self.addChild(healthBar);
self.backgroundBar = backgroundBar;
self.healthBar = healthBar;
self.maxWidth = backgroundBar.width * backgroundBar.scaleX;
self.updateHealth = function (currentHealth, maxHealth) {
var healthPercentage = Math.max(0, currentHealth / maxHealth);
self.healthBar.scaleX = 2 * healthPercentage;
// Change color based on health percentage
if (healthPercentage > 0.6) {
self.healthBar.tint = 0x00AA00; // Green
} else if (healthPercentage > 0.3) {
self.healthBar.tint = 0xFFAA00; // Orange
} else {
self.healthBar.tint = 0xFF0000; // Red
}
};
return self;
});
var HealthPack = Container.expand(function () {
var self = Container.call(this);
var healthGraphics = self.attachAsset('reward', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it red for health
healthGraphics.tint = 0xFF4444;
healthGraphics.scaleX = 0.8;
healthGraphics.scaleY = 0.8;
self.bobOffset = Math.random() * Math.PI * 2;
self.baseY = 0;
self.lifetime = 420; // 7 seconds at 60fps
self.healAmount = 25; // Restore 25 health
self.update = function () {
// Gentle bobbing animation
self.y = self.baseY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5;
// Gentle pulsing effect
var pulseScale = 0.8 + Math.sin(LK.ticks * 0.2) * 0.1;
healthGraphics.scaleX = pulseScale;
healthGraphics.scaleY = pulseScale;
// Decrease lifetime
self.lifetime--;
// Start fading when close to expiring
if (self.lifetime <= 120) {
var alpha = self.lifetime / 120;
healthGraphics.alpha = alpha;
}
// Remove when lifetime expires
if (self.lifetime <= 0) {
self.destroy();
for (var i = healthPacks.length - 1; i >= 0; i--) {
if (healthPacks[i] === self) {
healthPacks.splice(i, 1);
break;
}
}
}
};
self.down = function (x, y, obj) {
// Heal castle
var healAmount = Math.min(self.healAmount, castle.maxHealth - castle.health);
if (healAmount > 0) {
castle.health += healAmount;
LK.getSound('award').play();
LK.effects.flashObject(castle, 0x44FF44, 500);
// Show heal amount
var healText = new Text2('+' + healAmount + ' HEALTH', {
size: 40,
fill: 0x44FF44
});
healText.anchor.set(0.5, 0.5);
healText.x = castle.x;
healText.y = castle.y - 60;
game.addChild(healText);
// Animate heal text
tween(healText, {
y: healText.y - 50,
alpha: 0
}, {
duration: 1500,
onFinish: function onFinish() {
healText.destroy();
}
});
}
self.destroy();
for (var i = healthPacks.length - 1; i >= 0; i--) {
if (healthPacks[i] === self) {
healthPacks.splice(i, 1);
break;
}
}
};
return self;
});
var RegenEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('healerEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGraphics.tint = 0x32CD32; // Lime green for regen enemy
self.health = 6;
self.maxHealth = 6;
self.speed = 0.9;
self.damage = 14;
self.regenRate = 1;
self.regenTimer = 0;
self.regenInterval = 120; // Regenerate every 2 seconds
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistanceSquared = Infinity;
// Create health bar for this enemy
self.healthBar = new EnemyHealthBar();
self.healthBar.y = -50; // Position above enemy
enemyHealthBars.push(self.healthBar);
self.addChild(self.healthBar);
self.update = function () {
// Regeneration logic
self.regenTimer++;
if (self.regenTimer >= self.regenInterval && self.health < self.maxHealth) {
self.health = Math.min(self.maxHealth, self.health + self.regenRate);
self.regenTimer = 0;
// Visual feedback for regeneration
LK.effects.flashObject(self, 0x32CD32, 300);
if (self.healthBar) {
self.healthBar.updateHealth(self.health, self.maxHealth);
}
}
// Move toward castle
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared > 3600) {
var distance = Math.sqrt(distanceSquared);
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Check if reached castle
if (self.lastCastleDistanceSquared > 3600 && distanceSquared <= 3600) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistanceSquared = distanceSquared;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
// Update health bar
if (self.healthBar) {
self.healthBar.updateHealth(self.health, self.maxHealth);
}
if (self.health <= 0) {
LK.setScore(LK.getScore() + 30);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
// Drop guaranteed scrap metal
var scrap = new ScrapMetalDrop();
scrap.x = self.x;
scrap.y = self.y;
scrap.baseY = scrap.y;
scrap.value = 8; // Higher value for tougher enemy
scrapMetalDrops.push(scrap);
game.addChild(scrap);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var Reward = Container.expand(function () {
var self = Container.call(this);
var rewardGraphics = self.attachAsset('reward', {
anchorX: 0.5,
anchorY: 0.5
});
self.bobOffset = Math.random() * Math.PI * 2;
self.baseY = 0;
self.lifetime = 420; // 7 seconds at 60fps
self.update = function () {
// Gentle bobbing animation
self.y = self.baseY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5;
// Decrease lifetime
self.lifetime--;
// Start fading when close to expiring
if (self.lifetime <= 120) {
var alpha = self.lifetime / 120;
rewardGraphics.alpha = alpha;
// Add flashing animation when close to expiring
var flashInterval = Math.max(5, Math.floor(self.lifetime / 20));
if (LK.ticks % flashInterval === 0) {
tween(rewardGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: flashInterval * 8,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(rewardGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: flashInterval * 8,
easing: tween.easeIn
});
}
});
}
}
// Remove when lifetime expires
if (self.lifetime <= 0) {
tween(self, {
scaleX: 0,
scaleY: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
for (var i = rewards.length - 1; i >= 0; i--) {
if (rewards[i] === self) {
rewards.splice(i, 1);
break;
}
}
}
});
}
};
self.down = function (x, y, obj) {
// Define maximum bullet limit to prevent excessive stacking
var maxBullets = 8;
if (castle.bulletsPerShot < maxBullets) {
// Add 1 bullet per reward (consistent increment)
castle.bulletsPerShot++;
bulletsText.setText('Bullets: ' + castle.bulletsPerShot);
LK.getSound('award').play();
LK.effects.flashObject(castle, 0x00FF00, 500);
// Visual feedback for bullet upgrade with scaling effect
tween(castle, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(castle, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
} else {
// At maximum bullets - give score bonus instead
LK.setScore(LK.getScore() + 100);
scoreText.setText(LK.getScore());
bulletsText.setText('Bullets: ' + castle.bulletsPerShot + ' (MAX)');
LK.getSound('award').play();
LK.effects.flashObject(castle, 0xFFD700, 500); // Gold flash for max bonus
// Brief text notification
var maxText = new Text2('+100 SCORE!', {
size: 40,
fill: 0xFFD700
});
maxText.anchor.set(0.5, 0.5);
maxText.x = castle.x;
maxText.y = castle.y - 60;
game.addChild(maxText);
// Animate and remove notification
tween(maxText, {
y: maxText.y - 50,
alpha: 0
}, {
duration: 1500,
onFinish: function onFinish() {
maxText.destroy();
}
});
}
self.destroy();
for (var i = rewards.length - 1; i >= 0; i--) {
if (rewards[i] === self) {
rewards.splice(i, 1);
break;
}
}
};
return self;
});
var ScrapCollectionIndicator = Container.expand(function () {
var self = Container.call(this);
// Create collection range visualization
var rangeCircle = LK.getAsset('bgCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeCircle.tint = 0xC0C0C0;
rangeCircle.alpha = 0.1;
rangeCircle.scaleX = 0.6; // 180px radius visualization
rangeCircle.scaleY = 0.6;
self.addChild(rangeCircle);
self.rangeCircle = rangeCircle;
self.pulseDirection = 1;
self.update = function () {
// Gentle pulsing effect to show collection range
var pulseSpeed = 0.02;
self.alpha += self.pulseDirection * pulseSpeed;
if (self.alpha >= 0.3) {
self.pulseDirection = -1;
} else if (self.alpha <= 0.1) {
self.pulseDirection = 1;
}
// Only show when scrap metal is nearby
var nearbyScrap = false;
for (var i = 0; i < scrapMetalDrops.length; i++) {
var scrap = scrapMetalDrops[i];
var dx = scrap.x - castle.x;
var dy = scrap.y - castle.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 250) {
// Show range when scrap is within extended range
nearbyScrap = true;
break;
}
}
self.visible = nearbyScrap;
};
return self;
});
var ScrapMetalDrop = Container.expand(function () {
var self = Container.call(this);
var scrapGraphics = self.attachAsset('reward', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it metallic silver for scrap metal
scrapGraphics.tint = 0xC0C0C0;
scrapGraphics.scaleX = 0.6;
scrapGraphics.scaleY = 0.6;
self.value = 5; // Base scrap metal value
self.bobOffset = Math.random() * Math.PI * 2;
self.baseY = 0;
self.lifetime = 600; // 10 seconds at 60fps
self.magnetRange = 180; // Increased range for easier collection
self.collectDistance = 80; // Distance at which scrap is automatically collected
self.update = function () {
// Gentle bobbing animation
self.y = self.baseY + Math.sin(LK.ticks * 0.15 + self.bobOffset) * 3;
// Decrease lifetime
self.lifetime--;
// Distance calculation
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Auto-collect when very close
if (distance <= self.collectDistance) {
currentScrapMetal += self.value;
scrapMetalText.setText('Scrap Metal: ' + (storage.scrapMetal + currentScrapMetal));
LK.getSound('award').play();
// Visual collection effect
LK.effects.flashObject(castle, 0xC0C0C0, 300);
// Show collection amount
var collectText = new Text2('+' + self.value + ' SCRAP', {
size: 35,
fill: 0xC0C0C0
});
collectText.anchor.set(0.5, 0.5);
collectText.x = self.x;
collectText.y = self.y - 30;
game.addChild(collectText);
// Animate collection text towards castle
tween(collectText, {
x: castle.x,
y: castle.y - 50,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
collectText.destroy();
}
});
self.destroy();
for (var i = scrapMetalDrops.length - 1; i >= 0; i--) {
if (scrapMetalDrops[i] === self) {
scrapMetalDrops.splice(i, 1);
break;
}
}
return;
}
// Magnet effect when castle is nearby
if (distance <= self.magnetRange) {
var magnetSpeed = Math.min(5, distance * 0.05); // Speed increases as it gets closer
var moveX = dx / distance * magnetSpeed;
var moveY = dy / distance * magnetSpeed;
self.x += moveX;
self.y += moveY;
// Add sparkle effect during magnet pull
if (LK.ticks % 10 === 0) {
LK.effects.flashObject(self, 0xFFFFFF, 100);
}
}
// Start fading when close to expiring
if (self.lifetime <= 120) {
var alpha = self.lifetime / 120;
scrapGraphics.alpha = alpha;
}
// Remove when lifetime expires
if (self.lifetime <= 0) {
self.destroy();
for (var i = scrapMetalDrops.length - 1; i >= 0; i--) {
if (scrapMetalDrops[i] === self) {
scrapMetalDrops.splice(i, 1);
break;
}
}
}
};
return self;
});
var Shield = Container.expand(function () {
var self = Container.call(this);
var shieldGraphics = self.attachAsset('castle', {
anchorX: 0.5,
anchorY: 0.5
});
shieldGraphics.tint = 0x0088FF;
shieldGraphics.alpha = 0.3;
self.scaleX = 1.5;
self.scaleY = 1.5;
self.duration = 300; // 5 seconds at 60fps
self.pulseDirection = 1;
self.update = function () {
// Pulsing effect
var pulseSpeed = 0.1;
self.alpha += self.pulseDirection * pulseSpeed;
if (self.alpha >= 0.6) {
self.pulseDirection = -1;
} else if (self.alpha <= 0.2) {
self.pulseDirection = 1;
}
// Decrease duration
self.duration--;
if (self.duration <= 0) {
castle.shield = null;
self.destroy();
}
};
return self;
});
var ShieldEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('shieldEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 6;
self.maxHealth = 6;
self.speed = 0.7;
self.damage = 15;
self.shieldRadius = 80;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 40);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var Shockwave = Container.expand(function () {
var self = Container.call(this);
var shockwaveGraphics = self.attachAsset('castle', {
anchorX: 0.5,
anchorY: 0.5
});
shockwaveGraphics.tint = 0xFF4400;
shockwaveGraphics.alpha = 0.8;
self.maxRadius = 400;
self.currentRadius = 50;
self.damage = 5;
self.speed = 15;
self.hasDealtDamage = [];
self.update = function () {
// Expand shockwave
self.currentRadius += self.speed;
var scale = self.currentRadius / 100;
self.scaleX = scale;
self.scaleY = scale;
// Fade out as it expands
self.alpha = Math.max(0, 0.8 - self.currentRadius / self.maxRadius * 0.8);
// Damage enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if enemy is within shockwave range and hasn't been damaged yet
if (distance <= self.currentRadius && self.hasDealtDamage.indexOf(enemy) === -1) {
enemy.takeDamage(self.damage);
self.hasDealtDamage.push(enemy);
LK.effects.flashObject(enemy, 0xFF4400, 200);
}
}
// Remove when fully expanded
if (self.currentRadius >= self.maxRadius) {
self.destroy();
for (var j = shockwaves.length - 1; j >= 0; j--) {
if (shockwaves[j] === self) {
shockwaves.splice(j, 1);
break;
}
}
}
};
return self;
});
var SniperEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('sniperEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 2;
self.maxHealth = 2;
self.speed = 1.5;
self.damage = 15;
self.shootCooldown = 0;
self.shootInterval = 120; // Shoots every 2 seconds
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move toward castle until in range (300 pixels)
if (distance > 300) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
} else {
// In range, shoot at castle
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (self.shootCooldown <= 0) {
castle.takeDamage(self.damage);
LK.effects.flashObject(self, 0x00FFFF, 300);
self.shootCooldown = self.shootInterval;
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = distance;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 20);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var SplitterEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('splitterEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 5;
self.maxHealth = 5;
self.speed = 0.9;
self.damage = 18;
self.splitCount = 2;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.split = function () {
for (var i = 0; i < self.splitCount; i++) {
var smallEnemy = new FastEnemy();
var angle = Math.PI * 2 / self.splitCount * i;
smallEnemy.x = self.x + Math.cos(angle) * 30;
smallEnemy.y = self.y + Math.sin(angle) * 30;
smallEnemy.lastX = smallEnemy.x;
smallEnemy.lastY = smallEnemy.y;
var dx = castle.x - smallEnemy.x;
var dy = castle.y - smallEnemy.y;
smallEnemy.lastCastleDistance = Math.sqrt(dx * dx + dy * dy);
enemies.push(smallEnemy);
game.addChild(smallEnemy);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 50);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.split();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var TankEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('tankEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 8;
self.maxHealth = 8;
self.speed = 0.5;
self.damage = 20;
self.armor = 0.5; // Takes 50% less damage
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
// Move toward castle slowly
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Check if reached castle
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
var reducedDamage = damage * (1 - self.armor);
self.health -= reducedDamage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 25);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var TeleporterEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('teleporterEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 2;
self.maxHealth = 2;
self.speed = 1;
self.damage = 12;
self.teleportCooldown = 0;
self.teleportInterval = 240; // Teleports every 4 seconds
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Teleport randomly
if (self.teleportCooldown > 0) {
self.teleportCooldown--;
}
if (self.teleportCooldown <= 0) {
var angle = Math.random() * Math.PI * 2;
var teleportDistance = 200 + Math.random() * 200;
self.x = castle.x + Math.cos(angle) * teleportDistance;
self.y = castle.y + Math.sin(angle) * teleportDistance;
// Keep within bounds
self.x = Math.max(50, Math.min(1998, self.x));
self.y = Math.max(50, Math.min(2682, self.y));
LK.effects.flashObject(self, 0xFF00FF, 400);
self.teleportCooldown = self.teleportInterval;
}
var currentDistance = Math.sqrt((castle.x - self.x) * (castle.x - self.x) + (castle.y - self.y) * (castle.y - self.y));
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 25);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var ThreatIndicator = Container.expand(function () {
var self = Container.call(this);
var indicator = LK.getAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
indicator.tint = 0xFF4400;
indicator.scaleX = 0.5;
indicator.scaleY = 0.5;
indicator.alpha = 0.8;
self.addChild(indicator);
self.indicator = indicator;
self.pulseDirection = 1;
self.update = function () {
// Pulsing animation
var pulseSpeed = 0.05;
self.alpha += self.pulseDirection * pulseSpeed;
if (self.alpha >= 1) {
self.pulseDirection = -1;
} else if (self.alpha <= 0.5) {
self.pulseDirection = 1;
}
};
return self;
});
var UpgradeMenu = Container.expand(function () {
var self = Container.call(this);
// Create semi-transparent background
var background = LK.getAsset('bgTile', {
anchorX: 0,
anchorY: 0
});
background.tint = 0x000000;
background.alpha = 0.8;
background.scaleX = 10.24; // Cover full screen width
background.scaleY = 13.66; // Cover full screen height
self.addChild(background);
// Menu title
var titleText = new Text2('CASTLE UPGRADES', {
size: 80,
fill: 0xFFD700
});
titleText.anchor.set(0.5, 0);
titleText.x = 1024;
titleText.y = 200;
self.addChild(titleText);
// Close button
var closeButton = new Text2('β CLOSE', {
size: 60,
fill: 0xFF4444
});
closeButton.anchor.set(1, 0);
closeButton.x = 1900;
closeButton.y = 250;
self.addChild(closeButton);
// Upgrade buttons array
self.upgradeButtons = [];
// Create upgrade buttons for each type
var upgradeTypes = [{
type: 'fireRate',
name: 'FIRE RATE',
color: 0xFF8800,
description: '+15% faster shooting'
}, {
type: 'damage',
name: 'DAMAGE',
color: 0xFF0000,
description: '+20% bullet damage'
}, {
type: 'health',
name: 'HEALTH',
color: 0x00FF00,
description: '+20 max health'
}, {
type: 'bulletCount',
name: 'BULLETS',
color: 0x00AAFF,
description: '+1 bullet per shot'
}, {
type: 'movementSpeed',
name: 'MOVEMENT',
color: 0xFFFF00,
description: '+25% movement speed'
}, {
type: 'shield',
name: 'SHIELD',
color: 0x0088FF,
description: '10% shorter cooldown'
}, {
type: 'shockwave',
name: 'SHOCKWAVE',
color: 0xFF4400,
description: '10% shorter cooldown'
}];
for (var i = 0; i < upgradeTypes.length; i++) {
var upgrade = upgradeTypes[i];
var buttonContainer = new Container();
// Button background
var buttonBg = LK.getAsset('bgTile', {
anchorX: 0,
anchorY: 0
});
buttonBg.tint = upgrade.color;
buttonBg.alpha = 0.7;
buttonBg.scaleX = 4;
buttonBg.scaleY = 0.8;
buttonContainer.addChild(buttonBg);
// Button text
var buttonText = new Text2('', {
size: 45,
fill: 0xFFFFFF
});
buttonText.anchor.set(0, 0.5);
buttonText.x = 20;
buttonText.y = buttonBg.height * buttonBg.scaleY / 2;
buttonContainer.addChild(buttonText);
// Position button
var col = i % 2;
var row = Math.floor(i / 2);
buttonContainer.x = 300 + col * 800;
buttonContainer.y = 400 + row * 200;
// Store references
buttonContainer.upgradeType = upgrade.type;
buttonContainer.buttonText = buttonText;
buttonContainer.buttonBg = buttonBg;
buttonContainer.originalColor = upgrade.color;
buttonContainer.upgradeName = upgrade.name;
buttonContainer.description = upgrade.description;
self.upgradeButtons.push(buttonContainer);
self.addChild(buttonContainer);
}
// Current scrap display
var scrapDisplay = new Text2('', {
size: 55,
fill: 0xC0C0C0
});
scrapDisplay.anchor.set(0.5, 0);
scrapDisplay.x = 1024;
scrapDisplay.y = 320;
self.addChild(scrapDisplay);
self.scrapDisplay = scrapDisplay;
// Update button states and text
self.updateButtons = function () {
var totalScrap = storage.scrapMetal + currentScrapMetal;
self.scrapDisplay.setText('Available Scrap Metal: ' + totalScrap);
for (var i = 0; i < self.upgradeButtons.length; i++) {
var button = self.upgradeButtons[i];
var currentLevel = storage[button.upgradeType + 'Level'] || 1;
var cost = getUpgradeCost(button.upgradeType, currentLevel);
var canAfford = canAffordUpgrade(button.upgradeType, currentLevel);
var isMaxLevel = currentLevel >= storage.maxUpgradeTier;
if (isMaxLevel) {
button.buttonText.setText(button.upgradeName + ' - MAX LEVEL');
button.buttonBg.tint = 0x666666;
button.buttonBg.alpha = 0.5;
} else {
button.buttonText.setText(button.upgradeName + ' T' + currentLevel + 'βT' + (currentLevel + 1) + '\nCost: ' + cost + ' | ' + button.description);
button.buttonBg.tint = canAfford ? button.originalColor : 0x666666;
button.buttonBg.alpha = canAfford ? 0.7 : 0.3;
}
}
};
// Handle button clicks
self.down = function (x, y, obj) {
// Check close button
if (x >= 1700 && x <= 1900 && y >= 250 && y <= 320) {
upgradeMenuVisible = false;
self.visible = false;
return;
}
// Check upgrade buttons
for (var i = 0; i < self.upgradeButtons.length; i++) {
var button = self.upgradeButtons[i];
var buttonX = button.x;
var buttonY = button.y;
var buttonWidth = button.buttonBg.width * button.buttonBg.scaleX;
var buttonHeight = button.buttonBg.height * button.buttonBg.scaleY;
if (x >= buttonX && x <= buttonX + buttonWidth && y >= buttonY && y <= buttonY + buttonHeight) {
var currentLevel = storage[button.upgradeType + 'Level'] || 1;
if (currentLevel < storage.maxUpgradeTier && canAffordUpgrade(button.upgradeType, currentLevel)) {
if (purchaseUpgrade(button.upgradeType)) {
// Apply upgrade immediately
self.applyUpgrade(button.upgradeType);
// Update button states
self.updateButtons();
// Visual feedback
LK.effects.flashObject(button, 0x00FF00, 500);
LK.getSound('award').play();
}
}
break;
}
}
};
// Apply upgrades to castle immediately
self.applyUpgrade = function (upgradeType) {
var newLevel = storage[upgradeType + 'Level'];
switch (upgradeType) {
case 'fireRate':
var fireRateBonus = getUpgradeBonus('fireRate', newLevel);
castle.shootInterval = Math.max(20, Math.floor(60 * (1 - fireRateBonus)));
break;
case 'health':
var healthBonus = getUpgradeBonus('health', newLevel);
var newMaxHealth = 100 + healthBonus;
var healthIncrease = newMaxHealth - castle.maxHealth;
castle.maxHealth = newMaxHealth;
castle.health += healthIncrease; // Add the health increase
castleHealthBar.updateHealth(castle.health, castle.maxHealth);
break;
case 'damage':
var damageBonus = getUpgradeBonus('damage', newLevel);
castle.upgradedBulletDamage = 1.01 * (1 + damageBonus);
break;
case 'bulletCount':
var bulletCountBonus = getUpgradeBonus('bulletCount', newLevel);
castle.bulletsPerShot = 1 + bulletCountBonus;
bulletsText.setText('Bullets: ' + castle.bulletsPerShot);
break;
case 'movementSpeed':
var movementSpeedBonus = getUpgradeBonus('movementSpeed', newLevel);
castleMovementSpeed = 400 * (1 + movementSpeedBonus);
break;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F2F
});
/****
* Game Code
****/
// Game variables
var castle;
var enemies = [];
var bullets = [];
var rewards = [];
var bombRewards = [];
var bombs = [];
var scrapMetalDrops = [];
var shockwaves = [];
var healthPacks = [];
var waveTimer = 0;
var enemySpawnTimer = 0;
var rewardSpawnTimer = 0;
var waveNumber = 1;
var isMiniBossWave = false;
var miniBossSpawned = false;
var survivalTime = 0; // Track total survival time
var lastMilestoneWave = 0; // Track last milestone for rewards
// Initialize persistent storage with defaults
// Current game scrap metal (resets each game)
var currentScrapMetal = 0;
// Upgrade tier system functions
function getUpgradeCost(upgradeType, currentLevel) {
var baseCosts = {
fireRate: storage.fireRateBaseCost,
movementSpeed: storage.movementSpeedBaseCost,
health: storage.healthBaseCost,
damage: storage.damageBaseCost,
bulletCount: storage.bulletCountBaseCost,
shield: storage.shieldBaseCost,
shockwave: storage.shockwaveBaseCost
};
var baseCost = baseCosts[upgradeType] || 100;
// Progressive cost increase: base * (1.5^currentLevel)
return Math.floor(baseCost * Math.pow(1.5, currentLevel - 1));
}
function canAffordUpgrade(upgradeType, currentLevel) {
if (currentLevel >= storage.maxUpgradeTier) return false;
var cost = getUpgradeCost(upgradeType, currentLevel);
return storage.scrapMetal + currentScrapMetal >= cost;
}
function purchaseUpgrade(upgradeType) {
var currentLevel = storage[upgradeType + 'Level'] || 1;
if (!canAffordUpgrade(upgradeType, currentLevel)) return false;
var cost = getUpgradeCost(upgradeType, currentLevel);
var totalScrap = storage.scrapMetal + currentScrapMetal;
// Deduct cost and update storage
if (currentScrapMetal >= cost) {
currentScrapMetal -= cost;
} else {
var remainingCost = cost - currentScrapMetal;
currentScrapMetal = 0;
storage.scrapMetal -= remainingCost;
}
// Increase upgrade level
storage[upgradeType + 'Level'] = currentLevel + 1;
// Update scrap metal display
scrapMetalText.setText('Scrap Metal: ' + (storage.scrapMetal + currentScrapMetal));
return true;
}
function getUpgradeBonus(upgradeType, level) {
var bonuses = {
fireRate: (level - 1) * 0.15,
// 15% faster per level
movementSpeed: (level - 1) * 0.25,
// 25% faster per level
health: (level - 1) * 20,
// +20 health per level
damage: (level - 1) * 0.2,
// +20% damage per level
bulletCount: level - 1,
// +1 bullet per level
shield: (level - 1) * 0.1,
// 10% cooldown reduction per level
shockwave: (level - 1) * 0.1 // 10% cooldown reduction per level
};
return bonuses[upgradeType] || 0;
}
// Create background pattern
function createBackground() {
var bgContainer = new Container();
// Create simpler grid pattern with fewer random calculations
for (var x = 0; x <= 2048; x += 240) {
for (var y = 0; y <= 2732; y += 240) {
// Main background tiles with minimal randomization
var tile = LK.getAsset('bgTile', {
anchorX: 0.5,
anchorY: 0.5
});
tile.x = x;
tile.y = y;
bgContainer.addChild(tile);
// Reduce accent frequency for better performance
if ((x + y) % 960 === 0) {
// Every 4th tile instead of random
var accent = LK.getAsset('bgAccent', {
anchorX: 0.5,
anchorY: 0.5
});
accent.x = x;
accent.y = y;
accent.alpha = 0.5;
bgContainer.addChild(accent);
}
}
}
// Set background container to lowest z-index
game.addChildAt(bgContainer, 0);
}
// Initialize background
createBackground();
// UI elements
var scoreText = new Text2('0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var bulletsText = new Text2('Bullets: 1', {
size: 50,
fill: 0x00FF00
});
bulletsText.anchor.set(0, 0);
bulletsText.x = 50;
bulletsText.y = 50;
LK.gui.topLeft.addChild(bulletsText);
var healthText = new Text2('Health: 100', {
size: 50,
fill: 0xFF0000
});
healthText.anchor.set(1, 0);
LK.gui.topRight.addChild(healthText);
var scrapMetalText = new Text2('Scrap Metal: 0', {
size: 45,
fill: 0xC0C0C0
});
scrapMetalText.anchor.set(0, 0);
scrapMetalText.x = 50;
scrapMetalText.y = 120;
LK.gui.topLeft.addChild(scrapMetalText);
// Wave counter
var waveText = new Text2('Wave: 1', {
size: 55,
fill: 0xFFD700
});
waveText.anchor.set(1, 0);
waveText.x = -50;
waveText.y = 50;
LK.gui.topRight.addChild(waveText);
// Survival time counter
var survivalTimeText = new Text2('Time: 0:00', {
size: 45,
fill: 0x88DDFF
});
survivalTimeText.anchor.set(1, 0);
survivalTimeText.x = -50;
survivalTimeText.y = 120;
LK.gui.topRight.addChild(survivalTimeText);
// High score display
var highScoreText = new Text2('Best: ' + storage.highScore + ' (Wave ' + storage.bestWave + ')', {
size: 40,
fill: 0xFFD700
});
highScoreText.anchor.set(0.5, 0);
highScoreText.x = 0;
highScoreText.y = 150;
LK.gui.top.addChild(highScoreText);
// Stats display
var statsText = new Text2('Games: ' + storage.totalGamesPlayed + ' | Enemies: ' + storage.totalEnemiesDefeated, {
size: 35,
fill: 0xCCCCCC
});
statsText.anchor.set(0.5, 0);
statsText.x = 0;
statsText.y = 200;
LK.gui.top.addChild(statsText);
// Castle health bar
var castleHealthBar = new HealthBar();
castleHealthBar.x = 50;
castleHealthBar.y = 300;
LK.gui.topLeft.addChild(castleHealthBar);
// Arrays for UI elements
var enemyHealthBars = [];
var threatIndicators = [];
// Power-up status display
var powerUpText = new Text2('', {
size: 40,
fill: 0x00FF88
});
powerUpText.anchor.set(0.5, 0);
powerUpText.x = 0;
powerUpText.y = 100;
LK.gui.top.addChild(powerUpText);
// Track active power-ups
var activePowerUps = {
shield: 0,
extraBullets: 0
};
// Ability UI buttons
var shieldButton = new Text2('SHIELD', {
size: 45,
fill: 0x0088FF
});
shieldButton.anchor.set(0, 0);
shieldButton.x = 50;
shieldButton.y = 180;
LK.gui.topLeft.addChild(shieldButton);
var shockwaveButton = new Text2('SHOCKWAVE', {
size: 45,
fill: 0xFF4400
});
shockwaveButton.anchor.set(0, 0);
shockwaveButton.x = 50;
shockwaveButton.y = 240;
LK.gui.topLeft.addChild(shockwaveButton);
// Upgrade menu button
var upgradeButton = new Text2('β‘ UPGRADES', {
size: 50,
fill: 0xFFD700
});
upgradeButton.anchor.set(0, 0);
upgradeButton.x = 50;
upgradeButton.y = 300;
LK.gui.topLeft.addChild(upgradeButton);
// Initialize castle
castle = game.addChild(new Castle());
castle.x = 1024;
castle.y = 1366;
// Add scrap collection range indicator to castle
var scrapIndicator = new ScrapCollectionIndicator();
scrapIndicator.x = castle.x;
scrapIndicator.y = castle.y;
game.addChild(scrapIndicator);
// Apply permanent upgrades from storage
var fireRateBonus = getUpgradeBonus('fireRate', storage.fireRateLevel);
castle.shootInterval = Math.max(20, Math.floor(castle.shootInterval * (1 - fireRateBonus)));
var healthBonus = getUpgradeBonus('health', storage.healthLevel);
castle.maxHealth += healthBonus;
castle.health = castle.maxHealth; // Start with full health
var damageBonus = getUpgradeBonus('damage', storage.damageLevel);
var baseBulletDamage = 1.01;
// Store upgraded damage for bullets
castle.upgradedBulletDamage = baseBulletDamage * (1 + damageBonus);
// Apply permanent bullet count bonus (separate from temporary power-ups)
var bulletCountBonus = getUpgradeBonus('bulletCount', storage.bulletCountLevel);
castle.bulletsPerShot += bulletCountBonus;
// Update bullets text with correct value after applying bonus
bulletsText.setText('Bullets: ' + castle.bulletsPerShot);
// Initialize castle health bar
castleHealthBar.updateHealth(castle.health, castle.maxHealth);
// Create upgrade menu
var upgradeMenu = new UpgradeMenu();
upgradeMenu.visible = false;
game.addChild(upgradeMenu);
var upgradeMenuVisible = false;
// Movement speed will be applied in the movement system
var movementSpeedBonus = getUpgradeBonus('movementSpeed', storage.movementSpeedLevel);
var baseCastleSpeed = 400;
castleMovementSpeed = baseCastleSpeed * (1 + movementSpeedBonus);
// Start background music
LK.playMusic('bgmusic');
// Update game counter at start
storage.totalGamesPlayed++;
// Spawn functions
function spawnEnemy() {
var enemy;
// Mini-boss wave logic - every 5th wave
if (isMiniBossWave) {
if (!miniBossSpawned) {
// Always spawn a boss enemy as the first enemy of mini-boss wave
enemy = new BossEnemy();
miniBossSpawned = true;
} else {
// Higher chance for tank/shield enemies in mini-boss waves
var enemyType = Math.random();
if (enemyType < 0.4) {
enemy = new TankEnemy();
} else if (enemyType < 0.7) {
enemy = new ShieldEnemy();
} else if (enemyType < 0.85) {
enemy = new BossEnemy();
} else {
enemy = new BomberEnemy();
}
}
} else {
// Normal wave enemy distribution with adaptive scaling
var enemyType = Math.random();
// Increase chance of advanced enemies in later waves
var advancedEnemyChance = Math.min(0.4, waveNumber * 0.02); // Up to 40% chance by wave 20
if (enemyType < 0.25) {
enemy = new Enemy();
} else if (enemyType < 0.35) {
enemy = new FastEnemy();
} else if (enemyType < 0.45) {
enemy = new TankEnemy();
} else if (enemyType < 0.55) {
enemy = new SniperEnemy();
} else if (enemyType < 0.63) {
enemy = new BomberEnemy();
} else if (enemyType < 0.7) {
enemy = new HealerEnemy();
} else if (enemyType < 0.76) {
enemy = new ShieldEnemy();
} else if (enemyType < 0.82) {
enemy = new TeleporterEnemy();
} else if (enemyType < 0.87) {
enemy = new SplitterEnemy();
} else if (enemyType < 0.87 + advancedEnemyChance * 0.3) {
// Adaptive enemies become more common in later waves
enemy = new AdaptiveEnemy();
} else if (enemyType < 0.87 + advancedEnemyChance * 0.6) {
// Regenerating enemies for sustained threat
enemy = new RegenEnemy();
} else if (enemyType < 0.87 + advancedEnemyChance * 0.8) {
// Elite enemies for high-value targets
enemy = new EliteEnemy();
} else if (enemyType < 0.95) {
enemy = new BossEnemy();
} else {
// Super rare - spawn multiple elite enemies for extreme challenge
enemy = new EliteEnemy();
var secondElite = new EliteEnemy();
secondElite.x = -40; // Will be positioned properly below
secondElite.y = Math.random() * 2732;
secondElite.lastX = secondElite.x;
secondElite.lastY = secondElite.y;
var dx2 = castle.x - secondElite.x;
var dy2 = castle.y - secondElite.y;
secondElite.lastCastleDistanceSquared = dx2 * dx2 + dy2 * dy2;
// Apply scaling to second elite
var healthMultiplier = 1 + (waveNumber - 1) * 0.05;
var speedMultiplier = 1 + (waveNumber - 1) * 0.02;
var damageMultiplier = 1 + (waveNumber - 1) * 0.05;
secondElite.health = Math.ceil(secondElite.health * healthMultiplier);
secondElite.maxHealth = secondElite.health;
secondElite.speed = secondElite.speed * speedMultiplier;
secondElite.damage = Math.ceil(secondElite.damage * damageMultiplier);
if (isMiniBossWave) {
secondElite.health = Math.ceil(secondElite.health * 1.25);
secondElite.maxHealth = secondElite.health;
secondElite.damage = Math.ceil(secondElite.damage * 1.15);
}
enemies.push(secondElite);
game.addChild(secondElite);
}
}
// Spawn from random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -20;
break;
case 1:
// Right
enemy.x = 2068;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2752;
break;
case 3:
// Left
enemy.x = -20;
enemy.y = Math.random() * 2732;
break;
}
// Quantified enemy scaling: +5% health and +2% speed per wave
var healthMultiplier = 1 + (waveNumber - 1) * 0.05;
var speedMultiplier = 1 + (waveNumber - 1) * 0.02;
var damageMultiplier = 1 + (waveNumber - 1) * 0.05;
// Apply scaling with proper rounding
enemy.health = Math.ceil(enemy.health * healthMultiplier);
enemy.maxHealth = enemy.health;
enemy.speed = enemy.speed * speedMultiplier;
enemy.damage = Math.ceil(enemy.damage * damageMultiplier);
// Extra scaling for mini-boss waves
if (isMiniBossWave) {
enemy.health = Math.ceil(enemy.health * 1.25); // 25% more health
enemy.maxHealth = enemy.health;
enemy.damage = Math.ceil(enemy.damage * 1.15); // 15% more damage
}
enemy.lastX = enemy.x;
enemy.lastY = enemy.y;
var dx = castle.x - enemy.x;
var dy = castle.y - enemy.y;
enemy.lastCastleDistanceSquared = dx * dx + dy * dy;
enemies.push(enemy);
game.addChild(enemy);
}
function spawnReward() {
var rewardType = Math.random();
var reward;
// Increased chance for health packs when castle health is low
var healthPercentage = castle.health / castle.maxHealth;
var healthPackChance = healthPercentage < 0.3 ? 0.25 : healthPercentage < 0.6 ? 0.15 : 0.1;
// 60% regular reward, 25% bomb reward, 15% health pack (adjusted based on health)
if (rewardType < 0.6) {
reward = new Reward();
rewards.push(reward);
} else if (rewardType < 0.85) {
reward = new BombReward();
bombRewards.push(reward);
} else {
reward = new HealthPack();
healthPacks.push(reward);
}
// Spawn in accessible areas further away from the castle
var angle = Math.random() * Math.PI * 2;
var distance = 400 + Math.random() * 500;
reward.x = castle.x + Math.cos(angle) * distance;
reward.y = castle.y + Math.sin(angle) * distance;
// Keep within bounds
reward.x = Math.max(100, Math.min(1948, reward.x));
reward.y = Math.max(100, Math.min(2632, reward.y));
reward.baseY = reward.y;
game.addChild(reward);
// Add spawn animation with tween
reward.scaleX = 0;
reward.scaleY = 0;
tween(reward, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
// Castle movement system with configurable speed
// Note: castleMovementSpeed is set above after applying movement speed bonus
var targetX = 1024;
var targetY = 1366;
var isMoving = false;
game.down = function (x, y, obj) {
// If upgrade menu is visible, let it handle the input
if (upgradeMenuVisible) {
upgradeMenu.down(x, y, obj);
return;
}
// Check if clicking on ability buttons
var buttonClicked = false;
// Check shield button area (approximate)
if (x >= 50 && x <= 200 && y >= 180 && y <= 220) {
castle.activateShield();
buttonClicked = true;
}
// Check shockwave button area (approximate)
else if (x >= 50 && x <= 250 && y >= 240 && y <= 280) {
castle.activateShockwave();
buttonClicked = true;
}
// Check upgrade button area
else if (x >= 50 && x <= 280 && y >= 300 && y <= 350) {
upgradeMenuVisible = !upgradeMenuVisible;
upgradeMenu.visible = upgradeMenuVisible;
if (upgradeMenuVisible) {
upgradeMenu.updateButtons();
}
buttonClicked = true;
}
// Only move castle if not clicking on ability buttons
if (!buttonClicked) {
// Set new target position
targetX = x;
targetY = y;
// Calculate distance to determine movement duration
var dx = targetX - castle.x;
var dy = targetY - castle.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Calculate duration based on configurable movement speed
var duration = Math.max(150, distance / castleMovementSpeed * 1000); // minimum 150ms for responsiveness
// Stop any existing movement tween
tween.stop(castle, {
x: true,
y: true
});
isMoving = true;
// Add walking animation with slight bobbing effect
var originalY = castle.y;
// Start smooth walking animation with subtle bounce
tween(castle, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
isMoving = false;
// Subtle landing effect
tween(castle, {
scaleX: 1.1,
scaleY: 0.9
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(castle, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeOut
});
}
});
}
});
// Add walking bobbing animation during movement
if (duration > 300) {
var bobDuration = duration / 3;
tween(castle, {
scaleY: 1.05
}, {
duration: bobDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(castle, {
scaleY: 1
}, {
duration: bobDuration,
easing: tween.easeInOut
});
}
});
}
}
};
game.move = function (x, y, obj) {
// Update target while moving (allows for path correction)
if (isMoving) {
targetX = x;
targetY = y;
// Calculate new distance and duration
var dx = targetX - castle.x;
var dy = targetY - castle.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Only update if the new target is significantly different
if (distance > 40) {
var duration = Math.max(150, distance / castleMovementSpeed * 1000);
// Stop current tween and start new one
tween.stop(castle, {
x: true,
y: true
});
tween(castle, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
isMoving = false;
}
});
}
}
};
game.up = function (x, y, obj) {
// Optional: Could add final position adjustment here if needed
};
// Game update loop
game.update = function () {
// Update wave timer
waveTimer++;
// Spawn enemies
enemySpawnTimer++;
var spawnRate = Math.max(30, 120 - waveNumber * 5); // Faster spawning each wave
// Increase spawn rate for mini-boss waves
if (isMiniBossWave) {
spawnRate = Math.max(20, spawnRate * 0.7); // 30% faster spawning in mini-boss waves
}
if (enemySpawnTimer >= spawnRate) {
spawnEnemy();
enemySpawnTimer = 0;
}
// Spawn rewards occasionally - tied to enemy defeats for better balance
rewardSpawnTimer++;
var enemiesDefeated = Math.floor(LK.getScore() / 10); // Approximate enemies defeated based on score
var rewardThreshold = 600 - Math.min(300, enemiesDefeated * 5); // Faster spawning as more enemies defeated
if (rewardSpawnTimer >= rewardThreshold && rewards.length + bombRewards.length < 3) {
// Spawn rate increases with progress, max 3 rewards total
spawnReward();
rewardSpawnTimer = 0;
}
// Advance wave every 30 seconds
if (waveTimer >= 1800) {
// 30 seconds at 60fps
// Wave completion bonus before advancing
var waveBonus = waveNumber * 50; // 50 points per wave number
var scrapBonus = Math.floor(waveNumber / 2) + 1; // 1-2 scrap metal per wave
LK.setScore(LK.getScore() + waveBonus);
currentScrapMetal += scrapBonus;
// Show wave completion bonus
var bonusText = new Text2('WAVE ' + waveNumber + ' COMPLETE!\n+' + waveBonus + ' SCORE +' + scrapBonus + ' SCRAP', {
size: 60,
fill: 0x00FF88
});
bonusText.anchor.set(0.5, 0.5);
bonusText.x = 1024;
bonusText.y = 600;
game.addChild(bonusText);
// Animate wave completion bonus
tween(bonusText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(bonusText, {
alpha: 0,
y: bonusText.y - 100
}, {
duration: 1500,
onFinish: function onFinish() {
bonusText.destroy();
}
});
}
});
waveNumber++;
waveTimer = 0;
miniBossSpawned = false; // Reset mini-boss spawn flag
// Check if this is a mini-boss wave (every 5th wave)
isMiniBossWave = waveNumber % 5 === 0;
if (isMiniBossWave) {
// Special visual effect for mini-boss waves
LK.effects.flashScreen(0xFF4400, 1000); // Orange flash for mini-boss
// Bigger bonus for mini-boss waves
var miniBossBonus = waveNumber * 100;
var miniBossScrapBonus = Math.floor(waveNumber / 3) + 3;
LK.setScore(LK.getScore() + miniBossBonus);
currentScrapMetal += miniBossScrapBonus;
// Show wave announcement
var waveAnnouncementText = new Text2('MINI-BOSS WAVE ' + waveNumber + '\nBONUS: +' + miniBossBonus + ' SCORE +' + miniBossScrapBonus + ' SCRAP', {
size: 70,
fill: 0xFF4400
});
waveAnnouncementText.anchor.set(0.5, 0.5);
waveAnnouncementText.x = 1024;
waveAnnouncementText.y = 800;
game.addChild(waveAnnouncementText);
// Fade out wave text after 3 seconds for mini-boss
tween(waveAnnouncementText, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
waveAnnouncementText.destroy();
}
});
} else if (waveNumber >= 10 && waveNumber % 10 === 0) {
// Show special threat warning every 10 waves
var threatWarningText = new Text2('THREAT LEVEL INCREASED!\nWAVE ' + waveNumber + ' - ADAPTIVE ENEMIES INCOMING', {
size: 65,
fill: 0x8A2BE2
});
threatWarningText.anchor.set(0.5, 0.5);
threatWarningText.x = 1024;
threatWarningText.y = 800;
game.addChild(threatWarningText);
LK.effects.flashScreen(0x8A2BE2, 800);
tween(threatWarningText, {
alpha: 0
}, {
duration: 2500,
onFinish: function onFinish() {
threatWarningText.destroy();
}
});
} else {
// Normal wave flash
LK.effects.flashScreen(0x0066FF, 500);
}
}
// Check bullet-enemy collisions
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
var bulletHit = false;
// Early exit if bullet is off-screen
if (bullet.x < -100 || bullet.x > 2148 || bullet.y < -100 || bullet.y > 2832) {
bullet.destroy();
bullets.splice(b, 1);
continue;
}
for (var e = enemies.length - 1; e >= 0; e--) {
var enemy = enemies[e];
if (bullet.intersects(enemy)) {
enemy.takeDamage(bullet.damage);
bullet.destroy();
bullets.splice(b, 1);
bulletHit = true;
break;
}
}
if (bulletHit) continue;
}
// Check castle-reward collisions for pickup
for (var r = rewards.length - 1; r >= 0; r--) {
var reward = rewards[r];
if (castle.intersects(reward)) {
// Define maximum bullet limit to prevent excessive stacking
var maxBullets = 8;
if (castle.bulletsPerShot < maxBullets) {
// Add 1 bullet per reward (consistent increment)
castle.bulletsPerShot++;
bulletsText.setText('Bullets: ' + castle.bulletsPerShot);
LK.getSound('award').play();
LK.effects.flashObject(castle, 0x00FF00, 500);
// Visual feedback for bullet upgrade with scaling effect
tween(castle, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(castle, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
} else {
// At maximum bullets - give score bonus instead
LK.setScore(LK.getScore() + 100);
scoreText.setText(LK.getScore());
bulletsText.setText('Bullets: ' + castle.bulletsPerShot + ' (MAX)');
LK.getSound('award').play();
LK.effects.flashObject(castle, 0xFFD700, 500); // Gold flash for max bonus
// Brief text notification
var maxText = new Text2('+100 SCORE!', {
size: 40,
fill: 0xFFD700
});
maxText.anchor.set(0.5, 0.5);
maxText.x = castle.x;
maxText.y = castle.y - 60;
game.addChild(maxText);
// Animate and remove notification
tween(maxText, {
y: maxText.y - 50,
alpha: 0
}, {
duration: 1500,
onFinish: function onFinish() {
maxText.destroy();
}
});
}
reward.destroy();
rewards.splice(r, 1);
}
}
// Check castle-bomb reward collisions for pickup
for (var br = bombRewards.length - 1; br >= 0; br--) {
var bombReward = bombRewards[br];
if (castle.intersects(bombReward)) {
// Schedule bomb to appear at character position after 3 seconds
var bombX = castle.x;
var bombY = castle.y;
LK.setTimeout(function () {
var bomb = new Bomb();
bomb.x = bombX;
bomb.y = bombY;
bombs.push(bomb);
game.addChild(bomb);
}, 3000);
LK.getSound('award').play();
LK.effects.flashObject(castle, 0xFF8800, 500);
bombReward.destroy();
bombRewards.splice(br, 1);
}
}
// Scrap metal collection is now handled automatically in ScrapMetalDrop class
// Check castle-health pack collisions for pickup
for (var hp = healthPacks.length - 1; hp >= 0; hp--) {
var healthPack = healthPacks[hp];
if (castle.intersects(healthPack)) {
// Heal castle
var healAmount = Math.min(healthPack.healAmount, castle.maxHealth - castle.health);
if (healAmount > 0) {
castle.health += healAmount;
LK.getSound('award').play();
LK.effects.flashObject(castle, 0x44FF44, 500);
// Update health bar
if (castleHealthBar) {
castleHealthBar.updateHealth(castle.health, castle.maxHealth);
}
// Show heal amount
var healText = new Text2('+' + healAmount + ' HEALTH', {
size: 40,
fill: 0x44FF44
});
healText.anchor.set(0.5, 0.5);
healText.x = castle.x;
healText.y = castle.y - 60;
game.addChild(healText);
// Animate heal text
tween(healText, {
y: healText.y - 50,
alpha: 0
}, {
duration: 1500,
onFinish: function onFinish() {
healText.destroy();
}
});
}
healthPack.destroy();
healthPacks.splice(hp, 1);
}
}
// Update ability button text with cooldown status
if (castle.shieldCooldown > 0) {
var shieldSeconds = Math.ceil(castle.shieldCooldown / 60);
shieldButton.setText('SHIELD (' + shieldSeconds + 's)');
shieldButton.fill = 0x666666; // Grayed out
} else {
shieldButton.setText('SHIELD');
shieldButton.fill = 0x0088FF; // Active color
}
if (castle.shockwaveCooldown > 0) {
var shockwaveSeconds = Math.ceil(castle.shockwaveCooldown / 60);
shockwaveButton.setText('SHOCKWAVE (' + shockwaveSeconds + 's)');
shockwaveButton.fill = 0x666666; // Grayed out
} else {
shockwaveButton.setText('SHOCKWAVE');
shockwaveButton.fill = 0xFF4400; // Active color
}
// Update survival time
survivalTime++;
var minutes = Math.floor(survivalTime / 3600); // 60 seconds * 60 fps
var seconds = Math.floor(survivalTime % 3600 / 60);
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
// Update wave counter and survival time
waveText.setText('Wave: ' + waveNumber);
survivalTimeText.setText('Time: ' + timeString);
// Check for survival milestones (every 10 waves)
if (waveNumber >= lastMilestoneWave + 10) {
lastMilestoneWave = waveNumber;
// Milestone rewards
var milestoneScoreBonus = waveNumber * 200;
var milestoneScrapBonus = Math.floor(waveNumber / 5) + 5;
LK.setScore(LK.getScore() + milestoneScoreBonus);
currentScrapMetal += milestoneScrapBonus;
// Show milestone achievement
var milestoneText = new Text2('SURVIVAL MILESTONE!\nWAVE ' + waveNumber + ' REACHED\n+' + milestoneScoreBonus + ' SCORE +' + milestoneScrapBonus + ' SCRAP', {
size: 55,
fill: 0xFFD700
});
milestoneText.anchor.set(0.5, 0.5);
milestoneText.x = 1024;
milestoneText.y = 1000;
game.addChild(milestoneText);
// Special visual effects for milestone
LK.effects.flashScreen(0xFFD700, 1200);
tween(milestoneText, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(milestoneText, {
alpha: 0,
y: milestoneText.y - 150
}, {
duration: 2000,
onFinish: function onFinish() {
milestoneText.destroy();
}
});
}
});
}
// Update power-up display
var powerUpDisplay = '';
if (castle.shield) {
var shieldTime = Math.ceil(castle.shield.duration / 60);
powerUpDisplay += 'SHIELD: ' + shieldTime + 's ';
}
var permanentBullets = getUpgradeBonus('bulletCount', storage.bulletCountLevel) + 1;
var tempBullets = castle.bulletsPerShot - permanentBullets;
if (tempBullets > 0) {
powerUpDisplay += '+' + tempBullets + ' BULLETS ';
}
powerUpText.setText(powerUpDisplay);
// Update threat indicators
// Clear old indicators
for (var ti = threatIndicators.length - 1; ti >= 0; ti--) {
threatIndicators[ti].destroy();
threatIndicators.splice(ti, 1);
}
// Create threat indicators for enemies near screen edges
for (var ei = 0; ei < enemies.length; ei++) {
var enemy = enemies[ei];
var indicator = null;
// Check if enemy is near edges and create indicators
if (enemy.x < 100) {
// Left edge
indicator = new ThreatIndicator();
indicator.x = 50;
indicator.y = Math.max(100, Math.min(2632, enemy.y));
indicator.rotation = Math.PI; // Point right
} else if (enemy.x > 1948) {
// Right edge
indicator = new ThreatIndicator();
indicator.x = 1998;
indicator.y = Math.max(100, Math.min(2632, enemy.y));
indicator.rotation = 0; // Point left
} else if (enemy.y < 100) {
// Top edge
indicator = new ThreatIndicator();
indicator.x = Math.max(100, Math.min(1948, enemy.x));
indicator.y = 50;
indicator.rotation = Math.PI / 2; // Point down
} else if (enemy.y > 2632) {
// Bottom edge
indicator = new ThreatIndicator();
indicator.x = Math.max(100, Math.min(1948, enemy.x));
indicator.y = 2682;
indicator.rotation = -Math.PI / 2; // Point up
}
if (indicator) {
threatIndicators.push(indicator);
game.addChild(indicator);
}
}
// Update scrap collection indicator position
if (scrapIndicator) {
scrapIndicator.x = castle.x;
scrapIndicator.y = castle.y;
}
// Update UI with tier information
var totalScrap = storage.scrapMetal + currentScrapMetal;
var scrapDisplay = 'Scrap: ' + totalScrap;
// Show next upgrade cost for fire rate as example
if (storage.fireRateLevel < storage.maxUpgradeTier) {
var nextCost = getUpgradeCost('fireRate', storage.fireRateLevel);
scrapDisplay += ' | Fire Rate T' + storage.fireRateLevel + 'βT' + (storage.fireRateLevel + 1) + ': ' + nextCost;
} else {
scrapDisplay += ' | Fire Rate: MAX';
}
scrapMetalText.setText(scrapDisplay);
healthText.setText('Health: ' + castle.health + '/' + castle.maxHealth);
scoreText.setText(LK.getScore());
// Update high score display with current vs best comparison
var currentScore = LK.getScore();
if (currentScore > storage.highScore) {
highScoreText.setText('BEST: ' + currentScore + ' (Wave ' + waveNumber + ') NEW!');
highScoreText.fill = 0x00FF00; // Green for new record
} else {
highScoreText.setText('Best: ' + storage.highScore + ' (Wave ' + storage.bestWave + ') | Current: ' + currentScore);
highScoreText.fill = 0xFFD700; // Gold for normal display
}
// Update stats display
statsText.setText('Games: ' + storage.totalGamesPlayed + ' | Enemies: ' + storage.totalEnemiesDefeated + ' | Best Time: ' + Math.floor(storage.longestSurvivalTime / 3600) + ':' + (Math.floor(storage.longestSurvivalTime % 3600 / 60) < 10 ? '0' : '') + Math.floor(storage.longestSurvivalTime % 3600 / 60));
// Update upgrade menu if visible
if (upgradeMenuVisible && upgradeMenu.visible) {
upgradeMenu.updateButtons();
}
// Update upgrade button text based on available scrap
var upgradeButtonText = 'β‘ UPGRADES';
var totalScrap = storage.scrapMetal + currentScrapMetal;
if (totalScrap > 0) {
upgradeButtonText += ' (' + totalScrap + ')';
}
upgradeButton.setText(upgradeButtonText);
};
square smiley face thick eyebrows. In-Game asset
pink stone round. In-Game asset
purple square crab. In-Game asset
round mouse face. In-Game asset
bright green fat star. In-Game asset
round red lava stone. In-Game asset
square yellow poisonous stone top view. In-Game asset
yellow flame ze poisonous bead top view. In-Game asset
grass top view. In-Game asset. 2d. High contrast. No shadows
bomb in green star