User prompt
make powerup level display appear in top most layer
User prompt
remove everything except background from transition fade ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
remove all powerups from transmission fade ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make sniper bullet pulse from 25% to 100% size ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make powerup level display not fade with transition ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make sniper fire only at targets farther away than the initial maximum firing range for normal bullets and target the farthest enemy from player that is on screen
User prompt
make sniper fire rate increase by 10% per level of sniper powerup, make sniper fire rate not affected by fire rate upgrade level
User prompt
why is sniper powerup is disappearing when other drops are picked up
User prompt
make sniper bullet 50% smaller and spin 25% faster and speed 10 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
increase sniper bullet speed to 5, make sniper bullet spin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
reduce sniper bullet speed to 10%
User prompt
reduce sniper bullet speed by 50%
User prompt
create sniperPowerUp that fires a bullet 10x stronger, 10x faster flight speed, 2x farther fire range, 1 bullet per 5 seconds, fires independent of all other weapons ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make leaderboard scroll faster by 50% and always display, make leaderboard not fade out with transition ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
increase leaderboard size and make it start over when it finishes
User prompt
remove player invulnerability from the game
User prompt
hi score should be perpetual and show the highest score that player ever got independent of the leaderboards ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
create HiScore display matching the size of the Score display, put it below the score display
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 20;
self.damage = 10;
self.vx = 0;
self.vy = 0;
self.piercing = false;
self.maxDistance = 800;
self.travelDistance = 0;
self.update = function () {
var moveX = self.vx;
var moveY = self.vy;
var distance = Math.sqrt(moveX * moveX + moveY * moveY);
self.travelDistance += distance;
self.x += moveX;
self.y += moveY;
};
return self;
});
var HealthPack = Container.expand(function () {
var self = Container.call(this);
var healthGraphics = self.attachAsset('healthPack', {
anchorX: 0.5,
anchorY: 0.5
});
healthGraphics.tint = 0xFF0000; // Bright red color
// Add + text on the healthpack with white color
var plusText = new Text2('+', {
size: 48,
fill: 0xFFFFFF,
font: "'Arial Black'"
});
plusText.anchor.set(0.5, 0.5);
self.addChild(plusText);
self.healAmount = 25; // Will be calculated as percentage in Player.heal method
self.spawnTime = LK.ticks;
self.update = function () {
// Pulsing effect using tween every 1 second
if (LK.ticks % 60 === 0) {
// Pulse every 1 second from 100% down to 70%
tween(self, {
scaleX: 0.7,
scaleY: 0.7
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
// Keep + text readable during scaling
plusText.scaleX = 1 / Math.max(self.scaleX, 0.7);
plusText.scaleY = 1 / Math.max(self.scaleY, 0.7);
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.fireRate = 10;
self.fireCooldown = 0;
self.moveSpeed = 5;
self.takeDamage = function (amount) {
self.health -= amount;
LK.getSound('playerHurt').play();
LK.effects.flashObject(self, 0xFF0000, 500);
if (self.health <= 0) {
self.health = 0;
return true;
}
return false;
};
self.heal = function (amount) {
// Calculate 25% of max health for health pack healing
var healAmount = Math.floor(self.maxHealth * 0.25);
self.health = Math.min(self.health + healAmount, self.maxHealth);
};
self.update = function () {
if (self.fireCooldown > 0) self.fireCooldown--;
// Ensure player is always fully visible
self.alpha = 1;
};
return self;
});
var PowerShotPowerUp = Container.expand(function () {
var self = Container.call(this);
var powerUpGraphics = self.attachAsset('powerShot', {
anchorX: 0.5,
anchorY: 0.5
});
// Add P text on the powerup with white color
var pText = new Text2('P', {
size: 48,
fill: 0xFFFFFF,
font: "'Arial Black'"
});
pText.anchor.set(0.5, 0.5);
self.addChild(pText);
self.ammoAmount = 10;
self.spawnTime = LK.ticks;
self.update = function () {
// Large pulsing effect using tween every 1 second
if (LK.ticks % 60 === 0) {
// Pulse every 1 second from 100% down to 10%
tween(self, {
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
// Keep P text readable during scaling
pText.scaleX = 1 / Math.max(self.scaleX, 0.1);
pText.scaleY = 1 / Math.max(self.scaleY, 0.1);
};
return self;
});
var SniperBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('sniperBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 10; // Set sniper bullet speed to 10
self.damage = 100; // 10x damage (10 * 10)
self.vx = 0;
self.vy = 0;
self.piercing = true; // Sniper bullets pierce through zombies
self.maxDistance = 1600; // 2x range (800 * 2)
self.travelDistance = 0;
self.update = function () {
var moveX = self.vx;
var moveY = self.vy;
var distance = Math.sqrt(moveX * moveX + moveY * moveY);
self.travelDistance += distance;
self.x += moveX;
self.y += moveY;
// Add spinning animation - 25% faster
self.rotation += 0.375;
// Add pulsing animation from 25% to 100% size
var pulseScale = 0.25 + 0.75 * (Math.sin(LK.ticks * 0.2) * 0.5 + 0.5);
self.scaleX = pulseScale;
self.scaleY = pulseScale;
};
return self;
});
var SniperPowerUp = Container.expand(function () {
var self = Container.call(this);
var powerUpGraphics = self.attachAsset('powerShot', {
anchorX: 0.5,
anchorY: 0.5
});
powerUpGraphics.tint = 0xFF0000; // Red tint for sniper
// Add S text on the powerup with white color
var sText = new Text2('S', {
size: 48,
fill: 0xFFFFFF,
font: "'Arial Black'"
});
sText.anchor.set(0.5, 0.5);
self.addChild(sText);
self.spawnTime = LK.ticks;
self.update = function () {
// Large pulsing effect using tween every 1 second
if (LK.ticks % 60 === 0) {
// Pulse every 1 second from 100% down to 10%
tween(self, {
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
// Keep S text readable during scaling
sText.scaleX = 1 / Math.max(self.scaleX, 0.1);
sText.scaleY = 1 / Math.max(self.scaleY, 0.1);
};
return self;
});
var TripleShot = Container.expand(function () {
var self = Container.call(this);
// Create center bullet
var centerBullet = self.attachAsset('tripleShotBullet', {
anchorX: 0.5,
anchorY: 0.5
});
centerBullet.x = 0;
centerBullet.y = 0;
centerBullet.rotation = 0;
// Create left bullet tilted 10 degrees away
var leftBullet = self.attachAsset('tripleShotBullet', {
anchorX: 0.5,
anchorY: 0.5
});
leftBullet.x = -20;
leftBullet.y = -8;
leftBullet.rotation = -10 * Math.PI / 180; // -10 degrees in radians
// Create right bullet tilted 10 degrees away
var rightBullet = self.attachAsset('tripleShotBullet', {
anchorX: 0.5,
anchorY: 0.5
});
rightBullet.x = 20;
rightBullet.y = -8;
rightBullet.rotation = 10 * Math.PI / 180; // 10 degrees in radians
self.spawnTime = LK.ticks;
self.update = function () {
// Spinning animation like Zoin - rotate the entire powerup
self.rotation += 0.075;
// Add pulsing effect with tween every 2 seconds
if (LK.ticks % 120 === 0) {
// Pulse every 2 seconds
tween(self, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
// Add floating effect
self.y += Math.sin(LK.ticks * 0.1) * 0.3;
};
return self;
});
var Zoin = Container.expand(function () {
var self = Container.call(this);
var zoinGraphics = self.attachAsset('zoin', {
anchorX: 0.5,
anchorY: 0.5
});
// Add Z text on the coin with neon green color
var zText = new Text2('Z', {
size: 24,
fill: 0x00ff00,
font: "'Arial Black'"
});
zText.anchor.set(0.5, 0.5);
self.addChild(zText);
self.spawnTime = LK.ticks;
self.spinPhase = 0; // Track spinning phase for coin flip effect
self.update = function () {
// Spinning animation - slower spin to show coin flip effect at 50% speed
self.spinPhase += 0.075;
// Scale X based on sine wave to create flipping effect
var scaleX = Math.abs(Math.sin(self.spinPhase));
self.scaleX = scaleX;
zText.scaleX = 1 / Math.max(scaleX, 0.1); // Keep Z readable and prevent division by 0
// Z is always visible on both sides - no longer hide it
zText.visible = true;
// Pulsing effect using tween
if (LK.ticks % 120 === 0) {
// Pulse every 2 seconds
tween(self, {
scaleY: 1.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
// Add floating effect
self.y += Math.sin(LK.ticks * 0.1) * 0.3;
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
// Randomly choose zombie visual from available assets
var zombieAssets = ['zombie', 'zombie2', 'zombie3', 'zombie4'];
var randomAsset = zombieAssets[Math.floor(Math.random() * zombieAssets.length)];
var zombieGraphics = self.attachAsset(randomAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.baseSpeed = 1.5;
var variation = self.baseSpeed * 0.25; // 25% variation
self.speed = Math.round(self.baseSpeed + (Math.random() - 0.5) * 2 * variation); // Random between baseSpeed ± 25%
self.health = 20;
self.maxHealth = 20; // Store base health for wave scaling
self.damage = 1;
self.baseDamage = 1; // Store base damage for wave scaling
self.attackCooldown = 0;
self.isDead = false;
self.takeDamage = function (amount) {
if (self.isDead) return false;
self.health -= amount;
if (self.health <= 0) {
self.isDead = true;
LK.getSound('zombieHit').play();
return true;
}
zombieGraphics.tint = 0xFF0000;
tween(zombieGraphics, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
zombieGraphics.tint = 0xFFFFFF;
}
});
return false;
};
self.update = function () {
if (self.isDead) return;
if (self.attackCooldown > 0) self.attackCooldown--;
};
return self;
});
var ZombieHulk = Zombie.expand(function () {
var self = Zombie.call(this);
// Enhanced stats: 100% more life, 25% more speed, 50% larger
self.baseSpeed = 1.5 * 1.25; // 25% more speed
var variation = self.baseSpeed * 0.25; // 25% variation
self.speed = Math.round(self.baseSpeed + (Math.random() - 0.5) * 2 * variation); // Random between baseSpeed ± 25%
self.health = 40; // 100% more life (20 * 2)
self.maxHealth = 40; // Store base health for wave scaling
// Make 50% larger by scaling the existing zombie graphics after brief delay to ensure visual is ready
LK.setTimeout(function () {
tween(self, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut
});
}, 50);
// 50% chance to have a random powerup in inventory
self.hasPowerup = Math.random() < 0.5;
if (self.hasPowerup) {
var powerUpTypes = ['powerShotPowerUp', 'healthPack', 'tripleShot'];
self.powerupType = powerUpTypes[Math.floor(Math.random() * powerUpTypes.length)];
}
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
var player;
var zombies = [];
var bullets = [];
var pickups = [];
var wave = 1;
var zombiesPerWave = 5;
var zombiesSpawned = 0;
var zombiesKilled = 0;
var spawnTimer = 0;
var scoreMultiplier = 1;
var killStreak = 0;
var powerAmmo = 0;
var lastPowerUpSpawn = 0;
var killsSinceLastPowerUp = 0;
var powerUpList = ['powerShotPowerUp', 'healthPack', 'tripleShot', 'sniperPowerUp'];
var lastPowerUpType = null;
var usedPowerUps = []; // Track which powerups have been used in current cycle
var availablePowerUps = powerUpList.slice(); // Copy of powerups available for selection
var hasTripleShot = false;
var tripleShotLevel = 0;
var maxTripleShotLevel = 5;
var hasSniperPowerUp = false;
var sniperLevel = 0;
var sniperCooldown = 0;
var sniperFireRate = 300; // 5 seconds at 60fps
var noZombiesTimer = 0;
var noZombiesThreshold = 600; // 10 seconds at 60fps
// Load persistent data from storage (removed reset statements)
// Load persistent data from storage without clearing
var zoins = storage.zoins || 0;
// Upgrade costs and levels - loaded from storage
var maxHealthUpgrades = storage.maxHealthUpgrades || 0;
var fireRateUpgrades = storage.fireRateUpgrades || 0;
var damageUpgrades = storage.damageUpgrades || 0;
// Save damage upgrades to storage whenever modified
storage.damageUpgrades = damageUpgrades;
// Load leaderboard data from storage
// Leaderboard data - reconstruct from flattened storage
var storedLeaderboard = storage.leaderboard || {};
var leaderboard = [];
if (storedLeaderboard.length) {
for (var i = 0; i < storedLeaderboard.length; i++) {
if (storedLeaderboard['name_' + i] !== undefined) {
leaderboard.push({
name: String(storedLeaderboard['name_' + i]),
score: Number(storedLeaderboard['score_' + i]),
wave: Number(storedLeaderboard['wave_' + i])
});
}
}
} else {
leaderboard = [];
}
// Arrays for generating player names
var adjectives = ['Brave', 'Swift', 'Mighty', 'Fierce', 'Bold', 'Quick', 'Strong', 'Wild', 'Sharp', 'Dark', 'Bright', 'Cold', 'Hot', 'Fast', 'Slow', 'Big', 'Small', 'Red', 'Blue', 'Green'];
var commonNames = ['Alex', 'Sam', 'Max', 'Jake', 'Mike', 'Tom', 'Ben', 'Dan', 'Joe', 'Ryan', 'Kyle', 'Luke', 'Dave', 'Jack', 'Matt', 'Nick', 'Chris', 'Josh', 'Mark', 'Paul', 'Emma', 'Sarah', 'Lisa', 'Amy', 'Kate', 'Anna', 'Maya', 'Zoe', 'Mia', 'Ella', 'Eva', 'Ava', 'Lily', 'Ruby', 'Nina', 'Rose', 'Lucy', 'Jade', 'Hope', 'Iris'];
// Load or generate player name
var currentPlayerName = storage.playerName || '';
if (!currentPlayerName) {
var randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
var randomName = commonNames[Math.floor(Math.random() * commonNames.length)];
currentPlayerName = randomAdjective + '-' + randomName;
storage.playerName = currentPlayerName;
}
var moveControl;
var moveStick;
var isDraggingMove = false;
// UI Elements
var waveText = new Text2('Wave: 1', {
size: 80,
fill: 0xFFFFFF
});
waveText.anchor.set(0, 0);
waveText.x = 150;
LK.gui.topLeft.addChild(waveText);
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
scoreText.x = -150;
LK.gui.topRight.addChild(scoreText);
// Load hi-score from storage or default to 0
var hiScore = storage.hiScore || 0;
var hiScoreText = new Text2('Hi-Score: ' + hiScore, {
size: 60,
fill: 0xFFD700
});
hiScoreText.anchor.set(1, 0);
hiScoreText.x = -150;
hiScoreText.y = 70; // Position below score display
LK.gui.topRight.addChild(hiScoreText);
// Health bar black outline
var healthBarOutline = LK.getAsset('ammoBox', {
width: 1849,
height: 81,
anchorX: 0.5,
anchorY: 0
});
healthBarOutline.tint = 0x000000;
healthBarOutline.x = 1024;
healthBarOutline.y = 97;
game.addChildAt(healthBarOutline, 0);
// Health bar background using lifebar asset
var healthBarBg = LK.getAsset('lifebar', {
width: 2048,
height: 225,
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.x = 1024;
healthBarBg.y = 166.25;
game.addChildAt(healthBarBg, 1);
// Health bar fill
var healthBarFill = LK.getAsset('ammoBox', {
width: 1382.25,
// Reduced by 25% (1843 * 0.75)
height: 58.59375,
// 25% taller (46.875 * 1.25)
anchorX: 0.5,
anchorY: 0
});
healthBarFill.tint = 0x000000;
healthBarFill.x = 1024;
healthBarFill.y = 113.75; // Moved up 5px (118.75 - 5)
game.addChildAt(healthBarFill, 2);
// Health text display
var healthText = new Text2('100/100', {
size: 56,
fill: 0xFFFFFF
});
healthText.anchor.set(0.5, 0.5);
healthText.x = 1024;
healthText.y = 137.5; // Center of health bar (100 + 75/2)
game.addChildAt(healthText, 3);
var powerAmmoText = new Text2('Power Ammo: 0', {
size: 45,
fill: 0xFFD700
});
powerAmmoText.anchor.set(1, 1);
powerAmmoText.x = -20;
powerAmmoText.y = -80;
LK.gui.bottomRight.addChild(powerAmmoText);
var zoinText = new Text2('Zoins: ' + zoins, {
size: 45,
fill: 0xFFD700
});
zoinText.anchor.set(1, 1);
zoinText.x = -20;
zoinText.y = -140;
LK.gui.bottomRight.addChild(zoinText);
// Leaderboard display as scrolling banner
var leaderboardText = new Text2('LEADERBOARD\n', {
size: 40,
fill: 0xFFFFFF
});
leaderboardText.anchor.set(0, 0);
leaderboardText.x = 2048; // Start off-screen to the right
leaderboardText.y = 200;
game.addChild(leaderboardText);
// Leaderboard scrolling variables
var leaderboardScrolling = false;
// Update leaderboard display
function updateLeaderboardDisplay() {
var displayText = 'LEADERBOARD ';
var sortedLeaderboard = leaderboard.slice().sort(function (a, b) {
return b.score - a.score;
});
// Show top 20 entries
for (var i = 0; i < Math.min(20, sortedLeaderboard.length); i++) {
var entry = sortedLeaderboard[i];
displayText += i + 1 + '. ' + entry.name + ' - ' + entry.score + ' (Wave ' + entry.wave + ') • ';
}
// Find current player's best entry and position
var playerBestEntry = null;
var playerPosition = -1;
for (var i = 0; i < sortedLeaderboard.length; i++) {
var entry = sortedLeaderboard[i];
if (entry.name === currentPlayerName && (!playerBestEntry || entry.score > playerBestEntry.score)) {
playerBestEntry = entry;
playerPosition = i + 1;
}
}
// If player has an entry and it's not in top 20, show their position
if (playerBestEntry && playerPosition > 20) {
displayText += '... ' + playerPosition + '. ' + playerBestEntry.name + ' - ' + playerBestEntry.score + ' (Wave ' + playerBestEntry.wave + ') • ';
}
leaderboardText.setText(displayText);
startLeaderboardScroll();
}
// Start leaderboard scrolling animation
function startLeaderboardScroll() {
if (leaderboardScrolling) return;
leaderboardScrolling = true;
// Stop any existing tween
tween.stop(leaderboardText, {
x: true
});
// Reset position to start from right
leaderboardText.x = 2048;
// Calculate scroll distance based on text width
var scrollDistance = leaderboardText.width + 2048;
// Start scrolling animation
tween(leaderboardText, {
x: -scrollDistance
}, {
duration: scrollDistance * 10,
// 50% faster scrolling (15 -> 10)
easing: tween.linear,
onFinish: function onFinish() {
leaderboardScrolling = false;
// Restart the scroll immediately for continuous display
startLeaderboardScroll();
}
});
}
updateLeaderboardDisplay();
// Function to submit score to leaderboard
function submitScoreToLeaderboard() {
// Player name is now persistently generated at game start
// Create simple literal object for leaderboard entry
var newEntry = {
name: currentPlayerName,
score: LK.getScore(),
wave: wave
};
// Create cleaned leaderboard first
var cleanLeaderboard = [];
for (var i = 0; i < leaderboard.length; i++) {
var entry = leaderboard[i];
cleanLeaderboard.push({
name: String(entry.name),
score: Number(entry.score),
wave: Number(entry.wave)
});
}
// Add new entry as literal object
cleanLeaderboard.push({
name: String(newEntry.name),
score: Number(newEntry.score),
wave: Number(newEntry.wave)
});
// Sort and limit to top 50
cleanLeaderboard.sort(function (a, b) {
return b.score - a.score;
});
if (cleanLeaderboard.length > 50) {
cleanLeaderboard = cleanLeaderboard.slice(0, 50);
}
// Update local leaderboard and save to storage
leaderboard = cleanLeaderboard;
// Flatten leaderboard for storage (storage only accepts literals and 1-level objects)
var flatLeaderboard = {};
for (var i = 0; i < cleanLeaderboard.length; i++) {
var entry = cleanLeaderboard[i];
flatLeaderboard['name_' + i] = String(entry.name);
flatLeaderboard['score_' + i] = Number(entry.score);
flatLeaderboard['wave_' + i] = Number(entry.wave);
}
flatLeaderboard.length = cleanLeaderboard.length;
storage.leaderboard = flatLeaderboard;
// Update display
updateLeaderboardDisplay();
}
// Set random background on game start
var backgroundAssets = ['background1', 'background2', 'background3', 'background4', 'background5', 'background6'];
var randomBackgroundId = backgroundAssets[Math.floor(Math.random() * backgroundAssets.length)];
var gameBackground = LK.getAsset(randomBackgroundId, {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
gameBackground.x = 0;
gameBackground.y = 0;
game.addChildAt(gameBackground, 0); // Add at bottom layer
// Ensure all health bar elements are above background
game.removeChild(healthBarOutline);
game.addChild(healthBarOutline);
game.removeChild(healthBarBg);
game.addChild(healthBarBg);
game.removeChild(healthBarFill);
game.addChild(healthBarFill);
game.removeChild(healthText);
game.addChild(healthText);
// Initialize player
player = game.addChild(new Player());
player.maxHealth = Math.round((maxHealthUpgrades + 1) * 2);
player.health = Math.round(player.maxHealth);
player.fireRate = Math.round(3 * Math.pow(1.1, fireRateUpgrades));
player.x = 1024;
player.y = 1366;
// Initialize controls
moveControl = LK.getAsset('moveControl', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
moveControl.x = 300;
moveControl.y = 2432;
game.addChild(moveControl);
moveStick = LK.getAsset('moveControl', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0
});
moveStick.x = moveControl.x;
moveStick.y = moveControl.y;
game.addChild(moveStick);
// Aim control removed - using automatic targeting
// Helper functions
function countHealthPacksOnScreen() {
var healthPackCount = 0;
for (var i = 0; i < pickups.length; i++) {
if (pickups[i] instanceof HealthPack) {
healthPackCount++;
}
}
return healthPackCount;
}
function findNonOverlappingPosition(startX, startY) {
var attempts = 0;
var maxAttempts = 10;
var minDistance = 80; // Minimum distance between pickups
var finalX = startX;
var finalY = startY;
while (attempts < maxAttempts) {
var overlapping = false;
// Check distance from all existing pickups
for (var i = 0; i < pickups.length; i++) {
var existingPickup = pickups[i];
var dx = finalX - existingPickup.x;
var dy = finalY - existingPickup.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
overlapping = true;
break;
}
}
if (!overlapping) {
break; // Found a good position
}
// Try a new position in a circle around the original point
var angle = Math.random() * Math.PI * 2;
var radius = 40 + attempts * 20; // Increase radius with each attempt
finalX = startX + Math.cos(angle) * radius;
finalY = startY + Math.sin(angle) * radius;
// Keep within screen bounds
finalX = Math.max(60, Math.min(1988, finalX));
finalY = Math.max(60, Math.min(2672, finalY));
attempts++;
}
return {
x: finalX,
y: finalY
};
}
function calculateCumulativeCost(upgradeLevel) {
var totalCost = 0;
for (var i = 0; i <= upgradeLevel; i++) {
totalCost += Math.round((10 + i * 2) / 2);
}
return totalCost;
}
function resetGame() {
// Set new random background on game reset
var backgroundAssets = ['background1', 'background2', 'background3', 'background4', 'background5', 'background6'];
var randomBackgroundId = backgroundAssets[Math.floor(Math.random() * backgroundAssets.length)];
var newGameBackground = LK.getAsset(randomBackgroundId, {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
newGameBackground.x = 0;
newGameBackground.y = 0;
// Remove old background if it exists
if (game.children[0] && game.children[0].width === 2048 && game.children[0].height === 2732) {
game.children[0].destroy();
game.removeChildAt(0);
}
game.addChildAt(newGameBackground, 0); // Add at bottom layer
// Ensure all health bar elements are above background
game.removeChild(healthBarOutline);
game.addChild(healthBarOutline);
game.removeChild(healthBarBg);
game.addChild(healthBarBg);
game.removeChild(healthBarFill);
game.addChild(healthBarFill);
game.removeChild(healthText);
game.addChild(healthText);
// Reset wave and zombies
wave = 1;
zombiesPerWave = 5;
zombiesSpawned = 0;
zombiesKilled = 0;
spawnTimer = 0;
hulksSpawned = 0; // Reset hulk spawn counter
// Reset score and multipliers
LK.setScore(0);
scoreMultiplier = 1;
killStreak = 0;
killsSinceLastPowerUp = 0;
lastPowerUpSpawn = 0;
lastPowerUpType = null;
// Reset powerup cycle tracking
usedPowerUps = [];
availablePowerUps = powerUpList.slice();
// Reset powerups
powerAmmo = 0;
hasTripleShot = false;
tripleShotLevel = 0;
hasSniperPowerUp = false;
sniperLevel = 0;
sniperCooldown = 0;
// Reset player state with upgrades
player.maxHealth = Math.round((maxHealthUpgrades + 1) * 2);
player.health = Math.round(player.maxHealth);
player.fireRate = Math.round(3 * Math.pow(1.1, fireRateUpgrades));
player.fireCooldown = 0;
player.alpha = 1;
// Clear all game objects
for (var i = zombies.length - 1; i >= 0; i--) {
zombies[i].destroy();
}
zombies = [];
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
bullets = [];
for (var i = pickups.length - 1; i >= 0; i--) {
pickups[i].destroy();
}
pickups = [];
// Update UI
waveText.setText('Wave: ' + wave);
scoreText.setText('Score: ' + LK.getScore());
hiScoreText.setText('Hi-Score: ' + hiScore);
// Update health bar
var healthPercent = player.health / player.maxHealth;
var depletedPercent = 1 - healthPercent; // Invert to show depleted health
healthBarFill.width = 1382.25 * depletedPercent;
healthBarFill.height = 58.59375;
healthText.setText(player.health + '/' + player.maxHealth);
powerAmmoText.setText('Power Ammo: ' + powerAmmo);
powerAmmoText.visible = false;
// Reset player position
player.x = 1024;
player.y = 1366;
// Reset no zombies timer
noZombiesTimer = 0;
// Update powerup display
updatePowerupDisplay();
}
// Track hulks spawned for current wave
var hulksSpawned = 0;
function spawnZombie() {
var zombie;
// Spawn regular zombies first, then hulks at the end
var regularZombiesCount = zombiesPerWave - wave; // Total zombies minus hulks
if (zombiesSpawned < regularZombiesCount) {
zombie = new Zombie();
} else {
zombie = new ZombieHulk();
hulksSpawned++;
}
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
zombie.x = Math.random() * 2048;
zombie.y = -50;
break;
case 1:
// Right
zombie.x = 2098;
zombie.y = Math.random() * 2732;
break;
case 2:
// Bottom
zombie.x = Math.random() * 2048;
zombie.y = 2782;
break;
case 3:
// Left
zombie.x = -50;
zombie.y = Math.random() * 2732;
break;
}
zombie.speed = Math.round(zombie.baseSpeed * (1 + wave * 0.1) + (Math.random() - 0.5) * 2 * (zombie.baseSpeed * 0.25));
zombie.health = Math.round(zombie.health + wave * 5);
zombie.damage = Math.round(zombie.damage + Math.floor(wave * 2)); // Increase damage by 2 per wave
zombie.scaleX = 0;
zombie.scaleY = 0;
zombie.alpha = 0;
zombies.push(zombie);
game.addChild(zombie);
zombiesSpawned++;
// Animate zombie spawn
tween(zombie, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
function spawnPickup(x, y, zombie) {
var shouldSpawn = false;
var pickup;
var powerUpType;
// Helper function to find a non-overlapping position
function findNonOverlappingPosition(startX, startY) {
var attempts = 0;
var maxAttempts = 10;
var minDistance = 80; // Minimum distance between pickups
var finalX = startX;
var finalY = startY;
while (attempts < maxAttempts) {
var overlapping = false;
// Check distance from all existing pickups
for (var i = 0; i < pickups.length; i++) {
var existingPickup = pickups[i];
var dx = finalX - existingPickup.x;
var dy = finalY - existingPickup.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
overlapping = true;
break;
}
}
if (!overlapping) {
break; // Found a good position
}
// Try a new position in a circle around the original point
var angle = Math.random() * Math.PI * 2;
var radius = 40 + attempts * 20; // Increase radius with each attempt
finalX = startX + Math.cos(angle) * radius;
finalY = startY + Math.sin(angle) * radius;
// Keep within screen bounds
finalX = Math.max(60, Math.min(1988, finalX));
finalY = Math.max(60, Math.min(2672, finalY));
attempts++;
}
return {
x: finalX,
y: finalY
};
}
// Check if zombie hulk has powerup in inventory
if (zombie instanceof ZombieHulk && zombie.hasPowerup) {
shouldSpawn = true;
// If all powerups have been used, reset the cycle
if (availablePowerUps.length === 0) {
availablePowerUps = powerUpList.slice(); // Reset available powerups
usedPowerUps = []; // Clear used powerups
}
// Select random powerup from available list for hulk
var randomIndex = Math.floor(Math.random() * availablePowerUps.length);
powerUpType = availablePowerUps[randomIndex];
// Move selected powerup from available to used
usedPowerUps.push(powerUpType);
availablePowerUps.splice(randomIndex, 1);
if (powerUpType === 'powerShotPowerUp') {
pickup = new PowerShotPowerUp();
} else if (powerUpType === 'tripleShot') {
pickup = new TripleShot();
} else if (powerUpType === 'sniperPowerUp') {
pickup = new SniperPowerUp();
} else {
// Check if healthpack would be spawned and limit is reached
if (countHealthPacksOnScreen() >= 2) {
// Replace with 2 zoin drops with non-overlapping positions
var pos1 = findNonOverlappingPosition(x - 30, y);
var pos2 = findNonOverlappingPosition(x + 30, y);
var zoin1 = new Zoin();
zoin1.x = pos1.x;
zoin1.y = pos1.y;
zoin1.scaleX = 0;
zoin1.scaleY = 0;
pickups.push(zoin1);
game.addChild(zoin1);
var zoin2 = new Zoin();
zoin2.x = pos2.x;
zoin2.y = pos2.y;
zoin2.scaleX = 0;
zoin2.scaleY = 0;
pickups.push(zoin2);
game.addChild(zoin2);
// Animate both zoin spawns
tween(zoin1, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
tween(zoin2, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
// Don't create healthpack
pickup = null;
} else {
pickup = new HealthPack();
}
}
killsSinceLastPowerUp = 0;
if (pickup instanceof PowerShotPowerUp) {
lastPowerUpSpawn = LK.ticks;
}
}
// Force powerup spawn if 50+ kills without powerup (but not for normal zombies)
else if (killsSinceLastPowerUp >= 50) {
shouldSpawn = true;
// If all powerups have been used, reset the cycle
if (availablePowerUps.length === 0) {
availablePowerUps = powerUpList.slice(); // Reset available powerups
usedPowerUps = []; // Clear used powerups
}
// Select random powerup from available list
var randomIndex = Math.floor(Math.random() * availablePowerUps.length);
powerUpType = availablePowerUps[randomIndex];
// Move selected powerup from available to used
usedPowerUps.push(powerUpType);
availablePowerUps.splice(randomIndex, 1);
if (powerUpType === 'powerShotPowerUp') {
pickup = new PowerShotPowerUp();
} else if (powerUpType === 'tripleShot') {
pickup = new TripleShot();
} else if (powerUpType === 'sniperPowerUp') {
pickup = new SniperPowerUp();
} else {
// Check if healthpack would be spawned and limit is reached
if (countHealthPacksOnScreen() >= 2) {
// Replace with 2 zoin drops with non-overlapping positions
var pos1 = findNonOverlappingPosition(x - 30, y);
var pos2 = findNonOverlappingPosition(x + 30, y);
var zoin1 = new Zoin();
zoin1.x = pos1.x;
zoin1.y = pos1.y;
zoin1.scaleX = 0;
zoin1.scaleY = 0;
pickups.push(zoin1);
game.addChild(zoin1);
var zoin2 = new Zoin();
zoin2.x = pos2.x;
zoin2.y = pos2.y;
zoin2.scaleX = 0;
zoin2.scaleY = 0;
pickups.push(zoin2);
game.addChild(zoin2);
// Animate both zoin spawns
tween(zoin1, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
tween(zoin2, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
// Don't create healthpack
pickup = null;
} else {
pickup = new HealthPack();
}
}
killsSinceLastPowerUp = 0;
if (pickup instanceof PowerShotPowerUp) {
lastPowerUpSpawn = LK.ticks;
}
}
// No powerup spawn for normal zombies
if (shouldSpawn && pickup) {
// Find non-overlapping position for the pickup
var position = findNonOverlappingPosition(x, y);
pickup.x = position.x;
pickup.y = position.y;
pickup.scaleX = 0;
pickup.scaleY = 0;
pickups.push(pickup);
game.addChild(pickup);
// Animate pickup spawn with bounce effect
tween(pickup, {
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.elasticOut
});
}
}
function fireBullet() {
// Handle sniper firing independently
if (hasSniperPowerUp && sniperCooldown <= 0) {
// Find farthest zombie that is beyond normal bullet range (500px) and on screen
var sniperTarget = null;
var sniperDistance = 0; // Track farthest distance instead of closest
var normalBulletRange = 500; // Normal bullet maximum range
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (!zombie || zombie.isDead) continue;
var dx = zombie.x - player.x;
var dy = zombie.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Only target zombies beyond normal bullet range and on screen
if (distance > normalBulletRange && distance > sniperDistance && zombie.x >= -50 && zombie.x <= 2098 && zombie.y >= -50 && zombie.y <= 2782) {
sniperDistance = distance;
sniperTarget = zombie;
}
}
if (sniperTarget) {
var dx = sniperTarget.x - player.x;
var dy = sniperTarget.y - player.y;
var angle = Math.atan2(dy, dx);
var sniperBullet = new SniperBullet();
sniperBullet.x = player.x;
sniperBullet.y = player.y;
sniperBullet.damage = Math.round(100 + damageUpgrades * 50); // 10x base damage + upgrades
sniperBullet.vx = Math.cos(angle) * sniperBullet.speed;
sniperBullet.vy = Math.sin(angle) * sniperBullet.speed;
sniperBullet.rotation = angle;
// Visual effect for sniper bullet
tween(sniperBullet, {
scaleX: 2,
scaleY: 2
}, {
duration: 100
});
bullets.push(sniperBullet);
game.addChild(sniperBullet);
// Calculate sniper fire rate: base rate decreases by 10% per sniper level (faster firing)
var sniperFireRateWithUpgrade = Math.round(sniperFireRate * Math.pow(0.9, sniperLevel - 1));
sniperCooldown = sniperFireRateWithUpgrade; // Reset sniper cooldown with improved rate
LK.getSound('shoot').play();
}
}
// Convert shots per second to ticks between shots (60 ticks = 1 second)
var shotsPerSecond = powerAmmo > 0 ? player.fireRate * 0.25 : player.fireRate; // Power ammo shoots at 25% of normal rate
var ticksBetweenShots = Math.max(1, Math.floor(60 / shotsPerSecond)); // Ensure at least 1 tick between shots
if (player.fireCooldown > 0) return;
// Find closest zombie in range
var closestZombie = null;
var closestDistance = 500; // Maximum firing range
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (!zombie || zombie.isDead) continue;
var dx = zombie.x - player.x;
var dy = zombie.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestZombie = zombie;
}
}
// Only fire if there's a zombie in range
if (!closestZombie) return;
var dx = closestZombie.x - player.x;
var dy = closestZombie.y - player.y;
var angle = Math.atan2(dy, dx);
// Player visual does not rotate - only bullets aim at target
// Create bullets based on triple shot level
var bulletCount = hasTripleShot ? 1 + tripleShotLevel * 2 : 1;
var angleOffsets = [0]; // Always include center bullet
if (hasTripleShot) {
// Add bullets in pairs at increasing angles
for (var level = 1; level <= tripleShotLevel; level++) {
var angleOffset = level * 10 * Math.PI / 180;
angleOffsets.push(-angleOffset); // Left bullet
angleOffsets.push(angleOffset); // Right bullet
}
}
// Consume power ammo once per shot, not per bullet
var usingPowerAmmo = powerAmmo > 0;
if (usingPowerAmmo) {
powerAmmo--; // Consume one power ammo per shot
}
for (var b = 0; b < bulletCount; b++) {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
bullet.damage = Math.round(10 + damageUpgrades * 5);
var bulletAngle = angle + angleOffsets[b];
bullet.vx = Math.cos(bulletAngle) * bullet.speed;
bullet.vy = Math.sin(bulletAngle) * bullet.speed;
bullet.rotation = bulletAngle;
if (usingPowerAmmo) {
bullet.piercing = true;
bullet.maxDistance = 1600; // 2x distance
bullet.damage = Math.round((10 + damageUpgrades * 5) * 5); // 5x normal damage
// Visual effect for power bullet
tween(bullet, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 100
});
bullet.tint = 0xFFD700;
} else {
bullet.maxDistance = 800;
}
bullets.push(bullet);
game.addChild(bullet);
}
player.fireCooldown = ticksBetweenShots;
LK.getSound('shoot').play();
}
// Event handlers
game.down = function (x, y, obj) {
// Prevent control stick activation in bottom 1/4 of viewport (below y = 2049)
if (y > 2049) return;
isDraggingMove = true;
// Move moveControl to click position
moveControl.x = x;
moveControl.y = y;
// Move moveStick to the same position (center of control)
moveStick.x = x;
moveStick.y = y;
// Show control stick with smooth animation
tween(moveControl, {
alpha: 0.3
}, {
duration: 200,
easing: tween.easeOut
});
tween(moveStick, {
alpha: 0.6
}, {
duration: 200,
easing: tween.easeOut
});
};
game.move = function (x, y, obj) {
if (isDraggingMove) {
moveStick.x = Math.max(moveControl.x - 150, Math.min(moveControl.x + 150, x));
moveStick.y = Math.max(moveControl.y - 150, Math.min(moveControl.y + 150, y));
}
};
game.up = function (x, y, obj) {
if (isDraggingMove) {
isDraggingMove = false;
moveStick.x = moveControl.x;
moveStick.y = moveControl.y;
// Hide control stick with smooth animation
tween(moveControl, {
alpha: 0
}, {
duration: 150,
easing: tween.easeIn
});
tween(moveStick, {
alpha: 0
}, {
duration: 150,
easing: tween.easeIn
});
}
};
// Main game loop
game.update = function () {
// Player movement
if (isDraggingMove) {
var moveX = (moveStick.x - moveControl.x) / 150;
var moveY = (moveStick.y - moveControl.y) / 150;
player.x += moveX * player.moveSpeed;
player.y += moveY * player.moveSpeed;
player.x = Math.max(40, Math.min(2008, player.x));
player.y = Math.max(40, Math.min(2692, player.y));
}
// Update sniper cooldown
if (sniperCooldown > 0) sniperCooldown--;
// Auto-fire (fireBullet now handles range checking internally)
fireBullet();
// Spawn zombies
if (zombiesSpawned < zombiesPerWave) {
spawnTimer++;
if (spawnTimer > 60) {
spawnZombie();
spawnTimer = 0;
}
} else if (zombies.length === 0) {
// Next wave with transition
wave++;
zombiesPerWave = Math.min(5 + wave * 2, 30); // Cap at 30 zombies per wave to prevent performance issues
zombiesSpawned = 0;
zombiesKilled = 0;
spawnTimer = 0; // Reset spawn timer for new wave
noZombiesTimer = 0; // Reset no zombies timer
hulksSpawned = 0; // Reset hulk spawn counter for new wave
waveText.setText('Wave: ' + wave);
transitionToNextWave();
} else {
// Check if there are zombies in view area (within screen bounds plus margin)
var zombiesInView = false;
for (var z = 0; z < zombies.length; z++) {
var zombie = zombies[z];
if (!zombie || zombie.isDead) continue;
// Check if zombie is within view area (screen bounds + 200px margin)
if (zombie.x >= -200 && zombie.x <= 2248 && zombie.y >= -200 && zombie.y <= 2932) {
zombiesInView = true;
break;
}
}
// If no zombies in view, increment timer
if (!zombiesInView && zombiesSpawned >= zombiesPerWave) {
noZombiesTimer++;
// If 10 seconds have passed without zombies in view, advance to next wave
if (noZombiesTimer >= noZombiesThreshold) {
// Remove all remaining zombies
for (var i = zombies.length - 1; i >= 0; i--) {
zombies[i].destroy();
zombies.splice(i, 1);
}
// Next wave with transition
wave++;
zombiesPerWave = Math.min(5 + wave * 2, 30); // Cap at 30 zombies per wave to prevent performance issues
zombiesSpawned = 0;
zombiesKilled = 0;
spawnTimer = 0; // Reset spawn timer for new wave
noZombiesTimer = 0; // Reset no zombies timer
hulksSpawned = 0; // Reset hulk spawn counter for new wave
waveText.setText('Wave: ' + wave);
transitionToNextWave();
}
} else {
// Reset timer if zombies are in view or we haven't spawned all zombies yet
noZombiesTimer = 0;
}
}
// Update zombies
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
// Skip if zombie is undefined or dead
if (!zombie || zombie.isDead) continue;
// Move towards player
var dx = player.x - zombie.x;
var dy = player.y - zombie.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
zombie.x += dx / dist * zombie.speed;
zombie.y += dy / dist * zombie.speed;
// Removed zombie rotation to keep zombies upright
}
// Check collision with player
if (zombie.intersects(player) && zombie.attackCooldown === 0) {
var playerDied = player.takeDamage(zombie.damage);
zombie.attackCooldown = 60;
killStreak = 0;
scoreMultiplier = 1;
// Check if player died after taking damage
if (playerDied || player.health <= 0) {
// Submit score to leaderboard before game over
submitScoreToLeaderboard();
LK.showGameOver();
return; // Exit game loop immediately when player dies
}
}
}
// Check if player died during zombie processing - exit immediately
if (player.health <= 0) {
return;
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Check if bullet is off screen or traveled max distance
if (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782 || bullet.travelDistance > bullet.maxDistance) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check collision with zombies
var bulletDestroyed = false;
for (var j = zombies.length - 1; j >= 0; j--) {
var zombie = zombies[j];
if (!zombie || zombie.isDead) continue;
if (bullet.intersects(zombie)) {
if (zombie.takeDamage(bullet.damage)) {
// Zombie died - add death animation
spawnPickup(zombie.x, zombie.y, zombie);
// 10% chance to drop 1 zoin when zombie dies
if (Math.random() < 0.1) {
// Find non-overlapping position for zoin drop
var zoinPosition = findNonOverlappingPosition(zombie.x, zombie.y);
var zoin = new Zoin();
zoin.x = zoinPosition.x;
zoin.y = zoinPosition.y;
zoin.scaleX = 0;
zoin.scaleY = 0;
pickups.push(zoin);
game.addChild(zoin);
// Animate zoin spawn
tween(zoin, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
var zombieToRemove = zombie; // Capture zombie reference for closure
var zombieIndex = j; // Capture index for closure
tween(zombie, {
scaleX: 0,
scaleY: 0,
alpha: 0,
rotation: zombie.rotation + Math.PI
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
zombieToRemove.destroy();
// Remove from zombies array after animation
var currentIndex = zombies.indexOf(zombieToRemove);
if (currentIndex !== -1) {
zombies.splice(currentIndex, 1);
}
}
});
zombiesKilled++;
killStreak++;
killsSinceLastPowerUp++;
if (killStreak % 5 === 0) {
scoreMultiplier = Math.min(scoreMultiplier + 1, 5);
}
// Give 2x score for ZombieHulk, regular score for normal zombies
var scoreToAdd = zombie instanceof ZombieHulk ? 20 * scoreMultiplier : 10 * scoreMultiplier;
LK.setScore(LK.getScore() + scoreToAdd);
scoreText.setText('Score: ' + LK.getScore());
}
if (!bullet.piercing) {
bullet.destroy();
bullets.splice(i, 1);
bulletDestroyed = true;
break;
}
}
}
if (bulletDestroyed) continue;
}
// Update pickups
for (var i = pickups.length - 1; i >= 0; i--) {
var pickup = pickups[i];
// Check if pickup has expired (10 seconds) - auto pickup non-healthpack items
var age = LK.ticks - pickup.spawnTime;
if (age >= pickup.lifespan) {
// Auto pickup all items except health packs when expiring - healthpacks are perpetual
if (pickup instanceof HealthPack) {
// HealthPack no longer expires - skip expiration logic completely
continue;
} else if (pickup instanceof PowerShotPowerUp) {
powerAmmo += pickup.ammoAmount;
LK.effects.flashObject(player, 0xFFD700, 500);
} else if (pickup instanceof TripleShot) {
if (!hasTripleShot) {
hasTripleShot = true;
tripleShotLevel = 1;
} else if (tripleShotLevel < maxTripleShotLevel) {
tripleShotLevel++;
}
LK.effects.flashObject(player, 0x00FF00, 500);
} else if (pickup instanceof SniperPowerUp) {
hasSniperPowerUp = true;
sniperLevel++; // Increase sniper level each time powerup is collected
sniperCooldown = 0; // Ready to fire immediately
LK.effects.flashObject(player, 0xFF0000, 500);
} else if (pickup instanceof Zoin) {
zoins++;
storage.zoins = zoins; // Persist zoin count
zoinText.setText('Zoins: ' + zoins);
LK.effects.flashObject(player, 0xFFD700, 300);
}
LK.getSound('pickup').play();
// Animate pickup collection
tween(pickup, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
pickup.destroy();
}
});
pickups.splice(i, 1);
continue;
}
// Manual pickup when touching
if (pickup.intersects(player)) {
if (pickup instanceof HealthPack) {
player.heal(pickup.healAmount);
} else if (pickup instanceof PowerShotPowerUp) {
powerAmmo += pickup.ammoAmount;
LK.effects.flashObject(player, 0xFFD700, 500);
} else if (pickup instanceof TripleShot) {
if (!hasTripleShot) {
hasTripleShot = true;
tripleShotLevel = 1;
} else if (tripleShotLevel < maxTripleShotLevel) {
tripleShotLevel++;
}
LK.effects.flashObject(player, 0x00FF00, 500);
} else if (pickup instanceof SniperPowerUp) {
hasSniperPowerUp = true;
sniperLevel++; // Increase sniper level each time powerup is collected
sniperCooldown = 0; // Ready to fire immediately
LK.effects.flashObject(player, 0xFF0000, 500);
} else if (pickup instanceof Zoin) {
zoins++;
storage.zoins = zoins; // Persist zoin count
zoinText.setText('Zoins: ' + zoins);
LK.effects.flashObject(player, 0xFFD700, 300);
}
LK.getSound('pickup').play();
// Animate pickup collection
tween(pickup, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
pickup.destroy();
}
});
pickups.splice(i, 1);
}
}
// Update UI
// Update hi-score if current score is higher
var currentScore = LK.getScore();
if (currentScore > hiScore) {
hiScore = currentScore;
storage.hiScore = hiScore;
hiScoreText.setText('Hi-Score: ' + hiScore);
}
// Update health bar
var healthPercent = player.health / player.maxHealth;
var depletedPercent = 1 - healthPercent; // Invert to show depleted health
healthBarFill.width = 1382.25 * depletedPercent;
healthBarFill.height = 58.59375;
healthText.setText(player.health + '/' + player.maxHealth);
powerAmmoText.setText('Power Ammo: ' + powerAmmo);
// Hide/show power ammo counter based on availability
powerAmmoText.visible = powerAmmo > 0;
// Update powerup display
updatePowerupDisplay();
// Update button state based on zoin availability
var currentPrice = calculateCumulativeCost(maxHealthUpgrades);
if (zoins < currentPrice) {
bottomLeftButton.alpha = 0.5; // Dim button when unaffordable
lifeLabel.alpha = 0.5;
priceLabel.alpha = 0.5;
} else {
bottomLeftButton.alpha = 1.0; // Full opacity when affordable
lifeLabel.alpha = 1.0;
priceLabel.alpha = 1.0;
}
// Update fire rate button state based on zoin availability
var fireRateCurrentPrice = calculateCumulativeCost(fireRateUpgrades);
if (zoins < fireRateCurrentPrice) {
secondButton.alpha = 0.5; // Dim button when unaffordable
fireRateLabel.alpha = 0.5;
fireRatePriceLabel.alpha = 0.5;
} else {
secondButton.alpha = 1.0; // Full opacity when affordable
fireRateLabel.alpha = 1.0;
fireRatePriceLabel.alpha = 1.0;
}
// Despawn distant pickups
for (var i = pickups.length - 1; i >= 0; i--) {
var pickup = pickups[i];
var dx = player.x - pickup.x;
var dy = player.y - pickup.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 2000) {
pickup.destroy();
pickups.splice(i, 1);
}
}
};
// Create bottom left button
var bottomLeftButton = LK.getAsset('ammoBox', {
width: 250,
height: 100,
anchorX: 0,
anchorY: 1
});
bottomLeftButton.tint = 0xFF0000; // Red background
bottomLeftButton.x = 20;
bottomLeftButton.y = -20;
LK.gui.bottomLeft.addChild(bottomLeftButton);
// Create black outline for button
var buttonOutline = LK.getAsset('ammoBox', {
width: 256,
height: 106,
anchorX: 0,
anchorY: 1
});
buttonOutline.tint = 0x000000; // Black outline
buttonOutline.x = 17;
buttonOutline.y = -17;
LK.gui.bottomLeft.addChildAt(buttonOutline, 0); // Add behind the button
// Create LIFE label for button
var lifeLabel = new Text2('LIFE', {
size: 36,
fill: 0xFFFFFF,
font: "'Arial Black'"
});
lifeLabel.anchor.set(0.5, 0.5);
lifeLabel.x = 145; // Center of button (250/2 + 20 offset for button position)
lifeLabel.y = -70; // Center of button (100/2 + 20 offset for button position)
LK.gui.bottomLeft.addChild(lifeLabel);
// Create price label for button with dynamic pricing
var maxLifePrice = calculateCumulativeCost(maxHealthUpgrades);
var priceLabel = new Text2(maxLifePrice + ' Zoin' + (maxLifePrice === 1 ? '' : 's'), {
size: 24,
fill: 0xFFD700
});
priceLabel.anchor.set(0.5, 1);
priceLabel.x = 145; // Center of button horizontally
priceLabel.y = -25; // Bottom of button
LK.gui.bottomLeft.addChild(priceLabel);
// Make button interactive
bottomLeftButton.buttonMode = true;
// Add button click handlers
bottomLeftButton.down = function (x, y, obj) {
var currentPrice = calculateCumulativeCost(maxHealthUpgrades);
if (zoins < currentPrice) {
return; // Don't allow clicking if insufficient zoins
}
// Change to white when pressed
tween(bottomLeftButton, {
tint: 0xFFFFFF
}, {
duration: 100,
easing: tween.easeOut
});
};
bottomLeftButton.up = function (x, y, obj) {
var currentPrice = calculateCumulativeCost(maxHealthUpgrades);
if (zoins < currentPrice) {
return; // Don't allow clicking if insufficient zoins
}
// Deduct cost from zoins
zoins = zoins - currentPrice;
storage.zoins = zoins; // Persist updated zoin count
zoinText.setText('Zoins: ' + zoins);
// Increment upgrade level
maxHealthUpgrades++;
storage.maxHealthUpgrades = maxHealthUpgrades; // Persist upgrade level
// Update player max health
player.maxHealth = Math.round((maxHealthUpgrades + 1) * 2);
player.health = player.maxHealth; // Full heal on upgrade
// Update health bar to reflect new maximum
var healthPercent = player.health / player.maxHealth;
var depletedPercent = 1 - healthPercent; // Invert to show depleted health
healthBarFill.width = 1382.25 * depletedPercent;
healthBarFill.height = 58.59375;
healthText.setText(player.health + '/' + player.maxHealth);
// Update price label with new cost
var newPrice = calculateCumulativeCost(maxHealthUpgrades);
priceLabel.setText(newPrice + ' Zoin' + (newPrice === 1 ? '' : 's'));
// Update powerup display
updatePowerupDisplay();
// Change back to red when released
tween(bottomLeftButton, {
tint: 0xFF0000
}, {
duration: 100,
easing: tween.easeOut
});
};
// Create second button next to the max life button
var secondButton = LK.getAsset('ammoBox', {
width: 250,
height: 100,
anchorX: 0,
anchorY: 1
});
secondButton.tint = 0xFFFF00; // Yellow background
secondButton.x = 290; // Position next to max life button (20 + 250 + 20 space = 290)
secondButton.y = -20;
LK.gui.bottomLeft.addChild(secondButton);
// Create black outline for second button
var secondButtonOutline = LK.getAsset('ammoBox', {
width: 256,
height: 106,
anchorX: 0,
anchorY: 1
});
secondButtonOutline.tint = 0x000000; // Black outline
secondButtonOutline.x = 287; // Position outline behind second button
secondButtonOutline.y = -17;
LK.gui.bottomLeft.addChildAt(secondButtonOutline, 0); // Add behind the button
// Create FIRE RATE label for second button
var fireRateLabel = new Text2('FIRE RATE', {
size: 36,
fill: 0xFFFFFF,
font: "'Arial Black'"
});
fireRateLabel.anchor.set(0.5, 0.5);
fireRateLabel.x = 415; // Center of second button (250/2 + 290 offset for button position)
fireRateLabel.y = -70; // Center of button (100/2 + 20 offset for button position)
LK.gui.bottomLeft.addChild(fireRateLabel);
// Create price label for second button with dynamic pricing
var fireRatePrice = calculateCumulativeCost(fireRateUpgrades);
var fireRatePriceLabel = new Text2(fireRatePrice + ' Zoin' + (fireRatePrice === 1 ? '' : 's'), {
size: 24,
fill: 0xFFD700
});
fireRatePriceLabel.anchor.set(0.5, 1);
fireRatePriceLabel.x = 415; // Center of second button horizontally
fireRatePriceLabel.y = -25; // Bottom of button
LK.gui.bottomLeft.addChild(fireRatePriceLabel);
// Make second button interactive
secondButton.buttonMode = true;
// Add second button click handlers
secondButton.down = function (x, y, obj) {
var currentPrice = calculateCumulativeCost(fireRateUpgrades);
if (zoins < currentPrice) {
return; // Don't allow clicking if insufficient zoins
}
// Change to white when pressed
tween(secondButton, {
tint: 0xFFFFFF
}, {
duration: 100,
easing: tween.easeOut
});
};
secondButton.up = function (x, y, obj) {
var currentPrice = calculateCumulativeCost(fireRateUpgrades);
if (zoins < currentPrice) {
return; // Don't allow clicking if insufficient zoins
}
// Deduct cost from zoins
zoins = zoins - currentPrice;
storage.zoins = zoins; // Persist updated zoin count
zoinText.setText('Zoins: ' + zoins);
// Increment upgrade level
fireRateUpgrades++;
storage.fireRateUpgrades = fireRateUpgrades; // Persist upgrade level
// Update player fire rate
player.fireRate = Math.round(3 * Math.pow(1.1, fireRateUpgrades));
// Update price label with new cost
var newPrice = calculateCumulativeCost(fireRateUpgrades);
fireRatePriceLabel.setText(newPrice + ' Zoin' + (newPrice === 1 ? '' : 's'));
// Update powerup display
updatePowerupDisplay();
// Change back to yellow when released
tween(secondButton, {
tint: 0xFFFF00
}, {
duration: 100,
easing: tween.easeOut
});
};
// Create player name display at bottom center
var playerNameText = new Text2(currentPlayerName, {
size: 45,
fill: 0xFFFFFF
});
playerNameText.anchor.set(0.5, 0.5);
playerNameText.x = 0;
playerNameText.y = -70; // Center vertically with buttons (same as button labels)
playerNameText.buttonMode = true;
// Add click handler to randomize name
playerNameText.down = function (x, y, obj) {
// Generate new random name
var randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
var randomName = commonNames[Math.floor(Math.random() * commonNames.length)];
var newPlayerName = randomAdjective + '-' + randomName;
// Update current player name and save to storage
currentPlayerName = newPlayerName;
storage.playerName = currentPlayerName;
// Update display text
playerNameText.setText(currentPlayerName);
// Visual feedback - flash effect
tween(playerNameText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(playerNameText, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
}
});
};
LK.gui.bottom.addChild(playerNameText);
// Create powerup display container
var powerupDisplay = new Container();
powerupDisplay.x = 1024; // Center horizontally
powerupDisplay.y = 280; // Below leaderboard area
game.addChild(powerupDisplay);
// Powerup display background removed
// Triple Shot powerup icon and level
var tripleShotIcon = new TripleShot();
tripleShotIcon.x = -540;
tripleShotIcon.y = 0;
tripleShotIcon.visible = false;
powerupDisplay.addChild(tripleShotIcon);
var tripleShotLevelText = new Text2('Lv.1', {
size: 72,
fill: 0xFFFFFF
});
tripleShotLevelText.anchor.set(0.5, 0.5);
tripleShotLevelText.x = -540;
tripleShotLevelText.y = 75;
tripleShotLevelText.visible = false;
powerupDisplay.addChild(tripleShotLevelText);
// Power Ammo display
var powerAmmoIcon = new PowerShotPowerUp();
powerAmmoIcon.x = -180;
powerAmmoIcon.y = 0;
powerAmmoIcon.visible = false;
powerupDisplay.addChild(powerAmmoIcon);
var powerAmmoCountText = new Text2('x0', {
size: 72,
fill: 0xFFD700
});
powerAmmoCountText.anchor.set(0.5, 0.5);
powerAmmoCountText.x = -180;
powerAmmoCountText.y = 75;
powerAmmoCountText.visible = false;
powerupDisplay.addChild(powerAmmoCountText);
// Sniper powerup display
var sniperIcon = new SniperPowerUp();
sniperIcon.x = 180;
sniperIcon.y = 0;
sniperIcon.visible = false;
powerupDisplay.addChild(sniperIcon);
var sniperCooldownText = new Text2('READY', {
size: 72,
fill: 0xFF0000
});
sniperCooldownText.anchor.set(0.5, 0.5);
sniperCooldownText.x = 180;
sniperCooldownText.y = 75;
sniperCooldownText.visible = false;
powerupDisplay.addChild(sniperCooldownText);
// Wave transition function with fade effects
function transitionToNextWave() {
// Change background immediately without fade overlay
var backgroundAssets = ['background1', 'background2', 'background3', 'background4', 'background5', 'background6'];
var randomBackgroundId = backgroundAssets[Math.floor(Math.random() * backgroundAssets.length)];
var newGameBackground = LK.getAsset(randomBackgroundId, {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
newGameBackground.x = 0;
newGameBackground.y = 0;
// Remove old background if it exists
if (game.children[0] && game.children[0].width === 2048 && game.children[0].height === 2732) {
game.children[0].destroy();
game.removeChildAt(0);
}
game.addChildAt(newGameBackground, 0);
// Ensure all health bar elements are above background
game.removeChild(healthBarOutline);
game.addChild(healthBarOutline);
game.removeChild(healthBarBg);
game.addChild(healthBarBg);
game.removeChild(healthBarFill);
game.addChild(healthBarFill);
game.removeChild(healthText);
game.addChild(healthText);
// Ensure leaderboard stays visible during transition
game.removeChild(leaderboardText);
game.addChild(leaderboardText);
// Ensure player visibility
player.alpha = 1;
player.visible = true;
// Move player to topmost layer after wave transition
game.removeChild(player);
game.addChild(player);
}
// Update function for powerup display
function updatePowerupDisplay() {
// Update Triple Shot display
if (hasTripleShot) {
tripleShotIcon.visible = true;
tripleShotLevelText.visible = true;
tripleShotLevelText.setText('Lv.' + tripleShotLevel);
} else {
tripleShotIcon.visible = false;
tripleShotLevelText.visible = false;
}
// Update Power Ammo display
if (powerAmmo > 0) {
powerAmmoIcon.visible = true;
powerAmmoCountText.visible = true;
powerAmmoCountText.setText('x' + powerAmmo);
} else {
powerAmmoIcon.visible = false;
powerAmmoCountText.visible = false;
}
// Update Sniper display
if (hasSniperPowerUp) {
sniperIcon.visible = true;
sniperCooldownText.visible = true;
var cooldownSeconds = Math.max(0, Math.ceil(sniperCooldown / 60));
sniperCooldownText.setText(cooldownSeconds > 0 ? cooldownSeconds + 's' : 'READY');
} else {
sniperIcon.visible = false;
sniperCooldownText.visible = false;
}
}
// Start music
LK.playMusic('battleMusic');
// Ensure player is always on top layer
game.removeChild(player);
game.addChild(player);
// Ensure powerup display is always on top layer
game.removeChild(powerupDisplay);
game.addChild(powerupDisplay);
;
; ===================================================================
--- original.js
+++ change.js
@@ -1941,5 +1941,9 @@
LK.playMusic('battleMusic');
// Ensure player is always on top layer
game.removeChild(player);
game.addChild(player);
+// Ensure powerup display is always on top layer
+game.removeChild(powerupDisplay);
+game.addChild(powerupDisplay);
+;
;
\ No newline at end of file