User prompt
Please fix the bug: 'Error: Invalid value. Only literals or 1-level deep objects/arrays containing literals are allowed.' in or related to this line: 'storage.leaderboard = cleanLeaderboard;' Line Number: 441 βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
remover player invulnerability after zombie hit
User prompt
Please fix the bug: 'Error: Invalid value. Only literals or 1-level deep objects/arrays containing literals are allowed.' in or related to this line: 'leaderboard.push(newEntry);' Line Number: 420 βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
player still not dieing when they should
User prompt
fix player not dieing when hp <= 0
User prompt
Please fix the bug: 'Error: Invalid value. Only literals or 1-level deep objects/arrays containing literals are allowed.' in or related to this line: 'leaderboard.push(newEntry);' Line Number: 419 βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Error: Invalid value. Only literals or 1-level deep objects/arrays containing literals are allowed.' in or related to this line: 'leaderboard.push(newEntry);' Line Number: 419 βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
stop removing permanent variables βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
begin saving permanent variables βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
set all permanent variables to 0 βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
leaderboard should show top 9 and the player's placement in total leaderboard after top 9 βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
make leaderboard a scrolling banner that fills the width of the viewport βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
User prompt
move leaderboard display to below score and healthbar
User prompt
create public leaderboard with name score wave βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
if no zombies in view area for 10 seconds advance to next wave
User prompt
set upgrade costs to follow (10+(current upgrade level * 2))/2 + previous level cost βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
do not reset persistent data βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
set upgrade costs to follow (10+(current upgrade level * 2))/2
User prompt
round all costs to nearest whole number
User prompt
set upgrade costs to follow (5+(current upgrade level * 2))/2
User prompt
clear all persistent data βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'storage.clear is not a function' in or related to this line: 'storage.clear();' Line Number: 1093 βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
perform 1 time delete of all save game data βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
change all upgrade costs to reflect x2 per upgrade level
User prompt
set power shot ammo to 10 per powershot powerup
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AmmoBox = Container.expand(function () {
var self = Container.call(this);
var ammoGraphics = self.attachAsset('ammoBox', {
anchorX: 0.5,
anchorY: 0.5
});
self.ammoAmount = 50;
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60fps
self.update = function () {
self.rotation += 0.02;
};
return self;
});
var AmmoPowerUp = Container.expand(function () {
var self = Container.call(this);
var powerUpGraphics = self.attachAsset('ammoPowerUp', {
anchorX: 0.5,
anchorY: 0.5
});
self.ammoAmount = 10;
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60fps
self.update = function () {
self.rotation += 0.02;
// Add pulsing effect
self.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
self.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
};
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 = 20;
self.damage = 10;
self.vx = 0;
self.vy = 0;
self.piercing = false;
self.killHit = 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
});
self.healAmount = 30;
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60fps
self.update = function () {
self.rotation += 0.02;
};
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.invulnerable = 0;
self.takeDamage = function (amount) {
if (self.invulnerable > 0) return;
self.health -= amount;
self.invulnerable = 60;
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) {
self.health = Math.min(self.health + amount, self.maxHealth);
};
self.update = function () {
if (self.fireCooldown > 0) self.fireCooldown--;
if (self.invulnerable > 0) {
self.invulnerable--;
self.alpha = Math.floor(self.invulnerable / 5) % 2 ? 0.5 : 1;
}
};
return self;
});
var TripleShot = Container.expand(function () {
var self = Container.call(this);
var tripleShotGraphics = self.attachAsset('tripleShot', {
anchorX: 0.5,
anchorY: 0.5
});
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60fps
self.update = function () {
self.rotation += 0.02;
// Add pulsing effect
self.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
self.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
};
return self;
});
var Zoin = Container.expand(function () {
var self = Container.call(this);
var zoinGraphics = self.attachAsset('zoin', {
anchorX: 0.5,
anchorY: 0.5
});
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60fps
self.update = function () {
self.rotation += 0.05;
// 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);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.baseSpeed = 1.5;
var variation = self.baseSpeed * 0.25; // 25% variation
self.speed = 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;
});
/****
* 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 = ['ammoPowerUp', 'healthPack', 'ammoBox', 'tripleShot'];
var lastPowerUpType = null;
var hasTripleShot = false;
var tripleShotLevel = 0;
var maxTripleShotLevel = 5;
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;
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);
// 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
var healthBarBg = LK.getAsset('ammoBox', {
width: 1843,
height: 75,
anchorX: 0.5,
anchorY: 0
});
healthBarBg.tint = 0xFF0000;
healthBarBg.x = 1024;
healthBarBg.y = 100;
game.addChildAt(healthBarBg, 1);
// Health bar fill
var healthBarFill = LK.getAsset('ammoBox', {
width: 1843,
height: 75,
anchorX: 0.5,
anchorY: 0
});
healthBarFill.tint = 0x444444;
healthBarFill.x = 1024;
healthBarFill.y = 100;
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);
// Initialize player
player = game.addChild(new Player());
player.maxHealth = (maxHealthUpgrades + 1) * 2;
player.health = player.maxHealth;
player.fireRate = 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 resetGame() {
// Reset wave and zombies
wave = 1;
zombiesPerWave = 5;
zombiesSpawned = 0;
zombiesKilled = 0;
spawnTimer = 0;
// Reset score and multipliers
LK.setScore(0);
scoreMultiplier = 1;
killStreak = 0;
killsSinceLastPowerUp = 0;
lastPowerUpSpawn = 0;
lastPowerUpType = null;
// Reset powerups
powerAmmo = 0;
hasTripleShot = false;
tripleShotLevel = 0;
// Reset player state with upgrades
player.maxHealth = (maxHealthUpgrades + 1) * 2;
player.health = player.maxHealth;
player.fireRate = 3 * Math.pow(1.1, fireRateUpgrades);
player.fireCooldown = 0;
player.invulnerable = 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());
// Update health bar
var healthPercent = player.health / player.maxHealth;
healthBarFill.width = 1843 * healthPercent;
healthText.setText(player.health + '/' + player.maxHealth);
powerAmmoText.setText('Power Ammo: ' + powerAmmo);
powerAmmoText.visible = false;
// Reset player position
player.x = 1024;
player.y = 1366;
}
function spawnZombie() {
var zombie = new Zombie();
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 = zombie.baseSpeed * (1 + wave * 0.1) + (Math.random() - 0.5) * 2 * (zombie.baseSpeed * 0.25);
zombie.health += wave * 5;
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) {
var shouldSpawn = false;
var pickup;
var powerUpType;
// Force powerup spawn if 50+ kills without powerup
if (killsSinceLastPowerUp >= 50) {
shouldSpawn = true;
// Create available powerup list excluding last spawned type
var availablePowerUps = powerUpList.slice();
if (lastPowerUpType !== null) {
var index = availablePowerUps.indexOf(lastPowerUpType);
if (index > -1) {
availablePowerUps.splice(index, 1);
}
}
powerUpType = availablePowerUps[Math.floor(Math.random() * availablePowerUps.length)];
if (powerUpType === 'ammoPowerUp') {
pickup = new AmmoPowerUp();
} else if (powerUpType === 'ammoBox') {
pickup = new AmmoBox();
} else if (powerUpType === 'tripleShot') {
pickup = new TripleShot();
} else {
pickup = new HealthPack();
}
lastPowerUpType = powerUpType;
killsSinceLastPowerUp = 0;
if (pickup instanceof AmmoPowerUp) {
lastPowerUpSpawn = LK.ticks;
}
} else if (Math.random() < 0.3) {
shouldSpawn = true;
var rand = Math.random();
if (rand < 0.1 && LK.ticks - lastPowerUpSpawn > 1800) {
pickup = new AmmoPowerUp();
powerUpType = 'ammoPowerUp';
lastPowerUpSpawn = LK.ticks;
killsSinceLastPowerUp = 0;
} else if (rand < 0.15 && LK.ticks - lastPowerUpSpawn > 1800) {
pickup = new TripleShot();
powerUpType = 'tripleShot';
lastPowerUpSpawn = LK.ticks;
killsSinceLastPowerUp = 0;
} else if (rand < 0.6) {
pickup = new AmmoBox();
powerUpType = 'ammoBox';
} else {
pickup = new HealthPack();
powerUpType = 'healthPack';
}
lastPowerUpType = powerUpType;
}
if (shouldSpawn) {
pickup.x = x;
pickup.y = 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() {
// 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.rotation = angle; // Rotate player to face 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
}
}
for (var b = 0; b < bulletCount; b++) {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
bullet.damage = 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 (powerAmmo > 0) {
bullet.piercing = true;
bullet.maxDistance = 1600; // 2x distance
bullet.damage = (10 + damageUpgrades * 5) * 5; // 5x normal damage
if (b === 0) powerAmmo--; // Only consume one power ammo per shot
// 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));
}
// 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) {
// Force spawn a powerup if none spawned this wave
var powerupSpawnedThisWave = false;
for (var p = 0; p < pickups.length; p++) {
var pickup = pickups[p];
if (pickup instanceof AmmoPowerUp || pickup instanceof HealthPack || pickup instanceof AmmoBox || pickup instanceof TripleShot) {
powerupSpawnedThisWave = true;
break;
}
}
if (!powerupSpawnedThisWave) {
// Spawn a guaranteed powerup at a random location near the center
var guaranteedX = 1024 + (Math.random() - 0.5) * 800;
var guaranteedY = 1366 + (Math.random() - 0.5) * 800;
spawnPickup(guaranteedX, guaranteedY);
}
// Next wave
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
waveText.setText('Wave: ' + wave);
}
// 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;
zombie.rotation = Math.atan2(dy, dx);
}
// Check collision with player
if (zombie.intersects(player) && zombie.attackCooldown === 0) {
if (player.takeDamage(zombie.damage)) {
LK.showGameOver();
}
zombie.attackCooldown = 60;
killStreak = 0;
scoreMultiplier = 1;
}
}
// 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);
// Always drop 1 zoin when zombie dies
var zoin = new Zoin();
zoin.x = zombie.x;
zoin.y = zombie.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);
}
LK.setScore(LK.getScore() + 10 * scoreMultiplier);
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 regardless of distance
var age = LK.ticks - pickup.spawnTime;
if (age >= pickup.lifespan) {
// Auto pickup all items when expiring
if (pickup instanceof HealthPack) {
player.heal(pickup.healAmount);
} else if (pickup instanceof AmmoBox) {
powerAmmo += pickup.ammoAmount;
LK.effects.flashObject(player, 0x228b22, 500);
} else if (pickup instanceof AmmoPowerUp) {
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 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 AmmoBox) {
powerAmmo += pickup.ammoAmount;
LK.effects.flashObject(player, 0x228b22, 500);
} else if (pickup instanceof AmmoPowerUp) {
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 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 health bar
var healthPercent = player.health / player.maxHealth;
healthBarFill.width = 1843 * healthPercent;
healthText.setText(player.health + '/' + player.maxHealth);
powerAmmoText.setText('Power Ammo: ' + powerAmmo);
// Hide/show power ammo counter based on availability
powerAmmoText.visible = powerAmmo > 0;
// Update button state based on zoin availability
var currentPrice = (1 + maxHealthUpgrades) * 2;
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 = (1 + fireRateUpgrades) * 3;
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 = (1 + maxHealthUpgrades) * 2;
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 = (1 + maxHealthUpgrades) * 2;
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 = (1 + maxHealthUpgrades) * 2;
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 = (maxHealthUpgrades + 1) * 2;
player.health = player.maxHealth; // Full heal on upgrade
// Update health bar to reflect new maximum
var healthPercent = player.health / player.maxHealth;
healthBarFill.width = 1843 * healthPercent;
healthText.setText(player.health + '/' + player.maxHealth);
// Update price label with new cost
var newPrice = (1 + maxHealthUpgrades) * 2;
priceLabel.setText(newPrice + ' Zoin' + (newPrice === 1 ? '' : 's'));
// 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 = (1 + fireRateUpgrades) * 3;
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 = (1 + fireRateUpgrades) * 3;
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 = (1 + fireRateUpgrades) * 3;
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 = 3 * Math.pow(1.1, fireRateUpgrades);
// Update price label with new cost
var newPrice = (1 + fireRateUpgrades) * 3;
fireRatePriceLabel.setText(newPrice + ' Zoin' + (newPrice === 1 ? '' : 's'));
// Change back to yellow when released
tween(secondButton, {
tint: 0xFFFF00
}, {
duration: 100,
easing: tween.easeOut
});
};
// Start music
LK.playMusic('battleMusic');
; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AmmoBox = Container.expand(function () {
var self = Container.call(this);
var ammoGraphics = self.attachAsset('ammoBox', {
anchorX: 0.5,
anchorY: 0.5
});
self.ammoAmount = 50;
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60fps
self.update = function () {
self.rotation += 0.02;
};
return self;
});
var AmmoPowerUp = Container.expand(function () {
var self = Container.call(this);
var powerUpGraphics = self.attachAsset('ammoPowerUp', {
anchorX: 0.5,
anchorY: 0.5
});
self.ammoAmount = 10;
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60fps
self.update = function () {
self.rotation += 0.02;
// Add pulsing effect
self.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
self.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
};
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 = 20;
self.damage = 10;
self.vx = 0;
self.vy = 0;
self.piercing = false;
self.killHit = 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
});
self.healAmount = 30;
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60fps
self.update = function () {
self.rotation += 0.02;
};
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.invulnerable = 0;
self.takeDamage = function (amount) {
if (self.invulnerable > 0) return;
self.health -= amount;
self.invulnerable = 60;
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) {
self.health = Math.min(self.health + amount, self.maxHealth);
};
self.update = function () {
if (self.fireCooldown > 0) self.fireCooldown--;
if (self.invulnerable > 0) {
self.invulnerable--;
self.alpha = Math.floor(self.invulnerable / 5) % 2 ? 0.5 : 1;
}
};
return self;
});
var TripleShot = Container.expand(function () {
var self = Container.call(this);
var tripleShotGraphics = self.attachAsset('tripleShot', {
anchorX: 0.5,
anchorY: 0.5
});
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60fps
self.update = function () {
self.rotation += 0.02;
// Add pulsing effect
self.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
self.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
};
return self;
});
var Zoin = Container.expand(function () {
var self = Container.call(this);
var zoinGraphics = self.attachAsset('zoin', {
anchorX: 0.5,
anchorY: 0.5
});
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60fps
self.update = function () {
self.rotation += 0.05;
// 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);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.baseSpeed = 1.5;
var variation = self.baseSpeed * 0.25; // 25% variation
self.speed = 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;
});
/****
* 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 = ['ammoPowerUp', 'healthPack', 'ammoBox', 'tripleShot'];
var lastPowerUpType = null;
var hasTripleShot = false;
var tripleShotLevel = 0;
var maxTripleShotLevel = 5;
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;
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);
// 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
var healthBarBg = LK.getAsset('ammoBox', {
width: 1843,
height: 75,
anchorX: 0.5,
anchorY: 0
});
healthBarBg.tint = 0xFF0000;
healthBarBg.x = 1024;
healthBarBg.y = 100;
game.addChildAt(healthBarBg, 1);
// Health bar fill
var healthBarFill = LK.getAsset('ammoBox', {
width: 1843,
height: 75,
anchorX: 0.5,
anchorY: 0
});
healthBarFill.tint = 0x444444;
healthBarFill.x = 1024;
healthBarFill.y = 100;
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);
// Initialize player
player = game.addChild(new Player());
player.maxHealth = (maxHealthUpgrades + 1) * 2;
player.health = player.maxHealth;
player.fireRate = 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 resetGame() {
// Reset wave and zombies
wave = 1;
zombiesPerWave = 5;
zombiesSpawned = 0;
zombiesKilled = 0;
spawnTimer = 0;
// Reset score and multipliers
LK.setScore(0);
scoreMultiplier = 1;
killStreak = 0;
killsSinceLastPowerUp = 0;
lastPowerUpSpawn = 0;
lastPowerUpType = null;
// Reset powerups
powerAmmo = 0;
hasTripleShot = false;
tripleShotLevel = 0;
// Reset player state with upgrades
player.maxHealth = (maxHealthUpgrades + 1) * 2;
player.health = player.maxHealth;
player.fireRate = 3 * Math.pow(1.1, fireRateUpgrades);
player.fireCooldown = 0;
player.invulnerable = 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());
// Update health bar
var healthPercent = player.health / player.maxHealth;
healthBarFill.width = 1843 * healthPercent;
healthText.setText(player.health + '/' + player.maxHealth);
powerAmmoText.setText('Power Ammo: ' + powerAmmo);
powerAmmoText.visible = false;
// Reset player position
player.x = 1024;
player.y = 1366;
}
function spawnZombie() {
var zombie = new Zombie();
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 = zombie.baseSpeed * (1 + wave * 0.1) + (Math.random() - 0.5) * 2 * (zombie.baseSpeed * 0.25);
zombie.health += wave * 5;
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) {
var shouldSpawn = false;
var pickup;
var powerUpType;
// Force powerup spawn if 50+ kills without powerup
if (killsSinceLastPowerUp >= 50) {
shouldSpawn = true;
// Create available powerup list excluding last spawned type
var availablePowerUps = powerUpList.slice();
if (lastPowerUpType !== null) {
var index = availablePowerUps.indexOf(lastPowerUpType);
if (index > -1) {
availablePowerUps.splice(index, 1);
}
}
powerUpType = availablePowerUps[Math.floor(Math.random() * availablePowerUps.length)];
if (powerUpType === 'ammoPowerUp') {
pickup = new AmmoPowerUp();
} else if (powerUpType === 'ammoBox') {
pickup = new AmmoBox();
} else if (powerUpType === 'tripleShot') {
pickup = new TripleShot();
} else {
pickup = new HealthPack();
}
lastPowerUpType = powerUpType;
killsSinceLastPowerUp = 0;
if (pickup instanceof AmmoPowerUp) {
lastPowerUpSpawn = LK.ticks;
}
} else if (Math.random() < 0.3) {
shouldSpawn = true;
var rand = Math.random();
if (rand < 0.1 && LK.ticks - lastPowerUpSpawn > 1800) {
pickup = new AmmoPowerUp();
powerUpType = 'ammoPowerUp';
lastPowerUpSpawn = LK.ticks;
killsSinceLastPowerUp = 0;
} else if (rand < 0.15 && LK.ticks - lastPowerUpSpawn > 1800) {
pickup = new TripleShot();
powerUpType = 'tripleShot';
lastPowerUpSpawn = LK.ticks;
killsSinceLastPowerUp = 0;
} else if (rand < 0.6) {
pickup = new AmmoBox();
powerUpType = 'ammoBox';
} else {
pickup = new HealthPack();
powerUpType = 'healthPack';
}
lastPowerUpType = powerUpType;
}
if (shouldSpawn) {
pickup.x = x;
pickup.y = 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() {
// 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.rotation = angle; // Rotate player to face 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
}
}
for (var b = 0; b < bulletCount; b++) {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
bullet.damage = 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 (powerAmmo > 0) {
bullet.piercing = true;
bullet.maxDistance = 1600; // 2x distance
bullet.damage = (10 + damageUpgrades * 5) * 5; // 5x normal damage
if (b === 0) powerAmmo--; // Only consume one power ammo per shot
// 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));
}
// 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) {
// Force spawn a powerup if none spawned this wave
var powerupSpawnedThisWave = false;
for (var p = 0; p < pickups.length; p++) {
var pickup = pickups[p];
if (pickup instanceof AmmoPowerUp || pickup instanceof HealthPack || pickup instanceof AmmoBox || pickup instanceof TripleShot) {
powerupSpawnedThisWave = true;
break;
}
}
if (!powerupSpawnedThisWave) {
// Spawn a guaranteed powerup at a random location near the center
var guaranteedX = 1024 + (Math.random() - 0.5) * 800;
var guaranteedY = 1366 + (Math.random() - 0.5) * 800;
spawnPickup(guaranteedX, guaranteedY);
}
// Next wave
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
waveText.setText('Wave: ' + wave);
}
// 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;
zombie.rotation = Math.atan2(dy, dx);
}
// Check collision with player
if (zombie.intersects(player) && zombie.attackCooldown === 0) {
if (player.takeDamage(zombie.damage)) {
LK.showGameOver();
}
zombie.attackCooldown = 60;
killStreak = 0;
scoreMultiplier = 1;
}
}
// 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);
// Always drop 1 zoin when zombie dies
var zoin = new Zoin();
zoin.x = zombie.x;
zoin.y = zombie.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);
}
LK.setScore(LK.getScore() + 10 * scoreMultiplier);
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 regardless of distance
var age = LK.ticks - pickup.spawnTime;
if (age >= pickup.lifespan) {
// Auto pickup all items when expiring
if (pickup instanceof HealthPack) {
player.heal(pickup.healAmount);
} else if (pickup instanceof AmmoBox) {
powerAmmo += pickup.ammoAmount;
LK.effects.flashObject(player, 0x228b22, 500);
} else if (pickup instanceof AmmoPowerUp) {
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 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 AmmoBox) {
powerAmmo += pickup.ammoAmount;
LK.effects.flashObject(player, 0x228b22, 500);
} else if (pickup instanceof AmmoPowerUp) {
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 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 health bar
var healthPercent = player.health / player.maxHealth;
healthBarFill.width = 1843 * healthPercent;
healthText.setText(player.health + '/' + player.maxHealth);
powerAmmoText.setText('Power Ammo: ' + powerAmmo);
// Hide/show power ammo counter based on availability
powerAmmoText.visible = powerAmmo > 0;
// Update button state based on zoin availability
var currentPrice = (1 + maxHealthUpgrades) * 2;
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 = (1 + fireRateUpgrades) * 3;
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 = (1 + maxHealthUpgrades) * 2;
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 = (1 + maxHealthUpgrades) * 2;
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 = (1 + maxHealthUpgrades) * 2;
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 = (maxHealthUpgrades + 1) * 2;
player.health = player.maxHealth; // Full heal on upgrade
// Update health bar to reflect new maximum
var healthPercent = player.health / player.maxHealth;
healthBarFill.width = 1843 * healthPercent;
healthText.setText(player.health + '/' + player.maxHealth);
// Update price label with new cost
var newPrice = (1 + maxHealthUpgrades) * 2;
priceLabel.setText(newPrice + ' Zoin' + (newPrice === 1 ? '' : 's'));
// 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 = (1 + fireRateUpgrades) * 3;
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 = (1 + fireRateUpgrades) * 3;
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 = (1 + fireRateUpgrades) * 3;
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 = 3 * Math.pow(1.1, fireRateUpgrades);
// Update price label with new cost
var newPrice = (1 + fireRateUpgrades) * 3;
fireRatePriceLabel.setText(newPrice + ' Zoin' + (newPrice === 1 ? '' : 's'));
// Change back to yellow when released
tween(secondButton, {
tint: 0xFFFF00
}, {
duration: 100,
easing: tween.easeOut
});
};
// Start music
LK.playMusic('battleMusic');
;