/****
* 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--;
}
// Prioritize collecting resources when needed
var targetResource = null;
var nearestResourceDistance = Infinity;
var resourcePriority = false; // Flag to indicate high priority resource need
// Check for health packs if health is low (below 70% for more aggressive collection)
if (self.health < self.maxHealth * 0.7) {
resourcePriority = self.health < self.maxHealth * 0.4; // Critical health needs immediate attention
for (var h = 0; h < healthPacks.length; h++) {
var healthPack = healthPacks[h];
var dx = healthPack.x - self.x;
var dy = healthPack.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestResourceDistance) {
nearestResourceDistance = distance;
targetResource = healthPack;
}
}
}
// Check for ammo boxes if ammo is low (below 50% or less than 12 bullets for more aggressive collection)
if (self.ammo < Math.max(12, self.maxAmmo * 0.5)) {
if (!targetResource || self.ammo < Math.max(5, self.maxAmmo * 0.2)) {
resourcePriority = self.ammo < Math.max(5, self.maxAmmo * 0.2); // Critical ammo needs immediate attention
for (var a = 0; a < ammoBoxes.length; a++) {
var ammoBox = ammoBoxes[a];
var dx = ammoBox.x - self.x;
var dy = ammoBox.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestResourceDistance) {
nearestResourceDistance = distance;
targetResource = ammoBox;
}
}
}
}
// 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 target (prioritize resources, then enemies)
var primaryTarget = targetResource || nearestEnemy;
var targetDistance = targetResource ? nearestResourceDistance : nearestDistance;
if (primaryTarget) {
// If targeting a resource, move directly to it (ignore enemy distance check)
// If targeting an enemy, maintain the 100 distance check
var shouldMove = targetResource || nearestEnemy && nearestDistance > 100;
if (shouldMove) {
var dx = primaryTarget.x - self.x;
var dy = primaryTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Move much faster toward resources when needed, even faster when critical
var speedMultiplier = 1.0;
if (targetResource) {
speedMultiplier = resourcePriority ? 2.5 : 2.0; // Critical resources get 2.5x speed, normal resources get 2x speed
}
// Combine target attraction with NPC repulsion
var moveX = dx / distance * self.speed * speedMultiplier + repulsionX;
var moveY = dy / distance * self.speed * speedMultiplier + repulsionY;
self.x += moveX;
self.y += moveY;
}
} else if (repulsionX !== 0 || repulsionY !== 0) {
// If no valid target, just apply repulsion
self.x += repulsionX;
self.y += repulsionY;
}
} else if (repulsionX !== 0 || repulsionY !== 0) {
// If no target at all, 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 first, then nearest enemy, then tap location
var dx, dy, distance;
if (boss) {
// Target boss when present (highest priority)
dx = boss.x - player.x;
dy = boss.y - player.y;
} else {
// Find nearest enemy to target
var nearestEnemy = null;
var nearestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyDx = enemy.x - player.x;
var enemyDy = enemy.y - player.y;
var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
if (enemyDistance < nearestDistance) {
nearestDistance = enemyDistance;
nearestEnemy = enemy;
}
}
if (nearestEnemy) {
// Target nearest enemy
dx = nearestEnemy.x - player.x;
dy = nearestEnemy.y - player.y;
} else {
// No enemies found, 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.showYouWin();
}
});
}
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;
}
var collected = false;
// Check player collision first (priority)
if (player.intersects(pack)) {
player.heal(pack.healAmount);
LK.getSound('heal').play();
pack.destroy();
healthPacks.splice(i, 1);
collected = true;
}
// Check NPC collisions if not collected by player and NPC needs health
if (!collected) {
for (var k = 0; k < npcs.length; k++) {
var npc = npcs[k];
if (npc.intersects(pack) && npc.health < npc.maxHealth * 0.6) {
// NPCs take health when they need it (below 60% health)
npc.health = Math.min(npc.maxHealth, npc.health + Math.floor(pack.healAmount * 0.8)); // NPCs get 80% of heal amount
LK.getSound('heal').play();
pack.destroy();
healthPacks.splice(i, 1);
collected = true;
break;
}
}
}
}
// 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 < Math.max(8, npc.maxAmmo * 0.4)) {
// NPCs take ammo when they really need it (less than 40% or 8 bullets)
npc.addAmmo(Math.floor(box.ammoAmount * 0.7)); // NPCs get 70% 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--;
}
// Prioritize collecting resources when needed
var targetResource = null;
var nearestResourceDistance = Infinity;
var resourcePriority = false; // Flag to indicate high priority resource need
// Check for health packs if health is low (below 70% for more aggressive collection)
if (self.health < self.maxHealth * 0.7) {
resourcePriority = self.health < self.maxHealth * 0.4; // Critical health needs immediate attention
for (var h = 0; h < healthPacks.length; h++) {
var healthPack = healthPacks[h];
var dx = healthPack.x - self.x;
var dy = healthPack.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestResourceDistance) {
nearestResourceDistance = distance;
targetResource = healthPack;
}
}
}
// Check for ammo boxes if ammo is low (below 50% or less than 12 bullets for more aggressive collection)
if (self.ammo < Math.max(12, self.maxAmmo * 0.5)) {
if (!targetResource || self.ammo < Math.max(5, self.maxAmmo * 0.2)) {
resourcePriority = self.ammo < Math.max(5, self.maxAmmo * 0.2); // Critical ammo needs immediate attention
for (var a = 0; a < ammoBoxes.length; a++) {
var ammoBox = ammoBoxes[a];
var dx = ammoBox.x - self.x;
var dy = ammoBox.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestResourceDistance) {
nearestResourceDistance = distance;
targetResource = ammoBox;
}
}
}
}
// 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 target (prioritize resources, then enemies)
var primaryTarget = targetResource || nearestEnemy;
var targetDistance = targetResource ? nearestResourceDistance : nearestDistance;
if (primaryTarget) {
// If targeting a resource, move directly to it (ignore enemy distance check)
// If targeting an enemy, maintain the 100 distance check
var shouldMove = targetResource || nearestEnemy && nearestDistance > 100;
if (shouldMove) {
var dx = primaryTarget.x - self.x;
var dy = primaryTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Move much faster toward resources when needed, even faster when critical
var speedMultiplier = 1.0;
if (targetResource) {
speedMultiplier = resourcePriority ? 2.5 : 2.0; // Critical resources get 2.5x speed, normal resources get 2x speed
}
// Combine target attraction with NPC repulsion
var moveX = dx / distance * self.speed * speedMultiplier + repulsionX;
var moveY = dy / distance * self.speed * speedMultiplier + repulsionY;
self.x += moveX;
self.y += moveY;
}
} else if (repulsionX !== 0 || repulsionY !== 0) {
// If no valid target, just apply repulsion
self.x += repulsionX;
self.y += repulsionY;
}
} else if (repulsionX !== 0 || repulsionY !== 0) {
// If no target at all, 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 first, then nearest enemy, then tap location
var dx, dy, distance;
if (boss) {
// Target boss when present (highest priority)
dx = boss.x - player.x;
dy = boss.y - player.y;
} else {
// Find nearest enemy to target
var nearestEnemy = null;
var nearestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyDx = enemy.x - player.x;
var enemyDy = enemy.y - player.y;
var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
if (enemyDistance < nearestDistance) {
nearestDistance = enemyDistance;
nearestEnemy = enemy;
}
}
if (nearestEnemy) {
// Target nearest enemy
dx = nearestEnemy.x - player.x;
dy = nearestEnemy.y - player.y;
} else {
// No enemies found, 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.showYouWin();
}
});
}
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;
}
var collected = false;
// Check player collision first (priority)
if (player.intersects(pack)) {
player.heal(pack.healAmount);
LK.getSound('heal').play();
pack.destroy();
healthPacks.splice(i, 1);
collected = true;
}
// Check NPC collisions if not collected by player and NPC needs health
if (!collected) {
for (var k = 0; k < npcs.length; k++) {
var npc = npcs[k];
if (npc.intersects(pack) && npc.health < npc.maxHealth * 0.6) {
// NPCs take health when they need it (below 60% health)
npc.health = Math.min(npc.maxHealth, npc.health + Math.floor(pack.healAmount * 0.8)); // NPCs get 80% of heal amount
LK.getSound('heal').play();
pack.destroy();
healthPacks.splice(i, 1);
collected = true;
break;
}
}
}
}
// 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 < Math.max(8, npc.maxAmmo * 0.4)) {
// NPCs take ammo when they really need it (less than 40% or 8 bullets)
npc.addAmmo(Math.floor(box.ammoAmount * 0.7)); // NPCs get 70% of ammo amount
LK.getSound('ammo').play();
box.destroy();
ammoBoxes.splice(i, 1);
collected = true;
break;
}
}
}
}
// Update UI
updateUI();
};