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