User prompt
Make enemy4 start to spawn after 600 score only in standard and god modes
User prompt
Make enemy4 random explosion timer maximum 1,5 seconds
User prompt
Make enemy4 spawn from the 0 score in score mode
User prompt
Make enemy4 spawn in score mode too
User prompt
Do it when hero dies in score mode
User prompt
stop scoremode music and play gameover music at name entering section in score mode
User prompt
When game over in score mode, stop scoremode music and play gameover music
User prompt
In scoremode play scoremode music
User prompt
Make enemy4 explode after getting killed by hero too
User prompt
Set ulti cooldown to 20 seconds
User prompt
Decrease x value of score entities by 60
User prompt
Increase y value of top 10 scores text by 30
User prompt
Increase y value of top 10 scores text and score entries by 100
User prompt
Decrease y value of top 10 scores text and score entries by 70
User prompt
Make scoreboard container background bigger
User prompt
Place scoreboard entries to x: -240
User prompt
Place scoreboard entries to x: -120
User prompt
Make scoreboard entries left aligned
User prompt
No locate them to x: 0
User prompt
No locate them to x: -120
User prompt
Locate scores and names to x: 1380 too
User prompt
Make only score texts has same x value with scoreboard text
User prompt
Replace the letter buttons in the gameover screen and place them like a q keyboard
User prompt
Stop the game when gameover screen in scoremode
User prompt
Please fix the bug: 'TypeError: LK.prompt is not a function' in or related to this line: 'LK.prompt("Enter your nickname:", "", function (nickname) {' Line Number: 2659
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Beam item class var BeamItem = Container.expand(function () { var self = Container.call(this); var beamSprite = self.attachAsset('beamItem', { anchorX: 0.5, anchorY: 0.5 }); beamSprite.width = 90; beamSprite.height = 90; beamSprite.alpha = 0.95; self.update = function () { self.y += 12 * (typeof gameSpeed !== "undefined" ? gameSpeed : 1); }; return self; }); // Enemy type 1: moves straight down var Enemy1 = Container.expand(function () { var self = Container.call(this); var enemySprite = self.attachAsset('enemy1', { anchorX: 0.5, anchorY: 0.5 }); enemySprite.width = 200; enemySprite.height = 200; self.speed = 8 + Math.random() * 4; self.update = function () { self.y += self.speed * (typeof gameSpeed !== "undefined" ? gameSpeed : 1); }; return self; }); // Enemy type 2: moves in sine wave var Enemy2 = Container.expand(function () { var self = Container.call(this); var enemySprite = self.attachAsset('enemy2', { anchorX: 0.5, anchorY: 0.5 }); enemySprite.width = 160; enemySprite.height = 160; self.speed = 7 + Math.random() * 3; self.waveAmplitude = 120 + Math.random() * 80; self.waveSpeed = 0.02 + Math.random() * 0.01; self.baseX = 0; self.t = 0; self.update = function () { var speedMult = typeof gameSpeed !== "undefined" ? gameSpeed : 1; self.y += self.speed * speedMult; self.t += self.waveSpeed * speedMult; self.x = self.baseX + Math.sin(self.t * 6.28) * self.waveAmplitude; }; return self; }); // Strong Enemy3: needs 5 hits to die, moves straight down, larger and purple var Enemy3 = Container.expand(function () { var self = Container.call(this); var enemySprite = self.attachAsset('enemy3', { anchorX: 0.5, anchorY: 0.5 }); enemySprite.width = 260; enemySprite.height = 260; self.speed = 6 + Math.random() * 2; self.hp = 5; self.update = function () { self.y += self.speed * (typeof gameSpeed !== "undefined" ? gameSpeed : 1); }; return self; }); // Enemy4: new enemy type, moves diagonally, bounces off screen edges, explodes when near hero, needs 3 hits to die var Enemy4 = Container.expand(function () { var self = Container.call(this); var enemySprite = self.attachAsset('Enemy4', { anchorX: 0.5, anchorY: 0.5 }); enemySprite.width = 180; enemySprite.height = 180; self.speedX = (Math.random() < 0.5 ? 1 : -1) * (7 + Math.random() * 3); self.speedY = 7 + Math.random() * 3; self.exploded = false; self.hp = 3; // Needs 3 hits to die self.update = function () { // Initialize random explosion timer if not set if (typeof self.randomExplosionTimer === "undefined") { // Random explosion time between 2-8 seconds (120-480 frames at 60fps) self.randomExplosionTimer = 120 + Math.floor(Math.random() * 361); } var speedMult = typeof gameSpeed !== "undefined" ? gameSpeed : 1; // Countdown random explosion timer if (!self.exploded) { self.randomExplosionTimer -= speedMult; // Check for random explosion if (self.randomExplosionTimer <= 0) { self.exploded = true; // Flash explosion effect LK.effects.flashObject(self, 0xffffff, 120); // Throw 4 bullets around if (typeof enemyBullets !== "undefined" && typeof game !== "undefined") { for (var bulletDir = 0; bulletDir < 4; bulletDir++) { var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y; // 4 directions: up, right, down, left (90 degrees apart) var angle = bulletDir * Math.PI / 2; var speed = 20; bullet.speedX = Math.cos(angle) * speed; bullet.speedY = Math.sin(angle) * speed; enemyBullets.push(bullet); game.addChild(bullet); } LK.getSound('enemyShoot').play(); } // Play explosion animation (particles) var particleCount = 16; for (var pi = 0; pi < particleCount; pi++) { var part = LK.getAsset('Enemy4', { anchorX: 0.5, anchorY: 0.5 }); part.x = self.x; part.y = self.y; part.width = 24 + Math.random() * 24; part.height = 24 + Math.random() * 24; part.alpha = 0.8 + Math.random() * 0.2; var angle = Math.PI * 2 * (pi / particleCount) + Math.random() * 0.2; var speed = 12 + Math.random() * 8; var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = 10 + Math.floor(Math.random() * 10); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.88 + Math.random() * 0.06; if (this.life <= 0) { this.destroy(); } }; if (typeof game !== "undefined") { game.addChild(part); } } // Remove Enemy4 from game/enemies array on next update if (typeof self.destroy === "function") { self.destroy(); } return; } } // --- Enemy4 parabolic movement to attack hero from behind --- // Only move if hero exists and Enemy4 is not exploded if (typeof hero !== "undefined" && !self.exploded) { // Initialize parabolic movement if not already started if (typeof self.parabolicStarted === "undefined") { self.parabolicStarted = true; self.startX = self.x; self.startY = self.y; // Calculate target position behind the hero var heroBackOffset = 200; // Distance behind hero var targetX = hero.x; var targetY = hero.y + heroBackOffset; // Position behind hero // Calculate control point for parabolic curve (creates the arc) var midX = (self.startX + targetX) / 2; var midY = Math.min(self.startY, targetY) - 300; // Arc height above both points self.controlX = midX + (Math.random() - 0.5) * 400; // Add some randomness self.controlY = midY; // Set final target self.targetX = targetX; self.targetY = targetY; // Animation progress self.progress = 0; self.duration = 180; // 3 seconds at 60fps } // Update parabolic movement if (self.progress < 1) { self.progress += 1 / self.duration * speedMult; if (self.progress > 1) self.progress = 1; // Quadratic Bezier curve calculation var t = self.progress; var invT = 1 - t; // B(t) = (1-t)²P₀ + 2(1-t)tP₁ + t²P₂ self.x = invT * invT * self.startX + 2 * invT * t * self.controlX + t * t * self.targetX; self.y = invT * invT * self.startY + 2 * invT * t * self.controlY + t * t * self.targetY; } else { // After parabolic movement, move straight toward hero var dx = hero.x - self.x; var dy = hero.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); var speed = 18; if (dist > 0) { self.speedX = dx / dist * speed; self.speedY = dy / dist * speed; self.x += self.speedX * speedMult; self.y += self.speedY * speedMult; } } } // Bounce off left/right edges only if not in parabolic movement if (typeof self.progress === "undefined" || self.progress >= 1) { if (self.x - enemySprite.width / 2 <= 0 && self.speedX < 0 || self.x + enemySprite.width / 2 >= 2048 && self.speedX > 0) { self.speedX *= -1; } } // --- Enemy4 explodes and deals 2 damage when near or colliding with hero --- if (typeof hero !== "undefined" && !self.exploded && ( // Collides with hero self.intersects(hero) || // Or is "near" hero (distance < 120px) Math.abs(self.x - hero.x) < 120 && Math.abs(self.y - hero.y) < 120)) { self.exploded = true; // Tiny flash/explosion effect on Enemy4 itself LK.effects.flashObject(self, 0xffffff, 120); // Only deal damage if not in godMode or shieldActive if (!godMode && !shieldActive) { LK.getSound('heroHit').play(); LK.effects.flashScreen(0xff0000, 800); if (typeof heroHealth !== "undefined") { heroHealth -= 2; if (heroHealth < 0) { heroHealth = 0; } if (typeof updateHealthBar === "function") { updateHealthBar(); } if (typeof updateDemoHealthSquares === "function") { updateDemoHealthSquares(); } if (heroHealth <= 0) { LK.getSound('Death').play(); LK.showGameOver(); return; } } } else if (shieldActive) { LK.effects.flashObject(hero, 0x00ffff, 200); } // Play explosion animation (particles) var particleCount = 16; for (var pi = 0; pi < particleCount; pi++) { var part = LK.getAsset('Enemy4', { anchorX: 0.5, anchorY: 0.5 }); part.x = self.x; part.y = self.y; part.width = 24 + Math.random() * 24; part.height = 24 + Math.random() * 24; part.alpha = 0.8 + Math.random() * 0.2; var angle = Math.PI * 2 * (pi / particleCount) + Math.random() * 0.2; var speed = 12 + Math.random() * 8; var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = 10 + Math.floor(Math.random() * 10); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.88 + Math.random() * 0.06; if (this.life <= 0) { this.destroy(); } }; if (typeof game !== "undefined") { game.addChild(part); } } // Remove Enemy4 from game/enemies array on next update if (typeof self.destroy === "function") { self.destroy(); } // Remove from enemies[] in main game.update (handled by main loop) return; } // Enemy4 now ignores proximity to hero and does not explode or interact when flying through the hero }; return self; }); // Enemy bullet class var EnemyBullet = Container.expand(function () { var self = Container.call(this); var bulletSprite = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speedX = 0; self.speedY = 16; self.update = function () { var speedMult = typeof gameSpeed !== "undefined" ? gameSpeed : 1; self.x += self.speedX * speedMult; self.y += self.speedY * speedMult; }; return self; }); // Extraordinary EnemyX: spawns only once after 250 score, unique appearance and behavior var EnemyX = Container.expand(function () { var self = Container.call(this); // Use dedicated enemyX asset var enemySprite = self.attachAsset('enemyX', { anchorX: 0.5, anchorY: 0.5 }); enemySprite.width = 600; enemySprite.height = 600; enemySprite.alpha = 0.98; self.speed = 4 + Math.random() * 2; // Set EnemyX health to 500 in boss mode, 250 in normal mode self.hp = typeof bossMode !== "undefined" && bossMode ? 500 : 250; // much higher HP (500 hits required) // --- EnemyX shooting logic --- self.shootTick = 0; self.beamCooldown = 0; self.update = function () { // Boss mode: randomly drop health potion, shield, or beam items from EnemyX (less frequently) if (typeof bossMode !== "undefined" && bossMode && Math.random() < 0.005) { // 1/3 chance for each item var dropType = Math.floor(Math.random() * 3); var item; if (dropType === 0) { item = new HealthPotion(); } else if (dropType === 1) { item = new ShieldItem(); } else { item = new BeamItem(); } item.x = self.x + (Math.random() - 0.5) * 120; item.y = self.y + 120; if (typeof items !== "undefined" && typeof game !== "undefined") { items.push(item); game.addChild(item); } } // Only allow EnemyX to wander in the upper part of the screen (y between 0 and 500) if (typeof self.t === "undefined") { self.t = Math.random() * 2; } var speedMult = typeof gameSpeed !== "undefined" ? gameSpeed : 1; self.t += 0.012 * speedMult; // Sway left/right slowly self.x += Math.sin(self.t * 2.5) * 6 * speedMult; // Restrict vertical movement: only allow y to increase up to a certain limit, then bounce back up if (typeof self.directionY === "undefined") { self.directionY = 1; } // Set upper and lower bounds for wandering var upperY = 60 + enemySprite.height / 2; var lowerY = 420 + enemySprite.height / 2; // Move down or up depending on direction self.y += self.speed * self.directionY * 0.5 * speedMult; // much slower vertical movement // If reached lower bound, go up; if reached upper bound, go down if (self.y >= lowerY) { self.y = lowerY; self.directionY = -1; } else if (self.y <= upperY) { self.y = upperY; self.directionY = 1; } // --- Shooting less frequently --- self.shootTick = (self.shootTick || 0) + speedMult; self.beamCooldown = (self.beamCooldown || 0) - speedMult; // Shoot a bullet every 90-150 ticks (randomized, less frequent) if (self.shootTick >= 90 + Math.floor(Math.random() * 61)) { if (typeof game !== "undefined" && typeof hero !== "undefined") { // Shoot 3-way spread for (var i = -1; i <= 1; i++) { var bullet = new EnemyBullet(); bullet.x = self.x + i * 60; bullet.y = self.y + 120; // Aim at hero, but with spread var dx = hero.x - bullet.x + i * 80; var dy = hero.y - bullet.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0) { bullet.speedX = dx / len * 20; bullet.speedY = dy / len * 20; } enemyBullets.push(bullet); game.addChild(bullet); LK.getSound('enemyShoot').play(); } } self.shootTick = 0; } // Sometimes shoot a beam down (every 360-540 ticks, much more rare, and only hit when visually active) if (self.beamCooldown <= 0 && Math.random() < 0.012) { if (typeof game !== "undefined") { // Visual: big vertical beam var beam = LK.getAsset('beamItem', { anchorX: 0.5, anchorY: 0, x: self.x, y: self.y + enemySprite.height / 2 }); beam.width = 120; beam.height = 2732 - (self.y + enemySprite.height / 2); beam.alpha = 0.38 + Math.random() * 0.12; game.addChild(beam); // Beam effect: damage hero if in column for 60 frames, but only on first 20 frames is it "active" beam.life = 60; beam.activeFrames = 20; beam.update = function () { this.life--; // Flicker effect this.alpha = 0.32 + Math.random() * 0.18; // Only hit hero if beam is visually active (first 20 frames) if (this.life > 60 - this.activeFrames) { if (typeof hero !== "undefined" && hero.y > this.y && hero.y < this.y + this.height) { if (Math.abs(hero.x - this.x) < this.width / 2 + hero.width / 2) { if (!godMode && !shieldActive && this.life === 60 - this.activeFrames + 1) { LK.getSound('heroHit').play(); LK.effects.flashScreen(0xff0000, 800); heroHealth--; updateHealthBar(); updateDemoHealthSquares && updateDemoHealthSquares(); if (heroHealth <= 0) { LK.getSound('Death').play(); LK.showGameOver(); return; } } else if (!godMode && shieldActive && this.life === 60 - this.activeFrames + 1) { LK.effects.flashObject(hero, 0x00ffff, 200); } } } } if (this.life <= 0) { this.destroy(); } }; // Add to enemyBullets for update/removal enemyBullets.push(beam); // Play laser sound LK.getSound('laser').play(); // Set cooldown for next beam (much more rare) self.beamCooldown = 360 + Math.floor(Math.random() * 180); } } }; return self; }); // EnemyZ: New boss type, appears as a large, fast, zig-zagging boss with high HP and double beam attack var EnemyZ = Container.expand(function () { var self = Container.call(this); // Use enemyZ asset, large size var enemySprite = self.attachAsset('EnemyZ', { anchorX: 0.5, anchorY: 0.5 }); enemySprite.width = 520; enemySprite.height = 520; enemySprite.alpha = 0.99; self.speed = 10 + Math.random() * 3; self.hp = typeof bossMode !== "undefined" && bossMode ? 800 : 500; // EnemyZ health: 800 in boss mode, 500 in normal mode self.t = Math.random() * 2; self.zigzagAmplitude = 320 + Math.random() * 80; self.zigzagSpeed = 0.018 + Math.random() * 0.008; self.baseX = 2048 / 2; self.directionY = 1; self.shootTick = 0; self.beamCooldown = 0; self.update = function () { // Zig-zag movement, stays in upper half of screen var speedMult = typeof gameSpeed !== "undefined" ? gameSpeed : 1; self.t += self.zigzagSpeed * speedMult; self.x = self.baseX + Math.sin(self.t * 2.5) * self.zigzagAmplitude; // Restrict vertical movement: only allow y to increase up to a certain limit, then bounce back up var upperY = 80 + enemySprite.height / 2; var lowerY = 600 + enemySprite.height / 2; self.y += self.speed * self.directionY * 0.5 * speedMult; if (self.y >= lowerY) { self.y = lowerY; self.directionY = -1; } else if (self.y <= upperY) { self.y = upperY; self.directionY = 1; } // --- Shooting logic: fires 5-way spread every 48-72 ticks --- self.shootTick = (self.shootTick || 0) + speedMult; self.beamCooldown = (self.beamCooldown || 0) - speedMult; if (self.shootTick >= 72 + Math.floor(Math.random() * 49)) { if (typeof game !== "undefined" && typeof hero !== "undefined") { // Shoot 5-way spread for (var i = -2; i <= 2; i++) { var bullet = new EnemyBullet(); bullet.x = self.x + i * 60; bullet.y = self.y + 120; // Aim at hero, but with spread var dx = hero.x - bullet.x + i * 60; var dy = hero.y - bullet.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0) { bullet.speedX = dx / len * 22; bullet.speedY = dy / len * 22; } enemyBullets.push(bullet); game.addChild(bullet); LK.getSound('enemyShoot').play(); } } self.shootTick = 0; } // --- Single explosive attack: one explosive bullet at once, more frequent than EnemyX --- if (self.beamCooldown <= 0 && Math.random() < 0.025) { if (typeof game !== "undefined") { // Fire one explosive bullet at hero var explosive = new EnemyBullet(); explosive.x = self.x; explosive.y = self.y + 120; // Aim at hero var dx = hero.x - explosive.x; var dy = hero.y - explosive.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0) { explosive.speedX = dx / len * 25; explosive.speedY = dy / len * 25; } // Make explosive bullets larger and more visible explosive.children[0].width = 60; explosive.children[0].height = 60; explosive.children[0].tint = 0xff4400; // Orange tint for explosive look // Add explosive properties explosive.isExplosive = true; explosive.explosionTimer = 12; // 0.2 seconds until explosion (12 ticks at 60fps) explosive.hasExploded = false; explosive.explosionRadius = 200; // Area damage radius enemyBullets.push(explosive); game.addChild(explosive); LK.getSound('enemyShoot').play(); // Set cooldown for next explosive (more frequent than EnemyX) self.beamCooldown = 180 + Math.floor(Math.random() * 90); } } }; return self; }); // Health potion class (separate asset) var HealthPotion = Container.expand(function () { var self = Container.call(this); var potionSprite = self.attachAsset('healthPotion', { anchorX: 0.5, anchorY: 0.5 }); potionSprite.width = 90; potionSprite.height = 90; potionSprite.alpha = 0.95; self.update = function () { self.y += 16 * (typeof gameSpeed !== "undefined" ? gameSpeed : 1); }; return self; }); // Hero class var Hero = Container.expand(function () { var self = Container.call(this); var heroSprite = self.attachAsset('hero', { anchorX: 0.5, anchorY: 0.5 }); self.radius = heroSprite.width / 2; self.shootCooldown = 0; self.update = function () { if (self.shootCooldown > 0) { self.shootCooldown--; } }; return self; }); // Hero bullet class var HeroBullet = Container.expand(function () { var self = Container.call(this); var bulletSprite = self.attachAsset('heroBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -32; self.update = function () { self.y += self.speed * (typeof gameSpeed !== "undefined" ? gameSpeed : 1); }; return self; }); // Shield item class var ShieldItem = Container.expand(function () { var self = Container.call(this); var shieldSprite = self.attachAsset('shieldItem', { anchorX: 0.5, anchorY: 0.5 }); shieldSprite.width = 90; shieldSprite.height = 90; shieldSprite.alpha = 0.95; // Make it much brighter/less transparent self.update = function () { self.y += 12 * (typeof gameSpeed !== "undefined" ? gameSpeed : 1); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Add 6 healthbar assets to the screen (for demo/test, not for health UI) function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var greenSquares = []; // (defer adding to game until after background is added) for (var i = 0; i < 6; i++) { var square = LK.getAsset('Healthbar', { anchorX: 0, anchorY: 1, x: 60 + i * 70, y: 2732 - 60, width: 60, height: 60, tint: 0x44ff44 }); greenSquares.push(square); } // Helper to update demo health squares to match heroHealth function updateDemoHealthSquares() { for (var i = 0; i < greenSquares.length; i++) { if (i < heroHealth) { greenSquares[i].visible = true; } else { greenSquares[i].visible = false; } } } // separate asset for health potion // Play menu music and stop background music when menu is shown // --- MENU OVERLAY --- // Add menu asset as background in menu, and background asset in game var menuBgSprite = LK.getAsset('Menu', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); menuBgSprite.width = 2048; menuBgSprite.height = 2732; game.addChild(menuBgSprite); var backgroundSprite = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); // Scale background to fill the screen (2048x2732) var bgOriginalWidth = backgroundSprite.width; var bgOriginalHeight = backgroundSprite.height; var scaleX = 2048 / bgOriginalWidth; var scaleY = 2732 / bgOriginalHeight; var scale = Math.max(scaleX, scaleY); // cover entire area backgroundSprite.width = bgOriginalWidth * scale; backgroundSprite.height = bgOriginalHeight * scale; // Center if needed (in case aspect ratio doesn't match) backgroundSprite.x = (2048 - backgroundSprite.width) / 2; backgroundSprite.y = (2732 - backgroundSprite.height) / 2; backgroundSprite.visible = false; // Only show in game, not in menu game.addChild(backgroundSprite); // Add green squares after background so they are in front for (var i = 0; i < greenSquares.length; i++) { greenSquares[i].visible = false; // Hide in menu game.addChild(greenSquares[i]); } LK.stopMusic(); LK.playMusic('Menu'); var menuOverlay = new Container(); menuOverlay.zIndex = 10000; // ensure on top // Title text var titleTxt = new Text2("GALAXY DODGE SHOOTER", { size: 160, fill: "#fff", fontWeight: "bold" }); titleTxt.anchor.set(0.5, 0); titleTxt.x = 2048 / 2; titleTxt.y = 220; menuOverlay.addChild(titleTxt); // Play button background var playBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); playBtnBg.width = 600; playBtnBg.height = 220; playBtnBg.alpha = 0.7; playBtnBg.x = 2048 / 2; playBtnBg.y = 700; menuOverlay.addChild(playBtnBg); // Play button text var playBtn = new Text2("PLAY", { size: 140, fill: 0x00EAFF, fontWeight: "bold" }); playBtn.anchor.set(0.5, 0.5); playBtn.x = 2048 / 2; playBtn.y = 700; menuOverlay.addChild(playBtn); // Game mode variables var godMode = false; var bossMode = false; var scoreMode = false; // Add music toggle button to menu var musicOn = true; // Place music button below play button (with margin) var musicBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); musicBtnBg.width = 340; musicBtnBg.height = 140; musicBtnBg.alpha = 0.7; musicBtnBg.x = 2048 / 2; musicBtnBg.y = 700 + 220 / 2 + 60 + musicBtnBg.height / 2; // below playBtnBg menuOverlay.addChild(musicBtnBg); var musicBtn = new Text2("MUSIC: ON", { size: 80, fill: 0x00EAFF, fontWeight: "bold" }); musicBtn.anchor.set(0.5, 0.5); musicBtn.x = 2048 / 2; musicBtn.y = musicBtnBg.y; menuOverlay.addChild(musicBtn); // Add help button below music button var helpBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); helpBtnBg.width = 340; helpBtnBg.height = 140; helpBtnBg.alpha = 0.7; helpBtnBg.x = 2048 / 2; helpBtnBg.y = musicBtnBg.y + musicBtnBg.height / 2 + 60 + helpBtnBg.height / 2; menuOverlay.addChild(helpBtnBg); var helpBtn = new Text2("HELP", { size: 80, fill: 0x00EAFF, fontWeight: "bold" }); helpBtn.anchor.set(0.5, 0.5); helpBtn.x = 2048 / 2; helpBtn.y = helpBtnBg.y; menuOverlay.addChild(helpBtn); // Add scoreboard container with scoreboard asset background var scoreboardBg = LK.getAsset('Scoreboard', { anchorX: 0.5, anchorY: 0 }); // Make the scoreboard background bigger scoreboardBg.width = 1100; scoreboardBg.height = 1400; scoreboardBg.alpha = 0.9; scoreboardBg.x = 1500; // Further right side of screen scoreboardBg.y = helpBtnBg.y + helpBtnBg.height / 2 + 80; // Below help button with margin menuOverlay.addChild(scoreboardBg); var scoreboardContainer = new Container(); scoreboardContainer.x = scoreboardBg.x; scoreboardContainer.y = scoreboardBg.y; menuOverlay.addChild(scoreboardContainer); // Scoreboard title var scoreboardTitle = new Text2("TOP 10 SCORES", { size: 70, fill: "#fff", fontWeight: "bold" }); scoreboardTitle.anchor.set(0.5, 0); scoreboardTitle.x = 0; scoreboardTitle.y = 130; scoreboardContainer.addChild(scoreboardTitle); // Array to hold scoreboard text objects var scoreboardTexts = []; // Function to update scoreboard display function updateScoreboard() { // Get current high scores and names from storage as individual keys var highScores = []; var highScoreNames = []; for (var i = 0; i < 10; i++) { var score = storage["highScore_" + i]; var name = storage["highScoreName_" + i]; if (score !== undefined && score !== null) { highScores.push(score); highScoreNames.push(name || "Player"); } } // Clear existing scoreboard text objects for (var i = 0; i < scoreboardTexts.length; i++) { scoreboardContainer.removeChild(scoreboardTexts[i]); } scoreboardTexts = []; // Display top 10 scores for (var i = 0; i < Math.min(10, highScores.length); i++) { var score = highScores[i]; var name = highScoreNames[i] || "Player"; var displayText = i + 1 + ". " + name + " - " + score; var scoreText = new Text2(displayText, { size: 70, fill: "#fff" }); scoreText.anchor.set(0, 0); scoreText.x = -300; scoreText.y = 260 + i * 80; // Space entries 80 pixels apart, start at 260 scoreboardContainer.addChild(scoreText); scoreboardTexts.push(scoreText); } // If no scores yet, show placeholder if (highScores.length === 0) { var noScoresText = new Text2("No scores yet!", { size: 70, fill: "#aaa" }); noScoresText.anchor.set(0, 0); noScoresText.x = -300; noScoresText.y = 260; scoreboardContainer.addChild(noScoresText); scoreboardTexts.push(noScoresText); } } // Initialize scoreboard updateScoreboard(); // Function to save high score function saveHighScore(newScore, nickname) { // Only save scores from score mode if (!scoreMode) return; // Get current high scores using individual storage keys var highScores = []; var highScoreNames = []; for (var i = 0; i < 10; i++) { var score = storage["highScore_" + i]; var name = storage["highScoreName_" + i]; if (score !== undefined && score !== null) { highScores.push(score); highScoreNames.push(name || "Player"); } } // Use nickname if provided, otherwise fallback to Player var playerName = "Player"; if (typeof nickname === "string" && nickname.trim().length > 0) { playerName = nickname.trim(); } // Add new score and name highScores.push(newScore); highScoreNames.push(playerName); // Create combined array for sorting var combined = []; for (var i = 0; i < highScores.length; i++) { combined.push({ score: highScores[i], name: highScoreNames[i] }); } // Sort in descending order by score combined.sort(function (a, b) { return b.score - a.score; }); // Keep only top 10 if (combined.length > 10) { combined = combined.slice(0, 10); } // Separate back into two arrays var newHighScores = []; var newHighScoreNames = []; for (var i = 0; i < combined.length; i++) { newHighScores.push(combined[i].score); newHighScoreNames.push(combined[i].name); } // Save back to storage as individual keys (storage only supports literals) for (var i = 0; i < newHighScores.length; i++) { storage["highScore_" + i] = newHighScores[i]; storage["highScoreName_" + i] = newHighScoreNames[i]; } // Clear any remaining old scores beyond the new length for (var i = newHighScores.length; i < 10; i++) { if (storage["highScore_" + i] !== undefined) { storage["highScore_" + i] = undefined; storage["highScoreName_" + i] = undefined; } } // Update display updateScoreboard(); } // --- MODE SELECTION MENU --- var modeSelectionOverlay = null; function showModeSelection() { if (modeSelectionOverlay && modeSelectionOverlay.visible) { return; } if (modeSelectionOverlay) { modeSelectionOverlay.visible = true; return; } modeSelectionOverlay = new Container(); modeSelectionOverlay.zIndex = 15000; // between menu and help // Mode selection background using menu asset var bg = LK.getAsset('Menu', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); bg.width = 2048; bg.height = 2732; bg.alpha = 0.95; modeSelectionOverlay.addChild(bg); // Mode selection title var modeTitle = new Text2("SELECT GAME MODE", { size: 140, fill: "#fff", fontWeight: "bold" }); modeTitle.anchor.set(0.5, 0); modeTitle.x = 2048 / 2; modeTitle.y = 400; modeSelectionOverlay.addChild(modeTitle); // Standard Mode Button var standardBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); standardBtnBg.width = 600; standardBtnBg.height = 220; standardBtnBg.alpha = 0.7; standardBtnBg.x = 2048 / 2; standardBtnBg.y = 800; modeSelectionOverlay.addChild(standardBtnBg); var standardBtn = new Text2("STANDARD MODE", { size: 90, fill: 0x00EAFF, fontWeight: "bold" }); standardBtn.anchor.set(0.5, 0.5); standardBtn.x = 2048 / 2; standardBtn.y = 800; modeSelectionOverlay.addChild(standardBtn); // God Mode Button var godBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); godBtnBg.width = 600; godBtnBg.height = 220; godBtnBg.alpha = 0.7; godBtnBg.x = 2048 / 2; godBtnBg.y = 1100; modeSelectionOverlay.addChild(godBtnBg); var godBtn = new Text2("GOD MODE", { size: 100, fill: 0xffd700, fontWeight: "bold" }); godBtn.anchor.set(0.5, 0.5); godBtn.x = 2048 / 2; godBtn.y = 1100; modeSelectionOverlay.addChild(godBtn); // Boss Mode Button var bossBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); bossBtnBg.width = 600; bossBtnBg.height = 220; bossBtnBg.alpha = 0.7; bossBtnBg.x = 2048 / 2; bossBtnBg.y = 1400; modeSelectionOverlay.addChild(bossBtnBg); var bossBtn = new Text2("BOSS MODE", { size: 100, fill: 0xff4444, fontWeight: "bold" }); bossBtn.anchor.set(0.5, 0.5); bossBtn.x = 2048 / 2; bossBtn.y = 1400; modeSelectionOverlay.addChild(bossBtn); // Score Mode Button var scoreBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); scoreBtnBg.width = 600; scoreBtnBg.height = 220; scoreBtnBg.alpha = 0.7; scoreBtnBg.x = 2048 / 2; scoreBtnBg.y = 1700; modeSelectionOverlay.addChild(scoreBtnBg); var scoreBtn = new Text2("SCORE MODE", { size: 100, fill: 0x00EAFF, fontWeight: "bold" }); scoreBtn.anchor.set(0.5, 0.5); scoreBtn.x = 2048 / 2; scoreBtn.y = 1700; modeSelectionOverlay.addChild(scoreBtn); // Back button var backBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); backBtnBg.width = 340; backBtnBg.height = 140; backBtnBg.alpha = 0.7; backBtnBg.x = 2048 / 2; backBtnBg.y = 2000; modeSelectionOverlay.addChild(backBtnBg); var backBtn = new Text2("BACK", { size: 80, fill: 0x00EAFF, fontWeight: "bold" }); backBtn.anchor.set(0.5, 0.5); backBtn.x = 2048 / 2; backBtn.y = 2000; modeSelectionOverlay.addChild(backBtn); // Mode selection interactions modeSelectionOverlay.down = function (x, y, obj) { // Standard Mode var dx = x - standardBtn.x; var dy = y - standardBtn.y; if (Math.abs(dx) <= standardBtnBg.width / 2 && Math.abs(dy) <= standardBtnBg.height / 2) { // Start standard mode modeSelectionOverlay.visible = false; menuOverlay.visible = false; menuActive = false; godMode = false; bossMode = false; scoreMode = false; if (typeof menuBgSprite !== "undefined") { menuBgSprite.visible = false; } if (typeof backgroundSprite !== "undefined") { backgroundSprite.visible = true; } // Show UI elements healthBarBg.visible = true; healthBar.visible = true; for (var i = 0; i < healthBarVSquares.length; i++) { healthBarVSquares[i].visible = true; } ultiBtn.visible = true; ultiBtn.bg.visible = true; hero.visible = true; for (var i = 0; i < greenSquares.length; i++) { greenSquares[i].visible = true; } LK.stopMusic(); if (musicOn) { LK.playMusic('bgmusic'); } return; } // God Mode var gdx = x - godBtn.x; var gdy = y - godBtn.y; if (Math.abs(gdx) <= godBtnBg.width / 2 && Math.abs(gdy) <= godBtnBg.height / 2) { // Start god mode modeSelectionOverlay.visible = false; menuOverlay.visible = false; menuActive = false; godMode = true; bossMode = false; scoreMode = false; if (typeof menuBgSprite !== "undefined") { menuBgSprite.visible = false; } if (typeof backgroundSprite !== "undefined") { backgroundSprite.visible = true; } // Show UI elements healthBarBg.visible = true; healthBar.visible = true; for (var i = 0; i < healthBarVSquares.length; i++) { healthBarVSquares[i].visible = true; } ultiBtn.visible = true; ultiBtn.bg.visible = true; hero.visible = true; for (var i = 0; i < greenSquares.length; i++) { greenSquares[i].visible = true; } LK.stopMusic(); if (musicOn) { LK.playMusic('Godmode'); } return; } // Boss Mode var bdx = x - bossBtn.x; var bdy = y - bossBtn.y; if (Math.abs(bdx) <= bossBtnBg.width / 2 && Math.abs(bdy) <= bossBtnBg.height / 2) { // Start boss mode modeSelectionOverlay.visible = false; menuOverlay.visible = false; menuActive = false; godMode = false; bossMode = true; scoreMode = false; if (typeof menuBgSprite !== "undefined") { menuBgSprite.visible = false; } if (typeof backgroundSprite !== "undefined") { backgroundSprite.visible = true; } // Show UI elements healthBarBg.visible = true; healthBar.visible = true; for (var i = 0; i < healthBarVSquares.length; i++) { healthBarVSquares[i].visible = true; } ultiBtn.visible = true; ultiBtn.bg.visible = true; hero.visible = true; for (var i = 0; i < greenSquares.length; i++) { greenSquares[i].visible = true; } LK.stopMusic(); if (musicOn) { LK.playMusic('Boss'); } // Set up for boss mode: reset score, enemyXSpawned, etc. score = 0; scoreTxt.setText(score); enemyXSpawned = false; bossMusicPlayed = true; // already playing spawnTick = 0; return; } // Score Mode var sdx = x - scoreBtn.x; var sdy = y - scoreBtn.y; if (Math.abs(sdx) <= scoreBtnBg.width / 2 && Math.abs(sdy) <= scoreBtnBg.height / 2) { // Start score mode modeSelectionOverlay.visible = false; menuOverlay.visible = false; menuActive = false; godMode = false; bossMode = false; scoreMode = true; if (typeof menuBgSprite !== "undefined") { menuBgSprite.visible = false; } if (typeof backgroundSprite !== "undefined") { backgroundSprite.visible = true; } // Show UI elements healthBarBg.visible = true; healthBar.visible = true; for (var i = 0; i < healthBarVSquares.length; i++) { healthBarVSquares[i].visible = true; } ultiBtn.visible = true; ultiBtn.bg.visible = true; hero.visible = true; for (var i = 0; i < greenSquares.length; i++) { greenSquares[i].visible = true; } LK.stopMusic(); if (musicOn) { LK.playMusic('Scoremode'); } return; } // Back button var backDx = x - backBtn.x; var backDy = y - backBtn.y; if (Math.abs(backDx) <= backBtnBg.width / 2 && Math.abs(backDy) <= backBtnBg.height / 2) { modeSelectionOverlay.visible = false; return; } }; game.addChild(modeSelectionOverlay); } // Add menu overlay to game game.addChild(menuOverlay); // --- HELP MENU POPUP --- var helpMenuOverlay = null; function showHelpMenu() { if (helpMenuOverlay && helpMenuOverlay.visible) { return; } if (helpMenuOverlay) { helpMenuOverlay.visible = true; return; } helpMenuOverlay = new Container(); helpMenuOverlay.zIndex = 20000; // Help menu background using helpbackground asset var bg = LK.getAsset('Helpbackground', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); bg.width = 2048; bg.height = 2732; bg.alpha = 1.0; helpMenuOverlay.addChild(bg); // Help title var helpTitle = new Text2("HELP", { size: 120, fill: "#fff", fontWeight: "bold" }); helpTitle.anchor.set(0.5, 0); helpTitle.x = 2048 / 2; helpTitle.y = 320; helpMenuOverlay.addChild(helpTitle); // --- PAGE SYSTEM --- var helpPage = 0; // 0 = enemies, 1 = items // Enemy types info for help menu var enemyTypes = [{ asset: 'enemy1', name: 'Enemy 1', desc: 'Standard enemy. Moves straight down. Easy to destroy.' }, { asset: 'enemy2', name: 'Enemy 2', desc: 'Sine-wave enemy. Moves in a wavy pattern. Harder to hit.' }, { asset: 'enemy3', name: 'Enemy 3', desc: 'Strong enemy. Takes 5 hits to destroy. Large and slow.' }, { asset: 'Enemy4', name: 'Enemy 4', desc: 'Diagonal bouncer. Moves diagonally, bounces off edges, and explodes when near the hero!' }, { asset: 'enemyX', name: 'Boss Enemy X', desc: 'Boss enemy. Appears after 250 score or in Boss Mode. Very tough!' }, { asset: 'EnemyZ', name: 'Boss Enemy Z', desc: 'Boss enemy. Zig-zags, shoots double beams, and is very fast!' }]; // Item types info for help menu var itemTypes = [{ asset: 'healthPotion', name: 'Health Potion', desc: 'Restores 1 health. Pick up to heal. If at max health, does nothing.' }, { asset: 'shieldItem', name: 'Shield', desc: 'Grants a shield for 6 seconds if at max health. Absorbs one hit from enemies or bullets.' }, { asset: 'beamItem', name: 'Beam Powerup', desc: 'Enables triple-shot for a short time. Fires 3 bullets in a spread.' }]; // --- CONTAINER FOR PAGE CONTENT --- var pageContent = new Container(); helpMenuOverlay.addChild(pageContent); // --- RENDER PAGE FUNCTION --- function renderHelpPage() { // Remove all children from pageContent while (pageContent.children.length > 0) { pageContent.removeChild(pageContent.children[0]); } // Title helpTitle.setText(helpPage === 0 ? "HELP" : "ITEMS"); // Layout: vertical list, left-aligned for both image and text var startY = 500; var spacingY = 340; var leftMargin = 220; var imageTextGap = 40; var data = helpPage === 0 ? enemyTypes : itemTypes; for (var i = 0; i < data.length; i++) { var et = data[i]; // Image var img = LK.getAsset(et.asset, { anchorX: 0, anchorY: 0, x: leftMargin, y: startY + i * spacingY, width: et.asset === 'enemyX' ? 180 : 120, height: et.asset === 'enemyX' ? 180 : 120 }); pageContent.addChild(img); // Name var nameTxt = new Text2(et.name, { size: 80, fill: "#fff", fontWeight: "bold" }); nameTxt.anchor.set(0, 0); nameTxt.x = leftMargin + (et.asset === 'enemyX' ? 180 : 120) + imageTextGap; nameTxt.y = startY + i * spacingY + 10; pageContent.addChild(nameTxt); // Description var descTxt = new Text2(et.desc, { size: 60, fill: "#fff", align: "left" }); descTxt.anchor.set(0, 0); descTxt.x = leftMargin + (et.asset === 'enemyX' ? 180 : 120) + imageTextGap; descTxt.y = startY + i * spacingY + 100; pageContent.addChild(descTxt); } } renderHelpPage(); // --- PAGE NAVIGATION BUTTONS --- // Place under the last description text // Compute button Y based on number of entries in current help page var numEntries = helpPage === 0 ? enemyTypes.length : itemTypes.length; var lastDescY = 500 + (numEntries - 1) * 340 + 100 + 120; // 100 for desc offset, 120 for some margin // Close button (centered, first so it's under others visually) var closeBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); closeBtnBg.width = 340; closeBtnBg.height = 140; closeBtnBg.alpha = 0.7; closeBtnBg.x = 2048 / 2; closeBtnBg.y = lastDescY; helpMenuOverlay.addChild(closeBtnBg); var closeBtn = new Text2("CLOSE", { size: 80, fill: 0x00EAFF, fontWeight: "bold" }); closeBtn.anchor.set(0.5, 0.5); closeBtn.x = 2048 / 2; closeBtn.y = lastDescY; helpMenuOverlay.addChild(closeBtn); // Items button (right) var nextBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); nextBtnBg.width = 340; nextBtnBg.height = 140; nextBtnBg.alpha = 0.7; nextBtnBg.x = 2048 / 2 + 400; nextBtnBg.y = lastDescY; helpMenuOverlay.addChild(nextBtnBg); var nextBtn = new Text2("ITEMS", { size: 60, fill: 0x00EAFF, fontWeight: "bold" }); nextBtn.anchor.set(0.5, 0.5); nextBtn.x = nextBtnBg.x; nextBtn.y = nextBtnBg.y; helpMenuOverlay.addChild(nextBtn); // Enemies button (left) var prevBtnBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); prevBtnBg.width = 340; prevBtnBg.height = 140; prevBtnBg.alpha = 0.7; prevBtnBg.x = 2048 / 2 - 400; prevBtnBg.y = lastDescY; helpMenuOverlay.addChild(prevBtnBg); var prevBtn = new Text2("ENEMIES", { size: 60, fill: 0x00EAFF, fontWeight: "bold" }); prevBtn.anchor.set(0.5, 0.5); prevBtn.x = prevBtnBg.x; prevBtn.y = prevBtnBg.y; helpMenuOverlay.addChild(prevBtn); // Dismiss help menu on close button press, and handle page navigation helpMenuOverlay.down = function (x, y, obj) { // Close var dx = x - closeBtn.x; var dy = y - closeBtn.y; if (Math.abs(dx) <= closeBtnBg.width / 2 && Math.abs(dy) <= closeBtnBg.height / 2) { helpMenuOverlay.visible = false; return; } // Prev (ENEMIES) var pdx = x - prevBtn.x; var pdy = y - prevBtn.y; if (Math.abs(pdx) <= prevBtnBg.width / 2 && Math.abs(pdy) <= prevBtnBg.height / 2) { if (helpPage !== 0) { helpPage = 0; renderHelpPage(); } return; } // Next (ITEMS) var ndx = x - nextBtn.x; var ndy = y - nextBtn.y; if (Math.abs(ndx) <= nextBtnBg.width / 2 && Math.abs(ndy) <= nextBtnBg.height / 2) { if (helpPage !== 1) { helpPage = 1; renderHelpPage(); } return; } }; game.addChild(helpMenuOverlay); } // Menu state var menuActive = true; // Play button interaction menuOverlay.down = function (x, y, obj) { // Check if play button was pressed - show mode selection var dx = x - playBtn.x; var dy = y - playBtn.y; if (Math.abs(dx) <= playBtnBg.width / 2 && Math.abs(dy) <= playBtnBg.height / 2) { // Show mode selection menu showModeSelection(); return; } // Check if music button was pressed var mdx = x - musicBtn.x; var mdy = y - musicBtn.y; if (Math.abs(mdx) <= musicBtnBg.width / 2 && Math.abs(mdy) <= musicBtnBg.height / 2) { musicOn = !musicOn; if (musicOn) { musicBtn.setText("MUSIC: ON"); LK.playMusic('Menu'); } else { musicBtn.setText("MUSIC: OFF"); LK.stopMusic(); } return; } // Check if help button was pressed if (typeof helpBtn !== "undefined" && typeof helpBtnBg !== "undefined") { var hdx = x - helpBtn.x; var hdy = y - helpBtn.y; if (Math.abs(hdx) <= helpBtnBg.width / 2 && Math.abs(hdy) <= helpBtnBg.height / 2) { // Show help popup (custom help menu) showHelpMenu(); return; } } }; menuOverlay.move = function (x, y, obj) {}; menuOverlay.up = function (x, y, obj) {}; // Score display var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Health bar UI (horizontal, left bottom) var maxHealth = 6; var heroHealth = maxHealth; var healthBarBg = LK.getAsset('enemy2', { anchorX: 0, // left edge anchorY: 1, // bottom edge tint: 0x222222 }); healthBarBg.width = 420; healthBarBg.height = 60; healthBarBg.alpha = 0.45; // Place at left bottom, with margin (60px from left, 60px from bottom) healthBarBg.x = 60; healthBarBg.y = 2732 - 60; healthBarBg.visible = false; // Hide in menu LK.gui.bottomLeft.addChild(healthBarBg); var healthBar = LK.getAsset('enemy1', { anchorX: 0, // left edge anchorY: 1, // bottom edge tint: 0xff4444 }); healthBar.width = 420; healthBar.height = 60; healthBar.alpha = 0.85; healthBar.x = 60; healthBar.y = 2732 - 60; healthBar.visible = false; // Hide in menu LK.gui.bottomLeft.addChild(healthBar); // --- Health bar squares (vertical, left bottom) --- var healthBarVSquares = []; var squareSpacing = 12; var squareSize = 60; var baseX = 60; var baseY = 2732 - 60; for (var i = 0; i < maxHealth; i++) { var square = LK.getAsset('Healthbar', { anchorX: 0, anchorY: 1, x: baseX + i * (squareSize + squareSpacing), y: baseY, width: squareSize, height: squareSize, tint: 0x44ff44 }); square.visible = false; // Hide in menu LK.gui.bottomLeft.addChild(square); healthBarVSquares.push(square); } function updateHealthBar() { // Horizontal bar (left) healthBar.width = 420 * (heroHealth / maxHealth); if (heroHealth <= 2) { healthBar.tint = 0xff2222; } else if (heroHealth <= 4) { healthBar.tint = 0xffbb22; } else { healthBar.tint = 0x44ff44; } // Vertical bar (center) - show/hide squares and tint by health for (var i = 0; i < healthBarVSquares.length; i++) { if (i < heroHealth) { healthBarVSquares[i].visible = true; // Tint by health: green if >4, yellow if 3-4, red if 1-2 if (heroHealth <= 2) { healthBarVSquares[i].tint = 0xff2222; } else if (heroHealth <= 4) { healthBarVSquares[i].tint = 0xffbb22; } else { healthBarVSquares[i].tint = 0x44ff44; } // Add a white border highlight if health is full healthBarVSquares[i].alpha = heroHealth === maxHealth ? 1.0 : 0.95; } else { healthBarVSquares[i].visible = false; } } } updateDemoHealthSquares && updateDemoHealthSquares(); // Custom nickname prompt overlay for score mode game over function showNicknamePrompt(callback) { // Prevent multiple prompts if (game.nicknamePromptOverlay && game.nicknamePromptOverlay.visible) return; var overlay = new Container(); overlay.zIndex = 99999; // Semi-transparent background var bg = LK.getAsset('Menu', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); bg.width = 2048; bg.height = 2732; bg.alpha = 0.92; overlay.addChild(bg); // Title var title = new Text2("GAME OVER!", { size: 120, fill: "#fff", fontWeight: "bold" }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 400; overlay.addChild(title); // Prompt text var prompt = new Text2("Enter your nickname:", { size: 80, fill: "#fff" }); prompt.anchor.set(0.5, 0); prompt.x = 2048 / 2; prompt.y = 600; overlay.addChild(prompt); // Nickname input (simulate with text and +/- buttons) var nickname = ""; var maxLen = 12; var nicknameText = new Text2("_", { size: 100, fill: 0x00EAFF }); nicknameText.anchor.set(0.5, 0.5); nicknameText.x = 2048 / 2; nicknameText.y = 800; overlay.addChild(nicknameText); // On-screen keyboard (QWERTY layout: 3 rows + 1 row for numbers, backspace, OK) var qwertyRows = [["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], ["A", "S", "D", "F", "G", "H", "J", "K", "L"], ["Z", "X", "C", "V", "B", "N", "M"], ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], ["<", "OK"]]; var keyButtons = []; var keySize = 120; var keySpacing = 18; var startY = 1000; for (var row = 0; row < qwertyRows.length; row++) { var keysInRow = qwertyRows[row]; // Calculate row width for centering var rowWidth = keysInRow.length * keySize + (keysInRow.length - 1) * keySpacing; var startX = 2048 / 2 - rowWidth / 2 + keySize / 2; var y = startY + row * (keySize + keySpacing); for (var col = 0; col < keysInRow.length; col++) { var key = keysInRow[col]; var keyBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); keyBg.width = keySize; keyBg.height = keySize; keyBg.alpha = 0.7; keyBg.x = startX + col * (keySize + keySpacing); keyBg.y = y; overlay.addChild(keyBg); var keyTxt = new Text2(key, { size: 60, fill: "#fff", fontWeight: "bold" }); keyTxt.anchor.set(0.5, 0.5); keyTxt.x = keyBg.x; keyTxt.y = keyBg.y; overlay.addChild(keyTxt); keyButtons.push({ bg: keyBg, txt: keyTxt, key: key }); } } // Button interaction overlay.down = function (x, y, obj) { for (var i = 0; i < keyButtons.length; i++) { var btn = keyButtons[i]; var dx = x - btn.bg.x; var dy = y - btn.bg.y; if (Math.abs(dx) <= btn.bg.width / 2 && Math.abs(dy) <= btn.bg.height / 2) { if (btn.key === "OK") { // Accept nickname // Stop scoremode music and play gameover music if in score mode if (scoreMode) { LK.stopMusic(); if (musicOn) { LK.playMusic('Gameover'); } } overlay.visible = false; if (typeof callback === "function") callback(nickname.length > 0 ? nickname : "Player"); if (game.nicknamePromptOverlay) { game.removeChild(game.nicknamePromptOverlay); game.nicknamePromptOverlay = null; } return; } else if (btn.key === "<") { // Backspace if (nickname.length > 0) { nickname = nickname.substring(0, nickname.length - 1); nicknameText.setText(nickname.length > 0 ? nickname : "_"); } } else { // Add character if (nickname.length < maxLen) { nickname += btn.key; nicknameText.setText(nickname); } } break; } } }; // Add overlay to game and track game.addChild(overlay); game.nicknamePromptOverlay = overlay; } // Game variables var hero = new Hero(); game.addChild(hero); hero.x = 2048 / 2; hero.y = 2732 - 350; hero.visible = false; // Hide in menu // Game speed controller (only visible in god mode) var gameSpeed = 1.0; // Default speed multiplier var speedControllerVisible = false; // Speed controller UI elements - positioned on left side and made vertical var speedControllerBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); speedControllerBg.width = 180; speedControllerBg.height = 600; speedControllerBg.alpha = 0.8; speedControllerBg.x = 200; speedControllerBg.y = 500; speedControllerBg.visible = false; game.addChild(speedControllerBg); var speedControllerLabel = new Text2("1.0x", { size: 90, fill: "#fff", fontWeight: "bold" }); speedControllerLabel.anchor.set(0.5, 0.5); speedControllerLabel.x = 200; speedControllerLabel.y = 450; speedControllerLabel.visible = false; game.addChild(speedControllerLabel); // Speed decrease button var speedDecreaseBtn = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); speedDecreaseBtn.width = 120; speedDecreaseBtn.height = 120; speedDecreaseBtn.alpha = 0.7; speedDecreaseBtn.x = 200; speedDecreaseBtn.y = 500 + 180; speedDecreaseBtn.visible = false; game.addChild(speedDecreaseBtn); var speedDecreaseTxt = new Text2("-", { size: 120, fill: "#fff", fontWeight: "bold" }); speedDecreaseTxt.anchor.set(0.5, 0.5); speedDecreaseTxt.x = speedDecreaseBtn.x; speedDecreaseTxt.y = speedDecreaseBtn.y; speedDecreaseTxt.visible = false; game.addChild(speedDecreaseTxt); // Speed increase button var speedIncreaseBtn = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); speedIncreaseBtn.width = 120; speedIncreaseBtn.height = 120; speedIncreaseBtn.alpha = 0.7; speedIncreaseBtn.x = 200; speedIncreaseBtn.y = 500 - 180; speedIncreaseBtn.visible = false; game.addChild(speedIncreaseBtn); var speedIncreaseTxt = new Text2("+", { size: 120, fill: "#fff", fontWeight: "bold" }); speedIncreaseTxt.anchor.set(0.5, 0.5); speedIncreaseTxt.x = speedIncreaseBtn.x; speedIncreaseTxt.y = speedIncreaseBtn.y; speedIncreaseTxt.visible = false; game.addChild(speedIncreaseTxt); // Function to update speed controller display function updateSpeedController() { var visible = godMode && !menuActive; speedControllerBg.visible = visible; speedControllerLabel.visible = visible; speedDecreaseBtn.visible = visible; speedDecreaseTxt.visible = visible; speedIncreaseBtn.visible = visible; speedIncreaseTxt.visible = visible; if (visible) { speedControllerLabel.setText(gameSpeed.toFixed(1) + "x"); } } // Track if EnemyX has spawned var enemyXSpawned = false; var enemyZSpawned = false; // Track if EnemyZ is present in boss mode // Track if EnemyX has been killed after 250 score (not in boss mode) var enemyXKilledAfter250 = false; // Ulti button UI and state var ultiReady = true; var ultiCooldown = 0; var ultiCooldownMax = 1200; // 20 seconds at 60fps // Create ulti button var ultiBtn = new Text2("ULTI", { size: 120, fill: 0x00EAFF, fontWeight: "bold" }); ultiBtn.anchor.set(0.5, 0.5); // Place in right corner, leaving margin for touch and not overlapping top menu ultiBtn.x = 2048 - 180; ultiBtn.y = 2732 - 180; ultiBtn.bg = LK.getAsset('enemy2', { anchorX: 0.5, anchorY: 0.5, tint: 0x00eaff }); ultiBtn.bg.width = 340; ultiBtn.bg.height = 220; ultiBtn.bg.alpha = 0.25; ultiBtn.bg.x = ultiBtn.x; ultiBtn.bg.y = ultiBtn.y; ultiBtn.bg.visible = false; // Hide in menu game.addChild(ultiBtn.bg); ultiBtn.visible = false; // Hide in menu game.addChild(ultiBtn); // Ulti button cooldown overlay var ultiBtnOverlay = LK.getAsset('enemy2', { anchorX: 0.5, anchorY: 0.5, tint: 0x222222 }); ultiBtnOverlay.width = 340; ultiBtnOverlay.height = 220; ultiBtnOverlay.alpha = 0.55; ultiBtnOverlay.x = ultiBtn.x; ultiBtnOverlay.y = ultiBtn.y; ultiBtnOverlay.visible = false; game.addChild(ultiBtnOverlay); // Laser Cannon state (no button UI) var laserReady = true; var laserCooldown = 0; var laserCooldownMax = 720; // 12 seconds at 60fps // Armor visual overlay for shield var heroArmor = LK.getAsset('enemy2', { anchorX: 0.5, anchorY: 0.5, tint: 0x80eaff // light blue }); heroArmor.width = hero.width * 1.35; heroArmor.height = hero.height * 1.35; heroArmor.alpha = 0.55; heroArmor.visible = false; game.addChild(heroArmor); var heroBullets = []; var enemies = []; var enemyBullets = []; var items = []; var dragNode = null; var lastHeroIntersecting = false; var score = 0; var spawnTick = 0; var enemyShootTick = 0; // Powerup state var shieldActive = false; var shieldTimer = 0; var beamActive = false; var beamTimer = 0; // Helper: spawn item function spawnItem() { var itemType = Math.random() < 0.5 ? "shield" : "beam"; var item; if (itemType === "shield") { item = new ShieldItem(); } else { item = new BeamItem(); } item.x = 200 + Math.random() * (2048 - 400); item.y = -80; items.push(item); game.addChild(item); } // Helper: spawn enemy function spawnEnemy() { // 10% Enemy4 (now allowed in score mode at any score), 20% Enemy3, 35% Enemy1, 35% Enemy2 var rand = Math.random(); var enemy; // Always allow Enemy4 to spawn from score 0 in score mode (10% chance) if (rand < 0.35) { enemy = new Enemy1(); enemy.x = 200 + Math.random() * (2048 - 400); enemy.y = -80; } else if (rand < 0.7) { enemy = new Enemy2(); enemy.baseX = 200 + Math.random() * (2048 - 400); enemy.x = enemy.baseX; enemy.y = -80; enemy.t = Math.random() * 2; } else if (rand < 0.9) { enemy = new Enemy3(); enemy.x = 200 + Math.random() * (2048 - 400); enemy.y = -80; } else { // Enemy4 can spawn at any score, including score mode from 0 enemy = new Enemy4(); enemy.x = 200 + Math.random() * (2048 - 400); enemy.y = -80; } enemies.push(enemy); game.addChild(enemy); } // Helper: spawn enemy bullet function spawnEnemyBullet(enemy, targetX, targetY) { var bullet = new EnemyBullet(); bullet.x = enemy.x; bullet.y = enemy.y + 60; // Aim at hero var dx = targetX - bullet.x; var dy = targetY - bullet.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0) { bullet.speedX = dx / len * 18; bullet.speedY = dy / len * 18; } enemyBullets.push(bullet); game.addChild(bullet); LK.getSound('enemyShoot').play(); } // Move handler for dragging hero function handleMove(x, y, obj) { // Always follow mouse if not dragging and not in menu if (!dragNode && !(typeof menuActive !== "undefined" && menuActive)) { // Clamp hero inside screen var hw = hero.width / 2; var hh = hero.height / 2; var nx = Math.max(hw, Math.min(2048 - hw, x)); var ny = Math.max(hh + 100, Math.min(2732 - hh, y)); hero.x = nx; hero.y = ny; return; } if (dragNode) { // Clamp hero inside screen var hw = dragNode.width / 2; var hh = dragNode.height / 2; var nx = Math.max(hw, Math.min(2048 - hw, x)); var ny = Math.max(hh + 100, Math.min(2732 - hh, y)); dragNode.x = nx; dragNode.y = ny; } } // --- Make hero follow mouse automatically at game start (PC only) --- if (typeof window !== "undefined" && window.addEventListener) { window.addEventListener("mousemove", function (evt) { // Only follow if not in menu and not dragging if (typeof menuActive !== "undefined" && menuActive) { return; } if (dragNode) { return; } // Get bounding rect of canvas var canvas = LK.getCanvas ? LK.getCanvas() : document.querySelector("canvas") || null; if (!canvas) { return; } var rect = canvas.getBoundingClientRect(); // Convert mouse coordinates to game coordinates var scaleX = 2048 / rect.width; var scaleY = 2732 / rect.height; var x = (evt.clientX - rect.left) * scaleX; var y = (evt.clientY - rect.top) * scaleY; handleMove(x, y, { event: evt }); }); } // Touch down: start dragging hero game.down = function (x, y, obj) { if (typeof menuActive !== "undefined" && menuActive) { return; } // PC: left mouse click triggers laser cannon if ready and not on menu if (obj && obj.event && obj.event.type === "mousedown" && obj.event.button === 0 && laserReady) { // Only fire if click is inside hero sprite (circle) var dx = x - hero.x; var dy = y - hero.y; if (dx * dx + dy * dy <= hero.radius * hero.radius) { // Activate laser laserReady = false; laserCooldown = laserCooldownMax; // Laser effect: fire a vertical laser column at hero.x var laserX = hero.x; // Visual effect: draw a vertical beam for a short time, and make it follow the hero var laserBeam = LK.getAsset('heroBullet', { anchorX: 0.5, anchorY: 1, x: laserX, y: hero.y - hero.height / 2 }); laserBeam.width = 80; laserBeam.height = hero.y - hero.height / 2; // from hero to top laserBeam.alpha = 0.45; game.addChild(laserBeam); // Store reference to active laser beam and set timer game.activeLaserBeam = laserBeam; game.activeLaserBeamTimer = 90; // 1.5 seconds at 60fps LK.getSound('laser').play(); // Destroy all enemies and enemy bullets in the column for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; if (Math.abs(e.x - laserX) <= 80) { // Laser cannon does NOT affect EnemyX or EnemyZ (bosses) if (e instanceof EnemyX || e instanceof EnemyZ) { // Flash to show hit, but do not damage or destroy LK.effects.flashObject(e, 0xff0000, 120); continue; } LK.effects.flashObject(e, 0xffffff, 200); for (var pi = 0; pi < 12; pi++) { var part = LK.getAsset(e instanceof Enemy1 ? 'enemy1' : e instanceof Enemy2 ? 'enemy2' : 'enemy3', { anchorX: 0.5, anchorY: 0.5 }); part.x = e.x; part.y = e.y; part.width = 20 + Math.random() * 20; part.height = 20 + Math.random() * 20; part.alpha = 0.7 + Math.random() * 0.3; var angle = Math.PI * 2 * (pi / 12) + Math.random() * 0.2; var speed = 10 + Math.random() * 10; var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = 10 + Math.floor(Math.random() * 10); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.88 + Math.random() * 0.06; if (this.life <= 0) { this.destroy(); } }; game.addChild(part); } // 35% chance to drop a health potion if Enemy3 if (e instanceof Enemy3 && Math.random() < 0.35) { var healthPotion = new HealthPotion(); healthPotion.x = e.x; healthPotion.y = e.y; items.push(healthPotion); game.addChild(healthPotion); } e.destroy(); enemies.splice(i, 1); } } for (var i = enemyBullets.length - 1; i >= 0; i--) { var b = enemyBullets[i]; if (Math.abs(b.x - laserX) <= 80) { b.destroy(); enemyBullets.splice(i, 1); } } return; } } dragNode = hero; handleMove(x, y, obj); }; // Touch up: stop dragging game.up = function (x, y, obj) { if (typeof menuActive !== "undefined" && menuActive) { return; } dragNode = null; }; // Touch move: move hero game.move = function (x, y, obj) { if (typeof menuActive !== "undefined" && menuActive) { return; } // Handle speed controller interactions (only in god mode) if (godMode && !menuActive) { // Speed decrease button var dx = x - speedDecreaseBtn.x; var dy = y - speedDecreaseBtn.y; if (Math.abs(dx) <= speedDecreaseBtn.width / 2 && Math.abs(dy) <= speedDecreaseBtn.height / 2) { gameSpeed = Math.max(0.1, gameSpeed - 0.1); updateSpeedController(); return; } // Speed increase button var idx = x - speedIncreaseBtn.x; var idy = y - speedIncreaseBtn.y; if (Math.abs(idx) <= speedIncreaseBtn.width / 2 && Math.abs(idy) <= speedIncreaseBtn.height / 2) { gameSpeed = Math.min(3.0, gameSpeed + 0.1); updateSpeedController(); return; } } handleMove(x, y, obj); // Handle ulti button press (touch/click) if (ultiReady) { // Check if touch is inside ulti button area var dx = x - ultiBtn.x; var dy = y - ultiBtn.y; if (Math.abs(dx) <= ultiBtn.bg.width / 2 && Math.abs(dy) <= ultiBtn.bg.height / 2) { // Activate ulti ultiReady = false; ultiCooldown = ultiCooldownMax; ultiBtnOverlay.visible = true; // Play ulti sound LK.getSound('Ulti').play(); // Ulti effect: clear all enemies and enemy bullets for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; if (e instanceof EnemyX) { // Ulti is ineffective if EnemyX has under 50 health if (e.hp < 50) { // Flash to show ulti hit but no damage LK.effects.flashObject(e, 0xff0000, 120); continue; } // Ulti does 50 damage to EnemyX in both boss mode and normal mode e.hp -= 50; LK.effects.flashObject(e, 0xffffff, 400); if (e.hp <= 0) { // Play death animation for EnemyX var enemyToDestroy = e; for (var pi = 0; pi < 48; pi++) { var part = LK.getAsset('enemyX', { anchorX: 0.5, anchorY: 0.5 }); part.x = enemyToDestroy.x; part.y = enemyToDestroy.y; part.width = 48 + Math.random() * 48; part.height = 48 + Math.random() * 48; part.alpha = 0.8 + Math.random() * 0.2; var angle = Math.PI * 2 * (pi / 48) + Math.random() * 0.2; var speed = 24 + Math.random() * 16; var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = 32 + Math.floor(Math.random() * 32); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.90 + Math.random() * 0.04; if (this.life <= 0) { this.destroy(); } }; game.addChild(part); } // Add score for killing EnemyX with ulti score += 1; scoreTxt.setText(score); // Drop 3 health potions when EnemyX is killed by ulti for (var hp = 0; hp < 3; hp++) { var healthPotion = new HealthPotion(); healthPotion.x = enemyToDestroy.x - 60 + 60 * hp; healthPotion.y = enemyToDestroy.y; items.push(healthPotion); game.addChild(healthPotion); } enemyToDestroy.destroy(); enemies.splice(i, 1); } } else if (e instanceof EnemyZ) { // Ulti is ineffective if EnemyZ has under 50 health if (e.hp < 50) { // Flash to show ulti hit but no damage LK.effects.flashObject(e, 0xff0000, 120); continue; } // Ulti does 50 damage to EnemyZ in both boss mode and normal mode e.hp -= 50; LK.effects.flashObject(e, 0xffffff, 400); if (e.hp <= 0) { // Play death animation for EnemyZ var enemyToDestroy = e; for (var pi = 0; pi < 48; pi++) { var part = LK.getAsset('EnemyZ', { anchorX: 0.5, anchorY: 0.5 }); part.x = enemyToDestroy.x; part.y = enemyToDestroy.y; part.width = 48 + Math.random() * 48; part.height = 48 + Math.random() * 48; part.alpha = 0.8 + Math.random() * 0.2; var angle = Math.PI * 2 * (pi / 48) + Math.random() * 0.2; var speed = 24 + Math.random() * 16; var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = 32 + Math.floor(Math.random() * 32); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.90 + Math.random() * 0.04; if (this.life <= 0) { this.destroy(); } }; game.addChild(part); } // Add score for killing EnemyZ with ulti score += 1; scoreTxt.setText(score); // Drop 3 health potions when EnemyZ is killed by ulti for (var hp = 0; hp < 3; hp++) { var healthPotion = new HealthPotion(); healthPotion.x = enemyToDestroy.x - 60 + 60 * hp; healthPotion.y = enemyToDestroy.y; items.push(healthPotion); game.addChild(healthPotion); } enemyToDestroy.destroy(); enemies.splice(i, 1); } } else { // Add score for killing normal enemies with ulti score += 1; scoreTxt.setText(score); enemies[i].destroy(); enemies.splice(i, 1); } } for (var i = enemyBullets.length - 1; i >= 0; i--) { enemyBullets[i].destroy(); enemyBullets.splice(i, 1); } LK.effects.flashScreen(0x00eaff, 600); return; } } // Handle laser cannon activation by clicking on the hero if (laserReady) { var dx = x - hero.x; var dy = y - hero.y; // Check if touch/click is inside hero sprite (circle) if (dx * dx + dy * dy <= hero.radius * hero.radius) { // Activate laser laserReady = false; laserCooldown = laserCooldownMax; // Laser effect: fire a vertical laser column at hero.x var laserX = hero.x; // Visual effect: draw a vertical beam for a short time, and make it follow the hero var laserBeam = LK.getAsset('heroBullet', { anchorX: 0.5, anchorY: 1, //{37} // anchor at bottom x: laserX, y: hero.y - hero.height / 2 }); laserBeam.width = 80; laserBeam.height = hero.y - hero.height / 2; // from hero to top laserBeam.alpha = 0.45; game.addChild(laserBeam); // Store reference to active laser beam and set timer game.activeLaserBeam = laserBeam; game.activeLaserBeamTimer = 90; // 1.5 seconds at 60fps LK.getSound('laser').play(); // Destroy all enemies and enemy bullets in the column for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; if (Math.abs(e.x - laserX) <= 80) { // Laser cannon does NOT affect EnemyX or EnemyZ (bosses) if (e instanceof EnemyX || e instanceof EnemyZ) { // Flash to show hit, but do not damage or destroy LK.effects.flashObject(e, 0xff0000, 120); continue; } // Play death animation for each LK.effects.flashObject(e, 0xffffff, 200); // Particle effect for each for (var pi = 0; pi < 12; pi++) { var part = LK.getAsset(e instanceof Enemy1 ? 'enemy1' : e instanceof Enemy2 ? 'enemy2' : 'enemy3', { anchorX: 0.5, anchorY: 0.5 }); part.x = e.x; part.y = e.y; part.width = 20 + Math.random() * 20; part.height = 20 + Math.random() * 20; part.alpha = 0.7 + Math.random() * 0.3; var angle = Math.PI * 2 * (pi / 12) + Math.random() * 0.2; var speed = 10 + Math.random() * 10; var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = 10 + Math.floor(Math.random() * 10); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.88 + Math.random() * 0.06; if (this.life <= 0) { this.destroy(); } }; game.addChild(part); } // 35% chance to drop a health potion if Enemy3 if (e instanceof Enemy3 && Math.random() < 0.35) { var healthPotion = new HealthPotion(); healthPotion.x = e.x; healthPotion.y = e.y; items.push(healthPotion); game.addChild(healthPotion); } e.destroy(); enemies.splice(i, 1); } } for (var i = enemyBullets.length - 1; i >= 0; i--) { var b = enemyBullets[i]; if (Math.abs(b.x - laserX) <= 80) { b.destroy(); enemyBullets.splice(i, 1); } } return; } } }; // Main game update game.update = function () { if (typeof menuActive !== "undefined" && menuActive) { // Block all game logic while menu is active return; } // Stop all game logic if gameover screen is shown in score mode if (scoreMode && game.nicknamePromptOverlay && game.nicknamePromptOverlay.visible) { return; } // Update speed controller visibility updateSpeedController(); // Apply game speed multiplier to all timed operations var speedMultiplier = gameSpeed; // Update hero hero.update(); // Laser beam follow logic if (game.activeLaserBeam) { // Move the beam to follow the hero's x and y, and always stretch from hero to top game.activeLaserBeam.x = hero.x; game.activeLaserBeam.y = hero.y - hero.height / 2; game.activeLaserBeam.height = hero.y - hero.height / 2; // Laser beam hits enemies it touches for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; // Check if enemy is within the laser beam's column (80px wide, same as beam) if (e.y + (e.height ? e.height / 2 : 60) >= 0 && // enemy is not above top e.y - (e.height ? e.height / 2 : 60) <= hero.y - hero.height / 2 && // enemy is not below beam bottom Math.abs(e.x - game.activeLaserBeam.x) <= game.activeLaserBeam.width / 2) { // Laser cannon does NOT affect EnemyX or EnemyZ (bosses) if (e instanceof EnemyX || e instanceof EnemyZ) { // Flash to show hit, but do not damage or destroy LK.effects.flashObject(e, 0xff0000, 120); continue; } // Play hit sound and flash LK.getSound('enemyHit').play(); LK.effects.flashObject(e, 0xffffff, 200); // Score and handle Enemy3 HP score += 1; scoreTxt.setText(score); if (e instanceof Enemy3) { e.hp--; if (e.hp <= 0) { // Play death animation before destroying Enemy3 var enemyToDestroy = e; for (var pi = 0; pi < 24; pi++) { var part = LK.getAsset('enemy3', { anchorX: 0.5, anchorY: 0.5 }); part.x = enemyToDestroy.x; part.y = enemyToDestroy.y; part.width = 32 + Math.random() * 32; part.height = 32 + Math.random() * 32; part.alpha = 0.8 + Math.random() * 0.2; var angle = Math.PI * 2 * (pi / 24) + Math.random() * 0.2; var speed = 16 + Math.random() * 12; var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = 16 + Math.floor(Math.random() * 16); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.90 + Math.random() * 0.04; if (this.life <= 0) { this.destroy(); } }; game.addChild(part); } // 35% chance to drop a health potion when Enemy3 is killed if (Math.random() < 0.35) { var healthPotion = new HealthPotion(); healthPotion.x = enemyToDestroy.x; healthPotion.y = enemyToDestroy.y; items.push(healthPotion); game.addChild(healthPotion); } enemyToDestroy.destroy(); enemies.splice(i, 1); } else { // Flash for hit, but don't destroy LK.effects.flashObject(e, 0x8e24aa, 120); } } else { // Play death animation before destroying normal enemies var enemyToDestroy = e; for (var pi = 0; pi < 16; pi++) { var part = LK.getAsset(enemyToDestroy instanceof Enemy1 ? 'enemy1' : 'enemy2', { anchorX: 0.5, anchorY: 0.5 }); part.x = enemyToDestroy.x; part.y = enemyToDestroy.y; part.width = 20 + Math.random() * 20; part.height = 20 + Math.random() * 20; part.alpha = 0.7 + Math.random() * 0.3; var angle = Math.PI * 2 * (pi / 16) + Math.random() * 0.2; var speed = 10 + Math.random() * 10; var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = 10 + Math.floor(Math.random() * 10); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.88 + Math.random() * 0.06; if (this.life <= 0) { this.destroy(); } }; game.addChild(part); } enemyToDestroy.destroy(); enemies.splice(i, 1); } } } // Decrement timer and destroy when done game.activeLaserBeamTimer--; if (game.activeLaserBeamTimer <= 0) { game.activeLaserBeam.destroy(); game.activeLaserBeam = null; } } // Ulti cooldown logic if (godMode) { ultiReady = true; ultiCooldown = 0; ultiBtnOverlay.visible = false; ultiBtn.text = "ULTI"; } else { if (!ultiReady) { ultiCooldown -= speedMultiplier; if (ultiCooldown <= 0) { ultiReady = true; ultiBtnOverlay.visible = false; } else { // Show overlay and update text to show seconds left ultiBtnOverlay.visible = true; ultiBtn.text = "ULTI\n" + Math.ceil(ultiCooldown / 60) + "s"; } } if (ultiReady) { ultiBtn.text = "ULTI"; ultiBtnOverlay.visible = false; } } // Update armor overlay position and visibility heroArmor.x = hero.x; heroArmor.y = hero.y; heroArmor.visible = shieldActive; // Laser cannon cooldown logic (no button UI) if (!laserReady) { laserCooldown -= speedMultiplier; if (laserCooldown <= 0) { laserReady = true; } } // Update shield/beam timers if (shieldActive) { shieldTimer -= speedMultiplier; if (shieldTimer <= 0) { shieldActive = false; } } if (beamActive) { beamTimer -= speedMultiplier; if (beamTimer <= 0) { beamActive = false; } } // Update items (powerups) for (var i = items.length - 1; i >= 0; i--) { var item = items[i]; item.update(); // Remove if off screen if (item.y > 2732 + 100) { item.destroy(); items.splice(i, 1); continue; } // Pickup by hero if (item.intersects(hero)) { if (item instanceof HealthPotion) { // Health potion always gives 1 health (if not at max) if (heroHealth < maxHealth) { heroHealth = Math.min(maxHealth, heroHealth + 1); updateHealthBar(); updateDemoHealthSquares && updateDemoHealthSquares(); LK.effects.flashObject(hero, 0x44ff44, 400); } } else if (item instanceof ShieldItem) { // If already at max health, treat as shield if (heroHealth >= maxHealth) { shieldActive = true; shieldTimer = 360; // 6 seconds at 60fps LK.effects.flashObject(hero, 0x00ffff, 400); } else { // If not at max health, ignore shield pickup (health potions now handled separately) } } else if (item instanceof BeamItem) { beamActive = true; beamTimer = 180; // 3 seconds at 60fps LK.effects.flashObject(hero, 0xff00ff, 400); } item.destroy(); items.splice(i, 1); continue; } } // Hero shooting (auto-fire) hero.shootCooldown -= speedMultiplier; if (hero.shootCooldown <= 0) { if (beamActive) { // Beam: fire 3 bullets in spread for (var bdir = -1; bdir <= 1; bdir++) { var bullet = new HeroBullet(); bullet.x = hero.x + bdir * 40; bullet.y = hero.y - hero.height / 2 - 20; bullet.spread = bdir * 0.18; // radians bullet.update = function (origUpdate, spread) { return function () { this.y += this.speed; this.x += Math.sin(spread) * 18; }; }(bullet.update, bullet.spread); heroBullets.push(bullet); game.addChild(bullet); } hero.shootCooldown = 6 / speedMultiplier; LK.getSound('laser').play(); } else { var bullet = new HeroBullet(); bullet.x = hero.x; bullet.y = hero.y - hero.height / 2 - 20; heroBullets.push(bullet); game.addChild(bullet); hero.shootCooldown = 10 / speedMultiplier; LK.getSound('laser').play(); } } // Update hero bullets for (var i = heroBullets.length - 1; i >= 0; i--) { var b = heroBullets[i]; b.update(); // Remove if off screen if (b.y < -60) { b.destroy(); heroBullets.splice(i, 1); continue; } // Check collision with enemies for (var j = enemies.length - 1; j >= 0; j--) { var e = enemies[j]; // In boss mode, if EnemyZ is killed, allow next boss to spawn if (typeof bossMode !== "undefined" && bossMode && e instanceof EnemyZ && e.hp <= 1 && b.intersects(e)) { enemyZSpawned = false; spawnTick = 0; bossNextToSpawn = "X"; // Always alternate to X after Z dies } if (b.intersects(e)) { // Enemy hit LK.getSound('enemyHit').play(); score += 1; scoreTxt.setText(score); // Flash enemy LK.effects.flashObject(e, 0xffffff, 200); // Remove both or handle Enemy3/EnemyX/Enemy4/EnemyZ hp if (e instanceof Enemy3 || e instanceof EnemyX || e instanceof Enemy4 || e instanceof EnemyZ) { e.hp--; if (e.hp <= 0) { // Play death animation before destroying Enemy3/EnemyX/Enemy4/EnemyZ var enemyToDestroy = e; // --- Enemy4 explosion: visual effect and bullet burst when killed by hero (bullet, laser, shield, or ulti) --- if (e instanceof Enemy4) { // Flash explosion effect LK.effects.flashObject(enemyToDestroy, 0xffffff, 120); // Throw 4 bullets around if (typeof enemyBullets !== "undefined" && typeof game !== "undefined") { for (var bulletDir = 0; bulletDir < 4; bulletDir++) { var bullet = new EnemyBullet(); bullet.x = enemyToDestroy.x; bullet.y = enemyToDestroy.y; // 4 directions: up, right, down, left (90 degrees apart) var angle = bulletDir * Math.PI / 2; var speed = 20; bullet.speedX = Math.cos(angle) * speed; bullet.speedY = Math.sin(angle) * speed; enemyBullets.push(bullet); game.addChild(bullet); } LK.getSound('enemyShoot').play(); } } // Particle animation for Enemy3/EnemyX/Enemy4/EnemyZ death (more particles, richer effect) var particleCount = e instanceof EnemyX ? 48 : e instanceof Enemy3 ? 24 : e instanceof EnemyZ ? 48 : 16; var assetName = e instanceof EnemyX ? 'enemyX' : e instanceof Enemy3 ? 'enemy3' : e instanceof EnemyZ ? 'EnemyZ' : 'Enemy4'; for (var pi = 0; pi < particleCount; pi++) { var part = LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); part.x = enemyToDestroy.x; part.y = enemyToDestroy.y; part.width = (e instanceof EnemyX ? 48 : e instanceof Enemy3 ? 32 : e instanceof EnemyZ ? 48 : 24) + Math.random() * (e instanceof EnemyX ? 48 : e instanceof Enemy3 ? 32 : e instanceof EnemyZ ? 48 : 24); part.height = (e instanceof EnemyX ? 48 : e instanceof Enemy3 ? 32 : e instanceof EnemyZ ? 48 : 24) + Math.random() * (e instanceof EnemyX ? 48 : e instanceof Enemy3 ? 32 : e instanceof EnemyZ ? 48 : 24); part.alpha = 0.8 + Math.random() * 0.2; var angle = Math.PI * 2 * (pi / particleCount) + Math.random() * 0.2; var speed = (e instanceof EnemyX ? 24 : e instanceof Enemy3 ? 16 : e instanceof EnemyZ ? 24 : 12) + Math.random() * (e instanceof EnemyX ? 16 : e instanceof Enemy3 ? 12 : e instanceof EnemyZ ? 16 : 8); var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = (e instanceof EnemyX ? 32 : e instanceof Enemy3 ? 16 : e instanceof EnemyZ ? 32 : 10) + Math.floor(Math.random() * (e instanceof EnemyX ? 32 : e instanceof Enemy3 ? 16 : e instanceof EnemyZ ? 32 : 8)); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.90 + Math.random() * 0.04; if (this.life <= 0) { this.destroy(); } }; game.addChild(part); } // 35% chance to drop a health potion when Enemy3 is killed (not for EnemyX/Enemy4/EnemyZ) if (e instanceof Enemy3 && Math.random() < 0.35) { var healthPotion = new HealthPotion(); healthPotion.x = enemyToDestroy.x; healthPotion.y = enemyToDestroy.y; items.push(healthPotion); game.addChild(healthPotion); } enemyToDestroy.destroy(); enemies.splice(j, 1); // If EnemyX was killed, drop 3 health potions and restart normal enemy spawning if (e instanceof EnemyX) { for (var hp = 0; hp < 3; hp++) { var healthPotion = new HealthPotion(); // Spread potions horizontally a bit healthPotion.x = enemyToDestroy.x - 60 + 60 * hp; healthPotion.y = enemyToDestroy.y; items.push(healthPotion); game.addChild(healthPotion); } // --- In boss mode, alternate to EnemyZ after EnemyX dies if (typeof bossMode !== "undefined" && bossMode) { enemyXSpawned = false; enemyZSpawned = false; // Allow next boss to spawn (EnemyZ) spawnTick = 0; bossNextToSpawn = "Z"; // Always alternate to Z after X dies // Music continues } else { // Reset boss state and allow normal enemies to spawn again enemyXSpawned = false; bossMusicPlayed = false; // Stop boss music and play bgmusic when EnemyX is killed LK.stopMusic(); if (musicOn) { LK.playMusic('bgmusic'); } // If score >= 250, mark EnemyX as killed so it never spawns again (not in boss mode) if (score >= 250 && !(typeof bossMode !== "undefined" && bossMode)) { enemyXKilledAfter250 = true; } // Reset spawnTick so normal enemies start coming again, but slowly spawnTick = 0; // Reset spawnIntervalNormal to its original value when resuming normal enemy spawns spawnIntervalNormal = 60; // Optionally, set a delay before next normal spawn (e.g. 60 frames) // spawnTick = -60; } } // If EnemyZ was killed, alternate to EnemyX after Z dies (boss mode) if (e instanceof EnemyZ && typeof bossMode !== "undefined" && bossMode) { enemyZSpawned = false; spawnTick = 0; bossNextToSpawn = "X"; // Always alternate to X after Z dies } } else { // Flash for hit, but don't destroy LK.effects.flashObject(e, 0x8e24aa, 120); } b.destroy(); heroBullets.splice(i, 1); break; } else { b.destroy(); heroBullets.splice(i, 1); // Play death animation before destroying normal enemies var enemyToDestroy = e; // Particle animation for normal enemy death (more particles, richer effect) for (var pi = 0; pi < 16; pi++) { var part = LK.getAsset(enemyToDestroy instanceof Enemy1 ? 'enemy1' : 'enemy2', { anchorX: 0.5, anchorY: 0.5 }); part.x = enemyToDestroy.x; part.y = enemyToDestroy.y; part.width = 20 + Math.random() * 20; part.height = 20 + Math.random() * 20; part.alpha = 0.7 + Math.random() * 0.3; var angle = Math.PI * 2 * (pi / 16) + Math.random() * 0.2; var speed = 10 + Math.random() * 10; var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = 10 + Math.floor(Math.random() * 10); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.88 + Math.random() * 0.06; if (this.life <= 0) { this.destroy(); } }; game.addChild(part); } enemyToDestroy.destroy(); enemies.splice(j, 1); break; } } } } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; if (typeof bossMode !== "undefined" && bossMode && e instanceof EnemyZ && e.hp <= 0) { enemyZSpawned = false; spawnTick = 0; bossNextToSpawn = "X"; // Always alternate to X after Z dies } e.update(); // Remove if off screen if (e.y > 2732 + 100) { e.destroy(); enemies.splice(i, 1); continue; } // Enemy shoots randomly if (Math.random() < 0.004 * speedMultiplier) { spawnEnemyBullet(e, hero.x, hero.y); } // Check collision with hero // Prevent hero from affecting or being affected by boss type enemies (EnemyX, EnemyZ) if (e instanceof EnemyX || e instanceof EnemyZ) { // Do nothing: hero does not affect or get hit by boss type enemies continue; } if (e.intersects(hero)) { if (godMode) { // In god mode, ignore all damage and just destroy enemy enemies[i].destroy(); enemies.splice(i, 1); continue; } if (!shieldActive) { // Hero hit LK.getSound('heroHit').play(); LK.effects.flashScreen(0xff0000, 800); heroHealth--; updateHealthBar(); updateDemoHealthSquares && updateDemoHealthSquares(); if (heroHealth <= 0) { // Save high score before game over if (scoreMode) { // Stop scoremode music and play gameover music when hero dies in score mode LK.stopMusic(); if (musicOn) { LK.playMusic('Gameover'); } // Show custom nickname prompt overlay showNicknamePrompt(function (nickname) { if (!nickname || typeof nickname !== "string" || nickname.trim().length === 0) { nickname = "Player"; } saveHighScore(score, nickname.trim()); LK.getSound('Death').play(); LK.showGameOver(); }); return; } else { saveHighScore(score); LK.getSound('Death').play(); LK.showGameOver(); return; } } enemies[i].destroy(); enemies.splice(i, 1); continue; } else { // Shield absorbs hit, destroy enemy LK.effects.flashObject(hero, 0x00ffff, 200); // Play death animation before destroying enemy killed by shield var enemyToDestroy = e; // Particle animation for shield kill (more particles, richer effect) for (var pi = 0; pi < 16; pi++) { var part = LK.getAsset(enemyToDestroy instanceof Enemy1 ? 'enemy1' : enemyToDestroy instanceof Enemy2 ? 'enemy2' : 'enemy3', { anchorX: 0.5, anchorY: 0.5 }); part.x = enemyToDestroy.x; part.y = enemyToDestroy.y; part.width = 20 + Math.random() * 20; part.height = 20 + Math.random() * 20; part.alpha = 0.7 + Math.random() * 0.3; var angle = Math.PI * 2 * (pi / 16) + Math.random() * 0.2; var speed = 10 + Math.random() * 10; var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; part.life = 10 + Math.floor(Math.random() * 10); part.update = function () { this.x += vx; this.y += vy; this.life--; this.alpha *= 0.88 + Math.random() * 0.06; if (this.life <= 0) { this.destroy(); } }; game.addChild(part); } enemyToDestroy.destroy(); enemies.splice(i, 1); continue; } } } // Update enemy bullets for (var i = enemyBullets.length - 1; i >= 0; i--) { var b = enemyBullets[i]; if (typeof bossMode !== "undefined" && bossMode && b instanceof EnemyBullet) { // Check if this bullet killed EnemyZ (shouldn't happen, but for completeness) for (var j = enemies.length - 1; j >= 0; j--) { var e = enemies[j]; if (e instanceof EnemyZ && e.hp <= 0) { enemyZSpawned = false; spawnTick = 0; bossNextToSpawn = "X"; // Always alternate to X after Z dies } } } b.update(); // Handle explosive bullets if (b.isExplosive && !b.hasExploded) { b.explosionTimer -= speedMultiplier; // Flash bullet red as it gets closer to exploding if (b.explosionTimer <= 6) { b.children[0].tint = 0xff0000; } // Explode when timer reaches 0 (0.2 seconds = 12 ticks at 60fps) if (b.explosionTimer <= 0) { b.hasExploded = true; // Create explosion visual effect var explosionEffect = LK.getAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5, x: b.x, y: b.y, width: 100, height: 100, tint: 0xff8800 }); explosionEffect.alpha = 0.8; game.addChild(explosionEffect); // Animate explosion with tween tween(explosionEffect, { width: 400, height: 400, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { explosionEffect.destroy(); } }); // Create 6 normal bullets flying in different directions for (var bulletIndex = 0; bulletIndex < 6; bulletIndex++) { var normalBullet = new EnemyBullet(); normalBullet.x = b.x; normalBullet.y = b.y; // Calculate angle for 6 directions (60 degrees apart) var angle = bulletIndex * Math.PI * 2 / 6; var speed = 15; normalBullet.speedX = Math.cos(angle) * speed; normalBullet.speedY = Math.sin(angle) * speed; enemyBullets.push(normalBullet); game.addChild(normalBullet); } // Area damage to hero (reduced from original) var dx = hero.x - b.x; var dy = hero.y - b.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= b.explosionRadius) { if (!godMode && !shieldActive) { LK.getSound('heroHit').play(); LK.effects.flashScreen(0xff0000, 800); heroHealth -= 1; // Reduced damage since bullets are now additional threat if (heroHealth < 0) { heroHealth = 0; } updateHealthBar(); updateDemoHealthSquares && updateDemoHealthSquares(); if (heroHealth <= 0) { // Save high score before game over if (scoreMode) { // Show custom nickname prompt overlay showNicknamePrompt(function (nickname) { if (!nickname || typeof nickname !== "string" || nickname.trim().length === 0) { nickname = "Player"; } saveHighScore(score, nickname.trim()); LK.getSound('Death').play(); LK.showGameOver(); }); return; } else { saveHighScore(score); LK.getSound('Death').play(); LK.showGameOver(); return; } } } else if (shieldActive) { LK.effects.flashObject(hero, 0x00ffff, 200); } } // Remove explosive bullet after explosion b.destroy(); enemyBullets.splice(i, 1); continue; } } // Remove if off screen if (b.y < -60 || b.y > 2732 + 60 || b.x < -60 || b.x > 2048 + 60) { b.destroy(); enemyBullets.splice(i, 1); continue; } // Check collision with hero if (b.intersects(hero)) { if (godMode) { // In god mode, ignore all damage and just destroy bullet b.destroy(); enemyBullets.splice(i, 1); continue; } if (!shieldActive) { LK.getSound('heroHit').play(); LK.effects.flashScreen(0xff0000, 800); heroHealth--; updateHealthBar(); updateDemoHealthSquares && updateDemoHealthSquares(); if (heroHealth <= 0) { // Save high score before game over if (scoreMode) { // Show custom nickname prompt overlay showNicknamePrompt(function (nickname) { if (!nickname || typeof nickname !== "string" || nickname.trim().length === 0) { nickname = "Player"; } saveHighScore(score, nickname.trim()); LK.getSound('Death').play(); LK.showGameOver(); }); return; } else { saveHighScore(score); LK.getSound('Death').play(); LK.showGameOver(); return; } } b.destroy(); enemyBullets.splice(i, 1); continue; } else { // Shield absorbs bullet LK.effects.flashObject(hero, 0x00ffff, 120); b.destroy(); enemyBullets.splice(i, 1); continue; } } } // --- ENEMY SPAWN RATE BOOST LOGIC --- if (typeof spawnRateBoostActive === "undefined") { var spawnRateBoostActive = false; var spawnRateBoostTimer = 0; var nextBoostScore = 50; var spawnIntervalNormal = 60; var spawnIntervalBoost = 30; } // Score mode: all enemy types, no bosses, gets harder by score if (typeof scoreMode !== "undefined" && scoreMode) { spawnTick += speedMultiplier; // Check if we reached a new boost score (50, 150, 250, ...) and trigger boost if (score >= nextBoostScore && !spawnRateBoostActive) { spawnRateBoostActive = true; spawnRateBoostTimer = 180; // 3 seconds at 60fps // After 50, next is 150, then 250, 350, ... if (nextBoostScore === 50) { nextBoostScore = 150; } else { nextBoostScore += 100; } } // Handle boost timer if (spawnRateBoostActive) { spawnRateBoostTimer -= speedMultiplier; if (spawnRateBoostTimer <= 0) { spawnRateBoostActive = false; } } // Calculate spawn interval based on score var baseInterval = spawnRateBoostActive ? spawnIntervalBoost : Math.max(18, spawnIntervalNormal - Math.floor(score / 20) * 2); var spawnInterval = score >= 600 ? Math.max(12, baseInterval - Math.floor((score - 600) / 30) * 2) : baseInterval; if (spawnTick >= spawnInterval) { // Calculate how many enemies to spawn based on score (1 + 1 per 50 score) var numEnemies = 1 + Math.floor(score / 50); for (var i = 0; i < numEnemies; i++) { spawnEnemy(); } // 1 in 8 chance to spawn an item if (Math.random() < 0.125) { spawnItem(); } spawnTick = 0; } } // Boss mode: alternate EnemyX and EnemyZ, never normal enemies or items else if (typeof bossMode !== "undefined" && bossMode) { spawnTick += speedMultiplier; // Track which boss to spawn next: "X" or "Z" if (typeof bossNextToSpawn === "undefined") { bossNextToSpawn = "X"; } if (!enemyXSpawned && !enemyZSpawned && spawnTick >= 30) { var enemy; // Special case: at score 451, always spawn EnemyZ regardless of alternation if (score === 451) { enemy = new EnemyZ(); enemy.x = 2048 / 2; enemy.y = -80; enemies.push(enemy); game.addChild(enemy); enemyZSpawned = true; bossNextToSpawn = "X"; // After Z, alternate to X spawnTick = 0; } else if (bossNextToSpawn === "X") { enemy = new EnemyX(); enemy.x = 200 + Math.random() * (2048 - 400); enemy.y = -80; enemies.push(enemy); game.addChild(enemy); enemyXSpawned = true; // Do not alternate bossNextToSpawn here; alternate only on death spawnTick = 0; } else if (bossNextToSpawn === "Z") { enemy = new EnemyZ(); enemy.x = 2048 / 2; enemy.y = -80; enemies.push(enemy); game.addChild(enemy); enemyZSpawned = true; // Do not alternate bossNextToSpawn here; alternate only on death spawnTick = 0; } } // Never spawn items or normal enemies in boss mode } else { // Check if we reached a new boost score (50, 150, 250, ...) and trigger boost if (score >= nextBoostScore && !spawnRateBoostActive) { spawnRateBoostActive = true; spawnRateBoostTimer = 180; // 3 seconds at 60fps // After 50, next is 150, then 250, 350, ... if (nextBoostScore === 50) { nextBoostScore = 150; } else { nextBoostScore += 100; } } // Handle boost timer if (spawnRateBoostActive) { spawnRateBoostTimer -= speedMultiplier; if (spawnRateBoostTimer <= 0) { spawnRateBoostActive = false; } } // Spawn enemies spawnTick += speedMultiplier; // Use boosted or normal interval // Make spawn rate scale more slowly with score (easier): decrease by 2 per 20 score, not 4 per 10 // After 600 score, apply additional spawn rate increase var baseInterval = spawnRateBoostActive ? spawnIntervalBoost : Math.max(18, spawnIntervalNormal - Math.floor(score / 20) * 2); var spawnInterval = score >= 600 ? Math.max(12, baseInterval - Math.floor((score - 600) / 30) * 2) : baseInterval; if (score < 250) { if (spawnTick >= spawnInterval) { // Calculate how many enemies to spawn based on score (1 + 1 per 50 score) var numEnemies = 1 + Math.floor(score / 50); for (var i = 0; i < numEnemies; i++) { spawnEnemy(); } // 1 in 8 chance to spawn an item if (Math.random() < 0.125) { spawnItem(); } spawnTick = 0; } } else if (score >= 250) { // Stop current music and play boss music when score passes 250 if (typeof bossMusicPlayed === "undefined") { var bossMusicPlayed = false; } if (!enemyXKilledAfter250) { // --- Ensure EnemyX spawns immediately after passing 250 score before any other enemies --- if (!enemyXSpawned) { // Only spawn EnemyX if not already present var enemy = new EnemyX(); enemy.x = 200 + Math.random() * (2048 - 400); enemy.y = -80; enemies.push(enemy); game.addChild(enemy); enemyXSpawned = true; // Stop current music and play boss music when EnemyX spawns if (!bossMusicPlayed) { LK.stopMusic(); if (musicOn) { LK.playMusic('Boss'); } bossMusicPlayed = true; } spawnTick = 0; } // Do NOT spawn any other enemies or items until EnemyX is killed else { // Wait until EnemyX is killed before resuming normal spawns // (do nothing here) } } else { // EnemyX has been killed after 250 score, resume normal enemy and item spawns spawnTick++; // After score reaches 600, start to increase enemy spawn speed significantly var postBossSpawnInterval = 60; if (score >= 600) { // Decrease interval by 2 every 30 score above 600, minimum 12 (much faster spawn rate) postBossSpawnInterval = Math.max(12, 60 - Math.floor((score - 600) / 30) * 2); } // Always spawn exactly 1 enemy per postBossSpawnInterval ticks after EnemyX is killed post-250 if (score < 800 && spawnTick >= postBossSpawnInterval) { spawnEnemy(); // 1 in 8 chance to spawn an item if (Math.random() < 0.125) { spawnItem(); } spawnTick = 0; } else if (score === 800 && !enemyZSpawned) { // Spawn EnemyZ at score 800 var enemy = new EnemyZ(); enemy.x = 2048 / 2; enemy.y = -80; enemies.push(enemy); game.addChild(enemy); enemyZSpawned = true; // Optionally play boss music if not already playing if (typeof bossMusicPlayed === "undefined" || !bossMusicPlayed) { LK.stopMusic(); if (musicOn) { LK.playMusic('Boss'); } bossMusicPlayed = true; } spawnTick = 0; } } // In god mode, also allow enemies to spawn after EnemyX is killed, just like normal mode if (godMode && enemyXKilledAfter250) { spawnTick++; var postBossSpawnInterval = 60; if (score >= 600) { // Use same aggressive spawn rate increase as normal mode postBossSpawnInterval = Math.max(12, 60 - Math.floor((score - 600) / 30) * 2); } if (score < 800 && spawnTick >= postBossSpawnInterval) { spawnEnemy(); if (Math.random() < 0.125) { spawnItem(); } spawnTick = 0; } } // In god mode, also apply increased spawn rate for pre-250 score enemies after 600 score if (godMode && score < 250 && score >= 600) { // Apply same spawn rate boost logic as normal mode for pre-250 enemies var baseInterval = spawnRateBoostActive ? spawnIntervalBoost : Math.max(18, spawnIntervalNormal - Math.floor(score / 20) * 2); var spawnInterval = Math.max(12, baseInterval - Math.floor((score - 600) / 30) * 2); if (spawnTick >= spawnInterval) { // Calculate how many enemies to spawn based on score (1 + 1 per 50 score) var numEnemies = 1 + Math.floor(score / 50); for (var i = 0; i < numEnemies; i++) { spawnEnemy(); } // 1 in 8 chance to spawn an item if (Math.random() < 0.125) { spawnItem(); } spawnTick = 0; } } } } };
===================================================================
--- original.js
+++ change.js
@@ -1855,8 +1855,9 @@
function spawnEnemy() {
// 10% Enemy4 (now allowed in score mode at any score), 20% Enemy3, 35% Enemy1, 35% Enemy2
var rand = Math.random();
var enemy;
+ // Always allow Enemy4 to spawn from score 0 in score mode (10% chance)
if (rand < 0.35) {
enemy = new Enemy1();
enemy.x = 200 + Math.random() * (2048 - 400);
enemy.y = -80;
@@ -1870,9 +1871,9 @@
enemy = new Enemy3();
enemy.x = 200 + Math.random() * (2048 - 400);
enemy.y = -80;
} else {
- // Always allow Enemy4 to spawn (in all modes, including score mode, at any score)
+ // Enemy4 can spawn at any score, including score mode from 0
enemy = new Enemy4();
enemy.x = 200 + Math.random() * (2048 - 400);
enemy.y = -80;
}
A power core with electricity and blue lights. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A blue space ship with laser gun and powerful engines. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A shiny purple crystal. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A red winged alien with exoskeleton. . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A tiny green alien with spikes. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A strong yellow-white alien with horns. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A king yellow alien with black and white stripes and wings. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Neboula background. In-Game asset. 2d. High contrast. No shadows. Cinematic deep
A blue button. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A dark green alien with a gray orb above it. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A dark green alien boss with two gray orbs around it. On top it has a green crown. Has light gray stripes. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A futuristic scoreboard tablet with blue and cyan colors. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat