User prompt
// Wave 1: Left side of the road const wave1 = createWave('tank', 3, 1); // Wave 2: Right side of the road const wave2 = createWave('tank', 3, 3); // Wave 3: Center of the road const wave3 = createWave('tank', 3, 2);
User prompt
You should load the wave 8 tank enemy to the right side of the road. You have nothing to do with wave 4 tank enemy!!!
User prompt
You should load the wave 8 tank enemy to the right side of the road. You have nothing to do with wave 5 tanke enemy.
User prompt
Move 4. Wave tank enemy left by 12 units
User prompt
Move 4. Wave tank enemy loading left by 12 units
User prompt
Ensure the tank 8 wave is loading to the left edge of the road instead of right edge.
User prompt
Speed up fast 6 wave speed by 1.1
User prompt
Speed up fast 5 wave speed by 1.2
User prompt
You didn't change valid placing tank box to green. Fix it. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Color the boxes that the player is about to place green if they can be placed there and red if they cannot. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Change tank weapon box color darker
User prompt
Change Tank weapon box color to dark green
User prompt
Change Tank box color to dark green
User prompt
Increase immune 2 wave enemy speed to 2.4 instead of 2 when they reached the second turn.
User prompt
Increase immune 2 wave enemy speed to 2.5 instead of 2
User prompt
Speed up immune 2 enemies by 1.3 when they reached the road second turn. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Speed up immune 2 enemies if they reached the second turn. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Increase immune 2 enemies speed after first turn
User prompt
Swap immune 5 wave and fast 6 wave order
User prompt
Immune 5 wave enemies moving is to slow. Increase their moving speed but do nothing with immune 2 wave!!!!
User prompt
I ASKED FROM YOU THAT INCREASE IMMUNE 5 WAVE ENEMIES SPEED NOT THE IMMUNE 2 WAVE ENEMIES!!!!!!!!!!!
User prompt
Immune 5 wave enemies moving is to slow. Increase their moving speed
User prompt
Immune 5 wave enemies are to slow
User prompt
Change Immune 4. wave to tank.
User prompt
Idiot you changed all immune enemies to tank... This wasn't the task
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) { var self = Container.call(this); self.targetEnemy = targetEnemy; self.damage = damage || 10; self.speed = speed || 5; self.x = startX; self.y = startY; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { // Handle enemy bullets targeting towers if (self.isEnemyBullet) { if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { // Damage the tower instead of an enemy var tower = self.targetEnemy; if (tower.takeDamage) { tower.takeDamage(self.damage); } self.destroy(); } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } return; } // Original enemy targeting logic if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { // Special handling for missiles hitting immune, flying, or tank enemies - destroy instantly if (self.type === 'slow' && (self.targetEnemy.isImmune || self.targetEnemy.isFlying || self.targetEnemy.type === 'tank')) { // Instantly destroy immune, flying, or tank enemy regardless of health self.targetEnemy.health = 0; } else if (self.type === 'tank' && (self.targetEnemy.isImmune || self.targetEnemy.isFlying || self.targetEnemy.type === 'tank' || self.targetEnemy.isBoss)) { // Tank weapons instantly destroy immune, flying, tank, and boss enemies self.targetEnemy.health = 0; } else { // Apply damage to target enemy normally self.targetEnemy.health -= self.damage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } else { self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; } } // Create explosion effect for missile hits if (self.type === 'slow') { // Store explosion position for area damage calculation var explosionX = self.targetEnemy.x; var explosionY = self.targetEnemy.y; // Increased explosion radius to destroy nearby enemies (especially fast enemies) var explosionRadius = CELL_SIZE * 2.5; // Increased from smaller radius to 2.5 cells // Apply area damage to all enemies within explosion radius for (var i = 0; i < enemies.length; i++) { var nearbyEnemy = enemies[i]; if (nearbyEnemy !== self.targetEnemy) { var dx = nearbyEnemy.x - explosionX; var dy = nearbyEnemy.y - explosionY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRadius) { // Instantly destroy fast enemies within explosion radius if (nearbyEnemy.type === 'fast') { nearbyEnemy.health = 0; } else { // Apply significant damage to other enemy types nearbyEnemy.health -= self.damage * 1.5; // 1.5x damage for area effect if (nearbyEnemy.health <= 0) { nearbyEnemy.health = 0; } else { nearbyEnemy.healthBar.width = nearbyEnemy.health / nearbyEnemy.maxHealth * 70; } } // Create individual explosion effect for each affected enemy var nearbyExplosion = new EffectIndicator(nearbyEnemy.x, nearbyEnemy.y, 'splash'); nearbyExplosion.children[0].tint = 0xFF6600; // Orange-red for area damage nearbyExplosion.children[0].width = nearbyExplosion.children[0].height = CELL_SIZE * 1.5; nearbyExplosion.alpha = 0.8; nearbyExplosion.scaleX = 0.2; nearbyExplosion.scaleY = 0.2; game.addChild(nearbyExplosion); // Animate nearby explosion tween(nearbyExplosion, { scaleX: 1.2, scaleY: 1.2, alpha: 0.6 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(nearbyExplosion, { scaleX: 1.8, scaleY: 1.8, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { nearbyExplosion.destroy(); } }); } }); } } } // Create MASSIVE tank weapon explosion with devastating area damage if (self.type === 'tank') { // Store explosion position for area damage calculation var explosionX = self.targetEnemy.x; var explosionY = self.targetEnemy.y; // MASSIVE explosion radius - much larger than missiles for devastating effect var explosionRadius = CELL_SIZE * 4.0; // 4 cell radius for tank weapons (60% larger than missiles) // Apply devastating area damage to all enemies within explosion radius for (var i = 0; i < enemies.length; i++) { var nearbyEnemy = enemies[i]; if (nearbyEnemy !== self.targetEnemy) { var dx = nearbyEnemy.x - explosionX; var dy = nearbyEnemy.y - explosionY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRadius) { // Tank weapons cause MASSIVE damage - instantly destroy most enemies if (nearbyEnemy.type === 'fast' || nearbyEnemy.type === 'flying' || nearbyEnemy.isFlying) { // Instantly destroy fast and flying enemies nearbyEnemy.health = 0; } else if (nearbyEnemy.type === 'immune' || nearbyEnemy.isImmune) { // Tank weapons instantly destroy immune enemies nearbyEnemy.health = 0; } else if (nearbyEnemy.type === 'tank') { // Tank weapons instantly destroy tank enemies nearbyEnemy.health = 0; } else if (nearbyEnemy.isBoss) { // Tank weapons instantly destroy boss enemies nearbyEnemy.health = 0; } else { // Massive damage to other enemy types nearbyEnemy.health -= self.damage * 3.0; // 3x damage for devastating effect if (nearbyEnemy.health <= 0) { nearbyEnemy.health = 0; } else { nearbyEnemy.healthBar.width = nearbyEnemy.health / nearbyEnemy.maxHealth * 70; } } // Create individual massive explosion effect for each affected enemy var nearbyExplosion = new EffectIndicator(nearbyEnemy.x, nearbyEnemy.y, 'splash'); nearbyExplosion.children[0].tint = 0x8B4513; // Dark brown for tank explosion nearbyExplosion.children[0].width = nearbyExplosion.children[0].height = CELL_SIZE * 2.0; // Larger than missile explosions nearbyExplosion.alpha = 1.0; nearbyExplosion.scaleX = 0.3; nearbyExplosion.scaleY = 0.3; game.addChild(nearbyExplosion); // Animate massive nearby explosion tween(nearbyExplosion, { scaleX: 1.8, scaleY: 1.8, alpha: 0.8 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(nearbyExplosion, { scaleX: 2.5, scaleY: 2.5, alpha: 0 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { nearbyExplosion.destroy(); } }); } }); } } } // Create ULTRA-MASSIVE tank weapon explosion with enhanced visual effects // Stage 1: Initial massive impact flash - GIGANTIC white flash var impactFlash = new EffectIndicator(explosionX, explosionY, 'splash'); impactFlash.children[0].tint = 0xFFFFFF; // Bright white impact flash impactFlash.children[0].width = impactFlash.children[0].height = CELL_SIZE * 6.0; // ENORMOUS initial flash impactFlash.alpha = 1; impactFlash.scaleX = 0.1; impactFlash.scaleY = 0.1; game.addChild(impactFlash); // Ultra-fast bright flash expansion tween(impactFlash, { scaleX: 8.0, // Much bigger than missile explosions scaleY: 8.0, alpha: 0 }, { duration: 100, easing: tween.easeOut }); // Stage 2: MASSIVE main tank explosion fireball - dark brown/orange core var mainExplosion = new EffectIndicator(explosionX, explosionY, 'splash'); mainExplosion.children[0].tint = 0x8B4513; // Dark brown for tank explosion mainExplosion.children[0].width = mainExplosion.children[0].height = CELL_SIZE * 10.0; // ENORMOUS main explosion mainExplosion.alpha = 1; mainExplosion.scaleX = 0.2; mainExplosion.scaleY = 0.2; game.addChild(mainExplosion); // Dramatic MASSIVE fireball expansion tween(mainExplosion, { scaleX: 6.5, // Bigger than missile explosions scaleY: 6.5, alpha: 0.95 }, { duration: 300, easing: tween.bounceOut, onFinish: function onFinish() { tween(mainExplosion, { scaleX: 10.0, // ENORMOUS final scale scaleY: 10.0, alpha: 0 }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { mainExplosion.destroy(); } }); } }); // Stage 3: Create QUADRUPLE the explosion particles for MASSIVE debris effect for (var particleIndex = 0; particleIndex < 32; particleIndex++) { // More particles than missiles var explosionParticle = new EffectIndicator(explosionX, explosionY, 'splash'); // Tank explosion colors - browns and oranges var particleColors = [0x8B4513, 0xA0522D, 0xD2691E, 0xFF8C00, 0xCD853F, 0xDEB887, 0x654321, 0x8B7355]; explosionParticle.children[0].tint = particleColors[particleIndex % particleColors.length]; explosionParticle.children[0].width = explosionParticle.children[0].height = CELL_SIZE * (2.2 + Math.random() * 1.8); // Bigger particles explosionParticle.alpha = 0.95; explosionParticle.scaleX = 0.6 + Math.random() * 0.5; explosionParticle.scaleY = 0.6 + Math.random() * 0.5; // Random direction for particle scatter with ULTRA-LONG distance var particleAngle = particleIndex / 32 * Math.PI * 2 + (Math.random() - 0.5) * 0.8; var particleDistance = CELL_SIZE * (4.5 + Math.random() * 3.5); // Ultra-far particle scatter var targetX = explosionX + Math.cos(particleAngle) * particleDistance; var targetY = explosionY + Math.sin(particleAngle) * particleDistance; game.addChild(explosionParticle); // Animate particles flying outward tween(explosionParticle, { x: targetX, y: targetY, scaleX: 4.0 + Math.random() * 1.5, // Much bigger final scale scaleY: 4.0 + Math.random() * 1.5, alpha: 0.8 }, { duration: 400 + Math.random() * 300, easing: tween.easeOut, onFinish: function onFinish() { tween(explosionParticle, { scaleX: 0.3, scaleY: 0.3, alpha: 0 }, { duration: 500 + Math.random() * 250, easing: tween.easeIn, onFinish: function onFinish() { explosionParticle.destroy(); } }); } }); } // Stage 4: QUINTUPLE shockwave rings for tank weapons var primaryShockwave = new EffectIndicator(explosionX, explosionY, 'splash'); primaryShockwave.children[0].tint = 0xDEB887; // Light brown shockwave primaryShockwave.children[0].alpha = 0.8; primaryShockwave.children[0].width = primaryShockwave.children[0].height = CELL_SIZE * 2.0; primaryShockwave.alpha = 1.0; game.addChild(primaryShockwave); // MASSIVE expanding primary shockwave tween(primaryShockwave, { scaleX: 15.0, // Bigger than missile shockwaves scaleY: 15.0, alpha: 0 }, { duration: 900, easing: tween.easeOut, onFinish: function onFinish() { primaryShockwave.destroy(); } }); // Multiple additional shockwaves for devastating effect for (var shockIndex = 1; shockIndex <= 4; shockIndex++) { var additionalShockwave = new EffectIndicator(explosionX, explosionY, 'splash'); additionalShockwave.children[0].tint = 0x8B7355; // Darker brown shockwave additionalShockwave.children[0].alpha = 0.6 - shockIndex * 0.1; additionalShockwave.children[0].width = additionalShockwave.children[0].height = CELL_SIZE * (2.0 - shockIndex * 0.2); additionalShockwave.alpha = 0.8 - shockIndex * 0.1; game.addChild(additionalShockwave); // Delayed massive shockwaves tween(additionalShockwave, { scaleX: 16.0 + shockIndex * 1.0, scaleY: 16.0 + shockIndex * 1.0, alpha: 0 }, { duration: 1000 + shockIndex * 100, delay: shockIndex * 200, easing: tween.easeOut, onFinish: function onFinish() { additionalShockwave.destroy(); } }); } // Stage 5: MASSIVE lingering smoke cloud var smokeCloud = new EffectIndicator(explosionX, explosionY, 'splash'); smokeCloud.children[0].tint = 0x696969; // Dark gray smoke smokeCloud.children[0].alpha = 0.7; smokeCloud.children[0].width = smokeCloud.children[0].height = CELL_SIZE * 8.0; // Bigger smoke cloud smokeCloud.alpha = 0; smokeCloud.scaleX = 1.2; smokeCloud.scaleY = 1.2; game.addChild(smokeCloud); // Delayed massive smoke appearance tween(smokeCloud, { alpha: 1.0, scaleX: 8.0, scaleY: 8.0 }, { duration: 500, delay: 500, easing: tween.easeOut, onFinish: function onFinish() { // Smoke slowly dissipates over very long time tween(smokeCloud, { alpha: 0, scaleX: 14.0, // ENORMOUS final scale scaleY: 14.0 }, { duration: 2000, // Very long dissipation time easing: tween.easeIn, onFinish: function onFinish() { smokeCloud.destroy(); } }); } }); } // Create ULTRA-MASSIVE spectacular multi-stage missile explosion with ENHANCED particle effects // Stage 1: Initial impact flash - ULTRA bright white flash (GIGANTIC) var impactFlash = new EffectIndicator(explosionX, explosionY, 'splash'); impactFlash.children[0].tint = 0xFFFFFF; // Bright white impact flash impactFlash.children[0].width = impactFlash.children[0].height = CELL_SIZE * 5.0; // GIGANTIC initial flash (increased from 3.5) impactFlash.alpha = 1; impactFlash.scaleX = 0.1; impactFlash.scaleY = 0.1; game.addChild(impactFlash); // Ultra-fast bright flash expansion (GIGANTIC SCALE) tween(impactFlash, { scaleX: 7.0, // MUCH bigger scale (increased from 4.5) scaleY: 7.0, alpha: 0 }, { duration: 120, easing: tween.easeOut }); // Stage 2: ULTRA-MASSIVE main explosion fireball - orange/red core (ENORMOUS) var mainExplosion = new EffectIndicator(explosionX, explosionY, 'splash'); mainExplosion.children[0].tint = 0xFF4500; // Orange-red fireball mainExplosion.children[0].width = mainExplosion.children[0].height = CELL_SIZE * 8.0; // ENORMOUS main explosion (increased from 6.0) mainExplosion.alpha = 1; mainExplosion.scaleX = 0.2; mainExplosion.scaleY = 0.2; game.addChild(mainExplosion); // Dramatic ULTRA-MASSIVE fireball expansion with bounce effect tween(mainExplosion, { scaleX: 5.5, // Much bigger scale (increased from 3.8) scaleY: 5.5, alpha: 0.95 }, { duration: 250, easing: tween.bounceOut, onFinish: function onFinish() { tween(mainExplosion, { scaleX: 8.5, // ENORMOUS final scale (increased from 6.0) scaleY: 8.5, alpha: 0 }, { duration: 700, easing: tween.easeIn, onFinish: function onFinish() { mainExplosion.destroy(); } }); } }); // Stage 3: Create TRIPLE the explosion particles for ULTRA-MASSIVE debris effect (TRIPLED) for (var particleIndex = 0; particleIndex < 24; particleIndex++) { // Increased from 16 to 24 var explosionParticle = new EffectIndicator(explosionX, explosionY, 'splash'); // Vary particle colors for realistic explosion with MORE variety var particleColors = [0xFF6600, 0xFF4500, 0xFF8800, 0xFFAA00, 0xFF2200, 0xFFCC00, 0xFF3300, 0xFFDD00]; explosionParticle.children[0].tint = particleColors[particleIndex % particleColors.length]; explosionParticle.children[0].width = explosionParticle.children[0].height = CELL_SIZE * (1.8 + Math.random() * 1.5); // MUCH bigger particles (increased from 1.2 + 1.0) explosionParticle.alpha = 0.9; explosionParticle.scaleX = 0.5 + Math.random() * 0.4; explosionParticle.scaleY = 0.5 + Math.random() * 0.4; // Random direction for particle scatter with ULTRA-LONG distance var particleAngle = particleIndex / 24 * Math.PI * 2 + (Math.random() - 0.5) * 0.8; var particleDistance = CELL_SIZE * (3.5 + Math.random() * 3.0); // ULTRA-FAR particle scatter (increased from 2.5 + 2.0) var targetX = explosionX + Math.cos(particleAngle) * particleDistance; var targetY = explosionY + Math.sin(particleAngle) * particleDistance; game.addChild(explosionParticle); // Animate particles flying outward with ULTRA-enhanced physics tween(explosionParticle, { x: targetX, y: targetY, scaleX: 3.5 + Math.random() * 1.2, // Much bigger final scale (increased from 2.5 + 0.8) scaleY: 3.5 + Math.random() * 1.2, alpha: 0.7 }, { duration: 350 + Math.random() * 250, // Longer duration easing: tween.easeOut, onFinish: function onFinish() { tween(explosionParticle, { scaleX: 0.2, scaleY: 0.2, alpha: 0 }, { duration: 450 + Math.random() * 200, // Longer fade duration easing: tween.easeIn, onFinish: function onFinish() { explosionParticle.destroy(); } }); } }); } // Stage 4: ULTRA-MASSIVE enhanced shockwave with QUADRUPLE rings var primaryShockwave = new EffectIndicator(explosionX, explosionY, 'splash'); primaryShockwave.children[0].tint = 0xFFDDDD; // Light pink shockwave primaryShockwave.children[0].alpha = 0.7; primaryShockwave.children[0].width = primaryShockwave.children[0].height = CELL_SIZE * 1.5; // Bigger base primaryShockwave.alpha = 0.9; game.addChild(primaryShockwave); // ULTRA-HUGE expanding primary shockwave tween(primaryShockwave, { scaleX: 12.0, // ENORMOUS scale (increased from 8.5) scaleY: 12.0, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { primaryShockwave.destroy(); } }); // Secondary ULTRA-MASSIVE shockwave var secondaryShockwave = new EffectIndicator(explosionX, explosionY, 'splash'); secondaryShockwave.children[0].tint = 0xFFFFFF; // White secondary wave secondaryShockwave.children[0].alpha = 0.5; secondaryShockwave.children[0].width = secondaryShockwave.children[0].height = CELL_SIZE * 1.0; secondaryShockwave.alpha = 0.8; game.addChild(secondaryShockwave); // Delayed ULTRA-MASSIVE secondary shockwave tween(secondaryShockwave, { scaleX: 13.0, // ENORMOUS scale (increased from 9.0) scaleY: 13.0, alpha: 0 }, { duration: 900, delay: 150, easing: tween.easeOut, onFinish: function onFinish() { secondaryShockwave.destroy(); } }); // Third shockwave for QUADRUPLE-ring ULTRA-MASSIVE effect var tertiaryShockwave = new EffectIndicator(explosionX, explosionY, 'splash'); tertiaryShockwave.children[0].tint = 0xFFAAAA; // Light red tertiary wave tertiaryShockwave.children[0].alpha = 0.3; tertiaryShockwave.children[0].width = tertiaryShockwave.children[0].height = CELL_SIZE * 0.8; tertiaryShockwave.alpha = 0.7; game.addChild(tertiaryShockwave); // Third delayed ULTRA-MASSIVE shockwave tween(tertiaryShockwave, { scaleX: 14.0, // ENORMOUS scale (increased from 10.0) scaleY: 14.0, alpha: 0 }, { duration: 1000, delay: 300, easing: tween.easeOut, onFinish: function onFinish() { tertiaryShockwave.destroy(); } }); // Fourth shockwave for QUADRUPLE-ring effect (NEW!) var quaternaryShockwave = new EffectIndicator(explosionX, explosionY, 'splash'); quaternaryShockwave.children[0].tint = 0xFFCCCC; // Very light pink quaternary wave quaternaryShockwave.children[0].alpha = 0.2; quaternaryShockwave.children[0].width = quaternaryShockwave.children[0].height = CELL_SIZE * 0.6; quaternaryShockwave.alpha = 0.6; game.addChild(quaternaryShockwave); // Fourth delayed ULTRA-MASSIVE shockwave tween(quaternaryShockwave, { scaleX: 15.0, // ULTRA-ENORMOUS scale scaleY: 15.0, alpha: 0 }, { duration: 1100, delay: 450, easing: tween.easeOut, onFinish: function onFinish() { quaternaryShockwave.destroy(); } }); // Stage 5: ULTRA-MASSIVE smoke cloud effect - ENORMOUS and lingers much longer var smokeCloud = new EffectIndicator(explosionX, explosionY, 'splash'); smokeCloud.children[0].tint = 0x666666; // Dark gray smoke smokeCloud.children[0].alpha = 0.6; smokeCloud.children[0].width = smokeCloud.children[0].height = CELL_SIZE * 6.0; // ENORMOUS smoke cloud (increased from 4.0) smokeCloud.alpha = 0; smokeCloud.scaleX = 1.0; smokeCloud.scaleY = 1.0; game.addChild(smokeCloud); // Delayed ULTRA-MASSIVE smoke appearance that grows slowly and lingers tween(smokeCloud, { alpha: 0.9, scaleX: 6.0, // Bigger initial expansion (increased from 4.0) scaleY: 6.0 }, { duration: 450, delay: 400, easing: tween.easeOut, onFinish: function onFinish() { // Smoke slowly dissipates over ULTRA-LONG time tween(smokeCloud, { alpha: 0, scaleX: 10.0, // ENORMOUS final scale (increased from 7.0) scaleY: 10.0 }, { duration: 1500, // ULTRA-LONG dissipation time (increased from 1200) easing: tween.easeIn, onFinish: function onFinish() { smokeCloud.destroy(); } }); } }); } // Apply special effects based on bullet type if (self.type === 'flamethrower') { // Create visual flame effect with enhanced animation var flameEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'flamethrower'); game.addChild(flameEffect); // Apply burning effect to target enemy if (!self.targetEnemy.isImmune) { self.targetEnemy.burning = true; self.targetEnemy.burnDamage = self.damage * 0.3; // 30% of original damage per tick self.targetEnemy.burnDuration = 180; // 3 seconds at 60 FPS } // Flame damage to enemies in a cone behind the target var coneRange = CELL_SIZE * 1.2; var targetAngle = Math.atan2(self.targetEnemy.y - self.y, self.targetEnemy.x - self.x); for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self.targetEnemy) { var flameDx = otherEnemy.x - self.targetEnemy.x; var flameDy = otherEnemy.y - self.targetEnemy.y; var flameDistance = Math.sqrt(flameDx * flameDx + flameDy * flameDy); var enemyAngle = Math.atan2(flameDy, flameDx); var angleDiff = Math.abs(enemyAngle - targetAngle); // Normalize angle difference while (angleDiff > Math.PI) angleDiff -= Math.PI * 2; while (angleDiff < -Math.PI) angleDiff += Math.PI * 2; angleDiff = Math.abs(angleDiff); // Check if enemy is within cone (45 degrees = PI/4 radians) if (flameDistance <= coneRange && angleDiff <= Math.PI / 4) { // Apply flame damage (40% of original damage) otherEnemy.health -= self.damage * 0.4; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } else { otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70; } // Apply burning effect if (!otherEnemy.isImmune) { otherEnemy.burning = true; otherEnemy.burnDamage = self.damage * 0.2; otherEnemy.burnDuration = 120; // 2 seconds for cone targets } } } } } else if (self.type === 'slow') { // Prevent slow effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual slow effect var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); // Apply slow effect // Make slow percentage scale with tower level (default 50%, up to 80% at max level) var slowPct = 0.5; if (self.sourceTowerLevel !== undefined) { // Scale: 50% at level 1, 60% at 2, 65% at 3, 70% at 4, 75% at 5, 80% at 6 var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8]; var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1)); slowPct = slowLevels[idx]; } if (!self.targetEnemy.slowed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; self.targetEnemy.speed *= 1 - slowPct; // Slow by X% self.targetEnemy.slowed = true; self.targetEnemy.slowDuration = 180; // 3 seconds at 60 FPS } else { self.targetEnemy.slowDuration = 180; // Reset duration } } } else if (self.type === 'poison') { // Prevent poison effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual poison effect var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Apply poison effect self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS } } else if (self.type === 'sniper') { // Create visual critical hit effect for sniper var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper'); game.addChild(sniperEffect); } self.destroy(); } else { var angle = Math.atan2(dy, dx); // Apply spread angle for machine gun bullets if (self.type === 'trap' && self.spreadAngle !== undefined) { angle += self.spreadAngle; } // Rotate missile asset to face target direction if (self.type === 'slow' && self.children.length > 0) { var missileGraphics = self.children[0]; if (missileGraphics.targetRotation === undefined) { missileGraphics.targetRotation = angle; missileGraphics.rotation = angle; } else { // Only rotate if angle difference is significant if (Math.abs(angle - missileGraphics.targetRotation) > 0.1) { tween.stop(missileGraphics, { rotation: true }); // Calculate shortest rotation path var currentRotation = missileGraphics.rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } missileGraphics.targetRotation = angle; tween(missileGraphics, { rotation: currentRotation + angleDiff }, { duration: 100, easing: tween.easeOut }); } } } self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); // DebugCell class removed var EffectIndicator = Container.expand(function (x, y, type) { var self = Container.call(this); self.x = x; self.y = y; var effectGraphics = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.blendMode = 1; switch (type) { case 'splash': effectGraphics.tint = 0x33CC00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5; break; case 'slow': effectGraphics.tint = 0x9900FF; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'poison': effectGraphics.tint = 0x00FFAA; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'sniper': effectGraphics.tint = 0xFF5500; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'flamethrower': effectGraphics.tint = 0xFF4500; effectGraphics.width = CELL_SIZE * 1.5; effectGraphics.height = CELL_SIZE * 0.8; // Elongated flame effect break; } effectGraphics.alpha = 0.7; self.alpha = 0; // Animate the effect tween(self, { alpha: 0.8, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); // Base enemy class for common functionality var Enemy = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'normal'; self.speed = .02; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 100; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.isFlying = false; self.isImmune = false; self.isBoss = false; self.collisionRadius = 25; // Base collision detection radius self.lastCollisionCheck = 0; // Performance optimization // Adjust collision radius based on enemy type switch (self.type) { case 'tank': self.collisionRadius = 40; // Larger radius for tank enemies break; case 'fast': self.collisionRadius = 20; // Smaller radius for fast enemies break; case 'flying': self.collisionRadius = 30; // Medium radius for flying enemies break; default: self.collisionRadius = 25; // Default radius break; } // Boss enemies have larger collision radius if (self.isBoss) { self.collisionRadius *= 1.5; } // Check if this is a boss wave // Check if this is a boss wave // Apply different stats based on enemy type switch (self.type) { case 'fast': self.speed *= 1; // Normal speed (half of previous 2x) // Speed boost for wave 5 fast enemies if (self.waveNumber === 5) { self.speed *= 1.2; // 1.2x speed for wave 5 fast enemies } // Speed boost for wave 6 fast enemies if (self.waveNumber === 6) { self.speed *= 1.1; // 1.1x speed for wave 6 fast enemies } self.maxHealth = 1; break; case 'immune': self.isImmune = true; self.maxHealth = 200; // Increase speed for wave 5 immune enemies only if (self.waveNumber === 5) { self.speed *= 3; // 3x speed for wave 5 immune enemies } // Speed boost for wave 2 immune enemies when they reach the second turn self.hasReachedSecondTurn = false; break; case 'flying': self.isFlying = true; self.speed *= 2; // 2x speed for flying enemies self.maxHealth = 80; break; case 'tank': self.speed *= 0.5; // Half speed for tank enemies self.maxHealth = 300; // Very high health for tank enemies break; default: // Default enemy uses fast values self.speed *= 1; // Normal speed (half of previous 2x) self.maxHealth = 1; break; } if (currentWave === totalWaves && type !== 'swarm') { self.isBoss = true; // Boss enemies have 20x health and are larger self.maxHealth *= 20; // Slower speed for bosses self.speed = self.speed * 0.7; } self.health = self.maxHealth; // Get appropriate asset for this enemy type var assetId = 'enemy'; if (self.isBoss) { assetId = 'Boss'; } else if (self.type === 'tank') { assetId = 'Tank'; } else if (self.type !== 'normal') { assetId = 'enemy_' + self.type; } var enemyGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Scale up boss enemies if (self.isBoss) { enemyGraphics.scaleX = 1.8; enemyGraphics.scaleY = 1.8; } // Fall back to regular enemy asset if specific type asset not found // Apply tint to differentiate enemy types /*switch (self.type) { case 'fast': enemyGraphics.tint = 0x00AAFF; // Blue for fast enemies break; case 'immune': enemyGraphics.tint = 0xAA0000; // Red for immune enemies break; case 'flying': enemyGraphics.tint = 0xFFFF00; // Yellow for flying enemies break; case 'swarm': enemyGraphics.tint = 0xFF00FF; // Pink for swarm enemies break; }*/ // Create shadow for flying enemies if (self.isFlying) { // Create a shadow container that will be added to the shadow layer self.shadow = new Container(); // Clone the enemy graphics for the shadow var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow effect shadowGraphics.tint = 0x000000; // Black shadow shadowGraphics.alpha = 0.4; // Semi-transparent // If this is a boss, scale up the shadow to match if (self.isBoss) { shadowGraphics.scaleX = 1.8; shadowGraphics.scaleY = 1.8; } // Position shadow slightly offset self.shadow.x = 20; // Offset right self.shadow.y = 20; // Offset down // Ensure shadow has the same rotation as the enemy shadowGraphics.rotation = enemyGraphics.rotation; } var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBarBG = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; self.update = function () { if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; } // Handle slow effect if (self.isImmune) { // Immune enemies cannot be slowed, poisoned, or burned, clear any such effects self.slowed = false; self.slowEffect = false; self.poisoned = false; self.poisonEffect = false; self.burning = false; self.burnEffect = false; // Reset speed to original if needed if (self.originalSpeed !== undefined) { self.speed = self.originalSpeed; } } else { // Handle slow effect if (self.slowed) { // Visual indication of slowed status if (!self.slowEffect) { self.slowEffect = true; } self.slowDuration--; if (self.slowDuration <= 0) { self.speed = self.originalSpeed; self.slowed = false; self.slowEffect = false; // Only reset tint if not poisoned if (!self.poisoned) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } // Handle poison effect if (self.poisoned) { // Visual indication of poisoned status if (!self.poisonEffect) { self.poisonEffect = true; } // Apply poison damage every 30 frames (twice per second) if (LK.ticks % 30 === 0) { self.health -= self.poisonDamage; if (self.health <= 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; } self.poisonDuration--; if (self.poisonDuration <= 0) { self.poisoned = false; self.poisonEffect = false; // Only reset tint if not slowed if (!self.slowed) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } // Handle burning effect from flamethrower if (self.burning) { // Visual indication of burning status if (!self.burnEffect) { self.burnEffect = true; } // Apply burn damage every 20 frames (3 times per second) if (LK.ticks % 20 === 0) { self.health -= self.burnDamage; if (self.health <= 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; } self.burnDuration--; if (self.burnDuration <= 0) { self.burning = false; self.burnEffect = false; // Only reset tint if not slowed or poisoned if (!self.slowed && !self.poisoned) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } } // Enemy overlap avoidance - check every frame for immune and tank enemies for better convoy formation if (self.type === 'immune' || self.type === 'tank' || LK.ticks % 3 === 0) { for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self && otherEnemy.parent) { // Prevent collision between flying and ground enemies (fast, immune, tank) if (self.isFlying && !otherEnemy.isFlying || !self.isFlying && otherEnemy.isFlying) { continue; // Skip collision detection between flying and ground enemies } var dx = otherEnemy.x - self.x; var dy = otherEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var minDistance = (self.collisionRadius + otherEnemy.collisionRadius) * 1.4; // Increased buffer for better separation if (distance < minDistance && distance > 0) { // Much stronger separation force for immune and tank enemies var overlap = minDistance - distance; var separationForce; if (self.type === 'immune' || self.type === 'tank' || otherEnemy.type === 'immune' || otherEnemy.type === 'tank') { // Extra strong separation for immune/tank enemies to maintain convoy formation separationForce = Math.pow(overlap / minDistance, 1.5) * 4.0; // Stronger force with smoother scaling } else { separationForce = Math.pow(overlap / minDistance, 2) * 2.0; // Original force for other enemies } var separationX = dx / distance * separationForce; var separationY = dy / distance * separationForce; // Apply separation with stronger horizontal bias to maintain lanes self.currentCellX -= separationX; self.currentCellY -= separationY * 0.7; // Reduced Y separation to maintain forward movement // Apply weaker counter-force to maintain formation otherEnemy.currentCellX += separationX * 0.3; otherEnemy.currentCellY += separationY * 0.3; // Keep enemies within reasonable bounds self.currentCellX = Math.max(2, Math.min(22, self.currentCellX)); self.currentCellY = Math.max(-20, Math.min(33, self.currentCellY)); otherEnemy.currentCellX = Math.max(2, Math.min(22, otherEnemy.currentCellX)); otherEnemy.currentCellY = Math.max(-20, Math.min(33, otherEnemy.currentCellY)); } } } } // Fast enemy shooting capability if (self.type === 'fast') { // Initialize shooting properties if not already set if (self.shootingCooldown === undefined) { self.shootingCooldown = 0; self.shootingRange = 12 * CELL_SIZE; // 12 cell shooting range (3x increase) self.shootingDamage = 5; self.bulletSpeed = 3; } // Decrease shooting cooldown if (self.shootingCooldown > 0) { self.shootingCooldown--; } // Try to find and shoot at towers within range if (self.shootingCooldown <= 0 && self.currentCellY >= 4) { var targetTower = null; var closestDistance = self.shootingRange; // Find closest tower within shooting range for (var i = 0; i < towers.length; i++) { var tower = towers[i]; // Skip landmine towers - enemies cannot see them if (tower.id === 'landmine') { continue; } // Allow targeting of all other tower types including trap/shooter towers var dx = tower.x - self.x; var dy = tower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.shootingRange && distance < closestDistance) { closestDistance = distance; targetTower = tower; } } // Shoot at the target tower if found if (targetTower) { var bulletX = self.x; var bulletY = self.y; var enemyBullet = new Bullet(bulletX, bulletY, targetTower, self.shootingDamage, self.bulletSpeed); // Customize enemy bullet appearance if (enemyBullet.children[0]) { enemyBullet.children[0].tint = 0xFF0000; // Red color for enemy bullets enemyBullet.children[0].width = 20; enemyBullet.children[0].height = 20; } // Mark as enemy bullet to handle differently enemyBullet.isEnemyBullet = true; game.addChild(enemyBullet); bullets.push(enemyBullet); // Set cooldown (shoot every 1.5 seconds at 60 FPS) self.shootingCooldown = 90; } } } // Immune enemy shooting capability if (self.type === 'immune') { // Initialize shooting properties if not already set if (self.shootingCooldown === undefined) { self.shootingCooldown = 0; self.shootingRange = 8 * CELL_SIZE; // 8 cell shooting range for immune enemies self.shootingDamage = 8; self.bulletSpeed = 4; } // Decrease shooting cooldown if (self.shootingCooldown > 0) { self.shootingCooldown--; } // Try to find and shoot at towers within range if (self.shootingCooldown <= 0 && self.currentCellY >= 4) { var targetTower = null; var closestDistance = self.shootingRange; // Find closest tower within shooting range for (var i = 0; i < towers.length; i++) { var tower = towers[i]; // Skip landmine towers - enemies cannot see them if (tower.id === 'landmine') { continue; } // Allow targeting of all other tower types including trap/shooter towers var dx = tower.x - self.x; var dy = tower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.shootingRange && distance < closestDistance) { closestDistance = distance; targetTower = tower; } } // Shoot at the target tower if found if (targetTower) { var bulletX = self.x; var bulletY = self.y; var enemyBullet = new Bullet(bulletX, bulletY, targetTower, self.shootingDamage, self.bulletSpeed); // Customize immune enemy bullet appearance if (enemyBullet.children[0]) { enemyBullet.children[0].tint = 0xAA0000; // Dark red color for immune enemy bullets enemyBullet.children[0].width = 25; enemyBullet.children[0].height = 25; } // Mark as enemy bullet to handle differently enemyBullet.isEnemyBullet = true; game.addChild(enemyBullet); bullets.push(enemyBullet); // Set cooldown (shoot every 1 second at 60 FPS) self.shootingCooldown = 60; } } } // Immune enemy trap collision detection if (self.type === 'immune') { // Initialize trap contact tracking if not set if (self.trapContactMap === undefined) { self.trapContactMap = {}; } // Check collision with trap towers every 10 frames for performance if (LK.ticks % 10 === 0) { for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.id === 'trap') { var dx = tower.x - self.x; var dy = tower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if immune enemy is touching the trap (within 80 pixels) if (distance <= 80) { // Only reduce health if this trap hasn't been touched before var trapKey = tower.x + ',' + tower.y; // Unique identifier for this trap if (!self.trapContactMap[trapKey]) { self.trapContactMap[trapKey] = true; // Reduce immune enemy's health by half self.health = Math.floor(self.health / 2); if (self.health <= 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; // Create visual effect for trap contact var trapEffect = new EffectIndicator(self.x, self.y, 'splash'); trapEffect.children[0].tint = 0xAAAAAA; // Gray color for trap effect trapEffect.children[0].width = trapEffect.children[0].height = CELL_SIZE * 1.2; game.addChild(trapEffect); } // Break after first trap collision to avoid multiple hits per frame break; } } } } } // Tank enemy shooting capability if (self.type === 'tank') { // Initialize shooting properties if not already set if (self.shootingCooldown === undefined) { self.shootingCooldown = 0; self.shootingRange = 6 * CELL_SIZE; // 6 cell shooting range for tank enemies self.shootingDamage = 12; self.bulletSpeed = 2; } // Decrease shooting cooldown if (self.shootingCooldown > 0) { self.shootingCooldown--; } // Try to find and shoot at towers within range if (self.shootingCooldown <= 0 && self.currentCellY >= 4) { var targetTower = null; var closestDistance = self.shootingRange; // Find closest tower within shooting range for (var i = 0; i < towers.length; i++) { var tower = towers[i]; // Skip landmine towers - enemies cannot see them if (tower.id === 'landmine') { continue; } // Allow targeting of all other tower types including trap/shooter towers var dx = tower.x - self.x; var dy = tower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.shootingRange && distance < closestDistance) { closestDistance = distance; targetTower = tower; } } // Shoot at the target tower if found if (targetTower) { var bulletX = self.x; var bulletY = self.y; var enemyBullet = new Bullet(bulletX, bulletY, targetTower, self.shootingDamage, self.bulletSpeed); // Customize tank enemy bullet appearance if (enemyBullet.children[0]) { enemyBullet.children[0].tint = 0x888888; // Gray color for tank enemy bullets enemyBullet.children[0].width = 30; enemyBullet.children[0].height = 30; } // Mark as enemy bullet to handle differently enemyBullet.isEnemyBullet = true; game.addChild(enemyBullet); bullets.push(enemyBullet); // Set cooldown (shoot every 2 seconds at 60 FPS) self.shootingCooldown = 120; } } } // Convoy formation for immune and tank enemies - maintain proper spacing and formation if ((self.type === 'immune' || self.type === 'tank') && self.currentCellY >= 4) { // Find nearby same-type enemies to form convoy with var nearbyConvoyEnemies = []; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self && otherEnemy.parent && otherEnemy.type === self.type) { var dx = otherEnemy.x - self.x; var dy = otherEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= CELL_SIZE * 4) { // Within 4 cells for convoy formation nearbyConvoyEnemies.push({ enemy: otherEnemy, distance: distance, dx: dx, dy: dy }); } } } // Apply convoy formation forces if (nearbyConvoyEnemies.length > 0) { for (var j = 0; j < nearbyConvoyEnemies.length; j++) { var convoyData = nearbyConvoyEnemies[j]; var convoyEnemy = convoyData.enemy; var distance = convoyData.distance; var dx = convoyData.dx; var dy = convoyData.dy; var idealDistance = CELL_SIZE * 2.8; // Ideal convoy spacing if (distance < idealDistance) { // Too close - apply separation var separationForce = (idealDistance - distance) / idealDistance * 0.8; var separationX = dx / distance * separationForce; var separationY = dy / distance * separationForce * 0.4; // Less Y separation self.currentCellX -= separationX; self.currentCellY -= separationY; } else if (distance > idealDistance * 1.5) { // Too far - apply attraction to maintain convoy var attractionForce = (distance - idealDistance) / distance * 0.3; var attractionX = dx / distance * attractionForce; var attractionY = dy / distance * attractionForce * 0.2; self.currentCellX += attractionX; self.currentCellY += attractionY; } } } // Keep enemies within valid bounds self.currentCellX = Math.max(3, Math.min(21, self.currentCellX)); self.currentCellY = Math.max(-20, Math.min(33, self.currentCellY)); } // Set tint based on effect status if (self.isImmune) { enemyGraphics.tint = 0xFFFFFF; } else if (self.poisoned && self.slowed && self.burning) { // Combine all three effects enemyGraphics.tint = 0x8B4513; // Brown-ish mix } else if (self.poisoned && self.slowed) { // Combine poison (0x00FFAA) and slow (0x9900FF) colors // Simple average: R: (0+153)/2=76, G: (255+0)/2=127, B: (170+255)/2=212 enemyGraphics.tint = 0x4C7FD4; } else if (self.poisoned && self.burning) { // Combine poison and burn enemyGraphics.tint = 0x8B7355; // Brownish-green mix } else if (self.slowed && self.burning) { // Combine slow and burn enemyGraphics.tint = 0xCC4500; // Orange-red mix } else if (self.burning) { enemyGraphics.tint = 0xFF4500; // Orange for burning } else if (self.poisoned) { enemyGraphics.tint = 0x00FFAA; } else if (self.slowed) { enemyGraphics.tint = 0x9900FF; } else { enemyGraphics.tint = 0xFFFFFF; } if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; if (ox !== 0 || oy !== 0) { var angle = Math.atan2(oy, ox) + Math.PI / 2; if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { if (Math.abs(angle - enemyGraphics.targetRotation) > 0.25) { // Further increased threshold to reduce tween frequency tween.stop(enemyGraphics, { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemyGraphics.rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemyGraphics.targetRotation = angle; tween(enemyGraphics, { rotation: currentRotation + angleDiff }, { duration: 150, // Reduced duration for faster rotation easing: tween.easeOut }); } } } } // Keep health bars positioned below level indicators var healthBarY = CELL_SIZE * 0.7 + 25; if (self.towerHealthBarOutline) { self.towerHealthBarOutline.y = healthBarY; self.towerHealthBarBG.y = healthBarY; self.towerHealthBar.y = healthBarY; } healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; }; return self; }); var GoldIndicator = Container.expand(function (value, x, y) { var self = Container.call(this); var shadowText = new Text2("+" + value, { size: 45, fill: 0x000000, weight: 800 }); shadowText.anchor.set(0.5, 0.5); shadowText.x = 2; shadowText.y = 2; self.addChild(shadowText); var goldText = new Text2("+" + value, { size: 45, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); self.x = x; self.y = y; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2, y: y - 40 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: y - 80 }, { duration: 600, easing: tween.easeIn, delay: 800, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; self.spawns = []; self.goals = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { score: 0, pathId: 0, towersInRange: [] }; } } /* Cell Types 0: Transparent floor 1: Wall 2: Spawn 3: Goal */ // Create simplified straight road path waypoints with 3-cell width var pathWaypoints = [{ x: 12, y: 0 }, // Start point (spawn) { x: 12, y: 8 }, // First horizontal road { x: 2, y: 8 }, // Turn down { x: 2, y: 15 }, // Second horizontal road { x: 22, y: 15 }, // Turn down { x: 22, y: 22 }, // Third horizontal road { x: 12, y: 22 }, // Final segment to goal { x: 12, y: gridHeight - 1 } // End point (goal) ]; // Create path cells array to mark which cells are part of the road var pathCells = []; // Generate path between waypoints for (var w = 0; w < pathWaypoints.length - 1; w++) { var start = pathWaypoints[w]; var end = pathWaypoints[w + 1]; // Create straight line between waypoints var steps = Math.max(Math.abs(end.x - start.x), Math.abs(end.y - start.y)); for (var s = 0; s <= steps; s++) { var t = steps === 0 ? 0 : s / steps; var pathX = Math.round(start.x + (end.x - start.x) * t); var pathY = Math.round(start.y + (end.y - start.y) * t); // Add path width - make road 7 cells wide horizontally, 3 cells wide vertically for (var offsetX = -3; offsetX <= 3; offsetX++) { for (var offsetY = -1; offsetY <= 1; offsetY++) { var cellX = pathX + offsetX; var cellY = pathY + offsetY; if (cellX >= 0 && cellX < gridWidth && cellY >= 0 && cellY < gridHeight) { var key = cellX + ',' + cellY; if (pathCells.indexOf(key) === -1) { pathCells.push(key); } } } } } } for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; var cellKey = i + ',' + j; var isOnPath = pathCells.indexOf(cellKey) !== -1; // Default to wall, then carve out path and other areas var cellType = 1; // Wall by default // Set path cells if (isOnPath) { cellType = 0; // Path } // Set spawn points at the start of the path if (i >= 11 && i <= 13 && j === 0) { cellType = 2; // Spawn self.spawns.push(cell); } // Set goal points at the end of the path if (i >= 11 && i <= 13 && j === gridHeight - 1) { cellType = 3; // Goal self.goals.push(cell); } // Keep borders as walls if (i === 0 || i === gridWidth - 1 || j === 0 || j === gridHeight - 1) { if (cellType !== 2 && cellType !== 3) { // Don't override spawn/goal cellType = 1; // Wall } } cell.type = cellType; cell.x = i; cell.y = j; cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1]; cell.up = self.cells[i - 1] && self.cells[i - 1][j]; cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1]; cell.left = self.cells[i][j - 1]; cell.right = self.cells[i][j + 1]; cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1]; cell.down = self.cells[i + 1] && self.cells[i + 1][j]; cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1]; cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left]; cell.targets = []; // Render road asset for path cells if (isOnPath) { var roadVisual = self.attachAsset('road', { anchorX: 0.5, anchorY: 0.6 }); roadVisual.x = i * CELL_SIZE + CELL_SIZE / 2; roadVisual.y = j * CELL_SIZE + CELL_SIZE / 2; } // Debug cells removed for performance } } self.getCell = function (x, y) { return self.cells[x] && self.cells[x][y]; }; self.pathFind = function () { var before = new Date().getTime(); var toProcess = self.goals.concat([]); maxScore = 0; pathId += 1; for (var a = 0; a < toProcess.length; a++) { toProcess[a].pathId = pathId; } function processNode(node, targetValue, targetNode) { if (node && node.type != 1) { if (node.pathId < pathId || targetValue < node.score) { node.targets = [targetNode]; } else if (node.pathId == pathId && targetValue == node.score) { node.targets.push(targetNode); } if (node.pathId < pathId || targetValue < node.score) { node.score = targetValue; if (node.pathId != pathId) { toProcess.push(node); } node.pathId = pathId; if (targetValue > maxScore) { maxScore = targetValue; } } } } while (toProcess.length) { var nodes = toProcess; toProcess = []; for (var a = 0; a < nodes.length; a++) { var node = nodes[a]; var targetScore = node.score + 14142; if (node.up && node.left && node.up.type != 1 && node.left.type != 1) { processNode(node.upLeft, targetScore, node); } if (node.up && node.right && node.up.type != 1 && node.right.type != 1) { processNode(node.upRight, targetScore, node); } if (node.down && node.right && node.down.type != 1 && node.right.type != 1) { processNode(node.downRight, targetScore, node); } if (node.down && node.left && node.down.type != 1 && node.left.type != 1) { processNode(node.downLeft, targetScore, node); } targetScore = node.score + 10000; processNode(node.up, targetScore, node); processNode(node.right, targetScore, node); processNode(node.down, targetScore, node); processNode(node.left, targetScore, node); } } for (var a = 0; a < self.spawns.length; a++) { if (self.spawns[a].pathId != pathId) { console.warn("Spawn blocked"); return true; } } for (var a = 0; a < enemies.length; a++) { var enemy = enemies[a]; // Skip enemies that haven't entered the viewable area yet if (enemy.currentCellY < 4) { continue; } // Skip flying enemies from path check as they can fly over obstacles if (enemy.isFlying) { continue; } var target = self.getCell(enemy.cellX, enemy.cellY); if (enemy.currentTarget) { if (enemy.currentTarget.pathId != pathId) { if (!target || target.pathId != pathId) { console.warn("Enemy blocked 1 "); return true; } } } else if (!target || target.pathId != pathId) { console.warn("Enemy blocked 2"); return true; } } console.log("Speed", new Date().getTime() - before); }; self.renderDebug = function () { // Debug cells removed for performance }; self.updateEnemy = function (enemy) { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell && cell.type == 3) { // Enemy reached the goal - reduce lives by 10 (10% of 100 total lives) lives = Math.max(0, lives - 10); updateUI(); if (lives <= 0) { LK.showGameOver(); } return true; } if (enemy.isFlying && enemy.shadow) { enemy.shadow.x = enemy.x + 20; // Match enemy x-position + offset enemy.shadow.y = enemy.y + 20; // Match enemy y-position + offset // Match shadow rotation with enemy rotation if (enemy.children[0] && enemy.shadow.children[0]) { enemy.shadow.children[0].rotation = enemy.children[0].rotation; } } // Check if the enemy has reached the entry area (y position is at least 5) var hasReachedEntryArea = enemy.currentCellY >= 4; // If enemy hasn't reached the entry area yet, just move down vertically if (!hasReachedEntryArea) { // Consistent movement speed for all enemy types during initial phase var initialSpeed = enemy.speed; if (enemy.type === 'tank') { initialSpeed = 0.03; // 3x faster than normal tank speed for smoother entry } // Move directly downward enemy.currentCellY += initialSpeed; // Rotate enemy graphic to face downward (PI/2 radians = 90 degrees) + 90 degrees right var angle = Math.PI / 2 + Math.PI / 2; if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update enemy's position (reduce calculation frequency) if (LK.ticks % 2 === 0) { enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; } // If enemy has now reached the entry area, update cell coordinates if (enemy.currentCellY >= 4) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); } return false; } // After reaching entry area, handle flying enemies differently if (enemy.isFlying) { // Initialize flying enemy direction if not set if (enemy.flyingDirection === undefined) { // Use the global alternating direction flag enemy.flyingDirection = lastDirectionWasRight ? 1 : -1; // 1 for right, -1 for left enemy.startX = enemy.currentCellX; // Remember starting X position enemy.hasChangedDirection = false; // Track if enemy has changed direction yet } // Assign flying enemies to one of 3 vertical lanes if not already assigned if (enemy.flyingLane === undefined) { // Determine lane based on initial direction instead of spawn position if (enemy.flyingDirection === -1) { // Flying left: start from right side lanes enemy.flyingLane = 2; // Right lane - x position 18 enemy.targetX = 18; } else { // Flying right: start from left side lanes enemy.flyingLane = 0; // Left lane - x position 6 enemy.targetX = 6; } } // Move straight down in the assigned lane enemy.currentCellY += enemy.speed; // Implement alternating direction movement for flying enemies if (!enemy.hasChangedDirection && enemy.currentCellY > 15) { // After reaching middle of map, start moving in assigned direction enemy.hasChangedDirection = true; // Set new target X based on direction - enemies move to opposite side if (enemy.flyingDirection > 0) { // Moving right - target right side of map enemy.targetX = 20; } else { // Moving left - target left side of map enemy.targetX = 4; } } // Gradually move to the target X position (lane center or direction target) var xDiff = enemy.targetX - enemy.currentCellX; if (Math.abs(xDiff) > 0.1) { enemy.currentCellX += xDiff * 0.05; // Gradual movement to target } // Rotate enemy graphic to face downward var angle = Math.PI; // Face downward (180 degrees) if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update the cell position to track where the flying enemy is enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); // Update visual position enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // Check if enemy reached the bottom (goal) var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell && cell.type == 3) { // Flying enemy reached the goal - reduce lives by 10 (10% of 100 total lives) lives = Math.max(0, lives - 10); updateUI(); if (lives <= 0) { LK.showGameOver(); } return true; } // Update shadow position if this is a flying enemy return false; } // Handle normal pathfinding enemies if (enemy.type === 'fast') { // Fast enemies move straight down without pathfinding or turning enemy.currentCellY += enemy.speed; enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); // Set rotation to face straight down without animation if (enemy.children[0]) { enemy.children[0].rotation = Math.PI; // Face downward (180 degrees) enemy.children[0].targetRotation = Math.PI; } } else if (enemy.type === 'immune' && enemy.waveNumber === 2 && !enemy.hasReachedSecondTurn && enemy.currentCellY >= 15) { // Wave 2 immune enemies get speed boost when they reach the second turn (y >= 15) enemy.hasReachedSecondTurn = true; enemy.speed = 2.4 * 0.02; // Set speed to 2.4 times base speed (0.02) } else if (enemy.type === 'tank') { // Tank enemies move to upper quarter vertically and stop there, staying at their spawned x-position var upperQuarterY = (29 + 6) * 0.25; // Upper quarter of map (around row 8.75) var supportPositionY = Math.max(8, upperQuarterY); // Stop at upper quarter // Initialize tank target position if not set if (enemy.tankTargetX === undefined) { enemy.tankTargetX = enemy.currentCellX; // Store spawned x-position enemy.tankTargetY = supportPositionY; // Store target support position enemy.tankMovementComplete = false; } // Use smooth interpolation movement instead of tween animations if (!enemy.tankMovementComplete && enemy.currentCellY >= 4) { // Calculate distance to target var dx = enemy.tankTargetX - enemy.currentCellX; var dy = enemy.tankTargetY - enemy.currentCellY; var distanceToTarget = Math.sqrt(dx * dx + dy * dy); // Check if we've reached the target (within 0.1 cells) if (distanceToTarget < 0.1) { enemy.currentCellX = enemy.tankTargetX; enemy.currentCellY = enemy.tankTargetY; enemy.tankMovementComplete = true; } else { // Use smooth interpolation movement var speed = 0.04; // Movement speed in cells per frame var t = Math.min(1, speed / distanceToTarget); enemy.currentCellX = enemy.currentCellX + (enemy.tankTargetX - enemy.currentCellX) * t; enemy.currentCellY = enemy.currentCellY + (enemy.tankTargetY - enemy.currentCellY) * t; } // Tank enemies do not rotate - keep original rotation } else { // Tank has reached support position - stop moving and face downward to support convoy // Lock tank position at target location enemy.currentCellX = enemy.tankTargetX; // Stay at spawned x-position enemy.currentCellY = enemy.tankTargetY; // Lock at support position // Tank enemies do not rotate - keep original rotation } enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); } else { // Handle normal pathfinding enemies if (!enemy.currentTarget && cell && cell.targets && cell.targets.length > 0) { enemy.currentTarget = cell.targets[0]; } if (enemy.currentTarget) { // Check if current cell has a better target (closer to goal) if (cell && cell.targets && cell.targets.length > 0 && cell.score < enemy.currentTarget.score) { enemy.currentTarget = cell.targets[0]; } var ox = enemy.currentTarget.x - enemy.currentCellX; var oy = enemy.currentTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { // Speed boost logic removed to prevent any tank speed boost effects enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentTarget = undefined; return; } var angle = Math.atan2(oy, ox) + Math.PI / 2; enemy.currentCellX += Math.cos(angle - Math.PI / 2) * enemy.speed; enemy.currentCellY += Math.sin(angle - Math.PI / 2) * enemy.speed; } else if (cell && cell.targets && cell.targets.length > 0) { // If no current target, pick the first available target from current cell enemy.currentTarget = cell.targets[0]; } } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; }; }); var NextWaveButton = Container.expand(function () { var self = Container.call(this); var buttonBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 300; buttonBackground.height = 100; buttonBackground.tint = 0x0088FF; var buttonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.enabled = false; self.visible = false; self.update = function () { if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) { self.enabled = true; self.visible = true; buttonBackground.tint = 0x0088FF; self.alpha = 1; } else { self.enabled = false; self.visible = false; buttonBackground.tint = 0x888888; self.alpha = 0.7; } }; self.down = function () { if (!self.enabled) { return; } if (waveIndicator.gameStarted && currentWave < totalWaves) { currentWave++; // Increment to the next wave directly waveTimer = 0; // Reset wave timer waveInProgress = true; waveSpawned = false; // Get the type of the current wave (which is now the next wave) var waveType = waveIndicator.getWaveTypeName(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 30; self.addChild(notificationText); self.alpha = 1; var fadeOutTime = 120; self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'trap'; // Increase size of base for easier touch var baseGraphics = self.attachAsset('Box', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); switch (self.towerType) { case 'landmine': baseGraphics.tint = 0x9B8B6D; // Lighter tan/brown camouflage break; case 'sniper': baseGraphics.tint = 0x7A9B5F; // Lighter olive green camouflage break; case 'flamethrower': baseGraphics.tint = 0xBBA385; //{aO} // Lighter sandy brown camouflage break; case 'slow': baseGraphics.tint = 0x7F7F5F; //{aR} // Lighter olive drab camouflage break; case 'poison': baseGraphics.tint = 0x969696; //{aU} // Lighter gray camouflage break; case 'tank': baseGraphics.tint = 0x006400; // Dark green for tank break; default: baseGraphics.tint = 0x8C8C77; //{aX} // Lighter neutral camouflage green-gray } var towerCost = getTowerCost(self.towerType); // Add shadow for tower type label var displayTextShadow = self.towerType === 'flamethrower' ? 'Flame' : self.towerType === 'trap' ? 'Shooter' : self.towerType === 'slow' ? 'Missiles' : self.towerType === 'tank' ? 'Tank' : self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1); var typeLabelShadow = new Text2(displayTextShadow, { size: 50, fill: 0x000000, weight: 800 }); typeLabelShadow.anchor.set(0.5, 0.5); typeLabelShadow.x = 4; typeLabelShadow.y = -20 + 4; self.addChild(typeLabelShadow); // Add tower type label var displayText = self.towerType === 'flamethrower' ? 'Flame' : self.towerType === 'trap' ? 'Shooter' : self.towerType === 'slow' ? 'Missiles' : self.towerType === 'tank' ? 'Tank' : self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1); var typeLabel = new Text2(displayText, { size: 50, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.y = -20; // Position above center of tower self.addChild(typeLabel); // Add cost shadow var costLabelShadow = new Text2(towerCost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; costLabelShadow.y = 24 + 12; self.addChild(costLabelShadow); // Add cost label var costLabel = new Text2(towerCost, { size: 50, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.y = 20 + 12; self.addChild(costLabel); self.update = function () { // Check if player can afford this tower var canAfford = gold >= getTowerCost(self.towerType); // Set opacity based on affordability self.alpha = canAfford ? 1 : 0.5; }; return self; }); var Tower = Container.expand(function (id) { var self = Container.call(this); self.id = id || 'trap'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.range = 3 * CELL_SIZE; // Standardized method to get the current range of the tower self.getRange = function () { // Always calculate range based on tower type and level switch (self.id) { case 'sniper': // Sniper: massive range to cover entire map from any position if (self.level === self.maxLevel) { return 75 * CELL_SIZE; // Extreme range for max level to cover entire map (1.5x increase) } return (22.5 + (self.level - 1) * 7.5) * CELL_SIZE; // Increased base and scaling (1.5x increase) case 'flamethrower': // Flamethrower: base 4.5, +0.5 per level for cone range (increased aiming distance) return (4.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'landmine': // Landmine: base 2.5, +0.5 per level return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'slow': // Slow: base 8.75, +1.25 per level (2.5x increase) return (8.75 + (self.level - 1) * 1.25) * CELL_SIZE; case 'poison': // Poison: base 3.2, +0.5 per level return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; case 'tank': // Tank: base 14.0, +2.8 per level (3.5x increase) return (14.0 + (self.level - 1) * 2.8) * CELL_SIZE; default: // Trap: base 6, +1.0 per level (double range) return (6 + (self.level - 1) * 1.0) * CELL_SIZE; } }; self.cellsInRange = []; self.fireRate = 60; self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; // Continuous shooting variables for flamethrower self.isContinuousFiring = false; self.continuousFireTimer = 0; self.continuousFireDuration = 300; // 5 seconds at 60 FPS self.continuousReloadTimer = 0; self.continuousReloadDuration = 180; // 3 seconds at 60 FPS switch (self.id) { case 'landmine': self.fireRate = 30; self.damage = 5; self.range = 2.5 * CELL_SIZE; self.bulletSpeed = 7; break; case 'sniper': self.fireRate = 90; self.damage = 25; self.range = 5 * CELL_SIZE; self.bulletSpeed = 25; break; case 'flamethrower': self.fireRate = 2.5; // Very fast fire rate during continuous shooting (quarter of original delay) self.damage = 8; // Lower per-hit damage but continuous self.range = 2.5 * CELL_SIZE; self.bulletSpeed = 6; break; case 'slow': self.fireRate = 30; self.damage = 15; // Increased from 8 to 15 for stronger missiles self.range = 3.5 * CELL_SIZE; self.bulletSpeed = 5; break; case 'poison': self.fireRate = 70; self.damage = 12; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 5; break; case 'tank': self.fireRate = 90; self.damage = 50; // Increased base damage for tank self.range = 4.0 * CELL_SIZE; self.bulletSpeed = 4; break; } var assetToUse = self.id === 'landmine' ? 'landmine' : self.id === 'sniper' ? 'Sniper' : self.id === 'flamethrower' ? 'Flamethrower' : self.id === 'slow' ? 'Rocket' : self.id === 'trap' ? 'Shooter' : self.id === 'tank' ? 'Tank' : 'tower'; var baseGraphics = self.attachAsset(assetToUse, { anchorX: 0.5, anchorY: 0.5 }); // Flame effects removed for performance switch (self.id) { case 'landmine': // Remove tint to show natural landmine asset break; case 'sniper': // Remove tint to show natural sniper asset break; case 'flamethrower': // Remove tint to show natural flamethrower asset break; case 'slow': // Remove tint to show natural rocket asset break; case 'poison': baseGraphics.tint = 0x00FFAA; break; case 'tank': // Remove tint to show natural tank asset break; case 'trap': baseGraphics.tint = 0x555555; // Dark gray for machine gun break; default: baseGraphics.tint = 0xAAAAAA; } // Add health bar for towers that need them (excluding landmine) self.towerHealthBarOutline = null; self.towerHealthBarBG = null; self.towerHealthBar = null; if (self.id === 'flamethrower' || self.id === 'trap' || self.id === 'sniper' || self.id === 'slow' || self.id === 'tank') { // Create health bar outline self.towerHealthBarOutline = self.attachAsset('towerHealthBarOutline', { anchorX: 0, anchorY: 0.5 }); // Create health bar background (red) self.towerHealthBarBG = self.attachAsset('towerHealthBar', { anchorX: 0, anchorY: 0.5 }); self.towerHealthBarBG.tint = 0xff0000; // Create health bar foreground (green) self.towerHealthBar = self.attachAsset('towerHealthBar', { anchorX: 0, anchorY: 0.5 }); self.towerHealthBar.tint = 0x00ff00; // Position health bars below the level indicators to avoid overlap var healthBarY = CELL_SIZE * 0.7 + 25; // Position below level indicators with some spacing self.towerHealthBarOutline.y = healthBarY; self.towerHealthBarBG.y = healthBarY; self.towerHealthBar.y = healthBarY; // Center health bars horizontally self.towerHealthBarOutline.x = -self.towerHealthBarOutline.width / 2; self.towerHealthBarBG.x = -self.towerHealthBarBG.width / 2; self.towerHealthBar.x = -self.towerHealthBar.width / 2; } var levelIndicators = []; var maxDots = self.maxLevel; var dotSpacing = baseGraphics.width / (maxDots + 1); var dotSize = CELL_SIZE / 6; for (var i = 0; i < maxDots; i++) { var dot = new Container(); var outlineCircle = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); outlineCircle.width = dotSize + 4; outlineCircle.height = dotSize + 4; outlineCircle.tint = 0x000000; var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); towerLevelIndicator.width = dotSize; towerLevelIndicator.height = dotSize; towerLevelIndicator.tint = 0xCCCCCC; dot.x = -CELL_SIZE + dotSpacing * (i + 1); dot.y = CELL_SIZE * 0.7; self.addChild(dot); levelIndicators.push(dot); } var gunContainer = new Container(); self.addChild(gunContainer); self.updateLevelIndicators = function () { for (var i = 0; i < maxDots; i++) { var dot = levelIndicators[i]; var towerLevelIndicator = dot.children[1]; if (i < self.level) { towerLevelIndicator.tint = 0xFFFFFF; } else { switch (self.id) { case 'landmine': towerLevelIndicator.tint = 0x00AAFF; break; case 'sniper': towerLevelIndicator.tint = 0xFF5500; break; case 'flamethrower': towerLevelIndicator.tint = 0xFF4500; break; case 'slow': towerLevelIndicator.tint = 0x9900FF; break; case 'poison': towerLevelIndicator.tint = 0x00FFAA; break; default: towerLevelIndicator.tint = 0xAAAAAA; } } } }; self.updateLevelIndicators(); self.refreshCellsInRange = function () { for (var i = 0; i < self.cellsInRange.length; i++) { var cell = self.cellsInRange[i]; var towerIndex = cell.towersInRange.indexOf(self); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } self.cellsInRange = []; var rangeRadius = self.getRange() / CELL_SIZE; var centerX = self.gridX + 1; var centerY = self.gridY + 1; var minI = Math.floor(centerX - rangeRadius - 0.5); var maxI = Math.ceil(centerX + rangeRadius + 0.5); var minJ = Math.floor(centerY - rangeRadius - 0.5); var maxJ = Math.ceil(centerY + rangeRadius + 0.5); for (var i = minI; i <= maxI; i++) { for (var j = minJ; j <= maxJ; j++) { var closestX = Math.max(i, Math.min(centerX, i + 1)); var closestY = Math.max(j, Math.min(centerY, j + 1)); var deltaX = closestX - centerX; var deltaY = closestY - centerY; var distanceSquared = deltaX * deltaX + deltaY * deltaY; if (distanceSquared <= rangeRadius * rangeRadius) { var cell = grid.getCell(i, j); if (cell) { self.cellsInRange.push(cell); cell.towersInRange.push(self); } } } } // Only render debug when tower is selected to improve performance if (selectedTower === self) { grid.renderDebug(); } }; self.getTotalValue = function () { var baseTowerCost = getTowerCost(self.id); var totalInvestment = baseTowerCost; var baseUpgradeCost = baseTowerCost; // Upgrade cost now scales with base tower cost for (var i = 1; i < self.level; i++) { totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1)); } return totalInvestment; }; self.upgrade = function () { if (self.level < self.maxLevel) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.id); var upgradeCost; // Make last upgrade level extra expensive if (self.level === self.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); // Half the cost for final upgrade } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1)); } if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; // No need to update self.range here; getRange() is now the source of truth // Apply tower-specific upgrades based on type if (self.id === 'landmine') { if (self.level === self.maxLevel) { // Extra powerful last upgrade (double the effect) self.fireRate = Math.max(4, 30 - self.level * 9); // double the effect self.damage = 5 + self.level * 10; // double the effect self.bulletSpeed = 7 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(15, 30 - self.level * 3); // Fast tower gets faster with upgrades self.damage = 5 + self.level * 3; self.bulletSpeed = 7 + self.level * 0.7; } } else { // Special scaling for missiles towers if (self.id === 'slow') { if (self.level === self.maxLevel) { // Extra powerful last upgrade for missiles (triple the effect) self.fireRate = Math.max(3, 30 - self.level * 15); // Much faster fire rate self.damage = 15 + self.level * 30; // Much higher damage scaling self.bulletSpeed = 5 + self.level * 3; // Faster missiles } else { self.fireRate = Math.max(10, 30 - self.level * 5); // Faster than base scaling self.damage = 15 + self.level * 10; // Higher damage per level self.bulletSpeed = 5 + self.level * 1; } } else { if (self.level === self.maxLevel) { // Extra powerful last upgrade for all other towers (double the effect) self.fireRate = Math.max(5, 60 - self.level * 24); // double the effect self.damage = 10 + self.level * 20; // double the effect self.bulletSpeed = 5 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(20, 60 - self.level * 8); self.damage = 10 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.5; } } } self.refreshCellsInRange(); self.updateLevelIndicators(); if (self.level > 1) { var levelDot = levelIndicators[self.level - 1].children[1]; tween(levelDot, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut, onFinish: function onFinish() { tween(levelDot, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); } return true; } else { var notification = game.addChild(new Notification("Not enough gold to upgrade!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } return false; }; self.findTarget = function () { var closestEnemy = null; var closestScore = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if enemy is in range if (distance <= self.getRange()) { // Handle flying enemies differently - they can be targeted regardless of path if (enemy.isFlying) { // For flying enemies, prioritize by distance to the goal if (enemy.flyingTarget) { var goalX = enemy.flyingTarget.x; var goalY = enemy.flyingTarget.y; var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY)); // Use distance to goal as score if (distToGoal < closestScore) { closestScore = distToGoal; closestEnemy = enemy; } } else { // If no flying target yet (shouldn't happen), prioritize by distance to tower if (distance < closestScore) { closestScore = distance; closestEnemy = enemy; } } } else { // For ground enemies, landmine towers target any ground enemy in range if (self.id === 'landmine') { // Landmine targets any ground enemy by distance if (distance < closestScore) { closestScore = distance; closestEnemy = enemy; } } else { // For other towers, use the original path-based targeting // Get the cell for this enemy var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell && cell.pathId === pathId) { // Use the cell's score (distance to exit) for prioritization // Lower score means closer to exit if (cell.score < closestScore) { closestScore = cell.score; closestEnemy = enemy; } } } } } } if (!closestEnemy) { self.targetEnemy = null; } return closestEnemy; }; self.update = function () { // Only find targets every 3 frames to reduce performance impact if (LK.ticks % 3 === 0) { self.targetEnemy = self.findTarget(); } // Flame animation removed for flamethrower towers // Special handling for landmine towers - explode on contact if (self.id === 'landmine' && self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if enemy is close enough to trigger explosion (within 50 pixels) and is ground-based if (distance <= 50 && !self.targetEnemy.isFlying) { // Create landmine explosion effect with enhanced animation var landmineExplosion = new EffectIndicator(self.x, self.y, 'splash'); landmineExplosion.children[0].tint = 0xFF4500; // Orange explosion color landmineExplosion.children[0].width = landmineExplosion.children[0].height = CELL_SIZE * 2.5; landmineExplosion.scaleX = 0.3; landmineExplosion.scaleY = 0.3; landmineExplosion.alpha = 1; game.addChild(landmineExplosion); // Multi-stage landmine explosion animation tween(landmineExplosion, { scaleX: 1.8, scaleY: 1.8, alpha: 0.9 }, { duration: 150, easing: tween.elasticOut, onFinish: function onFinish() { tween(landmineExplosion, { scaleX: 2.5, scaleY: 2.5, alpha: 0 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { landmineExplosion.destroy(); } }); } }); // Create enemy explosion effect with enhanced animation var enemyExplosion = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); enemyExplosion.children[0].tint = 0xFF0000; // Red explosion color enemyExplosion.children[0].width = enemyExplosion.children[0].height = CELL_SIZE * 2; enemyExplosion.scaleX = 0.2; enemyExplosion.scaleY = 0.2; enemyExplosion.alpha = 1; game.addChild(enemyExplosion); // Multi-stage enemy explosion animation tween(enemyExplosion, { scaleX: 1.5, scaleY: 1.5, alpha: 0.8 }, { duration: 120, easing: tween.bounceOut, onFinish: function onFinish() { tween(enemyExplosion, { scaleX: 2.2, scaleY: 2.2, alpha: 0 }, { duration: 250, easing: tween.easeOut, onFinish: function onFinish() { enemyExplosion.destroy(); } }); } }); // Create additional shockwave effect for landmine var shockwave = new EffectIndicator(self.x, self.y, 'splash'); shockwave.children[0].tint = 0xFFFFFF; shockwave.children[0].alpha = 0.3; shockwave.children[0].width = shockwave.children[0].height = CELL_SIZE * 0.5; shockwave.alpha = 0.8; game.addChild(shockwave); // Animate shockwave expanding rapidly tween(shockwave, { scaleX: 4, scaleY: 4, alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { shockwave.destroy(); } }); // Mark enemy for destruction self.targetEnemy.health = 0; // Mark landmine tower for destruction self.shouldDestroy = true; return; } } if (self.targetEnemy && self.id !== 'landmine') { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); // Special rotation handling for sniper towers if (self.id === 'sniper') { // Use the main tower graphics (baseGraphics) for sniper rotation var sniperGraphics = self.children[0]; // The sniper asset if (sniperGraphics.targetRotation === undefined) { sniperGraphics.targetRotation = angle; sniperGraphics.rotation = angle; } else { if (Math.abs(angle - sniperGraphics.targetRotation) > 0.1) { // Stop any ongoing rotation tweens tween.stop(sniperGraphics, { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = sniperGraphics.rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } sniperGraphics.targetRotation = angle; tween(sniperGraphics, { rotation: currentRotation + angleDiff }, { duration: 200, easing: tween.easeOut }); } } } else { gunContainer.rotation = angle; } // Special continuous firing logic for flamethrower if (self.id === 'flamethrower') { // Handle continuous reload timer if (self.continuousReloadTimer > 0) { self.continuousReloadTimer--; } else if (!self.isContinuousFiring && self.targetEnemy) { // Start continuous firing self.isContinuousFiring = true; self.continuousFireTimer = self.continuousFireDuration; } else if (self.isContinuousFiring) { // Continue firing during the continuous period if (self.continuousFireTimer > 0) { self.continuousFireTimer--; // Fire at the fast rate during continuous shooting if (LK.ticks - self.lastFired >= self.fireRate) { self.fire(); self.lastFired = LK.ticks; } } else { // End continuous firing, start reload self.isContinuousFiring = false; self.continuousReloadTimer = self.continuousReloadDuration; } } } else { // Normal firing logic for other tower types if (LK.ticks - self.lastFired >= self.fireRate) { self.fire(); self.lastFired = LK.ticks; } } } }; self.down = function (x, y, obj) { var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); var hasOwnMenu = false; var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self) { rangeCircle = game.children[i]; break; } } for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hasOwnMenu = true; break; } } if (hasOwnMenu) { for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } if (rangeCircle) { game.removeChild(rangeCircle); } selectedTower = null; grid.renderDebug(); return; } for (var i = 0; i < existingMenus.length; i++) { existingMenus[i].destroy(); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = self; var rangeIndicator = new Container(); rangeIndicator.isTowerRange = true; rangeIndicator.tower = self; game.addChild(rangeIndicator); rangeIndicator.x = self.x; rangeIndicator.y = self.y; var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.getRange() * 2; rangeGraphics.alpha = 0.3; var upgradeMenu = new UpgradeMenu(self); game.addChild(upgradeMenu); upgradeMenu.x = 2048 / 2; tween(upgradeMenu, { y: 2732 - 225 }, { duration: 200, easing: tween.backOut }); grid.renderDebug(); }; self.isInRange = function (enemy) { if (!enemy) { return false; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance <= self.getRange(); }; // Initialize tower health based on tower type if (self.id === 'flamethrower' || self.id === 'trap' || self.id === 'sniper' || self.id === 'slow' || self.id === 'tank') { // Set health values based on tower type if (self.id === 'slow') { self.towerHealth = 300; // 3x base health for missiles self.maxTowerHealth = 300; } else if (self.id === 'tank') { self.towerHealth = 500; // 5x base health for tank towers - very strong self.maxTowerHealth = 500; } else if (self.id === 'sniper') { self.towerHealth = 150; // Medium health for sniper self.maxTowerHealth = 150; } else if (self.id === 'flamethrower') { self.towerHealth = 120; // Medium health for flamethrower self.maxTowerHealth = 120; } else { self.towerHealth = 100; // Base tower health for trap/shooter self.maxTowerHealth = 100; } } // Method to handle damage from enemy bullets self.takeDamage = function (damage) { // Special handling for sniper towers - track hits instead of health if (self.id === 'sniper') { // Initialize hit counter if not set if (self.hitCount === undefined) { self.hitCount = 0; } self.hitCount++; // Create hit indicator effect var hitEffect = new EffectIndicator(self.x, self.y, 'sniper'); hitEffect.children[0].tint = 0xFF0000; // Red hit indicator hitEffect.children[0].width = hitEffect.children[0].height = CELL_SIZE; game.addChild(hitEffect); // Update health bar if (self.towerHealthBar) { var healthPercentage = Math.max(0, (5 - self.hitCount) / 5); self.towerHealthBar.width = 60 * healthPercentage; } // Destroy sniper after 5 hits if (self.hitCount >= 5) { // Mark sniper for destruction self.shouldDestroy = true; // Create destruction effect var destructionEffect = new EffectIndicator(self.x, self.y, 'splash'); destructionEffect.children[0].tint = 0xFF4500; // Orange destruction color destructionEffect.children[0].width = destructionEffect.children[0].height = CELL_SIZE * 2; game.addChild(destructionEffect); } } else { // Initialize tower health if not set for non-sniper towers if (self.towerHealth === undefined) { // Missiles towers have much higher health to make them stronger if (self.id === 'slow') { self.towerHealth = 300; // 3x base health for missiles self.maxTowerHealth = 300; } else if (self.id === 'tank') { self.towerHealth = 500; // 5x base health for tank towers - very strong self.maxTowerHealth = 500; } else { self.towerHealth = 100; // Base tower health self.maxTowerHealth = 100; } } self.towerHealth -= damage; // Update health bar if (self.towerHealthBar && self.maxTowerHealth > 0) { var healthPercentage = Math.max(0, self.towerHealth / self.maxTowerHealth); self.towerHealthBar.width = 60 * healthPercentage; } if (self.towerHealth <= 0) { self.towerHealth = 0; // Mark tower for destruction self.shouldDestroy = true; // Create destruction effect var destructionEffect = new EffectIndicator(self.x, self.y, 'splash'); destructionEffect.children[0].tint = 0xFF4500; // Orange destruction color destructionEffect.children[0].width = destructionEffect.children[0].height = CELL_SIZE * 2; game.addChild(destructionEffect); } } }; self.fire = function () { // Landmine towers cannot shoot normally if (self.id === 'landmine') { return; } // Machine gun shooting function if (self.id === 'trap') { if (self.targetEnemy) { // Fire multiple bullets in rapid succession for higher ammunition density var bulletsToFire = 3; // Fire 3 bullets per shot for higher density for (var bulletIndex = 0; bulletIndex < bulletsToFire; bulletIndex++) { var bulletX = self.x + Math.cos(gunContainer.rotation) * 40; var bulletY = self.y + Math.sin(gunContainer.rotation) * 40; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); // Set bullet type for machine gun bullet.type = 'trap'; // Machine gun bullets are smaller and faster bullet.children[0].tint = 0x555555; // Dark gray for machine gun bullets bullet.children[0].width = 25; bullet.children[0].height = 25; // Add slight delay between bullets for visual effect if (bulletIndex > 0) { bullet.x += (Math.random() - 0.5) * 10; // Small random offset bullet.y += (Math.random() - 0.5) * 10; } game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); } // Machine gun recoil effect tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); if (gunContainer._restX === undefined) { gunContainer._restX = 0; } if (gunContainer._restY === undefined) { gunContainer._restY = 0; } if (gunContainer._restScaleX === undefined) { gunContainer._restScaleX = 1; } if (gunContainer._restScaleY === undefined) { gunContainer._restScaleY = 1; } gunContainer.x = gunContainer._restX; gunContainer.y = gunContainer._restY; gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; var recoilDistance = 6; var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance; var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance; tween(gunContainer, { x: gunContainer._restX + recoilX, y: gunContainer._restY + recoilY }, { duration: 40, easing: tween.cubicOut, onFinish: function onFinish() { tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: 60, easing: tween.cubicIn }); } }); } return; } if (self.targetEnemy) { var potentialDamage = 0; for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) { potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage; } if (self.targetEnemy.health > potentialDamage) { // Special handling for flamethrower - create continuous flame spray if (self.id === 'flamethrower') { // Create multiple flame particles for continuous spray effect var flamesToCreate = 3; // Create multiple flames for denser spray for (var flameIndex = 0; flameIndex < flamesToCreate; flameIndex++) { var flameParticle = new Container(); var flameGraphics = flameParticle.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); flameGraphics.width = 25 + Math.random() * 15; flameGraphics.height = 35 + Math.random() * 20; flameGraphics.tint = Math.random() > 0.5 ? 0xFF4500 : 0xFF6600; flameGraphics.alpha = 0.7 + Math.random() * 0.3; // Position flame particle at tower with slight spread var spreadAngle = (Math.random() - 0.5) * 0.4; // 20 degree spread var startAngle = gunContainer.rotation + spreadAngle; var startDistance = 30 + Math.random() * 10; flameParticle.x = self.x + Math.cos(startAngle) * startDistance; flameParticle.y = self.y + Math.sin(startAngle) * startDistance; // Calculate target position with spread var targetDistance = self.getRange() * (0.6 + Math.random() * 0.4); var targetX = self.x + Math.cos(startAngle) * targetDistance; var targetY = self.y + Math.sin(startAngle) * targetDistance; // Store damage info for area effect flameParticle.damage = self.damage; flameParticle.sourceTowerLevel = self.level; game.addChild(flameParticle); // Animate flame particle moving toward target area tween(flameParticle, { x: targetX, y: targetY, scaleX: 1.5, scaleY: 1.5 }, { duration: 400 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { // Apply area damage when flame reaches target area var coneRange = CELL_SIZE * 1.2; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - flameParticle.x; var dy = enemy.y - flameParticle.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= coneRange) { // Apply flame damage enemy.health -= flameParticle.damage * 0.8; if (enemy.health <= 0) { enemy.health = 0; } else { enemy.healthBar.width = enemy.health / enemy.maxHealth * 70; } // Apply burning effect if (!enemy.isImmune) { enemy.burning = true; enemy.burnDamage = flameParticle.damage * 0.3; enemy.burnDuration = 180; } // Create visual flame effect var flameEffect = new EffectIndicator(enemy.x, enemy.y, 'flamethrower'); game.addChild(flameEffect); } } // Fade out flame particle tween(flameParticle, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { flameParticle.destroy(); } }); } }); } } else { // Regular bullet creation for other tower types var bulletX = self.x + Math.cos(gunContainer.rotation) * 40; var bulletY = self.y + Math.sin(gunContainer.rotation) * 40; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); // Set bullet type based on tower type bullet.type = self.id; // For slow tower, pass level for scaling slow effect if (self.id === 'slow') { bullet.sourceTowerLevel = self.level; } // Customize bullet appearance based on tower type switch (self.id) { case 'sniper': bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; case 'slow': // Replace bullet graphics with missile asset var missileGraphics = bullet.attachAsset('missiles', { anchorX: 0.5, anchorY: 0.5 }); // Remove the original bullet graphics bullet.removeChild(bullet.children[0]); break; case 'poison': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'tank': bullet.children[0].tint = 0x8B4513; bullet.children[0].width = 40; bullet.children[0].height = 40; break; } game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); } // --- Fire recoil effect for gunContainer --- // Stop any ongoing recoil tweens before starting a new one tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); // Always use the original resting position for recoil, never accumulate offset if (gunContainer._restX === undefined) { gunContainer._restX = 0; } if (gunContainer._restY === undefined) { gunContainer._restY = 0; } if (gunContainer._restScaleX === undefined) { gunContainer._restScaleX = 1; } if (gunContainer._restScaleY === undefined) { gunContainer._restScaleY = 1; } // Reset to resting position before animating (in case of interrupted tweens) gunContainer.x = gunContainer._restX; gunContainer.y = gunContainer._restY; gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; // Calculate recoil offset (recoil back along the gun's rotation) var recoilDistance = 8; var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance; var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance; // Animate recoil back from the resting position tween(gunContainer, { x: gunContainer._restX + recoilX, y: gunContainer._restY + recoilY }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { // Animate return to original position/scale tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: 90, easing: tween.cubicIn }); } }); } } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { // Store original type before placing tower cell.originalType = cell.type; // Allow placement on any cell type - don't force to wall type } } } self.refreshCellsInRange(); }; return self; }); var TowerPreview = Container.expand(function () { var self = Container.call(this); var towerRange = 3; var rangeInPixels = towerRange * CELL_SIZE; self.towerType = 'trap'; self.hasEnoughGold = true; var rangeIndicator = new Container(); self.addChild(rangeIndicator); var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.alpha = 0.3; var previewGraphics = self.attachAsset('Box', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; self.canPlace = false; self.gridX = 0; self.gridY = 0; self.blockedByEnemy = false; var updateCounter = 0; self.update = function () { // Only check every 10 frames to reduce performance impact updateCounter++; if (updateCounter % 10 === 0) { var previousHasEnoughGold = self.hasEnoughGold; self.hasEnoughGold = gold >= getTowerCost(self.towerType); // Only update appearance if the affordability status has changed if (previousHasEnoughGold !== self.hasEnoughGold) { self.updateAppearance(); } } }; self.updateAppearance = function () { // Use Tower class to get the source of truth for range var tempTower = new Tower(self.towerType); var previewRange = tempTower.getRange(); // Clean up tempTower to avoid memory leaks if (tempTower && tempTower.destroy) { tempTower.destroy(); } // Set range indicator using unified range logic rangeGraphics.width = rangeGraphics.height = previewRange * 2; // Color preview box based on placement validity if (self.canPlace && self.hasEnoughGold) { // Green for valid placement (including tank towers) previewGraphics.tint = 0x00FF00; // Animate green pulsing for valid placement tween.stop(previewGraphics, { alpha: true }); tween(previewGraphics, { alpha: 0.7 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(previewGraphics, { alpha: 1.0 }, { duration: 500, easing: tween.easeInOut }); } }); } else { // Red for invalid placement or insufficient gold previewGraphics.tint = 0xFF0000; // Stop any ongoing animations for invalid placement tween.stop(previewGraphics, { alpha: true }); previewGraphics.alpha = 1.0; } }; self.updatePlacementStatus = function () { var validGridPlacement = true; // Prevent placement in upper sixth of screen (y < 455) var screenY = grid.y + self.gridY * CELL_SIZE; if (screenY < 455) { validGridPlacement = false; } else { // For tank weapons, only allow placement in lower half of screen if (self.towerType === 'tank') { var screenCenterY = grid.y + (29 + 6) * CELL_SIZE / 2; // Grid height / 2 if (screenY < screenCenterY) { validGridPlacement = false; } } // Allow placement anywhere else on the grid for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(self.gridX + i, self.gridY + j); if (!cell) { validGridPlacement = false; break; } } if (!validGridPlacement) { break; } } } self.blockedByEnemy = false; if (validGridPlacement) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.currentCellY < 4) { continue; } // Only check non-flying enemies, flying enemies can pass over towers if (!enemy.isFlying) { if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) { self.blockedByEnemy = true; break; } if (enemy.currentTarget) { var targetX = enemy.currentTarget.x; var targetY = enemy.currentTarget.y; if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) { self.blockedByEnemy = true; break; } } } } } self.canPlace = validGridPlacement && !self.blockedByEnemy; self.hasEnoughGold = gold >= getTowerCost(self.towerType); self.updateAppearance(); }; self.checkPlacement = function () { self.updatePlacementStatus(); }; self.snapToGrid = function (x, y) { var gridPosX = x - grid.x; var gridPosY = y - grid.y; self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; self.checkPlacement(); }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 500; menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; var towerTypeName = self.tower.id === 'slow' ? 'Missiles' : self.tower.id === 'tank' ? 'Tank' : self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1); var towerTypeText = new Text2(towerTypeName + ' Tower', { size: 80, fill: 0xFFFFFF, weight: 800 }); towerTypeText.anchor.set(0, 0); towerTypeText.x = -840; towerTypeText.y = -160; self.addChild(towerTypeText); var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', { size: 70, fill: 0xFFFFFF, weight: 400 }); statsText.anchor.set(0, 0.5); statsText.x = -840; statsText.y = 50; self.addChild(statsText); var buttonsContainer = new Container(); buttonsContainer.x = 500; self.addChild(buttonsContainer); var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; buttonBackground.height = 150; var isMaxLevel = self.tower.level >= self.tower.maxLevel; // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var upgradeCost; if (isMaxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888; var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; sellButtonBackground.height = 150; sellButtonBackground.tint = 0xCC0000; var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); upgradeButton.y = -85; sellButton.y = 85; var closeButton = new Container(); self.addChild(closeButton); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; upgradeButton.down = function (x, y, obj) { if (self.tower.level >= self.tower.maxLevel) { var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (self.tower.upgrade()) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); if (self.tower.level >= self.tower.maxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s'); buttonText.setText('Upgrade: ' + upgradeCost + ' gold'); var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); sellButtonText.setText('Sell: +' + sellValue + ' gold'); if (self.tower.level >= self.tower.maxLevel) { buttonBackground.tint = 0x888888; buttonText.setText('Max Level'); } var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { rangeCircle = game.children[i]; break; } } if (rangeCircle) { var rangeGraphics = rangeCircle.children[0]; rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; } else { var newRangeIndicator = new Container(); newRangeIndicator.isTowerRange = true; newRangeIndicator.tower = self.tower; game.addChildAt(newRangeIndicator, 0); newRangeIndicator.x = self.tower.x; newRangeIndicator.y = self.tower.y; var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; rangeGraphics.alpha = 0.3; } tween(self, { scaleX: 1.05, scaleY: 1.05 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn }); } }); } }; sellButton.down = function (x, y, obj) { var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); setGold(gold + sellValue); var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; var gridX = self.tower.gridX; var gridY = self.tower.gridY; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { // Restore original cell type or default to ground (type 1) if no original type stored cell.type = cell.originalType !== undefined ? cell.originalType : 1; var towerIndex = cell.towersInRange.indexOf(self.tower); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } if (selectedTower === self.tower) { selectedTower = null; } var towerIndex = towers.indexOf(self.tower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } towerLayer.removeChild(self.tower); grid.pathFind(); grid.renderDebug(); self.destroy(); for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } }; closeButton.down = function (x, y, obj) { hideUpgradeMenu(self); selectedTower = null; grid.renderDebug(); }; self.update = function () { if (self.tower.level >= self.tower.maxLevel) { if (buttonText.text !== 'Max Level') { buttonText.setText('Max Level'); buttonBackground.tint = 0x888888; } return; } // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var currentUpgradeCost; if (self.tower.level >= self.tower.maxLevel) { currentUpgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } var canAfford = gold >= currentUpgradeCost; buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888; var newText = 'Upgrade: ' + currentUpgradeCost + ' gold'; if (buttonText.text !== newText) { buttonText.setText(newText); } }; return self; }); var WaveIndicator = Container.expand(function () { var self = Container.call(this); self.gameStarted = false; self.waveMarkers = []; self.waveTypes = []; self.enemyCounts = []; self.indicatorWidth = 0; self.lastBossType = null; // Track the last boss type to avoid repeating var blockWidth = 400; var totalBlocksWidth = blockWidth * totalWaves; var startMarker = new Container(); var startBlock = startMarker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); startBlock.width = blockWidth - 10; startBlock.height = 70 * 2; startBlock.tint = 0x00AA00; // Add shadow for start text var startTextShadow = new Text2("Start Game", { size: 50, fill: 0x000000, weight: 800 }); startTextShadow.anchor.set(0.5, 0.5); startTextShadow.x = 4; startTextShadow.y = 4; startMarker.addChild(startTextShadow); var startText = new Text2("Start Game", { size: 50, fill: 0xFFFFFF, weight: 800 }); startText.anchor.set(0.5, 0.5); startMarker.addChild(startText); startMarker.x = -self.indicatorWidth; self.addChild(startMarker); self.waveMarkers.push(startMarker); startMarker.down = function () { if (!self.gameStarted) { self.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; startBlock.tint = 0x00FF00; startText.setText("Started!"); startTextShadow.setText("Started!"); // Make sure shadow position remains correct after text change startTextShadow.x = 4; startTextShadow.y = 4; var notification = game.addChild(new Notification("Game started! Wave 1 incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; for (var i = 0; i < totalWaves; i++) { var marker = new Container(); var block = marker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth - 10; block.height = 70 * 2; // --- Begin new unified wave logic --- var waveType = "normal"; var enemyType = "normal"; var enemyCount = 10; var isBossWave = i + 1 === totalWaves; // Ensure all types appear in early waves if (i === 0) { block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if (i === 1) { block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; enemyCount = 2; } else if (i === 2) { block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 3; } else if (i === 3) { block.tint = 0x888888; waveType = "Tank"; enemyType = "tank"; enemyCount = 1; } else if (i === 4) { block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if (i === 8) { // Wave 9 - Immune block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; enemyCount = 2; } else if (i === 9) { // Wave 10 - Flying block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 3; } else if (i === 10) { // Wave 11 - Tank (changed from immune) block.tint = 0x888888; waveType = "Tank"; enemyType = "tank"; enemyCount = 1; } else if (isBossWave) { // Boss waves: cycle through all boss types, last boss is always flying var bossTypes = ['fast', 'immune', 'flying']; var bossTypeIndex = Math.floor((i + 1) / 10) - 1; if (i === totalWaves - 1) { // Last boss is always flying enemyType = 'flying'; waveType = "Boss Flying"; block.tint = 0xFFFF00; } else { enemyType = bossTypes[bossTypeIndex % bossTypes.length]; switch (enemyType) { case 'fast': block.tint = 0x00AAFF; waveType = "Boss Fast"; break; case 'immune': block.tint = 0xAA0000; waveType = "Boss Immune"; break; case 'flying': block.tint = 0xFFFF00; waveType = "Boss Flying"; break; } } enemyCount = 1; // Make the wave indicator for boss waves stand out // Set boss wave color to the color of the wave type switch (enemyType) { case 'fast': block.tint = 0x00AAFF; break; case 'immune': block.tint = 0xAA0000; break; case 'flying': block.tint = 0xFFFF00; break; default: block.tint = 0xFF0000; break; } } else if ((i + 1) % 5 === 0) { // Every 5th non-boss wave is fast block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if ((i + 1) % 4 === 0) { // Every 4th non-boss wave is tank block.tint = 0x888888; waveType = "Tank"; enemyType = "tank"; enemyCount = 1; } else if ((i + 1) % 7 === 0) { // Every 7th non-boss wave is flying block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 3; } else if ((i + 1) % 3 === 0) { // Every 3rd non-boss wave is fast block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else { block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } // --- End new unified wave logic --- // Mark boss waves with a special visual indicator if (isBossWave) { // Add a crown or some indicator to the wave marker for boss waves var bossIndicator = marker.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); bossIndicator.width = 30; bossIndicator.height = 30; bossIndicator.tint = 0xFFD700; // Gold color bossIndicator.y = -block.height / 2 - 15; // Change the wave type text to indicate boss waveType = "BOSS"; } // Store the wave type and enemy count self.waveTypes[i] = enemyType; self.enemyCounts[i] = enemyCount; // Add shadow for wave type - 30% smaller than before var waveTypeShadow = new Text2(waveType, { size: 56, fill: 0x000000, weight: 800 }); waveTypeShadow.anchor.set(0.5, 0.5); waveTypeShadow.x = 4; waveTypeShadow.y = 4; marker.addChild(waveTypeShadow); // Add wave type text - 30% smaller than before var waveTypeText = new Text2(waveType, { size: 56, fill: 0xFFFFFF, weight: 800 }); waveTypeText.anchor.set(0.5, 0.5); waveTypeText.y = 0; marker.addChild(waveTypeText); // Add shadow for wave number - 20% larger than before var waveNumShadow = new Text2((i + 1).toString(), { size: 48, fill: 0x000000, weight: 800 }); waveNumShadow.anchor.set(1.0, 1.0); waveNumShadow.x = blockWidth / 2 - 16 + 5; waveNumShadow.y = block.height / 2 - 12 + 5; marker.addChild(waveNumShadow); // Main wave number text - 20% larger than before var waveNum = new Text2((i + 1).toString(), { size: 48, fill: 0xFFFFFF, weight: 800 }); waveNum.anchor.set(1.0, 1.0); waveNum.x = blockWidth / 2 - 16; waveNum.y = block.height / 2 - 12; marker.addChild(waveNum); marker.x = -self.indicatorWidth + (i + 1) * blockWidth; self.addChild(marker); self.waveMarkers.push(marker); } // Get wave type for a specific wave number self.getWaveType = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return "fast"; } // If this is a boss wave (waveNumber % 10 === 0), and the type is the same as lastBossType // then we should return a different boss type var waveType = self.waveTypes[waveNumber - 1]; return waveType; }; // Get enemy count for a specific wave number self.getEnemyCount = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return 10; } return self.enemyCounts[waveNumber - 1]; }; // Get display name for a wave type self.getWaveTypeName = function (waveNumber) { var type = self.getWaveType(waveNumber); var typeName = type.charAt(0).toUpperCase() + type.slice(1); // Add boss prefix for boss waves (final wave) if (waveNumber === totalWaves) { typeName = "BOSS"; } return typeName; }; self.positionIndicator = new Container(); var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = blockWidth - 10; indicator.height = 16; indicator.tint = 0xffad0e; indicator.y = -65; var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator2.width = blockWidth - 10; indicator2.height = 16; indicator2.tint = 0xffad0e; indicator2.y = 65; var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); leftWall.width = 16; leftWall.height = 146; leftWall.tint = 0xffad0e; leftWall.x = -(blockWidth - 16) / 2; var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); rightWall.width = 16; rightWall.height = 146; rightWall.tint = 0xffad0e; rightWall.x = (blockWidth - 16) / 2; self.addChild(self.positionIndicator); self.update = function () { var progress = waveTimer / nextWaveTime; var moveAmount = (progress + currentWave) * blockWidth; for (var i = 0; i < self.waveMarkers.length; i++) { var marker = self.waveMarkers[i]; marker.x = -moveAmount + i * blockWidth; } self.positionIndicator.x = 0; for (var i = 0; i < totalWaves + 1; i++) { var marker = self.waveMarkers[i]; if (i === 0) { continue; } var block = marker.children[0]; if (i - 1 < currentWave) { block.alpha = .5; } } self.handleWaveProgression = function () { if (!self.gameStarted) { return; } if (currentWave < totalWaves) { waveTimer++; if (waveTimer >= nextWaveTime) { waveTimer = 0; currentWave++; waveInProgress = true; waveSpawned = false; if (currentWave != 1) { var waveType = self.getWaveTypeName(currentWave); var enemyCount = self.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } }; self.handleWaveProgression(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xD2B48C }); /**** * Game Code ****/ var isHidingUpgradeMenu = false; // Global flag for alternating fly direction var lastDirectionWasRight = false; function spawnFly() { lastDirectionWasRight = !lastDirectionWasRight; // You would set fly.vx and fly.vy here based on your fly object // fly.vx = (lastDirectionWasRight ? 1 : -1) * horizontalSpeed; // fly.vy = downSpeed; // For this tower defense game, we can apply this pattern to flying enemies // by modifying their target lanes based on direction console.log("Fly spawning with direction:", lastDirectionWasRight ? "right" : "left"); } function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; } }); } var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var enemies = []; var towers = []; var bullets = []; var defenses = []; var selectedTower = null; var gold = 100; var lives = 100; var score = 0; var currentWave = 0; var totalWaves = 12; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 600; // Reduced from 1500 to 600 frames (10 seconds instead of 25) var sourceTower = null; var enemiesToSpawn = 10; // Default number of enemies per wave var goldText = new Text2('Gold: ' + gold, { size: 60, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); var livesText = new Text2('Lives: ' + lives, { size: 60, fill: 0x00FF00, weight: 800 }); livesText.anchor.set(0.5, 0.5); var scoreText = new Text2('Score: ' + score, { size: 60, fill: 0xFF0000, weight: 800 }); scoreText.anchor.set(0.5, 0.5); var topMargin = 50; var centerX = 2048 / 2; var spacing = 400; LK.gui.top.addChild(goldText); LK.gui.top.addChild(livesText); LK.gui.top.addChild(scoreText); livesText.x = 0; livesText.y = topMargin; goldText.x = -spacing; goldText.y = topMargin; scoreText.x = spacing; scoreText.y = topMargin; var lastGold = -1; var lastLives = -1; var lastScore = -1; function updateUI() { if (gold !== lastGold) { goldText.setText('Gold: ' + gold); lastGold = gold; } if (lives !== lastLives) { livesText.setText('Lives: ' + lives); lastLives = lives; } if (score !== lastScore) { scoreText.setText('Score: ' + score); lastScore = score; } } function setGold(value) { gold = value; updateUI(); } var debugLayer = new Container(); var towerLayer = new Container(); // Fast enemy can shoot - already implemented // This functionality is already working in the Enemy class update method // Fast enemies have shooting range, damage, cooldown, and create red bullets targeting towers var enemyLayerBottom = new Container(); // For normal enemies var enemyLayerMiddle = new Container(); // For shadows var enemyLayerTop = new Container(); // For flying enemies var enemyLayer = new Container(); // Main container to hold all enemy layers // Add layers in correct order (bottom first, then middle for shadows, then top) enemyLayer.addChild(enemyLayerBottom); enemyLayer.addChild(enemyLayerMiddle); enemyLayer.addChild(enemyLayerTop); var grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); game.addChild(debugLayer); game.addChild(towerLayer); game.addChild(enemyLayer); var offset = 0; var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; var isDragging = false; function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); // Only set to blocking type if it's currently a path (type 0) // Ground cells (type 1) can be built on without blocking paths if (cell.type === 0) { cell.type = 1; } } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); return blocked; } function getTowerCost(towerType) { var cost = 30; switch (towerType) { case 'landmine': cost = 10; break; case 'sniper': cost = 40; break; case 'flamethrower': cost = 20; break; case 'slow': cost = 50; break; case 'poison': cost = 55; break; case 'tank': cost = 60; break; } return cost; } function getTowerSellValue(totalValue) { return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue; } function placeTower(gridX, gridY, towerType) { var towerCost = getTowerCost(towerType); if (gold >= towerCost) { var tower = new Tower(towerType || 'trap'); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); // Only recalculate pathfinding if needed if (!grid.pathFind()) { grid.renderDebug(); } return true; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } game.down = function (x, y, obj) { var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenuVisible) { return; } for (var i = 0; i < sourceTowers.length; i++) { var tower = sourceTowers[i]; if (x >= tower.x - tower.width / 2 && x <= tower.x + tower.width / 2 && y >= tower.y - tower.height / 2 && y <= tower.y + tower.height / 2) { towerPreview.visible = true; isDragging = true; towerPreview.towerType = tower.towerType; towerPreview.updateAppearance(); // Apply the same offset as in move handler to ensure consistency when starting drag towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); break; } } }; game.move = function (x, y, obj) { if (isDragging) { // Shift the y position upward by 1.5 tiles to show preview above finger towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); } }; game.up = function (x, y, obj) { var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerLeft = tower.x - tower.width / 2; var towerRight = tower.x + tower.width / 2; var towerTop = tower.y - tower.height / 2; var towerBottom = tower.y + tower.height / 2; if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) { var clickedOnMenu = false; for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; var menuWidth = 2048; var menuHeight = 450; var menuLeft = menu.x - menuWidth / 2; var menuRight = menu.x + menuWidth / 2; var menuTop = menu.y - menuHeight / 2; var menuBottom = menu.y + menuHeight / 2; if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) { clickedOnMenu = true; break; } } if (!clickedOnMenu) { for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; hideUpgradeMenu(menu); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = null; grid.renderDebug(); } } if (isDragging) { isDragging = false; if (towerPreview.canPlace) { placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType); } else if (towerPreview.blockedByEnemy) { var notification = game.addChild(new Notification("Cannot build: Enemy in the way!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (towerPreview.visible) { var notification = game.addChild(new Notification("Cannot build here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } towerPreview.visible = false; if (isDragging) { var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } } } }; var waveIndicator = new WaveIndicator(); waveIndicator.x = 2048 / 2; waveIndicator.y = 2732 - 80; game.addChild(waveIndicator); var nextWaveButtonContainer = new Container(); var nextWaveButton = new NextWaveButton(); nextWaveButton.x = 2048 - 200; nextWaveButton.y = 2732 - 100 + 20; nextWaveButtonContainer.addChild(nextWaveButton); game.addChild(nextWaveButtonContainer); var towerTypes = ['landmine', 'flamethrower', 'trap', 'sniper', 'slow', 'tank']; var sourceTowers = []; var towerSpacing = 350; // Increase spacing for larger towers var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2; var towerY = 2732 - CELL_SIZE * 3 - 90; for (var i = 0; i < towerTypes.length; i++) { var tower = new SourceTower(towerTypes[i]); tower.x = startX + i * towerSpacing; tower.y = towerY; towerLayer.addChild(tower); sourceTowers.push(tower); } sourceTower = null; enemiesToSpawn = 10; game.update = function () { if (waveInProgress) { if (!waveSpawned) { waveSpawned = true; // Get wave type and enemy count from the wave indicator var waveType = waveIndicator.getWaveType(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); // Check if this is a boss wave var isBossWave = currentWave === totalWaves; if (isBossWave) { // Boss waves have just 1 enemy regardless of what the wave indicator says enemyCount = 1; // Show boss announcement var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️")); notification.x = 2048 / 2; notification.y = grid.height - 200; } // Spawn the appropriate number of enemies for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); // Initialize flying enemy direction using global alternating flag if (enemy.isFlying) { // Call the spawnFly function to update the global direction flag spawnFly(); // Set the flying enemy's initial direction based on the global flag enemy.flyingDirection = lastDirectionWasRight ? 1 : -1; // 1 for right, -1 for left } // Add enemy to the appropriate layer based on type for proper display order if (enemy.isFlying) { // Add flying enemy to the top layer to ensure they appear above all other enemies enemyLayerTop.addChild(enemy); // If it's a flying enemy, add its shadow to the middle layer if (enemy.shadow) { enemyLayerMiddle.addChild(enemy.shadow); } } else if (enemy.type === 'fast') { // Add fast enemies to the bottom layer to ensure flying enemies appear above them enemyLayerBottom.addChild(enemy); } else { // Add other ground enemies (immune, tank, etc.) to the middle layer enemyLayerMiddle.addChild(enemy); } // Scale difficulty with wave number but don't apply to boss // as bosses already have their health multiplier // Use exponential scaling for health var healthMultiplier = Math.pow(1.12, currentWave); // ~20% increase per wave enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier); enemy.health = enemy.maxHealth; // Increment speed slightly with wave number //enemy.speed = enemy.speed + currentWave * 0.002; // Assign specific lanes for different enemy types to prevent overlapping var gridWidth = 24; var midPoint = Math.floor(gridWidth / 2); // 12 var spawnX; // Define strict non-overlapping lanes with buffer zones for different enemy types if (waveType === 'fast') { // Fast enemies spawn in horizontal lines var enemiesPerLine = 5; // Number of enemies per horizontal line var lineNumber = Math.floor(i / enemiesPerLine); // Which horizontal line this enemy belongs to var positionInLine = i % enemiesPerLine; // Position within the line (0-4) var lineColumns = [6, 9, 12, 15, 18]; // Available columns for horizontal lines - moved left spawnX = lineColumns[positionInLine]; // Position enemy in the line } else if (waveType === 'immune') { // Immune enemies use center-left lanes (columns 11-12) with buffer from fast and tank var immuneColumns = [11, 12]; var availableImmuneColumns = []; for (var col = 0; col < immuneColumns.length; col++) { var columnOccupied = false; for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === immuneColumns[col] && enemies[e].currentCellY < 8) { columnOccupied = true; break; } } if (!columnOccupied) { availableImmuneColumns.push(immuneColumns[col]); } } // If no columns available, queue the enemy by using the first column spawnX = availableImmuneColumns.length > 0 ? availableImmuneColumns[Math.floor(Math.random() * availableImmuneColumns.length)] : immuneColumns[0]; } else if (waveType === 'tank') { // Tank enemies spawn from right edge of road var tankColumns = [21, 22]; // Right edge lanes for tank enemies spawnX = tankColumns[i % tankColumns.length]; // Cycle through right edge lanes } else { // Flying and other enemy types use middle lanes (columns 12-14) avoiding other types var flyingColumns = [12, 13, 14]; var availableColumns = []; for (var col = 0; col < flyingColumns.length; col++) { var columnOccupied = false; // Check if any enemy is already in this column but not yet in view for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === flyingColumns[col] && enemies[e].currentCellY < 8) { columnOccupied = true; break; } } if (!columnOccupied) { availableColumns.push(flyingColumns[col]); } } // If no columns available, use the center column spawnX = availableColumns.length > 0 ? availableColumns[Math.floor(Math.random() * availableColumns.length)] : flyingColumns[1]; } // Calculate spawn position with much larger spacing to ensure no visual overlap // Convert units to cell units (pixels / 76 pixels per cell) var enemySpacing; if (waveType === 'fast') { // Calculate spacing between horizontal lines (not individual enemies) var lineSpacing = 300 / CELL_SIZE; // Reduced spacing between horizontal lines (~3.9 cells) var lineNumber = Math.floor(i / 5); // Which horizontal line this enemy belongs to enemySpacing = lineSpacing; // Position fast enemies in their assigned column without horizontal offset enemy.cellX = spawnX; enemy.cellY = 5; // Position after entry enemy.currentCellX = spawnX; enemy.currentCellY = -1 - lineNumber * enemySpacing; // Each line spawns with line spacing, not individual spacing } else { if (waveType === 'tank') { enemySpacing = 800 / CELL_SIZE; // Reduced spacing for tank enemies with better lane distribution (~10.5 cells) } else if (waveType === 'immune') { enemySpacing = 400 / CELL_SIZE; // Reduced spacing for immune enemies (~5.3 cells) } else { enemySpacing = 900 / CELL_SIZE; // Large spacing for other enemies (~11.8 cells) } var spawnY = -1 - i * enemySpacing; // Each enemy spawns based on enemy type spacing enemy.cellX = spawnX; enemy.cellY = 5; // Position after entry enemy.currentCellX = spawnX; enemy.currentCellY = spawnY; } enemy.waveNumber = currentWave; enemies.push(enemy); } } var currentWaveEnemiesRemaining = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].waveNumber === currentWave) { currentWaveEnemiesRemaining = true; break; } } if (waveSpawned && !currentWaveEnemiesRemaining) { waveInProgress = false; waveSpawned = false; } } for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; if (enemy.health <= 0) { for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } // Remove all flame particles from the map when enemy is destroyed for (var flameIndex = game.children.length - 1; flameIndex >= 0; flameIndex--) { var child = game.children[flameIndex]; // Remove EffectIndicator flame effects if (child instanceof EffectIndicator) { var dx = child.x - enemy.x; var dy = child.y - enemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= CELL_SIZE * 3) { child.destroy(); } } // Remove flame particles (Container objects with flame graphics) else if (child.children && child.children.length > 0 && child.children[0].tint !== undefined) { var flameGraphics = child.children[0]; // Check for flame particles by tint colors and properties if ((flameGraphics.tint === 0xFF4500 || flameGraphics.tint === 0xFF6600) && flameGraphics.width && flameGraphics.height) { // Stop any ongoing tweens and destroy all flame particles tween.stop(child); child.destroy(); } } // Remove flame particles that have damage property (flamethrower particles) else if (child.damage !== undefined && child.sourceTowerLevel !== undefined) { // Stop any ongoing tweens and destroy flamethrower particles tween.stop(child); child.destroy(); } } // Boss enemies give more gold and score - significantly increased rewards var goldEarned = enemy.isBoss ? Math.floor(200 + (enemy.waveNumber - 1) * 25) : Math.floor(15 + (enemy.waveNumber - 1) * 3); var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); // Give more score for defeating a boss var scoreValue = enemy.isBoss ? 100 : 5; score += scoreValue; // Add a notification for boss defeat if (enemy.isBoss) { var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } updateUI(); // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else if (enemy.type === 'fast') { enemyLayerBottom.removeChild(enemy); } else { enemyLayerMiddle.removeChild(enemy); } enemies.splice(a, 1); continue; } // Only update enemy position every few frames to reduce performance impact if (LK.ticks % 2 === 0) { if (grid.updateEnemy(enemy)) { // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else if (enemy.type === 'fast') { enemyLayerBottom.removeChild(enemy); } else { enemyLayerMiddle.removeChild(enemy); } enemies.splice(a, 1); // Each individual enemy reduces lives by 10 (10% of 100 total lives) lives = Math.max(0, lives - 10); updateUI(); if (lives <= 0) { LK.showGameOver(); } } } } // Only clean bullets every 5 frames to reduce processing if (LK.ticks % 5 === 0) { for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; if (!bullet.parent) { if (bullet.targetEnemy) { var targetEnemy = bullet.targetEnemy; if (targetEnemy && targetEnemy.bulletsTargetingThis) { var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullet); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } } // Clear bullet references bullet.targetEnemy = null; bullets.splice(i, 1); } } } // Check for landmine towers that need to be destroyed after explosion for (var i = towers.length - 1; i >= 0; i--) { var tower = towers[i]; if (tower.shouldDestroy) { // Remove from grid cells var gridX = tower.gridX; var gridY = tower.gridY; for (var j = 0; j < 2; j++) { for (var k = 0; k < 2; k++) { var cell = grid.getCell(gridX + j, gridY + k); if (cell) { // Restore original cell type or default to ground (type 1) if no original type stored cell.type = cell.originalType !== undefined ? cell.originalType : 1; var towerIndex = cell.towersInRange.indexOf(tower); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } // Remove from towers array and tower layer towers.splice(i, 1); towerLayer.removeChild(tower); // Clean up any upgrade menus or range indicators if (selectedTower === tower) { selectedTower = null; } // Remove any upgrade menus for this tower var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu && child.tower === tower; }); for (var menuIndex = 0; menuIndex < upgradeMenus.length; menuIndex++) { upgradeMenus[menuIndex].destroy(); } // Remove any range indicators for this tower for (var rangeIndex = game.children.length - 1; rangeIndex >= 0; rangeIndex--) { if (game.children[rangeIndex].isTowerRange && game.children[rangeIndex].tower === tower) { game.removeChild(game.children[rangeIndex]); } } // Recalculate pathfinding grid.pathFind(); grid.renderDebug(); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -4205,11 +4205,11 @@
}
// If no columns available, queue the enemy by using the first column
spawnX = availableImmuneColumns.length > 0 ? availableImmuneColumns[Math.floor(Math.random() * availableImmuneColumns.length)] : immuneColumns[0];
} else if (waveType === 'tank') {
- // Tank enemies spawn from left edge of road: columns moved left by 12 units
- var tankColumns = [3 - 12, 4 - 12]; // Left edge lanes for tank enemies moved left by 12 units
- spawnX = tankColumns[i % tankColumns.length]; // Cycle through left edge lanes
+ // Tank enemies spawn from right edge of road
+ var tankColumns = [21, 22]; // Right edge lanes for tank enemies
+ spawnX = tankColumns[i % tankColumns.length]; // Cycle through right edge lanes
} else {
// Flying and other enemy types use middle lanes (columns 12-14) avoiding other types
var flyingColumns = [12, 13, 14];
var availableColumns = [];
White circle with black outline. Blue background.. In-Game asset. 2d. High contrast. No shadows
Light grey asphalt texture. Top view
Armed off-road vehicle with camouflage livery. Top view.
Military tank, top view
Army Soldier with gun. Topview.
Bomber Drone with spinning rotors, from top view.
Army box,but no text needed on image. top view
landmine from top view
Sniper man from top view
Rocket launcher cannon, from top view.
Army soldier laying with machine gun, top view from back
Missile, top view