/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var AngelAlly = Container.expand(function () { var self = Container.call(this); self.health = 150; self.maxHealth = 150; self.target = null; self.speed = 3; self.fireRate = 60; self.fireTimer = 0; self.chargeRate = 300; self.chargeTimer = 0; self.isCharging = false; self.chargeTarget = null; self.chargeCooldown = 0; // Create angel ally graphics (using player ship image with white tint) var angelGraphics = self.attachAsset('playerShip', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); // Make it white to show it's friendly angelGraphics.tint = 0xFFFFFF; // Create health bar background self.healthBarBg = LK.getAsset('healthBarBackground', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 0.8, y: -70 }); // Create health bar self.healthBar = LK.getAsset('healthBar', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 0.8, y: -67 }); // Add special white aura to show it's friendly var aura = LK.getAsset('powerUp', { anchorX: 0.5, anchorY: 0.5, scaleX: 8, scaleY: 8, alpha: 0.3 }); aura.tint = 0xFFFFFF; self.addChild(aura); self.addChild(self.healthBarBg); self.addChild(self.healthBar); self.takeDamage = function (amount) { self.health -= amount; // Update health bar self.healthBar.scale.x = self.health / self.maxHealth * 1.5; if (self.health <= 0) { // Angel ally destruction effect LK.effects.flashObject(self, 0xFFFFFF, 800); LK.getSound('explosion').play(); return true; // Ally is destroyed } else { // Flash white when taking damage LK.effects.flashObject(self, 0xFFFFFF, 200); return false; } }; self.shoot = function () { if (self.fireTimer <= 0) { // Shoot white holy bullets upward if (enemies.length > 0) { // Always fire 3 bullets in a spread pattern upward var bulletSpread = [-30, 0, 30]; // Spread angles in degrees for (var i = 0; i < bulletSpread.length; i++) { var bullet = new Bullet(); bullet.x = self.x + bulletSpread[i]; bullet.y = self.y; bullet.damage = 8; // Don't use auto-hit for upward shooting bullets bullet.isAutoHit = false; // Set custom velocity for angled shots var angle = -Math.PI / 2 + bulletSpread[i] * Math.PI / 180; // Convert to radians, -90 degrees (up) + spread bullet.vx = Math.cos(angle) * bullet.speed; bullet.vy = Math.sin(angle) * bullet.speed; bullet.hasCustomVelocity = true; bullet.getChildAt(0).tint = 0xFFFFFF; // White bullets bullet.scale.set(1.5, 1.5); bullets.push(bullet); game.addChild(bullet); } LK.getSound('shoot').play(); self.fireTimer = self.fireRate; } } }; self.charge = function () { if (!self.isCharging && self.chargeTimer <= 0 && self.chargeCooldown <= 0) { // Find nearest enemy to charge at if (enemies.length > 0) { var closestEnemy = enemies[0]; var closestDist = Math.hypot(closestEnemy.x - self.x, closestEnemy.y - self.y); for (var i = 1; i < enemies.length; i++) { var dist = Math.hypot(enemies[i].x - self.x, enemies[i].y - self.y); if (dist < closestDist) { closestEnemy = enemies[i]; closestDist = dist; } } // Initiate charge if enemy is not too far if (closestDist < 800) { self.isCharging = true; self.chargeTimer = 90; // 1.5 seconds of charging self.chargeTarget = closestEnemy; // Flash before charging LK.effects.flashObject(self, 0xFFFFFF, 500); } } } }; self.findTarget = function () { if (enemies.length > 0) { // Find the closest enemy var closest = enemies[0]; var closestDist = Math.hypot(closest.x - self.x, closest.y - self.y); for (var i = 1; i < enemies.length; i++) { var dist = Math.hypot(enemies[i].x - self.x, enemies[i].y - self.y); if (dist < closestDist) { closest = enemies[i]; closestDist = dist; } } self.target = closest; } else { self.target = null; } }; self.update = function () { // Update timers if (self.fireTimer > 0) self.fireTimer--; if (self.chargeTimer > 0) self.chargeTimer--; if (self.chargeCooldown > 0) self.chargeCooldown--; // Update health bar self.healthBar.scale.x = self.health / self.maxHealth * 1.5; // Pulse the aura aura.alpha = 0.2 + Math.sin(LK.ticks * 0.1) * 0.1; // Make sure angel stays at the bottom of the screen if (self.y < 2732 - 200) { self.y = 2732 - 200; } // Handle charging behavior if (self.isCharging) { if (self.chargeTarget && enemies.includes(self.chargeTarget)) { var dx = self.chargeTarget.x - self.x; var dy = self.chargeTarget.y - self.y; var dist = Math.hypot(dx, dy); // Calculate angle to face the target self.rotation = Math.atan2(dy, dx); // Move towards target at high speed if (dist > 20) { self.x += dx / dist * self.speed * 4; self.y += dy / dist * self.speed * 4; } else { // Hit the target var destroyed = self.chargeTarget.takeDamage(30); if (destroyed) { enemiesDefeated++; totalEnemiesDefeated++; LK.setScore(LK.getScore() + self.chargeTarget.scoreValue); scoreTxt.setText('Score: ' + LK.getScore()); self.chargeTarget.destroy(); enemies.splice(enemies.indexOf(self.chargeTarget), 1); } // End charge self.isCharging = false; self.chargeCooldown = 180; // 3 second cooldown } // Charge duration limit if (self.chargeTimer <= 0) { self.isCharging = false; self.chargeCooldown = 180; // 3 second cooldown } } else { self.isCharging = false; } } else { // Find a target if we don't have one or current target is destroyed if (!self.target || !enemies.includes(self.target)) { self.findTarget(); } // Move normally when not charging if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.hypot(dx, dy); // Always face upward regardless of target position self.rotation = -Math.PI / 2; // -90 degrees (upward) // Only move horizontally at the bottom of the screen if (dist > 300) { // Move toward target horizontally only self.x += dx / dist * self.speed; // Keep at bottom of screen self.y = 2732 - 200; } else if (dist < 200) { // Back away horizontally only self.x -= dx / dist * self.speed * 0.5; // Keep at bottom of screen self.y = 2732 - 200; } // Shoot at enemies self.shoot(); // Try to charge occasionally if (LK.ticks % 60 === 0) { self.charge(); } } else { // No target, move back toward player horizontally only var dx = player.x - 200 - self.x; var dist = Math.abs(dx); // Update rotation to face upward self.rotation = -Math.PI / 2; // Rotate to face up if (dist > 50) { self.x += dx / dist * self.speed; // Keep at bottom of screen self.y = 2732 - 200; } } } }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); self.speed = 15; self.damage = 1; self.isPlayerBullet = true; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (self.isAutoHit && self.targetEnemy && enemies.includes(self.targetEnemy)) { // Auto-hit bullets home in on their target var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var dist = Math.hypot(dx, dy); // If we're very close to the enemy, hit it automatically if (dist < 30) { // Handle direct hit var destroyed = self.targetEnemy.takeDamage(self.damage); if (destroyed) { enemiesDefeated++; totalEnemiesDefeated++; LK.setScore(LK.getScore() + self.targetEnemy.scoreValue); scoreTxt.setText('Score: ' + LK.getScore()); self.targetEnemy.destroy(); enemies.splice(enemies.indexOf(self.targetEnemy), 1); } // Mark bullet for removal self.y = -100; // This will cause bullet to be removed in game.update } else { // Home in on the target with perfect tracking self.vx = dx / dist * self.speed * 1.5; self.vy = dy / dist * self.speed * 1.5; self.x += self.vx; self.y += self.vy; // Rotate bullet to face direction of travel self.rotation = Math.atan2(self.vy, self.vx); } } else if (self.hasCustomVelocity) { // Use vectorized movement if bullet has custom velocity self.x += self.vx; self.y += self.vy; // Rotate bullet to face direction of travel self.rotation = Math.atan2(self.vy, self.vx); } else { // Default upward movement self.y -= self.speed; } }; return self; }); var ClonePowerUp = Container.expand(function () { var self = Container.call(this); var powerUpGraphics = self.attachAsset('powerUp', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); // Set to cyan color powerUpGraphics.tint = 0x00FFFF; self.speed = 2; self.update = function () { self.y += self.speed; // Pulse effect var scale = 2 + Math.sin(LK.ticks * 0.15) * 0.2; self.scale.set(scale, scale); }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); self.health = 2; self.speed = 3 + Math.random() * 2; self.fireRate = 90 + Math.floor(Math.random() * 60); self.fireTimer = Math.floor(Math.random() * 60); self.scoreValue = 10; self.movingRight = Math.random() > 0.5; self.moveCounter = 0; self.moveChangeTime = 60 + Math.floor(Math.random() * 120); var enemyGraphics = self.attachAsset('enemyShip', { anchorX: 0.5, anchorY: 0.5 }); self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { // Chance to drop regular power up if (Math.random() < 0.15) { var powerUp = new PowerUp(); powerUp.x = self.x; powerUp.y = self.y; powerUps.push(powerUp); game.addChild(powerUp); } // Small chance to drop clone power up if (Math.random() < 0.05) { var clonePowerUp = new ClonePowerUp(); clonePowerUp.x = self.x; clonePowerUp.y = self.y; clonePowerUps.push(clonePowerUp); game.addChild(clonePowerUp); } // Explosion effect LK.effects.flashObject(self, 0xff0000, 300); LK.getSound('explosion').play(); return true; // Enemy is destroyed } else { // Flash red when taking damage LK.effects.flashObject(self, 0xff0000, 200); return false; } }; self.shoot = function () { if (self.fireTimer <= 0) { var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y + 40; enemyBullets.push(bullet); game.addChild(bullet); LK.getSound('enemyShoot').play(); self.fireTimer = self.fireRate; } }; self.update = function () { // Basic movement pattern self.y += self.speed * 0.5; // Check if player is in bottom left safe zone var playerInSafeZone = player.x < 300 && player.y > 2732 - 300; var safeZoneDistance = 400; // Distance to maintain from safe zone // If player is in safe zone and enemy is getting too close, steer away if (playerInSafeZone && self.x < safeZoneDistance && self.y > 2732 - safeZoneDistance) { // Move right to avoid safe zone self.x += self.speed * 1.5; self.movingRight = true; } else { // Normal horizontal movement if (self.movingRight) { self.x += self.speed; } else { self.x -= self.speed; } } // Reverse direction if reaching screen edges if (self.x < 100) { self.movingRight = true; } else if (self.x > 2048 - 100) { self.movingRight = false; } // Randomly change direction self.moveCounter++; if (self.moveCounter >= self.moveChangeTime) { self.movingRight = !self.movingRight; self.moveCounter = 0; } // Decrease fire timer and shoot self.fireTimer--; if (self.fireTimer <= 0 && !(playerInSafeZone && self.x < safeZoneDistance && self.y > 2732 - safeZoneDistance)) { self.shoot(); } }; return self; }); var EnemyBullet = Container.expand(function () { var self = Container.call(this); self.speed = 8; self.damage = 1; self.isPlayerBullet = false; var bulletGraphics = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { // Allow for both upward (negative speed) and downward (positive speed) bullets self.y += self.speed; }; return self; }); var Mine = Container.expand(function () { var self = Container.call(this); self.speed = 4; self.damage = 15; self.isPlayerBullet = false; self.rotationSpeed = 0.05; var mineGraphics = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); mineGraphics.tint = 0xFF5500; self.update = function () { // Allow for both upward (negative speed) and downward (positive speed) mines self.y += self.speed; self.rotation += self.rotationSpeed; // Slight wobble movement self.x += Math.sin(LK.ticks * 0.1) * 2; }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); self.health = 100; self.maxHealth = 100; self.speed = 10; self.fireRate = 15; self.fireTimer = 0; self.powerUpTimer = 0; self.hasPowerUp = false; self.invincible = false; self.invincibleTimer = 0; var playerGraphics = self.attachAsset('playerShip', { anchorX: 0.5, anchorY: 0.5 }); self.takeDamage = function (amount) { if (self.invincible) return; self.health -= amount; if (self.health <= 0) { self.health = 0; } else { // Flash player when taking damage self.invincible = true; self.invincibleTimer = 60; // 1 second invincibility LK.effects.flashObject(self, 0xff0000, 1000); LK.getSound('playerHit').play(); } }; self.shoot = function () { if (self.fireTimer <= 0) { var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y - 50; // If player has power up, enhance bullet if (self.hasPowerUp) { bullet.damage = 2; bullet.speed = 20; bullet.scale.set(1.5, 1.5); } bullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.fireTimer = self.fireRate; } }; self.update = function () { // Decrease fire timer if (self.fireTimer > 0) { self.fireTimer--; } // Handle power up timer if (self.hasPowerUp) { self.powerUpTimer--; if (self.powerUpTimer <= 0) { self.hasPowerUp = false; playerGraphics.tint = 0xFFFFFF; } } // Handle invincibility timer if (self.invincible) { self.invincibleTimer--; if (self.invincibleTimer <= 0) { self.invincible = false; } // Flicker effect during invincibility self.alpha = LK.ticks % 6 < 3 ? 0.7 : 1; } else { self.alpha = 1; } }; self.activatePowerUp = function () { self.hasPowerUp = true; self.powerUpTimer = 300; // 5 seconds playerGraphics.tint = 0x2ecc71; // Green tint when powered up LK.getSound('powerUpCollect').play(); }; return self; }); var PlayerClone = Container.expand(function () { var self = Container.call(this); self.health = 50; self.maxHealth = 50; self.target = null; self.speed = 2; // Super slow speed (was 8) self.fireRate = 80; // Even slower fire rate self.fireTimer = 0; // Create clone graphics (same as player but with different tint) var cloneGraphics = self.attachAsset('playerShip', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7 }); cloneGraphics.tint = 0x00FFFF; // Cyan tint // Create health bar background self.healthBarBg = LK.getAsset('healthBarBackground', { anchorX: 0.5, anchorY: 0, scaleX: 0.3, scaleY: 0.3, y: -40 }); // Create health bar self.healthBar = LK.getAsset('healthBar', { anchorX: 0.5, anchorY: 0, scaleX: 0.3, scaleY: 0.3, y: -38 }); self.addChild(self.healthBarBg); self.addChild(self.healthBar); self.takeDamage = function (amount) { self.health -= amount; // Update health bar self.healthBar.scale.x = self.health / self.maxHealth * 0.3; if (self.health <= 0) { // Clone destruction effect LK.effects.flashObject(self, 0x00FFFF, 500); LK.getSound('playerHit').play(); return true; // Clone is destroyed } else { // Flash cyan when taking damage LK.effects.flashObject(self, 0x00FFFF, 200); return false; } }; self.shoot = function () { if (self.fireTimer <= 0) { // Shoot auto-hit bullets in 4 cardinal directions (up, down, left, right) for (var i = 0; i < 4; i++) { var angle = i * Math.PI / 2; // 90 degree increments (cardinal directions only) // Only shoot if there are enemies to hit if (enemies.length > 0) { // Find a random enemy to hit directly var targetEnemy = enemies[Math.floor(Math.random() * enemies.length)]; var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.damage = 10; // More damage for auto-hit bullets bullet.isAutoHit = true; // Mark this as an auto-hit bullet bullet.targetEnemy = targetEnemy; // Store the target enemy bullet.speed = 20; bullet.scale.set(1.2, 1.2); // Set initial direction based on cardinal direction var dirX = Math.cos(angle); var dirY = Math.sin(angle); bullet.vx = dirX * bullet.speed; bullet.vy = dirY * bullet.speed; bullet.hasCustomVelocity = true; bullets.push(bullet); game.addChild(bullet); } } LK.getSound('shoot').play(); self.fireTimer = self.fireRate; // Slower fire rate } }; self.findTarget = function () { if (enemies.length > 0) { // Just find the closest enemy for movement purposes var closest = enemies[0]; var closestDist = Math.hypot(closest.x - self.x, closest.y - self.y); for (var i = 1; i < enemies.length; i++) { var dist = Math.hypot(enemies[i].x - self.x, enemies[i].y - self.y); if (dist < closestDist) { closest = enemies[i]; closestDist = dist; } } self.target = closest; } else { self.target = null; } }; self.update = function () { // Find a target if we don't have one or current target is destroyed if (!self.target || !enemies.includes(self.target)) { self.findTarget(); } // Always shoot if we can, regardless of position if (self.fireTimer <= 0) { self.shoot(); } // Move toward target if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.hypot(dx, dy); // Update rotation to face the target self.rotation = Math.atan2(dy, dx); if (dist > 200) { // Move toward target if not too close - super slow! self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { // When close, just shoot (but don't try to get closer) // No need to call shoot here as we do it regardless now } } else { // No target, move back toward player var dx = player.x - self.x; var dy = player.y - 150 - self.y; var dist = Math.hypot(dx, dy); // Update rotation to face direction of movement if (dist > 10) { self.rotation = Math.atan2(dy, dx); } if (dist > 50) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } } // Decrease fire timer if (self.fireTimer > 0) { self.fireTimer--; } // Update health bar position self.healthBarBg.x = 0; self.healthBar.x = 0; }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var powerUpGraphics = self.attachAsset('powerUp', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 3; self.update = function () { self.y += self.speed; // Slight wobble effect self.x += Math.sin(LK.ticks * 0.1) * 1.5; // Pulse effect var scale = 1 + Math.sin(LK.ticks * 0.15) * 0.1; self.scale.set(scale, scale); }; return self; }); // 1 second var SuperBoss = Container.expand(function () { var self = Container.call(this); self.health = 400; self.maxHealth = 400; self.speed = 3; self.fireRate = 120; self.fireTimer = 0; self.laserTimer = 0; self.laserCharging = false; self.laserChargingTime = 180; // 3 seconds to charge self.laserFiringTime = 120; // 2 seconds of firing self.scoreValue = 1000; self.movingRight = true; self.bossDefeated = 0; // Create super boss shark asset var bossGraphics = self.attachAsset('sharkBoss', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // No tint needed for image asset // Create boss health bar background self.healthBarBg = LK.getAsset('healthBarBackground', { anchorX: 0.5, anchorY: 0, scaleX: 3, scaleY: 1.5, y: -120 }); // Create boss health bar self.healthBar = LK.getAsset('healthBar', { anchorX: 0.5, anchorY: 0, scaleX: 3, scaleY: 1.5, y: -115 }); // Create laser cannons self.laserCannons = []; for (var i = 0; i < 3; i++) { var cannon = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0, scaleX: 0.4, scaleY: 3, x: (i - 1) * 100, // Left, center, right positioning y: 80 }); cannon.tint = 0xFF0000; // Red tint cannon.alpha = 0.5; // Start dim self.laserCannons.push(cannon); self.addChild(cannon); } self.addChild(self.healthBarBg); self.addChild(self.healthBar); self.takeDamage = function (amount) { self.health -= amount; // Update health bar self.healthBar.scale.x = self.health / self.maxHealth * 3; if (self.health <= 0) { // Boss explosion effect - bigger and longer LK.effects.flashObject(self, 0xff0000, 1500); LK.getSound('explosion').play(); // Always spawn a clone power-up when boss is defeated var clonePowerUp = new ClonePowerUp(); clonePowerUp.x = self.x; clonePowerUp.y = self.y; clonePowerUps.push(clonePowerUp); game.addChild(clonePowerUp); // Spawn 4 small exploding sharks in a circular pattern around where the boss was var numSharks = 4; for (var i = 0; i < numSharks; i++) { var angle = i / numSharks * Math.PI * 2; var tinyShark = new TinyShark(); tinyShark.x = self.x + Math.cos(angle) * 200; tinyShark.y = self.y + Math.sin(angle) * 200; enemies.push(tinyShark); game.addChild(tinyShark); } // Show warning message about tiny sharks var sharkWarning = new Text2("BEWARE! TINY EXPLODING SHARKS!", { size: 90, fill: 0x00BFFF }); sharkWarning.anchor.set(0.5, 0.5); LK.gui.center.addChild(sharkWarning); // Fade out warning tween(sharkWarning, { alpha: 0 }, { duration: 2500, onFinish: function onFinish() { sharkWarning.destroy(); } }); return true; // Boss is destroyed } else { // Flash red when taking damage LK.effects.flashObject(self, 0xff0000, 200); return false; } }; self.shootBullet = function () { if (self.fireTimer <= 0 && !self.laserCharging) { // Shoot multiple bullets in a spread pattern for (var i = -3; i <= 3; i++) { var bullet = new EnemyBullet(); bullet.x = self.x + i * 40; bullet.y = self.y + 100; bullet.damage = 5; bullet.scale.set(1.5, 1.5); enemyBullets.push(bullet); game.addChild(bullet); } // Also shoot mines var mine = new Mine(); mine.x = self.x; mine.y = self.y + 120; mine.damage = 20; enemyBullets.push(mine); game.addChild(mine); LK.getSound('enemyShoot').play(); self.fireTimer = self.fireRate; } }; self.chargeLasers = function () { if (!self.laserCharging && self.laserTimer <= 0) { self.laserCharging = true; self.laserTimer = self.laserChargingTime; // Show charging warning var warningText = new Text2('WARNING: LASERS CHARGING!', { size: 100, fill: 0xFF0000 }); warningText.anchor.set(0.5, 0.5); LK.gui.center.addChild(warningText); // Fade out warning after 2 seconds LK.setTimeout(function () { warningText.destroy(); }, 2000); // Make cannons pulse while charging for (var i = 0; i < self.laserCannons.length; i++) { self.laserCannons[i].alpha = 0.8; } } }; self.fireLasers = function () { // Fire three deadly laser beams that extend to the bottom of the screen for (var i = 0; i < self.laserCannons.length; i++) { var laser = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0, scaleX: 0.5, scaleY: 50, x: self.x + (i - 1) * 100, y: self.y + 80, tint: 0xFF0000 }); // Check if player is hit by laser var laserX = self.x + (i - 1) * 100; if (Math.abs(player.x - laserX) < 50) { player.takeDamage(10); } // Check if clones are hit for (var j = 0; j < playerClones.length; j++) { var clone = playerClones[j]; if (Math.abs(clone.x - laserX) < 50) { clone.takeDamage(15); } } game.addChild(laser); // Remove laser after a short time (function (laserBeam) { LK.setTimeout(function () { laserBeam.destroy(); }, 100); })(laser); } }; self.update = function () { // Check if player is in bottom left safe zone var playerInSafeZone = player.x < 300 && player.y > 2732 - 300; var safeZoneDistance = 600; // Largest distance for super boss // Basic movement pattern - side to side if (playerInSafeZone && self.x < safeZoneDistance && self.y > 1800) { // Move right to avoid safe zone self.x += self.speed * 2; self.movingRight = true; } else { if (self.movingRight) { self.x += self.speed; if (self.x > 2048 - 250) { self.movingRight = false; } } else { self.x -= self.speed; if (self.x < 250) { self.movingRight = true; } } } // Handle laser charging and firing if (self.laserCharging) { self.laserTimer--; // Pulse the cannons faster as charging increases var pulseRate = Math.max(0.6, 1 + Math.sin(LK.ticks * (0.1 + (1 - self.laserTimer / self.laserChargingTime) * 0.2)) * 0.4); for (var i = 0; i < self.laserCannons.length; i++) { self.laserCannons[i].alpha = pulseRate; self.laserCannons[i].tint = 0xFF0000 + Math.floor((1 - self.laserTimer / self.laserChargingTime) * 255) * 0x10000; } if (self.laserTimer <= 0) { // Fire the lasers - but don't fire at player in safe zone if (!playerInSafeZone || self.x >= safeZoneDistance) { self.fireLasers(); } // Reset the charging flag and set timer for next charge self.laserCharging = false; self.laserTimer = self.laserFiringTime; // Reset cannon appearance for (var i = 0; i < self.laserCannons.length; i++) { self.laserCannons[i].alpha = 0.5; self.laserCannons[i].tint = 0xFF0000; } } } else { // Count down to next laser charging or normal bullet firing self.laserTimer--; self.fireTimer--; // Shoot regular bullets if (self.fireTimer <= 0 && !(playerInSafeZone && self.x < safeZoneDistance && self.y > 1800)) { self.shootBullet(); } // Start charging lasers again when timer expires if (self.laserTimer <= 0 && !(playerInSafeZone && self.x < safeZoneDistance && self.y > 1800)) { self.chargeLasers(); } } }; return self; }); var TinyShark = Container.expand(function () { var self = Container.call(this); self.health = 15; self.speed = 7; self.damage = 30; // High damage when exploding self.isPlayerBullet = false; self.target = null; self.exploding = false; self.explodeRadius = 150; // Create tiny shark graphics var sharkGraphics = self.attachAsset('enemyShip', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4 }); // Add a light blue tint to the sharks sharkGraphics.tint = 0x00BFFF; self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { self.explode(); return true; } else { // Flash blue when taking damage LK.effects.flashObject(self, 0x00BFFF, 200); return false; } }; self.explode = function () { self.exploding = true; // Create explosion effect LK.effects.flashObject(self, 0xFF5500, 500); LK.getSound('explosion').play(); // Check if player is within explosion radius var distToPlayer = Math.hypot(player.x - self.x, player.y - self.y); if (distToPlayer < self.explodeRadius) { player.takeDamage(self.damage); } // Check if clones are within explosion radius for (var i = 0; i < playerClones.length; i++) { var clone = playerClones[i]; var distToClone = Math.hypot(clone.x - self.x, clone.y - self.y); if (distToClone < self.explodeRadius) { clone.takeDamage(self.damage); } } }; self.update = function () { if (self.exploding) return; // Always target the player self.target = player; // Check if player is in bottom left safe zone var playerInSafeZone = player.x < 300 && player.y > 2732 - 300; var safeZoneDistance = 450; // Distance for tiny sharks if (self.target) { // Avoid player in safe zone if (playerInSafeZone && self.x < safeZoneDistance && self.y > 2732 - safeZoneDistance) { // Move away from safe zone self.x += self.speed * 1.2; self.y -= self.speed * 0.5; // Add some wobble self.y += Math.sin(LK.ticks * 0.2) * 1.5; } else { // Normal shark behavior - chase the player var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.hypot(dx, dy); // Rotate to face player self.rotation = Math.atan2(dy, dx); // Move toward player if (dist > 10) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } // Check if close enough to explode if (dist < 30) { self.explode(); return; } // Slightly wobble while swimming self.y += Math.sin(LK.ticks * 0.2) * 1; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game state variables var gameActive = true; var player; var bullets = []; var enemies = []; var enemyBullets = []; var powerUps = []; var clonePowerUps = []; var playerClones = []; var waveNumber = 1; var enemiesThisWave = 5; var enemiesDefeated = 0; var totalEnemiesDefeated = 0; var waveDelay = 0; var spawnDelay = 0; var dragNode = null; var isBossWave = false; var clonePowerUpSpawnTimer = 0; var bossDefeatsCount = 0; var superBoss = null; // GUI elements var healthBar; var healthBarBg; var scoreTxt; var waveTxt; var signTxt; // Initialize game elements function initGame() { // Set background game.setBackgroundColor(0x0a0a2a); // Initialize player player = new Player(); player.x = 2048 / 2; player.y = 2732 - 300; game.addChild(player); // Create GUI elements createGUI(); // Start game gameActive = true; waveNumber = 1; enemiesThisWave = 3; // Fewer enemies at start enemiesDefeated = 0; totalEnemiesDefeated = 0; bossDefeatsCount = 0; playerClones = []; clonePowerUps = []; LK.setScore(0); // Play intense background music LK.playMusic('bgMusic', { loop: true, fade: { start: 0, end: 1, duration: 1000 } }); } function createGUI() { // Health bar background healthBarBg = LK.getAsset('healthBarBackground', { anchorX: 0, anchorY: 0, x: 20, y: 20 }); // Health bar healthBar = LK.getAsset('healthBar', { anchorX: 0, anchorY: 0, x: 25, y: 25 }); // Score text scoreTxt = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreTxt.anchor.set(1, 0); // Wave text waveTxt = new Text2('Wave: 1', { size: 60, fill: 0xFFFFFF }); waveTxt.anchor.set(0.5, 0); // Create sign signTxt = new Text2('cyan powerup = slow clone with auto-hit bullets!\nevil sharks = explosive damage!', { size: 80, fill: 0xFFFFFF }); signTxt.anchor.set(1, 1); // Add GUI elements LK.gui.topRight.addChild(scoreTxt); LK.gui.top.addChild(waveTxt); LK.gui.topLeft.addChild(healthBarBg); LK.gui.topLeft.addChild(healthBar); LK.gui.bottomRight.addChild(signTxt); } // Spawn a wave of enemies function spawnEnemyWave() { // Check if this is a boss wave (every 3 waves after wave 3) if (waveNumber >= 3 && (waveNumber - 3) % 3 === 0) { isBossWave = true; // Show boss warning message var warningText = 'SUPER BOSS INCOMING!'; var warningColor = 0xFF0000; // Increase music intensity for boss fights LK.stopMusic(); LK.playMusic('bgMusic', { volume: 1, fade: { start: 0.7, end: 1, duration: 500 } }); var bossWarning = new Text2(warningText, { size: 120, fill: warningColor }); bossWarning.anchor.set(0.5, 0.5); LK.gui.center.addChild(bossWarning); // Fade out warning message tween(bossWarning, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { bossWarning.destroy(); // Spawn the boss if (!gameActive) return; // Always spawn the super boss superBoss = new SuperBoss(); superBoss.x = 2048 / 2; superBoss.y = 200; enemies.push(superBoss); game.addChild(superBoss); // Show special super boss announcement var superBossText = new Text2('BEWARE THE LASER CANNONS!', { size: 90, fill: 0xFF0000 }); superBossText.anchor.set(0.5, 0.5); superBossText.y = 400; LK.gui.center.addChild(superBossText); // Fade out super boss text tween(superBossText, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { superBossText.destroy(); } }); } }); } else { isBossWave = false; // Regular enemy wave with increased spawn delay to reduce crowding for (var i = 0; i < enemiesThisWave; i++) { // Set a longer delay for each enemy spawn (1.5 seconds instead of 1) LK.setTimeout(function () { if (!gameActive) return; var enemy = new Enemy(); enemy.x = 200 + Math.random() * (2048 - 400); enemy.y = -100 - Math.random() * 400; // Increase difficulty with each wave if (waveNumber > 1) { enemy.health += Math.floor(waveNumber / 2); enemy.speed += waveNumber * 0.2; enemy.scoreValue += waveNumber * 2; } enemies.push(enemy); game.addChild(enemy); }, i * 1500); } } } // Handle player movement function handleMove(x, y, obj) { if (dragNode && gameActive) { // Limit player movement to screen bounds var newX = Math.max(50, Math.min(2048 - 50, x)); var newY = Math.max(50, Math.min(2732 - 50, y)); dragNode.x = newX; dragNode.y = newY; } } // Game down handler game.down = function (x, y, obj) { if (gameActive) { dragNode = player; handleMove(x, y, obj); // Shoot when pressing down player.shoot(); } }; // Game move handler game.move = handleMove; // Game up handler game.up = function (x, y, obj) { dragNode = null; }; // Update game state game.update = function () { if (!gameActive) return; // Update player player.update(); // Auto-fire for player if (LK.ticks % 10 === 0) { // Check if player is in bottom left corner (coordinates below 300) if (!(player.x < 300 && player.y > 2732 - 300)) { player.shoot(); } else { // Auto create angel ally when player enters bottom left corner if (playerClones.length < 3) { var angelAlly = new AngelAlly(); angelAlly.x = player.x; angelAlly.y = 2732 - 200; // Position at bottom of screen angelAlly.rotation = -Math.PI / 2; // Rotate to face upward playerClones.push(angelAlly); game.addChild(angelAlly); // Show message about angel joining team var joinMessage = new Text2("ANGEL JOINS YOUR TEAM!", { size: 90, fill: 0xFFFFFF }); joinMessage.anchor.set(0.5, 0.5); LK.gui.center.addChild(joinMessage); // Fade out message tween(joinMessage, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { joinMessage.destroy(); } }); } } } // Update health bar healthBar.scale.x = player.health / player.maxHealth; // Update player clones for (var i = playerClones.length - 1; i >= 0; i--) { var clone = playerClones[i]; clone.update(); // Remove destroyed clones if (clone.health <= 0) { clone.destroy(); playerClones.splice(i, 1); } } // Update clone power-ups for (var i = clonePowerUps.length - 1; i >= 0; i--) { var clonePowerUp = clonePowerUps[i]; clonePowerUp.update(); // Remove power-ups that go off screen if (clonePowerUp.y > 2732 + 50) { clonePowerUp.destroy(); clonePowerUps.splice(i, 1); continue; } // Check for player collision if (gameActive && clonePowerUp.intersects(player)) { // Only create a new clone if we have less than 2 if (playerClones.length < 2) { // Create a new player clone var newClone = new PlayerClone(); newClone.x = player.x + 150; newClone.y = player.y - 150; playerClones.push(newClone); game.addChild(newClone); // Play power-up sound LK.getSound('powerUpCollect').play(); } else { // Flash player to indicate max clones reached LK.effects.flashObject(player, 0x00FFFF, 300); } // Remove the power-up clonePowerUp.destroy(); clonePowerUps.splice(i, 1); } } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; bullet.update(); // Remove bullets that go off screen if (bullet.y < -50) { bullet.destroy(); bullets.splice(i, 1); continue; } // Check for enemy collisions for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; if (bullet.intersects(enemy)) { // Enemy takes damage var destroyed = enemy.takeDamage(bullet.damage); if (destroyed) { enemiesDefeated++; totalEnemiesDefeated++; LK.setScore(LK.getScore() + enemy.scoreValue); scoreTxt.setText('Score: ' + LK.getScore()); // Check if we defeated a boss if (enemy === superBoss) { bossDefeatsCount++; } enemy.destroy(); enemies.splice(j, 1); } // Remove bullet bullet.destroy(); bullets.splice(i, 1); break; } } } // Update enemy bullets for (var i = enemyBullets.length - 1; i >= 0; i--) { var bullet = enemyBullets[i]; bullet.update(); // Remove bullets that go off screen if (bullet.y > 2732 + 50) { bullet.destroy(); enemyBullets.splice(i, 1); continue; } // Check for player collision if (gameActive && bullet.intersects(player)) { player.takeDamage(bullet.damage); // We've removed the angel boss, so we don't need this code anymore bullet.destroy(); enemyBullets.splice(i, 1); // Check if player is defeated if (player.health <= 0) { gameActive = false; LK.showGameOver(); } continue; } // Check for clone collisions var cloneHit = false; for (var j = playerClones.length - 1; j >= 0; j--) { var clone = playerClones[j]; if (bullet.intersects(clone)) { var destroyed = clone.takeDamage(bullet.damage); if (destroyed) { clone.destroy(); playerClones.splice(j, 1); } bullet.destroy(); enemyBullets.splice(i, 1); cloneHit = true; break; } } if (cloneHit) continue; } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; enemy.update(); // Enemies die when they reach the bottom of the screen if (enemy.y > 2732 - 50) { // Grant score for the enemy LK.setScore(LK.getScore() + enemy.scoreValue); scoreTxt.setText('Score: ' + LK.getScore()); // Show explosion effect LK.effects.flashObject(enemy, 0xff0000, 300); LK.getSound('explosion').play(); // Remove the enemy enemy.destroy(); enemies.splice(i, 1); continue; } // Check for collision with player if (gameActive && enemy.intersects(player)) { player.takeDamage(10); // Destroy enemy on collision enemy.destroy(); enemies.splice(i, 1); // Check if player is defeated if (player.health <= 0) { gameActive = false; LK.showGameOver(); } } } // Update power-ups for (var i = powerUps.length - 1; i >= 0; i--) { var powerUp = powerUps[i]; powerUp.update(); // Remove power-ups that go off screen if (powerUp.y > 2732 + 50) { powerUp.destroy(); powerUps.splice(i, 1); continue; } // Check for player collision if (gameActive && powerUp.intersects(player)) { player.activatePowerUp(); powerUp.destroy(); powerUps.splice(i, 1); } } // Check if wave is complete - we simply need to check if enemies array is empty if (enemies.length === 0 && waveDelay <= 0) { // Start next wave waveNumber++; // Implement infinite waves with fewer enemies to prevent overcrowding if (waveNumber % 15 === 1) { // Reset back to first wave pattern every 15 waves enemiesThisWave = 5; // Start with fewer enemies } else if (waveNumber % 15 <= 3) { enemiesThisWave = 5 + waveNumber % 15; // Slower increasing difficulty } else if (waveNumber % 15 <= 6) { enemiesThisWave = 8 - waveNumber % 15 * 0.5; // Start reducing } else if (waveNumber % 15 <= 10) { enemiesThisWave = 5 - Math.floor(waveNumber % 15 / 3); // Further reduction } else { enemiesThisWave = Math.max(2, 7 - waveNumber % 15); // Minimum of 2 enemies } enemiesDefeated = 0; waveTxt.setText('Wave: ' + waveNumber); // Show wave message var waveMessage = new Text2('WAVE ' + waveNumber, { size: 120, fill: 0xFFFFFF }); waveMessage.anchor.set(0.5, 0.5); LK.gui.center.addChild(waveMessage); // Fade out wave message tween(waveMessage, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { waveMessage.destroy(); } }); // Set delay before spawning next wave waveDelay = 120; // 2 seconds } // Count down wave delay if (waveDelay > 0) { waveDelay--; if (waveDelay === 0) { // Spawn next wave spawnEnemyWave(); } } // Count down spawn delay if (spawnDelay > 0) { spawnDelay--; if (spawnDelay === 0) { // Start first wave spawnEnemyWave(); } } }; // Initialize the game initGame(); // Set a short delay before spawning the first wave spawnDelay = 60;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AngelAlly = Container.expand(function () {
var self = Container.call(this);
self.health = 150;
self.maxHealth = 150;
self.target = null;
self.speed = 3;
self.fireRate = 60;
self.fireTimer = 0;
self.chargeRate = 300;
self.chargeTimer = 0;
self.isCharging = false;
self.chargeTarget = null;
self.chargeCooldown = 0;
// Create angel ally graphics (using player ship image with white tint)
var angelGraphics = self.attachAsset('playerShip', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Make it white to show it's friendly
angelGraphics.tint = 0xFFFFFF;
// Create health bar background
self.healthBarBg = LK.getAsset('healthBarBackground', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 0.8,
y: -70
});
// Create health bar
self.healthBar = LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 0.8,
y: -67
});
// Add special white aura to show it's friendly
var aura = LK.getAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8,
scaleY: 8,
alpha: 0.3
});
aura.tint = 0xFFFFFF;
self.addChild(aura);
self.addChild(self.healthBarBg);
self.addChild(self.healthBar);
self.takeDamage = function (amount) {
self.health -= amount;
// Update health bar
self.healthBar.scale.x = self.health / self.maxHealth * 1.5;
if (self.health <= 0) {
// Angel ally destruction effect
LK.effects.flashObject(self, 0xFFFFFF, 800);
LK.getSound('explosion').play();
return true; // Ally is destroyed
} else {
// Flash white when taking damage
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
}
};
self.shoot = function () {
if (self.fireTimer <= 0) {
// Shoot white holy bullets upward
if (enemies.length > 0) {
// Always fire 3 bullets in a spread pattern upward
var bulletSpread = [-30, 0, 30]; // Spread angles in degrees
for (var i = 0; i < bulletSpread.length; i++) {
var bullet = new Bullet();
bullet.x = self.x + bulletSpread[i];
bullet.y = self.y;
bullet.damage = 8;
// Don't use auto-hit for upward shooting bullets
bullet.isAutoHit = false;
// Set custom velocity for angled shots
var angle = -Math.PI / 2 + bulletSpread[i] * Math.PI / 180; // Convert to radians, -90 degrees (up) + spread
bullet.vx = Math.cos(angle) * bullet.speed;
bullet.vy = Math.sin(angle) * bullet.speed;
bullet.hasCustomVelocity = true;
bullet.getChildAt(0).tint = 0xFFFFFF; // White bullets
bullet.scale.set(1.5, 1.5);
bullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
self.fireTimer = self.fireRate;
}
}
};
self.charge = function () {
if (!self.isCharging && self.chargeTimer <= 0 && self.chargeCooldown <= 0) {
// Find nearest enemy to charge at
if (enemies.length > 0) {
var closestEnemy = enemies[0];
var closestDist = Math.hypot(closestEnemy.x - self.x, closestEnemy.y - self.y);
for (var i = 1; i < enemies.length; i++) {
var dist = Math.hypot(enemies[i].x - self.x, enemies[i].y - self.y);
if (dist < closestDist) {
closestEnemy = enemies[i];
closestDist = dist;
}
}
// Initiate charge if enemy is not too far
if (closestDist < 800) {
self.isCharging = true;
self.chargeTimer = 90; // 1.5 seconds of charging
self.chargeTarget = closestEnemy;
// Flash before charging
LK.effects.flashObject(self, 0xFFFFFF, 500);
}
}
}
};
self.findTarget = function () {
if (enemies.length > 0) {
// Find the closest enemy
var closest = enemies[0];
var closestDist = Math.hypot(closest.x - self.x, closest.y - self.y);
for (var i = 1; i < enemies.length; i++) {
var dist = Math.hypot(enemies[i].x - self.x, enemies[i].y - self.y);
if (dist < closestDist) {
closest = enemies[i];
closestDist = dist;
}
}
self.target = closest;
} else {
self.target = null;
}
};
self.update = function () {
// Update timers
if (self.fireTimer > 0) self.fireTimer--;
if (self.chargeTimer > 0) self.chargeTimer--;
if (self.chargeCooldown > 0) self.chargeCooldown--;
// Update health bar
self.healthBar.scale.x = self.health / self.maxHealth * 1.5;
// Pulse the aura
aura.alpha = 0.2 + Math.sin(LK.ticks * 0.1) * 0.1;
// Make sure angel stays at the bottom of the screen
if (self.y < 2732 - 200) {
self.y = 2732 - 200;
}
// Handle charging behavior
if (self.isCharging) {
if (self.chargeTarget && enemies.includes(self.chargeTarget)) {
var dx = self.chargeTarget.x - self.x;
var dy = self.chargeTarget.y - self.y;
var dist = Math.hypot(dx, dy);
// Calculate angle to face the target
self.rotation = Math.atan2(dy, dx);
// Move towards target at high speed
if (dist > 20) {
self.x += dx / dist * self.speed * 4;
self.y += dy / dist * self.speed * 4;
} else {
// Hit the target
var destroyed = self.chargeTarget.takeDamage(30);
if (destroyed) {
enemiesDefeated++;
totalEnemiesDefeated++;
LK.setScore(LK.getScore() + self.chargeTarget.scoreValue);
scoreTxt.setText('Score: ' + LK.getScore());
self.chargeTarget.destroy();
enemies.splice(enemies.indexOf(self.chargeTarget), 1);
}
// End charge
self.isCharging = false;
self.chargeCooldown = 180; // 3 second cooldown
}
// Charge duration limit
if (self.chargeTimer <= 0) {
self.isCharging = false;
self.chargeCooldown = 180; // 3 second cooldown
}
} else {
self.isCharging = false;
}
} else {
// Find a target if we don't have one or current target is destroyed
if (!self.target || !enemies.includes(self.target)) {
self.findTarget();
}
// Move normally when not charging
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.hypot(dx, dy);
// Always face upward regardless of target position
self.rotation = -Math.PI / 2; // -90 degrees (upward)
// Only move horizontally at the bottom of the screen
if (dist > 300) {
// Move toward target horizontally only
self.x += dx / dist * self.speed;
// Keep at bottom of screen
self.y = 2732 - 200;
} else if (dist < 200) {
// Back away horizontally only
self.x -= dx / dist * self.speed * 0.5;
// Keep at bottom of screen
self.y = 2732 - 200;
}
// Shoot at enemies
self.shoot();
// Try to charge occasionally
if (LK.ticks % 60 === 0) {
self.charge();
}
} else {
// No target, move back toward player horizontally only
var dx = player.x - 200 - self.x;
var dist = Math.abs(dx);
// Update rotation to face upward
self.rotation = -Math.PI / 2; // Rotate to face up
if (dist > 50) {
self.x += dx / dist * self.speed;
// Keep at bottom of screen
self.y = 2732 - 200;
}
}
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
self.speed = 15;
self.damage = 1;
self.isPlayerBullet = true;
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (self.isAutoHit && self.targetEnemy && enemies.includes(self.targetEnemy)) {
// Auto-hit bullets home in on their target
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var dist = Math.hypot(dx, dy);
// If we're very close to the enemy, hit it automatically
if (dist < 30) {
// Handle direct hit
var destroyed = self.targetEnemy.takeDamage(self.damage);
if (destroyed) {
enemiesDefeated++;
totalEnemiesDefeated++;
LK.setScore(LK.getScore() + self.targetEnemy.scoreValue);
scoreTxt.setText('Score: ' + LK.getScore());
self.targetEnemy.destroy();
enemies.splice(enemies.indexOf(self.targetEnemy), 1);
}
// Mark bullet for removal
self.y = -100; // This will cause bullet to be removed in game.update
} else {
// Home in on the target with perfect tracking
self.vx = dx / dist * self.speed * 1.5;
self.vy = dy / dist * self.speed * 1.5;
self.x += self.vx;
self.y += self.vy;
// Rotate bullet to face direction of travel
self.rotation = Math.atan2(self.vy, self.vx);
}
} else if (self.hasCustomVelocity) {
// Use vectorized movement if bullet has custom velocity
self.x += self.vx;
self.y += self.vy;
// Rotate bullet to face direction of travel
self.rotation = Math.atan2(self.vy, self.vx);
} else {
// Default upward movement
self.y -= self.speed;
}
};
return self;
});
var ClonePowerUp = Container.expand(function () {
var self = Container.call(this);
var powerUpGraphics = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
// Set to cyan color
powerUpGraphics.tint = 0x00FFFF;
self.speed = 2;
self.update = function () {
self.y += self.speed;
// Pulse effect
var scale = 2 + Math.sin(LK.ticks * 0.15) * 0.2;
self.scale.set(scale, scale);
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
self.health = 2;
self.speed = 3 + Math.random() * 2;
self.fireRate = 90 + Math.floor(Math.random() * 60);
self.fireTimer = Math.floor(Math.random() * 60);
self.scoreValue = 10;
self.movingRight = Math.random() > 0.5;
self.moveCounter = 0;
self.moveChangeTime = 60 + Math.floor(Math.random() * 120);
var enemyGraphics = self.attachAsset('enemyShip', {
anchorX: 0.5,
anchorY: 0.5
});
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
// Chance to drop regular power up
if (Math.random() < 0.15) {
var powerUp = new PowerUp();
powerUp.x = self.x;
powerUp.y = self.y;
powerUps.push(powerUp);
game.addChild(powerUp);
}
// Small chance to drop clone power up
if (Math.random() < 0.05) {
var clonePowerUp = new ClonePowerUp();
clonePowerUp.x = self.x;
clonePowerUp.y = self.y;
clonePowerUps.push(clonePowerUp);
game.addChild(clonePowerUp);
}
// Explosion effect
LK.effects.flashObject(self, 0xff0000, 300);
LK.getSound('explosion').play();
return true; // Enemy is destroyed
} else {
// Flash red when taking damage
LK.effects.flashObject(self, 0xff0000, 200);
return false;
}
};
self.shoot = function () {
if (self.fireTimer <= 0) {
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + 40;
enemyBullets.push(bullet);
game.addChild(bullet);
LK.getSound('enemyShoot').play();
self.fireTimer = self.fireRate;
}
};
self.update = function () {
// Basic movement pattern
self.y += self.speed * 0.5;
// Check if player is in bottom left safe zone
var playerInSafeZone = player.x < 300 && player.y > 2732 - 300;
var safeZoneDistance = 400; // Distance to maintain from safe zone
// If player is in safe zone and enemy is getting too close, steer away
if (playerInSafeZone && self.x < safeZoneDistance && self.y > 2732 - safeZoneDistance) {
// Move right to avoid safe zone
self.x += self.speed * 1.5;
self.movingRight = true;
} else {
// Normal horizontal movement
if (self.movingRight) {
self.x += self.speed;
} else {
self.x -= self.speed;
}
}
// Reverse direction if reaching screen edges
if (self.x < 100) {
self.movingRight = true;
} else if (self.x > 2048 - 100) {
self.movingRight = false;
}
// Randomly change direction
self.moveCounter++;
if (self.moveCounter >= self.moveChangeTime) {
self.movingRight = !self.movingRight;
self.moveCounter = 0;
}
// Decrease fire timer and shoot
self.fireTimer--;
if (self.fireTimer <= 0 && !(playerInSafeZone && self.x < safeZoneDistance && self.y > 2732 - safeZoneDistance)) {
self.shoot();
}
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
self.speed = 8;
self.damage = 1;
self.isPlayerBullet = false;
var bulletGraphics = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// Allow for both upward (negative speed) and downward (positive speed) bullets
self.y += self.speed;
};
return self;
});
var Mine = Container.expand(function () {
var self = Container.call(this);
self.speed = 4;
self.damage = 15;
self.isPlayerBullet = false;
self.rotationSpeed = 0.05;
var mineGraphics = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
mineGraphics.tint = 0xFF5500;
self.update = function () {
// Allow for both upward (negative speed) and downward (positive speed) mines
self.y += self.speed;
self.rotation += self.rotationSpeed;
// Slight wobble movement
self.x += Math.sin(LK.ticks * 0.1) * 2;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
self.health = 100;
self.maxHealth = 100;
self.speed = 10;
self.fireRate = 15;
self.fireTimer = 0;
self.powerUpTimer = 0;
self.hasPowerUp = false;
self.invincible = false;
self.invincibleTimer = 0;
var playerGraphics = self.attachAsset('playerShip', {
anchorX: 0.5,
anchorY: 0.5
});
self.takeDamage = function (amount) {
if (self.invincible) return;
self.health -= amount;
if (self.health <= 0) {
self.health = 0;
} else {
// Flash player when taking damage
self.invincible = true;
self.invincibleTimer = 60; // 1 second invincibility
LK.effects.flashObject(self, 0xff0000, 1000);
LK.getSound('playerHit').play();
}
};
self.shoot = function () {
if (self.fireTimer <= 0) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y - 50;
// If player has power up, enhance bullet
if (self.hasPowerUp) {
bullet.damage = 2;
bullet.speed = 20;
bullet.scale.set(1.5, 1.5);
}
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.fireTimer = self.fireRate;
}
};
self.update = function () {
// Decrease fire timer
if (self.fireTimer > 0) {
self.fireTimer--;
}
// Handle power up timer
if (self.hasPowerUp) {
self.powerUpTimer--;
if (self.powerUpTimer <= 0) {
self.hasPowerUp = false;
playerGraphics.tint = 0xFFFFFF;
}
}
// Handle invincibility timer
if (self.invincible) {
self.invincibleTimer--;
if (self.invincibleTimer <= 0) {
self.invincible = false;
}
// Flicker effect during invincibility
self.alpha = LK.ticks % 6 < 3 ? 0.7 : 1;
} else {
self.alpha = 1;
}
};
self.activatePowerUp = function () {
self.hasPowerUp = true;
self.powerUpTimer = 300; // 5 seconds
playerGraphics.tint = 0x2ecc71; // Green tint when powered up
LK.getSound('powerUpCollect').play();
};
return self;
});
var PlayerClone = Container.expand(function () {
var self = Container.call(this);
self.health = 50;
self.maxHealth = 50;
self.target = null;
self.speed = 2; // Super slow speed (was 8)
self.fireRate = 80; // Even slower fire rate
self.fireTimer = 0;
// Create clone graphics (same as player but with different tint)
var cloneGraphics = self.attachAsset('playerShip', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
cloneGraphics.tint = 0x00FFFF; // Cyan tint
// Create health bar background
self.healthBarBg = LK.getAsset('healthBarBackground', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.3,
scaleY: 0.3,
y: -40
});
// Create health bar
self.healthBar = LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.3,
scaleY: 0.3,
y: -38
});
self.addChild(self.healthBarBg);
self.addChild(self.healthBar);
self.takeDamage = function (amount) {
self.health -= amount;
// Update health bar
self.healthBar.scale.x = self.health / self.maxHealth * 0.3;
if (self.health <= 0) {
// Clone destruction effect
LK.effects.flashObject(self, 0x00FFFF, 500);
LK.getSound('playerHit').play();
return true; // Clone is destroyed
} else {
// Flash cyan when taking damage
LK.effects.flashObject(self, 0x00FFFF, 200);
return false;
}
};
self.shoot = function () {
if (self.fireTimer <= 0) {
// Shoot auto-hit bullets in 4 cardinal directions (up, down, left, right)
for (var i = 0; i < 4; i++) {
var angle = i * Math.PI / 2; // 90 degree increments (cardinal directions only)
// Only shoot if there are enemies to hit
if (enemies.length > 0) {
// Find a random enemy to hit directly
var targetEnemy = enemies[Math.floor(Math.random() * enemies.length)];
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.damage = 10; // More damage for auto-hit bullets
bullet.isAutoHit = true; // Mark this as an auto-hit bullet
bullet.targetEnemy = targetEnemy; // Store the target enemy
bullet.speed = 20;
bullet.scale.set(1.2, 1.2);
// Set initial direction based on cardinal direction
var dirX = Math.cos(angle);
var dirY = Math.sin(angle);
bullet.vx = dirX * bullet.speed;
bullet.vy = dirY * bullet.speed;
bullet.hasCustomVelocity = true;
bullets.push(bullet);
game.addChild(bullet);
}
}
LK.getSound('shoot').play();
self.fireTimer = self.fireRate; // Slower fire rate
}
};
self.findTarget = function () {
if (enemies.length > 0) {
// Just find the closest enemy for movement purposes
var closest = enemies[0];
var closestDist = Math.hypot(closest.x - self.x, closest.y - self.y);
for (var i = 1; i < enemies.length; i++) {
var dist = Math.hypot(enemies[i].x - self.x, enemies[i].y - self.y);
if (dist < closestDist) {
closest = enemies[i];
closestDist = dist;
}
}
self.target = closest;
} else {
self.target = null;
}
};
self.update = function () {
// Find a target if we don't have one or current target is destroyed
if (!self.target || !enemies.includes(self.target)) {
self.findTarget();
}
// Always shoot if we can, regardless of position
if (self.fireTimer <= 0) {
self.shoot();
}
// Move toward target
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.hypot(dx, dy);
// Update rotation to face the target
self.rotation = Math.atan2(dy, dx);
if (dist > 200) {
// Move toward target if not too close - super slow!
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
// When close, just shoot (but don't try to get closer)
// No need to call shoot here as we do it regardless now
}
} else {
// No target, move back toward player
var dx = player.x - self.x;
var dy = player.y - 150 - self.y;
var dist = Math.hypot(dx, dy);
// Update rotation to face direction of movement
if (dist > 10) {
self.rotation = Math.atan2(dy, dx);
}
if (dist > 50) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
}
// Decrease fire timer
if (self.fireTimer > 0) {
self.fireTimer--;
}
// Update health bar position
self.healthBarBg.x = 0;
self.healthBar.x = 0;
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerUpGraphics = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.update = function () {
self.y += self.speed;
// Slight wobble effect
self.x += Math.sin(LK.ticks * 0.1) * 1.5;
// Pulse effect
var scale = 1 + Math.sin(LK.ticks * 0.15) * 0.1;
self.scale.set(scale, scale);
};
return self;
});
// 1 second
var SuperBoss = Container.expand(function () {
var self = Container.call(this);
self.health = 400;
self.maxHealth = 400;
self.speed = 3;
self.fireRate = 120;
self.fireTimer = 0;
self.laserTimer = 0;
self.laserCharging = false;
self.laserChargingTime = 180; // 3 seconds to charge
self.laserFiringTime = 120; // 2 seconds of firing
self.scoreValue = 1000;
self.movingRight = true;
self.bossDefeated = 0;
// Create super boss shark asset
var bossGraphics = self.attachAsset('sharkBoss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// No tint needed for image asset
// Create boss health bar background
self.healthBarBg = LK.getAsset('healthBarBackground', {
anchorX: 0.5,
anchorY: 0,
scaleX: 3,
scaleY: 1.5,
y: -120
});
// Create boss health bar
self.healthBar = LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0,
scaleX: 3,
scaleY: 1.5,
y: -115
});
// Create laser cannons
self.laserCannons = [];
for (var i = 0; i < 3; i++) {
var cannon = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.4,
scaleY: 3,
x: (i - 1) * 100,
// Left, center, right positioning
y: 80
});
cannon.tint = 0xFF0000; // Red tint
cannon.alpha = 0.5; // Start dim
self.laserCannons.push(cannon);
self.addChild(cannon);
}
self.addChild(self.healthBarBg);
self.addChild(self.healthBar);
self.takeDamage = function (amount) {
self.health -= amount;
// Update health bar
self.healthBar.scale.x = self.health / self.maxHealth * 3;
if (self.health <= 0) {
// Boss explosion effect - bigger and longer
LK.effects.flashObject(self, 0xff0000, 1500);
LK.getSound('explosion').play();
// Always spawn a clone power-up when boss is defeated
var clonePowerUp = new ClonePowerUp();
clonePowerUp.x = self.x;
clonePowerUp.y = self.y;
clonePowerUps.push(clonePowerUp);
game.addChild(clonePowerUp);
// Spawn 4 small exploding sharks in a circular pattern around where the boss was
var numSharks = 4;
for (var i = 0; i < numSharks; i++) {
var angle = i / numSharks * Math.PI * 2;
var tinyShark = new TinyShark();
tinyShark.x = self.x + Math.cos(angle) * 200;
tinyShark.y = self.y + Math.sin(angle) * 200;
enemies.push(tinyShark);
game.addChild(tinyShark);
}
// Show warning message about tiny sharks
var sharkWarning = new Text2("BEWARE! TINY EXPLODING SHARKS!", {
size: 90,
fill: 0x00BFFF
});
sharkWarning.anchor.set(0.5, 0.5);
LK.gui.center.addChild(sharkWarning);
// Fade out warning
tween(sharkWarning, {
alpha: 0
}, {
duration: 2500,
onFinish: function onFinish() {
sharkWarning.destroy();
}
});
return true; // Boss is destroyed
} else {
// Flash red when taking damage
LK.effects.flashObject(self, 0xff0000, 200);
return false;
}
};
self.shootBullet = function () {
if (self.fireTimer <= 0 && !self.laserCharging) {
// Shoot multiple bullets in a spread pattern
for (var i = -3; i <= 3; i++) {
var bullet = new EnemyBullet();
bullet.x = self.x + i * 40;
bullet.y = self.y + 100;
bullet.damage = 5;
bullet.scale.set(1.5, 1.5);
enemyBullets.push(bullet);
game.addChild(bullet);
}
// Also shoot mines
var mine = new Mine();
mine.x = self.x;
mine.y = self.y + 120;
mine.damage = 20;
enemyBullets.push(mine);
game.addChild(mine);
LK.getSound('enemyShoot').play();
self.fireTimer = self.fireRate;
}
};
self.chargeLasers = function () {
if (!self.laserCharging && self.laserTimer <= 0) {
self.laserCharging = true;
self.laserTimer = self.laserChargingTime;
// Show charging warning
var warningText = new Text2('WARNING: LASERS CHARGING!', {
size: 100,
fill: 0xFF0000
});
warningText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(warningText);
// Fade out warning after 2 seconds
LK.setTimeout(function () {
warningText.destroy();
}, 2000);
// Make cannons pulse while charging
for (var i = 0; i < self.laserCannons.length; i++) {
self.laserCannons[i].alpha = 0.8;
}
}
};
self.fireLasers = function () {
// Fire three deadly laser beams that extend to the bottom of the screen
for (var i = 0; i < self.laserCannons.length; i++) {
var laser = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.5,
scaleY: 50,
x: self.x + (i - 1) * 100,
y: self.y + 80,
tint: 0xFF0000
});
// Check if player is hit by laser
var laserX = self.x + (i - 1) * 100;
if (Math.abs(player.x - laserX) < 50) {
player.takeDamage(10);
}
// Check if clones are hit
for (var j = 0; j < playerClones.length; j++) {
var clone = playerClones[j];
if (Math.abs(clone.x - laserX) < 50) {
clone.takeDamage(15);
}
}
game.addChild(laser);
// Remove laser after a short time
(function (laserBeam) {
LK.setTimeout(function () {
laserBeam.destroy();
}, 100);
})(laser);
}
};
self.update = function () {
// Check if player is in bottom left safe zone
var playerInSafeZone = player.x < 300 && player.y > 2732 - 300;
var safeZoneDistance = 600; // Largest distance for super boss
// Basic movement pattern - side to side
if (playerInSafeZone && self.x < safeZoneDistance && self.y > 1800) {
// Move right to avoid safe zone
self.x += self.speed * 2;
self.movingRight = true;
} else {
if (self.movingRight) {
self.x += self.speed;
if (self.x > 2048 - 250) {
self.movingRight = false;
}
} else {
self.x -= self.speed;
if (self.x < 250) {
self.movingRight = true;
}
}
}
// Handle laser charging and firing
if (self.laserCharging) {
self.laserTimer--;
// Pulse the cannons faster as charging increases
var pulseRate = Math.max(0.6, 1 + Math.sin(LK.ticks * (0.1 + (1 - self.laserTimer / self.laserChargingTime) * 0.2)) * 0.4);
for (var i = 0; i < self.laserCannons.length; i++) {
self.laserCannons[i].alpha = pulseRate;
self.laserCannons[i].tint = 0xFF0000 + Math.floor((1 - self.laserTimer / self.laserChargingTime) * 255) * 0x10000;
}
if (self.laserTimer <= 0) {
// Fire the lasers - but don't fire at player in safe zone
if (!playerInSafeZone || self.x >= safeZoneDistance) {
self.fireLasers();
}
// Reset the charging flag and set timer for next charge
self.laserCharging = false;
self.laserTimer = self.laserFiringTime;
// Reset cannon appearance
for (var i = 0; i < self.laserCannons.length; i++) {
self.laserCannons[i].alpha = 0.5;
self.laserCannons[i].tint = 0xFF0000;
}
}
} else {
// Count down to next laser charging or normal bullet firing
self.laserTimer--;
self.fireTimer--;
// Shoot regular bullets
if (self.fireTimer <= 0 && !(playerInSafeZone && self.x < safeZoneDistance && self.y > 1800)) {
self.shootBullet();
}
// Start charging lasers again when timer expires
if (self.laserTimer <= 0 && !(playerInSafeZone && self.x < safeZoneDistance && self.y > 1800)) {
self.chargeLasers();
}
}
};
return self;
});
var TinyShark = Container.expand(function () {
var self = Container.call(this);
self.health = 15;
self.speed = 7;
self.damage = 30; // High damage when exploding
self.isPlayerBullet = false;
self.target = null;
self.exploding = false;
self.explodeRadius = 150;
// Create tiny shark graphics
var sharkGraphics = self.attachAsset('enemyShip', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
// Add a light blue tint to the sharks
sharkGraphics.tint = 0x00BFFF;
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
self.explode();
return true;
} else {
// Flash blue when taking damage
LK.effects.flashObject(self, 0x00BFFF, 200);
return false;
}
};
self.explode = function () {
self.exploding = true;
// Create explosion effect
LK.effects.flashObject(self, 0xFF5500, 500);
LK.getSound('explosion').play();
// Check if player is within explosion radius
var distToPlayer = Math.hypot(player.x - self.x, player.y - self.y);
if (distToPlayer < self.explodeRadius) {
player.takeDamage(self.damage);
}
// Check if clones are within explosion radius
for (var i = 0; i < playerClones.length; i++) {
var clone = playerClones[i];
var distToClone = Math.hypot(clone.x - self.x, clone.y - self.y);
if (distToClone < self.explodeRadius) {
clone.takeDamage(self.damage);
}
}
};
self.update = function () {
if (self.exploding) return;
// Always target the player
self.target = player;
// Check if player is in bottom left safe zone
var playerInSafeZone = player.x < 300 && player.y > 2732 - 300;
var safeZoneDistance = 450; // Distance for tiny sharks
if (self.target) {
// Avoid player in safe zone
if (playerInSafeZone && self.x < safeZoneDistance && self.y > 2732 - safeZoneDistance) {
// Move away from safe zone
self.x += self.speed * 1.2;
self.y -= self.speed * 0.5;
// Add some wobble
self.y += Math.sin(LK.ticks * 0.2) * 1.5;
} else {
// Normal shark behavior - chase the player
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.hypot(dx, dy);
// Rotate to face player
self.rotation = Math.atan2(dy, dx);
// Move toward player
if (dist > 10) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Check if close enough to explode
if (dist < 30) {
self.explode();
return;
}
// Slightly wobble while swimming
self.y += Math.sin(LK.ticks * 0.2) * 1;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state variables
var gameActive = true;
var player;
var bullets = [];
var enemies = [];
var enemyBullets = [];
var powerUps = [];
var clonePowerUps = [];
var playerClones = [];
var waveNumber = 1;
var enemiesThisWave = 5;
var enemiesDefeated = 0;
var totalEnemiesDefeated = 0;
var waveDelay = 0;
var spawnDelay = 0;
var dragNode = null;
var isBossWave = false;
var clonePowerUpSpawnTimer = 0;
var bossDefeatsCount = 0;
var superBoss = null;
// GUI elements
var healthBar;
var healthBarBg;
var scoreTxt;
var waveTxt;
var signTxt;
// Initialize game elements
function initGame() {
// Set background
game.setBackgroundColor(0x0a0a2a);
// Initialize player
player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 300;
game.addChild(player);
// Create GUI elements
createGUI();
// Start game
gameActive = true;
waveNumber = 1;
enemiesThisWave = 3; // Fewer enemies at start
enemiesDefeated = 0;
totalEnemiesDefeated = 0;
bossDefeatsCount = 0;
playerClones = [];
clonePowerUps = [];
LK.setScore(0);
// Play intense background music
LK.playMusic('bgMusic', {
loop: true,
fade: {
start: 0,
end: 1,
duration: 1000
}
});
}
function createGUI() {
// Health bar background
healthBarBg = LK.getAsset('healthBarBackground', {
anchorX: 0,
anchorY: 0,
x: 20,
y: 20
});
// Health bar
healthBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0,
x: 25,
y: 25
});
// Score text
scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(1, 0);
// Wave text
waveTxt = new Text2('Wave: 1', {
size: 60,
fill: 0xFFFFFF
});
waveTxt.anchor.set(0.5, 0);
// Create sign
signTxt = new Text2('cyan powerup = slow clone with auto-hit bullets!\nevil sharks = explosive damage!', {
size: 80,
fill: 0xFFFFFF
});
signTxt.anchor.set(1, 1);
// Add GUI elements
LK.gui.topRight.addChild(scoreTxt);
LK.gui.top.addChild(waveTxt);
LK.gui.topLeft.addChild(healthBarBg);
LK.gui.topLeft.addChild(healthBar);
LK.gui.bottomRight.addChild(signTxt);
}
// Spawn a wave of enemies
function spawnEnemyWave() {
// Check if this is a boss wave (every 3 waves after wave 3)
if (waveNumber >= 3 && (waveNumber - 3) % 3 === 0) {
isBossWave = true;
// Show boss warning message
var warningText = 'SUPER BOSS INCOMING!';
var warningColor = 0xFF0000;
// Increase music intensity for boss fights
LK.stopMusic();
LK.playMusic('bgMusic', {
volume: 1,
fade: {
start: 0.7,
end: 1,
duration: 500
}
});
var bossWarning = new Text2(warningText, {
size: 120,
fill: warningColor
});
bossWarning.anchor.set(0.5, 0.5);
LK.gui.center.addChild(bossWarning);
// Fade out warning message
tween(bossWarning, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
bossWarning.destroy();
// Spawn the boss
if (!gameActive) return;
// Always spawn the super boss
superBoss = new SuperBoss();
superBoss.x = 2048 / 2;
superBoss.y = 200;
enemies.push(superBoss);
game.addChild(superBoss);
// Show special super boss announcement
var superBossText = new Text2('BEWARE THE LASER CANNONS!', {
size: 90,
fill: 0xFF0000
});
superBossText.anchor.set(0.5, 0.5);
superBossText.y = 400;
LK.gui.center.addChild(superBossText);
// Fade out super boss text
tween(superBossText, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
superBossText.destroy();
}
});
}
});
} else {
isBossWave = false;
// Regular enemy wave with increased spawn delay to reduce crowding
for (var i = 0; i < enemiesThisWave; i++) {
// Set a longer delay for each enemy spawn (1.5 seconds instead of 1)
LK.setTimeout(function () {
if (!gameActive) return;
var enemy = new Enemy();
enemy.x = 200 + Math.random() * (2048 - 400);
enemy.y = -100 - Math.random() * 400;
// Increase difficulty with each wave
if (waveNumber > 1) {
enemy.health += Math.floor(waveNumber / 2);
enemy.speed += waveNumber * 0.2;
enemy.scoreValue += waveNumber * 2;
}
enemies.push(enemy);
game.addChild(enemy);
}, i * 1500);
}
}
}
// Handle player movement
function handleMove(x, y, obj) {
if (dragNode && gameActive) {
// Limit player movement to screen bounds
var newX = Math.max(50, Math.min(2048 - 50, x));
var newY = Math.max(50, Math.min(2732 - 50, y));
dragNode.x = newX;
dragNode.y = newY;
}
}
// Game down handler
game.down = function (x, y, obj) {
if (gameActive) {
dragNode = player;
handleMove(x, y, obj);
// Shoot when pressing down
player.shoot();
}
};
// Game move handler
game.move = handleMove;
// Game up handler
game.up = function (x, y, obj) {
dragNode = null;
};
// Update game state
game.update = function () {
if (!gameActive) return;
// Update player
player.update();
// Auto-fire for player
if (LK.ticks % 10 === 0) {
// Check if player is in bottom left corner (coordinates below 300)
if (!(player.x < 300 && player.y > 2732 - 300)) {
player.shoot();
} else {
// Auto create angel ally when player enters bottom left corner
if (playerClones.length < 3) {
var angelAlly = new AngelAlly();
angelAlly.x = player.x;
angelAlly.y = 2732 - 200; // Position at bottom of screen
angelAlly.rotation = -Math.PI / 2; // Rotate to face upward
playerClones.push(angelAlly);
game.addChild(angelAlly);
// Show message about angel joining team
var joinMessage = new Text2("ANGEL JOINS YOUR TEAM!", {
size: 90,
fill: 0xFFFFFF
});
joinMessage.anchor.set(0.5, 0.5);
LK.gui.center.addChild(joinMessage);
// Fade out message
tween(joinMessage, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
joinMessage.destroy();
}
});
}
}
}
// Update health bar
healthBar.scale.x = player.health / player.maxHealth;
// Update player clones
for (var i = playerClones.length - 1; i >= 0; i--) {
var clone = playerClones[i];
clone.update();
// Remove destroyed clones
if (clone.health <= 0) {
clone.destroy();
playerClones.splice(i, 1);
}
}
// Update clone power-ups
for (var i = clonePowerUps.length - 1; i >= 0; i--) {
var clonePowerUp = clonePowerUps[i];
clonePowerUp.update();
// Remove power-ups that go off screen
if (clonePowerUp.y > 2732 + 50) {
clonePowerUp.destroy();
clonePowerUps.splice(i, 1);
continue;
}
// Check for player collision
if (gameActive && clonePowerUp.intersects(player)) {
// Only create a new clone if we have less than 2
if (playerClones.length < 2) {
// Create a new player clone
var newClone = new PlayerClone();
newClone.x = player.x + 150;
newClone.y = player.y - 150;
playerClones.push(newClone);
game.addChild(newClone);
// Play power-up sound
LK.getSound('powerUpCollect').play();
} else {
// Flash player to indicate max clones reached
LK.effects.flashObject(player, 0x00FFFF, 300);
}
// Remove the power-up
clonePowerUp.destroy();
clonePowerUps.splice(i, 1);
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
bullet.update();
// Remove bullets that go off screen
if (bullet.y < -50) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check for enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bullet.intersects(enemy)) {
// Enemy takes damage
var destroyed = enemy.takeDamage(bullet.damage);
if (destroyed) {
enemiesDefeated++;
totalEnemiesDefeated++;
LK.setScore(LK.getScore() + enemy.scoreValue);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if we defeated a boss
if (enemy === superBoss) {
bossDefeatsCount++;
}
enemy.destroy();
enemies.splice(j, 1);
}
// Remove bullet
bullet.destroy();
bullets.splice(i, 1);
break;
}
}
}
// Update enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var bullet = enemyBullets[i];
bullet.update();
// Remove bullets that go off screen
if (bullet.y > 2732 + 50) {
bullet.destroy();
enemyBullets.splice(i, 1);
continue;
}
// Check for player collision
if (gameActive && bullet.intersects(player)) {
player.takeDamage(bullet.damage);
// We've removed the angel boss, so we don't need this code anymore
bullet.destroy();
enemyBullets.splice(i, 1);
// Check if player is defeated
if (player.health <= 0) {
gameActive = false;
LK.showGameOver();
}
continue;
}
// Check for clone collisions
var cloneHit = false;
for (var j = playerClones.length - 1; j >= 0; j--) {
var clone = playerClones[j];
if (bullet.intersects(clone)) {
var destroyed = clone.takeDamage(bullet.damage);
if (destroyed) {
clone.destroy();
playerClones.splice(j, 1);
}
bullet.destroy();
enemyBullets.splice(i, 1);
cloneHit = true;
break;
}
}
if (cloneHit) continue;
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
enemy.update();
// Enemies die when they reach the bottom of the screen
if (enemy.y > 2732 - 50) {
// Grant score for the enemy
LK.setScore(LK.getScore() + enemy.scoreValue);
scoreTxt.setText('Score: ' + LK.getScore());
// Show explosion effect
LK.effects.flashObject(enemy, 0xff0000, 300);
LK.getSound('explosion').play();
// Remove the enemy
enemy.destroy();
enemies.splice(i, 1);
continue;
}
// Check for collision with player
if (gameActive && enemy.intersects(player)) {
player.takeDamage(10);
// Destroy enemy on collision
enemy.destroy();
enemies.splice(i, 1);
// Check if player is defeated
if (player.health <= 0) {
gameActive = false;
LK.showGameOver();
}
}
}
// Update power-ups
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
powerUp.update();
// Remove power-ups that go off screen
if (powerUp.y > 2732 + 50) {
powerUp.destroy();
powerUps.splice(i, 1);
continue;
}
// Check for player collision
if (gameActive && powerUp.intersects(player)) {
player.activatePowerUp();
powerUp.destroy();
powerUps.splice(i, 1);
}
}
// Check if wave is complete - we simply need to check if enemies array is empty
if (enemies.length === 0 && waveDelay <= 0) {
// Start next wave
waveNumber++;
// Implement infinite waves with fewer enemies to prevent overcrowding
if (waveNumber % 15 === 1) {
// Reset back to first wave pattern every 15 waves
enemiesThisWave = 5; // Start with fewer enemies
} else if (waveNumber % 15 <= 3) {
enemiesThisWave = 5 + waveNumber % 15; // Slower increasing difficulty
} else if (waveNumber % 15 <= 6) {
enemiesThisWave = 8 - waveNumber % 15 * 0.5; // Start reducing
} else if (waveNumber % 15 <= 10) {
enemiesThisWave = 5 - Math.floor(waveNumber % 15 / 3); // Further reduction
} else {
enemiesThisWave = Math.max(2, 7 - waveNumber % 15); // Minimum of 2 enemies
}
enemiesDefeated = 0;
waveTxt.setText('Wave: ' + waveNumber);
// Show wave message
var waveMessage = new Text2('WAVE ' + waveNumber, {
size: 120,
fill: 0xFFFFFF
});
waveMessage.anchor.set(0.5, 0.5);
LK.gui.center.addChild(waveMessage);
// Fade out wave message
tween(waveMessage, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
waveMessage.destroy();
}
});
// Set delay before spawning next wave
waveDelay = 120; // 2 seconds
}
// Count down wave delay
if (waveDelay > 0) {
waveDelay--;
if (waveDelay === 0) {
// Spawn next wave
spawnEnemyWave();
}
}
// Count down spawn delay
if (spawnDelay > 0) {
spawnDelay--;
if (spawnDelay === 0) {
// Start first wave
spawnEnemyWave();
}
}
};
// Initialize the game
initGame();
// Set a short delay before spawning the first wave
spawnDelay = 60;