/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
scoreboard: []
});
/****
* 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 (3x Enemy1 hitpoints)
self.turnSpeed = 0.03; // How fast Enemy4 turns toward hero
self.currentAngle = Math.random() * Math.PI * 2; // Current movement direction
self.speed = 12; // Movement speed
self.update = function () {
var speedMult = typeof gameSpeed !== "undefined" ? gameSpeed : 1;
// Only move and turn if hero exists and Enemy4 is not exploded
if (typeof hero !== "undefined" && !self.exploded) {
// Calculate angle to hero for gradual turning
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var angleToHero = Math.atan2(dy, dx);
// Gradually turn toward hero
var angleDiff = angleToHero - self.currentAngle;
// Normalize angle difference to [-PI, PI]
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
// Turn gradually toward hero
if (Math.abs(angleDiff) > self.turnSpeed * speedMult) {
self.currentAngle += Math.sign(angleDiff) * self.turnSpeed * speedMult;
} else {
self.currentAngle = angleToHero;
}
// Move in current direction
self.x += Math.cos(self.currentAngle) * self.speed * speedMult;
self.y += Math.sin(self.currentAngle) * self.speed * speedMult;
}
// Check if Enemy4 reached middle of screen and explode
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var distanceToCenter = Math.sqrt((self.x - centerX) * (self.x - centerX) + (self.y - centerY) * (self.y - centerY));
if (!self.exploded && distanceToCenter < 100) {
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 can fly through hero and try to collide - no special collision handling here
};
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);
}
}
// Standard and god modes: EnemyX rarely drops a health potion
if ((typeof bossMode === "undefined" || !bossMode) && (typeof scoreMode === "undefined" || !scoreMode) && (typeof godMode !== "undefined" && godMode || typeof godMode !== "undefined" && !godMode)) {
if (Math.random() < 0.003) {
// 0.3% chance per update (less frequent)
var potion = new HealthPotion();
potion.x = self.x + (Math.random() - 0.5) * 120;
potion.y = self.y + 120;
if (typeof items !== "undefined" && typeof game !== "undefined") {
items.push(potion);
game.addChild(potion);
}
}
}
// 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;
}
// Check if laser beam is purchased before allowing automatic beam shooting
var laserBeamPurchased = storage.laserBeamPurchased || false;
// Sometimes shoot a beam down (every 360-540 ticks, much more rare, and only hit when visually active)
if (laserBeamPurchased && 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();
// Return to main menu instead of game over
returnToMainMenu();
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;
}
// Boss mode: randomly drop health potion, shield, or beam items from EnemyZ (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);
}
}
// Standard and god modes: EnemyZ rarely drops a health potion
if ((typeof bossMode === "undefined" || !bossMode) && (typeof scoreMode === "undefined" || !scoreMode) && (typeof godMode !== "undefined" && godMode || typeof godMode !== "undefined" && !godMode)) {
if (Math.random() < 0.003) {
// 0.3% chance per update (less frequent)
var potion = new HealthPotion();
potion.x = self.x + (Math.random() - 0.5) * 120;
potion.y = self.y + 120;
if (typeof items !== "undefined" && typeof game !== "undefined") {
items.push(potion);
game.addChild(potion);
}
}
}
// --- 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;
});
// Great Shield Orb class - spinning shields around hero
var GreatShieldOrb = Container.expand(function () {
var self = Container.call(this);
var shieldSprite = self.attachAsset('GreatShield', {
anchorX: 0.5,
anchorY: 0.5
});
shieldSprite.width = 180;
shieldSprite.height = 180;
shieldSprite.alpha = 0.9;
self.angle = 0;
self.radius = 280;
self.rotationSpeed = 0.04;
self.update = function () {
// Update rotation angle
self.angle += self.rotationSpeed * (typeof gameSpeed !== "undefined" ? gameSpeed : 1);
// Position around hero
if (typeof hero !== "undefined") {
self.x = hero.x + Math.cos(self.angle) * self.radius;
self.y = hero.y + Math.sin(self.angle) * self.radius;
// Make the bottom of the shield always point toward opposite of hero center
// Calculate angle from shield to hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var angleToHero = Math.atan2(dy, dx);
// Set shield rotation to point away from hero (add PI to reverse direction, plus PI/2 to make bottom point outward)
shieldSprite.rotation = angleToHero + Math.PI + Math.PI / 2;
// Update shield hitbox to match larger visual size
self.width = 180;
self.height = 180;
}
// Remove self-rotation to prevent spinning around itself
// shieldSprite.rotation += 0.08 * (typeof gameSpeed !== "undefined" ? gameSpeed : 1);
};
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;
});
// Laser Ball class for beam button ability
var LaserBall = Container.expand(function () {
var self = Container.call(this);
var ballSprite = self.attachAsset('heroBullet', {
anchorX: 0.5,
anchorY: 0.5
});
ballSprite.width = 60;
ballSprite.height = 150;
ballSprite.tint = 0xffaa00; // Orange color
self.speed = 28;
self.targetEnemy = null;
self.rotationSpeed = 0.08;
self.killCount = 0; // Track how many enemies this ball has killed
self.maxKills = 2; // Allow 2 kills before disappearing
self.update = function () {
var speedMult = typeof gameSpeed !== "undefined" ? gameSpeed : 1;
// Find nearest enemy if we don't have a target or target is destroyed
if (!self.targetEnemy || !self.targetEnemy.parent) {
var nearestDistance = Infinity;
self.targetEnemy = null;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
self.targetEnemy = enemy;
}
}
}
// Rotate towards target enemy
if (self.targetEnemy) {
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var targetAngle = Math.atan2(dy, dx) + Math.PI / 2; // Add 90 degrees since bullet points up
var currentAngle = self.rotation;
// Calculate shortest rotation path
var angleDiff = targetAngle - currentAngle;
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
// Rotate towards target
if (Math.abs(angleDiff) > 0.1) {
self.rotation += Math.sign(angleDiff) * self.rotationSpeed * speedMult;
} else {
self.rotation = targetAngle;
}
}
// Move forward in current direction
self.x += Math.sin(self.rotation) * self.speed * speedMult;
self.y -= Math.cos(self.rotation) * self.speed * speedMult;
};
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
****/
//Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property.
// 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 shop button below help button
var shopBtnBg = LK.getAsset('Button', {
anchorX: 0.5,
anchorY: 0.5
});
shopBtnBg.width = 340;
shopBtnBg.height = 140;
shopBtnBg.alpha = 0.7;
shopBtnBg.x = 2048 / 2;
shopBtnBg.y = helpBtnBg.y + helpBtnBg.height / 2 + 60 + shopBtnBg.height / 2;
menuOverlay.addChild(shopBtnBg);
var shopBtn = new Text2("SHOP", {
size: 80,
fill: 0x00EAFF,
fontWeight: "bold"
});
shopBtn.anchor.set(0.5, 0.5);
shopBtn.x = 2048 / 2;
shopBtn.y = shopBtnBg.y;
menuOverlay.addChild(shopBtn);
// Add scoreboard to right bottom (quarter of screen size)
var scoreboardBg = LK.getAsset('Scoreboard', {
anchorX: 1,
anchorY: 1,
x: 2048 - 60,
y: 2732 - 60,
width: 512,
height: 683
});
scoreboardBg.alpha = 0.9;
menuOverlay.addChild(scoreboardBg);
// Scoreboard entries container
var scoreboardContainer = new Container();
scoreboardContainer.x = scoreboardBg.x - scoreboardBg.width + 30;
scoreboardContainer.y = scoreboardBg.y - scoreboardBg.height + 80;
menuOverlay.addChild(scoreboardContainer);
// Function to refresh scoreboard display
function refreshScoreboard() {
// Clear existing entries
while (scoreboardContainer.children.length > 0) {
scoreboardContainer.removeChild(scoreboardContainer.children[0]);
}
// Get scoreboard data and sort by score (highest first)
var scoreboard = storage.scoreboard || [];
scoreboard.sort(function (a, b) {
return b.score - a.score;
});
// Show top 8 entries
var maxEntries = Math.min(8, scoreboard.length);
var entrySpacing = 70;
for (var i = 0; i < maxEntries; i++) {
var entry = scoreboard[i];
var entryY = i * entrySpacing;
// Rank number
var rankText = new Text2(i + 1 + ".", {
size: 45,
fill: "#fff",
fontWeight: "bold"
});
rankText.anchor.set(0, 0);
rankText.x = 20;
rankText.y = entryY;
scoreboardContainer.addChild(rankText);
// Player name
var nameText = new Text2(entry.name.substring(0, 8), {
size: 45,
fill: 0x00EAFF
});
nameText.anchor.set(0, 0);
nameText.x = 80;
nameText.y = entryY;
scoreboardContainer.addChild(nameText);
// Score
var scoreText = new Text2(entry.score.toString(), {
size: 45,
fill: 0xFFD700,
fontWeight: "bold"
});
scoreText.anchor.set(1, 0);
scoreText.x = 450;
scoreText.y = entryY;
scoreboardContainer.addChild(scoreText);
}
}
// Initial scoreboard display
refreshScoreboard();
// --- 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;
// Show wave button only if solar wave is purchased
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased) {
waveBtn.visible = true;
waveBtn.bg.visible = true;
}
// Show beam button only if laser beam is purchased
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased) {
beamBtn.visible = true;
beamBtn.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;
// Show wave button only if solar wave is purchased
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased) {
waveBtn.visible = true;
waveBtn.bg.visible = true;
}
// Show beam button only if laser beam is purchased
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased) {
beamBtn.visible = true;
beamBtn.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;
// Show wave button only if solar wave is purchased
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased) {
waveBtn.visible = true;
waveBtn.bg.visible = true;
}
// Show beam button only if laser beam is purchased
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased) {
beamBtn.visible = true;
beamBtn.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;
// Show wave button only if solar wave is purchased
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased) {
waveBtn.visible = true;
waveBtn.bg.visible = true;
}
// Show beam button only if laser beam is purchased
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased) {
beamBtn.visible = true;
beamBtn.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);
}
// --- SHOP MENU POPUP ---
var shopMenuOverlay = null;
function showShopMenu() {
if (shopMenuOverlay && shopMenuOverlay.visible) {
return;
}
if (shopMenuOverlay) {
shopMenuOverlay.visible = true;
return;
}
shopMenuOverlay = new Container();
shopMenuOverlay.zIndex = 20000;
// Shop menu background
var bg = LK.getAsset('Helpbackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
bg.width = 2048;
bg.height = 2732;
bg.alpha = 1.0;
shopMenuOverlay.addChild(bg);
// Shop title
var shopTitle = new Text2("SHOP", {
size: 120,
fill: "#fff",
fontWeight: "bold"
});
shopTitle.anchor.set(0.5, 0);
shopTitle.x = 2048 / 2;
shopTitle.y = 320;
shopMenuOverlay.addChild(shopTitle);
// Display coins
var shopCoinsText = new Text2('Coins: ' + coins, {
size: 80,
fill: 0xFFD700
});
shopCoinsText.anchor.set(0.5, 0);
shopCoinsText.x = 2048 / 2;
shopCoinsText.y = 450;
shopMenuOverlay.addChild(shopCoinsText);
// Shop items display
var startY = 700;
var spacingY = 420;
var shopItemElements = [];
for (var i = 0; i < shopItems.length; i++) {
var item = shopItems[i];
var itemY = startY + i * spacingY;
// Item background
var itemBg = LK.getAsset('Button', {
anchorX: 0.5,
anchorY: 0.5
});
itemBg.width = 1200;
itemBg.height = 300;
itemBg.alpha = item.purchased ? 0.3 : 0.7;
itemBg.x = 2048 / 2;
itemBg.y = itemY;
shopMenuOverlay.addChild(itemBg);
// Item name
var itemName = new Text2(item.name, {
size: 110,
fill: item.purchased ? 0x44ff44 : "#fff",
fontWeight: "bold"
});
itemName.anchor.set(0.5, 0);
itemName.x = 2048 / 2;
itemName.y = itemY - 100;
shopMenuOverlay.addChild(itemName);
// Item description
var itemDesc = new Text2(item.description, {
size: 75,
fill: item.purchased ? 0xaaaaaa : "#fff"
});
itemDesc.anchor.set(0.5, 0);
itemDesc.x = 2048 / 2;
itemDesc.y = itemY - 20;
shopMenuOverlay.addChild(itemDesc);
// Price/Status text
var priceText = new Text2(item.purchased ? "PURCHASED" : item.price + " Coins", {
size: 85,
fill: item.purchased ? 0x44ff44 : coins >= item.price ? 0xFFD700 : 0xff4444,
fontWeight: "bold"
});
priceText.anchor.set(0.5, 0);
priceText.x = 2048 / 2;
priceText.y = itemY + 60;
shopMenuOverlay.addChild(priceText);
shopItemElements.push({
bg: itemBg,
item: item,
index: i,
nameText: itemName,
priceText: priceText
});
}
// Code button (bottom right)
var codeBtnBg = LK.getAsset('Button', {
anchorX: 0.5,
anchorY: 0.5
});
codeBtnBg.width = 200;
codeBtnBg.height = 140;
codeBtnBg.alpha = 0.7;
codeBtnBg.x = 2048 - 150;
codeBtnBg.y = 2500;
shopMenuOverlay.addChild(codeBtnBg);
var codeBtn = new Text2("CODE", {
size: 70,
fill: 0x00EAFF,
fontWeight: "bold"
});
codeBtn.anchor.set(0.5, 0.5);
codeBtn.x = 2048 - 150;
codeBtn.y = 2500;
shopMenuOverlay.addChild(codeBtn);
// Close button
var closeShopBtnBg = LK.getAsset('Button', {
anchorX: 0.5,
anchorY: 0.5
});
closeShopBtnBg.width = 340;
closeShopBtnBg.height = 140;
closeShopBtnBg.alpha = 0.7;
closeShopBtnBg.x = 2048 / 2;
closeShopBtnBg.y = 2500;
shopMenuOverlay.addChild(closeShopBtnBg);
var closeShopBtn = new Text2("CLOSE", {
size: 80,
fill: 0x00EAFF,
fontWeight: "bold"
});
closeShopBtn.anchor.set(0.5, 0.5);
closeShopBtn.x = 2048 / 2;
closeShopBtn.y = 2500;
shopMenuOverlay.addChild(closeShopBtn);
// Shop menu interaction
shopMenuOverlay.down = function (x, y, obj) {
// Code button
var codeDx = x - codeBtn.x;
var codeDy = y - codeBtn.y;
if (Math.abs(codeDx) <= codeBtnBg.width / 2 && Math.abs(codeDy) <= codeBtnBg.height / 2) {
showCodeInput();
return;
}
// Close button
var dx = x - closeShopBtn.x;
var dy = y - closeShopBtn.y;
if (Math.abs(dx) <= closeShopBtnBg.width / 2 && Math.abs(dy) <= closeShopBtnBg.height / 2) {
shopMenuOverlay.visible = false;
return;
}
// Shop item clicks
for (var i = 0; i < shopItemElements.length; i++) {
var element = shopItemElements[i];
var itemDx = x - element.bg.x;
var itemDy = y - element.bg.y;
if (Math.abs(itemDx) <= 600 && Math.abs(itemDy) <= element.bg.height / 2) {
var item = element.item;
if (!item.purchased && coins >= item.price) {
// Purchase item
coins -= item.price;
storage.coins = coins;
item.purchased = true;
// Update storage
if (i === 0) storage.laserBeamPurchased = true;else if (i === 1) storage.solarWavePurchased = true;else if (i === 2) storage.greatShieldPurchased = true;
// Update display
coinsTxt.setText('Coins: ' + coins);
shopCoinsText.setText('Coins: ' + coins);
element.nameText.fill = 0x44ff44;
element.priceText.setText("PURCHASED");
element.priceText.fill = 0x44ff44;
// Update other items' price colors
for (var j = 0; j < shopItemElements.length; j++) {
if (j !== i && !shopItemElements[j].item.purchased) {
shopItemElements[j].priceText.fill = coins >= shopItemElements[j].item.price ? 0xFFD700 : 0xff4444;
}
}
}
return;
}
}
};
game.addChild(shopMenuOverlay);
}
// 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;
}
}
// Check if shop button was pressed
if (typeof shopBtn !== "undefined" && typeof shopBtnBg !== "undefined") {
var sdx = x - shopBtn.x;
var sdy = y - shopBtn.y;
if (Math.abs(sdx) <= shopBtnBg.width / 2 && Math.abs(sdy) <= shopBtnBg.height / 2) {
// Show shop menu
showShopMenu();
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);
// Coins display in top right corner
var coins = storage.coins || 0;
var coinsTxt = new Text2('Coins: ' + coins, {
size: 80,
fill: 0xFFD700
});
coinsTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(coinsTxt);
// Shop items data
var shopItems = [{
name: "Laser Cannon",
price: 500,
purchased: storage.laserBeamPurchased || false,
description: "Powerful cannon that fires laser balls in sequence"
}, {
name: "Solar Wave",
price: 750,
purchased: storage.solarWavePurchased || false,
description: "Powerful wave attack"
}, {
name: "Great Shield",
price: 1000,
purchased: storage.greatShieldPurchased || false,
description: "Enhanced shield protection"
}];
// 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() {
// Hide all health bar elements if in main menu
if (typeof menuActive !== "undefined" && menuActive) {
healthBarBg.visible = false;
healthBar.visible = false;
for (var i = 0; i < healthBarVSquares.length; i++) {
healthBarVSquares[i].visible = false;
}
return;
}
// 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();
// Function to return to main menu and restart game
function returnToMainMenu() {
// Stop all music (including gameover music)
LK.stopMusic();
// Reset all game variables to initial state
heroHealth = maxHealth;
score = 0;
spawnTick = 0;
enemyXSpawned = false;
enemyZSpawned = false;
enemyXKilledAfter250 = false;
bossMusicPlayed = false;
lastCoinScore = 0;
ultiReady = true;
ultiCooldown = 0;
laserReady = true;
laserCooldown = 0;
waveButtonReady = true;
waveButtonCooldown = 0;
beamButtonReady = true;
beamButtonCooldown = 0;
shieldActive = false;
shieldTimer = 0;
beamActive = false;
beamTimer = 0;
gameSpeed = 1.0;
if (typeof spawnRateBoostActive !== "undefined") {
spawnRateBoostActive = false;
spawnRateBoostTimer = 0;
nextBoostScore = 50;
}
if (typeof bossNextToSpawn !== "undefined") {
bossNextToSpawn = "X";
}
// Reset great shield system
greatShields = [];
greatShieldActive = false;
greatShieldSpawnTimer = 0;
greatShieldRespawnTimer = 0;
greatShieldGameStarted = false;
// Clear all game objects
for (var i = heroBullets.length - 1; i >= 0; i--) {
heroBullets[i].destroy();
heroBullets.splice(i, 1);
}
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
for (var i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
}
for (var i = items.length - 1; i >= 0; i--) {
items[i].destroy();
items.splice(i, 1);
}
// Hide active laser beam if exists
if (game.activeLaserBeam) {
game.activeLaserBeam.destroy();
game.activeLaserBeam = null;
game.activeLaserBeamTimer = 0;
}
// Reset hero position
hero.x = 2048 / 2;
hero.y = 2732 - 350;
hero.visible = false;
// Reset score display
scoreTxt.setText('0');
// Update health display
updateHealthBar();
updateDemoHealthSquares && updateDemoHealthSquares();
// Hide all game UI elements
healthBarBg.visible = false;
healthBar.visible = false;
for (var i = 0; i < healthBarVSquares.length; i++) {
healthBarVSquares[i].visible = false;
}
ultiBtn.visible = false;
ultiBtn.bg.visible = false;
ultiBtnOverlay.visible = false;
waveBtn.visible = false;
waveBtn.bg.visible = false;
waveBtnOverlay.visible = false;
beamBtn.visible = false;
beamBtn.bg.visible = false;
beamBtnOverlay.visible = false;
for (var i = 0; i < greenSquares.length; i++) {
greenSquares[i].visible = false;
}
// Hide game background and show menu
if (typeof backgroundSprite !== "undefined") {
backgroundSprite.visible = false;
}
if (typeof menuBgSprite !== "undefined") {
menuBgSprite.visible = true;
}
// Reset game modes
godMode = false;
bossMode = false;
scoreMode = false;
// Reset gameover music flag for new game
gameoverMusicPlayed = false;
// Show menu overlay and activate menu state
menuOverlay.visible = true;
menuActive = true;
// Hide mode selection if visible
if (modeSelectionOverlay) {
modeSelectionOverlay.visible = false;
}
// Hide help menu if visible
if (helpMenuOverlay) {
helpMenuOverlay.visible = false;
}
// Hide shop menu if visible
if (shopMenuOverlay) {
shopMenuOverlay.visible = false;
}
// Hide nickname prompt if visible
if (game.nicknamePromptOverlay) {
game.nicknamePromptOverlay.visible = false;
}
// Update speed controller visibility
updateSpeedController();
// Play menu music
if (musicOn) {
LK.playMusic('Menu');
}
}
// 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
var finalNickname = nickname.length > 0 ? nickname : "Player";
// Add to scoreboard if in score mode
if (scoreMode) {
var scoreboard = storage.scoreboard || [];
scoreboard.push({
name: finalNickname,
score: score
});
// Keep only top 20 scores
scoreboard.sort(function (a, b) {
return b.score - a.score;
});
if (scoreboard.length > 20) {
scoreboard = scoreboard.slice(0, 20);
}
storage.scoreboard = scoreboard;
// Refresh scoreboard display
refreshScoreboard();
}
// Stop scoremode music and play gameover music if in score mode (only once)
if (scoreMode && !gameoverMusicPlayed) {
LK.stopMusic();
if (musicOn) {
LK.playMusic('Gameover');
gameoverMusicPlayed = true;
}
}
overlay.visible = false;
if (typeof callback === "function") {
callback(finalNickname);
}
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 gameoverMusicPlayed = false; // Track if gameover music has been played for current game over
var hero = new Hero();
game.addChild(hero);
hero.x = 2048 / 2;
hero.y = 2732 - 350;
hero.visible = false; // Hide in menu
// Great Shield system variables
var greatShields = [];
var greatShieldActive = false;
var greatShieldSpawnTimer = 0;
var greatShieldRespawnTimer = 0;
var greatShieldGameStarted = false;
// 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
// Beam button UI and state
var beamButtonReady = true;
var beamButtonCooldown = 0;
var beamButtonCooldownMax = 600; // 10 seconds at 60fps (half of ulti)
// Wave button UI and state
var waveButtonReady = true;
var waveButtonCooldown = 0;
var waveButtonCooldownMax = 900; // 15 seconds at 60fps (0.75x of ulti)
// 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);
// Create beam button above ulti button
var beamBtn = new Text2("CANNON", {
size: 120,
fill: 0x00EAFF,
fontWeight: "bold"
});
beamBtn.anchor.set(0.5, 0.5);
// Place above ulti button with gap
beamBtn.x = 2048 - 180;
beamBtn.y = 2732 - 180 - 240; // 60px gap above ulti button
beamBtn.bg = LK.getAsset('enemy2', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xffaa00 // Orange color to distinguish from ulti
});
beamBtn.bg.width = 340;
beamBtn.bg.height = 220;
beamBtn.bg.alpha = 0.25;
beamBtn.bg.x = beamBtn.x;
beamBtn.bg.y = beamBtn.y;
beamBtn.bg.visible = false; // Hide in menu and until purchased
game.addChild(beamBtn.bg);
beamBtn.visible = false; // Hide in menu and until purchased
game.addChild(beamBtn);
// Create wave button above beam button
var waveBtn = new Text2("WAVE", {
size: 120,
fill: 0x00EAFF,
fontWeight: "bold"
});
waveBtn.anchor.set(0.5, 0.5);
// Place above beam button with gap
waveBtn.x = 2048 - 180;
waveBtn.y = 2732 - 180 - 240 - 240; // 60px gap above beam button
waveBtn.bg = LK.getAsset('enemy2', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x9c27b0 // Purple color to distinguish from others
});
waveBtn.bg.width = 340;
waveBtn.bg.height = 220;
waveBtn.bg.alpha = 0.25;
waveBtn.bg.x = waveBtn.x;
waveBtn.bg.y = waveBtn.y;
waveBtn.bg.visible = false; // Hide in menu and until purchased
game.addChild(waveBtn.bg);
waveBtn.visible = false; // Hide in menu and until purchased
game.addChild(waveBtn);
// Beam button cooldown overlay
var beamBtnOverlay = LK.getAsset('enemy2', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
beamBtnOverlay.width = 340;
beamBtnOverlay.height = 220;
beamBtnOverlay.alpha = 0.55;
beamBtnOverlay.x = beamBtn.x;
beamBtnOverlay.y = beamBtn.y;
beamBtnOverlay.visible = false;
game.addChild(beamBtnOverlay);
// Wave button cooldown overlay
var waveBtnOverlay = LK.getAsset('enemy2', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
waveBtnOverlay.width = 340;
waveBtnOverlay.height = 220;
waveBtnOverlay.alpha = 0.55;
waveBtnOverlay.x = waveBtn.x;
waveBtnOverlay.y = waveBtn.y;
waveBtnOverlay.visible = false;
game.addChild(waveBtnOverlay);
// 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;
var lastCoinScore = 0; // Track last score milestone for coin rewards
// 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;
// Only allow Enemy4 to spawn after 600 score in standard and god modes
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 spawn logic
if (typeof scoreMode !== "undefined" && scoreMode || (typeof godMode !== "undefined" && godMode || typeof bossMode !== "undefined" && !bossMode) && (typeof score !== "undefined" && score >= 600 || typeof enemyXKilledAfter250 !== "undefined" && enemyXKilledAfter250)) {
// In score mode, always allow Enemy4. In standard/god mode, allow after 600 score OR after EnemyX is defeated.
enemy = new Enemy4();
enemy.x = 200 + Math.random() * (2048 - 400);
enemy.y = -80;
} else {
// If not allowed, fallback to Enemy1
enemy = new Enemy1();
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();
}
// Helper: create great shields
function createGreatShields() {
// Clear any existing shields
for (var i = greatShields.length - 1; i >= 0; i--) {
greatShields[i].destroy();
greatShields.splice(i, 1);
}
// Create 4 new shields
for (var i = 0; i < 4; i++) {
var shield = new GreatShieldOrb();
shield.angle = i * Math.PI * 2 / 4; // Space them 90 degrees apart
greatShields.push(shield);
game.addChild(shield);
}
greatShieldActive = true;
}
// Helper: check great shield collision and destroy shield
function checkGreatShieldCollisions() {
if (!greatShieldActive || greatShields.length === 0) return;
// Check collisions with enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var bullet = enemyBullets[i];
for (var j = greatShields.length - 1; j >= 0; j--) {
var shield = greatShields[j];
if (shield.intersects(bullet)) {
// Destroy the shield that was hit
LK.effects.flashObject(shield, 0xffffff, 200);
shield.destroy();
greatShields.splice(j, 1);
// Destroy the bullet
bullet.destroy();
enemyBullets.splice(i, 1);
// Check if all shields are gone
if (greatShields.length === 0) {
greatShieldActive = false;
greatShieldRespawnTimer = 420; // 7 seconds at 60fps
}
break;
}
}
}
// Check collisions with enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
// Skip boss enemies
if (enemy instanceof EnemyX || enemy instanceof EnemyZ) continue;
for (var j = greatShields.length - 1; j >= 0; j--) {
var shield = greatShields[j];
if (shield.intersects(enemy)) {
// Destroy the shield that was hit
LK.effects.flashObject(shield, 0xffffff, 200);
shield.destroy();
greatShields.splice(j, 1);
// Destroy the enemy (with particle effect)
LK.effects.flashObject(enemy, 0xffffff, 200);
for (var pi = 0; pi < 12; pi++) {
var part = LK.getAsset(enemy instanceof Enemy1 ? 'enemy1' : enemy instanceof Enemy2 ? 'enemy2' : 'enemy3', {
anchorX: 0.5,
anchorY: 0.5
});
part.x = enemy.x;
part.y = enemy.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);
}
enemy.destroy();
enemies.splice(i, 1);
// Check if all shields are gone
if (greatShields.length === 0) {
greatShieldActive = false;
greatShieldRespawnTimer = 420; // 7 seconds at 60fps
}
break;
}
}
}
}
// 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;
}
// 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;
}
}
// Handle wave button press (touch/click)
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased && waveButtonReady) {
// Check if touch is inside wave button area
var wdx = x - waveBtn.x;
var wdy = y - waveBtn.y;
if (Math.abs(wdx) <= waveBtn.bg.width / 2 && Math.abs(wdy) <= waveBtn.bg.height / 2) {
// Activate wave
if (!godMode) {
waveButtonReady = false;
waveButtonCooldown = waveButtonCooldownMax;
waveBtnOverlay.visible = true;
}
// Find nearest enemy for targeting
var targetX = hero.x;
var targetY = hero.y - 200; // Default target above hero
var nearestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - hero.x;
var dy = enemy.y - hero.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
targetX = enemy.x;
targetY = enemy.y;
}
}
// Calculate base angle toward target
var baseDx = targetX - hero.x;
var baseDy = targetY - hero.y;
var baseAngle = Math.atan2(baseDy, baseDx);
// Shoot 7 waves of bullets with delays using tween
var waveCount = 7;
var bulletsPerWave = 15;
var waveDelay = 150; // milliseconds between waves
var initialDelay = 200; // initial wait time
// Start shooting waves after initial delay
tween({}, {}, {
duration: initialDelay,
onFinish: function onFinish() {
// Shoot each wave with increasing delays
for (var wave = 0; wave < waveCount; wave++) {
(function (waveIndex) {
tween({}, {}, {
duration: waveIndex * waveDelay,
onFinish: function onFinish() {
// Shoot 15 bullets in crescent shape for this wave
var spreadAngle = Math.PI / 3; // 60 degrees total spread
for (var i = 0; i < bulletsPerWave; i++) {
var bullet = new HeroBullet();
bullet.x = hero.x;
bullet.y = hero.y - hero.height / 2 - 20;
// Calculate angle for this bullet (spread from left to right)
var angleOffset = (i - 7) * (spreadAngle / 14); // Center around middle bullet
var finalAngle = baseAngle + angleOffset;
// Set bullet velocity
var bulletSpeed = 28;
bullet.speedX = Math.cos(finalAngle) * bulletSpeed;
bullet.speedY = Math.sin(finalAngle) * bulletSpeed;
// Override update to use custom velocity
bullet.update = function (speedX, speedY) {
return function () {
this.x += speedX * (typeof gameSpeed !== "undefined" ? gameSpeed : 1);
this.y += speedY * (typeof gameSpeed !== "undefined" ? gameSpeed : 1);
};
}(bullet.speedX, bullet.speedY);
heroBullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('Cannon').play();
}
});
})(wave);
}
LK.getSound('laser').play();
}
});
return;
}
}
// Handle beam button press (touch/click)
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased && beamButtonReady) {
// Check if touch is inside beam button area
var bdx = x - beamBtn.x;
var bdy = y - beamBtn.y;
if (Math.abs(bdx) <= beamBtn.bg.width / 2 && Math.abs(bdy) <= beamBtn.bg.height / 2) {
// Activate beam
if (!godMode) {
beamButtonReady = false;
beamButtonCooldown = beamButtonCooldownMax;
beamBtnOverlay.visible = true;
}
// Shoot 5 laser balls one after another with 0.2 second delays
var shootPositions = [{
x: 2048 * 0.2,
delay: 200
},
// Left
{
x: 2048 * 0.4,
delay: 400
},
// Left-Center
{
x: 2048 * 0.5,
delay: 600
},
// Center
{
x: 2048 * 0.6,
delay: 800
},
// Right-Center
{
x: 2048 * 0.8,
delay: 1000
} // Right
];
for (var i = 0; i < shootPositions.length; i++) {
var pos = shootPositions[i];
// Create delayed shooting using setTimeout
var delayedShoot = function (shootX, shootDelay) {
return function () {
var laserBall = new LaserBall();
laserBall.x = shootX;
laserBall.y = 2732; // Start from bottom of screen
heroBullets.push(laserBall);
game.addChild(laserBall);
LK.getSound('Cannon').play();
};
}(pos.x, pos.delay);
// Use setTimeout to delay each shot
LK.setTimeout(delayedShoot, pos.delay);
}
LK.getSound('laser').play();
return;
}
}
// 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
if (!godMode) {
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;
}
}
// 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;
}
handleMove(x, y, obj);
// 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();
// Great Shield system logic
var greatShieldPurchased = storage.greatShieldPurchased || false;
if (greatShieldPurchased && !menuActive) {
// Mark game as started when not in menu
if (!greatShieldGameStarted) {
greatShieldGameStarted = true;
greatShieldSpawnTimer = 600; // 10 seconds at 60fps
}
// Handle initial spawn timer (10 seconds after game start)
if (greatShieldSpawnTimer > 0 && !greatShieldActive && greatShields.length === 0) {
greatShieldSpawnTimer -= typeof gameSpeed !== "undefined" ? gameSpeed : 1;
if (greatShieldSpawnTimer <= 0) {
createGreatShields();
}
}
// Handle respawn timer (7 seconds after all shields destroyed)
if (greatShieldRespawnTimer > 0) {
greatShieldRespawnTimer -= typeof gameSpeed !== "undefined" ? gameSpeed : 1;
if (greatShieldRespawnTimer <= 0) {
createGreatShields();
}
}
// Update existing shields
for (var i = 0; i < greatShields.length; i++) {
greatShields[i].update();
}
// Check shield collisions
checkGreatShieldCollisions();
} else if (!greatShieldPurchased || menuActive) {
// Clean up shields if not purchased or in menu
greatShieldGameStarted = false;
greatShieldSpawnTimer = 0;
greatShieldRespawnTimer = 0;
greatShieldActive = false;
for (var i = greatShields.length - 1; i >= 0; i--) {
greatShields[i].destroy();
greatShields.splice(i, 1);
}
}
// Apply game speed multiplier to all timed operations
var speedMultiplier = gameSpeed;
// Coin reward system - give 10 coins for every 50 score passed (except god mode)
if (!godMode && score > 0) {
var currentCoinMilestone = Math.floor(score / 50) * 50;
var lastCoinMilestone = Math.floor(lastCoinScore / 50) * 50;
if (currentCoinMilestone > lastCoinMilestone) {
var coinsEarned = (currentCoinMilestone - lastCoinMilestone) / 50 * 10;
coins += coinsEarned;
storage.coins = coins;
coinsTxt.setText('Coins: ' + coins);
}
}
lastCoinScore = score;
// 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;
}
}
// Wave button cooldown logic
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased) {
// Show wave button if purchased
waveBtn.visible = !menuActive;
waveBtn.bg.visible = !menuActive;
if (godMode) {
waveButtonReady = true;
waveButtonCooldown = 0;
waveBtnOverlay.visible = false;
waveBtn.text = "WAVE";
} else {
if (!waveButtonReady) {
waveButtonCooldown -= speedMultiplier;
if (waveButtonCooldown <= 0) {
waveButtonReady = true;
waveBtnOverlay.visible = false;
} else {
// Show overlay and update text to show seconds left
waveBtnOverlay.visible = true;
waveBtn.text = "WAVE\n" + Math.ceil(waveButtonCooldown / 60) + "s";
}
}
if (waveButtonReady) {
waveBtn.text = "WAVE";
waveBtnOverlay.visible = false;
}
}
} else {
// Hide wave button if not purchased
waveBtn.visible = false;
waveBtn.bg.visible = false;
waveBtnOverlay.visible = false;
}
// Beam button cooldown logic
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased) {
// Show beam button if purchased
beamBtn.visible = !menuActive;
beamBtn.bg.visible = !menuActive;
if (godMode) {
beamButtonReady = true;
beamButtonCooldown = 0;
beamBtnOverlay.visible = false;
beamBtn.text = "CANNON";
} else {
if (!beamButtonReady) {
beamButtonCooldown -= speedMultiplier;
if (beamButtonCooldown <= 0) {
beamButtonReady = true;
beamBtnOverlay.visible = false;
} else {
// Show overlay and update text to show seconds left
beamBtnOverlay.visible = true;
beamBtn.text = "CANNON\n" + Math.ceil(beamButtonCooldown / 60) + "s";
}
}
if (beamButtonReady) {
beamBtn.text = "CANNON";
beamBtnOverlay.visible = false;
}
}
} else {
// Hide beam button if not purchased
beamBtn.visible = false;
beamBtn.bg.visible = false;
beamBtnOverlay.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) {
// Always activate beam when BeamItem is picked up (no purchase required)
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) {
// Always allow triple bullet shooting (laser beam enabled by default)
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.y > 2732 + 60 || b.x < -60 || b.x > 2048 + 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);
// Handle laser ball kill count
if (b instanceof LaserBall) {
b.killCount++;
// Destroy laser ball after 2 kills
if (b.killCount >= b.maxKills) {
b.destroy();
heroBullets.splice(i, 1);
}
}
// Remove both or handle Enemy3/EnemyX/Enemy4/EnemyZ hp
if (e instanceof Enemy3 || e instanceof EnemyX || e instanceof Enemy4 || e instanceof EnemyZ) {
// LaserBall deals 10 damage, regular bullets deal 1 damage
var damage = b instanceof LaserBall ? 10 : 1;
e.hp -= damage;
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 {
// Handle laser ball kill count for normal enemies
if (b instanceof LaserBall) {
b.killCount++;
// Only destroy laser ball after 2 kills or if it should be removed
if (b.killCount >= b.maxKills) {
b.destroy();
heroBullets.splice(i, 1);
}
} else {
// Regular bullets get destroyed after one hit
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) {
// Game over
if (scoreMode) {
// Show custom nickname prompt overlay (gameover music will be handled there)
showNicknamePrompt(function (nickname) {
if (!nickname || typeof nickname !== "string" || nickname.trim().length === 0) {
nickname = "Player";
}
LK.getSound('Death').play();
// Return to main menu instead of game over
returnToMainMenu();
});
return;
} else {
LK.getSound('Death').play();
// Play gameover music once if not already played
if (!gameoverMusicPlayed && musicOn) {
LK.stopMusic();
LK.playMusic('Gameover');
gameoverMusicPlayed = true;
}
// Return to main menu instead of game over
returnToMainMenu();
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) {
// Game over
if (scoreMode) {
// Show custom nickname prompt overlay
showNicknamePrompt(function (nickname) {
if (!nickname || typeof nickname !== "string" || nickname.trim().length === 0) {
nickname = "Player";
}
LK.getSound('Death').play();
// Return to main menu instead of game over
returnToMainMenu();
});
return;
} else {
LK.getSound('Death').play();
// Play gameover music once if not already played
if (!gameoverMusicPlayed && musicOn) {
LK.stopMusic();
LK.playMusic('Gameover');
gameoverMusicPlayed = true;
}
// Return to main menu instead of game over
returnToMainMenu();
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) {
// Game over
if (scoreMode) {
// Show custom nickname prompt overlay
showNicknamePrompt(function (nickname) {
if (!nickname || typeof nickname !== "string" || nickname.trim().length === 0) {
nickname = "Player";
}
LK.getSound('Death').play();
// Return to main menu instead of game over
returnToMainMenu();
});
return;
} else {
LK.getSound('Death').play();
// Play gameover music once if not already played
if (!gameoverMusicPlayed && musicOn) {
LK.stopMusic();
LK.playMusic('Gameover');
gameoverMusicPlayed = true;
}
// Return to main menu instead of game over
returnToMainMenu();
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;
}
}
}
}
};
// --- CODE INPUT OVERLAY ---
function showCodeInput() {
if (game.codeInputOverlay && game.codeInputOverlay.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("ENTER CODE", {
size: 120,
fill: "#fff",
fontWeight: "bold"
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 800;
overlay.addChild(title);
// Code input display
var codeInput = "";
var maxLen = 4;
var codeText = new Text2("____", {
size: 150,
fill: 0x00EAFF
});
codeText.anchor.set(0.5, 0.5);
codeText.x = 2048 / 2;
codeText.y = 1200;
overlay.addChild(codeText);
// On-screen number pad (3x4 grid: 1-9, 0, clear, OK)
var numPadRows = [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"], ["C", "0", "OK"]];
var keyButtons = [];
var keySize = 180;
var keySpacing = 30;
var startY = 1400;
for (var row = 0; row < numPadRows.length; row++) {
var keysInRow = numPadRows[row];
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: 80,
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") {
// Process code
if (codeInput === "3131") {
// Reset all purchases and set coins to 0
storage.laserBeamPurchased = false;
storage.solarWavePurchased = false;
storage.greatShieldPurchased = false;
shopItems[0].purchased = false;
shopItems[1].purchased = false;
shopItems[2].purchased = false;
coins = 0;
storage.coins = coins;
coinsTxt.setText('Coins: ' + coins);
// Hide purchased abilities UI elements immediately
beamBtn.visible = false;
beamBtn.bg.visible = false;
beamBtnOverlay.visible = false;
waveBtn.visible = false;
waveBtn.bg.visible = false;
waveBtnOverlay.visible = false;
} else if (codeInput === "6262") {
// Add 5000 coins
coins += 5000;
storage.coins = coins;
coinsTxt.setText('Coins: ' + coins);
}
overlay.visible = false;
if (game.codeInputOverlay) {
game.removeChild(game.codeInputOverlay);
game.codeInputOverlay = null;
}
// Refresh shop menu display
if (shopMenuOverlay && shopMenuOverlay.visible) {
shopMenuOverlay.visible = false;
showShopMenu();
}
return;
} else if (btn.key === "C") {
// Clear input
codeInput = "";
var displayText = "";
for (var j = 0; j < maxLen; j++) {
if (j < codeInput.length) {
displayText += codeInput[j];
} else {
displayText += "_";
}
}
codeText.setText(displayText);
} else {
// Add digit
if (codeInput.length < maxLen) {
codeInput += btn.key;
var displayText = "";
for (var j = 0; j < maxLen; j++) {
if (j < codeInput.length) {
displayText += codeInput[j];
} else {
displayText += "_";
}
}
codeText.setText(displayText);
}
}
break;
}
}
};
// Add overlay to game and track
game.addChild(overlay);
game.codeInputOverlay = overlay;
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
scoreboard: []
});
/****
* 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 (3x Enemy1 hitpoints)
self.turnSpeed = 0.03; // How fast Enemy4 turns toward hero
self.currentAngle = Math.random() * Math.PI * 2; // Current movement direction
self.speed = 12; // Movement speed
self.update = function () {
var speedMult = typeof gameSpeed !== "undefined" ? gameSpeed : 1;
// Only move and turn if hero exists and Enemy4 is not exploded
if (typeof hero !== "undefined" && !self.exploded) {
// Calculate angle to hero for gradual turning
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var angleToHero = Math.atan2(dy, dx);
// Gradually turn toward hero
var angleDiff = angleToHero - self.currentAngle;
// Normalize angle difference to [-PI, PI]
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
// Turn gradually toward hero
if (Math.abs(angleDiff) > self.turnSpeed * speedMult) {
self.currentAngle += Math.sign(angleDiff) * self.turnSpeed * speedMult;
} else {
self.currentAngle = angleToHero;
}
// Move in current direction
self.x += Math.cos(self.currentAngle) * self.speed * speedMult;
self.y += Math.sin(self.currentAngle) * self.speed * speedMult;
}
// Check if Enemy4 reached middle of screen and explode
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var distanceToCenter = Math.sqrt((self.x - centerX) * (self.x - centerX) + (self.y - centerY) * (self.y - centerY));
if (!self.exploded && distanceToCenter < 100) {
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 can fly through hero and try to collide - no special collision handling here
};
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);
}
}
// Standard and god modes: EnemyX rarely drops a health potion
if ((typeof bossMode === "undefined" || !bossMode) && (typeof scoreMode === "undefined" || !scoreMode) && (typeof godMode !== "undefined" && godMode || typeof godMode !== "undefined" && !godMode)) {
if (Math.random() < 0.003) {
// 0.3% chance per update (less frequent)
var potion = new HealthPotion();
potion.x = self.x + (Math.random() - 0.5) * 120;
potion.y = self.y + 120;
if (typeof items !== "undefined" && typeof game !== "undefined") {
items.push(potion);
game.addChild(potion);
}
}
}
// 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;
}
// Check if laser beam is purchased before allowing automatic beam shooting
var laserBeamPurchased = storage.laserBeamPurchased || false;
// Sometimes shoot a beam down (every 360-540 ticks, much more rare, and only hit when visually active)
if (laserBeamPurchased && 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();
// Return to main menu instead of game over
returnToMainMenu();
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;
}
// Boss mode: randomly drop health potion, shield, or beam items from EnemyZ (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);
}
}
// Standard and god modes: EnemyZ rarely drops a health potion
if ((typeof bossMode === "undefined" || !bossMode) && (typeof scoreMode === "undefined" || !scoreMode) && (typeof godMode !== "undefined" && godMode || typeof godMode !== "undefined" && !godMode)) {
if (Math.random() < 0.003) {
// 0.3% chance per update (less frequent)
var potion = new HealthPotion();
potion.x = self.x + (Math.random() - 0.5) * 120;
potion.y = self.y + 120;
if (typeof items !== "undefined" && typeof game !== "undefined") {
items.push(potion);
game.addChild(potion);
}
}
}
// --- 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;
});
// Great Shield Orb class - spinning shields around hero
var GreatShieldOrb = Container.expand(function () {
var self = Container.call(this);
var shieldSprite = self.attachAsset('GreatShield', {
anchorX: 0.5,
anchorY: 0.5
});
shieldSprite.width = 180;
shieldSprite.height = 180;
shieldSprite.alpha = 0.9;
self.angle = 0;
self.radius = 280;
self.rotationSpeed = 0.04;
self.update = function () {
// Update rotation angle
self.angle += self.rotationSpeed * (typeof gameSpeed !== "undefined" ? gameSpeed : 1);
// Position around hero
if (typeof hero !== "undefined") {
self.x = hero.x + Math.cos(self.angle) * self.radius;
self.y = hero.y + Math.sin(self.angle) * self.radius;
// Make the bottom of the shield always point toward opposite of hero center
// Calculate angle from shield to hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var angleToHero = Math.atan2(dy, dx);
// Set shield rotation to point away from hero (add PI to reverse direction, plus PI/2 to make bottom point outward)
shieldSprite.rotation = angleToHero + Math.PI + Math.PI / 2;
// Update shield hitbox to match larger visual size
self.width = 180;
self.height = 180;
}
// Remove self-rotation to prevent spinning around itself
// shieldSprite.rotation += 0.08 * (typeof gameSpeed !== "undefined" ? gameSpeed : 1);
};
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;
});
// Laser Ball class for beam button ability
var LaserBall = Container.expand(function () {
var self = Container.call(this);
var ballSprite = self.attachAsset('heroBullet', {
anchorX: 0.5,
anchorY: 0.5
});
ballSprite.width = 60;
ballSprite.height = 150;
ballSprite.tint = 0xffaa00; // Orange color
self.speed = 28;
self.targetEnemy = null;
self.rotationSpeed = 0.08;
self.killCount = 0; // Track how many enemies this ball has killed
self.maxKills = 2; // Allow 2 kills before disappearing
self.update = function () {
var speedMult = typeof gameSpeed !== "undefined" ? gameSpeed : 1;
// Find nearest enemy if we don't have a target or target is destroyed
if (!self.targetEnemy || !self.targetEnemy.parent) {
var nearestDistance = Infinity;
self.targetEnemy = null;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
self.targetEnemy = enemy;
}
}
}
// Rotate towards target enemy
if (self.targetEnemy) {
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var targetAngle = Math.atan2(dy, dx) + Math.PI / 2; // Add 90 degrees since bullet points up
var currentAngle = self.rotation;
// Calculate shortest rotation path
var angleDiff = targetAngle - currentAngle;
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
// Rotate towards target
if (Math.abs(angleDiff) > 0.1) {
self.rotation += Math.sign(angleDiff) * self.rotationSpeed * speedMult;
} else {
self.rotation = targetAngle;
}
}
// Move forward in current direction
self.x += Math.sin(self.rotation) * self.speed * speedMult;
self.y -= Math.cos(self.rotation) * self.speed * speedMult;
};
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
****/
//Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property.
// 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 shop button below help button
var shopBtnBg = LK.getAsset('Button', {
anchorX: 0.5,
anchorY: 0.5
});
shopBtnBg.width = 340;
shopBtnBg.height = 140;
shopBtnBg.alpha = 0.7;
shopBtnBg.x = 2048 / 2;
shopBtnBg.y = helpBtnBg.y + helpBtnBg.height / 2 + 60 + shopBtnBg.height / 2;
menuOverlay.addChild(shopBtnBg);
var shopBtn = new Text2("SHOP", {
size: 80,
fill: 0x00EAFF,
fontWeight: "bold"
});
shopBtn.anchor.set(0.5, 0.5);
shopBtn.x = 2048 / 2;
shopBtn.y = shopBtnBg.y;
menuOverlay.addChild(shopBtn);
// Add scoreboard to right bottom (quarter of screen size)
var scoreboardBg = LK.getAsset('Scoreboard', {
anchorX: 1,
anchorY: 1,
x: 2048 - 60,
y: 2732 - 60,
width: 512,
height: 683
});
scoreboardBg.alpha = 0.9;
menuOverlay.addChild(scoreboardBg);
// Scoreboard entries container
var scoreboardContainer = new Container();
scoreboardContainer.x = scoreboardBg.x - scoreboardBg.width + 30;
scoreboardContainer.y = scoreboardBg.y - scoreboardBg.height + 80;
menuOverlay.addChild(scoreboardContainer);
// Function to refresh scoreboard display
function refreshScoreboard() {
// Clear existing entries
while (scoreboardContainer.children.length > 0) {
scoreboardContainer.removeChild(scoreboardContainer.children[0]);
}
// Get scoreboard data and sort by score (highest first)
var scoreboard = storage.scoreboard || [];
scoreboard.sort(function (a, b) {
return b.score - a.score;
});
// Show top 8 entries
var maxEntries = Math.min(8, scoreboard.length);
var entrySpacing = 70;
for (var i = 0; i < maxEntries; i++) {
var entry = scoreboard[i];
var entryY = i * entrySpacing;
// Rank number
var rankText = new Text2(i + 1 + ".", {
size: 45,
fill: "#fff",
fontWeight: "bold"
});
rankText.anchor.set(0, 0);
rankText.x = 20;
rankText.y = entryY;
scoreboardContainer.addChild(rankText);
// Player name
var nameText = new Text2(entry.name.substring(0, 8), {
size: 45,
fill: 0x00EAFF
});
nameText.anchor.set(0, 0);
nameText.x = 80;
nameText.y = entryY;
scoreboardContainer.addChild(nameText);
// Score
var scoreText = new Text2(entry.score.toString(), {
size: 45,
fill: 0xFFD700,
fontWeight: "bold"
});
scoreText.anchor.set(1, 0);
scoreText.x = 450;
scoreText.y = entryY;
scoreboardContainer.addChild(scoreText);
}
}
// Initial scoreboard display
refreshScoreboard();
// --- 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;
// Show wave button only if solar wave is purchased
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased) {
waveBtn.visible = true;
waveBtn.bg.visible = true;
}
// Show beam button only if laser beam is purchased
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased) {
beamBtn.visible = true;
beamBtn.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;
// Show wave button only if solar wave is purchased
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased) {
waveBtn.visible = true;
waveBtn.bg.visible = true;
}
// Show beam button only if laser beam is purchased
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased) {
beamBtn.visible = true;
beamBtn.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;
// Show wave button only if solar wave is purchased
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased) {
waveBtn.visible = true;
waveBtn.bg.visible = true;
}
// Show beam button only if laser beam is purchased
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased) {
beamBtn.visible = true;
beamBtn.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;
// Show wave button only if solar wave is purchased
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased) {
waveBtn.visible = true;
waveBtn.bg.visible = true;
}
// Show beam button only if laser beam is purchased
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased) {
beamBtn.visible = true;
beamBtn.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);
}
// --- SHOP MENU POPUP ---
var shopMenuOverlay = null;
function showShopMenu() {
if (shopMenuOverlay && shopMenuOverlay.visible) {
return;
}
if (shopMenuOverlay) {
shopMenuOverlay.visible = true;
return;
}
shopMenuOverlay = new Container();
shopMenuOverlay.zIndex = 20000;
// Shop menu background
var bg = LK.getAsset('Helpbackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
bg.width = 2048;
bg.height = 2732;
bg.alpha = 1.0;
shopMenuOverlay.addChild(bg);
// Shop title
var shopTitle = new Text2("SHOP", {
size: 120,
fill: "#fff",
fontWeight: "bold"
});
shopTitle.anchor.set(0.5, 0);
shopTitle.x = 2048 / 2;
shopTitle.y = 320;
shopMenuOverlay.addChild(shopTitle);
// Display coins
var shopCoinsText = new Text2('Coins: ' + coins, {
size: 80,
fill: 0xFFD700
});
shopCoinsText.anchor.set(0.5, 0);
shopCoinsText.x = 2048 / 2;
shopCoinsText.y = 450;
shopMenuOverlay.addChild(shopCoinsText);
// Shop items display
var startY = 700;
var spacingY = 420;
var shopItemElements = [];
for (var i = 0; i < shopItems.length; i++) {
var item = shopItems[i];
var itemY = startY + i * spacingY;
// Item background
var itemBg = LK.getAsset('Button', {
anchorX: 0.5,
anchorY: 0.5
});
itemBg.width = 1200;
itemBg.height = 300;
itemBg.alpha = item.purchased ? 0.3 : 0.7;
itemBg.x = 2048 / 2;
itemBg.y = itemY;
shopMenuOverlay.addChild(itemBg);
// Item name
var itemName = new Text2(item.name, {
size: 110,
fill: item.purchased ? 0x44ff44 : "#fff",
fontWeight: "bold"
});
itemName.anchor.set(0.5, 0);
itemName.x = 2048 / 2;
itemName.y = itemY - 100;
shopMenuOverlay.addChild(itemName);
// Item description
var itemDesc = new Text2(item.description, {
size: 75,
fill: item.purchased ? 0xaaaaaa : "#fff"
});
itemDesc.anchor.set(0.5, 0);
itemDesc.x = 2048 / 2;
itemDesc.y = itemY - 20;
shopMenuOverlay.addChild(itemDesc);
// Price/Status text
var priceText = new Text2(item.purchased ? "PURCHASED" : item.price + " Coins", {
size: 85,
fill: item.purchased ? 0x44ff44 : coins >= item.price ? 0xFFD700 : 0xff4444,
fontWeight: "bold"
});
priceText.anchor.set(0.5, 0);
priceText.x = 2048 / 2;
priceText.y = itemY + 60;
shopMenuOverlay.addChild(priceText);
shopItemElements.push({
bg: itemBg,
item: item,
index: i,
nameText: itemName,
priceText: priceText
});
}
// Code button (bottom right)
var codeBtnBg = LK.getAsset('Button', {
anchorX: 0.5,
anchorY: 0.5
});
codeBtnBg.width = 200;
codeBtnBg.height = 140;
codeBtnBg.alpha = 0.7;
codeBtnBg.x = 2048 - 150;
codeBtnBg.y = 2500;
shopMenuOverlay.addChild(codeBtnBg);
var codeBtn = new Text2("CODE", {
size: 70,
fill: 0x00EAFF,
fontWeight: "bold"
});
codeBtn.anchor.set(0.5, 0.5);
codeBtn.x = 2048 - 150;
codeBtn.y = 2500;
shopMenuOverlay.addChild(codeBtn);
// Close button
var closeShopBtnBg = LK.getAsset('Button', {
anchorX: 0.5,
anchorY: 0.5
});
closeShopBtnBg.width = 340;
closeShopBtnBg.height = 140;
closeShopBtnBg.alpha = 0.7;
closeShopBtnBg.x = 2048 / 2;
closeShopBtnBg.y = 2500;
shopMenuOverlay.addChild(closeShopBtnBg);
var closeShopBtn = new Text2("CLOSE", {
size: 80,
fill: 0x00EAFF,
fontWeight: "bold"
});
closeShopBtn.anchor.set(0.5, 0.5);
closeShopBtn.x = 2048 / 2;
closeShopBtn.y = 2500;
shopMenuOverlay.addChild(closeShopBtn);
// Shop menu interaction
shopMenuOverlay.down = function (x, y, obj) {
// Code button
var codeDx = x - codeBtn.x;
var codeDy = y - codeBtn.y;
if (Math.abs(codeDx) <= codeBtnBg.width / 2 && Math.abs(codeDy) <= codeBtnBg.height / 2) {
showCodeInput();
return;
}
// Close button
var dx = x - closeShopBtn.x;
var dy = y - closeShopBtn.y;
if (Math.abs(dx) <= closeShopBtnBg.width / 2 && Math.abs(dy) <= closeShopBtnBg.height / 2) {
shopMenuOverlay.visible = false;
return;
}
// Shop item clicks
for (var i = 0; i < shopItemElements.length; i++) {
var element = shopItemElements[i];
var itemDx = x - element.bg.x;
var itemDy = y - element.bg.y;
if (Math.abs(itemDx) <= 600 && Math.abs(itemDy) <= element.bg.height / 2) {
var item = element.item;
if (!item.purchased && coins >= item.price) {
// Purchase item
coins -= item.price;
storage.coins = coins;
item.purchased = true;
// Update storage
if (i === 0) storage.laserBeamPurchased = true;else if (i === 1) storage.solarWavePurchased = true;else if (i === 2) storage.greatShieldPurchased = true;
// Update display
coinsTxt.setText('Coins: ' + coins);
shopCoinsText.setText('Coins: ' + coins);
element.nameText.fill = 0x44ff44;
element.priceText.setText("PURCHASED");
element.priceText.fill = 0x44ff44;
// Update other items' price colors
for (var j = 0; j < shopItemElements.length; j++) {
if (j !== i && !shopItemElements[j].item.purchased) {
shopItemElements[j].priceText.fill = coins >= shopItemElements[j].item.price ? 0xFFD700 : 0xff4444;
}
}
}
return;
}
}
};
game.addChild(shopMenuOverlay);
}
// 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;
}
}
// Check if shop button was pressed
if (typeof shopBtn !== "undefined" && typeof shopBtnBg !== "undefined") {
var sdx = x - shopBtn.x;
var sdy = y - shopBtn.y;
if (Math.abs(sdx) <= shopBtnBg.width / 2 && Math.abs(sdy) <= shopBtnBg.height / 2) {
// Show shop menu
showShopMenu();
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);
// Coins display in top right corner
var coins = storage.coins || 0;
var coinsTxt = new Text2('Coins: ' + coins, {
size: 80,
fill: 0xFFD700
});
coinsTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(coinsTxt);
// Shop items data
var shopItems = [{
name: "Laser Cannon",
price: 500,
purchased: storage.laserBeamPurchased || false,
description: "Powerful cannon that fires laser balls in sequence"
}, {
name: "Solar Wave",
price: 750,
purchased: storage.solarWavePurchased || false,
description: "Powerful wave attack"
}, {
name: "Great Shield",
price: 1000,
purchased: storage.greatShieldPurchased || false,
description: "Enhanced shield protection"
}];
// 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() {
// Hide all health bar elements if in main menu
if (typeof menuActive !== "undefined" && menuActive) {
healthBarBg.visible = false;
healthBar.visible = false;
for (var i = 0; i < healthBarVSquares.length; i++) {
healthBarVSquares[i].visible = false;
}
return;
}
// 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();
// Function to return to main menu and restart game
function returnToMainMenu() {
// Stop all music (including gameover music)
LK.stopMusic();
// Reset all game variables to initial state
heroHealth = maxHealth;
score = 0;
spawnTick = 0;
enemyXSpawned = false;
enemyZSpawned = false;
enemyXKilledAfter250 = false;
bossMusicPlayed = false;
lastCoinScore = 0;
ultiReady = true;
ultiCooldown = 0;
laserReady = true;
laserCooldown = 0;
waveButtonReady = true;
waveButtonCooldown = 0;
beamButtonReady = true;
beamButtonCooldown = 0;
shieldActive = false;
shieldTimer = 0;
beamActive = false;
beamTimer = 0;
gameSpeed = 1.0;
if (typeof spawnRateBoostActive !== "undefined") {
spawnRateBoostActive = false;
spawnRateBoostTimer = 0;
nextBoostScore = 50;
}
if (typeof bossNextToSpawn !== "undefined") {
bossNextToSpawn = "X";
}
// Reset great shield system
greatShields = [];
greatShieldActive = false;
greatShieldSpawnTimer = 0;
greatShieldRespawnTimer = 0;
greatShieldGameStarted = false;
// Clear all game objects
for (var i = heroBullets.length - 1; i >= 0; i--) {
heroBullets[i].destroy();
heroBullets.splice(i, 1);
}
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
for (var i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
}
for (var i = items.length - 1; i >= 0; i--) {
items[i].destroy();
items.splice(i, 1);
}
// Hide active laser beam if exists
if (game.activeLaserBeam) {
game.activeLaserBeam.destroy();
game.activeLaserBeam = null;
game.activeLaserBeamTimer = 0;
}
// Reset hero position
hero.x = 2048 / 2;
hero.y = 2732 - 350;
hero.visible = false;
// Reset score display
scoreTxt.setText('0');
// Update health display
updateHealthBar();
updateDemoHealthSquares && updateDemoHealthSquares();
// Hide all game UI elements
healthBarBg.visible = false;
healthBar.visible = false;
for (var i = 0; i < healthBarVSquares.length; i++) {
healthBarVSquares[i].visible = false;
}
ultiBtn.visible = false;
ultiBtn.bg.visible = false;
ultiBtnOverlay.visible = false;
waveBtn.visible = false;
waveBtn.bg.visible = false;
waveBtnOverlay.visible = false;
beamBtn.visible = false;
beamBtn.bg.visible = false;
beamBtnOverlay.visible = false;
for (var i = 0; i < greenSquares.length; i++) {
greenSquares[i].visible = false;
}
// Hide game background and show menu
if (typeof backgroundSprite !== "undefined") {
backgroundSprite.visible = false;
}
if (typeof menuBgSprite !== "undefined") {
menuBgSprite.visible = true;
}
// Reset game modes
godMode = false;
bossMode = false;
scoreMode = false;
// Reset gameover music flag for new game
gameoverMusicPlayed = false;
// Show menu overlay and activate menu state
menuOverlay.visible = true;
menuActive = true;
// Hide mode selection if visible
if (modeSelectionOverlay) {
modeSelectionOverlay.visible = false;
}
// Hide help menu if visible
if (helpMenuOverlay) {
helpMenuOverlay.visible = false;
}
// Hide shop menu if visible
if (shopMenuOverlay) {
shopMenuOverlay.visible = false;
}
// Hide nickname prompt if visible
if (game.nicknamePromptOverlay) {
game.nicknamePromptOverlay.visible = false;
}
// Update speed controller visibility
updateSpeedController();
// Play menu music
if (musicOn) {
LK.playMusic('Menu');
}
}
// 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
var finalNickname = nickname.length > 0 ? nickname : "Player";
// Add to scoreboard if in score mode
if (scoreMode) {
var scoreboard = storage.scoreboard || [];
scoreboard.push({
name: finalNickname,
score: score
});
// Keep only top 20 scores
scoreboard.sort(function (a, b) {
return b.score - a.score;
});
if (scoreboard.length > 20) {
scoreboard = scoreboard.slice(0, 20);
}
storage.scoreboard = scoreboard;
// Refresh scoreboard display
refreshScoreboard();
}
// Stop scoremode music and play gameover music if in score mode (only once)
if (scoreMode && !gameoverMusicPlayed) {
LK.stopMusic();
if (musicOn) {
LK.playMusic('Gameover');
gameoverMusicPlayed = true;
}
}
overlay.visible = false;
if (typeof callback === "function") {
callback(finalNickname);
}
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 gameoverMusicPlayed = false; // Track if gameover music has been played for current game over
var hero = new Hero();
game.addChild(hero);
hero.x = 2048 / 2;
hero.y = 2732 - 350;
hero.visible = false; // Hide in menu
// Great Shield system variables
var greatShields = [];
var greatShieldActive = false;
var greatShieldSpawnTimer = 0;
var greatShieldRespawnTimer = 0;
var greatShieldGameStarted = false;
// 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
// Beam button UI and state
var beamButtonReady = true;
var beamButtonCooldown = 0;
var beamButtonCooldownMax = 600; // 10 seconds at 60fps (half of ulti)
// Wave button UI and state
var waveButtonReady = true;
var waveButtonCooldown = 0;
var waveButtonCooldownMax = 900; // 15 seconds at 60fps (0.75x of ulti)
// 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);
// Create beam button above ulti button
var beamBtn = new Text2("CANNON", {
size: 120,
fill: 0x00EAFF,
fontWeight: "bold"
});
beamBtn.anchor.set(0.5, 0.5);
// Place above ulti button with gap
beamBtn.x = 2048 - 180;
beamBtn.y = 2732 - 180 - 240; // 60px gap above ulti button
beamBtn.bg = LK.getAsset('enemy2', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xffaa00 // Orange color to distinguish from ulti
});
beamBtn.bg.width = 340;
beamBtn.bg.height = 220;
beamBtn.bg.alpha = 0.25;
beamBtn.bg.x = beamBtn.x;
beamBtn.bg.y = beamBtn.y;
beamBtn.bg.visible = false; // Hide in menu and until purchased
game.addChild(beamBtn.bg);
beamBtn.visible = false; // Hide in menu and until purchased
game.addChild(beamBtn);
// Create wave button above beam button
var waveBtn = new Text2("WAVE", {
size: 120,
fill: 0x00EAFF,
fontWeight: "bold"
});
waveBtn.anchor.set(0.5, 0.5);
// Place above beam button with gap
waveBtn.x = 2048 - 180;
waveBtn.y = 2732 - 180 - 240 - 240; // 60px gap above beam button
waveBtn.bg = LK.getAsset('enemy2', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x9c27b0 // Purple color to distinguish from others
});
waveBtn.bg.width = 340;
waveBtn.bg.height = 220;
waveBtn.bg.alpha = 0.25;
waveBtn.bg.x = waveBtn.x;
waveBtn.bg.y = waveBtn.y;
waveBtn.bg.visible = false; // Hide in menu and until purchased
game.addChild(waveBtn.bg);
waveBtn.visible = false; // Hide in menu and until purchased
game.addChild(waveBtn);
// Beam button cooldown overlay
var beamBtnOverlay = LK.getAsset('enemy2', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
beamBtnOverlay.width = 340;
beamBtnOverlay.height = 220;
beamBtnOverlay.alpha = 0.55;
beamBtnOverlay.x = beamBtn.x;
beamBtnOverlay.y = beamBtn.y;
beamBtnOverlay.visible = false;
game.addChild(beamBtnOverlay);
// Wave button cooldown overlay
var waveBtnOverlay = LK.getAsset('enemy2', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
waveBtnOverlay.width = 340;
waveBtnOverlay.height = 220;
waveBtnOverlay.alpha = 0.55;
waveBtnOverlay.x = waveBtn.x;
waveBtnOverlay.y = waveBtn.y;
waveBtnOverlay.visible = false;
game.addChild(waveBtnOverlay);
// 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;
var lastCoinScore = 0; // Track last score milestone for coin rewards
// 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;
// Only allow Enemy4 to spawn after 600 score in standard and god modes
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 spawn logic
if (typeof scoreMode !== "undefined" && scoreMode || (typeof godMode !== "undefined" && godMode || typeof bossMode !== "undefined" && !bossMode) && (typeof score !== "undefined" && score >= 600 || typeof enemyXKilledAfter250 !== "undefined" && enemyXKilledAfter250)) {
// In score mode, always allow Enemy4. In standard/god mode, allow after 600 score OR after EnemyX is defeated.
enemy = new Enemy4();
enemy.x = 200 + Math.random() * (2048 - 400);
enemy.y = -80;
} else {
// If not allowed, fallback to Enemy1
enemy = new Enemy1();
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();
}
// Helper: create great shields
function createGreatShields() {
// Clear any existing shields
for (var i = greatShields.length - 1; i >= 0; i--) {
greatShields[i].destroy();
greatShields.splice(i, 1);
}
// Create 4 new shields
for (var i = 0; i < 4; i++) {
var shield = new GreatShieldOrb();
shield.angle = i * Math.PI * 2 / 4; // Space them 90 degrees apart
greatShields.push(shield);
game.addChild(shield);
}
greatShieldActive = true;
}
// Helper: check great shield collision and destroy shield
function checkGreatShieldCollisions() {
if (!greatShieldActive || greatShields.length === 0) return;
// Check collisions with enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var bullet = enemyBullets[i];
for (var j = greatShields.length - 1; j >= 0; j--) {
var shield = greatShields[j];
if (shield.intersects(bullet)) {
// Destroy the shield that was hit
LK.effects.flashObject(shield, 0xffffff, 200);
shield.destroy();
greatShields.splice(j, 1);
// Destroy the bullet
bullet.destroy();
enemyBullets.splice(i, 1);
// Check if all shields are gone
if (greatShields.length === 0) {
greatShieldActive = false;
greatShieldRespawnTimer = 420; // 7 seconds at 60fps
}
break;
}
}
}
// Check collisions with enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
// Skip boss enemies
if (enemy instanceof EnemyX || enemy instanceof EnemyZ) continue;
for (var j = greatShields.length - 1; j >= 0; j--) {
var shield = greatShields[j];
if (shield.intersects(enemy)) {
// Destroy the shield that was hit
LK.effects.flashObject(shield, 0xffffff, 200);
shield.destroy();
greatShields.splice(j, 1);
// Destroy the enemy (with particle effect)
LK.effects.flashObject(enemy, 0xffffff, 200);
for (var pi = 0; pi < 12; pi++) {
var part = LK.getAsset(enemy instanceof Enemy1 ? 'enemy1' : enemy instanceof Enemy2 ? 'enemy2' : 'enemy3', {
anchorX: 0.5,
anchorY: 0.5
});
part.x = enemy.x;
part.y = enemy.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);
}
enemy.destroy();
enemies.splice(i, 1);
// Check if all shields are gone
if (greatShields.length === 0) {
greatShieldActive = false;
greatShieldRespawnTimer = 420; // 7 seconds at 60fps
}
break;
}
}
}
}
// 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;
}
// 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;
}
}
// Handle wave button press (touch/click)
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased && waveButtonReady) {
// Check if touch is inside wave button area
var wdx = x - waveBtn.x;
var wdy = y - waveBtn.y;
if (Math.abs(wdx) <= waveBtn.bg.width / 2 && Math.abs(wdy) <= waveBtn.bg.height / 2) {
// Activate wave
if (!godMode) {
waveButtonReady = false;
waveButtonCooldown = waveButtonCooldownMax;
waveBtnOverlay.visible = true;
}
// Find nearest enemy for targeting
var targetX = hero.x;
var targetY = hero.y - 200; // Default target above hero
var nearestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - hero.x;
var dy = enemy.y - hero.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
targetX = enemy.x;
targetY = enemy.y;
}
}
// Calculate base angle toward target
var baseDx = targetX - hero.x;
var baseDy = targetY - hero.y;
var baseAngle = Math.atan2(baseDy, baseDx);
// Shoot 7 waves of bullets with delays using tween
var waveCount = 7;
var bulletsPerWave = 15;
var waveDelay = 150; // milliseconds between waves
var initialDelay = 200; // initial wait time
// Start shooting waves after initial delay
tween({}, {}, {
duration: initialDelay,
onFinish: function onFinish() {
// Shoot each wave with increasing delays
for (var wave = 0; wave < waveCount; wave++) {
(function (waveIndex) {
tween({}, {}, {
duration: waveIndex * waveDelay,
onFinish: function onFinish() {
// Shoot 15 bullets in crescent shape for this wave
var spreadAngle = Math.PI / 3; // 60 degrees total spread
for (var i = 0; i < bulletsPerWave; i++) {
var bullet = new HeroBullet();
bullet.x = hero.x;
bullet.y = hero.y - hero.height / 2 - 20;
// Calculate angle for this bullet (spread from left to right)
var angleOffset = (i - 7) * (spreadAngle / 14); // Center around middle bullet
var finalAngle = baseAngle + angleOffset;
// Set bullet velocity
var bulletSpeed = 28;
bullet.speedX = Math.cos(finalAngle) * bulletSpeed;
bullet.speedY = Math.sin(finalAngle) * bulletSpeed;
// Override update to use custom velocity
bullet.update = function (speedX, speedY) {
return function () {
this.x += speedX * (typeof gameSpeed !== "undefined" ? gameSpeed : 1);
this.y += speedY * (typeof gameSpeed !== "undefined" ? gameSpeed : 1);
};
}(bullet.speedX, bullet.speedY);
heroBullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('Cannon').play();
}
});
})(wave);
}
LK.getSound('laser').play();
}
});
return;
}
}
// Handle beam button press (touch/click)
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased && beamButtonReady) {
// Check if touch is inside beam button area
var bdx = x - beamBtn.x;
var bdy = y - beamBtn.y;
if (Math.abs(bdx) <= beamBtn.bg.width / 2 && Math.abs(bdy) <= beamBtn.bg.height / 2) {
// Activate beam
if (!godMode) {
beamButtonReady = false;
beamButtonCooldown = beamButtonCooldownMax;
beamBtnOverlay.visible = true;
}
// Shoot 5 laser balls one after another with 0.2 second delays
var shootPositions = [{
x: 2048 * 0.2,
delay: 200
},
// Left
{
x: 2048 * 0.4,
delay: 400
},
// Left-Center
{
x: 2048 * 0.5,
delay: 600
},
// Center
{
x: 2048 * 0.6,
delay: 800
},
// Right-Center
{
x: 2048 * 0.8,
delay: 1000
} // Right
];
for (var i = 0; i < shootPositions.length; i++) {
var pos = shootPositions[i];
// Create delayed shooting using setTimeout
var delayedShoot = function (shootX, shootDelay) {
return function () {
var laserBall = new LaserBall();
laserBall.x = shootX;
laserBall.y = 2732; // Start from bottom of screen
heroBullets.push(laserBall);
game.addChild(laserBall);
LK.getSound('Cannon').play();
};
}(pos.x, pos.delay);
// Use setTimeout to delay each shot
LK.setTimeout(delayedShoot, pos.delay);
}
LK.getSound('laser').play();
return;
}
}
// 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
if (!godMode) {
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;
}
}
// 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;
}
handleMove(x, y, obj);
// 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();
// Great Shield system logic
var greatShieldPurchased = storage.greatShieldPurchased || false;
if (greatShieldPurchased && !menuActive) {
// Mark game as started when not in menu
if (!greatShieldGameStarted) {
greatShieldGameStarted = true;
greatShieldSpawnTimer = 600; // 10 seconds at 60fps
}
// Handle initial spawn timer (10 seconds after game start)
if (greatShieldSpawnTimer > 0 && !greatShieldActive && greatShields.length === 0) {
greatShieldSpawnTimer -= typeof gameSpeed !== "undefined" ? gameSpeed : 1;
if (greatShieldSpawnTimer <= 0) {
createGreatShields();
}
}
// Handle respawn timer (7 seconds after all shields destroyed)
if (greatShieldRespawnTimer > 0) {
greatShieldRespawnTimer -= typeof gameSpeed !== "undefined" ? gameSpeed : 1;
if (greatShieldRespawnTimer <= 0) {
createGreatShields();
}
}
// Update existing shields
for (var i = 0; i < greatShields.length; i++) {
greatShields[i].update();
}
// Check shield collisions
checkGreatShieldCollisions();
} else if (!greatShieldPurchased || menuActive) {
// Clean up shields if not purchased or in menu
greatShieldGameStarted = false;
greatShieldSpawnTimer = 0;
greatShieldRespawnTimer = 0;
greatShieldActive = false;
for (var i = greatShields.length - 1; i >= 0; i--) {
greatShields[i].destroy();
greatShields.splice(i, 1);
}
}
// Apply game speed multiplier to all timed operations
var speedMultiplier = gameSpeed;
// Coin reward system - give 10 coins for every 50 score passed (except god mode)
if (!godMode && score > 0) {
var currentCoinMilestone = Math.floor(score / 50) * 50;
var lastCoinMilestone = Math.floor(lastCoinScore / 50) * 50;
if (currentCoinMilestone > lastCoinMilestone) {
var coinsEarned = (currentCoinMilestone - lastCoinMilestone) / 50 * 10;
coins += coinsEarned;
storage.coins = coins;
coinsTxt.setText('Coins: ' + coins);
}
}
lastCoinScore = score;
// 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;
}
}
// Wave button cooldown logic
var solarWavePurchased = storage.solarWavePurchased || false;
if (solarWavePurchased) {
// Show wave button if purchased
waveBtn.visible = !menuActive;
waveBtn.bg.visible = !menuActive;
if (godMode) {
waveButtonReady = true;
waveButtonCooldown = 0;
waveBtnOverlay.visible = false;
waveBtn.text = "WAVE";
} else {
if (!waveButtonReady) {
waveButtonCooldown -= speedMultiplier;
if (waveButtonCooldown <= 0) {
waveButtonReady = true;
waveBtnOverlay.visible = false;
} else {
// Show overlay and update text to show seconds left
waveBtnOverlay.visible = true;
waveBtn.text = "WAVE\n" + Math.ceil(waveButtonCooldown / 60) + "s";
}
}
if (waveButtonReady) {
waveBtn.text = "WAVE";
waveBtnOverlay.visible = false;
}
}
} else {
// Hide wave button if not purchased
waveBtn.visible = false;
waveBtn.bg.visible = false;
waveBtnOverlay.visible = false;
}
// Beam button cooldown logic
var laserBeamPurchased = storage.laserBeamPurchased || false;
if (laserBeamPurchased) {
// Show beam button if purchased
beamBtn.visible = !menuActive;
beamBtn.bg.visible = !menuActive;
if (godMode) {
beamButtonReady = true;
beamButtonCooldown = 0;
beamBtnOverlay.visible = false;
beamBtn.text = "CANNON";
} else {
if (!beamButtonReady) {
beamButtonCooldown -= speedMultiplier;
if (beamButtonCooldown <= 0) {
beamButtonReady = true;
beamBtnOverlay.visible = false;
} else {
// Show overlay and update text to show seconds left
beamBtnOverlay.visible = true;
beamBtn.text = "CANNON\n" + Math.ceil(beamButtonCooldown / 60) + "s";
}
}
if (beamButtonReady) {
beamBtn.text = "CANNON";
beamBtnOverlay.visible = false;
}
}
} else {
// Hide beam button if not purchased
beamBtn.visible = false;
beamBtn.bg.visible = false;
beamBtnOverlay.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) {
// Always activate beam when BeamItem is picked up (no purchase required)
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) {
// Always allow triple bullet shooting (laser beam enabled by default)
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.y > 2732 + 60 || b.x < -60 || b.x > 2048 + 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);
// Handle laser ball kill count
if (b instanceof LaserBall) {
b.killCount++;
// Destroy laser ball after 2 kills
if (b.killCount >= b.maxKills) {
b.destroy();
heroBullets.splice(i, 1);
}
}
// Remove both or handle Enemy3/EnemyX/Enemy4/EnemyZ hp
if (e instanceof Enemy3 || e instanceof EnemyX || e instanceof Enemy4 || e instanceof EnemyZ) {
// LaserBall deals 10 damage, regular bullets deal 1 damage
var damage = b instanceof LaserBall ? 10 : 1;
e.hp -= damage;
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 {
// Handle laser ball kill count for normal enemies
if (b instanceof LaserBall) {
b.killCount++;
// Only destroy laser ball after 2 kills or if it should be removed
if (b.killCount >= b.maxKills) {
b.destroy();
heroBullets.splice(i, 1);
}
} else {
// Regular bullets get destroyed after one hit
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) {
// Game over
if (scoreMode) {
// Show custom nickname prompt overlay (gameover music will be handled there)
showNicknamePrompt(function (nickname) {
if (!nickname || typeof nickname !== "string" || nickname.trim().length === 0) {
nickname = "Player";
}
LK.getSound('Death').play();
// Return to main menu instead of game over
returnToMainMenu();
});
return;
} else {
LK.getSound('Death').play();
// Play gameover music once if not already played
if (!gameoverMusicPlayed && musicOn) {
LK.stopMusic();
LK.playMusic('Gameover');
gameoverMusicPlayed = true;
}
// Return to main menu instead of game over
returnToMainMenu();
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) {
// Game over
if (scoreMode) {
// Show custom nickname prompt overlay
showNicknamePrompt(function (nickname) {
if (!nickname || typeof nickname !== "string" || nickname.trim().length === 0) {
nickname = "Player";
}
LK.getSound('Death').play();
// Return to main menu instead of game over
returnToMainMenu();
});
return;
} else {
LK.getSound('Death').play();
// Play gameover music once if not already played
if (!gameoverMusicPlayed && musicOn) {
LK.stopMusic();
LK.playMusic('Gameover');
gameoverMusicPlayed = true;
}
// Return to main menu instead of game over
returnToMainMenu();
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) {
// Game over
if (scoreMode) {
// Show custom nickname prompt overlay
showNicknamePrompt(function (nickname) {
if (!nickname || typeof nickname !== "string" || nickname.trim().length === 0) {
nickname = "Player";
}
LK.getSound('Death').play();
// Return to main menu instead of game over
returnToMainMenu();
});
return;
} else {
LK.getSound('Death').play();
// Play gameover music once if not already played
if (!gameoverMusicPlayed && musicOn) {
LK.stopMusic();
LK.playMusic('Gameover');
gameoverMusicPlayed = true;
}
// Return to main menu instead of game over
returnToMainMenu();
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;
}
}
}
}
};
// --- CODE INPUT OVERLAY ---
function showCodeInput() {
if (game.codeInputOverlay && game.codeInputOverlay.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("ENTER CODE", {
size: 120,
fill: "#fff",
fontWeight: "bold"
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 800;
overlay.addChild(title);
// Code input display
var codeInput = "";
var maxLen = 4;
var codeText = new Text2("____", {
size: 150,
fill: 0x00EAFF
});
codeText.anchor.set(0.5, 0.5);
codeText.x = 2048 / 2;
codeText.y = 1200;
overlay.addChild(codeText);
// On-screen number pad (3x4 grid: 1-9, 0, clear, OK)
var numPadRows = [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"], ["C", "0", "OK"]];
var keyButtons = [];
var keySize = 180;
var keySpacing = 30;
var startY = 1400;
for (var row = 0; row < numPadRows.length; row++) {
var keysInRow = numPadRows[row];
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: 80,
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") {
// Process code
if (codeInput === "3131") {
// Reset all purchases and set coins to 0
storage.laserBeamPurchased = false;
storage.solarWavePurchased = false;
storage.greatShieldPurchased = false;
shopItems[0].purchased = false;
shopItems[1].purchased = false;
shopItems[2].purchased = false;
coins = 0;
storage.coins = coins;
coinsTxt.setText('Coins: ' + coins);
// Hide purchased abilities UI elements immediately
beamBtn.visible = false;
beamBtn.bg.visible = false;
beamBtnOverlay.visible = false;
waveBtn.visible = false;
waveBtn.bg.visible = false;
waveBtnOverlay.visible = false;
} else if (codeInput === "6262") {
// Add 5000 coins
coins += 5000;
storage.coins = coins;
coinsTxt.setText('Coins: ' + coins);
}
overlay.visible = false;
if (game.codeInputOverlay) {
game.removeChild(game.codeInputOverlay);
game.codeInputOverlay = null;
}
// Refresh shop menu display
if (shopMenuOverlay && shopMenuOverlay.visible) {
shopMenuOverlay.visible = false;
showShopMenu();
}
return;
} else if (btn.key === "C") {
// Clear input
codeInput = "";
var displayText = "";
for (var j = 0; j < maxLen; j++) {
if (j < codeInput.length) {
displayText += codeInput[j];
} else {
displayText += "_";
}
}
codeText.setText(displayText);
} else {
// Add digit
if (codeInput.length < maxLen) {
codeInput += btn.key;
var displayText = "";
for (var j = 0; j < maxLen; j++) {
if (j < codeInput.length) {
displayText += codeInput[j];
} else {
displayText += "_";
}
}
codeText.setText(displayText);
}
}
break;
}
}
};
// Add overlay to game and track
game.addChild(overlay);
game.codeInputOverlay = overlay;
}
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
an empty hologram scoreboard, blue and cyan colors, futuristic, tablet shape. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat