User prompt
30 level bosunu test etmek istiyorum deneme yapabilmem için 30 level bosunu oyunun başında çıkarırmısın vaz geçtim
User prompt
ben level 30 a kadar gelemiyorum level 30 bosunu denemem lazım denemek için level 30 bosunu oyunun başında çıkarır mısın
User prompt
geri eski haline al çalışmadı
User prompt
30 level bosunu test etmek istiyorum deneme yapabilmem için 30 level bosunu oyunun başında çıkarırmısın
User prompt
Boss öldükten sonra boss müziği dursun oyun arka plan müziği devam etsin
User prompt
Ana menü müziği ana menü açıldığı anda başlasın
User prompt
Ana menüye music on off tuşu koyalım off olduğunda oyundaki bütün sesler devre dışı olsun ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Ana menüye music on off tuşu koyalım off olduğunda oyundaki bütün sesler devre dışı olsun ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Ana menüde assetlerdeki 2 isimli ses çalsın bos fightda 1 ve genel oyun arka planında 3
User prompt
Ana menüde assetlerdeki 2 isimli ses çalsın bos fightda 1 ve genel oyun arka planında 3
User prompt
Main menü müziği play diyip oyun başlayana kadar devam etsin
User prompt
Ana menü ekranında da bir müzik çalsın ve normal oyunun arka planında da çalsın
User prompt
Boss fightlarda 1 isimli müzik çalsın assetlerdeki
User prompt
Oyuncu veya bosslar öldüklerinde patlama efekti oluyor ya bu efektede ses eklemek istiyorum
User prompt
Düşman bize vurduğunda da bir ses oynatılsın sesi ben seçmek istiyorum
User prompt
Yeşil düşmandan çıkan bütün itemler için geçerli olsun
User prompt
Yerden makineli tüfek aldım alırken ses değişmedi hala coin toplama sesi çıkıyor
User prompt
Yerden yeşil düşmanlardan çıkan item alındığında farklı bir ses dosyası oynatsın
User prompt
Tam olmadı fikri sana bırakıyorum diğer oyunlarda analog nasıl çalışıyorsa bu oyunda da öyle çalışsın Diğer oyunların analoglarını incele benim oyunumda da bu şekilde yap
User prompt
Tam olmadı fikri sana bırakıyorum diğer oyunlarda analog nasıl çalışıyorsa bu oyunda da öyle çalışsın
User prompt
Analoglar parmağı anında algılasınyukarısağa sola aşağı ve ara yönler kusursuz çalışsın
User prompt
Çalışmıyor analog
User prompt
Analoglar stabil hassas ve iyi çalışmıyor lütfen kusursuz hale getirir misin Şöyle tam tur çevirmeden analoglar çalışmıyor elimi analoğun üzerine koyduğum an bütün yönler açık olsun anında analoglar tepki versin çalışmaya başlasın lütfen
User prompt
Analoglar stabil hassas ve iyi çalışmıyor lütfen kusursuz hale getirir misin Şöyle tam tur çevirmeden analoglar çalışmıyor elimi analoğun üzerine koyduğum an bütün yönler açık olsun anında analoglar tepki versin çalışmaya başlasın lütfen
User prompt
Analoglar stabil hassas ve iyi çalışmıyor lütfen kusursuz hale getirir misin Şöyle tam tur çevirmeden analoglar çalışmıyor elimi analoğun üzerine koyduğum an bütün yönler açık olsun anında analoglar tepki versin çalışmaya başlasın lütfen
/****
* 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');
// Determine which boss to spawn based on current level
if (currentLevel === 30) {
currentBoss = new Level30Boss();
} else if (currentLevel >= 50 && (currentLevel - 50) % 10 === 0) {
// Spawner boss for levels 50, 60, 70, etc.
currentBoss = new SpawnerBoss();
} else {
// Regular boss for levels 10, 20, 40, 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();
}
}
}; ===================================================================
--- original.js
+++ change.js
@@ -4774,10 +4774,18 @@
}
function spawnBoss() {
// Play boss fight music
if (musicEnabled) LK.playMusic('1');
- // Force Level30Boss for testing purposes
- currentBoss = new Level30Boss();
+ // Determine which boss to spawn based on current level
+ if (currentLevel === 30) {
+ currentBoss = new Level30Boss();
+ } else if (currentLevel >= 50 && (currentLevel - 50) % 10 === 0) {
+ // Spawner boss for levels 50, 60, 70, etc.
+ currentBoss = new SpawnerBoss();
+ } else {
+ // Regular boss for levels 10, 20, 40, etc.
+ currentBoss = new Boss();
+ }
currentBoss.x = 1024;
currentBoss.y = 200;
game.addChild(currentBoss);
// Reset boss green enemy timer
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