/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
musicEnabled: true
});
/****
* Classes
****/
var Boss = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
// Calculate total health of enemies from previous level (currentLevel - 1)
var previousLevel = Math.max(1, currentLevel - 1);
var enemyMaxHealth = 40 + (previousLevel - 1) * 8;
var enemiesInPreviousLevel = Math.floor(enemiesPerLevel * (1 + Math.floor((previousLevel - 1) / 5) * 0.4));
var totalEnemyHealth = enemyMaxHealth * enemiesInPreviousLevel;
self.maxHealth = totalEnemyHealth * 3;
self.health = self.maxHealth;
self.speed = 0.8 + Math.max(0, currentLevel - 10) * 0.1;
var baseDamage = 30;
var bossNumber = Math.floor(currentLevel / 10); // Boss 1 at level 10, Boss 2 at level 20, etc.
var damageMultiplier = 1 + (bossNumber - 1) * 0.5; // 50% increase per boss
self.damage = Math.floor(baseDamage * damageMultiplier);
self.lastAttack = 0;
// Create boss health bar background
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 30
});
self.healthBarBg.y = -80;
self.addChild(self.healthBarBg);
// Create boss health bar fill
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5,
width: 196,
height: 26
});
self.healthBarFill.x = -98;
self.healthBarFill.y = -80;
self.addChild(self.healthBarFill);
self.attackCooldown = 120;
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
// Apply blue tint when frozen
if (graphics.tint !== 0x4444ff) {
graphics.tint = 0x4444ff;
}
} else {
// Remove blue tint when not frozen
if (graphics.tint !== 0xffffff) {
graphics.tint = 0xffffff;
}
}
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * effectiveSpeed;
self.y += dy / distance * effectiveSpeed;
}
// Attack player on physical contact only
self.lastAttack++;
if (self.lastAttack >= self.attackCooldown) {
// Determine number of bullets based on health
var bulletsToFire = self.health <= self.maxHealth / 2 ? 3 : 1;
// Shoot bullets at player
for (var b = 0; b < bulletsToFire; b++) {
var bullet = new BossBullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Add spread for multiple bullets
var spreadAngle = 0;
if (bulletsToFire > 1) {
spreadAngle = (b - (bulletsToFire - 1) / 2) * 0.3; // Spread bullets
}
var angle = Math.atan2(dy, dx) + spreadAngle;
bullet.velocityX = Math.cos(angle) * 6;
bullet.velocityY = Math.sin(angle) * 6;
// Set bullet rotation to face movement direction
if (bullet.setRotation) {
bullet.setRotation(bullet.velocityX, bullet.velocityY);
}
}
bossBullets.push(bullet);
game.addChild(bullet);
}
if (musicEnabled) LK.getSound('shoot').play();
self.lastAttack = 0;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
// Update boss health bar
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 196 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Play explosion sound
if (musicEnabled) LK.getSound('explosion').play();
// Create massive explosion effect with multiple waves of particles
for (var e = 0; e < 8; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 60 + e * 25,
height: 60 + e * 25,
alpha: 1.0 - e * 0.1,
tint: e === 0 ? 0xFF0000 : e === 1 ? 0xFF2200 : e === 2 ? 0xFF4500 : e === 3 ? 0xFF6600 : e === 4 ? 0xFF8800 : e === 5 ? 0xFFAA00 : e === 6 ? 0xFFCC00 : 0xFFFF00
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 80;
explosionCircle.y = self.y + (Math.random() - 0.5) * 80;
game.addChild(explosionCircle);
// Animate explosion with dramatic scaling and movement
tween(explosionCircle, {
scaleX: 5 + e * 0.8,
scaleY: 5 + e * 0.8,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 200,
y: explosionCircle.y + (Math.random() - 0.5) * 200
}, {
duration: 800 + e * 150,
easing: e % 3 === 0 ? tween.easeOut : e % 3 === 1 ? tween.easeInOut : tween.bounceOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Drop guaranteed 10-20 coins
var numCoins = 10 + Math.floor(Math.random() * 11); // 10-20 coins
for (var i = 0; i < numCoins; i++) {
var coin = new Coin();
// Calculate coin value - first 5 levels = 10, then +5 every 5 levels
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
coin.value = 10; // Levels 1-5
} else {
coin.value = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
coin.x = self.x + (Math.random() - 0.5) * 150;
coin.y = self.y + (Math.random() - 0.5) * 150;
coins.push(coin);
game.addChild(coin);
}
// Drop permanent bullet upgrade
var bulletUpgrade = new BulletUpgrade();
bulletUpgrade.x = self.x + (Math.random() - 0.5) * 100;
bulletUpgrade.y = self.y + (Math.random() - 0.5) * 100;
powerUps.push(bulletUpgrade);
game.addChild(bulletUpgrade);
// Guaranteed diamond drop
var diamond1 = new Diamond();
// Calculate diamond value - 5x current coin value
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
diamond1.value = coinValue * 5; // Boss diamonds worth 5x current coin value
diamond1.x = self.x + (Math.random() - 0.5) * 100;
diamond1.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond1);
game.addChild(diamond1);
// 50% chance for 2 additional diamonds
if (Math.random() < 0.50) {
for (var d = 0; d < 2; d++) {
var diamond = new Diamond();
// Calculate diamond value - 5x current coin value
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
diamond.value = coinValue * 5;
diamond.x = self.x + (Math.random() - 0.5) * 100;
diamond.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond);
game.addChild(diamond);
}
}
// 50% chance for ember drop
if (Math.random() < 0.50) {
var ember = new Ember();
ember.x = self.x + (Math.random() - 0.5) * 100;
ember.y = self.y + (Math.random() - 0.5) * 100;
embers.push(ember);
game.addChild(ember);
}
currentBoss = null;
// Stop boss fight music and start gameplay music
if (musicEnabled) {
LK.stopMusic();
LK.playMusic('3', {
loop: true
});
}
self.destroy();
// Boss is defeated, level progression will be handled in main update loop
enemiesKilled = 0;
};
return self;
});
var BossBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.damage = 15;
// Set rotation method for bullet orientation
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with player using precise distance calculation
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (player.width + self.width) / 8; // Tight collision bounds
if (distance <= collisionDistance) {
player.takeDamage(self.damage);
LK.getSound('playerHit').play();
self.removeFromGame();
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < bossBullets.length; i++) {
if (bossBullets[i] === self) {
bossBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var BulletUpgrade = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFD700,
scaleX: 1.5,
scaleY: 1.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.update = function () {
// Never expire - permanent upgrade
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// Permanently increase fire ball bullet count (maximum 3)
if (fireBallBulletCount < 3) {
fireBallBulletCount++;
}
if (musicEnabled) LK.getSound('coinCollect').play();
// Remove from powerUps array
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
// Calculate coin value - first 5 levels = 10, then +5 every 5 levels
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
self.value = 10; // Levels 1-5
} else {
self.value = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
self.creationTime = LK.ticks; // Track when coin was created
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this coin came from a green enemy
// Start continuous Y-axis rotation
self.startRotation = function () {
tween(graphics, {
scaleX: -1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.expirationStarted) {
self.startRotation();
}
}
});
}
});
};
self.startRotation();
self.update = function () {
// Check for expiration after 30 seconds (30 * 60 = 1800 ticks at 60 FPS)
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
// Start fade out animation over 1 second
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
// Remove from coins array
for (var i = 0; i < coins.length; i++) {
if (coins[i] === self) {
coins.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
// Check if coin is within player's attack range for magnetism
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4; // Tight collision bounds for collection
// Only collect on precise physical contact using distance calculation
if (distance <= collectionDistance) {
var coinValue = self.value;
if (wealthActive) {
coinValue *= 2; // Double value during wealth effect
}
playerCoins += coinValue;
coinText.setText(formatCurrency(playerCoins));
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
// Create floating value text
var displayValue = wealthActive ? self.value * 2 : self.value;
var valueText = new Text2('+' + displayValue, {
size: 45,
fill: 0x00ff00
});
valueText.anchor.set(0.5, 0.5);
valueText.x = self.x;
valueText.y = self.y;
game.addChild(valueText);
// Animate the value text floating up and fading out
tween(valueText, {
y: valueText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
valueText.destroy();
}
});
// Remove from coins array
for (var i = 0; i < coins.length; i++) {
if (coins[i] === self) {
coins.splice(i, 1);
break;
}
}
self.destroy();
} else if (magnetActive || distance <= player.attackRange && distance > collectionDistance) {
// Coin is within magnet range (entire scene) or attack range but not collected yet - apply magnetism
if (!self.magnetismActive) {
self.magnetismActive = true;
// Stop any existing movement tweens
tween.stop(self, {
x: true,
y: true
});
}
// Calculate target position with much stronger pull when magnet is active
var pullStrength = magnetActive ? 0.25 : 0.04; // Much stronger pull with magnet
var targetX = self.x + dx * pullStrength;
var targetY = self.y + dy * pullStrength;
// Smoothly move toward player using tween
tween(self, {
x: targetX,
y: targetY
}, {
duration: magnetActive ? 100 : 400,
easing: tween.easeOut
});
} else if (!magnetActive && distance > player.attackRange) {
// Coin is outside attack range and magnet is not active - stop magnetism
if (self.magnetismActive) {
self.magnetismActive = false;
tween.stop(self, {
x: true,
y: true
});
}
}
}
};
return self;
});
var Diamond = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('diamond', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 10;
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this diamond came from a green enemy
// Start continuous Y-axis rotation
self.startRotation = function () {
tween(graphics, {
scaleX: -1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.expirationStarted) {
self.startRotation();
}
}
});
}
});
};
self.startRotation();
self.update = function () {
// Check for expiration after 45 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 2700) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < diamonds.length; i++) {
if (diamonds[i] === self) {
diamonds.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
var diamondValue = self.value;
if (wealthActive) {
diamondValue *= 2; // Double value during wealth effect
}
playerCoins += diamondValue;
coinText.setText(formatCurrency(playerCoins));
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
// Create floating value text
var displayValue = wealthActive ? self.value * 2 : self.value;
var valueText = new Text2('+' + displayValue, {
size: 45,
fill: 0x00ff00
});
valueText.anchor.set(0.5, 0.5);
valueText.x = self.x;
valueText.y = self.y;
game.addChild(valueText);
// Animate the value text floating up and fading out
tween(valueText, {
y: valueText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
valueText.destroy();
}
});
for (var i = 0; i < diamonds.length; i++) {
if (diamonds[i] === self) {
diamonds.splice(i, 1);
break;
}
}
self.destroy();
} else if (magnetActive || distance <= player.attackRange && distance > collectionDistance) {
if (!self.magnetismActive) {
self.magnetismActive = true;
tween.stop(self, {
x: true,
y: true
});
}
var pullStrength = magnetActive ? 0.25 : 0.04;
var targetX = self.x + dx * pullStrength;
var targetY = self.y + dy * pullStrength;
tween(self, {
x: targetX,
y: targetY
}, {
duration: magnetActive ? 100 : 400,
easing: tween.easeOut
});
} else if (!magnetActive && distance > player.attackRange) {
if (self.magnetismActive) {
self.magnetismActive = false;
tween.stop(self, {
x: true,
y: true
});
}
}
}
};
return self;
});
var Duplicate = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('duplicate', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this duplicate came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// Activate duplicate effect
if (duplicateActive) {
duplicateEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate duplicate effect for first time
duplicateActive = true;
duplicateEndTime = LK.ticks + 1800; // 30 seconds
// Create the clone
createPlayerClone();
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var Ember = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('ember', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF8C00 // Orange color for ember
});
// Calculate ember value - 10x current coin value
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
self.value = 100; // 10x level 1-5 coin value (10 * 10)
} else {
self.value = (10 + levelGroup * 5) * 10; // 10x current coin value
}
self.creationTime = LK.ticks;
self.expirationStarted = false;
// Start continuous Y-axis rotation with orange glow effect
self.startRotation = function () {
tween(graphics, {
scaleX: -1.2,
tint: 0xFFD700 // Gold glow effect
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1.2,
tint: 0xFF8C00 // Back to orange
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.expirationStarted) {
self.startRotation();
}
}
});
}
});
};
self.startRotation();
self.update = function () {
// Check for expiration after 45 seconds (like diamonds)
if (!self.expirationStarted && LK.ticks - self.creationTime >= 2700) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < embers.length; i++) {
if (embers[i] === self) {
embers.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
var emberValue = self.value;
if (wealthActive) {
emberValue *= 2; // Double value during wealth effect
}
playerCoins += emberValue;
coinText.setText(formatCurrency(playerCoins));
LK.getSound('coinCollect').play();
// Create floating value text with orange color
var displayValue = wealthActive ? self.value * 2 : self.value;
var valueText = new Text2('+' + displayValue + ' EMBER', {
size: 50,
fill: 0xFF8C00
});
valueText.anchor.set(0.5, 0.5);
valueText.x = self.x;
valueText.y = self.y;
game.addChild(valueText);
// Animate the value text floating up and fading out
tween(valueText, {
y: valueText.y - 100,
alpha: 0
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
valueText.destroy();
}
});
// Remove from embers array
for (var i = 0; i < embers.length; i++) {
if (embers[i] === self) {
embers.splice(i, 1);
break;
}
}
self.destroy();
} else if (magnetActive || distance <= player.attackRange && distance > collectionDistance) {
if (!self.magnetismActive) {
self.magnetismActive = true;
tween.stop(self, {
x: true,
y: true
});
}
var pullStrength = magnetActive ? 0.25 : 0.04;
var targetX = self.x + dx * pullStrength;
var targetY = self.y + dy * pullStrength;
tween(self, {
x: targetX,
y: targetY
}, {
duration: magnetActive ? 100 : 400,
easing: tween.easeOut
});
} else if (!magnetActive && distance > player.attackRange) {
if (self.magnetismActive) {
self.magnetismActive = false;
tween.stop(self, {
x: true,
y: true
});
}
}
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHealth = 40 + (currentLevel - 1) * 8;
self.health = self.maxHealth;
self.speed = 1.5 + (currentLevel - 1) * 0.15;
self.damage = 10 * (1 + (currentLevel - 1) * 0.1);
self.lastAttack = 0;
self.attackCooldown = 120;
// Create enemy health bar background (simple rectangle)
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBg.y = -70;
self.addChild(self.healthBarBg);
// Create enemy health bar fill (simple rectangle)
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5
});
self.healthBarFill.x = -59;
self.healthBarFill.y = -70;
self.addChild(self.healthBarFill);
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
// Apply blue tint when frozen
if (graphics.tint !== 0x4444ff) {
graphics.tint = 0x4444ff;
}
} else {
// Remove blue tint when not frozen
if (graphics.tint !== 0xffffff) {
graphics.tint = 0xffffff;
}
}
// Move towards player with optimized collision avoidance
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Calculate base movement direction toward player
var targetAngle = Math.atan2(dy, dx);
var baseMovementX = Math.cos(targetAngle) * effectiveSpeed;
var baseMovementY = Math.sin(targetAngle) * effectiveSpeed;
// Only check collision avoidance every 5 frames to reduce CPU load
if (LK.ticks % 5 === 0) {
// Calculate avoidance vector
var avoidanceX = 0;
var avoidanceY = 0;
var nearbyEnemyCount = 0;
var maxChecks = Math.min(5, enemies.length); // Limit checks to 5 nearest enemies
// Check collision with limited number of other enemies
for (var e = 0; e < maxChecks && e < enemies.length; e++) {
var otherEnemy = enemies[e];
if (otherEnemy !== self) {
var otherDx = self.x - otherEnemy.x;
var otherDy = self.y - otherEnemy.y;
// Use faster distance check (no sqrt for initial filtering)
var otherDistanceSq = otherDx * otherDx + otherDy * otherDy;
if (otherDistanceSq < 10000 && otherDistanceSq > 0) {
// 100^2 = 10000
var otherDistance = Math.sqrt(otherDistanceSq);
// Calculate avoidance force that gets stronger as enemies get closer
var avoidanceStrength = (100 - otherDistance) / 100;
avoidanceX += otherDx / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
avoidanceY += otherDy / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
nearbyEnemyCount++;
}
}
}
// Check collision with limited number of green enemies
var maxGreenChecks = Math.min(3, greenEnemies.length); // Limit green enemy checks
for (var g = 0; g < maxGreenChecks && g < greenEnemies.length; g++) {
var otherGreen = greenEnemies[g];
if (otherGreen !== self) {
var otherDx = self.x - otherGreen.x;
var otherDy = self.y - otherGreen.y;
var otherDistanceSq = otherDx * otherDx + otherDy * otherDy;
if (otherDistanceSq < 10000 && otherDistanceSq > 0) {
var otherDistance = Math.sqrt(otherDistanceSq);
var avoidanceStrength = (100 - otherDistance) / 100;
avoidanceX += otherDx / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
avoidanceY += otherDy / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
nearbyEnemyCount++;
}
}
}
// Store avoidance for next few frames
self.cachedAvoidanceX = avoidanceX;
self.cachedAvoidanceY = avoidanceY;
self.cachedNearbyCount = nearbyEnemyCount;
}
// Combine movement and avoidance forces
var finalMovementX = baseMovementX;
var finalMovementY = baseMovementY;
if (self.cachedNearbyCount > 0) {
// Add cached avoidance when enemies are nearby
finalMovementX += (self.cachedAvoidanceX || 0) * 0.8; // Strong avoidance
finalMovementY += (self.cachedAvoidanceY || 0) * 0.8;
// Normalize the final movement to maintain consistent speed
var finalDistance = Math.sqrt(finalMovementX * finalMovementX + finalMovementY * finalMovementY);
if (finalDistance > effectiveSpeed) {
finalMovementX = finalMovementX / finalDistance * effectiveSpeed;
finalMovementY = finalMovementY / finalDistance * effectiveSpeed;
}
}
// Apply final movement
self.x += finalMovementX;
self.y += finalMovementY;
}
// Attack player on physical contact only
self.lastAttack++;
if (self.lastAttack >= self.attackCooldown) {
// Only damage on direct collision using precise distance calculation
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (player.width + self.width) / 8; // Much tighter collision bounds
if (distance <= collisionDistance) {
player.takeDamage(self.damage);
LK.getSound('playerHit').play();
self.lastAttack = 0;
// Push back enemy by a small amount (larger if Iron Body is active)
var pushDistance = ironBodyActive ? 135 : 45; // 3x stronger pushback with Iron Body
var pushbackX = self.x - dx / distance * pushDistance;
var pushbackY = self.y - dy / distance * pushDistance;
// Use tween to animate the pushback
tween(self, {
x: pushbackX,
y: pushbackY
}, {
duration: 200,
easing: tween.easeOut
});
}
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
// Update enemy health bar
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 118 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Prevent multiple calls to die() for the same enemy
if (self.isDead) {
return;
}
self.isDead = true;
// Store position for item drops
var deathX = self.x;
var deathY = self.y;
// Enemy death animation - shrinking puff effect
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create soft puff particles after shrinking
for (var e = 0; e < 4; e++) {
var puffCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 20 + e * 10,
height: 20 + e * 10,
alpha: 0.4 - e * 0.08,
tint: e === 0 ? 0xFFFFFF : e === 1 ? 0xF8F8F8 : e === 2 ? 0xF0F0F0 : 0xE8E8E8
});
puffCircle.x = deathX + (Math.random() - 0.5) * 30;
puffCircle.y = deathY + (Math.random() - 0.5) * 30;
game.addChild(puffCircle);
// Animate puff with gentle expansion and fade
tween(puffCircle, {
scaleX: 2.2 + e * 0.3,
scaleY: 2.2 + e * 0.3,
alpha: 0,
y: puffCircle.y - 20 - e * 8
}, {
duration: 500 + e * 120,
easing: tween.easeOut,
onFinish: function onFinish() {
puffCircle.destroy();
}
});
}
// Drop items immediately after puff effect starts
// Calculate coin value - first 5 levels = 10, then +5 every 5 levels
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
// Drop coin with doubled value every 10 levels
var coin = new Coin();
coin.value = coinValue;
coin.x = deathX;
coin.y = deathY;
coins.push(coin);
game.addChild(coin);
// Drop ember (special orange coin) - only starts from level 20
if (currentLevel > 20) {
var emberChance = 0.02; // 2% base chance after level 20
if (currentLevel >= 50) {
emberChance = 0.15; // 15% chance at level 50+
} else if (currentLevel >= 40) {
emberChance = 0.10; // 10% chance at level 40-49
}
if (Math.random() < emberChance) {
var ember = new Ember();
ember.x = deathX + (Math.random() - 0.5) * 60;
ember.y = deathY + (Math.random() - 0.5) * 60;
embers.push(ember);
game.addChild(ember);
}
}
// Drop diamond (5% chance, 30% during wealth effect)
var diamondChance = wealthActive ? 0.30 : 0.05;
if (Math.random() < diamondChance) {
var diamond = new Diamond();
diamond.value = coinValue * 3; // 3x current coin value
diamond.x = deathX + (Math.random() - 0.5) * 60;
diamond.y = deathY + (Math.random() - 0.5) * 60;
diamonds.push(diamond);
game.addChild(diamond);
}
// Rarely drop health pack (5% chance)
if (Math.random() < 0.05) {
var healthPack = new HealthPack();
healthPack.x = deathX + (Math.random() - 0.5) * 60;
healthPack.y = deathY + (Math.random() - 0.5) * 60;
healthPacks.push(healthPack);
game.addChild(healthPack);
}
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
enemiesKilled++;
remainingEnemies--;
}
});
};
return self;
});
var FreezeBomb = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('freezeBomb', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this freeze bomb came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If freeze effect is already active, only refresh the timer
if (freezeEffectActive) {
freezeEffectEndTime = LK.ticks + 1200; // 20 seconds
} else {
// Activate freeze effect for first time
freezeEffectActive = true;
freezeEffectEndTime = LK.ticks + 1200; // 20 seconds
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var GreenEnemy = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('greenEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHealth = 40 + (currentLevel - 1) * 8; // Same health as regular enemies
self.health = self.maxHealth;
self.speed = 1.5 + (currentLevel - 1) * 0.15; // Same speed as regular enemies
self.damage = 10 * (1 + (currentLevel - 1) * 0.1); // Same damage as regular enemies
self.lastAttack = 0;
self.attackCooldown = 120;
// Create enemy health bar background
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBg.y = -70;
self.addChild(self.healthBarBg);
// Create enemy health bar fill
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5
});
self.healthBarFill.x = -59;
self.healthBarFill.y = -70;
self.addChild(self.healthBarFill);
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
// Apply blue tint when frozen
if (graphics.tint !== 0x4444ff) {
graphics.tint = 0x4444ff;
}
} else {
// Remove blue tint when not frozen (return to green color)
if (graphics.tint !== 0x00ff00) {
graphics.tint = 0x00ff00;
}
}
// Move towards player with optimized collision avoidance
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Calculate base movement direction toward player
var targetAngle = Math.atan2(dy, dx);
var baseMovementX = Math.cos(targetAngle) * effectiveSpeed;
var baseMovementY = Math.sin(targetAngle) * effectiveSpeed;
// Only check collision avoidance every 5 frames to reduce CPU load
if (LK.ticks % 5 === 0) {
// Calculate avoidance vector
var avoidanceX = 0;
var avoidanceY = 0;
var nearbyEnemyCount = 0;
var maxChecks = Math.min(5, enemies.length); // Limit checks to 5 nearest enemies
// Check collision with limited number of regular enemies
for (var e = 0; e < maxChecks && e < enemies.length; e++) {
var otherEnemy = enemies[e];
var otherDx = self.x - otherEnemy.x;
var otherDy = self.y - otherEnemy.y;
// Use faster distance check (no sqrt for initial filtering)
var otherDistanceSq = otherDx * otherDx + otherDy * otherDy;
if (otherDistanceSq < 10000 && otherDistanceSq > 0) {
// 100^2 = 10000
var otherDistance = Math.sqrt(otherDistanceSq);
// Calculate avoidance force that gets stronger as enemies get closer
var avoidanceStrength = (100 - otherDistance) / 100;
avoidanceX += otherDx / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
avoidanceY += otherDy / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
nearbyEnemyCount++;
}
}
// Check collision with limited number of other green enemies
var maxGreenChecks = Math.min(3, greenEnemies.length); // Limit green enemy checks
for (var g = 0; g < maxGreenChecks && g < greenEnemies.length; g++) {
var otherGreen = greenEnemies[g];
if (otherGreen !== self) {
var otherDx = self.x - otherGreen.x;
var otherDy = self.y - otherGreen.y;
var otherDistanceSq = otherDx * otherDx + otherDy * otherDy;
if (otherDistanceSq < 10000 && otherDistanceSq > 0) {
var otherDistance = Math.sqrt(otherDistanceSq);
var avoidanceStrength = (100 - otherDistance) / 100;
avoidanceX += otherDx / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
avoidanceY += otherDy / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
nearbyEnemyCount++;
}
}
}
// Store avoidance for next few frames
self.cachedAvoidanceX = avoidanceX;
self.cachedAvoidanceY = avoidanceY;
self.cachedNearbyCount = nearbyEnemyCount;
}
// Combine movement and avoidance forces
var finalMovementX = baseMovementX;
var finalMovementY = baseMovementY;
if (self.cachedNearbyCount > 0) {
// Add cached avoidance when enemies are nearby
finalMovementX += (self.cachedAvoidanceX || 0) * 0.8; // Strong avoidance
finalMovementY += (self.cachedAvoidanceY || 0) * 0.8;
// Normalize the final movement to maintain consistent speed
var finalDistance = Math.sqrt(finalMovementX * finalMovementX + finalMovementY * finalMovementY);
if (finalDistance > effectiveSpeed) {
finalMovementX = finalMovementX / finalDistance * effectiveSpeed;
finalMovementY = finalMovementY / finalDistance * effectiveSpeed;
}
}
// Apply final movement
self.x += finalMovementX;
self.y += finalMovementY;
}
// Attack player on physical contact
self.lastAttack++;
if (self.lastAttack >= self.attackCooldown) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (player.width + self.width) / 8;
if (distance <= collisionDistance) {
player.takeDamage(self.damage);
LK.getSound('playerHit').play();
self.lastAttack = 0;
var pushDistance = ironBodyActive ? 135 : 45; // 3x stronger pushback with Iron Body
var pushbackX = self.x - dx / distance * pushDistance;
var pushbackY = self.y - dy / distance * pushDistance;
tween(self, {
x: pushbackX,
y: pushbackY
}, {
duration: 200,
easing: tween.easeOut
});
}
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 118 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Prevent multiple calls to die() for the same enemy
if (self.isDead) {
return;
}
self.isDead = true;
var deathX = self.x;
var deathY = self.y;
// Death animation
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Puff effect
for (var e = 0; e < 4; e++) {
var puffCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 20 + e * 10,
height: 20 + e * 10,
alpha: 0.4 - e * 0.08,
tint: e === 0 ? 0x00FF00 : e === 1 ? 0x44FF44 : e === 2 ? 0x88FF88 : 0xAAFFAA
});
puffCircle.x = deathX + (Math.random() - 0.5) * 30;
puffCircle.y = deathY + (Math.random() - 0.5) * 30;
game.addChild(puffCircle);
tween(puffCircle, {
scaleX: 2.2 + e * 0.3,
scaleY: 2.2 + e * 0.3,
alpha: 0,
y: puffCircle.y - 20 - e * 8
}, {
duration: 500 + e * 120,
easing: tween.easeOut,
onFinish: function onFinish() {
puffCircle.destroy();
}
});
}
// Drop ember (special orange coin) - only starts from level 20
if (currentLevel > 20) {
var emberChance = 0.02; // 2% base chance after level 20
if (currentLevel >= 50) {
emberChance = 0.15; // 15% chance at level 50+
} else if (currentLevel >= 40) {
emberChance = 0.10; // 10% chance at level 40-49
}
if (Math.random() < emberChance) {
var ember = new Ember();
ember.x = deathX + (Math.random() - 0.5) * 60;
ember.y = deathY + (Math.random() - 0.5) * 60;
embers.push(ember);
game.addChild(ember);
}
}
// Drop diamond (5% chance, 30% during wealth effect)
var diamondChance = wealthActive ? 0.30 : 0.05;
if (Math.random() < diamondChance) {
// Calculate coin value - first 5 levels = 10, then +5 every 5 levels
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
var diamond = new Diamond();
diamond.value = coinValue * 3; // 3x current coin value
diamond.fromGreenEnemy = true; // Mark as from green enemy
diamond.x = deathX + (Math.random() - 0.5) * 60;
diamond.y = deathY + (Math.random() - 0.5) * 60;
diamonds.push(diamond);
game.addChild(diamond);
}
// 100% chance to drop power-up with equal chances for all items
if (Math.random() < 1.0) {
var powerUpTypes = [Magnet, Speed, Wealth, IronBody, HealingNeedle, MachineGun, RocketLauncher, FreezeBomb, Duplicate];
// Use equal distribution by ensuring each item has exactly the same chance
var randomValue = Math.random();
var itemChance = 1.0 / powerUpTypes.length; // Each item gets exactly equal chance
var selectedIndex = Math.floor(randomValue / itemChance);
// Ensure we don't go out of bounds due to floating point precision
selectedIndex = Math.min(selectedIndex, powerUpTypes.length - 1);
var powerUp = new powerUpTypes[selectedIndex]();
powerUp.x = deathX;
powerUp.y = deathY;
// Mark ALL items as from green enemy
powerUp.fromGreenEnemy = true;
powerUps.push(powerUp);
game.addChild(powerUp);
}
// Remove from greenEnemies array
for (var i = 0; i < greenEnemies.length; i++) {
if (greenEnemies[i] === self) {
greenEnemies.splice(i, 1);
break;
}
}
self.destroy();
enemiesKilled++;
remainingEnemies--;
}
});
};
return self;
});
var HealingNeedle = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('healingNeedle', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this healing needle came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If healing needle is already active, only refresh the timer
if (healingNeedleActive) {
healingNeedleEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate healing needle effect for first time
healingNeedleActive = true;
healingNeedleEndTime = LK.ticks + 1800; // 30 seconds
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var HealingNeedleBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('healingNeedleBullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply special healing needle visual effect
graphics.tint = 0x00FF88; // Bright green healing color
graphics.scaleX = 1.2;
graphics.scaleY = 1.2;
// Add pulsing glow effect
tween(graphics, {
scaleX: 1.4,
scaleY: 1.4,
tint: 0xFFD700
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0x00FF88
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
self.velocityX = 0;
self.velocityY = 0;
self.damage = 20;
// Set rotation method for bullet orientation
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
enemy.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting enemies
if (player) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = enemy.x;
healText.y = enemy.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with green enemies
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
if (self.intersects(greenEnemy)) {
greenEnemy.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting green enemies
if (player) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = greenEnemy.x;
healText.y = greenEnemy.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with boss
if (currentBoss && self.intersects(currentBoss)) {
currentBoss.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting boss
if (player && currentBoss) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = currentBoss.x;
healText.y = currentBoss.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
}
};
self.removeFromGame = function () {
for (var i = 0; i < playerBullets.length; i++) {
if (playerBullets[i] === self) {
playerBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var HealthPack = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('healthPack', {
anchorX: 0.5,
anchorY: 0.5
});
self.healAmount = 0.20; // 20% of max health
self.creationTime = LK.ticks; // Track when health pack was created
self.expirationStarted = false;
// Start continuous Y-axis rotation
self.startRotation = function () {
tween(graphics, {
scaleX: -1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.expirationStarted) {
self.startRotation();
}
}
});
}
});
};
self.startRotation();
self.update = function () {
// Check for expiration after 1 minute (60 * 60 = 3600 ticks at 60 FPS)
if (!self.expirationStarted && LK.ticks - self.creationTime >= 3600) {
self.expirationStarted = true;
// Start fade out animation over 1 second
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
// Remove from healthPacks array
for (var i = 0; i < healthPacks.length; i++) {
if (healthPacks[i] === self) {
healthPacks.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player && player.health < player.maxHealth) {
// Only collect on precise physical contact using distance calculation
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4; // Tight collision bounds for collection
// Apply magnet effect if active and player is not at full health (affects entire scene)
if (magnetActive && distance > collectionDistance) {
if (!self.magnetismActive) {
self.magnetismActive = true;
tween.stop(self, {
x: true,
y: true
});
}
var pullStrength = 0.25;
var targetX = self.x + dx * pullStrength;
var targetY = self.y + dy * pullStrength;
tween(self, {
x: targetX,
y: targetY
}, {
duration: 100,
easing: tween.easeOut
});
} else if (!magnetActive) {
if (self.magnetismActive) {
self.magnetismActive = false;
tween.stop(self, {
x: true,
y: true
});
}
}
if (distance <= collectionDistance) {
var healValue = Math.floor(player.maxHealth * self.healAmount);
player.health = Math.min(player.maxHealth, player.health + healValue);
LK.getSound('coinCollect').play();
// Remove from healthPacks array
for (var i = 0; i < healthPacks.length; i++) {
if (healthPacks[i] === self) {
healthPacks.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var IronBody = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('ironBody', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this iron body came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If iron body is already active, only refresh the timer
if (ironBodyActive) {
ironBodyEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate iron body effect for first time
ironBodyActive = true;
ironBodyEndTime = LK.ticks + 1800; // 30 seconds
// Change player appearance to iron body version
if (player) {
var currentPlayerGraphics = player.children[0]; // First child is the player graphics
currentPlayerGraphics.visible = false; // Hide original player
// Create iron body player graphics if it doesn't exist
if (!player.ironBodyGraphics) {
player.ironBodyGraphics = player.attachAsset('ironBodyPlayer', {
anchorX: 0.5,
anchorY: 0.5
});
}
player.ironBodyGraphics.visible = true; // Show iron body appearance
}
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
// Note: Game title "Fight Bacteria" should be set at the engine/platform level
// The LK engine handles game metadata automatically
var Level30Boss = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('level30Boss', {
anchorX: 0.5,
anchorY: 0.5
});
// Calculate damage from previous level enemies (level 29)
var previousLevel = Math.max(1, currentLevel - 1);
var previousLevelDamage = 10 * (1 + (previousLevel - 1) * 0.1);
self.damage = previousLevelDamage;
// Health is 3x the damage value
self.maxHealth = self.damage * 3;
self.health = self.maxHealth;
self.speed = 0.8 + Math.max(0, currentLevel - 10) * 0.1;
self.lastAttack = 0;
self.attackCooldown = 60; // 1 second at 60 FPS
self.lastClone = 0;
self.cloneCooldown = 3600; // 60 seconds at 60 FPS
self.hasCloned = false;
// Create boss health bar background
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 30
});
self.healthBarBg.y = -80;
self.addChild(self.healthBarBg);
// Create boss health bar fill
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5,
width: 196,
height: 26
});
self.healthBarFill.x = -98;
self.healthBarFill.y = -80;
self.addChild(self.healthBarFill);
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
if (graphics.tint !== 0x4444ff) {
graphics.tint = 0x4444ff;
}
} else {
if (graphics.tint !== 0xffffff) {
graphics.tint = 0xffffff;
}
}
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * effectiveSpeed;
self.y += dy / distance * effectiveSpeed;
}
// Shoot bullets at player
self.lastAttack++;
if (self.lastAttack >= self.attackCooldown) {
var bullet = new Level30BossBullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Boss bullet speed is 50% of player's current bullet speed (which is 8)
var bulletSpeed = 4; // 50% of player bullet speed (8)
var angle = Math.atan2(dy, dx);
bullet.velocityX = Math.cos(angle) * bulletSpeed;
bullet.velocityY = Math.sin(angle) * bulletSpeed;
if (bullet.setRotation) {
bullet.setRotation(bullet.velocityX, bullet.velocityY);
}
}
bullet.bossDamage = self.damage; // Pass boss damage to bullet for health stealing calculation
bossBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.lastAttack = 0;
}
// Clone creation when health drops below 50%
if (self.health <= self.maxHealth / 2 && !self.hasCloned) {
self.lastClone++;
if (self.lastClone >= self.cloneCooldown) {
// Create a clone with 20% of original stats
var clone = new Level30BossClone();
clone.x = self.x + (Math.random() - 0.5) * 200;
clone.y = self.y + (Math.random() - 0.5) * 200;
// Ensure clone spawns within game bounds
clone.x = Math.max(50, Math.min(1998, clone.x));
clone.y = Math.max(50, Math.min(2682, clone.y));
// Set clone stats to 20% of original
clone.maxHealth = Math.floor(self.maxHealth * 0.2);
clone.health = clone.maxHealth;
clone.damage = Math.floor(self.damage * 0.2);
clone.speed = self.speed * 0.2;
game.addChild(clone);
self.hasCloned = true; // Prevent further cloning
self.lastClone = 0;
}
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
// Update boss health bar
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 196 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Play explosion sound
LK.getSound('explosion').play();
// Create massive explosion effect
for (var e = 0; e < 8; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 60 + e * 25,
height: 60 + e * 25,
alpha: 1.0 - e * 0.1,
tint: e === 0 ? 0xFF0000 : e === 1 ? 0xFF2200 : e === 2 ? 0xFF4500 : e === 3 ? 0xFF6600 : e === 4 ? 0xFF8800 : e === 5 ? 0xFFAA00 : e === 6 ? 0xFFCC00 : 0xFFFF00
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 80;
explosionCircle.y = self.y + (Math.random() - 0.5) * 80;
game.addChild(explosionCircle);
tween(explosionCircle, {
scaleX: 5 + e * 0.8,
scaleY: 5 + e * 0.8,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 200,
y: explosionCircle.y + (Math.random() - 0.5) * 200
}, {
duration: 800 + e * 150,
easing: e % 3 === 0 ? tween.easeOut : e % 3 === 1 ? tween.easeInOut : tween.bounceOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Drop rewards (same as other bosses)
var numCoins = 10 + Math.floor(Math.random() * 11);
for (var i = 0; i < numCoins; i++) {
var coin = new Coin();
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
coin.value = 10;
} else {
coin.value = 10 + levelGroup * 5;
}
coin.x = self.x + (Math.random() - 0.5) * 150;
coin.y = self.y + (Math.random() - 0.5) * 150;
coins.push(coin);
game.addChild(coin);
}
var bulletUpgrade = new BulletUpgrade();
bulletUpgrade.x = self.x + (Math.random() - 0.5) * 100;
bulletUpgrade.y = self.y + (Math.random() - 0.5) * 100;
powerUps.push(bulletUpgrade);
game.addChild(bulletUpgrade);
var diamond1 = new Diamond();
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10;
} else {
coinValue = 10 + levelGroup * 5;
}
diamond1.value = coinValue * 5;
diamond1.x = self.x + (Math.random() - 0.5) * 100;
diamond1.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond1);
game.addChild(diamond1);
if (Math.random() < 0.50) {
for (var d = 0; d < 2; d++) {
var diamond = new Diamond();
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10;
} else {
coinValue = 10 + levelGroup * 5;
}
diamond.value = coinValue * 5;
diamond.x = self.x + (Math.random() - 0.5) * 100;
diamond.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond);
game.addChild(diamond);
}
}
// 50% chance for ember drop
if (Math.random() < 0.50) {
var ember = new Ember();
ember.x = self.x + (Math.random() - 0.5) * 100;
ember.y = self.y + (Math.random() - 0.5) * 100;
embers.push(ember);
game.addChild(ember);
}
currentBoss = null;
// Stop boss fight music and start gameplay music
if (musicEnabled) {
LK.stopMusic();
LK.playMusic('3', {
loop: true
});
}
self.destroy();
enemiesKilled = 0;
};
return self;
});
var Level30BossBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('level30BossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.bossDamage = 0; // Will be set by the boss
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with player and steal health
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (player.width + self.width) / 8;
if (distance <= collisionDistance) {
// Deal damage to player
player.takeDamage(self.bossDamage);
LK.getSound('playerHit').play();
// Boss steals health - 3% of damage dealt
if (currentBoss) {
var stolenHealth = Math.floor(self.bossDamage * 0.03);
currentBoss.health = Math.min(currentBoss.maxHealth, currentBoss.health + stolenHealth);
// Update boss health bar
var healthPercent = currentBoss.health / currentBoss.maxHealth;
currentBoss.healthBarFill.width = 196 * healthPercent;
// Create floating heal text for boss
var healText = new Text2('+' + stolenHealth + ' HP', {
size: 40,
fill: 0xff0000
});
healText.anchor.set(0.5, 0.5);
healText.x = currentBoss.x;
healText.y = currentBoss.y;
game.addChild(healText);
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
self.removeFromGame();
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < bossBullets.length; i++) {
if (bossBullets[i] === self) {
bossBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Level30BossClone = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
// Scale down the clone to 0.5x to show it's smaller
graphics.scaleX = 0.5;
graphics.scaleY = 0.5;
graphics.tint = 0x888888; // Gray tint to differentiate from original
self.maxHealth = 1; // Will be overridden by creator
self.health = 1;
self.damage = 1; // Will be overridden by creator
self.speed = 0.1; // Will be overridden by creator
self.lastAttack = 0;
self.attackCooldown = 120; // 2 seconds at 60 FPS (slower than original)
// Create clone health bar background
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
// Smaller health bar
height: 15
});
self.healthBarBg.y = -60;
self.addChild(self.healthBarBg);
// Create clone health bar fill
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5,
width: 96,
height: 11
});
self.healthBarFill.x = -48;
self.healthBarFill.y = -60;
self.addChild(self.healthBarFill);
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
if (graphics.tint !== 0x444488) {
graphics.tint = 0x444488; // Darker blue for frozen clone
}
} else {
if (graphics.tint !== 0x888888) {
graphics.tint = 0x888888; // Gray for normal clone
}
}
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * effectiveSpeed;
self.y += dy / distance * effectiveSpeed;
}
// Shoot bullets at player (slower than original)
self.lastAttack++;
if (self.lastAttack >= self.attackCooldown) {
var bullet = new Level30BossCloneBullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Clone bullet speed is also 20% of player bullet speed
var bulletSpeed = 1.6; // 20% of player bullet speed (8)
var angle = Math.atan2(dy, dx);
bullet.velocityX = Math.cos(angle) * bulletSpeed;
bullet.velocityY = Math.sin(angle) * bulletSpeed;
if (bullet.setRotation) {
bullet.setRotation(bullet.velocityX, bullet.velocityY);
}
}
bullet.bossDamage = self.damage; // Pass clone damage to bullet
bossBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.lastAttack = 0;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
// Update clone health bar
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 96 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Play explosion sound
LK.getSound('explosion').play();
// Small explosion for clone
for (var e = 0; e < 4; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 30 + e * 10,
height: 30 + e * 10,
alpha: 0.8 - e * 0.15,
tint: e === 0 ? 0x888888 : e === 1 ? 0xAAAAAA : e === 2 ? 0xCCCCCC : 0xEEEEEE
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 40;
explosionCircle.y = self.y + (Math.random() - 0.5) * 40;
game.addChild(explosionCircle);
tween(explosionCircle, {
scaleX: 2 + e * 0.3,
scaleY: 2 + e * 0.3,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 80,
y: explosionCircle.y + (Math.random() - 0.5) * 80
}, {
duration: 400 + e * 75,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Drop small coin reward
var coin = new Coin();
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
coin.value = 5; // Half value for clone
} else {
coin.value = Math.floor((10 + levelGroup * 5) / 2); // Half value for clone
}
coin.x = self.x;
coin.y = self.y;
coins.push(coin);
game.addChild(coin);
self.destroy();
};
return self;
});
var Level30BossCloneBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('level30BossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Scale down clone bullet to 0.7x
graphics.scaleX = 0.7;
graphics.scaleY = 0.7;
graphics.tint = 0x888888; // Gray tint like clone
self.velocityX = 0;
self.velocityY = 0;
self.bossDamage = 0; // Will be set by the clone
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with player and steal health (but less than original)
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (player.width + self.width) / 8;
if (distance <= collisionDistance) {
// Deal damage to player
player.takeDamage(self.bossDamage);
LK.getSound('playerHit').play();
// Clone steals less health - 1% of damage dealt (instead of 3%)
if (currentBoss) {
var stolenHealth = Math.max(1, Math.floor(self.bossDamage * 0.01));
currentBoss.health = Math.min(currentBoss.maxHealth, currentBoss.health + stolenHealth);
// Update boss health bar
var healthPercent = currentBoss.health / currentBoss.maxHealth;
currentBoss.healthBarFill.width = 196 * healthPercent;
}
self.removeFromGame();
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < bossBullets.length; i++) {
if (bossBullets[i] === self) {
bossBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var MachineGun = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('machineGun', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this machine gun came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If machine gun is already active, only refresh the timer
if (machineGunActive) {
machineGunEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate machine gun effect for first time
machineGunActive = true;
machineGunEndTime = LK.ticks + 1800; // 30 seconds
}
// Use appropriate sound based on source tracking if it exists
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var MachineGunBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('machineGunBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.damage = 20; // Same as regular bullet damage
// Set rotation method for bullet orientation
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with enemies (limit checks for performance)
var maxEnemyChecks = Math.min(15, enemies.length); // Limit enemy checks for machine gun bullets
for (var i = 0; i < maxEnemyChecks; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
enemy.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting enemies
if (healingNeedleActive && player) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = enemy.x;
healText.y = enemy.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
// Create spark effect for machine gun bullets
for (var s = 0; s < 3; s++) {
var spark = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 8 + s * 4,
height: 8 + s * 4,
alpha: 0.8 - s * 0.2,
tint: s === 0 ? 0xFFFF44 : s === 1 ? 0xFFAA00 : 0xFF6600
});
spark.x = self.x + (Math.random() - 0.5) * 20;
spark.y = self.y + (Math.random() - 0.5) * 20;
game.addChild(spark);
tween(spark, {
scaleX: 1.5 + s * 0.3,
scaleY: 1.5 + s * 0.3,
alpha: 0,
x: spark.x + (Math.random() - 0.5) * 40,
y: spark.y + (Math.random() - 0.5) * 40
}, {
duration: 200 + s * 50,
easing: tween.easeOut,
onFinish: function onFinish() {
spark.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with green enemies (limit checks for performance)
var maxGreenChecks = Math.min(8, greenEnemies.length); // Limit green enemy checks for machine gun bullets
for (var g = 0; g < maxGreenChecks; g++) {
var greenEnemy = greenEnemies[g];
if (self.intersects(greenEnemy)) {
greenEnemy.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting green enemies
if (healingNeedleActive && player) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = greenEnemy.x;
healText.y = greenEnemy.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
// Create spark effect for machine gun bullets
for (var s = 0; s < 3; s++) {
var spark = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 8 + s * 4,
height: 8 + s * 4,
alpha: 0.8 - s * 0.2,
tint: s === 0 ? 0xFFFF44 : s === 1 ? 0xFFAA00 : 0xFF6600
});
spark.x = self.x + (Math.random() - 0.5) * 20;
spark.y = self.y + (Math.random() - 0.5) * 20;
game.addChild(spark);
tween(spark, {
scaleX: 1.5 + s * 0.3,
scaleY: 1.5 + s * 0.3,
alpha: 0,
x: spark.x + (Math.random() - 0.5) * 40,
y: spark.y + (Math.random() - 0.5) * 40
}, {
duration: 200 + s * 50,
easing: tween.easeOut,
onFinish: function onFinish() {
spark.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with boss
if (currentBoss && self.intersects(currentBoss)) {
currentBoss.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting boss
if (healingNeedleActive && player && currentBoss) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = currentBoss.x;
healText.y = currentBoss.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
// Create spark effect for machine gun bullets
for (var s = 0; s < 3; s++) {
var spark = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 8 + s * 4,
height: 8 + s * 4,
alpha: 0.8 - s * 0.2,
tint: s === 0 ? 0xFFFF44 : s === 1 ? 0xFFAA00 : 0xFF6600
});
spark.x = self.x + (Math.random() - 0.5) * 20;
spark.y = self.y + (Math.random() - 0.5) * 20;
game.addChild(spark);
tween(spark, {
scaleX: 1.5 + s * 0.3,
scaleY: 1.5 + s * 0.3,
alpha: 0,
x: spark.x + (Math.random() - 0.5) * 40,
y: spark.y + (Math.random() - 0.5) * 40
}, {
duration: 200 + s * 50,
easing: tween.easeOut,
onFinish: function onFinish() {
spark.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
}
};
self.removeFromGame = function () {
for (var i = 0; i < playerBullets.length; i++) {
if (playerBullets[i] === self) {
playerBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Magnet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('magnet', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this magnet came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If magnet is already active, only refresh the timer
if (magnetActive) {
magnetEndTime = LK.ticks + 3600; // 60 seconds
} else {
// Activate magnet effect for first time
magnetActive = true;
magnetEndTime = LK.ticks + 3600; // 60 seconds
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Add attack range visual
var rangeGraphics = self.attachAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.2,
width: 600,
// 300 radius * 2
height: 600 // 300 radius * 2
});
self.maxHealth = 50;
self.health = self.maxHealth;
self.speed = 2.2;
self.damage = 5;
self.attackSpeed = 103;
self.attackRange = 300; // Base attack range radius
self.lastShot = 0;
self.update = function () {
if (gameState === 'playing') {
// Apply Iron Body visual effect
if (ironBodyActive) {
if (graphics.tint !== 0x808080) {
graphics.tint = 0x808080; // Metallic gray color
}
} else {
if (graphics.tint !== 0xffffff) {
graphics.tint = 0xffffff; // Normal color
}
}
// Handle analog stick movement
if (analogStickEnabled && analogStickActive && (Math.abs(analogStickDirection.x) > 0.1 || Math.abs(analogStickDirection.y) > 0.1)) {
// Move based on analog stick direction
var moveX = analogStickDirection.x * self.speed;
var moveY = analogStickDirection.y * self.speed;
self.x += moveX;
self.y += moveY;
// Keep player within game bounds
self.x = Math.max(50, Math.min(1998, self.x));
self.y = Math.max(50, Math.min(2682, self.y));
// Make player face movement direction
if (moveX > 0) {
graphics.scaleX = -Math.abs(graphics.scaleX); // Face right
} else if (moveX < 0) {
graphics.scaleX = Math.abs(graphics.scaleX); // Face left
}
} else if (touchPosition && !analogStickEnabled && (Math.abs(self.x - touchPosition.x) > 5 || Math.abs(self.y - touchPosition.y) > 5)) {
// Move towards touch position only if analog stick is disabled
var dx = touchPosition.x - self.x;
var dy = touchPosition.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Make player face movement direction
if (dx > 0) {
graphics.scaleX = -Math.abs(graphics.scaleX); // Face right
} else if (dx < 0) {
graphics.scaleX = Math.abs(graphics.scaleX); // Face left
}
}
}
// Auto-shoot when enemies are in range
self.lastShot++;
var target = self.findNearestEnemy();
var effectiveAttackSpeed = self.attackSpeed;
if (machineGunActive) {
effectiveAttackSpeed = Math.floor(self.attackSpeed / 3); // 3x faster
}
if (self.lastShot >= effectiveAttackSpeed && target) {
// Limit machine gun bullets to prevent performance issues
if (machineGunActive && playerBullets.length > 30) {
// Skip shooting if too many bullets exist
self.lastShot = Math.floor(effectiveAttackSpeed * 0.8); // Slight delay
} else {
self.shoot();
self.lastShot = 0;
}
}
}
};
self.shoot = function () {
var target = self.findNearestEnemy();
if (target) {
// Determine bullet count based on weapon type
var bulletsToFire = 1; // Default to 1 bullet for special weapons
if (!rocketLauncherActive && !machineGunActive && !healingNeedleActive) {
// Only use fire ball bullet count for basic fire ball bullets
bulletsToFire = fireBallBulletCount;
}
// Fire multiple bullets based on weapon type
for (var b = 0; b < bulletsToFire; b++) {
var bullet;
// Create appropriate bullet type based on active power-ups
if (rocketLauncherActive) {
bullet = new RocketBullet();
} else if (machineGunActive) {
bullet = new MachineGunBullet();
} else if (healingNeedleActive) {
bullet = new HealingNeedleBullet();
} else {
bullet = new PlayerBullet();
}
// Position bullets with spread for fire ball bullets (basic bullets)
if (!rocketLauncherActive && !machineGunActive && !healingNeedleActive && bulletsToFire > 1) {
// Fire ball bullets - spread them in a line formation
var spreadOffset = (b - (bulletsToFire - 1) / 2) * 40; // 40 pixel spacing between bullets
var perpAngle = Math.atan2(target.y - self.y, target.x - self.x) + Math.PI / 2; // Perpendicular to target direction
bullet.x = self.x + Math.cos(perpAngle) * spreadOffset;
bullet.y = self.y + Math.sin(perpAngle) * spreadOffset;
} else {
bullet.x = self.x;
bullet.y = self.y;
}
var dx = target.x - bullet.x;
var dy = target.y - bullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var spreadAngle = 0;
// Only spread angle for special weapons, not for fire ball bullets
if (bulletsToFire > 1 && (rocketLauncherActive || machineGunActive || healingNeedleActive)) {
spreadAngle = (b - (bulletsToFire - 1) / 2) * 0.2; // Small spread for special weapons only
}
var angle = Math.atan2(dy, dx) + spreadAngle;
bullet.velocityX = Math.cos(angle) * 8;
bullet.velocityY = Math.sin(angle) * 8;
// Set bullet rotation to face movement direction
if (bullet.setRotation) {
bullet.setRotation(bullet.velocityX, bullet.velocityY);
}
playerBullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
}
};
self.findNearestEnemy = function () {
if (currentBoss) {
var dx = currentBoss.x - self.x;
var dy = currentBoss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange) {
return currentBoss;
}
}
var nearest = null;
var nearestDistance = Infinity;
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 <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearest = enemy;
}
}
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
var dx = greenEnemy.x - self.x;
var dy = greenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearest = greenEnemy;
}
}
return nearest;
};
self.takeDamage = function (damage) {
var actualDamage = damage;
// Apply Iron Body damage reduction
if (ironBodyActive) {
actualDamage = Math.floor(damage * 0.1); // 90% reduction
}
self.health -= actualDamage;
if (self.health < 0) {
self.health = 0;
}
// Health bar now handled in GUI
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
// Remove player from scene
self.destroy();
player = null;
// Play explosion sound
LK.getSound('explosion').play();
// Trigger nova explosion when player dies
for (var e = 0; e < 12; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 80 + e * 40,
height: 80 + e * 40,
alpha: 1.0 - e * 0.08,
tint: e % 4 === 0 ? 0xFFFFFF : e % 4 === 1 ? 0xFFFF00 : e % 4 === 2 ? 0xFF8800 : 0xFF0000
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 100;
explosionCircle.y = self.y + (Math.random() - 0.5) * 100;
game.addChild(explosionCircle);
// Animate massive nova explosion
tween(explosionCircle, {
scaleX: 8 + e * 1.2,
scaleY: 8 + e * 1.2,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 300,
y: explosionCircle.y + (Math.random() - 0.5) * 300
}, {
duration: 1500 + e * 200,
easing: e % 3 === 0 ? tween.easeOut : e % 3 === 1 ? tween.easeInOut : tween.bounceOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Save score immediately and show menu after 3 seconds
// Save player score to leaderboard immediately
var playerName = promptPlayerName();
saveScore(playerName, currentLevel);
console.log('Player died at level', currentLevel, 'saving score for', playerName);
LK.setTimeout(function () {
// Create custom game over display with level info
var gameOverContainer = new Container();
var gameOverBg = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 1.0,
scaleX: 12,
scaleY: 6,
x: 0,
y: 0,
tint: 0x000000
});
gameOverContainer.addChild(gameOverBg);
var gameOverText = new Text2('GAME OVER', {
size: 120,
fill: 0xFF0000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = 0;
gameOverText.y = -200;
gameOverContainer.addChild(gameOverText);
var levelReachedText = new Text2('Level Reached: ' + currentLevel, {
size: 80,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
levelReachedText.anchor.set(0.5, 0.5);
levelReachedText.x = 0;
levelReachedText.y = -350;
gameOverContainer.addChild(levelReachedText);
var scoreEntryText = new Text2('Score saved as: ' + playerName, {
size: 60,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
scoreEntryText.anchor.set(0.5, 0.5);
scoreEntryText.x = 0;
scoreEntryText.y = -100;
gameOverContainer.addChild(scoreEntryText);
gameOverContainer.x = 1024;
gameOverContainer.y = 2732;
game.addChild(gameOverContainer);
// Return to main menu after displaying score info
LK.setTimeout(function () {
gameOverContainer.destroy();
showMainMenu();
}, 3000);
}, 3000);
}
};
return self;
});
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
// Set rotation method for bullet orientation
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.damage = 20;
self.update = function () {
// Find nearest target for homing behavior to guarantee hits
var nearestTarget = null;
var nearestDistance = Infinity;
var targetX = self.x + self.velocityX;
var targetY = self.y + self.velocityY;
// Check for nearest enemy
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = enemy;
}
}
// Check for nearest green enemy
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
var dx = greenEnemy.x - self.x;
var dy = greenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = greenEnemy;
}
}
// Check for boss
if (currentBoss) {
var dx = currentBoss.x - self.x;
var dy = currentBoss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = currentBoss;
}
}
// Apply homing behavior if target exists and is within reasonable range
if (nearestTarget && nearestDistance < 800) {
var dx = nearestTarget.x - self.x;
var dy = nearestTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Calculate homing strength - stronger when closer to target
var homingStrength = Math.min(0.3, 100 / distance);
// Normalize target direction
var targetVelX = dx / distance * 8;
var targetVelY = dy / distance * 8;
// Interpolate between current velocity and target velocity
self.velocityX = self.velocityX * (1 - homingStrength) + targetVelX * homingStrength;
self.velocityY = self.velocityY * (1 - homingStrength) + targetVelY * homingStrength;
}
}
self.x += self.velocityX;
self.y += self.velocityY;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with enemies using precise distance calculation - check ALL enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Use precise collision detection instead of intersects
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (enemy.width + self.width) / 8; // Tight collision bounds
if (distance <= collisionDistance) {
enemy.takeDamage(self.damage + (player ? player.damage : 0));
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with green enemies using precise distance calculation - check ALL green enemies
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
// Use precise collision detection instead of intersects
var dx = greenEnemy.x - self.x;
var dy = greenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (greenEnemy.width + self.width) / 8; // Tight collision bounds
if (distance <= collisionDistance) {
greenEnemy.takeDamage(self.damage + (player ? player.damage : 0));
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with boss using precise distance calculation
if (currentBoss) {
var dx = currentBoss.x - self.x;
var dy = currentBoss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (currentBoss.width + self.width) / 8; // Tight collision bounds
if (distance <= collisionDistance) {
currentBoss.takeDamage(self.damage + (player ? player.damage : 0));
LK.getSound('enemyHit').play();
self.removeFromGame();
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < playerBullets.length; i++) {
if (playerBullets[i] === self) {
playerBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var PlayerClone = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xcccccc // Gray tint to differentiate from original
});
// Copy all stats from the original player
if (player) {
self.maxHealth = player.maxHealth;
self.health = self.maxHealth;
self.speed = player.speed;
self.damage = player.damage;
self.attackSpeed = player.attackSpeed;
self.attackRange = player.attackRange;
}
self.lastShot = 0;
self.update = function () {
if (gameState === 'playing' && duplicateActive) {
// Continuously move around and attack enemies aggressively
var target = self.findNearestEnemy();
if (target) {
// Move towards target aggressively
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Move at full speed towards enemies
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Make clone face movement direction
if (dx > 0) {
graphics.scaleX = -Math.abs(graphics.scaleX); // Face right
} else if (dx < 0) {
graphics.scaleX = Math.abs(graphics.scaleX); // Face left
}
}
} else {
// If no target in attack range, patrol around the map to find enemies
// Move in a circular patrol pattern to cover more area
var patrolRadius = 300;
var patrolSpeed = self.speed * 0.7; // Slightly slower patrol speed
var centerX = 1024; // Center of map
var centerY = 1366;
// Create patrol movement based on game time
var patrolAngle = LK.ticks * 0.02 % (Math.PI * 2);
var targetX = centerX + Math.cos(patrolAngle) * patrolRadius;
var targetY = centerY + Math.sin(patrolAngle) * patrolRadius;
// Move towards patrol target
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
self.x += dx / distance * patrolSpeed;
self.y += dy / distance * patrolSpeed;
// Make clone face movement direction during patrol
if (dx > 0) {
graphics.scaleX = -Math.abs(graphics.scaleX); // Face right
} else if (dx < 0) {
graphics.scaleX = Math.abs(graphics.scaleX); // Face left
}
}
}
// Auto-shoot when enemies are in range
self.lastShot++;
if (self.lastShot >= self.attackSpeed) {
var shootTarget = self.findNearestEnemy();
if (shootTarget) {
self.shoot();
self.lastShot = 0;
}
}
}
};
self.shoot = function () {
var target = self.findNearestEnemy();
if (target) {
var bullet = new PlayerBullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
bullet.velocityX = Math.cos(angle) * 8;
bullet.velocityY = Math.sin(angle) * 8;
if (bullet.setRotation) {
bullet.setRotation(bullet.velocityX, bullet.velocityY);
}
playerBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
}
};
self.findNearestEnemy = function () {
if (currentBoss) {
var dx = currentBoss.x - self.x;
var dy = currentBoss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange) {
return currentBoss;
}
}
var nearest = null;
var nearestDistance = Infinity;
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 <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearest = enemy;
}
}
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
var dx = greenEnemy.x - self.x;
var dy = greenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearest = greenEnemy;
}
}
return nearest;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Clone died before timer expired, respawn it
if (duplicateActive) {
// Small death effect
for (var e = 0; e < 4; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 30 + e * 10,
height: 30 + e * 10,
alpha: 0.6 - e * 0.1,
tint: 0xcccccc
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 40;
explosionCircle.y = self.y + (Math.random() - 0.5) * 40;
game.addChild(explosionCircle);
tween(explosionCircle, {
scaleX: 2 + e * 0.3,
scaleY: 2 + e * 0.3,
alpha: 0
}, {
duration: 400 + e * 75,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
self.destroy();
// Respawn clone after short delay
LK.setTimeout(function () {
if (duplicateActive) {
createPlayerClone();
}
}, 1000);
}
};
self.explode = function () {
// Play explosion sound
LK.getSound('explosion').play();
// Create massive explosion effect
for (var e = 0; e < 8; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 120 + e * 50,
height: 120 + e * 50,
alpha: 1.0 - e * 0.1,
tint: e === 0 ? 0xFFFFFF : e === 1 ? 0xCCCCCC : e === 2 ? 0x999999 : e === 3 ? 0x666666 : e === 4 ? 0x888888 : e === 5 ? 0xAAAAAA : e === 6 ? 0xDDDDDD : 0xEEEEEE
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 160;
explosionCircle.y = self.y + (Math.random() - 0.5) * 160;
game.addChild(explosionCircle);
tween(explosionCircle, {
scaleX: 10 + e * 1.6,
scaleY: 10 + e * 1.6,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 400,
y: explosionCircle.y + (Math.random() - 0.5) * 400
}, {
duration: 800 + e * 150,
easing: e % 3 === 0 ? tween.easeOut : e % 3 === 1 ? tween.easeInOut : tween.bounceOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Damage all enemies within 1600 unit radius (doubled from 800)
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 <= 1600) {
enemy.takeDamage(3999996); // 4x damage (doubled from 2x)
}
}
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
var dx = greenEnemy.x - self.x;
var dy = greenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 1600) {
greenEnemy.takeDamage(3999996); // 4x damage (doubled from 2x)
}
}
if (currentBoss) {
var dx = currentBoss.x - self.x;
var dy = currentBoss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 1600) {
currentBoss.takeDamage(3999996); // 4x damage (doubled from 2x)
}
}
self.destroy();
};
return self;
});
var RocketBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('rocketBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.damage = 20;
// Set rotation method for bullet orientation
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
// Rocket splash damage - damage up to 4 nearest enemies within range
var nearbyEnemies = [];
// Collect all enemies within splash radius
for (var e = 0; e < enemies.length; e++) {
var splashEnemy = enemies[e];
var dx = splashEnemy.x - self.x;
var dy = splashEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 300) {
nearbyEnemies.push({
enemy: splashEnemy,
distance: distance
});
}
}
for (var g = 0; g < greenEnemies.length; g++) {
var splashGreen = greenEnemies[g];
var dx = splashGreen.x - self.x;
var dy = splashGreen.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 300) {
nearbyEnemies.push({
enemy: splashGreen,
distance: distance
});
}
}
// Sort by distance and damage up to 4 closest enemies
nearbyEnemies.sort(function (a, b) {
return a.distance - b.distance;
});
var enemiesToDamage = Math.min(4, nearbyEnemies.length);
for (var i = 0; i < enemiesToDamage; i++) {
nearbyEnemies[i].enemy.takeDamage(self.damage + (player ? player.damage : 0));
}
// Create explosion effect
var explosion = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 400,
alpha: 0.6,
tint: 0xFF6600
});
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with green enemies
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
if (self.intersects(greenEnemy)) {
// Rocket splash damage
for (var e = 0; e < enemies.length; e++) {
var splashEnemy = enemies[e];
var dx = splashEnemy.x - self.x;
var dy = splashEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 300) {
splashEnemy.takeDamage(self.damage + (player ? player.damage : 0));
}
}
for (var gg = 0; gg < greenEnemies.length; gg++) {
var splashGreen = greenEnemies[gg];
var dx = splashGreen.x - self.x;
var dy = splashGreen.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 300) {
splashGreen.takeDamage(self.damage + (player ? player.damage : 0));
}
}
// Create explosion effect
var explosion = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 400,
alpha: 0.6,
tint: 0xFF6600
});
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with boss
if (currentBoss && self.intersects(currentBoss)) {
// Splash damage on boss
var explosion = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 400,
alpha: 0.6,
tint: 0xFF6600
});
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
currentBoss.takeDamage(self.damage + (player ? player.damage : 0));
LK.getSound('enemyHit').play();
self.removeFromGame();
}
};
self.removeFromGame = function () {
for (var i = 0; i < playerBullets.length; i++) {
if (playerBullets[i] === self) {
playerBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var RocketLauncher = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('rocketLauncher', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this rocket launcher came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If rocket launcher is already active, only refresh the timer
if (rocketLauncherActive) {
rocketLauncherEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate rocket launcher effect for first time
rocketLauncherActive = true;
rocketLauncherEndTime = LK.ticks + 1800; // 30 seconds
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var SpawnerBoss = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('spawnerBoss', {
anchorX: 0.5,
anchorY: 0.5
});
// Calculate total health of enemies from previous level (currentLevel - 1)
var previousLevel = Math.max(1, currentLevel - 1);
var enemyMaxHealth = 40 + (previousLevel - 1) * 8;
var enemiesInPreviousLevel = Math.floor(enemiesPerLevel * (1 + Math.floor((previousLevel - 1) / 5) * 0.4));
var totalEnemyHealth = enemyMaxHealth * enemiesInPreviousLevel;
self.maxHealth = totalEnemyHealth * 3;
self.health = self.maxHealth;
self.speed = 0.8 + Math.max(0, currentLevel - 10) * 0.1;
var baseDamage = 30;
var bossNumber = Math.floor(currentLevel / 10); // Boss 1 at level 10, Boss 2 at level 20, etc.
var damageMultiplier = 1 + (bossNumber - 1) * 0.5; // 50% increase per boss
self.damage = Math.floor(baseDamage * damageMultiplier);
self.lastAttack = 0;
// Enemy spawning properties
self.lastSpawn = 0;
self.spawnCooldown = 300; // 5 seconds at 60 FPS (8 seconds when health > 50%, 5 seconds when health <= 50%)
self.previousLevelEnemyHealth = 40 + (previousLevel - 1) * 8; // Health of enemies from previous level
self.previousLevelEnemySpeed = 1.5 + (previousLevel - 1) * 0.15; // Speed of enemies from previous level
self.previousLevelEnemyDamage = 10 * (1 + (previousLevel - 1) * 0.1); // Damage of enemies from previous level
self.spawnedEnemyCount = 0; // Track total enemies spawned (max 15)
self.spawnerBossId = 'spawner_' + LK.ticks; // Unique ID for this spawner boss
// Create boss health bar background
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 30
});
self.healthBarBg.y = -90;
self.addChild(self.healthBarBg);
// Create boss health bar fill
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5,
width: 196,
height: 26
});
self.healthBarFill.x = -98;
self.healthBarFill.y = -90;
self.addChild(self.healthBarFill);
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
// Apply blue tint when frozen
if (graphics.tint !== 0x4444ff) {
graphics.tint = 0x4444ff;
}
} else {
// Remove blue tint when not frozen
if (graphics.tint !== 0xffffff) {
graphics.tint = 0xffffff;
}
}
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * effectiveSpeed;
self.y += dy / distance * effectiveSpeed;
}
// Spawn enemies
self.lastSpawn++;
// Count current alive enemies from this spawner
var currentAliveEnemies = 0;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].spawnerBossId === self.spawnerBossId) {
currentAliveEnemies++;
}
}
// Calculate how many enemies we need to spawn to reach 15
var enemiesToSpawn = Math.max(0, 15 - currentAliveEnemies);
// Dynamic cooldown: 8 seconds normally, 5 seconds when health <= 50%
var currentSpawnCooldown = self.health <= self.maxHealth / 2 ? 300 : 480; // 5 seconds vs 8 seconds at 60 FPS
if (self.lastSpawn >= currentSpawnCooldown && enemiesToSpawn > 0) {
// Limit spawning to prevent overwhelming
var maxSpawnPerCycle = self.health <= self.maxHealth / 2 ? 5 : 2;
enemiesToSpawn = Math.min(enemiesToSpawn, maxSpawnPerCycle);
for (var e = 0; e < enemiesToSpawn; e++) {
var enemy = new Enemy();
// Mark this enemy as spawned by this boss
enemy.spawnerBossId = self.spawnerBossId;
// Set spawned enemy stats to previous level stats
enemy.maxHealth = self.previousLevelEnemyHealth;
enemy.health = enemy.maxHealth;
enemy.speed = self.previousLevelEnemySpeed;
enemy.damage = self.previousLevelEnemyDamage;
// Position enemy next to spawner boss with some spread
var spawnAngle = Math.random() * 2 * Math.PI;
var spawnDistance = 100 + Math.random() * 50; // Spawn 100-150 pixels away
enemy.x = self.x + Math.cos(spawnAngle) * spawnDistance;
enemy.y = self.y + Math.sin(spawnAngle) * spawnDistance;
// Ensure enemy spawns within game bounds
enemy.x = Math.max(50, Math.min(1998, enemy.x));
enemy.y = Math.max(50, Math.min(2682, enemy.y));
// Add to game
enemies.push(enemy);
game.addChild(enemy);
}
self.lastSpawn = 0;
LK.getSound('shoot').play();
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
// Update boss health bar
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 196 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Play explosion sound
LK.getSound('explosion').play();
// Create massive explosion effect with multiple waves of particles
for (var e = 0; e < 8; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 60 + e * 25,
height: 60 + e * 25,
alpha: 1.0 - e * 0.1,
tint: e === 0 ? 0xFF0000 : e === 1 ? 0xFF2200 : e === 2 ? 0xFF4500 : e === 3 ? 0xFF6600 : e === 4 ? 0xFF8800 : e === 5 ? 0xFFAA00 : e === 6 ? 0xFFCC00 : 0xFFFF00
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 80;
explosionCircle.y = self.y + (Math.random() - 0.5) * 80;
game.addChild(explosionCircle);
// Animate explosion with dramatic scaling and movement
tween(explosionCircle, {
scaleX: 5 + e * 0.8,
scaleY: 5 + e * 0.8,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 200,
y: explosionCircle.y + (Math.random() - 0.5) * 200
}, {
duration: 800 + e * 150,
easing: e % 3 === 0 ? tween.easeOut : e % 3 === 1 ? tween.easeInOut : tween.bounceOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Drop guaranteed 10-20 coins
var numCoins = 10 + Math.floor(Math.random() * 11); // 10-20 coins
for (var i = 0; i < numCoins; i++) {
var coin = new Coin();
// Calculate coin value - first 5 levels = 10, then +5 every 5 levels
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
coin.value = 10; // Levels 1-5
} else {
coin.value = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
coin.x = self.x + (Math.random() - 0.5) * 150;
coin.y = self.y + (Math.random() - 0.5) * 150;
coins.push(coin);
game.addChild(coin);
}
// Drop permanent bullet upgrade
var bulletUpgrade = new BulletUpgrade();
bulletUpgrade.x = self.x + (Math.random() - 0.5) * 100;
bulletUpgrade.y = self.y + (Math.random() - 0.5) * 100;
powerUps.push(bulletUpgrade);
game.addChild(bulletUpgrade);
// Guaranteed diamond drop
var diamond1 = new Diamond();
// Calculate diamond value - 5x current coin value
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
diamond1.value = coinValue * 5; // Boss diamonds worth 5x current coin value
diamond1.x = self.x + (Math.random() - 0.5) * 100;
diamond1.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond1);
game.addChild(diamond1);
// 50% chance for 2 additional diamonds
if (Math.random() < 0.50) {
for (var d = 0; d < 2; d++) {
var diamond = new Diamond();
// Calculate diamond value - 5x current coin value
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
diamond.value = coinValue * 5;
diamond.x = self.x + (Math.random() - 0.5) * 100;
diamond.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond);
game.addChild(diamond);
}
}
currentBoss = null;
// Stop boss fight music and start gameplay music
if (musicEnabled) {
LK.stopMusic();
LK.playMusic('3', {
loop: true
});
}
self.destroy();
// Boss is defeated, level progression will be handled in main update loop
enemiesKilled = 0;
};
return self;
});
var Speed = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('speed', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this speed came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If speed is already active, only refresh the timer
if (speedActive) {
speedEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate speed effect for first time
speedActive = true;
speedEndTime = LK.ticks + 1800; // 30 seconds
speedOriginalSpeed = player.speed;
player.speed = speedOriginalSpeed * 2;
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var Wealth = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('wealth', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this wealth came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If wealth is already active, only refresh the timer
if (wealthActive) {
wealthEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate wealth effect for first time
wealthActive = true;
wealthEndTime = LK.ticks + 1800; // 30 seconds
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// Game variables
var gameState = 'menu'; // 'menu', 'nickname', 'playing', 'shop', 'bodyPartSelection', 'leaderboard'
var playerNickname = storage.playerNickname || null; // Load saved nickname
var currentLevel = 1;
var enemiesKilled = 0;
var enemiesPerLevel = 5;
var playerCoins = 0;
var touchPosition = null;
// Player upgrade levels and costs - reset to zero for each new game
var healthUpgradeLevel = 0;
var attackSpeedUpgradeLevel = 0;
var attackPowerUpgradeLevel = 0;
var movementSpeedUpgradeLevel = 0;
var attackRangeUpgradeLevel = 0;
// Game objects
var player = null;
var enemies = [];
var greenEnemies = [];
var playerBullets = [];
var bossBullets = [];
var coins = [];
var diamonds = [];
var embers = [];
var healthPacks = [];
var powerUps = [];
var currentBoss = null;
var remainingEnemies = 0;
var playerBulletCount = 1;
var fireBallBulletCount = 1; // Separate counter for fire ball bullets only
// Power-up effects
var rocketLauncherActive = false;
var rocketLauncherEndTime = 0;
var freezeEffectActive = false;
var freezeEffectEndTime = 0;
var machineGunActive = false;
var machineGunEndTime = 0;
var magnetActive = false;
var magnetEndTime = 0;
var speedActive = false;
var speedEndTime = 0;
var speedOriginalSpeed = 0;
var wealthActive = false;
var wealthEndTime = 0;
var ironBodyActive = false;
var ironBodyEndTime = 0;
var healingNeedleActive = false;
var healingNeedleEndTime = 0;
var duplicateActive = false;
var duplicateEndTime = 0;
var currentPlayerClone = null;
// Boss fight green enemy spawning
var bossGreenEnemyTimer = 0;
var bossGreenEnemyInterval = 1800; // 30 seconds at 60 FPS
// UI elements
var levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0, 0);
levelText.x = 50;
levelText.y = 50;
LK.gui.topLeft.addChild(levelText);
var remainingText = new Text2('Enemies: 0', {
size: 45,
fill: 0xFFFFFF
});
remainingText.anchor.set(0, 0);
remainingText.x = 50;
remainingText.y = 120;
LK.gui.topLeft.addChild(remainingText);
// Create player health bar background (longer and slimmer black rectangle) at top of screen
var playerHealthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0,
width: 600,
height: 60,
tint: 0x000000
});
playerHealthBarBg.x = 0;
playerHealthBarBg.y = 50;
LK.gui.top.addChild(playerHealthBarBg);
// Create player health bar fill (longer and slimmer green rectangle)
var playerHealthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0,
width: 592,
height: 52,
tint: 0x00ff00
});
playerHealthBarFill.x = -296;
playerHealthBarFill.y = 54;
LK.gui.top.addChild(playerHealthBarFill);
// Create player health text (smaller white text)
var playerHealthBarText = new Text2('30', {
size: 42,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
playerHealthBarText.anchor.set(0.5, 0.5);
playerHealthBarText.x = 0;
playerHealthBarText.y = 87;
LK.gui.top.addChild(playerHealthBarText);
// Create coin display container below health bar
var coinDisplayContainer = new Container();
// Create spinning coin icon
var coinIcon = LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
coinIcon.x = -30; // Position to the left of text
coinIcon.y = 0;
// Start continuous coin spinning animation
function startCoinSpin() {
tween(coinIcon, {
scaleX: -0.8
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(coinIcon, {
scaleX: 0.8
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
startCoinSpin(); // Loop the animation
}
});
}
});
}
startCoinSpin();
var coinText = new Text2('0', {
size: 40,
fill: 0xF1C40F
});
coinText.anchor.set(0, 0.5);
coinText.x = 10; // Position to the right of icon
coinText.y = 0;
coinDisplayContainer.addChild(coinIcon);
coinDisplayContainer.addChild(coinText);
// Position container below health bar
coinDisplayContainer.x = 0;
coinDisplayContainer.y = 160;
LK.gui.top.addChild(coinDisplayContainer);
// Stats display in top-right corner
var statsContainer = new Container();
var healthStatText = new Text2('Health: 30', {
size: 35,
fill: 0xE74C3C
});
healthStatText.anchor.set(1, 0);
healthStatText.x = -50;
healthStatText.y = 50;
var attackPowerStatText = new Text2('Attack: 5', {
size: 35,
fill: 0xFF6B6B
});
attackPowerStatText.anchor.set(1, 0);
attackPowerStatText.x = -50;
attackPowerStatText.y = 90;
var attackSpeedStatText = new Text2('A.Speed: 120', {
size: 35,
fill: 0x4ECDC4
});
attackSpeedStatText.anchor.set(1, 0);
attackSpeedStatText.x = -50;
attackSpeedStatText.y = 130;
var movementSpeedStatText = new Text2('M.Speed: 2.0', {
size: 35,
fill: 0x45B7D1
});
movementSpeedStatText.anchor.set(1, 0);
movementSpeedStatText.x = -50;
movementSpeedStatText.y = 170;
var attackRangeStatText = new Text2('Range: 600', {
size: 35,
fill: 0x9B59B6
});
attackRangeStatText.anchor.set(1, 0);
attackRangeStatText.x = -50;
attackRangeStatText.y = 210;
// Power-up timer displays
var rocketTimerText = new Text2('', {
size: 30,
fill: 0xff4444
});
rocketTimerText.anchor.set(0, 0);
rocketTimerText.x = 50;
rocketTimerText.y = 200;
rocketTimerText.visible = false;
var freezeTimerText = new Text2('', {
size: 30,
fill: 0x44aaff
});
freezeTimerText.anchor.set(0, 0);
freezeTimerText.x = 50;
freezeTimerText.y = 240;
freezeTimerText.visible = false;
var machineGunTimerText = new Text2('', {
size: 30,
fill: 0xffff44
});
machineGunTimerText.anchor.set(0, 0);
machineGunTimerText.x = 50;
machineGunTimerText.y = 280;
machineGunTimerText.visible = false;
var magnetTimerText = new Text2('', {
size: 30,
fill: 0xaa44ff
});
magnetTimerText.anchor.set(0, 0);
magnetTimerText.x = 50;
magnetTimerText.y = 320;
magnetTimerText.visible = false;
var speedTimerText = new Text2('', {
size: 30,
fill: 0x44ff44
});
speedTimerText.anchor.set(0, 0);
speedTimerText.x = 50;
speedTimerText.y = 360;
speedTimerText.visible = false;
var wealthTimerText = new Text2('', {
size: 30,
fill: 0xffaa44
});
wealthTimerText.anchor.set(0, 0);
wealthTimerText.x = 50;
wealthTimerText.y = 400;
wealthTimerText.visible = false;
var ironBodyTimerText = new Text2('', {
size: 30,
fill: 0x888888
});
ironBodyTimerText.anchor.set(0, 0);
ironBodyTimerText.x = 50;
ironBodyTimerText.y = 440;
ironBodyTimerText.visible = false;
var healingNeedleTimerText = new Text2('', {
size: 30,
fill: 0xff4488
});
healingNeedleTimerText.anchor.set(0, 0);
healingNeedleTimerText.x = 50;
healingNeedleTimerText.y = 480;
healingNeedleTimerText.visible = false;
var duplicateTimerText = new Text2('', {
size: 30,
fill: 0xcccccc
});
duplicateTimerText.anchor.set(0, 0);
duplicateTimerText.x = 50;
duplicateTimerText.y = 560;
duplicateTimerText.visible = false;
var wealthValueText = new Text2('', {
size: 35,
fill: 0xFFD700
});
wealthValueText.anchor.set(1, 0);
wealthValueText.x = -50;
wealthValueText.y = 250;
wealthValueText.visible = false;
// Analog stick control elements
var analogStickContainer = new Container();
var analogStickBase = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 280,
height: 280,
alpha: 0.3,
tint: 0x888888
});
var analogStickKnob = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 140,
height: 140,
alpha: 0.6,
tint: 0xFFFFFF
});
analogStickContainer.addChild(analogStickBase);
analogStickContainer.addChild(analogStickKnob);
analogStickContainer.x = -150; // Position in bottom right
analogStickContainer.y = -150;
LK.gui.bottomRight.addChild(analogStickContainer);
// Analog stick control preferences (loaded from storage)
var analogStickEnabled = storage.analogStickEnabled !== undefined ? storage.analogStickEnabled : true; // Default enabled
var analogStickPosition = storage.analogStickPosition || 'right'; // 'left' or 'right'
// Music control preferences (loaded from storage)
var musicEnabled = storage.musicEnabled !== undefined ? storage.musicEnabled : true; // Default enabled
// Analog stick state
var analogStickActive = false;
var analogStickStartPos = {
x: 0,
y: 0
};
var analogStickCurrentPos = {
x: 0,
y: 0
};
var analogStickDirection = {
x: 0,
y: 0
};
var analogStickRadius = 140; // Half of base width
var speedValueText = new Text2('', {
size: 35,
fill: 0x44ff44
});
speedValueText.anchor.set(1, 0);
speedValueText.x = -50;
speedValueText.y = 290;
speedValueText.visible = false;
var magnetValueText = new Text2('', {
size: 35,
fill: 0xaa44ff
});
magnetValueText.anchor.set(1, 0);
magnetValueText.x = -50;
magnetValueText.y = 330;
magnetValueText.visible = false;
var ironBodyValueText = new Text2('', {
size: 35,
fill: 0x888888
});
ironBodyValueText.anchor.set(1, 0);
ironBodyValueText.x = -50;
ironBodyValueText.y = 370;
ironBodyValueText.visible = false;
var healingNeedleValueText = new Text2('', {
size: 35,
fill: 0xff4488
});
healingNeedleValueText.anchor.set(1, 0);
healingNeedleValueText.x = -50;
healingNeedleValueText.y = 410;
healingNeedleValueText.visible = false;
var machineGunValueText = new Text2('', {
size: 35,
fill: 0xffff44
});
machineGunValueText.anchor.set(1, 0);
machineGunValueText.x = -50;
machineGunValueText.y = 450;
machineGunValueText.visible = false;
var rocketLauncherValueText = new Text2('', {
size: 35,
fill: 0xff4444
});
rocketLauncherValueText.anchor.set(1, 0);
rocketLauncherValueText.x = -50;
rocketLauncherValueText.y = 490;
rocketLauncherValueText.visible = false;
var freezeBombValueText = new Text2('', {
size: 35,
fill: 0x44aaff
});
freezeBombValueText.anchor.set(1, 0);
freezeBombValueText.x = -50;
freezeBombValueText.y = 530;
freezeBombValueText.visible = false;
var duplicateValueText = new Text2('', {
size: 35,
fill: 0xcccccc
});
duplicateValueText.anchor.set(1, 0);
duplicateValueText.x = -50;
duplicateValueText.y = 610;
duplicateValueText.visible = false;
LK.gui.topLeft.addChild(rocketTimerText);
LK.gui.topLeft.addChild(freezeTimerText);
LK.gui.topLeft.addChild(machineGunTimerText);
LK.gui.topLeft.addChild(magnetTimerText);
LK.gui.topLeft.addChild(speedTimerText);
LK.gui.topLeft.addChild(wealthTimerText);
LK.gui.topLeft.addChild(ironBodyTimerText);
LK.gui.topLeft.addChild(healingNeedleTimerText);
LK.gui.topRight.addChild(wealthValueText);
LK.gui.topRight.addChild(speedValueText);
LK.gui.topRight.addChild(magnetValueText);
LK.gui.topRight.addChild(ironBodyValueText);
LK.gui.topRight.addChild(healingNeedleValueText);
LK.gui.topRight.addChild(machineGunValueText);
LK.gui.topRight.addChild(rocketLauncherValueText);
LK.gui.topRight.addChild(freezeBombValueText);
LK.gui.topLeft.addChild(duplicateTimerText);
LK.gui.topRight.addChild(duplicateValueText);
LK.gui.topRight.addChild(healthStatText);
LK.gui.topRight.addChild(attackPowerStatText);
LK.gui.topRight.addChild(attackSpeedStatText);
LK.gui.topRight.addChild(movementSpeedStatText);
LK.gui.topRight.addChild(attackRangeStatText);
// Shop UI (hidden initially)
var shopContainer = new Container();
var shopBackground = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8,
scaleY: 6,
x: 0,
y: 0
});
shopContainer.addChild(shopBackground);
var shopTitle = new Text2('SHOP', {
size: 120,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopTitle.anchor.set(0.5, 0.5);
shopTitle.x = 0;
shopTitle.y = -400;
shopContainer.addChild(shopTitle);
var upgradeButtons = [];
var upgradeLabels = [];
var upgradePlusButtons = [];
var upgradeCostLabels = [];
var upgradeNames = ['Health +15', 'Attack Speed +10', 'Attack Power +10', 'Move Speed +0.8', 'Range +100'];
for (var i = 0; i < 5; i++) {
var buttonBg = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: -220 + i * 130
});
shopContainer.addChild(buttonBg);
upgradeButtons.push(buttonBg);
var label = new Text2(upgradeNames[i], {
size: 30,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
label.anchor.set(0.5, 0.5);
label.x = -200;
label.y = -220 + i * 130;
shopContainer.addChild(label);
upgradeLabels.push(label);
// Plus button for each upgrade
var plusButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
x: 100,
y: -220 + i * 130,
tint: 0xff0000
});
shopContainer.addChild(plusButton);
upgradePlusButtons.push(plusButton);
// Plus text
var plusText = new Text2('+', {
size: 40,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
plusText.anchor.set(0.5, 0.5);
plusText.x = 100;
plusText.y = -220 + i * 130;
shopContainer.addChild(plusText);
// Cost label
var costLabel = new Text2('Cost: 0', {
size: 28,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
costLabel.anchor.set(0.5, 0.5);
costLabel.x = 280;
costLabel.y = -220 + i * 130;
shopContainer.addChild(costLabel);
upgradeCostLabels.push(costLabel);
}
var closeShopButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 500,
y: -250,
scaleX: 0.7,
scaleY: 0.7
});
shopContainer.addChild(closeShopButton);
var closeShopText = new Text2('CLOSE', {
size: 40,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
closeShopText.anchor.set(0.5, 0.5);
closeShopText.x = 500;
closeShopText.y = -250;
shopContainer.addChild(closeShopText);
shopContainer.x = 1024;
shopContainer.y = 1366;
shopContainer.visible = false;
// Body part selection UI (hidden initially)
var bodyPartContainer = new Container();
var bodyPartBackground = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 10,
scaleY: 8,
x: 0,
y: 0
});
bodyPartContainer.addChild(bodyPartBackground);
var bodyPartTitle = new Text2('CHOOSE BODY PART', {
size: 70,
fill: 0xFFFFFF
});
bodyPartTitle.anchor.set(0.5, 0.5);
bodyPartTitle.x = 0;
bodyPartTitle.y = -250;
bodyPartContainer.addChild(bodyPartTitle);
var bodyPartButtons = [];
for (var i = 0; i < 3; i++) {
var partButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100 + i * 120
});
bodyPartContainer.addChild(partButton);
bodyPartButtons.push(partButton);
}
bodyPartContainer.x = 1024;
bodyPartContainer.y = 1366;
bodyPartContainer.visible = false;
// Main Menu UI
var mainMenuContainer = new Container();
var menuBackground = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 12,
scaleY: 8,
x: 0,
y: 0,
tint: 0x2c3e50
});
mainMenuContainer.addChild(menuBackground);
var gameTitle = new Text2('FIGHTING BACTERIA', {
size: 120,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
gameTitle.anchor.set(0.5, 0.5);
gameTitle.x = 0;
gameTitle.y = -250;
mainMenuContainer.addChild(gameTitle);
var playerNameDisplay = new Text2('', {
size: 60,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
playerNameDisplay.anchor.set(0.5, 0.5);
playerNameDisplay.x = 0;
playerNameDisplay.y = -150;
mainMenuContainer.addChild(playerNameDisplay);
var playButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 1.2,
scaleY: 1.0,
tint: 0x27ae60
});
mainMenuContainer.addChild(playButton);
var playText = new Text2('PLAY', {
size: 80,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
playText.anchor.set(0.5, 0.5);
playText.x = 0;
playText.y = 0;
mainMenuContainer.addChild(playText);
var scoreButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 150,
scaleX: 1.2,
scaleY: 1.0,
tint: 0x3498db
});
mainMenuContainer.addChild(scoreButton);
var scoreText = new Text2('SCORE', {
size: 80,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
scoreText.anchor.set(0.5, 0.5);
scoreText.x = 0;
scoreText.y = 150;
mainMenuContainer.addChild(scoreText);
// Analog control buttons
var analogControlTitle = new Text2('ANALOG CONTROL', {
size: 60,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
analogControlTitle.anchor.set(0.5, 0.5);
analogControlTitle.x = 0;
analogControlTitle.y = 250;
mainMenuContainer.addChild(analogControlTitle);
var analogOnButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 330,
scaleX: 0.8,
scaleY: 0.8,
tint: analogStickEnabled ? 0x27ae60 : 0x7f8c8d
});
mainMenuContainer.addChild(analogOnButton);
var analogOnText = new Text2('ON', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
analogOnText.anchor.set(0.5, 0.5);
analogOnText.x = -200;
analogOnText.y = 330;
mainMenuContainer.addChild(analogOnText);
var analogOffButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -50,
y: 330,
scaleX: 0.8,
scaleY: 0.8,
tint: !analogStickEnabled ? 0xe74c3c : 0x7f8c8d
});
mainMenuContainer.addChild(analogOffButton);
var analogOffText = new Text2('OFF', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
analogOffText.anchor.set(0.5, 0.5);
analogOffText.x = -50;
analogOffText.y = 330;
mainMenuContainer.addChild(analogOffText);
var analogLeftButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 330,
scaleX: 0.8,
scaleY: 0.8,
tint: analogStickPosition === 'left' ? 0xf39c12 : 0x7f8c8d
});
mainMenuContainer.addChild(analogLeftButton);
var analogLeftText = new Text2('LEFT', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
analogLeftText.anchor.set(0.5, 0.5);
analogLeftText.x = 100;
analogLeftText.y = 330;
mainMenuContainer.addChild(analogLeftText);
var analogRightButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 250,
y: 330,
scaleX: 0.8,
scaleY: 0.8,
tint: analogStickPosition === 'right' ? 0xf39c12 : 0x7f8c8d
});
mainMenuContainer.addChild(analogRightButton);
var analogRightText = new Text2('RIGHT', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
analogRightText.anchor.set(0.5, 0.5);
analogRightText.x = 250;
analogRightText.y = 330;
mainMenuContainer.addChild(analogRightText);
// Music control title
var musicControlTitle = new Text2('MUSIC & SOUND', {
size: 60,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
musicControlTitle.anchor.set(0.5, 0.5);
musicControlTitle.x = 0;
musicControlTitle.y = 410;
mainMenuContainer.addChild(musicControlTitle);
var musicOnButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -100,
y: 490,
scaleX: 0.8,
scaleY: 0.8,
tint: musicEnabled ? 0x27ae60 : 0x7f8c8d
});
mainMenuContainer.addChild(musicOnButton);
var musicOnText = new Text2('ON', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
musicOnText.anchor.set(0.5, 0.5);
musicOnText.x = -100;
musicOnText.y = 490;
mainMenuContainer.addChild(musicOnText);
var musicOffButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 490,
scaleX: 0.8,
scaleY: 0.8,
tint: !musicEnabled ? 0xe74c3c : 0x7f8c8d
});
mainMenuContainer.addChild(musicOffButton);
var musicOffText = new Text2('OFF', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
musicOffText.anchor.set(0.5, 0.5);
musicOffText.x = 100;
musicOffText.y = 490;
mainMenuContainer.addChild(musicOffText);
mainMenuContainer.x = 1024;
mainMenuContainer.y = 1366;
mainMenuContainer.visible = true;
// Nickname Selection UI
var nicknameContainer = new Container();
var nicknameBackground = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 12,
scaleY: 8,
x: 0,
y: 0,
tint: 0x34495e
});
nicknameContainer.addChild(nicknameBackground);
var nicknameTitle = new Text2('CHOOSE NICKNAME', {
size: 100,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
nicknameTitle.anchor.set(0.5, 0.5);
nicknameTitle.x = 0;
nicknameTitle.y = -200;
nicknameContainer.addChild(nicknameTitle);
var nicknameOptions = [];
var nicknameButtons = [];
var optionNames = ['Hero', 'Warrior', 'Fighter', 'Champion', 'Guardian', 'Legend'];
for (var i = 0; i < 6; i++) {
var nicknameButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: i % 3 * 300 - 300,
y: Math.floor(i / 3) * 120 - 50,
scaleX: 1.0,
scaleY: 0.8,
tint: 0x3498db
});
nicknameContainer.addChild(nicknameButton);
nicknameButtons.push(nicknameButton);
var nicknameText = new Text2(optionNames[i], {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
nicknameText.anchor.set(0.5, 0.5);
nicknameText.x = i % 3 * 300 - 300;
nicknameText.y = Math.floor(i / 3) * 120 - 50;
nicknameContainer.addChild(nicknameText);
nicknameOptions.push(nicknameText);
}
nicknameContainer.x = 1024;
nicknameContainer.y = 1366;
nicknameContainer.visible = false;
// Leaderboard UI
var leaderboardContainer = new Container();
var leaderboardBackground = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 12,
scaleY: 10,
x: 0,
y: 0,
tint: 0x34495e
});
leaderboardContainer.addChild(leaderboardBackground);
var leaderboardTitle = new Text2('HIGH SCORE', {
size: 80,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
leaderboardTitle.anchor.set(0.5, 0.5);
leaderboardTitle.x = 0;
leaderboardTitle.y = -380;
leaderboardContainer.addChild(leaderboardTitle);
var leaderboardTexts = [];
for (var i = 0; i < 10; i++) {
var leaderText = new Text2('', {
size: 45,
fill: i === 0 ? 0xFFD700 : i === 1 ? 0xC0C0C0 : i === 2 ? 0xCD7F32 : 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
leaderText.anchor.set(0.5, 0.5);
leaderText.x = 0;
leaderText.y = -300 + i * 60;
leaderboardContainer.addChild(leaderText);
leaderboardTexts.push(leaderText);
}
var backButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 400,
scaleX: 1.0,
scaleY: 0.8,
tint: 0xe74c3c
});
leaderboardContainer.addChild(backButton);
var backText = new Text2('BACK', {
size: 60,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
backText.anchor.set(0.5, 0.5);
backText.x = 0;
backText.y = 400;
leaderboardContainer.addChild(backText);
leaderboardContainer.x = 1024;
leaderboardContainer.y = 1366;
leaderboardContainer.visible = false;
game.addChild(mainMenuContainer);
game.addChild(nicknameContainer);
game.addChild(leaderboardContainer);
game.addChild(shopContainer);
game.addChild(bodyPartContainer);
// Initialize player as null (will be created when game starts)
player = null;
// Hide game UI elements initially since we start in menu
// Check if we need to show nickname selection or main menu
if (!playerNickname) {
gameState = 'nickname';
mainMenuContainer.visible = false;
nicknameContainer.visible = true;
} else {
gameState = 'menu';
playerNameDisplay.setText('Welcome, ' + playerNickname + '!');
}
levelText.visible = false;
remainingText.visible = false;
playerHealthBarBg.visible = false;
playerHealthBarFill.visible = false;
playerHealthBarText.visible = false;
coinDisplayContainer.visible = false;
healthStatText.visible = false;
attackPowerStatText.visible = false;
attackSpeedStatText.visible = false;
movementSpeedStatText.visible = false;
attackRangeStatText.visible = false;
analogStickContainer.visible = false;
// Spawn initial enemies
function spawnEnemies() {
// No enemies during boss rounds (every 10 levels: 10, 20, 30, etc.)
if (currentLevel % 10 === 0) {
return;
}
// Start gameplay music when spawning enemies (transitioning from main menu to actual gameplay)
if (currentLevel === 1) {
if (musicEnabled) LK.playMusic('3', {
loop: true
});
}
// Calculate enemy count: starts at level 1 with 1 enemy, adds 1 per level, capped at 15
var enemiesToSpawn = Math.min(15, currentLevel);
remainingEnemies = enemiesToSpawn;
remainingText.setText('Enemies: ' + remainingEnemies);
// Guarantee 30% chance to spawn exactly one green enemy per level (limited to 1 per level)
var shouldSpawnGreen = Math.random() < 0.3;
var greenEnemySpawned = false;
for (var i = 0; i < enemiesToSpawn; i++) {
// Spawn green enemy on first iteration if we should spawn one and haven't yet
var isGreen = shouldSpawnGreen && !greenEnemySpawned;
if (isGreen) {
greenEnemySpawned = true; // Mark that we've spawned our one green enemy for this level
}
var enemy = isGreen ? new GreenEnemy() : new Enemy();
var validPosition = false;
var attempts = 0;
// Try to find non-overlapping position
while (!validPosition && attempts < 50) {
// Spawn from edges
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -50;
break;
case 1:
// Right
enemy.x = 2098;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2782;
break;
case 3:
// Left
enemy.x = -50;
enemy.y = Math.random() * 2732;
break;
}
// Check if position overlaps with existing enemies
validPosition = true;
for (var j = 0; j < enemies.length; j++) {
var dx = enemy.x - enemies[j].x;
var dy = enemy.y - enemies[j].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
// Minimum distance between enemies
validPosition = false;
break;
}
}
for (var g = 0; g < greenEnemies.length; g++) {
var dx = enemy.x - greenEnemies[g].x;
var dy = enemy.y - greenEnemies[g].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
validPosition = false;
break;
}
}
attempts++;
}
if (isGreen) {
greenEnemies.push(enemy);
} else {
enemies.push(enemy);
}
game.addChild(enemy);
}
}
function spawnBoss() {
// Play boss fight music
if (musicEnabled) LK.playMusic('1');
// Check which boss to spawn based on level
if (currentLevel === 30) {
currentBoss = new Level30Boss();
} else if (currentLevel % 20 === 0) {
// Every 20 levels: Spawner Boss (levels 20, 40, 60, etc.)
currentBoss = new SpawnerBoss();
} else {
// Every 10 levels: Regular Boss (levels 10, 50, 70, etc.)
currentBoss = new Boss();
}
currentBoss.x = 1024;
currentBoss.y = 200;
game.addChild(currentBoss);
// Reset boss green enemy timer
bossGreenEnemyTimer = 0;
}
function showShop() {
gameState = 'shop';
shopContainer.visible = true;
updateShopDisplay();
// Restore player health to full when entering shop
if (player) {
player.health = player.maxHealth;
}
// Attract all coins and diamonds currently on the ground toward player
for (var i = 0; i < coins.length; i++) {
var coin = coins[i];
// Stop any existing movement tweens
tween.stop(coin, {
x: true,
y: true
});
// Calculate duration based on distance to player
var dx = player.x - coin.x;
var dy = player.y - coin.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var duration = Math.min(1000, distance * 2); // Max 1 second, scale with distance
// Tween coin toward player
tween(coin, {
x: player.x,
y: player.y
}, {
duration: duration,
easing: tween.easeInOut
});
}
for (var i = 0; i < diamonds.length; i++) {
var diamond = diamonds[i];
// Stop any existing movement tweens
tween.stop(diamond, {
x: true,
y: true
});
// Calculate duration based on distance to player
var dx = player.x - diamond.x;
var dy = player.y - diamond.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var duration = Math.min(1000, distance * 2); // Max 1 second, scale with distance
// Tween diamond toward player
tween(diamond, {
x: player.x,
y: player.y
}, {
duration: duration,
easing: tween.easeInOut
});
}
for (var i = 0; i < embers.length; i++) {
var ember = embers[i];
// Stop any existing movement tweens
tween.stop(ember, {
x: true,
y: true
});
// Calculate duration based on distance to player
var dx = player.x - ember.x;
var dy = player.y - ember.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var duration = Math.min(1000, distance * 2); // Max 1 second, scale with distance
// Tween ember toward player
tween(ember, {
x: player.x,
y: player.y
}, {
duration: duration,
easing: tween.easeInOut
});
}
// Hide player if within shop area to prevent overlap
if (player) {
player.visible = false;
}
// Hide health packs when shop opens
for (var i = 0; i < healthPacks.length; i++) {
healthPacks[i].visible = false;
}
// Pause all game objects
for (var i = 0; i < enemies.length; i++) {
enemies[i].visible = false;
}
}
function updateShopDisplay() {
var baseCosts = [15, 15, 15, 15, 15];
var upgradeLevels = [healthUpgradeLevel, attackSpeedUpgradeLevel, attackPowerUpgradeLevel, movementSpeedUpgradeLevel, attackRangeUpgradeLevel];
var anyAffordable = false;
for (var i = 0; i < 5; i++) {
var cost = Math.floor(baseCosts[i] * Math.pow(2.5, upgradeLevels[i]));
upgradeCostLabels[i].setText('Cost: ' + formatCurrency(cost));
// Enable/disable plus button based on affordability
if (playerCoins >= cost) {
upgradePlusButtons[i].visible = true;
upgradePlusButtons[i].alpha = 1.0;
anyAffordable = true;
} else {
upgradePlusButtons[i].visible = true;
upgradePlusButtons[i].alpha = 0.3;
}
}
// Show/hide close button based on affordability
if (!anyAffordable) {
closeShopButton.visible = true;
closeShopText.visible = true;
} else {
closeShopButton.visible = true;
closeShopText.visible = true;
}
}
function hideShop() {
// Start normal gameplay music when entering actual gameplay (unless it's a boss level)
if (currentLevel % 10 !== 0) {
if (musicEnabled) LK.playMusic('3', {
loop: true
});
}
gameState = 'playing';
shopContainer.visible = false;
// Show player again
if (player) {
player.visible = true;
}
// Show health packs when shop closes
for (var i = 0; i < healthPacks.length; i++) {
healthPacks[i].visible = true;
}
// Resume all game objects
for (var i = 0; i < enemies.length; i++) {
enemies[i].visible = true;
}
// Check if current level is a boss level and spawn boss instead of regular enemies
if (currentLevel % 10 === 0) {
// Spawn boss at boss levels
spawnBoss();
} else {
// Spawn regular enemies for non-boss levels
spawnEnemies();
}
}
function showBodyPartSelection() {
gameState = 'bodyPartSelection';
bodyPartContainer.visible = true;
}
function hideBodyPartSelection() {
gameState = 'playing';
bodyPartContainer.visible = false;
// Level progression is now handled in game.update
enemiesKilled = 0;
spawnEnemies();
}
// Event handlers
game.down = function (x, y, obj) {
if (gameState === 'nickname') {
// Convert screen coordinates to nickname container coordinates
var nicknamePos = {
x: x - nicknameContainer.x,
y: y - nicknameContainer.y
};
// Check nickname option buttons
for (var i = 0; i < nicknameButtons.length; i++) {
var button = nicknameButtons[i];
if (Math.abs(nicknamePos.x - button.x) < 140 && Math.abs(nicknamePos.y - button.y) < 60) {
selectNickname(optionNames[i]);
return;
}
}
}
if (gameState === 'menu') {
// Convert screen coordinates to menu container coordinates
var menuPos = {
x: x - mainMenuContainer.x,
y: y - mainMenuContainer.y
};
// Check play button
if (Math.abs(menuPos.x - playButton.x) < 140 && Math.abs(menuPos.y - playButton.y) < 80) {
startGame();
return;
}
// Check score button
if (Math.abs(menuPos.x - scoreButton.x) < 140 && Math.abs(menuPos.y - scoreButton.y) < 80) {
showLeaderboard();
return;
}
// Check analog control buttons
if (Math.abs(menuPos.x - analogOnButton.x) < 100 && Math.abs(menuPos.y - analogOnButton.y) < 40) {
analogStickEnabled = true;
storage.analogStickEnabled = analogStickEnabled;
updateAnalogStickDisplay();
return;
}
if (Math.abs(menuPos.x - analogOffButton.x) < 100 && Math.abs(menuPos.y - analogOffButton.y) < 40) {
analogStickEnabled = false;
storage.analogStickEnabled = analogStickEnabled;
updateAnalogStickDisplay();
return;
}
if (Math.abs(menuPos.x - analogLeftButton.x) < 100 && Math.abs(menuPos.y - analogLeftButton.y) < 40) {
analogStickPosition = 'left';
storage.analogStickPosition = analogStickPosition;
updateAnalogStickDisplay();
return;
}
if (Math.abs(menuPos.x - analogRightButton.x) < 100 && Math.abs(menuPos.y - analogRightButton.y) < 40) {
analogStickPosition = 'right';
storage.analogStickPosition = analogStickPosition;
updateAnalogStickDisplay();
return;
}
// Check music control buttons
if (Math.abs(menuPos.x - musicOnButton.x) < 100 && Math.abs(menuPos.y - musicOnButton.y) < 40) {
musicEnabled = true;
storage.musicEnabled = musicEnabled;
updateMusicDisplay();
// Resume current menu music if we just enabled music
if (musicEnabled) {
LK.playMusic('2', {
loop: true
});
}
return;
}
if (Math.abs(menuPos.x - musicOffButton.x) < 100 && Math.abs(menuPos.y - musicOffButton.y) < 40) {
musicEnabled = false;
storage.musicEnabled = musicEnabled;
updateMusicDisplay();
// Stop all music and sounds when disabled
if (!musicEnabled) {
LK.stopMusic();
}
return;
}
}
if (gameState === 'leaderboard') {
// Convert screen coordinates to leaderboard container coordinates
var leaderPos = {
x: x - leaderboardContainer.x,
y: y - leaderboardContainer.y
};
// Check back button
if (Math.abs(leaderPos.x - backButton.x) < 140 && Math.abs(leaderPos.y - backButton.y) < 60) {
showMainMenu();
return;
}
}
if (gameState === 'playing' && analogStickEnabled) {
// Check if touch is on analog stick first before setting touch position
var stickCenterX, stickCenterY;
if (analogStickPosition === 'left') {
stickCenterX = 350; // Bottom left x position, moved closer to center
stickCenterY = 2732 - 320; // Bottom left y position, moved closer to center
} else {
stickCenterX = 2048 - 350; // Bottom right x position, moved closer to center
stickCenterY = 2732 - 320; // Bottom right y position, moved closer to center
}
var distanceToStick = Math.sqrt((x - stickCenterX) * (x - stickCenterX) + (y - stickCenterY) * (y - stickCenterY));
if (distanceToStick <= analogStickRadius) {
// Start analog stick control immediately
analogStickActive = true;
analogStickStartPos.x = stickCenterX;
analogStickStartPos.y = stickCenterY;
analogStickCurrentPos.x = x;
analogStickCurrentPos.y = y;
// Clear any existing touchPosition when analog stick becomes active
touchPosition = null;
// Calculate initial direction immediately with high precision
var deltaX = x - stickCenterX;
var deltaY = y - stickCenterY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Update knob position relative to base immediately
if (distance > analogStickRadius) {
// Clamp to radius but maintain precise direction
var ratio = analogStickRadius / distance;
analogStickKnob.x = deltaX * ratio;
analogStickKnob.y = deltaY * ratio;
// Normalize direction to exactly 1.0 for edge cases
analogStickDirection.x = deltaX / distance;
analogStickDirection.y = deltaY / distance;
} else {
analogStickKnob.x = deltaX;
analogStickKnob.y = deltaY;
// Calculate normalized direction with no dead zone for immediate response
analogStickDirection.x = distance > 1 ? deltaX / analogStickRadius : 0;
analogStickDirection.y = distance > 1 ? deltaY / analogStickRadius : 0;
}
return; // Don't set touchPosition if using analog stick
}
}
// Only set touch position if analog stick is disabled or touch is outside analog area
if (!analogStickEnabled) {
touchPosition = {
x: x,
y: y
};
}
if (gameState === 'shop') {
// Convert screen coordinates to shop container coordinates
var shopPos = {
x: x - shopContainer.x,
y: y - shopContainer.y
};
// Check plus buttons
for (var i = 0; i < upgradePlusButtons.length; i++) {
var plusButton = upgradePlusButtons[i];
if (plusButton.visible && Math.abs(shopPos.x - plusButton.x) < 80 && Math.abs(shopPos.y - plusButton.y) < 40) {
var baseCosts = [15, 15, 15, 15, 15];
var upgradeLevels = [healthUpgradeLevel, attackSpeedUpgradeLevel, attackPowerUpgradeLevel, movementSpeedUpgradeLevel, attackRangeUpgradeLevel];
var cost = Math.floor(baseCosts[i] * Math.pow(2.5, upgradeLevels[i]));
if (playerCoins >= cost) {
purchaseUpgrade(i);
updateShopDisplay();
}
return;
}
}
// Check close button
if (Math.abs(shopPos.x - closeShopButton.x) < 120 && Math.abs(shopPos.y - closeShopButton.y) < 50) {
hideShop();
}
}
if (gameState === 'bodyPartSelection') {
// Convert screen coordinates to body part container coordinates
var bodyPos = {
x: x - bodyPartContainer.x,
y: y - bodyPartContainer.y
};
for (var i = 0; i < bodyPartButtons.length; i++) {
var button = bodyPartButtons[i];
if (Math.abs(bodyPos.x - button.x) < 200 && Math.abs(bodyPos.y - button.y) < 80) {
selectBodyPart(i);
hideBodyPartSelection();
return;
}
}
}
};
game.move = function (x, y, obj) {
if (gameState === 'playing') {
if (analogStickActive) {
// Update analog stick position with high precision
analogStickCurrentPos.x = x;
analogStickCurrentPos.y = y;
// Calculate direction and distance from center position with improved precision
var deltaX = x - analogStickStartPos.x;
var deltaY = y - analogStickStartPos.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Update knob position and direction with no artificial dead zone for maximum responsiveness
if (distance > analogStickRadius) {
// Clamp to radius but maintain precise direction
var ratio = analogStickRadius / distance;
analogStickKnob.x = deltaX * ratio;
analogStickKnob.y = deltaY * ratio;
// Normalize direction to exactly 1.0 magnitude for consistent max speed
analogStickDirection.x = deltaX / distance;
analogStickDirection.y = deltaY / distance;
} else {
analogStickKnob.x = deltaX;
analogStickKnob.y = deltaY;
// Calculate precise normalized direction with minimal dead zone for instant response
if (distance > 1) {
analogStickDirection.x = deltaX / analogStickRadius;
analogStickDirection.y = deltaY / analogStickRadius;
} else {
analogStickDirection.x = 0;
analogStickDirection.y = 0;
}
}
} else if (analogStickEnabled) {
// Check if touch enters analog stick area during move
var stickCenterX, stickCenterY;
if (analogStickPosition === 'left') {
stickCenterX = 350;
stickCenterY = 2732 - 320;
} else {
stickCenterX = 2048 - 350;
stickCenterY = 2732 - 320;
}
var distanceToStick = Math.sqrt((x - stickCenterX) * (x - stickCenterX) + (y - stickCenterY) * (y - stickCenterY));
if (distanceToStick <= analogStickRadius) {
// Activate analog stick immediately when entering area
analogStickActive = true;
analogStickStartPos.x = stickCenterX;
analogStickStartPos.y = stickCenterY;
analogStickCurrentPos.x = x;
analogStickCurrentPos.y = y;
touchPosition = null;
// Calculate initial direction with high precision
var deltaX = x - stickCenterX;
var deltaY = y - stickCenterY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > analogStickRadius) {
var ratio = analogStickRadius / distance;
analogStickKnob.x = deltaX * ratio;
analogStickKnob.y = deltaY * ratio;
analogStickDirection.x = deltaX / distance;
analogStickDirection.y = deltaY / distance;
} else {
analogStickKnob.x = deltaX;
analogStickKnob.y = deltaY;
analogStickDirection.x = distance > 1 ? deltaX / analogStickRadius : 0;
analogStickDirection.y = distance > 1 ? deltaY / analogStickRadius : 0;
}
} else {
// Regular touch movement outside analog area
touchPosition = {
x: x,
y: y
};
}
} else {
// Analog stick disabled, use regular touch movement
touchPosition = {
x: x,
y: y
};
}
// Make character turn head during iron body mode
if (player && ironBodyActive && player.ironBodyGraphics) {
var dx = x - player.x;
// Turn head based on mouse position
if (dx > 0) {
player.ironBodyGraphics.scaleX = -Math.abs(player.ironBodyGraphics.scaleX); // Face right
} else if (dx < 0) {
player.ironBodyGraphics.scaleX = Math.abs(player.ironBodyGraphics.scaleX); // Face left
}
}
}
};
game.up = function (x, y, obj) {
if (gameState === 'playing') {
if (analogStickActive) {
// Reset analog stick completely
analogStickActive = false;
analogStickKnob.x = 0;
analogStickKnob.y = 0;
analogStickDirection.x = 0;
analogStickDirection.y = 0;
analogStickStartPos.x = 0;
analogStickStartPos.y = 0;
analogStickCurrentPos.x = 0;
analogStickCurrentPos.y = 0;
}
// Always reset touch position when releasing touch
touchPosition = null;
}
};
function purchaseUpgrade(upgradeIndex) {
var baseCosts = [15, 15, 15, 15, 15];
var upgradeLevels = [healthUpgradeLevel, attackSpeedUpgradeLevel, attackPowerUpgradeLevel, movementSpeedUpgradeLevel, attackRangeUpgradeLevel];
var cost = Math.floor(baseCosts[upgradeIndex] * Math.pow(2.5, upgradeLevels[upgradeIndex]));
if (playerCoins >= cost) {
playerCoins -= cost;
coinText.setText(formatCurrency(playerCoins));
switch (upgradeIndex) {
case 0:
// Health upgrade
healthUpgradeLevel++;
storage.healthUpgradeLevel = healthUpgradeLevel;
player.maxHealth += 15;
player.health = player.maxHealth;
break;
case 1:
// Attack Speed upgrade
attackSpeedUpgradeLevel++;
storage.attackSpeedUpgradeLevel = attackSpeedUpgradeLevel;
// Calculate new attack speed to increase display by exactly 10
var currentDisplayValue = Math.round(3600 / player.attackSpeed);
var newDisplayValue = currentDisplayValue + 10;
player.attackSpeed = Math.round(3600 / newDisplayValue);
player.attackSpeed = Math.max(5, player.attackSpeed);
break;
case 2:
// Attack Power upgrade
attackPowerUpgradeLevel++;
storage.attackPowerUpgradeLevel = attackPowerUpgradeLevel;
player.damage += 10;
break;
case 3:
// Movement Speed upgrade
movementSpeedUpgradeLevel++;
storage.movementSpeedUpgradeLevel = movementSpeedUpgradeLevel;
player.speed += 0.8;
break;
case 4:
// Attack Range upgrade
attackRangeUpgradeLevel++;
storage.attackRangeUpgradeLevel = attackRangeUpgradeLevel;
player.attackRange += 50;
// Update visual range circle
var rangeGraphics = player.children[1]; // Second child is the range circle
rangeGraphics.width = player.attackRange * 2;
rangeGraphics.height = player.attackRange * 2;
break;
}
updateStatsDisplay();
}
}
function updateStatsDisplay() {
healthStatText.setText('Health: ' + player.maxHealth);
attackPowerStatText.setText('Attack: ' + player.damage);
attackSpeedStatText.setText('A.Speed: ' + Math.round(3600 / player.attackSpeed));
movementSpeedStatText.setText('M.Speed: ' + player.speed.toFixed(1));
attackRangeStatText.setText('Range: ' + player.attackRange * 2);
}
function selectBodyPart(partIndex) {
switch (partIndex) {
case 0:
// Strong Arms - More damage
player.damage += 25;
player.scaleX *= 1.1;
break;
case 1:
// Swift Legs - More speed
player.speed += 2;
player.scaleY *= 1.1;
break;
case 2:
// Tough Body - More health
player.maxHealth += 50;
player.health = player.maxHealth;
player.scaleX *= 1.05;
player.scaleY *= 1.05;
break;
}
}
function startGame() {
gameState = 'playing';
mainMenuContainer.visible = false;
leaderboardContainer.visible = false;
// Reset analog stick state completely when starting game
analogStickActive = false;
analogStickDirection.x = 0;
analogStickDirection.y = 0;
analogStickKnob.x = 0;
analogStickKnob.y = 0;
analogStickStartPos.x = 0;
analogStickStartPos.y = 0;
analogStickCurrentPos.x = 0;
analogStickCurrentPos.y = 0;
touchPosition = null;
// Show game UI elements
levelText.visible = true;
remainingText.visible = true;
playerHealthBarBg.visible = true;
playerHealthBarFill.visible = true;
playerHealthBarText.visible = true;
coinDisplayContainer.visible = true;
healthStatText.visible = true;
attackPowerStatText.visible = true;
attackSpeedStatText.visible = true;
movementSpeedStatText.visible = true;
attackRangeStatText.visible = true;
updateAnalogStickDisplay();
// Reset game state completely to start from beginning
currentLevel = 1;
enemiesKilled = 0;
playerCoins = 0;
healthUpgradeLevel = 0;
attackSpeedUpgradeLevel = 0;
attackPowerUpgradeLevel = 0;
movementSpeedUpgradeLevel = 0;
attackRangeUpgradeLevel = 0;
fireBallBulletCount = 1;
// Clear all game objects
for (var i = 0; i < enemies.length; i++) {
enemies[i].destroy();
}
for (var i = 0; i < greenEnemies.length; i++) {
greenEnemies[i].destroy();
}
for (var i = 0; i < playerBullets.length; i++) {
playerBullets[i].destroy();
}
for (var i = 0; i < bossBullets.length; i++) {
bossBullets[i].destroy();
}
for (var i = 0; i < coins.length; i++) {
coins[i].destroy();
}
for (var i = 0; i < diamonds.length; i++) {
diamonds[i].destroy();
}
for (var i = 0; i < embers.length; i++) {
embers[i].destroy();
}
for (var i = 0; i < healthPacks.length; i++) {
healthPacks[i].destroy();
}
for (var i = 0; i < powerUps.length; i++) {
powerUps[i].destroy();
}
if (currentBoss) {
currentBoss.destroy();
}
if (player) {
player.destroy();
}
// Clear all arrays
enemies = [];
greenEnemies = [];
playerBullets = [];
bossBullets = [];
coins = [];
diamonds = [];
embers = [];
healthPacks = [];
powerUps = [];
currentBoss = null;
// Reset power-up states
rocketLauncherActive = false;
freezeEffectActive = false;
machineGunActive = false;
magnetActive = false;
speedActive = false;
wealthActive = false;
ironBodyActive = false;
healingNeedleActive = false;
duplicateActive = false;
if (currentPlayerClone) {
currentPlayerClone.destroy();
currentPlayerClone = null;
}
// Initialize fresh player
player = new Player();
player.x = 1024;
player.y = 1366;
game.addChild(player);
// Update UI
levelText.setText('Level: 1');
coinText.setText(formatCurrency(0));
updateStatsDisplay();
// Spawn initial enemies
spawnEnemies();
}
function showLeaderboard() {
gameState = 'leaderboard';
mainMenuContainer.visible = false;
leaderboardContainer.visible = true;
// Make leaderboard background opaque to hide game elements
leaderboardBackground.tint = 0x000000;
leaderboardBackground.alpha = 0.95;
// Hide all game elements when leaderboard is open
for (var i = 0; i < enemies.length; i++) {
enemies[i].visible = false;
}
for (var i = 0; i < greenEnemies.length; i++) {
greenEnemies[i].visible = false;
}
for (var i = 0; i < playerBullets.length; i++) {
playerBullets[i].visible = false;
}
for (var i = 0; i < bossBullets.length; i++) {
bossBullets[i].visible = false;
}
for (var i = 0; i < coins.length; i++) {
coins[i].visible = false;
}
for (var i = 0; i < diamonds.length; i++) {
diamonds[i].visible = false;
}
for (var i = 0; i < embers.length; i++) {
embers[i].visible = false;
}
for (var i = 0; i < healthPacks.length; i++) {
healthPacks[i].visible = false;
}
for (var i = 0; i < powerUps.length; i++) {
powerUps[i].visible = false;
}
if (currentBoss) {
currentBoss.visible = false;
}
if (player) {
player.visible = false;
}
// Get player's personal best scores from storage
var playerHighestLevel = storage.playerHighestLevel || 1;
var playerSurvivalTime = storage.playerSurvivalTime || 0;
// Log for debugging
console.log('Loading personal scores - Level:', playerHighestLevel, 'Time:', playerSurvivalTime);
// Display personal best scores
for (var i = 0; i < 10; i++) {
if (i === 0) {
leaderboardTexts[i].setText('Your Highest Level: ' + playerHighestLevel);
} else if (i === 1) {
var minutes = Math.floor(playerSurvivalTime / 60);
var seconds = playerSurvivalTime % 60;
var timeText = minutes + 'm ' + seconds + 's';
leaderboardTexts[i].setText('Your Best Survival Time: ' + timeText);
} else {
leaderboardTexts[i].setText('');
}
}
}
function selectNickname(nickname) {
playerNickname = nickname + Math.floor(Math.random() * 1000); // Add random number to make unique
storage.playerNickname = playerNickname; // Save to storage permanently
console.log('Nickname selected and saved:', playerNickname);
// Stop any current music before showing main menu
if (musicEnabled) LK.stopMusic();
showMainMenu();
}
function showMainMenu() {
// Play main menu music
if (musicEnabled) LK.playMusic('2', {
loop: true
});
gameState = 'menu';
mainMenuContainer.visible = true;
nicknameContainer.visible = false;
leaderboardContainer.visible = false;
shopContainer.visible = false;
bodyPartContainer.visible = false;
// Reset analog stick state completely when returning to menu
analogStickActive = false;
analogStickDirection.x = 0;
analogStickDirection.y = 0;
analogStickKnob.x = 0;
analogStickKnob.y = 0;
analogStickStartPos.x = 0;
analogStickStartPos.y = 0;
analogStickCurrentPos.x = 0;
analogStickCurrentPos.y = 0;
touchPosition = null;
// Show all game elements when returning from leaderboard
for (var i = 0; i < enemies.length; i++) {
enemies[i].visible = true;
}
for (var i = 0; i < greenEnemies.length; i++) {
greenEnemies[i].visible = true;
}
for (var i = 0; i < playerBullets.length; i++) {
playerBullets[i].visible = true;
}
for (var i = 0; i < bossBullets.length; i++) {
bossBullets[i].visible = true;
}
for (var i = 0; i < coins.length; i++) {
coins[i].visible = true;
}
for (var i = 0; i < diamonds.length; i++) {
diamonds[i].visible = true;
}
for (var i = 0; i < embers.length; i++) {
embers[i].visible = true;
}
for (var i = 0; i < healthPacks.length; i++) {
healthPacks[i].visible = true;
}
for (var i = 0; i < powerUps.length; i++) {
powerUps[i].visible = true;
}
if (currentBoss) {
currentBoss.visible = true;
}
if (player) {
player.visible = true;
}
// Update player name display
if (playerNickname) {
playerNameDisplay.setText('Welcome, ' + playerNickname + '!');
}
// Hide game UI elements
levelText.visible = false;
remainingText.visible = false;
playerHealthBarBg.visible = false;
playerHealthBarFill.visible = false;
playerHealthBarText.visible = false;
coinDisplayContainer.visible = false;
healthStatText.visible = false;
attackPowerStatText.visible = false;
attackSpeedStatText.visible = false;
movementSpeedStatText.visible = false;
attackRangeStatText.visible = false;
analogStickContainer.visible = false;
// Update analog stick display when showing menu
updateAnalogStickDisplay();
// Update music display when showing menu
updateMusicDisplay();
}
function saveScore(playerName, level) {
// Calculate survival time in seconds (currentLevel - 1 because we start at level 1)
var survivalTime = Math.max(0, level - 1) * 30; // Approximate 30 seconds per level
// Get existing personal best scores
var currentHighestLevel = storage.playerHighestLevel || 1;
var currentSurvivalTime = storage.playerSurvivalTime || 0;
// Update personal best level if current level is higher
if (level > currentHighestLevel) {
storage.playerHighestLevel = level;
console.log('New highest level achieved:', level);
}
// Update personal best survival time if current time is higher
if (survivalTime > currentSurvivalTime) {
storage.playerSurvivalTime = survivalTime;
console.log('New best survival time achieved:', survivalTime, 'seconds');
}
// Log for debugging
console.log('Score saved for:', playerName, 'Level:', level, 'Survival time:', survivalTime, 'seconds');
}
function createPlayerClone() {
// Remove existing clone if any
if (currentPlayerClone) {
currentPlayerClone.destroy();
currentPlayerClone = null;
}
// Create new clone
currentPlayerClone = new PlayerClone();
// Position clone near player but not overlapping
currentPlayerClone.x = player.x + (Math.random() - 0.5) * 200;
currentPlayerClone.y = player.y + (Math.random() - 0.5) * 200;
// Ensure clone spawns within game bounds
currentPlayerClone.x = Math.max(50, Math.min(1998, currentPlayerClone.x));
currentPlayerClone.y = Math.max(50, Math.min(2682, currentPlayerClone.y));
// Update clone stats to match player's current stats exactly
if (player) {
currentPlayerClone.maxHealth = player.maxHealth;
currentPlayerClone.health = player.maxHealth;
currentPlayerClone.speed = player.speed;
currentPlayerClone.damage = player.damage;
currentPlayerClone.attackSpeed = player.attackSpeed;
currentPlayerClone.attackRange = player.attackRange;
}
game.addChild(currentPlayerClone);
}
function updateAnalogStickDisplay() {
// Update button colors
analogOnButton.tint = analogStickEnabled ? 0x27ae60 : 0x7f8c8d;
analogOffButton.tint = !analogStickEnabled ? 0xe74c3c : 0x7f8c8d;
analogLeftButton.tint = analogStickPosition === 'left' ? 0xf39c12 : 0x7f8c8d;
analogRightButton.tint = analogStickPosition === 'right' ? 0xf39c12 : 0x7f8c8d;
// Update analog stick position and visibility
analogStickContainer.visible = analogStickEnabled && gameState === 'playing';
if (analogStickEnabled) {
if (analogStickPosition === 'left') {
// Move to bottom left, moved closer to center
LK.gui.bottomLeft.addChild(analogStickContainer);
analogStickContainer.x = 350;
analogStickContainer.y = -320;
} else {
// Move to bottom right, moved closer to center
LK.gui.bottomRight.addChild(analogStickContainer);
analogStickContainer.x = -350;
analogStickContainer.y = -320;
}
}
}
function updateMusicDisplay() {
// Update music button colors
musicOnButton.tint = musicEnabled ? 0x27ae60 : 0x7f8c8d;
musicOffButton.tint = !musicEnabled ? 0xe74c3c : 0x7f8c8d;
}
function formatCurrency(amount) {
if (amount >= 1000000) {
var millions = Math.floor(amount / 1000000);
var remainder = amount % 1000000;
var thousands = Math.floor(remainder / 1000);
var coins = remainder % 1000;
var result = millions + 'm';
if (thousands > 0) {
result += ' ' + thousands + 'k';
}
if (coins > 0) {
result += ' ' + coins + 'c';
}
return result;
} else if (amount >= 1000) {
var thousands = Math.floor(amount / 1000);
var coins = amount % 1000;
var result = thousands + 'k';
if (coins > 0) {
result += ' ' + coins + 'c';
}
return result;
} else {
return amount.toString();
}
}
function promptPlayerName() {
// Return the stored nickname
return playerNickname || 'Player' + Math.floor(Math.random() * 1000);
}
// Initial enemies will be spawned when game starts from menu
game.update = function () {
// Only run game logic when in playing state
if (gameState !== 'playing') {
return;
}
// Update player health bar at top of screen only if player exists
if (player) {
var healthPercent = player.health / player.maxHealth;
playerHealthBarFill.width = 592 * healthPercent;
// Health bar color: green when above 20%, red when 20% or below
if (healthPercent <= 0.2) {
playerHealthBarFill.tint = 0xff0000; // Red when health is 20% or less
} else {
playerHealthBarFill.tint = 0x00ff00; // Green when health is above 20%
}
playerHealthBarText.setText(player.health.toString());
} else {
// Hide health bar when player is dead
playerHealthBarFill.width = 0;
playerHealthBarText.setText('0');
}
// Update stats display only if player exists
if (player) {
updateStatsDisplay();
}
// Update remaining enemies counter
if (currentBoss) {
remainingText.setText('Boss Fight!');
} else {
remainingText.setText('Enemies: ' + (enemies.length + greenEnemies.length));
}
// Check power-up expiration and update timers
if (rocketLauncherActive) {
if (LK.ticks >= rocketLauncherEndTime) {
rocketLauncherActive = false;
rocketTimerText.visible = false;
rocketLauncherValueText.visible = false;
} else {
var remainingTime = Math.ceil((rocketLauncherEndTime - LK.ticks) / 60);
rocketTimerText.setText('Rocket: ' + remainingTime + 's');
rocketTimerText.visible = true;
rocketLauncherValueText.setText('Splash Damage');
rocketLauncherValueText.visible = true;
}
} else {
rocketTimerText.visible = false;
rocketLauncherValueText.visible = false;
}
if (freezeEffectActive) {
if (LK.ticks >= freezeEffectEndTime) {
freezeEffectActive = false;
freezeTimerText.visible = false;
freezeBombValueText.visible = false;
} else {
var remainingTime = Math.ceil((freezeEffectEndTime - LK.ticks) / 60);
freezeTimerText.setText('Freeze: ' + remainingTime + 's');
freezeTimerText.visible = true;
freezeBombValueText.setText('Slow Enemies 50%');
freezeBombValueText.visible = true;
}
} else {
freezeTimerText.visible = false;
freezeBombValueText.visible = false;
}
if (machineGunActive) {
if (LK.ticks >= machineGunEndTime) {
machineGunActive = false;
machineGunTimerText.visible = false;
machineGunValueText.visible = false;
} else {
var remainingTime = Math.ceil((machineGunEndTime - LK.ticks) / 60);
machineGunTimerText.setText('Machine Gun: ' + remainingTime + 's');
machineGunTimerText.visible = true;
machineGunValueText.setText('3x Fire Rate');
machineGunValueText.visible = true;
}
} else {
machineGunTimerText.visible = false;
machineGunValueText.visible = false;
}
if (magnetActive) {
if (LK.ticks >= magnetEndTime) {
magnetActive = false;
magnetTimerText.visible = false;
magnetValueText.visible = false;
} else {
var remainingTime = Math.ceil((magnetEndTime - LK.ticks) / 60);
magnetTimerText.setText('Magnet: ' + remainingTime + 's');
magnetTimerText.visible = true;
magnetValueText.setText('Attract Items');
magnetValueText.visible = true;
}
} else {
magnetTimerText.visible = false;
magnetValueText.visible = false;
}
if (speedActive) {
if (LK.ticks >= speedEndTime) {
speedActive = false;
if (player) {
player.speed = speedOriginalSpeed;
}
speedTimerText.visible = false;
speedValueText.visible = false;
} else {
var remainingTime = Math.ceil((speedEndTime - LK.ticks) / 60);
speedTimerText.setText('Speed: ' + remainingTime + 's');
speedTimerText.visible = true;
speedValueText.setText('2x Move Speed');
speedValueText.visible = true;
}
} else {
speedTimerText.visible = false;
speedValueText.visible = false;
}
if (wealthActive) {
if (LK.ticks >= wealthEndTime) {
wealthActive = false;
wealthTimerText.visible = false;
wealthValueText.visible = false;
} else {
var remainingTime = Math.ceil((wealthEndTime - LK.ticks) / 60);
wealthTimerText.setText('Wealth: ' + remainingTime + 's');
wealthTimerText.visible = true;
wealthValueText.setText('2x Coin Value + 30% Diamond Drop');
wealthValueText.visible = true;
}
} else {
wealthTimerText.visible = false;
wealthValueText.visible = false;
}
if (ironBodyActive) {
if (LK.ticks >= ironBodyEndTime) {
ironBodyActive = false;
// Restore original player appearance
if (player && player.ironBodyGraphics) {
var currentPlayerGraphics = player.children[0]; // First child is the original player graphics
currentPlayerGraphics.visible = true; // Show original player
player.ironBodyGraphics.visible = false; // Hide iron body appearance
}
ironBodyTimerText.visible = false;
ironBodyValueText.visible = false;
} else {
var remainingTime = Math.ceil((ironBodyEndTime - LK.ticks) / 60);
ironBodyTimerText.setText('Iron Body: ' + remainingTime + 's');
ironBodyTimerText.visible = true;
ironBodyValueText.setText('90% Dmg Reduction + 3x Push Back');
ironBodyValueText.visible = true;
}
} else {
ironBodyTimerText.visible = false;
ironBodyValueText.visible = false;
}
if (healingNeedleActive) {
if (LK.ticks >= healingNeedleEndTime) {
healingNeedleActive = false;
healingNeedleTimerText.visible = false;
healingNeedleValueText.visible = false;
} else {
var remainingTime = Math.ceil((healingNeedleEndTime - LK.ticks) / 60);
healingNeedleTimerText.setText('Healing: ' + remainingTime + 's');
healingNeedleTimerText.visible = true;
healingNeedleValueText.setText('Heal on Hit');
healingNeedleValueText.visible = true;
}
} else {
healingNeedleTimerText.visible = false;
healingNeedleValueText.visible = false;
}
if (duplicateActive) {
if (LK.ticks >= duplicateEndTime) {
duplicateActive = false;
// Make clone explode when timer expires
if (currentPlayerClone) {
currentPlayerClone.explode();
currentPlayerClone = null;
}
duplicateTimerText.visible = false;
duplicateValueText.visible = false;
} else {
var remainingTime = Math.ceil((duplicateEndTime - LK.ticks) / 60);
duplicateTimerText.setText('Clone: ' + remainingTime + 's');
duplicateTimerText.visible = true;
duplicateValueText.setText('Clone Fighter');
duplicateValueText.visible = true;
}
} else {
duplicateTimerText.visible = false;
duplicateValueText.visible = false;
}
// Spawn green enemies during boss fights every 1 minute
if (gameState === 'playing' && currentBoss) {
bossGreenEnemyTimer++;
if (bossGreenEnemyTimer >= bossGreenEnemyInterval) {
// Spawn one green enemy at random location on the map
var greenEnemy = new GreenEnemy();
var validPosition = false;
var attempts = 0;
// Try to find non-overlapping position anywhere on the map
while (!validPosition && attempts < 50) {
// Spawn at random location on the map
greenEnemy.x = Math.random() * 2048;
greenEnemy.y = Math.random() * 2732;
// Check if position is far enough from player and boss
validPosition = true;
if (player) {
var dx = greenEnemy.x - player.x;
var dy = greenEnemy.y - player.y;
var distanceToPlayer = Math.sqrt(dx * dx + dy * dy);
if (distanceToPlayer < 200) {
validPosition = false;
}
}
if (currentBoss && validPosition) {
var dx = greenEnemy.x - currentBoss.x;
var dy = greenEnemy.y - currentBoss.y;
var distanceToBoss = Math.sqrt(dx * dx + dy * dy);
if (distanceToBoss < 200) {
validPosition = false;
}
}
// Check if position overlaps with existing enemies
for (var e = 0; e < enemies.length && validPosition; e++) {
var dx = greenEnemy.x - enemies[e].x;
var dy = greenEnemy.y - enemies[e].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
validPosition = false;
}
}
for (var g = 0; g < greenEnemies.length && validPosition; g++) {
var dx = greenEnemy.x - greenEnemies[g].x;
var dy = greenEnemy.y - greenEnemies[g].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
validPosition = false;
}
}
attempts++;
}
greenEnemies.push(greenEnemy);
game.addChild(greenEnemy);
// Reset timer for next spawn
bossGreenEnemyTimer = 0;
}
}
// Clean up excess bullets during machine gun usage to prevent performance issues
if (machineGunActive && playerBullets.length > 40) {
// Remove oldest bullets that are off-screen or furthest from enemies
var bulletsToRemove = [];
for (var i = 0; i < playerBullets.length; i++) {
var bullet = playerBullets[i];
// Remove bullets that are far off-screen
if (bullet.x < -200 || bullet.x > 2248 || bullet.y < -200 || bullet.y > 2932) {
bulletsToRemove.push(i);
}
}
// Remove bullets starting from the end to avoid index issues
for (var r = bulletsToRemove.length - 1; r >= 0; r--) {
var bulletIndex = bulletsToRemove[r];
if (playerBullets[bulletIndex]) {
playerBullets[bulletIndex].destroy();
playerBullets.splice(bulletIndex, 1);
}
}
}
// Check if all enemies are defeated and no boss exists
if (gameState === 'playing' && enemies.length === 0 && greenEnemies.length === 0 && !currentBoss) {
// Progress to next level first
currentLevel++;
levelText.setText('Level: ' + currentLevel);
enemiesKilled = 0;
// Check win condition at level 200
if (currentLevel > 200) {
LK.showYouWin();
return;
}
// Check if current level requires shop or boss
if (currentLevel % 10 === 0) {
// Boss levels (10, 20, 30, etc.) - show shop first, then boss
showShop();
} else if (currentLevel % 5 === 0) {
// Non-boss multiples of 5 (5, 15, 25, etc.) - show shop
showShop();
} else {
// Regular levels - spawn enemies
spawnEnemies();
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
musicEnabled: true
});
/****
* Classes
****/
var Boss = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
// Calculate total health of enemies from previous level (currentLevel - 1)
var previousLevel = Math.max(1, currentLevel - 1);
var enemyMaxHealth = 40 + (previousLevel - 1) * 8;
var enemiesInPreviousLevel = Math.floor(enemiesPerLevel * (1 + Math.floor((previousLevel - 1) / 5) * 0.4));
var totalEnemyHealth = enemyMaxHealth * enemiesInPreviousLevel;
self.maxHealth = totalEnemyHealth * 3;
self.health = self.maxHealth;
self.speed = 0.8 + Math.max(0, currentLevel - 10) * 0.1;
var baseDamage = 30;
var bossNumber = Math.floor(currentLevel / 10); // Boss 1 at level 10, Boss 2 at level 20, etc.
var damageMultiplier = 1 + (bossNumber - 1) * 0.5; // 50% increase per boss
self.damage = Math.floor(baseDamage * damageMultiplier);
self.lastAttack = 0;
// Create boss health bar background
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 30
});
self.healthBarBg.y = -80;
self.addChild(self.healthBarBg);
// Create boss health bar fill
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5,
width: 196,
height: 26
});
self.healthBarFill.x = -98;
self.healthBarFill.y = -80;
self.addChild(self.healthBarFill);
self.attackCooldown = 120;
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
// Apply blue tint when frozen
if (graphics.tint !== 0x4444ff) {
graphics.tint = 0x4444ff;
}
} else {
// Remove blue tint when not frozen
if (graphics.tint !== 0xffffff) {
graphics.tint = 0xffffff;
}
}
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * effectiveSpeed;
self.y += dy / distance * effectiveSpeed;
}
// Attack player on physical contact only
self.lastAttack++;
if (self.lastAttack >= self.attackCooldown) {
// Determine number of bullets based on health
var bulletsToFire = self.health <= self.maxHealth / 2 ? 3 : 1;
// Shoot bullets at player
for (var b = 0; b < bulletsToFire; b++) {
var bullet = new BossBullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Add spread for multiple bullets
var spreadAngle = 0;
if (bulletsToFire > 1) {
spreadAngle = (b - (bulletsToFire - 1) / 2) * 0.3; // Spread bullets
}
var angle = Math.atan2(dy, dx) + spreadAngle;
bullet.velocityX = Math.cos(angle) * 6;
bullet.velocityY = Math.sin(angle) * 6;
// Set bullet rotation to face movement direction
if (bullet.setRotation) {
bullet.setRotation(bullet.velocityX, bullet.velocityY);
}
}
bossBullets.push(bullet);
game.addChild(bullet);
}
if (musicEnabled) LK.getSound('shoot').play();
self.lastAttack = 0;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
// Update boss health bar
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 196 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Play explosion sound
if (musicEnabled) LK.getSound('explosion').play();
// Create massive explosion effect with multiple waves of particles
for (var e = 0; e < 8; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 60 + e * 25,
height: 60 + e * 25,
alpha: 1.0 - e * 0.1,
tint: e === 0 ? 0xFF0000 : e === 1 ? 0xFF2200 : e === 2 ? 0xFF4500 : e === 3 ? 0xFF6600 : e === 4 ? 0xFF8800 : e === 5 ? 0xFFAA00 : e === 6 ? 0xFFCC00 : 0xFFFF00
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 80;
explosionCircle.y = self.y + (Math.random() - 0.5) * 80;
game.addChild(explosionCircle);
// Animate explosion with dramatic scaling and movement
tween(explosionCircle, {
scaleX: 5 + e * 0.8,
scaleY: 5 + e * 0.8,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 200,
y: explosionCircle.y + (Math.random() - 0.5) * 200
}, {
duration: 800 + e * 150,
easing: e % 3 === 0 ? tween.easeOut : e % 3 === 1 ? tween.easeInOut : tween.bounceOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Drop guaranteed 10-20 coins
var numCoins = 10 + Math.floor(Math.random() * 11); // 10-20 coins
for (var i = 0; i < numCoins; i++) {
var coin = new Coin();
// Calculate coin value - first 5 levels = 10, then +5 every 5 levels
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
coin.value = 10; // Levels 1-5
} else {
coin.value = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
coin.x = self.x + (Math.random() - 0.5) * 150;
coin.y = self.y + (Math.random() - 0.5) * 150;
coins.push(coin);
game.addChild(coin);
}
// Drop permanent bullet upgrade
var bulletUpgrade = new BulletUpgrade();
bulletUpgrade.x = self.x + (Math.random() - 0.5) * 100;
bulletUpgrade.y = self.y + (Math.random() - 0.5) * 100;
powerUps.push(bulletUpgrade);
game.addChild(bulletUpgrade);
// Guaranteed diamond drop
var diamond1 = new Diamond();
// Calculate diamond value - 5x current coin value
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
diamond1.value = coinValue * 5; // Boss diamonds worth 5x current coin value
diamond1.x = self.x + (Math.random() - 0.5) * 100;
diamond1.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond1);
game.addChild(diamond1);
// 50% chance for 2 additional diamonds
if (Math.random() < 0.50) {
for (var d = 0; d < 2; d++) {
var diamond = new Diamond();
// Calculate diamond value - 5x current coin value
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
diamond.value = coinValue * 5;
diamond.x = self.x + (Math.random() - 0.5) * 100;
diamond.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond);
game.addChild(diamond);
}
}
// 50% chance for ember drop
if (Math.random() < 0.50) {
var ember = new Ember();
ember.x = self.x + (Math.random() - 0.5) * 100;
ember.y = self.y + (Math.random() - 0.5) * 100;
embers.push(ember);
game.addChild(ember);
}
currentBoss = null;
// Stop boss fight music and start gameplay music
if (musicEnabled) {
LK.stopMusic();
LK.playMusic('3', {
loop: true
});
}
self.destroy();
// Boss is defeated, level progression will be handled in main update loop
enemiesKilled = 0;
};
return self;
});
var BossBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.damage = 15;
// Set rotation method for bullet orientation
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with player using precise distance calculation
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (player.width + self.width) / 8; // Tight collision bounds
if (distance <= collisionDistance) {
player.takeDamage(self.damage);
LK.getSound('playerHit').play();
self.removeFromGame();
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < bossBullets.length; i++) {
if (bossBullets[i] === self) {
bossBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var BulletUpgrade = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFD700,
scaleX: 1.5,
scaleY: 1.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.update = function () {
// Never expire - permanent upgrade
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// Permanently increase fire ball bullet count (maximum 3)
if (fireBallBulletCount < 3) {
fireBallBulletCount++;
}
if (musicEnabled) LK.getSound('coinCollect').play();
// Remove from powerUps array
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
// Calculate coin value - first 5 levels = 10, then +5 every 5 levels
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
self.value = 10; // Levels 1-5
} else {
self.value = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
self.creationTime = LK.ticks; // Track when coin was created
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this coin came from a green enemy
// Start continuous Y-axis rotation
self.startRotation = function () {
tween(graphics, {
scaleX: -1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.expirationStarted) {
self.startRotation();
}
}
});
}
});
};
self.startRotation();
self.update = function () {
// Check for expiration after 30 seconds (30 * 60 = 1800 ticks at 60 FPS)
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
// Start fade out animation over 1 second
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
// Remove from coins array
for (var i = 0; i < coins.length; i++) {
if (coins[i] === self) {
coins.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
// Check if coin is within player's attack range for magnetism
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4; // Tight collision bounds for collection
// Only collect on precise physical contact using distance calculation
if (distance <= collectionDistance) {
var coinValue = self.value;
if (wealthActive) {
coinValue *= 2; // Double value during wealth effect
}
playerCoins += coinValue;
coinText.setText(formatCurrency(playerCoins));
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
// Create floating value text
var displayValue = wealthActive ? self.value * 2 : self.value;
var valueText = new Text2('+' + displayValue, {
size: 45,
fill: 0x00ff00
});
valueText.anchor.set(0.5, 0.5);
valueText.x = self.x;
valueText.y = self.y;
game.addChild(valueText);
// Animate the value text floating up and fading out
tween(valueText, {
y: valueText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
valueText.destroy();
}
});
// Remove from coins array
for (var i = 0; i < coins.length; i++) {
if (coins[i] === self) {
coins.splice(i, 1);
break;
}
}
self.destroy();
} else if (magnetActive || distance <= player.attackRange && distance > collectionDistance) {
// Coin is within magnet range (entire scene) or attack range but not collected yet - apply magnetism
if (!self.magnetismActive) {
self.magnetismActive = true;
// Stop any existing movement tweens
tween.stop(self, {
x: true,
y: true
});
}
// Calculate target position with much stronger pull when magnet is active
var pullStrength = magnetActive ? 0.25 : 0.04; // Much stronger pull with magnet
var targetX = self.x + dx * pullStrength;
var targetY = self.y + dy * pullStrength;
// Smoothly move toward player using tween
tween(self, {
x: targetX,
y: targetY
}, {
duration: magnetActive ? 100 : 400,
easing: tween.easeOut
});
} else if (!magnetActive && distance > player.attackRange) {
// Coin is outside attack range and magnet is not active - stop magnetism
if (self.magnetismActive) {
self.magnetismActive = false;
tween.stop(self, {
x: true,
y: true
});
}
}
}
};
return self;
});
var Diamond = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('diamond', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 10;
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this diamond came from a green enemy
// Start continuous Y-axis rotation
self.startRotation = function () {
tween(graphics, {
scaleX: -1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.expirationStarted) {
self.startRotation();
}
}
});
}
});
};
self.startRotation();
self.update = function () {
// Check for expiration after 45 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 2700) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < diamonds.length; i++) {
if (diamonds[i] === self) {
diamonds.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
var diamondValue = self.value;
if (wealthActive) {
diamondValue *= 2; // Double value during wealth effect
}
playerCoins += diamondValue;
coinText.setText(formatCurrency(playerCoins));
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
// Create floating value text
var displayValue = wealthActive ? self.value * 2 : self.value;
var valueText = new Text2('+' + displayValue, {
size: 45,
fill: 0x00ff00
});
valueText.anchor.set(0.5, 0.5);
valueText.x = self.x;
valueText.y = self.y;
game.addChild(valueText);
// Animate the value text floating up and fading out
tween(valueText, {
y: valueText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
valueText.destroy();
}
});
for (var i = 0; i < diamonds.length; i++) {
if (diamonds[i] === self) {
diamonds.splice(i, 1);
break;
}
}
self.destroy();
} else if (magnetActive || distance <= player.attackRange && distance > collectionDistance) {
if (!self.magnetismActive) {
self.magnetismActive = true;
tween.stop(self, {
x: true,
y: true
});
}
var pullStrength = magnetActive ? 0.25 : 0.04;
var targetX = self.x + dx * pullStrength;
var targetY = self.y + dy * pullStrength;
tween(self, {
x: targetX,
y: targetY
}, {
duration: magnetActive ? 100 : 400,
easing: tween.easeOut
});
} else if (!magnetActive && distance > player.attackRange) {
if (self.magnetismActive) {
self.magnetismActive = false;
tween.stop(self, {
x: true,
y: true
});
}
}
}
};
return self;
});
var Duplicate = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('duplicate', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this duplicate came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// Activate duplicate effect
if (duplicateActive) {
duplicateEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate duplicate effect for first time
duplicateActive = true;
duplicateEndTime = LK.ticks + 1800; // 30 seconds
// Create the clone
createPlayerClone();
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var Ember = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('ember', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF8C00 // Orange color for ember
});
// Calculate ember value - 10x current coin value
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
self.value = 100; // 10x level 1-5 coin value (10 * 10)
} else {
self.value = (10 + levelGroup * 5) * 10; // 10x current coin value
}
self.creationTime = LK.ticks;
self.expirationStarted = false;
// Start continuous Y-axis rotation with orange glow effect
self.startRotation = function () {
tween(graphics, {
scaleX: -1.2,
tint: 0xFFD700 // Gold glow effect
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1.2,
tint: 0xFF8C00 // Back to orange
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.expirationStarted) {
self.startRotation();
}
}
});
}
});
};
self.startRotation();
self.update = function () {
// Check for expiration after 45 seconds (like diamonds)
if (!self.expirationStarted && LK.ticks - self.creationTime >= 2700) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < embers.length; i++) {
if (embers[i] === self) {
embers.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
var emberValue = self.value;
if (wealthActive) {
emberValue *= 2; // Double value during wealth effect
}
playerCoins += emberValue;
coinText.setText(formatCurrency(playerCoins));
LK.getSound('coinCollect').play();
// Create floating value text with orange color
var displayValue = wealthActive ? self.value * 2 : self.value;
var valueText = new Text2('+' + displayValue + ' EMBER', {
size: 50,
fill: 0xFF8C00
});
valueText.anchor.set(0.5, 0.5);
valueText.x = self.x;
valueText.y = self.y;
game.addChild(valueText);
// Animate the value text floating up and fading out
tween(valueText, {
y: valueText.y - 100,
alpha: 0
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
valueText.destroy();
}
});
// Remove from embers array
for (var i = 0; i < embers.length; i++) {
if (embers[i] === self) {
embers.splice(i, 1);
break;
}
}
self.destroy();
} else if (magnetActive || distance <= player.attackRange && distance > collectionDistance) {
if (!self.magnetismActive) {
self.magnetismActive = true;
tween.stop(self, {
x: true,
y: true
});
}
var pullStrength = magnetActive ? 0.25 : 0.04;
var targetX = self.x + dx * pullStrength;
var targetY = self.y + dy * pullStrength;
tween(self, {
x: targetX,
y: targetY
}, {
duration: magnetActive ? 100 : 400,
easing: tween.easeOut
});
} else if (!magnetActive && distance > player.attackRange) {
if (self.magnetismActive) {
self.magnetismActive = false;
tween.stop(self, {
x: true,
y: true
});
}
}
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHealth = 40 + (currentLevel - 1) * 8;
self.health = self.maxHealth;
self.speed = 1.5 + (currentLevel - 1) * 0.15;
self.damage = 10 * (1 + (currentLevel - 1) * 0.1);
self.lastAttack = 0;
self.attackCooldown = 120;
// Create enemy health bar background (simple rectangle)
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBg.y = -70;
self.addChild(self.healthBarBg);
// Create enemy health bar fill (simple rectangle)
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5
});
self.healthBarFill.x = -59;
self.healthBarFill.y = -70;
self.addChild(self.healthBarFill);
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
// Apply blue tint when frozen
if (graphics.tint !== 0x4444ff) {
graphics.tint = 0x4444ff;
}
} else {
// Remove blue tint when not frozen
if (graphics.tint !== 0xffffff) {
graphics.tint = 0xffffff;
}
}
// Move towards player with optimized collision avoidance
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Calculate base movement direction toward player
var targetAngle = Math.atan2(dy, dx);
var baseMovementX = Math.cos(targetAngle) * effectiveSpeed;
var baseMovementY = Math.sin(targetAngle) * effectiveSpeed;
// Only check collision avoidance every 5 frames to reduce CPU load
if (LK.ticks % 5 === 0) {
// Calculate avoidance vector
var avoidanceX = 0;
var avoidanceY = 0;
var nearbyEnemyCount = 0;
var maxChecks = Math.min(5, enemies.length); // Limit checks to 5 nearest enemies
// Check collision with limited number of other enemies
for (var e = 0; e < maxChecks && e < enemies.length; e++) {
var otherEnemy = enemies[e];
if (otherEnemy !== self) {
var otherDx = self.x - otherEnemy.x;
var otherDy = self.y - otherEnemy.y;
// Use faster distance check (no sqrt for initial filtering)
var otherDistanceSq = otherDx * otherDx + otherDy * otherDy;
if (otherDistanceSq < 10000 && otherDistanceSq > 0) {
// 100^2 = 10000
var otherDistance = Math.sqrt(otherDistanceSq);
// Calculate avoidance force that gets stronger as enemies get closer
var avoidanceStrength = (100 - otherDistance) / 100;
avoidanceX += otherDx / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
avoidanceY += otherDy / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
nearbyEnemyCount++;
}
}
}
// Check collision with limited number of green enemies
var maxGreenChecks = Math.min(3, greenEnemies.length); // Limit green enemy checks
for (var g = 0; g < maxGreenChecks && g < greenEnemies.length; g++) {
var otherGreen = greenEnemies[g];
if (otherGreen !== self) {
var otherDx = self.x - otherGreen.x;
var otherDy = self.y - otherGreen.y;
var otherDistanceSq = otherDx * otherDx + otherDy * otherDy;
if (otherDistanceSq < 10000 && otherDistanceSq > 0) {
var otherDistance = Math.sqrt(otherDistanceSq);
var avoidanceStrength = (100 - otherDistance) / 100;
avoidanceX += otherDx / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
avoidanceY += otherDy / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
nearbyEnemyCount++;
}
}
}
// Store avoidance for next few frames
self.cachedAvoidanceX = avoidanceX;
self.cachedAvoidanceY = avoidanceY;
self.cachedNearbyCount = nearbyEnemyCount;
}
// Combine movement and avoidance forces
var finalMovementX = baseMovementX;
var finalMovementY = baseMovementY;
if (self.cachedNearbyCount > 0) {
// Add cached avoidance when enemies are nearby
finalMovementX += (self.cachedAvoidanceX || 0) * 0.8; // Strong avoidance
finalMovementY += (self.cachedAvoidanceY || 0) * 0.8;
// Normalize the final movement to maintain consistent speed
var finalDistance = Math.sqrt(finalMovementX * finalMovementX + finalMovementY * finalMovementY);
if (finalDistance > effectiveSpeed) {
finalMovementX = finalMovementX / finalDistance * effectiveSpeed;
finalMovementY = finalMovementY / finalDistance * effectiveSpeed;
}
}
// Apply final movement
self.x += finalMovementX;
self.y += finalMovementY;
}
// Attack player on physical contact only
self.lastAttack++;
if (self.lastAttack >= self.attackCooldown) {
// Only damage on direct collision using precise distance calculation
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (player.width + self.width) / 8; // Much tighter collision bounds
if (distance <= collisionDistance) {
player.takeDamage(self.damage);
LK.getSound('playerHit').play();
self.lastAttack = 0;
// Push back enemy by a small amount (larger if Iron Body is active)
var pushDistance = ironBodyActive ? 135 : 45; // 3x stronger pushback with Iron Body
var pushbackX = self.x - dx / distance * pushDistance;
var pushbackY = self.y - dy / distance * pushDistance;
// Use tween to animate the pushback
tween(self, {
x: pushbackX,
y: pushbackY
}, {
duration: 200,
easing: tween.easeOut
});
}
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
// Update enemy health bar
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 118 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Prevent multiple calls to die() for the same enemy
if (self.isDead) {
return;
}
self.isDead = true;
// Store position for item drops
var deathX = self.x;
var deathY = self.y;
// Enemy death animation - shrinking puff effect
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create soft puff particles after shrinking
for (var e = 0; e < 4; e++) {
var puffCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 20 + e * 10,
height: 20 + e * 10,
alpha: 0.4 - e * 0.08,
tint: e === 0 ? 0xFFFFFF : e === 1 ? 0xF8F8F8 : e === 2 ? 0xF0F0F0 : 0xE8E8E8
});
puffCircle.x = deathX + (Math.random() - 0.5) * 30;
puffCircle.y = deathY + (Math.random() - 0.5) * 30;
game.addChild(puffCircle);
// Animate puff with gentle expansion and fade
tween(puffCircle, {
scaleX: 2.2 + e * 0.3,
scaleY: 2.2 + e * 0.3,
alpha: 0,
y: puffCircle.y - 20 - e * 8
}, {
duration: 500 + e * 120,
easing: tween.easeOut,
onFinish: function onFinish() {
puffCircle.destroy();
}
});
}
// Drop items immediately after puff effect starts
// Calculate coin value - first 5 levels = 10, then +5 every 5 levels
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
// Drop coin with doubled value every 10 levels
var coin = new Coin();
coin.value = coinValue;
coin.x = deathX;
coin.y = deathY;
coins.push(coin);
game.addChild(coin);
// Drop ember (special orange coin) - only starts from level 20
if (currentLevel > 20) {
var emberChance = 0.02; // 2% base chance after level 20
if (currentLevel >= 50) {
emberChance = 0.15; // 15% chance at level 50+
} else if (currentLevel >= 40) {
emberChance = 0.10; // 10% chance at level 40-49
}
if (Math.random() < emberChance) {
var ember = new Ember();
ember.x = deathX + (Math.random() - 0.5) * 60;
ember.y = deathY + (Math.random() - 0.5) * 60;
embers.push(ember);
game.addChild(ember);
}
}
// Drop diamond (5% chance, 30% during wealth effect)
var diamondChance = wealthActive ? 0.30 : 0.05;
if (Math.random() < diamondChance) {
var diamond = new Diamond();
diamond.value = coinValue * 3; // 3x current coin value
diamond.x = deathX + (Math.random() - 0.5) * 60;
diamond.y = deathY + (Math.random() - 0.5) * 60;
diamonds.push(diamond);
game.addChild(diamond);
}
// Rarely drop health pack (5% chance)
if (Math.random() < 0.05) {
var healthPack = new HealthPack();
healthPack.x = deathX + (Math.random() - 0.5) * 60;
healthPack.y = deathY + (Math.random() - 0.5) * 60;
healthPacks.push(healthPack);
game.addChild(healthPack);
}
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
enemiesKilled++;
remainingEnemies--;
}
});
};
return self;
});
var FreezeBomb = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('freezeBomb', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this freeze bomb came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If freeze effect is already active, only refresh the timer
if (freezeEffectActive) {
freezeEffectEndTime = LK.ticks + 1200; // 20 seconds
} else {
// Activate freeze effect for first time
freezeEffectActive = true;
freezeEffectEndTime = LK.ticks + 1200; // 20 seconds
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var GreenEnemy = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('greenEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHealth = 40 + (currentLevel - 1) * 8; // Same health as regular enemies
self.health = self.maxHealth;
self.speed = 1.5 + (currentLevel - 1) * 0.15; // Same speed as regular enemies
self.damage = 10 * (1 + (currentLevel - 1) * 0.1); // Same damage as regular enemies
self.lastAttack = 0;
self.attackCooldown = 120;
// Create enemy health bar background
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBg.y = -70;
self.addChild(self.healthBarBg);
// Create enemy health bar fill
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5
});
self.healthBarFill.x = -59;
self.healthBarFill.y = -70;
self.addChild(self.healthBarFill);
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
// Apply blue tint when frozen
if (graphics.tint !== 0x4444ff) {
graphics.tint = 0x4444ff;
}
} else {
// Remove blue tint when not frozen (return to green color)
if (graphics.tint !== 0x00ff00) {
graphics.tint = 0x00ff00;
}
}
// Move towards player with optimized collision avoidance
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Calculate base movement direction toward player
var targetAngle = Math.atan2(dy, dx);
var baseMovementX = Math.cos(targetAngle) * effectiveSpeed;
var baseMovementY = Math.sin(targetAngle) * effectiveSpeed;
// Only check collision avoidance every 5 frames to reduce CPU load
if (LK.ticks % 5 === 0) {
// Calculate avoidance vector
var avoidanceX = 0;
var avoidanceY = 0;
var nearbyEnemyCount = 0;
var maxChecks = Math.min(5, enemies.length); // Limit checks to 5 nearest enemies
// Check collision with limited number of regular enemies
for (var e = 0; e < maxChecks && e < enemies.length; e++) {
var otherEnemy = enemies[e];
var otherDx = self.x - otherEnemy.x;
var otherDy = self.y - otherEnemy.y;
// Use faster distance check (no sqrt for initial filtering)
var otherDistanceSq = otherDx * otherDx + otherDy * otherDy;
if (otherDistanceSq < 10000 && otherDistanceSq > 0) {
// 100^2 = 10000
var otherDistance = Math.sqrt(otherDistanceSq);
// Calculate avoidance force that gets stronger as enemies get closer
var avoidanceStrength = (100 - otherDistance) / 100;
avoidanceX += otherDx / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
avoidanceY += otherDy / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
nearbyEnemyCount++;
}
}
// Check collision with limited number of other green enemies
var maxGreenChecks = Math.min(3, greenEnemies.length); // Limit green enemy checks
for (var g = 0; g < maxGreenChecks && g < greenEnemies.length; g++) {
var otherGreen = greenEnemies[g];
if (otherGreen !== self) {
var otherDx = self.x - otherGreen.x;
var otherDy = self.y - otherGreen.y;
var otherDistanceSq = otherDx * otherDx + otherDy * otherDy;
if (otherDistanceSq < 10000 && otherDistanceSq > 0) {
var otherDistance = Math.sqrt(otherDistanceSq);
var avoidanceStrength = (100 - otherDistance) / 100;
avoidanceX += otherDx / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
avoidanceY += otherDy / otherDistance * avoidanceStrength * effectiveSpeed * 1.5;
nearbyEnemyCount++;
}
}
}
// Store avoidance for next few frames
self.cachedAvoidanceX = avoidanceX;
self.cachedAvoidanceY = avoidanceY;
self.cachedNearbyCount = nearbyEnemyCount;
}
// Combine movement and avoidance forces
var finalMovementX = baseMovementX;
var finalMovementY = baseMovementY;
if (self.cachedNearbyCount > 0) {
// Add cached avoidance when enemies are nearby
finalMovementX += (self.cachedAvoidanceX || 0) * 0.8; // Strong avoidance
finalMovementY += (self.cachedAvoidanceY || 0) * 0.8;
// Normalize the final movement to maintain consistent speed
var finalDistance = Math.sqrt(finalMovementX * finalMovementX + finalMovementY * finalMovementY);
if (finalDistance > effectiveSpeed) {
finalMovementX = finalMovementX / finalDistance * effectiveSpeed;
finalMovementY = finalMovementY / finalDistance * effectiveSpeed;
}
}
// Apply final movement
self.x += finalMovementX;
self.y += finalMovementY;
}
// Attack player on physical contact
self.lastAttack++;
if (self.lastAttack >= self.attackCooldown) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (player.width + self.width) / 8;
if (distance <= collisionDistance) {
player.takeDamage(self.damage);
LK.getSound('playerHit').play();
self.lastAttack = 0;
var pushDistance = ironBodyActive ? 135 : 45; // 3x stronger pushback with Iron Body
var pushbackX = self.x - dx / distance * pushDistance;
var pushbackY = self.y - dy / distance * pushDistance;
tween(self, {
x: pushbackX,
y: pushbackY
}, {
duration: 200,
easing: tween.easeOut
});
}
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 118 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Prevent multiple calls to die() for the same enemy
if (self.isDead) {
return;
}
self.isDead = true;
var deathX = self.x;
var deathY = self.y;
// Death animation
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Puff effect
for (var e = 0; e < 4; e++) {
var puffCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 20 + e * 10,
height: 20 + e * 10,
alpha: 0.4 - e * 0.08,
tint: e === 0 ? 0x00FF00 : e === 1 ? 0x44FF44 : e === 2 ? 0x88FF88 : 0xAAFFAA
});
puffCircle.x = deathX + (Math.random() - 0.5) * 30;
puffCircle.y = deathY + (Math.random() - 0.5) * 30;
game.addChild(puffCircle);
tween(puffCircle, {
scaleX: 2.2 + e * 0.3,
scaleY: 2.2 + e * 0.3,
alpha: 0,
y: puffCircle.y - 20 - e * 8
}, {
duration: 500 + e * 120,
easing: tween.easeOut,
onFinish: function onFinish() {
puffCircle.destroy();
}
});
}
// Drop ember (special orange coin) - only starts from level 20
if (currentLevel > 20) {
var emberChance = 0.02; // 2% base chance after level 20
if (currentLevel >= 50) {
emberChance = 0.15; // 15% chance at level 50+
} else if (currentLevel >= 40) {
emberChance = 0.10; // 10% chance at level 40-49
}
if (Math.random() < emberChance) {
var ember = new Ember();
ember.x = deathX + (Math.random() - 0.5) * 60;
ember.y = deathY + (Math.random() - 0.5) * 60;
embers.push(ember);
game.addChild(ember);
}
}
// Drop diamond (5% chance, 30% during wealth effect)
var diamondChance = wealthActive ? 0.30 : 0.05;
if (Math.random() < diamondChance) {
// Calculate coin value - first 5 levels = 10, then +5 every 5 levels
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
var diamond = new Diamond();
diamond.value = coinValue * 3; // 3x current coin value
diamond.fromGreenEnemy = true; // Mark as from green enemy
diamond.x = deathX + (Math.random() - 0.5) * 60;
diamond.y = deathY + (Math.random() - 0.5) * 60;
diamonds.push(diamond);
game.addChild(diamond);
}
// 100% chance to drop power-up with equal chances for all items
if (Math.random() < 1.0) {
var powerUpTypes = [Magnet, Speed, Wealth, IronBody, HealingNeedle, MachineGun, RocketLauncher, FreezeBomb, Duplicate];
// Use equal distribution by ensuring each item has exactly the same chance
var randomValue = Math.random();
var itemChance = 1.0 / powerUpTypes.length; // Each item gets exactly equal chance
var selectedIndex = Math.floor(randomValue / itemChance);
// Ensure we don't go out of bounds due to floating point precision
selectedIndex = Math.min(selectedIndex, powerUpTypes.length - 1);
var powerUp = new powerUpTypes[selectedIndex]();
powerUp.x = deathX;
powerUp.y = deathY;
// Mark ALL items as from green enemy
powerUp.fromGreenEnemy = true;
powerUps.push(powerUp);
game.addChild(powerUp);
}
// Remove from greenEnemies array
for (var i = 0; i < greenEnemies.length; i++) {
if (greenEnemies[i] === self) {
greenEnemies.splice(i, 1);
break;
}
}
self.destroy();
enemiesKilled++;
remainingEnemies--;
}
});
};
return self;
});
var HealingNeedle = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('healingNeedle', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this healing needle came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If healing needle is already active, only refresh the timer
if (healingNeedleActive) {
healingNeedleEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate healing needle effect for first time
healingNeedleActive = true;
healingNeedleEndTime = LK.ticks + 1800; // 30 seconds
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var HealingNeedleBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('healingNeedleBullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply special healing needle visual effect
graphics.tint = 0x00FF88; // Bright green healing color
graphics.scaleX = 1.2;
graphics.scaleY = 1.2;
// Add pulsing glow effect
tween(graphics, {
scaleX: 1.4,
scaleY: 1.4,
tint: 0xFFD700
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0x00FF88
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
self.velocityX = 0;
self.velocityY = 0;
self.damage = 20;
// Set rotation method for bullet orientation
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
enemy.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting enemies
if (player) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = enemy.x;
healText.y = enemy.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with green enemies
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
if (self.intersects(greenEnemy)) {
greenEnemy.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting green enemies
if (player) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = greenEnemy.x;
healText.y = greenEnemy.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with boss
if (currentBoss && self.intersects(currentBoss)) {
currentBoss.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting boss
if (player && currentBoss) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = currentBoss.x;
healText.y = currentBoss.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
}
};
self.removeFromGame = function () {
for (var i = 0; i < playerBullets.length; i++) {
if (playerBullets[i] === self) {
playerBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var HealthPack = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('healthPack', {
anchorX: 0.5,
anchorY: 0.5
});
self.healAmount = 0.20; // 20% of max health
self.creationTime = LK.ticks; // Track when health pack was created
self.expirationStarted = false;
// Start continuous Y-axis rotation
self.startRotation = function () {
tween(graphics, {
scaleX: -1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.expirationStarted) {
self.startRotation();
}
}
});
}
});
};
self.startRotation();
self.update = function () {
// Check for expiration after 1 minute (60 * 60 = 3600 ticks at 60 FPS)
if (!self.expirationStarted && LK.ticks - self.creationTime >= 3600) {
self.expirationStarted = true;
// Start fade out animation over 1 second
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
// Remove from healthPacks array
for (var i = 0; i < healthPacks.length; i++) {
if (healthPacks[i] === self) {
healthPacks.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player && player.health < player.maxHealth) {
// Only collect on precise physical contact using distance calculation
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4; // Tight collision bounds for collection
// Apply magnet effect if active and player is not at full health (affects entire scene)
if (magnetActive && distance > collectionDistance) {
if (!self.magnetismActive) {
self.magnetismActive = true;
tween.stop(self, {
x: true,
y: true
});
}
var pullStrength = 0.25;
var targetX = self.x + dx * pullStrength;
var targetY = self.y + dy * pullStrength;
tween(self, {
x: targetX,
y: targetY
}, {
duration: 100,
easing: tween.easeOut
});
} else if (!magnetActive) {
if (self.magnetismActive) {
self.magnetismActive = false;
tween.stop(self, {
x: true,
y: true
});
}
}
if (distance <= collectionDistance) {
var healValue = Math.floor(player.maxHealth * self.healAmount);
player.health = Math.min(player.maxHealth, player.health + healValue);
LK.getSound('coinCollect').play();
// Remove from healthPacks array
for (var i = 0; i < healthPacks.length; i++) {
if (healthPacks[i] === self) {
healthPacks.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var IronBody = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('ironBody', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this iron body came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If iron body is already active, only refresh the timer
if (ironBodyActive) {
ironBodyEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate iron body effect for first time
ironBodyActive = true;
ironBodyEndTime = LK.ticks + 1800; // 30 seconds
// Change player appearance to iron body version
if (player) {
var currentPlayerGraphics = player.children[0]; // First child is the player graphics
currentPlayerGraphics.visible = false; // Hide original player
// Create iron body player graphics if it doesn't exist
if (!player.ironBodyGraphics) {
player.ironBodyGraphics = player.attachAsset('ironBodyPlayer', {
anchorX: 0.5,
anchorY: 0.5
});
}
player.ironBodyGraphics.visible = true; // Show iron body appearance
}
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
// Note: Game title "Fight Bacteria" should be set at the engine/platform level
// The LK engine handles game metadata automatically
var Level30Boss = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('level30Boss', {
anchorX: 0.5,
anchorY: 0.5
});
// Calculate damage from previous level enemies (level 29)
var previousLevel = Math.max(1, currentLevel - 1);
var previousLevelDamage = 10 * (1 + (previousLevel - 1) * 0.1);
self.damage = previousLevelDamage;
// Health is 3x the damage value
self.maxHealth = self.damage * 3;
self.health = self.maxHealth;
self.speed = 0.8 + Math.max(0, currentLevel - 10) * 0.1;
self.lastAttack = 0;
self.attackCooldown = 60; // 1 second at 60 FPS
self.lastClone = 0;
self.cloneCooldown = 3600; // 60 seconds at 60 FPS
self.hasCloned = false;
// Create boss health bar background
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 30
});
self.healthBarBg.y = -80;
self.addChild(self.healthBarBg);
// Create boss health bar fill
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5,
width: 196,
height: 26
});
self.healthBarFill.x = -98;
self.healthBarFill.y = -80;
self.addChild(self.healthBarFill);
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
if (graphics.tint !== 0x4444ff) {
graphics.tint = 0x4444ff;
}
} else {
if (graphics.tint !== 0xffffff) {
graphics.tint = 0xffffff;
}
}
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * effectiveSpeed;
self.y += dy / distance * effectiveSpeed;
}
// Shoot bullets at player
self.lastAttack++;
if (self.lastAttack >= self.attackCooldown) {
var bullet = new Level30BossBullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Boss bullet speed is 50% of player's current bullet speed (which is 8)
var bulletSpeed = 4; // 50% of player bullet speed (8)
var angle = Math.atan2(dy, dx);
bullet.velocityX = Math.cos(angle) * bulletSpeed;
bullet.velocityY = Math.sin(angle) * bulletSpeed;
if (bullet.setRotation) {
bullet.setRotation(bullet.velocityX, bullet.velocityY);
}
}
bullet.bossDamage = self.damage; // Pass boss damage to bullet for health stealing calculation
bossBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.lastAttack = 0;
}
// Clone creation when health drops below 50%
if (self.health <= self.maxHealth / 2 && !self.hasCloned) {
self.lastClone++;
if (self.lastClone >= self.cloneCooldown) {
// Create a clone with 20% of original stats
var clone = new Level30BossClone();
clone.x = self.x + (Math.random() - 0.5) * 200;
clone.y = self.y + (Math.random() - 0.5) * 200;
// Ensure clone spawns within game bounds
clone.x = Math.max(50, Math.min(1998, clone.x));
clone.y = Math.max(50, Math.min(2682, clone.y));
// Set clone stats to 20% of original
clone.maxHealth = Math.floor(self.maxHealth * 0.2);
clone.health = clone.maxHealth;
clone.damage = Math.floor(self.damage * 0.2);
clone.speed = self.speed * 0.2;
game.addChild(clone);
self.hasCloned = true; // Prevent further cloning
self.lastClone = 0;
}
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
// Update boss health bar
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 196 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Play explosion sound
LK.getSound('explosion').play();
// Create massive explosion effect
for (var e = 0; e < 8; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 60 + e * 25,
height: 60 + e * 25,
alpha: 1.0 - e * 0.1,
tint: e === 0 ? 0xFF0000 : e === 1 ? 0xFF2200 : e === 2 ? 0xFF4500 : e === 3 ? 0xFF6600 : e === 4 ? 0xFF8800 : e === 5 ? 0xFFAA00 : e === 6 ? 0xFFCC00 : 0xFFFF00
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 80;
explosionCircle.y = self.y + (Math.random() - 0.5) * 80;
game.addChild(explosionCircle);
tween(explosionCircle, {
scaleX: 5 + e * 0.8,
scaleY: 5 + e * 0.8,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 200,
y: explosionCircle.y + (Math.random() - 0.5) * 200
}, {
duration: 800 + e * 150,
easing: e % 3 === 0 ? tween.easeOut : e % 3 === 1 ? tween.easeInOut : tween.bounceOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Drop rewards (same as other bosses)
var numCoins = 10 + Math.floor(Math.random() * 11);
for (var i = 0; i < numCoins; i++) {
var coin = new Coin();
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
coin.value = 10;
} else {
coin.value = 10 + levelGroup * 5;
}
coin.x = self.x + (Math.random() - 0.5) * 150;
coin.y = self.y + (Math.random() - 0.5) * 150;
coins.push(coin);
game.addChild(coin);
}
var bulletUpgrade = new BulletUpgrade();
bulletUpgrade.x = self.x + (Math.random() - 0.5) * 100;
bulletUpgrade.y = self.y + (Math.random() - 0.5) * 100;
powerUps.push(bulletUpgrade);
game.addChild(bulletUpgrade);
var diamond1 = new Diamond();
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10;
} else {
coinValue = 10 + levelGroup * 5;
}
diamond1.value = coinValue * 5;
diamond1.x = self.x + (Math.random() - 0.5) * 100;
diamond1.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond1);
game.addChild(diamond1);
if (Math.random() < 0.50) {
for (var d = 0; d < 2; d++) {
var diamond = new Diamond();
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10;
} else {
coinValue = 10 + levelGroup * 5;
}
diamond.value = coinValue * 5;
diamond.x = self.x + (Math.random() - 0.5) * 100;
diamond.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond);
game.addChild(diamond);
}
}
// 50% chance for ember drop
if (Math.random() < 0.50) {
var ember = new Ember();
ember.x = self.x + (Math.random() - 0.5) * 100;
ember.y = self.y + (Math.random() - 0.5) * 100;
embers.push(ember);
game.addChild(ember);
}
currentBoss = null;
// Stop boss fight music and start gameplay music
if (musicEnabled) {
LK.stopMusic();
LK.playMusic('3', {
loop: true
});
}
self.destroy();
enemiesKilled = 0;
};
return self;
});
var Level30BossBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('level30BossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.bossDamage = 0; // Will be set by the boss
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with player and steal health
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (player.width + self.width) / 8;
if (distance <= collisionDistance) {
// Deal damage to player
player.takeDamage(self.bossDamage);
LK.getSound('playerHit').play();
// Boss steals health - 3% of damage dealt
if (currentBoss) {
var stolenHealth = Math.floor(self.bossDamage * 0.03);
currentBoss.health = Math.min(currentBoss.maxHealth, currentBoss.health + stolenHealth);
// Update boss health bar
var healthPercent = currentBoss.health / currentBoss.maxHealth;
currentBoss.healthBarFill.width = 196 * healthPercent;
// Create floating heal text for boss
var healText = new Text2('+' + stolenHealth + ' HP', {
size: 40,
fill: 0xff0000
});
healText.anchor.set(0.5, 0.5);
healText.x = currentBoss.x;
healText.y = currentBoss.y;
game.addChild(healText);
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
self.removeFromGame();
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < bossBullets.length; i++) {
if (bossBullets[i] === self) {
bossBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Level30BossClone = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
// Scale down the clone to 0.5x to show it's smaller
graphics.scaleX = 0.5;
graphics.scaleY = 0.5;
graphics.tint = 0x888888; // Gray tint to differentiate from original
self.maxHealth = 1; // Will be overridden by creator
self.health = 1;
self.damage = 1; // Will be overridden by creator
self.speed = 0.1; // Will be overridden by creator
self.lastAttack = 0;
self.attackCooldown = 120; // 2 seconds at 60 FPS (slower than original)
// Create clone health bar background
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
// Smaller health bar
height: 15
});
self.healthBarBg.y = -60;
self.addChild(self.healthBarBg);
// Create clone health bar fill
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5,
width: 96,
height: 11
});
self.healthBarFill.x = -48;
self.healthBarFill.y = -60;
self.addChild(self.healthBarFill);
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
if (graphics.tint !== 0x444488) {
graphics.tint = 0x444488; // Darker blue for frozen clone
}
} else {
if (graphics.tint !== 0x888888) {
graphics.tint = 0x888888; // Gray for normal clone
}
}
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * effectiveSpeed;
self.y += dy / distance * effectiveSpeed;
}
// Shoot bullets at player (slower than original)
self.lastAttack++;
if (self.lastAttack >= self.attackCooldown) {
var bullet = new Level30BossCloneBullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Clone bullet speed is also 20% of player bullet speed
var bulletSpeed = 1.6; // 20% of player bullet speed (8)
var angle = Math.atan2(dy, dx);
bullet.velocityX = Math.cos(angle) * bulletSpeed;
bullet.velocityY = Math.sin(angle) * bulletSpeed;
if (bullet.setRotation) {
bullet.setRotation(bullet.velocityX, bullet.velocityY);
}
}
bullet.bossDamage = self.damage; // Pass clone damage to bullet
bossBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.lastAttack = 0;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
// Update clone health bar
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 96 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Play explosion sound
LK.getSound('explosion').play();
// Small explosion for clone
for (var e = 0; e < 4; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 30 + e * 10,
height: 30 + e * 10,
alpha: 0.8 - e * 0.15,
tint: e === 0 ? 0x888888 : e === 1 ? 0xAAAAAA : e === 2 ? 0xCCCCCC : 0xEEEEEE
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 40;
explosionCircle.y = self.y + (Math.random() - 0.5) * 40;
game.addChild(explosionCircle);
tween(explosionCircle, {
scaleX: 2 + e * 0.3,
scaleY: 2 + e * 0.3,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 80,
y: explosionCircle.y + (Math.random() - 0.5) * 80
}, {
duration: 400 + e * 75,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Drop small coin reward
var coin = new Coin();
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
coin.value = 5; // Half value for clone
} else {
coin.value = Math.floor((10 + levelGroup * 5) / 2); // Half value for clone
}
coin.x = self.x;
coin.y = self.y;
coins.push(coin);
game.addChild(coin);
self.destroy();
};
return self;
});
var Level30BossCloneBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('level30BossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Scale down clone bullet to 0.7x
graphics.scaleX = 0.7;
graphics.scaleY = 0.7;
graphics.tint = 0x888888; // Gray tint like clone
self.velocityX = 0;
self.velocityY = 0;
self.bossDamage = 0; // Will be set by the clone
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with player and steal health (but less than original)
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (player.width + self.width) / 8;
if (distance <= collisionDistance) {
// Deal damage to player
player.takeDamage(self.bossDamage);
LK.getSound('playerHit').play();
// Clone steals less health - 1% of damage dealt (instead of 3%)
if (currentBoss) {
var stolenHealth = Math.max(1, Math.floor(self.bossDamage * 0.01));
currentBoss.health = Math.min(currentBoss.maxHealth, currentBoss.health + stolenHealth);
// Update boss health bar
var healthPercent = currentBoss.health / currentBoss.maxHealth;
currentBoss.healthBarFill.width = 196 * healthPercent;
}
self.removeFromGame();
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < bossBullets.length; i++) {
if (bossBullets[i] === self) {
bossBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var MachineGun = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('machineGun', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this machine gun came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If machine gun is already active, only refresh the timer
if (machineGunActive) {
machineGunEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate machine gun effect for first time
machineGunActive = true;
machineGunEndTime = LK.ticks + 1800; // 30 seconds
}
// Use appropriate sound based on source tracking if it exists
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var MachineGunBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('machineGunBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.damage = 20; // Same as regular bullet damage
// Set rotation method for bullet orientation
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with enemies (limit checks for performance)
var maxEnemyChecks = Math.min(15, enemies.length); // Limit enemy checks for machine gun bullets
for (var i = 0; i < maxEnemyChecks; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
enemy.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting enemies
if (healingNeedleActive && player) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = enemy.x;
healText.y = enemy.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
// Create spark effect for machine gun bullets
for (var s = 0; s < 3; s++) {
var spark = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 8 + s * 4,
height: 8 + s * 4,
alpha: 0.8 - s * 0.2,
tint: s === 0 ? 0xFFFF44 : s === 1 ? 0xFFAA00 : 0xFF6600
});
spark.x = self.x + (Math.random() - 0.5) * 20;
spark.y = self.y + (Math.random() - 0.5) * 20;
game.addChild(spark);
tween(spark, {
scaleX: 1.5 + s * 0.3,
scaleY: 1.5 + s * 0.3,
alpha: 0,
x: spark.x + (Math.random() - 0.5) * 40,
y: spark.y + (Math.random() - 0.5) * 40
}, {
duration: 200 + s * 50,
easing: tween.easeOut,
onFinish: function onFinish() {
spark.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with green enemies (limit checks for performance)
var maxGreenChecks = Math.min(8, greenEnemies.length); // Limit green enemy checks for machine gun bullets
for (var g = 0; g < maxGreenChecks; g++) {
var greenEnemy = greenEnemies[g];
if (self.intersects(greenEnemy)) {
greenEnemy.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting green enemies
if (healingNeedleActive && player) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = greenEnemy.x;
healText.y = greenEnemy.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
// Create spark effect for machine gun bullets
for (var s = 0; s < 3; s++) {
var spark = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 8 + s * 4,
height: 8 + s * 4,
alpha: 0.8 - s * 0.2,
tint: s === 0 ? 0xFFFF44 : s === 1 ? 0xFFAA00 : 0xFF6600
});
spark.x = self.x + (Math.random() - 0.5) * 20;
spark.y = self.y + (Math.random() - 0.5) * 20;
game.addChild(spark);
tween(spark, {
scaleX: 1.5 + s * 0.3,
scaleY: 1.5 + s * 0.3,
alpha: 0,
x: spark.x + (Math.random() - 0.5) * 40,
y: spark.y + (Math.random() - 0.5) * 40
}, {
duration: 200 + s * 50,
easing: tween.easeOut,
onFinish: function onFinish() {
spark.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with boss
if (currentBoss && self.intersects(currentBoss)) {
currentBoss.takeDamage(self.damage + (player ? player.damage : 0));
// Apply healing needle effect when hitting boss
if (healingNeedleActive && player && currentBoss) {
var healAmount = Math.floor(player.maxHealth * 0.1); // 10% of max health
player.health = Math.min(player.maxHealth, player.health + healAmount);
// Create floating heal text
var healText = new Text2('+' + healAmount + ' HP', {
size: 40,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = currentBoss.x;
healText.y = currentBoss.y;
game.addChild(healText);
// Animate the heal text floating up and fading out
tween(healText, {
y: healText.y - 80,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
healText.destroy();
}
});
}
// Create spark effect for machine gun bullets
for (var s = 0; s < 3; s++) {
var spark = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 8 + s * 4,
height: 8 + s * 4,
alpha: 0.8 - s * 0.2,
tint: s === 0 ? 0xFFFF44 : s === 1 ? 0xFFAA00 : 0xFF6600
});
spark.x = self.x + (Math.random() - 0.5) * 20;
spark.y = self.y + (Math.random() - 0.5) * 20;
game.addChild(spark);
tween(spark, {
scaleX: 1.5 + s * 0.3,
scaleY: 1.5 + s * 0.3,
alpha: 0,
x: spark.x + (Math.random() - 0.5) * 40,
y: spark.y + (Math.random() - 0.5) * 40
}, {
duration: 200 + s * 50,
easing: tween.easeOut,
onFinish: function onFinish() {
spark.destroy();
}
});
}
LK.getSound('enemyHit').play();
self.removeFromGame();
}
};
self.removeFromGame = function () {
for (var i = 0; i < playerBullets.length; i++) {
if (playerBullets[i] === self) {
playerBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Magnet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('magnet', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this magnet came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If magnet is already active, only refresh the timer
if (magnetActive) {
magnetEndTime = LK.ticks + 3600; // 60 seconds
} else {
// Activate magnet effect for first time
magnetActive = true;
magnetEndTime = LK.ticks + 3600; // 60 seconds
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Add attack range visual
var rangeGraphics = self.attachAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.2,
width: 600,
// 300 radius * 2
height: 600 // 300 radius * 2
});
self.maxHealth = 50;
self.health = self.maxHealth;
self.speed = 2.2;
self.damage = 5;
self.attackSpeed = 103;
self.attackRange = 300; // Base attack range radius
self.lastShot = 0;
self.update = function () {
if (gameState === 'playing') {
// Apply Iron Body visual effect
if (ironBodyActive) {
if (graphics.tint !== 0x808080) {
graphics.tint = 0x808080; // Metallic gray color
}
} else {
if (graphics.tint !== 0xffffff) {
graphics.tint = 0xffffff; // Normal color
}
}
// Handle analog stick movement
if (analogStickEnabled && analogStickActive && (Math.abs(analogStickDirection.x) > 0.1 || Math.abs(analogStickDirection.y) > 0.1)) {
// Move based on analog stick direction
var moveX = analogStickDirection.x * self.speed;
var moveY = analogStickDirection.y * self.speed;
self.x += moveX;
self.y += moveY;
// Keep player within game bounds
self.x = Math.max(50, Math.min(1998, self.x));
self.y = Math.max(50, Math.min(2682, self.y));
// Make player face movement direction
if (moveX > 0) {
graphics.scaleX = -Math.abs(graphics.scaleX); // Face right
} else if (moveX < 0) {
graphics.scaleX = Math.abs(graphics.scaleX); // Face left
}
} else if (touchPosition && !analogStickEnabled && (Math.abs(self.x - touchPosition.x) > 5 || Math.abs(self.y - touchPosition.y) > 5)) {
// Move towards touch position only if analog stick is disabled
var dx = touchPosition.x - self.x;
var dy = touchPosition.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Make player face movement direction
if (dx > 0) {
graphics.scaleX = -Math.abs(graphics.scaleX); // Face right
} else if (dx < 0) {
graphics.scaleX = Math.abs(graphics.scaleX); // Face left
}
}
}
// Auto-shoot when enemies are in range
self.lastShot++;
var target = self.findNearestEnemy();
var effectiveAttackSpeed = self.attackSpeed;
if (machineGunActive) {
effectiveAttackSpeed = Math.floor(self.attackSpeed / 3); // 3x faster
}
if (self.lastShot >= effectiveAttackSpeed && target) {
// Limit machine gun bullets to prevent performance issues
if (machineGunActive && playerBullets.length > 30) {
// Skip shooting if too many bullets exist
self.lastShot = Math.floor(effectiveAttackSpeed * 0.8); // Slight delay
} else {
self.shoot();
self.lastShot = 0;
}
}
}
};
self.shoot = function () {
var target = self.findNearestEnemy();
if (target) {
// Determine bullet count based on weapon type
var bulletsToFire = 1; // Default to 1 bullet for special weapons
if (!rocketLauncherActive && !machineGunActive && !healingNeedleActive) {
// Only use fire ball bullet count for basic fire ball bullets
bulletsToFire = fireBallBulletCount;
}
// Fire multiple bullets based on weapon type
for (var b = 0; b < bulletsToFire; b++) {
var bullet;
// Create appropriate bullet type based on active power-ups
if (rocketLauncherActive) {
bullet = new RocketBullet();
} else if (machineGunActive) {
bullet = new MachineGunBullet();
} else if (healingNeedleActive) {
bullet = new HealingNeedleBullet();
} else {
bullet = new PlayerBullet();
}
// Position bullets with spread for fire ball bullets (basic bullets)
if (!rocketLauncherActive && !machineGunActive && !healingNeedleActive && bulletsToFire > 1) {
// Fire ball bullets - spread them in a line formation
var spreadOffset = (b - (bulletsToFire - 1) / 2) * 40; // 40 pixel spacing between bullets
var perpAngle = Math.atan2(target.y - self.y, target.x - self.x) + Math.PI / 2; // Perpendicular to target direction
bullet.x = self.x + Math.cos(perpAngle) * spreadOffset;
bullet.y = self.y + Math.sin(perpAngle) * spreadOffset;
} else {
bullet.x = self.x;
bullet.y = self.y;
}
var dx = target.x - bullet.x;
var dy = target.y - bullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var spreadAngle = 0;
// Only spread angle for special weapons, not for fire ball bullets
if (bulletsToFire > 1 && (rocketLauncherActive || machineGunActive || healingNeedleActive)) {
spreadAngle = (b - (bulletsToFire - 1) / 2) * 0.2; // Small spread for special weapons only
}
var angle = Math.atan2(dy, dx) + spreadAngle;
bullet.velocityX = Math.cos(angle) * 8;
bullet.velocityY = Math.sin(angle) * 8;
// Set bullet rotation to face movement direction
if (bullet.setRotation) {
bullet.setRotation(bullet.velocityX, bullet.velocityY);
}
playerBullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
}
};
self.findNearestEnemy = function () {
if (currentBoss) {
var dx = currentBoss.x - self.x;
var dy = currentBoss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange) {
return currentBoss;
}
}
var nearest = null;
var nearestDistance = Infinity;
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 <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearest = enemy;
}
}
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
var dx = greenEnemy.x - self.x;
var dy = greenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearest = greenEnemy;
}
}
return nearest;
};
self.takeDamage = function (damage) {
var actualDamage = damage;
// Apply Iron Body damage reduction
if (ironBodyActive) {
actualDamage = Math.floor(damage * 0.1); // 90% reduction
}
self.health -= actualDamage;
if (self.health < 0) {
self.health = 0;
}
// Health bar now handled in GUI
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
// Remove player from scene
self.destroy();
player = null;
// Play explosion sound
LK.getSound('explosion').play();
// Trigger nova explosion when player dies
for (var e = 0; e < 12; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 80 + e * 40,
height: 80 + e * 40,
alpha: 1.0 - e * 0.08,
tint: e % 4 === 0 ? 0xFFFFFF : e % 4 === 1 ? 0xFFFF00 : e % 4 === 2 ? 0xFF8800 : 0xFF0000
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 100;
explosionCircle.y = self.y + (Math.random() - 0.5) * 100;
game.addChild(explosionCircle);
// Animate massive nova explosion
tween(explosionCircle, {
scaleX: 8 + e * 1.2,
scaleY: 8 + e * 1.2,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 300,
y: explosionCircle.y + (Math.random() - 0.5) * 300
}, {
duration: 1500 + e * 200,
easing: e % 3 === 0 ? tween.easeOut : e % 3 === 1 ? tween.easeInOut : tween.bounceOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Save score immediately and show menu after 3 seconds
// Save player score to leaderboard immediately
var playerName = promptPlayerName();
saveScore(playerName, currentLevel);
console.log('Player died at level', currentLevel, 'saving score for', playerName);
LK.setTimeout(function () {
// Create custom game over display with level info
var gameOverContainer = new Container();
var gameOverBg = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 1.0,
scaleX: 12,
scaleY: 6,
x: 0,
y: 0,
tint: 0x000000
});
gameOverContainer.addChild(gameOverBg);
var gameOverText = new Text2('GAME OVER', {
size: 120,
fill: 0xFF0000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = 0;
gameOverText.y = -200;
gameOverContainer.addChild(gameOverText);
var levelReachedText = new Text2('Level Reached: ' + currentLevel, {
size: 80,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
levelReachedText.anchor.set(0.5, 0.5);
levelReachedText.x = 0;
levelReachedText.y = -350;
gameOverContainer.addChild(levelReachedText);
var scoreEntryText = new Text2('Score saved as: ' + playerName, {
size: 60,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
scoreEntryText.anchor.set(0.5, 0.5);
scoreEntryText.x = 0;
scoreEntryText.y = -100;
gameOverContainer.addChild(scoreEntryText);
gameOverContainer.x = 1024;
gameOverContainer.y = 2732;
game.addChild(gameOverContainer);
// Return to main menu after displaying score info
LK.setTimeout(function () {
gameOverContainer.destroy();
showMainMenu();
}, 3000);
}, 3000);
}
};
return self;
});
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
// Set rotation method for bullet orientation
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.damage = 20;
self.update = function () {
// Find nearest target for homing behavior to guarantee hits
var nearestTarget = null;
var nearestDistance = Infinity;
var targetX = self.x + self.velocityX;
var targetY = self.y + self.velocityY;
// Check for nearest enemy
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = enemy;
}
}
// Check for nearest green enemy
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
var dx = greenEnemy.x - self.x;
var dy = greenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = greenEnemy;
}
}
// Check for boss
if (currentBoss) {
var dx = currentBoss.x - self.x;
var dy = currentBoss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = currentBoss;
}
}
// Apply homing behavior if target exists and is within reasonable range
if (nearestTarget && nearestDistance < 800) {
var dx = nearestTarget.x - self.x;
var dy = nearestTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Calculate homing strength - stronger when closer to target
var homingStrength = Math.min(0.3, 100 / distance);
// Normalize target direction
var targetVelX = dx / distance * 8;
var targetVelY = dy / distance * 8;
// Interpolate between current velocity and target velocity
self.velocityX = self.velocityX * (1 - homingStrength) + targetVelX * homingStrength;
self.velocityY = self.velocityY * (1 - homingStrength) + targetVelY * homingStrength;
}
}
self.x += self.velocityX;
self.y += self.velocityY;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with enemies using precise distance calculation - check ALL enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Use precise collision detection instead of intersects
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (enemy.width + self.width) / 8; // Tight collision bounds
if (distance <= collisionDistance) {
enemy.takeDamage(self.damage + (player ? player.damage : 0));
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with green enemies using precise distance calculation - check ALL green enemies
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
// Use precise collision detection instead of intersects
var dx = greenEnemy.x - self.x;
var dy = greenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (greenEnemy.width + self.width) / 8; // Tight collision bounds
if (distance <= collisionDistance) {
greenEnemy.takeDamage(self.damage + (player ? player.damage : 0));
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with boss using precise distance calculation
if (currentBoss) {
var dx = currentBoss.x - self.x;
var dy = currentBoss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collisionDistance = (currentBoss.width + self.width) / 8; // Tight collision bounds
if (distance <= collisionDistance) {
currentBoss.takeDamage(self.damage + (player ? player.damage : 0));
LK.getSound('enemyHit').play();
self.removeFromGame();
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < playerBullets.length; i++) {
if (playerBullets[i] === self) {
playerBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var PlayerClone = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xcccccc // Gray tint to differentiate from original
});
// Copy all stats from the original player
if (player) {
self.maxHealth = player.maxHealth;
self.health = self.maxHealth;
self.speed = player.speed;
self.damage = player.damage;
self.attackSpeed = player.attackSpeed;
self.attackRange = player.attackRange;
}
self.lastShot = 0;
self.update = function () {
if (gameState === 'playing' && duplicateActive) {
// Continuously move around and attack enemies aggressively
var target = self.findNearestEnemy();
if (target) {
// Move towards target aggressively
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Move at full speed towards enemies
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Make clone face movement direction
if (dx > 0) {
graphics.scaleX = -Math.abs(graphics.scaleX); // Face right
} else if (dx < 0) {
graphics.scaleX = Math.abs(graphics.scaleX); // Face left
}
}
} else {
// If no target in attack range, patrol around the map to find enemies
// Move in a circular patrol pattern to cover more area
var patrolRadius = 300;
var patrolSpeed = self.speed * 0.7; // Slightly slower patrol speed
var centerX = 1024; // Center of map
var centerY = 1366;
// Create patrol movement based on game time
var patrolAngle = LK.ticks * 0.02 % (Math.PI * 2);
var targetX = centerX + Math.cos(patrolAngle) * patrolRadius;
var targetY = centerY + Math.sin(patrolAngle) * patrolRadius;
// Move towards patrol target
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
self.x += dx / distance * patrolSpeed;
self.y += dy / distance * patrolSpeed;
// Make clone face movement direction during patrol
if (dx > 0) {
graphics.scaleX = -Math.abs(graphics.scaleX); // Face right
} else if (dx < 0) {
graphics.scaleX = Math.abs(graphics.scaleX); // Face left
}
}
}
// Auto-shoot when enemies are in range
self.lastShot++;
if (self.lastShot >= self.attackSpeed) {
var shootTarget = self.findNearestEnemy();
if (shootTarget) {
self.shoot();
self.lastShot = 0;
}
}
}
};
self.shoot = function () {
var target = self.findNearestEnemy();
if (target) {
var bullet = new PlayerBullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
bullet.velocityX = Math.cos(angle) * 8;
bullet.velocityY = Math.sin(angle) * 8;
if (bullet.setRotation) {
bullet.setRotation(bullet.velocityX, bullet.velocityY);
}
playerBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
}
};
self.findNearestEnemy = function () {
if (currentBoss) {
var dx = currentBoss.x - self.x;
var dy = currentBoss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange) {
return currentBoss;
}
}
var nearest = null;
var nearestDistance = Infinity;
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 <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearest = enemy;
}
}
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
var dx = greenEnemy.x - self.x;
var dy = greenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearest = greenEnemy;
}
}
return nearest;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Clone died before timer expired, respawn it
if (duplicateActive) {
// Small death effect
for (var e = 0; e < 4; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 30 + e * 10,
height: 30 + e * 10,
alpha: 0.6 - e * 0.1,
tint: 0xcccccc
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 40;
explosionCircle.y = self.y + (Math.random() - 0.5) * 40;
game.addChild(explosionCircle);
tween(explosionCircle, {
scaleX: 2 + e * 0.3,
scaleY: 2 + e * 0.3,
alpha: 0
}, {
duration: 400 + e * 75,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
self.destroy();
// Respawn clone after short delay
LK.setTimeout(function () {
if (duplicateActive) {
createPlayerClone();
}
}, 1000);
}
};
self.explode = function () {
// Play explosion sound
LK.getSound('explosion').play();
// Create massive explosion effect
for (var e = 0; e < 8; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 120 + e * 50,
height: 120 + e * 50,
alpha: 1.0 - e * 0.1,
tint: e === 0 ? 0xFFFFFF : e === 1 ? 0xCCCCCC : e === 2 ? 0x999999 : e === 3 ? 0x666666 : e === 4 ? 0x888888 : e === 5 ? 0xAAAAAA : e === 6 ? 0xDDDDDD : 0xEEEEEE
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 160;
explosionCircle.y = self.y + (Math.random() - 0.5) * 160;
game.addChild(explosionCircle);
tween(explosionCircle, {
scaleX: 10 + e * 1.6,
scaleY: 10 + e * 1.6,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 400,
y: explosionCircle.y + (Math.random() - 0.5) * 400
}, {
duration: 800 + e * 150,
easing: e % 3 === 0 ? tween.easeOut : e % 3 === 1 ? tween.easeInOut : tween.bounceOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Damage all enemies within 1600 unit radius (doubled from 800)
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 <= 1600) {
enemy.takeDamage(3999996); // 4x damage (doubled from 2x)
}
}
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
var dx = greenEnemy.x - self.x;
var dy = greenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 1600) {
greenEnemy.takeDamage(3999996); // 4x damage (doubled from 2x)
}
}
if (currentBoss) {
var dx = currentBoss.x - self.x;
var dy = currentBoss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 1600) {
currentBoss.takeDamage(3999996); // 4x damage (doubled from 2x)
}
}
self.destroy();
};
return self;
});
var RocketBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('rocketBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.damage = 20;
// Set rotation method for bullet orientation
self.setRotation = function (vx, vy) {
graphics.rotation = Math.atan2(vy, vx);
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
}
// Check collision with enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
// Rocket splash damage - damage up to 4 nearest enemies within range
var nearbyEnemies = [];
// Collect all enemies within splash radius
for (var e = 0; e < enemies.length; e++) {
var splashEnemy = enemies[e];
var dx = splashEnemy.x - self.x;
var dy = splashEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 300) {
nearbyEnemies.push({
enemy: splashEnemy,
distance: distance
});
}
}
for (var g = 0; g < greenEnemies.length; g++) {
var splashGreen = greenEnemies[g];
var dx = splashGreen.x - self.x;
var dy = splashGreen.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 300) {
nearbyEnemies.push({
enemy: splashGreen,
distance: distance
});
}
}
// Sort by distance and damage up to 4 closest enemies
nearbyEnemies.sort(function (a, b) {
return a.distance - b.distance;
});
var enemiesToDamage = Math.min(4, nearbyEnemies.length);
for (var i = 0; i < enemiesToDamage; i++) {
nearbyEnemies[i].enemy.takeDamage(self.damage + (player ? player.damage : 0));
}
// Create explosion effect
var explosion = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 400,
alpha: 0.6,
tint: 0xFF6600
});
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with green enemies
for (var g = 0; g < greenEnemies.length; g++) {
var greenEnemy = greenEnemies[g];
if (self.intersects(greenEnemy)) {
// Rocket splash damage
for (var e = 0; e < enemies.length; e++) {
var splashEnemy = enemies[e];
var dx = splashEnemy.x - self.x;
var dy = splashEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 300) {
splashEnemy.takeDamage(self.damage + (player ? player.damage : 0));
}
}
for (var gg = 0; gg < greenEnemies.length; gg++) {
var splashGreen = greenEnemies[gg];
var dx = splashGreen.x - self.x;
var dy = splashGreen.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 300) {
splashGreen.takeDamage(self.damage + (player ? player.damage : 0));
}
}
// Create explosion effect
var explosion = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 400,
alpha: 0.6,
tint: 0xFF6600
});
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
LK.getSound('enemyHit').play();
self.removeFromGame();
return;
}
}
// Check collision with boss
if (currentBoss && self.intersects(currentBoss)) {
// Splash damage on boss
var explosion = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 400,
alpha: 0.6,
tint: 0xFF6600
});
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
currentBoss.takeDamage(self.damage + (player ? player.damage : 0));
LK.getSound('enemyHit').play();
self.removeFromGame();
}
};
self.removeFromGame = function () {
for (var i = 0; i < playerBullets.length; i++) {
if (playerBullets[i] === self) {
playerBullets.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var RocketLauncher = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('rocketLauncher', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this rocket launcher came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If rocket launcher is already active, only refresh the timer
if (rocketLauncherActive) {
rocketLauncherEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate rocket launcher effect for first time
rocketLauncherActive = true;
rocketLauncherEndTime = LK.ticks + 1800; // 30 seconds
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var SpawnerBoss = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('spawnerBoss', {
anchorX: 0.5,
anchorY: 0.5
});
// Calculate total health of enemies from previous level (currentLevel - 1)
var previousLevel = Math.max(1, currentLevel - 1);
var enemyMaxHealth = 40 + (previousLevel - 1) * 8;
var enemiesInPreviousLevel = Math.floor(enemiesPerLevel * (1 + Math.floor((previousLevel - 1) / 5) * 0.4));
var totalEnemyHealth = enemyMaxHealth * enemiesInPreviousLevel;
self.maxHealth = totalEnemyHealth * 3;
self.health = self.maxHealth;
self.speed = 0.8 + Math.max(0, currentLevel - 10) * 0.1;
var baseDamage = 30;
var bossNumber = Math.floor(currentLevel / 10); // Boss 1 at level 10, Boss 2 at level 20, etc.
var damageMultiplier = 1 + (bossNumber - 1) * 0.5; // 50% increase per boss
self.damage = Math.floor(baseDamage * damageMultiplier);
self.lastAttack = 0;
// Enemy spawning properties
self.lastSpawn = 0;
self.spawnCooldown = 300; // 5 seconds at 60 FPS (8 seconds when health > 50%, 5 seconds when health <= 50%)
self.previousLevelEnemyHealth = 40 + (previousLevel - 1) * 8; // Health of enemies from previous level
self.previousLevelEnemySpeed = 1.5 + (previousLevel - 1) * 0.15; // Speed of enemies from previous level
self.previousLevelEnemyDamage = 10 * (1 + (previousLevel - 1) * 0.1); // Damage of enemies from previous level
self.spawnedEnemyCount = 0; // Track total enemies spawned (max 15)
self.spawnerBossId = 'spawner_' + LK.ticks; // Unique ID for this spawner boss
// Create boss health bar background
self.healthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 30
});
self.healthBarBg.y = -90;
self.addChild(self.healthBarBg);
// Create boss health bar fill
self.healthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0.5,
width: 196,
height: 26
});
self.healthBarFill.x = -98;
self.healthBarFill.y = -90;
self.addChild(self.healthBarFill);
self.update = function () {
if (gameState === 'playing' && player) {
// Apply freeze effect if active
var effectiveSpeed = self.speed;
if (freezeEffectActive) {
effectiveSpeed *= 0.5;
// Apply blue tint when frozen
if (graphics.tint !== 0x4444ff) {
graphics.tint = 0x4444ff;
}
} else {
// Remove blue tint when not frozen
if (graphics.tint !== 0xffffff) {
graphics.tint = 0xffffff;
}
}
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * effectiveSpeed;
self.y += dy / distance * effectiveSpeed;
}
// Spawn enemies
self.lastSpawn++;
// Count current alive enemies from this spawner
var currentAliveEnemies = 0;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].spawnerBossId === self.spawnerBossId) {
currentAliveEnemies++;
}
}
// Calculate how many enemies we need to spawn to reach 15
var enemiesToSpawn = Math.max(0, 15 - currentAliveEnemies);
// Dynamic cooldown: 8 seconds normally, 5 seconds when health <= 50%
var currentSpawnCooldown = self.health <= self.maxHealth / 2 ? 300 : 480; // 5 seconds vs 8 seconds at 60 FPS
if (self.lastSpawn >= currentSpawnCooldown && enemiesToSpawn > 0) {
// Limit spawning to prevent overwhelming
var maxSpawnPerCycle = self.health <= self.maxHealth / 2 ? 5 : 2;
enemiesToSpawn = Math.min(enemiesToSpawn, maxSpawnPerCycle);
for (var e = 0; e < enemiesToSpawn; e++) {
var enemy = new Enemy();
// Mark this enemy as spawned by this boss
enemy.spawnerBossId = self.spawnerBossId;
// Set spawned enemy stats to previous level stats
enemy.maxHealth = self.previousLevelEnemyHealth;
enemy.health = enemy.maxHealth;
enemy.speed = self.previousLevelEnemySpeed;
enemy.damage = self.previousLevelEnemyDamage;
// Position enemy next to spawner boss with some spread
var spawnAngle = Math.random() * 2 * Math.PI;
var spawnDistance = 100 + Math.random() * 50; // Spawn 100-150 pixels away
enemy.x = self.x + Math.cos(spawnAngle) * spawnDistance;
enemy.y = self.y + Math.sin(spawnAngle) * spawnDistance;
// Ensure enemy spawns within game bounds
enemy.x = Math.max(50, Math.min(1998, enemy.x));
enemy.y = Math.max(50, Math.min(2682, enemy.y));
// Add to game
enemies.push(enemy);
game.addChild(enemy);
}
self.lastSpawn = 0;
LK.getSound('shoot').play();
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
// Update boss health bar
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.width = 196 * healthPercent;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Play explosion sound
LK.getSound('explosion').play();
// Create massive explosion effect with multiple waves of particles
for (var e = 0; e < 8; e++) {
var explosionCircle = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 60 + e * 25,
height: 60 + e * 25,
alpha: 1.0 - e * 0.1,
tint: e === 0 ? 0xFF0000 : e === 1 ? 0xFF2200 : e === 2 ? 0xFF4500 : e === 3 ? 0xFF6600 : e === 4 ? 0xFF8800 : e === 5 ? 0xFFAA00 : e === 6 ? 0xFFCC00 : 0xFFFF00
});
explosionCircle.x = self.x + (Math.random() - 0.5) * 80;
explosionCircle.y = self.y + (Math.random() - 0.5) * 80;
game.addChild(explosionCircle);
// Animate explosion with dramatic scaling and movement
tween(explosionCircle, {
scaleX: 5 + e * 0.8,
scaleY: 5 + e * 0.8,
alpha: 0,
x: explosionCircle.x + (Math.random() - 0.5) * 200,
y: explosionCircle.y + (Math.random() - 0.5) * 200
}, {
duration: 800 + e * 150,
easing: e % 3 === 0 ? tween.easeOut : e % 3 === 1 ? tween.easeInOut : tween.bounceOut,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
}
// Drop guaranteed 10-20 coins
var numCoins = 10 + Math.floor(Math.random() * 11); // 10-20 coins
for (var i = 0; i < numCoins; i++) {
var coin = new Coin();
// Calculate coin value - first 5 levels = 10, then +5 every 5 levels
var levelGroup = Math.floor((currentLevel - 1) / 5);
if (levelGroup === 0) {
coin.value = 10; // Levels 1-5
} else {
coin.value = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
coin.x = self.x + (Math.random() - 0.5) * 150;
coin.y = self.y + (Math.random() - 0.5) * 150;
coins.push(coin);
game.addChild(coin);
}
// Drop permanent bullet upgrade
var bulletUpgrade = new BulletUpgrade();
bulletUpgrade.x = self.x + (Math.random() - 0.5) * 100;
bulletUpgrade.y = self.y + (Math.random() - 0.5) * 100;
powerUps.push(bulletUpgrade);
game.addChild(bulletUpgrade);
// Guaranteed diamond drop
var diamond1 = new Diamond();
// Calculate diamond value - 5x current coin value
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
diamond1.value = coinValue * 5; // Boss diamonds worth 5x current coin value
diamond1.x = self.x + (Math.random() - 0.5) * 100;
diamond1.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond1);
game.addChild(diamond1);
// 50% chance for 2 additional diamonds
if (Math.random() < 0.50) {
for (var d = 0; d < 2; d++) {
var diamond = new Diamond();
// Calculate diamond value - 5x current coin value
var levelGroup = Math.floor((currentLevel - 1) / 5);
var coinValue;
if (levelGroup === 0) {
coinValue = 10; // Levels 1-5
} else {
coinValue = 10 + levelGroup * 5; // Level 6-10 = 15, 11-15 = 20, etc.
}
diamond.value = coinValue * 5;
diamond.x = self.x + (Math.random() - 0.5) * 100;
diamond.y = self.y + (Math.random() - 0.5) * 100;
diamonds.push(diamond);
game.addChild(diamond);
}
}
currentBoss = null;
// Stop boss fight music and start gameplay music
if (musicEnabled) {
LK.stopMusic();
LK.playMusic('3', {
loop: true
});
}
self.destroy();
// Boss is defeated, level progression will be handled in main update loop
enemiesKilled = 0;
};
return self;
});
var Speed = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('speed', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this speed came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If speed is already active, only refresh the timer
if (speedActive) {
speedEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate speed effect for first time
speedActive = true;
speedEndTime = LK.ticks + 1800; // 30 seconds
speedOriginalSpeed = player.speed;
player.speed = speedOriginalSpeed * 2;
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
var Wealth = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('wealth', {
anchorX: 0.5,
anchorY: 0.5
});
self.creationTime = LK.ticks;
self.expirationStarted = false;
self.fromGreenEnemy = false; // Track if this wealth came from a green enemy
self.update = function () {
// Expire after 30 seconds
if (!self.expirationStarted && LK.ticks - self.creationTime >= 1800) {
self.expirationStarted = true;
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var collectionDistance = (player.width + self.width) / 4;
if (distance <= collectionDistance) {
// If wealth is already active, only refresh the timer
if (wealthActive) {
wealthEndTime = LK.ticks + 1800; // 30 seconds
} else {
// Activate wealth effect for first time
wealthActive = true;
wealthEndTime = LK.ticks + 1800; // 30 seconds
}
if (self.fromGreenEnemy) {
LK.getSound('greenItemCollect').play();
} else {
LK.getSound('coinCollect').play();
}
for (var i = 0; i < powerUps.length; i++) {
if (powerUps[i] === self) {
powerUps.splice(i, 1);
break;
}
}
self.destroy();
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// Game variables
var gameState = 'menu'; // 'menu', 'nickname', 'playing', 'shop', 'bodyPartSelection', 'leaderboard'
var playerNickname = storage.playerNickname || null; // Load saved nickname
var currentLevel = 1;
var enemiesKilled = 0;
var enemiesPerLevel = 5;
var playerCoins = 0;
var touchPosition = null;
// Player upgrade levels and costs - reset to zero for each new game
var healthUpgradeLevel = 0;
var attackSpeedUpgradeLevel = 0;
var attackPowerUpgradeLevel = 0;
var movementSpeedUpgradeLevel = 0;
var attackRangeUpgradeLevel = 0;
// Game objects
var player = null;
var enemies = [];
var greenEnemies = [];
var playerBullets = [];
var bossBullets = [];
var coins = [];
var diamonds = [];
var embers = [];
var healthPacks = [];
var powerUps = [];
var currentBoss = null;
var remainingEnemies = 0;
var playerBulletCount = 1;
var fireBallBulletCount = 1; // Separate counter for fire ball bullets only
// Power-up effects
var rocketLauncherActive = false;
var rocketLauncherEndTime = 0;
var freezeEffectActive = false;
var freezeEffectEndTime = 0;
var machineGunActive = false;
var machineGunEndTime = 0;
var magnetActive = false;
var magnetEndTime = 0;
var speedActive = false;
var speedEndTime = 0;
var speedOriginalSpeed = 0;
var wealthActive = false;
var wealthEndTime = 0;
var ironBodyActive = false;
var ironBodyEndTime = 0;
var healingNeedleActive = false;
var healingNeedleEndTime = 0;
var duplicateActive = false;
var duplicateEndTime = 0;
var currentPlayerClone = null;
// Boss fight green enemy spawning
var bossGreenEnemyTimer = 0;
var bossGreenEnemyInterval = 1800; // 30 seconds at 60 FPS
// UI elements
var levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0, 0);
levelText.x = 50;
levelText.y = 50;
LK.gui.topLeft.addChild(levelText);
var remainingText = new Text2('Enemies: 0', {
size: 45,
fill: 0xFFFFFF
});
remainingText.anchor.set(0, 0);
remainingText.x = 50;
remainingText.y = 120;
LK.gui.topLeft.addChild(remainingText);
// Create player health bar background (longer and slimmer black rectangle) at top of screen
var playerHealthBarBg = LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0,
width: 600,
height: 60,
tint: 0x000000
});
playerHealthBarBg.x = 0;
playerHealthBarBg.y = 50;
LK.gui.top.addChild(playerHealthBarBg);
// Create player health bar fill (longer and slimmer green rectangle)
var playerHealthBarFill = LK.getAsset('enemyHealthBarFill', {
anchorX: 0,
anchorY: 0,
width: 592,
height: 52,
tint: 0x00ff00
});
playerHealthBarFill.x = -296;
playerHealthBarFill.y = 54;
LK.gui.top.addChild(playerHealthBarFill);
// Create player health text (smaller white text)
var playerHealthBarText = new Text2('30', {
size: 42,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
playerHealthBarText.anchor.set(0.5, 0.5);
playerHealthBarText.x = 0;
playerHealthBarText.y = 87;
LK.gui.top.addChild(playerHealthBarText);
// Create coin display container below health bar
var coinDisplayContainer = new Container();
// Create spinning coin icon
var coinIcon = LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
coinIcon.x = -30; // Position to the left of text
coinIcon.y = 0;
// Start continuous coin spinning animation
function startCoinSpin() {
tween(coinIcon, {
scaleX: -0.8
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(coinIcon, {
scaleX: 0.8
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
startCoinSpin(); // Loop the animation
}
});
}
});
}
startCoinSpin();
var coinText = new Text2('0', {
size: 40,
fill: 0xF1C40F
});
coinText.anchor.set(0, 0.5);
coinText.x = 10; // Position to the right of icon
coinText.y = 0;
coinDisplayContainer.addChild(coinIcon);
coinDisplayContainer.addChild(coinText);
// Position container below health bar
coinDisplayContainer.x = 0;
coinDisplayContainer.y = 160;
LK.gui.top.addChild(coinDisplayContainer);
// Stats display in top-right corner
var statsContainer = new Container();
var healthStatText = new Text2('Health: 30', {
size: 35,
fill: 0xE74C3C
});
healthStatText.anchor.set(1, 0);
healthStatText.x = -50;
healthStatText.y = 50;
var attackPowerStatText = new Text2('Attack: 5', {
size: 35,
fill: 0xFF6B6B
});
attackPowerStatText.anchor.set(1, 0);
attackPowerStatText.x = -50;
attackPowerStatText.y = 90;
var attackSpeedStatText = new Text2('A.Speed: 120', {
size: 35,
fill: 0x4ECDC4
});
attackSpeedStatText.anchor.set(1, 0);
attackSpeedStatText.x = -50;
attackSpeedStatText.y = 130;
var movementSpeedStatText = new Text2('M.Speed: 2.0', {
size: 35,
fill: 0x45B7D1
});
movementSpeedStatText.anchor.set(1, 0);
movementSpeedStatText.x = -50;
movementSpeedStatText.y = 170;
var attackRangeStatText = new Text2('Range: 600', {
size: 35,
fill: 0x9B59B6
});
attackRangeStatText.anchor.set(1, 0);
attackRangeStatText.x = -50;
attackRangeStatText.y = 210;
// Power-up timer displays
var rocketTimerText = new Text2('', {
size: 30,
fill: 0xff4444
});
rocketTimerText.anchor.set(0, 0);
rocketTimerText.x = 50;
rocketTimerText.y = 200;
rocketTimerText.visible = false;
var freezeTimerText = new Text2('', {
size: 30,
fill: 0x44aaff
});
freezeTimerText.anchor.set(0, 0);
freezeTimerText.x = 50;
freezeTimerText.y = 240;
freezeTimerText.visible = false;
var machineGunTimerText = new Text2('', {
size: 30,
fill: 0xffff44
});
machineGunTimerText.anchor.set(0, 0);
machineGunTimerText.x = 50;
machineGunTimerText.y = 280;
machineGunTimerText.visible = false;
var magnetTimerText = new Text2('', {
size: 30,
fill: 0xaa44ff
});
magnetTimerText.anchor.set(0, 0);
magnetTimerText.x = 50;
magnetTimerText.y = 320;
magnetTimerText.visible = false;
var speedTimerText = new Text2('', {
size: 30,
fill: 0x44ff44
});
speedTimerText.anchor.set(0, 0);
speedTimerText.x = 50;
speedTimerText.y = 360;
speedTimerText.visible = false;
var wealthTimerText = new Text2('', {
size: 30,
fill: 0xffaa44
});
wealthTimerText.anchor.set(0, 0);
wealthTimerText.x = 50;
wealthTimerText.y = 400;
wealthTimerText.visible = false;
var ironBodyTimerText = new Text2('', {
size: 30,
fill: 0x888888
});
ironBodyTimerText.anchor.set(0, 0);
ironBodyTimerText.x = 50;
ironBodyTimerText.y = 440;
ironBodyTimerText.visible = false;
var healingNeedleTimerText = new Text2('', {
size: 30,
fill: 0xff4488
});
healingNeedleTimerText.anchor.set(0, 0);
healingNeedleTimerText.x = 50;
healingNeedleTimerText.y = 480;
healingNeedleTimerText.visible = false;
var duplicateTimerText = new Text2('', {
size: 30,
fill: 0xcccccc
});
duplicateTimerText.anchor.set(0, 0);
duplicateTimerText.x = 50;
duplicateTimerText.y = 560;
duplicateTimerText.visible = false;
var wealthValueText = new Text2('', {
size: 35,
fill: 0xFFD700
});
wealthValueText.anchor.set(1, 0);
wealthValueText.x = -50;
wealthValueText.y = 250;
wealthValueText.visible = false;
// Analog stick control elements
var analogStickContainer = new Container();
var analogStickBase = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 280,
height: 280,
alpha: 0.3,
tint: 0x888888
});
var analogStickKnob = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
width: 140,
height: 140,
alpha: 0.6,
tint: 0xFFFFFF
});
analogStickContainer.addChild(analogStickBase);
analogStickContainer.addChild(analogStickKnob);
analogStickContainer.x = -150; // Position in bottom right
analogStickContainer.y = -150;
LK.gui.bottomRight.addChild(analogStickContainer);
// Analog stick control preferences (loaded from storage)
var analogStickEnabled = storage.analogStickEnabled !== undefined ? storage.analogStickEnabled : true; // Default enabled
var analogStickPosition = storage.analogStickPosition || 'right'; // 'left' or 'right'
// Music control preferences (loaded from storage)
var musicEnabled = storage.musicEnabled !== undefined ? storage.musicEnabled : true; // Default enabled
// Analog stick state
var analogStickActive = false;
var analogStickStartPos = {
x: 0,
y: 0
};
var analogStickCurrentPos = {
x: 0,
y: 0
};
var analogStickDirection = {
x: 0,
y: 0
};
var analogStickRadius = 140; // Half of base width
var speedValueText = new Text2('', {
size: 35,
fill: 0x44ff44
});
speedValueText.anchor.set(1, 0);
speedValueText.x = -50;
speedValueText.y = 290;
speedValueText.visible = false;
var magnetValueText = new Text2('', {
size: 35,
fill: 0xaa44ff
});
magnetValueText.anchor.set(1, 0);
magnetValueText.x = -50;
magnetValueText.y = 330;
magnetValueText.visible = false;
var ironBodyValueText = new Text2('', {
size: 35,
fill: 0x888888
});
ironBodyValueText.anchor.set(1, 0);
ironBodyValueText.x = -50;
ironBodyValueText.y = 370;
ironBodyValueText.visible = false;
var healingNeedleValueText = new Text2('', {
size: 35,
fill: 0xff4488
});
healingNeedleValueText.anchor.set(1, 0);
healingNeedleValueText.x = -50;
healingNeedleValueText.y = 410;
healingNeedleValueText.visible = false;
var machineGunValueText = new Text2('', {
size: 35,
fill: 0xffff44
});
machineGunValueText.anchor.set(1, 0);
machineGunValueText.x = -50;
machineGunValueText.y = 450;
machineGunValueText.visible = false;
var rocketLauncherValueText = new Text2('', {
size: 35,
fill: 0xff4444
});
rocketLauncherValueText.anchor.set(1, 0);
rocketLauncherValueText.x = -50;
rocketLauncherValueText.y = 490;
rocketLauncherValueText.visible = false;
var freezeBombValueText = new Text2('', {
size: 35,
fill: 0x44aaff
});
freezeBombValueText.anchor.set(1, 0);
freezeBombValueText.x = -50;
freezeBombValueText.y = 530;
freezeBombValueText.visible = false;
var duplicateValueText = new Text2('', {
size: 35,
fill: 0xcccccc
});
duplicateValueText.anchor.set(1, 0);
duplicateValueText.x = -50;
duplicateValueText.y = 610;
duplicateValueText.visible = false;
LK.gui.topLeft.addChild(rocketTimerText);
LK.gui.topLeft.addChild(freezeTimerText);
LK.gui.topLeft.addChild(machineGunTimerText);
LK.gui.topLeft.addChild(magnetTimerText);
LK.gui.topLeft.addChild(speedTimerText);
LK.gui.topLeft.addChild(wealthTimerText);
LK.gui.topLeft.addChild(ironBodyTimerText);
LK.gui.topLeft.addChild(healingNeedleTimerText);
LK.gui.topRight.addChild(wealthValueText);
LK.gui.topRight.addChild(speedValueText);
LK.gui.topRight.addChild(magnetValueText);
LK.gui.topRight.addChild(ironBodyValueText);
LK.gui.topRight.addChild(healingNeedleValueText);
LK.gui.topRight.addChild(machineGunValueText);
LK.gui.topRight.addChild(rocketLauncherValueText);
LK.gui.topRight.addChild(freezeBombValueText);
LK.gui.topLeft.addChild(duplicateTimerText);
LK.gui.topRight.addChild(duplicateValueText);
LK.gui.topRight.addChild(healthStatText);
LK.gui.topRight.addChild(attackPowerStatText);
LK.gui.topRight.addChild(attackSpeedStatText);
LK.gui.topRight.addChild(movementSpeedStatText);
LK.gui.topRight.addChild(attackRangeStatText);
// Shop UI (hidden initially)
var shopContainer = new Container();
var shopBackground = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8,
scaleY: 6,
x: 0,
y: 0
});
shopContainer.addChild(shopBackground);
var shopTitle = new Text2('SHOP', {
size: 120,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopTitle.anchor.set(0.5, 0.5);
shopTitle.x = 0;
shopTitle.y = -400;
shopContainer.addChild(shopTitle);
var upgradeButtons = [];
var upgradeLabels = [];
var upgradePlusButtons = [];
var upgradeCostLabels = [];
var upgradeNames = ['Health +15', 'Attack Speed +10', 'Attack Power +10', 'Move Speed +0.8', 'Range +100'];
for (var i = 0; i < 5; i++) {
var buttonBg = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: -220 + i * 130
});
shopContainer.addChild(buttonBg);
upgradeButtons.push(buttonBg);
var label = new Text2(upgradeNames[i], {
size: 30,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
label.anchor.set(0.5, 0.5);
label.x = -200;
label.y = -220 + i * 130;
shopContainer.addChild(label);
upgradeLabels.push(label);
// Plus button for each upgrade
var plusButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
x: 100,
y: -220 + i * 130,
tint: 0xff0000
});
shopContainer.addChild(plusButton);
upgradePlusButtons.push(plusButton);
// Plus text
var plusText = new Text2('+', {
size: 40,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
plusText.anchor.set(0.5, 0.5);
plusText.x = 100;
plusText.y = -220 + i * 130;
shopContainer.addChild(plusText);
// Cost label
var costLabel = new Text2('Cost: 0', {
size: 28,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
costLabel.anchor.set(0.5, 0.5);
costLabel.x = 280;
costLabel.y = -220 + i * 130;
shopContainer.addChild(costLabel);
upgradeCostLabels.push(costLabel);
}
var closeShopButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 500,
y: -250,
scaleX: 0.7,
scaleY: 0.7
});
shopContainer.addChild(closeShopButton);
var closeShopText = new Text2('CLOSE', {
size: 40,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
closeShopText.anchor.set(0.5, 0.5);
closeShopText.x = 500;
closeShopText.y = -250;
shopContainer.addChild(closeShopText);
shopContainer.x = 1024;
shopContainer.y = 1366;
shopContainer.visible = false;
// Body part selection UI (hidden initially)
var bodyPartContainer = new Container();
var bodyPartBackground = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 10,
scaleY: 8,
x: 0,
y: 0
});
bodyPartContainer.addChild(bodyPartBackground);
var bodyPartTitle = new Text2('CHOOSE BODY PART', {
size: 70,
fill: 0xFFFFFF
});
bodyPartTitle.anchor.set(0.5, 0.5);
bodyPartTitle.x = 0;
bodyPartTitle.y = -250;
bodyPartContainer.addChild(bodyPartTitle);
var bodyPartButtons = [];
for (var i = 0; i < 3; i++) {
var partButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100 + i * 120
});
bodyPartContainer.addChild(partButton);
bodyPartButtons.push(partButton);
}
bodyPartContainer.x = 1024;
bodyPartContainer.y = 1366;
bodyPartContainer.visible = false;
// Main Menu UI
var mainMenuContainer = new Container();
var menuBackground = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 12,
scaleY: 8,
x: 0,
y: 0,
tint: 0x2c3e50
});
mainMenuContainer.addChild(menuBackground);
var gameTitle = new Text2('FIGHTING BACTERIA', {
size: 120,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
gameTitle.anchor.set(0.5, 0.5);
gameTitle.x = 0;
gameTitle.y = -250;
mainMenuContainer.addChild(gameTitle);
var playerNameDisplay = new Text2('', {
size: 60,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
playerNameDisplay.anchor.set(0.5, 0.5);
playerNameDisplay.x = 0;
playerNameDisplay.y = -150;
mainMenuContainer.addChild(playerNameDisplay);
var playButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 1.2,
scaleY: 1.0,
tint: 0x27ae60
});
mainMenuContainer.addChild(playButton);
var playText = new Text2('PLAY', {
size: 80,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
playText.anchor.set(0.5, 0.5);
playText.x = 0;
playText.y = 0;
mainMenuContainer.addChild(playText);
var scoreButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 150,
scaleX: 1.2,
scaleY: 1.0,
tint: 0x3498db
});
mainMenuContainer.addChild(scoreButton);
var scoreText = new Text2('SCORE', {
size: 80,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
scoreText.anchor.set(0.5, 0.5);
scoreText.x = 0;
scoreText.y = 150;
mainMenuContainer.addChild(scoreText);
// Analog control buttons
var analogControlTitle = new Text2('ANALOG CONTROL', {
size: 60,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
analogControlTitle.anchor.set(0.5, 0.5);
analogControlTitle.x = 0;
analogControlTitle.y = 250;
mainMenuContainer.addChild(analogControlTitle);
var analogOnButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 330,
scaleX: 0.8,
scaleY: 0.8,
tint: analogStickEnabled ? 0x27ae60 : 0x7f8c8d
});
mainMenuContainer.addChild(analogOnButton);
var analogOnText = new Text2('ON', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
analogOnText.anchor.set(0.5, 0.5);
analogOnText.x = -200;
analogOnText.y = 330;
mainMenuContainer.addChild(analogOnText);
var analogOffButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -50,
y: 330,
scaleX: 0.8,
scaleY: 0.8,
tint: !analogStickEnabled ? 0xe74c3c : 0x7f8c8d
});
mainMenuContainer.addChild(analogOffButton);
var analogOffText = new Text2('OFF', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
analogOffText.anchor.set(0.5, 0.5);
analogOffText.x = -50;
analogOffText.y = 330;
mainMenuContainer.addChild(analogOffText);
var analogLeftButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 330,
scaleX: 0.8,
scaleY: 0.8,
tint: analogStickPosition === 'left' ? 0xf39c12 : 0x7f8c8d
});
mainMenuContainer.addChild(analogLeftButton);
var analogLeftText = new Text2('LEFT', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
analogLeftText.anchor.set(0.5, 0.5);
analogLeftText.x = 100;
analogLeftText.y = 330;
mainMenuContainer.addChild(analogLeftText);
var analogRightButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 250,
y: 330,
scaleX: 0.8,
scaleY: 0.8,
tint: analogStickPosition === 'right' ? 0xf39c12 : 0x7f8c8d
});
mainMenuContainer.addChild(analogRightButton);
var analogRightText = new Text2('RIGHT', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
analogRightText.anchor.set(0.5, 0.5);
analogRightText.x = 250;
analogRightText.y = 330;
mainMenuContainer.addChild(analogRightText);
// Music control title
var musicControlTitle = new Text2('MUSIC & SOUND', {
size: 60,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
musicControlTitle.anchor.set(0.5, 0.5);
musicControlTitle.x = 0;
musicControlTitle.y = 410;
mainMenuContainer.addChild(musicControlTitle);
var musicOnButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -100,
y: 490,
scaleX: 0.8,
scaleY: 0.8,
tint: musicEnabled ? 0x27ae60 : 0x7f8c8d
});
mainMenuContainer.addChild(musicOnButton);
var musicOnText = new Text2('ON', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
musicOnText.anchor.set(0.5, 0.5);
musicOnText.x = -100;
musicOnText.y = 490;
mainMenuContainer.addChild(musicOnText);
var musicOffButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 490,
scaleX: 0.8,
scaleY: 0.8,
tint: !musicEnabled ? 0xe74c3c : 0x7f8c8d
});
mainMenuContainer.addChild(musicOffButton);
var musicOffText = new Text2('OFF', {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
musicOffText.anchor.set(0.5, 0.5);
musicOffText.x = 100;
musicOffText.y = 490;
mainMenuContainer.addChild(musicOffText);
mainMenuContainer.x = 1024;
mainMenuContainer.y = 1366;
mainMenuContainer.visible = true;
// Nickname Selection UI
var nicknameContainer = new Container();
var nicknameBackground = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 12,
scaleY: 8,
x: 0,
y: 0,
tint: 0x34495e
});
nicknameContainer.addChild(nicknameBackground);
var nicknameTitle = new Text2('CHOOSE NICKNAME', {
size: 100,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
nicknameTitle.anchor.set(0.5, 0.5);
nicknameTitle.x = 0;
nicknameTitle.y = -200;
nicknameContainer.addChild(nicknameTitle);
var nicknameOptions = [];
var nicknameButtons = [];
var optionNames = ['Hero', 'Warrior', 'Fighter', 'Champion', 'Guardian', 'Legend'];
for (var i = 0; i < 6; i++) {
var nicknameButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: i % 3 * 300 - 300,
y: Math.floor(i / 3) * 120 - 50,
scaleX: 1.0,
scaleY: 0.8,
tint: 0x3498db
});
nicknameContainer.addChild(nicknameButton);
nicknameButtons.push(nicknameButton);
var nicknameText = new Text2(optionNames[i], {
size: 50,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
nicknameText.anchor.set(0.5, 0.5);
nicknameText.x = i % 3 * 300 - 300;
nicknameText.y = Math.floor(i / 3) * 120 - 50;
nicknameContainer.addChild(nicknameText);
nicknameOptions.push(nicknameText);
}
nicknameContainer.x = 1024;
nicknameContainer.y = 1366;
nicknameContainer.visible = false;
// Leaderboard UI
var leaderboardContainer = new Container();
var leaderboardBackground = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 12,
scaleY: 10,
x: 0,
y: 0,
tint: 0x34495e
});
leaderboardContainer.addChild(leaderboardBackground);
var leaderboardTitle = new Text2('HIGH SCORE', {
size: 80,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
leaderboardTitle.anchor.set(0.5, 0.5);
leaderboardTitle.x = 0;
leaderboardTitle.y = -380;
leaderboardContainer.addChild(leaderboardTitle);
var leaderboardTexts = [];
for (var i = 0; i < 10; i++) {
var leaderText = new Text2('', {
size: 45,
fill: i === 0 ? 0xFFD700 : i === 1 ? 0xC0C0C0 : i === 2 ? 0xCD7F32 : 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
leaderText.anchor.set(0.5, 0.5);
leaderText.x = 0;
leaderText.y = -300 + i * 60;
leaderboardContainer.addChild(leaderText);
leaderboardTexts.push(leaderText);
}
var backButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 400,
scaleX: 1.0,
scaleY: 0.8,
tint: 0xe74c3c
});
leaderboardContainer.addChild(backButton);
var backText = new Text2('BACK', {
size: 60,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
backText.anchor.set(0.5, 0.5);
backText.x = 0;
backText.y = 400;
leaderboardContainer.addChild(backText);
leaderboardContainer.x = 1024;
leaderboardContainer.y = 1366;
leaderboardContainer.visible = false;
game.addChild(mainMenuContainer);
game.addChild(nicknameContainer);
game.addChild(leaderboardContainer);
game.addChild(shopContainer);
game.addChild(bodyPartContainer);
// Initialize player as null (will be created when game starts)
player = null;
// Hide game UI elements initially since we start in menu
// Check if we need to show nickname selection or main menu
if (!playerNickname) {
gameState = 'nickname';
mainMenuContainer.visible = false;
nicknameContainer.visible = true;
} else {
gameState = 'menu';
playerNameDisplay.setText('Welcome, ' + playerNickname + '!');
}
levelText.visible = false;
remainingText.visible = false;
playerHealthBarBg.visible = false;
playerHealthBarFill.visible = false;
playerHealthBarText.visible = false;
coinDisplayContainer.visible = false;
healthStatText.visible = false;
attackPowerStatText.visible = false;
attackSpeedStatText.visible = false;
movementSpeedStatText.visible = false;
attackRangeStatText.visible = false;
analogStickContainer.visible = false;
// Spawn initial enemies
function spawnEnemies() {
// No enemies during boss rounds (every 10 levels: 10, 20, 30, etc.)
if (currentLevel % 10 === 0) {
return;
}
// Start gameplay music when spawning enemies (transitioning from main menu to actual gameplay)
if (currentLevel === 1) {
if (musicEnabled) LK.playMusic('3', {
loop: true
});
}
// Calculate enemy count: starts at level 1 with 1 enemy, adds 1 per level, capped at 15
var enemiesToSpawn = Math.min(15, currentLevel);
remainingEnemies = enemiesToSpawn;
remainingText.setText('Enemies: ' + remainingEnemies);
// Guarantee 30% chance to spawn exactly one green enemy per level (limited to 1 per level)
var shouldSpawnGreen = Math.random() < 0.3;
var greenEnemySpawned = false;
for (var i = 0; i < enemiesToSpawn; i++) {
// Spawn green enemy on first iteration if we should spawn one and haven't yet
var isGreen = shouldSpawnGreen && !greenEnemySpawned;
if (isGreen) {
greenEnemySpawned = true; // Mark that we've spawned our one green enemy for this level
}
var enemy = isGreen ? new GreenEnemy() : new Enemy();
var validPosition = false;
var attempts = 0;
// Try to find non-overlapping position
while (!validPosition && attempts < 50) {
// Spawn from edges
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -50;
break;
case 1:
// Right
enemy.x = 2098;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2782;
break;
case 3:
// Left
enemy.x = -50;
enemy.y = Math.random() * 2732;
break;
}
// Check if position overlaps with existing enemies
validPosition = true;
for (var j = 0; j < enemies.length; j++) {
var dx = enemy.x - enemies[j].x;
var dy = enemy.y - enemies[j].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
// Minimum distance between enemies
validPosition = false;
break;
}
}
for (var g = 0; g < greenEnemies.length; g++) {
var dx = enemy.x - greenEnemies[g].x;
var dy = enemy.y - greenEnemies[g].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
validPosition = false;
break;
}
}
attempts++;
}
if (isGreen) {
greenEnemies.push(enemy);
} else {
enemies.push(enemy);
}
game.addChild(enemy);
}
}
function spawnBoss() {
// Play boss fight music
if (musicEnabled) LK.playMusic('1');
// Check which boss to spawn based on level
if (currentLevel === 30) {
currentBoss = new Level30Boss();
} else if (currentLevel % 20 === 0) {
// Every 20 levels: Spawner Boss (levels 20, 40, 60, etc.)
currentBoss = new SpawnerBoss();
} else {
// Every 10 levels: Regular Boss (levels 10, 50, 70, etc.)
currentBoss = new Boss();
}
currentBoss.x = 1024;
currentBoss.y = 200;
game.addChild(currentBoss);
// Reset boss green enemy timer
bossGreenEnemyTimer = 0;
}
function showShop() {
gameState = 'shop';
shopContainer.visible = true;
updateShopDisplay();
// Restore player health to full when entering shop
if (player) {
player.health = player.maxHealth;
}
// Attract all coins and diamonds currently on the ground toward player
for (var i = 0; i < coins.length; i++) {
var coin = coins[i];
// Stop any existing movement tweens
tween.stop(coin, {
x: true,
y: true
});
// Calculate duration based on distance to player
var dx = player.x - coin.x;
var dy = player.y - coin.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var duration = Math.min(1000, distance * 2); // Max 1 second, scale with distance
// Tween coin toward player
tween(coin, {
x: player.x,
y: player.y
}, {
duration: duration,
easing: tween.easeInOut
});
}
for (var i = 0; i < diamonds.length; i++) {
var diamond = diamonds[i];
// Stop any existing movement tweens
tween.stop(diamond, {
x: true,
y: true
});
// Calculate duration based on distance to player
var dx = player.x - diamond.x;
var dy = player.y - diamond.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var duration = Math.min(1000, distance * 2); // Max 1 second, scale with distance
// Tween diamond toward player
tween(diamond, {
x: player.x,
y: player.y
}, {
duration: duration,
easing: tween.easeInOut
});
}
for (var i = 0; i < embers.length; i++) {
var ember = embers[i];
// Stop any existing movement tweens
tween.stop(ember, {
x: true,
y: true
});
// Calculate duration based on distance to player
var dx = player.x - ember.x;
var dy = player.y - ember.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var duration = Math.min(1000, distance * 2); // Max 1 second, scale with distance
// Tween ember toward player
tween(ember, {
x: player.x,
y: player.y
}, {
duration: duration,
easing: tween.easeInOut
});
}
// Hide player if within shop area to prevent overlap
if (player) {
player.visible = false;
}
// Hide health packs when shop opens
for (var i = 0; i < healthPacks.length; i++) {
healthPacks[i].visible = false;
}
// Pause all game objects
for (var i = 0; i < enemies.length; i++) {
enemies[i].visible = false;
}
}
function updateShopDisplay() {
var baseCosts = [15, 15, 15, 15, 15];
var upgradeLevels = [healthUpgradeLevel, attackSpeedUpgradeLevel, attackPowerUpgradeLevel, movementSpeedUpgradeLevel, attackRangeUpgradeLevel];
var anyAffordable = false;
for (var i = 0; i < 5; i++) {
var cost = Math.floor(baseCosts[i] * Math.pow(2.5, upgradeLevels[i]));
upgradeCostLabels[i].setText('Cost: ' + formatCurrency(cost));
// Enable/disable plus button based on affordability
if (playerCoins >= cost) {
upgradePlusButtons[i].visible = true;
upgradePlusButtons[i].alpha = 1.0;
anyAffordable = true;
} else {
upgradePlusButtons[i].visible = true;
upgradePlusButtons[i].alpha = 0.3;
}
}
// Show/hide close button based on affordability
if (!anyAffordable) {
closeShopButton.visible = true;
closeShopText.visible = true;
} else {
closeShopButton.visible = true;
closeShopText.visible = true;
}
}
function hideShop() {
// Start normal gameplay music when entering actual gameplay (unless it's a boss level)
if (currentLevel % 10 !== 0) {
if (musicEnabled) LK.playMusic('3', {
loop: true
});
}
gameState = 'playing';
shopContainer.visible = false;
// Show player again
if (player) {
player.visible = true;
}
// Show health packs when shop closes
for (var i = 0; i < healthPacks.length; i++) {
healthPacks[i].visible = true;
}
// Resume all game objects
for (var i = 0; i < enemies.length; i++) {
enemies[i].visible = true;
}
// Check if current level is a boss level and spawn boss instead of regular enemies
if (currentLevel % 10 === 0) {
// Spawn boss at boss levels
spawnBoss();
} else {
// Spawn regular enemies for non-boss levels
spawnEnemies();
}
}
function showBodyPartSelection() {
gameState = 'bodyPartSelection';
bodyPartContainer.visible = true;
}
function hideBodyPartSelection() {
gameState = 'playing';
bodyPartContainer.visible = false;
// Level progression is now handled in game.update
enemiesKilled = 0;
spawnEnemies();
}
// Event handlers
game.down = function (x, y, obj) {
if (gameState === 'nickname') {
// Convert screen coordinates to nickname container coordinates
var nicknamePos = {
x: x - nicknameContainer.x,
y: y - nicknameContainer.y
};
// Check nickname option buttons
for (var i = 0; i < nicknameButtons.length; i++) {
var button = nicknameButtons[i];
if (Math.abs(nicknamePos.x - button.x) < 140 && Math.abs(nicknamePos.y - button.y) < 60) {
selectNickname(optionNames[i]);
return;
}
}
}
if (gameState === 'menu') {
// Convert screen coordinates to menu container coordinates
var menuPos = {
x: x - mainMenuContainer.x,
y: y - mainMenuContainer.y
};
// Check play button
if (Math.abs(menuPos.x - playButton.x) < 140 && Math.abs(menuPos.y - playButton.y) < 80) {
startGame();
return;
}
// Check score button
if (Math.abs(menuPos.x - scoreButton.x) < 140 && Math.abs(menuPos.y - scoreButton.y) < 80) {
showLeaderboard();
return;
}
// Check analog control buttons
if (Math.abs(menuPos.x - analogOnButton.x) < 100 && Math.abs(menuPos.y - analogOnButton.y) < 40) {
analogStickEnabled = true;
storage.analogStickEnabled = analogStickEnabled;
updateAnalogStickDisplay();
return;
}
if (Math.abs(menuPos.x - analogOffButton.x) < 100 && Math.abs(menuPos.y - analogOffButton.y) < 40) {
analogStickEnabled = false;
storage.analogStickEnabled = analogStickEnabled;
updateAnalogStickDisplay();
return;
}
if (Math.abs(menuPos.x - analogLeftButton.x) < 100 && Math.abs(menuPos.y - analogLeftButton.y) < 40) {
analogStickPosition = 'left';
storage.analogStickPosition = analogStickPosition;
updateAnalogStickDisplay();
return;
}
if (Math.abs(menuPos.x - analogRightButton.x) < 100 && Math.abs(menuPos.y - analogRightButton.y) < 40) {
analogStickPosition = 'right';
storage.analogStickPosition = analogStickPosition;
updateAnalogStickDisplay();
return;
}
// Check music control buttons
if (Math.abs(menuPos.x - musicOnButton.x) < 100 && Math.abs(menuPos.y - musicOnButton.y) < 40) {
musicEnabled = true;
storage.musicEnabled = musicEnabled;
updateMusicDisplay();
// Resume current menu music if we just enabled music
if (musicEnabled) {
LK.playMusic('2', {
loop: true
});
}
return;
}
if (Math.abs(menuPos.x - musicOffButton.x) < 100 && Math.abs(menuPos.y - musicOffButton.y) < 40) {
musicEnabled = false;
storage.musicEnabled = musicEnabled;
updateMusicDisplay();
// Stop all music and sounds when disabled
if (!musicEnabled) {
LK.stopMusic();
}
return;
}
}
if (gameState === 'leaderboard') {
// Convert screen coordinates to leaderboard container coordinates
var leaderPos = {
x: x - leaderboardContainer.x,
y: y - leaderboardContainer.y
};
// Check back button
if (Math.abs(leaderPos.x - backButton.x) < 140 && Math.abs(leaderPos.y - backButton.y) < 60) {
showMainMenu();
return;
}
}
if (gameState === 'playing' && analogStickEnabled) {
// Check if touch is on analog stick first before setting touch position
var stickCenterX, stickCenterY;
if (analogStickPosition === 'left') {
stickCenterX = 350; // Bottom left x position, moved closer to center
stickCenterY = 2732 - 320; // Bottom left y position, moved closer to center
} else {
stickCenterX = 2048 - 350; // Bottom right x position, moved closer to center
stickCenterY = 2732 - 320; // Bottom right y position, moved closer to center
}
var distanceToStick = Math.sqrt((x - stickCenterX) * (x - stickCenterX) + (y - stickCenterY) * (y - stickCenterY));
if (distanceToStick <= analogStickRadius) {
// Start analog stick control immediately
analogStickActive = true;
analogStickStartPos.x = stickCenterX;
analogStickStartPos.y = stickCenterY;
analogStickCurrentPos.x = x;
analogStickCurrentPos.y = y;
// Clear any existing touchPosition when analog stick becomes active
touchPosition = null;
// Calculate initial direction immediately with high precision
var deltaX = x - stickCenterX;
var deltaY = y - stickCenterY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Update knob position relative to base immediately
if (distance > analogStickRadius) {
// Clamp to radius but maintain precise direction
var ratio = analogStickRadius / distance;
analogStickKnob.x = deltaX * ratio;
analogStickKnob.y = deltaY * ratio;
// Normalize direction to exactly 1.0 for edge cases
analogStickDirection.x = deltaX / distance;
analogStickDirection.y = deltaY / distance;
} else {
analogStickKnob.x = deltaX;
analogStickKnob.y = deltaY;
// Calculate normalized direction with no dead zone for immediate response
analogStickDirection.x = distance > 1 ? deltaX / analogStickRadius : 0;
analogStickDirection.y = distance > 1 ? deltaY / analogStickRadius : 0;
}
return; // Don't set touchPosition if using analog stick
}
}
// Only set touch position if analog stick is disabled or touch is outside analog area
if (!analogStickEnabled) {
touchPosition = {
x: x,
y: y
};
}
if (gameState === 'shop') {
// Convert screen coordinates to shop container coordinates
var shopPos = {
x: x - shopContainer.x,
y: y - shopContainer.y
};
// Check plus buttons
for (var i = 0; i < upgradePlusButtons.length; i++) {
var plusButton = upgradePlusButtons[i];
if (plusButton.visible && Math.abs(shopPos.x - plusButton.x) < 80 && Math.abs(shopPos.y - plusButton.y) < 40) {
var baseCosts = [15, 15, 15, 15, 15];
var upgradeLevels = [healthUpgradeLevel, attackSpeedUpgradeLevel, attackPowerUpgradeLevel, movementSpeedUpgradeLevel, attackRangeUpgradeLevel];
var cost = Math.floor(baseCosts[i] * Math.pow(2.5, upgradeLevels[i]));
if (playerCoins >= cost) {
purchaseUpgrade(i);
updateShopDisplay();
}
return;
}
}
// Check close button
if (Math.abs(shopPos.x - closeShopButton.x) < 120 && Math.abs(shopPos.y - closeShopButton.y) < 50) {
hideShop();
}
}
if (gameState === 'bodyPartSelection') {
// Convert screen coordinates to body part container coordinates
var bodyPos = {
x: x - bodyPartContainer.x,
y: y - bodyPartContainer.y
};
for (var i = 0; i < bodyPartButtons.length; i++) {
var button = bodyPartButtons[i];
if (Math.abs(bodyPos.x - button.x) < 200 && Math.abs(bodyPos.y - button.y) < 80) {
selectBodyPart(i);
hideBodyPartSelection();
return;
}
}
}
};
game.move = function (x, y, obj) {
if (gameState === 'playing') {
if (analogStickActive) {
// Update analog stick position with high precision
analogStickCurrentPos.x = x;
analogStickCurrentPos.y = y;
// Calculate direction and distance from center position with improved precision
var deltaX = x - analogStickStartPos.x;
var deltaY = y - analogStickStartPos.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Update knob position and direction with no artificial dead zone for maximum responsiveness
if (distance > analogStickRadius) {
// Clamp to radius but maintain precise direction
var ratio = analogStickRadius / distance;
analogStickKnob.x = deltaX * ratio;
analogStickKnob.y = deltaY * ratio;
// Normalize direction to exactly 1.0 magnitude for consistent max speed
analogStickDirection.x = deltaX / distance;
analogStickDirection.y = deltaY / distance;
} else {
analogStickKnob.x = deltaX;
analogStickKnob.y = deltaY;
// Calculate precise normalized direction with minimal dead zone for instant response
if (distance > 1) {
analogStickDirection.x = deltaX / analogStickRadius;
analogStickDirection.y = deltaY / analogStickRadius;
} else {
analogStickDirection.x = 0;
analogStickDirection.y = 0;
}
}
} else if (analogStickEnabled) {
// Check if touch enters analog stick area during move
var stickCenterX, stickCenterY;
if (analogStickPosition === 'left') {
stickCenterX = 350;
stickCenterY = 2732 - 320;
} else {
stickCenterX = 2048 - 350;
stickCenterY = 2732 - 320;
}
var distanceToStick = Math.sqrt((x - stickCenterX) * (x - stickCenterX) + (y - stickCenterY) * (y - stickCenterY));
if (distanceToStick <= analogStickRadius) {
// Activate analog stick immediately when entering area
analogStickActive = true;
analogStickStartPos.x = stickCenterX;
analogStickStartPos.y = stickCenterY;
analogStickCurrentPos.x = x;
analogStickCurrentPos.y = y;
touchPosition = null;
// Calculate initial direction with high precision
var deltaX = x - stickCenterX;
var deltaY = y - stickCenterY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > analogStickRadius) {
var ratio = analogStickRadius / distance;
analogStickKnob.x = deltaX * ratio;
analogStickKnob.y = deltaY * ratio;
analogStickDirection.x = deltaX / distance;
analogStickDirection.y = deltaY / distance;
} else {
analogStickKnob.x = deltaX;
analogStickKnob.y = deltaY;
analogStickDirection.x = distance > 1 ? deltaX / analogStickRadius : 0;
analogStickDirection.y = distance > 1 ? deltaY / analogStickRadius : 0;
}
} else {
// Regular touch movement outside analog area
touchPosition = {
x: x,
y: y
};
}
} else {
// Analog stick disabled, use regular touch movement
touchPosition = {
x: x,
y: y
};
}
// Make character turn head during iron body mode
if (player && ironBodyActive && player.ironBodyGraphics) {
var dx = x - player.x;
// Turn head based on mouse position
if (dx > 0) {
player.ironBodyGraphics.scaleX = -Math.abs(player.ironBodyGraphics.scaleX); // Face right
} else if (dx < 0) {
player.ironBodyGraphics.scaleX = Math.abs(player.ironBodyGraphics.scaleX); // Face left
}
}
}
};
game.up = function (x, y, obj) {
if (gameState === 'playing') {
if (analogStickActive) {
// Reset analog stick completely
analogStickActive = false;
analogStickKnob.x = 0;
analogStickKnob.y = 0;
analogStickDirection.x = 0;
analogStickDirection.y = 0;
analogStickStartPos.x = 0;
analogStickStartPos.y = 0;
analogStickCurrentPos.x = 0;
analogStickCurrentPos.y = 0;
}
// Always reset touch position when releasing touch
touchPosition = null;
}
};
function purchaseUpgrade(upgradeIndex) {
var baseCosts = [15, 15, 15, 15, 15];
var upgradeLevels = [healthUpgradeLevel, attackSpeedUpgradeLevel, attackPowerUpgradeLevel, movementSpeedUpgradeLevel, attackRangeUpgradeLevel];
var cost = Math.floor(baseCosts[upgradeIndex] * Math.pow(2.5, upgradeLevels[upgradeIndex]));
if (playerCoins >= cost) {
playerCoins -= cost;
coinText.setText(formatCurrency(playerCoins));
switch (upgradeIndex) {
case 0:
// Health upgrade
healthUpgradeLevel++;
storage.healthUpgradeLevel = healthUpgradeLevel;
player.maxHealth += 15;
player.health = player.maxHealth;
break;
case 1:
// Attack Speed upgrade
attackSpeedUpgradeLevel++;
storage.attackSpeedUpgradeLevel = attackSpeedUpgradeLevel;
// Calculate new attack speed to increase display by exactly 10
var currentDisplayValue = Math.round(3600 / player.attackSpeed);
var newDisplayValue = currentDisplayValue + 10;
player.attackSpeed = Math.round(3600 / newDisplayValue);
player.attackSpeed = Math.max(5, player.attackSpeed);
break;
case 2:
// Attack Power upgrade
attackPowerUpgradeLevel++;
storage.attackPowerUpgradeLevel = attackPowerUpgradeLevel;
player.damage += 10;
break;
case 3:
// Movement Speed upgrade
movementSpeedUpgradeLevel++;
storage.movementSpeedUpgradeLevel = movementSpeedUpgradeLevel;
player.speed += 0.8;
break;
case 4:
// Attack Range upgrade
attackRangeUpgradeLevel++;
storage.attackRangeUpgradeLevel = attackRangeUpgradeLevel;
player.attackRange += 50;
// Update visual range circle
var rangeGraphics = player.children[1]; // Second child is the range circle
rangeGraphics.width = player.attackRange * 2;
rangeGraphics.height = player.attackRange * 2;
break;
}
updateStatsDisplay();
}
}
function updateStatsDisplay() {
healthStatText.setText('Health: ' + player.maxHealth);
attackPowerStatText.setText('Attack: ' + player.damage);
attackSpeedStatText.setText('A.Speed: ' + Math.round(3600 / player.attackSpeed));
movementSpeedStatText.setText('M.Speed: ' + player.speed.toFixed(1));
attackRangeStatText.setText('Range: ' + player.attackRange * 2);
}
function selectBodyPart(partIndex) {
switch (partIndex) {
case 0:
// Strong Arms - More damage
player.damage += 25;
player.scaleX *= 1.1;
break;
case 1:
// Swift Legs - More speed
player.speed += 2;
player.scaleY *= 1.1;
break;
case 2:
// Tough Body - More health
player.maxHealth += 50;
player.health = player.maxHealth;
player.scaleX *= 1.05;
player.scaleY *= 1.05;
break;
}
}
function startGame() {
gameState = 'playing';
mainMenuContainer.visible = false;
leaderboardContainer.visible = false;
// Reset analog stick state completely when starting game
analogStickActive = false;
analogStickDirection.x = 0;
analogStickDirection.y = 0;
analogStickKnob.x = 0;
analogStickKnob.y = 0;
analogStickStartPos.x = 0;
analogStickStartPos.y = 0;
analogStickCurrentPos.x = 0;
analogStickCurrentPos.y = 0;
touchPosition = null;
// Show game UI elements
levelText.visible = true;
remainingText.visible = true;
playerHealthBarBg.visible = true;
playerHealthBarFill.visible = true;
playerHealthBarText.visible = true;
coinDisplayContainer.visible = true;
healthStatText.visible = true;
attackPowerStatText.visible = true;
attackSpeedStatText.visible = true;
movementSpeedStatText.visible = true;
attackRangeStatText.visible = true;
updateAnalogStickDisplay();
// Reset game state completely to start from beginning
currentLevel = 1;
enemiesKilled = 0;
playerCoins = 0;
healthUpgradeLevel = 0;
attackSpeedUpgradeLevel = 0;
attackPowerUpgradeLevel = 0;
movementSpeedUpgradeLevel = 0;
attackRangeUpgradeLevel = 0;
fireBallBulletCount = 1;
// Clear all game objects
for (var i = 0; i < enemies.length; i++) {
enemies[i].destroy();
}
for (var i = 0; i < greenEnemies.length; i++) {
greenEnemies[i].destroy();
}
for (var i = 0; i < playerBullets.length; i++) {
playerBullets[i].destroy();
}
for (var i = 0; i < bossBullets.length; i++) {
bossBullets[i].destroy();
}
for (var i = 0; i < coins.length; i++) {
coins[i].destroy();
}
for (var i = 0; i < diamonds.length; i++) {
diamonds[i].destroy();
}
for (var i = 0; i < embers.length; i++) {
embers[i].destroy();
}
for (var i = 0; i < healthPacks.length; i++) {
healthPacks[i].destroy();
}
for (var i = 0; i < powerUps.length; i++) {
powerUps[i].destroy();
}
if (currentBoss) {
currentBoss.destroy();
}
if (player) {
player.destroy();
}
// Clear all arrays
enemies = [];
greenEnemies = [];
playerBullets = [];
bossBullets = [];
coins = [];
diamonds = [];
embers = [];
healthPacks = [];
powerUps = [];
currentBoss = null;
// Reset power-up states
rocketLauncherActive = false;
freezeEffectActive = false;
machineGunActive = false;
magnetActive = false;
speedActive = false;
wealthActive = false;
ironBodyActive = false;
healingNeedleActive = false;
duplicateActive = false;
if (currentPlayerClone) {
currentPlayerClone.destroy();
currentPlayerClone = null;
}
// Initialize fresh player
player = new Player();
player.x = 1024;
player.y = 1366;
game.addChild(player);
// Update UI
levelText.setText('Level: 1');
coinText.setText(formatCurrency(0));
updateStatsDisplay();
// Spawn initial enemies
spawnEnemies();
}
function showLeaderboard() {
gameState = 'leaderboard';
mainMenuContainer.visible = false;
leaderboardContainer.visible = true;
// Make leaderboard background opaque to hide game elements
leaderboardBackground.tint = 0x000000;
leaderboardBackground.alpha = 0.95;
// Hide all game elements when leaderboard is open
for (var i = 0; i < enemies.length; i++) {
enemies[i].visible = false;
}
for (var i = 0; i < greenEnemies.length; i++) {
greenEnemies[i].visible = false;
}
for (var i = 0; i < playerBullets.length; i++) {
playerBullets[i].visible = false;
}
for (var i = 0; i < bossBullets.length; i++) {
bossBullets[i].visible = false;
}
for (var i = 0; i < coins.length; i++) {
coins[i].visible = false;
}
for (var i = 0; i < diamonds.length; i++) {
diamonds[i].visible = false;
}
for (var i = 0; i < embers.length; i++) {
embers[i].visible = false;
}
for (var i = 0; i < healthPacks.length; i++) {
healthPacks[i].visible = false;
}
for (var i = 0; i < powerUps.length; i++) {
powerUps[i].visible = false;
}
if (currentBoss) {
currentBoss.visible = false;
}
if (player) {
player.visible = false;
}
// Get player's personal best scores from storage
var playerHighestLevel = storage.playerHighestLevel || 1;
var playerSurvivalTime = storage.playerSurvivalTime || 0;
// Log for debugging
console.log('Loading personal scores - Level:', playerHighestLevel, 'Time:', playerSurvivalTime);
// Display personal best scores
for (var i = 0; i < 10; i++) {
if (i === 0) {
leaderboardTexts[i].setText('Your Highest Level: ' + playerHighestLevel);
} else if (i === 1) {
var minutes = Math.floor(playerSurvivalTime / 60);
var seconds = playerSurvivalTime % 60;
var timeText = minutes + 'm ' + seconds + 's';
leaderboardTexts[i].setText('Your Best Survival Time: ' + timeText);
} else {
leaderboardTexts[i].setText('');
}
}
}
function selectNickname(nickname) {
playerNickname = nickname + Math.floor(Math.random() * 1000); // Add random number to make unique
storage.playerNickname = playerNickname; // Save to storage permanently
console.log('Nickname selected and saved:', playerNickname);
// Stop any current music before showing main menu
if (musicEnabled) LK.stopMusic();
showMainMenu();
}
function showMainMenu() {
// Play main menu music
if (musicEnabled) LK.playMusic('2', {
loop: true
});
gameState = 'menu';
mainMenuContainer.visible = true;
nicknameContainer.visible = false;
leaderboardContainer.visible = false;
shopContainer.visible = false;
bodyPartContainer.visible = false;
// Reset analog stick state completely when returning to menu
analogStickActive = false;
analogStickDirection.x = 0;
analogStickDirection.y = 0;
analogStickKnob.x = 0;
analogStickKnob.y = 0;
analogStickStartPos.x = 0;
analogStickStartPos.y = 0;
analogStickCurrentPos.x = 0;
analogStickCurrentPos.y = 0;
touchPosition = null;
// Show all game elements when returning from leaderboard
for (var i = 0; i < enemies.length; i++) {
enemies[i].visible = true;
}
for (var i = 0; i < greenEnemies.length; i++) {
greenEnemies[i].visible = true;
}
for (var i = 0; i < playerBullets.length; i++) {
playerBullets[i].visible = true;
}
for (var i = 0; i < bossBullets.length; i++) {
bossBullets[i].visible = true;
}
for (var i = 0; i < coins.length; i++) {
coins[i].visible = true;
}
for (var i = 0; i < diamonds.length; i++) {
diamonds[i].visible = true;
}
for (var i = 0; i < embers.length; i++) {
embers[i].visible = true;
}
for (var i = 0; i < healthPacks.length; i++) {
healthPacks[i].visible = true;
}
for (var i = 0; i < powerUps.length; i++) {
powerUps[i].visible = true;
}
if (currentBoss) {
currentBoss.visible = true;
}
if (player) {
player.visible = true;
}
// Update player name display
if (playerNickname) {
playerNameDisplay.setText('Welcome, ' + playerNickname + '!');
}
// Hide game UI elements
levelText.visible = false;
remainingText.visible = false;
playerHealthBarBg.visible = false;
playerHealthBarFill.visible = false;
playerHealthBarText.visible = false;
coinDisplayContainer.visible = false;
healthStatText.visible = false;
attackPowerStatText.visible = false;
attackSpeedStatText.visible = false;
movementSpeedStatText.visible = false;
attackRangeStatText.visible = false;
analogStickContainer.visible = false;
// Update analog stick display when showing menu
updateAnalogStickDisplay();
// Update music display when showing menu
updateMusicDisplay();
}
function saveScore(playerName, level) {
// Calculate survival time in seconds (currentLevel - 1 because we start at level 1)
var survivalTime = Math.max(0, level - 1) * 30; // Approximate 30 seconds per level
// Get existing personal best scores
var currentHighestLevel = storage.playerHighestLevel || 1;
var currentSurvivalTime = storage.playerSurvivalTime || 0;
// Update personal best level if current level is higher
if (level > currentHighestLevel) {
storage.playerHighestLevel = level;
console.log('New highest level achieved:', level);
}
// Update personal best survival time if current time is higher
if (survivalTime > currentSurvivalTime) {
storage.playerSurvivalTime = survivalTime;
console.log('New best survival time achieved:', survivalTime, 'seconds');
}
// Log for debugging
console.log('Score saved for:', playerName, 'Level:', level, 'Survival time:', survivalTime, 'seconds');
}
function createPlayerClone() {
// Remove existing clone if any
if (currentPlayerClone) {
currentPlayerClone.destroy();
currentPlayerClone = null;
}
// Create new clone
currentPlayerClone = new PlayerClone();
// Position clone near player but not overlapping
currentPlayerClone.x = player.x + (Math.random() - 0.5) * 200;
currentPlayerClone.y = player.y + (Math.random() - 0.5) * 200;
// Ensure clone spawns within game bounds
currentPlayerClone.x = Math.max(50, Math.min(1998, currentPlayerClone.x));
currentPlayerClone.y = Math.max(50, Math.min(2682, currentPlayerClone.y));
// Update clone stats to match player's current stats exactly
if (player) {
currentPlayerClone.maxHealth = player.maxHealth;
currentPlayerClone.health = player.maxHealth;
currentPlayerClone.speed = player.speed;
currentPlayerClone.damage = player.damage;
currentPlayerClone.attackSpeed = player.attackSpeed;
currentPlayerClone.attackRange = player.attackRange;
}
game.addChild(currentPlayerClone);
}
function updateAnalogStickDisplay() {
// Update button colors
analogOnButton.tint = analogStickEnabled ? 0x27ae60 : 0x7f8c8d;
analogOffButton.tint = !analogStickEnabled ? 0xe74c3c : 0x7f8c8d;
analogLeftButton.tint = analogStickPosition === 'left' ? 0xf39c12 : 0x7f8c8d;
analogRightButton.tint = analogStickPosition === 'right' ? 0xf39c12 : 0x7f8c8d;
// Update analog stick position and visibility
analogStickContainer.visible = analogStickEnabled && gameState === 'playing';
if (analogStickEnabled) {
if (analogStickPosition === 'left') {
// Move to bottom left, moved closer to center
LK.gui.bottomLeft.addChild(analogStickContainer);
analogStickContainer.x = 350;
analogStickContainer.y = -320;
} else {
// Move to bottom right, moved closer to center
LK.gui.bottomRight.addChild(analogStickContainer);
analogStickContainer.x = -350;
analogStickContainer.y = -320;
}
}
}
function updateMusicDisplay() {
// Update music button colors
musicOnButton.tint = musicEnabled ? 0x27ae60 : 0x7f8c8d;
musicOffButton.tint = !musicEnabled ? 0xe74c3c : 0x7f8c8d;
}
function formatCurrency(amount) {
if (amount >= 1000000) {
var millions = Math.floor(amount / 1000000);
var remainder = amount % 1000000;
var thousands = Math.floor(remainder / 1000);
var coins = remainder % 1000;
var result = millions + 'm';
if (thousands > 0) {
result += ' ' + thousands + 'k';
}
if (coins > 0) {
result += ' ' + coins + 'c';
}
return result;
} else if (amount >= 1000) {
var thousands = Math.floor(amount / 1000);
var coins = amount % 1000;
var result = thousands + 'k';
if (coins > 0) {
result += ' ' + coins + 'c';
}
return result;
} else {
return amount.toString();
}
}
function promptPlayerName() {
// Return the stored nickname
return playerNickname || 'Player' + Math.floor(Math.random() * 1000);
}
// Initial enemies will be spawned when game starts from menu
game.update = function () {
// Only run game logic when in playing state
if (gameState !== 'playing') {
return;
}
// Update player health bar at top of screen only if player exists
if (player) {
var healthPercent = player.health / player.maxHealth;
playerHealthBarFill.width = 592 * healthPercent;
// Health bar color: green when above 20%, red when 20% or below
if (healthPercent <= 0.2) {
playerHealthBarFill.tint = 0xff0000; // Red when health is 20% or less
} else {
playerHealthBarFill.tint = 0x00ff00; // Green when health is above 20%
}
playerHealthBarText.setText(player.health.toString());
} else {
// Hide health bar when player is dead
playerHealthBarFill.width = 0;
playerHealthBarText.setText('0');
}
// Update stats display only if player exists
if (player) {
updateStatsDisplay();
}
// Update remaining enemies counter
if (currentBoss) {
remainingText.setText('Boss Fight!');
} else {
remainingText.setText('Enemies: ' + (enemies.length + greenEnemies.length));
}
// Check power-up expiration and update timers
if (rocketLauncherActive) {
if (LK.ticks >= rocketLauncherEndTime) {
rocketLauncherActive = false;
rocketTimerText.visible = false;
rocketLauncherValueText.visible = false;
} else {
var remainingTime = Math.ceil((rocketLauncherEndTime - LK.ticks) / 60);
rocketTimerText.setText('Rocket: ' + remainingTime + 's');
rocketTimerText.visible = true;
rocketLauncherValueText.setText('Splash Damage');
rocketLauncherValueText.visible = true;
}
} else {
rocketTimerText.visible = false;
rocketLauncherValueText.visible = false;
}
if (freezeEffectActive) {
if (LK.ticks >= freezeEffectEndTime) {
freezeEffectActive = false;
freezeTimerText.visible = false;
freezeBombValueText.visible = false;
} else {
var remainingTime = Math.ceil((freezeEffectEndTime - LK.ticks) / 60);
freezeTimerText.setText('Freeze: ' + remainingTime + 's');
freezeTimerText.visible = true;
freezeBombValueText.setText('Slow Enemies 50%');
freezeBombValueText.visible = true;
}
} else {
freezeTimerText.visible = false;
freezeBombValueText.visible = false;
}
if (machineGunActive) {
if (LK.ticks >= machineGunEndTime) {
machineGunActive = false;
machineGunTimerText.visible = false;
machineGunValueText.visible = false;
} else {
var remainingTime = Math.ceil((machineGunEndTime - LK.ticks) / 60);
machineGunTimerText.setText('Machine Gun: ' + remainingTime + 's');
machineGunTimerText.visible = true;
machineGunValueText.setText('3x Fire Rate');
machineGunValueText.visible = true;
}
} else {
machineGunTimerText.visible = false;
machineGunValueText.visible = false;
}
if (magnetActive) {
if (LK.ticks >= magnetEndTime) {
magnetActive = false;
magnetTimerText.visible = false;
magnetValueText.visible = false;
} else {
var remainingTime = Math.ceil((magnetEndTime - LK.ticks) / 60);
magnetTimerText.setText('Magnet: ' + remainingTime + 's');
magnetTimerText.visible = true;
magnetValueText.setText('Attract Items');
magnetValueText.visible = true;
}
} else {
magnetTimerText.visible = false;
magnetValueText.visible = false;
}
if (speedActive) {
if (LK.ticks >= speedEndTime) {
speedActive = false;
if (player) {
player.speed = speedOriginalSpeed;
}
speedTimerText.visible = false;
speedValueText.visible = false;
} else {
var remainingTime = Math.ceil((speedEndTime - LK.ticks) / 60);
speedTimerText.setText('Speed: ' + remainingTime + 's');
speedTimerText.visible = true;
speedValueText.setText('2x Move Speed');
speedValueText.visible = true;
}
} else {
speedTimerText.visible = false;
speedValueText.visible = false;
}
if (wealthActive) {
if (LK.ticks >= wealthEndTime) {
wealthActive = false;
wealthTimerText.visible = false;
wealthValueText.visible = false;
} else {
var remainingTime = Math.ceil((wealthEndTime - LK.ticks) / 60);
wealthTimerText.setText('Wealth: ' + remainingTime + 's');
wealthTimerText.visible = true;
wealthValueText.setText('2x Coin Value + 30% Diamond Drop');
wealthValueText.visible = true;
}
} else {
wealthTimerText.visible = false;
wealthValueText.visible = false;
}
if (ironBodyActive) {
if (LK.ticks >= ironBodyEndTime) {
ironBodyActive = false;
// Restore original player appearance
if (player && player.ironBodyGraphics) {
var currentPlayerGraphics = player.children[0]; // First child is the original player graphics
currentPlayerGraphics.visible = true; // Show original player
player.ironBodyGraphics.visible = false; // Hide iron body appearance
}
ironBodyTimerText.visible = false;
ironBodyValueText.visible = false;
} else {
var remainingTime = Math.ceil((ironBodyEndTime - LK.ticks) / 60);
ironBodyTimerText.setText('Iron Body: ' + remainingTime + 's');
ironBodyTimerText.visible = true;
ironBodyValueText.setText('90% Dmg Reduction + 3x Push Back');
ironBodyValueText.visible = true;
}
} else {
ironBodyTimerText.visible = false;
ironBodyValueText.visible = false;
}
if (healingNeedleActive) {
if (LK.ticks >= healingNeedleEndTime) {
healingNeedleActive = false;
healingNeedleTimerText.visible = false;
healingNeedleValueText.visible = false;
} else {
var remainingTime = Math.ceil((healingNeedleEndTime - LK.ticks) / 60);
healingNeedleTimerText.setText('Healing: ' + remainingTime + 's');
healingNeedleTimerText.visible = true;
healingNeedleValueText.setText('Heal on Hit');
healingNeedleValueText.visible = true;
}
} else {
healingNeedleTimerText.visible = false;
healingNeedleValueText.visible = false;
}
if (duplicateActive) {
if (LK.ticks >= duplicateEndTime) {
duplicateActive = false;
// Make clone explode when timer expires
if (currentPlayerClone) {
currentPlayerClone.explode();
currentPlayerClone = null;
}
duplicateTimerText.visible = false;
duplicateValueText.visible = false;
} else {
var remainingTime = Math.ceil((duplicateEndTime - LK.ticks) / 60);
duplicateTimerText.setText('Clone: ' + remainingTime + 's');
duplicateTimerText.visible = true;
duplicateValueText.setText('Clone Fighter');
duplicateValueText.visible = true;
}
} else {
duplicateTimerText.visible = false;
duplicateValueText.visible = false;
}
// Spawn green enemies during boss fights every 1 minute
if (gameState === 'playing' && currentBoss) {
bossGreenEnemyTimer++;
if (bossGreenEnemyTimer >= bossGreenEnemyInterval) {
// Spawn one green enemy at random location on the map
var greenEnemy = new GreenEnemy();
var validPosition = false;
var attempts = 0;
// Try to find non-overlapping position anywhere on the map
while (!validPosition && attempts < 50) {
// Spawn at random location on the map
greenEnemy.x = Math.random() * 2048;
greenEnemy.y = Math.random() * 2732;
// Check if position is far enough from player and boss
validPosition = true;
if (player) {
var dx = greenEnemy.x - player.x;
var dy = greenEnemy.y - player.y;
var distanceToPlayer = Math.sqrt(dx * dx + dy * dy);
if (distanceToPlayer < 200) {
validPosition = false;
}
}
if (currentBoss && validPosition) {
var dx = greenEnemy.x - currentBoss.x;
var dy = greenEnemy.y - currentBoss.y;
var distanceToBoss = Math.sqrt(dx * dx + dy * dy);
if (distanceToBoss < 200) {
validPosition = false;
}
}
// Check if position overlaps with existing enemies
for (var e = 0; e < enemies.length && validPosition; e++) {
var dx = greenEnemy.x - enemies[e].x;
var dy = greenEnemy.y - enemies[e].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
validPosition = false;
}
}
for (var g = 0; g < greenEnemies.length && validPosition; g++) {
var dx = greenEnemy.x - greenEnemies[g].x;
var dy = greenEnemy.y - greenEnemies[g].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
validPosition = false;
}
}
attempts++;
}
greenEnemies.push(greenEnemy);
game.addChild(greenEnemy);
// Reset timer for next spawn
bossGreenEnemyTimer = 0;
}
}
// Clean up excess bullets during machine gun usage to prevent performance issues
if (machineGunActive && playerBullets.length > 40) {
// Remove oldest bullets that are off-screen or furthest from enemies
var bulletsToRemove = [];
for (var i = 0; i < playerBullets.length; i++) {
var bullet = playerBullets[i];
// Remove bullets that are far off-screen
if (bullet.x < -200 || bullet.x > 2248 || bullet.y < -200 || bullet.y > 2932) {
bulletsToRemove.push(i);
}
}
// Remove bullets starting from the end to avoid index issues
for (var r = bulletsToRemove.length - 1; r >= 0; r--) {
var bulletIndex = bulletsToRemove[r];
if (playerBullets[bulletIndex]) {
playerBullets[bulletIndex].destroy();
playerBullets.splice(bulletIndex, 1);
}
}
}
// Check if all enemies are defeated and no boss exists
if (gameState === 'playing' && enemies.length === 0 && greenEnemies.length === 0 && !currentBoss) {
// Progress to next level first
currentLevel++;
levelText.setText('Level: ' + currentLevel);
enemiesKilled = 0;
// Check win condition at level 200
if (currentLevel > 200) {
LK.showYouWin();
return;
}
// Check if current level requires shop or boss
if (currentLevel % 10 === 0) {
// Boss levels (10, 20, 30, etc.) - show shop first, then boss
showShop();
} else if (currentLevel % 5 === 0) {
// Non-boss multiples of 5 (5, 15, 25, etc.) - show shop
showShop();
} else {
// Regular levels - spawn enemies
spawnEnemies();
}
}
};
coin. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
health pack. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
scary toothed germ style enemy. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
cloud. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
emerald. In-Game asset. 2d. High contrast. No shadows
blue hand granade. In-Game asset. 2d. High contrast. No shadows
a green scary ball with metal teeth. In-Game asset. 2d. High contrast. No shadows
green poison ball. In-Game asset. 2d. High contrast. No shadows
machine gun. In-Game asset. 2d. High contrast. No shadows
machine gun bullet. In-Game asset. 2d. High contrast. No shadows
rocket launcher. In-Game asset. 2d. High contrast. No shadows
horizontal rocket bullet. In-Game asset. 2d. High contrast. No shadows
treasure chest. In-Game asset. 2d. High contrast. No shadows
vaccine with a plus sign. In-Game asset. 2d. High contrast. No shadows
blue steel vest. In-Game asset. 2d. High contrast. No shadows
u magnet. In-Game asset. 2d. High contrast. No shadows
red shoes with a 2x. In-Game asset. 2d. High contrast. No shadows
horizontal needle. In-Game asset. 2d. High contrast. No shadows
horizontal fire ball. In-Game asset. 2d. High contrast. No shadows
blue
pink blue black and orange mixed horizontal fire ball. In-Game asset. 2d. High contrast. No shadows
robot virüs. In-Game asset. 2d. High contrast. No shadows
artı işareti yeşil olsun bu artı işareti tedavi artı işareti yukarıda boyadığım kısım kırmızı olsun
metal kaplı görünsün
amber stone. In-Game asset. 2d. High contrast. No shadows
rengi mor olsun