/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Best Demon (Ultimate Enemy)
var BestDemon = Container.expand(function () {
var self = Container.call(this);
var bestDemonSprite = self.attachAsset('bestdemon', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bestDemonSprite.width * 0.5;
self.hp = 120;
self.shootCooldown = 0;
self.speed = 2.5;
self.groundSmashTimer = 0;
self.weapons = []; // Array to hold weapon sprites
// Create visual weapons (bestdemongun) for best demon
self.updateWeapons = function () {
// Remove any existing weapons
for (var i = 0; i < self.weapons.length; i++) {
self.weapons[i].destroy();
}
self.weapons = [];
// Create 2 bestdemonguns for best demon (double shot)
for (var i = -0.5; i <= 0.5; i++) {
var weapon = LK.getAsset('bestdemongun', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Position bestdemonguns around best demon
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + i * 0.3;
var radius = self.radius * 1.2;
weapon.x = Math.cos(angle) * radius;
weapon.y = Math.sin(angle) * radius;
weapon.rotation = angle + Math.PI / 2;
self.addChild(weapon);
self.weapons.push(weapon);
}
};
self.update = function () {
// Move toward marine
if (marine && !self.dead) {
var dx = marine.x - self.x;
var dy = marine.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Update weapon positions to match firing direction
if (self.weapons.length === 0) {
self.updateWeapons();
} else {
for (var i = 0; i < self.weapons.length; i++) {
var weapon = self.weapons[i];
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + (i - 0.5) * 0.3;
var radius = self.radius * 1.2;
weapon.x = Math.cos(angle) * radius;
weapon.y = Math.sin(angle) * radius;
weapon.rotation = angle + Math.PI / 2;
}
}
// Update health bar
if (!self.healthBar) {
// Create health bar background
self.healthBarBg = LK.getAsset('marineBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 5,
scaleY: 0.5
});
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar fill
self.healthBar = LK.getAsset('marineBullet', {
anchorX: 0,
anchorY: 0.5,
scaleX: 5,
scaleY: 0.45
});
self.healthBar.tint = 0x00ff00;
self.addChild(self.healthBar);
}
// Position health bar above best demon
self.healthBarBg.x = 0;
self.healthBarBg.y = -self.radius - 30;
self.healthBar.x = -self.radius * 2.5;
self.healthBar.y = -self.radius - 30;
// Update health bar width based on current HP
var healthPercent = Math.max(0, self.hp / 120);
self.healthBar.scaleX = 5 * healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xffff00; // Yellow
} else {
self.healthBar.tint = 0xff0000; // Red
}
// Shoot bestdemonmermi at marine (double shot)
if (self.shootCooldown > 0) self.shootCooldown--;else if (dist < 1500) {
for (var i = -0.5; i <= 0.5; i++) {
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + i * 0.2;
var tx = self.x + Math.cos(angle) * 100;
var ty = self.y + Math.sin(angle) * 100;
spawnBestDemonBullet(self.x, self.y, tx, ty, 12);
}
self.shootCooldown = 40;
}
}
};
self.hit = function () {
self.hp--;
tween(bestDemonSprite, {
tint: 0xffffff
}, {
duration: 60,
onFinish: function onFinish() {
tween(bestDemonSprite, {
tint: 0x8e7602
}, {
duration: 100
});
}
});
if (self.hp <= 0) {
self.dead = true;
// Add points to score - best demons give triple points
var points = doublePointsActive ? 300 : 150;
score += points;
updateUI();
// Fade out best demon
tween(self, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
}
};
return self;
});
// Best Demon Bullet
var BestDemonBullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('bestdemonmermi', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bulletSprite.width * 0.5;
self.speed = 16;
self.dx = 0;
self.dy = 1;
self.update = function () {
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
};
return self;
});
// Boss Demon
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossSprite = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bossSprite.width * 0.5;
self.hp = 80;
self.shootCooldown = 0;
self.speed = 3.5;
self.groundSmashTimer = 0; // Add this property to track smash cooldown
self.weapons = []; // Array to hold weapon sprites
// Create visual weapons (tridents) for boss
self.updateWeapons = function () {
// Remove any existing weapons
for (var i = 0; i < self.weapons.length; i++) {
self.weapons[i].destroy();
}
self.weapons = [];
// Create 3 tridents for boss (triple shot)
for (var i = -1; i <= 1; i++) {
var weapon = LK.getAsset('furca', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
// Position tridents around boss
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + i * 0.18;
var radius = self.radius * 1.1;
weapon.x = Math.cos(angle) * radius;
weapon.y = Math.sin(angle) * radius;
weapon.rotation = angle + Math.PI / 2;
self.addChild(weapon);
self.weapons.push(weapon);
}
};
self.update = function () {
// Move toward marine
if (marine && !self.dead) {
var dx = marine.x - self.x;
var dy = marine.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Update weapon positions to match firing direction
if (self.weapons.length === 0) {
self.updateWeapons();
} else {
for (var i = 0; i < self.weapons.length; i++) {
var weapon = self.weapons[i];
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + (i - 1) * 0.18;
var radius = self.radius * 1.1;
weapon.x = Math.cos(angle) * radius;
weapon.y = Math.sin(angle) * radius;
weapon.rotation = angle + Math.PI / 2;
}
}
// Update health bar
if (!self.healthBar) {
// Create health bar background
self.healthBarBg = LK.getAsset('marineBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 0.4
});
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar fill
self.healthBar = LK.getAsset('marineBullet', {
anchorX: 0,
anchorY: 0.5,
scaleX: 4,
scaleY: 0.35
});
self.healthBar.tint = 0x00ff00;
self.addChild(self.healthBar);
}
// Position health bar above boss
self.healthBarBg.x = 0;
self.healthBarBg.y = -self.radius - 25;
self.healthBar.x = -self.radius * 2;
self.healthBar.y = -self.radius - 25;
// Update health bar width based on current HP
var healthPercent = Math.max(0, self.hp / 80);
self.healthBar.scaleX = 4 * healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xffff00; // Yellow
} else {
self.healthBar.tint = 0xff0000; // Red
}
// Boss ground smash attack every 8 seconds (480 ticks)
if (typeof self.groundSmashTimer === "undefined") self.groundSmashTimer = 0;
self.groundSmashTimer++;
// 3 seconds (180 ticks) before smash, start color warning
if (self.groundSmashTimer === 300) {
// Flash red for 1.5 seconds, then orange for 1.5 seconds
tween(bossSprite, {
tint: 0xff0000
}, {
duration: 90,
onFinish: function onFinish() {
tween(bossSprite, {
tint: 0xffa500
}, {
duration: 90,
onFinish: function onFinish() {
// Do nothing, color will be reset after smash
}
});
}
});
}
if (self.groundSmashTimer >= 480) {
self.groundSmashTimer = 0;
// Visual effect: flash boss and screen
tween(bossSprite, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(bossSprite, {
tint: 0x8e44ad
}, {
duration: 120
});
}
});
LK.effects.flashScreen(0xccccff, 200);
// Damage marine if close to boss (melee range)
if (marine && !marine.dead && marine.invuln === 0) {
var smashDist = bossSprite.width * 1.1;
var mdx = marine.x - self.x;
var mdy = marine.y - self.y;
if (mdx * mdx + mdy * mdy < smashDist * smashDist) {
marine.hp -= 2;
if (marine.hp < 0) marine.hp = 0;
marine.invuln = 60;
marine.flash();
updateUI();
if (marine.hp <= 0) {
// Game over
gameOver = true;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
// Shoot at marine (triple shot)
if (self.shootCooldown > 0) self.shootCooldown--;else if (dist < 1200) {
for (var i = -1; i <= 1; i++) {
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + i * 0.18;
var tx = self.x + Math.cos(angle) * 100;
var ty = self.y + Math.sin(angle) * 100;
spawnDemonBullet(self.x, self.y, tx, ty, 10);
}
self.shootCooldown = 50;
}
}
};
self.hit = function () {
self.hp--;
tween(bossSprite, {
tint: 0xffffff
}, {
duration: 60,
onFinish: function onFinish() {
tween(bossSprite, {
tint: 0x8e44ad
}, {
duration: 100
});
}
});
if (self.hp <= 0) {
self.dead = true;
// Spawn a shield at boss position
var shield = new Shield();
shield.x = self.x;
shield.y = self.y;
game.addChild(shield);
// Add points to score - bosses give double points when health pack effect is active
var points = doublePointsActive ? 100 : 50;
score += points;
updateUI();
// Create visual effect for shield drop
tween(shield, {
y: self.y + 20
}, {
duration: 60,
onFinish: function onFinish() {
tween(shield, {
y: self.y
}, {
duration: 40
});
}
});
// Fade out boss
tween(self, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
}
};
return self;
});
// Demon (Enemy)
var Demon = Container.expand(function () {
var self = Container.call(this);
var demonSprite = self.attachAsset('demon', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = demonSprite.width * 0.5;
self.hp = 4;
self.shootCooldown = 0;
self.speed = 4 + Math.random() * 2;
self.weapon = null; // Weapon sprite
// Create visual weapon (trident) for demon
self.updateWeapon = function () {
// Remove existing weapon if any
if (self.weapon) {
self.weapon.destroy();
}
// Create a furca (trident) for demon
self.weapon = LK.getAsset('furca', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Position trident in front of demon
if (marine) {
var angle = Math.atan2(marine.y - self.y, marine.x - self.x);
var radius = self.radius * 1.1;
self.weapon.x = Math.cos(angle) * radius;
self.weapon.y = Math.sin(angle) * radius;
self.weapon.rotation = angle + Math.PI / 2;
} else {
self.weapon.x = self.radius;
self.weapon.y = 0;
}
self.addChild(self.weapon);
};
self.update = function () {
// Move toward marine
if (marine && !self.dead) {
var dx = marine.x - self.x;
var dy = marine.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Create weapon if it doesn't exist
if (!self.weapon) {
self.updateWeapon();
}
// Update weapon position to match firing direction
if (self.weapon) {
var angle = Math.atan2(marine.y - self.y, marine.x - self.x);
var radius = self.radius * 1.1;
self.weapon.x = Math.cos(angle) * radius;
self.weapon.y = Math.sin(angle) * radius;
self.weapon.rotation = angle + Math.PI / 2;
}
// Update health bar
if (!self.healthBar) {
// Create health bar background
self.healthBarBg = LK.getAsset('marineBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.3
});
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar fill
self.healthBar = LK.getAsset('marineBullet', {
anchorX: 0,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.25
});
self.healthBar.tint = 0x00ff00;
self.addChild(self.healthBar);
}
// Position health bar above demon
self.healthBarBg.x = 0;
self.healthBarBg.y = -self.radius - 15;
self.healthBar.x = -self.radius;
self.healthBar.y = -self.radius - 15;
// Update health bar width based on current HP
var healthPercent = Math.max(0, self.hp / 4);
self.healthBar.scaleX = 2 * healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xffff00; // Yellow
} else {
self.healthBar.tint = 0xff0000; // Red
}
// Shoot at marine
if (self.shootCooldown > 0) self.shootCooldown--;else if (dist < 900) {
spawnDemonBullet(self.x, self.y, marine.x, marine.y);
self.shootCooldown = 90 + Math.floor(Math.random() * 30);
}
}
};
self.hit = function () {
self.hp--;
tween(demonSprite, {
tint: 0xffffff
}, {
duration: 60,
onFinish: function onFinish() {
tween(demonSprite, {
tint: 0xc0392b
}, {
duration: 100
});
}
});
if (self.hp <= 0) {
self.dead = true;
// If the demon has a weapon, fade it out too
if (self.weapon) {
tween(self.weapon, {
alpha: 0
}, {
duration: 200
});
}
tween(self, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
}
};
return self;
});
// Demon Bullet
var DemonBullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('demonBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bulletSprite.width * 0.5;
self.speed = 14;
self.dx = 0;
self.dy = 1;
self.update = function () {
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
};
return self;
});
// FireParticle for hell effect
var FireParticle = Container.expand(function () {
var self = Container.call(this);
var particleSprite = self.attachAsset('fireParticle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1 + Math.random() * 3;
self.lifetime = 60 + Math.random() * 120;
self.initialAlpha = 0.4 + Math.random() * 0.6;
self.alpha = self.initialAlpha;
self.scaleValue = 0.3 + Math.random() * 0.7;
particleSprite.scale.set(self.scaleValue, self.scaleValue);
// Random tint between orange and red
var tintValue = Math.random();
if (tintValue < 0.3) {
particleSprite.tint = 0xFF4500; // Orange-Red
} else if (tintValue < 0.6) {
particleSprite.tint = 0xFF6347; // Tomato
} else if (tintValue < 0.9) {
particleSprite.tint = 0xFF0000; // Red
} else {
particleSprite.tint = 0xFFD700; // Gold
}
self.update = function () {
self.y -= self.speed;
self.lifetime--;
// Fade out as lifetime decreases
self.alpha = self.initialAlpha * (self.lifetime / 180);
if (self.lifetime <= 0) {
self.destroy();
}
};
return self;
});
// Health Pack
var HealthPack = Container.expand(function () {
var self = Container.call(this);
var hpSprite = self.attachAsset('healthPack', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = hpSprite.width * 0.5;
return self;
});
// HellRock for background decoration
var HellRock = Container.expand(function () {
var self = Container.call(this);
var rockSprite = self.attachAsset('hellRock', {
anchorX: 0.5,
anchorY: 0.5
});
// Randomly adjust rock appearance
self.scale.set(0.8 + Math.random() * 0.5, 0.8 + Math.random() * 0.5);
rockSprite.rotation = Math.random() * Math.PI * 2;
// Random dark colors for rocks
var darkColors = [0x3D0C02, 0x2F0A02, 0x3B1001, 0x330000];
rockSprite.tint = darkColors[Math.floor(Math.random() * darkColors.length)];
return self;
});
// Marine (Player)
var Marine = Container.expand(function () {
var self = Container.call(this);
var marineSprite = self.attachAsset('marine', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = marineSprite.width * 0.5;
self.hp = 1;
self.invuln = 0; // invulnerability frames
self.weapons = []; // Array to hold weapon sprites
// Create visual weapons based on bullet count
self.updateWeapons = function (bulletCount) {
// Remove any existing weapons
for (var i = 0; i < self.weapons.length; i++) {
self.weapons[i].destroy();
}
self.weapons = [];
// Create AK47 weapon
var weapon = LK.getAsset('ak47', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Position weapon at marine's side
var radius = self.radius * 1.3;
weapon.x = radius;
weapon.y = 0;
self.addChild(weapon);
self.weapons.push(weapon);
};
// Define findNearestTarget method at the top of the class
self.findNearestTarget = function () {
var minDist = 999999;
var tx = self.x;
var ty = self.y - 200; // Default target ahead of marine
// Check demons first
if (demons && demons.length > 0) {
for (var i = 0; i < demons.length; ++i) {
var d = demons[i];
if (!d.dead) {
var dx = d.x - self.x;
var dy = d.y - self.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
tx = d.x;
ty = d.y;
}
}
}
}
// Check boss
if (boss && !boss.dead) {
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
tx = boss.x;
ty = boss.y;
}
}
// Check best demon
if (bestDemon && !bestDemon.dead) {
var dx = bestDemon.x - self.x;
var dy = bestDemon.y - self.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
tx = bestDemon.x;
ty = bestDemon.y;
}
}
return {
x: tx,
y: ty,
distance: Math.sqrt(minDist)
};
};
self.update = function () {
if (self.invuln > 0) self.invuln--;
// Update weapon positions to match bullet fire positions
if (self.weapons && self.weapons.length > 0) {
// Get the target for bullet direction
var target = self.findNearestTarget();
var tx = target.x;
var ty = target.y;
// Calculate direction
var dx = tx - self.x;
var dy = ty - self.y;
var baseAngle = Math.atan2(dy, dx);
// Position AK47 in firing position
if (self.weapons.length > 0) {
var weapon = self.weapons[0];
// Position AK47 pointing at target
var angle = baseAngle;
// Position weapon at firing direction
var radius = self.radius * 1.1; // Slightly outside marine
weapon.x = Math.cos(angle) * radius;
weapon.y = Math.sin(angle) * radius;
// Rotate weapon to face firing direction
weapon.rotation = angle + Math.PI / 2;
}
}
};
// Flash when hit
self.flash = function () {
tween(marineSprite, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(marineSprite, {
tint: 0x3a9ad9
}, {
duration: 120
});
}
});
};
return self;
});
// Marine Bullet
var MarineBullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('healtbar', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bulletSprite.width * 0.5;
self.speed = 32;
self.dx = 0;
self.dy = -1;
self.update = function () {
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
};
return self;
});
// Shield (Protection)
var Shield = Container.expand(function () {
var self = Container.call(this);
var shieldSprite = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5
});
// Use a different tint for the shield
shieldSprite.tint = 0x3498db;
self.radius = shieldSprite.width * 1.1;
self.duration = 600; // 10 seconds
self.update = function () {
self.duration--;
// Flash purple and green when 5 seconds (300 ticks) remaining
if (self.duration === 300) {
// Start color transition sequence
tween(shieldSprite, {
tint: 0x9b59b6 // Purple
}, {
duration: 150,
onFinish: function onFinish() {
tween(shieldSprite, {
tint: 0x2ecc71 // Green
}, {
duration: 150,
onFinish: function onFinish() {
// Repeat the color cycle until shield expires
var flashInterval = LK.setInterval(function () {
tween(shieldSprite, {
tint: 0x9b59b6 // Purple
}, {
duration: 150,
onFinish: function onFinish() {
tween(shieldSprite, {
tint: 0x2ecc71 // Green
}, {
duration: 150
});
}
});
}, 300);
// Store interval ID on shield for cleanup
self.flashInterval = flashInterval;
}
});
}
});
}
if (self.duration <= 0) {
// Clear flash interval if it exists
if (self.flashInterval) {
LK.clearInterval(self.flashInterval);
}
tween(self, {
alpha: 0
}, {
duration: 120,
onFinish: function onFinish() {
self.destroy();
}
});
}
// Follow the marine
if (marine) {
self.x = marine.x;
self.y = marine.y;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x660000
});
/****
* Game Code
****/
// Hell background elements
// Marine (player)
// Demon (enemy)
// Boss Demon
// Marine bullet
// Demon bullet
// Health pack
// Power-up
var fireParticles = [];
var hellRocks = [];
var particleSpawnTimer = 0;
// Create lava ground background
var lavaGround = LK.getAsset('lavaGround', {
anchorX: 0,
anchorY: 0
});
game.addChild(lavaGround);
// Generate initial rocks for hell landscape
function generateHellLandscape() {
// Create rocks scattered around the arena
for (var i = 0; i < 15; i++) {
var rock = new HellRock();
rock.x = ARENA_LEFT + Math.random() * (ARENA_RIGHT - ARENA_LEFT);
rock.y = ARENA_TOP + Math.random() * (ARENA_BOTTOM - ARENA_TOP);
hellRocks.push(rock);
game.addChild(rock);
}
}
// Arena bounds
var ARENA_MARGIN = 80;
var ARENA_LEFT = ARENA_MARGIN;
var ARENA_TOP = 180;
var ARENA_RIGHT = 2048 - ARENA_MARGIN;
var ARENA_BOTTOM = 2732 - ARENA_MARGIN;
// Game state
var marine = null;
var demons = [];
var boss = null;
var bestDemon = null;
var marineBullets = [];
var demonBullets = [];
var bestDemonBullets = [];
var healthPacks = [];
var powerUps = [];
var shields = [];
var wave = 1;
var waveTimer = 0;
var spawnTimer = 0;
var bossActive = false;
var dragging = false;
var dragOffsetX = 0;
var dragOffsetY = 0;
var lastMoveX = 0;
var lastMoveY = 0;
var score = 0;
var marineFireTimer = 0;
var marineFireRate = 12; // ticks between shots
var marinePowerTimer = 0;
var marineMaxHP = 1;
var gameOver = false;
var doublePointsActive = false;
var doublePointsTimer = 0;
var doublePointsDuration = 300; // 5 seconds at 60 FPS
var doublePointsIndicator = null; // Visual indicator for double points
// UI
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var hpTxt = new Text2('♥♥♥', {
size: 90,
fill: 0xE74C3C
});
hpTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(hpTxt);
hpTxt.y = 120;
hpTxt.x = 2048 / 2;
// Timer display in top right corner
var gameStartTime = 0;
var timerTxt = new Text2('00:00', {
size: 80,
fill: 0xFFFFFF
});
timerTxt.anchor.set(1, 0); // Right align
LK.gui.topRight.addChild(timerTxt);
timerTxt.x = -20; // Small margin from right edge
timerTxt.y = 20; // Small margin from top
// Center arena
function centerArenaX() {
return (ARENA_LEFT + ARENA_RIGHT) / 2;
}
function centerArenaY() {
return (ARENA_TOP + ARENA_BOTTOM) / 2;
}
// Spawn marine
function spawnMarine() {
marine = new Marine();
marine.x = centerArenaX();
marine.y = ARENA_BOTTOM - 220;
game.addChild(marine);
}
// Spawn demon
function spawnDemon() {
var d = new Demon();
// Spawn at random edge
var edge = Math.floor(Math.random() * 4);
if (edge === 0) {
// top
d.x = ARENA_LEFT + Math.random() * (ARENA_RIGHT - ARENA_LEFT);
d.y = ARENA_TOP + 40;
} else if (edge === 1) {
// bottom
d.x = ARENA_LEFT + Math.random() * (ARENA_RIGHT - ARENA_LEFT);
d.y = ARENA_BOTTOM - 40;
} else if (edge === 2) {
// left
d.x = ARENA_LEFT + 40;
d.y = ARENA_TOP + Math.random() * (ARENA_BOTTOM - ARENA_TOP);
} else {
// right
d.x = ARENA_RIGHT - 40;
d.y = ARENA_TOP + Math.random() * (ARENA_BOTTOM - ARENA_TOP);
}
demons.push(d);
game.addChild(d);
// Initialize weapon for the demon
d.updateWeapon();
}
// Spawn boss
function spawnBoss() {
boss = new Boss();
boss.x = centerArenaX();
boss.y = ARENA_TOP + 200;
game.addChild(boss);
bossActive = true;
// Initialize weapons for the boss
boss.updateWeapons();
}
// Spawn best demon
function spawnBestDemon() {
bestDemon = new BestDemon();
bestDemon.x = centerArenaX();
bestDemon.y = ARENA_TOP + 150;
game.addChild(bestDemon);
// Initialize weapons for the best demon
bestDemon.updateWeapons();
}
// Spawn marine bullet
function spawnMarineBullet(x, y, dx, dy) {
var b = new MarineBullet();
b.x = x;
b.y = y;
var mag = Math.sqrt(dx * dx + dy * dy);
b.dx = dx / mag;
b.dy = dy / mag;
marineBullets.push(b);
game.addChild(b);
}
// Spawn demon bullet
function spawnDemonBullet(x, y, tx, ty, speed) {
var b = new DemonBullet();
b.x = x;
b.y = y;
var dx = tx - x;
var dy = ty - y;
var mag = Math.sqrt(dx * dx + dy * dy);
b.dx = dx / mag;
b.dy = dy / mag;
if (speed) b.speed = speed;
demonBullets.push(b);
game.addChild(b);
}
// Spawn best demon bullet
function spawnBestDemonBullet(x, y, tx, ty, speed) {
var b = new BestDemonBullet();
b.x = x;
b.y = y;
var dx = tx - x;
var dy = ty - y;
var mag = Math.sqrt(dx * dx + dy * dy);
b.dx = dx / mag;
b.dy = dy / mag;
if (speed) b.speed = speed;
bestDemonBullets.push(b);
game.addChild(b);
}
// Spawn health pack
function spawnHealthPack() {
var hp = new HealthPack();
hp.x = ARENA_LEFT + 100 + Math.random() * (ARENA_RIGHT - ARENA_LEFT - 200);
hp.y = ARENA_TOP + 100 + Math.random() * (ARENA_BOTTOM - ARENA_TOP - 200);
healthPacks.push(hp);
game.addChild(hp);
}
// Power-ups have been removed
function spawnPowerUp() {
// Function kept for compatibility but does nothing
}
// Update UI
function updateUI() {
scoreTxt.setText(score);
var hpStr = '';
for (var i = 0; i < marineMaxHP; ++i) {
hpStr += i < marine.hp ? '♥' : '♡';
}
hpTxt.setText(hpStr);
}
// Start game
function startGame() {
score = 0;
wave = 1;
waveTimer = 0;
spawnTimer = 0;
bossActive = false;
marineFireTimer = 0;
gameOver = false;
doublePointsActive = false;
doublePointsTimer = 0;
// Clean up double points indicator
if (doublePointsIndicator) {
doublePointsIndicator.destroy();
doublePointsIndicator = null;
}
gameStartTime = LK.ticks; // Record game start time
// Remove all
for (var i = 0; i < demons.length; ++i) demons[i].destroy();
for (var i = 0; i < marineBullets.length; ++i) marineBullets[i].destroy();
for (var i = 0; i < demonBullets.length; ++i) demonBullets[i].destroy();
for (var i = 0; i < bestDemonBullets.length; ++i) bestDemonBullets[i].destroy();
for (var i = 0; i < healthPacks.length; ++i) healthPacks[i].destroy();
// Remove fire particles and rocks
for (var i = 0; i < fireParticles.length; ++i) fireParticles[i].destroy();
for (var i = 0; i < hellRocks.length; ++i) hellRocks[i].destroy();
fireParticles = [];
hellRocks = [];
// Remove any active shields
for (var i = 0; i < game.children.length; i++) {
if (game.children[i] instanceof Shield) {
game.children[i].destroy();
}
}
if (marine) marine.destroy();
if (boss) boss.destroy();
if (bestDemon) bestDemon.destroy();
demons = [];
marineBullets = [];
demonBullets = [];
bestDemonBullets = [];
healthPacks = [];
shields = [];
boss = null;
bestDemon = null;
spawnMarine();
// Initialize marine with 1 weapon
if (marine) {
marine.updateWeapons(1);
}
updateUI();
}
// Generate initial hell landscape
generateHellLandscape();
startGame();
// Touch controls
game.down = function (x, y, obj) {
if (gameOver) return;
// Only drag if touch is inside marine
var dx = x - marine.x;
var dy = y - marine.y;
if (dx * dx + dy * dy < marine.radius * marine.radius * 1.2) {
dragging = true;
dragOffsetX = marine.x - x;
dragOffsetY = marine.y - y;
lastMoveX = x;
lastMoveY = y;
}
};
game.up = function (x, y, obj) {
dragging = false;
};
game.move = function (x, y, obj) {
if (gameOver) return;
// Always follow finger/mouse, clamp to arena
if (marine) {
var nx = x;
var ny = y;
nx = Math.max(ARENA_LEFT + marine.radius, Math.min(ARENA_RIGHT - marine.radius, nx));
ny = Math.max(ARENA_TOP + marine.radius, Math.min(ARENA_BOTTOM - marine.radius, ny));
marine.x = nx;
marine.y = ny;
lastMoveX = x;
lastMoveY = y;
}
};
// Main update loop
game.update = function () {
if (gameOver) return;
// Update timer display
var elapsedTicks = LK.ticks - gameStartTime;
var elapsedSeconds = Math.floor(elapsedTicks / 60);
var minutes = Math.floor(elapsedSeconds / 60);
var seconds = elapsedSeconds % 60;
var timeStr = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText(timeStr);
// Update fire particles (hellish effect)
particleSpawnTimer--;
if (particleSpawnTimer <= 0) {
// Spawn new fire particles
for (var i = 0; i < 3; i++) {
var particle = new FireParticle();
particle.x = ARENA_LEFT + Math.random() * (ARENA_RIGHT - ARENA_LEFT);
particle.y = ARENA_BOTTOM - Math.random() * 100;
fireParticles.push(particle);
game.addChild(particle);
}
particleSpawnTimer = 2 + Math.floor(Math.random() * 3);
}
// Update existing fire particles
for (var i = fireParticles.length - 1; i >= 0; i--) {
fireParticles[i].update();
if (fireParticles[i].lifetime <= 0) {
fireParticles.splice(i, 1);
}
}
// Update marine
if (marine) marine.update();
// Update double points timer
if (doublePointsActive) {
// Create indicator if it doesn't exist
if (!doublePointsIndicator) {
doublePointsIndicator = LK.getAsset('2xpo', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
game.addChild(doublePointsIndicator);
}
// Position indicator next to marine
if (marine && doublePointsIndicator) {
doublePointsIndicator.x = marine.x + marine.radius * 1.5;
doublePointsIndicator.y = marine.y - marine.radius * 0.5;
}
doublePointsTimer--;
if (doublePointsTimer <= 0) {
doublePointsActive = false;
doublePointsTimer = 0; // Reset timer to ensure clean state
// Remove indicator when effect ends
if (doublePointsIndicator) {
doublePointsIndicator.destroy();
doublePointsIndicator = null;
}
}
} else {
// Ensure indicator is removed when double points is not active
if (doublePointsIndicator) {
doublePointsIndicator.destroy();
doublePointsIndicator = null;
}
}
// Update demons
for (var i = demons.length - 1; i >= 0; --i) {
var d = demons[i];
d.update();
if (d.dead) {
demons.splice(i, 1);
var points = doublePointsActive ? 20 : 10; // Double points if active
score += points;
updateUI();
// Chance to drop health
if (Math.random() < 0.08) spawnHealthPack();
}
}
// Update boss
if (boss) {
boss.update();
if (boss.dead) {
bossActive = false;
boss = null;
// Score is now added in the boss.hit method
wave++;
waveTimer = 0;
}
// Marine takes damage when touching boss
if (marine && !marine.dead && marine.invuln === 0 && !boss.dead) {
var dx = marine.x - boss.x;
var dy = marine.y - boss.y;
var r = marine.radius + boss.radius;
if (dx * dx + dy * dy < r * r) {
marine.hp--;
if (marine.hp < 0) marine.hp = 0;
marine.invuln = 60;
marine.flash();
updateUI();
if (marine.hp <= 0) {
gameOver = true;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
// Update best demon
if (bestDemon) {
bestDemon.update();
if (bestDemon.dead) {
bestDemon = null;
wave++;
waveTimer = 0;
}
// Marine takes damage when touching best demon
if (marine && !marine.dead && marine.invuln === 0 && !bestDemon.dead) {
var dx = marine.x - bestDemon.x;
var dy = marine.y - bestDemon.y;
var r = marine.radius + bestDemon.radius;
if (dx * dx + dy * dy < r * r) {
marine.hp--;
if (marine.hp < 0) marine.hp = 0;
marine.invuln = 60;
marine.flash();
updateUI();
if (marine.hp <= 0) {
gameOver = true;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
// Spawn best demon when score reaches 500 or more
if (!bestDemon && score >= 500) {
spawnBestDemon();
}
// Update marine bullets
for (var i = marineBullets.length - 1; i >= 0; --i) {
var b = marineBullets[i];
b.update();
// Remove if out of bounds
if (b.x < ARENA_LEFT - 60 || b.x > ARENA_RIGHT + 60 || b.y < ARENA_TOP - 60 || b.y > ARENA_BOTTOM + 60) {
b.destroy();
marineBullets.splice(i, 1);
continue;
}
// Hit demons
var hit = false;
for (var j = demons.length - 1; j >= 0; --j) {
var d = demons[j];
var dx = b.x - d.x;
var dy = b.y - d.y;
var r = b.radius + d.radius;
if (dx * dx + dy * dy < r * r) {
d.hit();
hit = true;
break;
}
}
// Hit boss
if (!hit && boss) {
var dx = b.x - boss.x;
var dy = b.y - boss.y;
var r = b.radius + boss.radius;
if (dx * dx + dy * dy < r * r) {
boss.hit();
hit = true;
}
}
// Hit best demon
if (!hit && bestDemon) {
var dx = b.x - bestDemon.x;
var dy = b.y - bestDemon.y;
var r = b.radius + bestDemon.radius;
if (dx * dx + dy * dy < r * r) {
bestDemon.hit();
hit = true;
}
}
if (hit) {
b.destroy();
marineBullets.splice(i, 1);
}
}
// Update demon bullets
for (var i = demonBullets.length - 1; i >= 0; --i) {
var b = demonBullets[i];
b.update();
// Remove if out of bounds
if (b.x < ARENA_LEFT - 60 || b.x > ARENA_RIGHT + 60 || b.y < ARENA_TOP - 60 || b.y > ARENA_BOTTOM + 60) {
b.destroy();
demonBullets.splice(i, 1);
continue;
}
// Hit marine
if (marine && marine.invuln === 0) {
var dx = b.x - marine.x;
var dy = b.y - marine.y;
var r = b.radius + marine.radius;
if (dx * dx + dy * dy < r * r) {
b.destroy();
demonBullets.splice(i, 1);
// Check if a shield is protecting the marine
var hasShield = false;
for (var j = 0; j < game.children.length; j++) {
if (game.children[j] instanceof Shield) {
hasShield = true;
// Destroy shield immediately when hit
if (game.children[j].flashInterval) {
LK.clearInterval(game.children[j].flashInterval);
}
// Flash shield effect and destroy it
tween(game.children[j], {
alpha: 0
}, {
duration: 120,
onFinish: function onFinish(obj) {
obj.destroy();
}
});
break;
}
}
if (!hasShield) {
marine.hp--;
marine.invuln = 60;
marine.flash();
updateUI();
if (marine.hp <= 0) {
// Game over
gameOver = true;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
}
// Update best demon bullets
for (var i = bestDemonBullets.length - 1; i >= 0; --i) {
var b = bestDemonBullets[i];
b.update();
// Remove if out of bounds
if (b.x < ARENA_LEFT - 60 || b.x > ARENA_RIGHT + 60 || b.y < ARENA_TOP - 60 || b.y > ARENA_BOTTOM + 60) {
b.destroy();
bestDemonBullets.splice(i, 1);
continue;
}
// Hit marine
if (marine && marine.invuln === 0) {
var dx = b.x - marine.x;
var dy = b.y - marine.y;
var r = b.radius + marine.radius;
if (dx * dx + dy * dy < r * r) {
b.destroy();
bestDemonBullets.splice(i, 1);
// Check if a shield is protecting the marine
var hasShield = false;
for (var j = 0; j < game.children.length; j++) {
if (game.children[j] instanceof Shield) {
hasShield = true;
// Destroy shield immediately when hit
if (game.children[j].flashInterval) {
LK.clearInterval(game.children[j].flashInterval);
}
// Flash shield effect and destroy it
tween(game.children[j], {
alpha: 0
}, {
duration: 120,
onFinish: function onFinish(obj) {
obj.destroy();
}
});
break;
}
}
if (!hasShield) {
marine.hp--;
marine.invuln = 60;
marine.flash();
updateUI();
if (marine.hp <= 0) {
// Game over
gameOver = true;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
}
// Update health packs
for (var i = healthPacks.length - 1; i >= 0; --i) {
var hp = healthPacks[i];
if (marine) {
var dx = hp.x - marine.x;
var dy = hp.y - marine.y;
var r = hp.radius + marine.radius;
if (dx * dx + dy * dy < r * r) {
if (marine.hp < marineMaxHP) {
marine.hp++;
}
// Activate double points for 10 seconds
doublePointsActive = true;
doublePointsTimer = doublePointsDuration;
updateUI();
hp.destroy();
healthPacks.splice(i, 1);
}
}
}
// Power-ups have been removed
// Marine auto-fire
if (marine) {
marineFireTimer--;
if (marineFireTimer <= 0) {
marineFireTimer = marineFireRate;
// Auto-targeting: find nearest enemy
var target = marine.findNearestTarget();
var tx = target.x;
var ty = target.y;
// Fire
// Calculate bullets based on score - 1 bullet base + 1 extra per 500 points
var totalBullets = 1 + Math.floor(score / 500);
// Make sure marine has an AK47
if (marine.weapons.length === 0) {
marine.updateWeapons(1);
}
var dx = tx - marine.x;
var dy = ty - marine.y;
var baseAngle = Math.atan2(dy, dx);
for (var n = 0; n < totalBullets; ++n) {
// Spread out extra bullets a bit
var offset = (n - (totalBullets - 1) / 2) * 0.12;
var angle = baseAngle + offset;
var ddx = Math.cos(angle);
var ddy = Math.sin(angle);
spawnMarineBullet(marine.x, marine.y, ddx, ddy, false);
}
}
}
// Wave logic
if (!bossActive) {
// Spawn demons
if (demons.length < Math.min(3 + wave, 8)) {
spawnTimer--;
if (spawnTimer <= 0) {
spawnDemon();
spawnTimer = 120 + Math.floor(Math.random() * 60); // Increased timer for slower demon spawning
}
}
// Next wave
if (demons.length === 0 && !boss && waveTimer > 60) {
if (score >= 100 && score < 490 && Math.floor(score / 100) > Math.floor((score - (doublePointsActive ? 20 : 10)) / 100)) {
spawnBoss();
} else {
wave++;
waveTimer = 0;
}
}
waveTimer++;
} else {
// Wait for boss defeat
if (boss && boss.dead) {
bossActive = false;
boss = null;
wave++;
waveTimer = 0;
}
}
// Randomly spawn health packs
if (LK.ticks % 420 === 0 && Math.random() < 0.5) {
spawnHealthPack();
}
// Pulsate lava ground for hellish effect
if (LK.ticks % 120 === 0) {
var targetTint = Math.random() < 0.5 ? 0x990000 : 0x660000;
tween(lavaGround, {
tint: targetTint
}, {
duration: 1000,
easing: tween.easeInOut
});
}
};
// Reset game on game over
LK.on('gameover', function () {
startGame();
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Best Demon (Ultimate Enemy)
var BestDemon = Container.expand(function () {
var self = Container.call(this);
var bestDemonSprite = self.attachAsset('bestdemon', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bestDemonSprite.width * 0.5;
self.hp = 120;
self.shootCooldown = 0;
self.speed = 2.5;
self.groundSmashTimer = 0;
self.weapons = []; // Array to hold weapon sprites
// Create visual weapons (bestdemongun) for best demon
self.updateWeapons = function () {
// Remove any existing weapons
for (var i = 0; i < self.weapons.length; i++) {
self.weapons[i].destroy();
}
self.weapons = [];
// Create 2 bestdemonguns for best demon (double shot)
for (var i = -0.5; i <= 0.5; i++) {
var weapon = LK.getAsset('bestdemongun', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Position bestdemonguns around best demon
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + i * 0.3;
var radius = self.radius * 1.2;
weapon.x = Math.cos(angle) * radius;
weapon.y = Math.sin(angle) * radius;
weapon.rotation = angle + Math.PI / 2;
self.addChild(weapon);
self.weapons.push(weapon);
}
};
self.update = function () {
// Move toward marine
if (marine && !self.dead) {
var dx = marine.x - self.x;
var dy = marine.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Update weapon positions to match firing direction
if (self.weapons.length === 0) {
self.updateWeapons();
} else {
for (var i = 0; i < self.weapons.length; i++) {
var weapon = self.weapons[i];
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + (i - 0.5) * 0.3;
var radius = self.radius * 1.2;
weapon.x = Math.cos(angle) * radius;
weapon.y = Math.sin(angle) * radius;
weapon.rotation = angle + Math.PI / 2;
}
}
// Update health bar
if (!self.healthBar) {
// Create health bar background
self.healthBarBg = LK.getAsset('marineBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 5,
scaleY: 0.5
});
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar fill
self.healthBar = LK.getAsset('marineBullet', {
anchorX: 0,
anchorY: 0.5,
scaleX: 5,
scaleY: 0.45
});
self.healthBar.tint = 0x00ff00;
self.addChild(self.healthBar);
}
// Position health bar above best demon
self.healthBarBg.x = 0;
self.healthBarBg.y = -self.radius - 30;
self.healthBar.x = -self.radius * 2.5;
self.healthBar.y = -self.radius - 30;
// Update health bar width based on current HP
var healthPercent = Math.max(0, self.hp / 120);
self.healthBar.scaleX = 5 * healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xffff00; // Yellow
} else {
self.healthBar.tint = 0xff0000; // Red
}
// Shoot bestdemonmermi at marine (double shot)
if (self.shootCooldown > 0) self.shootCooldown--;else if (dist < 1500) {
for (var i = -0.5; i <= 0.5; i++) {
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + i * 0.2;
var tx = self.x + Math.cos(angle) * 100;
var ty = self.y + Math.sin(angle) * 100;
spawnBestDemonBullet(self.x, self.y, tx, ty, 12);
}
self.shootCooldown = 40;
}
}
};
self.hit = function () {
self.hp--;
tween(bestDemonSprite, {
tint: 0xffffff
}, {
duration: 60,
onFinish: function onFinish() {
tween(bestDemonSprite, {
tint: 0x8e7602
}, {
duration: 100
});
}
});
if (self.hp <= 0) {
self.dead = true;
// Add points to score - best demons give triple points
var points = doublePointsActive ? 300 : 150;
score += points;
updateUI();
// Fade out best demon
tween(self, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
}
};
return self;
});
// Best Demon Bullet
var BestDemonBullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('bestdemonmermi', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bulletSprite.width * 0.5;
self.speed = 16;
self.dx = 0;
self.dy = 1;
self.update = function () {
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
};
return self;
});
// Boss Demon
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossSprite = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bossSprite.width * 0.5;
self.hp = 80;
self.shootCooldown = 0;
self.speed = 3.5;
self.groundSmashTimer = 0; // Add this property to track smash cooldown
self.weapons = []; // Array to hold weapon sprites
// Create visual weapons (tridents) for boss
self.updateWeapons = function () {
// Remove any existing weapons
for (var i = 0; i < self.weapons.length; i++) {
self.weapons[i].destroy();
}
self.weapons = [];
// Create 3 tridents for boss (triple shot)
for (var i = -1; i <= 1; i++) {
var weapon = LK.getAsset('furca', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
// Position tridents around boss
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + i * 0.18;
var radius = self.radius * 1.1;
weapon.x = Math.cos(angle) * radius;
weapon.y = Math.sin(angle) * radius;
weapon.rotation = angle + Math.PI / 2;
self.addChild(weapon);
self.weapons.push(weapon);
}
};
self.update = function () {
// Move toward marine
if (marine && !self.dead) {
var dx = marine.x - self.x;
var dy = marine.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Update weapon positions to match firing direction
if (self.weapons.length === 0) {
self.updateWeapons();
} else {
for (var i = 0; i < self.weapons.length; i++) {
var weapon = self.weapons[i];
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + (i - 1) * 0.18;
var radius = self.radius * 1.1;
weapon.x = Math.cos(angle) * radius;
weapon.y = Math.sin(angle) * radius;
weapon.rotation = angle + Math.PI / 2;
}
}
// Update health bar
if (!self.healthBar) {
// Create health bar background
self.healthBarBg = LK.getAsset('marineBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 0.4
});
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar fill
self.healthBar = LK.getAsset('marineBullet', {
anchorX: 0,
anchorY: 0.5,
scaleX: 4,
scaleY: 0.35
});
self.healthBar.tint = 0x00ff00;
self.addChild(self.healthBar);
}
// Position health bar above boss
self.healthBarBg.x = 0;
self.healthBarBg.y = -self.radius - 25;
self.healthBar.x = -self.radius * 2;
self.healthBar.y = -self.radius - 25;
// Update health bar width based on current HP
var healthPercent = Math.max(0, self.hp / 80);
self.healthBar.scaleX = 4 * healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xffff00; // Yellow
} else {
self.healthBar.tint = 0xff0000; // Red
}
// Boss ground smash attack every 8 seconds (480 ticks)
if (typeof self.groundSmashTimer === "undefined") self.groundSmashTimer = 0;
self.groundSmashTimer++;
// 3 seconds (180 ticks) before smash, start color warning
if (self.groundSmashTimer === 300) {
// Flash red for 1.5 seconds, then orange for 1.5 seconds
tween(bossSprite, {
tint: 0xff0000
}, {
duration: 90,
onFinish: function onFinish() {
tween(bossSprite, {
tint: 0xffa500
}, {
duration: 90,
onFinish: function onFinish() {
// Do nothing, color will be reset after smash
}
});
}
});
}
if (self.groundSmashTimer >= 480) {
self.groundSmashTimer = 0;
// Visual effect: flash boss and screen
tween(bossSprite, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(bossSprite, {
tint: 0x8e44ad
}, {
duration: 120
});
}
});
LK.effects.flashScreen(0xccccff, 200);
// Damage marine if close to boss (melee range)
if (marine && !marine.dead && marine.invuln === 0) {
var smashDist = bossSprite.width * 1.1;
var mdx = marine.x - self.x;
var mdy = marine.y - self.y;
if (mdx * mdx + mdy * mdy < smashDist * smashDist) {
marine.hp -= 2;
if (marine.hp < 0) marine.hp = 0;
marine.invuln = 60;
marine.flash();
updateUI();
if (marine.hp <= 0) {
// Game over
gameOver = true;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
// Shoot at marine (triple shot)
if (self.shootCooldown > 0) self.shootCooldown--;else if (dist < 1200) {
for (var i = -1; i <= 1; i++) {
var angle = Math.atan2(marine.y - self.y, marine.x - self.x) + i * 0.18;
var tx = self.x + Math.cos(angle) * 100;
var ty = self.y + Math.sin(angle) * 100;
spawnDemonBullet(self.x, self.y, tx, ty, 10);
}
self.shootCooldown = 50;
}
}
};
self.hit = function () {
self.hp--;
tween(bossSprite, {
tint: 0xffffff
}, {
duration: 60,
onFinish: function onFinish() {
tween(bossSprite, {
tint: 0x8e44ad
}, {
duration: 100
});
}
});
if (self.hp <= 0) {
self.dead = true;
// Spawn a shield at boss position
var shield = new Shield();
shield.x = self.x;
shield.y = self.y;
game.addChild(shield);
// Add points to score - bosses give double points when health pack effect is active
var points = doublePointsActive ? 100 : 50;
score += points;
updateUI();
// Create visual effect for shield drop
tween(shield, {
y: self.y + 20
}, {
duration: 60,
onFinish: function onFinish() {
tween(shield, {
y: self.y
}, {
duration: 40
});
}
});
// Fade out boss
tween(self, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
}
};
return self;
});
// Demon (Enemy)
var Demon = Container.expand(function () {
var self = Container.call(this);
var demonSprite = self.attachAsset('demon', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = demonSprite.width * 0.5;
self.hp = 4;
self.shootCooldown = 0;
self.speed = 4 + Math.random() * 2;
self.weapon = null; // Weapon sprite
// Create visual weapon (trident) for demon
self.updateWeapon = function () {
// Remove existing weapon if any
if (self.weapon) {
self.weapon.destroy();
}
// Create a furca (trident) for demon
self.weapon = LK.getAsset('furca', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Position trident in front of demon
if (marine) {
var angle = Math.atan2(marine.y - self.y, marine.x - self.x);
var radius = self.radius * 1.1;
self.weapon.x = Math.cos(angle) * radius;
self.weapon.y = Math.sin(angle) * radius;
self.weapon.rotation = angle + Math.PI / 2;
} else {
self.weapon.x = self.radius;
self.weapon.y = 0;
}
self.addChild(self.weapon);
};
self.update = function () {
// Move toward marine
if (marine && !self.dead) {
var dx = marine.x - self.x;
var dy = marine.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Create weapon if it doesn't exist
if (!self.weapon) {
self.updateWeapon();
}
// Update weapon position to match firing direction
if (self.weapon) {
var angle = Math.atan2(marine.y - self.y, marine.x - self.x);
var radius = self.radius * 1.1;
self.weapon.x = Math.cos(angle) * radius;
self.weapon.y = Math.sin(angle) * radius;
self.weapon.rotation = angle + Math.PI / 2;
}
// Update health bar
if (!self.healthBar) {
// Create health bar background
self.healthBarBg = LK.getAsset('marineBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.3
});
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar fill
self.healthBar = LK.getAsset('marineBullet', {
anchorX: 0,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.25
});
self.healthBar.tint = 0x00ff00;
self.addChild(self.healthBar);
}
// Position health bar above demon
self.healthBarBg.x = 0;
self.healthBarBg.y = -self.radius - 15;
self.healthBar.x = -self.radius;
self.healthBar.y = -self.radius - 15;
// Update health bar width based on current HP
var healthPercent = Math.max(0, self.hp / 4);
self.healthBar.scaleX = 2 * healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xffff00; // Yellow
} else {
self.healthBar.tint = 0xff0000; // Red
}
// Shoot at marine
if (self.shootCooldown > 0) self.shootCooldown--;else if (dist < 900) {
spawnDemonBullet(self.x, self.y, marine.x, marine.y);
self.shootCooldown = 90 + Math.floor(Math.random() * 30);
}
}
};
self.hit = function () {
self.hp--;
tween(demonSprite, {
tint: 0xffffff
}, {
duration: 60,
onFinish: function onFinish() {
tween(demonSprite, {
tint: 0xc0392b
}, {
duration: 100
});
}
});
if (self.hp <= 0) {
self.dead = true;
// If the demon has a weapon, fade it out too
if (self.weapon) {
tween(self.weapon, {
alpha: 0
}, {
duration: 200
});
}
tween(self, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
}
};
return self;
});
// Demon Bullet
var DemonBullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('demonBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bulletSprite.width * 0.5;
self.speed = 14;
self.dx = 0;
self.dy = 1;
self.update = function () {
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
};
return self;
});
// FireParticle for hell effect
var FireParticle = Container.expand(function () {
var self = Container.call(this);
var particleSprite = self.attachAsset('fireParticle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1 + Math.random() * 3;
self.lifetime = 60 + Math.random() * 120;
self.initialAlpha = 0.4 + Math.random() * 0.6;
self.alpha = self.initialAlpha;
self.scaleValue = 0.3 + Math.random() * 0.7;
particleSprite.scale.set(self.scaleValue, self.scaleValue);
// Random tint between orange and red
var tintValue = Math.random();
if (tintValue < 0.3) {
particleSprite.tint = 0xFF4500; // Orange-Red
} else if (tintValue < 0.6) {
particleSprite.tint = 0xFF6347; // Tomato
} else if (tintValue < 0.9) {
particleSprite.tint = 0xFF0000; // Red
} else {
particleSprite.tint = 0xFFD700; // Gold
}
self.update = function () {
self.y -= self.speed;
self.lifetime--;
// Fade out as lifetime decreases
self.alpha = self.initialAlpha * (self.lifetime / 180);
if (self.lifetime <= 0) {
self.destroy();
}
};
return self;
});
// Health Pack
var HealthPack = Container.expand(function () {
var self = Container.call(this);
var hpSprite = self.attachAsset('healthPack', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = hpSprite.width * 0.5;
return self;
});
// HellRock for background decoration
var HellRock = Container.expand(function () {
var self = Container.call(this);
var rockSprite = self.attachAsset('hellRock', {
anchorX: 0.5,
anchorY: 0.5
});
// Randomly adjust rock appearance
self.scale.set(0.8 + Math.random() * 0.5, 0.8 + Math.random() * 0.5);
rockSprite.rotation = Math.random() * Math.PI * 2;
// Random dark colors for rocks
var darkColors = [0x3D0C02, 0x2F0A02, 0x3B1001, 0x330000];
rockSprite.tint = darkColors[Math.floor(Math.random() * darkColors.length)];
return self;
});
// Marine (Player)
var Marine = Container.expand(function () {
var self = Container.call(this);
var marineSprite = self.attachAsset('marine', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = marineSprite.width * 0.5;
self.hp = 1;
self.invuln = 0; // invulnerability frames
self.weapons = []; // Array to hold weapon sprites
// Create visual weapons based on bullet count
self.updateWeapons = function (bulletCount) {
// Remove any existing weapons
for (var i = 0; i < self.weapons.length; i++) {
self.weapons[i].destroy();
}
self.weapons = [];
// Create AK47 weapon
var weapon = LK.getAsset('ak47', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Position weapon at marine's side
var radius = self.radius * 1.3;
weapon.x = radius;
weapon.y = 0;
self.addChild(weapon);
self.weapons.push(weapon);
};
// Define findNearestTarget method at the top of the class
self.findNearestTarget = function () {
var minDist = 999999;
var tx = self.x;
var ty = self.y - 200; // Default target ahead of marine
// Check demons first
if (demons && demons.length > 0) {
for (var i = 0; i < demons.length; ++i) {
var d = demons[i];
if (!d.dead) {
var dx = d.x - self.x;
var dy = d.y - self.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
tx = d.x;
ty = d.y;
}
}
}
}
// Check boss
if (boss && !boss.dead) {
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
tx = boss.x;
ty = boss.y;
}
}
// Check best demon
if (bestDemon && !bestDemon.dead) {
var dx = bestDemon.x - self.x;
var dy = bestDemon.y - self.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
tx = bestDemon.x;
ty = bestDemon.y;
}
}
return {
x: tx,
y: ty,
distance: Math.sqrt(minDist)
};
};
self.update = function () {
if (self.invuln > 0) self.invuln--;
// Update weapon positions to match bullet fire positions
if (self.weapons && self.weapons.length > 0) {
// Get the target for bullet direction
var target = self.findNearestTarget();
var tx = target.x;
var ty = target.y;
// Calculate direction
var dx = tx - self.x;
var dy = ty - self.y;
var baseAngle = Math.atan2(dy, dx);
// Position AK47 in firing position
if (self.weapons.length > 0) {
var weapon = self.weapons[0];
// Position AK47 pointing at target
var angle = baseAngle;
// Position weapon at firing direction
var radius = self.radius * 1.1; // Slightly outside marine
weapon.x = Math.cos(angle) * radius;
weapon.y = Math.sin(angle) * radius;
// Rotate weapon to face firing direction
weapon.rotation = angle + Math.PI / 2;
}
}
};
// Flash when hit
self.flash = function () {
tween(marineSprite, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(marineSprite, {
tint: 0x3a9ad9
}, {
duration: 120
});
}
});
};
return self;
});
// Marine Bullet
var MarineBullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('healtbar', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bulletSprite.width * 0.5;
self.speed = 32;
self.dx = 0;
self.dy = -1;
self.update = function () {
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
};
return self;
});
// Shield (Protection)
var Shield = Container.expand(function () {
var self = Container.call(this);
var shieldSprite = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5
});
// Use a different tint for the shield
shieldSprite.tint = 0x3498db;
self.radius = shieldSprite.width * 1.1;
self.duration = 600; // 10 seconds
self.update = function () {
self.duration--;
// Flash purple and green when 5 seconds (300 ticks) remaining
if (self.duration === 300) {
// Start color transition sequence
tween(shieldSprite, {
tint: 0x9b59b6 // Purple
}, {
duration: 150,
onFinish: function onFinish() {
tween(shieldSprite, {
tint: 0x2ecc71 // Green
}, {
duration: 150,
onFinish: function onFinish() {
// Repeat the color cycle until shield expires
var flashInterval = LK.setInterval(function () {
tween(shieldSprite, {
tint: 0x9b59b6 // Purple
}, {
duration: 150,
onFinish: function onFinish() {
tween(shieldSprite, {
tint: 0x2ecc71 // Green
}, {
duration: 150
});
}
});
}, 300);
// Store interval ID on shield for cleanup
self.flashInterval = flashInterval;
}
});
}
});
}
if (self.duration <= 0) {
// Clear flash interval if it exists
if (self.flashInterval) {
LK.clearInterval(self.flashInterval);
}
tween(self, {
alpha: 0
}, {
duration: 120,
onFinish: function onFinish() {
self.destroy();
}
});
}
// Follow the marine
if (marine) {
self.x = marine.x;
self.y = marine.y;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x660000
});
/****
* Game Code
****/
// Hell background elements
// Marine (player)
// Demon (enemy)
// Boss Demon
// Marine bullet
// Demon bullet
// Health pack
// Power-up
var fireParticles = [];
var hellRocks = [];
var particleSpawnTimer = 0;
// Create lava ground background
var lavaGround = LK.getAsset('lavaGround', {
anchorX: 0,
anchorY: 0
});
game.addChild(lavaGround);
// Generate initial rocks for hell landscape
function generateHellLandscape() {
// Create rocks scattered around the arena
for (var i = 0; i < 15; i++) {
var rock = new HellRock();
rock.x = ARENA_LEFT + Math.random() * (ARENA_RIGHT - ARENA_LEFT);
rock.y = ARENA_TOP + Math.random() * (ARENA_BOTTOM - ARENA_TOP);
hellRocks.push(rock);
game.addChild(rock);
}
}
// Arena bounds
var ARENA_MARGIN = 80;
var ARENA_LEFT = ARENA_MARGIN;
var ARENA_TOP = 180;
var ARENA_RIGHT = 2048 - ARENA_MARGIN;
var ARENA_BOTTOM = 2732 - ARENA_MARGIN;
// Game state
var marine = null;
var demons = [];
var boss = null;
var bestDemon = null;
var marineBullets = [];
var demonBullets = [];
var bestDemonBullets = [];
var healthPacks = [];
var powerUps = [];
var shields = [];
var wave = 1;
var waveTimer = 0;
var spawnTimer = 0;
var bossActive = false;
var dragging = false;
var dragOffsetX = 0;
var dragOffsetY = 0;
var lastMoveX = 0;
var lastMoveY = 0;
var score = 0;
var marineFireTimer = 0;
var marineFireRate = 12; // ticks between shots
var marinePowerTimer = 0;
var marineMaxHP = 1;
var gameOver = false;
var doublePointsActive = false;
var doublePointsTimer = 0;
var doublePointsDuration = 300; // 5 seconds at 60 FPS
var doublePointsIndicator = null; // Visual indicator for double points
// UI
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var hpTxt = new Text2('♥♥♥', {
size: 90,
fill: 0xE74C3C
});
hpTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(hpTxt);
hpTxt.y = 120;
hpTxt.x = 2048 / 2;
// Timer display in top right corner
var gameStartTime = 0;
var timerTxt = new Text2('00:00', {
size: 80,
fill: 0xFFFFFF
});
timerTxt.anchor.set(1, 0); // Right align
LK.gui.topRight.addChild(timerTxt);
timerTxt.x = -20; // Small margin from right edge
timerTxt.y = 20; // Small margin from top
// Center arena
function centerArenaX() {
return (ARENA_LEFT + ARENA_RIGHT) / 2;
}
function centerArenaY() {
return (ARENA_TOP + ARENA_BOTTOM) / 2;
}
// Spawn marine
function spawnMarine() {
marine = new Marine();
marine.x = centerArenaX();
marine.y = ARENA_BOTTOM - 220;
game.addChild(marine);
}
// Spawn demon
function spawnDemon() {
var d = new Demon();
// Spawn at random edge
var edge = Math.floor(Math.random() * 4);
if (edge === 0) {
// top
d.x = ARENA_LEFT + Math.random() * (ARENA_RIGHT - ARENA_LEFT);
d.y = ARENA_TOP + 40;
} else if (edge === 1) {
// bottom
d.x = ARENA_LEFT + Math.random() * (ARENA_RIGHT - ARENA_LEFT);
d.y = ARENA_BOTTOM - 40;
} else if (edge === 2) {
// left
d.x = ARENA_LEFT + 40;
d.y = ARENA_TOP + Math.random() * (ARENA_BOTTOM - ARENA_TOP);
} else {
// right
d.x = ARENA_RIGHT - 40;
d.y = ARENA_TOP + Math.random() * (ARENA_BOTTOM - ARENA_TOP);
}
demons.push(d);
game.addChild(d);
// Initialize weapon for the demon
d.updateWeapon();
}
// Spawn boss
function spawnBoss() {
boss = new Boss();
boss.x = centerArenaX();
boss.y = ARENA_TOP + 200;
game.addChild(boss);
bossActive = true;
// Initialize weapons for the boss
boss.updateWeapons();
}
// Spawn best demon
function spawnBestDemon() {
bestDemon = new BestDemon();
bestDemon.x = centerArenaX();
bestDemon.y = ARENA_TOP + 150;
game.addChild(bestDemon);
// Initialize weapons for the best demon
bestDemon.updateWeapons();
}
// Spawn marine bullet
function spawnMarineBullet(x, y, dx, dy) {
var b = new MarineBullet();
b.x = x;
b.y = y;
var mag = Math.sqrt(dx * dx + dy * dy);
b.dx = dx / mag;
b.dy = dy / mag;
marineBullets.push(b);
game.addChild(b);
}
// Spawn demon bullet
function spawnDemonBullet(x, y, tx, ty, speed) {
var b = new DemonBullet();
b.x = x;
b.y = y;
var dx = tx - x;
var dy = ty - y;
var mag = Math.sqrt(dx * dx + dy * dy);
b.dx = dx / mag;
b.dy = dy / mag;
if (speed) b.speed = speed;
demonBullets.push(b);
game.addChild(b);
}
// Spawn best demon bullet
function spawnBestDemonBullet(x, y, tx, ty, speed) {
var b = new BestDemonBullet();
b.x = x;
b.y = y;
var dx = tx - x;
var dy = ty - y;
var mag = Math.sqrt(dx * dx + dy * dy);
b.dx = dx / mag;
b.dy = dy / mag;
if (speed) b.speed = speed;
bestDemonBullets.push(b);
game.addChild(b);
}
// Spawn health pack
function spawnHealthPack() {
var hp = new HealthPack();
hp.x = ARENA_LEFT + 100 + Math.random() * (ARENA_RIGHT - ARENA_LEFT - 200);
hp.y = ARENA_TOP + 100 + Math.random() * (ARENA_BOTTOM - ARENA_TOP - 200);
healthPacks.push(hp);
game.addChild(hp);
}
// Power-ups have been removed
function spawnPowerUp() {
// Function kept for compatibility but does nothing
}
// Update UI
function updateUI() {
scoreTxt.setText(score);
var hpStr = '';
for (var i = 0; i < marineMaxHP; ++i) {
hpStr += i < marine.hp ? '♥' : '♡';
}
hpTxt.setText(hpStr);
}
// Start game
function startGame() {
score = 0;
wave = 1;
waveTimer = 0;
spawnTimer = 0;
bossActive = false;
marineFireTimer = 0;
gameOver = false;
doublePointsActive = false;
doublePointsTimer = 0;
// Clean up double points indicator
if (doublePointsIndicator) {
doublePointsIndicator.destroy();
doublePointsIndicator = null;
}
gameStartTime = LK.ticks; // Record game start time
// Remove all
for (var i = 0; i < demons.length; ++i) demons[i].destroy();
for (var i = 0; i < marineBullets.length; ++i) marineBullets[i].destroy();
for (var i = 0; i < demonBullets.length; ++i) demonBullets[i].destroy();
for (var i = 0; i < bestDemonBullets.length; ++i) bestDemonBullets[i].destroy();
for (var i = 0; i < healthPacks.length; ++i) healthPacks[i].destroy();
// Remove fire particles and rocks
for (var i = 0; i < fireParticles.length; ++i) fireParticles[i].destroy();
for (var i = 0; i < hellRocks.length; ++i) hellRocks[i].destroy();
fireParticles = [];
hellRocks = [];
// Remove any active shields
for (var i = 0; i < game.children.length; i++) {
if (game.children[i] instanceof Shield) {
game.children[i].destroy();
}
}
if (marine) marine.destroy();
if (boss) boss.destroy();
if (bestDemon) bestDemon.destroy();
demons = [];
marineBullets = [];
demonBullets = [];
bestDemonBullets = [];
healthPacks = [];
shields = [];
boss = null;
bestDemon = null;
spawnMarine();
// Initialize marine with 1 weapon
if (marine) {
marine.updateWeapons(1);
}
updateUI();
}
// Generate initial hell landscape
generateHellLandscape();
startGame();
// Touch controls
game.down = function (x, y, obj) {
if (gameOver) return;
// Only drag if touch is inside marine
var dx = x - marine.x;
var dy = y - marine.y;
if (dx * dx + dy * dy < marine.radius * marine.radius * 1.2) {
dragging = true;
dragOffsetX = marine.x - x;
dragOffsetY = marine.y - y;
lastMoveX = x;
lastMoveY = y;
}
};
game.up = function (x, y, obj) {
dragging = false;
};
game.move = function (x, y, obj) {
if (gameOver) return;
// Always follow finger/mouse, clamp to arena
if (marine) {
var nx = x;
var ny = y;
nx = Math.max(ARENA_LEFT + marine.radius, Math.min(ARENA_RIGHT - marine.radius, nx));
ny = Math.max(ARENA_TOP + marine.radius, Math.min(ARENA_BOTTOM - marine.radius, ny));
marine.x = nx;
marine.y = ny;
lastMoveX = x;
lastMoveY = y;
}
};
// Main update loop
game.update = function () {
if (gameOver) return;
// Update timer display
var elapsedTicks = LK.ticks - gameStartTime;
var elapsedSeconds = Math.floor(elapsedTicks / 60);
var minutes = Math.floor(elapsedSeconds / 60);
var seconds = elapsedSeconds % 60;
var timeStr = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText(timeStr);
// Update fire particles (hellish effect)
particleSpawnTimer--;
if (particleSpawnTimer <= 0) {
// Spawn new fire particles
for (var i = 0; i < 3; i++) {
var particle = new FireParticle();
particle.x = ARENA_LEFT + Math.random() * (ARENA_RIGHT - ARENA_LEFT);
particle.y = ARENA_BOTTOM - Math.random() * 100;
fireParticles.push(particle);
game.addChild(particle);
}
particleSpawnTimer = 2 + Math.floor(Math.random() * 3);
}
// Update existing fire particles
for (var i = fireParticles.length - 1; i >= 0; i--) {
fireParticles[i].update();
if (fireParticles[i].lifetime <= 0) {
fireParticles.splice(i, 1);
}
}
// Update marine
if (marine) marine.update();
// Update double points timer
if (doublePointsActive) {
// Create indicator if it doesn't exist
if (!doublePointsIndicator) {
doublePointsIndicator = LK.getAsset('2xpo', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
game.addChild(doublePointsIndicator);
}
// Position indicator next to marine
if (marine && doublePointsIndicator) {
doublePointsIndicator.x = marine.x + marine.radius * 1.5;
doublePointsIndicator.y = marine.y - marine.radius * 0.5;
}
doublePointsTimer--;
if (doublePointsTimer <= 0) {
doublePointsActive = false;
doublePointsTimer = 0; // Reset timer to ensure clean state
// Remove indicator when effect ends
if (doublePointsIndicator) {
doublePointsIndicator.destroy();
doublePointsIndicator = null;
}
}
} else {
// Ensure indicator is removed when double points is not active
if (doublePointsIndicator) {
doublePointsIndicator.destroy();
doublePointsIndicator = null;
}
}
// Update demons
for (var i = demons.length - 1; i >= 0; --i) {
var d = demons[i];
d.update();
if (d.dead) {
demons.splice(i, 1);
var points = doublePointsActive ? 20 : 10; // Double points if active
score += points;
updateUI();
// Chance to drop health
if (Math.random() < 0.08) spawnHealthPack();
}
}
// Update boss
if (boss) {
boss.update();
if (boss.dead) {
bossActive = false;
boss = null;
// Score is now added in the boss.hit method
wave++;
waveTimer = 0;
}
// Marine takes damage when touching boss
if (marine && !marine.dead && marine.invuln === 0 && !boss.dead) {
var dx = marine.x - boss.x;
var dy = marine.y - boss.y;
var r = marine.radius + boss.radius;
if (dx * dx + dy * dy < r * r) {
marine.hp--;
if (marine.hp < 0) marine.hp = 0;
marine.invuln = 60;
marine.flash();
updateUI();
if (marine.hp <= 0) {
gameOver = true;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
// Update best demon
if (bestDemon) {
bestDemon.update();
if (bestDemon.dead) {
bestDemon = null;
wave++;
waveTimer = 0;
}
// Marine takes damage when touching best demon
if (marine && !marine.dead && marine.invuln === 0 && !bestDemon.dead) {
var dx = marine.x - bestDemon.x;
var dy = marine.y - bestDemon.y;
var r = marine.radius + bestDemon.radius;
if (dx * dx + dy * dy < r * r) {
marine.hp--;
if (marine.hp < 0) marine.hp = 0;
marine.invuln = 60;
marine.flash();
updateUI();
if (marine.hp <= 0) {
gameOver = true;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
// Spawn best demon when score reaches 500 or more
if (!bestDemon && score >= 500) {
spawnBestDemon();
}
// Update marine bullets
for (var i = marineBullets.length - 1; i >= 0; --i) {
var b = marineBullets[i];
b.update();
// Remove if out of bounds
if (b.x < ARENA_LEFT - 60 || b.x > ARENA_RIGHT + 60 || b.y < ARENA_TOP - 60 || b.y > ARENA_BOTTOM + 60) {
b.destroy();
marineBullets.splice(i, 1);
continue;
}
// Hit demons
var hit = false;
for (var j = demons.length - 1; j >= 0; --j) {
var d = demons[j];
var dx = b.x - d.x;
var dy = b.y - d.y;
var r = b.radius + d.radius;
if (dx * dx + dy * dy < r * r) {
d.hit();
hit = true;
break;
}
}
// Hit boss
if (!hit && boss) {
var dx = b.x - boss.x;
var dy = b.y - boss.y;
var r = b.radius + boss.radius;
if (dx * dx + dy * dy < r * r) {
boss.hit();
hit = true;
}
}
// Hit best demon
if (!hit && bestDemon) {
var dx = b.x - bestDemon.x;
var dy = b.y - bestDemon.y;
var r = b.radius + bestDemon.radius;
if (dx * dx + dy * dy < r * r) {
bestDemon.hit();
hit = true;
}
}
if (hit) {
b.destroy();
marineBullets.splice(i, 1);
}
}
// Update demon bullets
for (var i = demonBullets.length - 1; i >= 0; --i) {
var b = demonBullets[i];
b.update();
// Remove if out of bounds
if (b.x < ARENA_LEFT - 60 || b.x > ARENA_RIGHT + 60 || b.y < ARENA_TOP - 60 || b.y > ARENA_BOTTOM + 60) {
b.destroy();
demonBullets.splice(i, 1);
continue;
}
// Hit marine
if (marine && marine.invuln === 0) {
var dx = b.x - marine.x;
var dy = b.y - marine.y;
var r = b.radius + marine.radius;
if (dx * dx + dy * dy < r * r) {
b.destroy();
demonBullets.splice(i, 1);
// Check if a shield is protecting the marine
var hasShield = false;
for (var j = 0; j < game.children.length; j++) {
if (game.children[j] instanceof Shield) {
hasShield = true;
// Destroy shield immediately when hit
if (game.children[j].flashInterval) {
LK.clearInterval(game.children[j].flashInterval);
}
// Flash shield effect and destroy it
tween(game.children[j], {
alpha: 0
}, {
duration: 120,
onFinish: function onFinish(obj) {
obj.destroy();
}
});
break;
}
}
if (!hasShield) {
marine.hp--;
marine.invuln = 60;
marine.flash();
updateUI();
if (marine.hp <= 0) {
// Game over
gameOver = true;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
}
// Update best demon bullets
for (var i = bestDemonBullets.length - 1; i >= 0; --i) {
var b = bestDemonBullets[i];
b.update();
// Remove if out of bounds
if (b.x < ARENA_LEFT - 60 || b.x > ARENA_RIGHT + 60 || b.y < ARENA_TOP - 60 || b.y > ARENA_BOTTOM + 60) {
b.destroy();
bestDemonBullets.splice(i, 1);
continue;
}
// Hit marine
if (marine && marine.invuln === 0) {
var dx = b.x - marine.x;
var dy = b.y - marine.y;
var r = b.radius + marine.radius;
if (dx * dx + dy * dy < r * r) {
b.destroy();
bestDemonBullets.splice(i, 1);
// Check if a shield is protecting the marine
var hasShield = false;
for (var j = 0; j < game.children.length; j++) {
if (game.children[j] instanceof Shield) {
hasShield = true;
// Destroy shield immediately when hit
if (game.children[j].flashInterval) {
LK.clearInterval(game.children[j].flashInterval);
}
// Flash shield effect and destroy it
tween(game.children[j], {
alpha: 0
}, {
duration: 120,
onFinish: function onFinish(obj) {
obj.destroy();
}
});
break;
}
}
if (!hasShield) {
marine.hp--;
marine.invuln = 60;
marine.flash();
updateUI();
if (marine.hp <= 0) {
// Game over
gameOver = true;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
}
// Update health packs
for (var i = healthPacks.length - 1; i >= 0; --i) {
var hp = healthPacks[i];
if (marine) {
var dx = hp.x - marine.x;
var dy = hp.y - marine.y;
var r = hp.radius + marine.radius;
if (dx * dx + dy * dy < r * r) {
if (marine.hp < marineMaxHP) {
marine.hp++;
}
// Activate double points for 10 seconds
doublePointsActive = true;
doublePointsTimer = doublePointsDuration;
updateUI();
hp.destroy();
healthPacks.splice(i, 1);
}
}
}
// Power-ups have been removed
// Marine auto-fire
if (marine) {
marineFireTimer--;
if (marineFireTimer <= 0) {
marineFireTimer = marineFireRate;
// Auto-targeting: find nearest enemy
var target = marine.findNearestTarget();
var tx = target.x;
var ty = target.y;
// Fire
// Calculate bullets based on score - 1 bullet base + 1 extra per 500 points
var totalBullets = 1 + Math.floor(score / 500);
// Make sure marine has an AK47
if (marine.weapons.length === 0) {
marine.updateWeapons(1);
}
var dx = tx - marine.x;
var dy = ty - marine.y;
var baseAngle = Math.atan2(dy, dx);
for (var n = 0; n < totalBullets; ++n) {
// Spread out extra bullets a bit
var offset = (n - (totalBullets - 1) / 2) * 0.12;
var angle = baseAngle + offset;
var ddx = Math.cos(angle);
var ddy = Math.sin(angle);
spawnMarineBullet(marine.x, marine.y, ddx, ddy, false);
}
}
}
// Wave logic
if (!bossActive) {
// Spawn demons
if (demons.length < Math.min(3 + wave, 8)) {
spawnTimer--;
if (spawnTimer <= 0) {
spawnDemon();
spawnTimer = 120 + Math.floor(Math.random() * 60); // Increased timer for slower demon spawning
}
}
// Next wave
if (demons.length === 0 && !boss && waveTimer > 60) {
if (score >= 100 && score < 490 && Math.floor(score / 100) > Math.floor((score - (doublePointsActive ? 20 : 10)) / 100)) {
spawnBoss();
} else {
wave++;
waveTimer = 0;
}
}
waveTimer++;
} else {
// Wait for boss defeat
if (boss && boss.dead) {
bossActive = false;
boss = null;
wave++;
waveTimer = 0;
}
}
// Randomly spawn health packs
if (LK.ticks % 420 === 0 && Math.random() < 0.5) {
spawnHealthPack();
}
// Pulsate lava ground for hellish effect
if (LK.ticks % 120 === 0) {
var targetTint = Math.random() < 0.5 ? 0x990000 : 0x660000;
tween(lavaGround, {
tint: targetTint
}, {
duration: 1000,
easing: tween.easeInOut
});
}
};
// Reset game on game over
LK.on('gameover', function () {
startGame();
});
yumurta şeytan. In-Game asset. 2d. High contrast. No shadows
yuvarlak bir çerçevenin içerisinde şeytan kulaklı gülümseyen balkabağı kırmızı. In-Game asset. 2d. High contrast. No shadows
yarasa. In-Game asset. 2d. High contrast. No shadows
furca. In-Game asset. 2d. High contrast. No shadows
+. In-Game asset. 2d. High contrast. No shadows
angel . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
blue 2x. In-Game asset. 2d. High contrast. No shadows
bigg demon. In-Game asset. 2d. High contrast. No shadows
red sword. In-Game asset. 2d. High contrast. No shadows
ateş. In-Game asset. 2d. High contrast. No shadows
tsunami. In-Game asset. 2d. High contrast. No shadows
yeşil bar. In-Game asset. 2d. High contrast. No shadows