User prompt
Make the bullets rotate to direction ı shoot them at
User prompt
Im on pc can we do something better for walking
User prompt
Please fix the bug: 'TypeError: LK.getTouches is not a function' in or related to this line: 'var touches = LK.getTouches();' Line Number: 365
User prompt
Make us able to walk
User prompt
Make us able to walk around and change our wepon to a stick instead of a gun that we can swing and hit
Code edit (1 edits merged)
Please save this source code
User prompt
Monster Defense Arena
Initial prompt
Im planing a top view game. We will have a wepon and kill the monster comes close to us
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var BestZombie = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('monster', { anchorX: 0.5, anchorY: 0.5 }); graphics.tint = 0xffd700; // Gold tint to distinguish BestZombie self.speed = 50; // Double the fastest speed from CrayzZombie self.baseSpeed = 50; self.health = 8; // Double the high health from MegaSplitterZombie self.damage = 30; // Double the high damage from ToughZombie self.active = true; self.lastPlayerDistance = 0; self.spiralAngle = 0; // Angle for spiral movement from CrayzZombie self.crazyTimer = 0; // Timer for random direction changes from CrayzZombie self.suddenChangeX = 0; // Sudden movement offset X from CrayzZombie self.suddenChangeY = 0; // Sudden movement offset Y from CrayzZombie self.isChangingDirection = false; // Flag for direction change animation from CrayzZombie self.throwCooldown = 0; // Cooldown for throwing rocks from RockThrowerZombie self.throwRange = 1800; // Double the range to start throwing rocks from RockThrowerZombie self.isInRange = false; // Flag for range check from RockThrowerZombie self.update = function () { if (!self.active) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Apply time dilation effect when close to player var currentSpeed = self.baseSpeed; if (timeDilationStacks > 0 && distance <= 300) { var slowdownPercent = timeDilationStacks * 10; var slowdownMultiplier = 1 - slowdownPercent / 100; currentSpeed = self.baseSpeed * slowdownMultiplier; } // Spiral movement around screen center from CrayzZombie self.spiralAngle += 0.2; // Double the spiral rotation speed var screenCenterX = 1024; // Half of 2048 var screenCenterY = 1366; // Half of 2732 var dxToCenter = screenCenterX - self.x; var dyToCenter = screenCenterY - self.y; var distanceToCenter = Math.sqrt(dxToCenter * dxToCenter + dyToCenter * dyToCenter); var currentAngle = Math.atan2(self.y - screenCenterY, self.x - screenCenterX); var currentRadius = distanceToCenter; var targetRadius = Math.max(50, currentRadius - 1); // Minimum radius of 50 pixels var newAngle = currentAngle + 0.1; // Spiral by rotating around center var newX = screenCenterX + Math.cos(newAngle) * targetRadius; var newY = screenCenterY + Math.sin(newAngle) * targetRadius; // Move toward the new spiral position var dx = newX - self.x; var dy = newY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * currentSpeed; self.y += dy / distance * currentSpeed; // Rotate zombie to face movement direction graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } // CrayzZombie can deal damage when getting close to screen center if (distanceToCenter <= 250) { player.takeDamage(self.damage); self.active = false; } // Move toward player but stop at throwing range from RockThrowerZombie if (distance > self.throwRange) { if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Rotate zombie to face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } self.isInRange = false; } else { // In range - stop moving and start throwing self.isInRange = true; // Face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } // Throw rocks when in range from RockThrowerZombie if (self.isInRange && self.throwCooldown <= 0) { self.throwRock(); self.throwCooldown = 120; // 2 seconds between throws } if (self.throwCooldown > 0) { self.throwCooldown--; } self.lastPlayerDistance = distance; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.active = false; // Create blood explosion with spreading effect from ToughZombie for (var i = 0; i < 15; i++) { var bloodSplat = new BloodSplat(); bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; var scale = 0.3 + Math.random() * 1.2; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; var angle = Math.random() * Math.PI * 2; var distance = 300 + Math.random() * 600; // Double the spread distance var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); var delay = Math.random() * 200; var duration = 300 + Math.random() * 400; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } // Spawn 2 splitter zombies from MegaSplitterZombie for (var s = 0; s < 2; s++) { var splitterZombie = new SplitterZombie(); // Position splitter zombies around the original position var spawnAngle = Math.PI * s + Math.random() * 0.5; var spawnDistance = 100 + Math.random() * 50; splitterZombie.x = self.x + Math.cos(spawnAngle) * spawnDistance; splitterZombie.y = self.y + Math.sin(spawnAngle) * spawnDistance; // Keep within bounds splitterZombie.x = Math.max(125, Math.min(1923, splitterZombie.x)); splitterZombie.y = Math.max(125, Math.min(2607, splitterZombie.y)); monsters.push(splitterZombie); game.addChild(splitterZombie); } LK.setScore(LK.getScore() + 500); // Increase score reward for BestZombie to 500 points LK.getSound('hit').play(); return true; } return false; }; self.throwRock = function () { var rock = new Rock(); rock.x = self.x; rock.y = self.y; rock.setDirection(player.x, player.y); rocks.push(rock); game.addChild(rock); // Shake animation when throwing var originalX = self.x; var originalY = self.y; tween(self, { x: originalX + 15, y: originalY + 10 }, { duration: 80, onFinish: function onFinish() { tween(self, { x: originalX - 10, y: originalY - 8 }, { duration: 80, onFinish: function onFinish() { tween(self, { x: originalX, y: originalY }, { duration: 80 }); } }); } }); }; return self; }); var BloodSplat = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('blood', { anchorX: 0.5, anchorY: 0.5 }); self.lifetime = 300; // 5 seconds at 60fps self.fadeStartTime = 0; self.isFading = false; self.update = function () { self.lifetime--; // Start fading after 10 seconds if (self.lifetime <= 0 && !self.isFading) { self.isFading = true; tween(graphics, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { self.destroy(); } }); } }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 20; self.dx = 0; self.dy = 0; self.active = true; self.bounced = false; self.bounceCount = 0; // Track number of bounces performed self.piercedEnemies = []; // Track pierced enemies for piercing rounds self.targetX = 0; // Target position for magnetic bullets self.targetY = 0; self.frozenEnemies = []; // Track frozen enemies for freeze shot self.ricochetCount = 0; // Track number of ricochets self.isLuckyShot = false; // Track if this is a lucky shot self.update = function () { if (!self.active) return; // Apply pink tint for lucky shot bullets if (self.isLuckyShot) { graphics.tint = 0xff69b4; // Pink color } // Apply magnetic bullets homing if active if (magneticBulletsStacks > 0 && self.active) { var nearestMonster = null; var nearestDistance = 300 + magneticBulletsStacks * 50; // Increased range per stack for (var i = 0; i < monsters.length; i++) { var monster = monsters[i]; if (!monster.active) continue; var dx = monster.x - self.x; var dy = monster.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestMonster = monster; } } if (nearestMonster) { // Gradually adjust direction toward target var homingStrength = 0.1 + magneticBulletsStacks * 0.05; // Stronger homing per stack var targetDx = nearestMonster.x - self.x; var targetDy = nearestMonster.y - self.y; var targetDistance = Math.sqrt(targetDx * targetDx + targetDy * targetDy); if (targetDistance > 0) { var targetUnitX = targetDx / targetDistance; var targetUnitY = targetDy / targetDistance; self.dx = self.dx * (1 - homingStrength) + targetUnitX * self.speed * homingStrength; self.dy = self.dy * (1 - homingStrength) + targetUnitY * self.speed * homingStrength; } } } self.x += self.dx; self.y += self.dy; // Check for ricochet off walls if (ricochetMasterStacks > 0 && self.ricochetCount < ricochetMasterStacks) { var ricochetChance = Math.min(ricochetMasterStacks * 25, 100); // 25% per stack, max 100% var shouldRicochet = Math.random() * 100 < ricochetChance; if (shouldRicochet && (self.x <= 0 || self.x >= 2048 || self.y <= 0 || self.y >= 2732)) { self.ricochetCount++; // Bounce off walls if (self.x <= 0 || self.x >= 2048) { self.dx = -self.dx; self.x = Math.max(0, Math.min(2048, self.x)); } if (self.y <= 0 || self.y >= 2732) { self.dy = -self.dy; self.y = Math.max(0, Math.min(2732, self.y)); } // Find nearest enemy to ricochet toward var nearestMonster = null; var nearestDistance = Infinity; for (var i = 0; i < monsters.length; i++) { var monster = monsters[i]; if (!monster.active) continue; var dx = monster.x - self.x; var dy = monster.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestMonster = monster; } } if (nearestMonster) { self.setDirection(nearestMonster.x, nearestMonster.y); } } else if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { self.active = false; } } else if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { self.active = false; } }; self.setDirection = function (targetX, targetY) { var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.dx = dx / distance * self.speed; self.dy = dy / distance * self.speed; // Rotate bullet to face direction of travel graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } }; self.bounce = function (hitMonster) { // Increment bounce count first self.bounceCount++; // Check if we've reached the bounce limit if (self.bounceCount >= bouncyBulletCount) { self.active = false; return; } // Find nearest monster regardless of distance var nearestMonster = null; var nearestDistance = Infinity; // No distance limit for (var i = 0; i < monsters.length; i++) { var monster = monsters[i]; if (!monster.active || monster === hitMonster) continue; var dx = monster.x - self.x; var dy = monster.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestMonster = monster; } } if (nearestMonster) { self.bounced = true; self.bounceTarget = nearestMonster; // Store the target self.setDirection(nearestMonster.x, nearestMonster.y); } else { // No target found, deactivate bullet self.active = false; } }; return self; }); var Card = Container.expand(function (powerType) { var self = Container.call(this); // Card background var cardBg = self.attachAsset('cardBackground', { anchorX: 0.5, anchorY: 0.5 }); // Card title text var titleText = new Text2(getPowerTitle(powerType), { size: 50, fill: 0xFFFFFF, font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif" }); titleText.anchor.set(0.5, 0.5); titleText.y = -40; self.addChild(titleText); // Card description text var descText = new Text2(getPowerDescription(powerType), { size: 30, fill: 0xECF0F1, font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif" }); descText.anchor.set(0.5, 0.5); descText.y = 20; self.addChild(descText); self.powerType = powerType; // Card selection handler self.down = function (x, y, obj) { selectCard(self.powerType); }; return self; }); var CrayzZombie = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('crayzZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 25; // Even faster speed for maximum challenge self.baseSpeed = 25; // Store original speed for time dilation self.health = 1; self.damage = 12; self.active = true; self.lastPlayerDistance = 0; self.spiralAngle = 0; // Angle for spiral movement self.crazyTimer = 0; // Timer for random direction changes self.suddenChangeX = 0; // Sudden movement offset X self.suddenChangeY = 0; // Sudden movement offset Y self.isChangingDirection = false; // Flag for direction change animation // Start with a random crazy movement pattern immediately self.startCrazyMovement = function () { if (self.isChangingDirection) return; self.isChangingDirection = true; // Random sudden movement direction var randomAngle = Math.random() * Math.PI * 2; var randomDistance = 100 + Math.random() * 200; var targetX = Math.cos(randomAngle) * randomDistance; var targetY = Math.sin(randomAngle) * randomDistance; // Animate sudden direction change tween(self, { suddenChangeX: targetX, suddenChangeY: targetY }, { duration: 300 + Math.random() * 400, easing: tween.easeInOut, onFinish: function onFinish() { // Reset and prepare for next crazy movement self.suddenChangeX = 0; self.suddenChangeY = 0; self.isChangingDirection = false; } }); }; self.update = function () { if (!self.active) return; // Screen center coordinates var screenCenterX = 1024; // Half of 2048 var screenCenterY = 1366; // Half of 2732 // Calculate distance from screen center var dxToCenter = screenCenterX - self.x; var dyToCenter = screenCenterY - self.y; var distanceToCenter = Math.sqrt(dxToCenter * dxToCenter + dyToCenter * dyToCenter); // Calculate distance to player for damage detection var dxToPlayer = player.x - self.x; var dyToPlayer = player.y - self.y; var distanceToPlayer = Math.sqrt(dxToPlayer * dxToPlayer + dyToPlayer * dyToPlayer); // Spiral movement around screen center self.spiralAngle += 0.1; // Spiral rotation speed // Calculate current position relative to screen center var currentAngle = Math.atan2(self.y - screenCenterY, self.x - screenCenterX); var currentRadius = distanceToCenter; // Gradually decrease radius to spiral inward var targetRadius = Math.max(50, currentRadius - 1); // Minimum radius of 50 pixels // Calculate new position on the spiral var newAngle = currentAngle + 0.1; // Spiral by rotating around center var newX = screenCenterX + Math.cos(newAngle) * targetRadius; var newY = screenCenterY + Math.sin(newAngle) * targetRadius; // Apply time dilation effect when close to player var currentSpeed = self.baseSpeed; if (timeDilationStacks > 0 && distanceToPlayer <= 300) { // Within 300 pixels of player var slowdownPercent = timeDilationStacks * 10; // 10% per stack var slowdownMultiplier = 1 - slowdownPercent / 100; currentSpeed = self.baseSpeed * slowdownMultiplier; } // Move toward the new spiral position var dx = newX - self.x; var dy = newY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * currentSpeed; self.y += dy / distance * currentSpeed; // Rotate zombie to face movement direction graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } var currentDistance = distanceToPlayer; // CrayzZombie can deal damage when getting close to screen center if (distanceToCenter <= 250) { player.takeDamage(self.damage); self.active = false; } self.lastPlayerDistance = distanceToPlayer; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.active = false; // Create blood explosion with spreading effect for (var i = 0; i < 15; i++) { var bloodSplat = new BloodSplat(); // Start at monster position bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; var scale = 0.3 + Math.random() * 1.2; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; // Calculate random spread destination var angle = Math.random() * Math.PI * 2; var distance = 150 + Math.random() * 300; var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; // Keep within game bounds targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); // Animate spreading with random delay and duration var delay = Math.random() * 200; var duration = 300 + Math.random() * 400; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } LK.setScore(LK.getScore() + 15); // Score between normal and fast zombie LK.getSound('hit').play(); return true; } return false; }; return self; }); var DemonZombie = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('demonZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 4; // Same speed as fast zombie self.health = 1; self.damage = 10; self.active = true; self.lastPlayerDistance = 0; self.teleportsRemaining = 3; // Can teleport up to 3 times self.minTeleportDistance = 300; // Cannot teleport within 300 pixels of player self.update = function () { if (!self.active) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Rotate monster to face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } var currentDistance = distance; if (self.lastPlayerDistance > 50 && currentDistance <= 50) { player.takeDamage(self.damage); self.active = false; } self.lastPlayerDistance = currentDistance; }; self.takeDamage = function (damage) { // Teleport if bullets are about to hit and teleports remaining if (self.teleportsRemaining > 0) { self.teleport(); self.teleportsRemaining--; return false; // Did not take damage, teleported instead } // Out of teleports, take damage normally self.health -= damage; if (self.health <= 0) { self.active = false; // Create blood explosion with spreading effect for (var i = 0; i < 15; i++) { var bloodSplat = new BloodSplat(); // Start at monster position bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; var scale = 0.3 + Math.random() * 1.2; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; // Calculate random spread destination var angle = Math.random() * Math.PI * 2; var distance = 150 + Math.random() * 300; var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; // Keep within game bounds targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); // Animate spreading with random delay and duration var delay = Math.random() * 200; var duration = 300 + Math.random() * 400; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } LK.setScore(LK.getScore() + 15); // Same score as crayz zombie LK.getSound('hit').play(); return true; } return false; }; self.teleport = function () { // Visual effect before teleporting - fade out with purple effect tween(graphics, { alpha: 0, scaleX: 0.1, scaleY: 0.1, tint: 0x8B00FF }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { // Find a random valid teleport position var attempts = 0; var maxAttempts = 20; var newX, newY, distanceToPlayer; do { // Random position within game bounds newX = 100 + Math.random() * (2048 - 200); newY = 100 + Math.random() * (2732 - 200); // Calculate distance to player var dx = player.x - newX; var dy = player.y - newY; distanceToPlayer = Math.sqrt(dx * dx + dy * dy); attempts++; } while (distanceToPlayer < self.minTeleportDistance && attempts < maxAttempts); // Teleport to new position self.x = newX; self.y = newY; // Play teleport sound LK.getSound('teleport').play(); // Visual effect after teleporting - fade in with purple burst tween(graphics, { alpha: 1, scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200, easing: tween.easeOut }); // Red flash effect for visibility LK.effects.flashObject(self, 0xFF0000, 300); } }); }; return self; }); var FastZombie = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('fastZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 4; // Faster than normal zombie (2) self.baseSpeed = 4; // Store original speed for time dilation self.health = 1; self.damage = 10; self.active = true; self.lastPlayerDistance = 0; self.update = function () { if (!self.active) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Apply time dilation effect when close to player var currentSpeed = self.baseSpeed; if (timeDilationStacks > 0 && distance <= 300) { // Within 300 pixels of player var slowdownPercent = timeDilationStacks * 10; // 10% per stack var slowdownMultiplier = 1 - slowdownPercent / 100; currentSpeed = self.baseSpeed * slowdownMultiplier; } if (distance > 0) { self.x += dx / distance * currentSpeed; self.y += dy / distance * currentSpeed; // Rotate monster to face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } var currentDistance = distance; if (self.lastPlayerDistance > 50 && currentDistance <= 50) { player.takeDamage(self.damage); self.active = false; } self.lastPlayerDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.active = false; // Create blood explosion with spreading effect for (var i = 0; i < 15; i++) { var bloodSplat = new BloodSplat(); // Start at monster position bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; var scale = 0.3 + Math.random() * 1.2; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; // Calculate random spread destination var angle = Math.random() * Math.PI * 2; var distance = 150 + Math.random() * 300; var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; // Keep within game bounds targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); // Animate spreading with random delay and duration var delay = Math.random() * 200; var duration = 300 + Math.random() * 400; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } LK.setScore(LK.getScore() + 10); LK.getSound('hit').play(); return true; } return false; }; return self; }); var FrozenMonster = Container.expand(function (originalMonster) { var self = Container.call(this); self.originalMonster = originalMonster; self.originalSpeed = originalMonster.speed; self.freezeDuration = 180 + freezeShotStacks * 60; // 3 seconds base + 1 second per extra stack self.update = function () { if (self.freezeDuration > 0) { self.freezeDuration--; self.originalMonster.speed = self.originalSpeed * 0.5; // 50% slower // Visual effect - blue tint if (!self.originalMonster.children[0].tintApplied) { self.originalMonster.children[0].tint = 0x4444ff; self.originalMonster.children[0].tintApplied = true; } } else { // Restore original speed and remove tint self.originalMonster.speed = self.originalSpeed; if (self.originalMonster.children[0].tintApplied) { self.originalMonster.children[0].tint = 0xffffff; self.originalMonster.children[0].tintApplied = false; } self.destroy(); } }; return self; }); var MegaSplitterZombie = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('splitterZombie', { anchorX: 0.5, anchorY: 0.5 }); // Green tint to distinguish from regular splitter zombie graphics.tint = 0x44aa44; self.speed = 2; // Same speed as splitter zombie self.health = 4; // Double HP of splitter zombie (2 * 2) self.damage = 12; // Same damage as splitter zombie self.active = true; self.lastPlayerDistance = 0; self.update = function () { if (!self.active) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Rotate monster to face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } var currentDistance = distance; if (self.lastPlayerDistance > 50 && currentDistance <= 50) { player.takeDamage(self.damage); self.active = false; } self.lastPlayerDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.active = false; // Create blood explosion for (var i = 0; i < 15; i++) { var bloodSplat = new BloodSplat(); // Start at monster position bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; var scale = 0.3 + Math.random() * 1.2; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; // Calculate random spread destination var angle = Math.random() * Math.PI * 2; var distance = 150 + Math.random() * 300; var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; // Keep within game bounds targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); // Animate spreading with random delay and duration var delay = Math.random() * 200; var duration = 300 + Math.random() * 400; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } // Spawn 2 splitter zombies for (var s = 0; s < 2; s++) { var splitterZombie = new SplitterZombie(); // Position splitter zombies around the original position var spawnAngle = Math.PI * s + Math.random() * 0.5; var spawnDistance = 100 + Math.random() * 50; splitterZombie.x = self.x + Math.cos(spawnAngle) * spawnDistance; splitterZombie.y = self.y + Math.sin(spawnAngle) * spawnDistance; // Keep within bounds splitterZombie.x = Math.max(125, Math.min(1923, splitterZombie.x)); splitterZombie.y = Math.max(125, Math.min(2607, splitterZombie.y)); monsters.push(splitterZombie); game.addChild(splitterZombie); } LK.setScore(LK.getScore() + 35); // Higher score for mega splitter zombie LK.getSound('hit').play(); return true; } return false; }; return self; }); var MiniZombie = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('monster', { anchorX: 0.5, anchorY: 0.5 }); // Scale down to make it smaller graphics.scaleX = 0.5; graphics.scaleY = 0.5; // Tint it green to distinguish from normal zombies graphics.tint = 0x44aa44; self.speed = 5; // Fast movement self.health = 1; self.damage = 8; self.active = true; self.lastPlayerDistance = 0; self.update = function () { if (!self.active) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Rotate monster to face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } var currentDistance = distance; if (self.lastPlayerDistance > 50 && currentDistance <= 50) { player.takeDamage(self.damage); self.active = false; } self.lastPlayerDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.active = false; // Create smaller blood explosion for (var i = 0; i < 8; i++) { var bloodSplat = new BloodSplat(); // Start at monster position bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; var scale = 0.2 + Math.random() * 0.8; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; // Calculate random spread destination var angle = Math.random() * Math.PI * 2; var distance = 80 + Math.random() * 150; var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; // Keep within game bounds targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); // Animate spreading with random delay and duration var delay = Math.random() * 200; var duration = 200 + Math.random() * 300; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } LK.setScore(LK.getScore() + 5); // Lower score for mini zombie LK.getSound('hit').play(); return true; } return false; }; return self; }); var Monster = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('monster', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.baseSpeed = 2; // Store original speed for time dilation self.health = 1; self.damage = 10; self.active = true; self.lastPlayerDistance = 0; self.update = function () { if (!self.active) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Apply time dilation effect when close to player var currentSpeed = self.baseSpeed; if (timeDilationStacks > 0 && distance <= 300) { // Within 300 pixels of player var slowdownPercent = timeDilationStacks * 10; // 10% per stack var slowdownMultiplier = 1 - slowdownPercent / 100; currentSpeed = self.baseSpeed * slowdownMultiplier; } if (distance > 0) { self.x += dx / distance * currentSpeed; self.y += dy / distance * currentSpeed; // Rotate monster to face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } var currentDistance = distance; if (self.lastPlayerDistance > 50 && currentDistance <= 50) { player.takeDamage(self.damage); self.active = false; } self.lastPlayerDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.active = false; // Create blood explosion with spreading effect for (var i = 0; i < 15; i++) { var bloodSplat = new BloodSplat(); // Start at monster position bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; var scale = 0.3 + Math.random() * 1.2; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; // Calculate random spread destination var angle = Math.random() * Math.PI * 2; var distance = 150 + Math.random() * 300; var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; // Keep within game bounds targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); // Animate spreading with random delay and duration var delay = Math.random() * 200; var duration = 300 + Math.random() * 400; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } LK.setScore(LK.getScore() + 10); LK.getSound('hit').play(); return true; } return false; }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.maxHealth = 100; self.fireRate = 30; // Slower initial fire rate self.fireTimer = 0; self.update = function () { if (self.fireTimer > 0) { self.fireTimer--; } if (shieldCooldown > 0) { shieldCooldown--; } // Shield visual effect when available if (shieldGeneratorStacks > 0 && shieldCooldown <= 0) { // Create pulsing blue glow effect when shield is ready var pulseIntensity = Math.sin(LK.ticks * 0.1) * 0.3 + 0.7; // Pulse between 0.4 and 1.0 graphics.tint = 0x4488ff; // Blue tint graphics.alpha = pulseIntensity; } else { // Normal appearance when no shield or shield on cooldown graphics.tint = 0xffffff; // White (normal) graphics.alpha = 1.0; } }; self.takeDamage = function (damage) { // Check for shield protection if (shieldGeneratorStacks > 0 && shieldCooldown <= 0) { // Shield absorbs the attack shieldCooldown = Math.max(180, 900 - (shieldGeneratorStacks - 1) * 120); // 15 seconds base, -2 seconds per extra stack, min 3 seconds LK.effects.flashObject(self, 0x0088ff, 300); // Blue flash for shield return; // No damage taken } // Apply Thick Skin damage reduction var reducedDamage = Math.max(1, damage - thickSkinStacks); self.health -= reducedDamage; // Activate Boom Time explosion if we have stacks if (boomTimeStacks > 0) { var explosionDamage = boomTimeStacks; // 1 damage per stack var explosionRadius = 150 * (1 + (boomTimeStacks - 1)); // 150 base radius, doubled for each additional stack // Create explosion visual effect var explosionVisual = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, alpha: 0.8 }); var explosionScale = explosionRadius / 150 * (1 + (boomTimeStacks - 1)); // Scale based on radius and stacks - 2x bigger per additional stack explosionVisual.scaleX = explosionScale; explosionVisual.scaleY = explosionScale; game.addChild(explosionVisual); // Animate explosion tween(explosionVisual, { scaleX: explosionScale * 1.5, scaleY: explosionScale * 1.5, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { explosionVisual.destroy(); } }); // Play explosion sound LK.getSound('explosion').play(); // Damage nearby monsters for (var i = 0; i < monsters.length; i++) { var monster = monsters[i]; if (!monster.active) continue; var dx = monster.x - self.x; var dy = monster.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRadius) { monster.takeDamage(explosionDamage); } } } // Play damage sound LK.getSound('playerDamage').play(); // Shake player hard var originalX = self.x; var originalY = self.y; tween(self, { x: originalX + (Math.random() - 0.5) * 80, y: originalY + (Math.random() - 0.5) * 80 }, { duration: 60, onFinish: function onFinish() { tween(self, { x: originalX + (Math.random() - 0.5) * 50, y: originalY + (Math.random() - 0.5) * 50 }, { duration: 60, onFinish: function onFinish() { tween(self, { x: originalX + (Math.random() - 0.5) * 30, y: originalY + (Math.random() - 0.5) * 30 }, { duration: 60, onFinish: function onFinish() { tween(self, { x: originalX, y: originalY }, { duration: 40 }); } }); } }); } }); // Flash screen red for a moment (80% transparent) LK.effects.flashScreen(0x33ff0000, 500); if (self.health <= 0) { self.health = 0; // Set the score to the wave number for game over display LK.setScore(waveNumber); LK.showGameOver(); } LK.effects.flashObject(self, 0xff0000, 300); }; self.canFire = function () { return self.fireTimer <= 0; }; self.fire = function () { if (self.canFire()) { self.fireTimer = self.fireRate; return true; } return false; }; return self; }); var Rock = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('rock', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.dx = 0; self.dy = 0; self.active = true; self.damage = 5; self.update = function () { if (!self.active) return; self.x += self.dx; self.y += self.dy; // Remove rock if it goes off screen if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { self.active = false; } // Check collision with player var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= 85) { // Player radius + rock radius player.takeDamage(self.damage); self.active = false; } }; self.setDirection = function (targetX, targetY) { var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.dx = dx / distance * self.speed; self.dy = dy / distance * self.speed; // Rotate rock to face direction of travel graphics.rotation = Math.atan2(dy, dx); } }; return self; }); var RockThrowerZombie = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('rockThrowerZombie', { anchorX: 0.5, anchorY: 0.5 }); graphics.rotation = Math.PI; // Face downward like other zombies self.speed = 1; // Slower movement speed self.health = 1; self.damage = 0; // Doesn't deal contact damage self.active = true; self.lastPlayerDistance = 0; self.throwCooldown = 0; self.throwRange = 900; // Range to start throwing rocks self.isInRange = false; self.update = function () { if (!self.active) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Move toward player but stop at throwing range if (distance > self.throwRange) { if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Rotate zombie to face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } self.isInRange = false; } else { // In range - stop moving and start throwing self.isInRange = true; // Face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } // Throw rocks when in range if (self.isInRange && self.throwCooldown <= 0) { self.throwRock(); self.throwCooldown = 120; // 2 seconds between throws } if (self.throwCooldown > 0) { self.throwCooldown--; } self.lastPlayerDistance = distance; }; self.throwRock = function () { var rock = new Rock(); rock.x = self.x; rock.y = self.y; rock.setDirection(player.x, player.y); rocks.push(rock); game.addChild(rock); // Shake animation when throwing var originalX = self.x; var originalY = self.y; tween(self, { x: originalX + 15, y: originalY + 10 }, { duration: 80, onFinish: function onFinish() { tween(self, { x: originalX - 10, y: originalY - 8 }, { duration: 80, onFinish: function onFinish() { tween(self, { x: originalX, y: originalY }, { duration: 80 }); } }); } }); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.active = false; // Create blood explosion for (var i = 0; i < 12; i++) { var bloodSplat = new BloodSplat(); bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; var scale = 0.3 + Math.random() * 1.0; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; var angle = Math.random() * Math.PI * 2; var distance = 120 + Math.random() * 250; var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); var duration = 300 + Math.random() * 400; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } LK.setScore(LK.getScore() + 18); // Score between tough and splitter LK.getSound('hit').play(); return true; } return false; }; return self; }); var SplitterZombie = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('splitterZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; // Medium speed self.health = 2; // Same as tough zombie self.damage = 12; // Medium damage self.active = true; self.lastPlayerDistance = 0; self.update = function () { if (!self.active) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Rotate monster to face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } var currentDistance = distance; if (self.lastPlayerDistance > 50 && currentDistance <= 50) { player.takeDamage(self.damage); self.active = false; } self.lastPlayerDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.active = false; // Create blood explosion for (var i = 0; i < 12; i++) { var bloodSplat = new BloodSplat(); // Start at monster position bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; var scale = 0.3 + Math.random() * 1.0; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; // Calculate random spread destination var angle = Math.random() * Math.PI * 2; var distance = 120 + Math.random() * 250; var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; // Keep within game bounds targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); // Animate spreading with random delay and duration var delay = Math.random() * 200; var duration = 300 + Math.random() * 400; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } // Spawn 2-3 mini zombies var miniCount = 2 + Math.floor(Math.random() * 2); // 2 or 3 mini zombies for (var m = 0; m < miniCount; m++) { var miniZombie = new MiniZombie(); // Position mini zombies around the original position var spawnAngle = Math.PI * 2 / miniCount * m + Math.random() * 0.5; var spawnDistance = 80 + Math.random() * 40; miniZombie.x = self.x + Math.cos(spawnAngle) * spawnDistance; miniZombie.y = self.y + Math.sin(spawnAngle) * spawnDistance; // Keep within bounds miniZombie.x = Math.max(125, Math.min(1923, miniZombie.x)); miniZombie.y = Math.max(125, Math.min(2607, miniZombie.y)); monsters.push(miniZombie); game.addChild(miniZombie); } LK.setScore(LK.getScore() + 25); // Higher score for splitter zombie LK.getSound('hit').play(); return true; } return false; }; return self; }); var ToughZombie = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('toughZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 1.5; // Slower than normal zombie self.baseSpeed = 1.5; // Store original speed for time dilation self.health = 2; // Much tougher self.damage = 15; // Higher damage self.active = true; self.lastPlayerDistance = 0; self.update = function () { if (!self.active) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Apply time dilation effect when close to player var currentSpeed = self.baseSpeed; if (timeDilationStacks > 0 && distance <= 300) { // Within 300 pixels of player var slowdownPercent = timeDilationStacks * 10; // 10% per stack var slowdownMultiplier = 1 - slowdownPercent / 100; currentSpeed = self.baseSpeed * slowdownMultiplier; } if (distance > 0) { self.x += dx / distance * currentSpeed; self.y += dy / distance * currentSpeed; // Rotate monster to face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } var currentDistance = distance; if (self.lastPlayerDistance > 50 && currentDistance <= 50) { player.takeDamage(self.damage); self.active = false; } self.lastPlayerDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.active = false; // Create blood explosion with spreading effect for (var i = 0; i < 15; i++) { var bloodSplat = new BloodSplat(); // Start at monster position bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; var scale = 0.3 + Math.random() * 1.2; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; // Calculate random spread destination var angle = Math.random() * Math.PI * 2; var distance = 150 + Math.random() * 300; var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; // Keep within game bounds targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); // Animate spreading with random delay and duration var delay = Math.random() * 200; var duration = 300 + Math.random() * 400; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } LK.setScore(LK.getScore() + 20); // Higher score for tough zombie LK.getSound('hit').play(); return true; } return false; }; return self; }); var TransparentZombie = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('transparentZombie', { anchorX: 0.5, anchorY: 0.5 }); graphics.alpha = 0.05; // 95% transparent (5% opacity) self.speed = 4; // Same speed as fast zombie self.health = 1; self.damage = 10; self.active = true; self.lastPlayerDistance = 0; self.update = function () { if (!self.active) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Rotate monster to face the player graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } var currentDistance = distance; if (self.lastPlayerDistance > 50 && currentDistance <= 50) { player.takeDamage(self.damage); self.active = false; } self.lastPlayerDistance = currentDistance; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.active = false; // Create blood explosion with spreading effect for (var i = 0; i < 15; i++) { var bloodSplat = new BloodSplat(); // Start at monster position bloodSplat.x = self.x; bloodSplat.y = self.y; bloodSplat.graphics = bloodSplat.children[0]; bloodSplat.graphics.alpha = 0.5; // Make blood 50% transparent var scale = 0.3 + Math.random() * 1.2; bloodSplat.graphics.scaleX = scale; bloodSplat.graphics.scaleY = scale; // Calculate random spread destination var angle = Math.random() * Math.PI * 2; var distance = 150 + Math.random() * 300; var targetX = self.x + Math.cos(angle) * distance; var targetY = self.y + Math.sin(angle) * distance; // Keep within game bounds targetX = Math.max(50, Math.min(1998, targetX)); targetY = Math.max(50, Math.min(2682, targetY)); // Animate spreading with random delay and duration var delay = Math.random() * 200; var duration = 300 + Math.random() * 400; tween(bloodSplat, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); bloodSplats.push(bloodSplat); game.addChild(bloodSplat); } LK.setScore(LK.getScore() + 12); // Score between fast and crayz zombie LK.getSound('hit').play(); return true; } return false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2c3e50 }); /**** * Game Code ****/ // New asset for BestZombie var player = null; var bullets = []; var monsters = []; var bloodSplats = []; var rocks = []; var waveNumber = 1; var monstersInWave = 5; var monstersSpawned = 0; var monstersKilled = 0; var spawnTimer = 0; var waveDelay = 180; var bulletCooldown = 30; // Start with cooldown to prevent immediate shooting // Secret wave skip variables (hidden from normal players) var secretTapCount = 0; var secretTapTimer = 0; var secretZone = { x: 1900, y: 2600, width: 148, height: 132 }; // Bottom-right corner // Fast zombie spawn chance (starts at 10%, increases 1% per wave, max 80%) var fastZombieChance = 10; // Tough zombie spawn chance (starts at 5% after wave 5, increases 1% per wave, max 90%) var toughZombieChance = 5; // Crayz zombie spawn chance (10% after wave 15 to replace fast zombies) var crayzZombieChance = 10; // Splitter zombie spawn chance (15% after wave 18 to replace tough zombies) var splitterZombieChance = 15; // Mega splitter zombie spawn chance (20% after wave 30 to replace splitter zombies) var megaSplitterZombieChance = 20; // Rock thrower zombie spawn chance (10% after wave 25 to replace normal zombies) var rockThrowerZombieChance = 10; // Weather system variables var currentWeather = null; var weatherActive = false; var weatherChance = 5; // Starting at 5% after wave 10 var weatherText = null; var weatherTypes = ['windy', 'bloodSmell', 'bloodRain', 'unluckyClouds']; var isExtraSpecialWeather = false; var weatherEffects = { windy: false, bloodSmell: false, bloodRain: false, unluckyClouds: false, bloodMoon: false, "void": false }; // Weather utility functions function getWeatherName(weather) { var prefix = isExtraSpecialWeather ? 'EXTRA ' : ''; switch (weather) { case 'windy': return prefix + 'WINDY'; case 'bloodSmell': return prefix + 'BLOOD SMELL'; case 'bloodRain': return prefix + 'BLOOD RAIN'; case 'unluckyClouds': return prefix + 'UNLUCKY CLOUDS'; case 'bloodMoon': return prefix + 'BLOOD MOON'; case 'void': return 'VOID'; default: return ''; } } function getWeatherDescription(weather) { var multiplier = isExtraSpecialWeather ? 2 : 1; switch (weather) { case 'windy': return isExtraSpecialWeather ? 'All zombies are 100% faster' : 'All zombies are 50% faster'; case 'bloodSmell': return isExtraSpecialWeather ? 'Quadruple amount of zombies' : 'Double amount of zombies'; case 'bloodRain': return isExtraSpecialWeather ? 'Every zombie has 4x HP' : 'Every zombie has double HP'; case 'unluckyClouds': return isExtraSpecialWeather ? '40% chance your gun won\'t shoot' : '20% chance your gun won\'t shoot'; case 'bloodMoon': return isExtraSpecialWeather ? 'ALL EXTRA EFFECTS COMBINED!' : 'ALL EFFECTS COMBINED!'; case 'void': return 'ALL EFFECTS WITH 10X INTENSITY!'; default: return ''; } } function applyWeatherEffects(weather) { // Reset all weather effects first weatherEffects.windy = false; weatherEffects.bloodSmell = false; weatherEffects.bloodRain = false; weatherEffects.unluckyClouds = false; weatherEffects.bloodMoon = false; weatherEffects["void"] = false; if (weather === 'void') { // VOID activates all effects with extreme intensity weatherEffects.windy = true; weatherEffects.bloodSmell = true; weatherEffects.bloodRain = true; weatherEffects.unluckyClouds = true; weatherEffects["void"] = true; } else if (weather === 'bloodMoon') { // Blood moon activates all effects weatherEffects.windy = true; weatherEffects.bloodSmell = true; weatherEffects.bloodRain = true; weatherEffects.unluckyClouds = true; weatherEffects.bloodMoon = true; } else if (weather) { weatherEffects[weather] = true; } // Apply windy effect to existing monsters if (weatherEffects.windy) { for (var i = 0; i < monsters.length; i++) { var monster = monsters[i]; if (monster.active && monster.baseSpeed) { // Store original speed if not already stored if (!monster.originalSpeed) { monster.originalSpeed = monster.baseSpeed; } // Apply wind effect if (weatherEffects["void"]) { monster.speed = monster.originalSpeed * 11; // 10x effect (1000% faster) monster.baseSpeed = monster.speed; } else { var windMultiplier = isExtraSpecialWeather ? 2.0 : 1.5; // 100% faster for extra, 50% for normal monster.speed = monster.originalSpeed * windMultiplier; monster.baseSpeed = monster.speed; } } } } // Apply blood rain effect to existing monsters if (weatherEffects.bloodRain) { for (var i = 0; i < monsters.length; i++) { var monster = monsters[i]; if (monster.active) { // Store original health if not already stored if (!monster.originalHealth) { monster.originalHealth = monster.health; } // Apply blood rain effect if (weatherEffects["void"]) { monster.health = monster.originalHealth * 20; // 10x effect (2000% HP) } else { var healthMultiplier = isExtraSpecialWeather ? 4 : 2; // 4x HP for extra, 2x for normal monster.health = monster.originalHealth * healthMultiplier; } } } } } function showWeatherAnnouncement(weather) { var weatherName = getWeatherName(weather); var weatherDescription = getWeatherDescription(weather); // Determine color based on weather type and if it's extra special var textColor = 0xFFFFFF; // Default white if (weather === 'void') { textColor = 0x8A2BE2; // Purple for VOID weather } else if (weather === 'bloodMoon') { if (isExtraSpecialWeather) { textColor = 0x8B0000; // Dark red for extra special blood moon } else { textColor = 0xFF0000; // Red for normal blood moon } } else if (isExtraSpecialWeather) { textColor = 0xFFD700; // Golden for extra special weather } // Create weather text at center of screen weatherText = new Text2(weatherName + '\n' + weatherDescription, { size: 120, fill: textColor, font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif" }); weatherText.anchor.set(0.5, 0.5); weatherText.x = 1024; weatherText.y = 1366; game.addChild(weatherText); // After 3 seconds, shrink and move to bottom middle LK.setTimeout(function () { if (weatherText) { // Change text to only show weather name (remove description) weatherText.setText(weatherName); tween(weatherText, { scaleX: 0.3, scaleY: 0.3, x: 1024, y: 2650 }, { duration: 1000, easing: tween.easeInOut }); } }, 3000); } function checkForWeather() { // Force void weather at wave 100 and all subsequent waves with no other weather possible if (waveNumber >= 100) { currentWeather = 'void'; weatherActive = true; isExtraSpecialWeather = false; applyWeatherEffects('void'); showWeatherAnnouncement('void'); return; } // Only check for weather after wave 10 if (waveNumber < 10) return; // Calculate current weather chance (5% + 2.5% per wave, max 100%) var currentWeatherChance = Math.min(5 + (waveNumber - 10) * 2.5, 100); if (Math.random() * 100 < currentWeatherChance) { var selectedWeather; // Reset extra special weather flag isExtraSpecialWeather = false; // When weather chance is 100%, check for extra special weather if (currentWeatherChance >= 100) { // Calculate extra special weather chance (30% base + 5% per wave after reaching 100%) // Wave when weather chance first reaches 100% is approximately wave 46: 5 + (46-10) * 2.5 = 95, next wave is 97.5, next is 100 var waveWhenMaxReached = 46; // Approximate wave when weather chance reaches 100% var extraSpecialChance = 30; if (waveNumber > waveWhenMaxReached) { extraSpecialChance = 30 + (waveNumber - waveWhenMaxReached) * 5; } if (Math.random() * 100 < extraSpecialChance) { isExtraSpecialWeather = true; } } // Extremely rare chance for VOID weather (0.1% chance independent of other weather) if (Math.random() * 1000 < 1) { selectedWeather = 'void'; } else if (Math.random() * 100 < 1) { // Very small chance for blood moon (1% of weather events) selectedWeather = 'bloodMoon'; } else { // Equal chance for other weather types selectedWeather = weatherTypes[Math.floor(Math.random() * weatherTypes.length)]; } currentWeather = selectedWeather; weatherActive = true; applyWeatherEffects(selectedWeather); showWeatherAnnouncement(selectedWeather); } } function clearWeather() { // Restore original monster stats before clearing weather for (var i = 0; i < monsters.length; i++) { var monster = monsters[i]; if (monster.active) { // Restore original speed if it was stored if (monster.originalSpeed) { monster.speed = monster.originalSpeed; monster.baseSpeed = monster.originalSpeed; monster.originalSpeed = undefined; } // Restore original health if it was stored if (monster.originalHealth) { monster.health = monster.originalHealth; monster.originalHealth = undefined; } } } currentWeather = null; weatherActive = false; isExtraSpecialWeather = false; weatherEffects["void"] = false; applyWeatherEffects(null); // Remove weather text if it exists if (weatherText) { weatherText.destroy(); weatherText = null; } } // Card system variables var isCardSelectionActive = false; var cardContainer = null; var selectedPower = null; var availablePowers = ['bouncy', 'rapidfire', 'multishot', 'freshMeat', 'thickSkin', 'hotBullets', 'vampireBite', 'boomTime', 'shieldGenerator', 'piercingRounds', 'luckyShot', 'magneticBullets', 'chainLightning', 'freezeShot', 'ricochetMaster', 'timeDilation']; var activePowers = []; var bouncyBulletCount = 1; // Number of bounces for bouncy bullets var multishotBulletCount = 1; // Number of bullets to fire for multishot var freshMeatStacks = 0; // Number of Fresh Meat cards collected var thickSkinStacks = 0; // Number of Thick Skin cards collected for damage reduction var hotBulletStacks = 0; // Number of Hot Bullets cards collected for extra damage var vampireBiteStacks = 0; // Number of Vampire Bite cards collected var vampireBiteKillCount = 0; // Track kills for vampire bite healing var boomTimeStacks = 0; // Number of Boom Time cards collected var shieldGeneratorStacks = 0; // Number of Shield Generator cards collected var shieldCooldown = 0; // Cooldown timer for shield var piercingRoundsStacks = 0; // Number of Piercing Rounds cards collected var luckyShotStacks = 0; // Number of Lucky Shot cards collected var magneticBulletsStacks = 0; // Number of Magnetic Bullets cards collected var chainLightningStacks = 0; // Number of Chain Lightning cards collected var freezeShotStacks = 0; // Number of Freeze Shot cards collected var ricochetMasterStacks = 0; // Number of Ricochet Master cards collected var timeDilationStacks = 0; // Number of Time Dilation cards collected var scoreText = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF, font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif" }); scoreText.anchor.set(0, 0); scoreText.x = 120; scoreText.y = 80; LK.gui.topLeft.addChild(scoreText); var healthText = new Text2('Health: 100', { size: 50, fill: 0xFF4444, font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif" }); healthText.anchor.set(0, 0); healthText.x = 120; healthText.y = 20; LK.gui.topLeft.addChild(healthText); var waveText = new Text2('Wave: 1', { size: 50, fill: 0x4A90E2, font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif" }); waveText.anchor.set(0, 0); waveText.x = 120; waveText.y = 142; LK.gui.topLeft.addChild(waveText); // Power utility functions function getPowerTitle(powerType) { switch (powerType) { case 'bouncy': return 'Bouncy Bullets'; case 'rapidfire': return 'Rapid Fire'; case 'multishot': return 'Multi Shot'; case 'freshMeat': return 'Fresh Meat'; case 'thickSkin': return 'Thick Skin'; case 'hotBullets': return 'Hot Bullets'; case 'vampireBite': return 'Vampire Bite'; case 'boomTime': return 'Boom Time!'; case 'shieldGenerator': return 'Shield Generator'; case 'piercingRounds': return 'Piercing Rounds'; case 'luckyShot': return 'Lucky Shot'; case 'magneticBullets': return 'Magnetic Bullets'; case 'chainLightning': return 'Chain Lightning'; case 'freezeShot': return 'Freeze Shot'; case 'ricochetMaster': return 'Ricochet Master'; case 'timeDilation': return 'Time Dilation'; default: return 'Unknown Power'; } } function getPowerDescription(powerType) { switch (powerType) { case 'bouncy': return 'Bullets bounce to nearby enemies'; case 'rapidfire': return 'Faster shooting speed'; case 'multishot': return 'Shoot 3 bullets at once'; case 'freshMeat': return 'Regenerate 5 HP after each wave'; case 'thickSkin': return 'Reduce all damage taken by 1'; case 'hotBullets': return 'Bullets deal +1 damage'; case 'vampireBite': return 'Heal 1 HP for every 10 enemies killed'; case 'boomTime': return 'Explosion damages nearby zombies when hurt'; case 'shieldGenerator': return 'Absorb next attack every 15 seconds'; case 'piercingRounds': return 'Bullets pass through +1 enemy'; case 'luckyShot': return '10% chance for double damage bullets'; case 'magneticBullets': return 'Bullets home in on nearby enemies'; case 'chainLightning': return '20% chance to instantly kill nearby enemy'; case 'freezeShot': return '15% chance to slow hit enemies'; case 'ricochetMaster': return '25% chance bullets ricochet off walls'; case 'timeDilation': return 'Enemies move 10% slower when close to you'; default: return 'Unknown effect'; } } function showCardSelection() { isCardSelectionActive = true; // Create card container cardContainer = new Container(); game.addChild(cardContainer); // Create dark overlay var overlay = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0.8 }); overlay.tint = 0x000000; cardContainer.addChild(overlay); // Select 3 random powers var shuffledPowers = availablePowers.slice(); for (var i = shuffledPowers.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = shuffledPowers[i]; shuffledPowers[i] = shuffledPowers[j]; shuffledPowers[j] = temp; } var selectedPowers = shuffledPowers.slice(0, 3); // Create cards for (var i = 0; i < 3; i++) { var card = new Card(selectedPowers[i]); card.x = 340 + i * 684; // Increased spacing to 684 pixels (606 card width + 78 pixel gap) card.y = 1366; cardContainer.addChild(card); // Animate cards in tween(card, { y: 1000 }, { duration: 500, easing: tween.easeOut }); } // Add title text var titleText = new Text2('Choose Your Power!', { size: 80, fill: 0xFFFFFF, font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif" }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 600; cardContainer.addChild(titleText); } function selectCard(powerType) { if (!isCardSelectionActive) return; // Add power to active powers activePowers.push(powerType); // Apply power effect applyPowerEffect(powerType); // Animate cards out and clean up if (cardContainer) { tween(cardContainer, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { if (cardContainer) { cardContainer.destroy(); } cardContainer = null; isCardSelectionActive = false; } }); } } function applyPowerEffect(powerType) { switch (powerType) { case 'rapidfire': player.fireRate = Math.max(1, Math.floor(player.fireRate / 2)); break; case 'multishot': // Increase bullet count for multishot multishotBulletCount++; break; case 'bouncy': // Increase bounce count for all future bullets bouncyBulletCount++; break; case 'freshMeat': // Increase Fresh Meat stacks for HP regeneration freshMeatStacks++; break; case 'thickSkin': // Increase damage reduction stacks thickSkinStacks++; break; case 'hotBullets': // Increase bullet damage stacks hotBulletStacks++; break; case 'vampireBite': // Increase vampire bite stacks vampireBiteStacks++; break; case 'boomTime': // Increase boom time stacks boomTimeStacks++; break; case 'shieldGenerator': shieldGeneratorStacks++; break; case 'piercingRounds': piercingRoundsStacks++; break; case 'luckyShot': luckyShotStacks++; break; case 'magneticBullets': magneticBulletsStacks++; break; case 'chainLightning': chainLightningStacks++; break; case 'freezeShot': freezeShotStacks++; break; case 'ricochetMaster': ricochetMasterStacks++; break; case 'timeDilation': timeDilationStacks++; break; } } var background = game.addChild(LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 })); player = game.addChild(new Player()); player.x = 1024; player.y = 1366; function spawnMonster() { if (monstersSpawned >= monstersInWave) return; // Check if it's wave 66 and spawn only demon zombies if (waveNumber === 66) { monstersInWave = 22; // Set the number of monsters to spawn for (var i = 0; i < monstersInWave; i++) { var demonZombie = new DemonZombie(); var side = Math.floor(Math.random() * 4); switch (side) { case 0: demonZombie.x = Math.random() * 2048; demonZombie.y = -30; break; case 1: demonZombie.x = 2078; demonZombie.y = Math.random() * 2732; break; case 2: demonZombie.x = Math.random() * 2048; demonZombie.y = 2762; break; case 3: demonZombie.x = -30; demonZombie.y = Math.random() * 2732; break; } monsters.push(demonZombie); game.addChild(demonZombie); monstersSpawned++; } return; } // Spawn multiple monsters at once based on wave number - more balanced progression var monstersToSpawn = 1; if (waveNumber >= 3) monstersToSpawn = 2; if (waveNumber >= 6) monstersToSpawn = 3; if (waveNumber >= 10) monstersToSpawn = Math.min(4, Math.floor(waveNumber / 3)); var actualSpawn = Math.min(monstersToSpawn, monstersInWave - monstersSpawned); for (var spawnIndex = 0; spawnIndex < actualSpawn; spawnIndex++) { // Calculate current tough zombie chance (5% base + 1% per wave after wave 10, max 90%) var currentToughZombieChance = waveNumber >= 10 ? Math.min(5 + (waveNumber - 10), 90) : 0; // Determine zombie type with priority: splitter > tough > crayz > fast > rockthrower > normal var randomChance = Math.random() * 100; var monster; var bestZombieChance = 0.05; // 0.05% chance to spawn BestZombie if (Math.random() * 100 < bestZombieChance) { monster = new BestZombie(); } else if (waveNumber >= 10 && randomChance < currentToughZombieChance) { // 30% chance to spawn splitter zombie instead of tough zombie after wave 25 if (waveNumber >= 25 && Math.random() * 100 < 30) { // 20% chance to spawn mega splitter zombie instead of splitter zombie after wave 30 if (waveNumber >= 30 && Math.random() * 100 < megaSplitterZombieChance) { monster = new MegaSplitterZombie(); } else { monster = new SplitterZombie(); } } else { monster = new ToughZombie(); } } else if (waveNumber >= 15 && randomChance < currentToughZombieChance + crayzZombieChance) { // 10% chance to spawn crayz zombie instead of fast zombie after wave 15 monster = new CrayzZombie(); } else if (waveNumber >= 5 && randomChance < currentToughZombieChance + crayzZombieChance + fastZombieChance) { // 6.66% chance to spawn demon zombie instead of fast zombie after wave 36 if (waveNumber >= 36 && Math.random() * 100 < 6.66) { monster = new DemonZombie(); } else if (waveNumber >= 21 && Math.random() * 100 < 5) { // 5% chance to spawn transparent zombie instead of fast zombie after wave 21 monster = new TransparentZombie(); } else { monster = new FastZombie(); } } else if (waveNumber >= 25 && randomChance < currentToughZombieChance + crayzZombieChance + fastZombieChance + rockThrowerZombieChance) { // 10% chance to spawn rock thrower zombie after wave 25 monster = new RockThrowerZombie(); } else { monster = new Monster(); } var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top monster.x = Math.random() * 2048; monster.y = -30; break; case 1: // Right monster.x = 2078; monster.y = Math.random() * 2732; break; case 2: // Bottom monster.x = Math.random() * 2048; monster.y = 2762; break; case 3: // Left monster.x = -30; monster.y = Math.random() * 2732; break; } // Store original stats before applying weather effects monster.originalSpeed = monster.speed; monster.originalHealth = monster.health; // Apply weather effects to monster if (weatherEffects.windy) { if (weatherEffects["void"]) { monster.speed *= 11; // 10x effect (1000% faster) monster.baseSpeed = monster.speed; } else { monster.speed *= isExtraSpecialWeather ? 2.0 : 1.5; // 100% faster for extra, 50% for normal monster.baseSpeed = monster.speed; } } if (weatherEffects.bloodRain) { if (weatherEffects["void"]) { monster.health *= 20; // 10x effect (2000% HP) } else { monster.health *= isExtraSpecialWeather ? 4 : 2; // 4x HP for extra, 2x for normal } } monsters.push(monster); game.addChild(monster); monstersSpawned++; } } function cleanupBullets() { for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; if (!bullet.active) { bullet.destroy(); bullets.splice(i, 1); } } } function cleanupMonsters() { for (var i = monsters.length - 1; i >= 0; i--) { var monster = monsters[i]; if (!monster.active) { monster.destroy(); monsters.splice(i, 1); monstersKilled++; } } } function cleanupBloodSplats() { for (var i = bloodSplats.length - 1; i >= 0; i--) { var bloodSplat = bloodSplats[i]; if (!bloodSplat.parent) { bloodSplats.splice(i, 1); } } } function cleanupRocks() { for (var i = rocks.length - 1; i >= 0; i--) { var rock = rocks[i]; if (!rock.active) { rock.destroy(); rocks.splice(i, 1); } } } function checkBulletCollisions() { for (var i = 0; i < bullets.length; i++) { var bullet = bullets[i]; if (!bullet.active) continue; for (var j = 0; j < monsters.length; j++) { var monster = monsters[j]; if (!monster.active) continue; if (bullet.intersects(monster)) { // Skip if this enemy was already pierced by this bullet if (bullet.piercedEnemies.indexOf(monster) !== -1) { continue; } // Calculate damage with Hot Bullets and Lucky Shot bonus var bulletDamage = 1 + hotBulletStacks; if (luckyShotStacks > 0) { var luckyChance = Math.min(luckyShotStacks * 10, 50); // 10% per stack, max 50% if (Math.random() * 100 < luckyChance) { bulletDamage *= 2; // Double damage bullet.isLuckyShot = true; // Visual effect for lucky shot LK.effects.flashObject(monster, 0xffff00, 200); // Yellow flash } } var monsterKilled = monster.takeDamage(bulletDamage); // Apply freeze shot effect if (freezeShotStacks > 0 && bullet.frozenEnemies.indexOf(monster) === -1) { var freezeChance = Math.min(freezeShotStacks * 15, 60); // 15% per stack, max 60% if (Math.random() * 100 < freezeChance) { var frozenMonster = new FrozenMonster(monster); game.addChild(frozenMonster); bullet.frozenEnemies.push(monster); } } // Apply chain lightning effect if (chainLightningStacks > 0 && monsterKilled) { var chainChance = Math.min(chainLightningStacks * 20, 80); // 20% per stack, max 80% if (Math.random() * 100 < chainChance) { // Find nearest monster to chain to var nearestChainMonster = null; var nearestChainDistance = 200; // Chain range for (var k = 0; k < monsters.length; k++) { var chainMonster = monsters[k]; if (!chainMonster.active || chainMonster === monster) continue; var dx = chainMonster.x - monster.x; var dy = chainMonster.y - monster.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestChainDistance) { nearestChainDistance = distance; nearestChainMonster = chainMonster; } } if (nearestChainMonster) { // Create lightning bolt visual effect var lightningBolt = LK.getAsset('lightning', { anchorX: 0.5, anchorY: 0.5, x: (monster.x + nearestChainMonster.x) / 2, y: (monster.y + nearestChainMonster.y) / 2, alpha: 0.9 }); // Calculate rotation to point from monster to chained monster var lightningDx = nearestChainMonster.x - monster.x; var lightningDy = nearestChainMonster.y - monster.y; lightningBolt.rotation = Math.atan2(lightningDy, lightningDx) + Math.PI / 2; game.addChild(lightningBolt); // Animate lightning bolt tween(lightningBolt, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { lightningBolt.destroy(); } }); // Play lightning sound LK.getSound('lightning').play(); nearestChainMonster.takeDamage(999); // Instant kill // Visual lightning effect LK.effects.flashObject(nearestChainMonster, 0x00ffff, 300); // Cyan flash } } } // Check for Vampire Bite healing if (monsterKilled && vampireBiteStacks > 0) { vampireBiteKillCount++; var healsNeeded = Math.max(1, 10 - (vampireBiteStacks - 1)); // 10 kills for first stack, -1 for each additional (min 1) if (vampireBiteKillCount >= healsNeeded) { vampireBiteKillCount = 0; if (player.health < 100) { player.health = Math.min(100, player.health + 1); } } } // Add monster to pierced list for piercing rounds if (piercingRoundsStacks > 0) { bullet.piercedEnemies.push(monster); // Check if bullet has pierced maximum number of enemies if (bullet.piercedEnemies.length > piercingRoundsStacks) { bullet.active = false; break; } // Continue to next monster for piercing continue; } // Try to bounce bullet if bouncy power is active and bullet hasn't exceeded bounce limit if (bullet.bounceCount < bouncyBulletCount && activePowers.indexOf('bouncy') !== -1) { bullet.bounce(monster); // If bounce was successful (found a target), keep bullet active if (bullet.bounced) { break; // Stop checking other monsters for this bullet this frame } } // Only deactivate bullet if it reached bounce limit or bouncy power is not active if (bullet.bounceCount >= bouncyBulletCount || activePowers.indexOf('bouncy') === -1) { bullet.active = false; } break; } } } } function nextWave() { waveNumber++; // Apply Fresh Meat healing (5 HP per stack, max 100 HP) if (freshMeatStacks > 0 && player.health < 100) { var healAmount = freshMeatStacks * 5; player.health = Math.min(100, player.health + healAmount); } // Check for card selection every 5 waves if (waveNumber % 5 === 0 && waveNumber > 0) { showCardSelection(); return; // Don't start next wave until card is selected } // More balanced wave size progression: slower growth after wave 10 if (waveNumber <= 10) { monstersInWave = 3 + waveNumber * 2; } else { monstersInWave = 23 + Math.floor((waveNumber - 10) / 2); } // Apply blood smell weather effect (double or quadruple monsters) if (weatherEffects.bloodSmell) { if (weatherEffects["void"]) { monstersInWave *= 20; // 10x effect (2000% monsters) } else { monstersInWave *= isExtraSpecialWeather ? 4 : 2; } } // Blood harvest no longer uses temp health system monstersSpawned = 0; monstersKilled = 0; spawnTimer = 0; waveDelay = 180; // Increase fast zombie chance by 1% each wave, max 80% fastZombieChance = Math.min(10 + waveNumber, 80); waveText.setText('Wave: ' + waveNumber); // Clear previous weather and check for new weather clearWeather(); checkForWeather(); } // Mouse move handler to make player look at cursor game.move = function (x, y, obj) { // Calculate angle from player to mouse cursor var dx = x - player.x; var dy = y - player.y; // Rotate player to face mouse cursor (add PI/2 to align sprite correctly) player.children[0].rotation = Math.atan2(dy, dx) + Math.PI / 2; }; game.down = function (x, y, obj) { // Check unlucky clouds weather effect (20% or 40% chance gun doesn't shoot) var jamChance = isExtraSpecialWeather ? 40 : 20; // VOID weather does not apply 10x gun jamming to keep the game playable var gunJammed = weatherEffects.unluckyClouds && Math.random() * 100 < jamChance; var canShootNormal = bulletCooldown <= 0 && player.fire(); if (canShootNormal && !gunJammed) { // Check if multishot is active var shotCount = activePowers.indexOf('multishot') !== -1 ? multishotBulletCount : 1; for (var shotIndex = 0; shotIndex < shotCount; shotIndex++) { var bullet = new Bullet(); bullet.x = player.x; bullet.y = player.y; if (shotCount === 1) { // Single shot - aim directly at target bullet.setDirection(x, y); } else { // Multishot - spread bullets var baseAngle = Math.atan2(y - player.y, x - player.x); var spreadAngle = (shotIndex - (shotCount - 1) / 2) * 0.3; // Center the spread var finalAngle = baseAngle + spreadAngle; var targetX = player.x + Math.cos(finalAngle) * 500; var targetY = player.y + Math.sin(finalAngle) * 500; bullet.setDirection(targetX, targetY); } bullets.push(bullet); game.addChild(bullet); } LK.getSound('shoot').play(); bulletCooldown = player.fireRate; // Add shake effect to player when shooting var originalX = player.x; var originalY = player.y; tween(player, { x: originalX + (Math.random() - 0.5) * 50, y: originalY + (Math.random() - 0.5) * 50 }, { duration: 40, onFinish: function onFinish() { tween(player, { x: originalX + (Math.random() - 0.5) * 30, y: originalY + (Math.random() - 0.5) * 30 }, { duration: 40, onFinish: function onFinish() { tween(player, { x: originalX, y: originalY }, { duration: 30 }); } }); } }); } // Check unlucky clouds weather effect (20% or 40% chance gun doesn't shoot) var jamChance = isExtraSpecialWeather ? 40 : 20; // VOID weather does not apply 10x gun jamming to keep the game playable var gunJammed = weatherEffects.unluckyClouds && Math.random() * 100 < jamChance; var canShootNormal = bulletCooldown <= 0 && player.fire(); if (canShootNormal && !gunJammed) { // Check if multishot is active var shotCount = activePowers.indexOf('multishot') !== -1 ? multishotBulletCount : 1; for (var shotIndex = 0; shotIndex < shotCount; shotIndex++) { var bullet = new Bullet(); bullet.x = player.x; bullet.y = player.y; if (shotCount === 1) { // Single shot - aim directly at target bullet.setDirection(x, y); } else { // Multishot - spread bullets var baseAngle = Math.atan2(y - player.y, x - player.x); var spreadAngle = (shotIndex - (shotCount - 1) / 2) * 0.3; // Center the spread var finalAngle = baseAngle + spreadAngle; var targetX = player.x + Math.cos(finalAngle) * 500; var targetY = player.y + Math.sin(finalAngle) * 500; bullet.setDirection(targetX, targetY); } bullets.push(bullet); game.addChild(bullet); } LK.getSound('shoot').play(); bulletCooldown = player.fireRate; // Add shake effect to player when shooting var originalX = player.x; var originalY = player.y; tween(player, { x: originalX + (Math.random() - 0.5) * 50, y: originalY + (Math.random() - 0.5) * 50 }, { duration: 40, onFinish: function onFinish() { tween(player, { x: originalX + (Math.random() - 0.5) * 30, y: originalY + (Math.random() - 0.5) * 30 }, { duration: 40, onFinish: function onFinish() { tween(player, { x: originalX, y: originalY }, { duration: 30 }); } }); } }); } }; LK.playMusic('bgmusic'); game.update = function () { // Pause game during card selection if (isCardSelectionActive) { return; } // Secret wave skip timer if (secretTapTimer > 0) { secretTapTimer--; if (secretTapTimer <= 0) { secretTapCount = 0; // Reset if too slow } } if (bulletCooldown > 0) { bulletCooldown--; } if (waveDelay > 0) { waveDelay--; return; } spawnTimer++; if (spawnTimer >= 60 && monstersSpawned < monstersInWave) { spawnMonster(); spawnTimer = 0; } checkBulletCollisions(); cleanupBullets(); cleanupMonsters(); cleanupBloodSplats(); cleanupRocks(); if (monstersKilled >= monstersInWave && monsters.length === 0) { nextWave(); } scoreText.setText('Score: ' + LK.getScore()); healthText.setText('Health: ' + player.health + '/' + player.maxHealth); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BestZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('monster', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0xffd700; // Gold tint to distinguish BestZombie
self.speed = 50; // Double the fastest speed from CrayzZombie
self.baseSpeed = 50;
self.health = 8; // Double the high health from MegaSplitterZombie
self.damage = 30; // Double the high damage from ToughZombie
self.active = true;
self.lastPlayerDistance = 0;
self.spiralAngle = 0; // Angle for spiral movement from CrayzZombie
self.crazyTimer = 0; // Timer for random direction changes from CrayzZombie
self.suddenChangeX = 0; // Sudden movement offset X from CrayzZombie
self.suddenChangeY = 0; // Sudden movement offset Y from CrayzZombie
self.isChangingDirection = false; // Flag for direction change animation from CrayzZombie
self.throwCooldown = 0; // Cooldown for throwing rocks from RockThrowerZombie
self.throwRange = 1800; // Double the range to start throwing rocks from RockThrowerZombie
self.isInRange = false; // Flag for range check from RockThrowerZombie
self.update = function () {
if (!self.active) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Apply time dilation effect when close to player
var currentSpeed = self.baseSpeed;
if (timeDilationStacks > 0 && distance <= 300) {
var slowdownPercent = timeDilationStacks * 10;
var slowdownMultiplier = 1 - slowdownPercent / 100;
currentSpeed = self.baseSpeed * slowdownMultiplier;
}
// Spiral movement around screen center from CrayzZombie
self.spiralAngle += 0.2; // Double the spiral rotation speed
var screenCenterX = 1024; // Half of 2048
var screenCenterY = 1366; // Half of 2732
var dxToCenter = screenCenterX - self.x;
var dyToCenter = screenCenterY - self.y;
var distanceToCenter = Math.sqrt(dxToCenter * dxToCenter + dyToCenter * dyToCenter);
var currentAngle = Math.atan2(self.y - screenCenterY, self.x - screenCenterX);
var currentRadius = distanceToCenter;
var targetRadius = Math.max(50, currentRadius - 1); // Minimum radius of 50 pixels
var newAngle = currentAngle + 0.1; // Spiral by rotating around center
var newX = screenCenterX + Math.cos(newAngle) * targetRadius;
var newY = screenCenterY + Math.sin(newAngle) * targetRadius;
// Move toward the new spiral position
var dx = newX - self.x;
var dy = newY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * currentSpeed;
self.y += dy / distance * currentSpeed;
// Rotate zombie to face movement direction
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
// CrayzZombie can deal damage when getting close to screen center
if (distanceToCenter <= 250) {
player.takeDamage(self.damage);
self.active = false;
}
// Move toward player but stop at throwing range from RockThrowerZombie
if (distance > self.throwRange) {
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Rotate zombie to face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
self.isInRange = false;
} else {
// In range - stop moving and start throwing
self.isInRange = true;
// Face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
// Throw rocks when in range from RockThrowerZombie
if (self.isInRange && self.throwCooldown <= 0) {
self.throwRock();
self.throwCooldown = 120; // 2 seconds between throws
}
if (self.throwCooldown > 0) {
self.throwCooldown--;
}
self.lastPlayerDistance = distance;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create blood explosion with spreading effect from ToughZombie
for (var i = 0; i < 15; i++) {
var bloodSplat = new BloodSplat();
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
var scale = 0.3 + Math.random() * 1.2;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
var angle = Math.random() * Math.PI * 2;
var distance = 300 + Math.random() * 600; // Double the spread distance
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
var delay = Math.random() * 200;
var duration = 300 + Math.random() * 400;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
// Spawn 2 splitter zombies from MegaSplitterZombie
for (var s = 0; s < 2; s++) {
var splitterZombie = new SplitterZombie();
// Position splitter zombies around the original position
var spawnAngle = Math.PI * s + Math.random() * 0.5;
var spawnDistance = 100 + Math.random() * 50;
splitterZombie.x = self.x + Math.cos(spawnAngle) * spawnDistance;
splitterZombie.y = self.y + Math.sin(spawnAngle) * spawnDistance;
// Keep within bounds
splitterZombie.x = Math.max(125, Math.min(1923, splitterZombie.x));
splitterZombie.y = Math.max(125, Math.min(2607, splitterZombie.y));
monsters.push(splitterZombie);
game.addChild(splitterZombie);
}
LK.setScore(LK.getScore() + 500); // Increase score reward for BestZombie to 500 points
LK.getSound('hit').play();
return true;
}
return false;
};
self.throwRock = function () {
var rock = new Rock();
rock.x = self.x;
rock.y = self.y;
rock.setDirection(player.x, player.y);
rocks.push(rock);
game.addChild(rock);
// Shake animation when throwing
var originalX = self.x;
var originalY = self.y;
tween(self, {
x: originalX + 15,
y: originalY + 10
}, {
duration: 80,
onFinish: function onFinish() {
tween(self, {
x: originalX - 10,
y: originalY - 8
}, {
duration: 80,
onFinish: function onFinish() {
tween(self, {
x: originalX,
y: originalY
}, {
duration: 80
});
}
});
}
});
};
return self;
});
var BloodSplat = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('blood', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 300; // 5 seconds at 60fps
self.fadeStartTime = 0;
self.isFading = false;
self.update = function () {
self.lifetime--;
// Start fading after 10 seconds
if (self.lifetime <= 0 && !self.isFading) {
self.isFading = true;
tween(graphics, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
self.destroy();
}
});
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 20;
self.dx = 0;
self.dy = 0;
self.active = true;
self.bounced = false;
self.bounceCount = 0; // Track number of bounces performed
self.piercedEnemies = []; // Track pierced enemies for piercing rounds
self.targetX = 0; // Target position for magnetic bullets
self.targetY = 0;
self.frozenEnemies = []; // Track frozen enemies for freeze shot
self.ricochetCount = 0; // Track number of ricochets
self.isLuckyShot = false; // Track if this is a lucky shot
self.update = function () {
if (!self.active) return;
// Apply pink tint for lucky shot bullets
if (self.isLuckyShot) {
graphics.tint = 0xff69b4; // Pink color
}
// Apply magnetic bullets homing if active
if (magneticBulletsStacks > 0 && self.active) {
var nearestMonster = null;
var nearestDistance = 300 + magneticBulletsStacks * 50; // Increased range per stack
for (var i = 0; i < monsters.length; i++) {
var monster = monsters[i];
if (!monster.active) continue;
var dx = monster.x - self.x;
var dy = monster.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestMonster = monster;
}
}
if (nearestMonster) {
// Gradually adjust direction toward target
var homingStrength = 0.1 + magneticBulletsStacks * 0.05; // Stronger homing per stack
var targetDx = nearestMonster.x - self.x;
var targetDy = nearestMonster.y - self.y;
var targetDistance = Math.sqrt(targetDx * targetDx + targetDy * targetDy);
if (targetDistance > 0) {
var targetUnitX = targetDx / targetDistance;
var targetUnitY = targetDy / targetDistance;
self.dx = self.dx * (1 - homingStrength) + targetUnitX * self.speed * homingStrength;
self.dy = self.dy * (1 - homingStrength) + targetUnitY * self.speed * homingStrength;
}
}
}
self.x += self.dx;
self.y += self.dy;
// Check for ricochet off walls
if (ricochetMasterStacks > 0 && self.ricochetCount < ricochetMasterStacks) {
var ricochetChance = Math.min(ricochetMasterStacks * 25, 100); // 25% per stack, max 100%
var shouldRicochet = Math.random() * 100 < ricochetChance;
if (shouldRicochet && (self.x <= 0 || self.x >= 2048 || self.y <= 0 || self.y >= 2732)) {
self.ricochetCount++;
// Bounce off walls
if (self.x <= 0 || self.x >= 2048) {
self.dx = -self.dx;
self.x = Math.max(0, Math.min(2048, self.x));
}
if (self.y <= 0 || self.y >= 2732) {
self.dy = -self.dy;
self.y = Math.max(0, Math.min(2732, self.y));
}
// Find nearest enemy to ricochet toward
var nearestMonster = null;
var nearestDistance = Infinity;
for (var i = 0; i < monsters.length; i++) {
var monster = monsters[i];
if (!monster.active) continue;
var dx = monster.x - self.x;
var dy = monster.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestMonster = monster;
}
}
if (nearestMonster) {
self.setDirection(nearestMonster.x, nearestMonster.y);
}
} else if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.active = false;
}
} else if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.active = false;
}
};
self.setDirection = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.dx = dx / distance * self.speed;
self.dy = dy / distance * self.speed;
// Rotate bullet to face direction of travel
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
};
self.bounce = function (hitMonster) {
// Increment bounce count first
self.bounceCount++;
// Check if we've reached the bounce limit
if (self.bounceCount >= bouncyBulletCount) {
self.active = false;
return;
}
// Find nearest monster regardless of distance
var nearestMonster = null;
var nearestDistance = Infinity; // No distance limit
for (var i = 0; i < monsters.length; i++) {
var monster = monsters[i];
if (!monster.active || monster === hitMonster) continue;
var dx = monster.x - self.x;
var dy = monster.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestMonster = monster;
}
}
if (nearestMonster) {
self.bounced = true;
self.bounceTarget = nearestMonster; // Store the target
self.setDirection(nearestMonster.x, nearestMonster.y);
} else {
// No target found, deactivate bullet
self.active = false;
}
};
return self;
});
var Card = Container.expand(function (powerType) {
var self = Container.call(this);
// Card background
var cardBg = self.attachAsset('cardBackground', {
anchorX: 0.5,
anchorY: 0.5
});
// Card title text
var titleText = new Text2(getPowerTitle(powerType), {
size: 50,
fill: 0xFFFFFF,
font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif"
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -40;
self.addChild(titleText);
// Card description text
var descText = new Text2(getPowerDescription(powerType), {
size: 30,
fill: 0xECF0F1,
font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif"
});
descText.anchor.set(0.5, 0.5);
descText.y = 20;
self.addChild(descText);
self.powerType = powerType;
// Card selection handler
self.down = function (x, y, obj) {
selectCard(self.powerType);
};
return self;
});
var CrayzZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('crayzZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 25; // Even faster speed for maximum challenge
self.baseSpeed = 25; // Store original speed for time dilation
self.health = 1;
self.damage = 12;
self.active = true;
self.lastPlayerDistance = 0;
self.spiralAngle = 0; // Angle for spiral movement
self.crazyTimer = 0; // Timer for random direction changes
self.suddenChangeX = 0; // Sudden movement offset X
self.suddenChangeY = 0; // Sudden movement offset Y
self.isChangingDirection = false; // Flag for direction change animation
// Start with a random crazy movement pattern immediately
self.startCrazyMovement = function () {
if (self.isChangingDirection) return;
self.isChangingDirection = true;
// Random sudden movement direction
var randomAngle = Math.random() * Math.PI * 2;
var randomDistance = 100 + Math.random() * 200;
var targetX = Math.cos(randomAngle) * randomDistance;
var targetY = Math.sin(randomAngle) * randomDistance;
// Animate sudden direction change
tween(self, {
suddenChangeX: targetX,
suddenChangeY: targetY
}, {
duration: 300 + Math.random() * 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Reset and prepare for next crazy movement
self.suddenChangeX = 0;
self.suddenChangeY = 0;
self.isChangingDirection = false;
}
});
};
self.update = function () {
if (!self.active) return;
// Screen center coordinates
var screenCenterX = 1024; // Half of 2048
var screenCenterY = 1366; // Half of 2732
// Calculate distance from screen center
var dxToCenter = screenCenterX - self.x;
var dyToCenter = screenCenterY - self.y;
var distanceToCenter = Math.sqrt(dxToCenter * dxToCenter + dyToCenter * dyToCenter);
// Calculate distance to player for damage detection
var dxToPlayer = player.x - self.x;
var dyToPlayer = player.y - self.y;
var distanceToPlayer = Math.sqrt(dxToPlayer * dxToPlayer + dyToPlayer * dyToPlayer);
// Spiral movement around screen center
self.spiralAngle += 0.1; // Spiral rotation speed
// Calculate current position relative to screen center
var currentAngle = Math.atan2(self.y - screenCenterY, self.x - screenCenterX);
var currentRadius = distanceToCenter;
// Gradually decrease radius to spiral inward
var targetRadius = Math.max(50, currentRadius - 1); // Minimum radius of 50 pixels
// Calculate new position on the spiral
var newAngle = currentAngle + 0.1; // Spiral by rotating around center
var newX = screenCenterX + Math.cos(newAngle) * targetRadius;
var newY = screenCenterY + Math.sin(newAngle) * targetRadius;
// Apply time dilation effect when close to player
var currentSpeed = self.baseSpeed;
if (timeDilationStacks > 0 && distanceToPlayer <= 300) {
// Within 300 pixels of player
var slowdownPercent = timeDilationStacks * 10; // 10% per stack
var slowdownMultiplier = 1 - slowdownPercent / 100;
currentSpeed = self.baseSpeed * slowdownMultiplier;
}
// Move toward the new spiral position
var dx = newX - self.x;
var dy = newY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * currentSpeed;
self.y += dy / distance * currentSpeed;
// Rotate zombie to face movement direction
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
var currentDistance = distanceToPlayer;
// CrayzZombie can deal damage when getting close to screen center
if (distanceToCenter <= 250) {
player.takeDamage(self.damage);
self.active = false;
}
self.lastPlayerDistance = distanceToPlayer;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create blood explosion with spreading effect
for (var i = 0; i < 15; i++) {
var bloodSplat = new BloodSplat();
// Start at monster position
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
var scale = 0.3 + Math.random() * 1.2;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
// Calculate random spread destination
var angle = Math.random() * Math.PI * 2;
var distance = 150 + Math.random() * 300;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
// Keep within game bounds
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
// Animate spreading with random delay and duration
var delay = Math.random() * 200;
var duration = 300 + Math.random() * 400;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
LK.setScore(LK.getScore() + 15); // Score between normal and fast zombie
LK.getSound('hit').play();
return true;
}
return false;
};
return self;
});
var DemonZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('demonZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4; // Same speed as fast zombie
self.health = 1;
self.damage = 10;
self.active = true;
self.lastPlayerDistance = 0;
self.teleportsRemaining = 3; // Can teleport up to 3 times
self.minTeleportDistance = 300; // Cannot teleport within 300 pixels of player
self.update = function () {
if (!self.active) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Rotate monster to face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
var currentDistance = distance;
if (self.lastPlayerDistance > 50 && currentDistance <= 50) {
player.takeDamage(self.damage);
self.active = false;
}
self.lastPlayerDistance = currentDistance;
};
self.takeDamage = function (damage) {
// Teleport if bullets are about to hit and teleports remaining
if (self.teleportsRemaining > 0) {
self.teleport();
self.teleportsRemaining--;
return false; // Did not take damage, teleported instead
}
// Out of teleports, take damage normally
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create blood explosion with spreading effect
for (var i = 0; i < 15; i++) {
var bloodSplat = new BloodSplat();
// Start at monster position
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
var scale = 0.3 + Math.random() * 1.2;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
// Calculate random spread destination
var angle = Math.random() * Math.PI * 2;
var distance = 150 + Math.random() * 300;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
// Keep within game bounds
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
// Animate spreading with random delay and duration
var delay = Math.random() * 200;
var duration = 300 + Math.random() * 400;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
LK.setScore(LK.getScore() + 15); // Same score as crayz zombie
LK.getSound('hit').play();
return true;
}
return false;
};
self.teleport = function () {
// Visual effect before teleporting - fade out with purple effect
tween(graphics, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
tint: 0x8B00FF
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
// Find a random valid teleport position
var attempts = 0;
var maxAttempts = 20;
var newX, newY, distanceToPlayer;
do {
// Random position within game bounds
newX = 100 + Math.random() * (2048 - 200);
newY = 100 + Math.random() * (2732 - 200);
// Calculate distance to player
var dx = player.x - newX;
var dy = player.y - newY;
distanceToPlayer = Math.sqrt(dx * dx + dy * dy);
attempts++;
} while (distanceToPlayer < self.minTeleportDistance && attempts < maxAttempts);
// Teleport to new position
self.x = newX;
self.y = newY;
// Play teleport sound
LK.getSound('teleport').play();
// Visual effect after teleporting - fade in with purple burst
tween(graphics, {
alpha: 1,
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
// Red flash effect for visibility
LK.effects.flashObject(self, 0xFF0000, 300);
}
});
};
return self;
});
var FastZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('fastZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4; // Faster than normal zombie (2)
self.baseSpeed = 4; // Store original speed for time dilation
self.health = 1;
self.damage = 10;
self.active = true;
self.lastPlayerDistance = 0;
self.update = function () {
if (!self.active) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Apply time dilation effect when close to player
var currentSpeed = self.baseSpeed;
if (timeDilationStacks > 0 && distance <= 300) {
// Within 300 pixels of player
var slowdownPercent = timeDilationStacks * 10; // 10% per stack
var slowdownMultiplier = 1 - slowdownPercent / 100;
currentSpeed = self.baseSpeed * slowdownMultiplier;
}
if (distance > 0) {
self.x += dx / distance * currentSpeed;
self.y += dy / distance * currentSpeed;
// Rotate monster to face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
var currentDistance = distance;
if (self.lastPlayerDistance > 50 && currentDistance <= 50) {
player.takeDamage(self.damage);
self.active = false;
}
self.lastPlayerDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create blood explosion with spreading effect
for (var i = 0; i < 15; i++) {
var bloodSplat = new BloodSplat();
// Start at monster position
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
var scale = 0.3 + Math.random() * 1.2;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
// Calculate random spread destination
var angle = Math.random() * Math.PI * 2;
var distance = 150 + Math.random() * 300;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
// Keep within game bounds
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
// Animate spreading with random delay and duration
var delay = Math.random() * 200;
var duration = 300 + Math.random() * 400;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
LK.setScore(LK.getScore() + 10);
LK.getSound('hit').play();
return true;
}
return false;
};
return self;
});
var FrozenMonster = Container.expand(function (originalMonster) {
var self = Container.call(this);
self.originalMonster = originalMonster;
self.originalSpeed = originalMonster.speed;
self.freezeDuration = 180 + freezeShotStacks * 60; // 3 seconds base + 1 second per extra stack
self.update = function () {
if (self.freezeDuration > 0) {
self.freezeDuration--;
self.originalMonster.speed = self.originalSpeed * 0.5; // 50% slower
// Visual effect - blue tint
if (!self.originalMonster.children[0].tintApplied) {
self.originalMonster.children[0].tint = 0x4444ff;
self.originalMonster.children[0].tintApplied = true;
}
} else {
// Restore original speed and remove tint
self.originalMonster.speed = self.originalSpeed;
if (self.originalMonster.children[0].tintApplied) {
self.originalMonster.children[0].tint = 0xffffff;
self.originalMonster.children[0].tintApplied = false;
}
self.destroy();
}
};
return self;
});
var MegaSplitterZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('splitterZombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Green tint to distinguish from regular splitter zombie
graphics.tint = 0x44aa44;
self.speed = 2; // Same speed as splitter zombie
self.health = 4; // Double HP of splitter zombie (2 * 2)
self.damage = 12; // Same damage as splitter zombie
self.active = true;
self.lastPlayerDistance = 0;
self.update = function () {
if (!self.active) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Rotate monster to face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
var currentDistance = distance;
if (self.lastPlayerDistance > 50 && currentDistance <= 50) {
player.takeDamage(self.damage);
self.active = false;
}
self.lastPlayerDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create blood explosion
for (var i = 0; i < 15; i++) {
var bloodSplat = new BloodSplat();
// Start at monster position
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
var scale = 0.3 + Math.random() * 1.2;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
// Calculate random spread destination
var angle = Math.random() * Math.PI * 2;
var distance = 150 + Math.random() * 300;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
// Keep within game bounds
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
// Animate spreading with random delay and duration
var delay = Math.random() * 200;
var duration = 300 + Math.random() * 400;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
// Spawn 2 splitter zombies
for (var s = 0; s < 2; s++) {
var splitterZombie = new SplitterZombie();
// Position splitter zombies around the original position
var spawnAngle = Math.PI * s + Math.random() * 0.5;
var spawnDistance = 100 + Math.random() * 50;
splitterZombie.x = self.x + Math.cos(spawnAngle) * spawnDistance;
splitterZombie.y = self.y + Math.sin(spawnAngle) * spawnDistance;
// Keep within bounds
splitterZombie.x = Math.max(125, Math.min(1923, splitterZombie.x));
splitterZombie.y = Math.max(125, Math.min(2607, splitterZombie.y));
monsters.push(splitterZombie);
game.addChild(splitterZombie);
}
LK.setScore(LK.getScore() + 35); // Higher score for mega splitter zombie
LK.getSound('hit').play();
return true;
}
return false;
};
return self;
});
var MiniZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('monster', {
anchorX: 0.5,
anchorY: 0.5
});
// Scale down to make it smaller
graphics.scaleX = 0.5;
graphics.scaleY = 0.5;
// Tint it green to distinguish from normal zombies
graphics.tint = 0x44aa44;
self.speed = 5; // Fast movement
self.health = 1;
self.damage = 8;
self.active = true;
self.lastPlayerDistance = 0;
self.update = function () {
if (!self.active) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Rotate monster to face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
var currentDistance = distance;
if (self.lastPlayerDistance > 50 && currentDistance <= 50) {
player.takeDamage(self.damage);
self.active = false;
}
self.lastPlayerDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create smaller blood explosion
for (var i = 0; i < 8; i++) {
var bloodSplat = new BloodSplat();
// Start at monster position
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
var scale = 0.2 + Math.random() * 0.8;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
// Calculate random spread destination
var angle = Math.random() * Math.PI * 2;
var distance = 80 + Math.random() * 150;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
// Keep within game bounds
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
// Animate spreading with random delay and duration
var delay = Math.random() * 200;
var duration = 200 + Math.random() * 300;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
LK.setScore(LK.getScore() + 5); // Lower score for mini zombie
LK.getSound('hit').play();
return true;
}
return false;
};
return self;
});
var Monster = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('monster', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.baseSpeed = 2; // Store original speed for time dilation
self.health = 1;
self.damage = 10;
self.active = true;
self.lastPlayerDistance = 0;
self.update = function () {
if (!self.active) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Apply time dilation effect when close to player
var currentSpeed = self.baseSpeed;
if (timeDilationStacks > 0 && distance <= 300) {
// Within 300 pixels of player
var slowdownPercent = timeDilationStacks * 10; // 10% per stack
var slowdownMultiplier = 1 - slowdownPercent / 100;
currentSpeed = self.baseSpeed * slowdownMultiplier;
}
if (distance > 0) {
self.x += dx / distance * currentSpeed;
self.y += dy / distance * currentSpeed;
// Rotate monster to face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
var currentDistance = distance;
if (self.lastPlayerDistance > 50 && currentDistance <= 50) {
player.takeDamage(self.damage);
self.active = false;
}
self.lastPlayerDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create blood explosion with spreading effect
for (var i = 0; i < 15; i++) {
var bloodSplat = new BloodSplat();
// Start at monster position
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
var scale = 0.3 + Math.random() * 1.2;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
// Calculate random spread destination
var angle = Math.random() * Math.PI * 2;
var distance = 150 + Math.random() * 300;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
// Keep within game bounds
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
// Animate spreading with random delay and duration
var delay = Math.random() * 200;
var duration = 300 + Math.random() * 400;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
LK.setScore(LK.getScore() + 10);
LK.getSound('hit').play();
return true;
}
return false;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.fireRate = 30; // Slower initial fire rate
self.fireTimer = 0;
self.update = function () {
if (self.fireTimer > 0) {
self.fireTimer--;
}
if (shieldCooldown > 0) {
shieldCooldown--;
}
// Shield visual effect when available
if (shieldGeneratorStacks > 0 && shieldCooldown <= 0) {
// Create pulsing blue glow effect when shield is ready
var pulseIntensity = Math.sin(LK.ticks * 0.1) * 0.3 + 0.7; // Pulse between 0.4 and 1.0
graphics.tint = 0x4488ff; // Blue tint
graphics.alpha = pulseIntensity;
} else {
// Normal appearance when no shield or shield on cooldown
graphics.tint = 0xffffff; // White (normal)
graphics.alpha = 1.0;
}
};
self.takeDamage = function (damage) {
// Check for shield protection
if (shieldGeneratorStacks > 0 && shieldCooldown <= 0) {
// Shield absorbs the attack
shieldCooldown = Math.max(180, 900 - (shieldGeneratorStacks - 1) * 120); // 15 seconds base, -2 seconds per extra stack, min 3 seconds
LK.effects.flashObject(self, 0x0088ff, 300); // Blue flash for shield
return; // No damage taken
}
// Apply Thick Skin damage reduction
var reducedDamage = Math.max(1, damage - thickSkinStacks);
self.health -= reducedDamage;
// Activate Boom Time explosion if we have stacks
if (boomTimeStacks > 0) {
var explosionDamage = boomTimeStacks; // 1 damage per stack
var explosionRadius = 150 * (1 + (boomTimeStacks - 1)); // 150 base radius, doubled for each additional stack
// Create explosion visual effect
var explosionVisual = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
alpha: 0.8
});
var explosionScale = explosionRadius / 150 * (1 + (boomTimeStacks - 1)); // Scale based on radius and stacks - 2x bigger per additional stack
explosionVisual.scaleX = explosionScale;
explosionVisual.scaleY = explosionScale;
game.addChild(explosionVisual);
// Animate explosion
tween(explosionVisual, {
scaleX: explosionScale * 1.5,
scaleY: explosionScale * 1.5,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionVisual.destroy();
}
});
// Play explosion sound
LK.getSound('explosion').play();
// Damage nearby monsters
for (var i = 0; i < monsters.length; i++) {
var monster = monsters[i];
if (!monster.active) continue;
var dx = monster.x - self.x;
var dy = monster.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRadius) {
monster.takeDamage(explosionDamage);
}
}
}
// Play damage sound
LK.getSound('playerDamage').play();
// Shake player hard
var originalX = self.x;
var originalY = self.y;
tween(self, {
x: originalX + (Math.random() - 0.5) * 80,
y: originalY + (Math.random() - 0.5) * 80
}, {
duration: 60,
onFinish: function onFinish() {
tween(self, {
x: originalX + (Math.random() - 0.5) * 50,
y: originalY + (Math.random() - 0.5) * 50
}, {
duration: 60,
onFinish: function onFinish() {
tween(self, {
x: originalX + (Math.random() - 0.5) * 30,
y: originalY + (Math.random() - 0.5) * 30
}, {
duration: 60,
onFinish: function onFinish() {
tween(self, {
x: originalX,
y: originalY
}, {
duration: 40
});
}
});
}
});
}
});
// Flash screen red for a moment (80% transparent)
LK.effects.flashScreen(0x33ff0000, 500);
if (self.health <= 0) {
self.health = 0;
// Set the score to the wave number for game over display
LK.setScore(waveNumber);
LK.showGameOver();
}
LK.effects.flashObject(self, 0xff0000, 300);
};
self.canFire = function () {
return self.fireTimer <= 0;
};
self.fire = function () {
if (self.canFire()) {
self.fireTimer = self.fireRate;
return true;
}
return false;
};
return self;
});
var Rock = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('rock', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.dx = 0;
self.dy = 0;
self.active = true;
self.damage = 5;
self.update = function () {
if (!self.active) return;
self.x += self.dx;
self.y += self.dy;
// Remove rock if it goes off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.active = false;
}
// Check collision with player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 85) {
// Player radius + rock radius
player.takeDamage(self.damage);
self.active = false;
}
};
self.setDirection = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.dx = dx / distance * self.speed;
self.dy = dy / distance * self.speed;
// Rotate rock to face direction of travel
graphics.rotation = Math.atan2(dy, dx);
}
};
return self;
});
var RockThrowerZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('rockThrowerZombie', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.rotation = Math.PI; // Face downward like other zombies
self.speed = 1; // Slower movement speed
self.health = 1;
self.damage = 0; // Doesn't deal contact damage
self.active = true;
self.lastPlayerDistance = 0;
self.throwCooldown = 0;
self.throwRange = 900; // Range to start throwing rocks
self.isInRange = false;
self.update = function () {
if (!self.active) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move toward player but stop at throwing range
if (distance > self.throwRange) {
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Rotate zombie to face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
self.isInRange = false;
} else {
// In range - stop moving and start throwing
self.isInRange = true;
// Face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
// Throw rocks when in range
if (self.isInRange && self.throwCooldown <= 0) {
self.throwRock();
self.throwCooldown = 120; // 2 seconds between throws
}
if (self.throwCooldown > 0) {
self.throwCooldown--;
}
self.lastPlayerDistance = distance;
};
self.throwRock = function () {
var rock = new Rock();
rock.x = self.x;
rock.y = self.y;
rock.setDirection(player.x, player.y);
rocks.push(rock);
game.addChild(rock);
// Shake animation when throwing
var originalX = self.x;
var originalY = self.y;
tween(self, {
x: originalX + 15,
y: originalY + 10
}, {
duration: 80,
onFinish: function onFinish() {
tween(self, {
x: originalX - 10,
y: originalY - 8
}, {
duration: 80,
onFinish: function onFinish() {
tween(self, {
x: originalX,
y: originalY
}, {
duration: 80
});
}
});
}
});
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create blood explosion
for (var i = 0; i < 12; i++) {
var bloodSplat = new BloodSplat();
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
var scale = 0.3 + Math.random() * 1.0;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
var angle = Math.random() * Math.PI * 2;
var distance = 120 + Math.random() * 250;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
var duration = 300 + Math.random() * 400;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
LK.setScore(LK.getScore() + 18); // Score between tough and splitter
LK.getSound('hit').play();
return true;
}
return false;
};
return self;
});
var SplitterZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('splitterZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2; // Medium speed
self.health = 2; // Same as tough zombie
self.damage = 12; // Medium damage
self.active = true;
self.lastPlayerDistance = 0;
self.update = function () {
if (!self.active) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Rotate monster to face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
var currentDistance = distance;
if (self.lastPlayerDistance > 50 && currentDistance <= 50) {
player.takeDamage(self.damage);
self.active = false;
}
self.lastPlayerDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create blood explosion
for (var i = 0; i < 12; i++) {
var bloodSplat = new BloodSplat();
// Start at monster position
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
var scale = 0.3 + Math.random() * 1.0;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
// Calculate random spread destination
var angle = Math.random() * Math.PI * 2;
var distance = 120 + Math.random() * 250;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
// Keep within game bounds
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
// Animate spreading with random delay and duration
var delay = Math.random() * 200;
var duration = 300 + Math.random() * 400;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
// Spawn 2-3 mini zombies
var miniCount = 2 + Math.floor(Math.random() * 2); // 2 or 3 mini zombies
for (var m = 0; m < miniCount; m++) {
var miniZombie = new MiniZombie();
// Position mini zombies around the original position
var spawnAngle = Math.PI * 2 / miniCount * m + Math.random() * 0.5;
var spawnDistance = 80 + Math.random() * 40;
miniZombie.x = self.x + Math.cos(spawnAngle) * spawnDistance;
miniZombie.y = self.y + Math.sin(spawnAngle) * spawnDistance;
// Keep within bounds
miniZombie.x = Math.max(125, Math.min(1923, miniZombie.x));
miniZombie.y = Math.max(125, Math.min(2607, miniZombie.y));
monsters.push(miniZombie);
game.addChild(miniZombie);
}
LK.setScore(LK.getScore() + 25); // Higher score for splitter zombie
LK.getSound('hit').play();
return true;
}
return false;
};
return self;
});
var ToughZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('toughZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.5; // Slower than normal zombie
self.baseSpeed = 1.5; // Store original speed for time dilation
self.health = 2; // Much tougher
self.damage = 15; // Higher damage
self.active = true;
self.lastPlayerDistance = 0;
self.update = function () {
if (!self.active) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Apply time dilation effect when close to player
var currentSpeed = self.baseSpeed;
if (timeDilationStacks > 0 && distance <= 300) {
// Within 300 pixels of player
var slowdownPercent = timeDilationStacks * 10; // 10% per stack
var slowdownMultiplier = 1 - slowdownPercent / 100;
currentSpeed = self.baseSpeed * slowdownMultiplier;
}
if (distance > 0) {
self.x += dx / distance * currentSpeed;
self.y += dy / distance * currentSpeed;
// Rotate monster to face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
var currentDistance = distance;
if (self.lastPlayerDistance > 50 && currentDistance <= 50) {
player.takeDamage(self.damage);
self.active = false;
}
self.lastPlayerDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create blood explosion with spreading effect
for (var i = 0; i < 15; i++) {
var bloodSplat = new BloodSplat();
// Start at monster position
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
var scale = 0.3 + Math.random() * 1.2;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
// Calculate random spread destination
var angle = Math.random() * Math.PI * 2;
var distance = 150 + Math.random() * 300;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
// Keep within game bounds
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
// Animate spreading with random delay and duration
var delay = Math.random() * 200;
var duration = 300 + Math.random() * 400;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
LK.setScore(LK.getScore() + 20); // Higher score for tough zombie
LK.getSound('hit').play();
return true;
}
return false;
};
return self;
});
var TransparentZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('transparentZombie', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.alpha = 0.05; // 95% transparent (5% opacity)
self.speed = 4; // Same speed as fast zombie
self.health = 1;
self.damage = 10;
self.active = true;
self.lastPlayerDistance = 0;
self.update = function () {
if (!self.active) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Rotate monster to face the player
graphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
var currentDistance = distance;
if (self.lastPlayerDistance > 50 && currentDistance <= 50) {
player.takeDamage(self.damage);
self.active = false;
}
self.lastPlayerDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.active = false;
// Create blood explosion with spreading effect
for (var i = 0; i < 15; i++) {
var bloodSplat = new BloodSplat();
// Start at monster position
bloodSplat.x = self.x;
bloodSplat.y = self.y;
bloodSplat.graphics = bloodSplat.children[0];
bloodSplat.graphics.alpha = 0.5; // Make blood 50% transparent
var scale = 0.3 + Math.random() * 1.2;
bloodSplat.graphics.scaleX = scale;
bloodSplat.graphics.scaleY = scale;
// Calculate random spread destination
var angle = Math.random() * Math.PI * 2;
var distance = 150 + Math.random() * 300;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
// Keep within game bounds
targetX = Math.max(50, Math.min(1998, targetX));
targetY = Math.max(50, Math.min(2682, targetY));
// Animate spreading with random delay and duration
var delay = Math.random() * 200;
var duration = 300 + Math.random() * 400;
tween(bloodSplat, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
bloodSplats.push(bloodSplat);
game.addChild(bloodSplat);
}
LK.setScore(LK.getScore() + 12); // Score between fast and crayz zombie
LK.getSound('hit').play();
return true;
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// New asset for BestZombie
var player = null;
var bullets = [];
var monsters = [];
var bloodSplats = [];
var rocks = [];
var waveNumber = 1;
var monstersInWave = 5;
var monstersSpawned = 0;
var monstersKilled = 0;
var spawnTimer = 0;
var waveDelay = 180;
var bulletCooldown = 30; // Start with cooldown to prevent immediate shooting
// Secret wave skip variables (hidden from normal players)
var secretTapCount = 0;
var secretTapTimer = 0;
var secretZone = {
x: 1900,
y: 2600,
width: 148,
height: 132
}; // Bottom-right corner
// Fast zombie spawn chance (starts at 10%, increases 1% per wave, max 80%)
var fastZombieChance = 10;
// Tough zombie spawn chance (starts at 5% after wave 5, increases 1% per wave, max 90%)
var toughZombieChance = 5;
// Crayz zombie spawn chance (10% after wave 15 to replace fast zombies)
var crayzZombieChance = 10;
// Splitter zombie spawn chance (15% after wave 18 to replace tough zombies)
var splitterZombieChance = 15;
// Mega splitter zombie spawn chance (20% after wave 30 to replace splitter zombies)
var megaSplitterZombieChance = 20;
// Rock thrower zombie spawn chance (10% after wave 25 to replace normal zombies)
var rockThrowerZombieChance = 10;
// Weather system variables
var currentWeather = null;
var weatherActive = false;
var weatherChance = 5; // Starting at 5% after wave 10
var weatherText = null;
var weatherTypes = ['windy', 'bloodSmell', 'bloodRain', 'unluckyClouds'];
var isExtraSpecialWeather = false;
var weatherEffects = {
windy: false,
bloodSmell: false,
bloodRain: false,
unluckyClouds: false,
bloodMoon: false,
"void": false
};
// Weather utility functions
function getWeatherName(weather) {
var prefix = isExtraSpecialWeather ? 'EXTRA ' : '';
switch (weather) {
case 'windy':
return prefix + 'WINDY';
case 'bloodSmell':
return prefix + 'BLOOD SMELL';
case 'bloodRain':
return prefix + 'BLOOD RAIN';
case 'unluckyClouds':
return prefix + 'UNLUCKY CLOUDS';
case 'bloodMoon':
return prefix + 'BLOOD MOON';
case 'void':
return 'VOID';
default:
return '';
}
}
function getWeatherDescription(weather) {
var multiplier = isExtraSpecialWeather ? 2 : 1;
switch (weather) {
case 'windy':
return isExtraSpecialWeather ? 'All zombies are 100% faster' : 'All zombies are 50% faster';
case 'bloodSmell':
return isExtraSpecialWeather ? 'Quadruple amount of zombies' : 'Double amount of zombies';
case 'bloodRain':
return isExtraSpecialWeather ? 'Every zombie has 4x HP' : 'Every zombie has double HP';
case 'unluckyClouds':
return isExtraSpecialWeather ? '40% chance your gun won\'t shoot' : '20% chance your gun won\'t shoot';
case 'bloodMoon':
return isExtraSpecialWeather ? 'ALL EXTRA EFFECTS COMBINED!' : 'ALL EFFECTS COMBINED!';
case 'void':
return 'ALL EFFECTS WITH 10X INTENSITY!';
default:
return '';
}
}
function applyWeatherEffects(weather) {
// Reset all weather effects first
weatherEffects.windy = false;
weatherEffects.bloodSmell = false;
weatherEffects.bloodRain = false;
weatherEffects.unluckyClouds = false;
weatherEffects.bloodMoon = false;
weatherEffects["void"] = false;
if (weather === 'void') {
// VOID activates all effects with extreme intensity
weatherEffects.windy = true;
weatherEffects.bloodSmell = true;
weatherEffects.bloodRain = true;
weatherEffects.unluckyClouds = true;
weatherEffects["void"] = true;
} else if (weather === 'bloodMoon') {
// Blood moon activates all effects
weatherEffects.windy = true;
weatherEffects.bloodSmell = true;
weatherEffects.bloodRain = true;
weatherEffects.unluckyClouds = true;
weatherEffects.bloodMoon = true;
} else if (weather) {
weatherEffects[weather] = true;
}
// Apply windy effect to existing monsters
if (weatherEffects.windy) {
for (var i = 0; i < monsters.length; i++) {
var monster = monsters[i];
if (monster.active && monster.baseSpeed) {
// Store original speed if not already stored
if (!monster.originalSpeed) {
monster.originalSpeed = monster.baseSpeed;
}
// Apply wind effect
if (weatherEffects["void"]) {
monster.speed = monster.originalSpeed * 11; // 10x effect (1000% faster)
monster.baseSpeed = monster.speed;
} else {
var windMultiplier = isExtraSpecialWeather ? 2.0 : 1.5; // 100% faster for extra, 50% for normal
monster.speed = monster.originalSpeed * windMultiplier;
monster.baseSpeed = monster.speed;
}
}
}
}
// Apply blood rain effect to existing monsters
if (weatherEffects.bloodRain) {
for (var i = 0; i < monsters.length; i++) {
var monster = monsters[i];
if (monster.active) {
// Store original health if not already stored
if (!monster.originalHealth) {
monster.originalHealth = monster.health;
}
// Apply blood rain effect
if (weatherEffects["void"]) {
monster.health = monster.originalHealth * 20; // 10x effect (2000% HP)
} else {
var healthMultiplier = isExtraSpecialWeather ? 4 : 2; // 4x HP for extra, 2x for normal
monster.health = monster.originalHealth * healthMultiplier;
}
}
}
}
}
function showWeatherAnnouncement(weather) {
var weatherName = getWeatherName(weather);
var weatherDescription = getWeatherDescription(weather);
// Determine color based on weather type and if it's extra special
var textColor = 0xFFFFFF; // Default white
if (weather === 'void') {
textColor = 0x8A2BE2; // Purple for VOID weather
} else if (weather === 'bloodMoon') {
if (isExtraSpecialWeather) {
textColor = 0x8B0000; // Dark red for extra special blood moon
} else {
textColor = 0xFF0000; // Red for normal blood moon
}
} else if (isExtraSpecialWeather) {
textColor = 0xFFD700; // Golden for extra special weather
}
// Create weather text at center of screen
weatherText = new Text2(weatherName + '\n' + weatherDescription, {
size: 120,
fill: textColor,
font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif"
});
weatherText.anchor.set(0.5, 0.5);
weatherText.x = 1024;
weatherText.y = 1366;
game.addChild(weatherText);
// After 3 seconds, shrink and move to bottom middle
LK.setTimeout(function () {
if (weatherText) {
// Change text to only show weather name (remove description)
weatherText.setText(weatherName);
tween(weatherText, {
scaleX: 0.3,
scaleY: 0.3,
x: 1024,
y: 2650
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}, 3000);
}
function checkForWeather() {
// Force void weather at wave 100 and all subsequent waves with no other weather possible
if (waveNumber >= 100) {
currentWeather = 'void';
weatherActive = true;
isExtraSpecialWeather = false;
applyWeatherEffects('void');
showWeatherAnnouncement('void');
return;
}
// Only check for weather after wave 10
if (waveNumber < 10) return;
// Calculate current weather chance (5% + 2.5% per wave, max 100%)
var currentWeatherChance = Math.min(5 + (waveNumber - 10) * 2.5, 100);
if (Math.random() * 100 < currentWeatherChance) {
var selectedWeather;
// Reset extra special weather flag
isExtraSpecialWeather = false;
// When weather chance is 100%, check for extra special weather
if (currentWeatherChance >= 100) {
// Calculate extra special weather chance (30% base + 5% per wave after reaching 100%)
// Wave when weather chance first reaches 100% is approximately wave 46: 5 + (46-10) * 2.5 = 95, next wave is 97.5, next is 100
var waveWhenMaxReached = 46; // Approximate wave when weather chance reaches 100%
var extraSpecialChance = 30;
if (waveNumber > waveWhenMaxReached) {
extraSpecialChance = 30 + (waveNumber - waveWhenMaxReached) * 5;
}
if (Math.random() * 100 < extraSpecialChance) {
isExtraSpecialWeather = true;
}
}
// Extremely rare chance for VOID weather (0.1% chance independent of other weather)
if (Math.random() * 1000 < 1) {
selectedWeather = 'void';
} else if (Math.random() * 100 < 1) {
// Very small chance for blood moon (1% of weather events)
selectedWeather = 'bloodMoon';
} else {
// Equal chance for other weather types
selectedWeather = weatherTypes[Math.floor(Math.random() * weatherTypes.length)];
}
currentWeather = selectedWeather;
weatherActive = true;
applyWeatherEffects(selectedWeather);
showWeatherAnnouncement(selectedWeather);
}
}
function clearWeather() {
// Restore original monster stats before clearing weather
for (var i = 0; i < monsters.length; i++) {
var monster = monsters[i];
if (monster.active) {
// Restore original speed if it was stored
if (monster.originalSpeed) {
monster.speed = monster.originalSpeed;
monster.baseSpeed = monster.originalSpeed;
monster.originalSpeed = undefined;
}
// Restore original health if it was stored
if (monster.originalHealth) {
monster.health = monster.originalHealth;
monster.originalHealth = undefined;
}
}
}
currentWeather = null;
weatherActive = false;
isExtraSpecialWeather = false;
weatherEffects["void"] = false;
applyWeatherEffects(null);
// Remove weather text if it exists
if (weatherText) {
weatherText.destroy();
weatherText = null;
}
}
// Card system variables
var isCardSelectionActive = false;
var cardContainer = null;
var selectedPower = null;
var availablePowers = ['bouncy', 'rapidfire', 'multishot', 'freshMeat', 'thickSkin', 'hotBullets', 'vampireBite', 'boomTime', 'shieldGenerator', 'piercingRounds', 'luckyShot', 'magneticBullets', 'chainLightning', 'freezeShot', 'ricochetMaster', 'timeDilation'];
var activePowers = [];
var bouncyBulletCount = 1; // Number of bounces for bouncy bullets
var multishotBulletCount = 1; // Number of bullets to fire for multishot
var freshMeatStacks = 0; // Number of Fresh Meat cards collected
var thickSkinStacks = 0; // Number of Thick Skin cards collected for damage reduction
var hotBulletStacks = 0; // Number of Hot Bullets cards collected for extra damage
var vampireBiteStacks = 0; // Number of Vampire Bite cards collected
var vampireBiteKillCount = 0; // Track kills for vampire bite healing
var boomTimeStacks = 0; // Number of Boom Time cards collected
var shieldGeneratorStacks = 0; // Number of Shield Generator cards collected
var shieldCooldown = 0; // Cooldown timer for shield
var piercingRoundsStacks = 0; // Number of Piercing Rounds cards collected
var luckyShotStacks = 0; // Number of Lucky Shot cards collected
var magneticBulletsStacks = 0; // Number of Magnetic Bullets cards collected
var chainLightningStacks = 0; // Number of Chain Lightning cards collected
var freezeShotStacks = 0; // Number of Freeze Shot cards collected
var ricochetMasterStacks = 0; // Number of Ricochet Master cards collected
var timeDilationStacks = 0; // Number of Time Dilation cards collected
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF,
font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif"
});
scoreText.anchor.set(0, 0);
scoreText.x = 120;
scoreText.y = 80;
LK.gui.topLeft.addChild(scoreText);
var healthText = new Text2('Health: 100', {
size: 50,
fill: 0xFF4444,
font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif"
});
healthText.anchor.set(0, 0);
healthText.x = 120;
healthText.y = 20;
LK.gui.topLeft.addChild(healthText);
var waveText = new Text2('Wave: 1', {
size: 50,
fill: 0x4A90E2,
font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif"
});
waveText.anchor.set(0, 0);
waveText.x = 120;
waveText.y = 142;
LK.gui.topLeft.addChild(waveText);
// Power utility functions
function getPowerTitle(powerType) {
switch (powerType) {
case 'bouncy':
return 'Bouncy Bullets';
case 'rapidfire':
return 'Rapid Fire';
case 'multishot':
return 'Multi Shot';
case 'freshMeat':
return 'Fresh Meat';
case 'thickSkin':
return 'Thick Skin';
case 'hotBullets':
return 'Hot Bullets';
case 'vampireBite':
return 'Vampire Bite';
case 'boomTime':
return 'Boom Time!';
case 'shieldGenerator':
return 'Shield Generator';
case 'piercingRounds':
return 'Piercing Rounds';
case 'luckyShot':
return 'Lucky Shot';
case 'magneticBullets':
return 'Magnetic Bullets';
case 'chainLightning':
return 'Chain Lightning';
case 'freezeShot':
return 'Freeze Shot';
case 'ricochetMaster':
return 'Ricochet Master';
case 'timeDilation':
return 'Time Dilation';
default:
return 'Unknown Power';
}
}
function getPowerDescription(powerType) {
switch (powerType) {
case 'bouncy':
return 'Bullets bounce to nearby enemies';
case 'rapidfire':
return 'Faster shooting speed';
case 'multishot':
return 'Shoot 3 bullets at once';
case 'freshMeat':
return 'Regenerate 5 HP after each wave';
case 'thickSkin':
return 'Reduce all damage taken by 1';
case 'hotBullets':
return 'Bullets deal +1 damage';
case 'vampireBite':
return 'Heal 1 HP for every 10 enemies killed';
case 'boomTime':
return 'Explosion damages nearby zombies when hurt';
case 'shieldGenerator':
return 'Absorb next attack every 15 seconds';
case 'piercingRounds':
return 'Bullets pass through +1 enemy';
case 'luckyShot':
return '10% chance for double damage bullets';
case 'magneticBullets':
return 'Bullets home in on nearby enemies';
case 'chainLightning':
return '20% chance to instantly kill nearby enemy';
case 'freezeShot':
return '15% chance to slow hit enemies';
case 'ricochetMaster':
return '25% chance bullets ricochet off walls';
case 'timeDilation':
return 'Enemies move 10% slower when close to you';
default:
return 'Unknown effect';
}
}
function showCardSelection() {
isCardSelectionActive = true;
// Create card container
cardContainer = new Container();
game.addChild(cardContainer);
// Create dark overlay
var overlay = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.8
});
overlay.tint = 0x000000;
cardContainer.addChild(overlay);
// Select 3 random powers
var shuffledPowers = availablePowers.slice();
for (var i = shuffledPowers.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = shuffledPowers[i];
shuffledPowers[i] = shuffledPowers[j];
shuffledPowers[j] = temp;
}
var selectedPowers = shuffledPowers.slice(0, 3);
// Create cards
for (var i = 0; i < 3; i++) {
var card = new Card(selectedPowers[i]);
card.x = 340 + i * 684; // Increased spacing to 684 pixels (606 card width + 78 pixel gap)
card.y = 1366;
cardContainer.addChild(card);
// Animate cards in
tween(card, {
y: 1000
}, {
duration: 500,
easing: tween.easeOut
});
}
// Add title text
var titleText = new Text2('Choose Your Power!', {
size: 80,
fill: 0xFFFFFF,
font: "'Arial Black', 'Helvetica Bold', 'Georgia Bold', Impact, sans-serif"
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 600;
cardContainer.addChild(titleText);
}
function selectCard(powerType) {
if (!isCardSelectionActive) return;
// Add power to active powers
activePowers.push(powerType);
// Apply power effect
applyPowerEffect(powerType);
// Animate cards out and clean up
if (cardContainer) {
tween(cardContainer, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (cardContainer) {
cardContainer.destroy();
}
cardContainer = null;
isCardSelectionActive = false;
}
});
}
}
function applyPowerEffect(powerType) {
switch (powerType) {
case 'rapidfire':
player.fireRate = Math.max(1, Math.floor(player.fireRate / 2));
break;
case 'multishot':
// Increase bullet count for multishot
multishotBulletCount++;
break;
case 'bouncy':
// Increase bounce count for all future bullets
bouncyBulletCount++;
break;
case 'freshMeat':
// Increase Fresh Meat stacks for HP regeneration
freshMeatStacks++;
break;
case 'thickSkin':
// Increase damage reduction stacks
thickSkinStacks++;
break;
case 'hotBullets':
// Increase bullet damage stacks
hotBulletStacks++;
break;
case 'vampireBite':
// Increase vampire bite stacks
vampireBiteStacks++;
break;
case 'boomTime':
// Increase boom time stacks
boomTimeStacks++;
break;
case 'shieldGenerator':
shieldGeneratorStacks++;
break;
case 'piercingRounds':
piercingRoundsStacks++;
break;
case 'luckyShot':
luckyShotStacks++;
break;
case 'magneticBullets':
magneticBulletsStacks++;
break;
case 'chainLightning':
chainLightningStacks++;
break;
case 'freezeShot':
freezeShotStacks++;
break;
case 'ricochetMaster':
ricochetMasterStacks++;
break;
case 'timeDilation':
timeDilationStacks++;
break;
}
}
var background = game.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
player = game.addChild(new Player());
player.x = 1024;
player.y = 1366;
function spawnMonster() {
if (monstersSpawned >= monstersInWave) return;
// Check if it's wave 66 and spawn only demon zombies
if (waveNumber === 66) {
monstersInWave = 22; // Set the number of monsters to spawn
for (var i = 0; i < monstersInWave; i++) {
var demonZombie = new DemonZombie();
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
demonZombie.x = Math.random() * 2048;
demonZombie.y = -30;
break;
case 1:
demonZombie.x = 2078;
demonZombie.y = Math.random() * 2732;
break;
case 2:
demonZombie.x = Math.random() * 2048;
demonZombie.y = 2762;
break;
case 3:
demonZombie.x = -30;
demonZombie.y = Math.random() * 2732;
break;
}
monsters.push(demonZombie);
game.addChild(demonZombie);
monstersSpawned++;
}
return;
}
// Spawn multiple monsters at once based on wave number - more balanced progression
var monstersToSpawn = 1;
if (waveNumber >= 3) monstersToSpawn = 2;
if (waveNumber >= 6) monstersToSpawn = 3;
if (waveNumber >= 10) monstersToSpawn = Math.min(4, Math.floor(waveNumber / 3));
var actualSpawn = Math.min(monstersToSpawn, monstersInWave - monstersSpawned);
for (var spawnIndex = 0; spawnIndex < actualSpawn; spawnIndex++) {
// Calculate current tough zombie chance (5% base + 1% per wave after wave 10, max 90%)
var currentToughZombieChance = waveNumber >= 10 ? Math.min(5 + (waveNumber - 10), 90) : 0;
// Determine zombie type with priority: splitter > tough > crayz > fast > rockthrower > normal
var randomChance = Math.random() * 100;
var monster;
var bestZombieChance = 0.05; // 0.05% chance to spawn BestZombie
if (Math.random() * 100 < bestZombieChance) {
monster = new BestZombie();
} else if (waveNumber >= 10 && randomChance < currentToughZombieChance) {
// 30% chance to spawn splitter zombie instead of tough zombie after wave 25
if (waveNumber >= 25 && Math.random() * 100 < 30) {
// 20% chance to spawn mega splitter zombie instead of splitter zombie after wave 30
if (waveNumber >= 30 && Math.random() * 100 < megaSplitterZombieChance) {
monster = new MegaSplitterZombie();
} else {
monster = new SplitterZombie();
}
} else {
monster = new ToughZombie();
}
} else if (waveNumber >= 15 && randomChance < currentToughZombieChance + crayzZombieChance) {
// 10% chance to spawn crayz zombie instead of fast zombie after wave 15
monster = new CrayzZombie();
} else if (waveNumber >= 5 && randomChance < currentToughZombieChance + crayzZombieChance + fastZombieChance) {
// 6.66% chance to spawn demon zombie instead of fast zombie after wave 36
if (waveNumber >= 36 && Math.random() * 100 < 6.66) {
monster = new DemonZombie();
} else if (waveNumber >= 21 && Math.random() * 100 < 5) {
// 5% chance to spawn transparent zombie instead of fast zombie after wave 21
monster = new TransparentZombie();
} else {
monster = new FastZombie();
}
} else if (waveNumber >= 25 && randomChance < currentToughZombieChance + crayzZombieChance + fastZombieChance + rockThrowerZombieChance) {
// 10% chance to spawn rock thrower zombie after wave 25
monster = new RockThrowerZombie();
} else {
monster = new Monster();
}
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
monster.x = Math.random() * 2048;
monster.y = -30;
break;
case 1:
// Right
monster.x = 2078;
monster.y = Math.random() * 2732;
break;
case 2:
// Bottom
monster.x = Math.random() * 2048;
monster.y = 2762;
break;
case 3:
// Left
monster.x = -30;
monster.y = Math.random() * 2732;
break;
}
// Store original stats before applying weather effects
monster.originalSpeed = monster.speed;
monster.originalHealth = monster.health;
// Apply weather effects to monster
if (weatherEffects.windy) {
if (weatherEffects["void"]) {
monster.speed *= 11; // 10x effect (1000% faster)
monster.baseSpeed = monster.speed;
} else {
monster.speed *= isExtraSpecialWeather ? 2.0 : 1.5; // 100% faster for extra, 50% for normal
monster.baseSpeed = monster.speed;
}
}
if (weatherEffects.bloodRain) {
if (weatherEffects["void"]) {
monster.health *= 20; // 10x effect (2000% HP)
} else {
monster.health *= isExtraSpecialWeather ? 4 : 2; // 4x HP for extra, 2x for normal
}
}
monsters.push(monster);
game.addChild(monster);
monstersSpawned++;
}
}
function cleanupBullets() {
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (!bullet.active) {
bullet.destroy();
bullets.splice(i, 1);
}
}
}
function cleanupMonsters() {
for (var i = monsters.length - 1; i >= 0; i--) {
var monster = monsters[i];
if (!monster.active) {
monster.destroy();
monsters.splice(i, 1);
monstersKilled++;
}
}
}
function cleanupBloodSplats() {
for (var i = bloodSplats.length - 1; i >= 0; i--) {
var bloodSplat = bloodSplats[i];
if (!bloodSplat.parent) {
bloodSplats.splice(i, 1);
}
}
}
function cleanupRocks() {
for (var i = rocks.length - 1; i >= 0; i--) {
var rock = rocks[i];
if (!rock.active) {
rock.destroy();
rocks.splice(i, 1);
}
}
}
function checkBulletCollisions() {
for (var i = 0; i < bullets.length; i++) {
var bullet = bullets[i];
if (!bullet.active) continue;
for (var j = 0; j < monsters.length; j++) {
var monster = monsters[j];
if (!monster.active) continue;
if (bullet.intersects(monster)) {
// Skip if this enemy was already pierced by this bullet
if (bullet.piercedEnemies.indexOf(monster) !== -1) {
continue;
}
// Calculate damage with Hot Bullets and Lucky Shot bonus
var bulletDamage = 1 + hotBulletStacks;
if (luckyShotStacks > 0) {
var luckyChance = Math.min(luckyShotStacks * 10, 50); // 10% per stack, max 50%
if (Math.random() * 100 < luckyChance) {
bulletDamage *= 2; // Double damage
bullet.isLuckyShot = true;
// Visual effect for lucky shot
LK.effects.flashObject(monster, 0xffff00, 200); // Yellow flash
}
}
var monsterKilled = monster.takeDamage(bulletDamage);
// Apply freeze shot effect
if (freezeShotStacks > 0 && bullet.frozenEnemies.indexOf(monster) === -1) {
var freezeChance = Math.min(freezeShotStacks * 15, 60); // 15% per stack, max 60%
if (Math.random() * 100 < freezeChance) {
var frozenMonster = new FrozenMonster(monster);
game.addChild(frozenMonster);
bullet.frozenEnemies.push(monster);
}
}
// Apply chain lightning effect
if (chainLightningStacks > 0 && monsterKilled) {
var chainChance = Math.min(chainLightningStacks * 20, 80); // 20% per stack, max 80%
if (Math.random() * 100 < chainChance) {
// Find nearest monster to chain to
var nearestChainMonster = null;
var nearestChainDistance = 200; // Chain range
for (var k = 0; k < monsters.length; k++) {
var chainMonster = monsters[k];
if (!chainMonster.active || chainMonster === monster) continue;
var dx = chainMonster.x - monster.x;
var dy = chainMonster.y - monster.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestChainDistance) {
nearestChainDistance = distance;
nearestChainMonster = chainMonster;
}
}
if (nearestChainMonster) {
// Create lightning bolt visual effect
var lightningBolt = LK.getAsset('lightning', {
anchorX: 0.5,
anchorY: 0.5,
x: (monster.x + nearestChainMonster.x) / 2,
y: (monster.y + nearestChainMonster.y) / 2,
alpha: 0.9
});
// Calculate rotation to point from monster to chained monster
var lightningDx = nearestChainMonster.x - monster.x;
var lightningDy = nearestChainMonster.y - monster.y;
lightningBolt.rotation = Math.atan2(lightningDy, lightningDx) + Math.PI / 2;
game.addChild(lightningBolt);
// Animate lightning bolt
tween(lightningBolt, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
lightningBolt.destroy();
}
});
// Play lightning sound
LK.getSound('lightning').play();
nearestChainMonster.takeDamage(999); // Instant kill
// Visual lightning effect
LK.effects.flashObject(nearestChainMonster, 0x00ffff, 300); // Cyan flash
}
}
}
// Check for Vampire Bite healing
if (monsterKilled && vampireBiteStacks > 0) {
vampireBiteKillCount++;
var healsNeeded = Math.max(1, 10 - (vampireBiteStacks - 1)); // 10 kills for first stack, -1 for each additional (min 1)
if (vampireBiteKillCount >= healsNeeded) {
vampireBiteKillCount = 0;
if (player.health < 100) {
player.health = Math.min(100, player.health + 1);
}
}
}
// Add monster to pierced list for piercing rounds
if (piercingRoundsStacks > 0) {
bullet.piercedEnemies.push(monster);
// Check if bullet has pierced maximum number of enemies
if (bullet.piercedEnemies.length > piercingRoundsStacks) {
bullet.active = false;
break;
}
// Continue to next monster for piercing
continue;
}
// Try to bounce bullet if bouncy power is active and bullet hasn't exceeded bounce limit
if (bullet.bounceCount < bouncyBulletCount && activePowers.indexOf('bouncy') !== -1) {
bullet.bounce(monster);
// If bounce was successful (found a target), keep bullet active
if (bullet.bounced) {
break; // Stop checking other monsters for this bullet this frame
}
}
// Only deactivate bullet if it reached bounce limit or bouncy power is not active
if (bullet.bounceCount >= bouncyBulletCount || activePowers.indexOf('bouncy') === -1) {
bullet.active = false;
}
break;
}
}
}
}
function nextWave() {
waveNumber++;
// Apply Fresh Meat healing (5 HP per stack, max 100 HP)
if (freshMeatStacks > 0 && player.health < 100) {
var healAmount = freshMeatStacks * 5;
player.health = Math.min(100, player.health + healAmount);
}
// Check for card selection every 5 waves
if (waveNumber % 5 === 0 && waveNumber > 0) {
showCardSelection();
return; // Don't start next wave until card is selected
}
// More balanced wave size progression: slower growth after wave 10
if (waveNumber <= 10) {
monstersInWave = 3 + waveNumber * 2;
} else {
monstersInWave = 23 + Math.floor((waveNumber - 10) / 2);
}
// Apply blood smell weather effect (double or quadruple monsters)
if (weatherEffects.bloodSmell) {
if (weatherEffects["void"]) {
monstersInWave *= 20; // 10x effect (2000% monsters)
} else {
monstersInWave *= isExtraSpecialWeather ? 4 : 2;
}
}
// Blood harvest no longer uses temp health system
monstersSpawned = 0;
monstersKilled = 0;
spawnTimer = 0;
waveDelay = 180;
// Increase fast zombie chance by 1% each wave, max 80%
fastZombieChance = Math.min(10 + waveNumber, 80);
waveText.setText('Wave: ' + waveNumber);
// Clear previous weather and check for new weather
clearWeather();
checkForWeather();
}
// Mouse move handler to make player look at cursor
game.move = function (x, y, obj) {
// Calculate angle from player to mouse cursor
var dx = x - player.x;
var dy = y - player.y;
// Rotate player to face mouse cursor (add PI/2 to align sprite correctly)
player.children[0].rotation = Math.atan2(dy, dx) + Math.PI / 2;
};
game.down = function (x, y, obj) {
// Check unlucky clouds weather effect (20% or 40% chance gun doesn't shoot)
var jamChance = isExtraSpecialWeather ? 40 : 20;
// VOID weather does not apply 10x gun jamming to keep the game playable
var gunJammed = weatherEffects.unluckyClouds && Math.random() * 100 < jamChance;
var canShootNormal = bulletCooldown <= 0 && player.fire();
if (canShootNormal && !gunJammed) {
// Check if multishot is active
var shotCount = activePowers.indexOf('multishot') !== -1 ? multishotBulletCount : 1;
for (var shotIndex = 0; shotIndex < shotCount; shotIndex++) {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
if (shotCount === 1) {
// Single shot - aim directly at target
bullet.setDirection(x, y);
} else {
// Multishot - spread bullets
var baseAngle = Math.atan2(y - player.y, x - player.x);
var spreadAngle = (shotIndex - (shotCount - 1) / 2) * 0.3; // Center the spread
var finalAngle = baseAngle + spreadAngle;
var targetX = player.x + Math.cos(finalAngle) * 500;
var targetY = player.y + Math.sin(finalAngle) * 500;
bullet.setDirection(targetX, targetY);
}
bullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
bulletCooldown = player.fireRate;
// Add shake effect to player when shooting
var originalX = player.x;
var originalY = player.y;
tween(player, {
x: originalX + (Math.random() - 0.5) * 50,
y: originalY + (Math.random() - 0.5) * 50
}, {
duration: 40,
onFinish: function onFinish() {
tween(player, {
x: originalX + (Math.random() - 0.5) * 30,
y: originalY + (Math.random() - 0.5) * 30
}, {
duration: 40,
onFinish: function onFinish() {
tween(player, {
x: originalX,
y: originalY
}, {
duration: 30
});
}
});
}
});
}
// Check unlucky clouds weather effect (20% or 40% chance gun doesn't shoot)
var jamChance = isExtraSpecialWeather ? 40 : 20;
// VOID weather does not apply 10x gun jamming to keep the game playable
var gunJammed = weatherEffects.unluckyClouds && Math.random() * 100 < jamChance;
var canShootNormal = bulletCooldown <= 0 && player.fire();
if (canShootNormal && !gunJammed) {
// Check if multishot is active
var shotCount = activePowers.indexOf('multishot') !== -1 ? multishotBulletCount : 1;
for (var shotIndex = 0; shotIndex < shotCount; shotIndex++) {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
if (shotCount === 1) {
// Single shot - aim directly at target
bullet.setDirection(x, y);
} else {
// Multishot - spread bullets
var baseAngle = Math.atan2(y - player.y, x - player.x);
var spreadAngle = (shotIndex - (shotCount - 1) / 2) * 0.3; // Center the spread
var finalAngle = baseAngle + spreadAngle;
var targetX = player.x + Math.cos(finalAngle) * 500;
var targetY = player.y + Math.sin(finalAngle) * 500;
bullet.setDirection(targetX, targetY);
}
bullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
bulletCooldown = player.fireRate;
// Add shake effect to player when shooting
var originalX = player.x;
var originalY = player.y;
tween(player, {
x: originalX + (Math.random() - 0.5) * 50,
y: originalY + (Math.random() - 0.5) * 50
}, {
duration: 40,
onFinish: function onFinish() {
tween(player, {
x: originalX + (Math.random() - 0.5) * 30,
y: originalY + (Math.random() - 0.5) * 30
}, {
duration: 40,
onFinish: function onFinish() {
tween(player, {
x: originalX,
y: originalY
}, {
duration: 30
});
}
});
}
});
}
};
LK.playMusic('bgmusic');
game.update = function () {
// Pause game during card selection
if (isCardSelectionActive) {
return;
}
// Secret wave skip timer
if (secretTapTimer > 0) {
secretTapTimer--;
if (secretTapTimer <= 0) {
secretTapCount = 0; // Reset if too slow
}
}
if (bulletCooldown > 0) {
bulletCooldown--;
}
if (waveDelay > 0) {
waveDelay--;
return;
}
spawnTimer++;
if (spawnTimer >= 60 && monstersSpawned < monstersInWave) {
spawnMonster();
spawnTimer = 0;
}
checkBulletCollisions();
cleanupBullets();
cleanupMonsters();
cleanupBloodSplats();
cleanupRocks();
if (monstersKilled >= monstersInWave && monsters.length === 0) {
nextWave();
}
scoreText.setText('Score: ' + LK.getScore());
healthText.setText('Health: ' + player.health + '/' + player.maxHealth);
};
Bullet. In-Game asset. 2d. High contrast. No shadows
A extremly basic zombie look from top. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A round drop of blood. In-Game asset. 2d. High contrast. No shadows
Make it simple no blood or object
Make its skin blue and make it smile
A happier version of this zombie but its will be purple
Make it green
small rock. In-Game asset. 2d. High contrast. No shadows
Yellow version of it
make it white
Explosion!. In-Game asset. 2d. High contrast. No shadows
Lightning. In-Game asset. 2d. High contrast. No shadows
Make it rainbow and extremly happy
A cowboy hat from top. In-Game asset. 2d. High contrast. No shadows
Hake his skin red and add horns
A card. In-Game asset. 2d. High contrast. No shadows