User prompt
Compilation error[L3291]: Uncaught TypeError: Cannot read properties of undefined (reading 'attachAsset')
User prompt
design path and path visual for enemy to walk properly and follow the path design tricky path which looks creative and fun for enemy ensure game balance
User prompt
add all towers visual assets till all levels
User prompt
analyze the code and apply background image and music and other sounds
User prompt
add full tutorial in game ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
all the system warning not readable due to color correction is required for clean understanding and hint
User prompt
shift station position to right side below scores
User prompt
- **Branching Routes**: Enemies can choose between multiple paths add this ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
apply all ##### **5. Ultimate Abilities** - **Tower Ultimates**: Devastating special attacks with long cooldowns - **Orbital Strike**: Sniper tower calls down satellite laser - **Nanite Swarm**: Poison tower releases self-replicating nanobots - **Time Dilation**: Slow tower creates temporal distortion field ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
apply all ##### **4. Tower Specialization Paths** - **Branch Upgrades**: At level 3, towers can choose specialization paths - **Sniper Variants**: Anti-armor (pierce shields) vs Long-range (extended range) - **Splash Variants**: Nuclear (huge damage) vs Chain reaction (spreads) - **Support Integration**: Towers can link together for combined effects ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
apply all ##### **3. Environmental Hazards & Power-ups** - **Asteroid Fields**: Destructible obstacles that block paths - **Nebula Clouds**: Areas that slow all units but provide stealth - **Solar Flares**: Periodic events that boost energy-based towers - **Wormholes**: Teleportation portals that redirect enemies ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
apply all ##### **2. Space Station Customization** - **Modular Base**: Players can customize their space station layout - **Research Lab**: Unlock new technologies and tower upgrades - **Shield Generator**: Temporary invulnerability for critical moments - **Teleporter Network**: Instantly move heroes or redirect enemy paths ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
apply all ## Futuristic Update Patch Suggestions ### **🚀 "Galactic Expansion" Major Update** #### **New Core Features** ##### **1. Hero Units System** - **Deployable Heroes**: Special units that can be placed anywhere and move around - **Unique Abilities**: Each hero has special powers with cooldowns - **Leveling System**: Heroes gain experience and unlock new abilities - **Hero Types**: Tank (absorbs damage), Support (buffs towers), DPS (high damage) ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
apply fixes #### **User Experience Issues** 1. **Tutorial Absence**: No guidance for new players 2. **Visual Clarity**: Sometimes hard to distinguish enemy types 3. **Touch Responsiveness**: Mobile dragging could be more responsive
User prompt
apply fixes #### **Gameplay Balance Issues** 1. **Tower Balance**: Some towers significantly outperform others 2. **Wave Difficulty**: Exponential health scaling may become too steep in later waves 3. **Economy Balance**: Gold earning vs tower costs may need adjustment
User prompt
**Solution Approach**: - Create a comprehensive `destroy()` method for each class that: - Removes the object from all parent containers - Clears all array references to the object - Stops all active tweens on the object - Removes all event listeners - Nullifies all object references - Implement proper cleanup in the main game loop when removing objects from arrays - Use weak references where possible to avoid circular reference chains - Add memory monitoring to detect when cleanup isn't working properly - Ensure that when bullets are destroyed, they remove themselves from their target enemy's tracking array ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
**Solution Approach**: - Implement object pooling - reuse bullet and enemy objects instead of creating/destroying them constantly - Optimize collision detection using spatial partitioning (only check nearby objects) - Limit the maximum number of visual effects active at once - Cache pathfinding results and only recalculate when the map actually changes - Use level-of-detail rendering - simpler graphics for distant or numerous objects - Implement frame rate adaptive logic - skip some update cycles when performance drop
User prompt
**Solution Approach**: - Before destroying any enemy, stop all active tweens on that enemy using `tween.stop(enemy)` - Add cleanup code in enemy destruction that cancels any ongoing animations - Implement a "isDestroyed" flag on enemies to prevent new tweens from starting on dying enemies - Use defensive programming - check if objects still exist before applying tween effects ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: Cannot use 'in' operator to search for 'tint' in null' in or related to this line: 'tween(self.targetEnemy, {' Line Number: 101 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
will change the current game as theme of galaxy adventure TD defense and sci-fi tower type and animation and attack ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Remix started
Copy Tower Defense Template
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Asteroid = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; self.health = 150; self.maxHealth = 150; self.isDestroyed = false; self.gridX = Math.floor((x - grid.x) / CELL_SIZE); self.gridY = Math.floor((y - grid.y) / CELL_SIZE); var asteroidGraphics = self.attachAsset('asteroid', { anchorX: 0.5, anchorY: 0.5 }); // Add rotation animation tween(asteroidGraphics, { rotation: Math.PI * 2 }, { duration: 8000, easing: tween.linear, onFinish: function onFinish() { if (!self.isDestroyed) { asteroidGraphics.rotation = 0; } } }); // Health bar var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarOutline.y = healthBar.y = -asteroidGraphics.height / 2 - 15; healthBarOutline.x = -healthBarOutline.width / 2; healthBar.x = -healthBar.width / 2; healthBar.tint = 0x8B4513; self.healthBar = healthBar; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.health = 0; self.destroy(); } else { self.healthBar.width = self.health / self.maxHealth * 70; } }; self.down = function () { // Allow manual destruction by tapping self.takeDamage(50); }; self.destroy = function () { if (self.isDestroyed) return; self.isDestroyed = true; // Create debris effect for (var i = 0; i < 5; i++) { var debris = new Container(); debris.x = self.x + (Math.random() - 0.5) * 100; debris.y = self.y + (Math.random() - 0.5) * 100; var debrisGraphics = debris.attachAsset('asteroidDebris', { anchorX: 0.5, anchorY: 0.5 }); game.addChild(debris); tween(debris, { x: debris.x + (Math.random() - 0.5) * 200, y: debris.y + (Math.random() - 0.5) * 200, alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { if (debris.parent) { debris.parent.removeChild(debris); } } }); } // Clear grid cell var cell = grid.getCell(self.gridX, self.gridY); if (cell) { cell.type = 0; } // Remove from asteroids array var asteroidIndex = asteroids.indexOf(self); if (asteroidIndex !== -1) { asteroids.splice(asteroidIndex, 1); } // Recalculate paths grid.pathFind(); grid.renderDebug(); tween.stop(self); if (self.parent) { self.parent.removeChild(self); } Container.prototype.destroy.call(self); }; // Block the grid cell var cell = grid.getCell(self.gridX, self.gridY); if (cell) { cell.type = 1; } 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; // Create sci-fi projectile based on bullet type var assetName = 'bullet'; // default var bulletGraphics; if (self.type === 'rapid') { bulletGraphics = self.attachAsset('energyBeam', { anchorX: 0.5, anchorY: 0.5 }); bulletGraphics.tint = 0x00aaff; } else if (self.type === 'sniper') { bulletGraphics = self.attachAsset('laserBurst', { anchorX: 0.5, anchorY: 0.5 }); bulletGraphics.tint = 0xff0044; } else if (self.type === 'splash') { bulletGraphics = self.attachAsset('plasmaBolt', { anchorX: 0.5, anchorY: 0.5 }); bulletGraphics.tint = 0x44ff00; } else { bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); } // Add pulsing glow effect to projectiles tween(bulletGraphics, { scaleX: 1.3, scaleY: 1.3, alpha: 0.7 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tween(bulletGraphics, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 300, easing: tween.easeInOut }); } }); self.update = function () { if (!self.targetEnemy || !self.targetEnemy.parent) { returnBulletToPool(self); 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) { // Create sci-fi impact flash if (self.targetEnemy && self.targetEnemy.parent && !self.targetEnemy.isDestroyed) { tween(self.targetEnemy, { tint: 0xffffff }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { if (self.targetEnemy && self.targetEnemy.parent && !self.targetEnemy.isDestroyed) { tween(self.targetEnemy, { tint: 0xffffff }, { duration: 200, easing: tween.easeIn }); } } }); } // Apply damage to target enemy self.targetEnemy.health -= self.damage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; // Play enemy destroy sound LK.getSound('enemyDestroy').play(); } else { self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; // Play enemy hit sound LK.getSound('enemyHit').play(); } // Apply special effects based on bullet type if (self.type === 'splash') { // Create visual splash effect with limiting if (canCreateEffect()) { var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); game.addChild(splashEffect); registerEffect(splashEffect); } // Determine splash radius and damage based on specialization var splashRadius = CELL_SIZE * 1.5; var splashDamageMultiplier = 0.5; if (self.specialization === 'nuclear') { splashRadius = CELL_SIZE * 3; splashDamageMultiplier = 0.8; // Create nuclear explosion effect if (canCreateEffect()) { var nuclearEffect = new Container(); nuclearEffect.x = self.targetEnemy.x; nuclearEffect.y = self.targetEnemy.y; var nuclearGraphics = nuclearEffect.attachAsset('nuclearBlast', { anchorX: 0.5, anchorY: 0.5 }); nuclearGraphics.tint = 0xFF0000; nuclearGraphics.alpha = 0.8; game.addChild(nuclearEffect); tween(nuclearEffect, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (nuclearEffect.parent) { nuclearEffect.parent.removeChild(nuclearEffect); } } }); } } // Splash damage to nearby enemies 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) { var splashDamage = self.damage * splashDamageMultiplier; otherEnemy.health -= splashDamage; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } else { otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70; } // Chain reaction effect if (self.specialization === 'chainreaction' && Math.random() < 0.3) { // 30% chance to chain to another enemy for (var j = 0; j < enemies.length; j++) { var chainEnemy = enemies[j]; if (chainEnemy !== otherEnemy && chainEnemy !== self.targetEnemy) { var chainDx = chainEnemy.x - otherEnemy.x; var chainDy = chainEnemy.y - otherEnemy.y; var chainDistance = Math.sqrt(chainDx * chainDx + chainDy * chainDy); if (chainDistance <= CELL_SIZE * 2) { chainEnemy.health -= splashDamage * 0.7; if (chainEnemy.health <= 0) { chainEnemy.health = 0; } else { chainEnemy.healthBar.width = chainEnemy.health / chainEnemy.maxHealth * 70; } break; } } } } } } } } else if (self.type === 'slow') { // Prevent slow effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual slow effect with limiting if (canCreateEffect()) { var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); registerEffect(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 with limiting if (canCreateEffect()) { var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); registerEffect(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 with limiting if (canCreateEffect()) { var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper'); game.addChild(sniperEffect); registerEffect(sniperEffect); } } returnBulletToPool(self); } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; self.destroy = function () { if (self.isDestroyed) { return; } self.isDestroyed = true; // Stop all active tweens on this bullet and its graphics tween.stop(self); if (self.children && self.children[0]) { tween.stop(self.children[0]); } // Remove from target enemy's bullets array if (self.targetEnemy && self.targetEnemy.bulletsTargetingThis) { var bulletIndex = self.targetEnemy.bulletsTargetingThis.indexOf(self); if (bulletIndex !== -1) { self.targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } // Remove from bullets array var bulletArrayIndex = bullets.indexOf(self); if (bulletArrayIndex !== -1) { bullets.splice(bulletArrayIndex, 1); } // Remove from parent container if (self.parent) { self.parent.removeChild(self); } // Nullify all object references self.targetEnemy = null; // Call parent destroy Container.prototype.destroy.call(self); }; 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 = 0x220044; // Deep space color 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; // Energy field blue } else { // Create starfield effect with bluish tint var starfieldTint = 0x001122 + (tint << 8) + (tint >> 1); cellGraphics.tint = starfieldTint; } 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; // Create energy particles around main effect var particles = []; for (var i = 0; i < 6; i++) { var particle = self.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); particle.width = 8; particle.height = 8; var angle = i / 6 * Math.PI * 2; particle.x = Math.cos(angle) * 30; particle.y = Math.sin(angle) * 30; particles.push(particle); } switch (type) { case 'splash': effectGraphics.tint = 0x33CC00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5; for (var i = 0; i < particles.length; i++) { particles[i].tint = 0x88FF44; } break; case 'slow': effectGraphics.tint = 0x9900FF; effectGraphics.width = effectGraphics.height = CELL_SIZE; for (var i = 0; i < particles.length; i++) { particles[i].tint = 0xCC44FF; } break; case 'poison': effectGraphics.tint = 0x00FFAA; effectGraphics.width = effectGraphics.height = CELL_SIZE; for (var i = 0; i < particles.length; i++) { particles[i].tint = 0x44FFCC; } break; case 'sniper': effectGraphics.tint = 0xFF5500; effectGraphics.width = effectGraphics.height = CELL_SIZE; for (var i = 0; i < particles.length; i++) { particles[i].tint = 0xFF8844; } break; } effectGraphics.alpha = 0.7; // Animate particles spiraling outward for (var i = 0; i < particles.length; i++) { var particle = particles[i]; var delay = i * 50; tween(particle, { x: particle.x * 2, y: particle.y * 2, scaleX: 0.5, scaleY: 0.5, alpha: 0 }, { duration: 400, delay: delay, easing: tween.easeOut }); } 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(); } }); } }); self.destroy = function () { if (self.isDestroyed) { return; } self.isDestroyed = true; // Stop all active tweens on this effect and its children tween.stop(self); for (var i = 0; i < self.children.length; i++) { if (self.children[i]) { tween.stop(self.children[i]); } } // Remove from activeEffects array var effectIndex = activeEffects.indexOf(self); if (effectIndex !== -1) { activeEffects.splice(effectIndex, 1); } // Remove from parent container if (self.parent) { self.parent.removeChild(self); } // Call parent destroy Container.prototype.destroy.call(self); }; 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; self.isDestroyed = 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 }); // 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; // Add visual type indicator for better enemy identification if (self.type !== 'normal') { var typeIndicator = self.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); typeIndicator.width = 16; typeIndicator.height = 16; typeIndicator.y = -enemyGraphics.height / 2 - 35; // Above health bar // Color code the indicator based on enemy type switch (self.type) { case 'fast': typeIndicator.tint = 0x00AAFF; break; case 'immune': typeIndicator.tint = 0xAA0000; break; case 'flying': typeIndicator.tint = 0xFFFF00; break; case 'swarm': typeIndicator.tint = 0xFF00FF; break; } // Make boss indicators larger and more prominent if (self.isBoss) { typeIndicator.width = 24; typeIndicator.height = 24; typeIndicator.tint = 0xFFD700; // Gold for bosses // Add pulsing effect for boss indicator tween(typeIndicator, { scaleX: 1.3, scaleY: 1.3, alpha: 0.7 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(typeIndicator, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 800, easing: tween.easeInOut }); } }); } } self.update = function () { if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; } // Handle slow effect if (self.isImmune) { // Immune enemies cannot be slowed 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 } } } } // Improved visual clarity for enemy types with better differentiation var baseTint = 0xFFFFFF; // First set base tint based on enemy type for better visual clarity switch (self.type) { case 'fast': baseTint = 0x4488FF; // Bright blue for fast enemies break; case 'immune': baseTint = 0xFF4444; // Bright red for immune enemies break; case 'flying': baseTint = 0xFFDD44; // Bright yellow for flying enemies break; case 'swarm': baseTint = 0xFF44DD; // Bright magenta for swarm enemies break; case 'normal': default: baseTint = 0xCCCCCC; // Light gray for normal enemies break; } // Apply boss scaling to tint if this is a boss if (self.isBoss) { // Make boss enemies more vibrant and add a red outline effect var r = baseTint >> 16 & 0xFF; var g = baseTint >> 8 & 0xFF; var b = baseTint & 0xFF; // Boost color intensity for bosses r = Math.min(255, Math.floor(r * 1.3)); g = Math.min(255, Math.floor(g * 1.3)); b = Math.min(255, Math.floor(b * 1.3)); baseTint = r << 16 | g << 8 | b; } // Then apply status effect overlays if (self.isImmune) { // Keep immune tint but add pulsing effect for better visibility enemyGraphics.tint = baseTint; // Add shield shimmer effect for immune enemies if (!self.shieldEffect && !self.isDestroyed) { self.shieldEffect = true; tween(enemyGraphics, { alpha: 0.8 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isDestroyed) { tween(enemyGraphics, { alpha: 1.0 }, { duration: 500, easing: tween.easeInOut }); } } }); } } else if (self.poisoned && self.slowed) { // Blend poison green with slow purple over the base tint enemyGraphics.tint = 0x4C7FD4; // Keep existing blend color } else if (self.poisoned) { // Poison overlay - green tint var r = Math.floor((baseTint >> 16 & 0xFF) * 0.5); var g = Math.min(255, Math.floor((baseTint >> 8 & 0xFF) * 0.5 + 170)); var b = Math.floor((baseTint & 0xFF) * 0.5 + 85); enemyGraphics.tint = r << 16 | g << 8 | b; // Add crackling energy effect for poisoned if (!self.poisonFlicker && !self.isDestroyed) { self.poisonFlicker = true; tween(enemyGraphics, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { if (!self.isDestroyed) { tween(enemyGraphics, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.poisonFlicker = false; } }); } } }); } } else if (self.slowed) { // Slow overlay - purple tint var r = Math.floor((baseTint >> 16 & 0xFF) * 0.5 + 76); var g = Math.floor((baseTint >> 8 & 0xFF) * 0.5); var b = Math.min(255, Math.floor((baseTint & 0xFF) * 0.5 + 127)); enemyGraphics.tint = r << 16 | g << 8 | b; } else { enemyGraphics.tint = baseTint; self.shieldEffect = false; } 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 && !self.isDestroyed) { 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; }; self.destroy = function () { if (self.isDestroyed) { return; } self.isDestroyed = true; // Stop all active tweens on this enemy and its graphics tween.stop(self); if (self.children && self.children[0]) { tween.stop(self.children[0]); } // Clear all bullets targeting this enemy for (var i = 0; i < self.bulletsTargetingThis.length; i++) { var bullet = self.bulletsTargetingThis[i]; if (bullet) { bullet.targetEnemy = null; } } self.bulletsTargetingThis = []; // Remove from enemies array var enemyIndex = enemies.indexOf(self); if (enemyIndex !== -1) { enemies.splice(enemyIndex, 1); } // Remove from parent container if (self.parent) { self.parent.removeChild(self); } // Clean up shadow if it's a flying enemy if (self.isFlying && self.shadow) { if (self.shadow.parent) { self.shadow.parent.removeChild(self.shadow); } self.shadow = null; } // Nullify all object references self.targetEnemy = null; self.currentTarget = null; self.flyingTarget = null; self.healthBar = null; self.energyCore = null; // Call parent destroy Container.prototype.destroy.call(self); }; return self; }); var GoldIndicator = Container.expand(function (value, x, y) { var self = Container.call(this); var shadowText = new Text2("+" + value, { size: 45, fill: 0x000000, weight: 800 }); shadowText.anchor.set(0.5, 0.5); shadowText.x = 2; shadowText.y = 2; self.addChild(shadowText); var goldText = new Text2("+" + value, { size: 45, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); self.x = x; self.y = y; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2, y: y - 40 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: y - 80 }, { duration: 600, easing: tween.easeIn, delay: 800, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; self.spawns = []; self.goals = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { score: 0, pathId: 0, towersInRange: [] }; } } /* Cell Types 0: Transparent floor 1: Wall 2: Spawn 3: Goal */ 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; } // Mark junction cells for branching for (var i = 0; i < pathJunctions.length; i++) { var junction = pathJunctions[i]; var junctionCell = self.getCell(junction.gridX, junction.gridY); if (junctionCell) { junctionCell.isJunction = true; junctionCell.junction = junction; } } 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 && !enemy.isDestroyed) { 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 && !enemy.isDestroyed) { 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) { // Check if enemy is at a junction if (cell && cell.isJunction && !enemy.chosenBranch) { var junction = cell.junction; var chosenBranch = junction.choosePath(enemy); if (chosenBranch) { enemy.chosenBranch = chosenBranch; enemy.currentTarget = grid.getCell(chosenBranch.targetX, chosenBranch.targetY); // Show visual path indicator if (routeVisualizer && Math.random() < 0.3) { // Show for 30% of enemies routeVisualizer.showPaths(junction, enemy); } // Add notification for significant route changes if (Math.random() < 0.1) { // 10% chance var routeType = chosenBranch.isShortPath ? "direct" : "alternate"; var notification = game.addChild(new Notification("Enemy taking " + routeType + " route!")); notification.x = 2048 / 2; notification.y = grid.height - 100; } } else { enemy.currentTarget = cell.targets[0]; } } else { 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 Hero = Container.expand(function (heroType) { var self = Container.call(this); self.heroType = heroType || 'tank'; self.level = storage['hero_' + heroType + '_level'] || 1; self.experience = storage['hero_' + heroType + '_xp'] || 0; self.maxHealth = 200; self.health = self.maxHealth; self.isDestroyed = false; self.movementSpeed = 1.5; self.abilities = []; self.currentTarget = null; self.lastAbilityUse = 0; self.abilityCooldown = 300; // 5 seconds at 60fps // Set hero-specific stats switch (self.heroType) { case 'tank': self.maxHealth = 300 + (self.level - 1) * 50; self.movementSpeed = 1.0; self.abilityCooldown = 600; // 10 seconds break; case 'support': self.maxHealth = 150 + (self.level - 1) * 30; self.movementSpeed = 2.0; self.abilityCooldown = 480; // 8 seconds break; case 'dps': self.maxHealth = 180 + (self.level - 1) * 35; self.movementSpeed = 1.8; self.abilityCooldown = 360; // 6 seconds break; } self.health = self.maxHealth; // Get appropriate asset var assetName = 'hero_' + self.heroType; var heroGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Create health bar var healthBarOutline = self.attachAsset('heroHealthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('heroHealthBar', { anchorX: 0, anchorY: 0.5 }); healthBarOutline.y = healthBar.y = -heroGraphics.height / 2 - 20; healthBarOutline.x = -healthBarOutline.width / 2; healthBar.x = -healthBar.width / 2; self.healthBar = healthBar; // Create XP bar var xpBarOutline = self.attachAsset('heroXpBarOutline', { anchorX: 0, anchorY: 0.5 }); var xpBar = self.attachAsset('heroXpBar', { anchorX: 0, anchorY: 0.5 }); xpBarOutline.y = xpBar.y = -heroGraphics.height / 2 - 35; xpBarOutline.x = -xpBarOutline.width / 2; xpBar.x = -xpBar.width / 2; self.xpBar = xpBar; // Create ability icon var abilityIcon = self.attachAsset('heroAbilityIcon', { anchorX: 0.5, anchorY: 0.5 }); abilityIcon.y = heroGraphics.height / 2 + 20; self.abilityIcon = abilityIcon; // Level indicator var levelText = new Text2(self.level.toString(), { size: 40, fill: 0xFFFFFF, weight: 800 }); levelText.anchor.set(0.5, 0.5); levelText.y = heroGraphics.height / 2 + 20; levelText.x = 40; self.addChild(levelText); self.levelText = levelText; self.getExperienceForNextLevel = function () { return self.level * 100; // 100 XP per level }; self.gainExperience = function (amount) { self.experience += amount; var xpNeeded = self.getExperienceForNextLevel(); if (self.experience >= xpNeeded) { self.experience -= xpNeeded; self.level++; self.levelText.setText(self.level.toString()); // Save progression storage['hero_' + self.heroType + '_level'] = self.level; // Level up effects self.onLevelUp(); // Visual level up effect tween(heroGraphics, { scaleX: 1.3, scaleY: 1.3, tint: 0xFFD700 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(heroGraphics, { scaleX: 1.0, scaleY: 1.0, tint: 0xFFFFFF }, { duration: 300, easing: tween.easeIn }); } }); } // Save XP storage['hero_' + self.heroType + '_xp'] = self.experience; self.updateXpBar(); }; self.onLevelUp = function () { // Increase health var healthIncrease = self.heroType === 'tank' ? 50 : self.heroType === 'support' ? 30 : 35; self.maxHealth += healthIncrease; self.health = self.maxHealth; // Full heal on level up self.updateHealthBar(); }; self.updateHealthBar = function () { self.healthBar.width = self.health / self.maxHealth * 80; }; self.updateXpBar = function () { var xpProgress = self.experience / self.getExperienceForNextLevel(); self.xpBar.width = xpProgress * 80; }; self.useAbility = function () { if (LK.ticks - self.lastAbilityUse < self.abilityCooldown) { return false; // Ability on cooldown } self.lastAbilityUse = LK.ticks; // Play hero ability sound LK.getSound('heroAbility').play(); // Ability cooldown visual effect tween(self.abilityIcon, { tint: 0x888888, scaleX: 0.8, scaleY: 0.8 }, { duration: self.abilityCooldown, easing: tween.linear, onFinish: function onFinish() { tween(self.abilityIcon, { tint: 0xFFD700, scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.easeOut }); } }); switch (self.heroType) { case 'tank': return self.tankAbility(); case 'support': return self.supportAbility(); case 'dps': return self.dpsAbility(); } return false; }; self.tankAbility = function () { // Taunt - Draw all enemies within range to attack hero var tauntRadius = CELL_SIZE * 4; var taunted = 0; 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); if (distance <= tauntRadius) { enemy.heroTarget = self; enemy.heroTargetDuration = 300; // 5 seconds taunted++; } } // Visual effect var effectRadius = tauntRadius; var tauntEffect = new Container(); tauntEffect.x = self.x; tauntEffect.y = self.y; var effectGraphics = tauntEffect.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.width = effectGraphics.height = effectRadius * 2; effectGraphics.tint = 0x4CAF50; effectGraphics.alpha = 0.6; game.addChild(tauntEffect); tween(tauntEffect, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { if (tauntEffect.parent) { tauntEffect.parent.removeChild(tauntEffect); } } }); return taunted > 0; }; self.supportAbility = function () { // Buff nearby towers var buffRadius = CELL_SIZE * 3; var buffed = 0; 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 <= buffRadius) { tower.heroBuff = { damage: 1.5, fireRate: 0.7, duration: 600 // 10 seconds }; buffed++; // Visual effect on tower tween(tower.children[0], { tint: 0x2196F3 }, { duration: 600, easing: tween.linear, onFinish: function onFinish() { tower.children[0].tint = 0x445566; } }); } } return buffed > 0; }; self.dpsAbility = function () { // Area damage attack var damageRadius = CELL_SIZE * 2.5; var damaged = 0; 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); if (distance <= damageRadius) { var damage = 100 + (self.level - 1) * 20; enemy.health -= damage; if (enemy.health <= 0) { enemy.health = 0; } else { enemy.healthBar.width = enemy.health / enemy.maxHealth * 70; } damaged++; // Visual damage effect tween(enemy, { tint: 0xFF5722 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(enemy, { tint: 0xFFFFFF }, { duration: 200, easing: tween.easeIn }); } }); } } // Visual explosion effect var explosionEffect = new Container(); explosionEffect.x = self.x; explosionEffect.y = self.y; var effectGraphics = explosionEffect.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.width = effectGraphics.height = damageRadius * 2; effectGraphics.tint = 0xFF5722; effectGraphics.alpha = 0.8; game.addChild(explosionEffect); tween(explosionEffect, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (explosionEffect.parent) { explosionEffect.parent.removeChild(explosionEffect); } } }); return damaged > 0; }; self.update = function () { if (self.health <= 0) { return; } // Update bars self.updateHealthBar(); self.updateXpBar(); // Move towards enemies or patrol if (!self.currentTarget) { self.findTarget(); } if (self.currentTarget && self.currentTarget.parent && self.currentTarget.health > 0) { var dx = self.currentTarget.x - self.x; var dy = self.currentTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > CELL_SIZE * 0.8) { // Move towards target var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.movementSpeed; self.y += Math.sin(angle) * self.movementSpeed; } else { // Attack target if (self.heroType === 'tank') { self.currentTarget.health -= 15; } else if (self.heroType === 'dps') { self.currentTarget.health -= 25; } else { self.currentTarget.health -= 10; } if (self.currentTarget.health <= 0) { self.currentTarget.health = 0; self.gainExperience(20); self.currentTarget = null; } else { self.currentTarget.healthBar.width = self.currentTarget.health / self.currentTarget.maxHealth * 70; } } } else { self.currentTarget = null; } }; self.findTarget = function () { var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.health <= 0) continue; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance && distance < CELL_SIZE * 5) { closestDistance = distance; closestEnemy = enemy; } } self.currentTarget = closestEnemy; }; self.down = function (x, y, obj) { // Use ability when tapped if (self.useAbility()) { var notification = game.addChild(new Notification("Hero ability activated!")); notification.x = 2048 / 2; notification.y = grid.height - 100; } }; self.destroy = function () { if (self.isDestroyed) { return; } self.isDestroyed = true; tween.stop(self); for (var i = 0; i < self.children.length; i++) { if (self.children[i]) { tween.stop(self.children[i]); } } if (self.parent) { self.parent.removeChild(self); } Container.prototype.destroy.call(self); }; return self; }); var NebulaCloud = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; self.radius = 100; self.isDestroyed = false; var nebulaGraphics = self.attachAsset('nebulaCloud', { anchorX: 0.5, anchorY: 0.5 }); nebulaGraphics.alpha = 0.6; // Pulsing animation tween(nebulaGraphics, { scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isDestroyed) { tween(nebulaGraphics, { scaleX: 1.0, scaleY: 1.0, alpha: 0.6 }, { duration: 2000, easing: tween.easeInOut }); } } }); self.update = function () { // Apply effects to units within the nebula 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); if (distance <= self.radius) { // Slow effect if (!enemy.nebulaSlowed) { enemy.originalSpeed = enemy.originalSpeed || enemy.speed; enemy.speed *= 0.7; // 30% speed reduction enemy.nebulaSlowed = true; } // Stealth effect - harder for towers to target enemy.nebulaStealthed = true; } else { // Remove effects when outside nebula if (enemy.nebulaSlowed) { enemy.speed = enemy.originalSpeed || enemy.speed; enemy.nebulaSlowed = false; } enemy.nebulaStealthed = false; } } // Apply slow effect to heroes for (var i = 0; i < heroes.length; i++) { var hero = heroes[i]; var dx = hero.x - self.x; var dy = hero.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.radius) { if (!hero.nebulaSlowed) { hero.originalMovementSpeed = hero.originalMovementSpeed || hero.movementSpeed; hero.movementSpeed *= 0.7; hero.nebulaSlowed = true; } } else { if (hero.nebulaSlowed) { hero.movementSpeed = hero.originalMovementSpeed || hero.movementSpeed; hero.nebulaSlowed = false; } } } }; self.destroy = function () { if (self.isDestroyed) return; self.isDestroyed = true; // Remove effects from all units for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.nebulaSlowed) { enemy.speed = enemy.originalSpeed || enemy.speed; enemy.nebulaSlowed = false; } enemy.nebulaStealthed = false; } for (var i = 0; i < heroes.length; i++) { var hero = heroes[i]; if (hero.nebulaSlowed) { hero.movementSpeed = hero.originalMovementSpeed || hero.movementSpeed; hero.nebulaSlowed = false; } } var nebulaIndex = nebulaClouds.indexOf(self); if (nebulaIndex !== -1) { nebulaClouds.splice(nebulaIndex, 1); } tween.stop(self); if (self.parent) { self.parent.removeChild(self); } Container.prototype.destroy.call(self); }; 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: 0xFFFFFF, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 60; notificationGraphics.height = 80; notificationGraphics.tint = 0x1A1A1A; notificationGraphics.alpha = 0.95; 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 PathJunction = Container.expand(function (x, y, branches) { var self = Container.call(this); self.x = x; self.y = y; self.branches = branches || []; // Array of possible path directions self.gridX = Math.floor((x - grid.x) / CELL_SIZE); self.gridY = Math.floor((y - grid.y) / CELL_SIZE); self.isDestroyed = false; // Visual junction indicator var junctionGraphics = self.attachAsset('teleportEffect', { anchorX: 0.5, anchorY: 0.5 }); junctionGraphics.width = CELL_SIZE * 0.8; junctionGraphics.height = CELL_SIZE * 0.8; junctionGraphics.tint = 0x9c27b0; junctionGraphics.alpha = 0.6; // Pulsing animation to indicate junction tween(junctionGraphics, { scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isDestroyed) { tween(junctionGraphics, { scaleX: 1.0, scaleY: 1.0, alpha: 0.6 }, { duration: 1500, easing: tween.easeInOut }); } } }); // Choose path for enemy based on various factors self.choosePath = function (enemy) { if (self.branches.length === 0) return null; var pathScores = []; // Evaluate each branch for (var i = 0; i < self.branches.length; i++) { var branch = self.branches[i]; var score = 0; // Base score - some randomness for variety score += Math.random() * 100; // Factor in enemy type preferences switch (enemy.type) { case 'fast': // Fast enemies prefer shorter paths score += branch.isShortPath ? 150 : 50; break; case 'immune': // Immune enemies prefer heavily defended paths (they can tank it) score += branch.towerDensity * 80; break; case 'flying': // Flying enemies prefer paths with fewer anti-air towers score += branch.hasAntiAir ? 20 : 120; break; case 'swarm': // Swarm enemies prefer paths other swarm enemies haven't taken recently score += branch.recentSwarmTraffic > 3 ? 30 : 100; break; default: // Normal enemies prefer balanced paths score += branch.difficulty < 50 ? 80 : 60; break; } // Factor in current congestion (avoid clustering) var congestionPenalty = branch.currentEnemyCount * 25; score -= congestionPenalty; // Factor in tower coverage (enemies avoid heavy defenses unless immune) if (!enemy.isImmune) { score -= branch.towerDensity * 40; } // Factor in path length (generally prefer shorter paths) score -= branch.pathLength * 2; pathScores.push({ branch: branch, score: score }); } // Sort by score and add some weighted randomness pathScores.sort(function (a, b) { return b.score - a.score; }); // Weighted selection - higher scores more likely but not guaranteed var totalWeight = 0; for (var i = 0; i < pathScores.length; i++) { var weight = Math.max(1, pathScores[i].score); pathScores[i].weight = weight; totalWeight += weight; } var randomValue = Math.random() * totalWeight; var currentWeight = 0; for (var i = 0; i < pathScores.length; i++) { currentWeight += pathScores[i].weight; if (randomValue <= currentWeight) { return pathScores[i].branch; } } // Fallback to first option return pathScores[0].branch; }; // Update junction statistics self.update = function () { // Update branch statistics for (var i = 0; i < self.branches.length; i++) { var branch = self.branches[i]; // Count enemies currently on this branch branch.currentEnemyCount = 0; branch.recentSwarmTraffic = Math.max(0, branch.recentSwarmTraffic - 0.1); for (var j = 0; j < enemies.length; j++) { var enemy = enemies[j]; if (enemy.chosenBranch === branch) { branch.currentEnemyCount++; if (enemy.type === 'swarm') { branch.recentSwarmTraffic += 0.1; } } } // Calculate tower density in branch area branch.towerDensity = 0; branch.hasAntiAir = false; for (var t = 0; t < towers.length; t++) { var tower = towers[t]; var dx = tower.x - (grid.x + branch.targetX * CELL_SIZE); var dy = tower.y - (grid.y + branch.targetY * CELL_SIZE); var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= CELL_SIZE * 4) { branch.towerDensity++; if (tower.id === 'sniper' || tower.id === 'rapid') { branch.hasAntiAir = true; } } } // Update path difficulty branch.difficulty = branch.towerDensity * 10 + branch.pathLength; } }; self.destroy = function () { if (self.isDestroyed) return; self.isDestroyed = true; var junctionIndex = pathJunctions.indexOf(self); if (junctionIndex !== -1) { pathJunctions.splice(junctionIndex, 1); } tween.stop(self); if (self.parent) { self.parent.removeChild(self); } Container.prototype.destroy.call(self); }; return self; }); var RouteVisualizer = Container.expand(function () { var self = Container.call(this); self.pathLines = []; self.isDestroyed = false; self.showPaths = function (junction, enemy) { // Clear existing path lines self.clearPaths(); // Create visual lines for each possible branch for (var i = 0; i < junction.branches.length; i++) { var branch = junction.branches[i]; var pathLine = new Container(); // Create line segments to show the path var segments = []; var currentX = junction.gridX; var currentY = junction.gridY; // Draw path to target var targetX = branch.targetX; var targetY = branch.targetY; var steps = Math.max(Math.abs(targetX - currentX), Math.abs(targetY - currentY)); for (var step = 0; step <= steps; step++) { var progress = step / steps; var segmentX = Math.round(currentX + (targetX - currentX) * progress); var segmentY = Math.round(currentY + (targetY - currentY) * progress); var segment = pathLine.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); segment.width = 12; segment.height = 12; segment.x = grid.x + segmentX * CELL_SIZE; segment.y = grid.y + segmentY * CELL_SIZE; // Color code based on path difficulty var difficulty = branch.difficulty || 0; if (difficulty < 30) { segment.tint = 0x00FF00; // Green for easy } else if (difficulty < 60) { segment.tint = 0xFFFF00; // Yellow for medium } else { segment.tint = 0xFF0000; // Red for hard } segment.alpha = 0.7; } game.addChild(pathLine); self.pathLines.push(pathLine); // Animate the path appearance tween(pathLine, { alpha: 1.0 }, { duration: 500, easing: tween.easeOut }); } // Auto-hide after a few seconds LK.setTimeout(function () { self.clearPaths(); }, 3000); }; self.clearPaths = function () { for (var i = 0; i < self.pathLines.length; i++) { if (self.pathLines[i].parent) { self.pathLines[i].parent.removeChild(self.pathLines[i]); } } self.pathLines = []; }; self.destroy = function () { if (self.isDestroyed) return; self.isDestroyed = true; self.clearPaths(); tween.stop(self); if (self.parent) { self.parent.removeChild(self); } Container.prototype.destroy.call(self); }; return self; }); var SolarFlare = Container.expand(function () { var self = Container.call(this); self.duration = 600; // 10 seconds at 60fps self.remainingTime = self.duration; self.isActive = true; self.isDestroyed = false; // Position in center of battlefield self.x = grid.x + 12 * CELL_SIZE; self.y = grid.y + 15 * CELL_SIZE; var flareGraphics = self.attachAsset('solarFlare', { anchorX: 0.5, anchorY: 0.5 }); flareGraphics.alpha = 0.8; flareGraphics.blendMode = 1; // Additive blending // Intense pulsing animation tween(flareGraphics, { scaleX: 1.5, scaleY: 1.5, alpha: 1.0 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isDestroyed) { tween(flareGraphics, { scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }, { duration: 300, easing: tween.easeInOut }); } } }); // Show notification var notification = game.addChild(new Notification("⚡ Solar Flare! Energy towers boosted! ⚡")); notification.x = 2048 / 2; notification.y = grid.height - 100; self.update = function () { self.remainingTime--; if (self.remainingTime <= 0) { self.destroy(); return; } // Boost energy-based towers (rapid, sniper, default) for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.id === 'rapid' || tower.id === 'sniper' || tower.id === 'default') { if (!tower.solarFlareBoost) { tower.originalFireRate = tower.fireRate; tower.originalDamage = tower.damage; tower.fireRate = Math.floor(tower.fireRate * 0.6); // 40% faster tower.damage = Math.floor(tower.damage * 1.3); // 30% more damage tower.solarFlareBoost = true; // Visual effect on boosted towers tween(tower.energyCore, { tint: 0xFFD700, scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.easeOut }); } } } }; self.destroy = function () { if (self.isDestroyed) return; self.isDestroyed = true; // Remove boosts from all towers for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.solarFlareBoost) { tower.fireRate = tower.originalFireRate; tower.damage = tower.originalDamage; tower.solarFlareBoost = false; // Reset visual effects var originalTint = 0x88AACC; switch (tower.id) { case 'rapid': originalTint = 0x00AAFF; break; case 'sniper': originalTint = 0xFF5500; break; case 'default': originalTint = 0x88AACC; break; } tween(tower.energyCore, { tint: originalTint, scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.easeIn }); } } var flareIndex = solarFlares.indexOf(self); if (flareIndex !== -1) { solarFlares.splice(flareIndex, 1); } tween.stop(self); if (self.parent) { self.parent.removeChild(self); } Container.prototype.destroy.call(self); }; 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 = 0xFF5500; break; case 'splash': baseGraphics.tint = 0x33CC00; break; case 'slow': baseGraphics.tint = 0x9900FF; break; case 'poison': baseGraphics.tint = 0x00FFAA; break; default: baseGraphics.tint = 0xAAAAAA; } 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 SpecializationMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 300; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 1800; menuBackground.height = 600; menuBackground.tint = 0x1A1A1A; menuBackground.alpha = 0.98; var titleText = new Text2('Choose Specialization Path', { size: 80, fill: 0xFFFFFF, weight: 800 }); titleText.anchor.set(0.5, 0.5); titleText.y = -200; self.addChild(titleText); var subtitleText = new Text2('Your tower has reached level 3 and can specialize!', { size: 50, fill: 0xCCCCCC, weight: 400 }); subtitleText.anchor.set(0.5, 0.5); subtitleText.y = -140; self.addChild(subtitleText); // Create specialization options based on tower type var options = []; if (self.tower.id === 'sniper') { options = [{ type: 'antiarmor', name: 'Anti-Armor', desc: 'Pierce shields\n+50% damage vs immune', color: 0xFF6600 }, { type: 'longrange', name: 'Long-Range', desc: 'Double firing range\nBetter enemy targeting', color: 0x00AAFF }]; } else if (self.tower.id === 'splash') { options = [{ type: 'nuclear', name: 'Nuclear', desc: 'Huge damage\nLarger splash radius', color: 0xFF0000 }, { type: 'chainreaction', name: 'Chain Reaction', desc: 'Damage spreads\nbetween enemies', color: 0xFFFF00 }]; } // Create option buttons for (var i = 0; i < options.length; i++) { var option = options[i]; var button = new Container(); var buttonBg = button.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 700; buttonBg.height = 200; buttonBg.tint = option.color; buttonBg.alpha = 0.8; var nameText = new Text2(option.name, { size: 70, fill: 0xFFFFFF, weight: 800 }); nameText.anchor.set(0.5, 0.5); nameText.y = -40; button.addChild(nameText); var descText = new Text2(option.desc, { size: 45, fill: 0xFFFFFF, weight: 400 }); descText.anchor.set(0.5, 0.5); descText.y = 40; button.addChild(descText); button.x = (i - 0.5) * 800; button.y = 50; button.optionType = option.type; button.down = function () { self.selectSpecialization(this.optionType); }; self.addChild(button); } // Close button var closeButton = new Container(); var closeBg = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBg.width = 100; closeBg.height = 100; closeBg.tint = 0xAA0000; var closeText = new Text2('X', { size: 80, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = 850; closeButton.y = -250; closeButton.down = function () { self.destroy(); }; self.addChild(closeButton); self.selectSpecialization = function (branchType) { // Apply specialization to tower var specialization = new TowerSpecialization(self.tower, branchType); self.tower.specialization = specialization; self.tower.addChild(specialization); specialization.applySpecialization(); var notification = game.addChild(new Notification("Tower specialized: " + branchType)); notification.x = 2048 / 2; notification.y = grid.height - 50; self.destroy(); }; return self; }); var StationCustomizer = Container.expand(function () { var self = Container.call(this); self.modules = []; self.moduleSlots = []; self.selectedSlot = null; self.customizationMode = false; // Create module slots around the station var slotPositions = [{ x: -2, y: -2 }, { x: 0, y: -2 }, { x: 2, y: -2 }, { x: -2, y: 0 }, { x: 2, y: 0 }, { x: -2, y: 2 }, { x: 0, y: 2 }, { x: 2, y: 2 }]; for (var i = 0; i < slotPositions.length; i++) { var slot = new Container(); var slotGraphics = slot.attachAsset('moduleSocket', { anchorX: 0.5, anchorY: 0.5 }); slotGraphics.alpha = 0.5; slotGraphics.tint = 0x556677; slot.gridX = slotPositions[i].x; slot.gridY = slotPositions[i].y; slot.x = grid.x + (12 + slot.gridX) * CELL_SIZE; slot.y = grid.y + (15 + slot.gridY) * CELL_SIZE; slot.module = null; slot.slotIndex = i; slot.down = function () { if (self.customizationMode) { self.selectedSlot = this; self.showModuleMenu(); } }; self.addChild(slot); self.moduleSlots.push(slot); } self.showModuleMenu = function () { if (!self.selectedSlot) return; var menu = new Container(); var menuBg = menu.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBg.width = 600; menuBg.height = 300; menuBg.tint = 0x333333; var moduleTypes = ['research', 'shield', 'teleporter']; for (var i = 0; i < moduleTypes.length; i++) { var button = new Container(); var buttonBg = button.attachAsset('techTreeNode', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 120; buttonBg.height = 80; var cost = getModuleCost(moduleTypes[i], 1); var buttonText = new Text2(moduleTypes[i] + '\n' + cost + 'g', { size: 30, fill: 0xFFFFFF, weight: 600 }); buttonText.anchor.set(0.5, 0.5); button.addChild(buttonText); button.moduleType = moduleTypes[i]; button.x = -180 + i * 180; button.y = 50; button.down = function () { self.buildModule(this.moduleType); menu.destroy(); }; menu.addChild(button); } menu.x = 2048 / 2; menu.y = 1400; game.addChild(menu); LK.setTimeout(function () { if (menu.parent) { menu.parent.removeChild(menu); } }, 5000); }; self.buildModule = function (moduleType) { if (!self.selectedSlot || self.selectedSlot.module) return; var cost = getModuleCost(moduleType, 1); if (gold < cost) { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } var module; switch (moduleType) { case 'research': module = new ResearchLab(); break; case 'shield': module = new ShieldGenerator(); break; case 'teleporter': module = new TeleporterNode(); break; default: module = new StationModule(moduleType); } module.gridX = self.selectedSlot.gridX; module.gridY = self.selectedSlot.gridY; module.x = self.selectedSlot.x; module.y = self.selectedSlot.y; self.selectedSlot.module = module; self.selectedSlot.children[0].alpha = 0; // Hide slot graphic self.addChild(module); self.modules.push(module); setGold(gold - cost); var notification = game.addChild(new Notification(moduleType + " module built!")); notification.x = 2048 / 2; notification.y = grid.height - 50; self.selectedSlot = null; }; self.toggleCustomizationMode = function () { self.customizationMode = !self.customizationMode; for (var i = 0; i < self.moduleSlots.length; i++) { var slot = self.moduleSlots[i]; if (!slot.module) { slot.children[0].alpha = self.customizationMode ? 0.8 : 0.3; } } var modeText = self.customizationMode ? "ON" : "OFF"; var notification = game.addChild(new Notification("Customization mode: " + modeText)); notification.x = 2048 / 2; notification.y = grid.height - 50; }; self.update = function () { for (var i = 0; i < self.modules.length; i++) { if (self.modules[i].update) { self.modules[i].update(); } } }; return self; }); var StationModule = Container.expand(function (moduleType) { var self = Container.call(this); self.moduleType = moduleType || 'basic'; self.level = 1; self.maxLevel = 3; self.isActive = true; self.gridX = 0; self.gridY = 0; self.isDestroyed = false; // Get appropriate asset var assetName = 'stationModule'; switch (moduleType) { case 'research': assetName = 'researchLab'; break; case 'shield': assetName = 'shieldGenerator'; break; case 'teleporter': assetName = 'teleporter'; break; } var moduleGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Add energy core var energyCore = self.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); energyCore.width = 30; energyCore.height = 30; energyCore.tint = 0x00aaff; // Level indicators var levelIndicators = []; for (var i = 0; i < self.maxLevel; i++) { var indicator = self.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = 12; indicator.height = 12; indicator.x = -30 + i * 30; indicator.y = 60; indicator.tint = i < self.level ? 0xffffff : 0x444444; levelIndicators.push(indicator); } self.updateLevelIndicators = function () { for (var i = 0; i < levelIndicators.length; i++) { levelIndicators[i].tint = i < self.level ? 0xffffff : 0x444444; } }; self.upgrade = function () { if (self.level < self.maxLevel) { var upgradeCost = getModuleCost(self.moduleType, self.level + 1); if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; self.updateLevelIndicators(); self.onUpgrade(); return true; } } return false; }; self.onUpgrade = function () { // Override in specific module types }; self.update = function () { if (!self.isActive) return; // Pulsing energy core if (LK.ticks % 120 === 0) { tween(energyCore, { scaleX: 1.3, scaleY: 1.3, alpha: 0.7 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tween(energyCore, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 300, easing: tween.easeInOut }); } }); } }; self.destroy = function () { if (self.isDestroyed) return; self.isDestroyed = true; tween.stop(self); if (self.parent) { self.parent.removeChild(self); } Container.prototype.destroy.call(self); }; return self; }); var TeleporterNode = StationModule.expand(function () { var self = StationModule.call(this, 'teleporter'); self.teleportEnergy = 100; self.maxTeleportEnergy = 100; self.linkedNodes = []; self.onUpgrade = function () { self.maxTeleportEnergy = 100 + (self.level - 1) * 25; self.teleportEnergy = self.maxTeleportEnergy; }; self.teleportHero = function (hero, targetNode) { if (self.teleportEnergy >= 30 && targetNode && targetNode.teleportEnergy >= 30) { // Create teleport effect at source var sourceEffect = new Container(); sourceEffect.x = self.x; sourceEffect.y = self.y; var sourceGraphics = sourceEffect.attachAsset('teleportEffect', { anchorX: 0.5, anchorY: 0.5 }); sourceGraphics.tint = 0x9c27b0; sourceGraphics.alpha = 0.8; game.addChild(sourceEffect); // Create teleport effect at destination var destEffect = new Container(); destEffect.x = targetNode.x; destEffect.y = targetNode.y; var destGraphics = destEffect.attachAsset('teleportEffect', { anchorX: 0.5, anchorY: 0.5 }); destGraphics.tint = 0x9c27b0; destGraphics.alpha = 0.8; game.addChild(destEffect); // Animate effects tween(sourceEffect, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (sourceEffect.parent) { sourceEffect.parent.removeChild(sourceEffect); } } }); tween(destEffect, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (destEffect.parent) { destEffect.parent.removeChild(destEffect); } } }); // Teleport hero hero.x = targetNode.x; hero.y = targetNode.y; // Consume energy self.teleportEnergy = Math.max(0, self.teleportEnergy - 30); targetNode.teleportEnergy = Math.max(0, targetNode.teleportEnergy - 30); return true; } return false; }; self.redirectEnemyPath = function () { if (self.teleportEnergy >= 50 && self.linkedNodes.length > 0) { var targetNode = self.linkedNodes[Math.floor(Math.random() * self.linkedNodes.length)]; // Find nearby enemies and redirect one for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (!enemy.isFlying) { var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= CELL_SIZE * 2) { // Teleport enemy enemy.x = targetNode.x; enemy.y = targetNode.y; enemy.currentCellX = targetNode.gridX; enemy.currentCellY = targetNode.gridY; enemy.currentTarget = null; self.teleportEnergy = Math.max(0, self.teleportEnergy - 50); var notification = game.addChild(new Notification("Enemy redirected through teleporter!")); notification.x = 2048 / 2; notification.y = grid.height - 50; break; } } } } }; self.update = function () { StationModule.prototype.update.call(self); // Regenerate teleport energy if (LK.ticks % 120 === 0 && self.teleportEnergy < self.maxTeleportEnergy) { // Every 2 seconds self.teleportEnergy = Math.min(self.maxTeleportEnergy, self.teleportEnergy + self.level * 3); } }; self.down = function () { self.redirectEnemyPath(); }; return self; }); var ShieldGenerator = StationModule.expand(function () { var self = StationModule.call(this, 'shield'); self.shieldEnergy = 100; self.maxShieldEnergy = 100; self.shieldActive = false; self.shieldEffect = null; self.onUpgrade = function () { self.maxShieldEnergy = 100 + (self.level - 1) * 50; self.shieldEnergy = self.maxShieldEnergy; }; self.activateShield = function () { if (self.shieldEnergy >= 50 && !self.shieldActive) { self.shieldActive = true; self.shieldEnergy = Math.max(0, self.shieldEnergy - 50); // Create visual shield effect self.shieldEffect = new Container(); self.shieldEffect.x = self.x; self.shieldEffect.y = self.y; var shieldGraphics = self.shieldEffect.attachAsset('shieldEffect', { anchorX: 0.5, anchorY: 0.5 }); shieldGraphics.alpha = 0.3; shieldGraphics.tint = 0x00ffff; game.addChild(self.shieldEffect); // Shield lasts for 10 seconds LK.setTimeout(function () { self.deactivateShield(); }, 10000); var notification = game.addChild(new Notification("Station shields activated!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } }; self.deactivateShield = function () { self.shieldActive = false; if (self.shieldEffect && self.shieldEffect.parent) { self.shieldEffect.parent.removeChild(self.shieldEffect); self.shieldEffect = null; } }; self.update = function () { StationModule.prototype.update.call(self); // Regenerate shield energy if (LK.ticks % 180 === 0 && self.shieldEnergy < self.maxShieldEnergy) { // Every 3 seconds self.shieldEnergy = Math.min(self.maxShieldEnergy, self.shieldEnergy + self.level * 5); } // Update shield effect position if (self.shieldEffect) { self.shieldEffect.x = self.x; self.shieldEffect.y = self.y; } }; self.down = function () { self.activateShield(); }; return self; }); var ResearchLab = StationModule.expand(function () { var self = StationModule.call(this, 'research'); self.researchPoints = 0; self.activeResearch = null; self.completedTechs = []; self.onUpgrade = function () { // Higher level labs generate research points faster self.researchPointsPerTick = self.level; }; self.update = function () { StationModule.prototype.update.call(self); // Generate research points if (LK.ticks % 60 === 0) { // Every second self.researchPoints += self.level; storage.researchPoints = self.researchPoints; } // Complete active research if (self.activeResearch && self.researchPoints >= self.activeResearch.cost) { self.completeResearch(); } }; self.startResearch = function (techId) { var tech = getTechnology(techId); if (tech && !self.activeResearch) { self.activeResearch = tech; var notification = game.addChild(new Notification("Research started: " + tech.name)); notification.x = 2048 / 2; notification.y = grid.height - 50; } }; self.completeResearch = function () { if (self.activeResearch) { self.researchPoints -= self.activeResearch.cost; self.completedTechs.push(self.activeResearch.id); storage.completedTechs = self.completedTechs; // Apply research benefits self.activeResearch.onComplete(); var notification = game.addChild(new Notification("Research complete: " + self.activeResearch.name + "!")); notification.x = 2048 / 2; notification.y = grid.height - 50; self.activeResearch = null; } }; 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 var baseRange; switch (self.id) { case 'sniper': // Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost if (self.level === self.maxLevel) { baseRange = 12 * CELL_SIZE; // Significantly increased range for max level } else { baseRange = (5 + (self.level - 1) * 0.8) * CELL_SIZE; } break; 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; default: // Default: base 3, +0.5 per level baseRange = (3 + (self.level - 1) * 0.5) * CELL_SIZE; } // Apply specialization range multiplier if (self.specialization && self.specialization.branchType === 'longrange') { baseRange *= 2.0; } return baseRange; }; self.cellsInRange = []; self.fireRate = 60; self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; switch (self.id) { case 'rapid': self.fireRate = 35; // Slightly slower to reduce overwhelming power self.damage = 8; // Increased from 5 for better effectiveness self.range = 2.8 * CELL_SIZE; // Slightly better range self.bulletSpeed = 7; break; case 'sniper': self.fireRate = 100; // Slower but more impactful self.damage = 40; // Increased significantly for true sniper feel self.range = 5 * CELL_SIZE; self.bulletSpeed = 25; break; case 'splash': self.fireRate = 80; // Slightly slower self.damage = 18; // Increased base damage self.range = 2.2 * CELL_SIZE; // Slightly better range self.bulletSpeed = 4; break; case 'slow': self.fireRate = 45; // Faster to apply slow effects more consistently self.damage = 12; // Increased from 8 for better utility self.range = 3.8 * CELL_SIZE; // Better range for support role self.bulletSpeed = 5; break; case 'poison': self.fireRate = 60; // Faster to apply poison more effectively self.damage = 15; // Increased from 12 self.range = 3.5 * CELL_SIZE; // Better range self.bulletSpeed = 5; break; } // Create tower base structure var baseAssetName = 'tower_base_' + (self.id === 'default' ? 'default' : self.id); var baseGraphics = self.attachAsset(baseAssetName, { anchorX: 0.5, anchorY: 0.5 }); // Create support structures for higher levels self.supportStructures = []; // Create energy core based on tower type var energyCore = self.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); energyCore.width = 40; energyCore.height = 40; switch (self.id) { case 'rapid': energyCore.tint = 0x00AAFF; break; case 'sniper': energyCore.tint = 0xFF5500; break; case 'splash': energyCore.tint = 0x33CC00; break; case 'slow': energyCore.tint = 0x9900FF; break; case 'poison': energyCore.tint = 0x00FFAA; break; default: energyCore.tint = 0x88AACC; } // Add pulsing energy core animation self.energyCore = energyCore; tween(energyCore, { scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(energyCore, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 1000, easing: tween.easeInOut }); } }); // Method to update tower visual appearance based on level self.updateTowerVisuals = function () { // Ensure gunContainer exists before trying to use it if (!gunContainer) { console.warn("gunContainer not found, skipping visual update"); return; } // Clear existing gun graphics if (self.gunGraphics && self.gunGraphics.parent) { gunContainer.removeChild(self.gunGraphics); } // Clear existing support structures for (var i = 0; i < self.supportStructures.length; i++) { if (self.supportStructures[i].parent) { self.removeChild(self.supportStructures[i]); } } self.supportStructures = []; // Create new gun graphics based on current level var gunAssetName = 'tower_gun_' + (self.id === 'default' ? 'default' : self.id) + '_' + self.level; self.gunGraphics = gunContainer.attachAsset(gunAssetName, { anchorX: 0.5, anchorY: 0.5 }); // Add support structures for higher levels if (self.level >= 2) { // Add support beams for (var i = 0; i < 2; i++) { var beam = self.attachAsset('tower_support_beam', { anchorX: 0.5, anchorY: 1.0 }); beam.x = i === 0 ? -30 : 30; beam.y = baseGraphics.height / 2 - 10; beam.rotation = i === 0 ? -0.3 : 0.3; self.supportStructures.push(beam); } } if (self.level >= 3) { // Add radar dish for better targeting var radar = self.attachAsset('tower_radar_dish', { anchorX: 0.5, anchorY: 0.5 }); radar.x = 0; radar.y = -baseGraphics.height / 2 - 20; radar.alpha = 0.8; self.supportStructures.push(radar); // Rotate radar dish slowly tween(radar, { rotation: Math.PI * 2 }, { duration: 4000, easing: tween.linear, onFinish: function onFinish() { if (!self.isDestroyed && radar.parent) { radar.rotation = 0; } } }); } if (self.level >= 4) { // Add antenna for enhanced range var antenna = self.attachAsset('tower_antenna', { anchorX: 0.5, anchorY: 1.0 }); antenna.x = 0; antenna.y = -baseGraphics.height / 2 - 60; self.supportStructures.push(antenna); // Add armor plates for (var i = 0; i < 4; i++) { var armor = self.attachAsset('tower_armor_plate', { anchorX: 0.5, anchorY: 0.5 }); var angle = i / 4 * Math.PI * 2; armor.x = Math.cos(angle) * 40; armor.y = Math.sin(angle) * 40; armor.rotation = angle; armor.alpha = 0.7; self.supportStructures.push(armor); } } if (self.level >= 5) { // Add energy conduits for (var i = 0; i < 3; i++) { var conduit = self.attachAsset('tower_energy_conduit', { anchorX: 0.5, anchorY: 0.5 }); var angle = i / 3 * Math.PI * 2; conduit.x = Math.cos(angle) * 50; conduit.y = Math.sin(angle) * 50; conduit.rotation = angle + Math.PI / 2; // Pulsing energy effect tween(conduit, { alpha: 0.4 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isDestroyed && conduit.parent) { tween(conduit, { alpha: 1.0 }, { duration: 800, easing: tween.easeInOut }); } } }); self.supportStructures.push(conduit); } } if (self.level >= 6) { // Max level - add ultimate enhancement visual var enhancement = self.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); enhancement.width = 80; enhancement.height = 80; enhancement.tint = 0xFFD700; enhancement.alpha = 0.3; enhancement.y = 0; // Rotating golden aura for max level tween(enhancement, { rotation: Math.PI * 2, scaleX: 1.2, scaleY: 1.2 }, { duration: 3000, easing: tween.linear, onFinish: function onFinish() { if (!self.isDestroyed && enhancement.parent) { enhancement.rotation = 0; enhancement.scaleX = 1.0; enhancement.scaleY = 1.0; } } }); self.supportStructures.push(enhancement); } // Update energy core size based on level var coreSize = 40 + (self.level - 1) * 8; self.energyCore.width = coreSize; self.energyCore.height = coreSize; // Add specialization visuals if present if (self.specialization) { self.updateSpecializationVisuals(); } }; // Method to add specialization visual indicators self.updateSpecializationVisuals = function () { if (!self.specialization) return; var specAssetName; switch (self.specialization.branchType) { case 'antiarmor': specAssetName = 'tower_spec_armor_pierce'; break; case 'longrange': specAssetName = 'tower_spec_long_range'; break; case 'nuclear': specAssetName = 'tower_spec_nuclear'; break; case 'chainreaction': specAssetName = 'tower_spec_chain_reaction'; break; default: return; } var specVisual = self.attachAsset(specAssetName, { anchorX: 0.5, anchorY: 0.5 }); specVisual.y = -baseGraphics.height / 2 - 40; specVisual.alpha = 0.9; // Pulsing effect for specialization indicator tween(specVisual, { scaleX: 1.3, scaleY: 1.3, alpha: 0.6 }, { duration: 1200, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isDestroyed && specVisual.parent) { tween(specVisual, { scaleX: 1.0, scaleY: 1.0, alpha: 0.9 }, { duration: 1200, easing: tween.easeInOut }); } } }); self.supportStructures.push(specVisual); }; // Initialize tower visuals self.updateTowerVisuals(); var gunContainer = new Container(); self.addChild(gunContainer); var gunGraphics = gunContainer.attachAsset('defense', { anchorX: 0.5, anchorY: 0.5 }); 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); } 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; // More balanced upgrade cost scaling if (self.level === self.maxLevel - 1) { // Final upgrade is expensive but not prohibitive upgradeCost = Math.floor(baseUpgradeCost * Math.pow(1.8, self.level - 1) * 2.5); } else { // Use 1.8x multiplier instead of 2x for more manageable costs upgradeCost = Math.floor(baseUpgradeCost * Math.pow(1.8, self.level - 1)); } if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; // Play upgrade sound LK.getSound('towerUpgrade').play(); // Update tower visuals for new level self.updateTowerVisuals(); // Check for specialization unlock at level 3 if (self.level === 3 && !self.specialization && (self.id === 'sniper' || self.id === 'splash')) { var specMenu = new SpecializationMenu(self); game.addChild(specMenu); specMenu.x = 2048 / 2; tween(specMenu, { y: 2732 - 300 }, { duration: 300, easing: tween.backOut }); specializationMenus.push(specMenu); return true; } // No need to update self.range here; getRange() is now the source of truth // Apply tower-specific upgrades based on type with balanced scaling switch (self.id) { case 'rapid': if (self.level === self.maxLevel) { // Max level gets significant boost self.fireRate = Math.max(8, 35 - self.level * 4.2); self.damage = 8 + self.level * 6; // More modest scaling self.bulletSpeed = 7 + self.level * 1.5; } else { self.fireRate = Math.max(18, 35 - self.level * 2.8); self.damage = 8 + self.level * 3.5; self.bulletSpeed = 7 + self.level * 0.8; } break; case 'sniper': if (self.level === self.maxLevel) { self.fireRate = Math.max(15, 100 - self.level * 12); self.damage = 40 + self.level * 18; // High damage scaling self.bulletSpeed = 25 + self.level * 2; } else { self.fireRate = Math.max(30, 100 - self.level * 8); self.damage = 40 + self.level * 12; self.bulletSpeed = 25 + self.level * 1; } break; case 'splash': if (self.level === self.maxLevel) { self.fireRate = Math.max(12, 80 - self.level * 10); self.damage = 18 + self.level * 12; // Strong splash scaling self.bulletSpeed = 4 + self.level * 1.5; } else { self.fireRate = Math.max(25, 80 - self.level * 6); self.damage = 18 + self.level * 8; self.bulletSpeed = 4 + self.level * 0.8; } break; case 'slow': if (self.level === self.maxLevel) { self.fireRate = Math.max(8, 45 - self.level * 5.5); self.damage = 12 + self.level * 8; // Better damage for utility tower self.bulletSpeed = 5 + self.level * 1.2; } else { self.fireRate = Math.max(20, 45 - self.level * 3.5); self.damage = 12 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.7; } break; case 'poison': if (self.level === self.maxLevel) { self.fireRate = Math.max(10, 60 - self.level * 7.5); self.damage = 15 + self.level * 10; // Strong poison scaling self.bulletSpeed = 5 + self.level * 1.3; } else { self.fireRate = Math.max(22, 60 - self.level * 5); self.damage = 15 + self.level * 6; self.bulletSpeed = 5 + self.level * 0.8; } break; default: if (self.level === self.maxLevel) { self.fireRate = Math.max(10, 60 - self.level * 8); self.damage = 10 + self.level * 12; self.bulletSpeed = 5 + self.level * 1.5; } else { self.fireRate = Math.max(25, 60 - self.level * 5); self.damage = 10 + self.level * 7; self.bulletSpeed = 5 + self.level * 0.8; } } if (self.level > 1) { var levelDot = levelIndicators[self.level - 1].children[1]; tween(levelDot, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut, onFinish: function onFinish() { tween(levelDot, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); } return true; } else { var notification = game.addChild(new Notification("Not enough gold to upgrade!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } return false; }; self.findTarget = function () { var closestEnemy = null; var closestScore = Infinity; // Use spatial partitioning for better performance var nearbyEnemies = spatialGrid.getNearbyEnemies(self.x, self.y, self.getRange()); for (var i = 0; i < nearbyEnemies.length; i++) { var enemy = nearbyEnemies[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()) { // Apply nebula stealth effect - reduce targeting chance var targetingChance = enemy.nebulaStealthed ? 0.3 : 1.0; if (Math.random() > targetingChance) { continue; // Skip this enemy due to stealth } // 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 () { // Update ultimate ability if (self.ultimate) { self.ultimate.update(); } 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; var currentFireRate = self.fireRate; if (self.heroBuff && self.heroBuff.duration > 0) { currentFireRate = Math.floor(self.fireRate * self.heroBuff.fireRate); } if (LK.ticks - self.lastFired >= currentFireRate) { self.fire(); self.lastFired = LK.ticks; } } }; self.down = function (x, y, obj) { // Tutorial action tracking if (tutorialSystem && tutorialSystem.isActive) { tutorialSystem.checkActionCompleted('selectTower'); } // Check for ultimate ability activation (double tap or long press simulation) if (self.ultimate && self.ultimate.canActivate()) { // Simple activation - tap when ultimate is ready if (self.ultimate.activate()) { return; // Ultimate was activated, don't show menu } } 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.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; // Apply hero buff if present var finalDamage = self.damage; var finalFireRate = self.fireRate; if (self.heroBuff && self.heroBuff.duration > 0) { finalDamage = Math.floor(self.damage * self.heroBuff.damage); finalFireRate = Math.floor(self.fireRate * self.heroBuff.fireRate); self.heroBuff.duration--; if (self.heroBuff.duration <= 0) { self.heroBuff = null; } } // Apply support bonus from linked towers if (self.supportBonus) { finalDamage = Math.floor(finalDamage * self.supportBonus); finalFireRate = Math.floor(finalFireRate / self.supportBonus); } // Apply specialization effects if (self.specialization) { if (self.specialization.branchType === 'antiarmor' && self.targetEnemy.isImmune) { finalDamage = Math.floor(finalDamage * 1.5); } } var bullet = getBulletFromPool(bulletX, bulletY, self.targetEnemy, finalDamage, 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); // Play shooting sound LK.getSound('towerShoot').play(); // --- Sci-fi charge and firing effect --- // Stop any ongoing recoil tweens before starting a new one tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); // Add energy charge effect to core if (self.energyCore) { tween.stop(self.energyCore, { scaleX: true, scaleY: true, alpha: true }); tween(self.energyCore, { scaleX: 1.5, scaleY: 1.5, alpha: 1.5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self.energyCore, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 300, easing: tween.easeIn }); } }); } // 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(); }; self.destroy = function () { if (self.isDestroyed) { return; } self.isDestroyed = true; // Stop all active tweens on this tower and its components tween.stop(self); tween.stop(gunContainer); if (self.energyCore) { tween.stop(self.energyCore); } // Clean up all support structures and their tweens if (self.supportStructures) { for (var i = 0; i < self.supportStructures.length; i++) { if (self.supportStructures[i]) { tween.stop(self.supportStructures[i]); } } self.supportStructures = []; } // Clean up gun graphics if (self.gunGraphics) { tween.stop(self.gunGraphics); self.gunGraphics = null; } // Clear cells in range references 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 = []; // Clear grid cells occupied by this tower 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; } } } // Remove from towers array var towerIndex = towers.indexOf(self); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } // Remove from parent container if (self.parent) { self.parent.removeChild(self); } // Remove any associated range indicators for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange && game.children[i].tower === self) { game.removeChild(game.children[i]); } } // Close any upgrade menus for this tower var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu && child.tower === self; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } // Clear selected tower if it's this tower if (selectedTower === self) { selectedTower = null; } // Clean up ultimate ability if (self.ultimate) { self.ultimate.destroy(); self.ultimate = null; } // Nullify all object references self.targetEnemy = null; self.energyCore = null; // Call parent destroy Container.prototype.destroy.call(self); }; 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 TowerSpecialization = Container.expand(function (tower, branchType) { var self = Container.call(this); self.tower = tower; self.branchType = branchType || 'none'; self.linkedTowers = []; self.isDestroyed = false; // Add specialization visual indicator var specIcon = self.attachAsset('specializationIcon', { anchorX: 0.5, anchorY: 0.5 }); specIcon.width = 30; specIcon.height = 30; specIcon.y = -self.tower.children[0].height / 2 - 20; // Set specialization color based on branch switch (self.branchType) { case 'antiarmor': specIcon.tint = 0xFF6600; break; case 'longrange': specIcon.tint = 0x00AAFF; break; case 'nuclear': specIcon.tint = 0xFF0000; break; case 'chainreaction': specIcon.tint = 0xFFFF00; break; default: specIcon.tint = 0xFFD700; } self.applySpecialization = function () { switch (self.branchType) { case 'antiarmor': // Anti-armor: pierce shields, extra damage to immune enemies self.tower.armorPiercing = true; self.tower.damage = Math.floor(self.tower.damage * 1.5); break; case 'longrange': // Long-range: significantly extended range self.tower.rangeMultiplier = 2.0; self.tower.refreshCellsInRange(); break; case 'nuclear': // Nuclear: huge splash damage, slower fire rate self.tower.damage = Math.floor(self.tower.damage * 2.5); self.tower.fireRate = Math.floor(self.tower.fireRate * 1.8); self.tower.splashRadius = CELL_SIZE * 3; break; case 'chainreaction': // Chain reaction: damage spreads between enemies self.tower.chainReaction = true; self.tower.chainRange = CELL_SIZE * 2; self.tower.chainDamage = 0.7; break; } // Update tower visuals to show specialization if (self.tower.updateSpecializationVisuals) { self.tower.updateSpecializationVisuals(); } }; self.findNearbyTowers = function () { var nearby = []; var searchRadius = CELL_SIZE * 4; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower !== self.tower && tower.specialization) { var dx = tower.x - self.tower.x; var dy = tower.y - self.tower.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= searchRadius) { nearby.push(tower); } } } return nearby; }; self.createSupportLinks = function () { var nearbyTowers = self.findNearbyTowers(); for (var i = 0; i < nearbyTowers.length; i++) { var tower = nearbyTowers[i]; if (self.linkedTowers.indexOf(tower) === -1) { self.linkedTowers.push(tower); // Create visual link beam var linkBeam = new Container(); linkBeam.x = self.tower.x; linkBeam.y = self.tower.y; var beamGraphics = linkBeam.attachAsset('linkBeam', { anchorX: 0, anchorY: 0.5 }); var dx = tower.x - self.tower.x; var dy = tower.y - self.tower.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); beamGraphics.width = distance; beamGraphics.rotation = angle; beamGraphics.alpha = 0.5; beamGraphics.tint = 0x00FF88; game.addChild(linkBeam); self.tower.linkBeam = linkBeam; } } }; self.applySupportBonus = function () { // Linked towers get damage and fire rate bonus var bonusMultiplier = 1 + self.linkedTowers.length * 0.15; self.tower.supportBonus = bonusMultiplier; }; self.update = function () { if (self.branchType !== 'none') { self.createSupportLinks(); self.applySupportBonus(); } }; self.destroy = function () { if (self.isDestroyed) return; self.isDestroyed = true; // Remove link beams if (self.tower.linkBeam && self.tower.linkBeam.parent) { self.tower.linkBeam.parent.removeChild(self.tower.linkBeam); } tween.stop(self); if (self.parent) { self.parent.removeChild(self); } Container.prototype.destroy.call(self); }; return self; }); var TowerUltimate = Container.expand(function (tower, ultimateType) { var self = Container.call(this); self.tower = tower; self.ultimateType = ultimateType || 'none'; self.cooldownTime = 1800; // 30 seconds at 60fps self.currentCooldown = 0; self.isDestroyed = false; // Add ultimate indicator var ultimateIcon = self.attachAsset('ultimateIndicator', { anchorX: 0.5, anchorY: 0.5 }); ultimateIcon.y = -self.tower.children[0].height / 2 - 35; ultimateIcon.alpha = 0.3; // Dim when on cooldown // Set ultimate-specific properties switch (self.ultimateType) { case 'orbital': self.cooldownTime = 2400; // 40 seconds ultimateIcon.tint = 0xFF0000; break; case 'nanite': self.cooldownTime = 2100; // 35 seconds ultimateIcon.tint = 0x00FF88; break; case 'temporal': self.cooldownTime = 1800; // 30 seconds ultimateIcon.tint = 0x9966FF; break; default: ultimateIcon.tint = 0xFFD700; } self.canActivate = function () { return self.currentCooldown <= 0 && !self.isDestroyed; }; self.activate = function () { if (!self.canActivate()) return false; self.currentCooldown = self.cooldownTime; // Visual cooldown effect ultimateIcon.alpha = 0.3; tween(ultimateIcon, { alpha: 1.0 }, { duration: self.cooldownTime, easing: tween.linear }); switch (self.ultimateType) { case 'orbital': return self.orbitalStrike(); case 'nanite': return self.naniteSwarm(); case 'temporal': return self.timeDilation(); } return false; }; self.orbitalStrike = function () { // Find target area with most enemies var bestTarget = null; var maxEnemies = 0; var strikeRadius = CELL_SIZE * 3; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var nearbyCount = 0; for (var j = 0; j < enemies.length; j++) { var otherEnemy = enemies[j]; var dx = otherEnemy.x - enemy.x; var dy = otherEnemy.y - enemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= strikeRadius) { nearbyCount++; } } if (nearbyCount > maxEnemies) { maxEnemies = nearbyCount; bestTarget = enemy; } } if (bestTarget) { // Create orbital strike effect var strikeEffect = new Container(); strikeEffect.x = bestTarget.x; strikeEffect.y = bestTarget.y; var strikeGraphics = strikeEffect.attachAsset('orbitalStrike', { anchorX: 0.5, anchorY: 0.5 }); strikeGraphics.alpha = 0.0; strikeGraphics.scaleX = 0.1; strikeGraphics.scaleY = 0.1; strikeGraphics.blendMode = 1; // Additive game.addChild(strikeEffect); // Warning phase tween(strikeGraphics, { alpha: 0.7, scaleX: 1.0, scaleY: 1.0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { // Strike phase - massive damage var strikeDamage = self.tower.damage * 15; for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var dx = enemy.x - bestTarget.x; var dy = enemy.y - bestTarget.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= strikeRadius) { enemy.health -= strikeDamage; if (enemy.health <= 0) { enemy.health = 0; } else { enemy.healthBar.width = enemy.health / enemy.maxHealth * 70; } // Visual damage effect tween(enemy, { tint: 0xFFFFFF, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(enemy, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.easeIn }); } }); } } // Final explosion effect tween(strikeGraphics, { scaleX: 2.5, scaleY: 2.5, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (strikeEffect.parent) { strikeEffect.parent.removeChild(strikeEffect); } } }); } }); var notification = game.addChild(new Notification("🛰️ ORBITAL STRIKE INCOMING! 🛰️")); notification.x = 2048 / 2; notification.y = grid.height - 50; return true; } return false; }; self.naniteSwarm = function () { // Create nanite swarm that spreads poison and self-replicates var swarmCenter = self.tower; var swarmRadius = CELL_SIZE * 4; var swarmDamage = self.tower.damage * 3; // Create visual swarm effect var swarmEffect = new Container(); swarmEffect.x = swarmCenter.x; swarmEffect.y = swarmCenter.y; var swarmGraphics = swarmEffect.attachAsset('naniteSwarm', { anchorX: 0.5, anchorY: 0.5 }); swarmGraphics.alpha = 0.8; swarmGraphics.tint = 0x00FF88; game.addChild(swarmEffect); // Create multiple nanite particles var particles = []; for (var i = 0; i < 12; i++) { var particle = swarmEffect.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); particle.width = 15; particle.height = 15; particle.tint = 0x44FFAA; var angle = i / 12 * Math.PI * 2; particle.x = Math.cos(angle) * 50; particle.y = Math.sin(angle) * 50; particles.push(particle); } // Animate swarm expansion tween(swarmEffect, { scaleX: 3.0, scaleY: 3.0 }, { duration: 2000, easing: tween.easeOut }); // Animate particles spiraling outward for (var i = 0; i < particles.length; i++) { var particle = particles[i]; tween(particle, { x: particle.x * 3, y: particle.y * 3, rotation: Math.PI * 4 }, { duration: 2000, delay: i * 100, easing: tween.easeOut }); } // Apply nanite effects over time var swarmDuration = 180; // 3 seconds var _swarmTick = function swarmTick() { swarmDuration--; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - swarmCenter.x; var dy = enemy.y - swarmCenter.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= swarmRadius) { // Apply nanite damage enemy.health -= swarmDamage; if (enemy.health <= 0) { enemy.health = 0; } else { enemy.healthBar.width = enemy.health / enemy.maxHealth * 70; } // Apply enhanced poison effect enemy.poisoned = true; enemy.poisonDamage = swarmDamage * 0.5; enemy.poisonDuration = 300; // Extended duration // Self-replication chance if (Math.random() < 0.3 && swarmDuration > 60) { // 30% chance to spread to nearby enemies for (var j = 0; j < enemies.length; j++) { var nearbyEnemy = enemies[j]; if (nearbyEnemy !== enemy) { var nearbyDx = nearbyEnemy.x - enemy.x; var nearbyDy = nearbyEnemy.y - enemy.y; var nearbyDistance = Math.sqrt(nearbyDx * nearbyDx + nearbyDy * nearbyDy); if (nearbyDistance <= CELL_SIZE * 2) { nearbyEnemy.poisoned = true; nearbyEnemy.poisonDamage = swarmDamage * 0.3; nearbyEnemy.poisonDuration = 240; break; } } } } } } if (swarmDuration > 0) { LK.setTimeout(_swarmTick, 100); // Continue every 100ms } else { // Clean up swarm effect tween(swarmEffect, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { if (swarmEffect.parent) { swarmEffect.parent.removeChild(swarmEffect); } } }); } }; LK.setTimeout(_swarmTick, 100); // Start swarm effects var notification = game.addChild(new Notification("🤖 NANITE SWARM DEPLOYED! 🤖")); notification.x = 2048 / 2; notification.y = grid.height - 50; return true; }; self.timeDilation = function () { // Create temporal distortion field that slows time for enemies var dilationCenter = self.tower; var dilationRadius = CELL_SIZE * 5; var dilationDuration = 600; // 10 seconds // Create visual time dilation effect var dilationEffect = new Container(); dilationEffect.x = dilationCenter.x; dilationEffect.y = dilationCenter.y; var dilationGraphics = dilationEffect.attachAsset('timeDilation', { anchorX: 0.5, anchorY: 0.5 }); dilationGraphics.alpha = 0.4; dilationGraphics.tint = 0x9966FF; dilationGraphics.blendMode = 1; // Additive game.addChild(dilationEffect); // Pulsing time distortion effect tween(dilationGraphics, { scaleX: 1.3, scaleY: 1.3, alpha: 0.7 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(dilationGraphics, { scaleX: 1.0, scaleY: 1.0, alpha: 0.4 }, { duration: 1000, easing: tween.easeInOut }); } }); // Apply time dilation effects var affectedEnemies = []; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - dilationCenter.x; var dy = enemy.y - dilationCenter.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= dilationRadius) { // Store original speed if (!enemy.originalDilationSpeed) { enemy.originalDilationSpeed = enemy.speed; } // Extreme slow effect (90% speed reduction) enemy.speed = enemy.originalDilationSpeed * 0.1; enemy.temporallyDilated = true; enemy.dilationDuration = dilationDuration; affectedEnemies.push(enemy); // Visual distortion effect on enemy tween(enemy, { tint: 0x9966FF, alpha: 0.8 }, { duration: 300, easing: tween.easeOut }); } } // During dilation, towers in the field get boosted fire rate var boostedTowers = []; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx = tower.x - dilationCenter.x; var dy = tower.y - dilationCenter.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= dilationRadius && tower !== self.tower) { tower.originalDilationFireRate = tower.fireRate; tower.fireRate = Math.floor(tower.fireRate * 0.3); // 70% faster firing tower.temporallyBoosted = true; boostedTowers.push(tower); } } // Clean up after duration LK.setTimeout(function () { // Restore enemy speeds for (var i = 0; i < affectedEnemies.length; i++) { var enemy = affectedEnemies[i]; if (enemy.parent && !enemy.isDestroyed) { enemy.speed = enemy.originalDilationSpeed; enemy.temporallyDilated = false; // Restore enemy appearance tween(enemy, { alpha: 1.0 }, { duration: 300, easing: tween.easeIn }); } } // Restore tower fire rates for (var i = 0; i < boostedTowers.length; i++) { var tower = boostedTowers[i]; if (tower.parent && !tower.isDestroyed) { tower.fireRate = tower.originalDilationFireRate; tower.temporallyBoosted = false; } } // Remove visual effect tween(dilationEffect, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 1000, easing: tween.easeIn, onFinish: function onFinish() { if (dilationEffect.parent) { dilationEffect.parent.removeChild(dilationEffect); } } }); }, dilationDuration * 16.67); // Convert frames to milliseconds var notification = game.addChild(new Notification("⏰ TIME DILATION ACTIVATED! ⏰")); notification.x = 2048 / 2; notification.y = grid.height - 50; return true; }; self.update = function () { if (self.currentCooldown > 0) { self.currentCooldown--; } }; self.destroy = function () { if (self.isDestroyed) return; self.isDestroyed = true; tween.stop(self); if (self.parent) { self.parent.removeChild(self); } Container.prototype.destroy.call(self); }; return self; }); var TutorialSystem = Container.expand(function () { var self = Container.call(this); self.currentStep = 0; self.isActive = false; self.isWaitingForAction = false; self.tutorialCompleted = storage.tutorialCompleted || false; self.currentPhase = 'introduction'; // introduction, basics, combat, advanced, endgame self.highlightOverlay = null; self.actionArrow = null; self.tutorialSteps = [ // INTRODUCTION PHASE { phase: 'introduction', title: "Welcome to Space Tower Defense!", text: "Commander, our space station is under attack!\nYou must defend against waves of alien invaders.", highlight: null, action: null, waitForAction: false }, { phase: 'introduction', title: "Your Mission", text: "Build defensive towers to stop enemies from reaching\nthe bottom of the screen and destroying our base.", highlight: null, action: null, waitForAction: false }, { phase: 'introduction', title: "Resources Overview", text: "Gold: Used to build and upgrade towers\nLives: Lost when enemies reach the bottom\nScore: Points earned for defeating enemies", highlight: "ui", action: null, waitForAction: false }, // BASICS PHASE { phase: 'basics', title: "Building Your First Tower", text: "Let's start by building a basic tower.\nDrag the 'Default' tower from the bottom panel.", highlight: "sourceTowers", action: "buildTower", waitForAction: true }, { phase: 'basics', title: "Tower Placement", text: "Good! Towers need 2x2 space and cannot block enemy paths.\nThe green preview shows valid placement areas.", highlight: null, action: null, waitForAction: false }, { phase: 'basics', title: "Tower Information", text: "Tap on your tower to see its stats and upgrade options.\nTry tapping the tower you just built.", highlight: "tower", action: "selectTower", waitForAction: true }, { phase: 'basics', title: "Upgrading Towers", text: "Excellent! Upgrading increases damage and fire rate.\nUpgrades get more expensive at higher levels.", highlight: null, action: null, waitForAction: false }, { phase: 'basics', title: "Starting Combat", text: "Now let's face some enemies!\nTap the 'Start Game' button to begin wave 1.", highlight: "startButton", action: "startGame", waitForAction: true }, // COMBAT PHASE { phase: 'combat', title: "Enemy Types - Normal", text: "The first enemies are normal type - balanced health and speed.\nYour towers will automatically target and fire at them.", highlight: null, action: null, waitForAction: false }, { phase: 'combat', title: "Earning Gold", text: "Great! You earn gold for each enemy defeated.\nUse this gold to build more towers and upgrades.", highlight: "gold", action: null, waitForAction: false }, { phase: 'combat', title: "Tower Specialization", text: "Different tower types are effective against different enemies.\nLet's build a Rapid tower for faster firing.", highlight: "sourceTowers", action: "buildRapidTower", waitForAction: true }, { phase: 'combat', title: "Enemy Types - Fast", text: "Wave 2 brings fast enemies (blue).\nRapid towers are excellent against quick targets.", highlight: null, action: null, waitForAction: false }, { phase: 'combat', title: "Tower Range", text: "Each tower has a different range.\nSniper towers have long range but fire slowly.", highlight: null, action: null, waitForAction: false }, { phase: 'combat', title: "Build a Sniper Tower", text: "Try building a Sniper tower (orange) for long-range support.\nPlace it where it can cover a large area.", highlight: "sourceTowers", action: "buildSniperTower", waitForAction: true }, // ADVANCED PHASE { phase: 'advanced', title: "Special Enemy Types", text: "As waves progress, you'll face special enemies:\n• Flying (yellow) - immune to some towers\n• Immune (red) - resistant to effects", highlight: null, action: null, waitForAction: false }, { phase: 'advanced', title: "Splash Damage", text: "Splash towers (green) deal area damage.\nThey're effective against groups of enemies.", highlight: null, action: null, waitForAction: false }, { phase: 'advanced', title: "Support Towers", text: "Slow towers (purple) reduce enemy speed.\nPoison towers (cyan) deal damage over time.", highlight: null, action: null, waitForAction: false }, { phase: 'advanced', title: "Hero Units", text: "Deploy hero units for additional firepower!\nHeroes can move around and have special abilities.", highlight: "heroPanel", action: null, waitForAction: false }, { phase: 'advanced', title: "Deploy a Tank Hero", text: "Try deploying a Tank hero (green).\nTap the tank button, then tap on the battlefield.", highlight: "heroPanel", action: "deployHero", waitForAction: true }, { phase: 'advanced', title: "Hero Abilities", text: "Heroes gain experience and can use special abilities.\nTap on your hero to activate its ability when ready.", highlight: null, action: null, waitForAction: false }, { phase: 'advanced', title: "Station Modules", text: "Customize your space station with modules!\nTap the 'Customize' button to manage station upgrades.", highlight: "stationPanel", action: null, waitForAction: false }, // ENDGAME PHASE { phase: 'endgame', title: "Boss Waves", text: "Every 10th wave is a Boss wave with powerful enemies.\nBoss enemies have much more health but give more gold.", highlight: "waveIndicator", action: null, waitForAction: false }, { phase: 'endgame', title: "Environmental Hazards", text: "Watch for space hazards like asteroids, nebulas, and solar flares.\nThese can help or hinder your defense strategy.", highlight: null, action: null, waitForAction: false }, { phase: 'endgame', title: "Advanced Strategies", text: "• Upgrade key towers to maximum level\n• Use hero abilities strategically\n• Adapt to enemy types each wave", highlight: null, action: null, waitForAction: false }, { phase: 'endgame', title: "Tower Specializations", text: "At level 3, some towers can specialize:\n• Sniper: Anti-Armor or Long-Range\n• Splash: Nuclear or Chain Reaction", highlight: null, action: null, waitForAction: false }, { phase: 'endgame', title: "Path Branching", text: "Enemies may take different routes through your defenses.\nSome paths are shorter but more defended.", highlight: null, action: null, waitForAction: false }, { phase: 'endgame', title: "Tutorial Complete!", text: "You're ready to defend the station, Commander!\nSurvive all 50 waves to achieve victory.", highlight: null, action: null, waitForAction: false }]; var tutorialContainer = new Container(); self.addChild(tutorialContainer); var overlay = tutorialContainer.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 1900; overlay.height = 450; overlay.tint = 0x0A0A0A; overlay.alpha = 0.95; // Phase indicator var phaseText = new Text2("", { size: 40, fill: 0xFFD700, weight: 600 }); phaseText.anchor.set(0.5, 0.5); phaseText.y = -160; tutorialContainer.addChild(phaseText); var titleText = new Text2("", { size: 65, fill: 0xFFFFFF, weight: 800 }); titleText.anchor.set(0.5, 0.5); titleText.y = -100; tutorialContainer.addChild(titleText); var bodyText = new Text2("", { size: 45, fill: 0xE0E0E0, weight: 400 }); bodyText.anchor.set(0.5, 0.5); bodyText.y = 0; tutorialContainer.addChild(bodyText); // Progress indicator var progressText = new Text2("", { size: 35, fill: 0xCCCCCC, weight: 400 }); progressText.anchor.set(0.5, 0.5); progressText.y = 80; tutorialContainer.addChild(progressText); var buttonContainer = new Container(); buttonContainer.y = 140; tutorialContainer.addChild(buttonContainer); var nextButton = new Container(); var nextBg = nextButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); nextBg.width = 200; nextBg.height = 80; nextBg.tint = 0x00AA00; var nextText = new Text2("Next", { size: 55, fill: 0xFFFFFF, weight: 800 }); nextText.anchor.set(0.5, 0.5); nextButton.addChild(nextText); nextButton.x = -250; buttonContainer.addChild(nextButton); var skipButton = new Container(); var skipBg = skipButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); skipBg.width = 200; skipBg.height = 80; skipBg.tint = 0xAA0000; var skipText = new Text2("Skip", { size: 55, fill: 0xFFFFFF, weight: 800 }); skipText.anchor.set(0.5, 0.5); skipButton.addChild(skipText); skipButton.x = 0; buttonContainer.addChild(skipButton); var prevButton = new Container(); var prevBg = prevButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); prevBg.width = 200; prevBg.height = 80; prevBg.tint = 0x0066CC; var prevText = new Text2("Back", { size: 55, fill: 0xFFFFFF, weight: 800 }); prevText.anchor.set(0.5, 0.5); prevButton.addChild(prevText); prevButton.x = 250; buttonContainer.addChild(prevButton); self.createHighlight = function (target) { if (self.highlightOverlay) { self.highlightOverlay.parent.removeChild(self.highlightOverlay); } self.highlightOverlay = new Container(); var highlight = self.highlightOverlay.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); highlight.width = 300; highlight.height = 300; highlight.tint = 0xFFD700; highlight.alpha = 0.3; // Pulsing animation tween(highlight, { scaleX: 1.2, scaleY: 1.2, alpha: 0.6 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.highlightOverlay.isDestroyed) { tween(highlight, { scaleX: 1.0, scaleY: 1.0, alpha: 0.3 }, { duration: 1000, easing: tween.easeInOut }); } } }); game.addChild(self.highlightOverlay); return self.highlightOverlay; }; self.removeHighlight = function () { if (self.highlightOverlay && self.highlightOverlay.parent) { tween.stop(self.highlightOverlay); self.highlightOverlay.parent.removeChild(self.highlightOverlay); self.highlightOverlay = null; } }; self.showStep = function (stepIndex) { if (stepIndex >= self.tutorialSteps.length) { self.endTutorial(); return; } var step = self.tutorialSteps[stepIndex]; var phaseDisplay = step.phase.charAt(0).toUpperCase() + step.phase.slice(1) + " Phase"; phaseText.setText(phaseDisplay); titleText.setText(step.title); bodyText.setText(step.text); progressText.setText("Step " + (stepIndex + 1) + " of " + self.tutorialSteps.length); // Update button states nextButton.visible = !step.waitForAction; skipButton.visible = true; prevButton.visible = stepIndex > 0; if (stepIndex === self.tutorialSteps.length - 1) { nextText.setText("Finish!"); skipText.setText("Start!"); } // Handle highlights self.removeHighlight(); if (step.highlight) { switch (step.highlight) { case "ui": // Highlight UI area if (self.highlightOverlay) { self.highlightOverlay.x = 2048 / 2; self.highlightOverlay.y = 100; } break; case "sourceTowers": // Highlight tower selection area if (sourceTowers.length > 0) { var highlight = self.createHighlight(); highlight.x = sourceTowers[0].x; highlight.y = sourceTowers[0].y; } break; case "gold": // Highlight gold display var highlight = self.createHighlight(); highlight.x = 2048 / 2 - 400; highlight.y = 100; break; case "heroPanel": // Highlight hero panel var highlight = self.createHighlight(); highlight.x = 2048 / 2; highlight.y = 200; break; case "stationPanel": // Highlight station panel var highlight = self.createHighlight(); highlight.x = 2048 - 200; highlight.y = 250; break; case "waveIndicator": // Highlight wave indicator var highlight = self.createHighlight(); highlight.x = 2048 / 2; highlight.y = 2732 - 80; break; case "startButton": // Highlight start game button if (waveIndicator && waveIndicator.waveMarkers.length > 0) { var highlight = self.createHighlight(); highlight.x = waveIndicator.waveMarkers[0].x + waveIndicator.x; highlight.y = waveIndicator.waveMarkers[0].y + waveIndicator.y; } break; } } // Set up action waiting if (step.waitForAction) { self.isWaitingForAction = true; self.expectedAction = step.action; } }; self.startTutorial = function () { self.isActive = true; self.visible = true; self.currentStep = 0; self.showStep(0); // Position tutorial in center of screen tutorialContainer.x = 2048 / 2; tutorialContainer.y = 1400; }; self.nextStep = function () { if (self.isWaitingForAction) return; self.currentStep++; self.showStep(self.currentStep); }; self.prevStep = function () { if (self.currentStep > 0) { self.currentStep--; self.showStep(self.currentStep); } }; self.endTutorial = function () { self.isActive = false; self.visible = false; self.removeHighlight(); storage.tutorialCompleted = true; self.tutorialCompleted = true; // Auto-start the game after tutorial if (waveIndicator && !waveIndicator.gameStarted) { waveIndicator.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; } var notification = game.addChild(new Notification("Tutorial completed! Good luck, Commander!")); notification.x = 2048 / 2; notification.y = grid.height - 100; }; self.checkActionCompleted = function (action) { if (!self.isWaitingForAction || self.expectedAction !== action) return; self.isWaitingForAction = false; self.expectedAction = null; // Show next button and auto-advance after delay nextButton.visible = true; LK.setTimeout(function () { if (self.isActive && !self.isWaitingForAction) { self.nextStep(); } }, 1500); }; nextButton.down = function () { self.nextStep(); }; skipButton.down = function () { self.endTutorial(); }; prevButton.down = function () { self.prevStep(); }; self.visible = false; 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: 0xE0E0E0, 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; // Tutorial action tracking if (tutorialSystem && tutorialSystem.isActive) { tutorialSystem.checkActionCompleted('startGame'); } var notification = game.addChild(new Notification("Game started! Wave 1 incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; // Play wave start sound LK.getSound('waveStart').play(); } }; 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 = 3; waveTypeShadow.y = 3; 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); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } }; self.handleWaveProgression(); }; return self; }); var Wormhole = Container.expand(function (x, y, targetX, targetY) { var self = Container.call(this); self.x = x; self.y = y; self.targetX = targetX; self.targetY = targetY; self.radius = 90; self.isDestroyed = false; self.cooldown = 0; var wormholeGraphics = self.attachAsset('wormhole', { anchorX: 0.5, anchorY: 0.5 }); wormholeGraphics.alpha = 0.7; wormholeGraphics.blendMode = 1; // Additive blending // Spinning animation tween(wormholeGraphics, { rotation: Math.PI * 2 }, { duration: 3000, easing: tween.linear, onFinish: function onFinish() { if (!self.isDestroyed) { wormholeGraphics.rotation = 0; } } }); // Create target wormhole visual self.targetWormhole = new Container(); self.targetWormhole.x = targetX; self.targetWormhole.y = targetY; var targetGraphics = self.targetWormhole.attachAsset('wormhole', { anchorX: 0.5, anchorY: 0.5 }); targetGraphics.alpha = 0.5; targetGraphics.tint = 0xFF1493; // Different color for exit targetGraphics.blendMode = 1; game.addChild(self.targetWormhole); // Sync rotation with main wormhole tween(targetGraphics, { rotation: -Math.PI * 2 }, { duration: 3000, easing: tween.linear, onFinish: function onFinish() { if (!self.isDestroyed) { targetGraphics.rotation = 0; } } }); self.update = function () { if (self.cooldown > 0) { self.cooldown--; return; } // Check for enemies within range for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.isFlying) continue; // Flying enemies ignore wormholes var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.radius) { // Teleport enemy self.teleportEnemy(enemy); self.cooldown = 180; // 3 second cooldown break; } } }; self.teleportEnemy = function (enemy) { // Create teleport effect at source var sourceEffect = new Container(); sourceEffect.x = self.x; sourceEffect.y = self.y; var sourceGraphics = sourceEffect.attachAsset('teleportEffect', { anchorX: 0.5, anchorY: 0.5 }); sourceGraphics.tint = 0x9400D3; sourceGraphics.alpha = 0.8; game.addChild(sourceEffect); // Create teleport effect at destination var destEffect = new Container(); destEffect.x = self.targetX; destEffect.y = self.targetY; var destGraphics = destEffect.attachAsset('teleportEffect', { anchorX: 0.5, anchorY: 0.5 }); destGraphics.tint = 0xFF1493; destGraphics.alpha = 0.8; game.addChild(destEffect); // Animate effects tween(sourceEffect, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (sourceEffect.parent) { sourceEffect.parent.removeChild(sourceEffect); } } }); tween(destEffect, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (destEffect.parent) { destEffect.parent.removeChild(destEffect); } } }); // Teleport enemy enemy.x = self.targetX; enemy.y = self.targetY; enemy.currentCellX = Math.floor((self.targetX - grid.x) / CELL_SIZE); enemy.currentCellY = Math.floor((self.targetY - grid.y) / CELL_SIZE); enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentTarget = null; // Reset pathfinding // Flash the wormhole tween(wormholeGraphics, { scaleX: 1.5, scaleY: 1.5, alpha: 1.0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(wormholeGraphics, { scaleX: 1.0, scaleY: 1.0, alpha: 0.7 }, { duration: 300, easing: tween.easeIn }); } }); }; self.destroy = function () { if (self.isDestroyed) return; self.isDestroyed = true; // Remove target wormhole if (self.targetWormhole && self.targetWormhole.parent) { self.targetWormhole.parent.removeChild(self.targetWormhole); } var wormholeIndex = wormholes.indexOf(self); if (wormholeIndex !== -1) { wormholes.splice(wormholeIndex, 1); } tween.stop(self); if (self.parent) { self.parent.removeChild(self); } Container.prototype.destroy.call(self); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000011 }); /**** * Game Code ****/ // Add background image // Base tower structures for each type // Tower gun/weapon parts - Level 1 // Tower gun/weapon parts - Level 2 // Tower gun/weapon parts - Level 3 // Tower gun/weapon parts - Level 4 // Tower gun/weapon parts - Level 5 // Tower gun/weapon parts - Level 6 (Max Level) // Support structures and details // Visual indicators // Specialization visual elements var backgroundImage = game.attachAsset('spaceBackground', { anchorX: 0.5, anchorY: 0.5 }); backgroundImage.x = 2048 / 2; backgroundImage.y = 2732 / 2; backgroundImage.alpha = 0.8; var isHidingUpgradeMenu = false; 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 = []; // Object pooling for bullets var bulletPool = []; var maxBulletPoolSize = 100; function getBulletFromPool(startX, startY, targetEnemy, damage, speed) { var bullet; if (bulletPool.length > 0) { bullet = bulletPool.pop(); // Reset bullet properties bullet.targetEnemy = targetEnemy; bullet.damage = damage || 10; bullet.speed = speed || 5; bullet.x = startX; bullet.y = startY; bullet.isDestroyed = false; bullet.visible = true; bullet.alpha = 1; if (bullet.children[0]) { bullet.children[0].alpha = 1; bullet.children[0].scaleX = 1; bullet.children[0].scaleY = 1; } } else { bullet = new Bullet(startX, startY, targetEnemy, damage, speed); } return bullet; } function returnBulletToPool(bullet) { if (bulletPool.length < maxBulletPoolSize && bullet.parent) { // Clean up target enemy reference properly if (bullet.targetEnemy && bullet.targetEnemy.bulletsTargetingThis) { var bulletIndex = bullet.targetEnemy.bulletsTargetingThis.indexOf(bullet); if (bulletIndex !== -1) { bullet.targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } // Stop all tweens before pooling tween.stop(bullet); if (bullet.children && bullet.children[0]) { tween.stop(bullet.children[0]); } bullet.parent.removeChild(bullet); bullet.targetEnemy = null; bullet.visible = false; bulletPool.push(bullet); } else { bullet.destroy(); } } var defenses = []; var selectedTower = null; var gold = 60; // Reduced starting gold to make early choices more meaningful 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; var sourceTower = null; // Hero system variables var heroes = []; var maxHeroes = 3; var heroDeploymentMode = false; var heroPreview = null; var selectedHeroType = 'tank'; // Station customization variables var stationCustomizer = null; var researchLabs = []; var shieldGenerators = []; var teleporterNodes = []; // Environmental hazards variables var asteroids = []; var nebulaClouds = []; var solarFlares = []; var wormholes = []; var hazardTimer = 0; var nextHazardTime = 1800; // 30 seconds at 60fps // Branching routes system variables var pathJunctions = []; var alternativePaths = []; var routeChoiceInfluences = []; var pathDifficulties = {}; // Tower specialization variables var specializationMenus = []; var towerLinks = []; // Ultimate ability variables var activeUltimates = []; var ultimateEffects = []; // Load station data from storage var stationData = storage.stationData || { modules: [], researchPoints: 0, completedTechs: [] }; // Initialize route visualizer var routeVisualizer = new RouteVisualizer(); game.addChild(routeVisualizer); // Initialize branching routes system function initializeBranchingRoutes() { // Create main junction points in the middle area var mainJunction = new PathJunction(grid.x + 12 * CELL_SIZE, grid.y + 15 * CELL_SIZE, [{ targetX: 10, targetY: 22, // Left branch - shorter but more defended isShortPath: true, pathLength: 8, towerDensity: 0, currentEnemyCount: 0, recentSwarmTraffic: 0, hasAntiAir: false, difficulty: 20 }, { targetX: 14, targetY: 22, // Right branch - longer but less defended isShortPath: false, pathLength: 12, towerDensity: 0, currentEnemyCount: 0, recentSwarmTraffic: 0, hasAntiAir: false, difficulty: 15 }]); // Secondary junction for more complex routing var secondaryJunction = new PathJunction(grid.x + 8 * CELL_SIZE, grid.y + 12 * CELL_SIZE, [{ targetX: 6, targetY: 18, // Far left route isShortPath: false, pathLength: 15, towerDensity: 0, currentEnemyCount: 0, recentSwarmTraffic: 0, hasAntiAir: false, difficulty: 25 }, { targetX: 12, targetY: 15, // Center route to main junction isShortPath: true, pathLength: 6, towerDensity: 0, currentEnemyCount: 0, recentSwarmTraffic: 0, hasAntiAir: false, difficulty: 30 }]); game.addChild(mainJunction); game.addChild(secondaryJunction); pathJunctions.push(mainJunction); pathJunctions.push(secondaryJunction); var notification = game.addChild(new Notification("🔀 Multi-path routing system activated! 🔀")); notification.x = 2048 / 2; notification.y = grid.height - 150; } // Initialize the branching system after a short delay LK.setTimeout(function () { initializeBranchingRoutes(); }, 2000); function getModuleCost(moduleType, level) { var baseCosts = { research: 50, shield: 75, teleporter: 100 }; var baseCost = baseCosts[moduleType] || 50; return Math.floor(baseCost * Math.pow(1.5, level - 1)); } function getTechnology(techId) { var technologies = { 'tower_damage': { id: 'tower_damage', name: 'Enhanced Weapons', cost: 100, onComplete: function onComplete() { // Increase all tower damage by 20% for (var i = 0; i < towers.length; i++) { towers[i].damage = Math.floor(towers[i].damage * 1.2); } } }, 'tower_range': { id: 'tower_range', name: 'Extended Range', cost: 150, onComplete: function onComplete() { // Increase all tower ranges for (var i = 0; i < towers.length; i++) { towers[i].refreshCellsInRange(); } } }, 'shield_efficiency': { id: 'shield_efficiency', name: 'Shield Efficiency', cost: 200, onComplete: function onComplete() { // Reduce shield energy costs for (var i = 0; i < shieldGenerators.length; i++) { shieldGenerators[i].maxShieldEnergy *= 1.5; shieldGenerators[i].shieldEnergy = shieldGenerators[i].maxShieldEnergy; } } } }; return technologies[techId]; } // Spatial partitioning for collision optimization var spatialGrid = { cellSize: CELL_SIZE * 2, cells: {}, getKey: function getKey(x, y) { var gridX = Math.floor(x / this.cellSize); var gridY = Math.floor(y / this.cellSize); return gridX + ',' + gridY; }, addEnemy: function addEnemy(enemy) { var key = this.getKey(enemy.x, enemy.y); if (!this.cells[key]) this.cells[key] = []; this.cells[key].push(enemy); }, removeEnemy: function removeEnemy(enemy) { var key = this.getKey(enemy.x, enemy.y); if (this.cells[key]) { var index = this.cells[key].indexOf(enemy); if (index !== -1) { this.cells[key].splice(index, 1); } } }, getNearbyEnemies: function getNearbyEnemies(x, y, range) { var nearby = []; var checkRadius = Math.ceil(range / this.cellSize); var centerX = Math.floor(x / this.cellSize); var centerY = Math.floor(y / this.cellSize); for (var dx = -checkRadius; dx <= checkRadius; dx++) { for (var dy = -checkRadius; dy <= checkRadius; dy++) { var key = centerX + dx + ',' + (centerY + dy); if (this.cells[key]) { nearby = nearby.concat(this.cells[key]); } } } return nearby; }, clear: function clear() { this.cells = {}; } }; var enemiesToSpawn = 10; // Default number of enemies per wave // Visual effects limiting var activeEffects = []; var maxActiveEffects = 20; var effectSkipCounter = 0; function canCreateEffect() { // Count active effects and remove destroyed ones activeEffects = activeEffects.filter(function (effect) { return effect.parent && !effect.isDestroyed; }); // Limit based on performance if (activeEffects.length >= maxActiveEffects) { effectSkipCounter++; // Skip every other effect when at limit return effectSkipCounter % 2 === 0; } return true; } function registerEffect(effect) { if (activeEffects.length < maxActiveEffects) { activeEffects.push(effect); } } var goldText = new Text2('Gold: ' + gold, { size: 60, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); var livesText = new Text2('Lives: ' + lives, { size: 60, fill: 0x00FF00, weight: 800 }); livesText.anchor.set(0.5, 0.5); var scoreText = new Text2('Score: ' + score, { size: 60, fill: 0xFF0000, weight: 800 }); scoreText.anchor.set(0.5, 0.5); var topMargin = 50; var centerX = 2048 / 2; var spacing = 400; LK.gui.top.addChild(goldText); LK.gui.top.addChild(livesText); LK.gui.top.addChild(scoreText); livesText.x = 0; livesText.y = topMargin; goldText.x = -spacing; goldText.y = topMargin; scoreText.x = spacing; scoreText.y = topMargin; function updateUI() { goldText.setText('Gold: ' + gold); livesText.setText('Lives: ' + lives); scoreText.setText('Score: ' + score); } function setGold(value) { gold = value; updateUI(); } // Performance monitoring and adaptive logic var performanceMonitor = { frameTime: 16.67, // Target 60 FPS lastTime: 0, avgFrameTime: 16.67, frameCount: 0, performanceLevel: 1.0, // 1.0 = full performance, 0.5 = half performance update: function update() { var currentTime = Date.now(); if (this.lastTime > 0) { var deltaTime = currentTime - this.lastTime; this.avgFrameTime = this.avgFrameTime * 0.9 + deltaTime * 0.1; // Adjust performance level based on frame time if (this.avgFrameTime > 25) { // Below 40 FPS this.performanceLevel = Math.max(0.3, this.performanceLevel - 0.1); } else if (this.avgFrameTime < 18) { // Above 55 FPS this.performanceLevel = Math.min(1.0, this.performanceLevel + 0.05); } } this.lastTime = currentTime; this.frameCount++; }, shouldSkipUpdate: function shouldSkipUpdate() { return Math.random() > this.performanceLevel; } }; // Memory monitoring system var memoryMonitor = { lastEnemyCount: 0, lastBulletCount: 0, lastTowerCount: 0, lastEffectCount: 0, update: function update() { // Track object counts for memory leak detection this.lastEnemyCount = enemies.length; this.lastBulletCount = bullets.length; this.lastTowerCount = towers.length; this.lastEffectCount = activeEffects.length; // Log warnings if counts are unexpectedly high if (this.lastEnemyCount > 100) { console.warn("High enemy count detected:", this.lastEnemyCount); } if (this.lastBulletCount > 200) { console.warn("High bullet count detected:", this.lastBulletCount); } if (this.lastEffectCount > 50) { console.warn("High effect count detected:", this.lastEffectCount); } }, forceCleanup: function forceCleanup() { // Emergency cleanup when memory usage is too high console.log("Performing emergency cleanup..."); // Clean up destroyed effects activeEffects = activeEffects.filter(function (effect) { return effect.parent && !effect.isDestroyed; }); // Clean up orphaned bullets for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent || bullets[i].isDestroyed) { bullets.splice(i, 1); } } // Clean up bullet pool bulletPool = bulletPool.filter(function (bullet) { return !bullet.isDestroyed; }); console.log("Cleanup complete. Objects remaining:", { enemies: enemies.length, bullets: bullets.length, towers: towers.length, effects: activeEffects.length, pooledBullets: bulletPool.length }); } }; 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 = 8; // Increased base cost from 5 switch (towerType) { case 'rapid': cost = 12; // Reduced from 15 - cheaper early game tower break; case 'sniper': cost = 20; // Reduced from 25 for better accessibility break; case 'splash': cost = 28; // Reduced from 35 break; case 'slow': cost = 32; // Reduced from 45 - utility tower should be accessible break; case 'poison': cost = 38; // Reduced from 55 - was too expensive 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(); // Play tower placement sound LK.getSound('towerPlace').play(); // Tutorial action tracking if (tutorialSystem && tutorialSystem.isActive) { if (towerType === 'default') { tutorialSystem.checkActionCompleted('buildTower'); } else if (towerType === 'rapid') { tutorialSystem.checkActionCompleted('buildRapidTower'); } else if (towerType === 'sniper') { tutorialSystem.checkActionCompleted('buildSniperTower'); } } 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) { // Handle hero deployment if (heroDeploymentMode) { // Check if tap is in valid deployment area (not on towers or paths) var deploymentValid = true; var gridX = Math.floor((x - grid.x) / CELL_SIZE); var gridY = Math.floor((y - grid.y) / CELL_SIZE); // Check if position is within grid bounds and not on blocked areas if (gridX < 2 || gridX >= 22 || gridY < 6 || gridY >= 25) { deploymentValid = false; } else { var cell = grid.getCell(gridX, gridY); if (!cell || cell.type !== 0) { deploymentValid = false; } } if (deploymentValid && heroes.length < maxHeroes) { var hero = new Hero(selectedHeroType); hero.x = x; hero.y = y; enemyLayerTop.addChild(hero); // Add heroes to top layer heroes.push(hero); updateHeroPanel(); heroDeploymentMode = false; // Tutorial action tracking if (tutorialSystem && tutorialSystem.isActive) { tutorialSystem.checkActionCompleted('deployHero'); } var notification = game.addChild(new Notification(selectedHeroType.charAt(0).toUpperCase() + selectedHeroType.slice(1) + " hero deployed!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } else { var notification = game.addChild(new Notification("Cannot deploy hero here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; heroDeploymentMode = false; return; } } var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenuVisible) { return; } // Improved touch detection with larger hit areas for mobile var touchPadding = 40; // Extra padding for easier touch for (var i = 0; i < sourceTowers.length; i++) { var tower = sourceTowers[i]; var hitArea = { left: tower.x - tower.width / 2 - touchPadding, right: tower.x + tower.width / 2 + touchPadding, top: tower.y - tower.height / 2 - touchPadding, bottom: tower.y + tower.height / 2 + touchPadding }; if (x >= hitArea.left && x <= hitArea.right && y >= hitArea.top && y <= hitArea.bottom) { // Check if player can afford this tower if (gold >= getTowerCost(tower.towerType)) { towerPreview.visible = true; isDragging = true; towerPreview.towerType = tower.towerType; towerPreview.updateAppearance(); // Improved drag offset for better mobile experience var dragOffsetY = CELL_SIZE * 2; // Larger offset to keep preview visible above finger towerPreview.snapToGrid(x, y - dragOffsetY); // Add immediate visual feedback with scaling animation tween(tower, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(tower, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeIn }); } }); } else { // Visual feedback for unaffordable towers tween(tower, { scaleX: 0.95, scaleY: 0.95, alpha: 0.7 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(tower, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 150, easing: tween.easeIn }); } }); var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } 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']; 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; // Initialize station customizer stationCustomizer = new StationCustomizer(); stationCustomizer.x = 0; stationCustomizer.y = 0; game.addChild(stationCustomizer); // Create station customization panel var stationPanel = new Container(); var stationPanelBg = stationPanel.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); stationPanelBg.width = 400; stationPanelBg.height = 100; stationPanelBg.tint = 0x2E7D32; stationPanelBg.alpha = 0.9; var stationTitleText = new Text2("Station", { size: 50, fill: 0xFFFFFF, weight: 800 }); stationTitleText.anchor.set(0.5, 0.5); stationTitleText.y = -15; stationPanel.addChild(stationTitleText); var customizeButton = new Container(); var customizeBg = customizeButton.attachAsset('heroAbilityIcon', { anchorX: 0.5, anchorY: 0.5 }); customizeBg.width = 80; customizeBg.height = 40; customizeBg.tint = 0x4CAF50; customizeButton.y = 20; var customizeText = new Text2("Customize", { size: 30, fill: 0xFFFFFF, weight: 600 }); customizeText.anchor.set(0.5, 0.5); customizeButton.addChild(customizeText); customizeButton.down = function () { stationCustomizer.toggleCustomizationMode(); }; stationPanel.addChild(customizeButton); stationPanel.x = 2048 - 200; // Position on right side stationPanel.y = 200; // Below the score display which is at y=50 game.addChild(stationPanel); // Create hero deployment panel var heroPanel = new Container(); var heroPanelBg = heroPanel.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); heroPanelBg.width = 800; heroPanelBg.height = 120; heroPanelBg.tint = 0x1A237E; heroPanelBg.alpha = 0.9; var heroTitleText = new Text2("Heroes (" + heroes.length + "/" + maxHeroes + ")", { size: 50, fill: 0xFFFFFF, weight: 800 }); heroTitleText.anchor.set(0.5, 0.5); heroTitleText.y = -30; heroPanel.addChild(heroTitleText); // Hero type buttons var heroTypes = ['tank', 'support', 'dps']; var heroTypeButtons = []; for (var i = 0; i < heroTypes.length; i++) { var heroButton = new Container(); var buttonBg = heroButton.attachAsset('heroAbilityIcon', { anchorX: 0.5, anchorY: 0.5 }); switch (heroTypes[i]) { case 'tank': buttonBg.tint = 0x4CAF50; break; case 'support': buttonBg.tint = 0x2196F3; break; case 'dps': buttonBg.tint = 0xFF5722; break; } buttonBg.width = 80; buttonBg.height = 80; heroButton.heroType = heroTypes[i]; heroButton.x = -240 + i * 120; heroButton.y = 10; heroButton.down = function () { if (heroes.length >= maxHeroes) { var notification = game.addChild(new Notification("Maximum heroes deployed!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } selectedHeroType = this.heroType; heroDeploymentMode = true; var notification = game.addChild(new Notification("Tap anywhere to deploy " + this.heroType + " hero")); notification.x = 2048 / 2; notification.y = grid.height - 50; }; heroPanel.addChild(heroButton); heroTypeButtons.push(heroButton); } heroPanel.x = 2048 / 2; heroPanel.y = 120; game.addChild(heroPanel); function updateHeroPanel() { heroTitleText.setText("Heroes (" + heroes.length + "/" + maxHeroes + ")"); } enemiesToSpawn = 10; // Initialize tutorial system for new players var tutorialSystem = new TutorialSystem(); game.addChild(tutorialSystem); // Start background music LK.playMusic('bgMusic'); // Show tutorial automatically for new players var hasSeenTutorial = storage.tutorialCompleted || false; if (!hasSeenTutorial) { tutorialSystem.startTutorial(); } // Add tutorial restart button for experienced players var tutorialRestartButton = new Container(); var restartBg = tutorialRestartButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); restartBg.width = 200; restartBg.height = 60; restartBg.tint = 0x4A90E2; restartBg.alpha = 0.9; var restartText = new Text2("Tutorial", { size: 35, fill: 0xFFFFFF, weight: 600 }); restartText.anchor.set(0.5, 0.5); tutorialRestartButton.addChild(restartText); tutorialRestartButton.x = 150; tutorialRestartButton.y = 2732 - 50; tutorialRestartButton.down = function () { if (!tutorialSystem.isActive) { tutorialSystem.startTutorial(); } }; game.addChild(tutorialRestartButton); game.update = function () { performanceMonitor.update(); // Clear and rebuild spatial grid each frame for enemies spatialGrid.clear(); for (var i = 0; i < enemies.length; i++) { spatialGrid.addEnemy(enemies[i]); } 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; // Play boss spawn sound LK.getSound('bossSpawn').play(); } // Spawn the appropriate number of enemies for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); // 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 more balanced scaling that starts linear and becomes logarithmic var healthMultiplier; if (currentWave <= 10) { // Linear scaling for early waves healthMultiplier = 1 + (currentWave - 1) * 0.15; // 15% per wave for first 10 waves } else if (currentWave <= 25) { // Moderate scaling for mid waves healthMultiplier = 2.35 + (currentWave - 10) * 0.08; // Start at 2.35x, add 8% per wave } else { // Logarithmic scaling for late waves to prevent exponential growth healthMultiplier = 3.55 + Math.log(currentWave - 24) * 0.5; // Logarithmic growth } 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; } } // Update heroes for (var i = heroes.length - 1; i >= 0; i--) { var hero = heroes[i]; if (hero.health <= 0) { // Hero defeated - remove from play but don't reset progression var notification = game.addChild(new Notification(hero.heroType.charAt(0).toUpperCase() + hero.heroType.slice(1) + " hero defeated!")); notification.x = 2048 / 2; notification.y = grid.height - 100; hero.destroy(); heroes.splice(i, 1); updateHeroPanel(); continue; } hero.update(); } for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; // Skip some enemy updates when performance is poor if (performanceMonitor.shouldSkipUpdate() && enemy.health > 0) { continue; } // Handle hero targeting for enemies if (enemy.heroTarget && enemy.heroTarget.parent && enemy.heroTarget.health > 0) { if (enemy.heroTargetDuration > 0) { enemy.heroTargetDuration--; // Move towards hero instead of following path var dx = enemy.heroTarget.x - enemy.x; var dy = enemy.heroTarget.y - enemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > CELL_SIZE * 0.5) { var angle = Math.atan2(dy, dx); enemy.x += Math.cos(angle) * enemy.speed * 30; enemy.y += Math.sin(angle) * enemy.speed * 30; } else { // Attack hero enemy.heroTarget.health -= 10; if (enemy.heroTarget.health <= 0) { enemy.heroTarget.health = 0; } } continue; // Skip normal pathfinding } else { enemy.heroTarget = null; enemy.heroTargetDuration = 0; } } else { enemy.heroTarget = null; enemy.heroTargetDuration = 0; } if (enemy.health <= 0) { // Improved gold rewards that scale better with difficulty var goldEarned; if (enemy.isBoss) { // Boss gold scales more reasonably: 25 base + 3 per wave goldEarned = Math.floor(25 + (enemy.waveNumber - 1) * 3); } else { // Regular enemy gold: better scaling for later waves if (enemy.waveNumber <= 10) { goldEarned = Math.floor(2 + (enemy.waveNumber - 1) * 0.3); // 2-4.7 gold for waves 1-10 } else if (enemy.waveNumber <= 25) { goldEarned = Math.floor(4.7 + (enemy.waveNumber - 10) * 0.4); // 4.7-10.7 gold for waves 11-25 } else { goldEarned = Math.floor(10.7 + (enemy.waveNumber - 25) * 0.2); // Slower growth for late waves } } // Bonus gold for enemies who took difficult routes if (enemy.chosenBranch && enemy.chosenBranch.difficulty > 50) { var routeBonus = Math.floor(goldEarned * 0.3); goldEarned += routeBonus; if (routeBonus > 0) { var notification = game.addChild(new Notification("Route bonus: +" + routeBonus + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 200; } } var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); // Give more score for defeating a boss var scoreValue = enemy.isBoss ? 100 : 5; score += scoreValue; // Add a notification for boss defeat if (enemy.isBoss) { var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } updateUI(); // Use proper destroy method for comprehensive cleanup enemy.destroy(); continue; } if (grid.updateEnemy(enemy)) { // Check if shields are active var shieldsActive = false; for (var s = 0; s < stationCustomizer.modules.length; s++) { var module = stationCustomizer.modules[s]; if (module.moduleType === 'shield' && module.shieldActive) { shieldsActive = true; break; } } if (shieldsActive) { // Shields absorb damage var notification = game.addChild(new Notification("Enemy blocked by shields!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else { lives = Math.max(0, lives - 1); updateUI(); if (lives <= 0) { // Play game over sound LK.getSound('gameOver').play(); LK.showGameOver(); } } // Use proper destroy method for comprehensive cleanup enemy.destroy(); } } 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); } } // Limit bullet count when performance drops if (performanceMonitor.performanceLevel < 0.7 && bullets.length > 50) { // Remove oldest bullets when performance is poor var bulletsToRemove = Math.min(10, bullets.length - 30); for (var i = 0; i < bulletsToRemove; i++) { var oldBullet = bullets[i]; if (oldBullet.targetEnemy) { var bulletIndex = oldBullet.targetEnemy.bulletsTargetingThis.indexOf(oldBullet); if (bulletIndex !== -1) { oldBullet.targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } returnBulletToPool(oldBullet); bullets.splice(i, 1); i--; // Adjust index after removal bulletsToRemove--; } } if (towerPreview.visible) { towerPreview.checkPlacement(); } // Update station customizer if (stationCustomizer) { stationCustomizer.update(); // Update module arrays for quick access researchLabs = stationCustomizer.modules.filter(function (m) { return m.moduleType === 'research'; }); shieldGenerators = stationCustomizer.modules.filter(function (m) { return m.moduleType === 'shield'; }); teleporterNodes = stationCustomizer.modules.filter(function (m) { return m.moduleType === 'teleporter'; }); // Link teleporter nodes for (var i = 0; i < teleporterNodes.length; i++) { teleporterNodes[i].linkedNodes = teleporterNodes.filter(function (node, index) { return index !== i; }); } } // Update tower specializations and support links for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.specialization && tower.specialization.update) { tower.specialization.update(); } } // Clean up destroyed specialization menus specializationMenus = specializationMenus.filter(function (menu) { return menu.parent && !menu.isDestroyed; }); // Environmental hazards management if (waveIndicator && waveIndicator.gameStarted) { hazardTimer++; if (hazardTimer >= nextHazardTime) { hazardTimer = 0; nextHazardTime = 1200 + Math.random() * 1200; // 20-40 seconds // Spawn random environmental hazard var hazardType = Math.floor(Math.random() * 4); switch (hazardType) { case 0: // Asteroid field if (asteroids.length < 3) { var validPositions = []; for (var x = 2; x < 22; x++) { for (var y = 6; y < 25; y++) { var cell = grid.getCell(x, y); if (cell && cell.type === 0) { validPositions.push({ x: x, y: y }); } } } if (validPositions.length > 0) { var pos = validPositions[Math.floor(Math.random() * validPositions.length)]; var asteroid = new Asteroid(grid.x + pos.x * CELL_SIZE + CELL_SIZE / 2, grid.y + pos.y * CELL_SIZE + CELL_SIZE / 2); game.addChild(asteroid); asteroids.push(asteroid); var notification = game.addChild(new Notification("⚠️ Asteroid field detected! ⚠️")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } break; case 1: // Nebula cloud if (nebulaClouds.length < 2) { var cloudX = grid.x + (5 + Math.random() * 14) * CELL_SIZE; var cloudY = grid.y + (8 + Math.random() * 12) * CELL_SIZE; var nebula = new NebulaCloud(cloudX, cloudY); game.addChild(nebula); nebulaClouds.push(nebula); var notification = game.addChild(new Notification("🌌 Nebula cloud formation! 🌌")); notification.x = 2048 / 2; notification.y = grid.height - 50; // Remove after 15 seconds LK.setTimeout(function () { if (nebula.parent) { nebula.destroy(); } }, 15000); } break; case 2: // Solar flare if (solarFlares.length === 0) { var flare = new SolarFlare(); game.addChild(flare); solarFlares.push(flare); } break; case 3: // Wormhole if (wormholes.length < 2) { // Create entry and exit points var entryX = grid.x + (3 + Math.random() * 6) * CELL_SIZE; var entryY = grid.y + (8 + Math.random() * 8) * CELL_SIZE; var exitX = grid.x + (15 + Math.random() * 6) * CELL_SIZE; var exitY = grid.y + (16 + Math.random() * 6) * CELL_SIZE; var wormhole = new Wormhole(entryX, entryY, exitX, exitY); game.addChild(wormhole); wormholes.push(wormhole); var notification = game.addChild(new Notification("🌀 Wormhole opened! 🌀")); notification.x = 2048 / 2; notification.y = grid.height - 50; // Remove after 20 seconds LK.setTimeout(function () { if (wormhole.parent) { wormhole.destroy(); } }, 20000); } break; } } } // Update path junctions and route analysis for (var i = 0; i < pathJunctions.length; i++) { if (pathJunctions[i].parent) { pathJunctions[i].update(); } } // Update environmental hazards for (var i = asteroids.length - 1; i >= 0; i--) { if (asteroids[i].parent) { // Asteroids can be damaged by bullets for (var j = 0; j < bullets.length; j++) { var bullet = bullets[j]; var dx = bullet.x - asteroids[i].x; var dy = bullet.y - asteroids[i].y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= 60) { // Hit asteroid asteroids[i].takeDamage(bullet.damage); returnBulletToPool(bullet); break; } } } } for (var i = 0; i < nebulaClouds.length; i++) { if (nebulaClouds[i].parent) { nebulaClouds[i].update(); } } for (var i = 0; i < solarFlares.length; i++) { if (solarFlares[i].parent) { solarFlares[i].update(); } } for (var i = 0; i < wormholes.length; i++) { if (wormholes[i].parent) { wormholes[i].update(); } } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { // Play victory sound LK.getSound('victory').play(); LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -1345,65 +1345,10 @@
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
// Update shadow position if this is a flying enemy
return false;
}
- // Handle normal pathfinding enemies with enhanced path system
+ // Handle normal pathfinding enemies
if (!enemy.currentTarget) {
- // Check current path type for special behavior
- var pathType = pathSystem ? pathSystem.getPathType(enemy.cellX, enemy.cellY) : 'normal';
- var pathDifficulty = pathSystem ? pathSystem.getPathDifficulty(enemy.cellX, enemy.cellY) : 1;
- // Apply path-specific effects
- switch (pathType) {
- case 'danger':
- // Dangerous paths make enemies more vulnerable
- if (!enemy.pathEffectApplied) {
- enemy.originalSpeed = enemy.originalSpeed || enemy.speed;
- enemy.speed *= 1.2; // 20% faster but exposed
- enemy.pathEffectApplied = true;
- // Visual danger effect
- if (enemy.children[0]) {
- tween(enemy.children[0], {
- tint: 0xff4444
- }, {
- duration: 200,
- easing: tween.easeOut
- });
- }
- }
- break;
- case 'safe':
- // Safe paths provide some protection
- if (!enemy.pathEffectApplied) {
- enemy.originalSpeed = enemy.originalSpeed || enemy.speed;
- enemy.speed *= 0.9; // 10% slower but safer
- enemy.pathEffectApplied = true;
- // Visual safety effect
- if (enemy.children[0]) {
- tween(enemy.children[0], {
- tint: 0x44ff44
- }, {
- duration: 200,
- easing: tween.easeOut
- });
- }
- }
- break;
- case 'bridge':
- // Bridge paths are elevated - flying enemies get speed boost
- if (enemy.isFlying && !enemy.pathEffectApplied) {
- enemy.originalSpeed = enemy.originalSpeed || enemy.speed;
- enemy.speed *= 1.3; // 30% faster on bridges
- enemy.pathEffectApplied = true;
- }
- break;
- case 'tunnel':
- // Tunnel paths hide enemies from some tower types
- if (!enemy.pathEffectApplied) {
- enemy.tunnelStealth = true; // Harder for towers to target
- enemy.pathEffectApplied = true;
- }
- break;
- }
// Check if enemy is at a junction
if (cell && cell.isJunction && !enemy.chosenBranch) {
var junction = cell.junction;
var chosenBranch = junction.choosePath(enemy);
@@ -2183,566 +2128,8 @@
Container.prototype.destroy.call(self);
};
return self;
});
-var PathSystem = Container.expand(function (grid) {
- var self = Container.call(this);
- self.grid = grid;
- self.pathSegments = [];
- self.visualElements = [];
- self.junctions = [];
- self.alternativeRoutes = [];
- self.isDestroyed = false;
- // Create main path layout with creative routing
- self.createPathLayout = function () {
- // Clear existing visuals
- self.clearPathVisuals();
- // Define the creative path structure
- var pathLayout = [
- // Entry area - funnel effect
- {
- type: 'straight',
- startX: 9,
- startY: 0,
- endX: 15,
- endY: 4,
- width: 7,
- difficulty: 1
- },
- // First major junction - choice between speed vs safety
- {
- type: 'junction',
- x: 12,
- y: 6,
- branches: [{
- name: 'speed',
- targetX: 8,
- targetY: 12,
- difficulty: 2,
- bonus: 'fast'
- }, {
- name: 'safety',
- targetX: 16,
- targetY: 12,
- difficulty: 1,
- bonus: 'safe'
- }]
- },
- // Speed route - narrow and dangerous but faster
- {
- type: 'narrow',
- startX: 6,
- startY: 8,
- endX: 8,
- endY: 16,
- width: 3,
- difficulty: 4
- },
- // Safety route - wider but longer with defensive positions
- {
- type: 'wide',
- startX: 14,
- startY: 8,
- endX: 18,
- endY: 16,
- width: 5,
- difficulty: 2
- },
- // Bridge section - elevated path over dangerous terrain
- {
- type: 'bridge',
- startX: 6,
- startY: 16,
- endX: 12,
- endY: 18,
- difficulty: 3
- },
- // Tunnel section - underground passage
- {
- type: 'tunnel',
- startX: 14,
- startY: 16,
- endX: 16,
- endY: 20,
- difficulty: 2
- },
- // Second junction - tactical choice
- {
- type: 'junction',
- x: 12,
- y: 20,
- branches: [{
- name: 'direct',
- targetX: 12,
- targetY: 26,
- difficulty: 3,
- bonus: 'direct'
- }, {
- name: 'flank',
- targetX: 8,
- targetY: 24,
- difficulty: 2,
- bonus: 'flank'
- }, {
- name: 'siege',
- targetX: 16,
- targetY: 24,
- difficulty: 4,
- bonus: 'siege'
- }]
- },
- // Final convergence - all paths meet before exit
- {
- type: 'convergence',
- startX: 8,
- startY: 24,
- endX: 16,
- endY: 28,
- width: 9,
- difficulty: 2
- }];
- // Build visual representation of paths
- for (var i = 0; i < pathLayout.length; i++) {
- self.buildPathSection(pathLayout[i]);
- }
- // Add decorative elements
- self.addPathDecorations();
- };
- self.buildPathSection = function (section) {
- switch (section.type) {
- case 'straight':
- self.createStraightPath(section);
- break;
- case 'junction':
- self.createJunctionPath(section);
- break;
- case 'narrow':
- self.createNarrowPath(section);
- break;
- case 'wide':
- self.createWidePath(section);
- break;
- case 'bridge':
- self.createBridgePath(section);
- break;
- case 'tunnel':
- self.createTunnelPath(section);
- break;
- case 'convergence':
- self.createConvergencePath(section);
- break;
- }
- };
- self.createStraightPath = function (section) {
- var deltaX = section.endX - section.startX;
- var deltaY = section.endY - section.startY;
- var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- var steps = Math.floor(distance);
- for (var i = 0; i <= steps; i++) {
- var progress = i / steps;
- var x = Math.round(section.startX + deltaX * progress);
- var y = Math.round(section.startY + deltaY * progress);
- // Create path segments with width
- for (var w = -Math.floor(section.width / 2); w <= Math.floor(section.width / 2); w++) {
- self.createPathSegment(x + w, y, 'straight', section.difficulty);
- }
- // Add directional arrows every few segments
- if (i % 3 === 0) {
- self.createPathArrow(x, y, deltaX, deltaY);
- }
- }
- };
- self.createJunctionPath = function (section) {
- // Create central junction node
- var junction = self.createJunctionNode(section.x, section.y, section.branches);
- self.junctions.push(junction);
- // Create branch indicators
- for (var i = 0; i < section.branches.length; i++) {
- var branch = section.branches[i];
- self.createBranchIndicator(section.x, section.y, branch);
- }
- };
- self.createNarrowPath = function (section) {
- var deltaX = section.endX - section.startX;
- var deltaY = section.endY - section.startY;
- var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- var steps = Math.floor(distance);
- for (var i = 0; i <= steps; i++) {
- var progress = i / steps;
- var x = Math.round(section.startX + deltaX * progress);
- var y = Math.round(section.startY + deltaY * progress);
- // Create narrow path (width 3) with danger indicators
- for (var w = -1; w <= 1; w++) {
- var segmentType = Math.abs(w) === 1 ? 'danger' : 'path';
- self.createPathSegment(x + w, y, segmentType, section.difficulty);
- }
- }
- };
- self.createWidePath = function (section) {
- var deltaX = section.endX - section.startX;
- var deltaY = section.endY - section.startY;
- var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- var steps = Math.floor(distance);
- for (var i = 0; i <= steps; i++) {
- var progress = i / steps;
- var x = Math.round(section.startX + deltaX * progress);
- var y = Math.round(section.startY + deltaY * progress);
- // Create wide path (width 5) with safe zones
- for (var w = -2; w <= 2; w++) {
- var segmentType = Math.abs(w) === 2 ? 'safe' : 'path';
- self.createPathSegment(x + w, y, segmentType, section.difficulty);
- }
- }
- };
- self.createBridgePath = function (section) {
- var deltaX = section.endX - section.startX;
- var deltaY = section.endY - section.startY;
- var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- var steps = Math.floor(distance);
- for (var i = 0; i <= steps; i++) {
- var progress = i / steps;
- var x = Math.round(section.startX + deltaX * progress);
- var y = Math.round(section.startY + deltaY * progress);
- // Create elevated bridge segments
- var bridge = self.createBridgeSegment(x, y, section.difficulty);
- self.visualElements.push(bridge);
- }
- };
- self.createTunnelPath = function (section) {
- var deltaX = section.endX - section.startX;
- var deltaY = section.endY - section.startY;
- var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- var steps = Math.floor(distance);
- for (var i = 0; i <= steps; i++) {
- var progress = i / steps;
- var x = Math.round(section.startX + deltaX * progress);
- var y = Math.round(section.startY + deltaY * progress);
- // Create underground tunnel segments
- var tunnel = self.createTunnelSegment(x, y, section.difficulty);
- self.visualElements.push(tunnel);
- }
- };
- self.createConvergencePath = function (section) {
- // Create funnel effect where all paths merge
- var centerX = (section.startX + section.endX) / 2;
- var centerY = (section.startY + section.endY) / 2;
- // Create converging segments from multiple directions
- var convergencePoints = [{
- x: section.startX,
- y: section.startY
- }, {
- x: section.startX + 2,
- y: section.startY
- }, {
- x: section.startX + 4,
- y: section.startY
- }, {
- x: section.endX - 4,
- y: section.startY
- }, {
- x: section.endX - 2,
- y: section.startY
- }, {
- x: section.endX,
- y: section.startY
- }];
- for (var i = 0; i < convergencePoints.length; i++) {
- var point = convergencePoints[i];
- var deltaX = centerX - point.x;
- var deltaY = centerY - point.y;
- var steps = Math.max(Math.abs(deltaX), Math.abs(deltaY));
- for (var j = 0; j <= steps; j++) {
- var progress = j / steps;
- var x = Math.round(point.x + deltaX * progress);
- var y = Math.round(point.y + deltaY * progress);
- self.createPathSegment(x, y, 'convergence', section.difficulty);
- }
- }
- // Create final exit path
- for (var y = Math.round(centerY); y <= section.endY; y++) {
- for (var x = section.startX; x <= section.endX; x++) {
- self.createPathSegment(x, y, 'exit', section.difficulty);
- }
- }
- };
- self.createPathSegment = function (gridX, gridY, segmentType, difficulty) {
- var cell = self.grid.getCell(gridX, gridY);
- if (!cell) return;
- // Set cell as walkable path
- cell.type = 0;
- cell.pathType = segmentType;
- cell.pathDifficulty = difficulty;
- // Create visual representation
- var segment = new Container();
- segment.x = self.grid.x + gridX * CELL_SIZE;
- segment.y = self.grid.y + gridY * CELL_SIZE;
- var assetName = 'pathSegment';
- var tint = 0x2a4d3a;
- switch (segmentType) {
- case 'danger':
- assetName = 'pathDanger';
- tint = 0x8b2635;
- break;
- case 'safe':
- assetName = 'pathSafe';
- tint = 0x4a6741;
- break;
- case 'bridge':
- assetName = 'pathBridge';
- tint = 0x3d5a47;
- break;
- case 'tunnel':
- assetName = 'pathTunnel';
- tint = 0x2d4235;
- break;
- case 'convergence':
- tint = 0x4a7c59;
- break;
- case 'exit':
- tint = 0x5c8a6b;
- break;
- }
- var segmentGraphics = segment.attachAsset(assetName, {
- anchorX: 0.5,
- anchorY: 0.5
- });
- segmentGraphics.tint = tint;
- segmentGraphics.alpha = 0.7;
- // Add difficulty-based visual effects
- if (difficulty >= 3) {
- // High difficulty - add warning pulse
- tween(segmentGraphics, {
- alpha: 0.4
- }, {
- duration: 800,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (!self.isDestroyed) {
- tween(segmentGraphics, {
- alpha: 0.7
- }, {
- duration: 800,
- easing: tween.easeInOut
- });
- }
- }
- });
- }
- self.addChild(segment);
- self.visualElements.push(segment);
- };
- self.createJunctionNode = function (gridX, gridY, branches) {
- var junction = new Container();
- junction.x = self.grid.x + gridX * CELL_SIZE;
- junction.y = self.grid.y + gridY * CELL_SIZE;
- var junctionGraphics = junction.attachAsset('pathJunctionNode', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- junctionGraphics.tint = 0x4a7c59;
- junctionGraphics.alpha = 0.8;
- // Pulsing effect for junctions
- tween(junctionGraphics, {
- scaleX: 1.2,
- scaleY: 1.2,
- alpha: 1.0
- }, {
- duration: 1000,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (!self.isDestroyed) {
- tween(junctionGraphics, {
- scaleX: 1.0,
- scaleY: 1.0,
- alpha: 0.8
- }, {
- duration: 1000,
- easing: tween.easeInOut
- });
- }
- }
- });
- // Add branch count indicator
- var branchCount = new Text2(branches.length.toString(), {
- size: 30,
- fill: 0xFFFFFF,
- weight: 800
- });
- branchCount.anchor.set(0.5, 0.5);
- junction.addChild(branchCount);
- self.addChild(junction);
- self.visualElements.push(junction);
- return junction;
- };
- self.createBranchIndicator = function (junctionX, junctionY, branch) {
- var indicator = new Container();
- var angle = Math.atan2(branch.targetY - junctionY, branch.targetX - junctionX);
- var distance = 80;
- indicator.x = self.grid.x + junctionX * CELL_SIZE + Math.cos(angle) * distance;
- indicator.y = self.grid.y + junctionY * CELL_SIZE + Math.sin(angle) * distance;
- var indicatorGraphics = indicator.attachAsset('pathArrow', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- indicatorGraphics.rotation = angle;
- // Color code by difficulty and bonus
- switch (branch.bonus) {
- case 'fast':
- indicatorGraphics.tint = 0x00aaff;
- break;
- case 'safe':
- indicatorGraphics.tint = 0x4a6741;
- break;
- case 'direct':
- indicatorGraphics.tint = 0xff6600;
- break;
- case 'flank':
- indicatorGraphics.tint = 0x9900ff;
- break;
- case 'siege':
- indicatorGraphics.tint = 0xff0000;
- break;
- default:
- indicatorGraphics.tint = 0x7da085;
- }
- self.addChild(indicator);
- self.visualElements.push(indicator);
- };
- self.createPathArrow = function (gridX, gridY, deltaX, deltaY) {
- var arrow = new Container();
- arrow.x = self.grid.x + gridX * CELL_SIZE;
- arrow.y = self.grid.y + gridY * CELL_SIZE;
- var arrowGraphics = arrow.attachAsset('pathArrow', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- arrowGraphics.rotation = Math.atan2(deltaY, deltaX);
- arrowGraphics.tint = 0x7da085;
- arrowGraphics.alpha = 0.6;
- arrowGraphics.width = 30;
- arrowGraphics.height = 30;
- self.addChild(arrow);
- self.visualElements.push(arrow);
- };
- self.createBridgeSegment = function (gridX, gridY, difficulty) {
- var bridge = new Container();
- bridge.x = self.grid.x + gridX * CELL_SIZE;
- bridge.y = self.grid.y + gridY * CELL_SIZE;
- var bridgeGraphics = bridge.attachAsset('pathBridge', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- bridgeGraphics.tint = 0x3d5a47;
- bridgeGraphics.alpha = 0.8;
- // Add elevation effect
- bridge.y -= 5; // Slightly elevated
- self.addChild(bridge);
- return bridge;
- };
- self.createTunnelSegment = function (gridX, gridY, difficulty) {
- var tunnel = new Container();
- tunnel.x = self.grid.x + gridX * CELL_SIZE;
- tunnel.y = self.grid.y + gridY * CELL_SIZE;
- var tunnelGraphics = tunnel.attachAsset('pathTunnel', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- tunnelGraphics.tint = 0x2d4235;
- tunnelGraphics.alpha = 0.6;
- // Add underground effect
- tunnel.y += 3; // Slightly below surface
- self.addChild(tunnel);
- return tunnel;
- };
- self.addPathDecorations = function () {
- // Add checkpoints along major path sections
- var checkpointPositions = [{
- x: 12,
- y: 8
- }, {
- x: 9,
- y: 14
- }, {
- x: 15,
- y: 14
- }, {
- x: 12,
- y: 22
- }];
- for (var i = 0; i < checkpointPositions.length; i++) {
- var pos = checkpointPositions[i];
- var checkpoint = new Container();
- checkpoint.x = self.grid.x + pos.x * CELL_SIZE;
- checkpoint.y = self.grid.y + pos.y * CELL_SIZE;
- var checkpointGraphics = checkpoint.attachAsset('pathCheckpoint', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- checkpointGraphics.tint = 0x5c8a6b;
- checkpointGraphics.alpha = 0.7;
- // Checkpoint pulse animation
- tween(checkpointGraphics, {
- scaleX: 1.3,
- scaleY: 1.3,
- alpha: 1.0
- }, {
- duration: 1500,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (!self.isDestroyed) {
- tween(checkpointGraphics, {
- scaleX: 1.0,
- scaleY: 1.0,
- alpha: 0.7
- }, {
- duration: 1500,
- easing: tween.easeInOut
- });
- }
- }
- });
- self.addChild(checkpoint);
- self.visualElements.push(checkpoint);
- }
- };
- self.getPathDifficulty = function (gridX, gridY) {
- var cell = self.grid.getCell(gridX, gridY);
- return cell && cell.pathDifficulty ? cell.pathDifficulty : 1;
- };
- self.getPathType = function (gridX, gridY) {
- var cell = self.grid.getCell(gridX, gridY);
- return cell && cell.pathType ? cell.pathType : 'normal';
- };
- self.clearPathVisuals = function () {
- for (var i = 0; i < self.visualElements.length; i++) {
- if (self.visualElements[i].parent) {
- self.visualElements[i].parent.removeChild(self.visualElements[i]);
- }
- }
- self.visualElements = [];
- };
- self.update = function () {
- // Update junction states and path conditions
- for (var i = 0; i < self.junctions.length; i++) {
- var junction = self.junctions[i];
- // Junction could show current traffic or difficulty
- }
- };
- self.destroy = function () {
- if (self.isDestroyed) return;
- self.isDestroyed = true;
- self.clearPathVisuals();
- self.pathSegments = [];
- self.junctions = [];
- self.alternativeRoutes = [];
- tween.stop(self);
- if (self.parent) {
- self.parent.removeChild(self);
- }
- Container.prototype.destroy.call(self);
- };
- return self;
-});
var RouteVisualizer = Container.expand(function () {
var self = Container.call(this);
self.pathLines = [];
self.isDestroyed = false;
@@ -3749,8 +3136,13 @@
}
});
// Method to update tower visual appearance based on level
self.updateTowerVisuals = function () {
+ // Ensure gunContainer exists before trying to use it
+ if (!gunContainer) {
+ console.warn("gunContainer not found, skipping visual update");
+ return;
+ }
// Clear existing gun graphics
if (self.gunGraphics && self.gunGraphics.parent) {
gunContainer.removeChild(self.gunGraphics);
}
@@ -3946,8 +3338,14 @@
self.supportStructures.push(specVisual);
};
// Initialize tower visuals
self.updateTowerVisuals();
+ var gunContainer = new Container();
+ self.addChild(gunContainer);
+ var gunGraphics = gunContainer.attachAsset('defense', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
var levelIndicators = [];
var maxDots = self.maxLevel;
var dotSpacing = baseGraphics.width / (maxDots + 1);
var dotSize = CELL_SIZE / 6;
@@ -3971,14 +3369,8 @@
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];
@@ -4195,18 +3587,12 @@
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if enemy is in range
if (distance <= self.getRange()) {
- // Apply stealth effects - nebula and tunnel stealth
- var targetingChance = 1.0;
- if (enemy.nebulaStealthed) {
- targetingChance *= 0.3; // Nebula stealth
- }
- if (enemy.tunnelStealth) {
- targetingChance *= 0.6; // Tunnel stealth - less severe than nebula
- }
+ // Apply nebula stealth effect - reduce targeting chance
+ var targetingChance = enemy.nebulaStealthed ? 0.3 : 1.0;
if (Math.random() > targetingChance) {
- continue; // Skip this enemy due to stealth effects
+ continue; // Skip this enemy due to stealth
}
// Handle flying enemies differently - they can be targeted regardless of path
if (enemy.isFlying) {
// For flying enemies, prioritize by distance to the goal
@@ -6956,12 +6342,8 @@
enemyLayer.addChild(enemyLayerTop);
var grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * 4;
-// Initialize creative path system
-var pathSystem = new PathSystem(grid);
-pathSystem.createPathLayout();
-debugLayer.addChild(pathSystem);
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
game.addChild(debugLayer);
@@ -7786,12 +7168,8 @@
break;
}
}
}
- // Update path system
- if (pathSystem && pathSystem.update) {
- pathSystem.update();
- }
// Update path junctions and route analysis
for (var i = 0; i < pathJunctions.length; i++) {
if (pathJunctions[i].parent) {
pathJunctions[i].update();
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
wormwhole for TD game towdown. In-Game asset. 2d. High contrast. No shadows
tank hero character space galaxy theme tower defense game. In-Game asset. 2d. High contrast. No shadows
support hero character space galaxy theme tower defense game. In-Game asset. 2d. High contrast. No shadows
poison tower space galaxy theme tower defense game, advanced base level tower. In-Game asset. 2d. High contrast. No shadows