/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
totalKills: 0
});
/****
* Classes
****/
var ArmorPiece = Container.expand(function () {
var self = Container.call(this);
self.armorType = '';
self.defenseBonus = 0;
self.healthBonus = 0;
self.speedPenalty = 0;
self.equipped = false;
self.setArmorType = function (type) {
self.armorType = type;
switch (type) {
case 'light':
self.defenseBonus = 5;
self.healthBonus = 10;
self.speedPenalty = 0;
break;
case 'medium':
self.defenseBonus = 10;
self.healthBonus = 20;
self.speedPenalty = 0.5;
break;
case 'heavy':
self.defenseBonus = 20;
self.healthBonus = 40;
self.speedPenalty = 1;
break;
}
};
self.equip = function (target) {
if (!self.equipped) {
self.equipped = true;
target.defense = (target.defense || 0) + self.defenseBonus;
target.maxHealth += self.healthBonus;
target.health += self.healthBonus;
target.moveSpeed = Math.max(1, target.moveSpeed - self.speedPenalty);
updateHealthBar();
}
};
return self;
});
var BasicEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('basicEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Weapon graphics removed
self.health = 25;
self.maxHealth = 25;
self.damage = 15;
self.speed = 2;
self.scoreValue = 10;
// Enemy hitbox properties
self.width = 240; // Enemy visual width
self.height = 240; // Enemy visual height
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
// Drop XP orb
var xpOrb = new XPOrb();
xpOrb.x = self.x;
xpOrb.y = self.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
// 50% chance to drop coin
if (Math.random() < 0.5) {
var coin = new CoinPickup();
coin.x = self.x + (Math.random() - 0.5) * 60;
coin.y = self.y + (Math.random() - 0.5) * 60;
coinPickups.push(coin);
game.addChild(coin);
}
self.destroy();
LK.setScore(LK.getScore() + self.scoreValue);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDestroy').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
// updateWeapon method removed
self.update = function () {
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Weapon update removed
if (distance > 0) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
// Check collision with other enemies
var collisionFound = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self) {
var otherDx = newX - otherEnemy.x;
var otherDy = newY - otherEnemy.y;
var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy);
var minDistance = (self.width + otherEnemy.width) / 4; // Collision threshold
if (otherDistance < minDistance) {
collisionFound = true;
// Push enemies apart
if (otherDistance > 0) {
var pushForce = (minDistance - otherDistance) / 2;
var pushX = otherDx / otherDistance * pushForce;
var pushY = otherDy / otherDistance * pushForce;
newX += pushX;
newY += pushY;
otherEnemy.x -= pushX;
otherEnemy.y -= pushY;
}
}
}
}
// Only move if no major collision or after separation
if (!collisionFound || distance > 100) {
self.x = newX;
self.y = newY;
}
}
};
return self;
});
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('wikingboss', {
anchorX: 0.5,
anchorY: 0.5
});
bossGraphics.tint = 0x8B4513; // Brown viking color
bossGraphics.scaleX = 4.0; // Much larger viking boss model
bossGraphics.scaleY = 4.0; // Much larger viking boss model
// Weapon graphics removed
// Add viking boss aura effect
var auraGraphics = self.attachAsset('wikingboss', {
anchorX: 0.5,
anchorY: 0.5
});
auraGraphics.tint = 0xDAA520; // Golden viking aura
auraGraphics.alpha = 0.4;
auraGraphics.scaleX = 4.5; // Larger aura to match viking boss size
auraGraphics.scaleY = 4.5; // Larger aura to match viking boss size
// Add pulsing viking aura animation
tween(auraGraphics, {
scaleX: 5.0,
scaleY: 5.0,
alpha: 0.2
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(auraGraphics, {
scaleX: 4.5,
scaleY: 4.5,
alpha: 0.4
}, {
duration: 1500,
easing: tween.easeInOut
});
}
});
self.health = 400 + (currentWave - 3) * 85; // Much higher health for viking boss
self.maxHealth = self.health;
self.damage = 70; // Higher damage for intimidating viking boss
self.speed = 0.8; // Slightly slower due to massive viking size
self.scoreValue = 250; // Higher reward for defeating viking boss
// Boss hitbox properties for accurate collision
self.width = 760; // Boss visual width (190 * 4 scale)
self.height = 760; // Boss visual height (190 * 4 scale)
self.lastLightningTime = 0;
self.lightningCooldown = 120; // 2 seconds at 60fps - reduced attack frequency
self.lastChargeTime = 0;
self.chargeCooldown = 360; // 6 seconds at 60fps - less frequent
self.isCharging = false;
self.chargeSpeed = 8;
self.chargeTargetX = 0;
self.chargeTargetY = 0;
self.lastJumpTime = 0;
self.jumpCooldown = 300; // 5 seconds at 60fps - less frequent
self.isJumping = false;
self.jumpMarker = null;
self.lastAerialTime = 0;
self.aerialCooldown = 240; // 4 seconds at 60fps
self.isAerial = false;
self.aerialMarkers = [];
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
// Drop multiple XP orbs
for (var orbCount = 0; orbCount < 5; orbCount++) {
var xpOrb = new XPOrb();
xpOrb.x = self.x + (Math.random() - 0.5) * 100;
xpOrb.y = self.y + (Math.random() - 0.5) * 100;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
}
// Boss always drops coins
for (var coinCount = 0; coinCount < 10; coinCount++) {
var coin = new CoinPickup();
coin.x = self.x + (Math.random() - 0.5) * 150;
coin.y = self.y + (Math.random() - 0.5) * 150;
coin.coinValue = 10; // Boss coins are worth more
coinPickups.push(coin);
game.addChild(coin);
}
self.destroy();
LK.setScore(LK.getScore() + self.scoreValue);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDestroy').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
// Boss defeated - start new wave
LK.setTimeout(function () {
startNewWave();
}, 2000); // 2 second delay before starting new wave
}
};
self.shootBigBullets = function () {
// Create multiple big bullets - increased from 5 to 7 for more coverage
for (var bulletCount = 0; bulletCount < 7; bulletCount++) {
var bigBullet = new BossBullet();
bigBullet.x = self.x;
bigBullet.y = self.y;
// Spread bullets in a wider arc for better coverage
var angleOffset = (bulletCount - 3) * Math.PI / 10;
var targetX = hero.x + Math.cos(angleOffset) * 200;
var targetY = hero.y + Math.sin(angleOffset) * 200;
bigBullet.setTarget(targetX, targetY);
bossBullets.push(bigBullet);
game.addChild(bigBullet);
}
// More dramatic visual effect for primary attack
LK.effects.flashObject(self, 0xFF4500, 600);
LK.effects.flashScreen(0xFF4500, 100);
};
self.startCharge = function () {
self.isCharging = true;
self.chargeTargetX = hero.x;
self.chargeTargetY = hero.y;
LK.effects.flashObject(self, 0xFF4500, 500);
// Viking boss grows larger during charge
tween(bossGraphics, {
scaleX: 4.5,
scaleY: 4.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Charge for 1 second then return to normal
LK.setTimeout(function () {
self.isCharging = false;
tween(bossGraphics, {
scaleX: 4.0,
scaleY: 4.0
}, {
duration: 200,
easing: tween.easeIn
});
}, 1000);
}
});
};
self.startJumpAttack = function () {
self.isJumping = true;
// Create red marker at hero's position
self.jumpMarker = LK.getAsset('jumpMarker', {
anchorX: 0.5,
anchorY: 0.5
});
self.jumpMarker.x = hero.x;
self.jumpMarker.y = hero.y;
self.jumpMarker.alpha = 0.6;
game.addChild(self.jumpMarker);
// Pulse the marker
tween(self.jumpMarker, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.jumpMarker, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.6
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
// Boss prepares to jump - crouch animation
tween(bossGraphics, {
scaleY: 3.5
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
// Jump to marker position
var jumpX = self.jumpMarker.x;
var jumpY = self.jumpMarker.y;
// Jump animation
tween(self, {
x: jumpX,
y: jumpY
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Landing impact
LK.effects.flashScreen(0xFF0000, 200);
tween(bossGraphics, {
scaleY: 4.0
}, {
duration: 200,
easing: tween.easeOut
});
// Deal area damage at landing position
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy !== self) {
var impactDx = enemy.x - self.x;
var impactDy = enemy.y - self.y;
var impactDistance = Math.sqrt(impactDx * impactDx + impactDy * impactDy);
if (impactDistance < 250) {
enemy.takeDamage(40);
}
}
}
// Check if hero is in impact area
var heroDx = hero.x - self.x;
var heroDy = hero.y - self.y;
var heroDistance = Math.sqrt(heroDx * heroDx + heroDy * heroDy);
if (heroDistance < 250) {
hero.takeDamage(60);
}
// Remove marker
if (self.jumpMarker) {
self.jumpMarker.destroy();
self.jumpMarker = null;
}
self.isJumping = false;
}
});
}
});
};
self.startAerialAttack = function () {
self.isAerial = true;
// Create multiple red markers in a pattern around hero
var markerCount = 5;
for (var i = 0; i < markerCount; i++) {
var angle = Math.PI * 2 / markerCount * i;
var markerDistance = 150;
var marker = LK.getAsset('jumpMarker', {
anchorX: 0.5,
anchorY: 0.5
});
marker.x = hero.x + Math.cos(angle) * markerDistance;
marker.y = hero.y + Math.sin(angle) * markerDistance;
marker.alpha = 0.8;
marker.tint = 0xFF0000;
game.addChild(marker);
self.aerialMarkers.push(marker);
// Pulse animation for markers
tween(marker, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.4
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(marker, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
}
// Boss rises up animation
tween(bossGraphics, {
scaleX: 3.5,
scaleY: 3.5,
alpha: 0.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// After delay, rain bullets from sky
LK.setTimeout(function () {
// Create bullets at each marker position
for (var j = 0; j < self.aerialMarkers.length; j++) {
var targetMarker = self.aerialMarkers[j];
// Create bullet high above
var skyBullet = new BossBullet();
skyBullet.x = targetMarker.x;
skyBullet.y = targetMarker.y - 800; // Start high above
skyBullet.setTarget(targetMarker.x, targetMarker.y);
bossBullets.push(skyBullet);
game.addChild(skyBullet);
}
// Boss returns to normal
tween(bossGraphics, {
scaleX: 4.0,
scaleY: 4.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.easeIn
});
// Remove markers after bullets are fired
for (var k = 0; k < self.aerialMarkers.length; k++) {
self.aerialMarkers[k].destroy();
}
self.aerialMarkers = [];
self.isAerial = false;
}, 800);
}
});
};
self.update = function () {
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var currentTime = LK.ticks;
// Weapon update removed
// Big bullet attack - primary attack with extended range
if (distance < 900 && currentTime - self.lastLightningTime > self.lightningCooldown) {
self.shootBigBullets();
self.lastLightningTime = currentTime;
}
// Charge attack - secondary attack, less frequent
if (distance > 250 && distance < 600 && currentTime - self.lastChargeTime > self.chargeCooldown && !self.isCharging && !self.isJumping) {
self.startCharge();
self.lastChargeTime = currentTime;
}
// Jump attack - tertiary attack, least frequent
if (distance < 700 && currentTime - self.lastJumpTime > self.jumpCooldown && !self.isJumping && !self.isCharging && !self.isAerial) {
self.startJumpAttack();
self.lastJumpTime = currentTime;
}
// Aerial attack - quaternary attack
if (distance < 600 && currentTime - self.lastAerialTime > self.aerialCooldown && !self.isJumping && !self.isCharging && !self.isAerial) {
self.startAerialAttack();
self.lastAerialTime = currentTime;
}
// Movement behavior
if (self.isCharging) {
// Charge towards target position
var chargeDx = self.chargeTargetX - self.x;
var chargeDy = self.chargeTargetY - self.y;
var chargeDistance = Math.sqrt(chargeDx * chargeDx + chargeDy * chargeDy);
if (chargeDistance > 0) {
self.x += chargeDx / chargeDistance * self.chargeSpeed;
self.y += chargeDy / chargeDistance * self.chargeSpeed;
}
} else if (self.isJumping || self.isAerial) {
// Don't move while jumping or aerial attack
} else {
// Normal movement - maintain distance from hero
if (distance > 300) {
// Move closer
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
} else if (distance < 150) {
// Move away
if (distance > 0) {
self.x -= dx / distance * self.speed;
self.y -= dy / distance * self.speed;
}
}
}
};
return self;
});
var BossBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.scaleX = 1.5; // Scale for appropriate size
bulletGraphics.scaleY = 1.5; // Scale for appropriate size
// Add glowing effect to boss bullets
var glowGraphics = self.attachAsset('bossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
glowGraphics.tint = 0xFF6600; // Orange glow
glowGraphics.alpha = 0.4;
glowGraphics.scaleX = 2;
glowGraphics.scaleY = 2;
// Pulsing glow animation
tween(glowGraphics, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(glowGraphics, {
scaleX: 2,
scaleY: 2,
alpha: 0.4
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
self.speed = 3;
self.damage = 50;
// Boss bullet hitbox properties
self.width = 150; // Boss bullet visual width (100 * 1.5 scale)
self.height = 150; // Boss bullet visual height (100 * 1.5 scale)
self.targetX = 0;
self.targetY = 0;
self.directionX = 0;
self.directionY = 0;
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
var dx = x - self.x;
var dy = y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.directionX = dx / distance;
self.directionY = dy / distance;
}
// Rotate bullet to face direction
bulletGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
};
self.update = function () {
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Rotate the boss bullet for visual effect
self.rotation += 0.1;
// Remove bullet if it goes off screen
if (self.x < -50 || self.x > 8242 || self.y < -50 || self.y > 10978) {
self.destroy();
for (var i = bossBullets.length - 1; i >= 0; i--) {
if (bossBullets[i] === self) {
bossBullets.splice(i, 1);
break;
}
}
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = Bullet.prototype.baseSpeed || 8;
self.damage = 25;
self.targetX = 0;
self.targetY = 0;
self.directionX = 0;
self.directionY = 0;
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
var dx = x - self.x;
var dy = y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.directionX = dx / distance;
self.directionY = dy / distance;
}
// Rotate bullet to face direction
bulletGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
};
self.update = function () {
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Remove bullet if it goes off screen
if (self.x < -50 || self.x > 8242 || self.y < -50 || self.y > 10978) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
}
};
return self;
});
var CoinPickup = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
coinGraphics.scaleX = 0.8;
coinGraphics.scaleY = 0.8;
// Add glowing effect
var glowGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
glowGraphics.tint = 0xFFF700; // Bright yellow glow
glowGraphics.alpha = 0.3;
glowGraphics.scaleX = 1.1;
glowGraphics.scaleY = 1.1;
// Add pulsing animation
tween(coinGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(coinGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 600,
easing: tween.easeInOut
});
}
});
// Add continuous rotation animation
tween(coinGraphics, {
rotation: Math.PI * 2
}, {
duration: 2000,
easing: tween.linear,
onFinish: function onFinish() {
coinGraphics.rotation = 0;
// Restart rotation
tween(coinGraphics, {
rotation: Math.PI * 2
}, {
duration: 2000,
easing: tween.linear
});
}
});
self.coinValue = 5;
self.moveSpeed = 3;
self.attractRange = 120;
self.update = function () {
// Move toward hero if within attract range
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.attractRange) {
if (distance > 0) {
self.x += dx / distance * self.moveSpeed;
self.y += dy / distance * self.moveSpeed;
}
}
// Check if collected by hero
if (distance < 30) {
// Give coins to player
var currentCoins = storage.coins || 0;
storage.coins = currentCoins + self.coinValue;
updateCoinsDisplay();
LK.getSound('xpCollect').play();
LK.effects.flashObject(hero, 0xFFD700, 300); // Gold flash
// Remove from coins array
for (var i = coinPickups.length - 1; i >= 0; i--) {
if (coinPickups[i] === self) {
coinPickups.splice(i, 1);
break;
}
}
self.destroy();
}
};
return self;
});
var FastEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('fastEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Weapon graphics removed
self.health = 15;
self.maxHealth = 15;
self.damage = 10;
self.speed = 3;
self.scoreValue = 15;
// Enemy hitbox properties
self.width = 150; // Enemy visual width
self.height = 150; // Enemy visual height
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
// Create multiple explosion visual effects with different colors and timings
LK.effects.flashObject(self, 0xFF4500, 400); // Red-orange flash
LK.effects.flashScreen(0xFF8C00, 150); // Brief orange screen flash
// Add scaling explosion effect
tween(self, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Deal explosion damage to nearby enemies (but prevent chain explosions)
for (var j = 0; j < enemies.length; j++) {
var otherEnemy = enemies[j];
if (otherEnemy !== self) {
var explodeDx = otherEnemy.x - self.x;
var explodeDy = otherEnemy.y - self.y;
var explodeDistance = Math.sqrt(explodeDx * explodeDx + explodeDy * explodeDy);
// Damage enemies within 150 pixel explosion radius
if (explodeDistance < 150) {
// Prevent chain explosions by directly reducing health instead of calling takeDamage
otherEnemy.health -= 25;
if (otherEnemy.health <= 0) {
otherEnemy.health = 0;
// Manually trigger death without explosion effects for other FastEnemies
if (otherEnemy instanceof FastEnemy) {
// Simple death without explosion to prevent recursion
var xpOrb = new XPOrb();
xpOrb.x = otherEnemy.x;
xpOrb.y = otherEnemy.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
otherEnemy.destroy();
LK.setScore(LK.getScore() + otherEnemy.scoreValue);
scoreTxt.setText(LK.getScore());
for (var k = enemies.length - 1; k >= 0; k--) {
if (enemies[k] === otherEnemy) {
enemies.splice(k, 1);
break;
}
}
} else {
// For non-FastEnemies, call takeDamage normally to trigger their death effects
otherEnemy.takeDamage(0);
}
} else {
// Flash effect for damaged but alive enemies
LK.effects.flashObject(otherEnemy, 0xFF4500, 200);
}
// Push enemies away from explosion center
if (explodeDistance > 0) {
otherEnemy.x += explodeDx / explodeDistance * 80;
otherEnemy.y += explodeDy / explodeDistance * 80;
}
}
}
}
// Deal explosion damage to hero if in blast radius
var heroDx = hero.x - self.x;
var heroDy = hero.y - self.y;
var heroDistance = Math.sqrt(heroDx * heroDx + heroDy * heroDy);
if (heroDistance < 150) {
hero.takeDamage(20); // Explosion damage to hero
// Add visual feedback for hero being hit by explosion
LK.effects.flashObject(hero, 0xFF4500, 300);
}
// Drop XP orb
var xpOrb = new XPOrb();
xpOrb.x = self.x;
xpOrb.y = self.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
// 50% chance to drop coin
if (Math.random() < 0.5) {
var coin = new CoinPickup();
coin.x = self.x + (Math.random() - 0.5) * 60;
coin.y = self.y + (Math.random() - 0.5) * 60;
coinPickups.push(coin);
game.addChild(coin);
}
self.destroy();
LK.setScore(LK.getScore() + self.scoreValue);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDestroy').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
// updateWeapon method removed
self.update = function () {
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Weapon update removed
if (distance > 0) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
// Check collision with other enemies
var collisionFound = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self) {
var otherDx = newX - otherEnemy.x;
var otherDy = newY - otherEnemy.y;
var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy);
var minDistance = (self.width + otherEnemy.width) / 4; // Collision threshold
if (otherDistance < minDistance) {
collisionFound = true;
// Push enemies apart
if (otherDistance > 0) {
var pushForce = (minDistance - otherDistance) / 2;
var pushX = otherDx / otherDistance * pushForce;
var pushY = otherDy / otherDistance * pushForce;
newX += pushX;
newY += pushY;
otherEnemy.x -= pushX;
otherEnemy.y -= pushY;
}
}
}
}
// Only move if no major collision or after separation
if (!collisionFound || distance > 100) {
self.x = newX;
self.y = newY;
}
}
};
return self;
});
var HealthCrate = Container.expand(function () {
var self = Container.call(this);
var crateGraphics = self.attachAsset('crate', {
anchorX: 0.5,
anchorY: 0.5
});
crateGraphics.scaleX = 0.8;
crateGraphics.scaleY = 0.8;
self.health = 1;
self.takeDamage = function (damage) {
self.health -= damage;
// Shake effect when hit
LK.effects.flashObject(self, 0xFFFFFF, 100);
tween(self, {
rotation: self.rotation + 0.1
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
rotation: self.rotation - 0.2
}, {
duration: 50,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(self, {
rotation: self.rotation + 0.1
}, {
duration: 50,
easing: tween.easeOut
});
}
});
}
});
if (self.health <= 0) {
var dropChance = Math.random();
if (dropChance < 0.1) {
// 10% chance to drop level up item
var levelPickup = new LevelPickup();
levelPickup.x = self.x;
levelPickup.y = self.y;
levelPickups.push(levelPickup);
game.addChild(levelPickup);
} else if (dropChance < 0.55) {
// 45% chance to drop XP orb
var xpOrb = new XPOrb();
xpOrb.x = self.x;
xpOrb.y = self.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
} else {
// 45% chance to drop health pickup
var healthPickup = new HealthPickup();
healthPickup.x = self.x;
healthPickup.y = self.y;
healthPickups.push(healthPickup);
game.addChild(healthPickup);
}
self.destroy();
LK.getSound('enemyDestroy').play();
for (var i = healthCrates.length - 1; i >= 0; i--) {
if (healthCrates[i] === self) {
healthCrates.splice(i, 1);
break;
}
}
}
};
return self;
});
var HealthPickup = Container.expand(function () {
var self = Container.call(this);
var pickupGraphics = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
pickupGraphics.scaleX = 0.3;
pickupGraphics.scaleY = 0.3;
pickupGraphics.tint = 0xFF0000; // Red color for health
// Add pulsing animation
tween(pickupGraphics, {
scaleX: 0.35,
scaleY: 0.35
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(pickupGraphics, {
scaleX: 0.3,
scaleY: 0.3
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
self.healAmount = 15;
self.update = function () {
// Check if collected by hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 40) {
// Heal player
if (hero.health < hero.maxHealth) {
hero.health = Math.min(hero.health + self.healAmount, hero.maxHealth);
updateHealthBar();
LK.getSound('xpCollect').play();
LK.effects.flashObject(hero, 0x00FF00, 300); // Green flash
}
// Remove from healthPickups array
for (var i = healthPickups.length - 1; i >= 0; i--) {
if (healthPickups[i] === self) {
healthPickups.splice(i, 1);
break;
}
}
self.destroy();
}
};
return self;
});
var Hero = Container.expand(function () {
var self = Container.call(this);
var heroGraphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
var weaponGraphics = self.attachAsset('pistolModel', {
anchorX: 0.5,
anchorY: 0.8 // Better anchor point for weapon rotation
});
weaponGraphics.x = 0;
weaponGraphics.y = 0;
// Set initial weapon scale for better visibility
weaponGraphics.scaleX = 1.2;
weaponGraphics.scaleY = 1.2;
self.maxHealth = 100;
self.health = self.maxHealth;
self.attackDamage = 25;
self.isInvulnerable = false;
// Custom hitbox properties
self.hitboxWidth = 80; // Smaller than visual size for better gameplay
self.hitboxHeight = 100; // Adjusted for character proportions
self.hitboxOffsetX = 0; // Center aligned
self.hitboxOffsetY = 0; // Center aligned
self.takeDamage = function (damage) {
if (self.isInvulnerable) return;
// Apply shield protection
if (self.hasShield) {
damage = Math.floor(damage * 0.5); // 50% damage reduction
}
// Apply armor defense
var defense = self.defense || 0;
damage = Math.max(1, damage - defense); // Minimum 1 damage
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
// Add death visual effects
LK.effects.flashObject(self, 0xFF0000, 1000); // Long red flash
LK.effects.flashScreen(0x8B0000, 800); // Dark red screen flash
// Make hero semi-transparent to show death
tween(self, {
alpha: 0.3,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeOut
});
// Change to death state and show death menu
gameState = 'dead';
// Add a small delay before showing death menu for better UX
LK.setTimeout(function () {
createDeathMenu();
}, 500);
return;
}
// Flash red when taking damage
LK.effects.flashObject(self, 0xFF0000, 300);
LK.getSound('playerHit').play();
// Brief invulnerability
self.isInvulnerable = true;
LK.setTimeout(function () {
self.isInvulnerable = false;
}, 500);
};
self.attack = function (enemy) {
enemy.takeDamage(self.attackDamage);
LK.getSound('enemyHit').play();
// Weapon swing animation
tween(weaponGraphics, {
rotation: weaponGraphics.rotation + Math.PI / 3
}, {
duration: 100
});
};
self.updateWeaponGraphics = function () {
// Map weapon types to their corresponding models
var weaponModels = {
pistol: 'pistolModel',
rifle: 'rifleModel',
shotgun: 'shotgunModel',
machinegun: 'machinegunModel',
sniper: 'rifleModel',
// Use rifle model for sniper
laser: 'laserModel',
explosive: 'explosiveModel'
};
// Get the model for current weapon
var newModel = weaponModels[currentWeapon] || 'pistolModel';
// Remove old weapon graphics
if (weaponGraphics) {
self.removeChild(weaponGraphics);
weaponGraphics.destroy();
}
// Create new weapon graphics with proper model
weaponGraphics = self.attachAsset(newModel, {
anchorX: 0.5,
anchorY: 0.8
});
weaponGraphics.x = 0;
weaponGraphics.y = 0;
weaponGraphics.scaleX = 1.2;
weaponGraphics.scaleY = 1.2;
};
self.updateWeapon = function (targetX, targetY) {
// Calculate angle to target
var dx = targetX - self.x;
var dy = targetY - self.y;
var angle = Math.atan2(dy, dx);
weaponGraphics.rotation = angle + Math.PI / 2;
// Position weapon properly with offset based on rotation
var weaponOffset = 30; // Distance from hero center
weaponGraphics.x = Math.cos(angle) * weaponOffset;
weaponGraphics.y = Math.sin(angle) * weaponOffset;
};
self.shoot = function (targetX, targetY) {
var weapon = weaponTypes[currentWeapon];
// Pre-calculate common values to reduce calculations
var weaponAngle = weaponGraphics.rotation - Math.PI / 2;
var weaponTipOffset = 45; // Distance from hero center to weapon tip
var bulletStartX = self.x + Math.cos(weaponAngle) * weaponTipOffset;
var bulletStartY = self.y + Math.sin(weaponAngle) * weaponTipOffset;
if (currentWeapon === 'shotgun') {
// Ultra-optimized shotgun: pre-calculated spread directions
var bulletCount = 3; // Keep 3 bullets for balance
// Pre-calculated spread directions to avoid trigonometric calculations
var spreadDirections = [{
dx: -0.196,
dy: 0.981
},
// Left spread (~-11.3 degrees)
{
dx: 0,
dy: 1
},
// Center (straight)
{
dx: 0.196,
dy: 0.981
} // Right spread (~11.3 degrees)
];
// Calculate base direction once
var baseDx = targetX - bulletStartX;
var baseDy = targetY - bulletStartY;
var baseDistance = Math.sqrt(baseDx * baseDx + baseDy * baseDy);
if (baseDistance > 0) {
// Normalize base direction
baseDx /= baseDistance;
baseDy /= baseDistance;
for (var i = 0; i < bulletCount; i++) {
var bullet = new Bullet();
bullet.x = bulletStartX;
bullet.y = bulletStartY;
bullet.damage = weapon.damage;
bullet.speed = weapon.bulletSpeed;
// Apply pre-calculated spread using simple rotation
var spread = spreadDirections[i];
var finalDx = baseDx * spread.dy + baseDy * spread.dx;
var finalDy = baseDy * spread.dy - baseDx * spread.dx;
// Set target at fixed distance
bullet.setTarget(bulletStartX + finalDx * 300, bulletStartY + finalDy * 300);
bullets.push(bullet);
game.addChild(bullet);
}
} else {
// Fallback for zero distance
for (var j = 0; j < bulletCount; j++) {
var bullet = new Bullet();
bullet.x = bulletStartX;
bullet.y = bulletStartY;
bullet.damage = weapon.damage;
bullet.speed = weapon.bulletSpeed;
bullet.setTarget(targetX, targetY);
bullets.push(bullet);
game.addChild(bullet);
}
}
} else {
// Single bullet for other weapons
var bullet = new Bullet();
bullet.x = bulletStartX;
bullet.y = bulletStartY;
bullet.damage = weapon.damage;
bullet.speed = weapon.bulletSpeed;
bullet.setTarget(targetX, targetY);
bullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('weaponShoot').play();
// Weapon recoil animation - store original position
var originalWeaponY = weaponGraphics.y;
tween(weaponGraphics, {
y: originalWeaponY + 5
}, {
duration: 50,
onComplete: function onComplete() {
tween(weaponGraphics, {
y: originalWeaponY
}, {
duration: 50
});
}
});
};
self.isWalking = false;
self.moveSpeed = 4;
self.analogMove = function (deltaX, deltaY) {
// Analog movement with wall collision detection
var newX = self.x + deltaX * self.moveSpeed;
var newY = self.y + deltaY * self.moveSpeed;
// Check wall collisions - map boundaries
var mapWidth = 8192;
var mapHeight = 10928;
var wallThickness = 100;
// Prevent going through walls
if (newX < wallThickness + self.hitboxWidth / 2) {
newX = wallThickness + self.hitboxWidth / 2;
}
if (newX > mapWidth - wallThickness - self.hitboxWidth / 2) {
newX = mapWidth - wallThickness - self.hitboxWidth / 2;
}
if (newY < wallThickness + self.hitboxHeight / 2) {
newY = wallThickness + self.hitboxHeight / 2;
}
if (newY > mapHeight - wallThickness - self.hitboxHeight / 2) {
newY = mapHeight - wallThickness - self.hitboxHeight / 2;
}
// Check collision with enemies
var collisionFound = false;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyDx = newX - enemy.x;
var enemyDy = newY - enemy.y;
var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
var minDistance = (self.hitboxWidth + enemy.width) / 2 + 20; // Add buffer
if (enemyDistance < minDistance) {
collisionFound = true;
// Push hero away from enemy
if (enemyDistance > 0) {
var pushForce = minDistance - enemyDistance;
var pushX = enemyDx / enemyDistance * pushForce;
var pushY = enemyDy / enemyDistance * pushForce;
newX += pushX;
newY += pushY;
}
}
}
// Apply movement only if safe or after collision resolution
self.x = newX;
self.y = newY;
};
self.walkTo = function (targetX, targetY) {
// Don't start new walk if already walking
if (self.isWalking) {
tween.stop(self, {
x: true,
y: true
});
}
self.isWalking = true;
// Check wall collisions and clamp target position
var mapWidth = 8192;
var mapHeight = 10928;
var wallThickness = 100;
// Clamp target position within map boundaries
if (targetX < wallThickness + self.hitboxWidth / 2) {
targetX = wallThickness + self.hitboxWidth / 2;
}
if (targetX > mapWidth - wallThickness - self.hitboxWidth / 2) {
targetX = mapWidth - wallThickness - self.hitboxWidth / 2;
}
if (targetY < wallThickness + self.hitboxHeight / 2) {
targetY = wallThickness + self.hitboxHeight / 2;
}
if (targetY > mapHeight - wallThickness - self.hitboxHeight / 2) {
targetY = mapHeight - wallThickness - self.hitboxHeight / 2;
}
// Calculate distance for duration
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var duration = distance * 2; // Adjust speed by changing multiplier
// Walking animation with slight bounce
tween(heroGraphics, {
scaleY: 0.9
}, {
duration: duration / 4,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(heroGraphics, {
scaleY: 1.1
}, {
duration: duration / 4,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(heroGraphics, {
scaleY: 1.0
}, {
duration: duration / 2,
easing: tween.easeOut
});
}
});
}
});
// Check if target position collides with enemies
var safeTargetX = targetX;
var safeTargetY = targetY;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyDx = safeTargetX - enemy.x;
var enemyDy = safeTargetY - enemy.y;
var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
var minDistance = (self.hitboxWidth + enemy.width) / 2 + 30; // Safety buffer
if (enemyDistance < minDistance && enemyDistance > 0) {
// Move target position away from enemy
var pushDistance = minDistance - enemyDistance;
safeTargetX += enemyDx / enemyDistance * pushDistance;
safeTargetY += enemyDy / enemyDistance * pushDistance;
}
}
// Move to safe target position
tween(self, {
x: safeTargetX,
y: safeTargetY
}, {
duration: duration,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isWalking = false;
}
});
};
// Custom collision check method using hitbox
self.checkCollision = function (other) {
// Calculate hitbox bounds for hero
var heroLeft = self.x - self.hitboxWidth / 2 + self.hitboxOffsetX;
var heroRight = self.x + self.hitboxWidth / 2 + self.hitboxOffsetX;
var heroTop = self.y - self.hitboxHeight / 2 + self.hitboxOffsetY;
var heroBottom = self.y + self.hitboxHeight / 2 + self.hitboxOffsetY;
// Get other object bounds (assuming standard bounds)
var otherLeft = other.x - other.width / 2;
var otherRight = other.x + other.width / 2;
var otherTop = other.y - other.height / 2;
var otherBottom = other.y + other.height / 2;
// Check for overlap
return heroLeft < otherRight && heroRight > otherLeft && heroTop < otherBottom && heroBottom > otherTop;
};
// Debug method to visualize hitbox (can be called for testing)
self.showHitbox = function () {
if (!self.hitboxVisual) {
self.hitboxVisual = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5
});
self.hitboxVisual.width = self.hitboxWidth;
self.hitboxVisual.height = self.hitboxHeight;
self.hitboxVisual.x = self.hitboxOffsetX;
self.hitboxVisual.y = self.hitboxOffsetY;
self.hitboxVisual.alpha = 0.3;
self.hitboxVisual.tint = 0x00FF00;
self.addChild(self.hitboxVisual);
}
};
return self;
});
var LevelPickup = Container.expand(function () {
var self = Container.call(this);
var pickupGraphics = self.attachAsset('levelBar', {
anchorX: 0.5,
anchorY: 0.5
});
pickupGraphics.scaleX = 0.4;
pickupGraphics.scaleY = 0.4;
pickupGraphics.tint = 0xFFD700; // Gold color for level up
// Add glowing effect
var glowGraphics = self.attachAsset('levelBar', {
anchorX: 0.5,
anchorY: 0.5
});
glowGraphics.tint = 0xFFFF00; // Yellow glow
glowGraphics.alpha = 0.3;
glowGraphics.scaleX = 0.5;
glowGraphics.scaleY = 0.5;
// Add pulsing animation
tween(pickupGraphics, {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(pickupGraphics, {
scaleX: 0.4,
scaleY: 0.4
}, {
duration: 600,
easing: tween.easeInOut
});
}
});
// Rotating glow effect
tween(glowGraphics, {
rotation: Math.PI * 2
}, {
duration: 2000,
easing: tween.linear,
onFinish: function onFinish() {
tween(glowGraphics, {
rotation: 0
}, {
duration: 2000,
easing: tween.linear
});
}
});
self.update = function () {
// Check if collected by hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 40) {
// Level up player
playerLevel++;
currentXP = 0;
xpToNextLevel = playerLevel * 30;
levelText.setText('Level ' + playerLevel);
// Level up effects
LK.effects.flashScreen(0xFFD700, 500);
hero.maxHealth += 3;
hero.health = hero.maxHealth; // Full heal on level up
updateHealthBar();
updateLevelBar();
LK.getSound('xpCollect').play();
LK.effects.flashObject(hero, 0xFFD700, 500); // Gold flash
// Show upgrade selection
showUpgradeSelection();
// Remove from levelPickups array
for (var i = levelPickups.length - 1; i >= 0; i--) {
if (levelPickups[i] === self) {
levelPickups.splice(i, 1);
break;
}
}
self.destroy();
}
};
return self;
});
var Lightning = Container.expand(function () {
var self = Container.call(this);
var lightningGraphics = self.attachAsset('lightningBolt', {
anchorX: 0.5,
anchorY: 0.5
});
// Add glowing effect to lightning bolt
var glowGraphics = self.attachAsset('lightningBolt', {
anchorX: 0.5,
anchorY: 0.5
});
glowGraphics.tint = 0xFFFF00; // Yellow glow
glowGraphics.alpha = 0.5;
glowGraphics.scaleX = 1.3;
glowGraphics.scaleY = 1.3;
self.speed = 7;
self.damage = 30;
// Lightning bolt hitbox properties
self.width = 80; // Lightning visual width
self.height = 200; // Lightning visual height
self.targetX = 0;
self.targetY = 0;
self.directionX = 0;
self.directionY = 0;
self.lifetime = 0;
self.maxLifetime = 90; // 1.5 seconds at 60fps
// Lightning crackling effect
tween(lightningGraphics, {
alpha: 0.3
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(lightningGraphics, {
alpha: 1.0
}, {
duration: 100,
easing: tween.easeInOut
});
}
});
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
var dx = x - self.x;
var dy = y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.directionX = dx / distance;
self.directionY = dy / distance;
}
// Rotate lightning to face direction
lightningGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
glowGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
};
self.update = function () {
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
self.lifetime++;
// Random flickering effect
if (Math.random() < 0.3) {
lightningGraphics.alpha = Math.random() * 0.5 + 0.5;
}
// Remove lightning after lifetime expires or goes off screen
if (self.lifetime > self.maxLifetime || self.x < -50 || self.x > 8242 || self.y < -50 || self.y > 10978) {
self.destroy();
for (var i = lightningBolts.length - 1; i >= 0; i--) {
if (lightningBolts[i] === self) {
lightningBolts.splice(i, 1);
break;
}
}
}
};
return self;
});
var MapDecoration = Container.expand(function () {
var self = Container.call(this);
self.decorationType = '';
self.setDecoration = function (type) {
self.decorationType = type;
var decorationGraphics;
switch (type) {
case 'tree':
decorationGraphics = self.attachAsset('tree', {
anchorX: 0.5,
anchorY: 1.0
});
decorationGraphics.tint = 0x228B22;
// Add slight random rotation for variety
decorationGraphics.rotation = (Math.random() - 0.5) * 0.3;
break;
case 'grass':
decorationGraphics = self.attachAsset('grass', {
anchorX: 0.5,
anchorY: 0.5
});
decorationGraphics.tint = 0x32CD32;
decorationGraphics.scaleX = 0.8 + Math.random() * 0.4;
decorationGraphics.scaleY = 0.8 + Math.random() * 0.4;
break;
case 'campfire':
decorationGraphics = self.attachAsset('campfire', {
anchorX: 0.5,
anchorY: 0.5
});
decorationGraphics.tint = 0xFF4500;
// Add flickering animation to campfire
tween(decorationGraphics, {
scaleX: 1.1,
scaleY: 1.1,
alpha: 0.8
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(decorationGraphics, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
break;
}
// Make decorations non-interactive and lower layer
self.alpha = 0.7;
};
return self;
});
var Pet = Container.expand(function () {
var self = Container.call(this);
var petGraphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
petGraphics.scaleX = 0.4;
petGraphics.scaleY = 0.4;
petGraphics.alpha = 0.8;
self.petType = '';
self.owner = null;
self.followDistance = 80;
self.ability = '';
self.abilityTimer = 0;
self.abilityCooldown = 300; // 5 seconds
self.setPetType = function (type) {
self.petType = type;
switch (type) {
case 'healing':
petGraphics.tint = 0x00FF00; // Green for healing
self.ability = 'heal';
break;
case 'attack':
petGraphics.tint = 0xFF0000; // Red for attack
self.ability = 'attack';
break;
case 'shield':
petGraphics.tint = 0x0000FF; // Blue for shield
self.ability = 'shield';
break;
}
};
self.update = function () {
if (!self.owner) return;
// Follow owner
var dx = self.owner.x - self.x;
var dy = self.owner.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.followDistance) {
var moveSpeed = 3;
self.x += dx / distance * moveSpeed;
self.y += dy / distance * moveSpeed;
}
// Use ability
self.abilityTimer++;
if (self.abilityTimer >= self.abilityCooldown) {
self.useAbility();
self.abilityTimer = 0;
}
};
self.useAbility = function () {
switch (self.ability) {
case 'heal':
if (self.owner.health < self.owner.maxHealth) {
self.owner.health = Math.min(self.owner.health + 5, self.owner.maxHealth);
LK.effects.flashObject(self.owner, 0x00FF00, 200);
updateHealthBar();
}
break;
case 'attack':
// Find nearest enemy and attack
var nearestEnemy = null;
var nearestDistance = 200;
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) {
nearestEnemy = enemy;
nearestDistance = distance;
}
}
if (nearestEnemy) {
nearestEnemy.takeDamage(15);
LK.effects.flashObject(self, 0xFF0000, 200);
}
break;
case 'shield':
// Provide temporary damage reduction
if (!self.owner.hasShield) {
self.owner.hasShield = true;
self.owner.shieldTime = 180; // 3 seconds
LK.effects.flashObject(self.owner, 0x0000FF, 200);
}
break;
}
};
return self;
});
var SpellEffect = Container.expand(function () {
var self = Container.call(this);
self.spellType = '';
self.duration = 0;
self.power = 0;
self.target = null;
self.lifetime = 0;
self.setSpellType = function (type) {
self.spellType = type;
switch (type) {
case 'fireball':
self.power = 50;
self.duration = 1;
break;
case 'freeze':
self.power = 20;
self.duration = 180; // 3 seconds
break;
case 'heal':
self.power = 30;
self.duration = 1;
break;
case 'shield':
self.power = 0;
self.duration = 300; // 5 seconds
break;
}
};
self.cast = function (caster, target) {
self.target = target;
switch (self.spellType) {
case 'fireball':
if (target && target.takeDamage) {
target.takeDamage(self.power);
LK.effects.flashObject(target, 0xFF4500, 300);
}
break;
case 'freeze':
if (target && target.speed) {
target.frozen = true;
target.originalSpeed = target.speed;
target.speed = 0;
LK.effects.flashObject(target, 0x87CEEB, 300);
}
break;
case 'heal':
if (target && target.health && target.maxHealth) {
target.health = Math.min(target.health + self.power, target.maxHealth);
LK.effects.flashObject(target, 0x00FF00, 300);
if (target === hero) updateHealthBar();
}
break;
case 'shield':
if (target) {
target.hasShield = true;
target.shieldTime = self.duration;
LK.effects.flashObject(target, 0x4169E1, 300);
}
break;
}
};
self.update = function () {
self.lifetime++;
if (self.lifetime >= self.duration) {
if (self.spellType === 'freeze' && self.target && self.target.frozen) {
self.target.frozen = false;
self.target.speed = self.target.originalSpeed;
}
self.destroy();
}
};
return self;
});
var StrongEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('strongEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Weapon graphics removed
self.health = 50;
self.maxHealth = 50;
self.damage = 25;
self.speed = 2;
self.scoreValue = 25;
// Enemy hitbox properties
self.width = 300; // Enemy visual width
self.height = 300; // Enemy visual height
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
// Drop XP orb
var xpOrb = new XPOrb();
xpOrb.x = self.x;
xpOrb.y = self.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
// 50% chance to drop coin
if (Math.random() < 0.5) {
var coin = new CoinPickup();
coin.x = self.x + (Math.random() - 0.5) * 60;
coin.y = self.y + (Math.random() - 0.5) * 60;
coinPickups.push(coin);
game.addChild(coin);
}
self.destroy();
LK.setScore(LK.getScore() + self.scoreValue);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDestroy').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
// updateWeapon method removed
self.update = function () {
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Weapon update removed
if (distance > 0) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
// Check collision with other enemies
var collisionFound = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self) {
var otherDx = newX - otherEnemy.x;
var otherDy = newY - otherEnemy.y;
var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy);
var minDistance = (self.width + otherEnemy.width) / 4; // Collision threshold
if (otherDistance < minDistance) {
collisionFound = true;
// Push enemies apart
if (otherDistance > 0) {
var pushForce = (minDistance - otherDistance) / 2;
var pushX = otherDx / otherDistance * pushForce;
var pushY = otherDy / otherDistance * pushForce;
newX += pushX;
newY += pushY;
otherEnemy.x -= pushX;
otherEnemy.y -= pushY;
}
}
}
}
// Only move if no major collision or after separation
if (!collisionFound || distance > 100) {
self.x = newX;
self.y = newY;
}
}
};
return self;
});
var UpgradeOption = Container.expand(function () {
var self = Container.call(this);
// Background for upgrade option - made larger
var bgGraphics = self.attachAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
bgGraphics.scaleX = 3.5;
bgGraphics.scaleY = 4;
bgGraphics.tint = 0x2c3e50;
// Title text - made larger
self.titleText = new Text2('', {
size: 80,
fill: 0xFFD700
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.y = -100;
self.addChild(self.titleText);
// Description text - made larger
self.descText = new Text2('', {
size: 60,
fill: 0xFFFFFF
});
self.descText.anchor.set(0.5, 0.5);
self.descText.y = 0;
self.addChild(self.descText);
// Upgrade type
self.upgradeType = '';
self.setUpgrade = function (type, title, description) {
self.upgradeType = type;
self.titleText.setText(title);
self.descText.setText(description);
};
// Hover effect
self.down = function (x, y, obj) {
tween(bgGraphics, {
scaleX: 3.8,
scaleY: 4.3
}, {
duration: 100,
easing: tween.easeOut
});
};
self.up = function (x, y, obj) {
tween(bgGraphics, {
scaleX: 3.5,
scaleY: 4
}, {
duration: 100,
easing: tween.easeIn
});
// Apply the upgrade
applyUpgrade(self.upgradeType);
};
return self;
});
var Wizard = Container.expand(function () {
var self = Container.call(this);
var wizardGraphics = self.attachAsset('Wizard', {
anchorX: 0.5,
anchorY: 0.5
});
// Weapon graphics removed
// Add magical aura effect using the wizard asset
var auraGraphics = self.attachAsset('Wizard', {
anchorX: 0.5,
anchorY: 0.5
});
auraGraphics.tint = 0x9370DB; // Purple aura
auraGraphics.alpha = 0.3;
auraGraphics.scaleX = 1.3;
auraGraphics.scaleY = 1.3;
// Add pulsing magical aura animation
tween(auraGraphics, {
scaleX: 1.8,
scaleY: 1.8,
alpha: 0.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(auraGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
self.health = 60;
self.maxHealth = 60;
self.damage = 20;
self.speed = 1.8;
self.scoreValue = 30;
// Wizard hitbox properties
self.width = 310; // Wizard visual width
self.height = 310; // Wizard visual height
self.lastLightningTime = 0;
self.lightningCooldown = 60; // 1 second at 60fps - much faster attack speed
self.lightningRange = 600;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
// Drop XP orb
var xpOrb = new XPOrb();
xpOrb.x = self.x;
xpOrb.y = self.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
// 50% chance to drop coin
if (Math.random() < 0.5) {
var coin = new CoinPickup();
coin.x = self.x + (Math.random() - 0.5) * 60;
coin.y = self.y + (Math.random() - 0.5) * 60;
coinPickups.push(coin);
game.addChild(coin);
}
self.destroy();
LK.setScore(LK.getScore() + self.scoreValue);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDestroy').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
self.shootLightning = function () {
// Create lightning bolt
var lightning = new Lightning();
lightning.x = self.x;
lightning.y = self.y;
lightning.setTarget(hero.x, hero.y);
lightningBolts.push(lightning);
game.addChild(lightning);
// Enhanced lightning casting effects
LK.effects.flashObject(self, 0xFFFF00, 300);
LK.effects.flashObject(auraGraphics, 0xFFFFFF, 400);
// Dramatic casting animation - wizard grows larger then returns
tween(wizardGraphics, {
scaleX: 1.5,
scaleY: 1.5,
rotation: wizardGraphics.rotation + Math.PI / 6
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(wizardGraphics, {
scaleX: 1.2,
scaleY: 1.2,
rotation: wizardGraphics.rotation - Math.PI / 6
}, {
duration: 250,
easing: tween.easeIn
});
}
});
// Aura intensifies during casting
tween(auraGraphics, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0.6
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(auraGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 300,
easing: tween.easeIn
});
}
});
};
self.update = function () {
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Weapon update removed
// Keep distance and shoot lightning
if (distance < self.lightningRange && distance > 150) {
// Stop moving and prepare to shoot
var currentTime = LK.ticks;
if (currentTime - self.lastLightningTime > self.lightningCooldown) {
self.shootLightning();
self.lastLightningTime = currentTime;
}
} else if (distance > self.lightningRange) {
// Move closer to hero
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
} else if (distance < 150) {
// Move away from hero
if (distance > 0) {
self.x -= dx / distance * self.speed;
self.y -= dy / distance * self.speed;
}
}
};
return self;
});
var XPOrb = Container.expand(function () {
var self = Container.call(this);
var orbGraphics = self.attachAsset('xpOrb', {
anchorX: 0.5,
anchorY: 0.5
});
self.xpValue = 5;
self.moveSpeed = 2;
self.attractRange = 150;
// Add pulsing animation to make XP orbs more attractive
tween(orbGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(orbGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
tween(orbGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
});
self.update = function () {
// XP orb magnetic attraction to other nearby orbs
for (var o = 0; o < xpOrbs.length; o++) {
var otherOrb = xpOrbs[o];
if (otherOrb !== self) {
var orbDx = otherOrb.x - self.x;
var orbDy = otherOrb.y - self.y;
var orbDistance = Math.sqrt(orbDx * orbDx + orbDy * orbDy);
// Attract to nearby orbs within 80 pixels
if (orbDistance < 80 && orbDistance > 0) {
var attractForce = 0.5;
self.x += orbDx / orbDistance * attractForce;
self.y += orbDy / orbDistance * attractForce;
}
}
}
// Move toward hero if within attract range
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.attractRange) {
if (distance > 0) {
self.x += dx / distance * self.moveSpeed;
self.y += dy / distance * self.moveSpeed;
}
}
// Check if collected by hero
if (distance < 30) {
// Give XP to player
currentXP += self.xpValue;
LK.setScore(LK.getScore() + self.xpValue);
scoreTxt.setText(LK.getScore());
LK.getSound('xpCollect').play();
// Remove from xpOrbs array
for (var i = xpOrbs.length - 1; i >= 0; i--) {
if (xpOrbs[i] === self) {
xpOrbs.splice(i, 1);
break;
}
}
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Menu system variables
var gameState = 'menu'; // 'menu', 'playing', 'paused', 'dead'
var menuContainer;
var gameContainer;
var deathMenuContainer;
var weaponSelectContainer;
var hero;
var enemies = [];
var bullets = [];
var xpOrbs = [];
var lightningBolts = [];
var bossBullets = [];
var healthCrates = [];
var healthPickups = [];
var levelPickups = [];
var coinPickups = [];
var pets = [];
var armorPieces = [];
var spellEffects = [];
var equippedArmor = null;
var activePet = null;
var knownSpells = ['heal'];
var coinsText;
var shopContainer;
var scoreboardContainer;
var currentShopCategory = 'weapons'; // 'weapons', 'pets', 'armor', 'spells'
var spawnTimer = 0;
var difficultyLevel = 1;
var spawnedChunks = {}; // Track spawned decoration chunks
var scoreTxt;
var healthBar;
var healthBarBg;
var attackIndicator;
var lastShootTime = 0;
var joystickBase;
var joystickKnob;
var isJoystickActive = false;
var joystickStartX = 0;
var joystickStartY = 0;
var joystickRadius = 80;
var movementX = 0;
var movementY = 0;
var healthText;
var levelBar;
var levelBarBg;
var levelText;
var playerLevel = 1;
var currentXP = 0;
var xpToNextLevel = 30;
var currentWave = 1;
var waveStartTime = 0;
var waveDuration = 60000; // 1 minute in milliseconds
var waveEnemyCount = 0;
var baseEnemiesPerWave = 20;
var waveText;
var waveTimer;
var bossSpawned = false;
var bossHealthBarBg;
var bossHealthBar;
var bossHealthText;
var upgradeSelectionContainer;
var isSelectingUpgrade = false;
var availableUpgrades = [{
type: 'health',
title: 'Health Boost',
description: '+20 Max Health'
}, {
type: 'damage',
title: 'Damage Up',
description: '+10 Attack Damage'
}, {
type: 'speed',
title: 'Speed Boost',
description: '+1 Movement Speed'
}, {
type: 'attackSpeed',
title: 'Attack Speed',
description: 'Faster Shooting'
}, {
type: 'bulletSpeed',
title: 'Bullet Speed',
description: 'Faster Bullets'
}, {
type: 'regen',
title: 'Regeneration',
description: 'Heal 1 HP/sec'
}];
// Weapon types
var weaponTypes = {
pistol: {
name: 'Tabanca',
damage: 25,
shootCooldown: 80,
bulletSpeed: 8,
description: 'Standart silah'
},
rifle: {
name: 'Tüfek',
damage: 40,
shootCooldown: 120,
bulletSpeed: 12,
description: 'Yüksek hasar, yavaş atış'
},
shotgun: {
name: 'Pompalı',
damage: 25,
shootCooldown: 100,
bulletSpeed: 6,
description: '3 mermi, yakın mesafe'
},
machinegun: {
name: 'Makineli',
damage: 8,
shootCooldown: 20,
bulletSpeed: 10,
description: 'Çok hızlı atış'
},
sniper: {
name: 'Keskin Nişancı',
damage: 80,
shootCooldown: 180,
bulletSpeed: 15,
description: 'Yüksek hasar, çok yavaş'
},
laser: {
name: 'Lazer Silahı',
damage: 35,
shootCooldown: 40,
bulletSpeed: 20,
description: 'Hızlı ve hassas'
},
explosive: {
name: 'Patlayıcı',
damage: 50,
shootCooldown: 150,
bulletSpeed: 5,
description: 'Alan hasarı verir'
}
};
var currentWeapon = 'pistol';
var lastRegenTime = 0;
var hasRegen = false;
var mapDecorations = [];
function createEntryMenu() {
// Create menu container
menuContainer = new Container();
game.addChild(menuContainer);
// Create menu background
var menuBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
menuBg.x = 2048 / 2;
menuBg.y = 2732 / 2;
menuBg.scaleX = 8;
menuBg.scaleY = 8;
menuBg.tint = 0x1a1a2e;
menuBg.alpha = 0.9;
menuContainer.addChild(menuBg);
// Create game title
var gameTitle = new Text2('SAVAŞ ALANINDA', {
size: 120,
fill: 0xFFD700
});
gameTitle.anchor.set(0.5, 0.5);
gameTitle.x = 2048 / 2;
gameTitle.y = 2732 / 2 - 200;
menuContainer.addChild(gameTitle);
// Create subtitle
var gameSubtitle = new Text2('Hayatta Kal!', {
size: 80,
fill: 0xFFFFFF
});
gameSubtitle.anchor.set(0.5, 0.5);
gameSubtitle.x = 2048 / 2;
gameSubtitle.y = 2732 / 2 - 100;
menuContainer.addChild(gameSubtitle);
// Create play button background
var playButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
playButtonBg.x = 2048 / 2;
playButtonBg.y = 2732 / 2 + 100;
playButtonBg.scaleX = 4;
playButtonBg.scaleY = 3;
playButtonBg.tint = 0x27ae60;
menuContainer.addChild(playButtonBg);
// Create play button text
var playButtonText = new Text2('OYNA', {
size: 90,
fill: 0xFFFFFF
});
playButtonText.anchor.set(0.5, 0.5);
playButtonText.x = 2048 / 2;
playButtonText.y = 2732 / 2 + 100;
menuContainer.addChild(playButtonText);
// Add button interaction
playButtonBg.down = function (x, y, obj) {
// Button press animation
tween(playButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
playButtonBg.up = function (x, y, obj) {
// Button release animation and start game
tween(playButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
startGame();
}
});
};
// Create weapon select button background
var weaponButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
weaponButtonBg.x = 2048 / 2;
weaponButtonBg.y = 2732 / 2 + 200;
weaponButtonBg.scaleX = 4;
weaponButtonBg.scaleY = 3;
weaponButtonBg.tint = 0xe67e22;
menuContainer.addChild(weaponButtonBg);
// Create weapon select button text
var weaponButtonText = new Text2('SİLAH SEÇ', {
size: 70,
fill: 0xFFFFFF
});
weaponButtonText.anchor.set(0.5, 0.5);
weaponButtonText.x = 2048 / 2;
weaponButtonText.y = 2732 / 2 + 200;
menuContainer.addChild(weaponButtonText);
// Create shop button background
var shopButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
shopButtonBg.x = 2048 / 2;
shopButtonBg.y = 2732 / 2 + 300;
shopButtonBg.scaleX = 4;
shopButtonBg.scaleY = 3;
shopButtonBg.tint = 0x9b59b6;
menuContainer.addChild(shopButtonBg);
// Create shop button text
var shopButtonText = new Text2('DÜKKAN', {
size: 70,
fill: 0xFFFFFF
});
shopButtonText.anchor.set(0.5, 0.5);
shopButtonText.x = 2048 / 2;
shopButtonText.y = 2732 / 2 + 300;
menuContainer.addChild(shopButtonText);
// Create scoreboard button background
var scoreboardButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
scoreboardButtonBg.x = 2048 / 2;
scoreboardButtonBg.y = 2732 / 2 + 400;
scoreboardButtonBg.scaleX = 4;
scoreboardButtonBg.scaleY = 3;
scoreboardButtonBg.tint = 0x34495e;
menuContainer.addChild(scoreboardButtonBg);
// Create scoreboard button text
var scoreboardButtonText = new Text2('SKOR TABLOSU', {
size: 60,
fill: 0xFFFFFF
});
scoreboardButtonText.anchor.set(0.5, 0.5);
scoreboardButtonText.x = 2048 / 2;
scoreboardButtonText.y = 2732 / 2 + 400;
menuContainer.addChild(scoreboardButtonText);
// Add weapon button interaction
weaponButtonBg.down = function (x, y, obj) {
tween(weaponButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
weaponButtonBg.up = function (x, y, obj) {
tween(weaponButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
showWeaponSelect();
}
});
};
// Add shop button interaction
shopButtonBg.down = function (x, y, obj) {
tween(shopButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
shopButtonBg.up = function (x, y, obj) {
tween(shopButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
showShop();
}
});
};
// Add scoreboard button interaction
scoreboardButtonBg.down = function (x, y, obj) {
tween(scoreboardButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
scoreboardButtonBg.up = function (x, y, obj) {
tween(scoreboardButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
showScoreboard();
}
});
};
// Create decorative elements around menu
for (var i = 0; i < 6; i++) {
var decorElement = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
var angle = Math.PI * 2 / 6 * i;
var radius = 300;
decorElement.x = 2048 / 2 + Math.cos(angle) * radius;
decorElement.y = 2732 / 2 + Math.sin(angle) * radius;
decorElement.alpha = 0.3;
decorElement.scaleX = 0.8;
decorElement.scaleY = 0.8;
decorElement.tint = 0x3498db;
menuContainer.addChild(decorElement);
// Add rotation animation
tween(decorElement, {
rotation: Math.PI * 2
}, {
duration: 3000 + i * 500,
easing: tween.linear,
onFinish: function onFinish() {
this.rotation = 0;
}
});
}
}
function showWeaponSelect() {
// Hide menu
menuContainer.visible = false;
gameState = 'weaponSelect';
// Create weapon select container
weaponSelectContainer = new Container();
game.addChild(weaponSelectContainer);
// Create background
var selectBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
selectBg.x = 2048 / 2;
selectBg.y = 2732 / 2;
selectBg.scaleX = 10;
selectBg.scaleY = 10;
selectBg.tint = 0x2c3e50;
selectBg.alpha = 0.9;
weaponSelectContainer.addChild(selectBg);
// Create title
var selectTitle = new Text2('SİLAH SEÇİMİ VE MARKET', {
size: 100,
fill: 0xFFD700
});
selectTitle.anchor.set(0.5, 0.5);
selectTitle.x = 2048 / 2;
selectTitle.y = 250;
weaponSelectContainer.addChild(selectTitle);
// Create coins display
var selectCoinsText = new Text2('Paralar: ' + (storage.coins || 0), {
size: 60,
fill: 0xFFD700
});
selectCoinsText.anchor.set(0.5, 0.5);
selectCoinsText.x = 2048 / 2;
selectCoinsText.y = 320;
weaponSelectContainer.addChild(selectCoinsText);
// Get owned weapons and current coins
var ownedWeapons = storage.ownedWeapons || ['pistol'];
var currentCoins = storage.coins || 0;
// Create weapon options - show all weapons
var weaponIndex = 0;
for (var weaponType in weaponTypes) {
var weapon = weaponTypes[weaponType];
var weaponY = 450 + weaponIndex * 180;
var isOwned = ownedWeapons.indexOf(weaponType) !== -1;
var isSelected = currentWeapon === weaponType;
// Weapon background
var weaponBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
weaponBg.x = 2048 / 2;
weaponBg.y = weaponY;
weaponBg.scaleX = 6;
weaponBg.scaleY = 2.2;
// Set background color based on status
if (isSelected) {
weaponBg.tint = 0x27ae60; // Green for selected
} else if (isOwned) {
weaponBg.tint = 0x3498db; // Blue for owned
} else {
weaponBg.tint = 0x95a5a6; // Gray for not owned
}
weaponSelectContainer.addChild(weaponBg);
// Weapon name
var weaponName = new Text2(weapon.name, {
size: 55,
fill: 0xFFFFFF
});
weaponName.anchor.set(0, 0.5);
weaponName.x = 2048 / 2 - 450;
weaponName.y = weaponY;
weaponSelectContainer.addChild(weaponName);
// Weapon description
var weaponDesc = new Text2(weapon.description, {
size: 38,
fill: 0xBDC3C7
});
weaponDesc.anchor.set(0, 0.5);
weaponDesc.x = 2048 / 2 - 150;
weaponDesc.y = weaponY;
weaponSelectContainer.addChild(weaponDesc);
// Status/price text
var statusText;
if (isOwned) {
if (isSelected) {
statusText = new Text2('SEÇİLİ', {
size: 45,
fill: 0x2ecc71
});
} else {
statusText = new Text2('SEÇ', {
size: 45,
fill: 0x3498db
});
}
} else {
statusText = new Text2('ÜCRETSİZ', {
size: 45,
fill: 0xf39c12
});
}
statusText.anchor.set(1, 0.5);
statusText.x = 2048 / 2 + 450;
statusText.y = weaponY;
weaponSelectContainer.addChild(statusText);
// Create closure to capture weapon type
(function (type, bg, weapon, isOwned, coinsDisplay) {
bg.down = function (x, y, obj) {
tween(bg, {
scaleX: 6.2,
scaleY: 2.4
}, {
duration: 100,
easing: tween.easeOut
});
};
bg.up = function (x, y, obj) {
tween(bg, {
scaleX: 6,
scaleY: 2.2
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
handleWeaponSelectPurchase(type, weapon, isOwned, coinsDisplay);
}
});
};
})(weaponType, weaponBg, weapon, isOwned, selectCoinsText);
weaponIndex++;
}
// Create back button
var backButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
backButtonBg.x = 2048 / 2;
backButtonBg.y = 2732 - 150;
backButtonBg.scaleX = 3;
backButtonBg.scaleY = 2;
backButtonBg.tint = 0x95a5a6;
weaponSelectContainer.addChild(backButtonBg);
var backButtonText = new Text2('GERİ', {
size: 60,
fill: 0xFFFFFF
});
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = 2048 / 2;
backButtonText.y = 2732 - 150;
weaponSelectContainer.addChild(backButtonText);
backButtonBg.down = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3.2,
scaleY: 2.2
}, {
duration: 100,
easing: tween.easeOut
});
};
backButtonBg.up = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3,
scaleY: 2
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
hideWeaponSelect();
}
});
};
}
function hideWeaponSelect() {
weaponSelectContainer.destroy();
weaponSelectContainer = null;
// Update weapon graphics if hero exists
if (hero) {
hero.updateWeaponGraphics();
hero.lastWeapon = currentWeapon;
}
menuContainer.visible = true;
gameState = 'menu';
}
function startGame() {
// Hide menu
menuContainer.visible = false;
// Change game state
gameState = 'playing';
// Initialize game elements
initializeGame();
}
function createDeathMenu() {
// Create death menu container
deathMenuContainer = new Container();
game.addChild(deathMenuContainer);
// Create death menu background
var deathMenuBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
deathMenuBg.x = 2048 / 2;
deathMenuBg.y = 2732 / 2;
deathMenuBg.scaleX = 8;
deathMenuBg.scaleY = 8;
deathMenuBg.tint = 0x8B0000; // Dark red background
deathMenuBg.alpha = 0.9;
deathMenuContainer.addChild(deathMenuBg);
// Create death message
var deathTitle = new Text2('ÖLDÜN!', {
size: 150,
fill: 0xFF0000
});
deathTitle.anchor.set(0.5, 0.5);
deathTitle.x = 2048 / 2;
deathTitle.y = 2732 / 2 - 250;
deathMenuContainer.addChild(deathTitle);
// Create final score display
var finalScoreText = new Text2('Final Skor: ' + LK.getScore(), {
size: 80,
fill: 0xFFFFFF
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 2732 / 2 - 150;
deathMenuContainer.addChild(finalScoreText);
// Create wave reached display
var waveReachedText = new Text2('Ulaşılan Dalga: ' + currentWave, {
size: 60,
fill: 0xFFD700
});
waveReachedText.anchor.set(0.5, 0.5);
waveReachedText.x = 2048 / 2;
waveReachedText.y = 2732 / 2 - 80;
deathMenuContainer.addChild(waveReachedText);
// Create restart button background
var restartButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
restartButtonBg.x = 2048 / 2;
restartButtonBg.y = 2732 / 2 + 50;
restartButtonBg.scaleX = 4;
restartButtonBg.scaleY = 3;
restartButtonBg.tint = 0x27ae60;
deathMenuContainer.addChild(restartButtonBg);
// Create restart button text
var restartButtonText = new Text2('TEKRAR OYNA', {
size: 70,
fill: 0xFFFFFF
});
restartButtonText.anchor.set(0.5, 0.5);
restartButtonText.x = 2048 / 2;
restartButtonText.y = 2732 / 2 + 50;
deathMenuContainer.addChild(restartButtonText);
// Create menu button background
var menuButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
menuButtonBg.x = 2048 / 2;
menuButtonBg.y = 2732 / 2 + 150;
menuButtonBg.scaleX = 4;
menuButtonBg.scaleY = 3;
menuButtonBg.tint = 0x3498db;
deathMenuContainer.addChild(menuButtonBg);
// Create menu button text
var menuButtonText = new Text2('ANA MENÜ', {
size: 70,
fill: 0xFFFFFF
});
menuButtonText.anchor.set(0.5, 0.5);
menuButtonText.x = 2048 / 2;
menuButtonText.y = 2732 / 2 + 150;
deathMenuContainer.addChild(menuButtonText);
// Add restart button interaction
restartButtonBg.down = function (x, y, obj) {
// Button press animation
tween(restartButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
restartButtonBg.up = function (x, y, obj) {
// Button release animation and restart game
tween(restartButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
restartGame();
}
});
};
// Add menu button interaction
menuButtonBg.down = function (x, y, obj) {
// Button press animation
tween(menuButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
menuButtonBg.up = function (x, y, obj) {
// Button release animation and go to main menu
tween(menuButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
returnToMainMenu();
}
});
};
}
function initializeGame() {
// Create game container for all game elements
gameContainer = new Container();
game.addChild(gameContainer);
// Initialize all game UI and elements here
initializeGameUI();
initializeHero();
createMapBoundaries();
generateMapDecorations();
}
function createMapBoundaries() {
// Define map boundaries - large but bounded world
var mapWidth = 8192; // 4x screen width
var mapHeight = 10928; // 4x screen height
var wallThickness = 100;
// Create top wall
var topWall = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
topWall.x = mapWidth / 2;
topWall.y = 0;
topWall.scaleX = 2;
topWall.scaleY = 2.5;
topWall.tint = 0x404040;
gameContainer.addChild(topWall);
// Create bottom wall
var bottomWall = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
bottomWall.x = mapWidth / 2;
bottomWall.y = mapHeight;
bottomWall.scaleX = 2;
bottomWall.scaleY = 2.5;
bottomWall.tint = 0x404040;
gameContainer.addChild(bottomWall);
// Create left wall
var leftWall = LK.getAsset('wallVertical', {
anchorX: 0.5,
anchorY: 0.5
});
leftWall.x = 0;
leftWall.y = mapHeight / 2;
leftWall.scaleX = 2.5;
leftWall.scaleY = 1;
leftWall.tint = 0x404040;
gameContainer.addChild(leftWall);
// Create right wall
var rightWall = LK.getAsset('wallVertical', {
anchorX: 0.5,
anchorY: 0.5
});
rightWall.x = mapWidth;
rightWall.y = mapHeight / 2;
rightWall.scaleX = 2.5;
rightWall.scaleY = 1;
rightWall.tint = 0x404040;
gameContainer.addChild(rightWall);
// Place coins at map corners and edges
var coinPositions = [{
x: 200,
y: 200
},
// Top left corner
{
x: mapWidth - 200,
y: 200
},
// Top right corner
{
x: 200,
y: mapHeight - 200
},
// Bottom left corner
{
x: mapWidth - 200,
y: mapHeight - 200
},
// Bottom right corner
{
x: mapWidth / 2,
y: 200
},
// Top center
{
x: mapWidth / 2,
y: mapHeight - 200
},
// Bottom center
{
x: 200,
y: mapHeight / 2
},
// Left center
{
x: mapWidth - 200,
y: mapHeight / 2
} // Right center
];
for (var i = 0; i < coinPositions.length; i++) {
var coin = new CoinPickup();
coin.x = coinPositions[i].x;
coin.y = coinPositions[i].y;
coin.coinValue = 25; // Higher value coins at map edges
coinPickups.push(coin);
gameContainer.addChild(coin);
}
}
function initializeGameUI() {
// Create score display
scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create health bar background
healthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0
});
healthBarBg.x = 150;
healthBarBg.y = 50;
LK.gui.topLeft.addChild(healthBarBg);
// Create health bar
healthBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0
});
healthBar.x = 150;
healthBar.y = 50;
LK.gui.topLeft.addChild(healthBar);
// Create health text
healthText = new Text2('100/100', {
size: 40,
fill: 0xFFFFFF
});
healthText.anchor.set(0, 0.5);
healthText.x = 370;
healthText.y = 60;
LK.gui.topLeft.addChild(healthText);
// Create level bar background at bottom
levelBarBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
levelBarBg.x = 2048 / 2;
levelBarBg.y = 2732 - 60;
gameContainer.addChild(levelBarBg);
// Create level bar fill
levelBar = LK.getAsset('levelBar', {
anchorX: 0,
anchorY: 0.5
});
levelBar.x = levelBarBg.x - levelBarBg.width / 2;
levelBar.y = levelBarBg.y;
gameContainer.addChild(levelBar);
// Create level text
levelText = new Text2('Level 1', {
size: 50,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0.5);
levelText.x = levelBarBg.x;
levelText.y = levelBarBg.y - 50;
gameContainer.addChild(levelText);
// Create wave display
waveText = new Text2('Wave 1', {
size: 60,
fill: 0xFFD700
});
waveText.anchor.set(1, 0);
waveText.x = 2048 - 20;
waveText.y = 20;
LK.gui.topRight.addChild(waveText);
// Create wave timer display
waveTimer = new Text2('2:00', {
size: 50,
fill: 0xFFFFFF
});
waveTimer.anchor.set(0.5, 0);
waveTimer.x = 0;
waveTimer.y = 80;
LK.gui.top.addChild(waveTimer);
// Create coins display
coinsText = new Text2('Coins: 0', {
size: 50,
fill: 0xFFD700
});
coinsText.anchor.set(0, 0);
coinsText.x = 20;
coinsText.y = 100;
LK.gui.topLeft.addChild(coinsText);
updateCoinsDisplay();
// Create upgrade selection container
upgradeSelectionContainer = new Container();
upgradeSelectionContainer.visible = false;
LK.gui.center.addChild(upgradeSelectionContainer);
// Create semi-transparent background
var upgradeBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeBg.scaleX = 8;
upgradeBg.scaleY = 8;
upgradeBg.tint = 0x000000;
upgradeBg.alpha = 0.8;
upgradeSelectionContainer.addChild(upgradeBg);
// Create "Choose Upgrade" title
var upgradeTitle = new Text2('Choose Your Upgrade!', {
size: 80,
fill: 0xFFD700
});
upgradeTitle.anchor.set(0.5, 0.5);
upgradeTitle.y = -200;
upgradeSelectionContainer.addChild(upgradeTitle);
// Create boss health bar background
bossHealthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0
});
bossHealthBarBg.x = 0;
bossHealthBarBg.y = 140;
bossHealthBarBg.visible = false;
bossHealthBarBg.scaleX = 2;
LK.gui.top.addChild(bossHealthBarBg);
// Create boss health bar
bossHealthBar = LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0
});
bossHealthBar.x = 0;
bossHealthBar.y = 140;
bossHealthBar.visible = false;
bossHealthBar.scaleX = 2;
bossHealthBar.tint = 0xFF4444; // Red color for boss health
LK.gui.top.addChild(bossHealthBar);
// Create boss health text
bossHealthText = new Text2('', {
size: 40,
fill: 0xFFFFFF
});
bossHealthText.anchor.set(0.5, 0);
bossHealthText.x = 0;
bossHealthText.y = 180;
bossHealthText.visible = false;
LK.gui.top.addChild(bossHealthText);
}
function initializeHero() {
// Create hero
hero = gameContainer.addChild(new Hero());
hero.x = 4096;
hero.y = 5464;
// Initialize weapon tracking
hero.lastWeapon = currentWeapon;
// Apply purchased upgrades from storage
if (storage.maxHealth) {
hero.maxHealth = storage.maxHealth;
hero.health = hero.maxHealth;
}
if (storage.attackDamage) {
hero.attackDamage = storage.attackDamage;
}
if (storage.moveSpeed) {
hero.moveSpeed = storage.moveSpeed;
}
// Camera following variables
cameraX = 0;
cameraY = 0;
cameraFollowSpeed = 0.1;
cameraSmoothing = true;
// Create attack range indicator (initially hidden)
attackIndicator = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5
});
attackIndicator.alpha = 0.3;
attackIndicator.visible = false;
gameContainer.addChild(attackIndicator);
// Create attack range circle that shows max shooting distance
var attackRangeCircle = LK.getAsset('jumpMarker', {
anchorX: 0.5,
anchorY: 0.5
});
attackRangeCircle.scaleX = 8; // 800 pixel radius = 1600 pixel diameter
attackRangeCircle.scaleY = 8; // jumpMarker is 200x200, so 8x scale = 1600
attackRangeCircle.alpha = 0.2;
attackRangeCircle.tint = 0x00FF00; // Green color for range indicator
gameContainer.addChild(attackRangeCircle);
// Store reference for updating position
hero.attackRangeCircle = attackRangeCircle;
// Create analog joystick base
joystickBase = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5
});
joystickBase.alpha = 0.2;
joystickBase.visible = false;
joystickBase.scaleX = 1.5;
joystickBase.scaleY = 1.5;
gameContainer.addChild(joystickBase);
// Create analog joystick knob
joystickKnob = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
joystickKnob.alpha = 0.7;
joystickKnob.visible = false;
joystickKnob.scaleX = 0.6;
joystickKnob.scaleY = 0.6;
gameContainer.addChild(joystickKnob);
}
function generateMapDecorations() {
// Define chunk size for decoration spawning (512x512 pixel chunks)
var chunkSize = 512;
var currentChunkX = Math.floor(hero.x / chunkSize);
var currentChunkY = Math.floor(hero.y / chunkSize);
// Check much larger area around hero (7x7 grid for maximum decoration coverage)
for (var chunkOffsetX = -3; chunkOffsetX <= 3; chunkOffsetX++) {
for (var chunkOffsetY = -3; chunkOffsetY <= 3; chunkOffsetY++) {
var chunkX = currentChunkX + chunkOffsetX;
var chunkY = currentChunkY + chunkOffsetY;
var chunkKey = chunkX + ',' + chunkY;
// Skip if this chunk already has decorations
if (spawnedChunks[chunkKey]) {
continue;
}
// Mark chunk as spawned
spawnedChunks[chunkKey] = true;
// Calculate chunk world position
var chunkWorldX = chunkX * chunkSize + chunkSize / 2;
var chunkWorldY = chunkY * chunkSize + chunkSize / 2;
var decorationTypes = ['tree', 'grass', 'campfire'];
// Reduced decoration density for better performance
var decorationStrategies = {
tree: {
count: 3,
// Reduced trees per chunk
minDistance: 30,
maxDistance: 250,
spacing: 80,
heroAvoidDistance: 70
},
// Fewer trees per chunk
grass: {
count: 8,
// Reduced grass per chunk
minDistance: 10,
maxDistance: 250,
spacing: 30,
heroAvoidDistance: 25
},
// Less grass distributed in chunk
campfire: {
count: 2,
// Fewer campfires per chunk
minDistance: 80,
maxDistance: 220,
spacing: 150,
heroAvoidDistance: 50
} // Fewer campfires
};
// Generate decorations for this chunk
for (var type in decorationStrategies) {
var strategy = decorationStrategies[type];
var count = strategy.count;
// Much higher chance restrictions to ensure many more decorations spawn
if (type === 'campfire' && Math.random() > 0.85) continue; // 85% chance for campfire
if (type === 'tree' && Math.random() > 0.95) continue; // 95% chance for trees
for (var i = 0; i < count; i++) {
var decoration = new MapDecoration();
decoration.setDecoration(type);
var validPosition = false;
var attempts = 0;
var maxAttempts = 20; // Reduced attempts per decoration
// Try to find a valid position within this chunk
while (!validPosition && attempts < maxAttempts) {
// Generate random position within chunk bounds
var randomAngle = Math.random() * Math.PI * 2;
var randomDistance = strategy.minDistance + Math.random() * (strategy.maxDistance - strategy.minDistance);
decoration.x = chunkWorldX + Math.cos(randomAngle) * randomDistance;
decoration.y = chunkWorldY + Math.sin(randomAngle) * randomDistance;
// Ensure decoration stays within chunk bounds
decoration.x = Math.max(chunkX * chunkSize, Math.min((chunkX + 1) * chunkSize, decoration.x));
decoration.y = Math.max(chunkY * chunkSize, Math.min((chunkY + 1) * chunkSize, decoration.y));
// Check distance from hero - avoid spawning too close to hero's current position
var heroDistance = Math.sqrt((decoration.x - hero.x) * (decoration.x - hero.x) + (decoration.y - hero.y) * (decoration.y - hero.y));
if (heroDistance < strategy.heroAvoidDistance) {
attempts++;
continue; // Try again if too close to hero
}
// Check for minimum spacing between decorations of same type
var tooClose = false;
for (var j = 0; j < mapDecorations.length; j++) {
var existingDecoration = mapDecorations[j];
if (existingDecoration.decorationType === type) {
var spacingDx = decoration.x - existingDecoration.x;
var spacingDy = decoration.y - existingDecoration.y;
var spacingDistance = Math.sqrt(spacingDx * spacingDx + spacingDy * spacingDy);
if (spacingDistance < strategy.spacing) {
tooClose = true;
break;
}
}
}
if (!tooClose) {
validPosition = true;
}
attempts++;
}
// Only add decoration if we found a valid position
if (validPosition) {
mapDecorations.push(decoration);
gameContainer.addChild(decoration);
}
}
}
}
}
}
// Initialize entry menu on game start
createEntryMenu();
function spawnEnemy() {
// Determine group size with 30% chance for maximum (10 enemies)
var groupSize;
if (Math.random() < 0.3) {
// 30% chance for maximum group size
groupSize = 10;
} else {
// 70% chance for smaller random group size (3-9 enemies)
groupSize = 3 + Math.floor(Math.random() * 7); // Random between 3-9
}
var groupAngle = Math.random() * Math.PI * 2; // Random direction for group
var groupDistance = 500 + Math.random() * 300; // 500-800 pixels from hero
var groupCenterX = hero.x + Math.cos(groupAngle) * groupDistance;
var groupCenterY = hero.y + Math.sin(groupAngle) * groupDistance;
for (var i = 0; i < groupSize; i++) {
var enemyType = Math.random();
var enemy;
if (enemyType < 0.5) {
enemy = new BasicEnemy();
} else if (enemyType < 0.75) {
enemy = new FastEnemy();
} else if (enemyType < 0.95) {
enemy = new StrongEnemy();
} else {
enemy = new Wizard();
}
// Position enemies in a cluster around the group center
var clusterRadius = 150; // Radius of enemy cluster
var clusterAngle = Math.random() * Math.PI * 2;
var clusterDistance = Math.random() * clusterRadius;
enemy.x = groupCenterX + Math.cos(clusterAngle) * clusterDistance;
enemy.y = groupCenterY + Math.sin(clusterAngle) * clusterDistance;
enemies.push(enemy);
game.addChild(enemy);
}
}
function spawnHealthCrate() {
var crate = new HealthCrate();
// Spawn near hero in infinite world
var crateDistance = 200 + Math.random() * 300; // 200-500 pixels from hero
var crateAngle = Math.random() * Math.PI * 2; // Random angle around hero
crate.x = hero.x + Math.cos(crateAngle) * crateDistance;
crate.y = hero.y + Math.sin(crateAngle) * crateDistance;
healthCrates.push(crate);
game.addChild(crate);
}
function updateHealthBar() {
var healthPercent = hero.health / hero.maxHealth;
healthBar.scaleX = healthPercent;
if (healthPercent > 0.5) {
healthBar.tint = 0x4CAF50; // Green
} else if (healthPercent > 0.25) {
healthBar.tint = 0xFFC107; // Yellow
} else {
healthBar.tint = 0xF44336; // Red
}
// Update health text
healthText.setText(Math.ceil(hero.health) + '/' + hero.maxHealth);
}
function updateLevelBar() {
var xpPercent = currentXP / xpToNextLevel;
levelBar.scaleX = xpPercent;
// Check for level up
if (currentXP >= xpToNextLevel) {
playerLevel++;
currentXP = 0;
xpToNextLevel = playerLevel * 30; // Increase XP requirement
levelText.setText('Level ' + playerLevel);
// Level up effects
LK.effects.flashScreen(0xFFD700, 500);
hero.maxHealth += 3;
hero.health = hero.maxHealth; // Full heal on level up
// Show upgrade selection
showUpgradeSelection();
}
}
function showUpgradeSelection() {
isSelectingUpgrade = true;
upgradeSelectionContainer.visible = true;
// Remove any existing upgrade options
for (var i = upgradeSelectionContainer.children.length - 1; i >= 0; i--) {
var child = upgradeSelectionContainer.children[i];
if (child instanceof UpgradeOption) {
child.destroy();
}
}
// Randomly select 3 different upgrades
var selectedUpgrades = [];
var tempUpgrades = availableUpgrades.slice(); // Copy array
for (var j = 0; j < 3 && tempUpgrades.length > 0; j++) {
var randomIndex = Math.floor(Math.random() * tempUpgrades.length);
selectedUpgrades.push(tempUpgrades[randomIndex]);
tempUpgrades.splice(randomIndex, 1);
}
// Create upgrade option buttons - vertical layout
for (var k = 0; k < selectedUpgrades.length; k++) {
var option = new UpgradeOption();
var upgrade = selectedUpgrades[k];
option.setUpgrade(upgrade.type, upgrade.title, upgrade.description);
option.x = 0; // Center horizontally
option.y = (k - 1) * 300; // Position vertically: -300, 0, 300
upgradeSelectionContainer.addChild(option);
}
}
function applyUpgrade(upgradeType) {
// Mark upgrade as applied
for (var i = 0; i < availableUpgrades.length; i++) {
if (availableUpgrades[i].type === upgradeType) {
availableUpgrades[i].applied = true;
break;
}
}
switch (upgradeType) {
case 'health':
hero.maxHealth += 20;
hero.health += 20;
updateHealthBar();
break;
case 'damage':
hero.attackDamage += 10;
break;
case 'speed':
hero.moveSpeed += 1;
break;
case 'attackSpeed':
// Handled in auto-shoot logic
break;
case 'bulletSpeed':
// Increase all future bullet speeds
Bullet.prototype.baseSpeed = 12; // Default is 8
break;
case 'regen':
hasRegen = true;
break;
}
// Hide upgrade selection
isSelectingUpgrade = false;
upgradeSelectionContainer.visible = false;
}
function updateBossHealthBar() {
// Find if there's a boss enemy alive
var currentBoss = null;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] instanceof Boss) {
currentBoss = enemies[i];
break;
}
}
// Show/hide boss health bar based on boss presence
if (currentBoss) {
bossHealthBarBg.visible = true;
bossHealthBar.visible = true;
bossHealthText.visible = true;
// Update boss health bar
var bossHealthPercent = currentBoss.health / currentBoss.maxHealth;
bossHealthBar.scaleX = bossHealthPercent * 2; // Scale to match background
// Update boss health text
bossHealthText.setText('Boss: ' + Math.ceil(currentBoss.health) + '/' + currentBoss.maxHealth);
} else {
bossHealthBarBg.visible = false;
bossHealthBar.visible = false;
bossHealthText.visible = false;
}
}
function startNewWave() {
// Increment wave first
currentWave++;
waveStartTime = Date.now();
waveEnemyCount = 0;
bossSpawned = false;
waveText.setText('Wave ' + currentWave);
// Wave start effects
LK.effects.flashScreen(0x00FF00, 800);
// Increase difficulty each wave
difficultyLevel = currentWave;
}
function restartGame() {
// Hide death menu
deathMenuContainer.visible = false;
// Reset all game variables
resetGameVariables();
// Change game state
gameState = 'playing';
// Clean up existing game elements
cleanupGameElements();
// Initialize game again
initializeGame();
}
function returnToMainMenu() {
// Hide death menu
deathMenuContainer.visible = false;
// Clean up existing game elements
cleanupGameElements();
// Reset game variables
resetGameVariables();
// Show main menu
menuContainer.visible = true;
gameState = 'menu';
}
function resetGameVariables() {
// Reset player stats
playerLevel = 1;
currentXP = 0;
xpToNextLevel = 30;
currentWave = 1;
waveStartTime = 0;
waveEnemyCount = 0;
bossSpawned = false;
lastRegenTime = 0;
hasRegen = false;
spawnTimer = 0;
difficultyLevel = 1;
lastShootTime = 0;
isJoystickActive = false;
movementX = 0;
movementY = 0;
isSelectingUpgrade = false;
// Reset score
LK.setScore(0);
// Reset upgrade availability
for (var i = 0; i < availableUpgrades.length; i++) {
availableUpgrades[i].applied = false;
}
// Reset bullet speed
Bullet.prototype.baseSpeed = 8;
// Reset weapon to default
currentWeapon = 'pistol';
}
function cleanupGameElements() {
// Destroy all existing game elements
if (gameContainer) {
gameContainer.destroy();
gameContainer = null;
}
// Destroy death menu if it exists
if (deathMenuContainer) {
deathMenuContainer.destroy();
deathMenuContainer = null;
}
// Clear all arrays
enemies = [];
bullets = [];
xpOrbs = [];
lightningBolts = [];
bossBullets = [];
healthCrates = [];
healthPickups = [];
levelPickups = [];
coinPickups = [];
mapDecorations = [];
spawnedChunks = {}; // Reset spawned chunks tracking
}
function updateWaveTimer() {
var currentTime = Date.now();
var timeElapsed = currentTime - waveStartTime;
var timeRemaining = Math.max(0, waveDuration - timeElapsed);
var minutes = Math.floor(timeRemaining / 60000);
var seconds = Math.floor(timeRemaining % 60000 / 1000);
var timeText = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
waveTimer.setText(timeText);
// Check if wave time is up or all enemies defeated
if (timeRemaining <= 0 || enemies.length === 0 && waveEnemyCount >= 10) {
startNewWave();
}
}
function updateCamera() {
// Calculate target camera position (center hero on screen)
var targetCameraX = -(hero.x - 2048 / 2);
var targetCameraY = -(hero.y - 2732 / 2);
// No camera bounds for infinite world
if (cameraSmoothing) {
// Smooth camera following using interpolation
cameraX += (targetCameraX - cameraX) * cameraFollowSpeed;
cameraY += (targetCameraY - cameraY) * cameraFollowSpeed;
} else {
// Direct camera following
cameraX = targetCameraX;
cameraY = targetCameraY;
}
// Apply camera position to game container
game.x = cameraX;
game.y = cameraY;
// Update level bar position to follow hero
levelBarBg.x = hero.x;
levelBarBg.y = hero.y + 300; // Position below hero
levelBar.x = levelBarBg.x - levelBarBg.width / 2;
levelBar.y = levelBarBg.y;
levelText.x = levelBarBg.x;
levelText.y = levelBarBg.y - 50;
}
function handleMove(x, y, obj) {
// Handle analog joystick movement
if (isJoystickActive) {
var deltaX = x - joystickStartX;
var deltaY = y - joystickStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Limit joystick range
if (distance > joystickRadius) {
deltaX = deltaX / distance * joystickRadius;
deltaY = deltaY / distance * joystickRadius;
}
// Update joystick knob position
joystickKnob.x = joystickStartX + deltaX;
joystickKnob.y = joystickStartY + deltaY;
// Calculate movement values (-1 to 1)
movementX = deltaX / joystickRadius;
movementY = deltaY / joystickRadius;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only process game input when in playing state
if (gameState === 'playing') {
// Make hero walk to touch position
hero.walkTo(x, y);
}
};
game.up = function (x, y, obj) {
// Only process game input when in playing state
if (gameState === 'playing') {
// Deactivate analog joystick
if (isJoystickActive) {
isJoystickActive = false;
joystickBase.visible = false;
joystickKnob.visible = false;
movementX = 0;
movementY = 0;
}
attackIndicator.visible = false;
}
// Allow death menu interactions when dead
else if (gameState === 'dead') {
// Death menu interactions are handled by the menu buttons themselves
// No additional processing needed here
}
};
game.update = function () {
// Only run game logic when in playing state
if (gameState !== 'playing') {
return;
}
// Initialize wave system on first update
if (waveStartTime === 0) {
waveStartTime = Date.now();
}
// Pause game logic during upgrade selection
if (isSelectingUpgrade) {
return;
}
// Update wave timer
updateWaveTimer();
// Apply regeneration if unlocked
if (hasRegen && hero.health < hero.maxHealth) {
var currentTime = LK.ticks;
if (currentTime - lastRegenTime > 60) {
// 60 ticks = 1 second
hero.health = Math.min(hero.health + 1, hero.maxHealth);
lastRegenTime = currentTime;
updateHealthBar();
}
}
// Process analog movement
if (isJoystickActive && (Math.abs(movementX) > 0.1 || Math.abs(movementY) > 0.1)) {
hero.analogMove(movementX, movementY);
}
spawnTimer++;
// Check if this is a boss wave (every 3rd wave: 3, 6, 9, etc.)
var isBossWave = currentWave % 3 === 0;
// Boss spawning for every 3rd wave
if (isBossWave && !bossSpawned) {
var boss = new Boss();
// Spawn boss near hero in infinite world
var bossDistance = 600 + Math.random() * 200; // 600-800 pixels from hero
var bossAngle = Math.random() * Math.PI * 2; // Random angle around hero
boss.x = hero.x + Math.cos(bossAngle) * bossDistance;
boss.y = hero.y + Math.sin(bossAngle) * bossDistance;
enemies.push(boss);
game.addChild(boss);
bossSpawned = true;
// Boss spawn effects
LK.effects.flashScreen(0xFF0000, 1000);
} else if (!isBossWave) {
// Regular enemy spawning only on non-boss waves - spawn new groups when all enemies are defeated
// Check if all enemies are defeated and we haven't reached the wave limit
if (enemies.length === 0 && waveEnemyCount < 10) {
// Allow up to 10 groups per wave
spawnEnemy(); // This spawns one group of enemies
waveEnemyCount++; // Track number of groups spawned this wave
spawnTimer = 0;
}
}
// Check if weapon changed and update graphics if needed
if (hero.lastWeapon !== currentWeapon) {
hero.updateWeaponGraphics();
hero.lastWeapon = currentWeapon;
}
// Update weapon to point towards nearest enemy
var nearestEnemy = null;
var nearestDistance = Infinity;
for (var j = 0; j < enemies.length; j++) {
var tempEnemy = enemies[j];
var tempDx = hero.x - tempEnemy.x;
var tempDy = hero.y - tempEnemy.y;
var tempDistance = Math.sqrt(tempDx * tempDx + tempDy * tempDy);
if (tempDistance < nearestDistance) {
nearestEnemy = tempEnemy;
nearestDistance = tempDistance;
}
}
if (nearestEnemy) {
hero.updateWeapon(nearestEnemy.x, nearestEnemy.y);
// Auto-shoot while moving - shoot at nearest enemy
var currentTime = LK.ticks;
var weapon = weaponTypes[currentWeapon];
var shootCooldown = weapon.shootCooldown;
// Check if attack speed upgrade was selected
for (var u = 0; u < availableUpgrades.length; u++) {
if (availableUpgrades[u].type === 'attackSpeed' && availableUpgrades[u].applied) {
shootCooldown = Math.floor(shootCooldown * 0.5); // 50% faster shooting
break;
}
}
if (currentTime - lastShootTime > shootCooldown && nearestDistance < 800) {
hero.shoot(nearestEnemy.x, nearestEnemy.y);
lastShootTime = currentTime;
}
}
// Check bullet collisions with enemies
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
var bulletHit = false;
for (var e = enemies.length - 1; e >= 0; e--) {
var enemy = enemies[e];
if (bullet.intersects(enemy)) {
// Bullet hits enemy
enemy.takeDamage(bullet.damage);
bullet.destroy();
bullets.splice(b, 1);
bulletHit = true;
break;
}
}
}
// Check lightning bolt collisions with hero
for (var l = lightningBolts.length - 1; l >= 0; l--) {
var lightning = lightningBolts[l];
if (hero.checkCollision(lightning)) {
// Lightning hits hero
hero.takeDamage(lightning.damage);
LK.effects.flashObject(hero, 0xFFFF00, 300);
lightning.destroy();
lightningBolts.splice(l, 1);
}
}
// Check boss bullet collisions with hero
for (var bb = bossBullets.length - 1; bb >= 0; bb--) {
var bossBullet = bossBullets[bb];
if (hero.checkCollision(bossBullet)) {
// Boss bullet hits hero
hero.takeDamage(bossBullet.damage);
LK.effects.flashObject(hero, 0xFF4500, 300);
bossBullet.destroy();
bossBullets.splice(bb, 1);
}
}
// Check collisions and close combat between hero and enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var dx = hero.x - enemy.x;
var dy = hero.y - enemy.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Auto-attack when very close (melee range)
if (distance < 60 && !enemy.justAttacked) {
hero.attack(enemy);
enemy.justAttacked = true;
// Reset attack flag after short delay
LK.setTimeout(function () {
if (enemy && !enemy.destroyed) {
enemy.justAttacked = false;
}
}, 300);
} else if (hero.checkCollision(enemy) && !enemy.justHitHero) {
// Take damage from collision but don't destroy enemy
hero.takeDamage(enemy.damage);
enemy.justHitHero = true;
// Reset hit flag after short delay to prevent rapid damage
LK.setTimeout(function () {
if (enemy && !enemy.destroyed) {
enemy.justHitHero = false;
}
}, 1000); // 1 second cooldown between hits
}
// Enemies no longer disappear based on distance from hero
// They will persist until killed by the player
}
// Update high score
var currentScore = LK.getScore();
if (currentScore > (storage.highScore || 0)) {
storage.highScore = currentScore;
}
// Update health bar
updateHealthBar();
// Update level bar
updateLevelBar();
// Update boss health bar
updateBossHealthBar();
// Spawn health crates occasionally
if (LK.ticks % 600 === 0) {
// Every 10 seconds
spawnHealthCrate();
}
// Check bullet collisions with health crates
for (var bc = bullets.length - 1; bc >= 0; bc--) {
var bulletCrate = bullets[bc];
var crateHit = false;
for (var hc = healthCrates.length - 1; hc >= 0; hc--) {
var healthCrate = healthCrates[hc];
if (bulletCrate.intersects(healthCrate)) {
// Bullet hits crate
healthCrate.takeDamage(bulletCrate.damage);
bulletCrate.destroy();
bullets.splice(bc, 1);
crateHit = true;
break;
}
}
}
// Check hero melee attacks on crates
for (var cr = healthCrates.length - 1; cr >= 0; cr--) {
var crate = healthCrates[cr];
var crateDx = hero.x - crate.x;
var crateDy = hero.y - crate.y;
var crateDistance = Math.sqrt(crateDx * crateDx + crateDy * crateDy);
// Auto-attack crates when very close
if (crateDistance < 60 && !crate.justAttacked) {
crate.takeDamage(hero.attackDamage);
crate.justAttacked = true;
// Reset attack flag after short delay
LK.setTimeout(function () {
if (crate && !crate.destroyed) {
crate.justAttacked = false;
}
}, 300);
}
}
// Clean up decorations that are too far from hero
for (var d = mapDecorations.length - 1; d >= 0; d--) {
var decoration = mapDecorations[d];
var decorationDistance = Math.sqrt((decoration.x - hero.x) * (decoration.x - hero.x) + (decoration.y - hero.y) * (decoration.y - hero.y));
if (decorationDistance > 3000) {
// Remove decorations more than 3000 pixels from hero
decoration.destroy();
mapDecorations.splice(d, 1);
}
}
// Dynamically generate new decorations as hero moves
if (LK.ticks % 60 === 0) {
// Every 1 second - much more frequent decoration generation
generateMapDecorations();
}
// Update attack range circle position to follow hero
if (hero.attackRangeCircle) {
hero.attackRangeCircle.x = hero.x;
hero.attackRangeCircle.y = hero.y;
}
// Update pets
for (var petIndex = 0; petIndex < pets.length; petIndex++) {
pets[petIndex].update();
}
// Update spell effects
for (var spellIndex = spellEffects.length - 1; spellIndex >= 0; spellIndex--) {
var spell = spellEffects[spellIndex];
spell.update();
if (spell.destroyed) {
spellEffects.splice(spellIndex, 1);
}
}
// Update hero shield time
if (hero.hasShield && hero.shieldTime) {
hero.shieldTime--;
if (hero.shieldTime <= 0) {
hero.hasShield = false;
hero.shieldTime = 0;
}
}
// Cast spells randomly when enemies nearby
if (knownSpells.length > 1 && LK.ticks % 240 === 0) {
// Every 4 seconds
var nearbyEnemies = [];
for (var en = 0; en < enemies.length; en++) {
var enemy = enemies[en];
var dx = hero.x - enemy.x;
var dy = hero.y - enemy.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 300) {
nearbyEnemies.push(enemy);
}
}
if (nearbyEnemies.length > 0) {
var randomSpell = knownSpells[Math.floor(Math.random() * knownSpells.length)];
var spellEffect = new SpellEffect();
spellEffect.setSpellType(randomSpell);
if (randomSpell === 'heal' || randomSpell === 'shield') {
spellEffect.cast(hero, hero);
} else {
var randomEnemy = nearbyEnemies[Math.floor(Math.random() * nearbyEnemies.length)];
spellEffect.cast(hero, randomEnemy);
}
spellEffects.push(spellEffect);
}
}
// Update camera to follow hero
updateCamera();
scoreTxt.setText(LK.getScore());
updateCoinsDisplay();
};
function updateCoinsDisplay() {
var currentCoins = storage.coins || 0;
if (coinsText) {
coinsText.setText('Coins: ' + currentCoins);
}
}
function showShop() {
// Hide menu
menuContainer.visible = false;
gameState = 'shop';
// Create shop container
shopContainer = new Container();
game.addChild(shopContainer);
// Create background
var shopBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
shopBg.x = 2048 / 2;
shopBg.y = 2732 / 2;
shopBg.scaleX = 10;
shopBg.scaleY = 10;
shopBg.tint = 0x2c3e50;
shopBg.alpha = 0.9;
shopContainer.addChild(shopBg);
// Create title
var shopTitle = new Text2('DÜKKAN', {
size: 120,
fill: 0xFFD700
});
shopTitle.anchor.set(0.5, 0.5);
shopTitle.x = 2048 / 2;
shopTitle.y = 200;
shopContainer.addChild(shopTitle);
// Create category buttons
var categories = [{
name: 'SİLAHLAR',
key: 'weapons'
}, {
name: 'EVCIL HAYVANLAR',
key: 'pets'
}, {
name: 'ZIRHLAR',
key: 'armor'
}, {
name: 'BÜYÜLER',
key: 'spells'
}];
for (var c = 0; c < categories.length; c++) {
var category = categories[c];
var catBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
catBg.x = 2048 / 2 + (c - 1.5) * 300;
catBg.y = 300;
catBg.scaleX = 2.5;
catBg.scaleY = 1.5;
catBg.tint = currentShopCategory === category.key ? 0x27ae60 : 0x7f8c8d;
shopContainer.addChild(catBg);
var catText = new Text2(category.name, {
size: 35,
fill: 0xFFFFFF
});
catText.anchor.set(0.5, 0.5);
catText.x = catBg.x;
catText.y = catBg.y;
shopContainer.addChild(catText);
(function (categoryKey, bg) {
bg.down = function (x, y, obj) {
currentShopCategory = categoryKey;
hideShop();
showShop();
};
})(category.key, catBg);
}
// Create coins display
var shopCoinsText = new Text2('Coins: ' + (storage.coins || 0), {
size: 80,
fill: 0xFFD700
});
shopCoinsText.anchor.set(0.5, 0.5);
shopCoinsText.x = 2048 / 2;
shopCoinsText.y = 400;
shopContainer.addChild(shopCoinsText);
// Shop items based on category
var shopItems = [];
if (currentShopCategory === 'weapons') {
shopItems = [{
name: 'Max Health +20',
cost: 50,
type: 'health'
}, {
name: 'Attack +10',
cost: 75,
type: 'damage'
}, {
name: 'Speed +1',
cost: 100,
type: 'speed'
}, {
name: 'Makineli Tüfek',
cost: 200,
type: 'weapon',
weaponType: 'machinegun'
}, {
name: 'Keskin Nişancı',
cost: 300,
type: 'weapon',
weaponType: 'sniper'
}, {
name: 'Lazer Silahı',
cost: 250,
type: 'weapon',
weaponType: 'laser'
}, {
name: 'Patlayıcı',
cost: 350,
type: 'weapon',
weaponType: 'explosive'
}];
} else if (currentShopCategory === 'pets') {
shopItems = [{
name: 'İyileştirici Pet',
cost: 150,
type: 'pet',
petType: 'healing'
}, {
name: 'Saldırı Peti',
cost: 200,
type: 'pet',
petType: 'attack'
}, {
name: 'Kalkan Peti',
cost: 250,
type: 'pet',
petType: 'shield'
}];
} else if (currentShopCategory === 'armor') {
shopItems = [{
name: 'Hafif Zırh',
cost: 100,
type: 'armor',
armorType: 'light'
}, {
name: 'Orta Zırh',
cost: 200,
type: 'armor',
armorType: 'medium'
}, {
name: 'Ağır Zırh',
cost: 400,
type: 'armor',
armorType: 'heavy'
}];
} else if (currentShopCategory === 'spells') {
shopItems = [{
name: 'Ateş Topu',
cost: 80,
type: 'spell',
spellType: 'fireball'
}, {
name: 'Dondurucu',
cost: 120,
type: 'spell',
spellType: 'freeze'
}, {
name: 'Kalkan Büyüsü',
cost: 180,
type: 'spell',
spellType: 'shield'
}];
}
// Create shop item buttons
for (var i = 0; i < shopItems.length; i++) {
var item = shopItems[i];
var itemY = 600 + i * 150;
// Item background
var itemBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
itemBg.x = 2048 / 2;
itemBg.y = itemY;
itemBg.scaleX = 6;
itemBg.scaleY = 2;
var currentCoins = storage.coins || 0;
var ownedWeapons = storage.ownedWeapons || ['pistol'];
var canPurchase = true;
// Check weapon-specific conditions
if (item.type === 'weapon') {
// Already owned this weapon
if (ownedWeapons.indexOf(item.weaponType) !== -1) {
canPurchase = false;
itemBg.tint = 0x95a5a6; // Gray for already owned
}
// At weapon limit (2 weapons max)
else if (ownedWeapons.length >= 2) {
canPurchase = false;
itemBg.tint = 0x8e44ad; // Purple for weapon limit reached
}
// Can purchase weapon (has enough coins)
else if (currentCoins >= item.cost) {
canPurchase = true;
itemBg.tint = 0x27ae60; // Green for available
} else {
canPurchase = false;
itemBg.tint = 0x7f8c8d; // Gray for insufficient coins
}
} else {
// Non-weapon items
canPurchase = currentCoins >= item.cost;
itemBg.tint = currentCoins >= item.cost ? 0x27ae60 : 0x7f8c8d;
}
shopContainer.addChild(itemBg);
// Item name
var itemName = new Text2(item.name, {
size: 60,
fill: 0xFFFFFF
});
itemName.anchor.set(0, 0.5);
itemName.x = 2048 / 2 - 300;
itemName.y = itemY;
shopContainer.addChild(itemName);
// Item cost
var itemCost = new Text2(item.cost + ' Coins', {
size: 50,
fill: 0xFFD700
});
itemCost.anchor.set(1, 0.5);
itemCost.x = 2048 / 2 + 300;
itemCost.y = itemY;
shopContainer.addChild(itemCost);
// Create closure for button interaction
(function (shopItem, bg, nameText, costText) {
bg.down = function (x, y, obj) {
// Only animate if item can be purchased
var currentCoins = storage.coins || 0;
var ownedWeapons = storage.ownedWeapons || ['pistol'];
var canPurchaseNow = true;
if (shopItem.type === 'weapon') {
if (ownedWeapons.indexOf(shopItem.weaponType) !== -1 || ownedWeapons.length >= 2 || currentCoins < shopItem.cost) {
canPurchaseNow = false;
}
} else if (currentCoins < shopItem.cost) {
canPurchaseNow = false;
}
if (canPurchaseNow) {
tween(bg, {
scaleX: 6.2,
scaleY: 2.2
}, {
duration: 100,
easing: tween.easeOut
});
}
};
bg.up = function (x, y, obj) {
tween(bg, {
scaleX: 6,
scaleY: 2
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
buyItem(shopItem, shopCoinsText, bg, nameText, costText);
}
});
};
})(item, itemBg, itemName, itemCost);
}
// Create back button
var backButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
backButtonBg.x = 2048 / 2;
backButtonBg.y = 2732 - 200;
backButtonBg.scaleX = 3;
backButtonBg.scaleY = 2;
backButtonBg.tint = 0x95a5a6;
shopContainer.addChild(backButtonBg);
var backButtonText = new Text2('GERİ', {
size: 60,
fill: 0xFFFFFF
});
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = 2048 / 2;
backButtonText.y = 2732 - 200;
shopContainer.addChild(backButtonText);
backButtonBg.down = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3.2,
scaleY: 2.2
}, {
duration: 100,
easing: tween.easeOut
});
};
backButtonBg.up = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3,
scaleY: 2
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
hideShop();
}
});
};
}
function buyItem(item, coinsText, bg, nameText, costText) {
var currentCoins = storage.coins || 0;
if (currentCoins >= item.cost) {
// For weapon purchases, validate conditions first before deducting coins
if (item.type === 'weapon') {
if (!storage.ownedWeapons) {
storage.ownedWeapons = ['pistol']; // Start with only pistol
}
// Check if weapon is already owned
if (storage.ownedWeapons.indexOf(item.weaponType) !== -1) {
// Already owned, show message and don't purchase
LK.effects.flashScreen(0xFFFF00, 200);
return;
}
// Check weapon limit (maximum 2 weapons)
if (storage.ownedWeapons.length >= 2) {
// At weapon limit, show error message
LK.effects.flashScreen(0xFF0000, 300);
return;
}
}
// Deduct coins only after all validations pass
storage.coins = currentCoins - item.cost;
// Apply upgrade based on type
switch (item.type) {
case 'health':
var currentMaxHealth = storage.maxHealth || 100;
storage.maxHealth = currentMaxHealth + 20;
break;
case 'damage':
var currentDamage = storage.attackDamage || 25;
storage.attackDamage = currentDamage + 10;
break;
case 'speed':
var currentSpeed = storage.moveSpeed || 4;
storage.moveSpeed = currentSpeed + 1;
break;
case 'weapon':
// Add new weapon to inventory (validation already passed above)
if (!storage.ownedWeapons) {
storage.ownedWeapons = ['pistol'];
}
storage.ownedWeapons.push(item.weaponType);
// Set as current weapon if player wants
currentWeapon = item.weaponType;
// Update weapon graphics if hero exists
if (hero) {
hero.updateWeaponGraphics();
hero.lastWeapon = currentWeapon;
}
break;
case 'pet':
// Remove existing pet if any
if (activePet) {
activePet.destroy();
for (var p = pets.length - 1; p >= 0; p--) {
if (pets[p] === activePet) {
pets.splice(p, 1);
break;
}
}
}
// Create new pet
var newPet = new Pet();
newPet.setPetType(item.petType);
newPet.owner = hero;
newPet.x = hero.x + 50;
newPet.y = hero.y + 50;
pets.push(newPet);
gameContainer.addChild(newPet);
activePet = newPet;
break;
case 'armor':
// Remove existing armor if any
if (equippedArmor && hero) {
hero.defense = (hero.defense || 0) - equippedArmor.defenseBonus;
hero.maxHealth -= equippedArmor.healthBonus;
hero.health = Math.max(1, hero.health - equippedArmor.healthBonus);
hero.moveSpeed += equippedArmor.speedPenalty;
}
// Create and equip new armor
var newArmor = new ArmorPiece();
newArmor.setArmorType(item.armorType);
if (hero) {
newArmor.equip(hero);
}
equippedArmor = newArmor;
break;
case 'spell':
// Add spell to known spells
if (knownSpells.indexOf(item.spellType) === -1) {
knownSpells.push(item.spellType);
}
break;
}
// Update display
coinsText.setText('Coins: ' + storage.coins);
// Update button color
bg.tint = storage.coins >= item.cost ? 0x27ae60 : 0x7f8c8d;
// Success effect
LK.effects.flashScreen(0x00FF00, 300);
LK.getSound('xpCollect').play();
} else {
// Not enough coins effect
LK.effects.flashScreen(0xFF0000, 200);
}
}
function hideShop() {
shopContainer.destroy();
shopContainer = null;
menuContainer.visible = true;
gameState = 'menu';
}
function showScoreboard() {
// Hide menu
menuContainer.visible = false;
gameState = 'scoreboard';
// Create scoreboard container
scoreboardContainer = new Container();
game.addChild(scoreboardContainer);
// Create background
var scoreboardBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
scoreboardBg.x = 2048 / 2;
scoreboardBg.y = 2732 / 2;
scoreboardBg.scaleX = 10;
scoreboardBg.scaleY = 10;
scoreboardBg.tint = 0x2c3e50;
scoreboardBg.alpha = 0.9;
scoreboardContainer.addChild(scoreboardBg);
// Create title
var scoreboardTitle = new Text2('SKOR TABLOSU', {
size: 120,
fill: 0xFFD700
});
scoreboardTitle.anchor.set(0.5, 0.5);
scoreboardTitle.x = 2048 / 2;
scoreboardTitle.y = 400;
scoreboardContainer.addChild(scoreboardTitle);
// Create high score display
var highScoreText = new Text2('En Yüksek Skor: ' + (storage.highScore || 0), {
size: 80,
fill: 0x00FF00
});
highScoreText.anchor.set(0.5, 0.5);
highScoreText.x = 2048 / 2;
highScoreText.y = 600;
scoreboardContainer.addChild(highScoreText);
// Create total kills display
var totalKillsText = new Text2('Toplam Öldürme: ' + (storage.totalKills || 0), {
size: 70,
fill: 0xFF6B6B
});
totalKillsText.anchor.set(0.5, 0.5);
totalKillsText.x = 2048 / 2;
totalKillsText.y = 750;
scoreboardContainer.addChild(totalKillsText);
// Create current score display
var currentScoreText = new Text2('Şu Anki Skor: ' + LK.getScore(), {
size: 70,
fill: 0xFFFFFF
});
currentScoreText.anchor.set(0.5, 0.5);
currentScoreText.x = 2048 / 2;
currentScoreText.y = 900;
scoreboardContainer.addChild(currentScoreText);
// Create statistics
var coinsEarned = storage.coins || 0;
var statsText = new Text2('Toplam Para: ' + coinsEarned, {
size: 60,
fill: 0xFFD700
});
statsText.anchor.set(0.5, 0.5);
statsText.x = 2048 / 2;
statsText.y = 1050;
scoreboardContainer.addChild(statsText);
// Create back button
var backButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
backButtonBg.x = 2048 / 2;
backButtonBg.y = 2732 - 200;
backButtonBg.scaleX = 3;
backButtonBg.scaleY = 2;
backButtonBg.tint = 0x95a5a6;
scoreboardContainer.addChild(backButtonBg);
var backButtonText = new Text2('GERİ', {
size: 60,
fill: 0xFFFFFF
});
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = 2048 / 2;
backButtonText.y = 2732 - 200;
scoreboardContainer.addChild(backButtonText);
backButtonBg.down = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3.2,
scaleY: 2.2
}, {
duration: 100,
easing: tween.easeOut
});
};
backButtonBg.up = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3,
scaleY: 2
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
hideScoreboard();
}
});
};
}
function hideScoreboard() {
scoreboardContainer.destroy();
scoreboardContainer = null;
menuContainer.visible = true;
gameState = 'menu';
}
function handleWeaponSelectPurchase(weaponType, weapon, isOwned, coinsDisplay) {
if (isOwned) {
// If already owned, just select it
currentWeapon = weaponType;
// Update weapon graphics if hero exists
if (hero) {
hero.updateWeaponGraphics();
hero.lastWeapon = currentWeapon;
}
// Success effects
LK.effects.flashScreen(0x00FF00, 300);
LK.getSound('xpCollect').play();
hideWeaponSelect();
} else {
// Purchase the weapon (all weapons are now free)
if (!storage.ownedWeapons) {
storage.ownedWeapons = ['pistol'];
}
// Add to owned weapons
storage.ownedWeapons.push(weaponType);
// Set as current weapon
currentWeapon = weaponType;
// Update weapon graphics if hero exists
if (hero) {
hero.updateWeaponGraphics();
hero.lastWeapon = currentWeapon;
}
// Update coins display
updateCoinsDisplay();
// Success effects
LK.effects.flashScreen(0x00FF00, 500);
LK.getSound('xpCollect').play();
// Refresh the weapon select menu to show updated status
hideWeaponSelect();
LK.setTimeout(function () {
showWeaponSelect();
}, 100);
}
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
totalKills: 0
});
/****
* Classes
****/
var ArmorPiece = Container.expand(function () {
var self = Container.call(this);
self.armorType = '';
self.defenseBonus = 0;
self.healthBonus = 0;
self.speedPenalty = 0;
self.equipped = false;
self.setArmorType = function (type) {
self.armorType = type;
switch (type) {
case 'light':
self.defenseBonus = 5;
self.healthBonus = 10;
self.speedPenalty = 0;
break;
case 'medium':
self.defenseBonus = 10;
self.healthBonus = 20;
self.speedPenalty = 0.5;
break;
case 'heavy':
self.defenseBonus = 20;
self.healthBonus = 40;
self.speedPenalty = 1;
break;
}
};
self.equip = function (target) {
if (!self.equipped) {
self.equipped = true;
target.defense = (target.defense || 0) + self.defenseBonus;
target.maxHealth += self.healthBonus;
target.health += self.healthBonus;
target.moveSpeed = Math.max(1, target.moveSpeed - self.speedPenalty);
updateHealthBar();
}
};
return self;
});
var BasicEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('basicEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Weapon graphics removed
self.health = 25;
self.maxHealth = 25;
self.damage = 15;
self.speed = 2;
self.scoreValue = 10;
// Enemy hitbox properties
self.width = 240; // Enemy visual width
self.height = 240; // Enemy visual height
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
// Drop XP orb
var xpOrb = new XPOrb();
xpOrb.x = self.x;
xpOrb.y = self.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
// 50% chance to drop coin
if (Math.random() < 0.5) {
var coin = new CoinPickup();
coin.x = self.x + (Math.random() - 0.5) * 60;
coin.y = self.y + (Math.random() - 0.5) * 60;
coinPickups.push(coin);
game.addChild(coin);
}
self.destroy();
LK.setScore(LK.getScore() + self.scoreValue);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDestroy').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
// updateWeapon method removed
self.update = function () {
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Weapon update removed
if (distance > 0) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
// Check collision with other enemies
var collisionFound = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self) {
var otherDx = newX - otherEnemy.x;
var otherDy = newY - otherEnemy.y;
var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy);
var minDistance = (self.width + otherEnemy.width) / 4; // Collision threshold
if (otherDistance < minDistance) {
collisionFound = true;
// Push enemies apart
if (otherDistance > 0) {
var pushForce = (minDistance - otherDistance) / 2;
var pushX = otherDx / otherDistance * pushForce;
var pushY = otherDy / otherDistance * pushForce;
newX += pushX;
newY += pushY;
otherEnemy.x -= pushX;
otherEnemy.y -= pushY;
}
}
}
}
// Only move if no major collision or after separation
if (!collisionFound || distance > 100) {
self.x = newX;
self.y = newY;
}
}
};
return self;
});
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('wikingboss', {
anchorX: 0.5,
anchorY: 0.5
});
bossGraphics.tint = 0x8B4513; // Brown viking color
bossGraphics.scaleX = 4.0; // Much larger viking boss model
bossGraphics.scaleY = 4.0; // Much larger viking boss model
// Weapon graphics removed
// Add viking boss aura effect
var auraGraphics = self.attachAsset('wikingboss', {
anchorX: 0.5,
anchorY: 0.5
});
auraGraphics.tint = 0xDAA520; // Golden viking aura
auraGraphics.alpha = 0.4;
auraGraphics.scaleX = 4.5; // Larger aura to match viking boss size
auraGraphics.scaleY = 4.5; // Larger aura to match viking boss size
// Add pulsing viking aura animation
tween(auraGraphics, {
scaleX: 5.0,
scaleY: 5.0,
alpha: 0.2
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(auraGraphics, {
scaleX: 4.5,
scaleY: 4.5,
alpha: 0.4
}, {
duration: 1500,
easing: tween.easeInOut
});
}
});
self.health = 400 + (currentWave - 3) * 85; // Much higher health for viking boss
self.maxHealth = self.health;
self.damage = 70; // Higher damage for intimidating viking boss
self.speed = 0.8; // Slightly slower due to massive viking size
self.scoreValue = 250; // Higher reward for defeating viking boss
// Boss hitbox properties for accurate collision
self.width = 760; // Boss visual width (190 * 4 scale)
self.height = 760; // Boss visual height (190 * 4 scale)
self.lastLightningTime = 0;
self.lightningCooldown = 120; // 2 seconds at 60fps - reduced attack frequency
self.lastChargeTime = 0;
self.chargeCooldown = 360; // 6 seconds at 60fps - less frequent
self.isCharging = false;
self.chargeSpeed = 8;
self.chargeTargetX = 0;
self.chargeTargetY = 0;
self.lastJumpTime = 0;
self.jumpCooldown = 300; // 5 seconds at 60fps - less frequent
self.isJumping = false;
self.jumpMarker = null;
self.lastAerialTime = 0;
self.aerialCooldown = 240; // 4 seconds at 60fps
self.isAerial = false;
self.aerialMarkers = [];
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
// Drop multiple XP orbs
for (var orbCount = 0; orbCount < 5; orbCount++) {
var xpOrb = new XPOrb();
xpOrb.x = self.x + (Math.random() - 0.5) * 100;
xpOrb.y = self.y + (Math.random() - 0.5) * 100;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
}
// Boss always drops coins
for (var coinCount = 0; coinCount < 10; coinCount++) {
var coin = new CoinPickup();
coin.x = self.x + (Math.random() - 0.5) * 150;
coin.y = self.y + (Math.random() - 0.5) * 150;
coin.coinValue = 10; // Boss coins are worth more
coinPickups.push(coin);
game.addChild(coin);
}
self.destroy();
LK.setScore(LK.getScore() + self.scoreValue);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDestroy').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
// Boss defeated - start new wave
LK.setTimeout(function () {
startNewWave();
}, 2000); // 2 second delay before starting new wave
}
};
self.shootBigBullets = function () {
// Create multiple big bullets - increased from 5 to 7 for more coverage
for (var bulletCount = 0; bulletCount < 7; bulletCount++) {
var bigBullet = new BossBullet();
bigBullet.x = self.x;
bigBullet.y = self.y;
// Spread bullets in a wider arc for better coverage
var angleOffset = (bulletCount - 3) * Math.PI / 10;
var targetX = hero.x + Math.cos(angleOffset) * 200;
var targetY = hero.y + Math.sin(angleOffset) * 200;
bigBullet.setTarget(targetX, targetY);
bossBullets.push(bigBullet);
game.addChild(bigBullet);
}
// More dramatic visual effect for primary attack
LK.effects.flashObject(self, 0xFF4500, 600);
LK.effects.flashScreen(0xFF4500, 100);
};
self.startCharge = function () {
self.isCharging = true;
self.chargeTargetX = hero.x;
self.chargeTargetY = hero.y;
LK.effects.flashObject(self, 0xFF4500, 500);
// Viking boss grows larger during charge
tween(bossGraphics, {
scaleX: 4.5,
scaleY: 4.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Charge for 1 second then return to normal
LK.setTimeout(function () {
self.isCharging = false;
tween(bossGraphics, {
scaleX: 4.0,
scaleY: 4.0
}, {
duration: 200,
easing: tween.easeIn
});
}, 1000);
}
});
};
self.startJumpAttack = function () {
self.isJumping = true;
// Create red marker at hero's position
self.jumpMarker = LK.getAsset('jumpMarker', {
anchorX: 0.5,
anchorY: 0.5
});
self.jumpMarker.x = hero.x;
self.jumpMarker.y = hero.y;
self.jumpMarker.alpha = 0.6;
game.addChild(self.jumpMarker);
// Pulse the marker
tween(self.jumpMarker, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.jumpMarker, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.6
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
// Boss prepares to jump - crouch animation
tween(bossGraphics, {
scaleY: 3.5
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
// Jump to marker position
var jumpX = self.jumpMarker.x;
var jumpY = self.jumpMarker.y;
// Jump animation
tween(self, {
x: jumpX,
y: jumpY
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Landing impact
LK.effects.flashScreen(0xFF0000, 200);
tween(bossGraphics, {
scaleY: 4.0
}, {
duration: 200,
easing: tween.easeOut
});
// Deal area damage at landing position
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy !== self) {
var impactDx = enemy.x - self.x;
var impactDy = enemy.y - self.y;
var impactDistance = Math.sqrt(impactDx * impactDx + impactDy * impactDy);
if (impactDistance < 250) {
enemy.takeDamage(40);
}
}
}
// Check if hero is in impact area
var heroDx = hero.x - self.x;
var heroDy = hero.y - self.y;
var heroDistance = Math.sqrt(heroDx * heroDx + heroDy * heroDy);
if (heroDistance < 250) {
hero.takeDamage(60);
}
// Remove marker
if (self.jumpMarker) {
self.jumpMarker.destroy();
self.jumpMarker = null;
}
self.isJumping = false;
}
});
}
});
};
self.startAerialAttack = function () {
self.isAerial = true;
// Create multiple red markers in a pattern around hero
var markerCount = 5;
for (var i = 0; i < markerCount; i++) {
var angle = Math.PI * 2 / markerCount * i;
var markerDistance = 150;
var marker = LK.getAsset('jumpMarker', {
anchorX: 0.5,
anchorY: 0.5
});
marker.x = hero.x + Math.cos(angle) * markerDistance;
marker.y = hero.y + Math.sin(angle) * markerDistance;
marker.alpha = 0.8;
marker.tint = 0xFF0000;
game.addChild(marker);
self.aerialMarkers.push(marker);
// Pulse animation for markers
tween(marker, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.4
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(marker, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
}
// Boss rises up animation
tween(bossGraphics, {
scaleX: 3.5,
scaleY: 3.5,
alpha: 0.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// After delay, rain bullets from sky
LK.setTimeout(function () {
// Create bullets at each marker position
for (var j = 0; j < self.aerialMarkers.length; j++) {
var targetMarker = self.aerialMarkers[j];
// Create bullet high above
var skyBullet = new BossBullet();
skyBullet.x = targetMarker.x;
skyBullet.y = targetMarker.y - 800; // Start high above
skyBullet.setTarget(targetMarker.x, targetMarker.y);
bossBullets.push(skyBullet);
game.addChild(skyBullet);
}
// Boss returns to normal
tween(bossGraphics, {
scaleX: 4.0,
scaleY: 4.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.easeIn
});
// Remove markers after bullets are fired
for (var k = 0; k < self.aerialMarkers.length; k++) {
self.aerialMarkers[k].destroy();
}
self.aerialMarkers = [];
self.isAerial = false;
}, 800);
}
});
};
self.update = function () {
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var currentTime = LK.ticks;
// Weapon update removed
// Big bullet attack - primary attack with extended range
if (distance < 900 && currentTime - self.lastLightningTime > self.lightningCooldown) {
self.shootBigBullets();
self.lastLightningTime = currentTime;
}
// Charge attack - secondary attack, less frequent
if (distance > 250 && distance < 600 && currentTime - self.lastChargeTime > self.chargeCooldown && !self.isCharging && !self.isJumping) {
self.startCharge();
self.lastChargeTime = currentTime;
}
// Jump attack - tertiary attack, least frequent
if (distance < 700 && currentTime - self.lastJumpTime > self.jumpCooldown && !self.isJumping && !self.isCharging && !self.isAerial) {
self.startJumpAttack();
self.lastJumpTime = currentTime;
}
// Aerial attack - quaternary attack
if (distance < 600 && currentTime - self.lastAerialTime > self.aerialCooldown && !self.isJumping && !self.isCharging && !self.isAerial) {
self.startAerialAttack();
self.lastAerialTime = currentTime;
}
// Movement behavior
if (self.isCharging) {
// Charge towards target position
var chargeDx = self.chargeTargetX - self.x;
var chargeDy = self.chargeTargetY - self.y;
var chargeDistance = Math.sqrt(chargeDx * chargeDx + chargeDy * chargeDy);
if (chargeDistance > 0) {
self.x += chargeDx / chargeDistance * self.chargeSpeed;
self.y += chargeDy / chargeDistance * self.chargeSpeed;
}
} else if (self.isJumping || self.isAerial) {
// Don't move while jumping or aerial attack
} else {
// Normal movement - maintain distance from hero
if (distance > 300) {
// Move closer
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
} else if (distance < 150) {
// Move away
if (distance > 0) {
self.x -= dx / distance * self.speed;
self.y -= dy / distance * self.speed;
}
}
}
};
return self;
});
var BossBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.scaleX = 1.5; // Scale for appropriate size
bulletGraphics.scaleY = 1.5; // Scale for appropriate size
// Add glowing effect to boss bullets
var glowGraphics = self.attachAsset('bossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
glowGraphics.tint = 0xFF6600; // Orange glow
glowGraphics.alpha = 0.4;
glowGraphics.scaleX = 2;
glowGraphics.scaleY = 2;
// Pulsing glow animation
tween(glowGraphics, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(glowGraphics, {
scaleX: 2,
scaleY: 2,
alpha: 0.4
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
self.speed = 3;
self.damage = 50;
// Boss bullet hitbox properties
self.width = 150; // Boss bullet visual width (100 * 1.5 scale)
self.height = 150; // Boss bullet visual height (100 * 1.5 scale)
self.targetX = 0;
self.targetY = 0;
self.directionX = 0;
self.directionY = 0;
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
var dx = x - self.x;
var dy = y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.directionX = dx / distance;
self.directionY = dy / distance;
}
// Rotate bullet to face direction
bulletGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
};
self.update = function () {
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Rotate the boss bullet for visual effect
self.rotation += 0.1;
// Remove bullet if it goes off screen
if (self.x < -50 || self.x > 8242 || self.y < -50 || self.y > 10978) {
self.destroy();
for (var i = bossBullets.length - 1; i >= 0; i--) {
if (bossBullets[i] === self) {
bossBullets.splice(i, 1);
break;
}
}
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = Bullet.prototype.baseSpeed || 8;
self.damage = 25;
self.targetX = 0;
self.targetY = 0;
self.directionX = 0;
self.directionY = 0;
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
var dx = x - self.x;
var dy = y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.directionX = dx / distance;
self.directionY = dy / distance;
}
// Rotate bullet to face direction
bulletGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
};
self.update = function () {
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Remove bullet if it goes off screen
if (self.x < -50 || self.x > 8242 || self.y < -50 || self.y > 10978) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
}
};
return self;
});
var CoinPickup = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
coinGraphics.scaleX = 0.8;
coinGraphics.scaleY = 0.8;
// Add glowing effect
var glowGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
glowGraphics.tint = 0xFFF700; // Bright yellow glow
glowGraphics.alpha = 0.3;
glowGraphics.scaleX = 1.1;
glowGraphics.scaleY = 1.1;
// Add pulsing animation
tween(coinGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(coinGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 600,
easing: tween.easeInOut
});
}
});
// Add continuous rotation animation
tween(coinGraphics, {
rotation: Math.PI * 2
}, {
duration: 2000,
easing: tween.linear,
onFinish: function onFinish() {
coinGraphics.rotation = 0;
// Restart rotation
tween(coinGraphics, {
rotation: Math.PI * 2
}, {
duration: 2000,
easing: tween.linear
});
}
});
self.coinValue = 5;
self.moveSpeed = 3;
self.attractRange = 120;
self.update = function () {
// Move toward hero if within attract range
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.attractRange) {
if (distance > 0) {
self.x += dx / distance * self.moveSpeed;
self.y += dy / distance * self.moveSpeed;
}
}
// Check if collected by hero
if (distance < 30) {
// Give coins to player
var currentCoins = storage.coins || 0;
storage.coins = currentCoins + self.coinValue;
updateCoinsDisplay();
LK.getSound('xpCollect').play();
LK.effects.flashObject(hero, 0xFFD700, 300); // Gold flash
// Remove from coins array
for (var i = coinPickups.length - 1; i >= 0; i--) {
if (coinPickups[i] === self) {
coinPickups.splice(i, 1);
break;
}
}
self.destroy();
}
};
return self;
});
var FastEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('fastEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Weapon graphics removed
self.health = 15;
self.maxHealth = 15;
self.damage = 10;
self.speed = 3;
self.scoreValue = 15;
// Enemy hitbox properties
self.width = 150; // Enemy visual width
self.height = 150; // Enemy visual height
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
// Create multiple explosion visual effects with different colors and timings
LK.effects.flashObject(self, 0xFF4500, 400); // Red-orange flash
LK.effects.flashScreen(0xFF8C00, 150); // Brief orange screen flash
// Add scaling explosion effect
tween(self, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Deal explosion damage to nearby enemies (but prevent chain explosions)
for (var j = 0; j < enemies.length; j++) {
var otherEnemy = enemies[j];
if (otherEnemy !== self) {
var explodeDx = otherEnemy.x - self.x;
var explodeDy = otherEnemy.y - self.y;
var explodeDistance = Math.sqrt(explodeDx * explodeDx + explodeDy * explodeDy);
// Damage enemies within 150 pixel explosion radius
if (explodeDistance < 150) {
// Prevent chain explosions by directly reducing health instead of calling takeDamage
otherEnemy.health -= 25;
if (otherEnemy.health <= 0) {
otherEnemy.health = 0;
// Manually trigger death without explosion effects for other FastEnemies
if (otherEnemy instanceof FastEnemy) {
// Simple death without explosion to prevent recursion
var xpOrb = new XPOrb();
xpOrb.x = otherEnemy.x;
xpOrb.y = otherEnemy.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
otherEnemy.destroy();
LK.setScore(LK.getScore() + otherEnemy.scoreValue);
scoreTxt.setText(LK.getScore());
for (var k = enemies.length - 1; k >= 0; k--) {
if (enemies[k] === otherEnemy) {
enemies.splice(k, 1);
break;
}
}
} else {
// For non-FastEnemies, call takeDamage normally to trigger their death effects
otherEnemy.takeDamage(0);
}
} else {
// Flash effect for damaged but alive enemies
LK.effects.flashObject(otherEnemy, 0xFF4500, 200);
}
// Push enemies away from explosion center
if (explodeDistance > 0) {
otherEnemy.x += explodeDx / explodeDistance * 80;
otherEnemy.y += explodeDy / explodeDistance * 80;
}
}
}
}
// Deal explosion damage to hero if in blast radius
var heroDx = hero.x - self.x;
var heroDy = hero.y - self.y;
var heroDistance = Math.sqrt(heroDx * heroDx + heroDy * heroDy);
if (heroDistance < 150) {
hero.takeDamage(20); // Explosion damage to hero
// Add visual feedback for hero being hit by explosion
LK.effects.flashObject(hero, 0xFF4500, 300);
}
// Drop XP orb
var xpOrb = new XPOrb();
xpOrb.x = self.x;
xpOrb.y = self.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
// 50% chance to drop coin
if (Math.random() < 0.5) {
var coin = new CoinPickup();
coin.x = self.x + (Math.random() - 0.5) * 60;
coin.y = self.y + (Math.random() - 0.5) * 60;
coinPickups.push(coin);
game.addChild(coin);
}
self.destroy();
LK.setScore(LK.getScore() + self.scoreValue);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDestroy').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
// updateWeapon method removed
self.update = function () {
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Weapon update removed
if (distance > 0) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
// Check collision with other enemies
var collisionFound = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self) {
var otherDx = newX - otherEnemy.x;
var otherDy = newY - otherEnemy.y;
var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy);
var minDistance = (self.width + otherEnemy.width) / 4; // Collision threshold
if (otherDistance < minDistance) {
collisionFound = true;
// Push enemies apart
if (otherDistance > 0) {
var pushForce = (minDistance - otherDistance) / 2;
var pushX = otherDx / otherDistance * pushForce;
var pushY = otherDy / otherDistance * pushForce;
newX += pushX;
newY += pushY;
otherEnemy.x -= pushX;
otherEnemy.y -= pushY;
}
}
}
}
// Only move if no major collision or after separation
if (!collisionFound || distance > 100) {
self.x = newX;
self.y = newY;
}
}
};
return self;
});
var HealthCrate = Container.expand(function () {
var self = Container.call(this);
var crateGraphics = self.attachAsset('crate', {
anchorX: 0.5,
anchorY: 0.5
});
crateGraphics.scaleX = 0.8;
crateGraphics.scaleY = 0.8;
self.health = 1;
self.takeDamage = function (damage) {
self.health -= damage;
// Shake effect when hit
LK.effects.flashObject(self, 0xFFFFFF, 100);
tween(self, {
rotation: self.rotation + 0.1
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
rotation: self.rotation - 0.2
}, {
duration: 50,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(self, {
rotation: self.rotation + 0.1
}, {
duration: 50,
easing: tween.easeOut
});
}
});
}
});
if (self.health <= 0) {
var dropChance = Math.random();
if (dropChance < 0.1) {
// 10% chance to drop level up item
var levelPickup = new LevelPickup();
levelPickup.x = self.x;
levelPickup.y = self.y;
levelPickups.push(levelPickup);
game.addChild(levelPickup);
} else if (dropChance < 0.55) {
// 45% chance to drop XP orb
var xpOrb = new XPOrb();
xpOrb.x = self.x;
xpOrb.y = self.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
} else {
// 45% chance to drop health pickup
var healthPickup = new HealthPickup();
healthPickup.x = self.x;
healthPickup.y = self.y;
healthPickups.push(healthPickup);
game.addChild(healthPickup);
}
self.destroy();
LK.getSound('enemyDestroy').play();
for (var i = healthCrates.length - 1; i >= 0; i--) {
if (healthCrates[i] === self) {
healthCrates.splice(i, 1);
break;
}
}
}
};
return self;
});
var HealthPickup = Container.expand(function () {
var self = Container.call(this);
var pickupGraphics = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
pickupGraphics.scaleX = 0.3;
pickupGraphics.scaleY = 0.3;
pickupGraphics.tint = 0xFF0000; // Red color for health
// Add pulsing animation
tween(pickupGraphics, {
scaleX: 0.35,
scaleY: 0.35
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(pickupGraphics, {
scaleX: 0.3,
scaleY: 0.3
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
self.healAmount = 15;
self.update = function () {
// Check if collected by hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 40) {
// Heal player
if (hero.health < hero.maxHealth) {
hero.health = Math.min(hero.health + self.healAmount, hero.maxHealth);
updateHealthBar();
LK.getSound('xpCollect').play();
LK.effects.flashObject(hero, 0x00FF00, 300); // Green flash
}
// Remove from healthPickups array
for (var i = healthPickups.length - 1; i >= 0; i--) {
if (healthPickups[i] === self) {
healthPickups.splice(i, 1);
break;
}
}
self.destroy();
}
};
return self;
});
var Hero = Container.expand(function () {
var self = Container.call(this);
var heroGraphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
var weaponGraphics = self.attachAsset('pistolModel', {
anchorX: 0.5,
anchorY: 0.8 // Better anchor point for weapon rotation
});
weaponGraphics.x = 0;
weaponGraphics.y = 0;
// Set initial weapon scale for better visibility
weaponGraphics.scaleX = 1.2;
weaponGraphics.scaleY = 1.2;
self.maxHealth = 100;
self.health = self.maxHealth;
self.attackDamage = 25;
self.isInvulnerable = false;
// Custom hitbox properties
self.hitboxWidth = 80; // Smaller than visual size for better gameplay
self.hitboxHeight = 100; // Adjusted for character proportions
self.hitboxOffsetX = 0; // Center aligned
self.hitboxOffsetY = 0; // Center aligned
self.takeDamage = function (damage) {
if (self.isInvulnerable) return;
// Apply shield protection
if (self.hasShield) {
damage = Math.floor(damage * 0.5); // 50% damage reduction
}
// Apply armor defense
var defense = self.defense || 0;
damage = Math.max(1, damage - defense); // Minimum 1 damage
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
// Add death visual effects
LK.effects.flashObject(self, 0xFF0000, 1000); // Long red flash
LK.effects.flashScreen(0x8B0000, 800); // Dark red screen flash
// Make hero semi-transparent to show death
tween(self, {
alpha: 0.3,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeOut
});
// Change to death state and show death menu
gameState = 'dead';
// Add a small delay before showing death menu for better UX
LK.setTimeout(function () {
createDeathMenu();
}, 500);
return;
}
// Flash red when taking damage
LK.effects.flashObject(self, 0xFF0000, 300);
LK.getSound('playerHit').play();
// Brief invulnerability
self.isInvulnerable = true;
LK.setTimeout(function () {
self.isInvulnerable = false;
}, 500);
};
self.attack = function (enemy) {
enemy.takeDamage(self.attackDamage);
LK.getSound('enemyHit').play();
// Weapon swing animation
tween(weaponGraphics, {
rotation: weaponGraphics.rotation + Math.PI / 3
}, {
duration: 100
});
};
self.updateWeaponGraphics = function () {
// Map weapon types to their corresponding models
var weaponModels = {
pistol: 'pistolModel',
rifle: 'rifleModel',
shotgun: 'shotgunModel',
machinegun: 'machinegunModel',
sniper: 'rifleModel',
// Use rifle model for sniper
laser: 'laserModel',
explosive: 'explosiveModel'
};
// Get the model for current weapon
var newModel = weaponModels[currentWeapon] || 'pistolModel';
// Remove old weapon graphics
if (weaponGraphics) {
self.removeChild(weaponGraphics);
weaponGraphics.destroy();
}
// Create new weapon graphics with proper model
weaponGraphics = self.attachAsset(newModel, {
anchorX: 0.5,
anchorY: 0.8
});
weaponGraphics.x = 0;
weaponGraphics.y = 0;
weaponGraphics.scaleX = 1.2;
weaponGraphics.scaleY = 1.2;
};
self.updateWeapon = function (targetX, targetY) {
// Calculate angle to target
var dx = targetX - self.x;
var dy = targetY - self.y;
var angle = Math.atan2(dy, dx);
weaponGraphics.rotation = angle + Math.PI / 2;
// Position weapon properly with offset based on rotation
var weaponOffset = 30; // Distance from hero center
weaponGraphics.x = Math.cos(angle) * weaponOffset;
weaponGraphics.y = Math.sin(angle) * weaponOffset;
};
self.shoot = function (targetX, targetY) {
var weapon = weaponTypes[currentWeapon];
// Pre-calculate common values to reduce calculations
var weaponAngle = weaponGraphics.rotation - Math.PI / 2;
var weaponTipOffset = 45; // Distance from hero center to weapon tip
var bulletStartX = self.x + Math.cos(weaponAngle) * weaponTipOffset;
var bulletStartY = self.y + Math.sin(weaponAngle) * weaponTipOffset;
if (currentWeapon === 'shotgun') {
// Ultra-optimized shotgun: pre-calculated spread directions
var bulletCount = 3; // Keep 3 bullets for balance
// Pre-calculated spread directions to avoid trigonometric calculations
var spreadDirections = [{
dx: -0.196,
dy: 0.981
},
// Left spread (~-11.3 degrees)
{
dx: 0,
dy: 1
},
// Center (straight)
{
dx: 0.196,
dy: 0.981
} // Right spread (~11.3 degrees)
];
// Calculate base direction once
var baseDx = targetX - bulletStartX;
var baseDy = targetY - bulletStartY;
var baseDistance = Math.sqrt(baseDx * baseDx + baseDy * baseDy);
if (baseDistance > 0) {
// Normalize base direction
baseDx /= baseDistance;
baseDy /= baseDistance;
for (var i = 0; i < bulletCount; i++) {
var bullet = new Bullet();
bullet.x = bulletStartX;
bullet.y = bulletStartY;
bullet.damage = weapon.damage;
bullet.speed = weapon.bulletSpeed;
// Apply pre-calculated spread using simple rotation
var spread = spreadDirections[i];
var finalDx = baseDx * spread.dy + baseDy * spread.dx;
var finalDy = baseDy * spread.dy - baseDx * spread.dx;
// Set target at fixed distance
bullet.setTarget(bulletStartX + finalDx * 300, bulletStartY + finalDy * 300);
bullets.push(bullet);
game.addChild(bullet);
}
} else {
// Fallback for zero distance
for (var j = 0; j < bulletCount; j++) {
var bullet = new Bullet();
bullet.x = bulletStartX;
bullet.y = bulletStartY;
bullet.damage = weapon.damage;
bullet.speed = weapon.bulletSpeed;
bullet.setTarget(targetX, targetY);
bullets.push(bullet);
game.addChild(bullet);
}
}
} else {
// Single bullet for other weapons
var bullet = new Bullet();
bullet.x = bulletStartX;
bullet.y = bulletStartY;
bullet.damage = weapon.damage;
bullet.speed = weapon.bulletSpeed;
bullet.setTarget(targetX, targetY);
bullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('weaponShoot').play();
// Weapon recoil animation - store original position
var originalWeaponY = weaponGraphics.y;
tween(weaponGraphics, {
y: originalWeaponY + 5
}, {
duration: 50,
onComplete: function onComplete() {
tween(weaponGraphics, {
y: originalWeaponY
}, {
duration: 50
});
}
});
};
self.isWalking = false;
self.moveSpeed = 4;
self.analogMove = function (deltaX, deltaY) {
// Analog movement with wall collision detection
var newX = self.x + deltaX * self.moveSpeed;
var newY = self.y + deltaY * self.moveSpeed;
// Check wall collisions - map boundaries
var mapWidth = 8192;
var mapHeight = 10928;
var wallThickness = 100;
// Prevent going through walls
if (newX < wallThickness + self.hitboxWidth / 2) {
newX = wallThickness + self.hitboxWidth / 2;
}
if (newX > mapWidth - wallThickness - self.hitboxWidth / 2) {
newX = mapWidth - wallThickness - self.hitboxWidth / 2;
}
if (newY < wallThickness + self.hitboxHeight / 2) {
newY = wallThickness + self.hitboxHeight / 2;
}
if (newY > mapHeight - wallThickness - self.hitboxHeight / 2) {
newY = mapHeight - wallThickness - self.hitboxHeight / 2;
}
// Check collision with enemies
var collisionFound = false;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyDx = newX - enemy.x;
var enemyDy = newY - enemy.y;
var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
var minDistance = (self.hitboxWidth + enemy.width) / 2 + 20; // Add buffer
if (enemyDistance < minDistance) {
collisionFound = true;
// Push hero away from enemy
if (enemyDistance > 0) {
var pushForce = minDistance - enemyDistance;
var pushX = enemyDx / enemyDistance * pushForce;
var pushY = enemyDy / enemyDistance * pushForce;
newX += pushX;
newY += pushY;
}
}
}
// Apply movement only if safe or after collision resolution
self.x = newX;
self.y = newY;
};
self.walkTo = function (targetX, targetY) {
// Don't start new walk if already walking
if (self.isWalking) {
tween.stop(self, {
x: true,
y: true
});
}
self.isWalking = true;
// Check wall collisions and clamp target position
var mapWidth = 8192;
var mapHeight = 10928;
var wallThickness = 100;
// Clamp target position within map boundaries
if (targetX < wallThickness + self.hitboxWidth / 2) {
targetX = wallThickness + self.hitboxWidth / 2;
}
if (targetX > mapWidth - wallThickness - self.hitboxWidth / 2) {
targetX = mapWidth - wallThickness - self.hitboxWidth / 2;
}
if (targetY < wallThickness + self.hitboxHeight / 2) {
targetY = wallThickness + self.hitboxHeight / 2;
}
if (targetY > mapHeight - wallThickness - self.hitboxHeight / 2) {
targetY = mapHeight - wallThickness - self.hitboxHeight / 2;
}
// Calculate distance for duration
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var duration = distance * 2; // Adjust speed by changing multiplier
// Walking animation with slight bounce
tween(heroGraphics, {
scaleY: 0.9
}, {
duration: duration / 4,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(heroGraphics, {
scaleY: 1.1
}, {
duration: duration / 4,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(heroGraphics, {
scaleY: 1.0
}, {
duration: duration / 2,
easing: tween.easeOut
});
}
});
}
});
// Check if target position collides with enemies
var safeTargetX = targetX;
var safeTargetY = targetY;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyDx = safeTargetX - enemy.x;
var enemyDy = safeTargetY - enemy.y;
var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
var minDistance = (self.hitboxWidth + enemy.width) / 2 + 30; // Safety buffer
if (enemyDistance < minDistance && enemyDistance > 0) {
// Move target position away from enemy
var pushDistance = minDistance - enemyDistance;
safeTargetX += enemyDx / enemyDistance * pushDistance;
safeTargetY += enemyDy / enemyDistance * pushDistance;
}
}
// Move to safe target position
tween(self, {
x: safeTargetX,
y: safeTargetY
}, {
duration: duration,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isWalking = false;
}
});
};
// Custom collision check method using hitbox
self.checkCollision = function (other) {
// Calculate hitbox bounds for hero
var heroLeft = self.x - self.hitboxWidth / 2 + self.hitboxOffsetX;
var heroRight = self.x + self.hitboxWidth / 2 + self.hitboxOffsetX;
var heroTop = self.y - self.hitboxHeight / 2 + self.hitboxOffsetY;
var heroBottom = self.y + self.hitboxHeight / 2 + self.hitboxOffsetY;
// Get other object bounds (assuming standard bounds)
var otherLeft = other.x - other.width / 2;
var otherRight = other.x + other.width / 2;
var otherTop = other.y - other.height / 2;
var otherBottom = other.y + other.height / 2;
// Check for overlap
return heroLeft < otherRight && heroRight > otherLeft && heroTop < otherBottom && heroBottom > otherTop;
};
// Debug method to visualize hitbox (can be called for testing)
self.showHitbox = function () {
if (!self.hitboxVisual) {
self.hitboxVisual = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5
});
self.hitboxVisual.width = self.hitboxWidth;
self.hitboxVisual.height = self.hitboxHeight;
self.hitboxVisual.x = self.hitboxOffsetX;
self.hitboxVisual.y = self.hitboxOffsetY;
self.hitboxVisual.alpha = 0.3;
self.hitboxVisual.tint = 0x00FF00;
self.addChild(self.hitboxVisual);
}
};
return self;
});
var LevelPickup = Container.expand(function () {
var self = Container.call(this);
var pickupGraphics = self.attachAsset('levelBar', {
anchorX: 0.5,
anchorY: 0.5
});
pickupGraphics.scaleX = 0.4;
pickupGraphics.scaleY = 0.4;
pickupGraphics.tint = 0xFFD700; // Gold color for level up
// Add glowing effect
var glowGraphics = self.attachAsset('levelBar', {
anchorX: 0.5,
anchorY: 0.5
});
glowGraphics.tint = 0xFFFF00; // Yellow glow
glowGraphics.alpha = 0.3;
glowGraphics.scaleX = 0.5;
glowGraphics.scaleY = 0.5;
// Add pulsing animation
tween(pickupGraphics, {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(pickupGraphics, {
scaleX: 0.4,
scaleY: 0.4
}, {
duration: 600,
easing: tween.easeInOut
});
}
});
// Rotating glow effect
tween(glowGraphics, {
rotation: Math.PI * 2
}, {
duration: 2000,
easing: tween.linear,
onFinish: function onFinish() {
tween(glowGraphics, {
rotation: 0
}, {
duration: 2000,
easing: tween.linear
});
}
});
self.update = function () {
// Check if collected by hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 40) {
// Level up player
playerLevel++;
currentXP = 0;
xpToNextLevel = playerLevel * 30;
levelText.setText('Level ' + playerLevel);
// Level up effects
LK.effects.flashScreen(0xFFD700, 500);
hero.maxHealth += 3;
hero.health = hero.maxHealth; // Full heal on level up
updateHealthBar();
updateLevelBar();
LK.getSound('xpCollect').play();
LK.effects.flashObject(hero, 0xFFD700, 500); // Gold flash
// Show upgrade selection
showUpgradeSelection();
// Remove from levelPickups array
for (var i = levelPickups.length - 1; i >= 0; i--) {
if (levelPickups[i] === self) {
levelPickups.splice(i, 1);
break;
}
}
self.destroy();
}
};
return self;
});
var Lightning = Container.expand(function () {
var self = Container.call(this);
var lightningGraphics = self.attachAsset('lightningBolt', {
anchorX: 0.5,
anchorY: 0.5
});
// Add glowing effect to lightning bolt
var glowGraphics = self.attachAsset('lightningBolt', {
anchorX: 0.5,
anchorY: 0.5
});
glowGraphics.tint = 0xFFFF00; // Yellow glow
glowGraphics.alpha = 0.5;
glowGraphics.scaleX = 1.3;
glowGraphics.scaleY = 1.3;
self.speed = 7;
self.damage = 30;
// Lightning bolt hitbox properties
self.width = 80; // Lightning visual width
self.height = 200; // Lightning visual height
self.targetX = 0;
self.targetY = 0;
self.directionX = 0;
self.directionY = 0;
self.lifetime = 0;
self.maxLifetime = 90; // 1.5 seconds at 60fps
// Lightning crackling effect
tween(lightningGraphics, {
alpha: 0.3
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(lightningGraphics, {
alpha: 1.0
}, {
duration: 100,
easing: tween.easeInOut
});
}
});
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
var dx = x - self.x;
var dy = y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.directionX = dx / distance;
self.directionY = dy / distance;
}
// Rotate lightning to face direction
lightningGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
glowGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
};
self.update = function () {
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
self.lifetime++;
// Random flickering effect
if (Math.random() < 0.3) {
lightningGraphics.alpha = Math.random() * 0.5 + 0.5;
}
// Remove lightning after lifetime expires or goes off screen
if (self.lifetime > self.maxLifetime || self.x < -50 || self.x > 8242 || self.y < -50 || self.y > 10978) {
self.destroy();
for (var i = lightningBolts.length - 1; i >= 0; i--) {
if (lightningBolts[i] === self) {
lightningBolts.splice(i, 1);
break;
}
}
}
};
return self;
});
var MapDecoration = Container.expand(function () {
var self = Container.call(this);
self.decorationType = '';
self.setDecoration = function (type) {
self.decorationType = type;
var decorationGraphics;
switch (type) {
case 'tree':
decorationGraphics = self.attachAsset('tree', {
anchorX: 0.5,
anchorY: 1.0
});
decorationGraphics.tint = 0x228B22;
// Add slight random rotation for variety
decorationGraphics.rotation = (Math.random() - 0.5) * 0.3;
break;
case 'grass':
decorationGraphics = self.attachAsset('grass', {
anchorX: 0.5,
anchorY: 0.5
});
decorationGraphics.tint = 0x32CD32;
decorationGraphics.scaleX = 0.8 + Math.random() * 0.4;
decorationGraphics.scaleY = 0.8 + Math.random() * 0.4;
break;
case 'campfire':
decorationGraphics = self.attachAsset('campfire', {
anchorX: 0.5,
anchorY: 0.5
});
decorationGraphics.tint = 0xFF4500;
// Add flickering animation to campfire
tween(decorationGraphics, {
scaleX: 1.1,
scaleY: 1.1,
alpha: 0.8
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(decorationGraphics, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
break;
}
// Make decorations non-interactive and lower layer
self.alpha = 0.7;
};
return self;
});
var Pet = Container.expand(function () {
var self = Container.call(this);
var petGraphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
petGraphics.scaleX = 0.4;
petGraphics.scaleY = 0.4;
petGraphics.alpha = 0.8;
self.petType = '';
self.owner = null;
self.followDistance = 80;
self.ability = '';
self.abilityTimer = 0;
self.abilityCooldown = 300; // 5 seconds
self.setPetType = function (type) {
self.petType = type;
switch (type) {
case 'healing':
petGraphics.tint = 0x00FF00; // Green for healing
self.ability = 'heal';
break;
case 'attack':
petGraphics.tint = 0xFF0000; // Red for attack
self.ability = 'attack';
break;
case 'shield':
petGraphics.tint = 0x0000FF; // Blue for shield
self.ability = 'shield';
break;
}
};
self.update = function () {
if (!self.owner) return;
// Follow owner
var dx = self.owner.x - self.x;
var dy = self.owner.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.followDistance) {
var moveSpeed = 3;
self.x += dx / distance * moveSpeed;
self.y += dy / distance * moveSpeed;
}
// Use ability
self.abilityTimer++;
if (self.abilityTimer >= self.abilityCooldown) {
self.useAbility();
self.abilityTimer = 0;
}
};
self.useAbility = function () {
switch (self.ability) {
case 'heal':
if (self.owner.health < self.owner.maxHealth) {
self.owner.health = Math.min(self.owner.health + 5, self.owner.maxHealth);
LK.effects.flashObject(self.owner, 0x00FF00, 200);
updateHealthBar();
}
break;
case 'attack':
// Find nearest enemy and attack
var nearestEnemy = null;
var nearestDistance = 200;
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) {
nearestEnemy = enemy;
nearestDistance = distance;
}
}
if (nearestEnemy) {
nearestEnemy.takeDamage(15);
LK.effects.flashObject(self, 0xFF0000, 200);
}
break;
case 'shield':
// Provide temporary damage reduction
if (!self.owner.hasShield) {
self.owner.hasShield = true;
self.owner.shieldTime = 180; // 3 seconds
LK.effects.flashObject(self.owner, 0x0000FF, 200);
}
break;
}
};
return self;
});
var SpellEffect = Container.expand(function () {
var self = Container.call(this);
self.spellType = '';
self.duration = 0;
self.power = 0;
self.target = null;
self.lifetime = 0;
self.setSpellType = function (type) {
self.spellType = type;
switch (type) {
case 'fireball':
self.power = 50;
self.duration = 1;
break;
case 'freeze':
self.power = 20;
self.duration = 180; // 3 seconds
break;
case 'heal':
self.power = 30;
self.duration = 1;
break;
case 'shield':
self.power = 0;
self.duration = 300; // 5 seconds
break;
}
};
self.cast = function (caster, target) {
self.target = target;
switch (self.spellType) {
case 'fireball':
if (target && target.takeDamage) {
target.takeDamage(self.power);
LK.effects.flashObject(target, 0xFF4500, 300);
}
break;
case 'freeze':
if (target && target.speed) {
target.frozen = true;
target.originalSpeed = target.speed;
target.speed = 0;
LK.effects.flashObject(target, 0x87CEEB, 300);
}
break;
case 'heal':
if (target && target.health && target.maxHealth) {
target.health = Math.min(target.health + self.power, target.maxHealth);
LK.effects.flashObject(target, 0x00FF00, 300);
if (target === hero) updateHealthBar();
}
break;
case 'shield':
if (target) {
target.hasShield = true;
target.shieldTime = self.duration;
LK.effects.flashObject(target, 0x4169E1, 300);
}
break;
}
};
self.update = function () {
self.lifetime++;
if (self.lifetime >= self.duration) {
if (self.spellType === 'freeze' && self.target && self.target.frozen) {
self.target.frozen = false;
self.target.speed = self.target.originalSpeed;
}
self.destroy();
}
};
return self;
});
var StrongEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('strongEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Weapon graphics removed
self.health = 50;
self.maxHealth = 50;
self.damage = 25;
self.speed = 2;
self.scoreValue = 25;
// Enemy hitbox properties
self.width = 300; // Enemy visual width
self.height = 300; // Enemy visual height
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
// Drop XP orb
var xpOrb = new XPOrb();
xpOrb.x = self.x;
xpOrb.y = self.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
// 50% chance to drop coin
if (Math.random() < 0.5) {
var coin = new CoinPickup();
coin.x = self.x + (Math.random() - 0.5) * 60;
coin.y = self.y + (Math.random() - 0.5) * 60;
coinPickups.push(coin);
game.addChild(coin);
}
self.destroy();
LK.setScore(LK.getScore() + self.scoreValue);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDestroy').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
// updateWeapon method removed
self.update = function () {
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Weapon update removed
if (distance > 0) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
// Check collision with other enemies
var collisionFound = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self) {
var otherDx = newX - otherEnemy.x;
var otherDy = newY - otherEnemy.y;
var otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy);
var minDistance = (self.width + otherEnemy.width) / 4; // Collision threshold
if (otherDistance < minDistance) {
collisionFound = true;
// Push enemies apart
if (otherDistance > 0) {
var pushForce = (minDistance - otherDistance) / 2;
var pushX = otherDx / otherDistance * pushForce;
var pushY = otherDy / otherDistance * pushForce;
newX += pushX;
newY += pushY;
otherEnemy.x -= pushX;
otherEnemy.y -= pushY;
}
}
}
}
// Only move if no major collision or after separation
if (!collisionFound || distance > 100) {
self.x = newX;
self.y = newY;
}
}
};
return self;
});
var UpgradeOption = Container.expand(function () {
var self = Container.call(this);
// Background for upgrade option - made larger
var bgGraphics = self.attachAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
bgGraphics.scaleX = 3.5;
bgGraphics.scaleY = 4;
bgGraphics.tint = 0x2c3e50;
// Title text - made larger
self.titleText = new Text2('', {
size: 80,
fill: 0xFFD700
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.y = -100;
self.addChild(self.titleText);
// Description text - made larger
self.descText = new Text2('', {
size: 60,
fill: 0xFFFFFF
});
self.descText.anchor.set(0.5, 0.5);
self.descText.y = 0;
self.addChild(self.descText);
// Upgrade type
self.upgradeType = '';
self.setUpgrade = function (type, title, description) {
self.upgradeType = type;
self.titleText.setText(title);
self.descText.setText(description);
};
// Hover effect
self.down = function (x, y, obj) {
tween(bgGraphics, {
scaleX: 3.8,
scaleY: 4.3
}, {
duration: 100,
easing: tween.easeOut
});
};
self.up = function (x, y, obj) {
tween(bgGraphics, {
scaleX: 3.5,
scaleY: 4
}, {
duration: 100,
easing: tween.easeIn
});
// Apply the upgrade
applyUpgrade(self.upgradeType);
};
return self;
});
var Wizard = Container.expand(function () {
var self = Container.call(this);
var wizardGraphics = self.attachAsset('Wizard', {
anchorX: 0.5,
anchorY: 0.5
});
// Weapon graphics removed
// Add magical aura effect using the wizard asset
var auraGraphics = self.attachAsset('Wizard', {
anchorX: 0.5,
anchorY: 0.5
});
auraGraphics.tint = 0x9370DB; // Purple aura
auraGraphics.alpha = 0.3;
auraGraphics.scaleX = 1.3;
auraGraphics.scaleY = 1.3;
// Add pulsing magical aura animation
tween(auraGraphics, {
scaleX: 1.8,
scaleY: 1.8,
alpha: 0.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(auraGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
self.health = 60;
self.maxHealth = 60;
self.damage = 20;
self.speed = 1.8;
self.scoreValue = 30;
// Wizard hitbox properties
self.width = 310; // Wizard visual width
self.height = 310; // Wizard visual height
self.lastLightningTime = 0;
self.lightningCooldown = 60; // 1 second at 60fps - much faster attack speed
self.lightningRange = 600;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
// Drop XP orb
var xpOrb = new XPOrb();
xpOrb.x = self.x;
xpOrb.y = self.y;
xpOrbs.push(xpOrb);
game.addChild(xpOrb);
// 50% chance to drop coin
if (Math.random() < 0.5) {
var coin = new CoinPickup();
coin.x = self.x + (Math.random() - 0.5) * 60;
coin.y = self.y + (Math.random() - 0.5) * 60;
coinPickups.push(coin);
game.addChild(coin);
}
self.destroy();
LK.setScore(LK.getScore() + self.scoreValue);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDestroy').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
self.shootLightning = function () {
// Create lightning bolt
var lightning = new Lightning();
lightning.x = self.x;
lightning.y = self.y;
lightning.setTarget(hero.x, hero.y);
lightningBolts.push(lightning);
game.addChild(lightning);
// Enhanced lightning casting effects
LK.effects.flashObject(self, 0xFFFF00, 300);
LK.effects.flashObject(auraGraphics, 0xFFFFFF, 400);
// Dramatic casting animation - wizard grows larger then returns
tween(wizardGraphics, {
scaleX: 1.5,
scaleY: 1.5,
rotation: wizardGraphics.rotation + Math.PI / 6
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(wizardGraphics, {
scaleX: 1.2,
scaleY: 1.2,
rotation: wizardGraphics.rotation - Math.PI / 6
}, {
duration: 250,
easing: tween.easeIn
});
}
});
// Aura intensifies during casting
tween(auraGraphics, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0.6
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(auraGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 300,
easing: tween.easeIn
});
}
});
};
self.update = function () {
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Weapon update removed
// Keep distance and shoot lightning
if (distance < self.lightningRange && distance > 150) {
// Stop moving and prepare to shoot
var currentTime = LK.ticks;
if (currentTime - self.lastLightningTime > self.lightningCooldown) {
self.shootLightning();
self.lastLightningTime = currentTime;
}
} else if (distance > self.lightningRange) {
// Move closer to hero
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
} else if (distance < 150) {
// Move away from hero
if (distance > 0) {
self.x -= dx / distance * self.speed;
self.y -= dy / distance * self.speed;
}
}
};
return self;
});
var XPOrb = Container.expand(function () {
var self = Container.call(this);
var orbGraphics = self.attachAsset('xpOrb', {
anchorX: 0.5,
anchorY: 0.5
});
self.xpValue = 5;
self.moveSpeed = 2;
self.attractRange = 150;
// Add pulsing animation to make XP orbs more attractive
tween(orbGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(orbGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
tween(orbGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
});
self.update = function () {
// XP orb magnetic attraction to other nearby orbs
for (var o = 0; o < xpOrbs.length; o++) {
var otherOrb = xpOrbs[o];
if (otherOrb !== self) {
var orbDx = otherOrb.x - self.x;
var orbDy = otherOrb.y - self.y;
var orbDistance = Math.sqrt(orbDx * orbDx + orbDy * orbDy);
// Attract to nearby orbs within 80 pixels
if (orbDistance < 80 && orbDistance > 0) {
var attractForce = 0.5;
self.x += orbDx / orbDistance * attractForce;
self.y += orbDy / orbDistance * attractForce;
}
}
}
// Move toward hero if within attract range
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.attractRange) {
if (distance > 0) {
self.x += dx / distance * self.moveSpeed;
self.y += dy / distance * self.moveSpeed;
}
}
// Check if collected by hero
if (distance < 30) {
// Give XP to player
currentXP += self.xpValue;
LK.setScore(LK.getScore() + self.xpValue);
scoreTxt.setText(LK.getScore());
LK.getSound('xpCollect').play();
// Remove from xpOrbs array
for (var i = xpOrbs.length - 1; i >= 0; i--) {
if (xpOrbs[i] === self) {
xpOrbs.splice(i, 1);
break;
}
}
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Menu system variables
var gameState = 'menu'; // 'menu', 'playing', 'paused', 'dead'
var menuContainer;
var gameContainer;
var deathMenuContainer;
var weaponSelectContainer;
var hero;
var enemies = [];
var bullets = [];
var xpOrbs = [];
var lightningBolts = [];
var bossBullets = [];
var healthCrates = [];
var healthPickups = [];
var levelPickups = [];
var coinPickups = [];
var pets = [];
var armorPieces = [];
var spellEffects = [];
var equippedArmor = null;
var activePet = null;
var knownSpells = ['heal'];
var coinsText;
var shopContainer;
var scoreboardContainer;
var currentShopCategory = 'weapons'; // 'weapons', 'pets', 'armor', 'spells'
var spawnTimer = 0;
var difficultyLevel = 1;
var spawnedChunks = {}; // Track spawned decoration chunks
var scoreTxt;
var healthBar;
var healthBarBg;
var attackIndicator;
var lastShootTime = 0;
var joystickBase;
var joystickKnob;
var isJoystickActive = false;
var joystickStartX = 0;
var joystickStartY = 0;
var joystickRadius = 80;
var movementX = 0;
var movementY = 0;
var healthText;
var levelBar;
var levelBarBg;
var levelText;
var playerLevel = 1;
var currentXP = 0;
var xpToNextLevel = 30;
var currentWave = 1;
var waveStartTime = 0;
var waveDuration = 60000; // 1 minute in milliseconds
var waveEnemyCount = 0;
var baseEnemiesPerWave = 20;
var waveText;
var waveTimer;
var bossSpawned = false;
var bossHealthBarBg;
var bossHealthBar;
var bossHealthText;
var upgradeSelectionContainer;
var isSelectingUpgrade = false;
var availableUpgrades = [{
type: 'health',
title: 'Health Boost',
description: '+20 Max Health'
}, {
type: 'damage',
title: 'Damage Up',
description: '+10 Attack Damage'
}, {
type: 'speed',
title: 'Speed Boost',
description: '+1 Movement Speed'
}, {
type: 'attackSpeed',
title: 'Attack Speed',
description: 'Faster Shooting'
}, {
type: 'bulletSpeed',
title: 'Bullet Speed',
description: 'Faster Bullets'
}, {
type: 'regen',
title: 'Regeneration',
description: 'Heal 1 HP/sec'
}];
// Weapon types
var weaponTypes = {
pistol: {
name: 'Tabanca',
damage: 25,
shootCooldown: 80,
bulletSpeed: 8,
description: 'Standart silah'
},
rifle: {
name: 'Tüfek',
damage: 40,
shootCooldown: 120,
bulletSpeed: 12,
description: 'Yüksek hasar, yavaş atış'
},
shotgun: {
name: 'Pompalı',
damage: 25,
shootCooldown: 100,
bulletSpeed: 6,
description: '3 mermi, yakın mesafe'
},
machinegun: {
name: 'Makineli',
damage: 8,
shootCooldown: 20,
bulletSpeed: 10,
description: 'Çok hızlı atış'
},
sniper: {
name: 'Keskin Nişancı',
damage: 80,
shootCooldown: 180,
bulletSpeed: 15,
description: 'Yüksek hasar, çok yavaş'
},
laser: {
name: 'Lazer Silahı',
damage: 35,
shootCooldown: 40,
bulletSpeed: 20,
description: 'Hızlı ve hassas'
},
explosive: {
name: 'Patlayıcı',
damage: 50,
shootCooldown: 150,
bulletSpeed: 5,
description: 'Alan hasarı verir'
}
};
var currentWeapon = 'pistol';
var lastRegenTime = 0;
var hasRegen = false;
var mapDecorations = [];
function createEntryMenu() {
// Create menu container
menuContainer = new Container();
game.addChild(menuContainer);
// Create menu background
var menuBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
menuBg.x = 2048 / 2;
menuBg.y = 2732 / 2;
menuBg.scaleX = 8;
menuBg.scaleY = 8;
menuBg.tint = 0x1a1a2e;
menuBg.alpha = 0.9;
menuContainer.addChild(menuBg);
// Create game title
var gameTitle = new Text2('SAVAŞ ALANINDA', {
size: 120,
fill: 0xFFD700
});
gameTitle.anchor.set(0.5, 0.5);
gameTitle.x = 2048 / 2;
gameTitle.y = 2732 / 2 - 200;
menuContainer.addChild(gameTitle);
// Create subtitle
var gameSubtitle = new Text2('Hayatta Kal!', {
size: 80,
fill: 0xFFFFFF
});
gameSubtitle.anchor.set(0.5, 0.5);
gameSubtitle.x = 2048 / 2;
gameSubtitle.y = 2732 / 2 - 100;
menuContainer.addChild(gameSubtitle);
// Create play button background
var playButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
playButtonBg.x = 2048 / 2;
playButtonBg.y = 2732 / 2 + 100;
playButtonBg.scaleX = 4;
playButtonBg.scaleY = 3;
playButtonBg.tint = 0x27ae60;
menuContainer.addChild(playButtonBg);
// Create play button text
var playButtonText = new Text2('OYNA', {
size: 90,
fill: 0xFFFFFF
});
playButtonText.anchor.set(0.5, 0.5);
playButtonText.x = 2048 / 2;
playButtonText.y = 2732 / 2 + 100;
menuContainer.addChild(playButtonText);
// Add button interaction
playButtonBg.down = function (x, y, obj) {
// Button press animation
tween(playButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
playButtonBg.up = function (x, y, obj) {
// Button release animation and start game
tween(playButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
startGame();
}
});
};
// Create weapon select button background
var weaponButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
weaponButtonBg.x = 2048 / 2;
weaponButtonBg.y = 2732 / 2 + 200;
weaponButtonBg.scaleX = 4;
weaponButtonBg.scaleY = 3;
weaponButtonBg.tint = 0xe67e22;
menuContainer.addChild(weaponButtonBg);
// Create weapon select button text
var weaponButtonText = new Text2('SİLAH SEÇ', {
size: 70,
fill: 0xFFFFFF
});
weaponButtonText.anchor.set(0.5, 0.5);
weaponButtonText.x = 2048 / 2;
weaponButtonText.y = 2732 / 2 + 200;
menuContainer.addChild(weaponButtonText);
// Create shop button background
var shopButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
shopButtonBg.x = 2048 / 2;
shopButtonBg.y = 2732 / 2 + 300;
shopButtonBg.scaleX = 4;
shopButtonBg.scaleY = 3;
shopButtonBg.tint = 0x9b59b6;
menuContainer.addChild(shopButtonBg);
// Create shop button text
var shopButtonText = new Text2('DÜKKAN', {
size: 70,
fill: 0xFFFFFF
});
shopButtonText.anchor.set(0.5, 0.5);
shopButtonText.x = 2048 / 2;
shopButtonText.y = 2732 / 2 + 300;
menuContainer.addChild(shopButtonText);
// Create scoreboard button background
var scoreboardButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
scoreboardButtonBg.x = 2048 / 2;
scoreboardButtonBg.y = 2732 / 2 + 400;
scoreboardButtonBg.scaleX = 4;
scoreboardButtonBg.scaleY = 3;
scoreboardButtonBg.tint = 0x34495e;
menuContainer.addChild(scoreboardButtonBg);
// Create scoreboard button text
var scoreboardButtonText = new Text2('SKOR TABLOSU', {
size: 60,
fill: 0xFFFFFF
});
scoreboardButtonText.anchor.set(0.5, 0.5);
scoreboardButtonText.x = 2048 / 2;
scoreboardButtonText.y = 2732 / 2 + 400;
menuContainer.addChild(scoreboardButtonText);
// Add weapon button interaction
weaponButtonBg.down = function (x, y, obj) {
tween(weaponButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
weaponButtonBg.up = function (x, y, obj) {
tween(weaponButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
showWeaponSelect();
}
});
};
// Add shop button interaction
shopButtonBg.down = function (x, y, obj) {
tween(shopButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
shopButtonBg.up = function (x, y, obj) {
tween(shopButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
showShop();
}
});
};
// Add scoreboard button interaction
scoreboardButtonBg.down = function (x, y, obj) {
tween(scoreboardButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
scoreboardButtonBg.up = function (x, y, obj) {
tween(scoreboardButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
showScoreboard();
}
});
};
// Create decorative elements around menu
for (var i = 0; i < 6; i++) {
var decorElement = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
var angle = Math.PI * 2 / 6 * i;
var radius = 300;
decorElement.x = 2048 / 2 + Math.cos(angle) * radius;
decorElement.y = 2732 / 2 + Math.sin(angle) * radius;
decorElement.alpha = 0.3;
decorElement.scaleX = 0.8;
decorElement.scaleY = 0.8;
decorElement.tint = 0x3498db;
menuContainer.addChild(decorElement);
// Add rotation animation
tween(decorElement, {
rotation: Math.PI * 2
}, {
duration: 3000 + i * 500,
easing: tween.linear,
onFinish: function onFinish() {
this.rotation = 0;
}
});
}
}
function showWeaponSelect() {
// Hide menu
menuContainer.visible = false;
gameState = 'weaponSelect';
// Create weapon select container
weaponSelectContainer = new Container();
game.addChild(weaponSelectContainer);
// Create background
var selectBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
selectBg.x = 2048 / 2;
selectBg.y = 2732 / 2;
selectBg.scaleX = 10;
selectBg.scaleY = 10;
selectBg.tint = 0x2c3e50;
selectBg.alpha = 0.9;
weaponSelectContainer.addChild(selectBg);
// Create title
var selectTitle = new Text2('SİLAH SEÇİMİ VE MARKET', {
size: 100,
fill: 0xFFD700
});
selectTitle.anchor.set(0.5, 0.5);
selectTitle.x = 2048 / 2;
selectTitle.y = 250;
weaponSelectContainer.addChild(selectTitle);
// Create coins display
var selectCoinsText = new Text2('Paralar: ' + (storage.coins || 0), {
size: 60,
fill: 0xFFD700
});
selectCoinsText.anchor.set(0.5, 0.5);
selectCoinsText.x = 2048 / 2;
selectCoinsText.y = 320;
weaponSelectContainer.addChild(selectCoinsText);
// Get owned weapons and current coins
var ownedWeapons = storage.ownedWeapons || ['pistol'];
var currentCoins = storage.coins || 0;
// Create weapon options - show all weapons
var weaponIndex = 0;
for (var weaponType in weaponTypes) {
var weapon = weaponTypes[weaponType];
var weaponY = 450 + weaponIndex * 180;
var isOwned = ownedWeapons.indexOf(weaponType) !== -1;
var isSelected = currentWeapon === weaponType;
// Weapon background
var weaponBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
weaponBg.x = 2048 / 2;
weaponBg.y = weaponY;
weaponBg.scaleX = 6;
weaponBg.scaleY = 2.2;
// Set background color based on status
if (isSelected) {
weaponBg.tint = 0x27ae60; // Green for selected
} else if (isOwned) {
weaponBg.tint = 0x3498db; // Blue for owned
} else {
weaponBg.tint = 0x95a5a6; // Gray for not owned
}
weaponSelectContainer.addChild(weaponBg);
// Weapon name
var weaponName = new Text2(weapon.name, {
size: 55,
fill: 0xFFFFFF
});
weaponName.anchor.set(0, 0.5);
weaponName.x = 2048 / 2 - 450;
weaponName.y = weaponY;
weaponSelectContainer.addChild(weaponName);
// Weapon description
var weaponDesc = new Text2(weapon.description, {
size: 38,
fill: 0xBDC3C7
});
weaponDesc.anchor.set(0, 0.5);
weaponDesc.x = 2048 / 2 - 150;
weaponDesc.y = weaponY;
weaponSelectContainer.addChild(weaponDesc);
// Status/price text
var statusText;
if (isOwned) {
if (isSelected) {
statusText = new Text2('SEÇİLİ', {
size: 45,
fill: 0x2ecc71
});
} else {
statusText = new Text2('SEÇ', {
size: 45,
fill: 0x3498db
});
}
} else {
statusText = new Text2('ÜCRETSİZ', {
size: 45,
fill: 0xf39c12
});
}
statusText.anchor.set(1, 0.5);
statusText.x = 2048 / 2 + 450;
statusText.y = weaponY;
weaponSelectContainer.addChild(statusText);
// Create closure to capture weapon type
(function (type, bg, weapon, isOwned, coinsDisplay) {
bg.down = function (x, y, obj) {
tween(bg, {
scaleX: 6.2,
scaleY: 2.4
}, {
duration: 100,
easing: tween.easeOut
});
};
bg.up = function (x, y, obj) {
tween(bg, {
scaleX: 6,
scaleY: 2.2
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
handleWeaponSelectPurchase(type, weapon, isOwned, coinsDisplay);
}
});
};
})(weaponType, weaponBg, weapon, isOwned, selectCoinsText);
weaponIndex++;
}
// Create back button
var backButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
backButtonBg.x = 2048 / 2;
backButtonBg.y = 2732 - 150;
backButtonBg.scaleX = 3;
backButtonBg.scaleY = 2;
backButtonBg.tint = 0x95a5a6;
weaponSelectContainer.addChild(backButtonBg);
var backButtonText = new Text2('GERİ', {
size: 60,
fill: 0xFFFFFF
});
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = 2048 / 2;
backButtonText.y = 2732 - 150;
weaponSelectContainer.addChild(backButtonText);
backButtonBg.down = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3.2,
scaleY: 2.2
}, {
duration: 100,
easing: tween.easeOut
});
};
backButtonBg.up = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3,
scaleY: 2
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
hideWeaponSelect();
}
});
};
}
function hideWeaponSelect() {
weaponSelectContainer.destroy();
weaponSelectContainer = null;
// Update weapon graphics if hero exists
if (hero) {
hero.updateWeaponGraphics();
hero.lastWeapon = currentWeapon;
}
menuContainer.visible = true;
gameState = 'menu';
}
function startGame() {
// Hide menu
menuContainer.visible = false;
// Change game state
gameState = 'playing';
// Initialize game elements
initializeGame();
}
function createDeathMenu() {
// Create death menu container
deathMenuContainer = new Container();
game.addChild(deathMenuContainer);
// Create death menu background
var deathMenuBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
deathMenuBg.x = 2048 / 2;
deathMenuBg.y = 2732 / 2;
deathMenuBg.scaleX = 8;
deathMenuBg.scaleY = 8;
deathMenuBg.tint = 0x8B0000; // Dark red background
deathMenuBg.alpha = 0.9;
deathMenuContainer.addChild(deathMenuBg);
// Create death message
var deathTitle = new Text2('ÖLDÜN!', {
size: 150,
fill: 0xFF0000
});
deathTitle.anchor.set(0.5, 0.5);
deathTitle.x = 2048 / 2;
deathTitle.y = 2732 / 2 - 250;
deathMenuContainer.addChild(deathTitle);
// Create final score display
var finalScoreText = new Text2('Final Skor: ' + LK.getScore(), {
size: 80,
fill: 0xFFFFFF
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 2732 / 2 - 150;
deathMenuContainer.addChild(finalScoreText);
// Create wave reached display
var waveReachedText = new Text2('Ulaşılan Dalga: ' + currentWave, {
size: 60,
fill: 0xFFD700
});
waveReachedText.anchor.set(0.5, 0.5);
waveReachedText.x = 2048 / 2;
waveReachedText.y = 2732 / 2 - 80;
deathMenuContainer.addChild(waveReachedText);
// Create restart button background
var restartButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
restartButtonBg.x = 2048 / 2;
restartButtonBg.y = 2732 / 2 + 50;
restartButtonBg.scaleX = 4;
restartButtonBg.scaleY = 3;
restartButtonBg.tint = 0x27ae60;
deathMenuContainer.addChild(restartButtonBg);
// Create restart button text
var restartButtonText = new Text2('TEKRAR OYNA', {
size: 70,
fill: 0xFFFFFF
});
restartButtonText.anchor.set(0.5, 0.5);
restartButtonText.x = 2048 / 2;
restartButtonText.y = 2732 / 2 + 50;
deathMenuContainer.addChild(restartButtonText);
// Create menu button background
var menuButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
menuButtonBg.x = 2048 / 2;
menuButtonBg.y = 2732 / 2 + 150;
menuButtonBg.scaleX = 4;
menuButtonBg.scaleY = 3;
menuButtonBg.tint = 0x3498db;
deathMenuContainer.addChild(menuButtonBg);
// Create menu button text
var menuButtonText = new Text2('ANA MENÜ', {
size: 70,
fill: 0xFFFFFF
});
menuButtonText.anchor.set(0.5, 0.5);
menuButtonText.x = 2048 / 2;
menuButtonText.y = 2732 / 2 + 150;
deathMenuContainer.addChild(menuButtonText);
// Add restart button interaction
restartButtonBg.down = function (x, y, obj) {
// Button press animation
tween(restartButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
restartButtonBg.up = function (x, y, obj) {
// Button release animation and restart game
tween(restartButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
restartGame();
}
});
};
// Add menu button interaction
menuButtonBg.down = function (x, y, obj) {
// Button press animation
tween(menuButtonBg, {
scaleX: 4.2,
scaleY: 3.2
}, {
duration: 100,
easing: tween.easeOut
});
};
menuButtonBg.up = function (x, y, obj) {
// Button release animation and go to main menu
tween(menuButtonBg, {
scaleX: 4,
scaleY: 3
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
returnToMainMenu();
}
});
};
}
function initializeGame() {
// Create game container for all game elements
gameContainer = new Container();
game.addChild(gameContainer);
// Initialize all game UI and elements here
initializeGameUI();
initializeHero();
createMapBoundaries();
generateMapDecorations();
}
function createMapBoundaries() {
// Define map boundaries - large but bounded world
var mapWidth = 8192; // 4x screen width
var mapHeight = 10928; // 4x screen height
var wallThickness = 100;
// Create top wall
var topWall = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
topWall.x = mapWidth / 2;
topWall.y = 0;
topWall.scaleX = 2;
topWall.scaleY = 2.5;
topWall.tint = 0x404040;
gameContainer.addChild(topWall);
// Create bottom wall
var bottomWall = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
bottomWall.x = mapWidth / 2;
bottomWall.y = mapHeight;
bottomWall.scaleX = 2;
bottomWall.scaleY = 2.5;
bottomWall.tint = 0x404040;
gameContainer.addChild(bottomWall);
// Create left wall
var leftWall = LK.getAsset('wallVertical', {
anchorX: 0.5,
anchorY: 0.5
});
leftWall.x = 0;
leftWall.y = mapHeight / 2;
leftWall.scaleX = 2.5;
leftWall.scaleY = 1;
leftWall.tint = 0x404040;
gameContainer.addChild(leftWall);
// Create right wall
var rightWall = LK.getAsset('wallVertical', {
anchorX: 0.5,
anchorY: 0.5
});
rightWall.x = mapWidth;
rightWall.y = mapHeight / 2;
rightWall.scaleX = 2.5;
rightWall.scaleY = 1;
rightWall.tint = 0x404040;
gameContainer.addChild(rightWall);
// Place coins at map corners and edges
var coinPositions = [{
x: 200,
y: 200
},
// Top left corner
{
x: mapWidth - 200,
y: 200
},
// Top right corner
{
x: 200,
y: mapHeight - 200
},
// Bottom left corner
{
x: mapWidth - 200,
y: mapHeight - 200
},
// Bottom right corner
{
x: mapWidth / 2,
y: 200
},
// Top center
{
x: mapWidth / 2,
y: mapHeight - 200
},
// Bottom center
{
x: 200,
y: mapHeight / 2
},
// Left center
{
x: mapWidth - 200,
y: mapHeight / 2
} // Right center
];
for (var i = 0; i < coinPositions.length; i++) {
var coin = new CoinPickup();
coin.x = coinPositions[i].x;
coin.y = coinPositions[i].y;
coin.coinValue = 25; // Higher value coins at map edges
coinPickups.push(coin);
gameContainer.addChild(coin);
}
}
function initializeGameUI() {
// Create score display
scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create health bar background
healthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0
});
healthBarBg.x = 150;
healthBarBg.y = 50;
LK.gui.topLeft.addChild(healthBarBg);
// Create health bar
healthBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0
});
healthBar.x = 150;
healthBar.y = 50;
LK.gui.topLeft.addChild(healthBar);
// Create health text
healthText = new Text2('100/100', {
size: 40,
fill: 0xFFFFFF
});
healthText.anchor.set(0, 0.5);
healthText.x = 370;
healthText.y = 60;
LK.gui.topLeft.addChild(healthText);
// Create level bar background at bottom
levelBarBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
levelBarBg.x = 2048 / 2;
levelBarBg.y = 2732 - 60;
gameContainer.addChild(levelBarBg);
// Create level bar fill
levelBar = LK.getAsset('levelBar', {
anchorX: 0,
anchorY: 0.5
});
levelBar.x = levelBarBg.x - levelBarBg.width / 2;
levelBar.y = levelBarBg.y;
gameContainer.addChild(levelBar);
// Create level text
levelText = new Text2('Level 1', {
size: 50,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0.5);
levelText.x = levelBarBg.x;
levelText.y = levelBarBg.y - 50;
gameContainer.addChild(levelText);
// Create wave display
waveText = new Text2('Wave 1', {
size: 60,
fill: 0xFFD700
});
waveText.anchor.set(1, 0);
waveText.x = 2048 - 20;
waveText.y = 20;
LK.gui.topRight.addChild(waveText);
// Create wave timer display
waveTimer = new Text2('2:00', {
size: 50,
fill: 0xFFFFFF
});
waveTimer.anchor.set(0.5, 0);
waveTimer.x = 0;
waveTimer.y = 80;
LK.gui.top.addChild(waveTimer);
// Create coins display
coinsText = new Text2('Coins: 0', {
size: 50,
fill: 0xFFD700
});
coinsText.anchor.set(0, 0);
coinsText.x = 20;
coinsText.y = 100;
LK.gui.topLeft.addChild(coinsText);
updateCoinsDisplay();
// Create upgrade selection container
upgradeSelectionContainer = new Container();
upgradeSelectionContainer.visible = false;
LK.gui.center.addChild(upgradeSelectionContainer);
// Create semi-transparent background
var upgradeBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeBg.scaleX = 8;
upgradeBg.scaleY = 8;
upgradeBg.tint = 0x000000;
upgradeBg.alpha = 0.8;
upgradeSelectionContainer.addChild(upgradeBg);
// Create "Choose Upgrade" title
var upgradeTitle = new Text2('Choose Your Upgrade!', {
size: 80,
fill: 0xFFD700
});
upgradeTitle.anchor.set(0.5, 0.5);
upgradeTitle.y = -200;
upgradeSelectionContainer.addChild(upgradeTitle);
// Create boss health bar background
bossHealthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0
});
bossHealthBarBg.x = 0;
bossHealthBarBg.y = 140;
bossHealthBarBg.visible = false;
bossHealthBarBg.scaleX = 2;
LK.gui.top.addChild(bossHealthBarBg);
// Create boss health bar
bossHealthBar = LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0
});
bossHealthBar.x = 0;
bossHealthBar.y = 140;
bossHealthBar.visible = false;
bossHealthBar.scaleX = 2;
bossHealthBar.tint = 0xFF4444; // Red color for boss health
LK.gui.top.addChild(bossHealthBar);
// Create boss health text
bossHealthText = new Text2('', {
size: 40,
fill: 0xFFFFFF
});
bossHealthText.anchor.set(0.5, 0);
bossHealthText.x = 0;
bossHealthText.y = 180;
bossHealthText.visible = false;
LK.gui.top.addChild(bossHealthText);
}
function initializeHero() {
// Create hero
hero = gameContainer.addChild(new Hero());
hero.x = 4096;
hero.y = 5464;
// Initialize weapon tracking
hero.lastWeapon = currentWeapon;
// Apply purchased upgrades from storage
if (storage.maxHealth) {
hero.maxHealth = storage.maxHealth;
hero.health = hero.maxHealth;
}
if (storage.attackDamage) {
hero.attackDamage = storage.attackDamage;
}
if (storage.moveSpeed) {
hero.moveSpeed = storage.moveSpeed;
}
// Camera following variables
cameraX = 0;
cameraY = 0;
cameraFollowSpeed = 0.1;
cameraSmoothing = true;
// Create attack range indicator (initially hidden)
attackIndicator = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5
});
attackIndicator.alpha = 0.3;
attackIndicator.visible = false;
gameContainer.addChild(attackIndicator);
// Create attack range circle that shows max shooting distance
var attackRangeCircle = LK.getAsset('jumpMarker', {
anchorX: 0.5,
anchorY: 0.5
});
attackRangeCircle.scaleX = 8; // 800 pixel radius = 1600 pixel diameter
attackRangeCircle.scaleY = 8; // jumpMarker is 200x200, so 8x scale = 1600
attackRangeCircle.alpha = 0.2;
attackRangeCircle.tint = 0x00FF00; // Green color for range indicator
gameContainer.addChild(attackRangeCircle);
// Store reference for updating position
hero.attackRangeCircle = attackRangeCircle;
// Create analog joystick base
joystickBase = LK.getAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5
});
joystickBase.alpha = 0.2;
joystickBase.visible = false;
joystickBase.scaleX = 1.5;
joystickBase.scaleY = 1.5;
gameContainer.addChild(joystickBase);
// Create analog joystick knob
joystickKnob = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
joystickKnob.alpha = 0.7;
joystickKnob.visible = false;
joystickKnob.scaleX = 0.6;
joystickKnob.scaleY = 0.6;
gameContainer.addChild(joystickKnob);
}
function generateMapDecorations() {
// Define chunk size for decoration spawning (512x512 pixel chunks)
var chunkSize = 512;
var currentChunkX = Math.floor(hero.x / chunkSize);
var currentChunkY = Math.floor(hero.y / chunkSize);
// Check much larger area around hero (7x7 grid for maximum decoration coverage)
for (var chunkOffsetX = -3; chunkOffsetX <= 3; chunkOffsetX++) {
for (var chunkOffsetY = -3; chunkOffsetY <= 3; chunkOffsetY++) {
var chunkX = currentChunkX + chunkOffsetX;
var chunkY = currentChunkY + chunkOffsetY;
var chunkKey = chunkX + ',' + chunkY;
// Skip if this chunk already has decorations
if (spawnedChunks[chunkKey]) {
continue;
}
// Mark chunk as spawned
spawnedChunks[chunkKey] = true;
// Calculate chunk world position
var chunkWorldX = chunkX * chunkSize + chunkSize / 2;
var chunkWorldY = chunkY * chunkSize + chunkSize / 2;
var decorationTypes = ['tree', 'grass', 'campfire'];
// Reduced decoration density for better performance
var decorationStrategies = {
tree: {
count: 3,
// Reduced trees per chunk
minDistance: 30,
maxDistance: 250,
spacing: 80,
heroAvoidDistance: 70
},
// Fewer trees per chunk
grass: {
count: 8,
// Reduced grass per chunk
minDistance: 10,
maxDistance: 250,
spacing: 30,
heroAvoidDistance: 25
},
// Less grass distributed in chunk
campfire: {
count: 2,
// Fewer campfires per chunk
minDistance: 80,
maxDistance: 220,
spacing: 150,
heroAvoidDistance: 50
} // Fewer campfires
};
// Generate decorations for this chunk
for (var type in decorationStrategies) {
var strategy = decorationStrategies[type];
var count = strategy.count;
// Much higher chance restrictions to ensure many more decorations spawn
if (type === 'campfire' && Math.random() > 0.85) continue; // 85% chance for campfire
if (type === 'tree' && Math.random() > 0.95) continue; // 95% chance for trees
for (var i = 0; i < count; i++) {
var decoration = new MapDecoration();
decoration.setDecoration(type);
var validPosition = false;
var attempts = 0;
var maxAttempts = 20; // Reduced attempts per decoration
// Try to find a valid position within this chunk
while (!validPosition && attempts < maxAttempts) {
// Generate random position within chunk bounds
var randomAngle = Math.random() * Math.PI * 2;
var randomDistance = strategy.minDistance + Math.random() * (strategy.maxDistance - strategy.minDistance);
decoration.x = chunkWorldX + Math.cos(randomAngle) * randomDistance;
decoration.y = chunkWorldY + Math.sin(randomAngle) * randomDistance;
// Ensure decoration stays within chunk bounds
decoration.x = Math.max(chunkX * chunkSize, Math.min((chunkX + 1) * chunkSize, decoration.x));
decoration.y = Math.max(chunkY * chunkSize, Math.min((chunkY + 1) * chunkSize, decoration.y));
// Check distance from hero - avoid spawning too close to hero's current position
var heroDistance = Math.sqrt((decoration.x - hero.x) * (decoration.x - hero.x) + (decoration.y - hero.y) * (decoration.y - hero.y));
if (heroDistance < strategy.heroAvoidDistance) {
attempts++;
continue; // Try again if too close to hero
}
// Check for minimum spacing between decorations of same type
var tooClose = false;
for (var j = 0; j < mapDecorations.length; j++) {
var existingDecoration = mapDecorations[j];
if (existingDecoration.decorationType === type) {
var spacingDx = decoration.x - existingDecoration.x;
var spacingDy = decoration.y - existingDecoration.y;
var spacingDistance = Math.sqrt(spacingDx * spacingDx + spacingDy * spacingDy);
if (spacingDistance < strategy.spacing) {
tooClose = true;
break;
}
}
}
if (!tooClose) {
validPosition = true;
}
attempts++;
}
// Only add decoration if we found a valid position
if (validPosition) {
mapDecorations.push(decoration);
gameContainer.addChild(decoration);
}
}
}
}
}
}
// Initialize entry menu on game start
createEntryMenu();
function spawnEnemy() {
// Determine group size with 30% chance for maximum (10 enemies)
var groupSize;
if (Math.random() < 0.3) {
// 30% chance for maximum group size
groupSize = 10;
} else {
// 70% chance for smaller random group size (3-9 enemies)
groupSize = 3 + Math.floor(Math.random() * 7); // Random between 3-9
}
var groupAngle = Math.random() * Math.PI * 2; // Random direction for group
var groupDistance = 500 + Math.random() * 300; // 500-800 pixels from hero
var groupCenterX = hero.x + Math.cos(groupAngle) * groupDistance;
var groupCenterY = hero.y + Math.sin(groupAngle) * groupDistance;
for (var i = 0; i < groupSize; i++) {
var enemyType = Math.random();
var enemy;
if (enemyType < 0.5) {
enemy = new BasicEnemy();
} else if (enemyType < 0.75) {
enemy = new FastEnemy();
} else if (enemyType < 0.95) {
enemy = new StrongEnemy();
} else {
enemy = new Wizard();
}
// Position enemies in a cluster around the group center
var clusterRadius = 150; // Radius of enemy cluster
var clusterAngle = Math.random() * Math.PI * 2;
var clusterDistance = Math.random() * clusterRadius;
enemy.x = groupCenterX + Math.cos(clusterAngle) * clusterDistance;
enemy.y = groupCenterY + Math.sin(clusterAngle) * clusterDistance;
enemies.push(enemy);
game.addChild(enemy);
}
}
function spawnHealthCrate() {
var crate = new HealthCrate();
// Spawn near hero in infinite world
var crateDistance = 200 + Math.random() * 300; // 200-500 pixels from hero
var crateAngle = Math.random() * Math.PI * 2; // Random angle around hero
crate.x = hero.x + Math.cos(crateAngle) * crateDistance;
crate.y = hero.y + Math.sin(crateAngle) * crateDistance;
healthCrates.push(crate);
game.addChild(crate);
}
function updateHealthBar() {
var healthPercent = hero.health / hero.maxHealth;
healthBar.scaleX = healthPercent;
if (healthPercent > 0.5) {
healthBar.tint = 0x4CAF50; // Green
} else if (healthPercent > 0.25) {
healthBar.tint = 0xFFC107; // Yellow
} else {
healthBar.tint = 0xF44336; // Red
}
// Update health text
healthText.setText(Math.ceil(hero.health) + '/' + hero.maxHealth);
}
function updateLevelBar() {
var xpPercent = currentXP / xpToNextLevel;
levelBar.scaleX = xpPercent;
// Check for level up
if (currentXP >= xpToNextLevel) {
playerLevel++;
currentXP = 0;
xpToNextLevel = playerLevel * 30; // Increase XP requirement
levelText.setText('Level ' + playerLevel);
// Level up effects
LK.effects.flashScreen(0xFFD700, 500);
hero.maxHealth += 3;
hero.health = hero.maxHealth; // Full heal on level up
// Show upgrade selection
showUpgradeSelection();
}
}
function showUpgradeSelection() {
isSelectingUpgrade = true;
upgradeSelectionContainer.visible = true;
// Remove any existing upgrade options
for (var i = upgradeSelectionContainer.children.length - 1; i >= 0; i--) {
var child = upgradeSelectionContainer.children[i];
if (child instanceof UpgradeOption) {
child.destroy();
}
}
// Randomly select 3 different upgrades
var selectedUpgrades = [];
var tempUpgrades = availableUpgrades.slice(); // Copy array
for (var j = 0; j < 3 && tempUpgrades.length > 0; j++) {
var randomIndex = Math.floor(Math.random() * tempUpgrades.length);
selectedUpgrades.push(tempUpgrades[randomIndex]);
tempUpgrades.splice(randomIndex, 1);
}
// Create upgrade option buttons - vertical layout
for (var k = 0; k < selectedUpgrades.length; k++) {
var option = new UpgradeOption();
var upgrade = selectedUpgrades[k];
option.setUpgrade(upgrade.type, upgrade.title, upgrade.description);
option.x = 0; // Center horizontally
option.y = (k - 1) * 300; // Position vertically: -300, 0, 300
upgradeSelectionContainer.addChild(option);
}
}
function applyUpgrade(upgradeType) {
// Mark upgrade as applied
for (var i = 0; i < availableUpgrades.length; i++) {
if (availableUpgrades[i].type === upgradeType) {
availableUpgrades[i].applied = true;
break;
}
}
switch (upgradeType) {
case 'health':
hero.maxHealth += 20;
hero.health += 20;
updateHealthBar();
break;
case 'damage':
hero.attackDamage += 10;
break;
case 'speed':
hero.moveSpeed += 1;
break;
case 'attackSpeed':
// Handled in auto-shoot logic
break;
case 'bulletSpeed':
// Increase all future bullet speeds
Bullet.prototype.baseSpeed = 12; // Default is 8
break;
case 'regen':
hasRegen = true;
break;
}
// Hide upgrade selection
isSelectingUpgrade = false;
upgradeSelectionContainer.visible = false;
}
function updateBossHealthBar() {
// Find if there's a boss enemy alive
var currentBoss = null;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] instanceof Boss) {
currentBoss = enemies[i];
break;
}
}
// Show/hide boss health bar based on boss presence
if (currentBoss) {
bossHealthBarBg.visible = true;
bossHealthBar.visible = true;
bossHealthText.visible = true;
// Update boss health bar
var bossHealthPercent = currentBoss.health / currentBoss.maxHealth;
bossHealthBar.scaleX = bossHealthPercent * 2; // Scale to match background
// Update boss health text
bossHealthText.setText('Boss: ' + Math.ceil(currentBoss.health) + '/' + currentBoss.maxHealth);
} else {
bossHealthBarBg.visible = false;
bossHealthBar.visible = false;
bossHealthText.visible = false;
}
}
function startNewWave() {
// Increment wave first
currentWave++;
waveStartTime = Date.now();
waveEnemyCount = 0;
bossSpawned = false;
waveText.setText('Wave ' + currentWave);
// Wave start effects
LK.effects.flashScreen(0x00FF00, 800);
// Increase difficulty each wave
difficultyLevel = currentWave;
}
function restartGame() {
// Hide death menu
deathMenuContainer.visible = false;
// Reset all game variables
resetGameVariables();
// Change game state
gameState = 'playing';
// Clean up existing game elements
cleanupGameElements();
// Initialize game again
initializeGame();
}
function returnToMainMenu() {
// Hide death menu
deathMenuContainer.visible = false;
// Clean up existing game elements
cleanupGameElements();
// Reset game variables
resetGameVariables();
// Show main menu
menuContainer.visible = true;
gameState = 'menu';
}
function resetGameVariables() {
// Reset player stats
playerLevel = 1;
currentXP = 0;
xpToNextLevel = 30;
currentWave = 1;
waveStartTime = 0;
waveEnemyCount = 0;
bossSpawned = false;
lastRegenTime = 0;
hasRegen = false;
spawnTimer = 0;
difficultyLevel = 1;
lastShootTime = 0;
isJoystickActive = false;
movementX = 0;
movementY = 0;
isSelectingUpgrade = false;
// Reset score
LK.setScore(0);
// Reset upgrade availability
for (var i = 0; i < availableUpgrades.length; i++) {
availableUpgrades[i].applied = false;
}
// Reset bullet speed
Bullet.prototype.baseSpeed = 8;
// Reset weapon to default
currentWeapon = 'pistol';
}
function cleanupGameElements() {
// Destroy all existing game elements
if (gameContainer) {
gameContainer.destroy();
gameContainer = null;
}
// Destroy death menu if it exists
if (deathMenuContainer) {
deathMenuContainer.destroy();
deathMenuContainer = null;
}
// Clear all arrays
enemies = [];
bullets = [];
xpOrbs = [];
lightningBolts = [];
bossBullets = [];
healthCrates = [];
healthPickups = [];
levelPickups = [];
coinPickups = [];
mapDecorations = [];
spawnedChunks = {}; // Reset spawned chunks tracking
}
function updateWaveTimer() {
var currentTime = Date.now();
var timeElapsed = currentTime - waveStartTime;
var timeRemaining = Math.max(0, waveDuration - timeElapsed);
var minutes = Math.floor(timeRemaining / 60000);
var seconds = Math.floor(timeRemaining % 60000 / 1000);
var timeText = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
waveTimer.setText(timeText);
// Check if wave time is up or all enemies defeated
if (timeRemaining <= 0 || enemies.length === 0 && waveEnemyCount >= 10) {
startNewWave();
}
}
function updateCamera() {
// Calculate target camera position (center hero on screen)
var targetCameraX = -(hero.x - 2048 / 2);
var targetCameraY = -(hero.y - 2732 / 2);
// No camera bounds for infinite world
if (cameraSmoothing) {
// Smooth camera following using interpolation
cameraX += (targetCameraX - cameraX) * cameraFollowSpeed;
cameraY += (targetCameraY - cameraY) * cameraFollowSpeed;
} else {
// Direct camera following
cameraX = targetCameraX;
cameraY = targetCameraY;
}
// Apply camera position to game container
game.x = cameraX;
game.y = cameraY;
// Update level bar position to follow hero
levelBarBg.x = hero.x;
levelBarBg.y = hero.y + 300; // Position below hero
levelBar.x = levelBarBg.x - levelBarBg.width / 2;
levelBar.y = levelBarBg.y;
levelText.x = levelBarBg.x;
levelText.y = levelBarBg.y - 50;
}
function handleMove(x, y, obj) {
// Handle analog joystick movement
if (isJoystickActive) {
var deltaX = x - joystickStartX;
var deltaY = y - joystickStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Limit joystick range
if (distance > joystickRadius) {
deltaX = deltaX / distance * joystickRadius;
deltaY = deltaY / distance * joystickRadius;
}
// Update joystick knob position
joystickKnob.x = joystickStartX + deltaX;
joystickKnob.y = joystickStartY + deltaY;
// Calculate movement values (-1 to 1)
movementX = deltaX / joystickRadius;
movementY = deltaY / joystickRadius;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only process game input when in playing state
if (gameState === 'playing') {
// Make hero walk to touch position
hero.walkTo(x, y);
}
};
game.up = function (x, y, obj) {
// Only process game input when in playing state
if (gameState === 'playing') {
// Deactivate analog joystick
if (isJoystickActive) {
isJoystickActive = false;
joystickBase.visible = false;
joystickKnob.visible = false;
movementX = 0;
movementY = 0;
}
attackIndicator.visible = false;
}
// Allow death menu interactions when dead
else if (gameState === 'dead') {
// Death menu interactions are handled by the menu buttons themselves
// No additional processing needed here
}
};
game.update = function () {
// Only run game logic when in playing state
if (gameState !== 'playing') {
return;
}
// Initialize wave system on first update
if (waveStartTime === 0) {
waveStartTime = Date.now();
}
// Pause game logic during upgrade selection
if (isSelectingUpgrade) {
return;
}
// Update wave timer
updateWaveTimer();
// Apply regeneration if unlocked
if (hasRegen && hero.health < hero.maxHealth) {
var currentTime = LK.ticks;
if (currentTime - lastRegenTime > 60) {
// 60 ticks = 1 second
hero.health = Math.min(hero.health + 1, hero.maxHealth);
lastRegenTime = currentTime;
updateHealthBar();
}
}
// Process analog movement
if (isJoystickActive && (Math.abs(movementX) > 0.1 || Math.abs(movementY) > 0.1)) {
hero.analogMove(movementX, movementY);
}
spawnTimer++;
// Check if this is a boss wave (every 3rd wave: 3, 6, 9, etc.)
var isBossWave = currentWave % 3 === 0;
// Boss spawning for every 3rd wave
if (isBossWave && !bossSpawned) {
var boss = new Boss();
// Spawn boss near hero in infinite world
var bossDistance = 600 + Math.random() * 200; // 600-800 pixels from hero
var bossAngle = Math.random() * Math.PI * 2; // Random angle around hero
boss.x = hero.x + Math.cos(bossAngle) * bossDistance;
boss.y = hero.y + Math.sin(bossAngle) * bossDistance;
enemies.push(boss);
game.addChild(boss);
bossSpawned = true;
// Boss spawn effects
LK.effects.flashScreen(0xFF0000, 1000);
} else if (!isBossWave) {
// Regular enemy spawning only on non-boss waves - spawn new groups when all enemies are defeated
// Check if all enemies are defeated and we haven't reached the wave limit
if (enemies.length === 0 && waveEnemyCount < 10) {
// Allow up to 10 groups per wave
spawnEnemy(); // This spawns one group of enemies
waveEnemyCount++; // Track number of groups spawned this wave
spawnTimer = 0;
}
}
// Check if weapon changed and update graphics if needed
if (hero.lastWeapon !== currentWeapon) {
hero.updateWeaponGraphics();
hero.lastWeapon = currentWeapon;
}
// Update weapon to point towards nearest enemy
var nearestEnemy = null;
var nearestDistance = Infinity;
for (var j = 0; j < enemies.length; j++) {
var tempEnemy = enemies[j];
var tempDx = hero.x - tempEnemy.x;
var tempDy = hero.y - tempEnemy.y;
var tempDistance = Math.sqrt(tempDx * tempDx + tempDy * tempDy);
if (tempDistance < nearestDistance) {
nearestEnemy = tempEnemy;
nearestDistance = tempDistance;
}
}
if (nearestEnemy) {
hero.updateWeapon(nearestEnemy.x, nearestEnemy.y);
// Auto-shoot while moving - shoot at nearest enemy
var currentTime = LK.ticks;
var weapon = weaponTypes[currentWeapon];
var shootCooldown = weapon.shootCooldown;
// Check if attack speed upgrade was selected
for (var u = 0; u < availableUpgrades.length; u++) {
if (availableUpgrades[u].type === 'attackSpeed' && availableUpgrades[u].applied) {
shootCooldown = Math.floor(shootCooldown * 0.5); // 50% faster shooting
break;
}
}
if (currentTime - lastShootTime > shootCooldown && nearestDistance < 800) {
hero.shoot(nearestEnemy.x, nearestEnemy.y);
lastShootTime = currentTime;
}
}
// Check bullet collisions with enemies
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
var bulletHit = false;
for (var e = enemies.length - 1; e >= 0; e--) {
var enemy = enemies[e];
if (bullet.intersects(enemy)) {
// Bullet hits enemy
enemy.takeDamage(bullet.damage);
bullet.destroy();
bullets.splice(b, 1);
bulletHit = true;
break;
}
}
}
// Check lightning bolt collisions with hero
for (var l = lightningBolts.length - 1; l >= 0; l--) {
var lightning = lightningBolts[l];
if (hero.checkCollision(lightning)) {
// Lightning hits hero
hero.takeDamage(lightning.damage);
LK.effects.flashObject(hero, 0xFFFF00, 300);
lightning.destroy();
lightningBolts.splice(l, 1);
}
}
// Check boss bullet collisions with hero
for (var bb = bossBullets.length - 1; bb >= 0; bb--) {
var bossBullet = bossBullets[bb];
if (hero.checkCollision(bossBullet)) {
// Boss bullet hits hero
hero.takeDamage(bossBullet.damage);
LK.effects.flashObject(hero, 0xFF4500, 300);
bossBullet.destroy();
bossBullets.splice(bb, 1);
}
}
// Check collisions and close combat between hero and enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var dx = hero.x - enemy.x;
var dy = hero.y - enemy.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Auto-attack when very close (melee range)
if (distance < 60 && !enemy.justAttacked) {
hero.attack(enemy);
enemy.justAttacked = true;
// Reset attack flag after short delay
LK.setTimeout(function () {
if (enemy && !enemy.destroyed) {
enemy.justAttacked = false;
}
}, 300);
} else if (hero.checkCollision(enemy) && !enemy.justHitHero) {
// Take damage from collision but don't destroy enemy
hero.takeDamage(enemy.damage);
enemy.justHitHero = true;
// Reset hit flag after short delay to prevent rapid damage
LK.setTimeout(function () {
if (enemy && !enemy.destroyed) {
enemy.justHitHero = false;
}
}, 1000); // 1 second cooldown between hits
}
// Enemies no longer disappear based on distance from hero
// They will persist until killed by the player
}
// Update high score
var currentScore = LK.getScore();
if (currentScore > (storage.highScore || 0)) {
storage.highScore = currentScore;
}
// Update health bar
updateHealthBar();
// Update level bar
updateLevelBar();
// Update boss health bar
updateBossHealthBar();
// Spawn health crates occasionally
if (LK.ticks % 600 === 0) {
// Every 10 seconds
spawnHealthCrate();
}
// Check bullet collisions with health crates
for (var bc = bullets.length - 1; bc >= 0; bc--) {
var bulletCrate = bullets[bc];
var crateHit = false;
for (var hc = healthCrates.length - 1; hc >= 0; hc--) {
var healthCrate = healthCrates[hc];
if (bulletCrate.intersects(healthCrate)) {
// Bullet hits crate
healthCrate.takeDamage(bulletCrate.damage);
bulletCrate.destroy();
bullets.splice(bc, 1);
crateHit = true;
break;
}
}
}
// Check hero melee attacks on crates
for (var cr = healthCrates.length - 1; cr >= 0; cr--) {
var crate = healthCrates[cr];
var crateDx = hero.x - crate.x;
var crateDy = hero.y - crate.y;
var crateDistance = Math.sqrt(crateDx * crateDx + crateDy * crateDy);
// Auto-attack crates when very close
if (crateDistance < 60 && !crate.justAttacked) {
crate.takeDamage(hero.attackDamage);
crate.justAttacked = true;
// Reset attack flag after short delay
LK.setTimeout(function () {
if (crate && !crate.destroyed) {
crate.justAttacked = false;
}
}, 300);
}
}
// Clean up decorations that are too far from hero
for (var d = mapDecorations.length - 1; d >= 0; d--) {
var decoration = mapDecorations[d];
var decorationDistance = Math.sqrt((decoration.x - hero.x) * (decoration.x - hero.x) + (decoration.y - hero.y) * (decoration.y - hero.y));
if (decorationDistance > 3000) {
// Remove decorations more than 3000 pixels from hero
decoration.destroy();
mapDecorations.splice(d, 1);
}
}
// Dynamically generate new decorations as hero moves
if (LK.ticks % 60 === 0) {
// Every 1 second - much more frequent decoration generation
generateMapDecorations();
}
// Update attack range circle position to follow hero
if (hero.attackRangeCircle) {
hero.attackRangeCircle.x = hero.x;
hero.attackRangeCircle.y = hero.y;
}
// Update pets
for (var petIndex = 0; petIndex < pets.length; petIndex++) {
pets[petIndex].update();
}
// Update spell effects
for (var spellIndex = spellEffects.length - 1; spellIndex >= 0; spellIndex--) {
var spell = spellEffects[spellIndex];
spell.update();
if (spell.destroyed) {
spellEffects.splice(spellIndex, 1);
}
}
// Update hero shield time
if (hero.hasShield && hero.shieldTime) {
hero.shieldTime--;
if (hero.shieldTime <= 0) {
hero.hasShield = false;
hero.shieldTime = 0;
}
}
// Cast spells randomly when enemies nearby
if (knownSpells.length > 1 && LK.ticks % 240 === 0) {
// Every 4 seconds
var nearbyEnemies = [];
for (var en = 0; en < enemies.length; en++) {
var enemy = enemies[en];
var dx = hero.x - enemy.x;
var dy = hero.y - enemy.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 300) {
nearbyEnemies.push(enemy);
}
}
if (nearbyEnemies.length > 0) {
var randomSpell = knownSpells[Math.floor(Math.random() * knownSpells.length)];
var spellEffect = new SpellEffect();
spellEffect.setSpellType(randomSpell);
if (randomSpell === 'heal' || randomSpell === 'shield') {
spellEffect.cast(hero, hero);
} else {
var randomEnemy = nearbyEnemies[Math.floor(Math.random() * nearbyEnemies.length)];
spellEffect.cast(hero, randomEnemy);
}
spellEffects.push(spellEffect);
}
}
// Update camera to follow hero
updateCamera();
scoreTxt.setText(LK.getScore());
updateCoinsDisplay();
};
function updateCoinsDisplay() {
var currentCoins = storage.coins || 0;
if (coinsText) {
coinsText.setText('Coins: ' + currentCoins);
}
}
function showShop() {
// Hide menu
menuContainer.visible = false;
gameState = 'shop';
// Create shop container
shopContainer = new Container();
game.addChild(shopContainer);
// Create background
var shopBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
shopBg.x = 2048 / 2;
shopBg.y = 2732 / 2;
shopBg.scaleX = 10;
shopBg.scaleY = 10;
shopBg.tint = 0x2c3e50;
shopBg.alpha = 0.9;
shopContainer.addChild(shopBg);
// Create title
var shopTitle = new Text2('DÜKKAN', {
size: 120,
fill: 0xFFD700
});
shopTitle.anchor.set(0.5, 0.5);
shopTitle.x = 2048 / 2;
shopTitle.y = 200;
shopContainer.addChild(shopTitle);
// Create category buttons
var categories = [{
name: 'SİLAHLAR',
key: 'weapons'
}, {
name: 'EVCIL HAYVANLAR',
key: 'pets'
}, {
name: 'ZIRHLAR',
key: 'armor'
}, {
name: 'BÜYÜLER',
key: 'spells'
}];
for (var c = 0; c < categories.length; c++) {
var category = categories[c];
var catBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
catBg.x = 2048 / 2 + (c - 1.5) * 300;
catBg.y = 300;
catBg.scaleX = 2.5;
catBg.scaleY = 1.5;
catBg.tint = currentShopCategory === category.key ? 0x27ae60 : 0x7f8c8d;
shopContainer.addChild(catBg);
var catText = new Text2(category.name, {
size: 35,
fill: 0xFFFFFF
});
catText.anchor.set(0.5, 0.5);
catText.x = catBg.x;
catText.y = catBg.y;
shopContainer.addChild(catText);
(function (categoryKey, bg) {
bg.down = function (x, y, obj) {
currentShopCategory = categoryKey;
hideShop();
showShop();
};
})(category.key, catBg);
}
// Create coins display
var shopCoinsText = new Text2('Coins: ' + (storage.coins || 0), {
size: 80,
fill: 0xFFD700
});
shopCoinsText.anchor.set(0.5, 0.5);
shopCoinsText.x = 2048 / 2;
shopCoinsText.y = 400;
shopContainer.addChild(shopCoinsText);
// Shop items based on category
var shopItems = [];
if (currentShopCategory === 'weapons') {
shopItems = [{
name: 'Max Health +20',
cost: 50,
type: 'health'
}, {
name: 'Attack +10',
cost: 75,
type: 'damage'
}, {
name: 'Speed +1',
cost: 100,
type: 'speed'
}, {
name: 'Makineli Tüfek',
cost: 200,
type: 'weapon',
weaponType: 'machinegun'
}, {
name: 'Keskin Nişancı',
cost: 300,
type: 'weapon',
weaponType: 'sniper'
}, {
name: 'Lazer Silahı',
cost: 250,
type: 'weapon',
weaponType: 'laser'
}, {
name: 'Patlayıcı',
cost: 350,
type: 'weapon',
weaponType: 'explosive'
}];
} else if (currentShopCategory === 'pets') {
shopItems = [{
name: 'İyileştirici Pet',
cost: 150,
type: 'pet',
petType: 'healing'
}, {
name: 'Saldırı Peti',
cost: 200,
type: 'pet',
petType: 'attack'
}, {
name: 'Kalkan Peti',
cost: 250,
type: 'pet',
petType: 'shield'
}];
} else if (currentShopCategory === 'armor') {
shopItems = [{
name: 'Hafif Zırh',
cost: 100,
type: 'armor',
armorType: 'light'
}, {
name: 'Orta Zırh',
cost: 200,
type: 'armor',
armorType: 'medium'
}, {
name: 'Ağır Zırh',
cost: 400,
type: 'armor',
armorType: 'heavy'
}];
} else if (currentShopCategory === 'spells') {
shopItems = [{
name: 'Ateş Topu',
cost: 80,
type: 'spell',
spellType: 'fireball'
}, {
name: 'Dondurucu',
cost: 120,
type: 'spell',
spellType: 'freeze'
}, {
name: 'Kalkan Büyüsü',
cost: 180,
type: 'spell',
spellType: 'shield'
}];
}
// Create shop item buttons
for (var i = 0; i < shopItems.length; i++) {
var item = shopItems[i];
var itemY = 600 + i * 150;
// Item background
var itemBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
itemBg.x = 2048 / 2;
itemBg.y = itemY;
itemBg.scaleX = 6;
itemBg.scaleY = 2;
var currentCoins = storage.coins || 0;
var ownedWeapons = storage.ownedWeapons || ['pistol'];
var canPurchase = true;
// Check weapon-specific conditions
if (item.type === 'weapon') {
// Already owned this weapon
if (ownedWeapons.indexOf(item.weaponType) !== -1) {
canPurchase = false;
itemBg.tint = 0x95a5a6; // Gray for already owned
}
// At weapon limit (2 weapons max)
else if (ownedWeapons.length >= 2) {
canPurchase = false;
itemBg.tint = 0x8e44ad; // Purple for weapon limit reached
}
// Can purchase weapon (has enough coins)
else if (currentCoins >= item.cost) {
canPurchase = true;
itemBg.tint = 0x27ae60; // Green for available
} else {
canPurchase = false;
itemBg.tint = 0x7f8c8d; // Gray for insufficient coins
}
} else {
// Non-weapon items
canPurchase = currentCoins >= item.cost;
itemBg.tint = currentCoins >= item.cost ? 0x27ae60 : 0x7f8c8d;
}
shopContainer.addChild(itemBg);
// Item name
var itemName = new Text2(item.name, {
size: 60,
fill: 0xFFFFFF
});
itemName.anchor.set(0, 0.5);
itemName.x = 2048 / 2 - 300;
itemName.y = itemY;
shopContainer.addChild(itemName);
// Item cost
var itemCost = new Text2(item.cost + ' Coins', {
size: 50,
fill: 0xFFD700
});
itemCost.anchor.set(1, 0.5);
itemCost.x = 2048 / 2 + 300;
itemCost.y = itemY;
shopContainer.addChild(itemCost);
// Create closure for button interaction
(function (shopItem, bg, nameText, costText) {
bg.down = function (x, y, obj) {
// Only animate if item can be purchased
var currentCoins = storage.coins || 0;
var ownedWeapons = storage.ownedWeapons || ['pistol'];
var canPurchaseNow = true;
if (shopItem.type === 'weapon') {
if (ownedWeapons.indexOf(shopItem.weaponType) !== -1 || ownedWeapons.length >= 2 || currentCoins < shopItem.cost) {
canPurchaseNow = false;
}
} else if (currentCoins < shopItem.cost) {
canPurchaseNow = false;
}
if (canPurchaseNow) {
tween(bg, {
scaleX: 6.2,
scaleY: 2.2
}, {
duration: 100,
easing: tween.easeOut
});
}
};
bg.up = function (x, y, obj) {
tween(bg, {
scaleX: 6,
scaleY: 2
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
buyItem(shopItem, shopCoinsText, bg, nameText, costText);
}
});
};
})(item, itemBg, itemName, itemCost);
}
// Create back button
var backButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
backButtonBg.x = 2048 / 2;
backButtonBg.y = 2732 - 200;
backButtonBg.scaleX = 3;
backButtonBg.scaleY = 2;
backButtonBg.tint = 0x95a5a6;
shopContainer.addChild(backButtonBg);
var backButtonText = new Text2('GERİ', {
size: 60,
fill: 0xFFFFFF
});
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = 2048 / 2;
backButtonText.y = 2732 - 200;
shopContainer.addChild(backButtonText);
backButtonBg.down = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3.2,
scaleY: 2.2
}, {
duration: 100,
easing: tween.easeOut
});
};
backButtonBg.up = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3,
scaleY: 2
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
hideShop();
}
});
};
}
function buyItem(item, coinsText, bg, nameText, costText) {
var currentCoins = storage.coins || 0;
if (currentCoins >= item.cost) {
// For weapon purchases, validate conditions first before deducting coins
if (item.type === 'weapon') {
if (!storage.ownedWeapons) {
storage.ownedWeapons = ['pistol']; // Start with only pistol
}
// Check if weapon is already owned
if (storage.ownedWeapons.indexOf(item.weaponType) !== -1) {
// Already owned, show message and don't purchase
LK.effects.flashScreen(0xFFFF00, 200);
return;
}
// Check weapon limit (maximum 2 weapons)
if (storage.ownedWeapons.length >= 2) {
// At weapon limit, show error message
LK.effects.flashScreen(0xFF0000, 300);
return;
}
}
// Deduct coins only after all validations pass
storage.coins = currentCoins - item.cost;
// Apply upgrade based on type
switch (item.type) {
case 'health':
var currentMaxHealth = storage.maxHealth || 100;
storage.maxHealth = currentMaxHealth + 20;
break;
case 'damage':
var currentDamage = storage.attackDamage || 25;
storage.attackDamage = currentDamage + 10;
break;
case 'speed':
var currentSpeed = storage.moveSpeed || 4;
storage.moveSpeed = currentSpeed + 1;
break;
case 'weapon':
// Add new weapon to inventory (validation already passed above)
if (!storage.ownedWeapons) {
storage.ownedWeapons = ['pistol'];
}
storage.ownedWeapons.push(item.weaponType);
// Set as current weapon if player wants
currentWeapon = item.weaponType;
// Update weapon graphics if hero exists
if (hero) {
hero.updateWeaponGraphics();
hero.lastWeapon = currentWeapon;
}
break;
case 'pet':
// Remove existing pet if any
if (activePet) {
activePet.destroy();
for (var p = pets.length - 1; p >= 0; p--) {
if (pets[p] === activePet) {
pets.splice(p, 1);
break;
}
}
}
// Create new pet
var newPet = new Pet();
newPet.setPetType(item.petType);
newPet.owner = hero;
newPet.x = hero.x + 50;
newPet.y = hero.y + 50;
pets.push(newPet);
gameContainer.addChild(newPet);
activePet = newPet;
break;
case 'armor':
// Remove existing armor if any
if (equippedArmor && hero) {
hero.defense = (hero.defense || 0) - equippedArmor.defenseBonus;
hero.maxHealth -= equippedArmor.healthBonus;
hero.health = Math.max(1, hero.health - equippedArmor.healthBonus);
hero.moveSpeed += equippedArmor.speedPenalty;
}
// Create and equip new armor
var newArmor = new ArmorPiece();
newArmor.setArmorType(item.armorType);
if (hero) {
newArmor.equip(hero);
}
equippedArmor = newArmor;
break;
case 'spell':
// Add spell to known spells
if (knownSpells.indexOf(item.spellType) === -1) {
knownSpells.push(item.spellType);
}
break;
}
// Update display
coinsText.setText('Coins: ' + storage.coins);
// Update button color
bg.tint = storage.coins >= item.cost ? 0x27ae60 : 0x7f8c8d;
// Success effect
LK.effects.flashScreen(0x00FF00, 300);
LK.getSound('xpCollect').play();
} else {
// Not enough coins effect
LK.effects.flashScreen(0xFF0000, 200);
}
}
function hideShop() {
shopContainer.destroy();
shopContainer = null;
menuContainer.visible = true;
gameState = 'menu';
}
function showScoreboard() {
// Hide menu
menuContainer.visible = false;
gameState = 'scoreboard';
// Create scoreboard container
scoreboardContainer = new Container();
game.addChild(scoreboardContainer);
// Create background
var scoreboardBg = LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
scoreboardBg.x = 2048 / 2;
scoreboardBg.y = 2732 / 2;
scoreboardBg.scaleX = 10;
scoreboardBg.scaleY = 10;
scoreboardBg.tint = 0x2c3e50;
scoreboardBg.alpha = 0.9;
scoreboardContainer.addChild(scoreboardBg);
// Create title
var scoreboardTitle = new Text2('SKOR TABLOSU', {
size: 120,
fill: 0xFFD700
});
scoreboardTitle.anchor.set(0.5, 0.5);
scoreboardTitle.x = 2048 / 2;
scoreboardTitle.y = 400;
scoreboardContainer.addChild(scoreboardTitle);
// Create high score display
var highScoreText = new Text2('En Yüksek Skor: ' + (storage.highScore || 0), {
size: 80,
fill: 0x00FF00
});
highScoreText.anchor.set(0.5, 0.5);
highScoreText.x = 2048 / 2;
highScoreText.y = 600;
scoreboardContainer.addChild(highScoreText);
// Create total kills display
var totalKillsText = new Text2('Toplam Öldürme: ' + (storage.totalKills || 0), {
size: 70,
fill: 0xFF6B6B
});
totalKillsText.anchor.set(0.5, 0.5);
totalKillsText.x = 2048 / 2;
totalKillsText.y = 750;
scoreboardContainer.addChild(totalKillsText);
// Create current score display
var currentScoreText = new Text2('Şu Anki Skor: ' + LK.getScore(), {
size: 70,
fill: 0xFFFFFF
});
currentScoreText.anchor.set(0.5, 0.5);
currentScoreText.x = 2048 / 2;
currentScoreText.y = 900;
scoreboardContainer.addChild(currentScoreText);
// Create statistics
var coinsEarned = storage.coins || 0;
var statsText = new Text2('Toplam Para: ' + coinsEarned, {
size: 60,
fill: 0xFFD700
});
statsText.anchor.set(0.5, 0.5);
statsText.x = 2048 / 2;
statsText.y = 1050;
scoreboardContainer.addChild(statsText);
// Create back button
var backButtonBg = LK.getAsset('levelBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
backButtonBg.x = 2048 / 2;
backButtonBg.y = 2732 - 200;
backButtonBg.scaleX = 3;
backButtonBg.scaleY = 2;
backButtonBg.tint = 0x95a5a6;
scoreboardContainer.addChild(backButtonBg);
var backButtonText = new Text2('GERİ', {
size: 60,
fill: 0xFFFFFF
});
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = 2048 / 2;
backButtonText.y = 2732 - 200;
scoreboardContainer.addChild(backButtonText);
backButtonBg.down = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3.2,
scaleY: 2.2
}, {
duration: 100,
easing: tween.easeOut
});
};
backButtonBg.up = function (x, y, obj) {
tween(backButtonBg, {
scaleX: 3,
scaleY: 2
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
hideScoreboard();
}
});
};
}
function hideScoreboard() {
scoreboardContainer.destroy();
scoreboardContainer = null;
menuContainer.visible = true;
gameState = 'menu';
}
function handleWeaponSelectPurchase(weaponType, weapon, isOwned, coinsDisplay) {
if (isOwned) {
// If already owned, just select it
currentWeapon = weaponType;
// Update weapon graphics if hero exists
if (hero) {
hero.updateWeaponGraphics();
hero.lastWeapon = currentWeapon;
}
// Success effects
LK.effects.flashScreen(0x00FF00, 300);
LK.getSound('xpCollect').play();
hideWeaponSelect();
} else {
// Purchase the weapon (all weapons are now free)
if (!storage.ownedWeapons) {
storage.ownedWeapons = ['pistol'];
}
// Add to owned weapons
storage.ownedWeapons.push(weaponType);
// Set as current weapon
currentWeapon = weaponType;
// Update weapon graphics if hero exists
if (hero) {
hero.updateWeaponGraphics();
hero.lastWeapon = currentWeapon;
}
// Update coins display
updateCoinsDisplay();
// Success effects
LK.effects.flashScreen(0x00FF00, 500);
LK.getSound('xpCollect').play();
// Refresh the weapon select menu to show updated status
hideWeaponSelect();
LK.setTimeout(function () {
showWeaponSelect();
}, 100);
}
}
Ucan bomba. In-Game asset. 2d. High contrast. No shadows
Qanli iskelet. In-Game asset. 2d. High contrast. No shadows
Beyaz zirhli sovalye. In-Game asset. 2d. High contrast. No shadows
Iskeleten Elektirik buycusu. In-Game asset. 2d. High contrast. No shadows
Wiking boss. In-Game asset. 2d. High contrast. No shadows
Mavi tatli kucuk slime gozleri parildasin. In-Game asset. 2d. High contrast. No shadows
Yesil top halinde tukuruk. In-Game asset. 2d. High contrast. No shadows
Simsek topu. In-Game asset. 2d. High contrast. No shadows
Sandik. In-Game asset. 2d. High contrast. No shadows
Ağac. In-Game asset. 2d. High contrast. No shadows
Cim yesilik. In-Game asset. 2d. High contrast. No shadows
Kamp atesi. In-Game asset. 2d. High contrast. No shadows
Madeni para. In-Game asset. 2d. High contrast. No shadows
Bullet. In-Game asset. 2d. High contrast. No shadows
Lasergun. In-Game asset. 2d. High contrast. No shadows