/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var BloodTrail = Container.expand(function () { var self = Container.call(this); var bloodGraphics = self.attachAsset('bloodTrail', { anchorX: 0.5, anchorY: 0.5 }); self.lifetime = 180; // 3 seconds at 60fps bloodGraphics.alpha = 0.8; self.update = function () { self.lifetime--; // Fade out over time bloodGraphics.alpha = self.lifetime / 180; if (self.lifetime <= 0) { self.destroy(); // Remove from bloodTrails array for (var i = 0; i < bloodTrails.length; i++) { if (bloodTrails[i] === self) { bloodTrails.splice(i, 1); break; } } } }; return self; }); var Boss = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('boss', { anchorX: 0.5, anchorY: 0.5 }); self.health = 500; self.maxHealth = 500; self.speed = 0.8; self.damage = 30; self.lastPlayerDistance = 0; self.attackCooldown = 0; self.attackPhase = 0; self.isBoss = true; self.update = function () { // Check for nearby bullets and evade (bosses have moderate evasion) var evadeX = 0; var evadeY = 0; var shouldEvade = false; for (var i = 0; i < bullets.length; i++) { var bullet = bullets[i]; var bulletDx = bullet.x - self.x; var bulletDy = bullet.y - self.y; var bulletDistance = Math.sqrt(bulletDx * bulletDx + bulletDy * bulletDy); // Bosses detect bullets from medium range if (bulletDistance < 100) { var bulletSpeed = Math.sqrt(bullet.velocityX * bullet.velocityX + bullet.velocityY * bullet.velocityY); if (bulletSpeed > 0) { // Calculate if bullet is heading towards enemy var bulletDirection = Math.atan2(bullet.velocityY, bullet.velocityX); var enemyDirection = Math.atan2(bulletDy, bulletDx); var angleDiff = Math.abs(bulletDirection - enemyDirection); if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff; // Bosses have narrow detection angle but good reaction if (angleDiff < Math.PI / 6) { shouldEvade = true; // Calculate perpendicular evasion direction evadeX += -bulletDy / bulletDistance * (100 - bulletDistance) / 100; evadeY += bulletDx / bulletDistance * (100 - bulletDistance) / 100; } } } } ; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 100) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; // Apply evasion if needed (bosses have moderate dodge chance) if (shouldEvade) { // 50% chance to successfully evade if (Math.random() < 0.5) { moveX += evadeX * 1.5; moveY += evadeY * 1.5; } } // Track previous position for direction detection var prevX = self.x; self.x += moveX; self.y += moveY; // Flip boss asset based on movement direction var deltaX = self.x - prevX; if (Math.abs(deltaX) > 0.1) { // Only flip if there's significant horizontal movement if (deltaX > 0) { // Moving right - show normal version self.children[0].scaleX = Math.abs(self.children[0].scaleX); } else { // Moving left - show mirrored (flipped) version self.children[0].scaleX = -Math.abs(self.children[0].scaleX); } } } // Attack patterns if (self.attackCooldown > 0) { self.attackCooldown--; } else { self.performAttack(); self.attackCooldown = 120; // 2 seconds } // Check collision with player var currentPlayerDistance = distance; if (self.lastPlayerDistance > 80 && currentPlayerDistance <= 80) { player.takeDamage(self.damage); } self.lastPlayerDistance = currentPlayerDistance; }; self.performAttack = function () { switch (self.attackPhase) { case 0: self.circularAttack(); break; case 1: self.chargeAttack(); break; case 2: self.areaAttack(); break; } self.attackPhase = (self.attackPhase + 1) % 3; }; self.circularAttack = function () { for (var i = 0; i < 8; i++) { var angle = i * Math.PI / 4; var projectile = new EnemyProjectile(); projectile.x = self.x; projectile.y = self.y; projectile.velocityX = Math.cos(angle) * 8; projectile.velocityY = Math.sin(angle) * 8; enemyProjectiles.push(projectile); game.addChild(projectile); } }; self.chargeAttack = function () { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { tween(self, { x: self.x + dx / distance * 200, y: self.y + dy / distance * 200 }, { duration: 500, easing: tween.easeOut }); } }; self.areaAttack = function () { createExplosion(self.x, self.y, 150); LK.getSound('explosion').play(); var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 150) { player.takeDamage(self.damage); } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.die(); } else { LK.effects.flashObject(self, 0xFFFFFF, 100); } }; self.die = function () { createExplosion(self.x, self.y, 200); LK.getSound('explosion').play(); LK.setScore(LK.getScore() + 200); player.addCombo(); // Boss defeated - show upgrade screen showUpgradeScreen(); // Remove from enemies array for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = 0; self.velocityY = 0; self.damage = 25; self.isCritical = false; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Check collision with enemies for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (self.intersects(enemy)) { var finalDamage = self.damage; if (self.isCritical) { finalDamage *= 2; createExplosion(self.x, self.y, 50); } enemy.takeDamage(finalDamage); player.addCombo(); self.destroy(); // Remove from bullets array for (var j = 0; j < bullets.length; j++) { if (bullets[j] === self) { bullets.splice(j, 1); break; } } return; } } // Remove if out of bounds if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) { self.destroy(); for (var k = 0; k < bullets.length; k++) { if (bullets[k] === self) { bullets.splice(k, 1); break; } } } }; return self; }); var Character = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'warrior'; var characterGraphics = self.attachAsset(self.type, { anchorX: 0.5, anchorY: 0.5 }); // Add weapon graphics self.weapon = null; self.lastTargetX = 0; self.lastTargetY = 0; // Create initial pistol weapon self.weapon = self.addChild(LK.getAsset('pistolWeapon', { anchorX: 0.0, anchorY: 0.5 })); // Set stats based on character type switch (self.type) { case 'warrior': self.health = 120; self.maxHealth = 120; self.damage = 30; self.fireRate = 18; self.speed = 7; self.weaponType = 'sword'; break; case 'archer': self.health = 80; self.maxHealth = 80; self.damage = 20; self.fireRate = 10; self.speed = 10; self.weaponType = 'bow'; break; case 'mage': self.health = 60; self.maxHealth = 60; self.damage = 35; self.fireRate = 25; self.speed = 8; self.weaponType = 'staff'; break; } self.fireCooldown = 0; self.comboMultiplier = 1; self.comboTimer = 0; self.comboCount = 0; self.isDead = false; self.magazine = 7; self.maxMagazine = 7; self.reloadTime = 72; // 1.2 seconds at 60fps self.isReloading = false; self.weaponType = 'basic'; // Track current weapon type self.hasShotgun = false; self.lastHealthBelow65 = false; // Track if health was below 65 last frame self.lastX = 0; // Track last position for blood trail self.lastY = 0; self.bloodTrailTimer = 0; // Timer for blood trail spawning self.update = function () { // Stop all actions if character is dead if (self.isDead) { return; } if (self.fireCooldown > 0) { self.fireCooldown--; } if (self.comboTimer > 0) { self.comboTimer--; } else { self.comboMultiplier = 1; self.comboCount = 0; } // Handle reloading if (self.isReloading) { self.reloadTime--; if (self.reloadTime <= 0) { if (self.weaponType === 'shotgun') { self.magazine = 5; self.maxMagazine = 5; self.reloadTime = 90; // Reset reload time for shotgun } else { self.magazine = self.maxMagazine; self.reloadTime = 72; // Reset reload time } self.isReloading = false; } } // Auto-fire at nearest enemy with weapon-specific bullets if (self.fireCooldown <= 0 && enemies.length > 0 && !self.isReloading) { var nearestEnemy = self.findNearestEnemy(); if (nearestEnemy && self.magazine > 0) { // Update weapon orientation towards target self.lastTargetX = nearestEnemy.x; self.lastTargetY = nearestEnemy.y; self.updateWeaponOrientation(); if (self.weaponType === 'shotgun') { // Fire shotgun pellets in cone pattern var dx = nearestEnemy.x - self.x; var dy = nearestEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var baseAngle = Math.atan2(dy, dx); // Fire 5 pellets in cone for (var p = 0; p < 5; p++) { var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; // Spread pellets in 30-degree cone var angleOffset = (p - 2) * (Math.PI / 12); // 15 degrees each side var pelletAngle = baseAngle + angleOffset; bullet.velocityX = Math.cos(pelletAngle) * 15; bullet.velocityY = Math.sin(pelletAngle) * 15; bullet.damage = Math.floor(50 * 0.75); // 75% of basic enemy health (50) bullets.push(bullet); game.addChild(bullet); } } else { // Create simple bullet var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; var dx = nearestEnemy.x - self.x; var dy = nearestEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); bullet.velocityX = dx / distance * 15; bullet.velocityY = dy / distance * 15; bullet.damage = 25; // Basic damage bullets.push(bullet); game.addChild(bullet); } LK.getSound('shoot').play(); self.fireCooldown = self.fireRate; self.magazine--; // Start reloading if magazine is empty if (self.magazine <= 0) { self.isReloading = true; if (self.weaponType === 'shotgun') { self.reloadTime = 90; // 1.5 seconds at 60fps } else { self.reloadTime = 72; // 1.2 seconds at 60fps } } } } // Check if health drops below 65 (transition from >= 65 to < 65) var currentHealthBelow65 = self.health < 65; if (!self.lastHealthBelow65 && currentHealthBelow65) { // Health just dropped below 65 - play warrior low health sound LK.getSound('healthyGreeting').play(); } self.lastHealthBelow65 = currentHealthBelow65; // Create blood trail when health is below 65 and character is moving if (self.health < 65 && !self.isDead) { // Check if character has moved significantly var deltaX = self.x - self.lastX; var deltaY = self.y - self.lastY; var movementDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (movementDistance > 5) { // Only create trail if moving self.bloodTrailTimer++; if (self.bloodTrailTimer >= 8) { // Create blood trail every 8 frames var bloodTrail = new BloodTrail(); // Position blood trail slightly behind character bloodTrail.x = self.x + (Math.random() - 0.5) * 20; bloodTrail.y = self.y + (Math.random() - 0.5) * 20; // Use tween to make blood trail fade and shrink tween(bloodTrail.children[0], { scaleX: 0.3, scaleY: 0.3, alpha: 0 }, { duration: 3000, easing: tween.easeOut }); bloodTrails.push(bloodTrail); game.addChild(bloodTrail); self.bloodTrailTimer = 0; } } } // Update last position for next frame self.lastX = self.x; self.lastY = self.y; // Keep player within arena bounds var arenaLeft = arena.x - arena.width / 2; var arenaRight = arena.x + arena.width / 2; var arenaTop = arena.y - arena.height / 2; var arenaBottom = arena.y + arena.height / 2; if (self.x < arenaLeft + 40) self.x = arenaLeft + 40; if (self.x > arenaRight - 40) self.x = arenaRight - 40; if (self.y < arenaTop + 40) self.y = arenaTop + 40; if (self.y > arenaBottom - 40) self.y = arenaBottom - 40; }; self.findNearestEnemy = function () { var nearest = null; var nearestDistance = Infinity; var maxRange = self.weaponType === 'shotgun' ? 550 : Infinity; // Shotgun has limited range 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 && distance <= maxRange) { nearestDistance = distance; nearest = enemy; } } return nearest; }; self.fireAt = function (target) { var bullet = new Bullet(); 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); bullet.velocityX = dx / distance * 15; bullet.velocityY = dy / distance * 15; bullet.damage = self.damage * self.comboMultiplier; bullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); }; self.takeDamage = function (damage) { self.health -= damage; self.comboMultiplier = 1; self.comboCount = 0; self.comboTimer = 0; if (self.health <= 0) { self.health = 0; self.die(); } LK.effects.flashObject(self, 0xFF0000, 200); }; self.addCombo = function () { self.comboCount++; self.comboTimer = 180; // 3 seconds if (self.comboCount >= 5) { self.comboMultiplier = Math.min(self.comboMultiplier + 0.1, 3); } }; self.die = function () { self.isDead = true; self.alpha = 0.5; // Make character semi-transparent createExplosion(self.x, self.y, 120); LK.getSound('explosion').play(); LK.showGameOver(); }; self.updateWeaponOrientation = function () { // Create weapon if it doesn't exist if (!self.weapon && self.weaponType === 'shotgun') { self.weapon = self.addChild(LK.getAsset('shotgunWeapon', { anchorX: 0.0, anchorY: 0.5 })); } else if (!self.weapon && self.weaponType === 'basic') { self.weapon = self.addChild(LK.getAsset('pistolWeapon', { anchorX: 0.0, anchorY: 0.5 })); } // Orient weapon towards target if (self.weapon && self.lastTargetX !== 0 && self.lastTargetY !== 0) { var dx = self.lastTargetX - self.x; var dy = self.lastTargetY - self.y; var angle = Math.atan2(dy, dx); self.weapon.rotation = angle; // Position weapon so its left edge (tip) is at the player's right side var weaponDistance = 30; // Distance from player center to weapon tip self.weapon.x = Math.cos(angle) * weaponDistance; self.weapon.y = Math.sin(angle) * weaponDistance; // Flip weapon vertically if pointing left if (Math.abs(angle) > Math.PI / 2) { self.weapon.scaleY = -1; } else { self.weapon.scaleY = 1; } } }; self.switchToShotgun = function () { self.weaponType = 'shotgun'; self.hasShotgun = true; self.magazine = 5; self.maxMagazine = 5; self.reloadTime = 90; // 1.5 seconds at 60fps self.isReloading = false; // Create weapon graphics if (self.weapon) { self.weapon.destroy(); } self.weapon = self.addChild(LK.getAsset('shotgunWeapon', { anchorX: 0.0, anchorY: 0.5 })); }; return self; }); var Player = Character.expand(function (type) { var self = Character.call(this, type); // Add critical hit chance self.criticalChance = 0.1; // Override fireAt to add critical hits var originalFireAt = self.fireAt; self.fireAt = function (target) { originalFireAt.call(self, target); // Add critical hit chance var lastBullet = bullets[bullets.length - 1]; if (lastBullet && Math.random() < self.criticalChance) { lastBullet.isCritical = true; lastBullet.children[0].tint = 0xFF4444; } }; return self; }); var EliteEnemy = Container.expand(function () { var self = Container.call(this); var eliteGraphics = self.attachAsset('eliteEnemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 150; self.maxHealth = 150; self.speed = 1.5; self.damage = 20; self.lastPlayerDistance = 0; self.specialCooldown = 0; self.isElite = true; self.update = function () { // Check for nearby bullets and evade (elite enemies are better at dodging) var evadeX = 0; var evadeY = 0; var shouldEvade = false; for (var i = 0; i < bullets.length; i++) { var bullet = bullets[i]; var bulletDx = bullet.x - self.x; var bulletDy = bullet.y - self.y; var bulletDistance = Math.sqrt(bulletDx * bulletDx + bulletDy * bulletDy); // Elite enemies detect bullets from further away if (bulletDistance < 150) { var bulletSpeed = Math.sqrt(bullet.velocityX * bullet.velocityX + bullet.velocityY * bullet.velocityY); if (bulletSpeed > 0) { // Calculate if bullet is heading towards enemy var bulletDirection = Math.atan2(bullet.velocityY, bullet.velocityX); var enemyDirection = Math.atan2(bulletDy, bulletDx); var angleDiff = Math.abs(bulletDirection - enemyDirection); if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff; // Elite enemies have wider detection angle if (angleDiff < Math.PI / 3) { shouldEvade = true; // Calculate perpendicular evasion direction evadeX += -bulletDy / bulletDistance * (150 - bulletDistance) / 150; evadeY += bulletDx / bulletDistance * (150 - bulletDistance) / 150; } } } } // 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) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; // Apply evasion if needed (elite enemies are better at dodging) if (shouldEvade) { // 85% chance to successfully evade if (Math.random() < 0.85) { moveX += evadeX * 2.5; moveY += evadeY * 2.5; } } // Track previous position for direction detection var prevX = self.x; self.x += moveX; self.y += moveY; // Flip elite enemy asset based on movement direction var deltaX = self.x - prevX; if (Math.abs(deltaX) > 0.1) { // Only flip if there's significant horizontal movement if (deltaX > 0) { // Moving right - show normal version self.children[0].scaleX = Math.abs(self.children[0].scaleX); } else { // Moving left - show mirrored (flipped) version self.children[0].scaleX = -Math.abs(self.children[0].scaleX); } } } // Special ability - dash attack if (self.specialCooldown > 0) { self.specialCooldown--; } else if (distance < 200 && distance > 80) { self.specialCooldown = 300; // 5 seconds self.dashAttack(); } // Check collision with player var currentPlayerDistance = distance; if (self.lastPlayerDistance > 50 && currentPlayerDistance <= 50) { player.takeDamage(self.damage); } self.lastPlayerDistance = currentPlayerDistance; }; self.dashAttack = function () { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { tween(self, { x: self.x + dx / distance * 150, y: self.y + dy / distance * 150 }, { duration: 300, easing: tween.easeOut }); } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.die(); } else { LK.effects.flashObject(self, 0xFFFFFF, 100); } }; self.die = function () { createExplosion(self.x, self.y); LK.getSound('explosion').play(); LK.setScore(LK.getScore() + 50); player.addCombo(); // Elite enemies don't drop power-ups // Remove from enemies array for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 50; self.speed = 1; self.damage = 10; self.lastPlayerDistance = 0; self.update = function () { // Check for nearby bullets and evade var evadeX = 0; var evadeY = 0; var shouldEvade = false; for (var i = 0; i < bullets.length; i++) { var bullet = bullets[i]; var bulletDx = bullet.x - self.x; var bulletDy = bullet.y - self.y; var bulletDistance = Math.sqrt(bulletDx * bulletDx + bulletDy * bulletDy); // If bullet is close (within 120 pixels) and moving towards enemy if (bulletDistance < 120) { var bulletSpeed = Math.sqrt(bullet.velocityX * bullet.velocityX + bullet.velocityY * bullet.velocityY); if (bulletSpeed > 0) { // Calculate if bullet is heading towards enemy var bulletDirection = Math.atan2(bullet.velocityY, bullet.velocityX); var enemyDirection = Math.atan2(bulletDy, bulletDx); var angleDiff = Math.abs(bulletDirection - enemyDirection); if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff; // If bullet is heading towards enemy (within 45 degrees) if (angleDiff < Math.PI / 4) { shouldEvade = true; // Calculate perpendicular evasion direction evadeX += -bulletDy / bulletDistance * (120 - bulletDistance) / 120; evadeY += bulletDx / bulletDistance * (120 - bulletDistance) / 120; } } } } // 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) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; // Apply evasion if needed if (shouldEvade) { // 70% chance to successfully evade if (Math.random() < 0.7) { moveX += evadeX * 2; moveY += evadeY * 2; } } // Track previous position for direction detection var prevX = self.x; self.x += moveX; self.y += moveY; // Flip enemy asset based on movement direction var deltaX = self.x - prevX; if (Math.abs(deltaX) > 0.1) { // Only flip if there's significant horizontal movement if (deltaX > 0) { // Moving right - show normal version self.children[0].scaleX = Math.abs(self.children[0].scaleX); } else { // Moving left - show mirrored (flipped) version self.children[0].scaleX = -Math.abs(self.children[0].scaleX); } } } // Check collision with player var currentPlayerDistance = distance; if (self.lastPlayerDistance > 50 && currentPlayerDistance <= 50) { player.takeDamage(self.damage); } self.lastPlayerDistance = currentPlayerDistance; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.die(); } else { LK.effects.flashObject(self, 0xFFFFFF, 100); } }; self.die = function () { createExplosion(self.x, self.y); LK.getSound('enemyHit').play(); LK.setScore(LK.getScore() + 10); player.addCombo(); // Chance to drop power-up if (Math.random() < 0.24) { var powerup = new PowerUp(); powerup.x = self.x; powerup.y = self.y; powerups.push(powerup); game.addChild(powerup); } // Remove from enemies array for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); }; return self; }); var EnemyProjectile = Container.expand(function () { var self = Container.call(this); var projectileGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); projectileGraphics.tint = 0xFF0000; self.velocityX = 0; self.velocityY = 0; self.damage = 15; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Check collision with player if (self.intersects(player)) { player.takeDamage(self.damage); self.destroy(); for (var i = 0; i < enemyProjectiles.length; i++) { if (enemyProjectiles[i] === self) { enemyProjectiles.splice(i, 1); break; } } return; } // Remove if out of bounds if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) { self.destroy(); for (var j = 0; j < enemyProjectiles.length; j++) { if (enemyProjectiles[j] === self) { enemyProjectiles.splice(j, 1); break; } } } }; return self; }); var Explosion = Container.expand(function () { var self = Container.call(this); var explosionGraphics = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); self.lifetime = 30; explosionGraphics.alpha = 0.8; self.update = function () { self.lifetime--; explosionGraphics.alpha = self.lifetime / 30; explosionGraphics.scaleX = explosionGraphics.scaleY = (30 - self.lifetime) / 30 * 2; if (self.lifetime <= 0) { self.destroy(); } }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var powerupGraphics = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.type = Math.floor(Math.random() * 4); // 0: health, 1: damage, 2: fire rate, 3: speed self.lifetime = 600; // 10 seconds at 60fps // Keep power-ups in classic color (no tinting) self.update = function () { self.lifetime--; // Fade out near end of lifetime if (self.lifetime < 120) { powerupGraphics.alpha = self.lifetime / 120; } // Remove if expired if (self.lifetime <= 0) { self.destroy(); for (var i = 0; i < powerups.length; i++) { if (powerups[i] === self) { powerups.splice(i, 1); break; } } return; } // Check collision with player if (self.intersects(player)) { self.applyEffect(); LK.getSound('powerupCollect').play(); self.destroy(); for (var j = 0; j < powerups.length; j++) { if (powerups[j] === self) { powerups.splice(j, 1); break; } } } }; self.applyEffect = function () { // All power-ups now give health player.health = Math.min(player.health + 10, player.maxHealth); }; return self; }); var ShotgunPowerup = Container.expand(function () { var self = Container.call(this); var powerupGraphics = self.attachAsset('shotgunPowerup', { anchorX: 0.5, anchorY: 0.5 }); self.lifetime = 900; // 15 seconds at 60fps self.bobTimer = 0; self.initialY = 0; self.update = function () { self.lifetime--; self.bobTimer++; // Bobbing animation if (self.initialY === 0) self.initialY = self.y; self.y = self.initialY + Math.sin(self.bobTimer * 0.1) * 10; // Fade out near end of lifetime if (self.lifetime < 180) { powerupGraphics.alpha = self.lifetime / 180; } // Remove if expired if (self.lifetime <= 0) { self.destroy(); for (var i = 0; i < shotgunPowerups.length; i++) { if (shotgunPowerups[i] === self) { shotgunPowerups.splice(i, 1); break; } } return; } // Check collision with player if (self.intersects(player)) { player.switchToShotgun(); LK.getSound('powerupCollect').play(); self.destroy(); for (var j = 0; j < shotgunPowerups.length; j++) { if (shotgunPowerups[j] === self) { shotgunPowerups.splice(j, 1); break; } } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2C2C2C }); /**** * Game Code ****/ // Game variables var player; var enemies = []; var bullets = []; var powerups = []; var enemyProjectiles = []; var explosions = []; var shotgunPowerups = []; var bloodTrails = []; var arena; var waveNumber = 1; var enemySpawnTimer = 0; var enemySpawnRate = 120; // 2 seconds at 60fps var gameTime = 0; var selectedCharacter = storage.selectedCharacter || 'warrior'; var upgradeScreenActive = false; var currentMusicTrack = 'battleMusic'; var musicIntensity = 0; var lastHealthBelow60 = false; // Track if health was below 60 last frame // Create arena arena = game.addChild(LK.getAsset('arena', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 })); // Create player player = game.addChild(new Player(selectedCharacter)); player.x = 1024; player.y = 1600; // UI Elements var scoreText = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); LK.gui.topRight.addChild(scoreText); scoreText.x = -300; scoreText.y = 50; var healthText = new Text2('Health: 100', { size: 60, fill: 0xFFFFFF }); healthText.anchor.set(0, 0); LK.gui.topRight.addChild(healthText); healthText.x = -300; healthText.y = 120; var waveText = new Text2('Wave: 1', { size: 60, fill: 0xFFFFFF }); waveText.anchor.set(0, 0); LK.gui.topRight.addChild(waveText); waveText.x = -300; waveText.y = 190; var comboText = new Text2('Combo: x1', { size: 50, fill: 0xFFEB3B }); comboText.anchor.set(0, 0); LK.gui.topRight.addChild(comboText); comboText.x = -300; comboText.y = 260; var magazineText = new Text2('7/7', { size: 80, fill: 0xFFFFFF, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); magazineText.anchor.set(0.5, 1); magazineText.x = player.x; magazineText.y = player.y - 120; game.addChild(magazineText); // Tutorial text var tutorialText = new Text2('Move by dragging', { size: 80, fill: 0xFFFFFF }); tutorialText.anchor.set(0.5, 0.5); tutorialText.x = 1024; tutorialText.y = 1200; game.addChild(tutorialText); // Add rainbow cycling to tutorial text var tutorialColors = [0xFF0000, 0xFF7F00, 0xFFFF00, 0x00FF00, 0x0000FF, 0x4B0082, 0x9400D3]; var tutorialColorIndex = 0; function cycleTutorialRainbow() { var nextColor = tutorialColors[(tutorialColorIndex + 1) % tutorialColors.length]; tween(tutorialText, { tint: nextColor }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tutorialColorIndex = (tutorialColorIndex + 1) % tutorialColors.length; cycleTutorialRainbow(); } }); } cycleTutorialRainbow(); // Fade out tutorial text after 2 seconds LK.setTimeout(function () { tween(tutorialText, { alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { tutorialText.destroy(); LK.getSound('characterGreeting').play(); } }); }, 2000); // Touch controls var isDragging = false; var dragOffsetX = 0; var dragOffsetY = 0; var gameStarted = false; // Player speed limiting var maxPlayerSpeed = 10; var targetX = 0; var targetY = 0; game.down = function (x, y, obj) { isDragging = true; // Calculate offset from touch point to player center dragOffsetX = player.x - x; dragOffsetY = player.y - y; }; game.move = function (x, y, obj) { if (isDragging) { // Set target position instead of direct movement targetX = x + dragOffsetX; targetY = y + dragOffsetY; } }; game.up = function (x, y, obj) { isDragging = false; }; // Utility functions function createExplosion(x, y, size) { var explosion = new Explosion(); explosion.x = x; explosion.y = y; if (size) { explosion.children[0].scaleX = explosion.children[0].scaleY = size / 100; } explosions.push(explosion); game.addChild(explosion); } function showUpgradeScreen() { upgradeScreenActive = true; var upgradePanel = LK.getAsset('skillPanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); game.addChild(upgradePanel); var upgradeTitle = new Text2('Choose an Upgrade', { size: 60, fill: 0xFFFFFF }); upgradeTitle.anchor.set(0.5, 0.5); upgradeTitle.x = 1024; upgradeTitle.y = 1200; game.addChild(upgradeTitle); var upgrades = [{ name: 'Attack Speed', desc: 'Faster firing rate' }, { name: 'Health Boost', desc: 'Increase max health' }, { name: 'Critical Hit', desc: 'Higher crit chance' }]; for (var i = 0; i < 3; i++) { var button = LK.getAsset('skillButton', { anchorX: 0.5, anchorY: 0.5, x: 700 + i * 200, y: 1366 }); game.addChild(button); var buttonText = new Text2(upgrades[i].name, { size: 30, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); buttonText.x = 700 + i * 200; buttonText.y = 1366; game.addChild(buttonText); button.down = function (x, y, obj) { applyUpgrade(i); upgradePanel.destroy(); upgradeTitle.destroy(); for (var j = 0; j < 3; j++) { game.children[game.children.length - 1].destroy(); game.children[game.children.length - 1].destroy(); } upgradeScreenActive = false; }; } } function applyUpgrade(type) { switch (type) { case 0: // Attack Speed player.fireRate = Math.max(player.fireRate - 3, 5); break; case 1: // Health Boost player.maxHealth += 30; player.health = Math.min(player.health + 30, player.maxHealth); break; case 2: // Critical Hit player.criticalChance = Math.min(player.criticalChance + 0.1, 0.5); break; } } // Spawn enemy function function spawnEnemy() { var enemy; // Boss every 5 waves if (waveNumber % 5 === 0 && enemies.length === 0) { enemy = new Boss(); LK.getSound('bossSpawn').play(); LK.playMusic('bossMusic', { fade: { start: 0, end: 1, duration: 1000 } }); } else if (Math.random() < 0.2) { // 20% chance for elite enemy enemy = new EliteEnemy(); } else { enemy = new Enemy(); } // Spawn from arena edges var side = Math.floor(Math.random() * 4); var arenaLeft = arena.x - arena.width / 2; var arenaRight = arena.x + arena.width / 2; var arenaTop = arena.y - arena.height / 2; var arenaBottom = arena.y + arena.height / 2; switch (side) { case 0: // Top enemy.x = arenaLeft + Math.random() * arena.width; enemy.y = arenaTop; break; case 1: // Right enemy.x = arenaRight; enemy.y = arenaTop + Math.random() * arena.height; break; case 2: // Bottom enemy.x = arenaLeft + Math.random() * arena.width; enemy.y = arenaBottom; break; case 3: // Left enemy.x = arenaLeft; enemy.y = arenaTop + Math.random() * arena.height; break; } // Scale enemy stats with wave number if (!enemy.isBoss) { enemy.health += Math.floor(waveNumber * 5); enemy.speed += Math.floor(waveNumber * 0.2); enemy.damage += Math.floor(waveNumber * 2); } enemies.push(enemy); game.addChild(enemy); } // Main game loop game.update = function () { // Start game logic after 3 seconds (tutorial display time) if (gameTime < 180) { // 3 seconds at 60fps gameTime++; return; } if (!gameStarted) { gameStarted = true; LK.playMusic('battleMusic', { loop: true }); } if (upgradeScreenActive) { return; } // Apply speed-limited movement to player if (isDragging) { var dx = targetX - player.x; var dy = targetY - player.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { var moveX = dx / distance * maxPlayerSpeed; var moveY = dy / distance * maxPlayerSpeed; // Track previous position for direction detection var prevX = player.x; // Don't overshoot the target if (distance < maxPlayerSpeed) { player.x = targetX; player.y = targetY; } else { player.x += moveX; player.y += moveY; } // Flip warrior asset based on movement direction var deltaX = player.x - prevX; if (Math.abs(deltaX) > 0.1) { // Only flip if there's significant horizontal movement if (deltaX > 0) { // Moving right - show normal version player.children[0].scaleX = Math.abs(player.children[0].scaleX); } else { // Moving left - show mirrored (flipped) version player.children[0].scaleX = -Math.abs(player.children[0].scaleX); } } } } gameTime++; // Update wave number based on time var newWave = Math.floor(gameTime / 1800) + 1; // New wave every 30 seconds if (newWave > waveNumber) { waveNumber = newWave; enemySpawnRate = Math.max(enemySpawnRate - 10, 30); // Increase spawn rate // Dynamic music intensity musicIntensity = Math.min(waveNumber / 10, 1); if (waveNumber % 5 !== 0) { LK.playMusic('battleMusic', { fade: { start: 0.5, end: 0.5 + musicIntensity * 0.5, duration: 500 } }); } } // Spawn enemies enemySpawnTimer++; if (enemySpawnTimer >= enemySpawnRate) { var enemiesToSpawn = Math.min(waveNumber, 8); for (var i = 0; i < enemiesToSpawn; i++) { spawnEnemy(); } enemySpawnTimer = 0; } // Spawn shotgun powerup occasionally (every 20 seconds if none exist) if (gameTime % 1200 === 0 && shotgunPowerups.length === 0 && !player.hasShotgun) { var shotgunPowerup = new ShotgunPowerup(); // Spawn in random location within arena var arenaLeft = arena.x - arena.width / 2; var arenaRight = arena.x + arena.width / 2; var arenaTop = arena.y - arena.height / 2; var arenaBottom = arena.y + arena.height / 2; shotgunPowerup.x = arenaLeft + Math.random() * (arenaRight - arenaLeft); shotgunPowerup.y = arenaTop + Math.random() * (arenaBottom - arenaTop); shotgunPowerups.push(shotgunPowerup); game.addChild(shotgunPowerup); } // Update explosions for (var e = explosions.length - 1; e >= 0; e--) { var explosion = explosions[e]; if (explosion.lifetime <= 0) { explosions.splice(e, 1); } } // Update UI scoreText.setText('Score: ' + LK.getScore()); healthText.setText('Health: ' + player.health); waveText.setText('Wave: ' + waveNumber); comboText.setText('Combo: x' + player.comboMultiplier.toFixed(1)); // Track health transitions for red overlay effect var currentHealthBelow60 = player.health < 60; // Red full screen effect when health is below 60 if (currentHealthBelow60) { // Create red full screen overlay if it doesn't exist if (!game.redFullscreenOverlay) { // Full screen overlay game.redFullscreenOverlay = LK.getAsset('skillPanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 6, scaleY: 8 }); game.redFullscreenOverlay.tint = 0x8B0000; // Darker crimson red game.redFullscreenOverlay.alpha = 0; game.addChild(game.redFullscreenOverlay); // Create heal warning text game.healWarningText = new Text2('You Need To Heal Up!!', { size: 100, fill: 0xFFFFFF }); game.healWarningText.anchor.set(0.5, 0.5); game.healWarningText.x = 1024; game.healWarningText.y = 1366; game.healWarningText.alpha = 0; game.addChild(game.healWarningText); } // Calculate red intensity based on health (more red = less health) var healthPercentage = player.health / 60; var redIntensity = Math.max(0, (1 - healthPercentage) * 0.4); // Max 40% opacity for full screen // Animate red full screen overlay to target intensity tween(game.redFullscreenOverlay, { alpha: redIntensity }, { duration: 200, easing: tween.easeOut }); // Animate warning text with pulsing effect var textIntensity = Math.max(0, (1 - healthPercentage) * 0.8); // More visible text tween(game.healWarningText, { alpha: textIntensity }, { duration: 200, easing: tween.easeOut }); } // Check if health just went from below 60 to above 60 (transition) if (lastHealthBelow60 && !currentHealthBelow60) { // Health just went above 60 - fade out red overlay and text smoothly if (game.redFullscreenOverlay) { tween(game.redFullscreenOverlay, { alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (game.redFullscreenOverlay) { game.redFullscreenOverlay.destroy(); game.redFullscreenOverlay = null; } } }); tween(game.healWarningText, { alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (game.healWarningText) { game.healWarningText.destroy(); game.healWarningText = null; } } }); } } // Update last health state lastHealthBelow60 = currentHealthBelow60; // Update magazine UI if (player.isReloading) { magazineText.setText('RELOADING...'); magazineText.tint = 0xFF4444; } else { magazineText.setText(player.magazine + '/' + player.maxMagazine); if (player.weaponType === 'shotgun') { // Shotgun has orange/yellow color scheme if (player.magazine <= 1) { magazineText.tint = 0xFF4444; } else if (player.magazine <= 2) { magazineText.tint = 0xFFAA00; } else { magazineText.tint = 0xFFA500; // Orange for shotgun } } else { // Default weapon color scheme if (player.magazine <= 2) { magazineText.tint = 0xFF4444; } else if (player.magazine <= 4) { magazineText.tint = 0xFFAA00; } else { magazineText.tint = 0xFFFFFF; } } } magazineText.x = player.x; magazineText.y = player.y - 120; // Update combo text color if (player.comboMultiplier > 2) { comboText.tint = 0xFF4444; } else if (player.comboMultiplier > 1.5) { comboText.tint = 0xFFAA00; } else { comboText.tint = 0xFFEB3B; } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BloodTrail = Container.expand(function () {
var self = Container.call(this);
var bloodGraphics = self.attachAsset('bloodTrail', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 180; // 3 seconds at 60fps
bloodGraphics.alpha = 0.8;
self.update = function () {
self.lifetime--;
// Fade out over time
bloodGraphics.alpha = self.lifetime / 180;
if (self.lifetime <= 0) {
self.destroy();
// Remove from bloodTrails array
for (var i = 0; i < bloodTrails.length; i++) {
if (bloodTrails[i] === self) {
bloodTrails.splice(i, 1);
break;
}
}
}
};
return self;
});
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 500;
self.maxHealth = 500;
self.speed = 0.8;
self.damage = 30;
self.lastPlayerDistance = 0;
self.attackCooldown = 0;
self.attackPhase = 0;
self.isBoss = true;
self.update = function () {
// Check for nearby bullets and evade (bosses have moderate evasion)
var evadeX = 0;
var evadeY = 0;
var shouldEvade = false;
for (var i = 0; i < bullets.length; i++) {
var bullet = bullets[i];
var bulletDx = bullet.x - self.x;
var bulletDy = bullet.y - self.y;
var bulletDistance = Math.sqrt(bulletDx * bulletDx + bulletDy * bulletDy);
// Bosses detect bullets from medium range
if (bulletDistance < 100) {
var bulletSpeed = Math.sqrt(bullet.velocityX * bullet.velocityX + bullet.velocityY * bullet.velocityY);
if (bulletSpeed > 0) {
// Calculate if bullet is heading towards enemy
var bulletDirection = Math.atan2(bullet.velocityY, bullet.velocityX);
var enemyDirection = Math.atan2(bulletDy, bulletDx);
var angleDiff = Math.abs(bulletDirection - enemyDirection);
if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff;
// Bosses have narrow detection angle but good reaction
if (angleDiff < Math.PI / 6) {
shouldEvade = true;
// Calculate perpendicular evasion direction
evadeX += -bulletDy / bulletDistance * (100 - bulletDistance) / 100;
evadeY += bulletDx / bulletDistance * (100 - bulletDistance) / 100;
}
}
}
}
;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 100) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
// Apply evasion if needed (bosses have moderate dodge chance)
if (shouldEvade) {
// 50% chance to successfully evade
if (Math.random() < 0.5) {
moveX += evadeX * 1.5;
moveY += evadeY * 1.5;
}
}
// Track previous position for direction detection
var prevX = self.x;
self.x += moveX;
self.y += moveY;
// Flip boss asset based on movement direction
var deltaX = self.x - prevX;
if (Math.abs(deltaX) > 0.1) {
// Only flip if there's significant horizontal movement
if (deltaX > 0) {
// Moving right - show normal version
self.children[0].scaleX = Math.abs(self.children[0].scaleX);
} else {
// Moving left - show mirrored (flipped) version
self.children[0].scaleX = -Math.abs(self.children[0].scaleX);
}
}
}
// Attack patterns
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else {
self.performAttack();
self.attackCooldown = 120; // 2 seconds
}
// Check collision with player
var currentPlayerDistance = distance;
if (self.lastPlayerDistance > 80 && currentPlayerDistance <= 80) {
player.takeDamage(self.damage);
}
self.lastPlayerDistance = currentPlayerDistance;
};
self.performAttack = function () {
switch (self.attackPhase) {
case 0:
self.circularAttack();
break;
case 1:
self.chargeAttack();
break;
case 2:
self.areaAttack();
break;
}
self.attackPhase = (self.attackPhase + 1) % 3;
};
self.circularAttack = function () {
for (var i = 0; i < 8; i++) {
var angle = i * Math.PI / 4;
var projectile = new EnemyProjectile();
projectile.x = self.x;
projectile.y = self.y;
projectile.velocityX = Math.cos(angle) * 8;
projectile.velocityY = Math.sin(angle) * 8;
enemyProjectiles.push(projectile);
game.addChild(projectile);
}
};
self.chargeAttack = function () {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
tween(self, {
x: self.x + dx / distance * 200,
y: self.y + dy / distance * 200
}, {
duration: 500,
easing: tween.easeOut
});
}
};
self.areaAttack = function () {
createExplosion(self.x, self.y, 150);
LK.getSound('explosion').play();
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
player.takeDamage(self.damage);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.die();
} else {
LK.effects.flashObject(self, 0xFFFFFF, 100);
}
};
self.die = function () {
createExplosion(self.x, self.y, 200);
LK.getSound('explosion').play();
LK.setScore(LK.getScore() + 200);
player.addCombo();
// Boss defeated - show upgrade screen
showUpgradeScreen();
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.damage = 25;
self.isCritical = false;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
var finalDamage = self.damage;
if (self.isCritical) {
finalDamage *= 2;
createExplosion(self.x, self.y, 50);
}
enemy.takeDamage(finalDamage);
player.addCombo();
self.destroy();
// Remove from bullets array
for (var j = 0; j < bullets.length; j++) {
if (bullets[j] === self) {
bullets.splice(j, 1);
break;
}
}
return;
}
}
// Remove if out of bounds
if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) {
self.destroy();
for (var k = 0; k < bullets.length; k++) {
if (bullets[k] === self) {
bullets.splice(k, 1);
break;
}
}
}
};
return self;
});
var Character = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'warrior';
var characterGraphics = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
// Add weapon graphics
self.weapon = null;
self.lastTargetX = 0;
self.lastTargetY = 0;
// Create initial pistol weapon
self.weapon = self.addChild(LK.getAsset('pistolWeapon', {
anchorX: 0.0,
anchorY: 0.5
}));
// Set stats based on character type
switch (self.type) {
case 'warrior':
self.health = 120;
self.maxHealth = 120;
self.damage = 30;
self.fireRate = 18;
self.speed = 7;
self.weaponType = 'sword';
break;
case 'archer':
self.health = 80;
self.maxHealth = 80;
self.damage = 20;
self.fireRate = 10;
self.speed = 10;
self.weaponType = 'bow';
break;
case 'mage':
self.health = 60;
self.maxHealth = 60;
self.damage = 35;
self.fireRate = 25;
self.speed = 8;
self.weaponType = 'staff';
break;
}
self.fireCooldown = 0;
self.comboMultiplier = 1;
self.comboTimer = 0;
self.comboCount = 0;
self.isDead = false;
self.magazine = 7;
self.maxMagazine = 7;
self.reloadTime = 72; // 1.2 seconds at 60fps
self.isReloading = false;
self.weaponType = 'basic'; // Track current weapon type
self.hasShotgun = false;
self.lastHealthBelow65 = false; // Track if health was below 65 last frame
self.lastX = 0; // Track last position for blood trail
self.lastY = 0;
self.bloodTrailTimer = 0; // Timer for blood trail spawning
self.update = function () {
// Stop all actions if character is dead
if (self.isDead) {
return;
}
if (self.fireCooldown > 0) {
self.fireCooldown--;
}
if (self.comboTimer > 0) {
self.comboTimer--;
} else {
self.comboMultiplier = 1;
self.comboCount = 0;
}
// Handle reloading
if (self.isReloading) {
self.reloadTime--;
if (self.reloadTime <= 0) {
if (self.weaponType === 'shotgun') {
self.magazine = 5;
self.maxMagazine = 5;
self.reloadTime = 90; // Reset reload time for shotgun
} else {
self.magazine = self.maxMagazine;
self.reloadTime = 72; // Reset reload time
}
self.isReloading = false;
}
}
// Auto-fire at nearest enemy with weapon-specific bullets
if (self.fireCooldown <= 0 && enemies.length > 0 && !self.isReloading) {
var nearestEnemy = self.findNearestEnemy();
if (nearestEnemy && self.magazine > 0) {
// Update weapon orientation towards target
self.lastTargetX = nearestEnemy.x;
self.lastTargetY = nearestEnemy.y;
self.updateWeaponOrientation();
if (self.weaponType === 'shotgun') {
// Fire shotgun pellets in cone pattern
var dx = nearestEnemy.x - self.x;
var dy = nearestEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var baseAngle = Math.atan2(dy, dx);
// Fire 5 pellets in cone
for (var p = 0; p < 5; p++) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
// Spread pellets in 30-degree cone
var angleOffset = (p - 2) * (Math.PI / 12); // 15 degrees each side
var pelletAngle = baseAngle + angleOffset;
bullet.velocityX = Math.cos(pelletAngle) * 15;
bullet.velocityY = Math.sin(pelletAngle) * 15;
bullet.damage = Math.floor(50 * 0.75); // 75% of basic enemy health (50)
bullets.push(bullet);
game.addChild(bullet);
}
} else {
// Create simple bullet
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = nearestEnemy.x - self.x;
var dy = nearestEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
bullet.velocityX = dx / distance * 15;
bullet.velocityY = dy / distance * 15;
bullet.damage = 25; // Basic damage
bullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
self.fireCooldown = self.fireRate;
self.magazine--;
// Start reloading if magazine is empty
if (self.magazine <= 0) {
self.isReloading = true;
if (self.weaponType === 'shotgun') {
self.reloadTime = 90; // 1.5 seconds at 60fps
} else {
self.reloadTime = 72; // 1.2 seconds at 60fps
}
}
}
}
// Check if health drops below 65 (transition from >= 65 to < 65)
var currentHealthBelow65 = self.health < 65;
if (!self.lastHealthBelow65 && currentHealthBelow65) {
// Health just dropped below 65 - play warrior low health sound
LK.getSound('healthyGreeting').play();
}
self.lastHealthBelow65 = currentHealthBelow65;
// Create blood trail when health is below 65 and character is moving
if (self.health < 65 && !self.isDead) {
// Check if character has moved significantly
var deltaX = self.x - self.lastX;
var deltaY = self.y - self.lastY;
var movementDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (movementDistance > 5) {
// Only create trail if moving
self.bloodTrailTimer++;
if (self.bloodTrailTimer >= 8) {
// Create blood trail every 8 frames
var bloodTrail = new BloodTrail();
// Position blood trail slightly behind character
bloodTrail.x = self.x + (Math.random() - 0.5) * 20;
bloodTrail.y = self.y + (Math.random() - 0.5) * 20;
// Use tween to make blood trail fade and shrink
tween(bloodTrail.children[0], {
scaleX: 0.3,
scaleY: 0.3,
alpha: 0
}, {
duration: 3000,
easing: tween.easeOut
});
bloodTrails.push(bloodTrail);
game.addChild(bloodTrail);
self.bloodTrailTimer = 0;
}
}
}
// Update last position for next frame
self.lastX = self.x;
self.lastY = self.y;
// Keep player within arena bounds
var arenaLeft = arena.x - arena.width / 2;
var arenaRight = arena.x + arena.width / 2;
var arenaTop = arena.y - arena.height / 2;
var arenaBottom = arena.y + arena.height / 2;
if (self.x < arenaLeft + 40) self.x = arenaLeft + 40;
if (self.x > arenaRight - 40) self.x = arenaRight - 40;
if (self.y < arenaTop + 40) self.y = arenaTop + 40;
if (self.y > arenaBottom - 40) self.y = arenaBottom - 40;
};
self.findNearestEnemy = function () {
var nearest = null;
var nearestDistance = Infinity;
var maxRange = self.weaponType === 'shotgun' ? 550 : Infinity; // Shotgun has limited range
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 && distance <= maxRange) {
nearestDistance = distance;
nearest = enemy;
}
}
return nearest;
};
self.fireAt = function (target) {
var bullet = new Bullet();
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);
bullet.velocityX = dx / distance * 15;
bullet.velocityY = dy / distance * 15;
bullet.damage = self.damage * self.comboMultiplier;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.takeDamage = function (damage) {
self.health -= damage;
self.comboMultiplier = 1;
self.comboCount = 0;
self.comboTimer = 0;
if (self.health <= 0) {
self.health = 0;
self.die();
}
LK.effects.flashObject(self, 0xFF0000, 200);
};
self.addCombo = function () {
self.comboCount++;
self.comboTimer = 180; // 3 seconds
if (self.comboCount >= 5) {
self.comboMultiplier = Math.min(self.comboMultiplier + 0.1, 3);
}
};
self.die = function () {
self.isDead = true;
self.alpha = 0.5; // Make character semi-transparent
createExplosion(self.x, self.y, 120);
LK.getSound('explosion').play();
LK.showGameOver();
};
self.updateWeaponOrientation = function () {
// Create weapon if it doesn't exist
if (!self.weapon && self.weaponType === 'shotgun') {
self.weapon = self.addChild(LK.getAsset('shotgunWeapon', {
anchorX: 0.0,
anchorY: 0.5
}));
} else if (!self.weapon && self.weaponType === 'basic') {
self.weapon = self.addChild(LK.getAsset('pistolWeapon', {
anchorX: 0.0,
anchorY: 0.5
}));
}
// Orient weapon towards target
if (self.weapon && self.lastTargetX !== 0 && self.lastTargetY !== 0) {
var dx = self.lastTargetX - self.x;
var dy = self.lastTargetY - self.y;
var angle = Math.atan2(dy, dx);
self.weapon.rotation = angle;
// Position weapon so its left edge (tip) is at the player's right side
var weaponDistance = 30; // Distance from player center to weapon tip
self.weapon.x = Math.cos(angle) * weaponDistance;
self.weapon.y = Math.sin(angle) * weaponDistance;
// Flip weapon vertically if pointing left
if (Math.abs(angle) > Math.PI / 2) {
self.weapon.scaleY = -1;
} else {
self.weapon.scaleY = 1;
}
}
};
self.switchToShotgun = function () {
self.weaponType = 'shotgun';
self.hasShotgun = true;
self.magazine = 5;
self.maxMagazine = 5;
self.reloadTime = 90; // 1.5 seconds at 60fps
self.isReloading = false;
// Create weapon graphics
if (self.weapon) {
self.weapon.destroy();
}
self.weapon = self.addChild(LK.getAsset('shotgunWeapon', {
anchorX: 0.0,
anchorY: 0.5
}));
};
return self;
});
var Player = Character.expand(function (type) {
var self = Character.call(this, type);
// Add critical hit chance
self.criticalChance = 0.1;
// Override fireAt to add critical hits
var originalFireAt = self.fireAt;
self.fireAt = function (target) {
originalFireAt.call(self, target);
// Add critical hit chance
var lastBullet = bullets[bullets.length - 1];
if (lastBullet && Math.random() < self.criticalChance) {
lastBullet.isCritical = true;
lastBullet.children[0].tint = 0xFF4444;
}
};
return self;
});
var EliteEnemy = Container.expand(function () {
var self = Container.call(this);
var eliteGraphics = self.attachAsset('eliteEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 150;
self.maxHealth = 150;
self.speed = 1.5;
self.damage = 20;
self.lastPlayerDistance = 0;
self.specialCooldown = 0;
self.isElite = true;
self.update = function () {
// Check for nearby bullets and evade (elite enemies are better at dodging)
var evadeX = 0;
var evadeY = 0;
var shouldEvade = false;
for (var i = 0; i < bullets.length; i++) {
var bullet = bullets[i];
var bulletDx = bullet.x - self.x;
var bulletDy = bullet.y - self.y;
var bulletDistance = Math.sqrt(bulletDx * bulletDx + bulletDy * bulletDy);
// Elite enemies detect bullets from further away
if (bulletDistance < 150) {
var bulletSpeed = Math.sqrt(bullet.velocityX * bullet.velocityX + bullet.velocityY * bullet.velocityY);
if (bulletSpeed > 0) {
// Calculate if bullet is heading towards enemy
var bulletDirection = Math.atan2(bullet.velocityY, bullet.velocityX);
var enemyDirection = Math.atan2(bulletDy, bulletDx);
var angleDiff = Math.abs(bulletDirection - enemyDirection);
if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff;
// Elite enemies have wider detection angle
if (angleDiff < Math.PI / 3) {
shouldEvade = true;
// Calculate perpendicular evasion direction
evadeX += -bulletDy / bulletDistance * (150 - bulletDistance) / 150;
evadeY += bulletDx / bulletDistance * (150 - bulletDistance) / 150;
}
}
}
}
// 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) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
// Apply evasion if needed (elite enemies are better at dodging)
if (shouldEvade) {
// 85% chance to successfully evade
if (Math.random() < 0.85) {
moveX += evadeX * 2.5;
moveY += evadeY * 2.5;
}
}
// Track previous position for direction detection
var prevX = self.x;
self.x += moveX;
self.y += moveY;
// Flip elite enemy asset based on movement direction
var deltaX = self.x - prevX;
if (Math.abs(deltaX) > 0.1) {
// Only flip if there's significant horizontal movement
if (deltaX > 0) {
// Moving right - show normal version
self.children[0].scaleX = Math.abs(self.children[0].scaleX);
} else {
// Moving left - show mirrored (flipped) version
self.children[0].scaleX = -Math.abs(self.children[0].scaleX);
}
}
}
// Special ability - dash attack
if (self.specialCooldown > 0) {
self.specialCooldown--;
} else if (distance < 200 && distance > 80) {
self.specialCooldown = 300; // 5 seconds
self.dashAttack();
}
// Check collision with player
var currentPlayerDistance = distance;
if (self.lastPlayerDistance > 50 && currentPlayerDistance <= 50) {
player.takeDamage(self.damage);
}
self.lastPlayerDistance = currentPlayerDistance;
};
self.dashAttack = function () {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
tween(self, {
x: self.x + dx / distance * 150,
y: self.y + dy / distance * 150
}, {
duration: 300,
easing: tween.easeOut
});
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.die();
} else {
LK.effects.flashObject(self, 0xFFFFFF, 100);
}
};
self.die = function () {
createExplosion(self.x, self.y);
LK.getSound('explosion').play();
LK.setScore(LK.getScore() + 50);
player.addCombo();
// Elite enemies don't drop power-ups
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 50;
self.speed = 1;
self.damage = 10;
self.lastPlayerDistance = 0;
self.update = function () {
// Check for nearby bullets and evade
var evadeX = 0;
var evadeY = 0;
var shouldEvade = false;
for (var i = 0; i < bullets.length; i++) {
var bullet = bullets[i];
var bulletDx = bullet.x - self.x;
var bulletDy = bullet.y - self.y;
var bulletDistance = Math.sqrt(bulletDx * bulletDx + bulletDy * bulletDy);
// If bullet is close (within 120 pixels) and moving towards enemy
if (bulletDistance < 120) {
var bulletSpeed = Math.sqrt(bullet.velocityX * bullet.velocityX + bullet.velocityY * bullet.velocityY);
if (bulletSpeed > 0) {
// Calculate if bullet is heading towards enemy
var bulletDirection = Math.atan2(bullet.velocityY, bullet.velocityX);
var enemyDirection = Math.atan2(bulletDy, bulletDx);
var angleDiff = Math.abs(bulletDirection - enemyDirection);
if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff;
// If bullet is heading towards enemy (within 45 degrees)
if (angleDiff < Math.PI / 4) {
shouldEvade = true;
// Calculate perpendicular evasion direction
evadeX += -bulletDy / bulletDistance * (120 - bulletDistance) / 120;
evadeY += bulletDx / bulletDistance * (120 - bulletDistance) / 120;
}
}
}
}
// 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) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
// Apply evasion if needed
if (shouldEvade) {
// 70% chance to successfully evade
if (Math.random() < 0.7) {
moveX += evadeX * 2;
moveY += evadeY * 2;
}
}
// Track previous position for direction detection
var prevX = self.x;
self.x += moveX;
self.y += moveY;
// Flip enemy asset based on movement direction
var deltaX = self.x - prevX;
if (Math.abs(deltaX) > 0.1) {
// Only flip if there's significant horizontal movement
if (deltaX > 0) {
// Moving right - show normal version
self.children[0].scaleX = Math.abs(self.children[0].scaleX);
} else {
// Moving left - show mirrored (flipped) version
self.children[0].scaleX = -Math.abs(self.children[0].scaleX);
}
}
}
// Check collision with player
var currentPlayerDistance = distance;
if (self.lastPlayerDistance > 50 && currentPlayerDistance <= 50) {
player.takeDamage(self.damage);
}
self.lastPlayerDistance = currentPlayerDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.die();
} else {
LK.effects.flashObject(self, 0xFFFFFF, 100);
}
};
self.die = function () {
createExplosion(self.x, self.y);
LK.getSound('enemyHit').play();
LK.setScore(LK.getScore() + 10);
player.addCombo();
// Chance to drop power-up
if (Math.random() < 0.24) {
var powerup = new PowerUp();
powerup.x = self.x;
powerup.y = self.y;
powerups.push(powerup);
game.addChild(powerup);
}
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var EnemyProjectile = Container.expand(function () {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
projectileGraphics.tint = 0xFF0000;
self.velocityX = 0;
self.velocityY = 0;
self.damage = 15;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with player
if (self.intersects(player)) {
player.takeDamage(self.damage);
self.destroy();
for (var i = 0; i < enemyProjectiles.length; i++) {
if (enemyProjectiles[i] === self) {
enemyProjectiles.splice(i, 1);
break;
}
}
return;
}
// Remove if out of bounds
if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) {
self.destroy();
for (var j = 0; j < enemyProjectiles.length; j++) {
if (enemyProjectiles[j] === self) {
enemyProjectiles.splice(j, 1);
break;
}
}
}
};
return self;
});
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGraphics = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 30;
explosionGraphics.alpha = 0.8;
self.update = function () {
self.lifetime--;
explosionGraphics.alpha = self.lifetime / 30;
explosionGraphics.scaleX = explosionGraphics.scaleY = (30 - self.lifetime) / 30 * 2;
if (self.lifetime <= 0) {
self.destroy();
}
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = Math.floor(Math.random() * 4); // 0: health, 1: damage, 2: fire rate, 3: speed
self.lifetime = 600; // 10 seconds at 60fps
// Keep power-ups in classic color (no tinting)
self.update = function () {
self.lifetime--;
// Fade out near end of lifetime
if (self.lifetime < 120) {
powerupGraphics.alpha = self.lifetime / 120;
}
// Remove if expired
if (self.lifetime <= 0) {
self.destroy();
for (var i = 0; i < powerups.length; i++) {
if (powerups[i] === self) {
powerups.splice(i, 1);
break;
}
}
return;
}
// Check collision with player
if (self.intersects(player)) {
self.applyEffect();
LK.getSound('powerupCollect').play();
self.destroy();
for (var j = 0; j < powerups.length; j++) {
if (powerups[j] === self) {
powerups.splice(j, 1);
break;
}
}
}
};
self.applyEffect = function () {
// All power-ups now give health
player.health = Math.min(player.health + 10, player.maxHealth);
};
return self;
});
var ShotgunPowerup = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('shotgunPowerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 900; // 15 seconds at 60fps
self.bobTimer = 0;
self.initialY = 0;
self.update = function () {
self.lifetime--;
self.bobTimer++;
// Bobbing animation
if (self.initialY === 0) self.initialY = self.y;
self.y = self.initialY + Math.sin(self.bobTimer * 0.1) * 10;
// Fade out near end of lifetime
if (self.lifetime < 180) {
powerupGraphics.alpha = self.lifetime / 180;
}
// Remove if expired
if (self.lifetime <= 0) {
self.destroy();
for (var i = 0; i < shotgunPowerups.length; i++) {
if (shotgunPowerups[i] === self) {
shotgunPowerups.splice(i, 1);
break;
}
}
return;
}
// Check collision with player
if (self.intersects(player)) {
player.switchToShotgun();
LK.getSound('powerupCollect').play();
self.destroy();
for (var j = 0; j < shotgunPowerups.length; j++) {
if (shotgunPowerups[j] === self) {
shotgunPowerups.splice(j, 1);
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2C2C2C
});
/****
* Game Code
****/
// Game variables
var player;
var enemies = [];
var bullets = [];
var powerups = [];
var enemyProjectiles = [];
var explosions = [];
var shotgunPowerups = [];
var bloodTrails = [];
var arena;
var waveNumber = 1;
var enemySpawnTimer = 0;
var enemySpawnRate = 120; // 2 seconds at 60fps
var gameTime = 0;
var selectedCharacter = storage.selectedCharacter || 'warrior';
var upgradeScreenActive = false;
var currentMusicTrack = 'battleMusic';
var musicIntensity = 0;
var lastHealthBelow60 = false; // Track if health was below 60 last frame
// Create arena
arena = game.addChild(LK.getAsset('arena', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
// Create player
player = game.addChild(new Player(selectedCharacter));
player.x = 1024;
player.y = 1600;
// UI Elements
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -300;
scoreText.y = 50;
var healthText = new Text2('Health: 100', {
size: 60,
fill: 0xFFFFFF
});
healthText.anchor.set(0, 0);
LK.gui.topRight.addChild(healthText);
healthText.x = -300;
healthText.y = 120;
var waveText = new Text2('Wave: 1', {
size: 60,
fill: 0xFFFFFF
});
waveText.anchor.set(0, 0);
LK.gui.topRight.addChild(waveText);
waveText.x = -300;
waveText.y = 190;
var comboText = new Text2('Combo: x1', {
size: 50,
fill: 0xFFEB3B
});
comboText.anchor.set(0, 0);
LK.gui.topRight.addChild(comboText);
comboText.x = -300;
comboText.y = 260;
var magazineText = new Text2('7/7', {
size: 80,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
magazineText.anchor.set(0.5, 1);
magazineText.x = player.x;
magazineText.y = player.y - 120;
game.addChild(magazineText);
// Tutorial text
var tutorialText = new Text2('Move by dragging', {
size: 80,
fill: 0xFFFFFF
});
tutorialText.anchor.set(0.5, 0.5);
tutorialText.x = 1024;
tutorialText.y = 1200;
game.addChild(tutorialText);
// Add rainbow cycling to tutorial text
var tutorialColors = [0xFF0000, 0xFF7F00, 0xFFFF00, 0x00FF00, 0x0000FF, 0x4B0082, 0x9400D3];
var tutorialColorIndex = 0;
function cycleTutorialRainbow() {
var nextColor = tutorialColors[(tutorialColorIndex + 1) % tutorialColors.length];
tween(tutorialText, {
tint: nextColor
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tutorialColorIndex = (tutorialColorIndex + 1) % tutorialColors.length;
cycleTutorialRainbow();
}
});
}
cycleTutorialRainbow();
// Fade out tutorial text after 2 seconds
LK.setTimeout(function () {
tween(tutorialText, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tutorialText.destroy();
LK.getSound('characterGreeting').play();
}
});
}, 2000);
// Touch controls
var isDragging = false;
var dragOffsetX = 0;
var dragOffsetY = 0;
var gameStarted = false;
// Player speed limiting
var maxPlayerSpeed = 10;
var targetX = 0;
var targetY = 0;
game.down = function (x, y, obj) {
isDragging = true;
// Calculate offset from touch point to player center
dragOffsetX = player.x - x;
dragOffsetY = player.y - y;
};
game.move = function (x, y, obj) {
if (isDragging) {
// Set target position instead of direct movement
targetX = x + dragOffsetX;
targetY = y + dragOffsetY;
}
};
game.up = function (x, y, obj) {
isDragging = false;
};
// Utility functions
function createExplosion(x, y, size) {
var explosion = new Explosion();
explosion.x = x;
explosion.y = y;
if (size) {
explosion.children[0].scaleX = explosion.children[0].scaleY = size / 100;
}
explosions.push(explosion);
game.addChild(explosion);
}
function showUpgradeScreen() {
upgradeScreenActive = true;
var upgradePanel = LK.getAsset('skillPanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
});
game.addChild(upgradePanel);
var upgradeTitle = new Text2('Choose an Upgrade', {
size: 60,
fill: 0xFFFFFF
});
upgradeTitle.anchor.set(0.5, 0.5);
upgradeTitle.x = 1024;
upgradeTitle.y = 1200;
game.addChild(upgradeTitle);
var upgrades = [{
name: 'Attack Speed',
desc: 'Faster firing rate'
}, {
name: 'Health Boost',
desc: 'Increase max health'
}, {
name: 'Critical Hit',
desc: 'Higher crit chance'
}];
for (var i = 0; i < 3; i++) {
var button = LK.getAsset('skillButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 700 + i * 200,
y: 1366
});
game.addChild(button);
var buttonText = new Text2(upgrades[i].name, {
size: 30,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
buttonText.x = 700 + i * 200;
buttonText.y = 1366;
game.addChild(buttonText);
button.down = function (x, y, obj) {
applyUpgrade(i);
upgradePanel.destroy();
upgradeTitle.destroy();
for (var j = 0; j < 3; j++) {
game.children[game.children.length - 1].destroy();
game.children[game.children.length - 1].destroy();
}
upgradeScreenActive = false;
};
}
}
function applyUpgrade(type) {
switch (type) {
case 0:
// Attack Speed
player.fireRate = Math.max(player.fireRate - 3, 5);
break;
case 1:
// Health Boost
player.maxHealth += 30;
player.health = Math.min(player.health + 30, player.maxHealth);
break;
case 2:
// Critical Hit
player.criticalChance = Math.min(player.criticalChance + 0.1, 0.5);
break;
}
}
// Spawn enemy function
function spawnEnemy() {
var enemy;
// Boss every 5 waves
if (waveNumber % 5 === 0 && enemies.length === 0) {
enemy = new Boss();
LK.getSound('bossSpawn').play();
LK.playMusic('bossMusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
} else if (Math.random() < 0.2) {
// 20% chance for elite enemy
enemy = new EliteEnemy();
} else {
enemy = new Enemy();
}
// Spawn from arena edges
var side = Math.floor(Math.random() * 4);
var arenaLeft = arena.x - arena.width / 2;
var arenaRight = arena.x + arena.width / 2;
var arenaTop = arena.y - arena.height / 2;
var arenaBottom = arena.y + arena.height / 2;
switch (side) {
case 0:
// Top
enemy.x = arenaLeft + Math.random() * arena.width;
enemy.y = arenaTop;
break;
case 1:
// Right
enemy.x = arenaRight;
enemy.y = arenaTop + Math.random() * arena.height;
break;
case 2:
// Bottom
enemy.x = arenaLeft + Math.random() * arena.width;
enemy.y = arenaBottom;
break;
case 3:
// Left
enemy.x = arenaLeft;
enemy.y = arenaTop + Math.random() * arena.height;
break;
}
// Scale enemy stats with wave number
if (!enemy.isBoss) {
enemy.health += Math.floor(waveNumber * 5);
enemy.speed += Math.floor(waveNumber * 0.2);
enemy.damage += Math.floor(waveNumber * 2);
}
enemies.push(enemy);
game.addChild(enemy);
}
// Main game loop
game.update = function () {
// Start game logic after 3 seconds (tutorial display time)
if (gameTime < 180) {
// 3 seconds at 60fps
gameTime++;
return;
}
if (!gameStarted) {
gameStarted = true;
LK.playMusic('battleMusic', {
loop: true
});
}
if (upgradeScreenActive) {
return;
}
// Apply speed-limited movement to player
if (isDragging) {
var dx = targetX - player.x;
var dy = targetY - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var moveX = dx / distance * maxPlayerSpeed;
var moveY = dy / distance * maxPlayerSpeed;
// Track previous position for direction detection
var prevX = player.x;
// Don't overshoot the target
if (distance < maxPlayerSpeed) {
player.x = targetX;
player.y = targetY;
} else {
player.x += moveX;
player.y += moveY;
}
// Flip warrior asset based on movement direction
var deltaX = player.x - prevX;
if (Math.abs(deltaX) > 0.1) {
// Only flip if there's significant horizontal movement
if (deltaX > 0) {
// Moving right - show normal version
player.children[0].scaleX = Math.abs(player.children[0].scaleX);
} else {
// Moving left - show mirrored (flipped) version
player.children[0].scaleX = -Math.abs(player.children[0].scaleX);
}
}
}
}
gameTime++;
// Update wave number based on time
var newWave = Math.floor(gameTime / 1800) + 1; // New wave every 30 seconds
if (newWave > waveNumber) {
waveNumber = newWave;
enemySpawnRate = Math.max(enemySpawnRate - 10, 30); // Increase spawn rate
// Dynamic music intensity
musicIntensity = Math.min(waveNumber / 10, 1);
if (waveNumber % 5 !== 0) {
LK.playMusic('battleMusic', {
fade: {
start: 0.5,
end: 0.5 + musicIntensity * 0.5,
duration: 500
}
});
}
}
// Spawn enemies
enemySpawnTimer++;
if (enemySpawnTimer >= enemySpawnRate) {
var enemiesToSpawn = Math.min(waveNumber, 8);
for (var i = 0; i < enemiesToSpawn; i++) {
spawnEnemy();
}
enemySpawnTimer = 0;
}
// Spawn shotgun powerup occasionally (every 20 seconds if none exist)
if (gameTime % 1200 === 0 && shotgunPowerups.length === 0 && !player.hasShotgun) {
var shotgunPowerup = new ShotgunPowerup();
// Spawn in random location within arena
var arenaLeft = arena.x - arena.width / 2;
var arenaRight = arena.x + arena.width / 2;
var arenaTop = arena.y - arena.height / 2;
var arenaBottom = arena.y + arena.height / 2;
shotgunPowerup.x = arenaLeft + Math.random() * (arenaRight - arenaLeft);
shotgunPowerup.y = arenaTop + Math.random() * (arenaBottom - arenaTop);
shotgunPowerups.push(shotgunPowerup);
game.addChild(shotgunPowerup);
}
// Update explosions
for (var e = explosions.length - 1; e >= 0; e--) {
var explosion = explosions[e];
if (explosion.lifetime <= 0) {
explosions.splice(e, 1);
}
}
// Update UI
scoreText.setText('Score: ' + LK.getScore());
healthText.setText('Health: ' + player.health);
waveText.setText('Wave: ' + waveNumber);
comboText.setText('Combo: x' + player.comboMultiplier.toFixed(1));
// Track health transitions for red overlay effect
var currentHealthBelow60 = player.health < 60;
// Red full screen effect when health is below 60
if (currentHealthBelow60) {
// Create red full screen overlay if it doesn't exist
if (!game.redFullscreenOverlay) {
// Full screen overlay
game.redFullscreenOverlay = LK.getAsset('skillPanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 6,
scaleY: 8
});
game.redFullscreenOverlay.tint = 0x8B0000; // Darker crimson red
game.redFullscreenOverlay.alpha = 0;
game.addChild(game.redFullscreenOverlay);
// Create heal warning text
game.healWarningText = new Text2('You Need To Heal Up!!', {
size: 100,
fill: 0xFFFFFF
});
game.healWarningText.anchor.set(0.5, 0.5);
game.healWarningText.x = 1024;
game.healWarningText.y = 1366;
game.healWarningText.alpha = 0;
game.addChild(game.healWarningText);
}
// Calculate red intensity based on health (more red = less health)
var healthPercentage = player.health / 60;
var redIntensity = Math.max(0, (1 - healthPercentage) * 0.4); // Max 40% opacity for full screen
// Animate red full screen overlay to target intensity
tween(game.redFullscreenOverlay, {
alpha: redIntensity
}, {
duration: 200,
easing: tween.easeOut
});
// Animate warning text with pulsing effect
var textIntensity = Math.max(0, (1 - healthPercentage) * 0.8); // More visible text
tween(game.healWarningText, {
alpha: textIntensity
}, {
duration: 200,
easing: tween.easeOut
});
}
// Check if health just went from below 60 to above 60 (transition)
if (lastHealthBelow60 && !currentHealthBelow60) {
// Health just went above 60 - fade out red overlay and text smoothly
if (game.redFullscreenOverlay) {
tween(game.redFullscreenOverlay, {
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (game.redFullscreenOverlay) {
game.redFullscreenOverlay.destroy();
game.redFullscreenOverlay = null;
}
}
});
tween(game.healWarningText, {
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (game.healWarningText) {
game.healWarningText.destroy();
game.healWarningText = null;
}
}
});
}
}
// Update last health state
lastHealthBelow60 = currentHealthBelow60;
// Update magazine UI
if (player.isReloading) {
magazineText.setText('RELOADING...');
magazineText.tint = 0xFF4444;
} else {
magazineText.setText(player.magazine + '/' + player.maxMagazine);
if (player.weaponType === 'shotgun') {
// Shotgun has orange/yellow color scheme
if (player.magazine <= 1) {
magazineText.tint = 0xFF4444;
} else if (player.magazine <= 2) {
magazineText.tint = 0xFFAA00;
} else {
magazineText.tint = 0xFFA500; // Orange for shotgun
}
} else {
// Default weapon color scheme
if (player.magazine <= 2) {
magazineText.tint = 0xFF4444;
} else if (player.magazine <= 4) {
magazineText.tint = 0xFFAA00;
} else {
magazineText.tint = 0xFFFFFF;
}
}
}
magazineText.x = player.x;
magazineText.y = player.y - 120;
// Update combo text color
if (player.comboMultiplier > 2) {
comboText.tint = 0xFF4444;
} else if (player.comboMultiplier > 1.5) {
comboText.tint = 0xFFAA00;
} else {
comboText.tint = 0xFFEB3B;
}
};
An arow white. In-Game asset. 2d. High contrast. No shadows
Post apocalyptic man pixel art less pixel. In-Game asset. 2d. High contrast. No shadows. Pixel art
Kamp ateşi ve etrafındaki taşlar daha küçük olsun ve harita daha büyük olsun(daha da yukarıdan bakıyormuş gibi)
Post apocalyptic zombie pixel art less pixel. In-Game asset. 2d. High contrast. No shadows
Post Apocalyptic boss zombie pixel art less pixel. In-Game asset. 2d. High contrast. No shadows
Pixel art shotgun less pixel. In-Game asset. 2d. High contrast. No shadows. Pixel art
Particles are scattered around scattered particles pixel art less pixel. In-Game asset. 2d. High contrast. No shadows
Pistol post apocalyptic world pixel art less pixel. In-Game asset. 2d. High contrast. No shadows