User prompt
fix problem when player dies nothing happens ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
fix when player dies to animate an explosion on player death, an game over message appears in red then the leaderboard screen appears, if the player has an top 10 score he can submit his name next to his score if the player has not reached top 10 he can press the restart button to restart the game ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
fix leaderboard screen when game over the player no longer fires and is dead, fix command input for leaderboard when entering your name the buttons does not work ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.leaderboard = leaderboard;' Line Number: 1017 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.leaderboard = leaderboard;' Line Number: 1015 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
add function when game over show score leader board of the top 10 players scores and names, if player has reached the top 10 rank on scoreboard the player can enter his name next to his score ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
add function when player dies and has reached the top 10 score to be able to submit his name on an leaderboard with his score obtained in the game with additional option to pass and not submit any names or score ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
adjust all creeps for extra increased movement speed after wave 15 with 2% increase movement speed every wave after wave 15
User prompt
add one nova blade upgrade on start of game for testing purposes will be removed later
User prompt
add explosion sound when creeps are killed asset
User prompt
add animation to creeps when destroyed they explode in burs of flames add visual asset of the explosion ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Special creeps are slightly larger than the elite creeps, special creeps move slightly faster than normal creeps
User prompt
add special creeps type, add visual asset for special creeps, special creeps has three times the health of the elite creeps and has slightly faster movement speed as the regular creeps, special creeps spawn on wave count 9, 19, 29, 39 in that sequence one wave count before each boss fight, every wave count after wave 9 increase special creep spawn count by one, meaning on wave count 19 two special creeps spawn, on wave count 29 three special creeps spawn
User prompt
increase damage or regular creeps from 15 to 25, elite creeps from 42 to 50
User prompt
slightly increase damage of all creeps when they hit the player
User prompt
increase hit detection distance for creeps when player has shield active and when player has no shield
User prompt
increase hit detection distance for creeps when player has no shield
User prompt
increase hit detection distance for creeps when player has shield on
User prompt
the creeps and elite creeps can damage and hit the player from slightly further away
User prompt
fix collision elite creeps must do damage to the player decrease avoidance radius
User prompt
fix the problem when elite creeps hit the player they don't do damage
User prompt
decrease avoidance radius of creeps to 50 elite creeps to 70 and boss creeps to 100, and increase creeps hit radius from 25 to 40 elite creeps from 35 to 55 and boss creeps to 300, increase player hit detection when shield is active to 40
User prompt
increase the movement speed of creeps and elite creeps slightly
User prompt
the creeps that spawn and rotate around the boss increase their damage when they hit the player
User prompt
adjust increase all creep proximity hit radius
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var BossBullet = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('bossBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 3; // Slow moving bullets self.velocityX = 0; self.velocityY = 0; self.damage = 25; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; }; return self; }); var BossCreep = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('bossCreep', { anchorX: 0.5, anchorY: 0.5 }); self.health = 24000; // Double health for boss (60x increase) self.maxHealth = 24000; self.speed = 0.8; // Slower than elite creeps self.damage = 50; // More damage than elite creeps self.targetX = 0; self.targetY = 0; self.hasReachedPlayer = false; self.spawnedCreeps = []; // Track spawned rotating creeps self.lastSpawnTime = 0; // Track time for spawning self.rotationAngle = 0; // Current rotation angle for spawned creeps self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { return true; // Return true if boss is dead } return false; }; self.update = function () { // Reset flag to allow continued movement after hitting player self.hasReachedPlayer = false; // Continuously update target to player's current position self.targetX = player.x; self.targetY = player.y; var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 3) { // Calculate avoidance force from other creeps var avoidanceX = 0; var avoidanceY = 0; var avoidanceRadius = 240; // Largest avoidance radius for boss creeps for (var i = 0; i < creeps.length; i++) { var otherCreep = creeps[i]; if (otherCreep !== self) { var otherDx = self.x - otherCreep.x; var otherDy = self.y - otherCreep.y; var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy); if (otherDistance < avoidanceRadius && otherDistance > 0) { var avoidanceStrength = (avoidanceRadius - otherDistance) / avoidanceRadius; avoidanceX += otherDx / otherDistance * avoidanceStrength; avoidanceY += otherDy / otherDistance * avoidanceStrength; } } } // Combine movement towards player with avoidance var moveX = dx / distance * self.speed + avoidanceX * 0.3; var moveY = dy / distance * self.speed + avoidanceY * 0.3; self.x += moveX; self.y += moveY; } else { self.hasReachedPlayer = true; } // Spawn rotating creeps around boss every second (60 ticks) if (LK.ticks - self.lastSpawnTime >= 60) { self.lastSpawnTime = LK.ticks; // Create a new creep that will rotate around the boss var spawnedCreep = new Creep(); spawnedCreep.health = 40; // Less health than normal creeps spawnedCreep.maxHealth = 40; spawnedCreep.speed = 0; // They don't move on their own spawnedCreep.damage = 10; // Less damage than normal creeps spawnedCreep.bossParent = self; // Reference to boss spawnedCreep.orbitRadius = 600; // Distance from boss center spawnedCreep.orbitAngle = Math.random() * Math.PI * 2; // Random starting angle // Position the creep initially spawnedCreep.x = self.x + Math.cos(spawnedCreep.orbitAngle) * spawnedCreep.orbitRadius; spawnedCreep.y = self.y + Math.sin(spawnedCreep.orbitAngle) * spawnedCreep.orbitRadius; self.spawnedCreeps.push(spawnedCreep); creeps.push(spawnedCreep); game.addChild(spawnedCreep); } // Update rotation angle self.rotationAngle += 0.02; // Rotation speed // Update positions of spawned creeps to rotate around boss for (var k = self.spawnedCreeps.length - 1; k >= 0; k--) { var spawnedCreep = self.spawnedCreeps[k]; if (spawnedCreep && spawnedCreep.bossParent === self) { // Update orbit angle spawnedCreep.orbitAngle += 0.03; // Individual rotation speed // Calculate new position around boss spawnedCreep.x = self.x + Math.cos(spawnedCreep.orbitAngle) * spawnedCreep.orbitRadius; spawnedCreep.y = self.y + Math.sin(spawnedCreep.orbitAngle) * spawnedCreep.orbitRadius; // Override normal creep movement by setting hasReachedPlayer to prevent normal AI spawnedCreep.hasReachedPlayer = false; spawnedCreep.targetX = spawnedCreep.x; spawnedCreep.targetY = spawnedCreep.y; } else { // Remove reference if creep was destroyed self.spawnedCreeps.splice(k, 1); } } // Boss bullet shooting every 5 seconds (300 ticks at 60fps) if (LK.ticks % 300 === 0) { // Calculate direction to player var bulletDx = player.x - self.x; var bulletDy = player.y - self.y; var bulletDistance = Math.sqrt(bulletDx * bulletDx + bulletDy * bulletDy); if (bulletDistance > 0) { var normalizedX = bulletDx / bulletDistance; var normalizedY = bulletDy / bulletDistance; // Create 3 bullets in a burst pattern for (var bulletIndex = 0; bulletIndex < 3; bulletIndex++) { var bossBullet = new BossBullet(); bossBullet.x = self.x; bossBullet.y = self.y; // Slight angle variation for spread pattern var angleOffset = (bulletIndex - 1) * 0.2; // -0.2, 0, 0.2 radians var angle = Math.atan2(normalizedY, normalizedX) + angleOffset; bossBullet.velocityX = Math.cos(angle) * bossBullet.speed; bossBullet.velocityY = Math.sin(angle) * bossBullet.speed; // Add to boss bullets array (will be created in game code) bossBullets.push(bossBullet); game.addChild(bossBullet); } } } }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.velocityX = 0; self.velocityY = 0; self.damage = 20; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; }; return self; }); var Creep = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('creep', { anchorX: 0.5, anchorY: 0.5 }); self.health = 60; // Base health requiring 3 hits (20 damage per hit) self.maxHealth = 60; self.speed = 2; self.damage = 15; self.targetX = 0; self.targetY = 0; self.hasReachedPlayer = false; self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { return true; // Return true if creep is dead } return false; }; self.update = function () { // Reset flag to allow continued movement after hitting player self.hasReachedPlayer = false; // Continuously update target to player's current position self.targetX = player.x; self.targetY = player.y; var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 25) { // Calculate avoidance force from other creeps var avoidanceX = 0; var avoidanceY = 0; var avoidanceRadius = 120; // Distance to avoid other creeps for (var i = 0; i < creeps.length; i++) { var otherCreep = creeps[i]; if (otherCreep !== self) { var otherDx = self.x - otherCreep.x; var otherDy = self.y - otherCreep.y; var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy); if (otherDistance < avoidanceRadius && otherDistance > 0) { var avoidanceStrength = (avoidanceRadius - otherDistance) / avoidanceRadius; avoidanceX += otherDx / otherDistance * avoidanceStrength; avoidanceY += otherDy / otherDistance * avoidanceStrength; } } } // Combine movement towards player with avoidance var moveX = dx / distance * self.speed + avoidanceX * 0.5; var moveY = dy / distance * self.speed + avoidanceY * 0.5; self.x += moveX; self.y += moveY; } else { self.hasReachedPlayer = true; } }; return self; }); var EliteCreep = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('eliteCreep', { anchorX: 0.5, anchorY: 0.5 }); self.health = 150; // Much higher base health self.maxHealth = 150; self.speed = 1.2; // Slower than regular creeps self.damage = 35; // More damage than regular creeps self.targetX = 0; self.targetY = 0; self.hasReachedPlayer = false; self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { return true; // Return true if creep is dead } return false; }; self.update = function () { // Reset flag to allow continued movement after hitting player self.hasReachedPlayer = false; // Continuously update target to player's current position self.targetX = player.x; self.targetY = player.y; var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 25) { // Calculate avoidance force from other creeps var avoidanceX = 0; var avoidanceY = 0; var avoidanceRadius = 180; // Larger avoidance radius for elite creeps for (var i = 0; i < creeps.length; i++) { var otherCreep = creeps[i]; if (otherCreep !== self) { var otherDx = self.x - otherCreep.x; var otherDy = self.y - otherCreep.y; var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy); if (otherDistance < avoidanceRadius && otherDistance > 0) { var avoidanceStrength = (avoidanceRadius - otherDistance) / avoidanceRadius; avoidanceX += otherDx / otherDistance * avoidanceStrength; avoidanceY += otherDy / otherDistance * avoidanceStrength; } } } // Combine movement towards player with avoidance var moveX = dx / distance * self.speed + avoidanceX * 0.5; var moveY = dy / distance * self.speed + avoidanceY * 0.5; self.x += moveX; self.y += moveY; } else { self.hasReachedPlayer = true; } }; return self; }); var Pickup = Container.expand(function (type) { var self = Container.call(this); self.type = type; var assetName; if (type === 'health') { assetName = 'healthPotion'; } else if (type === 'damage') { assetName = 'damageUpgrade'; } else if (type === 'speed') { assetName = 'speedUpgrade'; // Use dedicated speed upgrade asset } else if (type === 'attackSpeed') { assetName = 'damageUpgrade'; // Reuse red upgrade for attack speed } else if (type === 'shield') { assetName = 'shieldUpgrade'; } else if (type === 'armor') { assetName = 'armorUpgrade'; } else if (type === 'novaBlade') { assetName = 'novaBlade'; } var graphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.lifetime = 3600; // 60 seconds at 60fps self.update = function () { self.lifetime--; if (self.lifetime < 300) { // Start fading at 5 seconds remaining graphics.alpha = self.lifetime / 300; } // Magnetic pull effect when player is close if (player) { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Start pulling when within 150 pixels if (distance < 150 && distance > 10) { var pullSpeed = 8; // Fast pull speed var normalizedX = dx / distance; var normalizedY = dy / distance; self.x += normalizedX * pullSpeed; self.y += normalizedY * pullSpeed; } } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); self.maxHealth = 100; self.health = 100; self.defense = 0; self.armor = 0; self.maxArmor = 100; self.baseDamage = 20; // Base damage that never changes self.damage = 20; // Current damage (base + upgrades) self.weaponUpgrades = 0; // Track weapon upgrades for double shot self.lastShootTime = 0; self.shootCooldown = 40; // Fixed cooldown that never changes self.baseSpeed = 4; // Base movement speed self.moveSpeed = 4; // Current movement speed (base + upgrades) self.shieldActive = false; self.shieldHealth = 0; self.shieldMaxHealth = 80; self.shieldDuration = 0; // In game ticks (60 fps) self.shieldAura = null; self.invincible = false; self.invincibilityTimer = 0; self.novaBladeActive = false; self.novaBladeTimer = 0; self.novaBladeLastActivation = 0; self.novaBladeLevel = 0; self.novaBladeProximity = 300; // Base proximity // Movement direction tracking self.lastX = 0; self.lastY = 0; self.currentDirection = 0; // 0 = north (default) self.takeDamage = function (amount) { // If player is invincible, ignore all damage if (self.invincible) { return; } if (self.shieldActive && self.shieldHealth > 0) { // Shield absorbs damage first self.shieldHealth -= amount; if (self.shieldHealth <= 0) { // Shield broken self.shieldHealth = 0; self.shieldActive = false; self.shieldDuration = 0; if (self.shieldAura) { self.shieldAura.destroy(); self.shieldAura = null; } } // Flash blue when shield takes damage tween(graphics, { tint: 0x4444FF }, { duration: 100, onFinish: function onFinish() { tween(graphics, { tint: 0xFFFFFF }, { duration: 100 }); } }); } else if (self.armor > 0) { // Armor absorbs damage second self.armor -= amount; if (self.armor < 0) { // Overflow damage goes to health self.health += self.armor; // armor is negative, so this subtracts from health self.armor = 0; } if (self.health < 0) self.health = 0; // Flash yellow when armor takes damage tween(graphics, { tint: 0xFFDD00 }, { duration: 100, onFinish: function onFinish() { tween(graphics, { tint: 0xFFFFFF }, { duration: 100 }); } }); } else { // Normal damage to health last self.health -= amount; if (self.health < 0) self.health = 0; // Flash red when taking damage tween(graphics, { tint: 0xFF0000 }, { duration: 100, onFinish: function onFinish() { tween(graphics, { tint: 0xFFFFFF }, { duration: 100 }); } }); } LK.getSound('hit').play(); }; self.heal = function (amount) { self.health = Math.min(self.maxHealth, self.health + amount); }; self.addDamage = function (amount) { self.damage += amount; self.weaponUpgrades++; // Track weapon upgrades }; self.addSpeed = function (amount) { // Cap movement speed upgrades at level 25 (24 upgrades from base level 1) if (self.weaponUpgrades < 24) { self.moveSpeed += amount; } }; self.addArmor = function (amount) { self.armor = Math.min(100, self.armor + amount); }; self.activateNovaBlade = function () { if (!self.novaBladeActive) { self.novaBladeActive = true; self.novaBladeLastActivation = LK.ticks; self.novaBladeLevel = 1; self.novaBladeProximity = 300; } else if (self.novaBladeLevel < 50) { // Increase Nova blade level and proximity up to level 50 self.novaBladeLevel++; self.novaBladeProximity += 10; } }; self.activateShield = function () { if (!self.shieldActive) { // First time activation self.shieldActive = true; self.shieldHealth = 40; // Start with 40 hit points self.shieldDuration = 3600; // 60 seconds at 60fps // Create shield visual self.shieldAura = self.addChild(LK.getAsset('shieldAura', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6, tint: 0x6666FF })); } else { // Shield already active, add duration up to max 180 seconds self.shieldDuration = Math.min(10800, self.shieldDuration + 3600); // Max 180 seconds (60 seconds per upgrade) // Add shield health up to max 80 hit points self.shieldHealth = Math.min(80, self.shieldHealth + 40); // Ensure shield visual is displayed if (!self.shieldAura) { self.shieldAura = self.addChild(LK.getAsset('shieldAura', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6, tint: 0x6666FF })); } } }; self.canShoot = function () { return LK.ticks - self.lastShootTime > self.shootCooldown; }; self.shoot = function (targetX, targetY) { if (!self.canShoot()) return null; self.lastShootTime = LK.ticks; var bullets = []; var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var normalizedX = dx / distance; var normalizedY = dy / distance; var angle = Math.atan2(normalizedY, normalizedX); if (self.weaponUpgrades >= 49) { // Level 50+: Six shots in burst fire pattern (Hexa Shot) var offsetDistance = 30; var perpX = -normalizedY; var perpY = normalizedX; // First bullet (left side, front) var bullet1 = new Bullet(); bullet1.x = self.x + perpX * offsetDistance; bullet1.y = self.y + perpY * offsetDistance; bullet1.velocityX = normalizedX * bullet1.speed; bullet1.velocityY = normalizedY * bullet1.speed; bullet1.damage = self.damage; bullets.push(bullet1); // Second bullet (left side, back) var bullet2 = new Bullet(); bullet2.x = self.x + perpX * offsetDistance - normalizedX * 25; bullet2.y = self.y + perpY * offsetDistance - normalizedY * 25; bullet2.velocityX = normalizedX * bullet2.speed; bullet2.velocityY = normalizedY * bullet2.speed; bullet2.damage = self.damage; bullets.push(bullet2); // Third bullet (right side, front) var bullet3 = new Bullet(); bullet3.x = self.x - perpX * offsetDistance; bullet3.y = self.y - perpY * offsetDistance; bullet3.velocityX = normalizedX * bullet3.speed; bullet3.velocityY = normalizedY * bullet3.speed; bullet3.damage = self.damage; bullets.push(bullet3); // Fourth bullet (right side, back) var bullet4 = new Bullet(); bullet4.x = self.x - perpX * offsetDistance - normalizedX * 25; bullet4.y = self.y - perpY * offsetDistance - normalizedY * 25; bullet4.velocityX = normalizedX * bullet4.speed; bullet4.velocityY = normalizedY * bullet4.speed; bullet4.damage = self.damage; bullets.push(bullet4); // Fifth bullet (center shot) var bullet5 = new Bullet(); bullet5.x = self.x; bullet5.y = self.y; bullet5.velocityX = normalizedX * bullet5.speed; bullet5.velocityY = normalizedY * bullet5.speed; bullet5.damage = self.damage; bullets.push(bullet5); // Sixth bullet (center shot, back) var bullet6 = new Bullet(); bullet6.x = self.x - normalizedX * 25; bullet6.y = self.y - normalizedY * 25; bullet6.velocityX = normalizedX * bullet6.speed; bullet6.velocityY = normalizedY * bullet6.speed; bullet6.damage = self.damage; bullets.push(bullet6); } else if (self.weaponUpgrades >= 39) { // Level 40+: Five shots in burst fire (two double shots + center shot) var offsetDistance = 25; var perpX = -normalizedY; var perpY = normalizedX; // First bullet (left side, front) var bullet1 = new Bullet(); bullet1.x = self.x + perpX * offsetDistance; bullet1.y = self.y + perpY * offsetDistance; bullet1.velocityX = normalizedX * bullet1.speed; bullet1.velocityY = normalizedY * bullet1.speed; bullet1.damage = self.damage; bullets.push(bullet1); // Second bullet (left side, back) var bullet2 = new Bullet(); bullet2.x = self.x + perpX * offsetDistance - normalizedX * 20; bullet2.y = self.y + perpY * offsetDistance - normalizedY * 20; bullet2.velocityX = normalizedX * bullet2.speed; bullet2.velocityY = normalizedY * bullet2.speed; bullet2.damage = self.damage; bullets.push(bullet2); // Third bullet (right side, front) var bullet3 = new Bullet(); bullet3.x = self.x - perpX * offsetDistance; bullet3.y = self.y - perpY * offsetDistance; bullet3.velocityX = normalizedX * bullet3.speed; bullet3.velocityY = normalizedY * bullet3.speed; bullet3.damage = self.damage; bullets.push(bullet3); // Fourth bullet (right side, back) var bullet4 = new Bullet(); bullet4.x = self.x - perpX * offsetDistance - normalizedX * 20; bullet4.y = self.y - perpY * offsetDistance - normalizedY * 20; bullet4.velocityX = normalizedX * bullet4.speed; bullet4.velocityY = normalizedY * bullet4.speed; bullet4.damage = self.damage; bullets.push(bullet4); // Fifth bullet (center shot) var bullet5 = new Bullet(); bullet5.x = self.x; bullet5.y = self.y; bullet5.velocityX = normalizedX * bullet5.speed; bullet5.velocityY = normalizedY * bullet5.speed; bullet5.damage = self.damage; bullets.push(bullet5); } else if (self.weaponUpgrades >= 29) { // Level 30+: Four shots in burst fire (two double shots) var offsetDistance = 25; var perpX = -normalizedY; var perpY = normalizedX; // First bullet (left side, front) var bullet1 = new Bullet(); bullet1.x = self.x + perpX * offsetDistance; bullet1.y = self.y + perpY * offsetDistance; bullet1.velocityX = normalizedX * bullet1.speed; bullet1.velocityY = normalizedY * bullet1.speed; bullet1.damage = self.damage; bullets.push(bullet1); // Second bullet (left side, back) var bullet2 = new Bullet(); bullet2.x = self.x + perpX * offsetDistance - normalizedX * 20; bullet2.y = self.y + perpY * offsetDistance - normalizedY * 20; bullet2.velocityX = normalizedX * bullet2.speed; bullet2.velocityY = normalizedY * bullet2.speed; bullet2.damage = self.damage; bullets.push(bullet2); // Third bullet (right side, front) var bullet3 = new Bullet(); bullet3.x = self.x - perpX * offsetDistance; bullet3.y = self.y - perpY * offsetDistance; bullet3.velocityX = normalizedX * bullet3.speed; bullet3.velocityY = normalizedY * bullet3.speed; bullet3.damage = self.damage; bullets.push(bullet3); // Fourth bullet (right side, back) var bullet4 = new Bullet(); bullet4.x = self.x - perpX * offsetDistance - normalizedX * 20; bullet4.y = self.y - perpY * offsetDistance - normalizedY * 20; bullet4.velocityX = normalizedX * bullet4.speed; bullet4.velocityY = normalizedY * bullet4.speed; bullet4.damage = self.damage; bullets.push(bullet4); } else if (self.weaponUpgrades >= 19) { // Level 20+: Triple shot (3 parallel shots) var offsetDistance = 15; var perpX = -normalizedY; var perpY = normalizedX; // Center bullet var bullet1 = new Bullet(); bullet1.x = self.x; bullet1.y = self.y; bullet1.velocityX = normalizedX * bullet1.speed; bullet1.velocityY = normalizedY * bullet1.speed; bullet1.damage = self.damage; bullets.push(bullet1); // Left bullet (parallel) var bullet2 = new Bullet(); bullet2.x = self.x + perpX * offsetDistance; bullet2.y = self.y + perpY * offsetDistance; bullet2.velocityX = normalizedX * bullet2.speed; bullet2.velocityY = normalizedY * bullet2.speed; bullet2.damage = self.damage; bullets.push(bullet2); // Right bullet (parallel) var bullet3 = new Bullet(); bullet3.x = self.x - perpX * offsetDistance; bullet3.y = self.y - perpY * offsetDistance; bullet3.velocityX = normalizedX * bullet3.speed; bullet3.velocityY = normalizedY * bullet3.speed; bullet3.damage = self.damage; bullets.push(bullet3); } else if (self.weaponUpgrades >= 9) { // Level 10+: Double shot var offsetDistance = 20; var perpX = -normalizedY; var perpY = normalizedX; // First bullet (left side) var bullet1 = new Bullet(); bullet1.x = self.x + perpX * offsetDistance; bullet1.y = self.y + perpY * offsetDistance; bullet1.velocityX = normalizedX * bullet1.speed; bullet1.velocityY = normalizedY * bullet1.speed; bullet1.damage = self.damage; bullets.push(bullet1); // Second bullet (right side) var bullet2 = new Bullet(); bullet2.x = self.x - perpX * offsetDistance; bullet2.y = self.y - perpY * offsetDistance; bullet2.velocityX = normalizedX * bullet2.speed; bullet2.velocityY = normalizedY * bullet2.speed; bullet2.damage = self.damage; bullets.push(bullet2); } else { // Single shot for levels < 10 var bullet1 = new Bullet(); bullet1.x = self.x; bullet1.y = self.y; bullet1.velocityX = normalizedX * bullet1.speed; bullet1.velocityY = normalizedY * bullet1.speed; bullet1.damage = self.damage; bullets.push(bullet1); } LK.getSound('shoot').play(); return bullets; }; self.update = function () { // Track movement direction and animate rotation if (self.lastX !== undefined && self.lastY !== undefined) { var deltaX = self.x - self.lastX; var deltaY = self.y - self.lastY; var moveDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // Only update direction if player is actually moving if (moveDistance > 0.5) { var targetDirection = Math.atan2(deltaY, deltaX); // Convert to 0-360 degree system where 0 is north targetDirection = targetDirection + Math.PI / 2; if (targetDirection < 0) targetDirection += Math.PI * 2; if (targetDirection > Math.PI * 2) targetDirection -= Math.PI * 2; // Smoothly rotate to face movement direction var currentRotation = graphics.rotation; var angleDiff = targetDirection - currentRotation; // Handle wrap around for shortest rotation path if (angleDiff > Math.PI) angleDiff -= Math.PI * 2; if (angleDiff < -Math.PI) angleDiff += Math.PI * 2; // Apply smooth rotation var rotationSpeed = 0.15; graphics.rotation += angleDiff * rotationSpeed; } } // Update invincibility timer if (self.invincible) { self.invincibilityTimer--; if (self.invincibilityTimer <= 0) { self.invincible = false; // Stop any tinting when invincibility ends tween.stop(graphics, { tint: true }); graphics.tint = 0xFFFFFF; graphics.alpha = 1.0; } } // Update Nova blade timer and activation if (self.novaBladeActive) { // Check if 5 seconds (300 ticks) have passed since last activation if (LK.ticks - self.novaBladeLastActivation >= 300) { // Create expanding nova blade effect var aura = LK.getAsset('novaBladeEffect', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8, tint: 0xFF6600, scaleX: 0.1, scaleY: 0.1 }); self.addChild(aura); // Knock back all creeps within proximity radius immediately for (var creepIdx = 0; creepIdx < creeps.length; creepIdx++) { var creep = creeps[creepIdx]; var dx = creep.x - self.x; var dy = creep.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.novaBladeProximity) { // Calculate knockback direction if (distance > 0) { var normalizedX = dx / distance; var normalizedY = dy / distance; var knockbackDistance = self.novaBladeProximity - distance + 50; // Push them outside the radius // Apply knockback using tween for smooth animation tween(creep, { x: creep.x + normalizedX * knockbackDistance, y: creep.y + normalizedY * knockbackDistance }, { duration: 500, easing: tween.easeOut }); } } } // Animate aura expansion from proximity 10 to current Nova blade proximity level var maxScale = self.novaBladeProximity / 80; // Convert proximity to scale (80 is base aura size) tween(aura, { scaleX: maxScale, scaleY: maxScale, alpha: 0.0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { aura.destroy(); } }); // Reset timer for next activation self.novaBladeLastActivation = LK.ticks; } } // Update last position for next frame self.lastX = self.x; self.lastY = self.y; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ title: 'Galaxy Of Terror' }); /**** * Game Code ****/ // Add background image var background = game.addChild(LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 })); // Game state variables var gameStarted = false; var startScreen = null; // Create start screen function createStartScreen() { startScreen = new Container(); game.addChild(startScreen); // Start screen background var startBg = startScreen.addChild(LK.getAsset('startBackground', { anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0.9, tint: 0x001122 })); // Game title var titleText = new Text2('GALAXY OF TERROR', { size: 120, fill: 0xFFD700 }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 800; startScreen.addChild(titleText); // Subtitle var subtitleText = new Text2('Survive the endless waves', { size: 60, fill: 0xFFFFFF }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 1024; subtitleText.y = 920; startScreen.addChild(subtitleText); // Play button background var playButton = startScreen.addChild(LK.getAsset('startButton', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1200, scaleX: 1.5, scaleY: 1.2, tint: 0x00AA44 })); // Play button text var playButtonText = new Text2('TAP TO PLAY', { size: 80, fill: 0xFFFFFF }); playButtonText.anchor.set(0.5, 0.5); playButtonText.x = 1024; playButtonText.y = 1200; startScreen.addChild(playButtonText); // Instructions var instructText = new Text2('Tap and drag to move\nAutomatic shooting', { size: 50, fill: 0xCCCCCC }); instructText.anchor.set(0.5, 0.5); instructText.x = 1024; instructText.y = 1400; startScreen.addChild(instructText); // Handle start screen input startScreen.down = function (x, y, obj) { // Check if clicked on play button area var buttonDx = x - 1024; var buttonDy = y - 1200; var buttonDistance = Math.sqrt(buttonDx * buttonDx + buttonDy * buttonDy); if (buttonDistance < 150) { startGame(); } }; } function startGame() { if (startScreen) { startScreen.destroy(); startScreen = null; } gameStarted = true; // Initialize player after game starts player = game.addChild(new Player()); player.x = 1024; player.y = 1366; } // Initialize start screen createStartScreen(); var player = null; var creeps = []; var bullets = []; var bossBullets = []; var pickups = []; var currentWave = 1; var creepsInWave = 5; var creepsSpawned = 0; var eliteCreepsInWave = 1; // Number of elite creeps equals wave number var eliteCreepsSpawned = 0; var waveCompleted = false; var nextWaveTimer = 0; var spawnTimer = 0; // Score tracking variables var gameScore = 0; // Boss system variables var currentBoss = null; var bossWave = false; // Speed modifier for creeps - increases 1% every 5th wave var creepSpeedMultiplier = 1.0; // Track special 5th wave speed upgrade drops var fifthWaveSpeedUpgradeDropped = false; // Track upgrade drops per wave var waveUpgradeDrops = {}; // UI Elements var healthText = new Text2('Health: 100', { size: 40, fill: 0xFF0000 }); healthText.anchor.set(0, 0); LK.gui.topLeft.addChild(healthText); healthText.x = 120; // Offset from left edge var armorText = new Text2('Armor: 0', { size: 40, fill: 0x00FF00 }); armorText.anchor.set(0, 0); LK.gui.topLeft.addChild(armorText); armorText.x = 120; // Offset from left edge armorText.y = 45; // Below health text var waveText = new Text2('Wave: 1', { size: 40, fill: 0xFFD700 }); waveText.anchor.set(0.5, 0); LK.gui.top.addChild(waveText); var weaponLevelText = new Text2('Weapon Level: 1', { size: 40, fill: 0xFFFFFF }); weaponLevelText.anchor.set(1, 0); LK.gui.topRight.addChild(weaponLevelText); var weaponTypeText = new Text2('Blaster', { size: 40, fill: 0xFFFFFF }); weaponTypeText.anchor.set(1, 0); weaponTypeText.y = 45; // Position below weapon level text LK.gui.topRight.addChild(weaponTypeText); // Nova blade level text var novaBladeText = new Text2('', { size: 40, fill: 0xFF6600 }); novaBladeText.anchor.set(1, 0); novaBladeText.y = 90; // Position below weapon type text LK.gui.topRight.addChild(novaBladeText); // Score counter text var scoreText = new Text2('Score: 0', { size: 40, fill: 0xFF1493 // Bright pink color }); scoreText.anchor.set(1, 0); scoreText.y = 135; // Position below Nova blade level text LK.gui.topRight.addChild(scoreText); // Boss health bar (initially hidden) var bossHealthBar = new Text2('', { size: 35, fill: 0xFF4444 }); bossHealthBar.anchor.set(0.5, 0); LK.gui.top.addChild(bossHealthBar); bossHealthBar.y = 50; bossHealthBar.alpha = 0; // Hidden initially // Shield status text var shieldText = new Text2('', { size: 35, fill: 0x4444FF }); shieldText.anchor.set(0, 0); LK.gui.topLeft.addChild(shieldText); shieldText.x = 120; // Offset from left edge shieldText.y = 90; // Below armor text // Player will be initialized when game starts function spawnCreep() { var creep = new Creep(); // Spawn from random edge var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top creep.x = Math.random() * 2048; creep.y = -50; break; case 1: // Right creep.x = 2098; creep.y = Math.random() * 2732; break; case 2: // Bottom creep.x = Math.random() * 2048; creep.y = 2782; break; case 3: // Left creep.x = -50; creep.y = Math.random() * 2732; break; } creep.targetX = player.x; creep.targetY = player.y; creep.health += (currentWave - 1) * 20; // Scale health with wave (20 per wave) // Apply 50% health increase every 5th wave var healthMultiplier = 1 + Math.floor(currentWave / 5) * 0.5; creep.health = Math.floor(creep.health * healthMultiplier); creep.maxHealth = creep.health; // Apply speed increase - 1% for every 5th wave var speedMultiplier = 1 + Math.floor(currentWave / 5) * 0.01; creep.speed = creep.speed * speedMultiplier; creeps.push(creep); game.addChild(creep); } function spawnEliteCreep() { var eliteCreep = new EliteCreep(); // Spawn from random edge var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top eliteCreep.x = Math.random() * 2048; eliteCreep.y = -50; break; case 1: // Right eliteCreep.x = 2098; eliteCreep.y = Math.random() * 2732; break; case 2: // Bottom eliteCreep.x = Math.random() * 2048; eliteCreep.y = 2782; break; case 3: // Left eliteCreep.x = -50; eliteCreep.y = Math.random() * 2732; break; } eliteCreep.targetX = player.x; eliteCreep.targetY = player.y; eliteCreep.health += (currentWave - 1) * 40; // Scale health with wave (40 per wave for elites) // Apply 50% health increase every 5th wave var healthMultiplier = 1 + Math.floor(currentWave / 5) * 0.5; eliteCreep.health = Math.floor(eliteCreep.health * healthMultiplier); eliteCreep.maxHealth = eliteCreep.health; // Apply speed increase - 1% for every 5th wave var speedMultiplier = 1 + Math.floor(currentWave / 5) * 0.01; eliteCreep.speed = eliteCreep.speed * speedMultiplier; creeps.push(eliteCreep); game.addChild(eliteCreep); } function spawnBoss() { var boss = new BossCreep(); // Spawn from random edge var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top boss.x = Math.random() * 2048; boss.y = -75; break; case 1: // Right boss.x = 2123; boss.y = Math.random() * 2732; break; case 2: // Bottom boss.x = Math.random() * 2048; boss.y = 2807; break; case 3: // Left boss.x = -75; boss.y = Math.random() * 2732; break; } boss.targetX = player.x; boss.targetY = player.y; // Calculate boss health based on wave (doubles each boss wave) var bossNumber = Math.floor(currentWave / 10); boss.health = 24000 * Math.pow(2, bossNumber - 1); // Apply 50% health increase every 5th wave var healthMultiplier = 1 + Math.floor(currentWave / 5) * 0.5; boss.health = Math.floor(boss.health * healthMultiplier); boss.maxHealth = boss.health; currentBoss = boss; creeps.push(boss); game.addChild(boss); // Show boss health bar bossHealthBar.alpha = 1; tween(bossHealthBar, { scaleX: 1.1, scaleY: 1.1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(bossHealthBar, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } function dropPickup(x, y, isEliteCreep) { // Check for special 5th wave speed upgrade drop first var isFifthWave = currentWave % 5 === 0; if (isFifthWave && !fifthWaveSpeedUpgradeDropped && player.weaponUpgrades < 9) { // 50% chance for speed upgrade on 5th wave (only once per wave) if (Math.random() < 0.5) { var speedPickup = new Pickup('speed'); speedPickup.x = x; speedPickup.y = y; pickups.push(speedPickup); game.addChild(speedPickup); fifthWaveSpeedUpgradeDropped = true; return; // Exit early, no other drops this time } } // 25% chance to drop random upgrades (normal drops) if (Math.random() < 0.25) { var upgradeTypes = []; // Check wave-based upgrade limits var shieldDropsThisWave = waveUpgradeDrops.shield || 0; var weaponDropsThisWave = waveUpgradeDrops.weapon || 0; var speedDropsThisWave = waveUpgradeDrops.speed || 0; var armorDropsThisWave = waveUpgradeDrops.armor || 0; var healthDropsThisWave = waveUpgradeDrops.health || 0; // Shield upgrades: limited to 2 per wave if (shieldDropsThisWave < 2) { upgradeTypes.push('shield'); } // Speed upgrades now have 5% chance and only if player speed level is below 10 and limit 1 per wave if (player.weaponUpgrades < 9 && speedDropsThisWave < 1 && Math.random() < 0.05) { upgradeTypes.push('speed'); } // Only elite creeps can drop health potions and armor upgrades, limited to 1 per wave each if (isEliteCreep) { if (healthDropsThisWave < 1) { upgradeTypes.push('health'); } if (armorDropsThisWave < 1) { upgradeTypes.push('armor'); } } // Only add damage upgrades if weapon has not reached maximum level and limit 2 per wave if (player.weaponUpgrades < 49 && weaponDropsThisWave < 2) { upgradeTypes.push('damage'); } // Only add novaBlade to upgrade types if weapon has reached maximum level (50) with 25% chance and limit 2 per wave if (player.weaponUpgrades >= 49 && weaponDropsThisWave < 2 && Math.random() < 0.25) { // weaponUpgrades is 0-based, so 49 = level 50 upgradeTypes.push('novaBlade'); } // Only proceed if we have upgrade types available if (upgradeTypes.length > 0) { var randomType = upgradeTypes[Math.floor(Math.random() * upgradeTypes.length)]; var pickup = new Pickup(randomType); pickup.x = x; pickup.y = y; pickups.push(pickup); game.addChild(pickup); // Track upgrade drops for this wave if (randomType === 'shield') { waveUpgradeDrops.shield = (waveUpgradeDrops.shield || 0) + 1; } else if (randomType === 'damage') { waveUpgradeDrops.weapon = (waveUpgradeDrops.weapon || 0) + 1; } else if (randomType === 'speed') { waveUpgradeDrops.speed = (waveUpgradeDrops.speed || 0) + 1; } else if (randomType === 'armor') { waveUpgradeDrops.armor = (waveUpgradeDrops.armor || 0) + 1; } else if (randomType === 'health') { waveUpgradeDrops.health = (waveUpgradeDrops.health || 0) + 1; } else if (randomType === 'novaBlade') { waveUpgradeDrops.weapon = (waveUpgradeDrops.weapon || 0) + 1; } } } } function startNextWave() { currentWave++; // Reset 5th wave speed upgrade flag for new wave fifthWaveSpeedUpgradeDropped = false; // Reset wave upgrade drop counters waveUpgradeDrops = {}; // Check if this is a boss wave (every 10th wave) if (currentWave % 10 === 0) { bossWave = true; creepsInWave = 0; // No regular creeps on boss waves eliteCreepsInWave = 0; // No elite creeps on boss waves } else { bossWave = false; creepsInWave = 8 + Math.floor(currentWave * 2.5); // More creeps per wave // Add 2 extra creeps for every wave after wave 1 if (currentWave > 1) { creepsInWave += 2; } eliteCreepsInWave = currentWave; // Number of elite creeps equals wave number } creepsSpawned = 0; eliteCreepsSpawned = 0; waveCompleted = false; waveText.setText('Wave: ' + currentWave + (bossWave ? ' (BOSS)' : '')); } // Player movement variables var targetX = 1024; var targetY = 1366; var isMoving = false; game.down = function (x, y, obj) { if (!gameStarted) return; targetX = x; targetY = y; isMoving = true; }; game.move = function (x, y, obj) { if (!gameStarted) return; if (isMoving) { targetX = x; targetY = y; } }; game.up = function (x, y, obj) { if (!gameStarted) return; isMoving = false; }; game.update = function () { // Don't update game logic until game has started if (!gameStarted) return; // Process pickups FIRST to ensure upgrades always activate before any damage for (var i = pickups.length - 1; i >= 0; i--) { var pickup = pickups[i]; // Check pickup collection using both intersection and distance var dx = pickup.x - player.x; var dy = pickup.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if Nova blade effect is active and if pickup is within its range var novaBladeBlocking = false; if (player.novaBladeActive) { var playerChildren = player.children; for (var childIdx = 0; childIdx < playerChildren.length; childIdx++) { var child = playerChildren[childIdx]; // Check if Nova blade aura is currently active (expanding) if (child.tint === 0xFF6600 && child.scaleX > 1.0) { var auraRadius = player.novaBladeProximity; if (distance <= auraRadius) { novaBladeBlocking = true; break; } } } } var collected = !novaBladeBlocking && (pickup.intersects(player) || distance < 50); if (collected) { // Immediately activate upgrade effect and calculate points var pickupPoints = 150; // Default points switch (pickup.type) { case 'health': // Check if player is at full health before healing var wasAtFullHealth = player.health >= player.maxHealth; player.heal(50); // Boss health potions heal 50 hit points // Double points if player was at full health when picking up health potion if (wasAtFullHealth) { pickupPoints = 300; } break; case 'damage': player.addDamage(5); break; case 'speed': player.addSpeed(1); break; case 'shield': player.activateShield(); break; case 'armor': // Check if player is at full armor before adding armor var wasAtFullArmor = player.armor >= player.maxArmor; player.addArmor(50); // Boss armor upgrade gives 50 armor points // Double points if player was at full armor when picking up armor upgrade if (wasAtFullArmor) { pickupPoints = 300; } break; case 'novaBlade': player.activateNovaBlade(); break; } // Give points for collecting upgrades gameScore += pickupPoints; LK.setScore(LK.getScore() + pickupPoints); LK.getSound('pickup').play(); pickup.destroy(); pickups.splice(i, 1); } else if (pickup.lifetime <= 0) { pickup.destroy(); pickups.splice(i, 1); } } // Player movement if (isMoving) { var dx = targetX - player.x; var dy = targetY - player.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 25) { player.x += dx / distance * player.moveSpeed; player.y += dy / distance * player.moveSpeed; } } // Automatic shooting at closest creep if (creeps.length > 0 && player.canShoot()) { var closestCreep = null; var closestDistance = Infinity; for (var i = 0; i < creeps.length; i++) { var creep = creeps[i]; var dx = creep.x - player.x; var dy = creep.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestCreep = creep; } } if (closestCreep) { var shotBullets = player.shoot(closestCreep.x, closestCreep.y); if (shotBullets) { for (var k = 0; k < shotBullets.length; k++) { bullets.push(shotBullets[k]); game.addChild(shotBullets[k]); } } } } // Update shield system if (player.shieldActive) { player.shieldDuration--; if (player.shieldDuration <= 0 || player.shieldHealth <= 0) { player.shieldActive = false; player.shieldHealth = 0; player.shieldDuration = 0; if (player.shieldAura) { player.shieldAura.destroy(); player.shieldAura = null; } } else if (player.shieldAura) { // Animate shield aura player.shieldAura.rotation += 0.05; // Flash when shield is about to expire (last 5 seconds) if (player.shieldDuration < 300) { player.shieldAura.alpha = 0.3 + 0.3 * Math.sin(LK.ticks * 0.3); } } } // Update player health display healthText.setText('Health: ' + player.health); armorText.setText('Armor: ' + player.armor); // Display weapon level or max level indicator var weaponLevel = player.weaponUpgrades + 1; if (weaponLevel >= 50) { weaponLevelText.setText('Weapon Level: Max Level'); } else { weaponLevelText.setText('Weapon Level: ' + weaponLevel); } // Update weapon type display based on level var weaponLevel = player.weaponUpgrades + 1; var weaponType = 'Blaster'; if (weaponLevel >= 50) { weaponType = 'Hexa Shot'; } else if (weaponLevel >= 40) { weaponType = 'Penta Shot'; } else if (weaponLevel >= 30) { weaponType = 'Quad Shot'; } else if (weaponLevel >= 20) { weaponType = 'Triple Shot'; } else if (weaponLevel >= 10) { weaponType = 'Double Shot'; } weaponTypeText.setText(weaponType); // Update Nova blade level display if (player.novaBladeActive && player.novaBladeLevel > 0) { if (player.novaBladeLevel >= 50) { novaBladeText.setText('Nova blade level Max Level'); } else { novaBladeText.setText('Nova blade level ' + player.novaBladeLevel); } } else { novaBladeText.setText(''); } // Update score display scoreText.setText('Score: ' + gameScore); // Update shield display if (player.shieldActive) { var shieldTimeLeft = Math.ceil(player.shieldDuration / 60); shieldText.setText('Shield: ' + player.shieldHealth + '/' + player.shieldMaxHealth + ' (' + shieldTimeLeft + 's)'); } else { shieldText.setText(''); } // Update boss health bar if (currentBoss && bossHealthBar.alpha > 0) { var healthPercent = Math.round(currentBoss.health / currentBoss.maxHealth * 100); bossHealthBar.setText('BOSS: ' + currentBoss.health + '/' + currentBoss.maxHealth + ' (' + healthPercent + '%)'); } // Check game over if (player.health <= 0) { // Create explosion effect - player explodes in flames var playerGraphics = player.children[0]; // Get player graphics // Create flame explosion effect with multiple tween stages tween(playerGraphics, { scaleX: 2.0, scaleY: 2.0, tint: 0xFF4400, // Orange flame color alpha: 1.0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Second stage - bright red explosion tween(playerGraphics, { scaleX: 3.0, scaleY: 3.0, tint: 0xFF0000, // Bright red alpha: 0.8 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { // Final stage - fade to black and shrink tween(playerGraphics, { scaleX: 0.1, scaleY: 0.1, tint: 0x000000, // Black alpha: 0.0 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { // Show game over after explosion completes LK.showGameOver(); } }); } }); } }); return; } // Wave management if (!waveCompleted) { if (bossWave) { // Boss wave logic if (!currentBoss && creeps.length === 0) { spawnBoss(); } // Check if boss wave is complete if (currentBoss && creeps.length === 0) { waveCompleted = true; nextWaveTimer = 180; // 3 second break currentBoss = null; // Hide boss health bar tween(bossHealthBar, { alpha: 0 }, { duration: 500 }); } } else { // Regular wave logic // Spawn creeps if (creepsSpawned < creepsInWave || eliteCreepsSpawned < eliteCreepsInWave) { spawnTimer++; if (spawnTimer >= 60) { // Spawn every second if (creepsSpawned < creepsInWave) { spawnCreep(); creepsSpawned++; } else if (eliteCreepsSpawned < eliteCreepsInWave) { spawnEliteCreep(); eliteCreepsSpawned++; } spawnTimer = 0; } } // Check if wave is complete if (creepsSpawned >= creepsInWave && eliteCreepsSpawned >= eliteCreepsInWave && creeps.length === 0) { waveCompleted = true; nextWaveTimer = 180; // 3 second break } } } else { nextWaveTimer--; if (nextWaveTimer <= 0) { startNextWave(); } } // Update boss bullets for (var i = bossBullets.length - 1; i >= 0; i--) { var bossBullet = bossBullets[i]; // Check if boss bullet is off screen if (bossBullet.x < -50 || bossBullet.x > 2098 || bossBullet.y < -50 || bossBullet.y > 2782) { bossBullet.destroy(); bossBullets.splice(i, 1); continue; } // Check boss bullet collision with player if (bossBullet.intersects(player)) { player.takeDamage(bossBullet.damage); bossBullet.destroy(); bossBullets.splice(i, 1); } } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; // Check if bullet is off screen if (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782) { bullet.destroy(); bullets.splice(i, 1); continue; } // Check bullet-creep collisions var hitCreep = false; for (var j = creeps.length - 1; j >= 0; j--) { var creep = creeps[j]; if (bullet.intersects(creep)) { var isDead = creep.takeDamage(bullet.damage); if (isDead) { // Give points for killing creeps var scoreValue; if (creep instanceof BossCreep) { scoreValue = 1000; // High score for boss // Clean up spawned creeps when boss dies for (var spawnIdx = creep.spawnedCreeps.length - 1; spawnIdx >= 0; spawnIdx--) { var spawnedCreep = creep.spawnedCreeps[spawnIdx]; if (spawnedCreep) { // Find and remove from creeps array var creepIndex = creeps.indexOf(spawnedCreep); if (creepIndex !== -1) { spawnedCreep.destroy(); creeps.splice(creepIndex, 1); } } } // Boss drops one speed upgrade, one armor upgrade, one health potion upgrade, and one weapon upgrade var speedPickup = new Pickup('speed'); speedPickup.x = creep.x - 52.5; // Position far left with 35px spacing speedPickup.y = creep.y; pickups.push(speedPickup); game.addChild(speedPickup); var armorPickup = new Pickup('armor'); armorPickup.x = creep.x - 17.5; // Position left of center with 35px spacing armorPickup.y = creep.y; pickups.push(armorPickup); game.addChild(armorPickup); var healthPickup = new Pickup('health'); healthPickup.x = creep.x + 17.5; // Position right of center with 35px spacing healthPickup.y = creep.y; pickups.push(healthPickup); game.addChild(healthPickup); // Check player weapon level to determine weapon upgrade type var weaponPickup; if (player.weaponUpgrades >= 49) { // Replace weapon upgrade with nova blade upgrade if player is at level 50 weaponPickup = new Pickup('novaBlade'); } else { weaponPickup = new Pickup('damage'); } weaponPickup.x = creep.x + 52.5; // Position far right with 35px spacing weaponPickup.y = creep.y; pickups.push(weaponPickup); game.addChild(weaponPickup); } else if (creep instanceof EliteCreep) { scoreValue = 200; // Elite creep upgrade drop chance reset to 0% } else { scoreValue = 100; } gameScore += scoreValue; LK.setScore(LK.getScore() + scoreValue); if (!(creep instanceof BossCreep)) { dropPickup(creep.x, creep.y, creep instanceof EliteCreep); } creep.destroy(); creeps.splice(j, 1); } bullet.destroy(); bullets.splice(i, 1); hitCreep = true; break; } } } // Nova blade damage dealing if (player.novaBladeActive && LK.ticks - player.novaBladeLastActivation >= 300) { // Check if any aura is currently active by checking for expanding aura var playerChildren = player.children; var auraActive = false; for (var childIdx = 0; childIdx < playerChildren.length; childIdx++) { var child = playerChildren[childIdx]; // Check if this is an expanding aura (scale > 1.0 indicates expansion) if (child.tint === 0xFF6600 && child.scaleX > 1.0) { auraActive = true; var auraRadius = player.novaBladeProximity; // Use dynamic proximity range // Push all creeps away from player when Nova blade is active for (var creepIdx = creeps.length - 1; creepIdx >= 0; creepIdx--) { var creep = creeps[creepIdx]; var dx = creep.x - player.x; var dy = creep.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= auraRadius) { // Push creep away from player if (distance > 0) { var normalizedPushX = dx / distance; var normalizedPushY = dy / distance; var pushStrength = 50; // Strong push away from Nova blade tween(creep, { x: creep.x + normalizedPushX * pushStrength, y: creep.y + normalizedPushY * pushStrength }, { duration: 300, easing: tween.easeOut }); } var isDead = creep.takeDamage(25); // Fixed 25 damage if (isDead) { // Give points for killing creeps with Nova blade var scoreValue; if (creep instanceof BossCreep) { scoreValue = 1000; // Clean up spawned creeps when boss dies for (var spawnIdx = creep.spawnedCreeps.length - 1; spawnIdx >= 0; spawnIdx--) { var spawnedCreep = creep.spawnedCreeps[spawnIdx]; if (spawnedCreep) { var creepIndex = creeps.indexOf(spawnedCreep); if (creepIndex !== -1) { spawnedCreep.destroy(); creeps.splice(creepIndex, 1); } } } // Boss drops one speed upgrade, one armor upgrade, one health potion upgrade, and one weapon upgrade var speedPickup = new Pickup('speed'); speedPickup.x = creep.x - 52.5; // Position far left with 35px spacing speedPickup.y = creep.y; pickups.push(speedPickup); game.addChild(speedPickup); var armorPickup = new Pickup('armor'); armorPickup.x = creep.x - 17.5; // Position left of center with 35px spacing armorPickup.y = creep.y; pickups.push(armorPickup); game.addChild(armorPickup); var healthPickup = new Pickup('health'); healthPickup.x = creep.x + 17.5; // Position right of center with 35px spacing healthPickup.y = creep.y; pickups.push(healthPickup); game.addChild(healthPickup); // Check player weapon level to determine weapon upgrade type var weaponPickup; if (player.weaponUpgrades >= 49) { // Replace weapon upgrade with nova blade upgrade if player is at level 50 weaponPickup = new Pickup('novaBlade'); } else { weaponPickup = new Pickup('damage'); } weaponPickup.x = creep.x + 52.5; // Position far right with 35px spacing weaponPickup.y = creep.y; pickups.push(weaponPickup); game.addChild(weaponPickup); } else if (creep instanceof EliteCreep) { scoreValue = 200; // Elite creep upgrade drop chance reset to 0% } else { scoreValue = 100; } gameScore += scoreValue; LK.setScore(LK.getScore() + scoreValue); if (!(creep instanceof BossCreep)) { dropPickup(creep.x, creep.y, creep instanceof EliteCreep); } creep.destroy(); creeps.splice(creepIdx, 1); } } } // Disable pickup collection within Nova blade aura range during effect for (var pickupIdx = pickups.length - 1; pickupIdx >= 0; pickupIdx--) { var pickup = pickups[pickupIdx]; var pickupDx = pickup.x - player.x; var pickupDy = pickup.y - player.y; var pickupDistance = Math.sqrt(pickupDx * pickupDx + pickupDy * pickupDy); // Prevent pickup collection if within Nova blade effect range if (pickupDistance <= auraRadius) { // Disable magnetic pull effect during Nova blade aura pickup.lifetime = pickup.lifetime; // Just maintain lifetime without collection } } break; // Only process one active aura at a time } } } // Update creeps var _loop = function _loop() { creep = creeps[i]; // Check if creep reached player - adjust hit detection based on shield status canHitPlayer = false; // Always use distance-based hit detection for consistency dx = creep.x - player.x; dy = creep.y - player.y; distance = Math.sqrt(dx * dx + dy * dy); if (player.shieldActive) { // When shield is active, creeps must be much closer to hit canHitPlayer = distance < 25; // Very close hit distance when shield is active } else { // Normal hit detection when no shield - slightly increased threshold canHitPlayer = distance < 45; // Increased from 25 to 45 for better gameplay } if (canHitPlayer) { if (!creep.hasReachedPlayer) { player.takeDamage(creep.damage); creep.hasReachedPlayer = true; // Calculate bump direction (away from creep) bumpDx = player.x - creep.x; bumpDy = player.y - creep.y; bumpDistance = Math.sqrt(bumpDx * bumpDx + bumpDy * bumpDy); if (bumpDistance > 0) { normalizedBumpX = bumpDx / bumpDistance; normalizedBumpY = bumpDy / bumpDistance; bumpStrength = 30; // How far to bump the player // Bump the player away from the creep tween(player, { x: player.x + normalizedBumpX * bumpStrength, y: player.y + normalizedBumpY * bumpStrength }, { duration: 200, easing: tween.easeOut }); } // Make player invincible and add red flicker effect if (!player.invincible) { var _flicker = function flicker() { flickerCount++; if (flickerCount <= maxFlickers && player.invincible) { var playerGraphics = player.children[0]; // Get player graphics tween(playerGraphics, { tint: 0xFF0000, alpha: 0.5 }, { duration: 50, onFinish: function onFinish() { tween(playerGraphics, { tint: 0xFFFFFF, alpha: 1.0 }, { duration: 50, onFinish: function onFinish() { if (flickerCount < maxFlickers && player.invincible) { _flicker(); } } }); } }); } }; player.invincible = true; player.invincibilityTimer = 30; // 0.5 seconds at 60fps // Create flickering red effect during invincibility flickerCount = 0; maxFlickers = 10; _flicker(); } } // Don't destroy creep - it stays alive after hitting player } }, creep, canHitPlayer, dx, dy, distance, bumpDx, bumpDy, bumpDistance, normalizedBumpX, normalizedBumpY, bumpStrength, flickerCount, maxFlickers; for (var i = creeps.length - 1; i >= 0; i--) { _loop(); } // Pickup processing moved to beginning of update loop };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BossBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3; // Slow moving bullets
self.velocityX = 0;
self.velocityY = 0;
self.damage = 25;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
};
return self;
});
var BossCreep = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bossCreep', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 24000; // Double health for boss (60x increase)
self.maxHealth = 24000;
self.speed = 0.8; // Slower than elite creeps
self.damage = 50; // More damage than elite creeps
self.targetX = 0;
self.targetY = 0;
self.hasReachedPlayer = false;
self.spawnedCreeps = []; // Track spawned rotating creeps
self.lastSpawnTime = 0; // Track time for spawning
self.rotationAngle = 0; // Current rotation angle for spawned creeps
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
return true; // Return true if boss is dead
}
return false;
};
self.update = function () {
// Reset flag to allow continued movement after hitting player
self.hasReachedPlayer = false;
// Continuously update target to player's current position
self.targetX = player.x;
self.targetY = player.y;
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 3) {
// Calculate avoidance force from other creeps
var avoidanceX = 0;
var avoidanceY = 0;
var avoidanceRadius = 240; // Largest avoidance radius for boss creeps
for (var i = 0; i < creeps.length; i++) {
var otherCreep = creeps[i];
if (otherCreep !== self) {
var otherDx = self.x - otherCreep.x;
var otherDy = self.y - otherCreep.y;
var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy);
if (otherDistance < avoidanceRadius && otherDistance > 0) {
var avoidanceStrength = (avoidanceRadius - otherDistance) / avoidanceRadius;
avoidanceX += otherDx / otherDistance * avoidanceStrength;
avoidanceY += otherDy / otherDistance * avoidanceStrength;
}
}
}
// Combine movement towards player with avoidance
var moveX = dx / distance * self.speed + avoidanceX * 0.3;
var moveY = dy / distance * self.speed + avoidanceY * 0.3;
self.x += moveX;
self.y += moveY;
} else {
self.hasReachedPlayer = true;
}
// Spawn rotating creeps around boss every second (60 ticks)
if (LK.ticks - self.lastSpawnTime >= 60) {
self.lastSpawnTime = LK.ticks;
// Create a new creep that will rotate around the boss
var spawnedCreep = new Creep();
spawnedCreep.health = 40; // Less health than normal creeps
spawnedCreep.maxHealth = 40;
spawnedCreep.speed = 0; // They don't move on their own
spawnedCreep.damage = 10; // Less damage than normal creeps
spawnedCreep.bossParent = self; // Reference to boss
spawnedCreep.orbitRadius = 600; // Distance from boss center
spawnedCreep.orbitAngle = Math.random() * Math.PI * 2; // Random starting angle
// Position the creep initially
spawnedCreep.x = self.x + Math.cos(spawnedCreep.orbitAngle) * spawnedCreep.orbitRadius;
spawnedCreep.y = self.y + Math.sin(spawnedCreep.orbitAngle) * spawnedCreep.orbitRadius;
self.spawnedCreeps.push(spawnedCreep);
creeps.push(spawnedCreep);
game.addChild(spawnedCreep);
}
// Update rotation angle
self.rotationAngle += 0.02; // Rotation speed
// Update positions of spawned creeps to rotate around boss
for (var k = self.spawnedCreeps.length - 1; k >= 0; k--) {
var spawnedCreep = self.spawnedCreeps[k];
if (spawnedCreep && spawnedCreep.bossParent === self) {
// Update orbit angle
spawnedCreep.orbitAngle += 0.03; // Individual rotation speed
// Calculate new position around boss
spawnedCreep.x = self.x + Math.cos(spawnedCreep.orbitAngle) * spawnedCreep.orbitRadius;
spawnedCreep.y = self.y + Math.sin(spawnedCreep.orbitAngle) * spawnedCreep.orbitRadius;
// Override normal creep movement by setting hasReachedPlayer to prevent normal AI
spawnedCreep.hasReachedPlayer = false;
spawnedCreep.targetX = spawnedCreep.x;
spawnedCreep.targetY = spawnedCreep.y;
} else {
// Remove reference if creep was destroyed
self.spawnedCreeps.splice(k, 1);
}
}
// Boss bullet shooting every 5 seconds (300 ticks at 60fps)
if (LK.ticks % 300 === 0) {
// Calculate direction to player
var bulletDx = player.x - self.x;
var bulletDy = player.y - self.y;
var bulletDistance = Math.sqrt(bulletDx * bulletDx + bulletDy * bulletDy);
if (bulletDistance > 0) {
var normalizedX = bulletDx / bulletDistance;
var normalizedY = bulletDy / bulletDistance;
// Create 3 bullets in a burst pattern
for (var bulletIndex = 0; bulletIndex < 3; bulletIndex++) {
var bossBullet = new BossBullet();
bossBullet.x = self.x;
bossBullet.y = self.y;
// Slight angle variation for spread pattern
var angleOffset = (bulletIndex - 1) * 0.2; // -0.2, 0, 0.2 radians
var angle = Math.atan2(normalizedY, normalizedX) + angleOffset;
bossBullet.velocityX = Math.cos(angle) * bossBullet.speed;
bossBullet.velocityY = Math.sin(angle) * bossBullet.speed;
// Add to boss bullets array (will be created in game code)
bossBullets.push(bossBullet);
game.addChild(bossBullet);
}
}
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.velocityX = 0;
self.velocityY = 0;
self.damage = 20;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
};
return self;
});
var Creep = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('creep', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 60; // Base health requiring 3 hits (20 damage per hit)
self.maxHealth = 60;
self.speed = 2;
self.damage = 15;
self.targetX = 0;
self.targetY = 0;
self.hasReachedPlayer = false;
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
return true; // Return true if creep is dead
}
return false;
};
self.update = function () {
// Reset flag to allow continued movement after hitting player
self.hasReachedPlayer = false;
// Continuously update target to player's current position
self.targetX = player.x;
self.targetY = player.y;
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 25) {
// Calculate avoidance force from other creeps
var avoidanceX = 0;
var avoidanceY = 0;
var avoidanceRadius = 120; // Distance to avoid other creeps
for (var i = 0; i < creeps.length; i++) {
var otherCreep = creeps[i];
if (otherCreep !== self) {
var otherDx = self.x - otherCreep.x;
var otherDy = self.y - otherCreep.y;
var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy);
if (otherDistance < avoidanceRadius && otherDistance > 0) {
var avoidanceStrength = (avoidanceRadius - otherDistance) / avoidanceRadius;
avoidanceX += otherDx / otherDistance * avoidanceStrength;
avoidanceY += otherDy / otherDistance * avoidanceStrength;
}
}
}
// Combine movement towards player with avoidance
var moveX = dx / distance * self.speed + avoidanceX * 0.5;
var moveY = dy / distance * self.speed + avoidanceY * 0.5;
self.x += moveX;
self.y += moveY;
} else {
self.hasReachedPlayer = true;
}
};
return self;
});
var EliteCreep = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('eliteCreep', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 150; // Much higher base health
self.maxHealth = 150;
self.speed = 1.2; // Slower than regular creeps
self.damage = 35; // More damage than regular creeps
self.targetX = 0;
self.targetY = 0;
self.hasReachedPlayer = false;
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
return true; // Return true if creep is dead
}
return false;
};
self.update = function () {
// Reset flag to allow continued movement after hitting player
self.hasReachedPlayer = false;
// Continuously update target to player's current position
self.targetX = player.x;
self.targetY = player.y;
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 25) {
// Calculate avoidance force from other creeps
var avoidanceX = 0;
var avoidanceY = 0;
var avoidanceRadius = 180; // Larger avoidance radius for elite creeps
for (var i = 0; i < creeps.length; i++) {
var otherCreep = creeps[i];
if (otherCreep !== self) {
var otherDx = self.x - otherCreep.x;
var otherDy = self.y - otherCreep.y;
var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy);
if (otherDistance < avoidanceRadius && otherDistance > 0) {
var avoidanceStrength = (avoidanceRadius - otherDistance) / avoidanceRadius;
avoidanceX += otherDx / otherDistance * avoidanceStrength;
avoidanceY += otherDy / otherDistance * avoidanceStrength;
}
}
}
// Combine movement towards player with avoidance
var moveX = dx / distance * self.speed + avoidanceX * 0.5;
var moveY = dy / distance * self.speed + avoidanceY * 0.5;
self.x += moveX;
self.y += moveY;
} else {
self.hasReachedPlayer = true;
}
};
return self;
});
var Pickup = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
var assetName;
if (type === 'health') {
assetName = 'healthPotion';
} else if (type === 'damage') {
assetName = 'damageUpgrade';
} else if (type === 'speed') {
assetName = 'speedUpgrade'; // Use dedicated speed upgrade asset
} else if (type === 'attackSpeed') {
assetName = 'damageUpgrade'; // Reuse red upgrade for attack speed
} else if (type === 'shield') {
assetName = 'shieldUpgrade';
} else if (type === 'armor') {
assetName = 'armorUpgrade';
} else if (type === 'novaBlade') {
assetName = 'novaBlade';
}
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 3600; // 60 seconds at 60fps
self.update = function () {
self.lifetime--;
if (self.lifetime < 300) {
// Start fading at 5 seconds remaining
graphics.alpha = self.lifetime / 300;
}
// Magnetic pull effect when player is close
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Start pulling when within 150 pixels
if (distance < 150 && distance > 10) {
var pullSpeed = 8; // Fast pull speed
var normalizedX = dx / distance;
var normalizedY = dy / distance;
self.x += normalizedX * pullSpeed;
self.y += normalizedY * pullSpeed;
}
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
self.maxHealth = 100;
self.health = 100;
self.defense = 0;
self.armor = 0;
self.maxArmor = 100;
self.baseDamage = 20; // Base damage that never changes
self.damage = 20; // Current damage (base + upgrades)
self.weaponUpgrades = 0; // Track weapon upgrades for double shot
self.lastShootTime = 0;
self.shootCooldown = 40; // Fixed cooldown that never changes
self.baseSpeed = 4; // Base movement speed
self.moveSpeed = 4; // Current movement speed (base + upgrades)
self.shieldActive = false;
self.shieldHealth = 0;
self.shieldMaxHealth = 80;
self.shieldDuration = 0; // In game ticks (60 fps)
self.shieldAura = null;
self.invincible = false;
self.invincibilityTimer = 0;
self.novaBladeActive = false;
self.novaBladeTimer = 0;
self.novaBladeLastActivation = 0;
self.novaBladeLevel = 0;
self.novaBladeProximity = 300; // Base proximity
// Movement direction tracking
self.lastX = 0;
self.lastY = 0;
self.currentDirection = 0; // 0 = north (default)
self.takeDamage = function (amount) {
// If player is invincible, ignore all damage
if (self.invincible) {
return;
}
if (self.shieldActive && self.shieldHealth > 0) {
// Shield absorbs damage first
self.shieldHealth -= amount;
if (self.shieldHealth <= 0) {
// Shield broken
self.shieldHealth = 0;
self.shieldActive = false;
self.shieldDuration = 0;
if (self.shieldAura) {
self.shieldAura.destroy();
self.shieldAura = null;
}
}
// Flash blue when shield takes damage
tween(graphics, {
tint: 0x4444FF
}, {
duration: 100,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
});
} else if (self.armor > 0) {
// Armor absorbs damage second
self.armor -= amount;
if (self.armor < 0) {
// Overflow damage goes to health
self.health += self.armor; // armor is negative, so this subtracts from health
self.armor = 0;
}
if (self.health < 0) self.health = 0;
// Flash yellow when armor takes damage
tween(graphics, {
tint: 0xFFDD00
}, {
duration: 100,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
});
} else {
// Normal damage to health last
self.health -= amount;
if (self.health < 0) self.health = 0;
// Flash red when taking damage
tween(graphics, {
tint: 0xFF0000
}, {
duration: 100,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
});
}
LK.getSound('hit').play();
};
self.heal = function (amount) {
self.health = Math.min(self.maxHealth, self.health + amount);
};
self.addDamage = function (amount) {
self.damage += amount;
self.weaponUpgrades++; // Track weapon upgrades
};
self.addSpeed = function (amount) {
// Cap movement speed upgrades at level 25 (24 upgrades from base level 1)
if (self.weaponUpgrades < 24) {
self.moveSpeed += amount;
}
};
self.addArmor = function (amount) {
self.armor = Math.min(100, self.armor + amount);
};
self.activateNovaBlade = function () {
if (!self.novaBladeActive) {
self.novaBladeActive = true;
self.novaBladeLastActivation = LK.ticks;
self.novaBladeLevel = 1;
self.novaBladeProximity = 300;
} else if (self.novaBladeLevel < 50) {
// Increase Nova blade level and proximity up to level 50
self.novaBladeLevel++;
self.novaBladeProximity += 10;
}
};
self.activateShield = function () {
if (!self.shieldActive) {
// First time activation
self.shieldActive = true;
self.shieldHealth = 40; // Start with 40 hit points
self.shieldDuration = 3600; // 60 seconds at 60fps
// Create shield visual
self.shieldAura = self.addChild(LK.getAsset('shieldAura', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0x6666FF
}));
} else {
// Shield already active, add duration up to max 180 seconds
self.shieldDuration = Math.min(10800, self.shieldDuration + 3600); // Max 180 seconds (60 seconds per upgrade)
// Add shield health up to max 80 hit points
self.shieldHealth = Math.min(80, self.shieldHealth + 40);
// Ensure shield visual is displayed
if (!self.shieldAura) {
self.shieldAura = self.addChild(LK.getAsset('shieldAura', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0x6666FF
}));
}
}
};
self.canShoot = function () {
return LK.ticks - self.lastShootTime > self.shootCooldown;
};
self.shoot = function (targetX, targetY) {
if (!self.canShoot()) return null;
self.lastShootTime = LK.ticks;
var bullets = [];
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var normalizedX = dx / distance;
var normalizedY = dy / distance;
var angle = Math.atan2(normalizedY, normalizedX);
if (self.weaponUpgrades >= 49) {
// Level 50+: Six shots in burst fire pattern (Hexa Shot)
var offsetDistance = 30;
var perpX = -normalizedY;
var perpY = normalizedX;
// First bullet (left side, front)
var bullet1 = new Bullet();
bullet1.x = self.x + perpX * offsetDistance;
bullet1.y = self.y + perpY * offsetDistance;
bullet1.velocityX = normalizedX * bullet1.speed;
bullet1.velocityY = normalizedY * bullet1.speed;
bullet1.damage = self.damage;
bullets.push(bullet1);
// Second bullet (left side, back)
var bullet2 = new Bullet();
bullet2.x = self.x + perpX * offsetDistance - normalizedX * 25;
bullet2.y = self.y + perpY * offsetDistance - normalizedY * 25;
bullet2.velocityX = normalizedX * bullet2.speed;
bullet2.velocityY = normalizedY * bullet2.speed;
bullet2.damage = self.damage;
bullets.push(bullet2);
// Third bullet (right side, front)
var bullet3 = new Bullet();
bullet3.x = self.x - perpX * offsetDistance;
bullet3.y = self.y - perpY * offsetDistance;
bullet3.velocityX = normalizedX * bullet3.speed;
bullet3.velocityY = normalizedY * bullet3.speed;
bullet3.damage = self.damage;
bullets.push(bullet3);
// Fourth bullet (right side, back)
var bullet4 = new Bullet();
bullet4.x = self.x - perpX * offsetDistance - normalizedX * 25;
bullet4.y = self.y - perpY * offsetDistance - normalizedY * 25;
bullet4.velocityX = normalizedX * bullet4.speed;
bullet4.velocityY = normalizedY * bullet4.speed;
bullet4.damage = self.damage;
bullets.push(bullet4);
// Fifth bullet (center shot)
var bullet5 = new Bullet();
bullet5.x = self.x;
bullet5.y = self.y;
bullet5.velocityX = normalizedX * bullet5.speed;
bullet5.velocityY = normalizedY * bullet5.speed;
bullet5.damage = self.damage;
bullets.push(bullet5);
// Sixth bullet (center shot, back)
var bullet6 = new Bullet();
bullet6.x = self.x - normalizedX * 25;
bullet6.y = self.y - normalizedY * 25;
bullet6.velocityX = normalizedX * bullet6.speed;
bullet6.velocityY = normalizedY * bullet6.speed;
bullet6.damage = self.damage;
bullets.push(bullet6);
} else if (self.weaponUpgrades >= 39) {
// Level 40+: Five shots in burst fire (two double shots + center shot)
var offsetDistance = 25;
var perpX = -normalizedY;
var perpY = normalizedX;
// First bullet (left side, front)
var bullet1 = new Bullet();
bullet1.x = self.x + perpX * offsetDistance;
bullet1.y = self.y + perpY * offsetDistance;
bullet1.velocityX = normalizedX * bullet1.speed;
bullet1.velocityY = normalizedY * bullet1.speed;
bullet1.damage = self.damage;
bullets.push(bullet1);
// Second bullet (left side, back)
var bullet2 = new Bullet();
bullet2.x = self.x + perpX * offsetDistance - normalizedX * 20;
bullet2.y = self.y + perpY * offsetDistance - normalizedY * 20;
bullet2.velocityX = normalizedX * bullet2.speed;
bullet2.velocityY = normalizedY * bullet2.speed;
bullet2.damage = self.damage;
bullets.push(bullet2);
// Third bullet (right side, front)
var bullet3 = new Bullet();
bullet3.x = self.x - perpX * offsetDistance;
bullet3.y = self.y - perpY * offsetDistance;
bullet3.velocityX = normalizedX * bullet3.speed;
bullet3.velocityY = normalizedY * bullet3.speed;
bullet3.damage = self.damage;
bullets.push(bullet3);
// Fourth bullet (right side, back)
var bullet4 = new Bullet();
bullet4.x = self.x - perpX * offsetDistance - normalizedX * 20;
bullet4.y = self.y - perpY * offsetDistance - normalizedY * 20;
bullet4.velocityX = normalizedX * bullet4.speed;
bullet4.velocityY = normalizedY * bullet4.speed;
bullet4.damage = self.damage;
bullets.push(bullet4);
// Fifth bullet (center shot)
var bullet5 = new Bullet();
bullet5.x = self.x;
bullet5.y = self.y;
bullet5.velocityX = normalizedX * bullet5.speed;
bullet5.velocityY = normalizedY * bullet5.speed;
bullet5.damage = self.damage;
bullets.push(bullet5);
} else if (self.weaponUpgrades >= 29) {
// Level 30+: Four shots in burst fire (two double shots)
var offsetDistance = 25;
var perpX = -normalizedY;
var perpY = normalizedX;
// First bullet (left side, front)
var bullet1 = new Bullet();
bullet1.x = self.x + perpX * offsetDistance;
bullet1.y = self.y + perpY * offsetDistance;
bullet1.velocityX = normalizedX * bullet1.speed;
bullet1.velocityY = normalizedY * bullet1.speed;
bullet1.damage = self.damage;
bullets.push(bullet1);
// Second bullet (left side, back)
var bullet2 = new Bullet();
bullet2.x = self.x + perpX * offsetDistance - normalizedX * 20;
bullet2.y = self.y + perpY * offsetDistance - normalizedY * 20;
bullet2.velocityX = normalizedX * bullet2.speed;
bullet2.velocityY = normalizedY * bullet2.speed;
bullet2.damage = self.damage;
bullets.push(bullet2);
// Third bullet (right side, front)
var bullet3 = new Bullet();
bullet3.x = self.x - perpX * offsetDistance;
bullet3.y = self.y - perpY * offsetDistance;
bullet3.velocityX = normalizedX * bullet3.speed;
bullet3.velocityY = normalizedY * bullet3.speed;
bullet3.damage = self.damage;
bullets.push(bullet3);
// Fourth bullet (right side, back)
var bullet4 = new Bullet();
bullet4.x = self.x - perpX * offsetDistance - normalizedX * 20;
bullet4.y = self.y - perpY * offsetDistance - normalizedY * 20;
bullet4.velocityX = normalizedX * bullet4.speed;
bullet4.velocityY = normalizedY * bullet4.speed;
bullet4.damage = self.damage;
bullets.push(bullet4);
} else if (self.weaponUpgrades >= 19) {
// Level 20+: Triple shot (3 parallel shots)
var offsetDistance = 15;
var perpX = -normalizedY;
var perpY = normalizedX;
// Center bullet
var bullet1 = new Bullet();
bullet1.x = self.x;
bullet1.y = self.y;
bullet1.velocityX = normalizedX * bullet1.speed;
bullet1.velocityY = normalizedY * bullet1.speed;
bullet1.damage = self.damage;
bullets.push(bullet1);
// Left bullet (parallel)
var bullet2 = new Bullet();
bullet2.x = self.x + perpX * offsetDistance;
bullet2.y = self.y + perpY * offsetDistance;
bullet2.velocityX = normalizedX * bullet2.speed;
bullet2.velocityY = normalizedY * bullet2.speed;
bullet2.damage = self.damage;
bullets.push(bullet2);
// Right bullet (parallel)
var bullet3 = new Bullet();
bullet3.x = self.x - perpX * offsetDistance;
bullet3.y = self.y - perpY * offsetDistance;
bullet3.velocityX = normalizedX * bullet3.speed;
bullet3.velocityY = normalizedY * bullet3.speed;
bullet3.damage = self.damage;
bullets.push(bullet3);
} else if (self.weaponUpgrades >= 9) {
// Level 10+: Double shot
var offsetDistance = 20;
var perpX = -normalizedY;
var perpY = normalizedX;
// First bullet (left side)
var bullet1 = new Bullet();
bullet1.x = self.x + perpX * offsetDistance;
bullet1.y = self.y + perpY * offsetDistance;
bullet1.velocityX = normalizedX * bullet1.speed;
bullet1.velocityY = normalizedY * bullet1.speed;
bullet1.damage = self.damage;
bullets.push(bullet1);
// Second bullet (right side)
var bullet2 = new Bullet();
bullet2.x = self.x - perpX * offsetDistance;
bullet2.y = self.y - perpY * offsetDistance;
bullet2.velocityX = normalizedX * bullet2.speed;
bullet2.velocityY = normalizedY * bullet2.speed;
bullet2.damage = self.damage;
bullets.push(bullet2);
} else {
// Single shot for levels < 10
var bullet1 = new Bullet();
bullet1.x = self.x;
bullet1.y = self.y;
bullet1.velocityX = normalizedX * bullet1.speed;
bullet1.velocityY = normalizedY * bullet1.speed;
bullet1.damage = self.damage;
bullets.push(bullet1);
}
LK.getSound('shoot').play();
return bullets;
};
self.update = function () {
// Track movement direction and animate rotation
if (self.lastX !== undefined && self.lastY !== undefined) {
var deltaX = self.x - self.lastX;
var deltaY = self.y - self.lastY;
var moveDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Only update direction if player is actually moving
if (moveDistance > 0.5) {
var targetDirection = Math.atan2(deltaY, deltaX);
// Convert to 0-360 degree system where 0 is north
targetDirection = targetDirection + Math.PI / 2;
if (targetDirection < 0) targetDirection += Math.PI * 2;
if (targetDirection > Math.PI * 2) targetDirection -= Math.PI * 2;
// Smoothly rotate to face movement direction
var currentRotation = graphics.rotation;
var angleDiff = targetDirection - currentRotation;
// Handle wrap around for shortest rotation path
if (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
if (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
// Apply smooth rotation
var rotationSpeed = 0.15;
graphics.rotation += angleDiff * rotationSpeed;
}
}
// Update invincibility timer
if (self.invincible) {
self.invincibilityTimer--;
if (self.invincibilityTimer <= 0) {
self.invincible = false;
// Stop any tinting when invincibility ends
tween.stop(graphics, {
tint: true
});
graphics.tint = 0xFFFFFF;
graphics.alpha = 1.0;
}
}
// Update Nova blade timer and activation
if (self.novaBladeActive) {
// Check if 5 seconds (300 ticks) have passed since last activation
if (LK.ticks - self.novaBladeLastActivation >= 300) {
// Create expanding nova blade effect
var aura = LK.getAsset('novaBladeEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
tint: 0xFF6600,
scaleX: 0.1,
scaleY: 0.1
});
self.addChild(aura);
// Knock back all creeps within proximity radius immediately
for (var creepIdx = 0; creepIdx < creeps.length; creepIdx++) {
var creep = creeps[creepIdx];
var dx = creep.x - self.x;
var dy = creep.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.novaBladeProximity) {
// Calculate knockback direction
if (distance > 0) {
var normalizedX = dx / distance;
var normalizedY = dy / distance;
var knockbackDistance = self.novaBladeProximity - distance + 50; // Push them outside the radius
// Apply knockback using tween for smooth animation
tween(creep, {
x: creep.x + normalizedX * knockbackDistance,
y: creep.y + normalizedY * knockbackDistance
}, {
duration: 500,
easing: tween.easeOut
});
}
}
}
// Animate aura expansion from proximity 10 to current Nova blade proximity level
var maxScale = self.novaBladeProximity / 80; // Convert proximity to scale (80 is base aura size)
tween(aura, {
scaleX: maxScale,
scaleY: maxScale,
alpha: 0.0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
aura.destroy();
}
});
// Reset timer for next activation
self.novaBladeLastActivation = LK.ticks;
}
}
// Update last position for next frame
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
title: 'Galaxy Of Terror'
});
/****
* Game Code
****/
// Add background image
var background = game.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
// Game state variables
var gameStarted = false;
var startScreen = null;
// Create start screen
function createStartScreen() {
startScreen = new Container();
game.addChild(startScreen);
// Start screen background
var startBg = startScreen.addChild(LK.getAsset('startBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.9,
tint: 0x001122
}));
// Game title
var titleText = new Text2('GALAXY OF TERROR', {
size: 120,
fill: 0xFFD700
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
startScreen.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Survive the endless waves', {
size: 60,
fill: 0xFFFFFF
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 1024;
subtitleText.y = 920;
startScreen.addChild(subtitleText);
// Play button background
var playButton = startScreen.addChild(LK.getAsset('startButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1200,
scaleX: 1.5,
scaleY: 1.2,
tint: 0x00AA44
}));
// Play button text
var playButtonText = new Text2('TAP TO PLAY', {
size: 80,
fill: 0xFFFFFF
});
playButtonText.anchor.set(0.5, 0.5);
playButtonText.x = 1024;
playButtonText.y = 1200;
startScreen.addChild(playButtonText);
// Instructions
var instructText = new Text2('Tap and drag to move\nAutomatic shooting', {
size: 50,
fill: 0xCCCCCC
});
instructText.anchor.set(0.5, 0.5);
instructText.x = 1024;
instructText.y = 1400;
startScreen.addChild(instructText);
// Handle start screen input
startScreen.down = function (x, y, obj) {
// Check if clicked on play button area
var buttonDx = x - 1024;
var buttonDy = y - 1200;
var buttonDistance = Math.sqrt(buttonDx * buttonDx + buttonDy * buttonDy);
if (buttonDistance < 150) {
startGame();
}
};
}
function startGame() {
if (startScreen) {
startScreen.destroy();
startScreen = null;
}
gameStarted = true;
// Initialize player after game starts
player = game.addChild(new Player());
player.x = 1024;
player.y = 1366;
}
// Initialize start screen
createStartScreen();
var player = null;
var creeps = [];
var bullets = [];
var bossBullets = [];
var pickups = [];
var currentWave = 1;
var creepsInWave = 5;
var creepsSpawned = 0;
var eliteCreepsInWave = 1; // Number of elite creeps equals wave number
var eliteCreepsSpawned = 0;
var waveCompleted = false;
var nextWaveTimer = 0;
var spawnTimer = 0;
// Score tracking variables
var gameScore = 0;
// Boss system variables
var currentBoss = null;
var bossWave = false;
// Speed modifier for creeps - increases 1% every 5th wave
var creepSpeedMultiplier = 1.0;
// Track special 5th wave speed upgrade drops
var fifthWaveSpeedUpgradeDropped = false;
// Track upgrade drops per wave
var waveUpgradeDrops = {};
// UI Elements
var healthText = new Text2('Health: 100', {
size: 40,
fill: 0xFF0000
});
healthText.anchor.set(0, 0);
LK.gui.topLeft.addChild(healthText);
healthText.x = 120; // Offset from left edge
var armorText = new Text2('Armor: 0', {
size: 40,
fill: 0x00FF00
});
armorText.anchor.set(0, 0);
LK.gui.topLeft.addChild(armorText);
armorText.x = 120; // Offset from left edge
armorText.y = 45; // Below health text
var waveText = new Text2('Wave: 1', {
size: 40,
fill: 0xFFD700
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
var weaponLevelText = new Text2('Weapon Level: 1', {
size: 40,
fill: 0xFFFFFF
});
weaponLevelText.anchor.set(1, 0);
LK.gui.topRight.addChild(weaponLevelText);
var weaponTypeText = new Text2('Blaster', {
size: 40,
fill: 0xFFFFFF
});
weaponTypeText.anchor.set(1, 0);
weaponTypeText.y = 45; // Position below weapon level text
LK.gui.topRight.addChild(weaponTypeText);
// Nova blade level text
var novaBladeText = new Text2('', {
size: 40,
fill: 0xFF6600
});
novaBladeText.anchor.set(1, 0);
novaBladeText.y = 90; // Position below weapon type text
LK.gui.topRight.addChild(novaBladeText);
// Score counter text
var scoreText = new Text2('Score: 0', {
size: 40,
fill: 0xFF1493 // Bright pink color
});
scoreText.anchor.set(1, 0);
scoreText.y = 135; // Position below Nova blade level text
LK.gui.topRight.addChild(scoreText);
// Boss health bar (initially hidden)
var bossHealthBar = new Text2('', {
size: 35,
fill: 0xFF4444
});
bossHealthBar.anchor.set(0.5, 0);
LK.gui.top.addChild(bossHealthBar);
bossHealthBar.y = 50;
bossHealthBar.alpha = 0; // Hidden initially
// Shield status text
var shieldText = new Text2('', {
size: 35,
fill: 0x4444FF
});
shieldText.anchor.set(0, 0);
LK.gui.topLeft.addChild(shieldText);
shieldText.x = 120; // Offset from left edge
shieldText.y = 90; // Below armor text
// Player will be initialized when game starts
function spawnCreep() {
var creep = new Creep();
// Spawn from random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
creep.x = Math.random() * 2048;
creep.y = -50;
break;
case 1:
// Right
creep.x = 2098;
creep.y = Math.random() * 2732;
break;
case 2:
// Bottom
creep.x = Math.random() * 2048;
creep.y = 2782;
break;
case 3:
// Left
creep.x = -50;
creep.y = Math.random() * 2732;
break;
}
creep.targetX = player.x;
creep.targetY = player.y;
creep.health += (currentWave - 1) * 20; // Scale health with wave (20 per wave)
// Apply 50% health increase every 5th wave
var healthMultiplier = 1 + Math.floor(currentWave / 5) * 0.5;
creep.health = Math.floor(creep.health * healthMultiplier);
creep.maxHealth = creep.health;
// Apply speed increase - 1% for every 5th wave
var speedMultiplier = 1 + Math.floor(currentWave / 5) * 0.01;
creep.speed = creep.speed * speedMultiplier;
creeps.push(creep);
game.addChild(creep);
}
function spawnEliteCreep() {
var eliteCreep = new EliteCreep();
// Spawn from random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
eliteCreep.x = Math.random() * 2048;
eliteCreep.y = -50;
break;
case 1:
// Right
eliteCreep.x = 2098;
eliteCreep.y = Math.random() * 2732;
break;
case 2:
// Bottom
eliteCreep.x = Math.random() * 2048;
eliteCreep.y = 2782;
break;
case 3:
// Left
eliteCreep.x = -50;
eliteCreep.y = Math.random() * 2732;
break;
}
eliteCreep.targetX = player.x;
eliteCreep.targetY = player.y;
eliteCreep.health += (currentWave - 1) * 40; // Scale health with wave (40 per wave for elites)
// Apply 50% health increase every 5th wave
var healthMultiplier = 1 + Math.floor(currentWave / 5) * 0.5;
eliteCreep.health = Math.floor(eliteCreep.health * healthMultiplier);
eliteCreep.maxHealth = eliteCreep.health;
// Apply speed increase - 1% for every 5th wave
var speedMultiplier = 1 + Math.floor(currentWave / 5) * 0.01;
eliteCreep.speed = eliteCreep.speed * speedMultiplier;
creeps.push(eliteCreep);
game.addChild(eliteCreep);
}
function spawnBoss() {
var boss = new BossCreep();
// Spawn from random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
boss.x = Math.random() * 2048;
boss.y = -75;
break;
case 1:
// Right
boss.x = 2123;
boss.y = Math.random() * 2732;
break;
case 2:
// Bottom
boss.x = Math.random() * 2048;
boss.y = 2807;
break;
case 3:
// Left
boss.x = -75;
boss.y = Math.random() * 2732;
break;
}
boss.targetX = player.x;
boss.targetY = player.y;
// Calculate boss health based on wave (doubles each boss wave)
var bossNumber = Math.floor(currentWave / 10);
boss.health = 24000 * Math.pow(2, bossNumber - 1);
// Apply 50% health increase every 5th wave
var healthMultiplier = 1 + Math.floor(currentWave / 5) * 0.5;
boss.health = Math.floor(boss.health * healthMultiplier);
boss.maxHealth = boss.health;
currentBoss = boss;
creeps.push(boss);
game.addChild(boss);
// Show boss health bar
bossHealthBar.alpha = 1;
tween(bossHealthBar, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(bossHealthBar, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
function dropPickup(x, y, isEliteCreep) {
// Check for special 5th wave speed upgrade drop first
var isFifthWave = currentWave % 5 === 0;
if (isFifthWave && !fifthWaveSpeedUpgradeDropped && player.weaponUpgrades < 9) {
// 50% chance for speed upgrade on 5th wave (only once per wave)
if (Math.random() < 0.5) {
var speedPickup = new Pickup('speed');
speedPickup.x = x;
speedPickup.y = y;
pickups.push(speedPickup);
game.addChild(speedPickup);
fifthWaveSpeedUpgradeDropped = true;
return; // Exit early, no other drops this time
}
}
// 25% chance to drop random upgrades (normal drops)
if (Math.random() < 0.25) {
var upgradeTypes = [];
// Check wave-based upgrade limits
var shieldDropsThisWave = waveUpgradeDrops.shield || 0;
var weaponDropsThisWave = waveUpgradeDrops.weapon || 0;
var speedDropsThisWave = waveUpgradeDrops.speed || 0;
var armorDropsThisWave = waveUpgradeDrops.armor || 0;
var healthDropsThisWave = waveUpgradeDrops.health || 0;
// Shield upgrades: limited to 2 per wave
if (shieldDropsThisWave < 2) {
upgradeTypes.push('shield');
}
// Speed upgrades now have 5% chance and only if player speed level is below 10 and limit 1 per wave
if (player.weaponUpgrades < 9 && speedDropsThisWave < 1 && Math.random() < 0.05) {
upgradeTypes.push('speed');
}
// Only elite creeps can drop health potions and armor upgrades, limited to 1 per wave each
if (isEliteCreep) {
if (healthDropsThisWave < 1) {
upgradeTypes.push('health');
}
if (armorDropsThisWave < 1) {
upgradeTypes.push('armor');
}
}
// Only add damage upgrades if weapon has not reached maximum level and limit 2 per wave
if (player.weaponUpgrades < 49 && weaponDropsThisWave < 2) {
upgradeTypes.push('damage');
}
// Only add novaBlade to upgrade types if weapon has reached maximum level (50) with 25% chance and limit 2 per wave
if (player.weaponUpgrades >= 49 && weaponDropsThisWave < 2 && Math.random() < 0.25) {
// weaponUpgrades is 0-based, so 49 = level 50
upgradeTypes.push('novaBlade');
}
// Only proceed if we have upgrade types available
if (upgradeTypes.length > 0) {
var randomType = upgradeTypes[Math.floor(Math.random() * upgradeTypes.length)];
var pickup = new Pickup(randomType);
pickup.x = x;
pickup.y = y;
pickups.push(pickup);
game.addChild(pickup);
// Track upgrade drops for this wave
if (randomType === 'shield') {
waveUpgradeDrops.shield = (waveUpgradeDrops.shield || 0) + 1;
} else if (randomType === 'damage') {
waveUpgradeDrops.weapon = (waveUpgradeDrops.weapon || 0) + 1;
} else if (randomType === 'speed') {
waveUpgradeDrops.speed = (waveUpgradeDrops.speed || 0) + 1;
} else if (randomType === 'armor') {
waveUpgradeDrops.armor = (waveUpgradeDrops.armor || 0) + 1;
} else if (randomType === 'health') {
waveUpgradeDrops.health = (waveUpgradeDrops.health || 0) + 1;
} else if (randomType === 'novaBlade') {
waveUpgradeDrops.weapon = (waveUpgradeDrops.weapon || 0) + 1;
}
}
}
}
function startNextWave() {
currentWave++;
// Reset 5th wave speed upgrade flag for new wave
fifthWaveSpeedUpgradeDropped = false;
// Reset wave upgrade drop counters
waveUpgradeDrops = {};
// Check if this is a boss wave (every 10th wave)
if (currentWave % 10 === 0) {
bossWave = true;
creepsInWave = 0; // No regular creeps on boss waves
eliteCreepsInWave = 0; // No elite creeps on boss waves
} else {
bossWave = false;
creepsInWave = 8 + Math.floor(currentWave * 2.5); // More creeps per wave
// Add 2 extra creeps for every wave after wave 1
if (currentWave > 1) {
creepsInWave += 2;
}
eliteCreepsInWave = currentWave; // Number of elite creeps equals wave number
}
creepsSpawned = 0;
eliteCreepsSpawned = 0;
waveCompleted = false;
waveText.setText('Wave: ' + currentWave + (bossWave ? ' (BOSS)' : ''));
}
// Player movement variables
var targetX = 1024;
var targetY = 1366;
var isMoving = false;
game.down = function (x, y, obj) {
if (!gameStarted) return;
targetX = x;
targetY = y;
isMoving = true;
};
game.move = function (x, y, obj) {
if (!gameStarted) return;
if (isMoving) {
targetX = x;
targetY = y;
}
};
game.up = function (x, y, obj) {
if (!gameStarted) return;
isMoving = false;
};
game.update = function () {
// Don't update game logic until game has started
if (!gameStarted) return;
// Process pickups FIRST to ensure upgrades always activate before any damage
for (var i = pickups.length - 1; i >= 0; i--) {
var pickup = pickups[i];
// Check pickup collection using both intersection and distance
var dx = pickup.x - player.x;
var dy = pickup.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if Nova blade effect is active and if pickup is within its range
var novaBladeBlocking = false;
if (player.novaBladeActive) {
var playerChildren = player.children;
for (var childIdx = 0; childIdx < playerChildren.length; childIdx++) {
var child = playerChildren[childIdx];
// Check if Nova blade aura is currently active (expanding)
if (child.tint === 0xFF6600 && child.scaleX > 1.0) {
var auraRadius = player.novaBladeProximity;
if (distance <= auraRadius) {
novaBladeBlocking = true;
break;
}
}
}
}
var collected = !novaBladeBlocking && (pickup.intersects(player) || distance < 50);
if (collected) {
// Immediately activate upgrade effect and calculate points
var pickupPoints = 150; // Default points
switch (pickup.type) {
case 'health':
// Check if player is at full health before healing
var wasAtFullHealth = player.health >= player.maxHealth;
player.heal(50); // Boss health potions heal 50 hit points
// Double points if player was at full health when picking up health potion
if (wasAtFullHealth) {
pickupPoints = 300;
}
break;
case 'damage':
player.addDamage(5);
break;
case 'speed':
player.addSpeed(1);
break;
case 'shield':
player.activateShield();
break;
case 'armor':
// Check if player is at full armor before adding armor
var wasAtFullArmor = player.armor >= player.maxArmor;
player.addArmor(50); // Boss armor upgrade gives 50 armor points
// Double points if player was at full armor when picking up armor upgrade
if (wasAtFullArmor) {
pickupPoints = 300;
}
break;
case 'novaBlade':
player.activateNovaBlade();
break;
}
// Give points for collecting upgrades
gameScore += pickupPoints;
LK.setScore(LK.getScore() + pickupPoints);
LK.getSound('pickup').play();
pickup.destroy();
pickups.splice(i, 1);
} else if (pickup.lifetime <= 0) {
pickup.destroy();
pickups.splice(i, 1);
}
}
// Player movement
if (isMoving) {
var dx = targetX - player.x;
var dy = targetY - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 25) {
player.x += dx / distance * player.moveSpeed;
player.y += dy / distance * player.moveSpeed;
}
}
// Automatic shooting at closest creep
if (creeps.length > 0 && player.canShoot()) {
var closestCreep = null;
var closestDistance = Infinity;
for (var i = 0; i < creeps.length; i++) {
var creep = creeps[i];
var dx = creep.x - player.x;
var dy = creep.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestCreep = creep;
}
}
if (closestCreep) {
var shotBullets = player.shoot(closestCreep.x, closestCreep.y);
if (shotBullets) {
for (var k = 0; k < shotBullets.length; k++) {
bullets.push(shotBullets[k]);
game.addChild(shotBullets[k]);
}
}
}
}
// Update shield system
if (player.shieldActive) {
player.shieldDuration--;
if (player.shieldDuration <= 0 || player.shieldHealth <= 0) {
player.shieldActive = false;
player.shieldHealth = 0;
player.shieldDuration = 0;
if (player.shieldAura) {
player.shieldAura.destroy();
player.shieldAura = null;
}
} else if (player.shieldAura) {
// Animate shield aura
player.shieldAura.rotation += 0.05;
// Flash when shield is about to expire (last 5 seconds)
if (player.shieldDuration < 300) {
player.shieldAura.alpha = 0.3 + 0.3 * Math.sin(LK.ticks * 0.3);
}
}
}
// Update player health display
healthText.setText('Health: ' + player.health);
armorText.setText('Armor: ' + player.armor);
// Display weapon level or max level indicator
var weaponLevel = player.weaponUpgrades + 1;
if (weaponLevel >= 50) {
weaponLevelText.setText('Weapon Level: Max Level');
} else {
weaponLevelText.setText('Weapon Level: ' + weaponLevel);
}
// Update weapon type display based on level
var weaponLevel = player.weaponUpgrades + 1;
var weaponType = 'Blaster';
if (weaponLevel >= 50) {
weaponType = 'Hexa Shot';
} else if (weaponLevel >= 40) {
weaponType = 'Penta Shot';
} else if (weaponLevel >= 30) {
weaponType = 'Quad Shot';
} else if (weaponLevel >= 20) {
weaponType = 'Triple Shot';
} else if (weaponLevel >= 10) {
weaponType = 'Double Shot';
}
weaponTypeText.setText(weaponType);
// Update Nova blade level display
if (player.novaBladeActive && player.novaBladeLevel > 0) {
if (player.novaBladeLevel >= 50) {
novaBladeText.setText('Nova blade level Max Level');
} else {
novaBladeText.setText('Nova blade level ' + player.novaBladeLevel);
}
} else {
novaBladeText.setText('');
}
// Update score display
scoreText.setText('Score: ' + gameScore);
// Update shield display
if (player.shieldActive) {
var shieldTimeLeft = Math.ceil(player.shieldDuration / 60);
shieldText.setText('Shield: ' + player.shieldHealth + '/' + player.shieldMaxHealth + ' (' + shieldTimeLeft + 's)');
} else {
shieldText.setText('');
}
// Update boss health bar
if (currentBoss && bossHealthBar.alpha > 0) {
var healthPercent = Math.round(currentBoss.health / currentBoss.maxHealth * 100);
bossHealthBar.setText('BOSS: ' + currentBoss.health + '/' + currentBoss.maxHealth + ' (' + healthPercent + '%)');
}
// Check game over
if (player.health <= 0) {
// Create explosion effect - player explodes in flames
var playerGraphics = player.children[0]; // Get player graphics
// Create flame explosion effect with multiple tween stages
tween(playerGraphics, {
scaleX: 2.0,
scaleY: 2.0,
tint: 0xFF4400,
// Orange flame color
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second stage - bright red explosion
tween(playerGraphics, {
scaleX: 3.0,
scaleY: 3.0,
tint: 0xFF0000,
// Bright red
alpha: 0.8
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Final stage - fade to black and shrink
tween(playerGraphics, {
scaleX: 0.1,
scaleY: 0.1,
tint: 0x000000,
// Black
alpha: 0.0
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Show game over after explosion completes
LK.showGameOver();
}
});
}
});
}
});
return;
}
// Wave management
if (!waveCompleted) {
if (bossWave) {
// Boss wave logic
if (!currentBoss && creeps.length === 0) {
spawnBoss();
}
// Check if boss wave is complete
if (currentBoss && creeps.length === 0) {
waveCompleted = true;
nextWaveTimer = 180; // 3 second break
currentBoss = null;
// Hide boss health bar
tween(bossHealthBar, {
alpha: 0
}, {
duration: 500
});
}
} else {
// Regular wave logic
// Spawn creeps
if (creepsSpawned < creepsInWave || eliteCreepsSpawned < eliteCreepsInWave) {
spawnTimer++;
if (spawnTimer >= 60) {
// Spawn every second
if (creepsSpawned < creepsInWave) {
spawnCreep();
creepsSpawned++;
} else if (eliteCreepsSpawned < eliteCreepsInWave) {
spawnEliteCreep();
eliteCreepsSpawned++;
}
spawnTimer = 0;
}
}
// Check if wave is complete
if (creepsSpawned >= creepsInWave && eliteCreepsSpawned >= eliteCreepsInWave && creeps.length === 0) {
waveCompleted = true;
nextWaveTimer = 180; // 3 second break
}
}
} else {
nextWaveTimer--;
if (nextWaveTimer <= 0) {
startNextWave();
}
}
// Update boss bullets
for (var i = bossBullets.length - 1; i >= 0; i--) {
var bossBullet = bossBullets[i];
// Check if boss bullet is off screen
if (bossBullet.x < -50 || bossBullet.x > 2098 || bossBullet.y < -50 || bossBullet.y > 2782) {
bossBullet.destroy();
bossBullets.splice(i, 1);
continue;
}
// Check boss bullet collision with player
if (bossBullet.intersects(player)) {
player.takeDamage(bossBullet.damage);
bossBullet.destroy();
bossBullets.splice(i, 1);
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Check if bullet is off screen
if (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check bullet-creep collisions
var hitCreep = false;
for (var j = creeps.length - 1; j >= 0; j--) {
var creep = creeps[j];
if (bullet.intersects(creep)) {
var isDead = creep.takeDamage(bullet.damage);
if (isDead) {
// Give points for killing creeps
var scoreValue;
if (creep instanceof BossCreep) {
scoreValue = 1000; // High score for boss
// Clean up spawned creeps when boss dies
for (var spawnIdx = creep.spawnedCreeps.length - 1; spawnIdx >= 0; spawnIdx--) {
var spawnedCreep = creep.spawnedCreeps[spawnIdx];
if (spawnedCreep) {
// Find and remove from creeps array
var creepIndex = creeps.indexOf(spawnedCreep);
if (creepIndex !== -1) {
spawnedCreep.destroy();
creeps.splice(creepIndex, 1);
}
}
}
// Boss drops one speed upgrade, one armor upgrade, one health potion upgrade, and one weapon upgrade
var speedPickup = new Pickup('speed');
speedPickup.x = creep.x - 52.5; // Position far left with 35px spacing
speedPickup.y = creep.y;
pickups.push(speedPickup);
game.addChild(speedPickup);
var armorPickup = new Pickup('armor');
armorPickup.x = creep.x - 17.5; // Position left of center with 35px spacing
armorPickup.y = creep.y;
pickups.push(armorPickup);
game.addChild(armorPickup);
var healthPickup = new Pickup('health');
healthPickup.x = creep.x + 17.5; // Position right of center with 35px spacing
healthPickup.y = creep.y;
pickups.push(healthPickup);
game.addChild(healthPickup);
// Check player weapon level to determine weapon upgrade type
var weaponPickup;
if (player.weaponUpgrades >= 49) {
// Replace weapon upgrade with nova blade upgrade if player is at level 50
weaponPickup = new Pickup('novaBlade');
} else {
weaponPickup = new Pickup('damage');
}
weaponPickup.x = creep.x + 52.5; // Position far right with 35px spacing
weaponPickup.y = creep.y;
pickups.push(weaponPickup);
game.addChild(weaponPickup);
} else if (creep instanceof EliteCreep) {
scoreValue = 200;
// Elite creep upgrade drop chance reset to 0%
} else {
scoreValue = 100;
}
gameScore += scoreValue;
LK.setScore(LK.getScore() + scoreValue);
if (!(creep instanceof BossCreep)) {
dropPickup(creep.x, creep.y, creep instanceof EliteCreep);
}
creep.destroy();
creeps.splice(j, 1);
}
bullet.destroy();
bullets.splice(i, 1);
hitCreep = true;
break;
}
}
}
// Nova blade damage dealing
if (player.novaBladeActive && LK.ticks - player.novaBladeLastActivation >= 300) {
// Check if any aura is currently active by checking for expanding aura
var playerChildren = player.children;
var auraActive = false;
for (var childIdx = 0; childIdx < playerChildren.length; childIdx++) {
var child = playerChildren[childIdx];
// Check if this is an expanding aura (scale > 1.0 indicates expansion)
if (child.tint === 0xFF6600 && child.scaleX > 1.0) {
auraActive = true;
var auraRadius = player.novaBladeProximity; // Use dynamic proximity range
// Push all creeps away from player when Nova blade is active
for (var creepIdx = creeps.length - 1; creepIdx >= 0; creepIdx--) {
var creep = creeps[creepIdx];
var dx = creep.x - player.x;
var dy = creep.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= auraRadius) {
// Push creep away from player
if (distance > 0) {
var normalizedPushX = dx / distance;
var normalizedPushY = dy / distance;
var pushStrength = 50; // Strong push away from Nova blade
tween(creep, {
x: creep.x + normalizedPushX * pushStrength,
y: creep.y + normalizedPushY * pushStrength
}, {
duration: 300,
easing: tween.easeOut
});
}
var isDead = creep.takeDamage(25); // Fixed 25 damage
if (isDead) {
// Give points for killing creeps with Nova blade
var scoreValue;
if (creep instanceof BossCreep) {
scoreValue = 1000;
// Clean up spawned creeps when boss dies
for (var spawnIdx = creep.spawnedCreeps.length - 1; spawnIdx >= 0; spawnIdx--) {
var spawnedCreep = creep.spawnedCreeps[spawnIdx];
if (spawnedCreep) {
var creepIndex = creeps.indexOf(spawnedCreep);
if (creepIndex !== -1) {
spawnedCreep.destroy();
creeps.splice(creepIndex, 1);
}
}
}
// Boss drops one speed upgrade, one armor upgrade, one health potion upgrade, and one weapon upgrade
var speedPickup = new Pickup('speed');
speedPickup.x = creep.x - 52.5; // Position far left with 35px spacing
speedPickup.y = creep.y;
pickups.push(speedPickup);
game.addChild(speedPickup);
var armorPickup = new Pickup('armor');
armorPickup.x = creep.x - 17.5; // Position left of center with 35px spacing
armorPickup.y = creep.y;
pickups.push(armorPickup);
game.addChild(armorPickup);
var healthPickup = new Pickup('health');
healthPickup.x = creep.x + 17.5; // Position right of center with 35px spacing
healthPickup.y = creep.y;
pickups.push(healthPickup);
game.addChild(healthPickup);
// Check player weapon level to determine weapon upgrade type
var weaponPickup;
if (player.weaponUpgrades >= 49) {
// Replace weapon upgrade with nova blade upgrade if player is at level 50
weaponPickup = new Pickup('novaBlade');
} else {
weaponPickup = new Pickup('damage');
}
weaponPickup.x = creep.x + 52.5; // Position far right with 35px spacing
weaponPickup.y = creep.y;
pickups.push(weaponPickup);
game.addChild(weaponPickup);
} else if (creep instanceof EliteCreep) {
scoreValue = 200;
// Elite creep upgrade drop chance reset to 0%
} else {
scoreValue = 100;
}
gameScore += scoreValue;
LK.setScore(LK.getScore() + scoreValue);
if (!(creep instanceof BossCreep)) {
dropPickup(creep.x, creep.y, creep instanceof EliteCreep);
}
creep.destroy();
creeps.splice(creepIdx, 1);
}
}
}
// Disable pickup collection within Nova blade aura range during effect
for (var pickupIdx = pickups.length - 1; pickupIdx >= 0; pickupIdx--) {
var pickup = pickups[pickupIdx];
var pickupDx = pickup.x - player.x;
var pickupDy = pickup.y - player.y;
var pickupDistance = Math.sqrt(pickupDx * pickupDx + pickupDy * pickupDy);
// Prevent pickup collection if within Nova blade effect range
if (pickupDistance <= auraRadius) {
// Disable magnetic pull effect during Nova blade aura
pickup.lifetime = pickup.lifetime; // Just maintain lifetime without collection
}
}
break; // Only process one active aura at a time
}
}
}
// Update creeps
var _loop = function _loop() {
creep = creeps[i]; // Check if creep reached player - adjust hit detection based on shield status
canHitPlayer = false;
// Always use distance-based hit detection for consistency
dx = creep.x - player.x;
dy = creep.y - player.y;
distance = Math.sqrt(dx * dx + dy * dy);
if (player.shieldActive) {
// When shield is active, creeps must be much closer to hit
canHitPlayer = distance < 25; // Very close hit distance when shield is active
} else {
// Normal hit detection when no shield - slightly increased threshold
canHitPlayer = distance < 45; // Increased from 25 to 45 for better gameplay
}
if (canHitPlayer) {
if (!creep.hasReachedPlayer) {
player.takeDamage(creep.damage);
creep.hasReachedPlayer = true;
// Calculate bump direction (away from creep)
bumpDx = player.x - creep.x;
bumpDy = player.y - creep.y;
bumpDistance = Math.sqrt(bumpDx * bumpDx + bumpDy * bumpDy);
if (bumpDistance > 0) {
normalizedBumpX = bumpDx / bumpDistance;
normalizedBumpY = bumpDy / bumpDistance;
bumpStrength = 30; // How far to bump the player
// Bump the player away from the creep
tween(player, {
x: player.x + normalizedBumpX * bumpStrength,
y: player.y + normalizedBumpY * bumpStrength
}, {
duration: 200,
easing: tween.easeOut
});
}
// Make player invincible and add red flicker effect
if (!player.invincible) {
var _flicker = function flicker() {
flickerCount++;
if (flickerCount <= maxFlickers && player.invincible) {
var playerGraphics = player.children[0]; // Get player graphics
tween(playerGraphics, {
tint: 0xFF0000,
alpha: 0.5
}, {
duration: 50,
onFinish: function onFinish() {
tween(playerGraphics, {
tint: 0xFFFFFF,
alpha: 1.0
}, {
duration: 50,
onFinish: function onFinish() {
if (flickerCount < maxFlickers && player.invincible) {
_flicker();
}
}
});
}
});
}
};
player.invincible = true;
player.invincibilityTimer = 30; // 0.5 seconds at 60fps
// Create flickering red effect during invincibility
flickerCount = 0;
maxFlickers = 10;
_flicker();
}
}
// Don't destroy creep - it stays alive after hitting player
}
},
creep,
canHitPlayer,
dx,
dy,
distance,
bumpDx,
bumpDy,
bumpDistance,
normalizedBumpX,
normalizedBumpY,
bumpStrength,
flickerCount,
maxFlickers;
for (var i = creeps.length - 1; i >= 0; i--) {
_loop();
}
// Pickup processing moved to beginning of update loop
};
An space fighter jet viewed from the top. In-Game asset. 2d. High contrast. No shadows
An large circle of electric energy an circular force field shield. In-Game asset. 2d. High contrast. No shadows
An alien space mine that has spikes. In-Game asset. 2d. High contrast. No shadows
Alien mothership with spikes and glowing patterns and alien eye in middle. In-Game asset. 2d. High contrast. No shadows
an Symbol for an Shield Force shield of electric power. In-Game asset. 2d. High contrast. No shadows
An solid plasma ball of electrical force emanating power filled with reds yellows and green electric power. In-Game asset. 2d. High contrast. No shadows
Symbol for spaceship weapon upgrade with green arrow pointing up. In-Game asset. 2d. High contrast. No shadows
Alien spherical bullet with spikes emanating energy. In-Game asset. 2d. High contrast. No shadows
Symbol for a spaceship armor upgrade using an green arrow pointing up and white coloring. In-Game asset. 2d. High contrast. No shadows
symbol for spaceship speed upgrade increase. In-Game asset. 2d. High contrast. No shadows
Symbol for spaceship health increase with green arrow pointing up and red plus. In-Game asset. 2d. High contrast. No shadows
Symbol for space ship upgrade nova force field around it, with an white spaceship and orange electric circle surrounding it and an green arrow pointing up. In-Game asset. 2d. High contrast. No shadows
Symbol for an play button wide screen. In-Game asset. 2d. High contrast. No shadows
An alien space mine with spikes and red glowing center. In-Game asset. 2d. High contrast. No shadows
explosion in space. In-Game asset. 2d. High contrast. No shadows
Play again button. In-Game asset. 2d. High contrast. No shadows