User prompt
Remove the secret wave skipper
User prompt
Change every font at the game to be same as items font
User prompt
show wave instead of score when player dies
User prompt
Can you change the items texts font? ıts a question
User prompt
dont make them touch each other
User prompt
Create a new asent for items background
User prompt
Can you fix inconsistent descriptions of some items?
User prompt
Make player look at our mouse
User prompt
İts not red its black
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'waveText.style.fill = 0xFF0000; // Change text color to red' Line Number: 2477
User prompt
also can you make the waves text red only when we are at 66. wave
User prompt
I want you to spawn 22 demon zombie at 66. wave and no other zombie
User prompt
windy and blood smell weather isnt working
User prompt
make the red flash %80 transparent ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Play a sound when taken damage, shake the player hard, make screen red for a moment ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make only the void weather happen after 100. wave
User prompt
At the 100. wave make the void weather happen and dont let any other weather to happen.
User prompt
Give demon zombie a teleport effect and sound ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
create a new asset for them
User prompt
After the 36. wave demon zombies have a %6,66 change to replace a fast zombie. Demon zombie is fast as fast zombie and have 1 hp but when a bullet is about to hit him he teleports somewhere diffrent. He can do this up to 3 times and he cant teleport very near to player
User prompt
make it shake more ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
when shooting make our character shake a little ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/**** * 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); };
===================================================================
--- original.js
+++ change.js
@@ -2408,41 +2408,62 @@
// 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) {
- // Secret wave skip check (bottom-right corner taps)
- if (x >= secretZone.x && x <= secretZone.x + secretZone.width && y >= secretZone.y && y <= secretZone.y + secretZone.height) {
- secretTapCount++;
- secretTapTimer = 120; // 2 seconds to complete sequence
- if (secretTapCount >= 5) {
- // Skip 3 waves
- var skipAmount = 3;
- // Skip waves instantly
- for (var skipCount = 0; skipCount < skipAmount; skipCount++) {
- waveNumber++;
- }
- // Calculate wave size using balanced progression
- if (waveNumber <= 10) {
- monstersInWave = 3 + waveNumber * 2;
+ // 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 {
- monstersInWave = 23 + Math.floor((waveNumber - 10) / 2);
+ // 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);
}
- // Update fast zombie chance for the new wave
- fastZombieChance = Math.min(10 + waveNumber, 80);
- monstersSpawned = 0;
- monstersKilled = monstersInWave;
- // Clear all current monsters
- for (var i = 0; i < monsters.length; i++) {
- if (monsters[i].active) {
- monsters[i].active = false;
- }
- }
- secretTapCount = 0;
- waveText.setText('Wave: ' + waveNumber);
- // Visual feedback (brief flash)
- LK.effects.flashScreen(0x00ff00, 200);
+ bullets.push(bullet);
+ game.addChild(bullet);
}
- return;
+ 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
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