User prompt
Get rid of the numbers
User prompt
Get rid of the large screen every time I kill anybody
User prompt
using the following plugins: @upit/tween.v1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add a neon cyberpunk look to everything ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
I wAnt to add new core mechanics
User prompt
Enhance visuals 100 times ↪💡 Consider importing and using the following plugins: @upit/tween.v1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add 100%better visuals and add 50 new features ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1, @upit/facekit.v1
Remix started
Copy Tower Defense Template
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ var AuraEffect = Container.expand(function (target, color, duration) { var self = Container.call(this); var aura = self.attachAsset('auraEffect', { anchorX: 0.5, anchorY: 0.5 }); aura.tint = color || 0x88ff88; aura.alpha = 0.4; aura.blendMode = 1; // Additive blending self.target = target; self.lifetime = duration || 3000; self.age = 0; // Pulsing animation function pulse() { tween(aura, { scaleX: 1.3, scaleY: 1.3, alpha: 0.6 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(aura, { scaleX: 1.0, scaleY: 1.0, alpha: 0.4 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { if (self.age < self.lifetime) { pulse(); } } }); } }); } pulse(); self.update = function () { if (self.target && self.target.parent) { self.x = self.target.x; self.y = self.target.y; } self.age += 16; // Assuming 60fps if (self.age >= self.lifetime) { self.destroy(); } }; return self; }); 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 () { 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) { // Enhanced impact visuals var isCritical = Math.random() < 0.15; // 15% critical hit chance var finalDamage = isCritical ? Math.floor(self.damage * 1.5) : self.damage; // Floating damage numbers removed // Enhanced impact particles based on bullet type var particleCount = isCritical ? 15 : 8; var particleType = 'fireParticle'; switch (self.type) { case 'sniper': particleType = 'sparkParticle'; particleCount *= 2; break; case 'splash': particleType = 'explosionParticle'; break; case 'poison': particleType = 'smokeParticle'; break; } var impactParticles = new ParticleSystem(particleType, self.targetEnemy.x, self.targetEnemy.y, particleCount); game.addChild(impactParticles); // Create explosion effect for critical hits if (isCritical) { var explosion = new ExplosionEffect(self.targetEnemy.x, self.targetEnemy.y, 60, 0xffaa00); game.addChild(explosion); } // For laser weapons, create enhanced beam effect if (self.type === 'sniper') { var laser = new LaserBeam(self.x, self.y, self.targetEnemy.x, self.targetEnemy.y, isCritical ? 0xffaa00 : 0xff0000); game.addChild(laser); // Screen flash for sniper critical hits if (isCritical) { LK.effects.flashScreen(0xffaa00, 200); } } // Apply damage to target enemy self.targetEnemy.health -= finalDamage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } else { self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; } // Apply special effects based on bullet type if (self.type === 'splash') { // Create visual splash effect var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); game.addChild(splashEffect); // Splash damage to nearby enemies var splashRadius = CELL_SIZE * 1.5; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self.targetEnemy) { var splashDx = otherEnemy.x - self.targetEnemy.x; var splashDy = otherEnemy.y - self.targetEnemy.y; var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy); if (splashDistance <= splashRadius) { // Apply splash damage (50% of original damage) otherEnemy.health -= self.damage * 0.5; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } else { otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70; } } } } } 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); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); var ChainLightning = Container.expand(function (startX, startY, targets, damage) { var self = Container.call(this); self.targets = targets || []; self.damage = damage || 15; self.chainCount = 0; self.maxChains = 5; self.lifetime = 60; // 1 second self.age = 0; // Create lightning bolts between targets for (var i = 0; i < self.targets.length - 1; i++) { var start = self.targets[i]; var end = self.targets[i + 1]; var bolt = self.attachAsset('electricArc', { anchorX: 0, anchorY: 0.5 }); var dx = end.x - start.x; var dy = end.y - start.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); bolt.x = start.x; bolt.y = start.y; bolt.rotation = angle; bolt.width = distance; bolt.tint = 0x88ffff; bolt.alpha = 0.8; // Apply damage to target if (end.health !== undefined) { var actualDamage = Math.floor(self.damage * Math.pow(0.8, i)); // Diminishing damage end.health -= actualDamage; if (end.health <= 0) { end.health = 0; } else { end.healthBar.width = end.health / end.maxHealth * 70; } // Floating damage numbers removed } } self.update = function () { self.age++; self.alpha = 1 - self.age / self.lifetime; if (self.age >= self.lifetime) { self.destroy(); } }; return self; }); var DebugCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = self.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5 }); cellGraphics.tint = Math.random() * 0xffffff; var debugArrows = []; var numberLabel = new Text2('0', { size: 30, fill: 0xFFFFFF, weight: 800 }); numberLabel.anchor.set(.5, .5); self.addChild(numberLabel); self.update = function () {}; self.down = function () { return; if (self.cell.type == 0 || self.cell.type == 1) { self.cell.type = self.cell.type == 1 ? 0 : 1; if (grid.pathFind()) { self.cell.type = self.cell.type == 1 ? 0 : 1; grid.pathFind(); var notification = game.addChild(new Notification("Path is blocked!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } grid.renderDebug(); } }; self.removeArrows = function () { while (debugArrows.length) { self.removeChild(debugArrows.pop()); } }; self.render = function (data) { switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); cellGraphics.tint = 0x880000; return; } numberLabel.visible = true; var tint = Math.floor(data.score / maxScore * 0x88); var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; cellGraphics.tint = 0x0088ff; } else { cellGraphics.tint = 0x88 - tint << 8 | tint; } while (debugArrows.length > data.targets.length) { self.removeChild(debugArrows.pop()); } for (var a = 0; a < data.targets.length; a++) { var destination = data.targets[a]; var ox = destination.x - data.x; var oy = destination.y - data.y; var angle = Math.atan2(oy, ox); if (!debugArrows[a]) { debugArrows[a] = LK.getAsset('arrow', { anchorX: -.5, anchorY: 0.5 }); debugArrows[a].alpha = .5; self.addChildAt(debugArrows[a], 1); } debugArrows[a].rotation = angle; } break; } case 1: { self.removeArrows(); cellGraphics.tint = 0xaaaaaa; numberLabel.visible = false; break; } case 3: { self.removeArrows(); cellGraphics.tint = 0x008800; numberLabel.visible = false; break; } } numberLabel.setText(Math.floor(data.score / 1000) / 10); }; }); // This update method was incorrectly placed here and should be 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; } 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 = .01; 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; // 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 *= 2; // Twice as fast self.maxHealth = 100; break; case 'immune': self.isImmune = true; self.maxHealth = 80; break; case 'flying': self.isFlying = true; self.maxHealth = 80; break; case 'swarm': self.maxHealth = 50; // Weaker enemies break; case 'normal': default: // Normal enemy uses default values break; } if (currentWave % 10 === 0 && currentWave > 0 && 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.type !== 'normal') { assetId = 'enemy_' + self.type; } var enemyGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Add cyberpunk glow outline to enemies var glowOutline = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); glowOutline.tint = 0x00ffaa; glowOutline.alpha = 0.4; glowOutline.blendMode = 1; // Additive blending glowOutline.scaleX = 1.1; glowOutline.scaleY = 1.1; // Add pulsing glow animation for cyberpunk effect function enemyGlow() { tween(glowOutline, { scaleX: 1.2, scaleY: 1.2, alpha: 0.6 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(glowOutline, { scaleX: 1.1, scaleY: 1.1, alpha: 0.4 }, { duration: 800, easing: tween.easeInOut, onFinish: enemyGlow }); } }); } enemyGlow(); // Animate enemy entrance enemyGraphics.alpha = 0; enemyGraphics.scaleX = 0.3; enemyGraphics.scaleY = 0.3; var targetScale = self.isBoss ? 1.8 : 1.0; tween(enemyGraphics, { alpha: 1, scaleX: targetScale, scaleY: targetScale }, { duration: 400, easing: tween.elasticOut }); // Add subtle floating animation for flying enemies if (self.isFlying) { var _floatAnimation = function floatAnimation() { tween(enemyGraphics, { y: -8 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { tween(enemyGraphics, { y: 0 }, { duration: 1500, easing: tween.easeInOut, onFinish: _floatAnimation }); } }); }; LK.setTimeout(_floatAnimation, 500); } // 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 special enemy abilities if (self.regenerating && self.health > 0 && self.health < self.maxHealth) { if (LK.ticks % 60 === 0) { // Once per second self.health = Math.min(self.maxHealth, self.health + self.regenRate); self.healthBar.width = self.health / self.maxHealth * 70; } } if (self.teleporting && self.health > 0) { self.teleportTimer--; if (self.teleportTimer <= 0) { // Teleport to a random position closer to goal var teleportParticles = new ParticleSystem('particle', self.x, self.y, 15); game.addChild(teleportParticles); // Jump forward 2-3 cells self.currentCellY += 2 + Math.random() * 2; self.cellY = Math.round(self.currentCellY); self.teleportTimer = 300 + Math.random() * 300; // 5-10 seconds } } // Handle slow effect if (self.isImmune) { // Immune enemies cannot be slowed or poisoned, clear any such effects self.slowed = false; self.slowEffect = false; self.poisoned = false; self.poisonEffect = 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 } } } } // Set tint based on effect status if (self.isImmune) { enemyGraphics.tint = 0xFFFFFF; } 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) { 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); if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) { 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: 250, easing: tween.easeOut }); } } } } healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; }; return self; }); var StealthEnemy = Enemy.expand(function (type) { var self = Enemy.call(this, type); self.stealthCooldown = 0; self.stealthDuration = 180; // 3 seconds self.stealthActive = false; self.detectionRange = CELL_SIZE * 2; var originalUpdate = self.update; self.update = function () { originalUpdate.call(self); // Check if towers are nearby to activate stealth var towersNearby = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx = tower.x - self.x; var dy = tower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.detectionRange) { towersNearby = true; break; } } // Activate stealth when towers nearby if (towersNearby && !self.stealthActive && self.stealthCooldown <= 0) { self.stealthActive = true; self.stealthCooldown = 600; // 10 seconds cooldown self.alpha = 0.3; // Become semi-transparent } // Deactivate stealth after duration if (self.stealthActive) { self.stealthDuration--; if (self.stealthDuration <= 0) { self.stealthActive = false; self.stealthDuration = 180; self.alpha = 1.0; } } if (self.stealthCooldown > 0) { self.stealthCooldown--; } }; return self; }); var SplitterEnemy = Enemy.expand(function (type) { var self = Enemy.call(this, type); self.canSplit = true; self.splitCount = 2; var originalUpdate = self.update; self.update = function () { originalUpdate.call(self); // Split when health drops below 50% if (self.canSplit && self.health <= self.maxHealth * 0.5) { self.split(); } }; self.split = function () { if (!self.canSplit) return; self.canSplit = false; // Create smaller enemies for (var i = 0; i < self.splitCount; i++) { var splitEnemy = new Enemy(self.type); splitEnemy.maxHealth = Math.floor(self.maxHealth * 0.4); splitEnemy.health = splitEnemy.maxHealth; splitEnemy.speed = self.speed * 1.3; // Faster splitEnemy.x = self.x + (Math.random() - 0.5) * 60; splitEnemy.y = self.y + (Math.random() - 0.5) * 60; splitEnemy.cellX = self.cellX; splitEnemy.cellY = self.cellY; splitEnemy.currentCellX = splitEnemy.x / CELL_SIZE; splitEnemy.currentCellY = splitEnemy.y / CELL_SIZE; splitEnemy.waveNumber = self.waveNumber; // Scale down visually splitEnemy.scaleX = 0.7; splitEnemy.scaleY = 0.7; enemies.push(splitEnemy); enemyLayerBottom.addChild(splitEnemy); } // Create split effect var splitEffect = new ExplosionEffect(self.x, self.y, 60, 0xff00ff); game.addChild(splitEffect); }; return self; }); var ShieldedEnemy = Enemy.expand(function (type) { var self = Enemy.call(this, type); self.shieldHealth = 50; self.maxShieldHealth = 50; self.shieldRegenRate = 2; self.shieldRegenDelay = 180; // 3 seconds self.lastShieldDamage = 0; var shieldGraphics = self.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5 }); shieldGraphics.alpha = 0.6; shieldGraphics.tint = 0x00aaff; shieldGraphics.y = -10; var originalUpdate = self.update; self.update = function () { originalUpdate.call(self); // Shield regeneration if (LK.ticks - self.lastShieldDamage > self.shieldRegenDelay && self.shieldHealth < self.maxShieldHealth) { self.shieldHealth = Math.min(self.maxShieldHealth, self.shieldHealth + self.shieldRegenRate); } // Update shield visual if (self.shieldHealth > 0) { shieldGraphics.alpha = 0.6 * (self.shieldHealth / self.maxShieldHealth); shieldGraphics.visible = true; } else { shieldGraphics.visible = false; } }; return self; }); var ExplosionEffect = Container.expand(function (x, y, size, color) { var self = Container.call(this); self.x = x; self.y = y; // Core explosion flash var flash = self.attachAsset('explosionParticle', { anchorX: 0.5, anchorY: 0.5 }); flash.tint = color || 0xff6600; flash.width = flash.height = size || 80; flash.alpha = 0.9; // Animate core flash tween(flash, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 300, easing: tween.easeOut }); // Create enhanced cyberpunk spark particles with neon trails for (var i = 0; i < 18; i++) { var spark = self.attachAsset('sparkParticle', { anchorX: 0.5, anchorY: 0.5 }); spark.tint = Math.random() > 0.5 ? 0x00ffaa : 0x0088ff; spark.blendMode = 1; // Additive blending for neon glow var angle = i / 18 * Math.PI * 2; var speed = 100 + Math.random() * 80; var endX = Math.cos(angle) * speed; var endY = Math.sin(angle) * speed; spark.alpha = 1.0; spark.scaleX = spark.scaleY = 1.5; // Animate with elastic bounce for cyberpunk effect tween(spark, { x: endX, y: endY, alpha: 0, scaleX: 0.2, scaleY: 0.2, rotation: Math.PI * 4 // Spinning effect }, { duration: 600 + Math.random() * 400, easing: tween.elasticOut }); // Create trailing glow effect var trail = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); trail.tint = spark.tint; trail.alpha = 0.6; trail.blendMode = 1; trail.scaleX = trail.scaleY = 2.0; tween(trail, { x: endX * 0.7, y: endY * 0.7, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 800 + Math.random() * 300, easing: tween.easeOut }); } // Auto-destroy after animation LK.setTimeout(function () { self.destroy(); }, 800); return self; }); // FloatingDamageNumber class removed 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 */ for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0; if (i > 11 - 3 && i <= 11 + 3) { if (j === 0) { cellType = 2; self.spawns.push(cell); } else if (j <= 4) { cellType = 0; } else if (j === gridHeight - 1) { cellType = 3; self.goals.push(cell); } else if (j >= gridHeight - 4) { cellType = 0; } } 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 = []; if (j > 3 && j <= gridHeight - 4) { var debugCell = new DebugCell(); self.addChild(debugCell); debugCell.cell = cell; debugCell.x = i * CELL_SIZE; debugCell.y = j * CELL_SIZE; cell.debugCell = debugCell; } } } 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 () { for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var debugCell = self.cells[i][j].debugCell; if (debugCell) { debugCell.render(self.cells[i][j]); } } } }; self.updateEnemy = function (enemy) { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell.type == 3) { 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) { // Move directly downward enemy.currentCellY += enemy.speed; // Rotate enemy graphic to face downward (PI/2 radians = 90 degrees) var angle = 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 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) { // Flying enemies head straight to the closest goal if (!enemy.flyingTarget) { // Set flying target to the closest goal enemy.flyingTarget = self.goals[0]; // Find closest goal if there are multiple if (self.goals.length > 1) { var closestDist = Infinity; for (var i = 0; i < self.goals.length; i++) { var goal = self.goals[i]; var dx = goal.x - enemy.cellX; var dy = goal.y - enemy.cellY; var dist = dx * dx + dy * dy; if (dist < closestDist) { closestDist = dist; enemy.flyingTarget = goal; } } } } // Move directly toward the goal var ox = enemy.flyingTarget.x - enemy.currentCellX; var oy = enemy.flyingTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { // Reached the goal return true; } var angle = Math.atan2(oy, ox); // Rotate enemy graphic to match movement direction 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); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // Update shadow position if this is a flying enemy return false; } // Handle normal pathfinding enemies if (!enemy.currentTarget) { enemy.currentTarget = cell.targets[0]; } if (enemy.currentTarget) { if (cell.score < enemy.currentTarget.score) { enemy.currentTarget = cell; } 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) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentTarget = undefined; return; } var angle = Math.atan2(oy, ox); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; }; }); var LaserBeam = Container.expand(function (startX, startY, endX, endY, color) { var self = Container.call(this); var beam = self.attachAsset('laser', { anchorX: 0, anchorY: 0.5 }); beam.tint = color || 0xff0000; var dx = endX - startX; var dy = endY - startY; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); self.x = startX; self.y = startY; beam.rotation = angle; beam.width = distance; beam.alpha = 0.8; self.lifetime = 30; // Half second self.age = 0; self.update = function () { self.age++; self.alpha = 1 - self.age / self.lifetime; if (self.age >= self.lifetime) { self.destroy(); } }; return self; }); 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 ParticleSystem = Container.expand(function (particleType, x, y, count) { var self = Container.call(this); self.x = x; self.y = y; self.particles = []; self.particleType = particleType || 'particle'; self.lifetime = 180; // 3 seconds at 60fps self.age = 0; for (var i = 0; i < (count || 20); i++) { var particle = self.attachAsset(self.particleType, { anchorX: 0.5, anchorY: 0.5 }); // Random velocity and properties var angle = Math.random() * Math.PI * 2; var speed = 2 + Math.random() * 4; var particleData = { graphic: particle, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: self.lifetime + Math.random() * 60, maxLife: self.lifetime + Math.random() * 60, scale: 0.5 + Math.random() * 0.5 }; particle.x = (Math.random() - 0.5) * 40; particle.y = (Math.random() - 0.5) * 40; particle.scaleX = particle.scaleY = particleData.scale; // Set particle color based on type - cyberpunk theme switch (self.particleType) { case 'fireParticle': particle.tint = Math.random() > 0.5 ? 0xff0044 : 0xff0088; particle.blendMode = 1; // Additive blending for neon glow break; case 'iceParticle': particle.tint = Math.random() > 0.5 ? 0x00ffff : 0x00aaff; particle.blendMode = 1; // Additive blending for neon glow break; case 'smokeParticle': particle.tint = 0x001144 + Math.floor(Math.random() * 0x002266); particle.blendMode = 1; // Additive blending for neon glow break; default: particle.tint = Math.random() > 0.5 ? 0x00ffaa : 0x0088ff; particle.blendMode = 1; // Additive blending for neon glow break; } // Add pulsing glow effect to particles tween(particle, { alpha: 1.2 }, { duration: 100 + Math.random() * 200, easing: tween.easeInOut, onFinish: function onFinish() { tween(particle, { alpha: 0.8 }, { duration: 100 + Math.random() * 200, easing: tween.easeInOut }); } }); self.particles.push(particleData); } self.update = function () { self.age++; for (var i = self.particles.length - 1; i >= 0; i--) { var p = self.particles[i]; p.life--; // Update position p.graphic.x += p.vx; p.graphic.y += p.vy; p.vy += 0.1; // gravity p.vx *= 0.99; // air resistance // Fade out var alpha = p.life / p.maxLife; p.graphic.alpha = alpha; p.graphic.scaleX = p.graphic.scaleY = p.scale * alpha; if (p.life <= 0) { self.removeChild(p.graphic); self.particles.splice(i, 1); } } if (self.particles.length === 0 || self.age > self.lifetime * 2) { self.destroy(); } }; return self; }); var PowerUp = Container.expand(function (type, x, y) { var self = Container.call(this); self.type = type || 'gold'; self.x = x; self.y = y; self.collected = false; var graphic = self.attachAsset('powerUp', { anchorX: 0.5, anchorY: 0.5 }); switch (self.type) { case 'gold': graphic.tint = 0xffd700; break; case 'health': graphic.tint = 0x00ff00; break; case 'damage': graphic.tint = 0xff0000; break; case 'speed': graphic.tint = 0x0088ff; break; case 'shield': graphic.tint = 0x8800ff; break; } self.bobTimer = 0; self.collectRadius = 40; self.update = function () { self.bobTimer += 0.1; graphic.y = Math.sin(self.bobTimer) * 10; graphic.rotation += 0.05; // Check for collection by player (towers) if (!self.collected) { for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx = tower.x - self.x; var dy = tower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.collectRadius) { self.collect(); break; } } } }; self.collect = function () { if (self.collected) return; self.collected = true; var particles = new ParticleSystem('particle', self.x, self.y, 15); game.addChild(particles); switch (self.type) { case 'gold': setGold(gold + 50); var goldIndicator = new GoldIndicator(50, self.x, self.y); game.addChild(goldIndicator); break; case 'health': lives = Math.min(lives + 5, 50); updateUI(); break; case 'damage': // Apply temporary damage boost to all towers for (var i = 0; i < towers.length; i++) { towers[i].damage = Math.floor(towers[i].damage * 1.5); } break; } tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; // Increase size of base for easier touch var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); switch (self.towerType) { case 'rapid': baseGraphics.tint = 0x00AAFF; break; case 'sniper': baseGraphics.tint = 0xFF0088; break; case 'splash': baseGraphics.tint = 0x00FF44; break; case 'slow': baseGraphics.tint = 0x8800FF; break; case 'poison': baseGraphics.tint = 0x00FFAA; break; default: baseGraphics.tint = 0x0088AA; } // Add neon glow effect to source towers var sourceGlow = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.4, scaleY: 1.4 }); sourceGlow.tint = baseGraphics.tint; sourceGlow.alpha = 0.3; sourceGlow.blendMode = 1; // Additive blending // Move the glow behind the main graphics self.removeChild(sourceGlow); self.addChildAt(sourceGlow, 0); // Create pulsing animation for cyberpunk effect function sourceGlowPulse() { tween(sourceGlow, { scaleX: 1.5, scaleY: 1.5, alpha: 0.5 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(sourceGlow, { scaleX: 1.4, scaleY: 1.4, alpha: 0.3 }, { duration: 1000, easing: tween.easeInOut, onFinish: sourceGlowPulse }); } }); } sourceGlowPulse(); var towerCost = getTowerCost(self.towerType); // Add shadow for tower type label var typeLabelShadow = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), { 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 typeLabel = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), { 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 || 'default'; 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: base 5, +0.8 per level, but final upgrade gets a huge boost if (self.level === self.maxLevel) { return 12 * CELL_SIZE; // Significantly increased range for max level } return (5 + (self.level - 1) * 0.8) * CELL_SIZE; case 'splash': // Splash: base 2, +0.2 per level (max ~4 blocks at max level) return (2 + (self.level - 1) * 0.2) * CELL_SIZE; case 'rapid': // Rapid: base 2.5, +0.5 per level return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'slow': // Slow: base 3.5, +0.5 per level return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'poison': // Poison: base 3.2, +0.5 per level return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; case 'lightning': // Lightning: base 4, +0.6 per level return (4 + (self.level - 1) * 0.6) * CELL_SIZE; case 'support': // Support: base 4, +0.4 per level return (4 + (self.level - 1) * 0.4) * CELL_SIZE; case 'beam': // Beam: base 6, +0.3 per level return (6 + (self.level - 1) * 0.3) * CELL_SIZE; default: // Default: base 3, +0.5 per level return (3 + (self.level - 1) * 0.5) * CELL_SIZE; } }; self.cellsInRange = []; self.fireRate = 60; self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; switch (self.id) { case 'rapid': 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 'splash': self.fireRate = 75; self.damage = 15; self.range = 2 * CELL_SIZE; self.bulletSpeed = 4; break; case 'slow': self.fireRate = 50; self.damage = 8; 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 'lightning': self.fireRate = 120; self.damage = 18; self.range = 4 * CELL_SIZE; self.bulletSpeed = 0; // Instant break; case 'support': self.fireRate = 0; // No firing self.damage = 0; self.range = 4 * CELL_SIZE; // Buff range self.bulletSpeed = 0; break; case 'beam': self.fireRate = 1; // Continuous self.damage = 3; // Per tick self.range = 6 * CELL_SIZE; self.bulletSpeed = 0; // Instant self.beamTarget = null; break; } var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); switch (self.id) { case 'rapid': baseGraphics.tint = 0x00AAFF; break; case 'sniper': baseGraphics.tint = 0xFF0088; break; case 'splash': baseGraphics.tint = 0x00FF44; break; case 'slow': baseGraphics.tint = 0x8800FF; break; case 'poison': baseGraphics.tint = 0x00FFAA; break; case 'lightning': baseGraphics.tint = 0x00FFFF; break; case 'support': baseGraphics.tint = 0xFF0088; break; case 'beam': baseGraphics.tint = 0xFF0099; break; default: baseGraphics.tint = 0x0088AA; } // Add cyberpunk glow effect to all towers var glowEffect = self.attachAsset('auraEffect', { anchorX: 0.5, anchorY: 0.5 }); glowEffect.alpha = 0.3; glowEffect.blendMode = 1; // Additive blending for glow glowEffect.tint = baseGraphics.tint; // Create pulsing glow animation function createGlow() { tween(glowEffect, { scaleX: 1.2, scaleY: 1.2, alpha: 0.5 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { tween(glowEffect, { scaleX: 1.0, scaleY: 1.0, alpha: 0.3 }, { duration: 1500, easing: tween.easeInOut, onFinish: createGlow }); } }); } createGlow(); 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); var gunGraphics = gunContainer.attachAsset('defense', { anchorX: 0.5, anchorY: 0.5 }); 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 'rapid': towerLevelIndicator.tint = 0x00AAFF; break; case 'sniper': towerLevelIndicator.tint = 0xFF5500; break; case 'splash': towerLevelIndicator.tint = 0x33CC00; 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); } } } } 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 === 'rapid') { 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 { 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) { // Enhanced upgrade visual feedback var upgradeGlow = self.attachAsset('upgradeGlow', { anchorX: 0.5, anchorY: 0.5 }); upgradeGlow.alpha = 0.8; upgradeGlow.tint = 0x00ff88; upgradeGlow.scaleX = 0.5; upgradeGlow.scaleY = 0.5; // Animate upgrade glow tween(upgradeGlow, { scaleX: 2.0, scaleY: 2.0, alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { self.removeChild(upgradeGlow); } }); // Enhanced level dot animation var levelDot = levelIndicators[self.level - 1].children[1]; tween(levelDot, { scaleX: 2.0, scaleY: 2.0, alpha: 1.5 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { tween(levelDot, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 300, easing: tween.bounceOut }); } }); // Whole tower celebration bounce tween(self, { scaleX: 1.1, scaleY: 1.1 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.elasticOut }); } }); // Create aura effect for max level towers if (self.level === self.maxLevel) { var aura = new AuraEffect(self, 0xffd700, 5000); game.addChild(aura); } } 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, 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 () { // Support tower buff system if (self.id === 'support') { // Buff nearby towers for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower !== self && tower.id !== 'support') { var dx = tower.x - self.x; var dy = tower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.getRange()) { // Apply buffs if (!tower.buffed) { tower.buffed = true; tower.originalFireRate = tower.fireRate; tower.originalDamage = tower.damage; tower.fireRate = Math.floor(tower.fireRate * 0.7); // 30% faster tower.damage = Math.floor(tower.damage * 1.3); // 30% more damage // Visual buff indicator if (!tower.buffIndicator) { tower.buffIndicator = tower.attachAsset('auraEffect', { anchorX: 0.5, anchorY: 0.5 }); tower.buffIndicator.tint = 0xff00ff; tower.buffIndicator.alpha = 0.4; tower.buffIndicator.scaleX = 1.2; tower.buffIndicator.scaleY = 1.2; } } tower.buffTimer = 5; // Refresh buff duration } } } } // Remove buffs if out of range or support tower destroyed if (self.buffed && self.buffTimer > 0) { self.buffTimer--; if (self.buffTimer <= 0) { self.buffed = false; self.fireRate = self.originalFireRate; self.damage = self.originalDamage; if (self.buffIndicator) { self.removeChild(self.buffIndicator); self.buffIndicator = null; } } } // Voice control for sniper towers if (self.id === 'sniper' && facekit.volume > 0.6) { self.fireRate = Math.max(30, self.fireRate * 0.7); // Boost fire rate with voice } // Face tracking for tower aiming (if face detected) if (facekit.mouthCenter && facekit.mouthCenter.x !== undefined) { var faceX = facekit.mouthCenter.x * 2048; var faceY = facekit.mouthCenter.y * 2732; // For rapid towers, follow face when no enemies if (self.id === 'rapid' && !self.targetEnemy) { var dx = faceX - self.x; var dy = faceY - self.y; var angle = Math.atan2(dy, dx); gunContainer.rotation = angle; } } // Special targeting for different tower types if (self.id === 'support') { // Support towers don't target enemies self.targetEnemy = null; } else { self.targetEnemy = self.findTarget(); } if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); gunContainer.rotation = angle; if (self.id === 'beam') { // Beam towers fire continuously self.fire(); } else 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(); }; self.fire = function () { if (self.id === 'lightning') { // Lightning tower chains between enemies var chainTargets = [self]; var currentTarget = self.targetEnemy; var chainRange = CELL_SIZE * 2; for (var chains = 0; chains < 5 && currentTarget; chains++) { chainTargets.push(currentTarget); // Find next enemy to chain to var nextTarget = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (chainTargets.indexOf(enemy) === -1) { // Not already in chain var dx = enemy.x - currentTarget.x; var dy = enemy.y - currentTarget.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= chainRange && distance < closestDistance) { closestDistance = distance; nextTarget = enemy; } } } currentTarget = nextTarget; } if (chainTargets.length > 1) { var lightning = new ChainLightning(self.x, self.y, chainTargets, self.damage); game.addChild(lightning); } } else if (self.id === 'beam') { // Beam tower creates continuous damage beam if (self.targetEnemy && self.beamTarget !== self.targetEnemy) { // Remove old beam if exists if (self.activeBeam) { self.removeChild(self.activeBeam); } self.beamTarget = self.targetEnemy; self.activeBeam = self.attachAsset('energyBeam', { anchorX: 0, anchorY: 0.5 }); self.activeBeam.tint = 0xff0080; self.activeBeam.alpha = 0.8; } // Update beam position and damage target if (self.activeBeam && self.beamTarget && self.beamTarget.parent) { var dx = self.beamTarget.x - self.x; var dy = self.beamTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); self.activeBeam.rotation = angle; self.activeBeam.width = distance; // Apply continuous damage self.beamTarget.health -= self.damage; if (self.beamTarget.health <= 0) { self.beamTarget.health = 0; } else { self.beamTarget.healthBar.width = self.beamTarget.health / self.beamTarget.maxHealth * 70; } } else if (self.activeBeam) { self.removeChild(self.activeBeam); self.activeBeam = null; self.beamTarget = null; } } else 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) { 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 'rapid': bullet.children[0].tint = 0x00AAFF; bullet.children[0].width = 20; bullet.children[0].height = 20; break; case 'sniper': bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; case 'splash': bullet.children[0].tint = 0x33CC00; bullet.children[0].width = 40; bullet.children[0].height = 40; break; case 'slow': bullet.children[0].tint = 0x9900FF; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'poison': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; } game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); // Enhanced muzzle flash with multiple effects var muzzleFlash = self.attachAsset('muzzleFlash', { anchorX: 0.5, anchorY: 0.5 }); muzzleFlash.x = Math.cos(gunContainer.rotation) * 40; muzzleFlash.y = Math.sin(gunContainer.rotation) * 40; muzzleFlash.rotation = gunContainer.rotation; muzzleFlash.alpha = 0.9; muzzleFlash.tint = self.id === 'sniper' ? 0xffaa00 : 0xffff88; // Animate muzzle flash with bounce effect tween(muzzleFlash, { scaleX: 2.5, scaleY: 2.5, alpha: 0 }, { duration: 150, easing: tween.elasticOut, onFinish: function onFinish() { self.removeChild(muzzleFlash); } }); // Enhanced energy ring system for all tower types var energyRing = self.attachAsset('energyRing', { anchorX: 0.5, anchorY: 0.5 }); energyRing.alpha = 0.8; energyRing.blendMode = 1; // Additive blending for cyberpunk glow // Cyberpunk color scheme based on tower type switch (self.id) { case 'sniper': energyRing.tint = 0xff0088; break; case 'splash': energyRing.tint = 0x00ff44; break; case 'rapid': energyRing.tint = 0x00aaff; break; case 'lightning': energyRing.tint = 0x00ffff; break; case 'poison': energyRing.tint = 0x00ffaa; break; case 'slow': energyRing.tint = 0x8800ff; break; default: energyRing.tint = 0x0088aa; } energyRing.scaleX = 0.1; energyRing.scaleY = 0.1; // Multi-stage cyberpunk animation tween(energyRing, { scaleX: 0.8, scaleY: 0.8, alpha: 1.2 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(energyRing, { scaleX: 2.2, scaleY: 2.2, alpha: 0 }, { duration: 400, easing: tween.cubicOut, onFinish: function onFinish() { self.removeChild(energyRing); } }); } }); // Create cyberpunk discharge particles for (var p = 0; p < 6; p++) { var particle = self.attachAsset('sparkParticle', { anchorX: 0.5, anchorY: 0.5 }); particle.tint = energyRing.tint; particle.blendMode = 1; particle.alpha = 0.9; particle.scaleX = particle.scaleY = 0.6; var pAngle = gunContainer.rotation + (Math.random() - 0.5) * 0.8; var pSpeed = 60 + Math.random() * 40; var pX = Math.cos(pAngle) * pSpeed; var pY = Math.sin(pAngle) * pSpeed; particle.x = Math.cos(gunContainer.rotation) * 30; particle.y = Math.sin(gunContainer.rotation) * 30; tween(particle, { x: particle.x + pX, y: particle.y + pY, alpha: 0, scaleX: 0.1, scaleY: 0.1, rotation: Math.PI * 2 }, { duration: 500 + Math.random() * 300, easing: tween.easeOut, onFinish: function onFinish() { self.removeChild(particle); } }); } // Add charging effect before high-damage shots if (self.damage > 20) { var chargeEffect = self.attachAsset('chargeEffect', { anchorX: 0.5, anchorY: 0.5 }); chargeEffect.alpha = 0.7; chargeEffect.tint = 0x88ff88; chargeEffect.scaleX = 0.5; chargeEffect.scaleY = 0.5; tween(chargeEffect, { scaleX: 1.2, scaleY: 1.2, alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.removeChild(chargeEffect); } }); } // --- 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) { cell.type = 1; } } } self.refreshCellsInRange(); }; return self; }); var TowerPreview = Container.expand(function () { var self = Container.call(this); var towerRange = 3; var rangeInPixels = towerRange * CELL_SIZE; self.towerType = 'default'; 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('towerpreview', { 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; self.update = function () { 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; switch (self.towerType) { case 'rapid': previewGraphics.tint = 0x00AAFF; break; case 'sniper': previewGraphics.tint = 0xFF5500; break; case 'splash': previewGraphics.tint = 0x33CC00; break; case 'slow': previewGraphics.tint = 0x9900FF; break; case 'poison': previewGraphics.tint = 0x00FFAA; break; default: previewGraphics.tint = 0xAAAAAA; } if (!self.canPlace || !self.hasEnoughGold) { previewGraphics.tint = 0xFF0000; } }; self.updatePlacementStatus = function () { var validGridPlacement = true; if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) { validGridPlacement = false; } else { 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 || cell.type !== 0) { 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 towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' 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) { cell.type = 0; 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) % 10 === 0; // Ensure all types appear in early waves if (i === 0) { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } else if (i === 1) { block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if (i === 2) { block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; enemyCount = 10; } else if (i === 3) { block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if (i === 4) { block.tint = 0xFF00FF; waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else if (isBossWave) { // Boss waves: cycle through all boss types, last boss is always flying var bossTypes = ['normal', '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 'normal': block.tint = 0xAAAAAA; waveType = "Boss Normal"; break; 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 'normal': block.tint = 0xAAAAAA; break; 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 immune block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; enemyCount = 10; } else if ((i + 1) % 7 === 0) { // Every 7th non-boss wave is flying block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if ((i + 1) % 3 === 0) { // Every 3rd non-boss wave is swarm block.tint = 0xFF00FF; waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } // --- End new unified wave logic --- // Mark boss waves with a special visual indicator if (isBossWave && enemyType !== 'swarm') { // 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 "normal"; } // 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 (every 10th wave) if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') { 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); // Enhanced cyberpunk wave transition effect var waveRipple = new Container(); // Main energy ripple var ripple = waveRipple.attachAsset('waveRipple', { anchorX: 0.5, anchorY: 0.5 }); ripple.alpha = 0.8; ripple.tint = 0x00ffaa; ripple.blendMode = 1; // Additive blending for neon glow ripple.scaleX = 0.05; ripple.scaleY = 0.05; // Secondary ripple for layered effect var ripple2 = waveRipple.attachAsset('waveRipple', { anchorX: 0.5, anchorY: 0.5 }); ripple2.alpha = 0.4; ripple2.tint = 0x0088ff; ripple2.blendMode = 1; ripple2.scaleX = 0.1; ripple2.scaleY = 0.1; waveRipple.x = 2048 / 2; waveRipple.y = 2732 / 2; game.addChild(waveRipple); // Animate main ripple with cyberpunk elastic effect tween(ripple, { scaleX: 5.0, scaleY: 5.0, alpha: 0, rotation: Math.PI * 2 }, { duration: 1500, easing: tween.elasticOut, onFinish: function onFinish() { waveRipple.destroy(); } }); // Animate secondary ripple with delay tween(ripple2, { scaleX: 3.5, scaleY: 3.5, alpha: 0, rotation: -Math.PI * 1.5 }, { duration: 1200, easing: tween.cubicOut }); // Create cyberpunk energy discharge particles for (var e = 0; e < 12; e++) { var energyParticle = waveRipple.attachAsset('sparkParticle', { anchorX: 0.5, anchorY: 0.5 }); energyParticle.tint = Math.random() > 0.5 ? 0x00ffaa : 0x0088ff; energyParticle.blendMode = 1; energyParticle.alpha = 1.0; energyParticle.scaleX = energyParticle.scaleY = 1.5; var eAngle = e / 12 * Math.PI * 2; var eDistance = 200 + Math.random() * 300; tween(energyParticle, { x: Math.cos(eAngle) * eDistance, y: Math.sin(eAngle) * eDistance, alpha: 0, scaleX: 0.2, scaleY: 0.2, rotation: Math.PI * 4 }, { duration: 1000 + Math.random() * 500, easing: tween.easeOut }); } // Enhanced notification with animation var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = -100; // Start off-screen // Animate notification entrance tween(notification, { y: grid.height - 150 }, { duration: 600, easing: tween.bounceOut }); // Screen flash for boss waves if (currentWave % 10 === 0) { LK.effects.flashScreen(0xff4400, 800); addScreenShake(15, 25); } } } } }; self.handleWaveProgression(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x001122 }); /**** * Game Code ****/ var isHidingUpgradeMenu = false; // Initialize persistent storage with defaults var gameStats = storage.gameStats || { totalEnemiesKilled: 0, totalGoldEarned: 0, highestWave: 0, towersBuilt: 0, bossesDefeated: 0, gamesPlayed: 0, totalPlayTime: 0 }; var achievements = storage.achievements || { firstKill: false, wave10: false, wave25: false, wave50: false, goldMaster: false, towerMaster: false, bossSlayer: false, perfectDefense: false }; var gameSettings = storage.gameSettings || { soundEnabled: true, particlesEnabled: true, autoUpgrade: false, difficulty: 'normal' }; // Save functions function saveGameStats() { storage.gameStats = gameStats; } function saveAchievements() { storage.achievements = achievements; } function saveSettings() { storage.gameSettings = gameSettings; } function checkAchievements() { if (!achievements.firstKill && gameStats.totalEnemiesKilled >= 1) { achievements.firstKill = true; showAchievement("First Blood!", "Defeat your first enemy"); } if (!achievements.wave10 && currentWave >= 10) { achievements.wave10 = true; showAchievement("Wave Warrior!", "Reach wave 10"); } if (!achievements.goldMaster && gameStats.totalGoldEarned >= 1000) { achievements.goldMaster = true; showAchievement("Gold Master!", "Earn 1000 total gold"); } if (!achievements.bossSlayer && gameStats.bossesDefeated >= 5) { achievements.bossSlayer = true; showAchievement("Boss Slayer!", "Defeat 5 bosses"); } saveAchievements(); } function showAchievement(title, description) { var achievement = new Container(); var bg = achievement.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); bg.width = 600; bg.height = 150; bg.tint = 0xffd700; var titleText = new Text2(title, { size: 50, fill: 0x000000, weight: 800 }); titleText.anchor.set(0.5, 0.5); titleText.y = -20; achievement.addChild(titleText); var descText = new Text2(description, { size: 35, fill: 0x333333, weight: 400 }); descText.anchor.set(0.5, 0.5); descText.y = 20; achievement.addChild(descText); achievement.x = 2048 / 2; achievement.y = -150; game.addChild(achievement); tween(achievement, { y: 150 }, { duration: 500, easing: tween.bounceOut, onFinish: function onFinish() { LK.setTimeout(function () { tween(achievement, { y: -150 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { achievement.destroy(); } }); }, 3000); } }); } 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 = 80; var lives = 20; var score = 0; var currentWave = 0; var totalWaves = 50; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 12000 / 2; // Weather system var weatherEffects = []; var currentWeather = 'clear'; var weatherTimer = 0; var weatherDuration = 1800; // 30 seconds function createWeatherEffect() { var weather = ['rain', 'snow', 'storm', 'fog'][Math.floor(Math.random() * 4)]; currentWeather = weather; weatherTimer = 0; // Clear existing weather for (var i = 0; i < weatherEffects.length; i++) { weatherEffects[i].destroy(); } weatherEffects = []; switch (weather) { case 'rain': for (var i = 0; i < 50; i++) { var drop = new Container(); var graphic = drop.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); graphic.tint = 0x4488ff; graphic.scaleX = 0.3; graphic.scaleY = 1.5; drop.x = Math.random() * 2048; drop.y = -20; drop.speed = 8 + Math.random() * 4; game.addChild(drop); weatherEffects.push(drop); } break; case 'snow': for (var i = 0; i < 30; i++) { var flake = new Container(); var graphic = flake.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); graphic.tint = 0xffffff; graphic.scaleX = graphic.scaleY = 0.8; flake.x = Math.random() * 2048; flake.y = -20; flake.speed = 2 + Math.random() * 2; flake.drift = (Math.random() - 0.5) * 2; game.addChild(flake); weatherEffects.push(flake); } break; } } function updateWeather() { weatherTimer++; if (weatherTimer > weatherDuration) { createWeatherEffect(); } for (var i = weatherEffects.length - 1; i >= 0; i--) { var effect = weatherEffects[i]; effect.y += effect.speed || 5; if (effect.drift) { effect.x += effect.drift; } if (effect.y > 2732 + 50) { effect.y = -20; effect.x = Math.random() * 2048; } } // Apply weather effects to gameplay switch (currentWeather) { case 'storm': // Lightning strikes randomly if (Math.random() < 0.001) { var lightning = new Container(); var bolt = lightning.attachAsset('lightningBolt', { anchorX: 0.5, anchorY: 0.5 }); bolt.tint = 0xffff00; lightning.x = Math.random() * 2048; lightning.y = Math.random() * 2732; lightning.rotation = Math.random() * Math.PI * 2; game.addChild(lightning); tween(lightning, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { lightning.destroy(); } }); // Damage nearby enemies for (var e = 0; e < enemies.length; e++) { var enemy = enemies[e]; var dx = enemy.x - lightning.x; var dy = enemy.y - lightning.y; if (dx * dx + dy * dy < 10000) { enemy.health -= 50; } } } break; case 'fog': // Reduce tower range slightly for (var t = 0; t < towers.length; t++) { towers[t].visibility = 0.8; } break; } } var sourceTower = null; var enemiesToSpawn = 10; // Default number of enemies per wave var goldText = new Text2('Gold: ' + gold, { size: 60, fill: 0x00FFAA, weight: 800 }); goldText.anchor.set(0.5, 0.5); var livesText = new Text2('Lives: ' + lives, { size: 60, fill: 0x00AAFF, weight: 800 }); livesText.anchor.set(0.5, 0.5); var scoreText = new Text2('Score: ' + score, { size: 60, fill: 0xFF0088, 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; function updateUI() { // Animate text changes with scale effect var oldGoldText = goldText.text; var oldLivesText = livesText.text; var oldScoreText = scoreText.text; goldText.setText('Gold: ' + gold); livesText.setText('Lives: ' + lives); scoreText.setText('Score: ' + score); // Add cyberpunk glow pulse to all UI text tween(goldText, { alpha: 1.2 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tween(goldText, { alpha: 1.0 }, { duration: 300, easing: tween.easeInOut }); } }); tween(livesText, { alpha: 1.2 }, { duration: 350, easing: tween.easeInOut, onFinish: function onFinish() { tween(livesText, { alpha: 1.0 }, { duration: 350, easing: tween.easeInOut }); } }); tween(scoreText, { alpha: 1.2 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { tween(scoreText, { alpha: 1.0 }, { duration: 400, easing: tween.easeInOut }); } }); // Animate gold changes if (oldGoldText !== goldText.text) { tween.stop(goldText, { scaleX: true, scaleY: true }); tween(goldText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(goldText, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.elasticOut }); } }); } // Animate lives changes with color feedback if (oldLivesText !== livesText.text) { tween.stop(livesText, { scaleX: true, scaleY: true }); var livesDiff = parseInt(livesText.text.split(': ')[1]) - parseInt(oldLivesText.split(': ')[1]); if (livesDiff < 0) { // Lives lost - red flash livesText.tint = 0xff4444; tween(livesText, { scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(livesText, { scaleX: 1.0, scaleY: 1.0, tint: 0x00FF00 }, { duration: 300, easing: tween.elasticOut }); } }); } else { // Lives gained - green pulse tween(livesText, { scaleX: 1.15, scaleY: 1.15 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(livesText, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.elasticOut }); } }); } } // Animate score changes if (oldScoreText !== scoreText.text) { tween.stop(scoreText, { scaleX: true, scaleY: true }); tween(scoreText, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(scoreText, { scaleX: 1.0, scaleY: 1.0 }, { duration: 150, easing: tween.elasticOut }); } }); } } // Screen shake system var screenShake = { intensity: 0, duration: 0, timer: 0 }; function addScreenShake(intensity, duration) { screenShake.intensity = Math.max(screenShake.intensity, intensity); screenShake.duration = Math.max(screenShake.duration, duration); screenShake.timer = 0; } function updateScreenShake() { if (screenShake.duration > 0) { screenShake.timer++; var progress = screenShake.timer / screenShake.duration; var currentIntensity = screenShake.intensity * (1 - progress); game.x = (Math.random() - 0.5) * currentIntensity * 2; game.y = (Math.random() - 0.5) * currentIntensity * 2; if (screenShake.timer >= screenShake.duration) { screenShake.duration = 0; game.x = 0; game.y = 0; } } else { game.x = 0; game.y = 0; } } function setGold(value) { gold = value; updateUI(); } var debugLayer = new Container(); var towerLayer = new Container(); // Create three separate layers for enemy hierarchy 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 }); 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 = 5; switch (towerType) { case 'rapid': cost = 15; break; case 'sniper': cost = 25; break; case 'splash': cost = 35; break; case 'slow': cost = 45; break; case 'poison': cost = 55; break; case 'lightning': cost = 65; break; case 'support': cost = 40; break; case 'beam': cost = 80; 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 || 'default'); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); 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) { if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) { placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType); } else { var notification = game.addChild(new Notification("Tower would block the path!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } 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 = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison', 'lightning', 'support', 'beam']; var sourceTowers = []; var towerSpacing = 300; // 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; var powerUps = []; // Initialize weather system LK.setTimeout(function () { createWeatherEffect(); }, 5000); // Start weather after 5 seconds // Create animated cyberpunk background grid var backgroundGrid = new Container(); game.addChildAt(backgroundGrid, 0); // Add behind everything // Create horizontal grid lines for (var i = 0; i < 15; i++) { var hLine = backgroundGrid.attachAsset('laser', { anchorX: 0, anchorY: 0.5 }); hLine.width = 2048; hLine.height = 2; hLine.y = i * (2732 / 15); hLine.tint = 0x001144; hLine.alpha = 0.3; hLine.blendMode = 1; // Additive blending for neon glow // Animate grid lines with pulsing effect tween(hLine, { alpha: 0.6 }, { duration: 2000 + Math.random() * 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(hLine, { alpha: 0.3 }, { duration: 2000 + Math.random() * 1000, easing: tween.easeInOut }); } }); } // Create vertical grid lines for (var i = 0; i < 12; i++) { var vLine = backgroundGrid.attachAsset('laser', { anchorX: 0.5, anchorY: 0 }); vLine.width = 2; vLine.height = 2732; vLine.x = i * (2048 / 12); vLine.rotation = Math.PI / 2; vLine.tint = 0x001144; vLine.alpha = 0.2; vLine.blendMode = 1; // Additive blending for neon glow // Staggered animation for vertical lines LK.setTimeout(function () { tween(vLine, { alpha: 0.5 }, { duration: 1500 + Math.random() * 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(vLine, { alpha: 0.2 }, { duration: 1500 + Math.random() * 1000, easing: tween.easeInOut }); } }); }, i * 200); } 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 % 10 === 0 && currentWave > 0; if (isBossWave && waveType !== 'swarm') { // 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; } // Dynamic difficulty based on performance var difficultyMultiplier = 1.0; if (lives > 15) { difficultyMultiplier = 1.2; // Make it harder if player is doing well } else if (lives < 5) { difficultyMultiplier = 0.8; // Make it easier if struggling } // Spawn the appropriate number of enemies for (var i = 0; i < Math.floor(enemyCount * difficultyMultiplier); i++) { var enemy = new Enemy(waveType); // 10% chance for special variants if (Math.random() < 0.1 && currentWave > 5) { // Create special variants var variant = Math.random(); if (variant < 0.3) { // Shielded enemy enemy.shielded = true; enemy.maxHealth *= 1.5; enemy.health = enemy.maxHealth; var shield = enemy.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5 }); shield.alpha = 0.3; shield.y = -10; } else if (variant < 0.6) { // Regenerating enemy enemy.regenerating = true; enemy.regenRate = 2; // HP per second } else { // Teleporting enemy enemy.teleporting = true; enemy.teleportTimer = 300; // 5 seconds } } // Add enemy to the appropriate layer based on type if (enemy.isFlying) { // Add flying enemy to the top layer enemyLayerTop.addChild(enemy); // If it's a flying enemy, add its shadow to the middle layer if (enemy.shadow) { enemyLayerMiddle.addChild(enemy.shadow); } } else { // Add normal/ground enemies to the bottom layer enemyLayerBottom.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; // All enemy types now spawn in the middle 6 tiles at the top spacing var gridWidth = 24; var midPoint = Math.floor(gridWidth / 2); // 12 // Find a column that isn't occupied by another enemy that's not yet in view var availableColumns = []; for (var col = midPoint - 3; col < midPoint + 3; 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 === col && enemies[e].currentCellY < 4) { columnOccupied = true; break; } } if (!columnOccupied) { availableColumns.push(col); } } // If all columns are occupied, use original random method var spawnX; if (availableColumns.length > 0) { // Choose a random unoccupied column spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)]; } else { // Fallback to random if all columns are occupied spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 9 to 14 } var spawnY = -1 - Math.random() * 5; // Random distance above the grid for spreading 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; } // Boss enemies give more gold and score var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5); var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); gameStats.totalGoldEarned += goldEarned; // Combo system - track consecutive kills without losing lives if (!window.comboCount) window.comboCount = 0; if (!window.lastLives) window.lastLives = lives; if (lives < window.lastLives) { window.comboCount = 0; // Reset combo if lives lost } else { window.comboCount++; } window.lastLives = lives; // Score multiplier based on combo var comboMultiplier = Math.min(1 + window.comboCount / 50, 3.0); var baseScore = enemy.isBoss ? 100 : 5; var finalScore = Math.floor(baseScore * comboMultiplier); score += finalScore; // Show combo indicator if (window.comboCount > 10 && window.comboCount % 10 === 0) { var comboText = new Container(); var text = new Text2("COMBO x" + Math.floor(comboMultiplier * 10) / 10, { size: 60, fill: 0xff4400, weight: 800 }); text.anchor.set(0.5, 0.5); comboText.addChild(text); comboText.x = enemy.x; comboText.y = enemy.y - 50; game.addChild(comboText); tween(comboText, { y: enemy.y - 100, alpha: 0 }, { duration: 1000, onFinish: function onFinish() { comboText.destroy(); } }); } gameStats.totalEnemiesKilled++; if (enemy.isBoss) { gameStats.bossesDefeated++; } checkAchievements(); saveGameStats(); // Enhanced death effects if (enemy.isBoss) { // Multiple explosion effects for boss death var mainExplosion = new ExplosionEffect(enemy.x, enemy.y, 150, 0xff6600); game.addChild(mainExplosion); // Secondary explosions for (var ex = 0; ex < 5; ex++) { LK.setTimeout(function () { var secExplosion = new ExplosionEffect(enemy.x + (Math.random() - 0.5) * 100, enemy.y + (Math.random() - 0.5) * 100, 80, 0xffaa00); game.addChild(secExplosion); }, ex * 100); } addScreenShake(25, 40); // Intense screen shake for boss death } else { // Standard enemy death effect var deathExplosion = new ExplosionEffect(enemy.x, enemy.y, 40, 0xff4444); game.addChild(deathExplosion); // Small screen shake for regular enemies addScreenShake(3, 10); } updateUI(); // Randomly spawn power-ups when enemies die if (Math.random() < 0.05) { // 5% chance var powerUpTypes = ['gold', 'health', 'damage', 'speed', 'shield']; var powerUpType = powerUpTypes[Math.floor(Math.random() * powerUpTypes.length)]; var powerUp = new PowerUp(powerUpType, enemy.x, enemy.y); game.addChild(powerUp); powerUps.push(powerUp); } // 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 { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); continue; } 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 { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); lives = Math.max(0, lives - 1); updateUI(); } } for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent) { if (bullets[i].targetEnemy) { var targetEnemy = bullets[i].targetEnemy; var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } bullets.splice(i, 1); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } // Update weather effects updateWeather(); // Update power-ups if (!window.powerUps) window.powerUps = []; for (var p = window.powerUps.length - 1; p >= 0; p--) { if (!window.powerUps[p].parent) { window.powerUps.splice(p, 1); } } // Update game statistics gameStats.totalPlayTime++; if (currentWave > gameStats.highestWave) { gameStats.highestWave = currentWave; saveGameStats(); } // Update screen shake updateScreenShake(); };
===================================================================
--- original.js
+++ change.js
@@ -81,11 +81,9 @@
if (distance < self.speed) {
// Enhanced impact visuals
var isCritical = Math.random() < 0.15; // 15% critical hit chance
var finalDamage = isCritical ? Math.floor(self.damage * 1.5) : self.damage;
- // Create floating damage number
- var damageNumber = new FloatingDamageNumber(finalDamage, self.targetEnemy.x, self.targetEnemy.y, isCritical);
- game.addChild(damageNumber);
+ // Floating damage numbers removed
// Enhanced impact particles based on bullet type
var particleCount = isCritical ? 15 : 8;
var particleType = 'fireParticle';
switch (self.type) {
@@ -230,11 +228,9 @@
end.health = 0;
} else {
end.healthBar.width = end.health / end.maxHealth * 70;
}
- // Create damage number
- var damageNumber = new FloatingDamageNumber(actualDamage, end.x, end.y, false);
- game.addChild(damageNumber);
+ // Floating damage numbers removed
}
}
self.update = function () {
self.age++;
@@ -895,117 +891,9 @@
self.destroy();
}, 800);
return self;
});
-var FloatingDamageNumber = Container.expand(function (damage, x, y, isCritical) {
- var self = Container.call(this);
- self.x = x;
- self.y = y;
- var color = isCritical ? 0xffaa00 : 0xff4444;
- var size = isCritical ? 80 : 60;
- var shadowText = new Text2(damage.toString(), {
- size: size,
- fill: 0x000000,
- weight: 800
- });
- shadowText.anchor.set(0.5, 0.5);
- shadowText.x = 3;
- shadowText.y = 3;
- self.addChild(shadowText);
- var damageText = new Text2(damage.toString(), {
- size: size,
- fill: color,
- weight: 800
- });
- damageText.anchor.set(0.5, 0.5);
- self.addChild(damageText);
- self.alpha = 0;
- self.scaleX = 0.3;
- self.scaleY = 0.3;
- // Add cyberpunk glow effect to damage text
- var glowText = new Text2(damage.toString(), {
- size: size + 8,
- fill: color,
- weight: 800
- });
- glowText.anchor.set(0.5, 0.5);
- glowText.alpha = 0.4;
- glowText.blendMode = 1; // Additive blending for glow
- self.addChildAt(glowText, 0); // Behind main text
- // Create electric particle effects for critical hits
- if (isCritical) {
- for (var p = 0; p < 8; p++) {
- var particle = self.attachAsset('sparkParticle', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- particle.tint = 0x00ffaa;
- particle.blendMode = 1;
- particle.alpha = 0.8;
- particle.scaleX = particle.scaleY = 0.8;
- var pAngle = p / 8 * Math.PI * 2;
- var pRadius = 30 + Math.random() * 20;
- particle.x = Math.cos(pAngle) * pRadius;
- particle.y = Math.sin(pAngle) * pRadius;
- tween(particle, {
- x: Math.cos(pAngle) * (pRadius + 40),
- y: Math.sin(pAngle) * (pRadius + 40),
- alpha: 0,
- scaleX: 0.2,
- scaleY: 0.2
- }, {
- duration: 800,
- easing: tween.easeOut
- });
- }
- }
- // Enhanced appearance animation with cyberpunk bounce
- tween(self, {
- alpha: 1,
- scaleX: isCritical ? 1.6 : 1.2,
- scaleY: isCritical ? 1.6 : 1.2,
- y: y - 60
- }, {
- duration: isCritical ? 500 : 350,
- easing: tween.bounceOut,
- onFinish: function onFinish() {
- // Pulse glow effect
- tween(glowText, {
- scaleX: 1.3,
- scaleY: 1.3,
- alpha: 0.7
- }, {
- duration: 200,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- tween(glowText, {
- scaleX: 1.0,
- scaleY: 1.0,
- alpha: 0.4
- }, {
- duration: 200,
- easing: tween.easeInOut
- });
- }
- });
- // Enhanced fade out with electric discharge effect
- tween(self, {
- alpha: 0,
- y: y - 140,
- scaleX: 0.6,
- scaleY: 0.6,
- rotation: isCritical ? Math.PI * 0.5 : 0
- }, {
- duration: 800,
- easing: tween.cubicIn,
- onFinish: function onFinish() {
- self.destroy();
- }
- });
- }
- });
- return self;
-});
+// FloatingDamageNumber class removed
var GoldIndicator = Container.expand(function (value, x, y) {
var self = Container.call(this);
var shadowText = new Text2("+" + value, {
size: 45,
White circle with two eyes, seen from above.. In-Game asset. 2d. High contrast. No shadows
White simple circular enemy seen from above, black outline. Black eyes, with a single shield in-font of it. Black and white only. Blue background.
White circle with black outline. Blue background.. In-Game asset. 2d. High contrast. No shadows