/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var AmmoBox = Container.expand(function () { var self = Container.call(this); var ammoGraphics = self.attachAsset('ammoBox', { anchorX: 0.5, anchorY: 0.5 }); self.ammoAmount = 15; self.lifetime = 600; // 10 seconds at 60fps self.update = function () { self.lifetime--; // Pulsing effect ammoGraphics.alpha = 0.7 + Math.sin(self.lifetime * 0.2) * 0.3; if (self.lifetime <= 0) { return true; // Should be removed } return false; }; return self; }); var Bazooka = Container.expand(function () { var self = Container.call(this); var bazookaGraphics = self.attachAsset('bazooka', { anchorX: 0.5, anchorY: 0.5 }); self.ammo = 5; self.maxAmmo = 5; self.cooldown = 0; self.lastX = 0; self.lastY = 0; self.update = function () { self.lastX = self.x; self.lastY = self.y; // Reset bazooka usage flag usingBazooka = false; // Always ready to fire pulsing effect bazookaGraphics.alpha = 0.8 + Math.sin(LK.ticks * 0.2) * 0.2; }; self.down = function (x, y, obj) { self.fireBazooka(); }; self.fireBazooka = function () { // Find nearest enemy to target first var nearestEnemy = 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 < nearestDistance) { nearestDistance = distance; nearestEnemy = enemy; } } // Target boss if no enemies or boss is closer if (boss) { var dx = boss.x - self.x; var dy = boss.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (!nearestEnemy || distance < nearestDistance) { nearestEnemy = boss; nearestDistance = distance; } } // Create bullet var bullet = new BazookaBullet(); bullet.x = self.x; bullet.y = self.y; if (nearestEnemy) { // Target the enemy var dx = nearestEnemy.x - self.x; var dy = nearestEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { bullet.directionX = dx / distance; bullet.directionY = dy / distance; } } else { // No enemy found - fire randomly var randomAngle = Math.random() * Math.PI * 2; bullet.directionX = Math.cos(randomAngle); bullet.directionY = Math.sin(randomAngle); } // Practice mode: 100% normal firing behavior if (selectedGameMode === 4) { bazookaBullets.push(bullet); game.addChild(bullet); // Play bazooka sound LK.getSound('bazookaShoot').play(); // Flash effect LK.effects.flashObject(self, 0xff8800, 300); // Set bazooka usage flag usingBazooka = true; return; } // Get difficulty-based bazooka chances for other modes var bazookaChances = [{ target: 0.85, blank: 0.12, explode: 0.03 }, // Easy: 85% target, 12% blank, 3% explode { target: 0.75, blank: 0.15, explode: 0.10 }, // Normal: 75% target, 15% blank, 10% explode { target: 0.65, blank: 0.20, explode: 0.15 }, // Hard: 65% target, 20% blank, 15% explode { target: 0.50, blank: 0.25, explode: 0.25 }, // Nightmare: 50% target, 25% blank, 25% explode { target: 0.30, blank: 0.30, explode: 0.40 } // Impossible: 30% target, 30% blank, 40% explode ]; var chances = bazookaChances[selectedDifficulty - 1]; // Random outcome based on difficulty var randomOutcome = Math.random(); if (randomOutcome < chances.explode) { // Self-explosion and death // Create explosion at bazooka position var explosion = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); explosion.x = self.x; explosion.y = self.y; explosion.alpha = 0.8; game.addChild(explosion); // Animate explosion tween(explosion, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 500, onFinish: function onFinish() { explosion.destroy(); } }); // Play explosion sound LK.getSound('explosion').play(); // Kill player if (player) { player.takeDamage(player.health); // Deal enough damage to kill } // Flash effect LK.effects.flashObject(self, 0xff0000, 300); // Set bazooka usage flag usingBazooka = true; return; } else if (randomOutcome < chances.explode + chances.blank) { // Blank ammo (do nothing) // Play a different sound or visual effect for blank LK.getSound('bazookaShoot').play(); // Flash effect with different color LK.effects.flashObject(self, 0x888888, 300); // Create blank ammo text var blankText = new Text2('blank ammo, try again', { size: 40, fill: 0xffffff }); blankText.anchor.set(0.5, 0.5); blankText.x = self.x; blankText.y = self.y - 60; game.addChild(blankText); // Fade out blank text after 2 seconds tween(blankText, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { blankText.destroy(); } }); // Set bazooka usage flag usingBazooka = true; return; } // Normal behavior - fire bazooka bazookaBullets.push(bullet); game.addChild(bullet); // Play bazooka sound LK.getSound('bazookaShoot').play(); // Flash effect LK.effects.flashObject(self, 0xff8800, 300); // Set bazooka usage flag usingBazooka = true; }; self.addAmmo = function (amount) { self.ammo = Math.min(self.maxAmmo, self.ammo + amount); }; return self; }); var BazookaBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bazookaBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.damage = 200; self.explosionRadius = 300; self.directionX = 0; self.directionY = 0; self.lastX = 0; self.lastY = 0; self.update = function () { self.lastX = self.x; self.lastY = self.y; self.x += self.directionX * self.speed; self.y += self.directionY * self.speed; // Remove if out of bounds if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { return true; // Should be removed } return false; }; self.explode = function () { // Create explosion visual effect var explosion = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); explosion.x = self.x; explosion.y = self.y; explosion.alpha = 0.8; game.addChild(explosion); // Animate explosion tween(explosion, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 500, onFinish: function onFinish() { explosion.destroy(); } }); // Play explosion sound LK.getSound('explosion').play(); // Damage all enemies in radius 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.explosionRadius) { enemy.takeDamage(self.damage); } } // Damage boss if in radius if (boss) { var dx = boss.x - self.x; var dy = boss.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.explosionRadius) { boss.takeDamage(self.damage); } } }; return self; }); var Boss = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 3, tint: 0x800080 }); // Apply difficulty-based boss health values var difficultyBossHealth = [50000, 100000, 150000, 200000, 300000]; // Easy, Normal, Hard, Nightmare, Impossible var bossHealth = difficultyBossHealth[selectedDifficulty - 1]; self.health = bossHealth; self.maxHealth = bossHealth; self.speed = 4; self.damage = 50; self.attackCooldown = 0; self.lastX = 0; self.lastY = 0; self.lastPlayerDistance = 1000; self.update = function () { self.lastX = self.x; self.lastY = self.y; if (self.attackCooldown > 0) { self.attackCooldown--; } // Find nearest NPC first, then player var targetEntity = null; var nearestDistance = Infinity; // Check NPCs first (priority targets) for (var k = 0; k < npcs.length; k++) { var npc = npcs[k]; var dx = npc.x - self.x; var dy = npc.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; targetEntity = npc; } } // If no NPCs found or player is closer, target player if (player) { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (!targetEntity || distance < nearestDistance) { nearestDistance = distance; targetEntity = player; } } // Move towards target entity if (targetEntity) { var dx = targetEntity.x - self.x; var dy = targetEntity.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; } // Attack if close enough var currentDistance = distance; if (currentDistance < 100 && self.attackCooldown <= 0) { // Practice mode: boss does no damage if (selectedGameMode === 4) { // No damage in practice mode } else { targetEntity.takeDamage(self.damage); } self.attackCooldown = 60; } self.lastPlayerDistance = currentDistance; } }; self.takeDamage = function (amount) { self.health -= amount; LK.effects.flashObject(self, 0xffffff, 200); if (self.health <= 0) { return true; // Boss died } return false; }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 12; self.damage = 100; // Ensure one-hit kills self.directionX = 0; self.directionY = 0; self.lastX = 0; self.lastY = 0; self.update = function () { self.lastX = self.x; self.lastY = self.y; self.x += self.directionX * self.speed; self.y += self.directionY * self.speed; // Remove if out of bounds if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { return true; // Should be removed } return false; }; return self; }); var DifficultySelectionScreen = Container.expand(function () { var self = Container.call(this); // Dark background var background = self.attachAsset('menuBackground', { x: 0, y: 0 }); // Blood moon effect in top right var moon = self.attachAsset('moonGlow', { x: 1600, y: 300, alpha: 0.7, anchorX: 0.5, anchorY: 0.5 }); // Main title var titleText = new Text2('CHOOSE DIFFICULTY', { size: 80, fill: 0xFF4444, align: 'center' }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 500; self.addChild(titleText); // Practice Mode var practiceText = new Text2('PRACTICE', { size: 60, fill: 0x00FFFF, align: 'center' }); practiceText.anchor.set(0.5, 0.5); practiceText.x = 1024; practiceText.y = 750; self.addChild(practiceText); var practiceDesc = new Text2('Learn the game with no pressure', { size: 50, fill: 0xCCCCCC, align: 'center' }); practiceDesc.anchor.set(0.5, 0.5); practiceDesc.x = 1024; practiceDesc.y = 830; self.addChild(practiceDesc); // Easy var easyText = new Text2('EASY', { size: 60, fill: 0x00FF00, align: 'center' }); easyText.anchor.set(0.5, 0.5); easyText.x = 1024; easyText.y = 1000; self.addChild(easyText); var easyDesc = new Text2('Slower enemies, more resources', { size: 50, fill: 0xCCCCCC, align: 'center' }); easyDesc.anchor.set(0.5, 0.5); easyDesc.x = 1024; easyDesc.y = 1080; self.addChild(easyDesc); // Normal var normalText = new Text2('NORMAL', { size: 60, fill: 0xFFFF00, align: 'center' }); normalText.anchor.set(0.5, 0.5); normalText.x = 1024; normalText.y = 1250; self.addChild(normalText); var normalDesc = new Text2('Balanced challenge', { size: 50, fill: 0xCCCCCC, align: 'center' }); normalDesc.anchor.set(0.5, 0.5); normalDesc.x = 1024; normalDesc.y = 1330; self.addChild(normalDesc); // Hard var hardText = new Text2('HARD', { size: 60, fill: 0xFF8800, align: 'center' }); hardText.anchor.set(0.5, 0.5); hardText.x = 1024; hardText.y = 1500; self.addChild(hardText); var hardDesc = new Text2('Faster enemies, less resources', { size: 50, fill: 0xCCCCCC, align: 'center' }); hardDesc.anchor.set(0.5, 0.5); hardDesc.x = 1024; hardDesc.y = 1580; self.addChild(hardDesc); // Nightmare var nightmareText = new Text2('NIGHTMARE', { size: 60, fill: 0xFF0000, align: 'center' }); nightmareText.anchor.set(0.5, 0.5); nightmareText.x = 1024; nightmareText.y = 1750; self.addChild(nightmareText); var nightmareDesc = new Text2('For the truly insane', { size: 50, fill: 0xCCCCCC, align: 'center' }); nightmareDesc.anchor.set(0.5, 0.5); nightmareDesc.x = 1024; nightmareDesc.y = 1830; self.addChild(nightmareDesc); // Impossible var impossibleText = new Text2('IMPOSSIBLE', { size: 60, fill: 0x660066, align: 'center' }); impossibleText.anchor.set(0.5, 0.5); impossibleText.x = 1024; impossibleText.y = 2000; self.addChild(impossibleText); var impossibleDesc = new Text2('Death awaits all who dare', { size: 50, fill: 0xCCCCCC, align: 'center' }); impossibleDesc.anchor.set(0.5, 0.5); impossibleDesc.x = 1024; impossibleDesc.y = 2080; self.addChild(impossibleDesc); // Selection instruction var selectText = new Text2('TAP TO SELECT DIFFICULTY', { size: 40, fill: 0xFF6666, align: 'center' }); selectText.anchor.set(0.5, 0.5); selectText.x = 1024; selectText.y = 2300; self.addChild(selectText); // Pulsing effect self.pulseTimer = 0; self.update = function () { self.pulseTimer += 0.1; selectText.alpha = 0.5 + Math.sin(self.pulseTimer) * 0.3; moon.alpha = 0.5 + Math.sin(self.pulseTimer * 0.5) * 0.2; }; // Handle difficulty selection self.down = function (x, y, obj) { var difficultyToSet = 2; // Default to normal if (y > 700 && y < 900) { // Practice mode - set to easy difficulty but with practice mode flag selectedGameMode = 4; selectedDifficulty = 1; self.destroy(); showWeaponsCutscene(); return; } else if (y > 950 && y < 1150) { difficultyToSet = 1; // Easy } else if (y > 1200 && y < 1400) { difficultyToSet = 2; // Normal } else if (y > 1450 && y < 1650) { difficultyToSet = 3; // Hard } else if (y > 1700 && y < 1900) { difficultyToSet = 4; // Nightmare } else if (y > 1950 && y < 2150) { difficultyToSet = 5; // Impossible } selectedDifficulty = difficultyToSet; self.destroy(); showWeaponsCutscene(); }; 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; // Will be one-shot by 100 damage bullets self.speed = 8; self.damage = 20; self.attackCooldown = 0; self.lastX = 0; self.lastY = 0; self.lastPlayerDistance = 1000; self.update = function () { self.lastX = self.x; self.lastY = self.y; if (self.attackCooldown > 0) { self.attackCooldown--; } // Find nearest NPC first, then player var targetEntity = null; var nearestDistance = Infinity; // Check NPCs first (priority targets) for (var k = 0; k < npcs.length; k++) { var npc = npcs[k]; var dx = npc.x - self.x; var dy = npc.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; targetEntity = npc; } } // If no NPCs found or player is closer, target player if (player) { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (!targetEntity || distance < nearestDistance) { nearestDistance = distance; targetEntity = player; } } // Move towards target entity if (targetEntity) { var dx = targetEntity.x - self.x; var dy = targetEntity.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; } // Attack if close enough var currentDistance = distance; if (currentDistance < 50 && self.attackCooldown <= 0) { // Practice mode: enemies do no damage if (selectedGameMode === 4) { // No damage in practice mode } else { targetEntity.takeDamage(self.damage); } self.attackCooldown = 60; } self.lastPlayerDistance = currentDistance; } }; self.takeDamage = function (amount) { self.health -= amount; LK.effects.flashObject(self, 0xffffff, 200); if (self.health <= 0) { return true; // Enemy died } return false; }; return self; }); var HealthPack = Container.expand(function () { var self = Container.call(this); var healthGraphics = self.attachAsset('healthPack', { anchorX: 0.5, anchorY: 0.5 }); self.healAmount = 30; self.lifetime = 600; // 10 seconds at 60fps self.update = function () { self.lifetime--; // Pulsing effect healthGraphics.alpha = 0.7 + Math.sin(self.lifetime * 0.2) * 0.3; if (self.lifetime <= 0) { return true; // Should be removed } return false; }; return self; }); var MenuScreen = Container.expand(function () { var self = Container.call(this); // Dark background var background = self.attachAsset('menuBackground', { x: 0, y: 0 }); // Blood moon effect in top right var moon = self.attachAsset('moonGlow', { x: 1600, y: 300, alpha: 0.7, anchorX: 0.5, anchorY: 0.5 }); // Blood splatter decorations var splatter1 = self.attachAsset('bloodSplatter', { x: 200, y: 500, alpha: 0.6, anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 1.2 }); var splatter2 = self.attachAsset('bloodSplatter', { x: 1700, y: 1800, alpha: 0.4, anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.7 }); // Main title var titleText = new Text2('NIGHT OF THE\nMASSACRE 3', { size: 120, fill: 0xFF4444, align: 'center' }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 800; self.addChild(titleText); // Subtitle var subtitleText = new Text2('BLOOD MOON RISING', { size: 60, fill: 0xFFFFFF, align: 'center' }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 1024; subtitleText.y = 1000; self.addChild(subtitleText); // Start instruction var startText = new Text2('TAP ANYWHERE TO BEGIN', { size: 40, fill: 0xCCCCCC, align: 'center' }); startText.anchor.set(0.5, 0.5); startText.x = 1024; startText.y = 1800; self.addChild(startText); // Pulsing effect for start text self.pulseTimer = 0; self.update = function () { self.pulseTimer += 0.1; startText.alpha = 0.5 + Math.sin(self.pulseTimer) * 0.3; // Subtle moon glow animation moon.alpha = 0.5 + Math.sin(self.pulseTimer * 0.5) * 0.2; }; // Handle tap to start self.down = function (x, y, obj) { // Transition to game (this will be handled by destroying menu and starting game) self.destroy(); startGame(); }; return self; }); var ModeSelectionScreen = Container.expand(function () { var self = Container.call(this); // Dark background var background = self.attachAsset('menuBackground', { x: 0, y: 0 }); // Blood moon effect in top right var moon = self.attachAsset('moonGlow', { x: 1600, y: 300, alpha: 0.7, anchorX: 0.5, anchorY: 0.5 }); // Main title var titleText = new Text2('CHOOSE YOUR NIGHTMARE', { size: 80, fill: 0xFF4444, align: 'center' }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 600; self.addChild(titleText); // Mode 1 - Classic Survival var mode1Text = new Text2('CLASSIC SURVIVAL', { size: 60, fill: 0xFFFFFF, align: 'center' }); mode1Text.anchor.set(0.5, 0.5); mode1Text.x = 1024; mode1Text.y = 1000; self.addChild(mode1Text); var mode1Desc = new Text2('Survive until dawn with limited resources', { size: 50, fill: 0xCCCCCC, align: 'center' }); mode1Desc.anchor.set(0.5, 0.5); mode1Desc.x = 1024; mode1Desc.y = 1080; self.addChild(mode1Desc); // Mode 2 - Endless Nightmare var mode2Text = new Text2('ENDLESS NIGHTMARE', { size: 60, fill: 0xFFFFFF, align: 'center' }); mode2Text.anchor.set(0.5, 0.5); mode2Text.x = 1024; mode2Text.y = 1300; self.addChild(mode2Text); var mode2Desc = new Text2('Face endless waves of increasing horror', { size: 50, fill: 0xCCCCCC, align: 'center' }); mode2Desc.anchor.set(0.5, 0.5); mode2Desc.x = 1024; mode2Desc.y = 1380; self.addChild(mode2Desc); // Mode 3 - Blood Eclipse var mode3Text = new Text2('BLOOD ECLIPSE', { size: 60, fill: 0xFFFFFF, align: 'center' }); mode3Text.anchor.set(0.5, 0.5); mode3Text.x = 1024; mode3Text.y = 1600; self.addChild(mode3Text); var mode3Desc = new Text2('Ultimate challenge with eclipse phases', { size: 50, fill: 0xCCCCCC, align: 'center' }); mode3Desc.anchor.set(0.5, 0.5); mode3Desc.x = 1024; mode3Desc.y = 1680; self.addChild(mode3Desc); // Back instruction var backText = new Text2('TAP TO SELECT MODE', { size: 40, fill: 0xFF6666, align: 'center' }); backText.anchor.set(0.5, 0.5); backText.x = 1024; backText.y = 2200; self.addChild(backText); // Pulsing effect self.pulseTimer = 0; self.update = function () { self.pulseTimer += 0.1; backText.alpha = 0.5 + Math.sin(self.pulseTimer) * 0.3; moon.alpha = 0.5 + Math.sin(self.pulseTimer * 0.5) * 0.2; }; // Handle mode selection self.down = function (x, y, obj) { selectedGameMode = 1; // Default to Classic Survival if (y > 1200 && y < 1400) { selectedGameMode = 2; // Endless Nightmare } else if (y > 1500 && y < 1700) { selectedGameMode = 3; // Blood Eclipse } self.destroy(); showDifficultySelection(); }; return self; }); var NPC = Container.expand(function () { var self = Container.call(this); var npcGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, tint: 0x00ff00 }); var knifeGraphics = self.attachAsset('knife', { anchorX: 0.5, anchorY: 1.0, x: 25, y: -10 }); // Apply difficulty-based health values var difficultyHealthValues = [120, 80, 60, 40, 25]; // Easy, Normal, Hard, Nightmare, Impossible var baseHealth = difficultyHealthValues[selectedDifficulty - 1]; self.health = baseHealth; self.maxHealth = baseHealth; // Apply difficulty-based speed values - more difficult = slower NPCs var difficultySpeedValues = [8, 6, 4, 2, 0.8]; // Easy, Normal, Hard, Nightmare, Impossible (0.1X speed) self.speed = difficultySpeedValues[selectedDifficulty - 1]; self.ammo = 20; // NPCs start with limited ammo self.maxAmmo = 25; // Maximum ammo NPCs can carry self.shootCooldown = 0; self.meleeRange = 60; // Range for knife attacks // Apply difficulty-based knife damage - harder difficulty = less knife damage var difficultyKnifeDamage = [120, 80, 60, 40, 20]; // Easy, Normal, Hard, Nightmare, Impossible self.meleeDamage = difficultyKnifeDamage[selectedDifficulty - 1]; self.meleeAttackCooldown = 0; // Cooldown for melee attacks self.lastX = 0; self.lastY = 0; self.lastPlayerDistance = 1000; self.update = function () { self.lastX = self.x; self.lastY = self.y; if (self.shootCooldown > 0) { self.shootCooldown--; } if (self.meleeAttackCooldown > 0) { self.meleeAttackCooldown--; } // Find nearest enemy to move towards var nearestEnemy = 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 < nearestDistance) { nearestDistance = distance; nearestEnemy = enemy; } } // Also consider boss if (boss) { var dx = boss.x - self.x; var dy = boss.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (!nearestEnemy || distance < nearestDistance) { nearestEnemy = boss; nearestDistance = distance; } } // Calculate repulsion force from other NPCs to maintain distance var repulsionX = 0; var repulsionY = 0; var minNPCDistance = 80; // Minimum distance to maintain from other NPCs for (var k = 0; k < npcs.length; k++) { var otherNPC = npcs[k]; if (otherNPC !== self) { var dx = self.x - otherNPC.x; var dy = self.y - otherNPC.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < minNPCDistance && distance > 0) { // Apply repulsion force (stronger when closer) var repulsionStrength = (minNPCDistance - distance) / minNPCDistance; repulsionX += dx / distance * repulsionStrength * 3; repulsionY += dy / distance * repulsionStrength * 3; } } } // Move towards nearest enemy if (nearestEnemy && nearestDistance > 100) { var dx = nearestEnemy.x - self.x; var dy = nearestEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // Combine enemy attraction with NPC repulsion var moveX = dx / distance * self.speed + repulsionX; var moveY = dy / distance * self.speed + repulsionY; self.x += moveX; self.y += moveY; } } else if (repulsionX !== 0 || repulsionY !== 0) { // If no enemy target, just apply repulsion self.x += repulsionX; self.y += repulsionY; } // Keep NPC in bounds if (self.x < 40) self.x = 40; if (self.x > 2008) self.x = 2008; if (self.y < 40) self.y = 40; if (self.y > 2692) self.y = 2692; // Try melee attack first if enemy is close enough var meleeTargetFound = false; if (self.meleeAttackCooldown <= 0) { // Check for enemies in melee range for (var m = 0; m < enemies.length; m++) { var enemy = enemies[m]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.meleeRange) { // Practice mode: NPCs do no damage if (selectedGameMode === 4) { // No damage in practice mode } else { enemy.takeDamage(self.meleeDamage); } LK.effects.flashObject(self, 0xffffff, 200); self.meleeAttackCooldown = 45; // Slower than shooting meleeTargetFound = true; break; } } // Check boss in melee range if (!meleeTargetFound && boss) { var dx = boss.x - self.x; var dy = boss.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.meleeRange) { // Practice mode: NPCs do no damage if (selectedGameMode === 4) { // No damage in practice mode } else { boss.takeDamage(self.meleeDamage); } LK.effects.flashObject(self, 0xffffff, 200); self.meleeAttackCooldown = 45; meleeTargetFound = true; } } } // Auto-shoot at enemies if no melee target and have ammo if (!meleeTargetFound && self.shootCooldown <= 0) { self.shootAtEnemies(); } }; self.shootAtEnemies = function () { // Find nearest enemy var nearestEnemy = 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 < nearestDistance) { nearestDistance = distance; nearestEnemy = enemy; } } // Also consider boss if (boss) { var dx = boss.x - self.x; var dy = boss.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (!nearestEnemy || distance < nearestDistance) { nearestEnemy = boss; nearestDistance = distance; } } // Shoot at nearest enemy only if we have ammo if (nearestEnemy && self.ammo > 0) { var bullet = new NPCBullet(); 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); if (distance > 0) { bullet.directionX = dx / distance; bullet.directionY = dy / distance; } npcBullets.push(bullet); game.addChild(bullet); self.ammo--; // Consume ammo self.shootCooldown = 30; // Slower than player LK.getSound('shoot').play(); } }; self.addAmmo = function (amount) { self.ammo = Math.min(self.maxAmmo, self.ammo + amount); }; self.takeDamage = function (amount) { self.health -= amount; LK.effects.flashObject(self, 0xff0000, 300); if (self.health <= 0) { return true; // NPC died } return false; }; return self; }); var NPCBullet = Container.expand(function () { var self = Container.call(this); // Difficulty-based bullet properties var difficultyAssets = ['npcBulletEasy', 'npcBulletNormal', 'npcBulletHard', 'npcBulletNightmare', 'npcBulletImpossible']; var difficultyProps = [{ speed: 10, damage: 80, color: 0x00ff88 }, // Easy: slower, less damage { speed: 12, damage: 100, color: 0x88ff00 }, // Normal: standard { speed: 14, damage: 120, color: 0xffaa00 }, // Hard: faster, more damage { speed: 16, damage: 140, color: 0xff4400 }, // Nightmare: fastest, most damage { speed: 18, damage: 160, color: 0x660066 } // Impossible: insanely fast, devastating damage ]; var props = difficultyProps[selectedDifficulty - 1]; var assetId = difficultyAssets[selectedDifficulty - 1]; var bulletGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.speed = props.speed; self.damage = props.damage; self.directionX = 0; self.directionY = 0; self.lastX = 0; self.lastY = 0; self.update = function () { self.lastX = self.x; self.lastY = self.y; self.x += self.directionX * self.speed; self.y += self.directionY * self.speed; // Remove if out of bounds if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { return true; // Should be removed } return false; }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.maxHealth = 100; self.ammo = 30; self.maxAmmo = 30; self.speed = 8; self.shootCooldown = 0; self.lastX = 0; self.lastY = 0; self.update = function () { self.lastX = self.x; self.lastY = self.y; if (self.shootCooldown > 0) { self.shootCooldown--; } // Keep player in bounds if (self.x < 40) self.x = 40; if (self.x > 2008) self.x = 2008; if (self.y < 40) self.y = 40; if (self.y > 2692) self.y = 2692; }; self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { self.health = 0; LK.showGameOver(); } LK.effects.flashObject(self, 0xff0000, 500); }; self.heal = function (amount) { self.health = Math.min(self.maxHealth, self.health + amount); }; self.addAmmo = function (amount) { self.ammo = Math.min(self.maxAmmo, self.ammo + amount); }; self.canShoot = function () { return self.ammo > 0 && self.shootCooldown <= 0; }; self.shoot = function () { if (self.canShoot()) { self.ammo--; self.shootCooldown = 15; return true; } return false; }; return self; }); var WeaponsCutscene = Container.expand(function () { var self = Container.call(this); // Dark background var background = self.attachAsset('menuBackground', { x: 0, y: 0 }); // Title text var titleText = new Text2('WEAPONS FOUND', { size: 100, fill: 0xff4444, align: 'center' }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 400; self.addChild(titleText); // Subtitle var subtitleText = new Text2('The survivor discovers powerful weapons', { size: 60, fill: 0xffffff, align: 'center' }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 1024; subtitleText.y = 520; self.addChild(subtitleText); // Assault Rifle var assaultRifle = self.attachAsset('assaultRifle', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 800, alpha: 0 }); var rifleText = new Text2('ASSAULT RIFLE', { size: 50, fill: 0xffff00, align: 'center' }); rifleText.anchor.set(0.5, 0.5); rifleText.x = 1024; rifleText.y = 880; rifleText.alpha = 0; self.addChild(rifleText); var rifleDesc = new Text2('Rapid fire weapon for long range combat', { size: 40, fill: 0xcccccc, align: 'center' }); rifleDesc.anchor.set(0.5, 0.5); rifleDesc.x = 1024; rifleDesc.y = 930; rifleDesc.alpha = 0; self.addChild(rifleDesc); // Knife var knife = self.attachAsset('knife', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1200, alpha: 0 }); var knifeText = new Text2('COMBAT KNIFE', { size: 50, fill: 0x00ff00, align: 'center' }); knifeText.anchor.set(0.5, 0.5); knifeText.x = 1024; knifeText.y = 1280; knifeText.alpha = 0; self.addChild(knifeText); var knifeDesc = new Text2('Silent melee weapon for close combat', { size: 40, fill: 0xcccccc, align: 'center' }); knifeDesc.anchor.set(0.5, 0.5); knifeDesc.x = 1024; knifeDesc.y = 1330; knifeDesc.alpha = 0; self.addChild(knifeDesc); // Bazooka var bazooka = self.attachAsset('bazooka', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1600, alpha: 0 }); var bazookaText = new Text2('BAZOOKA', { size: 50, fill: 0xff8800, align: 'center' }); bazookaText.anchor.set(0.5, 0.5); bazookaText.x = 1024; bazookaText.y = 1680; bazookaText.alpha = 0; self.addChild(bazookaText); var bazookaDesc = new Text2('Heavy explosive launcher with area damage', { size: 40, fill: 0xcccccc, align: 'center' }); bazookaDesc.anchor.set(0.5, 0.5); bazookaDesc.x = 1024; bazookaDesc.y = 1730; bazookaDesc.alpha = 0; self.addChild(bazookaDesc); // Continue text var continueText = new Text2('TAP TO CONTINUE', { size: 50, fill: 0xff6666, align: 'center' }); continueText.anchor.set(0.5, 0.5); continueText.x = 1024; continueText.y = 2200; continueText.alpha = 0; self.addChild(continueText); // Skip button var skipButton = new Text2('SKIP CUTSCENE', { size: 60, fill: 0xffaaaa, align: 'center' }); skipButton.anchor.set(1, 0); skipButton.x = 1950; skipButton.y = 2500; skipButton.alpha = 1; self.addChild(skipButton); // Animation state self.animationPhase = 0; self.animationTimer = 0; self.canContinue = false; self.update = function () { self.animationTimer++; // Phase 0: Show assault rifle (after 1 second) if (self.animationPhase === 0 && self.animationTimer > 60) { self.animationPhase = 1; tween(assaultRifle, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); tween(rifleText, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); tween(rifleDesc, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); } // Phase 1: Show knife (after 3 seconds) if (self.animationPhase === 1 && self.animationTimer > 180) { self.animationPhase = 2; tween(knife, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); tween(knifeText, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); tween(knifeDesc, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); } // Phase 2: Show bazooka (after 5 seconds) if (self.animationPhase === 2 && self.animationTimer > 300) { self.animationPhase = 3; tween(bazooka, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); tween(bazookaText, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); tween(bazookaDesc, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); } // Phase 3: Show continue text (after 7 seconds) if (self.animationPhase === 3 && self.animationTimer > 420) { self.animationPhase = 4; self.canContinue = true; // Destroy skip button when continue appears if (skipButton) { skipButton.destroy(); skipButton = null; } tween(continueText, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); } // Pulsing continue text if (self.canContinue) { continueText.alpha = 0.7 + Math.sin(self.animationTimer * 0.1) * 0.3; } }; self.down = function (x, y, obj) { // Check if skip button was tapped (bottom right area) if (x > 1700 && x < 2048 && y > 2450 && y < 2550) { // Destroy skip button before ending cutscene if (skipButton) { skipButton.destroy(); skipButton = null; } self.destroy(); startGameWithModeAndDifficulty(selectedGameMode, selectedDifficulty); return; } // Check if continue button was tapped (only if can continue and not in skip button area) if (self.canContinue && !(x > 1700 && x < 2048 && y > 2450 && y < 2550)) { // Destroy skip button before ending cutscene if (skipButton) { skipButton.destroy(); skipButton = null; } self.destroy(); startGameWithModeAndDifficulty(selectedGameMode, selectedDifficulty); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0400ff }); /**** * Game Code ****/ var menuScreen = null; var modeSelectionScreen = null; var difficultySelectionScreen = null; var weaponsCutscene = null; var gameStarted = false; var selectedGameMode = 1; var selectedDifficulty = 2; // 1=easy, 2=normal, 3=hard, 4=nightmare // Game variables var player = null; var enemies = []; var bullets = []; var npcBullets = []; var bazookaBullets = []; var bazooka = null; var healthPacks = []; var ammoBoxes = []; var boss = null; var bossSpawned = false; var bossDied = false; var npcs = []; var gameTimer = 0; var enemySpawnTimer = 0; var waveNumber = 1; var enemiesKilled = 0; var survivalTime = 0; var eclipsePhase = 0; // 0=normal, 1=blood moon, 2=eclipse var eclipseTimer = 0; var usingBazooka = false; // UI elements var healthText = null; var ammoText = null; var waveText = null; var timeText = null; var eclipseText = null; var bossHealthText = null; var bazookaInstructionText = null; function startGame() { // Show mode selection screen instead of starting game directly showModeSelection(); } function showMenu() { if (!menuScreen) { menuScreen = new MenuScreen(); game.addChild(menuScreen); LK.playMusic('menumusic'); } } function showModeSelection() { if (!modeSelectionScreen) { modeSelectionScreen = new ModeSelectionScreen(); game.addChild(modeSelectionScreen); LK.playMusic('menumusic'); } } function showDifficultySelection() { if (!difficultySelectionScreen) { difficultySelectionScreen = new DifficultySelectionScreen(); game.addChild(difficultySelectionScreen); LK.playMusic('menumusic'); } } function showWeaponsCutscene() { if (!weaponsCutscene) { weaponsCutscene = new WeaponsCutscene(); game.addChild(weaponsCutscene); LK.playMusic('cutscenemusic'); } } function startGameWithMode(mode) { selectedGameMode = mode; gameStarted = true; initializeGame(); } function startGameWithModeAndDifficulty(mode, difficulty) { selectedGameMode = mode; selectedDifficulty = difficulty; gameStarted = true; initializeGame(); } function initializeGame() { // Clear game state enemies = []; bullets = []; npcBullets = []; bazookaBullets = []; healthPacks = []; ammoBoxes = []; npcs = []; boss = null; bossSpawned = false; bossDied = false; gameTimer = 0; enemySpawnTimer = 0; waveNumber = 1; enemiesKilled = 0; survivalTime = 0; eclipsePhase = 0; eclipseTimer = 0; // Create player player = new Player(); player.x = 1024; player.y = 1366; game.addChild(player); // Create bazooka bazooka = new Bazooka(); bazooka.x = 1024; bazooka.y = 2000; game.addChild(bazooka); // Create UI healthText = new Text2('Health: 100', { size: 40, fill: 0x00ff00 }); healthText.x = 150; healthText.y = 150; LK.gui.topLeft.addChild(healthText); ammoText = new Text2('Ammo: 30', { size: 40, fill: 0xffff00 }); ammoText.x = 150; ammoText.y = 200; LK.gui.topLeft.addChild(ammoText); timeText = new Text2('Time: 0:00', { size: 40, fill: 0xffffff }); timeText.anchor.set(1, 0); timeText.x = -50; timeText.y = 150; LK.gui.topRight.addChild(timeText); if (selectedGameMode === 3) { eclipseText = new Text2('Phase: Normal', { size: 40, fill: 0xff6666 }); eclipseText.anchor.set(1, 0); eclipseText.x = -50; eclipseText.y = 200; LK.gui.topRight.addChild(eclipseText); } // Spawn 10 NPCs at game start for (var i = 0; i < 10; i++) { spawnNPC(); } // Create bazooka instruction text bazookaInstructionText = new Text2('BAZOOKA, CLICK TO LAUNCH', { size: 50, fill: 0xffaa00 }); bazookaInstructionText.anchor.set(0.5, 0); bazookaInstructionText.x = bazooka.x; bazookaInstructionText.y = bazooka.y + 30; game.addChild(bazookaInstructionText); // Create bazooka description text with difficulty-based percentages var bazookaChances = [{ target: 85, blank: 12, explode: 3 }, // Easy { target: 75, blank: 15, explode: 10 }, // Normal { target: 65, blank: 20, explode: 15 }, // Hard { target: 50, blank: 25, explode: 25 }, // Nightmare { target: 30, blank: 30, explode: 40 } // Impossible ]; var chances = bazookaChances[selectedDifficulty - 1]; var bazookaDescriptionText; if (selectedGameMode === 4) { // Practice mode: 100% guaranteed targeting bazookaDescriptionText = new Text2('100% GUARANTEED TARGET ENEMY', { size: 40, fill: 0x00ff00 }); } else { bazookaDescriptionText = new Text2(chances.target + '% target enemy, ' + chances.blank + '% blank ammo, ' + chances.explode + '% self explode', { size: 40, fill: 0xffcc66 }); } bazookaDescriptionText.anchor.set(0.5, 0); bazookaDescriptionText.x = bazooka.x; bazookaDescriptionText.y = bazooka.y + 80; game.addChild(bazookaDescriptionText); // Fade out instruction text after 3 seconds tween(bazookaInstructionText, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { if (bazookaInstructionText) { bazookaInstructionText.destroy(); bazookaInstructionText = null; } } }); // Fade out description text after 3 seconds tween(bazookaDescriptionText, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { if (bazookaDescriptionText) { bazookaDescriptionText.destroy(); bazookaDescriptionText = null; } } }); // Start background music LK.playMusic('bgmusic'); } function spawnEnemy() { var enemy = new Enemy(); // Spawn from random edge var edge = Math.floor(Math.random() * 4); switch (edge) { case 0: // Top enemy.x = Math.random() * 2048; enemy.y = -30; break; case 1: // Right enemy.x = 2078; enemy.y = Math.random() * 2732; break; case 2: // Bottom enemy.x = Math.random() * 2048; enemy.y = 2762; break; case 3: // Left enemy.x = -30; enemy.y = Math.random() * 2732; break; } // Apply difficulty-based health, speed and damage values var difficultyMultipliers = [{ health: 35, // Easy: 35 health speed: 0.8, damage: 0.7 }, // Easy { health: 50, // Normal: 50 health (default) speed: 1.0, damage: 1.0 }, // Normal { health: 75, // Hard: 75 health speed: 1.2, damage: 1.3 }, // Hard { health: 100, // Nightmare: 100 health speed: 1.4, damage: 1.6 }, // Nightmare { health: 150, // Impossible: 150 health speed: 4.0, damage: 2.0 } // Impossible: maximum challenge with insane speed ]; var multiplier = difficultyMultipliers[selectedDifficulty - 1]; enemy.health = multiplier.health; enemy.speed *= multiplier.speed; enemy.damage = Math.floor(enemy.damage * multiplier.damage); // Practice mode adjustments - make enemies very weak for learning if (selectedGameMode === 4) { enemy.health = 25; // Very low health enemy.speed *= 0.5; // Half speed enemy.damage = 5; // Very low damage } // Adjust enemy stats based on mode and wave if (selectedGameMode === 2) { // Endless Nightmare enemy.health += waveNumber * 10; enemy.speed += waveNumber * 0.3; enemy.damage += waveNumber * 5; } else if (selectedGameMode === 3) { // Blood Eclipse enemy.health += waveNumber * 15; enemy.speed += waveNumber * 0.5; enemy.damage += waveNumber * 8; if (eclipsePhase === 1) { // Blood moon enemy.health *= 1.5; enemy.speed *= 1.3; } else if (eclipsePhase === 2) { // Eclipse enemy.health *= 2; enemy.speed *= 1.5; enemy.damage *= 1.5; } } enemies.push(enemy); game.addChild(enemy); } function spawnHealthPack() { var healthPack = new HealthPack(); healthPack.x = 100 + Math.random() * 1848; healthPack.y = 100 + Math.random() * 2532; healthPacks.push(healthPack); game.addChild(healthPack); } function spawnAmmoBox() { var ammoBox = new AmmoBox(); ammoBox.x = 100 + Math.random() * 1848; ammoBox.y = 100 + Math.random() * 2532; ammoBoxes.push(ammoBox); game.addChild(ammoBox); } function spawnNPC() { var npc = new NPC(); // Spawn near player but not too close var angle = Math.random() * Math.PI * 2; var distance = 150 + Math.random() * 100; // 150-250 pixels from player npc.x = player.x + Math.cos(angle) * distance; npc.y = player.y + Math.sin(angle) * distance; // Keep in bounds if (npc.x < 40) npc.x = 40; if (npc.x > 2008) npc.x = 2008; if (npc.y < 40) npc.y = 40; if (npc.y > 2692) npc.y = 2692; npcs.push(npc); game.addChild(npc); } function spawnBoss() { boss = new Boss(); boss.x = 1024; boss.y = 100; bossSpawned = true; game.addChild(boss); // Create boss health UI bossHealthText = new Text2('BOSS: 100000 / 100000', { size: 50, fill: 0xff0000 }); bossHealthText.anchor.set(0.5, 0); LK.gui.top.addChild(bossHealthText); } function fireBullet(targetX, targetY) { if (player && player.shoot() && !usingBazooka) { var bullet = new Bullet(); bullet.x = player.x; bullet.y = player.y; // Determine target priority: boss only when present, then tap location var dx, dy, distance; if (boss) { // Target boss when present (ignore enemies completely) dx = boss.x - player.x; dy = boss.y - player.y; } else { // No boss, use tap location dx = targetX - player.x; dy = targetY - player.y; } distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { bullet.directionX = dx / distance; bullet.directionY = dy / distance; } bullets.push(bullet); game.addChild(bullet); // Play shooting sound LK.getSound('shoot').play(); } } function updateGameMode() { gameTimer++; survivalTime = Math.floor(gameTimer / 60); // Spawn boss at 06:00 (6 minutes) - but not in endless nightmare mode // Only spawn if boss hasn't been spawned yet, hasn't died yet, and no victory screen is showing if (survivalTime >= 360 && !bossSpawned && !bossDied && selectedGameMode !== 2 && boss === null) { spawnBoss(); } // Mode specific logic if (selectedGameMode === 1) { // Classic Survival // Win condition: survive 5 minutes if (survivalTime >= 300) { LK.showYouWin(); return; } // Spawn enemies based on time if (gameTimer % Math.max(30, 120 - survivalTime) === 0 && !bossDied) { spawnEnemy(); } // Spawn resources occasionally (adjusted by difficulty) var resourceMultipliers = [0.7, 1.0, 1.4, 2.0, 3.0]; // Easy, Normal, Hard, Nightmare, Impossible var resourceDelay = resourceMultipliers[selectedDifficulty - 1]; if (gameTimer % Math.floor(600 * resourceDelay) === 0) spawnHealthPack(); if (gameTimer % Math.floor(900 * resourceDelay) === 0) spawnAmmoBox(); // NPCs already spawned at game start - no additional spawning needed } else if (selectedGameMode === 2) { // Endless Nightmare // New wave every 30 seconds var newWave = Math.floor(survivalTime / 30) + 1; if (newWave > waveNumber) { waveNumber = newWave; } // Spawn enemies more frequently as waves progress var spawnRate = Math.max(10, 60 - waveNumber * 3); if (gameTimer % spawnRate === 0 && !bossDied) { spawnEnemy(); } // Spawn resources based on wave (adjusted by difficulty) var resourceMultipliers = [0.7, 1.0, 1.4, 2.0, 3.0]; // Easy, Normal, Hard, Nightmare, Impossible var resourceDelay = resourceMultipliers[selectedDifficulty - 1]; if (gameTimer % Math.floor((600 - waveNumber * 20) * resourceDelay) === 0) spawnHealthPack(); if (gameTimer % Math.floor((900 - waveNumber * 30) * resourceDelay) === 0) spawnAmmoBox(); // NPCs already spawned at game start - no additional spawning needed } else if (selectedGameMode === 3) { // Blood Eclipse eclipseTimer++; // Eclipse phases: 90s normal, 60s blood moon, 30s eclipse, repeat var cycleTime = eclipseTimer % (180 * 60); // 3 minutes cycle if (cycleTime < 90 * 60) { eclipsePhase = 0; // Normal } else if (cycleTime < 150 * 60) { eclipsePhase = 1; // Blood moon } else { eclipsePhase = 2; // Eclipse } // Spawn rate increases with phase var spawnRate = [60, 30, 15][eclipsePhase]; if (gameTimer % spawnRate === 0 && !bossDied) { spawnEnemy(); } // Resources spawn less during eclipse phases (adjusted by difficulty) var resourceRate = [600, 800, 1200][eclipsePhase]; var resourceMultipliers = [0.7, 1.0, 1.4, 2.0, 3.0]; // Easy, Normal, Hard, Nightmare, Impossible var adjustedRate = Math.floor(resourceRate * resourceMultipliers[selectedDifficulty - 1]); if (gameTimer % adjustedRate === 0) spawnHealthPack(); if (gameTimer % Math.floor(adjustedRate * 1.5) === 0) spawnAmmoBox(); // NPCs already spawned at game start - no additional spawning needed } else if (selectedGameMode === 4) { // Practice Mode // Very slow enemy spawning for learning if (gameTimer % 300 === 0 && !bossDied) { // Every 5 seconds spawnEnemy(); } // Frequent resource spawning to help learning if (gameTimer % 300 === 0) spawnHealthPack(); if (gameTimer % 450 === 0) spawnAmmoBox(); // No win condition - practice indefinitely // No boss spawning in practice mode } } function updateUI() { if (healthText) { healthText.setText('Health: ' + player.health); healthText.fill = player.health > 30 ? 0x00ff00 : 0xff0000; } if (ammoText) { ammoText.setText('Ammo: ' + player.ammo); ammoText.fill = player.ammo > 5 ? 0xffff00 : 0xff0000; } if (timeText) { var minutes = Math.floor(survivalTime / 60); var seconds = survivalTime % 60; timeText.setText('Time: ' + minutes + ':' + (seconds < 10 ? '0' : '') + seconds); } if (eclipseText && selectedGameMode === 3) { var phases = ['Normal', 'Blood Moon', 'Eclipse']; eclipseText.setText('Phase: ' + phases[eclipsePhase]); eclipseText.fill = [0xffffff, 0xff6666, 0x660000][eclipsePhase]; } if (bossHealthText && boss) { bossHealthText.setText('BOSS: ' + boss.health + ' / ' + boss.maxHealth); } } // Show menu on game start showMenu(); game.down = function (x, y, obj) { if (gameStarted && player) { fireBullet(x, y); } }; game.move = function (x, y, obj) { if (gameStarted && player) { player.x = x; player.y = y; } }; game.update = function () { if (!gameStarted) { return; } if (!player) return; // Update game mode logic updateGameMode(); // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].update(); // Check if enemy should be removed if (enemies[i].health <= 0) { enemies[i].destroy(); enemies.splice(i, 1); enemiesKilled++; } } // Update NPCs for (var i = npcs.length - 1; i >= 0; i--) { var npc = npcs[i]; npc.update(); // Check if NPC died if (npc.health <= 0) { npc.destroy(); npcs.splice(i, 1); continue; } // Check NPC-enemy collisions (enemies can attack NPCs) for (var j = 0; j < enemies.length; j++) { var enemy = enemies[j]; var dx = enemy.x - npc.x; var dy = enemy.y - npc.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 50 && enemy.attackCooldown <= 0) { // Practice mode: enemies do no damage to NPCs if (selectedGameMode === 4) { // No damage in practice mode } else { npc.takeDamage(enemy.damage); } enemy.attackCooldown = 60; break; } } // Check NPC-boss collision if (boss) { var dx = boss.x - npc.x; var dy = boss.y - npc.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 100 && boss.attackCooldown <= 0) { // Practice mode: boss does no damage to NPCs if (selectedGameMode === 4) { // No damage in practice mode } else { npc.takeDamage(boss.damage); } boss.attackCooldown = 60; } } } // Update boss if (boss) { boss.update(); // Check if boss died if (boss.health <= 0) { boss.destroy(); boss = null; bossSpawned = false; bossDied = true; // Kill all remaining enemies for (var k = enemies.length - 1; k >= 0; k--) { enemies[k].destroy(); enemies.splice(k, 1); } if (bossHealthText) { bossHealthText.destroy(); bossHealthText = null; } // Show victory screen for 10 seconds before game over var victoryText = new Text2('YOU SURVIVED THIRD NIGHT,\nHOPE YOU DIDNT DEAD AT THE FOURTH NIGHT', { size: 80, fill: 0x00ff00, align: 'center' }); victoryText.anchor.set(0.5, 0.5); victoryText.x = 1024; victoryText.y = 1366; game.addChild(victoryText); // Fade in the victory text victoryText.alpha = 0; tween(victoryText, { alpha: 1 }, { duration: 1000 }); // Wait 10 seconds then show game over tween({}, {}, { duration: 10000, onFinish: function onFinish() { LK.showGameOver(); } }); } } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; var shouldRemove = bullet.update(); if (shouldRemove) { bullet.destroy(); bullets.splice(i, 1); continue; } // Check bullet-enemy collisions for (var j = enemies.length - 1; j >= 0; j--) { if (bullet.intersects(enemies[j])) { var enemyDied = enemies[j].takeDamage(bullet.damage); bullet.destroy(); bullets.splice(i, 1); if (enemyDied) { enemies[j].destroy(); enemies.splice(j, 1); enemiesKilled++; } break; } } // Check bullet-boss collision if (boss && bullet.intersects(boss)) { var bossKilled = boss.takeDamage(bullet.damage); bullet.destroy(); bullets.splice(i, 1); if (bossKilled) { boss.destroy(); boss = null; bossSpawned = false; bossDied = true; // Kill all remaining enemies for (var k = enemies.length - 1; k >= 0; k--) { enemies[k].destroy(); enemies.splice(k, 1); } if (bossHealthText) { bossHealthText.destroy(); bossHealthText = null; } // Show victory screen for 10 seconds before game over var victoryText = new Text2('YOU SURVIVED THIRD NIGHT,\nHOPE YOU DIDNT DEAD AT THE FOURTH NIGHT', { size: 80, fill: 0x00ff00, align: 'center' }); victoryText.anchor.set(0.5, 0.5); victoryText.x = 1024; victoryText.y = 1366; game.addChild(victoryText); // Fade in the victory text victoryText.alpha = 0; tween(victoryText, { alpha: 1 }, { duration: 1000 }); // Wait 10 seconds then show game over tween({}, {}, { duration: 10000, onFinish: function onFinish() { LK.showGameOver(); } }); } break; } } // Update NPC bullets for (var i = npcBullets.length - 1; i >= 0; i--) { var bullet = npcBullets[i]; var shouldRemove = bullet.update(); if (shouldRemove) { bullet.destroy(); npcBullets.splice(i, 1); continue; } // Check NPC bullet-enemy collisions for (var j = enemies.length - 1; j >= 0; j--) { if (bullet.intersects(enemies[j])) { var enemyDied = enemies[j].takeDamage(bullet.damage); bullet.destroy(); npcBullets.splice(i, 1); if (enemyDied) { enemies[j].destroy(); enemies.splice(j, 1); enemiesKilled++; } break; } } // Check NPC bullet-boss collision if (boss && bullet.intersects(boss)) { var bossKilled = boss.takeDamage(bullet.damage); bullet.destroy(); npcBullets.splice(i, 1); if (bossKilled) { boss.destroy(); boss = null; bossSpawned = false; bossDied = true; // Kill all remaining enemies for (var k = enemies.length - 1; k >= 0; k--) { enemies[k].destroy(); enemies.splice(k, 1); } if (bossHealthText) { bossHealthText.destroy(); bossHealthText = null; } // Show victory screen for 10 seconds before game over var victoryText = new Text2('YOU SURVIVED THIRD NIGHT,\nHOPE YOU DIDNT DEAD AT THE FOURTH NIGHT', { size: 80, fill: 0x00ff00, align: 'center' }); victoryText.anchor.set(0.5, 0.5); victoryText.x = 1024; victoryText.y = 1366; game.addChild(victoryText); // Fade in the victory text victoryText.alpha = 0; tween(victoryText, { alpha: 1 }, { duration: 1000 }); // Wait 10 seconds then show game over tween({}, {}, { duration: 10000, onFinish: function onFinish() { LK.showGameOver(); } }); } break; } } // Update bazooka bullets for (var i = bazookaBullets.length - 1; i >= 0; i--) { var bullet = bazookaBullets[i]; var shouldRemove = bullet.update(); if (shouldRemove) { bullet.explode(); bullet.destroy(); bazookaBullets.splice(i, 1); continue; } // Check bazooka bullet-enemy collisions for (var j = enemies.length - 1; j >= 0; j--) { if (bullet.intersects(enemies[j])) { bullet.explode(); bullet.destroy(); bazookaBullets.splice(i, 1); break; } } // Check bazooka bullet-boss collision if (boss && bullet.intersects(boss)) { bullet.explode(); bullet.destroy(); bazookaBullets.splice(i, 1); break; } } // Update bazooka if (bazooka) { bazooka.update(); } // Update health packs for (var i = healthPacks.length - 1; i >= 0; i--) { var pack = healthPacks[i]; var shouldRemove = pack.update(); if (shouldRemove) { pack.destroy(); healthPacks.splice(i, 1); continue; } // Check player collision if (player.intersects(pack)) { player.heal(pack.healAmount); LK.getSound('heal').play(); pack.destroy(); healthPacks.splice(i, 1); } } // Update ammo boxes for (var i = ammoBoxes.length - 1; i >= 0; i--) { var box = ammoBoxes[i]; var shouldRemove = box.update(); if (shouldRemove) { box.destroy(); ammoBoxes.splice(i, 1); continue; } var collected = false; // Check player collision first (priority) if (player.intersects(box)) { player.addAmmo(box.ammoAmount); LK.getSound('ammo').play(); box.destroy(); ammoBoxes.splice(i, 1); collected = true; } // Check NPC collisions if not collected by player and NPC needs ammo if (!collected) { for (var k = 0; k < npcs.length; k++) { var npc = npcs[k]; if (npc.intersects(box) && npc.ammo < npc.maxAmmo - 5) { // NPCs only take if they really need ammo (leave some for player) npc.addAmmo(Math.floor(box.ammoAmount * 0.6)); // NPCs get 60% of ammo amount LK.getSound('ammo').play(); box.destroy(); ammoBoxes.splice(i, 1); collected = true; break; } } } } // Update UI updateUI(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AmmoBox = Container.expand(function () {
var self = Container.call(this);
var ammoGraphics = self.attachAsset('ammoBox', {
anchorX: 0.5,
anchorY: 0.5
});
self.ammoAmount = 15;
self.lifetime = 600; // 10 seconds at 60fps
self.update = function () {
self.lifetime--;
// Pulsing effect
ammoGraphics.alpha = 0.7 + Math.sin(self.lifetime * 0.2) * 0.3;
if (self.lifetime <= 0) {
return true; // Should be removed
}
return false;
};
return self;
});
var Bazooka = Container.expand(function () {
var self = Container.call(this);
var bazookaGraphics = self.attachAsset('bazooka', {
anchorX: 0.5,
anchorY: 0.5
});
self.ammo = 5;
self.maxAmmo = 5;
self.cooldown = 0;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Reset bazooka usage flag
usingBazooka = false;
// Always ready to fire pulsing effect
bazookaGraphics.alpha = 0.8 + Math.sin(LK.ticks * 0.2) * 0.2;
};
self.down = function (x, y, obj) {
self.fireBazooka();
};
self.fireBazooka = function () {
// Find nearest enemy to target first
var nearestEnemy = 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 < nearestDistance) {
nearestDistance = distance;
nearestEnemy = enemy;
}
}
// Target boss if no enemies or boss is closer
if (boss) {
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (!nearestEnemy || distance < nearestDistance) {
nearestEnemy = boss;
nearestDistance = distance;
}
}
// Create bullet
var bullet = new BazookaBullet();
bullet.x = self.x;
bullet.y = self.y;
if (nearestEnemy) {
// Target the enemy
var dx = nearestEnemy.x - self.x;
var dy = nearestEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
bullet.directionX = dx / distance;
bullet.directionY = dy / distance;
}
} else {
// No enemy found - fire randomly
var randomAngle = Math.random() * Math.PI * 2;
bullet.directionX = Math.cos(randomAngle);
bullet.directionY = Math.sin(randomAngle);
}
// Practice mode: 100% normal firing behavior
if (selectedGameMode === 4) {
bazookaBullets.push(bullet);
game.addChild(bullet);
// Play bazooka sound
LK.getSound('bazookaShoot').play();
// Flash effect
LK.effects.flashObject(self, 0xff8800, 300);
// Set bazooka usage flag
usingBazooka = true;
return;
}
// Get difficulty-based bazooka chances for other modes
var bazookaChances = [{
target: 0.85,
blank: 0.12,
explode: 0.03
},
// Easy: 85% target, 12% blank, 3% explode
{
target: 0.75,
blank: 0.15,
explode: 0.10
},
// Normal: 75% target, 15% blank, 10% explode
{
target: 0.65,
blank: 0.20,
explode: 0.15
},
// Hard: 65% target, 20% blank, 15% explode
{
target: 0.50,
blank: 0.25,
explode: 0.25
},
// Nightmare: 50% target, 25% blank, 25% explode
{
target: 0.30,
blank: 0.30,
explode: 0.40
} // Impossible: 30% target, 30% blank, 40% explode
];
var chances = bazookaChances[selectedDifficulty - 1];
// Random outcome based on difficulty
var randomOutcome = Math.random();
if (randomOutcome < chances.explode) {
// Self-explosion and death
// Create explosion at bazooka position
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
explosion.x = self.x;
explosion.y = self.y;
explosion.alpha = 0.8;
game.addChild(explosion);
// Animate explosion
tween(explosion, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Play explosion sound
LK.getSound('explosion').play();
// Kill player
if (player) {
player.takeDamage(player.health); // Deal enough damage to kill
}
// Flash effect
LK.effects.flashObject(self, 0xff0000, 300);
// Set bazooka usage flag
usingBazooka = true;
return;
} else if (randomOutcome < chances.explode + chances.blank) {
// Blank ammo (do nothing)
// Play a different sound or visual effect for blank
LK.getSound('bazookaShoot').play();
// Flash effect with different color
LK.effects.flashObject(self, 0x888888, 300);
// Create blank ammo text
var blankText = new Text2('blank ammo, try again', {
size: 40,
fill: 0xffffff
});
blankText.anchor.set(0.5, 0.5);
blankText.x = self.x;
blankText.y = self.y - 60;
game.addChild(blankText);
// Fade out blank text after 2 seconds
tween(blankText, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
blankText.destroy();
}
});
// Set bazooka usage flag
usingBazooka = true;
return;
}
// Normal behavior - fire bazooka
bazookaBullets.push(bullet);
game.addChild(bullet);
// Play bazooka sound
LK.getSound('bazookaShoot').play();
// Flash effect
LK.effects.flashObject(self, 0xff8800, 300);
// Set bazooka usage flag
usingBazooka = true;
};
self.addAmmo = function (amount) {
self.ammo = Math.min(self.maxAmmo, self.ammo + amount);
};
return self;
});
var BazookaBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bazookaBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 200;
self.explosionRadius = 300;
self.directionX = 0;
self.directionY = 0;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Remove if out of bounds
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
return true; // Should be removed
}
return false;
};
self.explode = function () {
// Create explosion visual effect
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
explosion.x = self.x;
explosion.y = self.y;
explosion.alpha = 0.8;
game.addChild(explosion);
// Animate explosion
tween(explosion, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Play explosion sound
LK.getSound('explosion').play();
// Damage all enemies in radius
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.explosionRadius) {
enemy.takeDamage(self.damage);
}
}
// Damage boss if in radius
if (boss) {
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.explosionRadius) {
boss.takeDamage(self.damage);
}
}
};
return self;
});
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 3,
tint: 0x800080
});
// Apply difficulty-based boss health values
var difficultyBossHealth = [50000, 100000, 150000, 200000, 300000]; // Easy, Normal, Hard, Nightmare, Impossible
var bossHealth = difficultyBossHealth[selectedDifficulty - 1];
self.health = bossHealth;
self.maxHealth = bossHealth;
self.speed = 4;
self.damage = 50;
self.attackCooldown = 0;
self.lastX = 0;
self.lastY = 0;
self.lastPlayerDistance = 1000;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Find nearest NPC first, then player
var targetEntity = null;
var nearestDistance = Infinity;
// Check NPCs first (priority targets)
for (var k = 0; k < npcs.length; k++) {
var npc = npcs[k];
var dx = npc.x - self.x;
var dy = npc.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
targetEntity = npc;
}
}
// If no NPCs found or player is closer, target player
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (!targetEntity || distance < nearestDistance) {
nearestDistance = distance;
targetEntity = player;
}
}
// Move towards target entity
if (targetEntity) {
var dx = targetEntity.x - self.x;
var dy = targetEntity.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;
}
// Attack if close enough
var currentDistance = distance;
if (currentDistance < 100 && self.attackCooldown <= 0) {
// Practice mode: boss does no damage
if (selectedGameMode === 4) {
// No damage in practice mode
} else {
targetEntity.takeDamage(self.damage);
}
self.attackCooldown = 60;
}
self.lastPlayerDistance = currentDistance;
}
};
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xffffff, 200);
if (self.health <= 0) {
return true; // Boss died
}
return false;
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 12;
self.damage = 100; // Ensure one-hit kills
self.directionX = 0;
self.directionY = 0;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Remove if out of bounds
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
return true; // Should be removed
}
return false;
};
return self;
});
var DifficultySelectionScreen = Container.expand(function () {
var self = Container.call(this);
// Dark background
var background = self.attachAsset('menuBackground', {
x: 0,
y: 0
});
// Blood moon effect in top right
var moon = self.attachAsset('moonGlow', {
x: 1600,
y: 300,
alpha: 0.7,
anchorX: 0.5,
anchorY: 0.5
});
// Main title
var titleText = new Text2('CHOOSE DIFFICULTY', {
size: 80,
fill: 0xFF4444,
align: 'center'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 500;
self.addChild(titleText);
// Practice Mode
var practiceText = new Text2('PRACTICE', {
size: 60,
fill: 0x00FFFF,
align: 'center'
});
practiceText.anchor.set(0.5, 0.5);
practiceText.x = 1024;
practiceText.y = 750;
self.addChild(practiceText);
var practiceDesc = new Text2('Learn the game with no pressure', {
size: 50,
fill: 0xCCCCCC,
align: 'center'
});
practiceDesc.anchor.set(0.5, 0.5);
practiceDesc.x = 1024;
practiceDesc.y = 830;
self.addChild(practiceDesc);
// Easy
var easyText = new Text2('EASY', {
size: 60,
fill: 0x00FF00,
align: 'center'
});
easyText.anchor.set(0.5, 0.5);
easyText.x = 1024;
easyText.y = 1000;
self.addChild(easyText);
var easyDesc = new Text2('Slower enemies, more resources', {
size: 50,
fill: 0xCCCCCC,
align: 'center'
});
easyDesc.anchor.set(0.5, 0.5);
easyDesc.x = 1024;
easyDesc.y = 1080;
self.addChild(easyDesc);
// Normal
var normalText = new Text2('NORMAL', {
size: 60,
fill: 0xFFFF00,
align: 'center'
});
normalText.anchor.set(0.5, 0.5);
normalText.x = 1024;
normalText.y = 1250;
self.addChild(normalText);
var normalDesc = new Text2('Balanced challenge', {
size: 50,
fill: 0xCCCCCC,
align: 'center'
});
normalDesc.anchor.set(0.5, 0.5);
normalDesc.x = 1024;
normalDesc.y = 1330;
self.addChild(normalDesc);
// Hard
var hardText = new Text2('HARD', {
size: 60,
fill: 0xFF8800,
align: 'center'
});
hardText.anchor.set(0.5, 0.5);
hardText.x = 1024;
hardText.y = 1500;
self.addChild(hardText);
var hardDesc = new Text2('Faster enemies, less resources', {
size: 50,
fill: 0xCCCCCC,
align: 'center'
});
hardDesc.anchor.set(0.5, 0.5);
hardDesc.x = 1024;
hardDesc.y = 1580;
self.addChild(hardDesc);
// Nightmare
var nightmareText = new Text2('NIGHTMARE', {
size: 60,
fill: 0xFF0000,
align: 'center'
});
nightmareText.anchor.set(0.5, 0.5);
nightmareText.x = 1024;
nightmareText.y = 1750;
self.addChild(nightmareText);
var nightmareDesc = new Text2('For the truly insane', {
size: 50,
fill: 0xCCCCCC,
align: 'center'
});
nightmareDesc.anchor.set(0.5, 0.5);
nightmareDesc.x = 1024;
nightmareDesc.y = 1830;
self.addChild(nightmareDesc);
// Impossible
var impossibleText = new Text2('IMPOSSIBLE', {
size: 60,
fill: 0x660066,
align: 'center'
});
impossibleText.anchor.set(0.5, 0.5);
impossibleText.x = 1024;
impossibleText.y = 2000;
self.addChild(impossibleText);
var impossibleDesc = new Text2('Death awaits all who dare', {
size: 50,
fill: 0xCCCCCC,
align: 'center'
});
impossibleDesc.anchor.set(0.5, 0.5);
impossibleDesc.x = 1024;
impossibleDesc.y = 2080;
self.addChild(impossibleDesc);
// Selection instruction
var selectText = new Text2('TAP TO SELECT DIFFICULTY', {
size: 40,
fill: 0xFF6666,
align: 'center'
});
selectText.anchor.set(0.5, 0.5);
selectText.x = 1024;
selectText.y = 2300;
self.addChild(selectText);
// Pulsing effect
self.pulseTimer = 0;
self.update = function () {
self.pulseTimer += 0.1;
selectText.alpha = 0.5 + Math.sin(self.pulseTimer) * 0.3;
moon.alpha = 0.5 + Math.sin(self.pulseTimer * 0.5) * 0.2;
};
// Handle difficulty selection
self.down = function (x, y, obj) {
var difficultyToSet = 2; // Default to normal
if (y > 700 && y < 900) {
// Practice mode - set to easy difficulty but with practice mode flag
selectedGameMode = 4;
selectedDifficulty = 1;
self.destroy();
showWeaponsCutscene();
return;
} else if (y > 950 && y < 1150) {
difficultyToSet = 1; // Easy
} else if (y > 1200 && y < 1400) {
difficultyToSet = 2; // Normal
} else if (y > 1450 && y < 1650) {
difficultyToSet = 3; // Hard
} else if (y > 1700 && y < 1900) {
difficultyToSet = 4; // Nightmare
} else if (y > 1950 && y < 2150) {
difficultyToSet = 5; // Impossible
}
selectedDifficulty = difficultyToSet;
self.destroy();
showWeaponsCutscene();
};
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; // Will be one-shot by 100 damage bullets
self.speed = 8;
self.damage = 20;
self.attackCooldown = 0;
self.lastX = 0;
self.lastY = 0;
self.lastPlayerDistance = 1000;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Find nearest NPC first, then player
var targetEntity = null;
var nearestDistance = Infinity;
// Check NPCs first (priority targets)
for (var k = 0; k < npcs.length; k++) {
var npc = npcs[k];
var dx = npc.x - self.x;
var dy = npc.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
targetEntity = npc;
}
}
// If no NPCs found or player is closer, target player
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (!targetEntity || distance < nearestDistance) {
nearestDistance = distance;
targetEntity = player;
}
}
// Move towards target entity
if (targetEntity) {
var dx = targetEntity.x - self.x;
var dy = targetEntity.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;
}
// Attack if close enough
var currentDistance = distance;
if (currentDistance < 50 && self.attackCooldown <= 0) {
// Practice mode: enemies do no damage
if (selectedGameMode === 4) {
// No damage in practice mode
} else {
targetEntity.takeDamage(self.damage);
}
self.attackCooldown = 60;
}
self.lastPlayerDistance = currentDistance;
}
};
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xffffff, 200);
if (self.health <= 0) {
return true; // Enemy died
}
return false;
};
return self;
});
var HealthPack = Container.expand(function () {
var self = Container.call(this);
var healthGraphics = self.attachAsset('healthPack', {
anchorX: 0.5,
anchorY: 0.5
});
self.healAmount = 30;
self.lifetime = 600; // 10 seconds at 60fps
self.update = function () {
self.lifetime--;
// Pulsing effect
healthGraphics.alpha = 0.7 + Math.sin(self.lifetime * 0.2) * 0.3;
if (self.lifetime <= 0) {
return true; // Should be removed
}
return false;
};
return self;
});
var MenuScreen = Container.expand(function () {
var self = Container.call(this);
// Dark background
var background = self.attachAsset('menuBackground', {
x: 0,
y: 0
});
// Blood moon effect in top right
var moon = self.attachAsset('moonGlow', {
x: 1600,
y: 300,
alpha: 0.7,
anchorX: 0.5,
anchorY: 0.5
});
// Blood splatter decorations
var splatter1 = self.attachAsset('bloodSplatter', {
x: 200,
y: 500,
alpha: 0.6,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 1.2
});
var splatter2 = self.attachAsset('bloodSplatter', {
x: 1700,
y: 1800,
alpha: 0.4,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.7
});
// Main title
var titleText = new Text2('NIGHT OF THE\nMASSACRE 3', {
size: 120,
fill: 0xFF4444,
align: 'center'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
self.addChild(titleText);
// Subtitle
var subtitleText = new Text2('BLOOD MOON RISING', {
size: 60,
fill: 0xFFFFFF,
align: 'center'
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 1024;
subtitleText.y = 1000;
self.addChild(subtitleText);
// Start instruction
var startText = new Text2('TAP ANYWHERE TO BEGIN', {
size: 40,
fill: 0xCCCCCC,
align: 'center'
});
startText.anchor.set(0.5, 0.5);
startText.x = 1024;
startText.y = 1800;
self.addChild(startText);
// Pulsing effect for start text
self.pulseTimer = 0;
self.update = function () {
self.pulseTimer += 0.1;
startText.alpha = 0.5 + Math.sin(self.pulseTimer) * 0.3;
// Subtle moon glow animation
moon.alpha = 0.5 + Math.sin(self.pulseTimer * 0.5) * 0.2;
};
// Handle tap to start
self.down = function (x, y, obj) {
// Transition to game (this will be handled by destroying menu and starting game)
self.destroy();
startGame();
};
return self;
});
var ModeSelectionScreen = Container.expand(function () {
var self = Container.call(this);
// Dark background
var background = self.attachAsset('menuBackground', {
x: 0,
y: 0
});
// Blood moon effect in top right
var moon = self.attachAsset('moonGlow', {
x: 1600,
y: 300,
alpha: 0.7,
anchorX: 0.5,
anchorY: 0.5
});
// Main title
var titleText = new Text2('CHOOSE YOUR NIGHTMARE', {
size: 80,
fill: 0xFF4444,
align: 'center'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 600;
self.addChild(titleText);
// Mode 1 - Classic Survival
var mode1Text = new Text2('CLASSIC SURVIVAL', {
size: 60,
fill: 0xFFFFFF,
align: 'center'
});
mode1Text.anchor.set(0.5, 0.5);
mode1Text.x = 1024;
mode1Text.y = 1000;
self.addChild(mode1Text);
var mode1Desc = new Text2('Survive until dawn with limited resources', {
size: 50,
fill: 0xCCCCCC,
align: 'center'
});
mode1Desc.anchor.set(0.5, 0.5);
mode1Desc.x = 1024;
mode1Desc.y = 1080;
self.addChild(mode1Desc);
// Mode 2 - Endless Nightmare
var mode2Text = new Text2('ENDLESS NIGHTMARE', {
size: 60,
fill: 0xFFFFFF,
align: 'center'
});
mode2Text.anchor.set(0.5, 0.5);
mode2Text.x = 1024;
mode2Text.y = 1300;
self.addChild(mode2Text);
var mode2Desc = new Text2('Face endless waves of increasing horror', {
size: 50,
fill: 0xCCCCCC,
align: 'center'
});
mode2Desc.anchor.set(0.5, 0.5);
mode2Desc.x = 1024;
mode2Desc.y = 1380;
self.addChild(mode2Desc);
// Mode 3 - Blood Eclipse
var mode3Text = new Text2('BLOOD ECLIPSE', {
size: 60,
fill: 0xFFFFFF,
align: 'center'
});
mode3Text.anchor.set(0.5, 0.5);
mode3Text.x = 1024;
mode3Text.y = 1600;
self.addChild(mode3Text);
var mode3Desc = new Text2('Ultimate challenge with eclipse phases', {
size: 50,
fill: 0xCCCCCC,
align: 'center'
});
mode3Desc.anchor.set(0.5, 0.5);
mode3Desc.x = 1024;
mode3Desc.y = 1680;
self.addChild(mode3Desc);
// Back instruction
var backText = new Text2('TAP TO SELECT MODE', {
size: 40,
fill: 0xFF6666,
align: 'center'
});
backText.anchor.set(0.5, 0.5);
backText.x = 1024;
backText.y = 2200;
self.addChild(backText);
// Pulsing effect
self.pulseTimer = 0;
self.update = function () {
self.pulseTimer += 0.1;
backText.alpha = 0.5 + Math.sin(self.pulseTimer) * 0.3;
moon.alpha = 0.5 + Math.sin(self.pulseTimer * 0.5) * 0.2;
};
// Handle mode selection
self.down = function (x, y, obj) {
selectedGameMode = 1; // Default to Classic Survival
if (y > 1200 && y < 1400) {
selectedGameMode = 2; // Endless Nightmare
} else if (y > 1500 && y < 1700) {
selectedGameMode = 3; // Blood Eclipse
}
self.destroy();
showDifficultySelection();
};
return self;
});
var NPC = Container.expand(function () {
var self = Container.call(this);
var npcGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00ff00
});
var knifeGraphics = self.attachAsset('knife', {
anchorX: 0.5,
anchorY: 1.0,
x: 25,
y: -10
});
// Apply difficulty-based health values
var difficultyHealthValues = [120, 80, 60, 40, 25]; // Easy, Normal, Hard, Nightmare, Impossible
var baseHealth = difficultyHealthValues[selectedDifficulty - 1];
self.health = baseHealth;
self.maxHealth = baseHealth;
// Apply difficulty-based speed values - more difficult = slower NPCs
var difficultySpeedValues = [8, 6, 4, 2, 0.8]; // Easy, Normal, Hard, Nightmare, Impossible (0.1X speed)
self.speed = difficultySpeedValues[selectedDifficulty - 1];
self.ammo = 20; // NPCs start with limited ammo
self.maxAmmo = 25; // Maximum ammo NPCs can carry
self.shootCooldown = 0;
self.meleeRange = 60; // Range for knife attacks
// Apply difficulty-based knife damage - harder difficulty = less knife damage
var difficultyKnifeDamage = [120, 80, 60, 40, 20]; // Easy, Normal, Hard, Nightmare, Impossible
self.meleeDamage = difficultyKnifeDamage[selectedDifficulty - 1];
self.meleeAttackCooldown = 0; // Cooldown for melee attacks
self.lastX = 0;
self.lastY = 0;
self.lastPlayerDistance = 1000;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (self.meleeAttackCooldown > 0) {
self.meleeAttackCooldown--;
}
// Find nearest enemy to move towards
var nearestEnemy = 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 < nearestDistance) {
nearestDistance = distance;
nearestEnemy = enemy;
}
}
// Also consider boss
if (boss) {
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (!nearestEnemy || distance < nearestDistance) {
nearestEnemy = boss;
nearestDistance = distance;
}
}
// Calculate repulsion force from other NPCs to maintain distance
var repulsionX = 0;
var repulsionY = 0;
var minNPCDistance = 80; // Minimum distance to maintain from other NPCs
for (var k = 0; k < npcs.length; k++) {
var otherNPC = npcs[k];
if (otherNPC !== self) {
var dx = self.x - otherNPC.x;
var dy = self.y - otherNPC.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minNPCDistance && distance > 0) {
// Apply repulsion force (stronger when closer)
var repulsionStrength = (minNPCDistance - distance) / minNPCDistance;
repulsionX += dx / distance * repulsionStrength * 3;
repulsionY += dy / distance * repulsionStrength * 3;
}
}
}
// Move towards nearest enemy
if (nearestEnemy && nearestDistance > 100) {
var dx = nearestEnemy.x - self.x;
var dy = nearestEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Combine enemy attraction with NPC repulsion
var moveX = dx / distance * self.speed + repulsionX;
var moveY = dy / distance * self.speed + repulsionY;
self.x += moveX;
self.y += moveY;
}
} else if (repulsionX !== 0 || repulsionY !== 0) {
// If no enemy target, just apply repulsion
self.x += repulsionX;
self.y += repulsionY;
}
// Keep NPC in bounds
if (self.x < 40) self.x = 40;
if (self.x > 2008) self.x = 2008;
if (self.y < 40) self.y = 40;
if (self.y > 2692) self.y = 2692;
// Try melee attack first if enemy is close enough
var meleeTargetFound = false;
if (self.meleeAttackCooldown <= 0) {
// Check for enemies in melee range
for (var m = 0; m < enemies.length; m++) {
var enemy = enemies[m];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.meleeRange) {
// Practice mode: NPCs do no damage
if (selectedGameMode === 4) {
// No damage in practice mode
} else {
enemy.takeDamage(self.meleeDamage);
}
LK.effects.flashObject(self, 0xffffff, 200);
self.meleeAttackCooldown = 45; // Slower than shooting
meleeTargetFound = true;
break;
}
}
// Check boss in melee range
if (!meleeTargetFound && boss) {
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.meleeRange) {
// Practice mode: NPCs do no damage
if (selectedGameMode === 4) {
// No damage in practice mode
} else {
boss.takeDamage(self.meleeDamage);
}
LK.effects.flashObject(self, 0xffffff, 200);
self.meleeAttackCooldown = 45;
meleeTargetFound = true;
}
}
}
// Auto-shoot at enemies if no melee target and have ammo
if (!meleeTargetFound && self.shootCooldown <= 0) {
self.shootAtEnemies();
}
};
self.shootAtEnemies = function () {
// Find nearest enemy
var nearestEnemy = 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 < nearestDistance) {
nearestDistance = distance;
nearestEnemy = enemy;
}
}
// Also consider boss
if (boss) {
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (!nearestEnemy || distance < nearestDistance) {
nearestEnemy = boss;
nearestDistance = distance;
}
}
// Shoot at nearest enemy only if we have ammo
if (nearestEnemy && self.ammo > 0) {
var bullet = new NPCBullet();
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);
if (distance > 0) {
bullet.directionX = dx / distance;
bullet.directionY = dy / distance;
}
npcBullets.push(bullet);
game.addChild(bullet);
self.ammo--; // Consume ammo
self.shootCooldown = 30; // Slower than player
LK.getSound('shoot').play();
}
};
self.addAmmo = function (amount) {
self.ammo = Math.min(self.maxAmmo, self.ammo + amount);
};
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
return true; // NPC died
}
return false;
};
return self;
});
var NPCBullet = Container.expand(function () {
var self = Container.call(this);
// Difficulty-based bullet properties
var difficultyAssets = ['npcBulletEasy', 'npcBulletNormal', 'npcBulletHard', 'npcBulletNightmare', 'npcBulletImpossible'];
var difficultyProps = [{
speed: 10,
damage: 80,
color: 0x00ff88
},
// Easy: slower, less damage
{
speed: 12,
damage: 100,
color: 0x88ff00
},
// Normal: standard
{
speed: 14,
damage: 120,
color: 0xffaa00
},
// Hard: faster, more damage
{
speed: 16,
damage: 140,
color: 0xff4400
},
// Nightmare: fastest, most damage
{
speed: 18,
damage: 160,
color: 0x660066
} // Impossible: insanely fast, devastating damage
];
var props = difficultyProps[selectedDifficulty - 1];
var assetId = difficultyAssets[selectedDifficulty - 1];
var bulletGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = props.speed;
self.damage = props.damage;
self.directionX = 0;
self.directionY = 0;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Remove if out of bounds
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
return true; // Should be removed
}
return false;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.ammo = 30;
self.maxAmmo = 30;
self.speed = 8;
self.shootCooldown = 0;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Keep player in bounds
if (self.x < 40) self.x = 40;
if (self.x > 2008) self.x = 2008;
if (self.y < 40) self.y = 40;
if (self.y > 2692) self.y = 2692;
};
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
self.health = 0;
LK.showGameOver();
}
LK.effects.flashObject(self, 0xff0000, 500);
};
self.heal = function (amount) {
self.health = Math.min(self.maxHealth, self.health + amount);
};
self.addAmmo = function (amount) {
self.ammo = Math.min(self.maxAmmo, self.ammo + amount);
};
self.canShoot = function () {
return self.ammo > 0 && self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
self.ammo--;
self.shootCooldown = 15;
return true;
}
return false;
};
return self;
});
var WeaponsCutscene = Container.expand(function () {
var self = Container.call(this);
// Dark background
var background = self.attachAsset('menuBackground', {
x: 0,
y: 0
});
// Title text
var titleText = new Text2('WEAPONS FOUND', {
size: 100,
fill: 0xff4444,
align: 'center'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 400;
self.addChild(titleText);
// Subtitle
var subtitleText = new Text2('The survivor discovers powerful weapons', {
size: 60,
fill: 0xffffff,
align: 'center'
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 1024;
subtitleText.y = 520;
self.addChild(subtitleText);
// Assault Rifle
var assaultRifle = self.attachAsset('assaultRifle', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 800,
alpha: 0
});
var rifleText = new Text2('ASSAULT RIFLE', {
size: 50,
fill: 0xffff00,
align: 'center'
});
rifleText.anchor.set(0.5, 0.5);
rifleText.x = 1024;
rifleText.y = 880;
rifleText.alpha = 0;
self.addChild(rifleText);
var rifleDesc = new Text2('Rapid fire weapon for long range combat', {
size: 40,
fill: 0xcccccc,
align: 'center'
});
rifleDesc.anchor.set(0.5, 0.5);
rifleDesc.x = 1024;
rifleDesc.y = 930;
rifleDesc.alpha = 0;
self.addChild(rifleDesc);
// Knife
var knife = self.attachAsset('knife', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1200,
alpha: 0
});
var knifeText = new Text2('COMBAT KNIFE', {
size: 50,
fill: 0x00ff00,
align: 'center'
});
knifeText.anchor.set(0.5, 0.5);
knifeText.x = 1024;
knifeText.y = 1280;
knifeText.alpha = 0;
self.addChild(knifeText);
var knifeDesc = new Text2('Silent melee weapon for close combat', {
size: 40,
fill: 0xcccccc,
align: 'center'
});
knifeDesc.anchor.set(0.5, 0.5);
knifeDesc.x = 1024;
knifeDesc.y = 1330;
knifeDesc.alpha = 0;
self.addChild(knifeDesc);
// Bazooka
var bazooka = self.attachAsset('bazooka', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1600,
alpha: 0
});
var bazookaText = new Text2('BAZOOKA', {
size: 50,
fill: 0xff8800,
align: 'center'
});
bazookaText.anchor.set(0.5, 0.5);
bazookaText.x = 1024;
bazookaText.y = 1680;
bazookaText.alpha = 0;
self.addChild(bazookaText);
var bazookaDesc = new Text2('Heavy explosive launcher with area damage', {
size: 40,
fill: 0xcccccc,
align: 'center'
});
bazookaDesc.anchor.set(0.5, 0.5);
bazookaDesc.x = 1024;
bazookaDesc.y = 1730;
bazookaDesc.alpha = 0;
self.addChild(bazookaDesc);
// Continue text
var continueText = new Text2('TAP TO CONTINUE', {
size: 50,
fill: 0xff6666,
align: 'center'
});
continueText.anchor.set(0.5, 0.5);
continueText.x = 1024;
continueText.y = 2200;
continueText.alpha = 0;
self.addChild(continueText);
// Skip button
var skipButton = new Text2('SKIP CUTSCENE', {
size: 60,
fill: 0xffaaaa,
align: 'center'
});
skipButton.anchor.set(1, 0);
skipButton.x = 1950;
skipButton.y = 2500;
skipButton.alpha = 1;
self.addChild(skipButton);
// Animation state
self.animationPhase = 0;
self.animationTimer = 0;
self.canContinue = false;
self.update = function () {
self.animationTimer++;
// Phase 0: Show assault rifle (after 1 second)
if (self.animationPhase === 0 && self.animationTimer > 60) {
self.animationPhase = 1;
tween(assaultRifle, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
tween(rifleText, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
tween(rifleDesc, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
}
// Phase 1: Show knife (after 3 seconds)
if (self.animationPhase === 1 && self.animationTimer > 180) {
self.animationPhase = 2;
tween(knife, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
tween(knifeText, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
tween(knifeDesc, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
}
// Phase 2: Show bazooka (after 5 seconds)
if (self.animationPhase === 2 && self.animationTimer > 300) {
self.animationPhase = 3;
tween(bazooka, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
tween(bazookaText, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
tween(bazookaDesc, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
}
// Phase 3: Show continue text (after 7 seconds)
if (self.animationPhase === 3 && self.animationTimer > 420) {
self.animationPhase = 4;
self.canContinue = true;
// Destroy skip button when continue appears
if (skipButton) {
skipButton.destroy();
skipButton = null;
}
tween(continueText, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut
});
}
// Pulsing continue text
if (self.canContinue) {
continueText.alpha = 0.7 + Math.sin(self.animationTimer * 0.1) * 0.3;
}
};
self.down = function (x, y, obj) {
// Check if skip button was tapped (bottom right area)
if (x > 1700 && x < 2048 && y > 2450 && y < 2550) {
// Destroy skip button before ending cutscene
if (skipButton) {
skipButton.destroy();
skipButton = null;
}
self.destroy();
startGameWithModeAndDifficulty(selectedGameMode, selectedDifficulty);
return;
}
// Check if continue button was tapped (only if can continue and not in skip button area)
if (self.canContinue && !(x > 1700 && x < 2048 && y > 2450 && y < 2550)) {
// Destroy skip button before ending cutscene
if (skipButton) {
skipButton.destroy();
skipButton = null;
}
self.destroy();
startGameWithModeAndDifficulty(selectedGameMode, selectedDifficulty);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0400ff
});
/****
* Game Code
****/
var menuScreen = null;
var modeSelectionScreen = null;
var difficultySelectionScreen = null;
var weaponsCutscene = null;
var gameStarted = false;
var selectedGameMode = 1;
var selectedDifficulty = 2; // 1=easy, 2=normal, 3=hard, 4=nightmare
// Game variables
var player = null;
var enemies = [];
var bullets = [];
var npcBullets = [];
var bazookaBullets = [];
var bazooka = null;
var healthPacks = [];
var ammoBoxes = [];
var boss = null;
var bossSpawned = false;
var bossDied = false;
var npcs = [];
var gameTimer = 0;
var enemySpawnTimer = 0;
var waveNumber = 1;
var enemiesKilled = 0;
var survivalTime = 0;
var eclipsePhase = 0; // 0=normal, 1=blood moon, 2=eclipse
var eclipseTimer = 0;
var usingBazooka = false;
// UI elements
var healthText = null;
var ammoText = null;
var waveText = null;
var timeText = null;
var eclipseText = null;
var bossHealthText = null;
var bazookaInstructionText = null;
function startGame() {
// Show mode selection screen instead of starting game directly
showModeSelection();
}
function showMenu() {
if (!menuScreen) {
menuScreen = new MenuScreen();
game.addChild(menuScreen);
LK.playMusic('menumusic');
}
}
function showModeSelection() {
if (!modeSelectionScreen) {
modeSelectionScreen = new ModeSelectionScreen();
game.addChild(modeSelectionScreen);
LK.playMusic('menumusic');
}
}
function showDifficultySelection() {
if (!difficultySelectionScreen) {
difficultySelectionScreen = new DifficultySelectionScreen();
game.addChild(difficultySelectionScreen);
LK.playMusic('menumusic');
}
}
function showWeaponsCutscene() {
if (!weaponsCutscene) {
weaponsCutscene = new WeaponsCutscene();
game.addChild(weaponsCutscene);
LK.playMusic('cutscenemusic');
}
}
function startGameWithMode(mode) {
selectedGameMode = mode;
gameStarted = true;
initializeGame();
}
function startGameWithModeAndDifficulty(mode, difficulty) {
selectedGameMode = mode;
selectedDifficulty = difficulty;
gameStarted = true;
initializeGame();
}
function initializeGame() {
// Clear game state
enemies = [];
bullets = [];
npcBullets = [];
bazookaBullets = [];
healthPacks = [];
ammoBoxes = [];
npcs = [];
boss = null;
bossSpawned = false;
bossDied = false;
gameTimer = 0;
enemySpawnTimer = 0;
waveNumber = 1;
enemiesKilled = 0;
survivalTime = 0;
eclipsePhase = 0;
eclipseTimer = 0;
// Create player
player = new Player();
player.x = 1024;
player.y = 1366;
game.addChild(player);
// Create bazooka
bazooka = new Bazooka();
bazooka.x = 1024;
bazooka.y = 2000;
game.addChild(bazooka);
// Create UI
healthText = new Text2('Health: 100', {
size: 40,
fill: 0x00ff00
});
healthText.x = 150;
healthText.y = 150;
LK.gui.topLeft.addChild(healthText);
ammoText = new Text2('Ammo: 30', {
size: 40,
fill: 0xffff00
});
ammoText.x = 150;
ammoText.y = 200;
LK.gui.topLeft.addChild(ammoText);
timeText = new Text2('Time: 0:00', {
size: 40,
fill: 0xffffff
});
timeText.anchor.set(1, 0);
timeText.x = -50;
timeText.y = 150;
LK.gui.topRight.addChild(timeText);
if (selectedGameMode === 3) {
eclipseText = new Text2('Phase: Normal', {
size: 40,
fill: 0xff6666
});
eclipseText.anchor.set(1, 0);
eclipseText.x = -50;
eclipseText.y = 200;
LK.gui.topRight.addChild(eclipseText);
}
// Spawn 10 NPCs at game start
for (var i = 0; i < 10; i++) {
spawnNPC();
}
// Create bazooka instruction text
bazookaInstructionText = new Text2('BAZOOKA, CLICK TO LAUNCH', {
size: 50,
fill: 0xffaa00
});
bazookaInstructionText.anchor.set(0.5, 0);
bazookaInstructionText.x = bazooka.x;
bazookaInstructionText.y = bazooka.y + 30;
game.addChild(bazookaInstructionText);
// Create bazooka description text with difficulty-based percentages
var bazookaChances = [{
target: 85,
blank: 12,
explode: 3
},
// Easy
{
target: 75,
blank: 15,
explode: 10
},
// Normal
{
target: 65,
blank: 20,
explode: 15
},
// Hard
{
target: 50,
blank: 25,
explode: 25
},
// Nightmare
{
target: 30,
blank: 30,
explode: 40
} // Impossible
];
var chances = bazookaChances[selectedDifficulty - 1];
var bazookaDescriptionText;
if (selectedGameMode === 4) {
// Practice mode: 100% guaranteed targeting
bazookaDescriptionText = new Text2('100% GUARANTEED TARGET ENEMY', {
size: 40,
fill: 0x00ff00
});
} else {
bazookaDescriptionText = new Text2(chances.target + '% target enemy, ' + chances.blank + '% blank ammo, ' + chances.explode + '% self explode', {
size: 40,
fill: 0xffcc66
});
}
bazookaDescriptionText.anchor.set(0.5, 0);
bazookaDescriptionText.x = bazooka.x;
bazookaDescriptionText.y = bazooka.y + 80;
game.addChild(bazookaDescriptionText);
// Fade out instruction text after 3 seconds
tween(bazookaInstructionText, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
if (bazookaInstructionText) {
bazookaInstructionText.destroy();
bazookaInstructionText = null;
}
}
});
// Fade out description text after 3 seconds
tween(bazookaDescriptionText, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
if (bazookaDescriptionText) {
bazookaDescriptionText.destroy();
bazookaDescriptionText = null;
}
}
});
// Start background music
LK.playMusic('bgmusic');
}
function spawnEnemy() {
var enemy = new Enemy();
// Spawn from random edge
var edge = Math.floor(Math.random() * 4);
switch (edge) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -30;
break;
case 1:
// Right
enemy.x = 2078;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2762;
break;
case 3:
// Left
enemy.x = -30;
enemy.y = Math.random() * 2732;
break;
}
// Apply difficulty-based health, speed and damage values
var difficultyMultipliers = [{
health: 35,
// Easy: 35 health
speed: 0.8,
damage: 0.7
},
// Easy
{
health: 50,
// Normal: 50 health (default)
speed: 1.0,
damage: 1.0
},
// Normal
{
health: 75,
// Hard: 75 health
speed: 1.2,
damage: 1.3
},
// Hard
{
health: 100,
// Nightmare: 100 health
speed: 1.4,
damage: 1.6
},
// Nightmare
{
health: 150,
// Impossible: 150 health
speed: 4.0,
damage: 2.0
} // Impossible: maximum challenge with insane speed
];
var multiplier = difficultyMultipliers[selectedDifficulty - 1];
enemy.health = multiplier.health;
enemy.speed *= multiplier.speed;
enemy.damage = Math.floor(enemy.damage * multiplier.damage);
// Practice mode adjustments - make enemies very weak for learning
if (selectedGameMode === 4) {
enemy.health = 25; // Very low health
enemy.speed *= 0.5; // Half speed
enemy.damage = 5; // Very low damage
}
// Adjust enemy stats based on mode and wave
if (selectedGameMode === 2) {
// Endless Nightmare
enemy.health += waveNumber * 10;
enemy.speed += waveNumber * 0.3;
enemy.damage += waveNumber * 5;
} else if (selectedGameMode === 3) {
// Blood Eclipse
enemy.health += waveNumber * 15;
enemy.speed += waveNumber * 0.5;
enemy.damage += waveNumber * 8;
if (eclipsePhase === 1) {
// Blood moon
enemy.health *= 1.5;
enemy.speed *= 1.3;
} else if (eclipsePhase === 2) {
// Eclipse
enemy.health *= 2;
enemy.speed *= 1.5;
enemy.damage *= 1.5;
}
}
enemies.push(enemy);
game.addChild(enemy);
}
function spawnHealthPack() {
var healthPack = new HealthPack();
healthPack.x = 100 + Math.random() * 1848;
healthPack.y = 100 + Math.random() * 2532;
healthPacks.push(healthPack);
game.addChild(healthPack);
}
function spawnAmmoBox() {
var ammoBox = new AmmoBox();
ammoBox.x = 100 + Math.random() * 1848;
ammoBox.y = 100 + Math.random() * 2532;
ammoBoxes.push(ammoBox);
game.addChild(ammoBox);
}
function spawnNPC() {
var npc = new NPC();
// Spawn near player but not too close
var angle = Math.random() * Math.PI * 2;
var distance = 150 + Math.random() * 100; // 150-250 pixels from player
npc.x = player.x + Math.cos(angle) * distance;
npc.y = player.y + Math.sin(angle) * distance;
// Keep in bounds
if (npc.x < 40) npc.x = 40;
if (npc.x > 2008) npc.x = 2008;
if (npc.y < 40) npc.y = 40;
if (npc.y > 2692) npc.y = 2692;
npcs.push(npc);
game.addChild(npc);
}
function spawnBoss() {
boss = new Boss();
boss.x = 1024;
boss.y = 100;
bossSpawned = true;
game.addChild(boss);
// Create boss health UI
bossHealthText = new Text2('BOSS: 100000 / 100000', {
size: 50,
fill: 0xff0000
});
bossHealthText.anchor.set(0.5, 0);
LK.gui.top.addChild(bossHealthText);
}
function fireBullet(targetX, targetY) {
if (player && player.shoot() && !usingBazooka) {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
// Determine target priority: boss only when present, then tap location
var dx, dy, distance;
if (boss) {
// Target boss when present (ignore enemies completely)
dx = boss.x - player.x;
dy = boss.y - player.y;
} else {
// No boss, use tap location
dx = targetX - player.x;
dy = targetY - player.y;
}
distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
bullet.directionX = dx / distance;
bullet.directionY = dy / distance;
}
bullets.push(bullet);
game.addChild(bullet);
// Play shooting sound
LK.getSound('shoot').play();
}
}
function updateGameMode() {
gameTimer++;
survivalTime = Math.floor(gameTimer / 60);
// Spawn boss at 06:00 (6 minutes) - but not in endless nightmare mode
// Only spawn if boss hasn't been spawned yet, hasn't died yet, and no victory screen is showing
if (survivalTime >= 360 && !bossSpawned && !bossDied && selectedGameMode !== 2 && boss === null) {
spawnBoss();
}
// Mode specific logic
if (selectedGameMode === 1) {
// Classic Survival
// Win condition: survive 5 minutes
if (survivalTime >= 300) {
LK.showYouWin();
return;
}
// Spawn enemies based on time
if (gameTimer % Math.max(30, 120 - survivalTime) === 0 && !bossDied) {
spawnEnemy();
}
// Spawn resources occasionally (adjusted by difficulty)
var resourceMultipliers = [0.7, 1.0, 1.4, 2.0, 3.0]; // Easy, Normal, Hard, Nightmare, Impossible
var resourceDelay = resourceMultipliers[selectedDifficulty - 1];
if (gameTimer % Math.floor(600 * resourceDelay) === 0) spawnHealthPack();
if (gameTimer % Math.floor(900 * resourceDelay) === 0) spawnAmmoBox();
// NPCs already spawned at game start - no additional spawning needed
} else if (selectedGameMode === 2) {
// Endless Nightmare
// New wave every 30 seconds
var newWave = Math.floor(survivalTime / 30) + 1;
if (newWave > waveNumber) {
waveNumber = newWave;
}
// Spawn enemies more frequently as waves progress
var spawnRate = Math.max(10, 60 - waveNumber * 3);
if (gameTimer % spawnRate === 0 && !bossDied) {
spawnEnemy();
}
// Spawn resources based on wave (adjusted by difficulty)
var resourceMultipliers = [0.7, 1.0, 1.4, 2.0, 3.0]; // Easy, Normal, Hard, Nightmare, Impossible
var resourceDelay = resourceMultipliers[selectedDifficulty - 1];
if (gameTimer % Math.floor((600 - waveNumber * 20) * resourceDelay) === 0) spawnHealthPack();
if (gameTimer % Math.floor((900 - waveNumber * 30) * resourceDelay) === 0) spawnAmmoBox();
// NPCs already spawned at game start - no additional spawning needed
} else if (selectedGameMode === 3) {
// Blood Eclipse
eclipseTimer++;
// Eclipse phases: 90s normal, 60s blood moon, 30s eclipse, repeat
var cycleTime = eclipseTimer % (180 * 60); // 3 minutes cycle
if (cycleTime < 90 * 60) {
eclipsePhase = 0; // Normal
} else if (cycleTime < 150 * 60) {
eclipsePhase = 1; // Blood moon
} else {
eclipsePhase = 2; // Eclipse
}
// Spawn rate increases with phase
var spawnRate = [60, 30, 15][eclipsePhase];
if (gameTimer % spawnRate === 0 && !bossDied) {
spawnEnemy();
}
// Resources spawn less during eclipse phases (adjusted by difficulty)
var resourceRate = [600, 800, 1200][eclipsePhase];
var resourceMultipliers = [0.7, 1.0, 1.4, 2.0, 3.0]; // Easy, Normal, Hard, Nightmare, Impossible
var adjustedRate = Math.floor(resourceRate * resourceMultipliers[selectedDifficulty - 1]);
if (gameTimer % adjustedRate === 0) spawnHealthPack();
if (gameTimer % Math.floor(adjustedRate * 1.5) === 0) spawnAmmoBox();
// NPCs already spawned at game start - no additional spawning needed
} else if (selectedGameMode === 4) {
// Practice Mode
// Very slow enemy spawning for learning
if (gameTimer % 300 === 0 && !bossDied) {
// Every 5 seconds
spawnEnemy();
}
// Frequent resource spawning to help learning
if (gameTimer % 300 === 0) spawnHealthPack();
if (gameTimer % 450 === 0) spawnAmmoBox();
// No win condition - practice indefinitely
// No boss spawning in practice mode
}
}
function updateUI() {
if (healthText) {
healthText.setText('Health: ' + player.health);
healthText.fill = player.health > 30 ? 0x00ff00 : 0xff0000;
}
if (ammoText) {
ammoText.setText('Ammo: ' + player.ammo);
ammoText.fill = player.ammo > 5 ? 0xffff00 : 0xff0000;
}
if (timeText) {
var minutes = Math.floor(survivalTime / 60);
var seconds = survivalTime % 60;
timeText.setText('Time: ' + minutes + ':' + (seconds < 10 ? '0' : '') + seconds);
}
if (eclipseText && selectedGameMode === 3) {
var phases = ['Normal', 'Blood Moon', 'Eclipse'];
eclipseText.setText('Phase: ' + phases[eclipsePhase]);
eclipseText.fill = [0xffffff, 0xff6666, 0x660000][eclipsePhase];
}
if (bossHealthText && boss) {
bossHealthText.setText('BOSS: ' + boss.health + ' / ' + boss.maxHealth);
}
}
// Show menu on game start
showMenu();
game.down = function (x, y, obj) {
if (gameStarted && player) {
fireBullet(x, y);
}
};
game.move = function (x, y, obj) {
if (gameStarted && player) {
player.x = x;
player.y = y;
}
};
game.update = function () {
if (!gameStarted) {
return;
}
if (!player) return;
// Update game mode logic
updateGameMode();
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].update();
// Check if enemy should be removed
if (enemies[i].health <= 0) {
enemies[i].destroy();
enemies.splice(i, 1);
enemiesKilled++;
}
}
// Update NPCs
for (var i = npcs.length - 1; i >= 0; i--) {
var npc = npcs[i];
npc.update();
// Check if NPC died
if (npc.health <= 0) {
npc.destroy();
npcs.splice(i, 1);
continue;
}
// Check NPC-enemy collisions (enemies can attack NPCs)
for (var j = 0; j < enemies.length; j++) {
var enemy = enemies[j];
var dx = enemy.x - npc.x;
var dy = enemy.y - npc.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 50 && enemy.attackCooldown <= 0) {
// Practice mode: enemies do no damage to NPCs
if (selectedGameMode === 4) {
// No damage in practice mode
} else {
npc.takeDamage(enemy.damage);
}
enemy.attackCooldown = 60;
break;
}
}
// Check NPC-boss collision
if (boss) {
var dx = boss.x - npc.x;
var dy = boss.y - npc.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100 && boss.attackCooldown <= 0) {
// Practice mode: boss does no damage to NPCs
if (selectedGameMode === 4) {
// No damage in practice mode
} else {
npc.takeDamage(boss.damage);
}
boss.attackCooldown = 60;
}
}
}
// Update boss
if (boss) {
boss.update();
// Check if boss died
if (boss.health <= 0) {
boss.destroy();
boss = null;
bossSpawned = false;
bossDied = true;
// Kill all remaining enemies
for (var k = enemies.length - 1; k >= 0; k--) {
enemies[k].destroy();
enemies.splice(k, 1);
}
if (bossHealthText) {
bossHealthText.destroy();
bossHealthText = null;
}
// Show victory screen for 10 seconds before game over
var victoryText = new Text2('YOU SURVIVED THIRD NIGHT,\nHOPE YOU DIDNT DEAD AT THE FOURTH NIGHT', {
size: 80,
fill: 0x00ff00,
align: 'center'
});
victoryText.anchor.set(0.5, 0.5);
victoryText.x = 1024;
victoryText.y = 1366;
game.addChild(victoryText);
// Fade in the victory text
victoryText.alpha = 0;
tween(victoryText, {
alpha: 1
}, {
duration: 1000
});
// Wait 10 seconds then show game over
tween({}, {}, {
duration: 10000,
onFinish: function onFinish() {
LK.showGameOver();
}
});
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
var shouldRemove = bullet.update();
if (shouldRemove) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
if (bullet.intersects(enemies[j])) {
var enemyDied = enemies[j].takeDamage(bullet.damage);
bullet.destroy();
bullets.splice(i, 1);
if (enemyDied) {
enemies[j].destroy();
enemies.splice(j, 1);
enemiesKilled++;
}
break;
}
}
// Check bullet-boss collision
if (boss && bullet.intersects(boss)) {
var bossKilled = boss.takeDamage(bullet.damage);
bullet.destroy();
bullets.splice(i, 1);
if (bossKilled) {
boss.destroy();
boss = null;
bossSpawned = false;
bossDied = true;
// Kill all remaining enemies
for (var k = enemies.length - 1; k >= 0; k--) {
enemies[k].destroy();
enemies.splice(k, 1);
}
if (bossHealthText) {
bossHealthText.destroy();
bossHealthText = null;
}
// Show victory screen for 10 seconds before game over
var victoryText = new Text2('YOU SURVIVED THIRD NIGHT,\nHOPE YOU DIDNT DEAD AT THE FOURTH NIGHT', {
size: 80,
fill: 0x00ff00,
align: 'center'
});
victoryText.anchor.set(0.5, 0.5);
victoryText.x = 1024;
victoryText.y = 1366;
game.addChild(victoryText);
// Fade in the victory text
victoryText.alpha = 0;
tween(victoryText, {
alpha: 1
}, {
duration: 1000
});
// Wait 10 seconds then show game over
tween({}, {}, {
duration: 10000,
onFinish: function onFinish() {
LK.showGameOver();
}
});
}
break;
}
}
// Update NPC bullets
for (var i = npcBullets.length - 1; i >= 0; i--) {
var bullet = npcBullets[i];
var shouldRemove = bullet.update();
if (shouldRemove) {
bullet.destroy();
npcBullets.splice(i, 1);
continue;
}
// Check NPC bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
if (bullet.intersects(enemies[j])) {
var enemyDied = enemies[j].takeDamage(bullet.damage);
bullet.destroy();
npcBullets.splice(i, 1);
if (enemyDied) {
enemies[j].destroy();
enemies.splice(j, 1);
enemiesKilled++;
}
break;
}
}
// Check NPC bullet-boss collision
if (boss && bullet.intersects(boss)) {
var bossKilled = boss.takeDamage(bullet.damage);
bullet.destroy();
npcBullets.splice(i, 1);
if (bossKilled) {
boss.destroy();
boss = null;
bossSpawned = false;
bossDied = true;
// Kill all remaining enemies
for (var k = enemies.length - 1; k >= 0; k--) {
enemies[k].destroy();
enemies.splice(k, 1);
}
if (bossHealthText) {
bossHealthText.destroy();
bossHealthText = null;
}
// Show victory screen for 10 seconds before game over
var victoryText = new Text2('YOU SURVIVED THIRD NIGHT,\nHOPE YOU DIDNT DEAD AT THE FOURTH NIGHT', {
size: 80,
fill: 0x00ff00,
align: 'center'
});
victoryText.anchor.set(0.5, 0.5);
victoryText.x = 1024;
victoryText.y = 1366;
game.addChild(victoryText);
// Fade in the victory text
victoryText.alpha = 0;
tween(victoryText, {
alpha: 1
}, {
duration: 1000
});
// Wait 10 seconds then show game over
tween({}, {}, {
duration: 10000,
onFinish: function onFinish() {
LK.showGameOver();
}
});
}
break;
}
}
// Update bazooka bullets
for (var i = bazookaBullets.length - 1; i >= 0; i--) {
var bullet = bazookaBullets[i];
var shouldRemove = bullet.update();
if (shouldRemove) {
bullet.explode();
bullet.destroy();
bazookaBullets.splice(i, 1);
continue;
}
// Check bazooka bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
if (bullet.intersects(enemies[j])) {
bullet.explode();
bullet.destroy();
bazookaBullets.splice(i, 1);
break;
}
}
// Check bazooka bullet-boss collision
if (boss && bullet.intersects(boss)) {
bullet.explode();
bullet.destroy();
bazookaBullets.splice(i, 1);
break;
}
}
// Update bazooka
if (bazooka) {
bazooka.update();
}
// Update health packs
for (var i = healthPacks.length - 1; i >= 0; i--) {
var pack = healthPacks[i];
var shouldRemove = pack.update();
if (shouldRemove) {
pack.destroy();
healthPacks.splice(i, 1);
continue;
}
// Check player collision
if (player.intersects(pack)) {
player.heal(pack.healAmount);
LK.getSound('heal').play();
pack.destroy();
healthPacks.splice(i, 1);
}
}
// Update ammo boxes
for (var i = ammoBoxes.length - 1; i >= 0; i--) {
var box = ammoBoxes[i];
var shouldRemove = box.update();
if (shouldRemove) {
box.destroy();
ammoBoxes.splice(i, 1);
continue;
}
var collected = false;
// Check player collision first (priority)
if (player.intersects(box)) {
player.addAmmo(box.ammoAmount);
LK.getSound('ammo').play();
box.destroy();
ammoBoxes.splice(i, 1);
collected = true;
}
// Check NPC collisions if not collected by player and NPC needs ammo
if (!collected) {
for (var k = 0; k < npcs.length; k++) {
var npc = npcs[k];
if (npc.intersects(box) && npc.ammo < npc.maxAmmo - 5) {
// NPCs only take if they really need ammo (leave some for player)
npc.addAmmo(Math.floor(box.ammoAmount * 0.6)); // NPCs get 60% of ammo amount
LK.getSound('ammo').play();
box.destroy();
ammoBoxes.splice(i, 1);
collected = true;
break;
}
}
}
}
// Update UI
updateUI();
};