User prompt
make the player and NPC shapes more like human
User prompt
make the player bullet targeting enemy
User prompt
make the bullet focus at enemy
User prompt
when shooting, only one shoot per NPC for shoot at the enemy
User prompt
NPC have 30 ammo for the start of the game
User prompt
reset the NPC ammo value
User prompt
ok testing done, put the boss at 06:00 again
User prompt
prevent enemy spawning after the boss death and kill them if they are still appearing
User prompt
prevent boss respawning after his death (pls)
User prompt
fix the bug that boss appearing after the winning screen
User prompt
after the boss dead make a YOU SURVIVED THIRD NIGHT, HOPE YOU DIDNT DEAD AT THE FOURTH NIGHT screen 10s before game over ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make the boss appear at 00:01 for testing
User prompt
make the gun only focus on boss and dont focus on anything
User prompt
when boss fight, make the gun shoots at boss only (except when there is enemy)
User prompt
appear the boss at 00:30 (note: this is for testing the boss)
User prompt
reset the NPC shooting range to default
User prompt
the bazooka shoot anytime but if there is no enemy the bazooka shoot randomly
User prompt
change the instruction for bazooka in practice mode since its 100% guaranteed
User prompt
bazooka is 100% shoot at enemy in practice mode
User prompt
in practice mode, bazooka guaranteed to shoot at enemy
User prompt
harder the difficulty is more least the NPC knife do
User prompt
NPC can have melee attack using knife
User prompt
in impossible mode, NPC speed is 0.1X speed
User prompt
make the NPC speed that more difficult more slow
User prompt
make the bazooka can shoot anytime without enemy appearing that makes the bazooka goes randomly
/****
* 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 selectedDifficulty = 2; // Default to normal
if (y > 700 && y < 900) {
// Practice mode - set to easy difficulty but with practice mode flag
startGameWithModeAndDifficulty(4, 1); // Practice mode with easy difficulty
self.destroy();
return;
} else if (y > 950 && y < 1150) {
selectedDifficulty = 1; // Easy
} else if (y > 1200 && y < 1400) {
selectedDifficulty = 2; // Normal
} else if (y > 1450 && y < 1650) {
selectedDifficulty = 3; // Hard
} else if (y > 1700 && y < 1900) {
selectedDifficulty = 4; // Nightmare
} else if (y > 1950 && y < 2150) {
selectedDifficulty = 5; // Impossible
}
self.destroy();
startGameWithModeAndDifficulty(selectedGameMode, selectedDifficulty);
};
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 = 30; // 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.shotAtEnemies = []; // Track which enemies this NPC has already shot at
self.lastX = 0;
self.lastY = 0;
self.lastPlayerDistance = 1000;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (self.meleeAttackCooldown > 0) {
self.meleeAttackCooldown--;
}
// Find nearest enemy to move towards
var nearestEnemy = null;
var nearestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestEnemy = enemy;
}
}
// Also consider boss
if (boss) {
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (!nearestEnemy || distance < nearestDistance) {
nearestEnemy = boss;
nearestDistance = distance;
}
}
// Calculate repulsion force from other NPCs to maintain distance
var repulsionX = 0;
var repulsionY = 0;
var minNPCDistance = 80; // Minimum distance to maintain from other NPCs
for (var k = 0; k < npcs.length; k++) {
var otherNPC = npcs[k];
if (otherNPC !== self) {
var dx = self.x - otherNPC.x;
var dy = self.y - otherNPC.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minNPCDistance && distance > 0) {
// Apply repulsion force (stronger when closer)
var repulsionStrength = (minNPCDistance - distance) / minNPCDistance;
repulsionX += dx / distance * repulsionStrength * 3;
repulsionY += dy / distance * repulsionStrength * 3;
}
}
}
// Move towards nearest enemy
if (nearestEnemy && nearestDistance > 100) {
var dx = nearestEnemy.x - self.x;
var dy = nearestEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Combine enemy attraction with NPC repulsion
var moveX = dx / distance * self.speed + repulsionX;
var moveY = dy / distance * self.speed + repulsionY;
self.x += moveX;
self.y += moveY;
}
} else if (repulsionX !== 0 || repulsionY !== 0) {
// If no enemy target, just apply repulsion
self.x += repulsionX;
self.y += repulsionY;
}
// Keep NPC in bounds
if (self.x < 40) self.x = 40;
if (self.x > 2008) self.x = 2008;
if (self.y < 40) self.y = 40;
if (self.y > 2692) self.y = 2692;
// Try melee attack first if enemy is close enough
var meleeTargetFound = false;
if (self.meleeAttackCooldown <= 0) {
// Check for enemies in melee range
for (var m = 0; m < enemies.length; m++) {
var enemy = enemies[m];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.meleeRange) {
// Practice mode: NPCs do no damage
if (selectedGameMode === 4) {
// No damage in practice mode
} else {
enemy.takeDamage(self.meleeDamage);
}
LK.effects.flashObject(self, 0xffffff, 200);
self.meleeAttackCooldown = 45; // Slower than shooting
meleeTargetFound = true;
break;
}
}
// Check boss in melee range
if (!meleeTargetFound && boss) {
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.meleeRange) {
// Practice mode: NPCs do no damage
if (selectedGameMode === 4) {
// No damage in practice mode
} else {
boss.takeDamage(self.meleeDamage);
}
LK.effects.flashObject(self, 0xffffff, 200);
self.meleeAttackCooldown = 45;
meleeTargetFound = true;
}
}
}
// Auto-shoot at enemies if no melee target and have ammo
if (!meleeTargetFound && self.shootCooldown <= 0) {
self.shootAtEnemies();
}
};
self.shootAtEnemies = function () {
// Find nearest enemy that hasn't been shot at yet
var nearestEnemy = null;
var nearestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Check if this NPC has already shot at this enemy
var alreadyShot = false;
for (var s = 0; s < self.shotAtEnemies.length; s++) {
if (self.shotAtEnemies[s] === enemy) {
alreadyShot = true;
break;
}
}
if (!alreadyShot) {
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 not shot at yet
if (boss) {
var alreadyShotAtBoss = false;
for (var s = 0; s < self.shotAtEnemies.length; s++) {
if (self.shotAtEnemies[s] === boss) {
alreadyShotAtBoss = true;
break;
}
}
if (!alreadyShotAtBoss) {
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 and haven't shot at this enemy yet
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
// Mark this enemy as shot at
self.shotAtEnemies.push(nearestEnemy);
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;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0400ff
});
/****
* Game Code
****/
var menuScreen = null;
var modeSelectionScreen = null;
var difficultySelectionScreen = 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 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;
// Reset NPC ammo for existing NPCs (though npcs array is cleared above)
for (var i = 0; i < npcs.length; i++) {
npcs[i].ammo = 20; // Reset to starting ammo value
}
// Create player
player = new Player();
player.x = 1024;
player.y = 1366;
game.addChild(player);
// Create bazooka
bazooka = new Bazooka();
bazooka.x = 1024;
bazooka.y = 2000;
game.addChild(bazooka);
// Create UI
healthText = new Text2('Health: 100', {
size: 40,
fill: 0x00ff00
});
healthText.x = 150;
healthText.y = 150;
LK.gui.topLeft.addChild(healthText);
ammoText = new Text2('Ammo: 30', {
size: 40,
fill: 0xffff00
});
ammoText.x = 150;
ammoText.y = 200;
LK.gui.topLeft.addChild(ammoText);
timeText = new Text2('Time: 0:00', {
size: 40,
fill: 0xffffff
});
timeText.anchor.set(1, 0);
timeText.x = -50;
timeText.y = 150;
LK.gui.topRight.addChild(timeText);
if (selectedGameMode === 3) {
eclipseText = new Text2('Phase: Normal', {
size: 40,
fill: 0xff6666
});
eclipseText.anchor.set(1, 0);
eclipseText.x = -50;
eclipseText.y = 200;
LK.gui.topRight.addChild(eclipseText);
}
// Spawn 10 NPCs at game start
for (var i = 0; i < 10; i++) {
spawnNPC();
}
// Create bazooka instruction text
bazookaInstructionText = new Text2('BAZOOKA, CLICK TO LAUNCH', {
size: 50,
fill: 0xffaa00
});
bazookaInstructionText.anchor.set(0.5, 0);
bazookaInstructionText.x = bazooka.x;
bazookaInstructionText.y = bazooka.y + 30;
game.addChild(bazookaInstructionText);
// Create bazooka description text with difficulty-based percentages
var bazookaChances = [{
target: 85,
blank: 12,
explode: 3
},
// Easy
{
target: 75,
blank: 15,
explode: 10
},
// Normal
{
target: 65,
blank: 20,
explode: 15
},
// Hard
{
target: 50,
blank: 25,
explode: 25
},
// Nightmare
{
target: 30,
blank: 30,
explode: 40
} // Impossible
];
var chances = bazookaChances[selectedDifficulty - 1];
var bazookaDescriptionText;
if (selectedGameMode === 4) {
// Practice mode: 100% guaranteed targeting
bazookaDescriptionText = new Text2('100% GUARANTEED TARGET ENEMY', {
size: 40,
fill: 0x00ff00
});
} else {
bazookaDescriptionText = new Text2(chances.target + '% target enemy, ' + chances.blank + '% blank ammo, ' + chances.explode + '% self explode', {
size: 40,
fill: 0xffcc66
});
}
bazookaDescriptionText.anchor.set(0.5, 0);
bazookaDescriptionText.x = bazooka.x;
bazookaDescriptionText.y = bazooka.y + 80;
game.addChild(bazookaDescriptionText);
// Fade out instruction text after 3 seconds
tween(bazookaInstructionText, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
if (bazookaInstructionText) {
bazookaInstructionText.destroy();
bazookaInstructionText = null;
}
}
});
// Fade out description text after 3 seconds
tween(bazookaDescriptionText, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
if (bazookaDescriptionText) {
bazookaDescriptionText.destroy();
bazookaDescriptionText = null;
}
}
});
// Start background music
LK.playMusic('bgmusic');
}
function spawnEnemy() {
var enemy = new Enemy();
// Spawn from random edge
var edge = Math.floor(Math.random() * 4);
switch (edge) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -30;
break;
case 1:
// Right
enemy.x = 2078;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2762;
break;
case 3:
// Left
enemy.x = -30;
enemy.y = Math.random() * 2732;
break;
}
// Apply difficulty-based health, speed and damage values
var difficultyMultipliers = [{
health: 35,
// Easy: 35 health
speed: 0.8,
damage: 0.7
},
// Easy
{
health: 50,
// Normal: 50 health (default)
speed: 1.0,
damage: 1.0
},
// Normal
{
health: 75,
// Hard: 75 health
speed: 1.2,
damage: 1.3
},
// Hard
{
health: 100,
// Nightmare: 100 health
speed: 1.4,
damage: 1.6
},
// Nightmare
{
health: 150,
// Impossible: 150 health
speed: 4.0,
damage: 2.0
} // Impossible: maximum challenge with insane speed
];
var multiplier = difficultyMultipliers[selectedDifficulty - 1];
enemy.health = multiplier.health;
enemy.speed *= multiplier.speed;
enemy.damage = Math.floor(enemy.damage * multiplier.damage);
// Practice mode adjustments - make enemies very weak for learning
if (selectedGameMode === 4) {
enemy.health = 25; // Very low health
enemy.speed *= 0.5; // Half speed
enemy.damage = 5; // Very low damage
}
// Adjust enemy stats based on mode and wave
if (selectedGameMode === 2) {
// Endless Nightmare
enemy.health += waveNumber * 10;
enemy.speed += waveNumber * 0.3;
enemy.damage += waveNumber * 5;
} else if (selectedGameMode === 3) {
// Blood Eclipse
enemy.health += waveNumber * 15;
enemy.speed += waveNumber * 0.5;
enemy.damage += waveNumber * 8;
if (eclipsePhase === 1) {
// Blood moon
enemy.health *= 1.5;
enemy.speed *= 1.3;
} else if (eclipsePhase === 2) {
// Eclipse
enemy.health *= 2;
enemy.speed *= 1.5;
enemy.damage *= 1.5;
}
}
enemies.push(enemy);
game.addChild(enemy);
}
function spawnHealthPack() {
var healthPack = new HealthPack();
healthPack.x = 100 + Math.random() * 1848;
healthPack.y = 100 + Math.random() * 2532;
healthPacks.push(healthPack);
game.addChild(healthPack);
}
function spawnAmmoBox() {
var ammoBox = new AmmoBox();
ammoBox.x = 100 + Math.random() * 1848;
ammoBox.y = 100 + Math.random() * 2532;
ammoBoxes.push(ammoBox);
game.addChild(ammoBox);
}
function spawnNPC() {
var npc = new NPC();
// Spawn near player but not too close
var angle = Math.random() * Math.PI * 2;
var distance = 150 + Math.random() * 100; // 150-250 pixels from player
npc.x = player.x + Math.cos(angle) * distance;
npc.y = player.y + Math.sin(angle) * distance;
// Keep in bounds
if (npc.x < 40) npc.x = 40;
if (npc.x > 2008) npc.x = 2008;
if (npc.y < 40) npc.y = 40;
if (npc.y > 2692) npc.y = 2692;
npcs.push(npc);
game.addChild(npc);
}
function spawnBoss() {
boss = new Boss();
boss.x = 1024;
boss.y = 100;
bossSpawned = true;
game.addChild(boss);
// Create boss health UI
bossHealthText = new Text2('BOSS: 100000 / 100000', {
size: 50,
fill: 0xff0000
});
bossHealthText.anchor.set(0.5, 0);
LK.gui.top.addChild(bossHealthText);
}
function fireBullet(targetX, targetY) {
if (player && player.shoot() && !usingBazooka) {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
// Determine target priority: boss only when present, then tap location
var dx, dy, distance;
if (boss) {
// Target boss when present (ignore enemies completely)
dx = boss.x - player.x;
dy = boss.y - player.y;
} else {
// No boss, use tap location
dx = targetX - player.x;
dy = targetY - player.y;
}
distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
bullet.directionX = dx / distance;
bullet.directionY = dy / distance;
}
bullets.push(bullet);
game.addChild(bullet);
// Play shooting sound
LK.getSound('shoot').play();
}
}
function updateGameMode() {
gameTimer++;
survivalTime = Math.floor(gameTimer / 60);
// Spawn boss at 06:00 (6 minutes) - but not in endless nightmare mode
// Only spawn if boss hasn't been spawned yet, hasn't died yet, and no victory screen is showing
if (survivalTime >= 360 && !bossSpawned && !bossDied && selectedGameMode !== 2 && boss === null) {
spawnBoss();
}
// Mode specific logic
if (selectedGameMode === 1) {
// Classic Survival
// Win condition: survive 5 minutes
if (survivalTime >= 300) {
LK.showYouWin();
return;
}
// Spawn enemies based on time
if (gameTimer % Math.max(30, 120 - survivalTime) === 0 && !bossDied) {
spawnEnemy();
}
// Spawn resources occasionally (adjusted by difficulty)
var resourceMultipliers = [0.7, 1.0, 1.4, 2.0, 3.0]; // Easy, Normal, Hard, Nightmare, Impossible
var resourceDelay = resourceMultipliers[selectedDifficulty - 1];
if (gameTimer % Math.floor(600 * resourceDelay) === 0) spawnHealthPack();
if (gameTimer % Math.floor(900 * resourceDelay) === 0) spawnAmmoBox();
// NPCs already spawned at game start - no additional spawning needed
} else if (selectedGameMode === 2) {
// Endless Nightmare
// New wave every 30 seconds
var newWave = Math.floor(survivalTime / 30) + 1;
if (newWave > waveNumber) {
waveNumber = newWave;
}
// Spawn enemies more frequently as waves progress
var spawnRate = Math.max(10, 60 - waveNumber * 3);
if (gameTimer % spawnRate === 0 && !bossDied) {
spawnEnemy();
}
// Spawn resources based on wave (adjusted by difficulty)
var resourceMultipliers = [0.7, 1.0, 1.4, 2.0, 3.0]; // Easy, Normal, Hard, Nightmare, Impossible
var resourceDelay = resourceMultipliers[selectedDifficulty - 1];
if (gameTimer % Math.floor((600 - waveNumber * 20) * resourceDelay) === 0) spawnHealthPack();
if (gameTimer % Math.floor((900 - waveNumber * 30) * resourceDelay) === 0) spawnAmmoBox();
// NPCs already spawned at game start - no additional spawning needed
} else if (selectedGameMode === 3) {
// Blood Eclipse
eclipseTimer++;
// Eclipse phases: 90s normal, 60s blood moon, 30s eclipse, repeat
var cycleTime = eclipseTimer % (180 * 60); // 3 minutes cycle
if (cycleTime < 90 * 60) {
eclipsePhase = 0; // Normal
} else if (cycleTime < 150 * 60) {
eclipsePhase = 1; // Blood moon
} else {
eclipsePhase = 2; // Eclipse
}
// Spawn rate increases with phase
var spawnRate = [60, 30, 15][eclipsePhase];
if (gameTimer % spawnRate === 0 && !bossDied) {
spawnEnemy();
}
// Resources spawn less during eclipse phases (adjusted by difficulty)
var resourceRate = [600, 800, 1200][eclipsePhase];
var resourceMultipliers = [0.7, 1.0, 1.4, 2.0, 3.0]; // Easy, Normal, Hard, Nightmare, Impossible
var adjustedRate = Math.floor(resourceRate * resourceMultipliers[selectedDifficulty - 1]);
if (gameTimer % adjustedRate === 0) spawnHealthPack();
if (gameTimer % Math.floor(adjustedRate * 1.5) === 0) spawnAmmoBox();
// NPCs already spawned at game start - no additional spawning needed
} else if (selectedGameMode === 4) {
// Practice Mode
// Very slow enemy spawning for learning
if (gameTimer % 300 === 0 && !bossDied) {
// Every 5 seconds
spawnEnemy();
}
// Frequent resource spawning to help learning
if (gameTimer % 300 === 0) spawnHealthPack();
if (gameTimer % 450 === 0) spawnAmmoBox();
// No win condition - practice indefinitely
// No boss spawning in practice mode
}
}
function updateUI() {
if (healthText) {
healthText.setText('Health: ' + player.health);
healthText.fill = player.health > 30 ? 0x00ff00 : 0xff0000;
}
if (ammoText) {
ammoText.setText('Ammo: ' + player.ammo);
ammoText.fill = player.ammo > 5 ? 0xffff00 : 0xff0000;
}
if (timeText) {
var minutes = Math.floor(survivalTime / 60);
var seconds = survivalTime % 60;
timeText.setText('Time: ' + minutes + ':' + (seconds < 10 ? '0' : '') + seconds);
}
if (eclipseText && selectedGameMode === 3) {
var phases = ['Normal', 'Blood Moon', 'Eclipse'];
eclipseText.setText('Phase: ' + phases[eclipsePhase]);
eclipseText.fill = [0xffffff, 0xff6666, 0x660000][eclipsePhase];
}
if (bossHealthText && boss) {
bossHealthText.setText('BOSS: ' + boss.health + ' / ' + boss.maxHealth);
}
}
// Show menu on game start
showMenu();
game.down = function (x, y, obj) {
if (gameStarted && player) {
fireBullet(x, y);
}
};
game.move = function (x, y, obj) {
if (gameStarted && player) {
player.x = x;
player.y = y;
}
};
game.update = function () {
if (!gameStarted) {
return;
}
if (!player) return;
// Update game mode logic
updateGameMode();
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].update();
// Check if enemy should be removed
if (enemies[i].health <= 0) {
var destroyedEnemy = enemies[i];
// Clean up shot tracking for all NPCs
for (var n = 0; n < npcs.length; n++) {
for (var s = npcs[n].shotAtEnemies.length - 1; s >= 0; s--) {
if (npcs[n].shotAtEnemies[s] === destroyedEnemy) {
npcs[n].shotAtEnemies.splice(s, 1);
}
}
}
destroyedEnemy.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) {
// Clean up shot tracking for boss from all NPCs
for (var n = 0; n < npcs.length; n++) {
for (var s = npcs[n].shotAtEnemies.length - 1; s >= 0; s--) {
if (npcs[n].shotAtEnemies[s] === boss) {
npcs[n].shotAtEnemies.splice(s, 1);
}
}
}
boss.destroy();
boss = null;
bossSpawned = false;
bossDied = true;
// Kill all remaining enemies
for (var k = enemies.length - 1; k >= 0; k--) {
enemies[k].destroy();
enemies.splice(k, 1);
}
if (bossHealthText) {
bossHealthText.destroy();
bossHealthText = null;
}
// Show victory screen for 10 seconds before game over
var victoryText = new Text2('YOU SURVIVED THIRD NIGHT,\nHOPE YOU DIDNT DEAD AT THE FOURTH NIGHT', {
size: 80,
fill: 0x00ff00,
align: 'center'
});
victoryText.anchor.set(0.5, 0.5);
victoryText.x = 1024;
victoryText.y = 1366;
game.addChild(victoryText);
// Fade in the victory text
victoryText.alpha = 0;
tween(victoryText, {
alpha: 1
}, {
duration: 1000
});
// Wait 10 seconds then show game over
tween({}, {}, {
duration: 10000,
onFinish: function onFinish() {
LK.showGameOver();
}
});
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
var shouldRemove = bullet.update();
if (shouldRemove) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
if (bullet.intersects(enemies[j])) {
var enemyDied = enemies[j].takeDamage(bullet.damage);
bullet.destroy();
bullets.splice(i, 1);
if (enemyDied) {
enemies[j].destroy();
enemies.splice(j, 1);
enemiesKilled++;
}
break;
}
}
// Check bullet-boss collision
if (boss && bullet.intersects(boss)) {
var bossKilled = boss.takeDamage(bullet.damage);
bullet.destroy();
bullets.splice(i, 1);
if (bossKilled) {
boss.destroy();
boss = null;
bossSpawned = false;
bossDied = true;
// Kill all remaining enemies
for (var k = enemies.length - 1; k >= 0; k--) {
enemies[k].destroy();
enemies.splice(k, 1);
}
if (bossHealthText) {
bossHealthText.destroy();
bossHealthText = null;
}
// Show victory screen for 10 seconds before game over
var victoryText = new Text2('YOU SURVIVED THIRD NIGHT,\nHOPE YOU DIDNT DEAD AT THE FOURTH NIGHT', {
size: 80,
fill: 0x00ff00,
align: 'center'
});
victoryText.anchor.set(0.5, 0.5);
victoryText.x = 1024;
victoryText.y = 1366;
game.addChild(victoryText);
// Fade in the victory text
victoryText.alpha = 0;
tween(victoryText, {
alpha: 1
}, {
duration: 1000
});
// Wait 10 seconds then show game over
tween({}, {}, {
duration: 10000,
onFinish: function onFinish() {
LK.showGameOver();
}
});
}
break;
}
}
// Update NPC bullets
for (var i = npcBullets.length - 1; i >= 0; i--) {
var bullet = npcBullets[i];
var shouldRemove = bullet.update();
if (shouldRemove) {
bullet.destroy();
npcBullets.splice(i, 1);
continue;
}
// Check NPC bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
if (bullet.intersects(enemies[j])) {
var enemyDied = enemies[j].takeDamage(bullet.damage);
bullet.destroy();
npcBullets.splice(i, 1);
if (enemyDied) {
enemies[j].destroy();
enemies.splice(j, 1);
enemiesKilled++;
}
break;
}
}
// Check NPC bullet-boss collision
if (boss && bullet.intersects(boss)) {
var bossKilled = boss.takeDamage(bullet.damage);
bullet.destroy();
npcBullets.splice(i, 1);
if (bossKilled) {
boss.destroy();
boss = null;
bossSpawned = false;
bossDied = true;
// Kill all remaining enemies
for (var k = enemies.length - 1; k >= 0; k--) {
enemies[k].destroy();
enemies.splice(k, 1);
}
if (bossHealthText) {
bossHealthText.destroy();
bossHealthText = null;
}
// Show victory screen for 10 seconds before game over
var victoryText = new Text2('YOU SURVIVED THIRD NIGHT,\nHOPE YOU DIDNT DEAD AT THE FOURTH NIGHT', {
size: 80,
fill: 0x00ff00,
align: 'center'
});
victoryText.anchor.set(0.5, 0.5);
victoryText.x = 1024;
victoryText.y = 1366;
game.addChild(victoryText);
// Fade in the victory text
victoryText.alpha = 0;
tween(victoryText, {
alpha: 1
}, {
duration: 1000
});
// Wait 10 seconds then show game over
tween({}, {}, {
duration: 10000,
onFinish: function onFinish() {
LK.showGameOver();
}
});
}
break;
}
}
// Update bazooka bullets
for (var i = bazookaBullets.length - 1; i >= 0; i--) {
var bullet = bazookaBullets[i];
var shouldRemove = bullet.update();
if (shouldRemove) {
bullet.explode();
bullet.destroy();
bazookaBullets.splice(i, 1);
continue;
}
// Check bazooka bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
if (bullet.intersects(enemies[j])) {
bullet.explode();
bullet.destroy();
bazookaBullets.splice(i, 1);
break;
}
}
// Check bazooka bullet-boss collision
if (boss && bullet.intersects(boss)) {
bullet.explode();
bullet.destroy();
bazookaBullets.splice(i, 1);
break;
}
}
// Update bazooka
if (bazooka) {
bazooka.update();
}
// Update health packs
for (var i = healthPacks.length - 1; i >= 0; i--) {
var pack = healthPacks[i];
var shouldRemove = pack.update();
if (shouldRemove) {
pack.destroy();
healthPacks.splice(i, 1);
continue;
}
// Check player collision
if (player.intersects(pack)) {
player.heal(pack.healAmount);
LK.getSound('heal').play();
pack.destroy();
healthPacks.splice(i, 1);
}
}
// Update ammo boxes
for (var i = ammoBoxes.length - 1; i >= 0; i--) {
var box = ammoBoxes[i];
var shouldRemove = box.update();
if (shouldRemove) {
box.destroy();
ammoBoxes.splice(i, 1);
continue;
}
var collected = false;
// Check player collision first (priority)
if (player.intersects(box)) {
player.addAmmo(box.ammoAmount);
LK.getSound('ammo').play();
box.destroy();
ammoBoxes.splice(i, 1);
collected = true;
}
// Check NPC collisions if not collected by player and NPC needs ammo
if (!collected) {
for (var k = 0; k < npcs.length; k++) {
var npc = npcs[k];
if (npc.intersects(box) && npc.ammo < npc.maxAmmo - 5) {
// NPCs only take if they really need ammo (leave some for player)
npc.addAmmo(Math.floor(box.ammoAmount * 0.6)); // NPCs get 60% of ammo amount
LK.getSound('ammo').play();
box.destroy();
ammoBoxes.splice(i, 1);
collected = true;
break;
}
}
}
}
// Update UI
updateUI();
}; ===================================================================
--- original.js
+++ change.js
@@ -884,8 +884,9 @@
// 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.shotAtEnemies = []; // Track which enemies this NPC has already shot at
self.lastX = 0;
self.lastY = 0;
self.lastPlayerDistance = 1000;
self.update = function () {
@@ -1005,32 +1006,51 @@
self.shootAtEnemies();
}
};
self.shootAtEnemies = function () {
- // Find nearest enemy
+ // Find nearest enemy that hasn't been shot at yet
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;
+ // Check if this NPC has already shot at this enemy
+ var alreadyShot = false;
+ for (var s = 0; s < self.shotAtEnemies.length; s++) {
+ if (self.shotAtEnemies[s] === enemy) {
+ alreadyShot = true;
+ break;
+ }
}
+ if (!alreadyShot) {
+ 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
+ // Also consider boss if not shot at yet
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;
+ var alreadyShotAtBoss = false;
+ for (var s = 0; s < self.shotAtEnemies.length; s++) {
+ if (self.shotAtEnemies[s] === boss) {
+ alreadyShotAtBoss = true;
+ break;
+ }
}
+ if (!alreadyShotAtBoss) {
+ 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
+ // Shoot at nearest enemy only if we have ammo and haven't shot at this enemy yet
if (nearestEnemy && self.ammo > 0) {
var bullet = new NPCBullet();
bullet.x = self.x;
bullet.y = self.y;
@@ -1044,8 +1064,10 @@
npcBullets.push(bullet);
game.addChild(bullet);
self.ammo--; // Consume ammo
self.shootCooldown = 30; // Slower than player
+ // Mark this enemy as shot at
+ self.shotAtEnemies.push(nearestEnemy);
LK.getSound('shoot').play();
}
};
self.addAmmo = function (amount) {
@@ -1706,9 +1728,18 @@
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();
+ var destroyedEnemy = enemies[i];
+ // Clean up shot tracking for all NPCs
+ for (var n = 0; n < npcs.length; n++) {
+ for (var s = npcs[n].shotAtEnemies.length - 1; s >= 0; s--) {
+ if (npcs[n].shotAtEnemies[s] === destroyedEnemy) {
+ npcs[n].shotAtEnemies.splice(s, 1);
+ }
+ }
+ }
+ destroyedEnemy.destroy();
enemies.splice(i, 1);
enemiesKilled++;
}
}
@@ -1759,8 +1790,16 @@
if (boss) {
boss.update();
// Check if boss died
if (boss.health <= 0) {
+ // Clean up shot tracking for boss from all NPCs
+ for (var n = 0; n < npcs.length; n++) {
+ for (var s = npcs[n].shotAtEnemies.length - 1; s >= 0; s--) {
+ if (npcs[n].shotAtEnemies[s] === boss) {
+ npcs[n].shotAtEnemies.splice(s, 1);
+ }
+ }
+ }
boss.destroy();
boss = null;
bossSpawned = false;
bossDied = true;