User prompt
change powershot drop visual to a Large pulsing bullet with a "P" in the center ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
change name of Ammo Power Up to Power Shot, adjust all references to reflect change
User prompt
delete ammobox powerup
User prompt
change the visual of the PowerShot powerup drop to a larger Power Shot bullet with a "P" in the center
User prompt
when picking powerup to drop ensure that each powerup is picked at least once before any powerups are repeated
User prompt
make the zombie hulks the last zombies spawned
User prompt
put a space between the bullets in tripleshot powerup visual
User prompt
make the tripleshot drop visual's bullets longer, make the powerup spin like the zoin powerup ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
change appearance of the tripshot powerup to three bullets with the side bullets tilted 10 degrees away from the center bullet
User prompt
add female names to the name array
User prompt
make the name clickable, on click the name will randomize to a new name ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
center the name display on the height of the adjacent buttons
User prompt
Display assigned Name at the bottom center of the view port
User prompt
give each Player a two word namethe format should be "Adjective-Common name" save this name perpetually for that player on an individual basis ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
make tripleshot powerup use as many powershot rounds as they fire bullets
User prompt
make zombie hulks have 50% chance to have a random powerup in their inventory to drop on death, change zombie hulk color to reflect having a powerup in inventory, remove normal zombie powerup drop chance ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
remove guaranteed powerup
User prompt
make zombie hulk score gained when killed 2x zombie score
User prompt
make hulk spawn x times per wave where x=wave number
User prompt
create "Zombie Hulk" enemy, it should inherit all behaviors from the existing zombie enemy but have 100% more life and 25% more speed and be 50% larger
User prompt
change spinning zoin drop to have a z on both sides make the Z neon green ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
change zoin drop to 10% chance
User prompt
spin zoin drops at 50% current speed ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make zoin drops a spinning pulsing coin with a Z on 1 side and blank on the other ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
immediately break all loops on player death
/****
* 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) {
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
});
// 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.lifespan = 600; // 10 seconds at 60fps
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);
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;
});
var ZombieHulk = Zombie.expand(function () {
var self = Zombie.call(this);
// Remove the original zombie graphics and add hulk graphics
self.removeChildAt(0); // Remove original zombie graphics
var zombieHulkGraphics = self.attachAsset('zombieHulk', {
anchorX: 0.5,
anchorY: 0.5
});
// 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 = 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
// Already 50% larger due to asset size (90x90 vs 60x60)
// 50% chance to have a random powerup in inventory
self.hasPowerup = Math.random() < 0.5;
if (self.hasPowerup) {
// Change color to indicate powerup inventory
zombieHulkGraphics.tint = 0x00ff00; // Green tint for powerup inventory
var powerUpTypes = ['ammoPowerUp', 'healthPack', 'ammoBox', '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 = ['ammoPowerUp', 'healthPack', 'ammoBox', 'tripleShot'];
var lastPowerUpType = null;
var hasTripleShot = false;
var tripleShotLevel = 0;
var maxTripleShotLevel = 5;
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'];
// 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);
// 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);
// 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 9 entries
for (var i = 0; i < Math.min(9, 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 9, show their position
if (playerBestEntry && playerPosition > 9) {
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 * 15,
// Adjust speed as needed
easing: tween.linear,
onFinish: function onFinish() {
leaderboardScrolling = false;
// Restart the scroll after a brief pause
LK.setTimeout(function () {
startLeaderboardScroll();
}, 2000);
}
});
}
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 20
cleanLeaderboard.sort(function (a, b) {
return b.score - a.score;
});
if (cleanLeaderboard.length > 20) {
cleanLeaderboard = cleanLeaderboard.slice(0, 20);
}
// 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();
}
// 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 calculateCumulativeCost(upgradeLevel) {
var totalCost = 0;
for (var i = 0; i <= upgradeLevel; i++) {
totalCost += Math.round((10 + i * 2) / 2);
}
return totalCost;
}
function resetGame() {
// 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 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;
// Reset no zombies timer
noZombiesTimer = 0;
}
// Track hulks spawned for current wave
var hulksSpawned = 0;
function spawnZombie() {
var zombie;
// Spawn hulks equal to wave number, then regular zombies
if (hulksSpawned < wave) {
zombie = new ZombieHulk();
hulksSpawned++;
} else {
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, zombie) {
var shouldSpawn = false;
var pickup;
var powerUpType;
// Check if zombie hulk has powerup in inventory
if (zombie instanceof ZombieHulk && zombie.hasPowerup) {
shouldSpawn = true;
powerUpType = zombie.powerupType;
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;
}
}
// Force powerup spawn if 50+ kills without powerup (but not for normal zombies)
else 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;
}
}
// No powerup spawn for normal zombies
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
powerAmmo--; // Consume one power ammo per bullet fired
// 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) {
// 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
noZombiesTimer = 0; // Reset no zombies timer
hulksSpawned = 0; // Reset hulk spawn counter for new wave
waveText.setText('Wave: ' + wave);
} 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
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);
}
} 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;
zombie.rotation = Math.atan2(dy, dx);
}
// 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) {
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);
}
// 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 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 = 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 = (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 = calculateCumulativeCost(maxHealthUpgrades);
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 = 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 = 3 * Math.pow(1.1, fireRateUpgrades);
// Update price label with new cost
var newPrice = calculateCumulativeCost(fireRateUpgrades);
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');
; ===================================================================
--- original.js
+++ change.js
@@ -299,9 +299,19 @@
}
} else {
leaderboard = [];
}
-var currentPlayerName = '';
+// 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'];
+// 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
@@ -446,12 +456,9 @@
}
updateLeaderboardDisplay();
// Function to submit score to leaderboard
function submitScoreToLeaderboard() {
- // Generate a simple player name if none exists
- if (!currentPlayerName) {
- currentPlayerName = 'Player' + Math.floor(Math.random() * 1000);
- }
+ // Player name is now persistently generated at game start
// Create simple literal object for leaderboard entry
var newEntry = {
name: currentPlayerName,
score: LK.getScore(),