User prompt
başlangıçta 130 altın ver
User prompt
snake in yılanı, assets deki yilan resmine benzesin
User prompt
snake in yılanı şuan mor görünüyor. bunun yerine gözleri olan yeşil renkte hareket eden gerçekçi bir yılan figürü yap
User prompt
snake in yılanı daha büyük olsun
User prompt
snape ateş ettiğinde bir yılan oluşsun ve düşmanların üzerinde dolaşarak ateş etsin. ağzından alev çıksın
User prompt
harry ateş ettiğinde düşmanlar ikiye bölünsün ve çoğalsın ama canları yarım güçte olsun daha kolay ölsünler
User prompt
harmony nin yavaşlatma gücü dolduğunda karakterin yanında ingilizce ‘ üzerine bas ve yavaşlat’ yazısı çıksın
User prompt
100 resmi ekranın tamamını kaplasın
User prompt
neden karakterler büyüdü eski hallerine getir
User prompt
resimleri netleştir 100 resmindeki oyundaki resimleri örnek al kendi yapay zekanı da kullan
User prompt
100 resmini kendi yapay zekanı kullanarak biraz daha netleştir bozmuşsun
User prompt
100 resmini ekranın tamamına kaplat
User prompt
100 resmini hogwartsundderattack resminden sonra göster. ona tıkladıktan sonra oyun başlasın
User prompt
neden görünmüyor 100 resmi
User prompt
100 resmi hagwardunderattack resminden sonra görünsün
User prompt
2. sırada 100 resmi görünsün
User prompt
100 resmine tıklayınca hogwartsunderataack resmine geç
User prompt
100 resmini kendi yapay zekanı kullanarak ekranın tamamına kapla görüntü kalitesini yükselt yazıları netleştir
User prompt
hogwartsunderattact resminden önce 100 resmi görünsün
User prompt
oyun ilk açıldığında 100 resmini göster
User prompt
oyunda ekranı karartmayı iptal et
User prompt
oyunda bir sorun var düzgün ilerlemiyor
User prompt
Please fix the bug: 'TypeError: undefined is not an object (evaluating 'self.fastWaveWarningShown')' in or related to this line: 'if (isFastWave && !self.fastWaveWarningShown) {' Line Number: 3930
User prompt
Before you go to fast wave, show a warning that the enemies that will come are very fast, you can change the position of the characters, and you have a slowing feature with hormony. After this warning, wait three seconds and the enemies will come.
User prompt
Please fix the bug: 'TypeError: undefined is not an object (evaluating 'self.fastWaveWarningShown')' in or related to this line: 'if (currentWave === 2 && !self.fastWaveWarningShown) {' Line Number: 3914
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var BackgroundFlame = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; var flameGraphics = self.attachAsset('flame', { anchorX: 0.5, anchorY: 1.0 }); // Random flame colors (orange to red) var flameColors = [0xff6b35, 0xff4500, 0xff6347, 0xdc143c, 0xb22222]; flameGraphics.tint = flameColors[Math.floor(Math.random() * flameColors.length)]; // Random size variation var sizeVariation = 0.5 + Math.random() * 0.8; // 0.5 to 1.3 scale flameGraphics.scaleX = sizeVariation; flameGraphics.scaleY = sizeVariation; // Start invisible self.alpha = 0; self.scaleX = 0.1; self.scaleY = 0.1; // Animate appearance var appearDuration = 800 + Math.random() * 600; // 800-1400ms tween(self, { alpha: 0.3 + Math.random() * 0.4, // 0.3 to 0.7 alpha scaleX: sizeVariation, scaleY: sizeVariation }, { duration: appearDuration, easing: tween.easeOut, onFinish: function onFinish() { // Flicker for a while self.startFlickering(); } }); self.startFlickering = function () { if (!self.parent) return; // Stop if destroyed // Random flicker duration var flickerTime = 2000 + Math.random() * 3000; // 2-5 seconds var flickerInterval = 80 + Math.random() * 120; // Flicker every 80-200ms var flickerTimer = LK.setInterval(function () { if (!self.parent) { LK.clearInterval(flickerTimer); return; } // Quick flicker animation var currentAlpha = self.alpha; var flickerAlpha = Math.max(0.1, currentAlpha - 0.15 + Math.random() * 0.3); tween(self, { alpha: flickerAlpha }, { duration: 60, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent) return; tween(self, { alpha: currentAlpha }, { duration: 60, easing: tween.easeInOut }); } }); }, flickerInterval); // Start disappearing after flicker time LK.setTimeout(function () { if (!self.parent) return; LK.clearInterval(flickerTimer); self.startDisappearing(); }, flickerTime); }; self.startDisappearing = function () { if (!self.parent) return; // Stop if destroyed var disappearDuration = 600 + Math.random() * 400; // 600-1000ms tween(self, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: disappearDuration, easing: tween.easeIn, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); }; return self; }); var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) { var self = Container.call(this); self.targetEnemy = targetEnemy; self.damage = damage || 10; self.speed = speed || 5; self.x = startX; self.y = startY; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); // Store reference for customization self.bulletGraphics = bulletGraphics; self.update = function () { if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { // Apply damage to target enemy self.targetEnemy.health -= self.damage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } else { self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; } // If this is a sniper (Harry) bullet and enemy survives, split it if (self.type === 'sniper' && self.targetEnemy.health > 0 && !self.targetEnemy.hasBeenSplit) { // Mark this enemy as split to prevent infinite splitting self.targetEnemy.hasBeenSplit = true; // Create two new enemies with half the remaining health var splitHealth = self.targetEnemy.health / 2; for (var splitIndex = 0; splitIndex < 2; splitIndex++) { var newEnemy = new Enemy(self.targetEnemy.type); // Copy properties from original enemy newEnemy.maxHealth = splitHealth; newEnemy.health = splitHealth; newEnemy.speed = self.targetEnemy.speed; newEnemy.hasBeenSplit = true; // Prevent further splitting newEnemy.waveNumber = self.targetEnemy.waveNumber; // Copy status effects if (self.targetEnemy.slowed) { newEnemy.slowed = true; newEnemy.slowDuration = self.targetEnemy.slowDuration; newEnemy.originalSpeed = self.targetEnemy.originalSpeed; newEnemy.speed = self.targetEnemy.speed; } if (self.targetEnemy.poisoned) { newEnemy.poisoned = true; newEnemy.poisonDuration = self.targetEnemy.poisonDuration; newEnemy.poisonDamage = self.targetEnemy.poisonDamage; } // Position the new enemies slightly offset from original var offsetX = splitIndex === 0 ? -0.5 : 0.5; var offsetY = (Math.random() - 0.5) * 0.5; newEnemy.cellX = self.targetEnemy.cellX; newEnemy.cellY = self.targetEnemy.cellY; newEnemy.currentCellX = self.targetEnemy.currentCellX + offsetX; newEnemy.currentCellY = self.targetEnemy.currentCellY + offsetY; newEnemy.x = grid.x + newEnemy.currentCellX * CELL_SIZE; newEnemy.y = grid.y + newEnemy.currentCellY * CELL_SIZE; // Copy flying target if applicable if (self.targetEnemy.isFlying && self.targetEnemy.flyingTarget) { newEnemy.flyingTarget = self.targetEnemy.flyingTarget; } // Add to appropriate layer if (newEnemy.isFlying) { enemyLayerTop.addChild(newEnemy); if (newEnemy.shadow) { enemyLayerMiddle.addChild(newEnemy.shadow); } } else { enemyLayerBottom.addChild(newEnemy); } // Add to enemies array enemies.push(newEnemy); // Update health bar newEnemy.healthBar.width = newEnemy.health / newEnemy.maxHealth * 70; } // Remove the original enemy self.targetEnemy.health = 0; } // Apply special effects based on bullet type if (self.type === 'splash') { // Create visual splash effect var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); game.addChild(splashEffect); // Splash damage to nearby enemies var splashRadius = CELL_SIZE * 1.5; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self.targetEnemy) { var splashDx = otherEnemy.x - self.targetEnemy.x; var splashDy = otherEnemy.y - self.targetEnemy.y; var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy); if (splashDistance <= splashRadius) { // Apply splash damage (50% of original damage) otherEnemy.health -= self.damage * 0.5; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } else { otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70; } } } } } else if (self.type === 'slow') { // Prevent slow effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual slow effect var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); // Apply slow effect // Make slow percentage scale with tower level (default 50%, up to 80% at max level) var slowPct = 0.5; if (self.sourceTowerLevel !== undefined) { // Scale: 50% at level 1, 60% at 2, 65% at 3, 70% at 4, 75% at 5, 80% at 6 var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8]; var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1)); slowPct = slowLevels[idx]; } if (!self.targetEnemy.slowed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; self.targetEnemy.speed *= 1 - slowPct; // Slow by X% self.targetEnemy.slowed = true; self.targetEnemy.slowDuration = 180; // 3 seconds at 60 FPS } else { self.targetEnemy.slowDuration = 180; // Reset duration } } } else if (self.type === 'poison') { // Prevent poison effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual poison effect var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Apply poison effect self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS } } else if (self.type === 'sniper') { // Create visual critical hit effect for sniper var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper'); game.addChild(sniperEffect); } self.destroy(); } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); var DebugCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = self.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5 }); // Remove the random tint to show the stone texture properly cellGraphics.tint = 0xFFFFFF; cellGraphics.alpha = 0.8; var debugArrows = []; var numberLabel = new Text2('0', { size: 30, fill: 0xFFFFFF, weight: 800 }); numberLabel.anchor.set(.5, .5); numberLabel.visible = false; 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("-"); // Dark reddish stone for non-path cells cellGraphics.tint = 0x8B4513; cellGraphics.alpha = 0.5; return; } numberLabel.visible = false; 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; cellGraphics.alpha = 0.6; } else { // Apply subtle tinting to the stone texture based on path score var pathTint = 0x88 - tint << 8 | tint; // Blend the tint with the stone texture color cellGraphics.tint = 0xD2B48C; // Light tan/stone color cellGraphics.alpha = 0.7 + tint / 0x88 * 0.3; // Vary alpha based on path score } // Arrows removed - no visual indicators break; } case 1: { self.removeArrows(); // Darker stone for walls cellGraphics.tint = 0x654321; cellGraphics.alpha = 0.9; numberLabel.visible = false; break; } case 3: { self.removeArrows(); // Goal cells with greenish stone tint cellGraphics.tint = 0x8FBC8F; cellGraphics.alpha = 0.8; numberLabel.visible = false; break; } } // Number labels removed }; }); // This update method was incorrectly placed here and should be removed var EffectIndicator = Container.expand(function (x, y, type) { var self = Container.call(this); self.x = x; self.y = y; var effectGraphics = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.blendMode = 1; switch (type) { case 'splash': effectGraphics.tint = 0x33CC00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5; break; case 'slow': effectGraphics.tint = 0x9900FF; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'poison': effectGraphics.tint = 0x00FFAA; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'sniper': effectGraphics.tint = 0xFF5500; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; } effectGraphics.alpha = 0.7; self.alpha = 0; // Animate the effect tween(self, { alpha: 0.8, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); // Base enemy class for common functionality var Enemy = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'normal'; self.speed = .01; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 100; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.isFlying = false; self.isImmune = false; self.isBoss = false; // Check if this is a boss wave // Check if this is a boss wave // Apply different stats based on enemy type switch (self.type) { case 'fast': self.speed *= 6; // Three times as fast (was 2x, now 6x total) self.maxHealth = 50; // Half health (was 100, now 50) 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; } // Make all enemies 25% faster self.speed = self.speed * 1.25; 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 }); self.enemyGraphics = enemyGraphics; // Store reference for walking animation // Scale up boss enemies if (self.isBoss) { enemyGraphics.scaleX = 1.8; enemyGraphics.scaleY = 1.8; } // Dark wizard enemies with magical effects // Different wizard types with distinct magical auras /*switch (self.type) { case 'fast': enemyGraphics.tint = 0x3742fa; // Blue aura for fast dark wizards break; case 'immune': enemyGraphics.tint = 0xff3838; // Red aura for immune dark wizards break; case 'flying': enemyGraphics.tint = 0xf1c40f; // Golden aura for broom-riding wizards break; case 'swarm': enemyGraphics.tint = 0x8c7ae6; // Purple aura for swarm dark wizards break; }*/ // Create shadow for flying enemies if (self.isFlying) { // Create a shadow container that will be added to the shadow layer self.shadow = new Container(); // Clone the enemy graphics for the shadow var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow effect shadowGraphics.tint = 0x000000; // Black shadow shadowGraphics.alpha = 0.4; // Semi-transparent // If this is a boss, scale up the shadow to match if (self.isBoss) { shadowGraphics.scaleX = 1.8; shadowGraphics.scaleY = 1.8; } // Position shadow slightly offset self.shadow.x = 20; // Offset right self.shadow.y = 20; // Offset down // Ensure shadow has the same rotation as the enemy shadowGraphics.rotation = enemyGraphics.rotation; } var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBarBG = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; self.update = function () { if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; } // Handle slow effect if (self.isImmune) { // Immune enemies cannot be slowed or poisoned, clear any such effects self.slowed = false; self.slowEffect = false; self.poisoned = false; self.poisonEffect = false; // Reset speed to original if needed if (self.originalSpeed !== undefined) { self.speed = self.originalSpeed; } } else { // Handle slow effect if (self.slowed) { // Visual indication of slowed status if (!self.slowEffect) { self.slowEffect = true; } self.slowDuration--; if (self.slowDuration <= 0) { self.speed = self.originalSpeed; self.slowed = false; self.slowEffect = false; // Only reset tint if not poisoned if (!self.poisoned) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } // Handle poison effect if (self.poisoned) { // Visual indication of poisoned status if (!self.poisonEffect) { self.poisonEffect = true; } // Apply poison damage every 30 frames (twice per second) if (LK.ticks % 30 === 0) { self.health -= self.poisonDamage; if (self.health <= 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; } self.poisonDuration--; if (self.poisonDuration <= 0) { self.poisoned = false; self.poisonEffect = false; // Only reset tint if not slowed if (!self.slowed) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } } // Set tint based on effect status if (self.isImmune) { enemyGraphics.tint = 0xFFFFFF; } else if (self.poisoned && self.slowed) { // Combine poison (0x00FFAA) and slow (0x9900FF) colors // Simple average: R: (0+153)/2=76, G: (255+0)/2=127, B: (170+255)/2=212 enemyGraphics.tint = 0x4C7FD4; } else if (self.poisoned) { enemyGraphics.tint = 0x00FFAA; } else if (self.slowed) { enemyGraphics.tint = 0x9900FF; } else { enemyGraphics.tint = 0xFFFFFF; } // Add walking animation effect if (!self.walkingAnimation && self.speed > 0) { // Create a subtle bobbing effect for walking var _createWalkingAnimation = function createWalkingAnimation() { if (!self.parent) return; // Stop if enemy is destroyed // Adjust animation speed based on enemy speed var animDuration = self.slowed ? 400 : 200; if (self.type === 'fast') animDuration = 100; // Vertical bobbing motion tween(enemyGraphics, { y: -5 }, { duration: animDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent) return; tween(enemyGraphics, { y: 0 }, { duration: animDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent) return; _createWalkingAnimation(); // Continue the animation loop } }); } }); }; self.walkingAnimation = true; _createWalkingAnimation(); } if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; if (ox !== 0 || oy !== 0) { var angle = Math.atan2(oy, ox); if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) { tween.stop(enemyGraphics, { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemyGraphics.rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemyGraphics.targetRotation = angle; tween(enemyGraphics, { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } } } healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; }; return self; }); var GoldIndicator = Container.expand(function (value, x, y) { var self = Container.call(this); var shadowText = new Text2("+" + value, { size: 45, fill: 0x000000, weight: 800 }); shadowText.anchor.set(0.5, 0.5); shadowText.x = 2; shadowText.y = 2; self.addChild(shadowText); var goldText = new Text2("+" + value, { size: 45, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); self.x = x; self.y = y; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2, y: y - 40 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: y - 80 }, { duration: 600, easing: tween.easeIn, delay: 800, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; self.spawns = []; self.goals = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { score: 0, pathId: 0, towersInRange: [] }; } } /* Cell Types 0: Transparent floor 1: Wall 2: Spawn 3: Goal */ for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0; if (i > 11 - 3 && i <= 11 + 3) { if (j === 0) { cellType = 2; self.spawns.push(cell); } else if (j <= 4) { cellType = 0; } else if (j === gridHeight - 1) { cellType = 3; self.goals.push(cell); } else if (j >= gridHeight - 4) { cellType = 0; } } cell.type = cellType; cell.x = i; cell.y = j; cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1]; cell.up = self.cells[i - 1] && self.cells[i - 1][j]; cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1]; cell.left = self.cells[i][j - 1]; cell.right = self.cells[i][j + 1]; cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1]; cell.down = self.cells[i + 1] && self.cells[i + 1][j]; cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1]; cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left]; cell.targets = []; if (j > 3 && j <= gridHeight - 4) { var debugCell = new DebugCell(); self.addChild(debugCell); debugCell.cell = cell; debugCell.x = i * CELL_SIZE; debugCell.y = j * CELL_SIZE; cell.debugCell = debugCell; } } } self.getCell = function (x, y) { return self.cells[x] && self.cells[x][y]; }; self.pathFind = function () { var before = new Date().getTime(); var toProcess = self.goals.concat([]); maxScore = 0; pathId += 1; for (var a = 0; a < toProcess.length; a++) { toProcess[a].pathId = pathId; } function processNode(node, targetValue, targetNode) { if (node && node.type != 1) { if (node.pathId < pathId || targetValue < node.score) { node.targets = [targetNode]; } else if (node.pathId == pathId && targetValue == node.score) { node.targets.push(targetNode); } if (node.pathId < pathId || targetValue < node.score) { node.score = targetValue; if (node.pathId != pathId) { toProcess.push(node); } node.pathId = pathId; if (targetValue > maxScore) { maxScore = targetValue; } } } } while (toProcess.length) { var nodes = toProcess; toProcess = []; for (var a = 0; a < nodes.length; a++) { var node = nodes[a]; var targetScore = node.score + 14142; if (node.up && node.left && node.up.type != 1 && node.left.type != 1) { processNode(node.upLeft, targetScore, node); } if (node.up && node.right && node.up.type != 1 && node.right.type != 1) { processNode(node.upRight, targetScore, node); } if (node.down && node.right && node.down.type != 1 && node.right.type != 1) { processNode(node.downRight, targetScore, node); } if (node.down && node.left && node.down.type != 1 && node.left.type != 1) { processNode(node.downLeft, targetScore, node); } targetScore = node.score + 10000; processNode(node.up, targetScore, node); processNode(node.right, targetScore, node); processNode(node.down, targetScore, node); processNode(node.left, targetScore, node); } } for (var a = 0; a < self.spawns.length; a++) { if (self.spawns[a].pathId != pathId) { console.warn("Spawn blocked"); return true; } } for (var a = 0; a < enemies.length; a++) { var enemy = enemies[a]; // Skip enemies that haven't entered the viewable area yet if (enemy.currentCellY < 4) { continue; } // Skip flying enemies from path check as they can fly over obstacles if (enemy.isFlying) { continue; } var target = self.getCell(enemy.cellX, enemy.cellY); if (enemy.currentTarget) { if (enemy.currentTarget.pathId != pathId) { if (!target || target.pathId != pathId) { console.warn("Enemy blocked 1 "); return true; } } } else if (!target || target.pathId != pathId) { console.warn("Enemy blocked 2"); return true; } } console.log("Speed", new Date().getTime() - before); }; self.renderDebug = function () { for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var debugCell = self.cells[i][j].debugCell; if (debugCell) { debugCell.render(self.cells[i][j]); } } } }; self.updateEnemy = function (enemy) { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell.type == 3) { return true; } if (enemy.isFlying && enemy.shadow) { enemy.shadow.x = enemy.x + 20; // Match enemy x-position + offset enemy.shadow.y = enemy.y + 20; // Match enemy y-position + offset // Match shadow rotation with enemy rotation if (enemy.children[0] && enemy.shadow.children[0]) { enemy.shadow.children[0].rotation = enemy.children[0].rotation; } } // Check if the enemy has reached the entry area (y position is at least 5) var hasReachedEntryArea = enemy.currentCellY >= 4; // If enemy hasn't reached the entry area yet, just move down vertically if (!hasReachedEntryArea) { // Move directly downward enemy.currentCellY += enemy.speed; // Rotate enemy graphic to face downward (PI/2 radians = 90 degrees) var angle = Math.PI / 2; if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update enemy's position enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // If enemy has now reached the entry area, update cell coordinates if (enemy.currentCellY >= 4) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); } return false; } // After reaching entry area, handle flying enemies differently if (enemy.isFlying) { // Flying enemies head straight to the closest goal if (!enemy.flyingTarget) { // Set flying target to the closest goal enemy.flyingTarget = self.goals[0]; // Find closest goal if there are multiple if (self.goals.length > 1) { var closestDist = Infinity; for (var i = 0; i < self.goals.length; i++) { var goal = self.goals[i]; var dx = goal.x - enemy.cellX; var dy = goal.y - enemy.cellY; var dist = dx * dx + dy * dy; if (dist < closestDist) { closestDist = dist; enemy.flyingTarget = goal; } } } } // Move directly toward the goal var ox = enemy.flyingTarget.x - enemy.currentCellX; var oy = enemy.flyingTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { // Reached the goal return true; } var angle = Math.atan2(oy, ox); // Rotate enemy graphic to match movement direction if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update the cell position to track where the flying enemy is enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // Update shadow position if this is a flying enemy return false; } // Handle normal pathfinding enemies if (!enemy.currentTarget) { enemy.currentTarget = cell.targets[0]; } if (enemy.currentTarget) { if (cell.score < enemy.currentTarget.score) { enemy.currentTarget = cell; } var ox = enemy.currentTarget.x - enemy.currentCellX; var oy = enemy.currentTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentTarget = undefined; return; } var angle = Math.atan2(oy, ox); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; }; }); var NextWaveButton = Container.expand(function () { var self = Container.call(this); var buttonBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 300; buttonBackground.height = 100; buttonBackground.tint = 0x0088FF; var buttonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.enabled = false; self.visible = false; self.update = function () { if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) { self.enabled = true; self.visible = true; buttonBackground.tint = 0x0088FF; self.alpha = 1; } else { self.enabled = false; self.visible = false; buttonBackground.tint = 0x888888; self.alpha = 0.7; } }; self.down = function () { if (!self.enabled) { return; } if (waveIndicator.gameStarted && currentWave < totalWaves) { currentWave++; // Increment to the next wave directly waveTimer = 0; // Reset wave timer waveInProgress = true; waveSpawned = false; // Get the type of the current wave (which is now the next wave) var waveType = waveIndicator.getWaveTypeName(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 30; self.addChild(notificationText); self.alpha = 1; self.fadeOutTime = 120; // Default fade out time self.update = function () { if (self.fadeOutTime > 0) { self.fadeOutTime--; self.alpha = Math.min(self.fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var SmokeParticle = Container.expand(function (startX, startY, targetX, targetY, color) { var self = Container.call(this); self.x = startX; self.y = startY; // Create multiple smoke tendrils for realistic wispy smoke var smokeTendrils = []; var numTendrils = 3 + Math.floor(Math.random() * 3); // 3-5 tendrils per smoke for (var i = 0; i < numTendrils; i++) { var tendrilContainer = new Container(); self.addChild(tendrilContainer); // Create elongated smoke shapes using multiple stretched circles var numSegments = 4 + Math.floor(Math.random() * 3); // 4-6 segments per tendril var segmentGraphics = []; for (var j = 0; j < numSegments; j++) { var segment = tendrilContainer.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); // Create elongated, thin smoke shapes var baseWidth = 80 + Math.random() * 120 - j * 15; // Tapers towards end var baseHeight = 250 + Math.random() * 350 + j * 50; // Gets longer segment.width = baseWidth; segment.height = baseHeight; // Position segments to create continuous tendril segment.y = -j * (baseHeight * 0.3); // Overlap segments segment.x = (Math.random() - 0.5) * 30 * j; // Slight sideways drift // Dark, horror-themed smoke colors var smokeType = Math.random(); if (smokeType < 0.4) { // Very dark grey-black smoke (40% chance) var darkness = 0x08 + Math.floor(Math.random() * 0x18); segment.tint = darkness << 16 | darkness << 8 | darkness; } else if (smokeType < 0.6) { // Dark purple-black smoke for horror effect (20% chance) var purple = 0x20 + Math.floor(Math.random() * 0x20); var darkness = 0x10 + Math.floor(Math.random() * 0x10); segment.tint = purple << 16 | darkness << 8 | purple; } else if (smokeType < 0.8) { // Dark red-black smoke (20% chance) var red = 0x30 + Math.floor(Math.random() * 0x20); var darkness = 0x08 + Math.floor(Math.random() * 0x10); segment.tint = red << 16 | darkness << 8 | darkness; } else { // Ashy grey smoke (20% chance) var grayValue = 0x30 + Math.floor(Math.random() * 0x30); segment.tint = grayValue << 16 | grayValue << 8 | grayValue; } // Vary alpha for depth - more transparent at the edges segment.alpha = 0.4 - j * 0.08 - Math.abs(i - numTendrils / 2) * 0.1; segment.blendMode = 1; // Additive blend for ethereal effect segmentGraphics.push(segment); } // Random rotation for each tendril tendrilContainer.rotation = (Math.random() - 0.5) * Math.PI * 0.4; // Offset tendrils for natural spread tendrilContainer.x = (i - numTendrils / 2) * 60 + (Math.random() - 0.5) * 40; tendrilContainer.y = (Math.random() - 0.5) * 100; smokeTendrils.push({ container: tendrilContainer, segments: segmentGraphics, baseRotation: tendrilContainer.rotation, swaySpeed: 0.02 + Math.random() * 0.03, swayAmount: 0.1 + Math.random() * 0.2 }); } // Add dark wisps occasionally for extra horror effect if (Math.random() < 0.4) { var wispLayer = new Container(); self.addChild(wispLayer); var wisp = wispLayer.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); wisp.width = 40 + Math.random() * 60; wisp.height = 200 + Math.random() * 300; // Very dark wisp wisp.tint = 0x000000; wisp.alpha = 0.3; wisp.blendMode = 1; wispLayer.x = (Math.random() - 0.5) * 150; wispLayer.y = -100 - Math.random() * 200; wispLayer.rotation = (Math.random() - 0.5) * Math.PI * 0.6; } // Start with 0 alpha and small scale self.alpha = 0; self.scaleX = 0.3; self.scaleY = 0.2; // Calculate movement direction with creeping motion var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); // Add strong upward bias and sideways drift for smoke var windDrift = (Math.random() - 0.5) * 400; var upwardForce = -150 - Math.random() * 100; // Strong upward movement // Animate appearance with slow, creeping emergence tween(self, { alpha: 0.85, scaleX: 0.8, scaleY: 1.2, x: startX + dx * 0.1 + windDrift * 0.2, y: startY + dy * 0.1 + upwardForce * 0.3 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // Continue creeping and growing tween(self, { alpha: 0.6, scaleX: 1.2, scaleY: 1.6, x: startX + dx * 0.3 + windDrift * 0.6, y: startY + dy * 0.3 + upwardForce * 0.7 }, { duration: 2500, easing: tween.linear, onFinish: function onFinish() { // Final dissipation tween(self, { alpha: 0, scaleX: 1.5, scaleY: 2.0, x: startX + dx * 0.5 + windDrift, y: startY + dy * 0.5 + upwardForce * 1.2 }, { duration: 1500, easing: tween.easeIn, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); } }); } }); // Animate individual tendrils for undulating, creepy movement self.animationTime = 0; self.update = function () { if (!self.parent) return; self.animationTime += 0.016; // Assuming 60 FPS // Animate each tendril with swaying motion for (var i = 0; i < smokeTendrils.length; i++) { var tendril = smokeTendrils[i]; // Sinusoidal swaying motion var swayAngle = Math.sin(self.animationTime * tendril.swaySpeed + i) * tendril.swayAmount; tendril.container.rotation = tendril.baseRotation + swayAngle; // Animate individual segments for more fluid motion for (var j = 0; j < tendril.segments.length; j++) { var segment = tendril.segments[j]; // Add slight pulsing to segments var pulseFactor = 1 + Math.sin(self.animationTime * 0.5 + j * 0.3) * 0.05; segment.scaleX = pulseFactor; // Slight vertical bobbing segment.y = -j * (segment.height * 0.3) + Math.sin(self.animationTime + j * 0.5) * 5; } } }; return self; }); var Snake = Container.expand(function (startX, startY, tower) { var self = Container.call(this); self.x = startX; self.y = startY; self.tower = tower; self.speed = 3; self.damage = 15; self.fireRate = 30; // Fire every 0.5 seconds self.lastFired = 0; self.lifeTime = 300; // 5 seconds at 60 FPS self.currentLifeTime = 0; // Create snake body segments using yilan image self.segments = []; var numSegments = 8; // More segments for smoother snake for (var i = 0; i < numSegments; i++) { var segment = new Container(); // Create snake segment using yilan image asset var segmentGraphics = segment.attachAsset('yilan', { anchorX: 0.5, anchorY: 0.5 }); // Make segments progressively smaller - increased base size var segmentSize = 90 - i * 8; // Larger, more gradual taper segmentGraphics.width = segmentSize; segmentGraphics.height = segmentSize; // Keep the natural color of the yilan image segmentGraphics.tint = 0xFFFFFF; // No tint to preserve original colors segmentGraphics.alpha = 0.9; segment.x = -i * 20; // Tighter spacing for realistic movement segment.y = 0; self.addChild(segment); self.segments.push(segment); } // Create snake head with larger yilan image var head = self.segments[0]; // Remove the old head graphics first head.removeChildAt(0); // Main head shape (larger) using yilan image var headGraphics = head.attachAsset('yilan', { anchorX: 0.5, anchorY: 0.5 }); headGraphics.width = 110; // Larger head headGraphics.height = 110; // Keep square aspect ratio for image headGraphics.tint = 0xFFFFFF; // No tint to preserve original image // The yilan image already has eyes and details, so we don't need to add them // Movement pattern variables self.moveAngle = Math.random() * Math.PI * 2; self.waveAmplitude = 30; self.waveFrequency = 0.1; self.time = 0; self.findTarget = function () { var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } return closestEnemy; }; self.shootFire = function (targetEnemy) { if (!targetEnemy || !targetEnemy.parent) return; // Create fire projectile from snake's mouth var fireX = self.x + self.segments[0].x; var fireY = self.y + self.segments[0].y; // Create flame bullet var flame = new Bullet(fireX, fireY, targetEnemy, self.damage, 8); flame.type = 'snake_fire'; // Customize flame appearance flame.bulletGraphics.tint = 0xFF4500; // Orange-red flame flame.bulletGraphics.width = 40; // Increased from 25 flame.bulletGraphics.height = 40; // Increased from 25 // Add flame effect tween(flame.bulletGraphics, { scaleX: 1.2, scaleY: 1.2, alpha: 0.7 }, { duration: 200, easing: tween.easeInOut, loop: true, yoyo: true }); game.addChild(flame); bullets.push(flame); targetEnemy.bulletsTargetingThis.push(flame); }; self.update = function () { self.currentLifeTime++; self.time++; // Destroy snake after lifetime expires if (self.currentLifeTime >= self.lifeTime) { self.destroy(); return; } // Find nearest enemy var target = self.findTarget(); if (target) { // Move towards target with snake-like motion var dx = target.x - self.x; var dy = target.y - self.y; var targetAngle = Math.atan2(dy, dx); // Smooth angle transition var angleDiff = targetAngle - self.moveAngle; while (angleDiff > Math.PI) angleDiff -= Math.PI * 2; while (angleDiff < -Math.PI) angleDiff += Math.PI * 2; self.moveAngle += angleDiff * 0.1; // Move with sinusoidal pattern var sineOffset = Math.sin(self.time * self.waveFrequency) * self.waveAmplitude; self.x += Math.cos(self.moveAngle) * self.speed; self.y += Math.sin(self.moveAngle) * self.speed; // Apply perpendicular offset for snake-like movement var perpAngle = self.moveAngle + Math.PI / 2; self.x += Math.cos(perpAngle) * sineOffset * 0.02; self.y += Math.sin(perpAngle) * sineOffset * 0.02; // Fire at enemies if (LK.ticks - self.lastFired >= self.fireRate) { self.shootFire(target); self.lastFired = LK.ticks; } } else { // Wander if no target self.x += Math.cos(self.moveAngle) * self.speed * 0.5; self.y += Math.sin(self.moveAngle) * self.speed * 0.5; // Random direction changes if (Math.random() < 0.05) { self.moveAngle += (Math.random() - 0.5) * Math.PI * 0.5; } } // Update segment positions to follow head with snake-like motion for (var i = 1; i < self.segments.length; i++) { var prevSegment = self.segments[i - 1]; var segment = self.segments[i]; // Smooth following with delay - more fluid for realistic motion var followSpeed = 0.25 - i * 0.02; segment.x += (prevSegment.x - segment.x - 20) * followSpeed; segment.y += (prevSegment.y - segment.y) * followSpeed; // Add more complex oscillation for realistic slithering var oscillation = Math.sin(self.time * 0.15 + i * 0.6) * (4 - i * 0.3); var perpAngle = self.moveAngle + Math.PI / 2; segment.x += Math.cos(perpAngle) * oscillation; segment.y += Math.sin(perpAngle) * oscillation; // Rotate segments to face movement direction if (i === 0 && self.segments[0].children[0]) { // Head should face movement direction self.segments[0].children[0].rotation = self.moveAngle; } else if (segment.children[0] && prevSegment) { // Body segments should face towards the previous segment var dx = prevSegment.x - segment.x; var dy = prevSegment.y - segment.y; segment.children[0].rotation = Math.atan2(dy, dx); } } // No eye animation needed since yilan image has built-in eyes // Fade out near end of life if (self.currentLifeTime > self.lifeTime - 60) { self.alpha = (self.lifeTime - self.currentLifeTime) / 60; } }; 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; if (self.towerType === 'default') { baseGraphics = self.attachAsset('1', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else if (self.towerType === 'rapid') { baseGraphics = self.attachAsset('2', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else if (self.towerType === 'sniper') { baseGraphics = self.attachAsset('3', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else if (self.towerType === 'splash') { baseGraphics = self.attachAsset('4', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else if (self.towerType === 'slow') { baseGraphics = self.attachAsset('5', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else if (self.towerType === 'poison') { baseGraphics = self.attachAsset('6', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else { baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } switch (self.towerType) { case 'rapid': // Don't tint if using image asset for rapid tower break; case 'sniper': // Don't tint if using image asset for sniper tower break; case 'splash': // Don't tint if using image asset for splash tower break; case 'slow': // Don't tint if using image asset for slow tower break; case 'poison': // Don't tint if using image asset for poison tower break; default: // Don't tint if using image asset for default tower break; } var towerCost = getTowerCost(self.towerType); // Add shadow for tower type label var towerDisplayNameShadow = self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1); if (self.towerType === 'rapid') { towerDisplayNameShadow = 'Twins'; } if (self.towerType === 'default') { towerDisplayNameShadow = 'Student'; } if (self.towerType === 'sniper') { towerDisplayNameShadow = 'Harry'; } if (self.towerType === 'splash') { towerDisplayNameShadow = 'Harmony'; } if (self.towerType === 'slow') { towerDisplayNameShadow = 'Ron'; } if (self.towerType === 'poison') { towerDisplayNameShadow = 'Snape'; } var typeLabelShadow = new Text2(towerDisplayNameShadow, { 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 towerDisplayName = self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1); if (self.towerType === 'rapid') { towerDisplayName = 'Twins'; } if (self.towerType === 'default') { towerDisplayName = 'Student'; } if (self.towerType === 'sniper') { towerDisplayName = 'Harry'; } if (self.towerType === 'splash') { towerDisplayName = 'Harmony'; } if (self.towerType === 'slow') { towerDisplayName = 'Ron'; } if (self.towerType === 'poison') { towerDisplayName = 'Snape'; } var typeLabel = new Text2(towerDisplayName, { size: 50, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.y = -20; // Position above center of tower self.addChild(typeLabel); // Add cost shadow var costLabelShadow = new Text2(towerCost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; costLabelShadow.y = 24 + 12; self.addChild(costLabelShadow); // Add cost label var costLabel = new Text2(towerCost, { size: 50, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.y = 20 + 12; self.addChild(costLabel); self.update = function () { // Check if player can afford this tower var canAfford = gold >= getTowerCost(self.towerType); // Set opacity based on affordability self.alpha = canAfford ? 1 : 0.5; }; return self; }); var Tower = Container.expand(function (id) { var self = Container.call(this); self.id = id || 'default'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.range = 3 * CELL_SIZE; self.specialAbilityCooldown = 0; // Cooldown for special abilities // Standardized method to get the current range of the tower self.getRange = function () { // Always calculate range based on tower type and level switch (self.id) { case 'sniper': // Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost if (self.level === self.maxLevel) { return 12 * CELL_SIZE; // Significantly increased range for max level } return (5 + (self.level - 1) * 0.8) * CELL_SIZE; case 'splash': // Splash: base 2, +0.2 per level (max ~4 blocks at max level) return (2 + (self.level - 1) * 0.2) * CELL_SIZE; case 'rapid': // Rapid: base 2.5, +0.5 per level return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'slow': // Slow: base 3.5, +0.5 per level return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'poison': // Poison: base 3.2, +0.5 per level return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; default: // Default: base 3, +0.5 per level return (3 + (self.level - 1) * 0.5) * CELL_SIZE; } }; self.cellsInRange = []; self.fireRate = 60; self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; switch (self.id) { case 'rapid': self.fireRate = 30; self.damage = 5; self.range = 2.5 * CELL_SIZE; self.bulletSpeed = 7; break; case 'sniper': self.fireRate = 90; self.damage = 25; self.range = 5 * CELL_SIZE; self.bulletSpeed = 25; break; case 'splash': self.fireRate = 75; self.damage = 15; self.range = 2 * CELL_SIZE; self.bulletSpeed = 4; break; case 'slow': self.fireRate = 50; self.damage = 8; self.range = 3.5 * CELL_SIZE; self.bulletSpeed = 5; break; case 'poison': self.fireRate = 70; self.damage = 12; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 5; break; } var baseGraphics; if (self.id === 'default') { baseGraphics = self.attachAsset('1', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else if (self.id === 'rapid') { baseGraphics = self.attachAsset('2', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else if (self.id === 'sniper') { baseGraphics = self.attachAsset('3', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else if (self.id === 'splash') { baseGraphics = self.attachAsset('4', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else if (self.id === 'slow') { baseGraphics = self.attachAsset('5', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else if (self.id === 'poison') { baseGraphics = self.attachAsset('6', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else { baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); } // Store reference to base graphics self.baseGraphics = baseGraphics; switch (self.id) { case 'rapid': // Don't tint if using image asset for rapid tower break; case 'sniper': // Don't tint if using image asset for sniper tower break; case 'splash': // Don't tint if using image asset for splash tower break; case 'slow': // Don't tint if using image asset for slow tower break; case 'poison': // Don't tint if using image asset for poison tower break; default: // Don't tint if using image asset for default tower // Ensure default tower image is not tinted if (self.id === 'default') { baseGraphics.tint = 0xFFFFFF; // Pure white (no tint) } break; } var levelIndicators = []; var maxDots = self.maxLevel; var dotSpacing = baseGraphics.width / (maxDots + 1); var dotSize = CELL_SIZE / 6; for (var i = 0; i < maxDots; i++) { var dot = new Container(); var outlineCircle = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); outlineCircle.width = dotSize + 4; outlineCircle.height = dotSize + 4; outlineCircle.tint = 0x000000; var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); towerLevelIndicator.width = dotSize; towerLevelIndicator.height = dotSize; towerLevelIndicator.tint = 0xCCCCCC; dot.x = -CELL_SIZE + dotSpacing * (i + 1); dot.y = CELL_SIZE * 0.7; self.addChild(dot); levelIndicators.push(dot); } var gunContainer = new Container(); self.addChild(gunContainer); var gunGraphics = gunContainer.attachAsset('defense', { anchorX: 0.5, anchorY: 0.5 }); // Make gun slightly smaller for default towers to not obscure the base if (self.id === 'default') { gunGraphics.scaleX = 0.8; gunGraphics.scaleY = 0.8; } 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(); // Add cooldown indicator for harmony tower if (self.id === 'splash') { var cooldownIndicator = new Container(); self.cooldownIndicator = cooldownIndicator; self.addChild(cooldownIndicator); var cooldownBg = cooldownIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); cooldownBg.width = CELL_SIZE * 0.8; cooldownBg.height = CELL_SIZE * 0.8; cooldownBg.tint = 0x000000; cooldownBg.alpha = 0.5; var cooldownFill = cooldownIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); cooldownFill.width = CELL_SIZE * 0.7; cooldownFill.height = CELL_SIZE * 0.7; cooldownFill.tint = 0x00FF00; self.cooldownFill = cooldownFill; cooldownIndicator.y = -CELL_SIZE * 1.2; } self.refreshCellsInRange = function () { for (var i = 0; i < self.cellsInRange.length; i++) { var cell = self.cellsInRange[i]; var towerIndex = cell.towersInRange.indexOf(self); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } self.cellsInRange = []; var rangeRadius = self.getRange() / CELL_SIZE; var centerX = self.gridX + 1; var centerY = self.gridY + 1; var minI = Math.floor(centerX - rangeRadius - 0.5); var maxI = Math.ceil(centerX + rangeRadius + 0.5); var minJ = Math.floor(centerY - rangeRadius - 0.5); var maxJ = Math.ceil(centerY + rangeRadius + 0.5); for (var i = minI; i <= maxI; i++) { for (var j = minJ; j <= maxJ; j++) { var closestX = Math.max(i, Math.min(centerX, i + 1)); var closestY = Math.max(j, Math.min(centerY, j + 1)); var deltaX = closestX - centerX; var deltaY = closestY - centerY; var distanceSquared = deltaX * deltaX + deltaY * deltaY; if (distanceSquared <= rangeRadius * rangeRadius) { var cell = grid.getCell(i, j); if (cell) { self.cellsInRange.push(cell); cell.towersInRange.push(self); } } } } grid.renderDebug(); }; self.getTotalValue = function () { var baseTowerCost = getTowerCost(self.id); var totalInvestment = baseTowerCost; var baseUpgradeCost = baseTowerCost; // Upgrade cost now scales with base tower cost for (var i = 1; i < self.level; i++) { totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1)); } return totalInvestment; }; self.upgrade = function () { if (self.level < self.maxLevel) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.id); var upgradeCost; // Make last upgrade level extra expensive if (self.level === self.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); // Half the cost for final upgrade } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1)); } if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; // No need to update self.range here; getRange() is now the source of truth // Apply tower-specific upgrades based on type if (self.id === 'rapid') { if (self.level === self.maxLevel) { // Extra powerful last upgrade (double the effect) self.fireRate = Math.max(4, 30 - self.level * 9); // double the effect self.damage = 5 + self.level * 10; // double the effect self.bulletSpeed = 7 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(15, 30 - self.level * 3); // Fast tower gets faster with upgrades self.damage = 5 + self.level * 3; self.bulletSpeed = 7 + self.level * 0.7; } } else { if (self.level === self.maxLevel) { // Extra powerful last upgrade for all other towers (double the effect) self.fireRate = Math.max(5, 60 - self.level * 24); // double the effect self.damage = 10 + self.level * 20; // double the effect self.bulletSpeed = 5 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(20, 60 - self.level * 8); self.damage = 10 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.5; } } self.refreshCellsInRange(); self.updateLevelIndicators(); if (self.level > 1) { var levelDot = levelIndicators[self.level - 1].children[1]; tween(levelDot, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut, onFinish: function onFinish() { tween(levelDot, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); } return true; } else { var notification = game.addChild(new Notification("Not enough gold to upgrade!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } return false; }; self.findTarget = function () { var closestEnemy = null; var closestScore = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if enemy is in range if (distance <= self.getRange()) { // Handle flying enemies differently - they can be targeted regardless of path if (enemy.isFlying) { // For flying enemies, prioritize by distance to the goal if (enemy.flyingTarget) { var goalX = enemy.flyingTarget.x; var goalY = enemy.flyingTarget.y; var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY)); // Use distance to goal as score if (distToGoal < closestScore) { closestScore = distToGoal; closestEnemy = enemy; } } else { // If no flying target yet (shouldn't happen), prioritize by distance to tower if (distance < closestScore) { closestScore = distance; closestEnemy = enemy; } } } else { // For ground enemies, 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 special ability cooldown if (self.specialAbilityCooldown > 0) { self.specialAbilityCooldown--; } // Update cooldown visual indicator for harmony tower if (self.id === 'splash' && self.cooldownIndicator) { if (self.specialAbilityCooldown > 0) { // Show cooldown progress self.cooldownIndicator.visible = true; var cooldownProgress = 1 - self.specialAbilityCooldown / 600; self.cooldownFill.scaleX = cooldownProgress; self.cooldownFill.scaleY = cooldownProgress; self.cooldownFill.tint = 0xFF0000; // Red when on cooldown // Hide the ability ready text when on cooldown if (self.abilityReadyText) { self.abilityReadyText.visible = false; } } else { // Ready to use self.cooldownIndicator.visible = true; self.cooldownFill.scaleX = 1; self.cooldownFill.scaleY = 1; self.cooldownFill.tint = 0x00FF00; // Green when ready // Show the ability ready text if (!self.abilityReadyText) { // Create the text if it doesn't exist self.abilityReadyText = new Container(); // Add shadow text var shadowText = new Text2('Press to slow down', { size: 45, fill: 0x000000, weight: 800 }); shadowText.anchor.set(0.5, 0.5); shadowText.x = 2; shadowText.y = 2; self.abilityReadyText.addChild(shadowText); // Add main text var mainText = new Text2('Press to slow down', { size: 45, fill: 0xFFFFFF, weight: 800 }); mainText.anchor.set(0.5, 0.5); self.abilityReadyText.addChild(mainText); // Position above the tower self.abilityReadyText.y = -CELL_SIZE * 2; self.addChild(self.abilityReadyText); } self.abilityReadyText.visible = true; } } self.targetEnemy = self.findTarget(); if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); gunContainer.rotation = angle; if (LK.ticks - self.lastFired >= self.fireRate) { self.fire(); self.lastFired = LK.ticks; } } // Flying behavior for students (default) towers if (self.id === 'default') { // Initialize flying properties if not already set if (!self.isFlying) { self.isFlying = true; self.flightRadius = 200 + Math.random() * 300; // Random radius between 200-500 self.flightSpeed = 0.02 + Math.random() * 0.03; // Random speed self.flightAngle = Math.random() * Math.PI * 2; // Random starting angle self.flightCenterX = 300 + Math.random() * (2048 - 600); // Random center X across screen self.flightCenterY = 400 + Math.random() * (2200 - 400); // Random center Y across screen self.flightTimer = 0; self.flightDuration = 1800 + Math.random() * 1200; // Flight duration 30-50 seconds } // Update flight position self.flightTimer++; self.flightAngle += self.flightSpeed; // Calculate circular flight path var targetX = self.flightCenterX + Math.cos(self.flightAngle) * self.flightRadius; var targetY = self.flightCenterY + Math.sin(self.flightAngle) * self.flightRadius; // Smoothly move to target position var lerpFactor = 0.1; self.x += (targetX - self.x) * lerpFactor; self.y += (targetY - self.y) * lerpFactor; // Change flight pattern periodically if (self.flightTimer >= self.flightDuration) { self.flightTimer = 0; self.flightRadius = 200 + Math.random() * 300; self.flightSpeed = 0.02 + Math.random() * 0.03; self.flightCenterX = 300 + Math.random() * (2048 - 600); self.flightCenterY = 400 + Math.random() * (2200 - 400); self.flightDuration = 1800 + Math.random() * 1200; } // Add floating animation to the tower graphic if (!self.floatingAnimation) { self.floatingAnimation = true; var _createFloatingAnimation2 = function _createFloatingAnimation() { if (!self.parent) return; tween(self.baseGraphics, { y: -8 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent) return; tween(self.baseGraphics, { y: 8 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent) return; _createFloatingAnimation2(); } }); } }); }; _createFloatingAnimation2(); } } else { // Original tower following behavior for non-students towers if (!self.isMoving && !self.moveTimer) { self.moveTimer = LK.ticks + Math.random() * 300 + 180; // Random interval 3-8 seconds } if (!self.isMoving && LK.ticks >= self.moveTimer) { // Find closest enemy in range to follow var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.originalX; var dy = enemy.y - self.originalY; var distance = Math.sqrt(dx * dx + dy * dy); // Increase following range to 2.5x the attack range if (distance <= self.getRange() * 2.5 && distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } if (closestEnemy) { self.isMoving = true; // Calculate movement towards enemy (but not too close) var enemyDx = closestEnemy.x - self.originalX; var enemyDy = closestEnemy.y - self.originalY; var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy); // Move 40% of the way towards the enemy (increased from 30%) var moveDistance = Math.min(CELL_SIZE * 3, enemyDistance * 0.4); var moveAngle = Math.atan2(enemyDy, enemyDx); var newX = self.originalX + Math.cos(moveAngle) * moveDistance; var newY = self.originalY + Math.sin(moveAngle) * moveDistance; // Animate towards enemy tween(self, { x: newX, y: newY }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { // Stay at new position for a moment LK.setTimeout(function () { // Return to original position tween(self, { x: self.originalX, y: self.originalY }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { self.isMoving = false; self.moveTimer = LK.ticks + Math.random() * 300 + 180; // Reset timer } }); }, 1000); } }); } else { // No enemy found, reset timer self.moveTimer = LK.ticks + Math.random() * 180 + 120; // Shorter wait if no target } } } }; self.down = function (x, y, obj) { // Handle harmony tower special ability if (self.id === 'splash' && self.specialAbilityCooldown === 0) { // Activate special ability: slow all enemies to 50% for 5 seconds var slowDuration = 300; // 5 seconds at 60 FPS var slowPercentage = 0.5; // 50% speed // Apply slow to all enemies for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; // Skip immune enemies if (!enemy.isImmune) { // Store original speed if not already slowed if (!enemy.slowed) { enemy.originalSpeed = enemy.speed; } enemy.speed = enemy.originalSpeed * slowPercentage; enemy.slowed = true; enemy.slowDuration = slowDuration; // Create visual effect var slowEffect = new EffectIndicator(enemy.x, enemy.y, 'slow'); game.addChild(slowEffect); } } // Set cooldown to 10 seconds self.specialAbilityCooldown = 600; // 10 seconds at 60 FPS // Play harmony sound effect LK.getSound('harmony').play(); // Create notification var notification = game.addChild(new Notification("Harmony activated! All enemies slowed!")); notification.x = 2048 / 2; notification.y = grid.height - 150; // Visual feedback on the tower tween(self, { scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } }); return; // Don't open upgrade menu when using ability } 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; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); // Set bullet type based on tower type bullet.type = self.id; // For slow tower, pass level for scaling slow effect if (self.id === 'slow') { bullet.sourceTowerLevel = self.level; } // Customize bullet appearance based on tower type switch (self.id) { case 'default': bullet.bulletGraphics.tint = 0xd2b48c; // Default tower bullet color bullet.bulletGraphics.width = 30; bullet.bulletGraphics.height = 30; // Randomly play either 111 or abrakadabra sound when default tower fires if (Math.random() < 0.5) { LK.getSound('111').play(); } else { LK.getSound('abrakadabra').play(); } // Change default tower to alternate between image assets 11 and 1 after firing if (self.baseGraphics) { // Check current asset and switch var currentAssetId = self.baseGraphics.texture ? '1' : '11'; if (self.lastUsedAsset === '1' || self.lastUsedAsset === undefined) { // Switch to asset 11 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('11', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '11'; } else { // Switch to asset 1 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('1', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '1'; } self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; case 'rapid': bullet.bulletGraphics.tint = 0x87ceeb; // Bright sky blue bolt bullet.bulletGraphics.width = 20; bullet.bulletGraphics.height = 20; // Play 222 sound when rapid tower fires LK.getSound('222').play(); // Change rapid tower to alternate between image assets 22 and 2 after firing if (self.baseGraphics) { // Check current asset and switch if (self.lastUsedAsset === '2' || self.lastUsedAsset === undefined) { // Switch to asset 22 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('22', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '22'; } else { // Switch to asset 2 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('2', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '2'; } self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; case 'sniper': bullet.bulletGraphics.tint = 0xff6347; // Bright orange-red spell bullet.bulletGraphics.width = 15; bullet.bulletGraphics.height = 15; // Play 33 sound when sniper tower fires LK.getSound('33').play(); // Change sniper tower to alternate between image assets 3 and 32 after firing if (self.baseGraphics) { // Check current asset and switch if (self.lastUsedAsset === '3' || self.lastUsedAsset === undefined) { // Switch to asset 32 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('32', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '32'; } else { // Switch to asset 3 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('3', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '3'; } self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; case 'splash': bullet.bulletGraphics.tint = 0x90ee90; // Bright light green spell bullet.bulletGraphics.width = 40; bullet.bulletGraphics.height = 40; // Play harmony sound when splash tower fires LK.getSound('harmony').play(); // Change splash tower to use image asset 42 after firing if (self.baseGraphics) { self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('42', { anchorX: 0.5, anchorY: 0.5 }); self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; case 'slow': bullet.bulletGraphics.tint = 0xdda0dd; // Bright plum spell bullet.bulletGraphics.width = 35; bullet.bulletGraphics.height = 35; // Play ron sound when slow tower fires LK.getSound('ron').play(); // Change slow tower to alternate between image assets 5 and 52 after firing if (self.baseGraphics) { // Check current asset and switch if (self.lastUsedAsset === '5' || self.lastUsedAsset === undefined) { // Switch to asset 52 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('52', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '52'; } else { // Switch to asset 5 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('5', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '5'; } self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; case 'poison': bullet.bulletGraphics.tint = 0x98fb98; // Bright pale green spell bullet.bulletGraphics.width = 35; bullet.bulletGraphics.height = 35; // Play snape sound when poison tower fires LK.getSound('snape').play(); // Create snake that shoots fire at enemies var snakeX = self.x + Math.cos(gunContainer.rotation) * 60; var snakeY = self.y + Math.sin(gunContainer.rotation) * 60; var snake = new Snake(snakeX, snakeY, self); game.addChild(snake); snakes.push(snake); // Change poison tower to alternate between image assets 6 and 62 after firing if (self.baseGraphics) { // Check current asset and switch if (self.lastUsedAsset === '6' || self.lastUsedAsset === undefined) { // Switch to asset 62 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('62', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '62'; } else { // Switch to asset 6 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('6', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '6'; } self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; } game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); // --- Fire recoil effect for gunContainer --- // Stop any ongoing recoil tweens before starting a new one tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); // Always use the original resting position for recoil, never accumulate offset if (gunContainer._restX === undefined) { gunContainer._restX = 0; } if (gunContainer._restY === undefined) { gunContainer._restY = 0; } if (gunContainer._restScaleX === undefined) { gunContainer._restScaleX = 1; } if (gunContainer._restScaleY === undefined) { gunContainer._restScaleY = 1; } // Reset to resting position before animating (in case of interrupted tweens) gunContainer.x = gunContainer._restX; gunContainer.y = gunContainer._restY; gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; // Calculate recoil offset (recoil back along the gun's rotation) var recoilDistance = 8; var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance; var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance; // Animate recoil back from the resting position tween(gunContainer, { x: gunContainer._restX + recoilX, y: gunContainer._restY + recoilY }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { // Animate return to original position/scale tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: 90, easing: tween.cubicIn }); } }); } } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; // Store original position for movement behavior self.originalX = self.x; self.originalY = self.y; self.isMoving = false; self.moveTimer = null; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 1; } } } self.refreshCellsInRange(); }; return self; }); var TowerPreview = Container.expand(function () { var self = Container.call(this); var towerRange = 3; var rangeInPixels = towerRange * CELL_SIZE; self.towerType = 'default'; self.hasEnoughGold = true; var rangeIndicator = new Container(); self.addChild(rangeIndicator); var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.alpha = 0.3; var previewGraphics = self.attachAsset('towerpreview', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; self.canPlace = false; self.gridX = 0; self.gridY = 0; self.blockedByEnemy = false; self.update = function () { var previousHasEnoughGold = self.hasEnoughGold; self.hasEnoughGold = gold >= getTowerCost(self.towerType); // Only update appearance if the affordability status has changed if (previousHasEnoughGold !== self.hasEnoughGold) { self.updateAppearance(); } }; self.updateAppearance = function () { // Use Tower class to get the source of truth for range var tempTower = new Tower(self.towerType); var previewRange = tempTower.getRange(); // Clean up tempTower to avoid memory leaks if (tempTower && tempTower.destroy) { tempTower.destroy(); } // Set range indicator using unified range logic rangeGraphics.width = rangeGraphics.height = previewRange * 2; // For default tower type, replace the preview graphics with image asset 1 if (self.towerType === 'default') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 1 as preview previewGraphics = self.attachAsset('1', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else if (self.towerType === 'rapid') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 2 as preview previewGraphics = self.attachAsset('2', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else if (self.towerType === 'sniper') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 3 as preview previewGraphics = self.attachAsset('3', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else if (self.towerType === 'splash') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 4 as preview previewGraphics = self.attachAsset('4', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else if (self.towerType === 'slow') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 5 as preview previewGraphics = self.attachAsset('5', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else if (self.towerType === 'poison') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 5 as preview previewGraphics = self.attachAsset('5', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else { // For non-default towers, ensure we're using the shape asset if (previewGraphics.texture && previewGraphics.texture.baseTexture) { // Already using an image, need to switch back to shape self.removeChild(previewGraphics); previewGraphics = self.attachAsset('towerpreview', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; self.setChildIndex(previewGraphics, 1); } } switch (self.towerType) { case 'rapid': // Don't tint if using image asset for rapid tower break; case 'sniper': // Don't tint if using image asset for sniper tower break; case 'splash': // Don't tint if using image asset for splash tower break; case 'slow': // Don't tint if using image asset for slow tower break; case 'poison': // Don't tint if using image asset for poison tower break; default: // Don't tint if using image asset for default tower if (self.towerType !== 'default' && self.towerType !== 'rapid' && self.towerType !== 'sniper' && self.towerType !== 'splash' && self.towerType !== 'slow' && self.towerType !== 'poison') { previewGraphics.tint = 0xd2b48c; // Light tan } } if (!self.canPlace || !self.hasEnoughGold) { previewGraphics.tint = 0xFF0000; } }; self.updatePlacementStatus = function () { var validGridPlacement = true; if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) { validGridPlacement = false; } else { for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(self.gridX + i, self.gridY + j); if (!cell || cell.type !== 0) { validGridPlacement = false; break; } } if (!validGridPlacement) { break; } } } self.blockedByEnemy = false; if (validGridPlacement) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.currentCellY < 4) { continue; } // Only check non-flying enemies, flying enemies can pass over towers if (!enemy.isFlying) { if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) { self.blockedByEnemy = true; break; } if (enemy.currentTarget) { var targetX = enemy.currentTarget.x; var targetY = enemy.currentTarget.y; if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) { self.blockedByEnemy = true; break; } } } } } self.canPlace = validGridPlacement && !self.blockedByEnemy; self.hasEnoughGold = gold >= getTowerCost(self.towerType); self.updateAppearance(); }; self.checkPlacement = function () { self.updatePlacementStatus(); }; self.snapToGrid = function (x, y) { var gridPosX = x - grid.x; var gridPosY = y - grid.y; self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; self.checkPlacement(); }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 500; menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; var towerTypeName = self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1); if (self.tower.id === 'rapid') { towerTypeName = 'Twins'; } if (self.tower.id === 'default') { towerTypeName = 'Student'; } if (self.tower.id === 'sniper') { towerTypeName = 'Harry'; } if (self.tower.id === 'splash') { towerTypeName = 'Harmony'; } if (self.tower.id === 'slow') { towerTypeName = 'Ron'; } if (self.tower.id === 'poison') { towerTypeName = 'Snape'; } var towerTypeText = new Text2(towerTypeName + ' Tower', { size: 80, fill: 0xFFFFFF, weight: 800 }); towerTypeText.anchor.set(0, 0); towerTypeText.x = -840; towerTypeText.y = -160; self.addChild(towerTypeText); var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', { size: 70, fill: 0xFFFFFF, weight: 400 }); statsText.anchor.set(0, 0.5); statsText.x = -840; statsText.y = 50; self.addChild(statsText); var buttonsContainer = new Container(); buttonsContainer.x = 500; self.addChild(buttonsContainer); var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; buttonBackground.height = 150; var isMaxLevel = self.tower.level >= self.tower.maxLevel; // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var upgradeCost; if (isMaxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888; var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; sellButtonBackground.height = 150; sellButtonBackground.tint = 0xCC0000; var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); upgradeButton.y = -85; sellButton.y = 85; var closeButton = new Container(); self.addChild(closeButton); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; upgradeButton.down = function (x, y, obj) { if (self.tower.level >= self.tower.maxLevel) { var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (self.tower.upgrade()) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); if (self.tower.level >= self.tower.maxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s'); buttonText.setText('Upgrade: ' + upgradeCost + ' gold'); var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); sellButtonText.setText('Sell: +' + sellValue + ' gold'); if (self.tower.level >= self.tower.maxLevel) { buttonBackground.tint = 0x888888; buttonText.setText('Max Level'); } var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { rangeCircle = game.children[i]; break; } } if (rangeCircle) { var rangeGraphics = rangeCircle.children[0]; rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; } else { var newRangeIndicator = new Container(); newRangeIndicator.isTowerRange = true; newRangeIndicator.tower = self.tower; game.addChildAt(newRangeIndicator, 0); newRangeIndicator.x = self.tower.x; newRangeIndicator.y = self.tower.y; var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; rangeGraphics.alpha = 0.3; } tween(self, { scaleX: 1.05, scaleY: 1.05 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn }); } }); } }; sellButton.down = function (x, y, obj) { var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); setGold(gold + sellValue); var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; var gridX = self.tower.gridX; var gridY = self.tower.gridY; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { 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 () { // Start button is now disabled - game starts automatically with countdown }; for (var i = 0; i < totalWaves; i++) { var marker = new Container(); var block = marker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth - 10; block.height = 70 * 2; // --- Begin new unified wave logic --- var waveType = "normal"; var enemyType = "normal"; var enemyCount = 10; var isBossWave = (i + 1) % 10 === 0; // Ensure all types appear in early waves if (i === 0) { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } else if (i === 1) { block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if (i === 2) { block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; enemyCount = 10; } else if (i === 3) { block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if (i === 4) { block.tint = 0xFF00FF; waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else if (isBossWave) { // Boss waves: cycle through all boss types, last boss is always flying var bossTypes = ['normal', 'fast', 'immune', 'flying']; var bossTypeIndex = Math.floor((i + 1) / 10) - 1; if (i === totalWaves - 1) { // Last boss is always flying enemyType = 'flying'; waveType = "Boss Flying"; block.tint = 0xFFFF00; } else { enemyType = bossTypes[bossTypeIndex % bossTypes.length]; switch (enemyType) { case 'normal': block.tint = 0xAAAAAA; waveType = "Boss Normal"; break; case 'fast': block.tint = 0x00AAFF; waveType = "Boss Fast"; break; case 'immune': block.tint = 0xAA0000; waveType = "Boss Immune"; break; case 'flying': block.tint = 0xFFFF00; waveType = "Boss Flying"; break; } } enemyCount = 1; // Make the wave indicator for boss waves stand out // Set boss wave color to the color of the wave type switch (enemyType) { case 'normal': block.tint = 0xAAAAAA; break; case 'fast': block.tint = 0x00AAFF; break; case 'immune': block.tint = 0xAA0000; break; case 'flying': block.tint = 0xFFFF00; break; default: block.tint = 0xFF0000; break; } } else if ((i + 1) % 5 === 0) { // Every 5th non-boss wave is fast block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if ((i + 1) % 4 === 0) { // Every 4th non-boss wave is immune block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; enemyCount = 10; } else if ((i + 1) % 7 === 0) { // Every 7th non-boss wave is flying block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if ((i + 1) % 3 === 0) { // Every 3rd non-boss wave is swarm block.tint = 0xFF00FF; waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } // --- End new unified wave logic --- // Mark boss waves with a special visual indicator if (isBossWave && enemyType !== 'swarm') { // Add a crown or some indicator to the wave marker for boss waves var bossIndicator = marker.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); bossIndicator.width = 30; bossIndicator.height = 30; bossIndicator.tint = 0xFFD700; // Gold color bossIndicator.y = -block.height / 2 - 15; // Change the wave type text to indicate boss waveType = "BOSS"; } // Store the wave type and enemy count self.waveTypes[i] = enemyType; self.enemyCounts[i] = enemyCount; // Add shadow for wave type - 30% smaller than before var waveTypeShadow = new Text2(waveType, { size: 56, fill: 0x000000, weight: 800 }); waveTypeShadow.anchor.set(0.5, 0.5); waveTypeShadow.x = 4; waveTypeShadow.y = 4; marker.addChild(waveTypeShadow); // Add wave type text - 30% smaller than before var waveTypeText = new Text2(waveType, { size: 56, fill: 0xFFFFFF, weight: 800 }); waveTypeText.anchor.set(0.5, 0.5); waveTypeText.y = 0; marker.addChild(waveTypeText); // Add shadow for wave number - 20% larger than before var waveNumShadow = new Text2((i + 1).toString(), { size: 48, fill: 0x000000, weight: 800 }); waveNumShadow.anchor.set(1.0, 1.0); waveNumShadow.x = blockWidth / 2 - 16 + 5; waveNumShadow.y = block.height / 2 - 12 + 5; marker.addChild(waveNumShadow); // Main wave number text - 20% larger than before var waveNum = new Text2((i + 1).toString(), { size: 48, fill: 0xFFFFFF, weight: 800 }); waveNum.anchor.set(1.0, 1.0); waveNum.x = blockWidth / 2 - 16; waveNum.y = block.height / 2 - 12; marker.addChild(waveNum); marker.x = -self.indicatorWidth + (i + 1) * blockWidth; self.addChild(marker); self.waveMarkers.push(marker); } // Get wave type for a specific wave number self.getWaveType = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return "normal"; } // If this is a boss wave (waveNumber % 10 === 0), and the type is the same as lastBossType // then we should return a different boss type var waveType = self.waveTypes[waveNumber - 1]; return waveType; }; // Get enemy count for a specific wave number self.getEnemyCount = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return 10; } return self.enemyCounts[waveNumber - 1]; }; // Get display name for a wave type self.getWaveTypeName = function (waveNumber) { var type = self.getWaveType(waveNumber); var typeName = type.charAt(0).toUpperCase() + type.slice(1); // Add boss prefix for boss waves (every 10th wave) if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') { typeName = "BOSS"; } return typeName; }; self.positionIndicator = new Container(); var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = blockWidth - 10; indicator.height = 16; indicator.tint = 0xffad0e; indicator.y = -65; var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator2.width = blockWidth - 10; indicator2.height = 16; indicator2.tint = 0xffad0e; indicator2.y = 65; var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); leftWall.width = 16; leftWall.height = 146; leftWall.tint = 0xffad0e; leftWall.x = -(blockWidth - 16) / 2; var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); rightWall.width = 16; rightWall.height = 146; rightWall.tint = 0xffad0e; rightWall.x = (blockWidth - 16) / 2; self.addChild(self.positionIndicator); self.update = function () { var progress = waveTimer / nextWaveTime; var moveAmount = (progress + currentWave) * blockWidth; for (var i = 0; i < self.waveMarkers.length; i++) { var marker = self.waveMarkers[i]; marker.x = -moveAmount + i * blockWidth; } self.positionIndicator.x = 0; for (var i = 0; i < totalWaves + 1; i++) { var marker = self.waveMarkers[i]; if (i === 0) { continue; } var block = marker.children[0]; if (i - 1 < currentWave) { block.alpha = .5; } } self.handleWaveProgression = function () { if (!self.gameStarted) { return; } if (currentWave < totalWaves) { waveTimer++; if (waveTimer >= nextWaveTime) { waveTimer = 0; currentWave++; waveInProgress = true; waveSpawned = false; if (currentWave != 1) { var waveType = self.getWaveTypeName(currentWave); var enemyCount = self.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } }; self.handleWaveProgression(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x8B7355 }); /**** * Game Code ****/ var isHidingUpgradeMenu = false; var gameStarted = false; var startScreen = new Container(); function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; } }); } var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var enemies = []; var towers = []; var bullets = []; var defenses = []; var snakes = []; var selectedTower = null; var gold = 130; var lives = 5; var score = 0; var xp = 0; var level = 1; var currentWave = 0; var totalWaves = 50; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 2000; var sourceTower = null; var enemiesToSpawn = 10; // Default number of enemies per wave var backgroundFlames = []; var flameSpawnTimer = 0; var flameSpawnInterval = 180 + Math.random() * 240; // Spawn flame every 3-7 seconds var lightningTimer = 0; var lightningInterval = 900; // 15 seconds at 60 FPS var groundShakeTimer = 0; var groundShakeInterval = 1200; // 20 seconds at 60 FPS var flameLayer = new Container(); var smokeLayer = new Container(); var smokeParticles = []; 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 xpText = new Text2('XP: ' + xp, { size: 60, fill: 0x9400D3, weight: 800 }); xpText.anchor.set(0.5, 0.5); var levelText = new Text2('Level: ' + level, { size: 60, fill: 0x00CED1, weight: 800 }); levelText.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); LK.gui.top.addChild(xpText); LK.gui.top.addChild(levelText); livesText.x = 0; livesText.y = topMargin; goldText.x = -spacing; goldText.y = topMargin; scoreText.x = spacing; scoreText.y = topMargin; xpText.x = 0; xpText.y = topMargin + 80; levelText.x = 0; levelText.y = topMargin + 160; function updateUI() { goldText.setText('Gold: ' + gold); livesText.setText('Lives: ' + lives); scoreText.setText('Score: ' + score); xpText.setText('XP: ' + xp); levelText.setText('Level: ' + level); } function setGold(value) { gold = value; updateUI(); } var debugLayer = new Container(); var towerLayer = new Container(); // Create three separate layers for enemy hierarchy var enemyLayerBottom = new Container(); // For normal enemies var enemyLayerMiddle = new Container(); // For shadows var enemyLayerTop = new Container(); // For flying enemies var enemyLayer = new Container(); // Main container to hold all enemy layers // Add layers in correct order (bottom first, then middle for shadows, then top) enemyLayer.addChild(enemyLayerBottom); enemyLayer.addChild(enemyLayerMiddle); enemyLayer.addChild(enemyLayerTop); var grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); // Add background image var backgroundImage = LK.getAsset('arkaplan', { anchorX: 0, anchorY: 0, scaleX: 1, scaleY: 1 }); backgroundImage.x = 0; backgroundImage.y = 0; game.addChild(backgroundImage); debugLayer.addChild(grid); game.addChild(debugLayer); game.addChild(flameLayer); game.addChild(smokeLayer); game.addChild(towerLayer); game.addChild(enemyLayer); var offset = 0; var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; var isDragging = false; var draggingTower = null; var dragOffset = { x: 0, y: 0 }; function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); return blocked; } function getTowerCost(towerType) { var cost = 5; switch (towerType) { case 'rapid': cost = 15; break; case 'sniper': cost = 25; break; case 'splash': cost = 35; break; case 'slow': cost = 45; break; case 'poison': cost = 55; break; } 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) { // Ensure towerType is 'default' if not specified var actualTowerType = towerType || 'default'; var tower = new Tower(actualTowerType); // Apply level 2 fire rate bonus if applicable if (level >= 2) { tower.fireRate = 30; // 0.5 second attack interval } tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); grid.pathFind(); grid.renderDebug(); return true; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } game.down = function (x, y, obj) { if (!gameStarted) { return; } var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenuVisible) { return; } // Check if clicking on an existing tower first for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (x >= tower.x - tower.baseGraphics.width / 2 && x <= tower.x + tower.baseGraphics.width / 2 && y >= tower.y - tower.baseGraphics.height / 2 && y <= tower.y + tower.baseGraphics.height / 2) { // Start dragging the existing tower draggingTower = tower; isDragging = true; dragOffset.x = x - tower.x; dragOffset.y = y - tower.y; // Clear the tower's grid cells for (var j = 0; j < 2; j++) { for (var k = 0; k < 2; k++) { var cell = grid.getCell(tower.gridX + j, tower.gridY + k); if (cell) { cell.type = 0; } } } // Show visual feedback that tower is being dragged tower.alpha = 0.7; // Remove tower from towers in range for all cells tower.refreshCellsInRange(); grid.pathFind(); grid.renderDebug(); return; } } // Check source towers for creating new towers for (var i = 0; i < sourceTowers.length; i++) { var tower = sourceTowers[i]; if (x >= tower.x - tower.width / 2 && x <= tower.x + tower.width / 2 && y >= tower.y - tower.height / 2 && y <= tower.y + tower.height / 2) { towerPreview.visible = true; isDragging = true; towerPreview.towerType = tower.towerType; towerPreview.updateAppearance(); // Apply the same offset as in move handler to ensure consistency when starting drag towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); break; } } }; game.move = function (x, y, obj) { if (!gameStarted) { return; } if (isDragging) { if (draggingTower) { // Move the existing tower draggingTower.x = x - dragOffset.x; draggingTower.y = y - dragOffset.y; } else { // 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) { if (!gameStarted) { return; } 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 (draggingTower) { // Handle existing tower repositioning var gridPosX = draggingTower.x - grid.x; var gridPosY = draggingTower.y - grid.y; var newGridX = Math.floor(gridPosX / CELL_SIZE); var newGridY = Math.floor(gridPosY / CELL_SIZE); // Check if the new position is valid var validPlacement = true; var blockedByEnemy = false; // Check bounds if (newGridY <= 4 || newGridY + 1 >= grid.cells[0].length - 4) { validPlacement = false; } else { // Check if cells are available for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(newGridX + i, newGridY + j); if (!cell || cell.type !== 0) { validPlacement = false; break; } } if (!validPlacement) break; } // Check for enemies in the way if (validPlacement) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.currentCellY < 4) continue; if (!enemy.isFlying) { if (enemy.cellX >= newGridX && enemy.cellX < newGridX + 2 && enemy.cellY >= newGridY && enemy.cellY < newGridY + 2) { blockedByEnemy = true; break; } } } } } // Check if placement would block path var wouldBlock = false; if (validPlacement && !blockedByEnemy) { wouldBlock = wouldBlockPath(newGridX, newGridY); } if (validPlacement && !blockedByEnemy && !wouldBlock) { // Place tower in new position draggingTower.placeOnGrid(newGridX, newGridY); draggingTower.alpha = 1; var notification = game.addChild(new Notification("Tower moved!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else { // Return tower to original position draggingTower.x = grid.x + draggingTower.gridX * CELL_SIZE + CELL_SIZE / 2; draggingTower.y = grid.y + draggingTower.gridY * CELL_SIZE + CELL_SIZE / 2; draggingTower.alpha = 1; // Restore grid cells for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(draggingTower.gridX + i, draggingTower.gridY + j); if (cell) { cell.type = 1; } } } draggingTower.refreshCellsInRange(); if (!validPlacement) { var notification = game.addChild(new Notification("Cannot move tower here!")); } else if (blockedByEnemy) { var notification = game.addChild(new Notification("Cannot move: Enemy in the way!")); } else if (wouldBlock) { var notification = game.addChild(new Notification("Cannot move: Would block path!")); } notification.x = 2048 / 2; notification.y = grid.height - 50; } draggingTower = null; grid.pathFind(); grid.renderDebug(); } else { // Handle new tower placement 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); // Create start screen with Hogwarts under attack image var startScreenBackground = startScreen.attachAsset('hogwartsAttack', { anchorX: 0.5, anchorY: 0.5 }); startScreenBackground.x = 1024; startScreenBackground.y = 1366; // Center vertically // Image is already 2048x2732, perfect fit startScreenBackground.scaleX = 1; startScreenBackground.scaleY = 1; // Title text removed as requested // Subtitle removed as requested // Add instruction text to tap anywhere var tapInstructionShadow = new Text2("Tap anywhere to start", { size: 80, fill: 0x000000, weight: 600 }); tapInstructionShadow.anchor.set(0.5, 0.5); tapInstructionShadow.x = 1024 + 4; tapInstructionShadow.y = 2732 - 600 + 4; startScreen.addChild(tapInstructionShadow); var tapInstruction = new Text2("Tap anywhere to start", { size: 80, fill: 0xFFD700, weight: 600 }); tapInstruction.anchor.set(0.5, 0.5); tapInstruction.x = 1024; tapInstruction.y = 2732 - 600; startScreen.addChild(tapInstruction); // Add pulsing animation to instruction text var _pulseAnimation = function pulseAnimation() { if (tapInstruction.parent) { tween(tapInstruction, { alpha: 0.4, scaleX: 0.95, scaleY: 0.95 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (tapInstruction.parent) { tween(tapInstruction, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 1000, easing: tween.easeInOut, onFinish: _pulseAnimation }); } } }); } }; _pulseAnimation(); // Make entire start screen clickable startScreen.down = function () { // Remove start screen game.removeChild(startScreen); // Start countdown directly startCountdown(); }; // Function to start countdown function startCountdown() { // Add 100 image after hogwartsAttack var image100Container = new Container(); game.addChild(image100Container); var image100 = image100Container.attachAsset('100', { anchorX: 0.5, anchorY: 0.5 }); // Scale to fill the entire screen image100.width = 2048; image100.height = 2732; image100Container.x = 2048 / 2; image100Container.y = 2732 / 2; image100Container.alpha = 0; // Ensure it's on top of other elements game.setChildIndex(image100Container, game.children.length - 1); // Fade in the 100 image tween(image100Container, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); // Make 100 image clickable to start the game image100Container.down = function () { // Fade out and start game tween(image100Container, { alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { if (image100Container.parent) { image100Container.destroy(); } // Start the actual game after clicking 100 gameStarted = true; // Update wave indicator to show game has started waveIndicator.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; // Update start marker visual var startMarker = waveIndicator.waveMarkers[0]; if (startMarker && startMarker.children[0]) { startMarker.children[0].tint = 0x00FF00; } if (startMarker && startMarker.children[1]) { startMarker.children[1].setText("Started!"); } if (startMarker && startMarker.children[2]) { startMarker.children[2].setText("Started!"); startMarker.children[2].x = 4; startMarker.children[2].y = 4; } // Add wizard movement warning var movementWarning = game.addChild(new Notification("Wizards can be moved by dragging them!")); movementWarning.x = 2048 / 2; movementWarning.y = 2732 / 2; // Make the warning last longer than normal notifications movementWarning.fadeOutTime = 300; // 5 seconds at 60 FPS } }); }; } // Add start screen to game game.addChild(startScreen); // Play exciting music on start screen LK.playMusic('g1', { loop: true }); // Wizard movement warning and music are now handled in the start screen button handler // Create restart button var restartButton = new Container(); var restartBackground = restartButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); restartBackground.width = 300; restartBackground.height = 120; restartBackground.tint = 0xFF6B6B; var restartText = new Text2("Restart", { size: 60, fill: 0xFFFFFF, weight: 800 }); restartText.anchor.set(0.5, 0.5); restartButton.addChild(restartText); // Position in top left area but outside the reserved 100x100 zone restartButton.x = 250; // Adjusted position for larger button restartButton.y = 140; // Adjusted position for larger button restartButton.down = function () { // Reset all game variables to initial state gold = 200; lives = 20; score = 0; xp = 0; level = 1; currentWave = 0; waveTimer = 0; waveInProgress = false; waveSpawned = false; selectedTower = null; isDragging = false; draggingTower = null; // Clear all enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); } if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } } enemies = []; // Clear all bullets for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i].parent) { bullets[i].destroy(); } } bullets = []; // Clear all towers for (var i = towers.length - 1; i >= 0; i--) { var tower = towers[i]; // Reset grid cells for (var j = 0; j < 2; j++) { for (var k = 0; k < 2; k++) { var cell = grid.getCell(tower.gridX + j, tower.gridY + k); if (cell) { cell.type = 0; } } } towerLayer.removeChild(tower); } towers = []; // Clear any upgrade menus var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } // Clear any range indicators for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } // Clear background flames for (var i = backgroundFlames.length - 1; i >= 0; i--) { if (backgroundFlames[i].parent) { backgroundFlames[i].destroy(); } } backgroundFlames = []; // Clear smoke particles for (var i = smokeParticles.length - 1; i >= 0; i--) { if (smokeParticles[i].parent) { smokeParticles[i].destroy(); } } smokeParticles = []; flameSpawnTimer = 0; lightningTimer = 0; groundShakeTimer = 0; // Reset wave indicator waveIndicator.gameStarted = false; var startMarker = waveIndicator.waveMarkers[0]; if (startMarker && startMarker.children[0]) { startMarker.children[0].tint = 0x00AA00; } if (startMarker && startMarker.children[1]) { startMarker.children[1].setText("Start Game"); } if (startMarker && startMarker.children[2]) { startMarker.children[2].setText("Start Game"); } // Reset grid grid.pathFind(); grid.renderDebug(); // Update UI updateUI(); // Hide tower preview towerPreview.visible = false; var notification = game.addChild(new Notification("Game restarted!")); notification.x = 2048 / 2; notification.y = grid.height - 150; }; game.addChild(restartButton); 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; enemiesToSpawn = 10; game.update = function () { if (!gameStarted) { return; } 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; } // Spawn the appropriate number of enemies for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); // Play monster sound when enemy spawns LK.getSound('ron').play(); // Add enemy to the appropriate layer based on type if (enemy.isFlying) { // Add flying enemy to the top layer enemyLayerTop.addChild(enemy); // If it's a flying enemy, add its shadow to the middle layer if (enemy.shadow) { enemyLayerMiddle.addChild(enemy.shadow); } } else { // Add normal/ground enemies to the bottom layer enemyLayerBottom.addChild(enemy); } // Scale difficulty with wave number but don't apply to boss // as bosses already have their health multiplier // Use exponential scaling for health var healthMultiplier = Math.pow(1.12, currentWave); // ~20% increase per wave enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier); enemy.health = enemy.maxHealth; // Increment speed slightly with wave number //enemy.speed = enemy.speed + currentWave * 0.002; // All enemy types now spawn in the middle 6 tiles at the top spacing var gridWidth = 24; var midPoint = Math.floor(gridWidth / 2); // 12 // Find a column that isn't occupied by another enemy that's not yet in view var availableColumns = []; for (var col = midPoint - 3; col < midPoint + 3; col++) { var columnOccupied = false; // Check if any enemy is already in this column but not yet in view for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === col && enemies[e].currentCellY < 4) { columnOccupied = true; break; } } if (!columnOccupied) { availableColumns.push(col); } } // If all columns are occupied, use original random method var spawnX; if (availableColumns.length > 0) { // Choose a random unoccupied column spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)]; } else { // Fallback to random if all columns are occupied spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 9 to 14 } var spawnY = -1 - Math.random() * 5; // Random distance above the grid for spreading enemy.cellX = spawnX; enemy.cellY = 5; // Position after entry enemy.currentCellX = spawnX; enemy.currentCellY = spawnY; enemy.waveNumber = currentWave; enemies.push(enemy); } } var currentWaveEnemiesRemaining = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].waveNumber === currentWave) { currentWaveEnemiesRemaining = true; break; } } if (waveSpawned && !currentWaveEnemiesRemaining) { waveInProgress = false; waveSpawned = false; } } for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; if (enemy.health <= 0) { for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } // Boss enemies give more gold and score var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : 5; 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; // Increase XP by 10 when enemy is defeated xp += 10; // Check for level up if (xp >= 100 && level < 2) { level = 2; xp = 0; // Reset XP to 0 when reaching level 2 // Set attack interval to 0.5 seconds (30 frames at 60 FPS) for all towers for (var i = 0; i < towers.length; i++) { towers[i].fireRate = 30; } // Show level up notification var levelUpNotification = game.addChild(new Notification("LEVEL UP! Attack speed increased!")); levelUpNotification.x = 2048 / 2; levelUpNotification.y = 2732 / 2; levelUpNotification.fadeOutTime = 240; // 4 seconds } // Add a notification for boss defeat if (enemy.isBoss) { var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } updateUI(); // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); continue; } if (grid.updateEnemy(enemy)) { // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); lives = Math.max(0, lives - 1); updateUI(); if (lives <= 0) { LK.showGameOver(); } } } 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); } } // Clean up destroyed snakes for (var i = snakes.length - 1; i >= 0; i--) { if (!snakes[i].parent) { snakes.splice(i, 1); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } // Background flame spawning logic if (gameStarted) { flameSpawnTimer++; if (flameSpawnTimer >= flameSpawnInterval) { flameSpawnTimer = 0; flameSpawnInterval = 180 + Math.random() * 240; // Reset interval // Spawn a new flame at random position var flameX = 100 + Math.random() * (2048 - 200); // Avoid edges var flameY = 300 + Math.random() * (2200 - 300); // Avoid UI areas var flame = new BackgroundFlame(flameX, flameY); flameLayer.addChild(flame); backgroundFlames.push(flame); } // Clean up destroyed flames from array for (var i = backgroundFlames.length - 1; i >= 0; i--) { if (!backgroundFlames[i].parent) { backgroundFlames.splice(i, 1); } } } // Lightning effect every 15 seconds if (gameStarted) { lightningTimer++; if (lightningTimer >= lightningInterval) { lightningTimer = 0; // Play pat sound for explosion LK.getSound('pat').play(); // Play lightning sound LK.getSound('lightning').play(); // Create smoke particles from edges without dark overlay var smokeCount = 80; // Even more particles for realistic creeping fog var centerX = 1024; var centerY = 1366; // Create initial wave of smoke with more focused origin points (like fire sources) for (var i = 0; i < smokeCount; i++) { var side = Math.floor(Math.random() * 4); // 0: top, 1: right, 2: bottom, 3: left var startX, startY; // Create clusters of smoke from specific "burning" points var clusterOffset = Math.floor(i / 8) * 300; // Larger clusters switch (side) { case 0: // Top edge - create fire source clusters startX = (clusterOffset + Math.random() * 200) % 2048; startY = -200 - Math.random() * 100; break; case 1: // Right edge - create fire source clusters startX = 2248 + Math.random() * 100; startY = (clusterOffset + Math.random() * 200) % 2732; break; case 2: // Bottom edge - create fire source clusters startX = (clusterOffset + Math.random() * 200) % 2048; startY = 2932 + Math.random() * 100; break; case 3: // Left edge - create fire source clusters startX = -200 - Math.random() * 100; startY = (clusterOffset + Math.random() * 200) % 2732; break; } // Create smoke with varied delays for waves of fog LK.setTimeout(function (x, y) { return function () { // Vary the target point for more organic movement // Smoke creeps inward with strong upward bias var inwardAngle = Math.atan2(centerY - y, centerX - x); var spreadAngle = inwardAngle + (Math.random() - 0.5) * Math.PI * 0.5; var spreadDistance = 300 + Math.random() * 600; var targetX = x + Math.cos(spreadAngle) * spreadDistance; var targetY = y + Math.sin(spreadAngle) * spreadDistance - 200; // Strong upward bias var smoke = new SmokeParticle(x, y, targetX, targetY); smokeLayer.addChild(smoke); smokeParticles.push(smoke); }; }(startX, startY), Math.random() * 1200); // Staggered over 1200ms for more gradual effect } // Create second wave of smoke for continuous burning effect LK.setTimeout(function () { for (var i = 0; i < smokeCount * 0.75; i++) { var side = Math.floor(Math.random() * 4); var startX, startY; // More concentrated fire sources for second wave var fireSourceX = Math.floor(Math.random() * 5) * 400 + 200; // 5 main fire sources var fireSourceY = Math.floor(Math.random() * 5) * 546 + 273; switch (side) { case 0: startX = fireSourceX + (Math.random() - 0.5) * 250; startY = -150 - Math.random() * 100; break; case 1: startX = 2198 + Math.random() * 100; startY = fireSourceY + (Math.random() - 0.5) * 250; break; case 2: startX = fireSourceX + (Math.random() - 0.5) * 250; startY = 2882 + Math.random() * 100; break; case 3: startX = -150 - Math.random() * 100; startY = fireSourceY + (Math.random() - 0.5) * 250; break; } // Create more varied smoke movement patterns LK.setTimeout(function (x, y) { return function () { // Smoke rises and creeps in a more realistic pattern var riseAngle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 2; // Mostly upward with variation var creepDistance = 400 + Math.random() * 600; var targetX = x + Math.cos(riseAngle) * creepDistance * 0.6; var targetY = y + Math.sin(riseAngle) * creepDistance - 100; var smoke = new SmokeParticle(x, y, targetX, targetY); smokeLayer.addChild(smoke); smokeParticles.push(smoke); }; }(startX, startY), Math.random() * 800); } }, 800); } } // Ground shake effect every 20 seconds if (gameStarted) { groundShakeTimer++; if (groundShakeTimer >= groundShakeInterval) { groundShakeTimer = 0; // Shake the ground (game container) var shakeIntensity = 15; var originalX = game.x; var originalY = game.y; // Create shake sequence var _shakeSequence = function shakeSequence(count) { if (count <= 0) { // Return to original position tween(game, { x: originalX, y: originalY }, { duration: 100, easing: tween.easeOut }); return; } // Random shake offset var shakeX = originalX + (Math.random() - 0.5) * shakeIntensity; var shakeY = originalY + (Math.random() - 0.5) * shakeIntensity; tween(game, { x: shakeX, y: shakeY }, { duration: 50, easing: tween.easeInOut, onFinish: function onFinish() { _shakeSequence(count - 1); } }); }; // Start shake sequence with 8 shakes _shakeSequence(8); } } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -3417,9 +3417,9 @@
var bullets = [];
var defenses = [];
var snakes = [];
var selectedTower = null;
-var gold = 200;
+var gold = 130;
var lives = 5;
var score = 0;
var xp = 0;
var level = 1;
ışıklı büyü ışıltısı. In-Game asset. 2d. High contrast. No shadows
3 boyutlu alev
elinde asa olsun ve sinirli olsun
harry potter evreninde kötü bir yaratık. In-Game asset. 2d. High contrast. No shadows
harry potter temasında korkutucu bir yılan yeşil renkte. In-Game asset. 2d. High contrast. No shadows