User prompt
uçak savarı kaldır
User prompt
oyunda aşağıda normal fast modları gözüküyor bunların hepsini sil gözükmesin sadece started kalsın
User prompt
yerden gelen düşmanlar 185 piksel sağda doğsunlar
User prompt
ilk flying gelsin
User prompt
menzil alanını büyüt ve biraz sık sık ateş etsinler nerdeyse hiç atmıyorlar
User prompt
ilk flying gelsin
User prompt
uçaklar tanklara belli aralıklarda ateş etsin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ilk flying gelsin
User prompt
Flying için bunu yap Bu özel düşman birimi, diğer hava düşmanlarının aksine arenada yalnızca hedefe ulaşmaya çalışmaz. *B-2 Spirit*, haritada belirli bir dairesel rota çizer ve bu rota boyunca ilerlerken altındaki dost birliklere (tanklara) yüksek hasar veren bombalar bırakır. - *Hedef*: Sona ulaşmak değil, alandaki tanklara sürekli hasar vermek - *Hareket*: Daire veya oval şeklinde sabit bir rota izler - *Saldırı Mekaniği*: Belirli aralıklarla altındaki bölgeye bomba bırakır Bu birim, oyuncunun savunma düzenini bozmak ve tankları zayıflatmak için tasarlanmış özel bir tehdittir. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
flying ilk gelsin
User prompt
flying mapin ortasında büyük çenberler atsın ve həzlı olsun biraz dona dona geziyorlar ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) { var self = Container.call(this); self.targetEnemy = targetEnemy; self.damage = damage || 10; self.speed = speed || 5; self.x = startX; self.y = startY; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { 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) { // Check if this is an enemy bullet targeting a tower if (self.type === 'Dusmannamlu' && self.targetEnemy instanceof Tower) { // Enemy bullet hitting a tower - deal 5 damage var tower = self.targetEnemy; // Initialize tower health if not set if (!tower.health) { tower.health = tower.maxHealth || 100; } if (!tower.maxHealth) { tower.maxHealth = 100; } // Deal 5 damage to the tower tower.health -= 5; if (tower.health <= 0) { tower.health = 0; // Tower is destroyed - remove it completely var towerIndex = towers.indexOf(tower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } // Clear tower from grid cells for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(tower.gridX + i, tower.gridY + j); if (cell) { cell.type = 0; var towerIndexInCell = cell.towersInRange.indexOf(tower); if (towerIndexInCell !== -1) { cell.towersInRange.splice(towerIndexInCell, 1); } } } } // Remove tower from layer towerLayer.removeChild(tower); // Clear selected tower if it was this one if (selectedTower === tower) { selectedTower = null; } // Refresh pathfinding grid.pathFind(); grid.renderDebug(); } self.destroy(); return; } // Apply damage to target enemy self.targetEnemy.health -= self.damage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } else if (self.targetEnemy.healthBar) { self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; } // Apply special effects based on bullet type if (self.type === 'tank4') { // 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 if (otherEnemy.healthBar) { 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 }); cellGraphics.tint = Math.random() * 0xffffff; var debugArrows = []; var numberLabel = new Text2('0', { size: 30, fill: 0xFFFFFF, weight: 800 }); numberLabel.anchor.set(.5, .5); self.addChild(numberLabel); self.update = function () {}; self.down = function () { return; if (self.cell.type == 0 || self.cell.type == 1) { self.cell.type = self.cell.type == 1 ? 0 : 1; if (grid.pathFind()) { self.cell.type = self.cell.type == 1 ? 0 : 1; grid.pathFind(); var notification = game.addChild(new Notification("Path is blocked!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } grid.renderDebug(); } }; self.removeArrows = function () { while (debugArrows.length) { self.removeChild(debugArrows.pop()); } }; self.render = function (data) { switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); cellGraphics.tint = 0x880000; return; } numberLabel.visible = true; var tint = Math.floor(data.score / maxScore * 0x88); var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; cellGraphics.tint = 0x0088ff; } else { cellGraphics.tint = 0x88 - tint << 8 | tint; } while (debugArrows.length > data.targets.length) { self.removeChild(debugArrows.pop()); } for (var a = 0; a < data.targets.length; a++) { var destination = data.targets[a]; var ox = destination.x - data.x; var oy = destination.y - data.y; var angle = Math.atan2(oy, ox); if (!debugArrows[a]) { debugArrows[a] = LK.getAsset('arrow', { anchorX: -.5, anchorY: 0.5 }); debugArrows[a].alpha = .5; self.addChildAt(debugArrows[a], 1); } debugArrows[a].rotation = angle; } break; } case 1: { self.removeArrows(); cellGraphics.tint = 0xaaaaaa; numberLabel.visible = false; break; } case 3: { self.removeArrows(); cellGraphics.tint = 0x008800; numberLabel.visible = false; break; } } numberLabel.setText(Math.floor(data.score / 1000) / 10); }; }); // This update method was incorrectly placed here and should be removed var EffectIndicator = Container.expand(function (x, y, type) { var self = Container.call(this); self.x = x; self.y = y; var effectGraphics = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.blendMode = 1; switch (type) { case 'splash': effectGraphics.tint = 0x33CC00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5; break; case 'slow': effectGraphics.tint = 0x9900FF; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'poison': effectGraphics.tint = 0x00FFAA; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'sniper': effectGraphics.tint = 0xFF5500; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'bomb': effectGraphics.tint = 0xFF4500; effectGraphics.width = effectGraphics.height = CELL_SIZE * 2; // Larger bomb effect break; } effectGraphics.alpha = 0.7; self.alpha = 0; // Animate the effect tween(self, { alpha: 0.8, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); // Base enemy class for common functionality var Enemy = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'normal'; self.speed = .02; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 100; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.isFlying = false; self.isImmune = false; self.isBoss = false; // Special bombing pattern properties for flying enemies self.circularPath = null; self.pathAngle = 0; self.bombCooldown = 0; self.bombRate = 60; // Bomb every 1 second (reduced from 180) // Check if this is a boss wave // Check if this is a boss wave // Apply different stats based on enemy type switch (self.type) { case 'fast': self.speed *= 2; // Twice as fast self.maxHealth = 100; break; case 'immune': self.isImmune = true; self.maxHealth = 80; break; case 'flying': self.isFlying = true; self.maxHealth = 80; // Initialize circular bombing path self.circularPath = { centerX: 6, // Center of the arena in grid coordinates centerY: 12, radius: 6, // Radius in grid cells clockwise: Math.random() > 0.5 // Random direction }; self.pathAngle = Math.random() * Math.PI * 2; // Start at random angle break; case 'swarm': self.maxHealth = 50; // Weaker enemies break; case 'normal': default: // Normal enemy uses default values break; } if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') { self.isBoss = true; // Boss enemies have 20x health and are larger self.maxHealth *= 20; // Slower speed for bosses self.speed = self.speed * 0.7; } self.health = self.maxHealth; // Get appropriate asset for this enemy type var assetId = 'enemy'; if (self.type !== 'normal') { assetId = 'enemy_' + self.type; } var enemyGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Scale up boss enemies if (self.isBoss) { enemyGraphics.scaleX = 1.8; enemyGraphics.scaleY = 1.8; } // For immune enemies, show both enemy_immune graphics and Dusmannamlu on top if (self.type === 'immune') { // Keep the enemy_immune graphics visible as the base and make it larger enemyGraphics.visible = true; enemyGraphics.scaleX = 1.5; enemyGraphics.scaleY = 1.5; // Add Dusmannamlu graphics on top of the enemy_immune graphics var barrelGraphics = self.attachAsset('Dusmannamlu', { anchorX: 0.5, anchorY: 0.5 }); barrelGraphics.scaleX = 0.3; barrelGraphics.scaleY = 0.3; // Position Dusmannamlu slightly above the enemy_immune graphics barrelGraphics.y = -20; // Store reference to barrel graphics for firing logic self.barrelGraphics = barrelGraphics; } // Fall back to regular enemy asset if specific type asset not found // Apply tint to differentiate enemy types /*switch (self.type) { case 'fast': enemyGraphics.tint = 0x00AAFF; // Blue for fast enemies break; case 'immune': enemyGraphics.tint = 0xAA0000; // Red for immune enemies break; case 'flying': enemyGraphics.tint = 0xFFFF00; // Yellow for flying enemies break; case 'swarm': enemyGraphics.tint = 0xFF00FF; // Pink for swarm enemies break; }*/ // Create shadow for flying enemies if (self.isFlying) { // Create a shadow container that will be added to the shadow layer self.shadow = new Container(); // For immune enemies, use Dusmannamlu for shadow, otherwise use the regular asset var shadowAssetId = self.type === 'immune' ? 'Dusmannamlu' : assetId || 'enemy'; // Use the same asset ID for shadow as main enemy var shadowGraphics = self.shadow.attachAsset(shadowAssetId, { 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; } // If this is an immune enemy, scale the shadow to match Dusmannamlu size if (self.type === 'immune') { shadowGraphics.scaleX = 0.3; shadowGraphics.scaleY = 0.3; } // 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 if (self.type === 'immune' && self.barrelGraphics) { shadowGraphics.rotation = self.barrelGraphics.rotation; } else { 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; } // Immune enemies can fire at towers if (!self.lastFired) self.lastFired = 0; if (!self.fireRate) self.fireRate = 120; // Fire every 2 seconds if (!self.damage) self.damage = 5; if (!self.bulletSpeed) self.bulletSpeed = 6; if (!self.range) self.range = 4 * CELL_SIZE; // Find and target nearest tower if (LK.ticks - self.lastFired >= self.fireRate) { var closestTower = null; var closestDistance = self.range; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx = tower.x - self.x; var dy = tower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestTower = tower; } } if (closestTower) { // Calculate bullet spawn position from the Dusmannamlu barrel tip var barrelLength = 45; // Distance from center to barrel tip for Dusmannamlu var dx = closestTower.x - self.x; var dy = closestTower.y - self.y; var angle = Math.atan2(dy, dx); // Rotate the Dusmannamlu barrel to face the target if (self.barrelGraphics) { self.barrelGraphics.rotation = angle; } var bulletX = self.x + Math.cos(angle) * barrelLength; var bulletY = self.y + Math.sin(angle) * barrelLength; // Create bullet targeting the tower var bullet = new Bullet(bulletX, bulletY, closestTower, self.damage, self.bulletSpeed); bullet.type = 'Dusmannamlu'; bullet.children[0].tint = 0xFF4500; // Orange-red fire ball color for immune enemies bullet.children[0].width = 35; // Larger size for fire balls bullet.children[0].height = 35; // Larger size for fire balls game.addChild(bullet); bullets.push(bullet); self.lastFired = LK.ticks; } } } else { // Handle slow effect if (self.slowed) { // Visual indication of slowed status if (!self.slowEffect) { self.slowEffect = true; } self.slowDuration--; if (self.slowDuration <= 0) { self.speed = self.originalSpeed; self.slowed = false; self.slowEffect = false; // Only reset tint if not poisoned if (!self.poisoned) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } // Handle poison effect if (self.poisoned) { // Visual indication of poisoned status if (!self.poisonEffect) { self.poisonEffect = true; } // Apply poison damage every 30 frames (twice per second) if (LK.ticks % 30 === 0) { self.health -= self.poisonDamage; if (self.health <= 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; } self.poisonDuration--; if (self.poisonDuration <= 0) { self.poisoned = false; self.poisonEffect = false; // Only reset tint if not slowed if (!self.slowed) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } } // Set tint based on effect status if (self.isImmune) { enemyGraphics.tint = 0xFFFFFF; } else if (self.poisoned && self.slowed) { // Combine poison (0x00FFAA) and slow (0x9900FF) colors // Simple average: R: (0+153)/2=76, G: (255+0)/2=127, B: (170+255)/2=212 enemyGraphics.tint = 0x4C7FD4; } else if (self.poisoned) { enemyGraphics.tint = 0x00FFAA; } else if (self.slowed) { enemyGraphics.tint = 0x9900FF; } else { enemyGraphics.tint = 0xFFFFFF; } if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; if (ox !== 0 || oy !== 0) { var angle = Math.atan2(oy, ox); if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) { tween.stop(enemyGraphics, { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemyGraphics.rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemyGraphics.targetRotation = angle; tween(enemyGraphics, { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } } } healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; }; return self; }); var 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 && 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 follow circular bombing pattern if (enemy.circularPath) { // Update angle for circular movement var angleSpeed = enemy.speed * 0.3; // Slower circular movement if (enemy.circularPath.clockwise) { enemy.pathAngle += angleSpeed; } else { enemy.pathAngle -= angleSpeed; } // Keep angle in 0-2π range if (enemy.pathAngle > Math.PI * 2) enemy.pathAngle -= Math.PI * 2; if (enemy.pathAngle < 0) enemy.pathAngle += Math.PI * 2; // Calculate position on circular path enemy.currentCellX = enemy.circularPath.centerX + Math.cos(enemy.pathAngle) * enemy.circularPath.radius; enemy.currentCellY = enemy.circularPath.centerY + Math.sin(enemy.pathAngle) * enemy.circularPath.radius; // Update visual position enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // Update grid position for targeting enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); // Rotate enemy to face movement direction var movementAngle = enemy.pathAngle + (enemy.circularPath.clockwise ? Math.PI / 2 : -Math.PI / 2); if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = movementAngle; enemy.children[0].rotation = movementAngle; } else if (enemy.children[0]) { if (Math.abs(movementAngle - 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 = movementAngle - 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 = movementAngle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Flying enemy projectile firing mechanism if (enemy.bombCooldown <= 0) { // Find towers within firing range var fireRange = CELL_SIZE * 6; // 6 cell radius for targeting (doubled from 3) var towersInRange = []; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx = tower.x - enemy.x; var dy = tower.y - enemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= fireRange) { towersInRange.push({ tower: tower, distance: distance }); } } // Fire at closest tower if any are in range if (towersInRange.length > 0) { // Sort by distance to find closest tower towersInRange.sort(function (a, b) { return a.distance - b.distance; }); var targetTower = towersInRange[0].tower; // Calculate bullet spawn position from enemy center var bulletX = enemy.x; var bulletY = enemy.y; // Create bullet targeting the tower var damage = enemy.isBoss ? 15 : 8; // Boss flying enemies do more damage var bulletSpeed = 4; var bullet = new Bullet(bulletX, bulletY, targetTower, damage, bulletSpeed); bullet.type = 'enemy_flying_projectile'; bullet.children[0].tint = 0xFF6600; // Orange color for flying enemy bullets bullet.children[0].width = 25; bullet.children[0].height = 25; game.addChild(bullet); bullets.push(bullet); enemy.bombCooldown = enemy.bombRate; // Reset cooldown } } else { enemy.bombCooldown--; // Decrease cooldown } } else { // If no circular path defined, fall back to direct goal movement if (!enemy.flyingTarget) { enemy.flyingTarget = self.goals[0]; } 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) { return true; } 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; enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); } return false; } // Handle ground enemies - follow the road path // Initialize path waypoints if not set if (!enemy.pathWaypoints) { // Convert block coordinates to world coordinates for the road path var blockSize = 185; var offsetX = blockSize / 2; var offsetY = blockSize / 2 + 185 + 270; enemy.pathWaypoints = [ // Start at top of road at block 2 { x: 2 * blockSize + offsetX, y: offsetY }, // Go to block 2 in row 2 { x: 2 * blockSize + offsetX, y: offsetY + blockSize }, // Go to block 9 in row 2 { x: 9 * blockSize + offsetX, y: offsetY + blockSize }, // Go to block 9 in row 3 { x: 9 * blockSize + offsetX, y: offsetY + 2 * blockSize }, // Go to block 9 in row 4 { x: 9 * blockSize + offsetX, y: offsetY + 3 * blockSize }, // Go to block 3 in row 4 { x: 3 * blockSize + offsetX, y: offsetY + 3 * blockSize }, // Go to block 3 in row 5 { x: 3 * blockSize + offsetX, y: offsetY + 4 * blockSize }, // Go to block 3 in row 6 { x: 3 * blockSize + offsetX, y: offsetY + 5 * blockSize }, // Go to block 3 in row 7 { x: 3 * blockSize + offsetX, y: offsetY + 6 * blockSize }, // Go to block 9 in row 7 { x: 9 * blockSize + offsetX, y: offsetY + 6 * blockSize }, // Go to block 9 in row 8 { x: 9 * blockSize + offsetX, y: offsetY + 7 * blockSize }, // Go to block 9 in row 9 { x: 9 * blockSize + offsetX, y: offsetY + 8 * blockSize }, // Go to block 5 in row 9 (28th road block) { x: 5 * blockSize + offsetX, y: offsetY + 8 * blockSize }, // Go down to block 5 in row 10 (33rd road block) { x: 5 * blockSize + offsetX, y: offsetY + 9 * blockSize }]; enemy.currentWaypointIndex = 0; enemy.pathX = enemy.pathWaypoints[0].x; enemy.pathY = enemy.pathWaypoints[0].y; } // Get current waypoint var currentWaypoint = enemy.pathWaypoints[enemy.currentWaypointIndex]; if (currentWaypoint) { // Calculate distance to current waypoint var dx = currentWaypoint.x - enemy.pathX; var dy = currentWaypoint.y - enemy.pathY; var distToWaypoint = Math.sqrt(dx * dx + dy * dy); // If close enough to waypoint, move to next one if (distToWaypoint < enemy.speed * CELL_SIZE) { enemy.currentWaypointIndex++; if (enemy.currentWaypointIndex >= enemy.pathWaypoints.length) { // Reached the end of the path return true; } currentWaypoint = enemy.pathWaypoints[enemy.currentWaypointIndex]; if (currentWaypoint) { dx = currentWaypoint.x - enemy.pathX; dy = currentWaypoint.y - enemy.pathY; distToWaypoint = Math.sqrt(dx * dx + dy * dy); } } // Move towards current waypoint if (distToWaypoint > 0) { var moveDistance = enemy.speed * CELL_SIZE; var angle = Math.atan2(dy, dx); enemy.pathX += Math.cos(angle) * moveDistance; enemy.pathY += Math.sin(angle) * moveDistance; // 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 enemy visual position enemy.x = enemy.pathX; enemy.y = enemy.pathY; // Update grid position for targeting enemy.currentCellX = (enemy.pathX - grid.x) / CELL_SIZE; enemy.currentCellY = (enemy.pathY - grid.y) / CELL_SIZE; enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); return false; }; }); var NextWaveButton = Container.expand(function () { var self = Container.call(this); var buttonBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 300; buttonBackground.height = 100; buttonBackground.tint = 0x0088FF; var buttonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.enabled = false; self.visible = false; self.update = function () { if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) { self.enabled = true; self.visible = true; buttonBackground.tint = 0x0088FF; self.alpha = 1; } else { self.enabled = false; self.visible = false; buttonBackground.tint = 0x888888; self.alpha = 0.7; } }; self.down = function () { if (!self.enabled) { return; } if (waveIndicator.gameStarted && currentWave < totalWaves) { currentWave++; // Increment to the next wave directly waveTimer = 0; // Reset wave timer waveInProgress = true; waveSpawned = false; // Get the type of the current wave (which is now the next wave) var waveType = waveIndicator.getWaveTypeName(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 30; self.addChild(notificationText); self.alpha = 1; var fadeOutTime = 120; self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; // Increase size of base for easier touch var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5, width: 185, height: 185 }); switch (self.towerType) { case 'rapid': baseGraphics.tint = 0x00AAFF; break; case 'sniper': baseGraphics.tint = 0xFF5500; break; case 'tank4': baseGraphics.tint = 0x33CC00; break; case 'slow': baseGraphics.tint = 0x9900FF; break; case 'poison': baseGraphics.tint = 0x00FFAA; break; case 'antiair': baseGraphics.tint = 0xFFAA00; break; default: baseGraphics.tint = 0xAAAAAA; } var towerCost = getTowerCost(self.towerType); // Add shadow for tower type label var typeLabelShadow = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), { size: 50, fill: 0x000000, weight: 800 }); typeLabelShadow.anchor.set(0.5, 0.5); typeLabelShadow.x = 4; typeLabelShadow.y = -20 + 4; self.addChild(typeLabelShadow); // Add tower type label var typeLabel = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), { size: 50, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.y = -20; // Position above center of tower self.addChild(typeLabel); // Add cost shadow var costLabelShadow = new Text2(towerCost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; costLabelShadow.y = 24 + 12; self.addChild(costLabelShadow); // Add cost label var costLabel = new Text2(towerCost, { size: 50, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.y = 20 + 12; self.addChild(costLabel); self.update = function () { // Check if player can afford this tower var canAfford = gold >= getTowerCost(self.towerType); // Set opacity based on affordability self.alpha = canAfford ? 1 : 0.5; }; return self; }); var Tower = Container.expand(function (id) { var self = Container.call(this); self.id = id || 'default'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.range = 3 * CELL_SIZE; // Standardized method to get the current range of the tower self.getRange = function () { // Always calculate range based on tower type and level switch (self.id) { case 'sniper': // Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost if (self.level === self.maxLevel) { return 12 * CELL_SIZE; // Significantly increased range for max level } return (5 + (self.level - 1) * 0.8) * CELL_SIZE; case 'tank4': // Tank4: base 2, +0.2 per level (max ~4 blocks at max level) return (2 + (self.level - 1) * 0.2) * CELL_SIZE; case 'rapid': // Rapid: base 2.5, +0.5 per level return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'slow': // Slow: base 3.5, +0.5 per level return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'poison': // Poison: base 3.2, +0.5 per level return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; case 'antiair': // Anti-air: base 4.5, +0.7 per level (long range for air targets) return (4.5 + (self.level - 1) * 0.7) * 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 = 10; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; // Initialize tower health self.maxHealth = 100; self.health = self.maxHealth; // Create health bar components (initially hidden) 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 }); // Health bar positioning will be done after baseGraphics is defined healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; // Store reference to health bar self.healthBar = healthBar; self.healthBarOutline = healthBarOutline; self.healthBarBG = healthBarBG; // Initially hide health bars (show only when damaged) healthBarOutline.visible = false; healthBarBG.visible = false; healthBar.visible = false; switch (self.id) { case 'rapid': self.fireRate = 30; self.damage = 5; self.range = 2.5 * CELL_SIZE; self.bulletSpeed = 14; break; case 'sniper': self.fireRate = 90; self.damage = 25; self.range = 5 * CELL_SIZE; self.bulletSpeed = 50; break; case 'tank4': self.fireRate = 75; self.damage = 15; self.range = 2 * CELL_SIZE; self.bulletSpeed = 16; break; case 'slow': self.fireRate = 50; self.damage = 8; self.range = 3.5 * CELL_SIZE; self.bulletSpeed = 10; break; case 'poison': self.fireRate = 70; self.damage = 12; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 10; break; case 'antiair': self.fireRate = 45; self.damage = 20; self.range = 4.5 * CELL_SIZE; self.bulletSpeed = 8; break; } var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5, width: 185, height: 185 }); // Hide the base graphics so only the tank graphics are visible baseGraphics.visible = false; // Position health bars above the tower (now that baseGraphics is defined) healthBarOutline.y = healthBarBG.y = healthBar.y = -baseGraphics.height / 2 - 20; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; switch (self.id) { case 'rapid': baseGraphics.tint = 0x00AAFF; break; case 'sniper': baseGraphics.tint = 0xFF5500; break; case 'tank4': baseGraphics.tint = 0x33CC00; break; case 'slow': baseGraphics.tint = 0x9900FF; break; case 'poison': baseGraphics.tint = 0x00FFAA; break; case 'antiair': baseGraphics.tint = 0xFFAA00; break; default: baseGraphics.tint = 0xAAAAAA; } // Create tank base (stays stationary) - use tank1 for rapid towers, tank2 for sniper towers, tank3 for slow towers, tank4 for tank4 towers var assetToUse = self.id === 'rapid' ? 'tank1' : self.id === 'sniper' ? 'tank2' : self.id === 'slow' ? 'tank3' : self.id === 'tank4' ? 'tank4' : self.id === 'poison' ? 'tank5' : 'tank'; var gunGraphics = self.attachAsset(assetToUse, { anchorX: 0.5, anchorY: 0.5, width: 165, height: 165 }); // Create separate barrel container that will rotate var barrelContainer = new Container(); self.addChild(barrelContainer); // Add barrel (namlu) that rotates independently - use namlu1 for rapid towers, namlu2 for sniper towers, namlu3 for slow towers, namlu4 for tank4 towers var barrelAssetToUse = self.id === 'rapid' ? 'Namlu1' : self.id === 'sniper' ? 'Namlu2' : self.id === 'slow' ? 'Namlu3' : self.id === 'tank4' ? 'namlu4' : self.id === 'poison' ? 'Namlu5' : 'Namlu'; var barrelGraphics = barrelContainer.attachAsset(barrelAssetToUse, { anchorX: 0.5, anchorY: 0.5, width: self.id === 'rapid' ? 175 : self.id === 'sniper' ? 150 : self.id === 'slow' ? 200 : self.id === 'tank4' ? 250 : self.id === 'poison' ? 180 : 140, height: self.id === 'rapid' ? 175 : self.id === 'sniper' ? 150 : self.id === 'slow' ? 200 : self.id === 'tank4' ? 250 : self.id === 'poison' ? 180 : 140 }); // Move namlu2 barrel up for sniper towers if (self.id === 'sniper') { barrelGraphics.y = -20; // Move namlu2 up by 20 pixels } // Move namlu3 barrel up for slow towers if (self.id === 'slow') { barrelGraphics.y = -30; // Move namlu3 up by 30 pixels } // Keep namlu1 at default position for rapid towers (no upward offset) if (self.id === 'rapid') { barrelGraphics.y = 0; // Keep namlu1 at center position } // Position namlu4 barrel for tank4 towers if (self.id === 'tank4') { barrelGraphics.y = -10; // Move namlu4 up by 10 pixels } // Position namlu5 barrel for poison towers if (self.id === 'poison') { barrelGraphics.y = -15; // Move namlu5 up by 15 pixels } 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(); 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()) { // Anti-air towers only target flying enemies if (self.id === 'antiair' && !enemy.isFlying) { continue; // Skip non-flying enemies for anti-air towers } // 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 health bar visibility and appearance if (self.health < self.maxHealth) { // Show health bars when damaged if (self.healthBarOutline) self.healthBarOutline.visible = true; if (self.healthBarBG) self.healthBarBG.visible = true; if (self.healthBar) { self.healthBar.visible = true; // Update health bar width based on current health self.healthBar.width = self.health / self.maxHealth * 70; } } else { // Hide health bars when at full health if (self.healthBarOutline) self.healthBarOutline.visible = false; if (self.healthBarBG) self.healthBarBG.visible = false; if (self.healthBar) self.healthBar.visible = false; } 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); // Orient the top of the barrel graphic toward the target var barrelAngle = angle - Math.PI / 2; // Top of barrel points toward target // Smoothly rotate only the barrel container towards target if (barrelContainer.targetRotation === undefined) { barrelContainer.targetRotation = barrelAngle; barrelContainer.rotation = barrelAngle; } else { if (Math.abs(barrelAngle - barrelContainer.targetRotation) > 0.05) { tween.stop(barrelContainer, { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = barrelContainer.rotation; var angleDiff = barrelAngle - 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; } barrelContainer.targetRotation = barrelAngle; tween(barrelContainer, { rotation: currentRotation + angleDiff }, { duration: 200, easing: tween.easeOut }); } } if (LK.ticks - self.lastFired >= self.fireRate) { self.fire(); self.lastFired = LK.ticks; } } }; self.down = function (x, y, obj) { var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); var hasOwnMenu = false; var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self) { rangeCircle = game.children[i]; break; } } for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hasOwnMenu = true; break; } } if (hasOwnMenu) { for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } if (rangeCircle) { game.removeChild(rangeCircle); } selectedTower = null; grid.renderDebug(); return; } for (var i = 0; i < existingMenus.length; i++) { existingMenus[i].destroy(); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = self; var rangeIndicator = new Container(); rangeIndicator.isTowerRange = true; rangeIndicator.tower = self; game.addChild(rangeIndicator); rangeIndicator.x = self.x; rangeIndicator.y = self.y; var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.getRange() * 2; rangeGraphics.alpha = 0.3; var upgradeMenu = new UpgradeMenu(self); game.addChild(upgradeMenu); upgradeMenu.x = 2048 / 2; tween(upgradeMenu, { y: 2732 - 225 }, { duration: 200, easing: tween.backOut }); grid.renderDebug(); }; self.isInRange = function (enemy) { if (!enemy) { return false; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance <= self.getRange(); }; self.fire = function () { if (self.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) { // Calculate bullet spawn position from the top center of the barrel var barrelLength = 60; // Distance from center to barrel tip var actualBarrelAngle = barrelContainer.rotation + Math.PI / 2; // Use barrel angle to spawn from top var bulletX = self.x + Math.cos(actualBarrelAngle) * barrelLength; var bulletY = self.y + Math.sin(actualBarrelAngle) * barrelLength; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); // Set bullet type based on tower type bullet.type = self.id; // For slow tower, pass level for scaling slow effect if (self.id === 'slow') { bullet.sourceTowerLevel = self.level; } // Customize bullet appearance based on tower type switch (self.id) { case 'rapid': bullet.children[0].tint = 0x00AAFF; bullet.children[0].width = 20; bullet.children[0].height = 20; break; case 'sniper': bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; case 'tank4': bullet.children[0].tint = 0x33CC00; bullet.children[0].width = 40; bullet.children[0].height = 40; break; case 'slow': bullet.children[0].tint = 0x9900FF; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'poison': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'antiair': bullet.children[0].tint = 0xFFAA00; bullet.children[0].width = 25; bullet.children[0].height = 25; break; } game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); // --- Fire recoil effect for barrelContainer --- // Stop any ongoing recoil tweens before starting a new one tween.stop(barrelContainer, { x: true, y: true, scaleX: true, scaleY: true }); // Always use the original resting position for recoil, never accumulate offset if (barrelContainer._restX === undefined) { barrelContainer._restX = 0; } if (barrelContainer._restY === undefined) { barrelContainer._restY = 0; } if (barrelContainer._restScaleX === undefined) { barrelContainer._restScaleX = 1; } if (barrelContainer._restScaleY === undefined) { barrelContainer._restScaleY = 1; } // Reset to resting position before animating (in case of interrupted tweens) barrelContainer.x = barrelContainer._restX; barrelContainer.y = barrelContainer._restY; barrelContainer.scaleX = barrelContainer._restScaleX; barrelContainer.scaleY = barrelContainer._restScaleY; // Calculate recoil offset (recoil back from the top of the barrel) var recoilDistance = 8; var actualBarrelAngle = barrelContainer.rotation + Math.PI / 2; // Use barrel angle for recoil var recoilX = -Math.cos(actualBarrelAngle) * recoilDistance; var recoilY = -Math.sin(actualBarrelAngle) * recoilDistance; // Animate recoil back from the resting position tween(barrelContainer, { x: barrelContainer._restX + recoilX, y: barrelContainer._restY + recoilY }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { // Animate return to original position/scale tween(barrelContainer, { x: barrelContainer._restX, y: barrelContainer._restY }, { duration: 90, easing: tween.cubicIn }); } }); } } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 1; } } } self.refreshCellsInRange(); }; return self; }); var TowerPreview = Container.expand(function () { var self = Container.call(this); var towerRange = 3; var rangeInPixels = towerRange * CELL_SIZE; self.towerType = 'default'; self.hasEnoughGold = true; var rangeIndicator = new Container(); self.addChild(rangeIndicator); var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.alpha = 0.3; var previewGraphics = self.attachAsset('towerpreview', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = 185; previewGraphics.height = 185; self.canPlace = false; self.gridX = 0; self.gridY = 0; self.blockedByEnemy = false; self.update = function () { var previousHasEnoughGold = self.hasEnoughGold; self.hasEnoughGold = gold >= getTowerCost(self.towerType); // Only update appearance if the affordability status has changed if (previousHasEnoughGold !== self.hasEnoughGold) { self.updateAppearance(); } }; self.updateAppearance = function () { // Use Tower class to get the source of truth for range var tempTower = new Tower(self.towerType); var previewRange = tempTower.getRange(); // Clean up tempTower to avoid memory leaks if (tempTower && tempTower.destroy) { tempTower.destroy(); } // Set range indicator using unified range logic rangeGraphics.width = rangeGraphics.height = previewRange * 2; switch (self.towerType) { case 'rapid': previewGraphics.tint = 0x00AAFF; break; case 'sniper': previewGraphics.tint = 0xFF5500; break; case 'tank4': previewGraphics.tint = 0x33CC00; break; case 'slow': previewGraphics.tint = 0x9900FF; break; case 'poison': previewGraphics.tint = 0x00FFAA; break; case 'antiair': previewGraphics.tint = 0xFFAA00; break; default: previewGraphics.tint = 0xAAAAAA; } if (!self.canPlace || !self.hasEnoughGold) { previewGraphics.tint = 0xFF0000; } }; self.updatePlacementStatus = function () { var validGridPlacement = true; // Check if position is invalid (on a block) if (self.gridX < 0 || self.gridY < 0) { validGridPlacement = false; } else if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) { // Allow placement in 10 rows (gridY from 5 to 14) if (self.gridY < 5 || self.gridY > 14) { validGridPlacement = false; } } else { // Check if tower would overlap with any road blocks (not grass blocks) var blockSize = 185; var towerWorldX = grid.x + self.gridX * CELL_SIZE; var towerWorldY = grid.y + self.gridY * CELL_SIZE; // Check all 4 corners of the 2x2 tower area for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var checkX = towerWorldX + i * CELL_SIZE; var checkY = towerWorldY + j * CELL_SIZE; // Convert to block grid coordinates var blockGridX = Math.floor(checkX / blockSize); var blockGridY = Math.floor((checkY - 185 - 270) / blockSize); // Check if this position overlaps with a road block (not grass block) var isRoadBlock = false; if (blockGridX >= 0 && blockGridX <= 12 && blockGridY >= -1 && blockGridY <= 9) { // Check specific road block positions based on the road pattern if (blockGridY === -1) isRoadBlock = true; // Road blocks above first row: all positions blocked if (blockGridY === 0 && blockGridX === 2) isRoadBlock = true; // Row 1: block 2 if (blockGridY === 1 && blockGridX >= 2 && blockGridX <= 9) isRoadBlock = true; // Row 2: blocks 2-9 if (blockGridY === 2 && blockGridX === 9) isRoadBlock = true; // Row 3: block 9 if (blockGridY === 3 && blockGridX >= 3 && blockGridX <= 9) isRoadBlock = true; // Row 4: blocks 3-9 if (blockGridY === 4 && blockGridX === 3) isRoadBlock = true; // Row 5: block 3 if (blockGridY === 5 && blockGridX === 3) isRoadBlock = true; // Row 6: block 3 if (blockGridY === 6 && blockGridX >= 3 && blockGridX <= 9) isRoadBlock = true; // Row 7: blocks 3-9 if (blockGridY === 7 && blockGridX === 9) isRoadBlock = true; // Row 8: block 9 if (blockGridY === 8 && blockGridX >= 5 && blockGridX <= 8) isRoadBlock = true; // Row 9: blocks 5,6,7,8 (excluding block 4 to allow tank placement) // Row 10: no road blocks, all positions available for towers } if (isRoadBlock) { validGridPlacement = false; break; } 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) { // Convert to block grid coordinates (185x185 blocks) var blockSize = 185; var blockGridX = Math.floor(x / blockSize); var blockGridY = Math.floor((y - 185 - 270) / blockSize); // Account for offset // Check if trying to place on a road block position (not grass block) var isOnRoadBlock = false; // Expand validation to allow 10 rows of tower placement if (blockGridX >= 0 && blockGridX <= 12 && blockGridY >= -1 && blockGridY <= 14) { // Check specific road block positions based on the road pattern if (blockGridY === -1) isOnRoadBlock = true; // Road blocks above first row: all positions blocked if (blockGridY === 0 && blockGridX === 2) isOnRoadBlock = true; // Row 1: block 2 if (blockGridY === 1 && blockGridX >= 2 && blockGridX <= 9) isOnRoadBlock = true; // Row 2: blocks 2-9 if (blockGridY === 2 && blockGridX === 9) isOnRoadBlock = true; // Row 3: block 9 if (blockGridY === 3 && blockGridX >= 3 && blockGridX <= 9) isOnRoadBlock = true; // Row 4: blocks 3-9 if (blockGridY === 4 && blockGridX === 3) isOnRoadBlock = true; // Row 5: block 3 if (blockGridY === 5 && blockGridX === 3) isOnRoadBlock = true; // Row 6: block 3 if (blockGridY === 6 && blockGridX >= 3 && blockGridX <= 9) isOnRoadBlock = true; // Row 7: blocks 3-9 if (blockGridY === 7 && blockGridX === 9) isOnRoadBlock = true; // Row 8: block 9 if (blockGridY === 8 && blockGridX >= 5 && blockGridX <= 8) isOnRoadBlock = true; // Row 9: blocks 5,6,7,8 (excluding block 4 to allow tank placement) // Row 10: no road blocks, all positions available for towers } if (isOnRoadBlock) { // If on a road block, reject placement self.canPlace = false; self.gridX = -1; self.gridY = -1; self.x = x; self.y = y; self.updateAppearance(); return; } // Use original grid logic for valid placement areas var gridPosX = x - grid.x; var gridPosY = y - grid.y; self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; self.checkPlacement(); }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 500; menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; var towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' Tower', { size: 80, fill: 0xFFFFFF, weight: 800 }); towerTypeText.anchor.set(0, 0); towerTypeText.x = -840; towerTypeText.y = -160; self.addChild(towerTypeText); var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', { size: 70, fill: 0xFFFFFF, weight: 400 }); statsText.anchor.set(0, 0.5); statsText.x = -840; statsText.y = 50; self.addChild(statsText); var buttonsContainer = new Container(); buttonsContainer.x = 500; self.addChild(buttonsContainer); var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; buttonBackground.height = 150; var isMaxLevel = self.tower.level >= self.tower.maxLevel; // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var upgradeCost; if (isMaxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888; var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; sellButtonBackground.height = 150; sellButtonBackground.tint = 0xCC0000; var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); upgradeButton.y = -85; sellButton.y = 85; var closeButton = new Container(); self.addChild(closeButton); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; upgradeButton.down = function (x, y, obj) { if (self.tower.level >= self.tower.maxLevel) { var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (self.tower.upgrade()) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); if (self.tower.level >= self.tower.maxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s'); buttonText.setText('Upgrade: ' + upgradeCost + ' gold'); var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); sellButtonText.setText('Sell: +' + sellValue + ' gold'); if (self.tower.level >= self.tower.maxLevel) { buttonBackground.tint = 0x888888; buttonText.setText('Max Level'); } var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { rangeCircle = game.children[i]; break; } } if (rangeCircle) { var rangeGraphics = rangeCircle.children[0]; rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; } else { var newRangeIndicator = new Container(); newRangeIndicator.isTowerRange = true; newRangeIndicator.tower = self.tower; game.addChildAt(newRangeIndicator, 0); newRangeIndicator.x = self.tower.x; newRangeIndicator.y = self.tower.y; var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; rangeGraphics.alpha = 0.3; } tween(self, { scaleX: 1.05, scaleY: 1.05 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn }); } }); } }; sellButton.down = function (x, y, obj) { var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); setGold(gold + sellValue); var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; var gridX = self.tower.gridX; var gridY = self.tower.gridY; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 0; var towerIndex = cell.towersInRange.indexOf(self.tower); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } if (selectedTower === self.tower) { selectedTower = null; } var towerIndex = towers.indexOf(self.tower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } towerLayer.removeChild(self.tower); grid.pathFind(); grid.renderDebug(); self.destroy(); for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } }; closeButton.down = function (x, y, obj) { hideUpgradeMenu(self); selectedTower = null; grid.renderDebug(); }; self.update = function () { if (self.tower.level >= self.tower.maxLevel) { if (buttonText.text !== 'Max Level') { buttonText.setText('Max Level'); buttonBackground.tint = 0x888888; } return; } // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var currentUpgradeCost; if (self.tower.level >= self.tower.maxLevel) { currentUpgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } var canAfford = gold >= currentUpgradeCost; buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888; var newText = 'Upgrade: ' + currentUpgradeCost + ' gold'; if (buttonText.text !== newText) { buttonText.setText(newText); } }; return self; }); var WaveIndicator = Container.expand(function () { var self = Container.call(this); self.gameStarted = false; self.waveMarkers = []; self.waveTypes = []; self.enemyCounts = []; self.indicatorWidth = 0; self.lastBossType = null; // Track the last boss type to avoid repeating var blockWidth = 400; var totalBlocksWidth = blockWidth * totalWaves; var startMarker = new Container(); var startBlock = startMarker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); startBlock.width = blockWidth - 10; startBlock.height = 70 * 2; startBlock.tint = 0x00AA00; // Add shadow for start text var startTextShadow = new Text2("Start Game", { size: 50, fill: 0x000000, weight: 800 }); startTextShadow.anchor.set(0.5, 0.5); startTextShadow.x = 4; startTextShadow.y = 4; startMarker.addChild(startTextShadow); var startText = new Text2("Start Game", { size: 50, fill: 0xFFFFFF, weight: 800 }); startText.anchor.set(0.5, 0.5); startMarker.addChild(startText); startMarker.x = -self.indicatorWidth; self.addChild(startMarker); self.waveMarkers.push(startMarker); startMarker.down = function () { if (!self.gameStarted) { self.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; startBlock.tint = 0x00FF00; startText.setText("Started!"); startTextShadow.setText("Started!"); // Make sure shadow position remains correct after text change startTextShadow.x = 4; startTextShadow.y = 4; var notification = game.addChild(new Notification("Game started! Wave 1 incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; for (var i = 0; i < totalWaves; i++) { var marker = new Container(); var block = marker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth - 10; block.height = 70 * 2; // --- Begin new unified wave logic --- var waveType = "normal"; var enemyType = "normal"; var enemyCount = 10; var isBossWave = (i + 1) % 10 === 0; // Use original pattern: normal, fast, immune, flying var wavePattern = ['normal', 'fast', 'immune', 'flying']; if (!isBossWave) { var patternIndex = i % wavePattern.length; enemyType = wavePattern[patternIndex]; switch (enemyType) { case 'normal': block.tint = 0xAAAAAA; waveType = "Normal"; enemyCount = 10; break; case 'fast': block.tint = 0x00AAFF; waveType = "Fast"; enemyCount = 10; break; case 'immune': block.tint = 0xAA0000; waveType = "Immune"; enemyCount = 10; break; case 'flying': block.tint = 0xFFFF00; waveType = "Flying"; enemyCount = 10; break; } } 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; } } // --- 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: 0x333333 }); /**** * Game Code ****/ // Add background image var backgroundImage = LK.getAsset('arkaplan', { anchorX: 0, anchorY: 0, width: 2048, height: 2732 }); game.addChild(backgroundImage); var isHidingUpgradeMenu = false; function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; } }); } var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var enemies = []; var towers = []; var bullets = []; var defenses = []; var selectedTower = null; var gold = 200; var lives = 20; var score = 0; var currentWave = 0; var totalWaves = 50; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 12000 / 4; var sourceTower = null; var enemiesToSpawn = 10; // Default number of enemies per wave var enemySpawnQueue = []; // Queue of enemies waiting to spawn var enemySpawnDelay = 240; // 4 seconds at 60 FPS (4 * 60 = 240 frames) var lastEnemySpawnTime = 0; var goldText = new Text2('Gold: ' + gold, { size: 60, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); var livesText = new Text2('Lives: ' + lives, { size: 60, fill: 0x00FF00, weight: 800 }); livesText.anchor.set(0.5, 0.5); var scoreText = new Text2('Score: ' + score, { size: 60, fill: 0xFF0000, weight: 800 }); scoreText.anchor.set(0.5, 0.5); var topMargin = 50; var centerX = 2048 / 2; var spacing = 400; LK.gui.top.addChild(goldText); LK.gui.top.addChild(livesText); LK.gui.top.addChild(scoreText); livesText.x = 0; livesText.y = topMargin; goldText.x = -spacing; goldText.y = topMargin; scoreText.x = spacing; scoreText.y = topMargin; function updateUI() { goldText.setText('Gold: ' + gold); livesText.setText('Lives: ' + lives); scoreText.setText('Score: ' + score); } function setGold(value) { gold = value; updateUI(); } 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(); // Remove the arena path visualization // grid.renderDebug(); var offset = 0; var towerPreview = new TowerPreview(); towerPreview.visible = false; var isDragging = false; function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); return blocked; } function getTowerCost(towerType) { var cost = 5; switch (towerType) { case 'rapid': cost = 15; break; case 'sniper': cost = 25; break; case 'tank4': cost = 35; break; case 'slow': cost = 45; break; case 'poison': cost = 55; break; case 'antiair': cost = 40; break; } return cost; } function getTowerSellValue(totalValue) { return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue; } function placeTower(gridX, gridY, towerType) { var towerCost = getTowerCost(towerType); if (gold >= towerCost) { var tower = new Tower(towerType || 'default'); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); grid.pathFind(); grid.renderDebug(); return true; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } game.down = function (x, y, obj) { var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenuVisible) { return; } for (var i = 0; i < sourceTowers.length; i++) { var tower = sourceTowers[i]; if (x >= tower.x - tower.width / 2 && x <= tower.x + tower.width / 2 && y >= tower.y - tower.height / 2 && y <= tower.y + tower.height / 2) { towerPreview.visible = true; isDragging = true; towerPreview.towerType = tower.towerType; towerPreview.updateAppearance(); // Apply the same offset as in move handler to ensure consistency when starting drag towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); break; } } }; game.move = function (x, y, obj) { if (isDragging) { // Calculate the 185x185 grid position var blockSize = 185; var adjustedY = y - CELL_SIZE * 1.5; // Shift the y position upward by 1.5 tiles to show preview above finger // Calculate which 185x185 grid cell the cursor is closest to var gridX = Math.round((x - blockSize / 2) / blockSize); var gridY = Math.round((adjustedY - (185 + 270) - blockSize / 2) / blockSize); // Calculate the center position of the nearest grid cell var snapX = gridX * blockSize + blockSize / 2; var snapY = gridY * blockSize + (185 + 270) + blockSize / 2; // Use the snapped position for the tower preview towerPreview.snapToGrid(snapX, snapY); } }; game.up = function (x, y, obj) { var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerLeft = tower.x - tower.width / 2; var towerRight = tower.x + tower.width / 2; var towerTop = tower.y - tower.height / 2; var towerBottom = tower.y + tower.height / 2; if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) { var clickedOnMenu = false; for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; var menuWidth = 2048; var menuHeight = 450; var menuLeft = menu.x - menuWidth / 2; var menuRight = menu.x + menuWidth / 2; var menuTop = menu.y - menuHeight / 2; var menuBottom = menu.y + menuHeight / 2; if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) { clickedOnMenu = true; break; } } if (!clickedOnMenu) { for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; hideUpgradeMenu(menu); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = null; grid.renderDebug(); } } if (isDragging) { isDragging = false; if (towerPreview.canPlace) { if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) { placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType); } else { var notification = game.addChild(new Notification("Tower would block the path!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } else if (towerPreview.blockedByEnemy) { var notification = game.addChild(new Notification("Cannot build: Enemy in the way!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (towerPreview.visible) { var notification = game.addChild(new Notification("Cannot build here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } towerPreview.visible = false; if (isDragging) { var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } } } }; var waveIndicator = new WaveIndicator(); waveIndicator.x = 2048 / 2; waveIndicator.y = 2732 - 80; game.addChild(waveIndicator); var nextWaveButtonContainer = new Container(); var nextWaveButton = new NextWaveButton(); nextWaveButton.x = 2048 - 200; nextWaveButton.y = 2732 - 100 + 20; nextWaveButtonContainer.addChild(nextWaveButton); game.addChild(nextWaveButtonContainer); var towerTypes = ['default', 'rapid', 'sniper', 'tank4', 'slow', 'poison', 'antiair']; // Create 185 pixel square structure and place it in the top left of the screen var centerSquare = LK.getAsset('cimen', { anchorX: 0.5, anchorY: 0.5 }); centerSquare.width = 185; centerSquare.height = 185; centerSquare.x = 185 / 2; // Position at left edge accounting for anchor centerSquare.y = 185 / 2 + 185 + 270; // Position at top edge accounting for anchor, moved down by 185 + 270 pixels game.addChild(centerSquare); // Add road block above the center square var centerSquareTopRoad = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); centerSquareTopRoad.width = 185; centerSquareTopRoad.height = 185; centerSquareTopRoad.x = centerSquare.x; centerSquareTopRoad.y = centerSquare.y - 185; // Position above center square game.addChild(centerSquareTopRoad); // Add tel image to the center square (block 1) var centerSquareTel = LK.getAsset('tel', { anchorX: 0.5, anchorY: 0.5 }); centerSquareTel.width = 120; centerSquareTel.height = 120; centerSquareTel.x = centerSquare.x; centerSquareTel.y = centerSquare.y; game.addChild(centerSquareTel); // Add number label to the center square (first block in column) var centerSquareNumber = new Text2('1', { size: 60, fill: 0x000000, weight: 800 }); centerSquareNumber.anchor.set(0.5, 0.5); centerSquareNumber.x = centerSquare.x; centerSquareNumber.y = centerSquare.y; game.addChild(centerSquareNumber); // Create 9 additional blocks below the first one to form a column var columnBlocks = []; for (var i = 0; i < 9; i++) { var block = LK.getAsset('cimen', { anchorX: 0.5, anchorY: 0.5 }); block.width = 185; block.height = 185; block.x = 185 / 2; // Same x position as the first block block.y = 185 / 2 + 185 + 270 + (i + 1) * 185; // Position below the first block game.addChild(block); columnBlocks.push(block); // Add tel image to each column block var blockTel = LK.getAsset('tel', { anchorX: 0.5, anchorY: 0.5 }); blockTel.width = 120; blockTel.height = 120; blockTel.x = block.x; blockTel.y = block.y; game.addChild(blockTel); // Add number label to each column block var blockNumber = new Text2((i + 2).toString(), { size: 60, fill: 0x000000, weight: 800 }); blockNumber.anchor.set(0.5, 0.5); blockNumber.x = block.x; blockNumber.y = block.y; game.addChild(blockNumber); } // Create 11 blocks to the right of the center square (reduced from 12) var centerSquareRightBlocks = []; for (var i = 0; i < 11; i++) { var block = LK.getAsset('cimen', { anchorX: 0.5, anchorY: 0.5 }); block.width = 185; block.height = 185; block.x = 185 / 2 + (i + 1) * 185; // Position to the right of center square block.y = 185 / 2 + 185 + 270; // Same y position as center square game.addChild(block); centerSquareRightBlocks.push(block); // Add road block above each right block in the first row var topRoad = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); topRoad.width = 185; topRoad.height = 185; topRoad.x = block.x; topRoad.y = block.y - 185; // Position above the block game.addChild(topRoad); } // Create 11 blocks to the right of each column block var columnRightBlocks = []; for (var i = 0; i < 9; i++) { var rowBlocks = []; for (var j = 0; j < 11; j++) { var block = LK.getAsset('cimen', { anchorX: 0.5, anchorY: 0.5 }); block.width = 185; block.height = 185; block.x = 185 / 2 + (j + 1) * 185; // Position to the right of column block block.y = 185 / 2 + 185 + 270 + (i + 1) * 185; // Same y position as corresponding column block game.addChild(block); rowBlocks.push(block); // Add tel image to all blocks in 11th grass column (rightmost column) if (j === 9) { var blockTel = LK.getAsset('tel', { anchorX: 0.5, anchorY: 0.5 }); blockTel.width = 120; blockTel.height = 120; blockTel.x = block.x; blockTel.y = block.y; game.addChild(blockTel); } // Add tel image to the 11th block of the first row (centerSquareRightBlocks[10]) if (centerSquareRightBlocks[10]) { var firstRowTel = LK.getAsset('tel', { anchorX: 0.5, anchorY: 0.5 }); firstRowTel.width = 120; firstRowTel.height = 120; firstRowTel.x = centerSquareRightBlocks[10].x; firstRowTel.y = centerSquareRightBlocks[10].y; game.addChild(firstRowTel); // Add another tel image above the existing tel on the 11th block of first row var firstRowTopTel = LK.getAsset('tel', { anchorX: 0.5, anchorY: 0.5 }); firstRowTopTel.width = 120; firstRowTopTel.height = 120; firstRowTopTel.x = centerSquareRightBlocks[10].x; firstRowTopTel.y = centerSquareRightBlocks[10].y - 60; // Position above the existing tel game.addChild(firstRowTopTel); } // Add tel image to the last grass block in the first row (centerSquareRightBlocks[10]) if (centerSquareRightBlocks[10]) { var lastGrassRowTel = LK.getAsset('tel', { anchorX: 0.5, anchorY: 0.5 }); lastGrassRowTel.width = 120; lastGrassRowTel.height = 120; lastGrassRowTel.x = centerSquareRightBlocks[10].x; lastGrassRowTel.y = centerSquareRightBlocks[10].y; game.addChild(lastGrassRowTel); } } columnRightBlocks.push(rowBlocks); } // Replace specific blocks with road image according to the pattern // Row 1: block 2 (index 1) if (centerSquareRightBlocks[1]) { game.removeChild(centerSquareRightBlocks[1]); var roadBlock = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); roadBlock.width = 185; roadBlock.height = 185; roadBlock.x = centerSquareRightBlocks[1].x; roadBlock.y = centerSquareRightBlocks[1].y; game.addChild(roadBlock); centerSquareRightBlocks[1] = roadBlock; // Add number to roadBlock (original) var roadBlockNumber = new Text2('1', { size: 60, fill: 0xFFFFFF, weight: 800 }); roadBlockNumber.anchor.set(0.5, 0.5); roadBlockNumber.x = roadBlock.x; roadBlockNumber.y = roadBlock.y; game.addChild(roadBlockNumber); var roadBlockCounter = 2; // Add grass blocks to the left and right of only the remaining road block var roadBlockPositions = [{ x: centerSquareRightBlocks[1].x, y: centerSquareRightBlocks[1].y }]; // roadBlock for (var i = 0; i < roadBlockPositions.length; i++) { // Add grass block to the left of each road block var leftGrass = LK.getAsset('cimen', { anchorX: 0.5, anchorY: 0.5 }); leftGrass.width = 185; leftGrass.height = 185; leftGrass.x = roadBlockPositions[i].x - 185; leftGrass.y = roadBlockPositions[i].y; game.addChild(leftGrass); // Add grass block to the right of each road block var rightGrass = LK.getAsset('cimen', { anchorX: 0.5, anchorY: 0.5 }); rightGrass.width = 185; rightGrass.height = 185; rightGrass.x = roadBlockPositions[i].x + 185; rightGrass.y = roadBlockPositions[i].y; game.addChild(rightGrass); } } // Row 2: blocks 2,3,4,5,6,7,8,9 (indices 1,2,3,4,5,6,7,8) for (var idx = 1; idx <= 8; idx++) { if (columnRightBlocks[0] && columnRightBlocks[0][idx]) { game.removeChild(columnRightBlocks[0][idx]); var roadBlock = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); roadBlock.width = 185; roadBlock.height = 185; roadBlock.x = columnRightBlocks[0][idx].x; roadBlock.y = columnRightBlocks[0][idx].y; game.addChild(roadBlock); columnRightBlocks[0][idx] = roadBlock; // Add number to this road block var roadBlockNumber = new Text2(roadBlockCounter.toString(), { size: 60, fill: 0xFFFFFF, weight: 800 }); roadBlockNumber.anchor.set(0.5, 0.5); roadBlockNumber.x = roadBlock.x; roadBlockNumber.y = roadBlock.y; game.addChild(roadBlockNumber); // Add tel image to the 9th road block (index 8 in Row 2) if (idx === 8) { var roadBlockTel = LK.getAsset('tel', { anchorX: 0.5, anchorY: 0.5 }); roadBlockTel.width = 120; roadBlockTel.height = 120; roadBlockTel.x = roadBlock.x + 185; // Move right by 185 pixels roadBlockTel.y = roadBlock.y - 185; // Move up by 185 pixels game.addChild(roadBlockTel); } roadBlockCounter++; } } // Row 3: block 9 (index 8) if (columnRightBlocks[1] && columnRightBlocks[1][8]) { game.removeChild(columnRightBlocks[1][8]); var roadBlock = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); roadBlock.width = 185; roadBlock.height = 185; roadBlock.x = columnRightBlocks[1][8].x; roadBlock.y = columnRightBlocks[1][8].y; game.addChild(roadBlock); columnRightBlocks[1][8] = roadBlock; // Add number to this road block var roadBlockNumber = new Text2(roadBlockCounter.toString(), { size: 60, fill: 0xFFFFFF, weight: 800 }); roadBlockNumber.anchor.set(0.5, 0.5); roadBlockNumber.x = roadBlock.x; roadBlockNumber.y = roadBlock.y; game.addChild(roadBlockNumber); roadBlockCounter++; } // Row 4: blocks 3,4,5,6,7,8,9 (indices 2,3,4,5,6,7,8) for (var idx = 2; idx <= 8; idx++) { if (columnRightBlocks[2] && columnRightBlocks[2][idx]) { game.removeChild(columnRightBlocks[2][idx]); var roadBlock = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); roadBlock.width = 185; roadBlock.height = 185; roadBlock.x = columnRightBlocks[2][idx].x; roadBlock.y = columnRightBlocks[2][idx].y; game.addChild(roadBlock); columnRightBlocks[2][idx] = roadBlock; // Add number to this road block var roadBlockNumber = new Text2(roadBlockCounter.toString(), { size: 60, fill: 0xFFFFFF, weight: 800 }); roadBlockNumber.anchor.set(0.5, 0.5); roadBlockNumber.x = roadBlock.x; roadBlockNumber.y = roadBlock.y; game.addChild(roadBlockNumber); roadBlockCounter++; } } // Row 5: block 3 (index 2) if (columnRightBlocks[3] && columnRightBlocks[3][2]) { game.removeChild(columnRightBlocks[3][2]); var roadBlock = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); roadBlock.width = 185; roadBlock.height = 185; roadBlock.x = columnRightBlocks[3][2].x; roadBlock.y = columnRightBlocks[3][2].y; game.addChild(roadBlock); columnRightBlocks[3][2] = roadBlock; // Add number to this road block var roadBlockNumber = new Text2(roadBlockCounter.toString(), { size: 60, fill: 0xFFFFFF, weight: 800 }); roadBlockNumber.anchor.set(0.5, 0.5); roadBlockNumber.x = roadBlock.x; roadBlockNumber.y = roadBlock.y; game.addChild(roadBlockNumber); roadBlockCounter++; } // Row 6: block 3 (index 2) if (columnRightBlocks[4] && columnRightBlocks[4][2]) { game.removeChild(columnRightBlocks[4][2]); var roadBlock = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); roadBlock.width = 185; roadBlock.height = 185; roadBlock.x = columnRightBlocks[4][2].x; roadBlock.y = columnRightBlocks[4][2].y; game.addChild(roadBlock); columnRightBlocks[4][2] = roadBlock; // Add number to this road block var roadBlockNumber = new Text2(roadBlockCounter.toString(), { size: 60, fill: 0xFFFFFF, weight: 800 }); roadBlockNumber.anchor.set(0.5, 0.5); roadBlockNumber.x = roadBlock.x; roadBlockNumber.y = roadBlock.y; game.addChild(roadBlockNumber); roadBlockCounter++; } // Row 7: blocks 3,4,5,6,7,8,9 (indices 2,3,4,5,6,7,8) for (var idx = 2; idx <= 8; idx++) { if (columnRightBlocks[5] && columnRightBlocks[5][idx]) { game.removeChild(columnRightBlocks[5][idx]); var roadBlock = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); roadBlock.width = 185; roadBlock.height = 185; roadBlock.x = columnRightBlocks[5][idx].x; roadBlock.y = columnRightBlocks[5][idx].y; game.addChild(roadBlock); columnRightBlocks[5][idx] = roadBlock; // Add number to this road block var roadBlockNumber = new Text2(roadBlockCounter.toString(), { size: 60, fill: 0xFFFFFF, weight: 800 }); roadBlockNumber.anchor.set(0.5, 0.5); roadBlockNumber.x = roadBlock.x; roadBlockNumber.y = roadBlock.y; game.addChild(roadBlockNumber); roadBlockCounter++; } } // Row 8: block 9 (index 8) if (columnRightBlocks[6] && columnRightBlocks[6][8]) { game.removeChild(columnRightBlocks[6][8]); var roadBlock = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); roadBlock.width = 185; roadBlock.height = 185; roadBlock.x = columnRightBlocks[6][8].x; roadBlock.y = columnRightBlocks[6][8].y; game.addChild(roadBlock); columnRightBlocks[6][8] = roadBlock; // Add number to this road block var roadBlockNumber = new Text2(roadBlockCounter.toString(), { size: 60, fill: 0xFFFFFF, weight: 800 }); roadBlockNumber.anchor.set(0.5, 0.5); roadBlockNumber.x = roadBlock.x; roadBlockNumber.y = roadBlock.y; game.addChild(roadBlockNumber); roadBlockCounter++; } // Row 9: blocks 5,6,7,8,9 (indices 4,5,6,7,8) - removed block 4 (index 3) for (var idx = 4; idx <= 8; idx++) { if (columnRightBlocks[7] && columnRightBlocks[7][idx]) { game.removeChild(columnRightBlocks[7][idx]); var roadBlock = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); roadBlock.width = 185; roadBlock.height = 185; roadBlock.x = columnRightBlocks[7][idx].x; roadBlock.y = columnRightBlocks[7][idx].y; game.addChild(roadBlock); columnRightBlocks[7][idx] = roadBlock; // Add number to this road block var roadBlockNumber = new Text2(roadBlockCounter.toString(), { size: 60, fill: 0xFFFFFF, weight: 800 }); roadBlockNumber.anchor.set(0.5, 0.5); roadBlockNumber.x = roadBlock.x; roadBlockNumber.y = roadBlock.y; game.addChild(roadBlockNumber); roadBlockCounter++; } } // Row 10: block 6 (index 5) - convert back to road block if (columnRightBlocks[8] && columnRightBlocks[8][4]) { game.removeChild(columnRightBlocks[8][4]); var roadBlock = LK.getAsset('Yol', { anchorX: 0.5, anchorY: 0.5 }); roadBlock.width = 185; roadBlock.height = 185; roadBlock.x = columnRightBlocks[8][4].x; roadBlock.y = columnRightBlocks[8][4].y; game.addChild(roadBlock); columnRightBlocks[8][4] = roadBlock; // Add number to this road block var roadBlockNumber = new Text2(roadBlockCounter.toString(), { size: 60, fill: 0xFFFFFF, weight: 800 }); roadBlockNumber.anchor.set(0.5, 0.5); roadBlockNumber.x = roadBlock.x; roadBlockNumber.y = roadBlock.y; game.addChild(roadBlockNumber); roadBlockCounter++; } 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; // Add enemy layer after all background elements but before tower elements to ensure enemies render above grass and road blocks game.addChild(enemyLayer); // Add tower preview and tower layer after all background elements to ensure they render above grass and road blocks game.addChild(towerPreview); game.addChild(towerLayer); game.update = function () { if (waveInProgress) { if (!waveSpawned) { waveSpawned = true; // Get wave type and enemy count from the wave indicator // Use original spawn pattern: normal, fast, immune, flying var waveType; var wavePattern = ['normal', 'fast', 'immune', 'flying']; // For non-boss waves, use the repeating pattern if (currentWave % 10 !== 0 || currentWave === 0) { var patternIndex = (currentWave - 1) % wavePattern.length; waveType = wavePattern[patternIndex]; } else { // Boss waves use the wave indicator type 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; } // Reset spawn timing for new wave lastEnemySpawnTime = LK.ticks; // Queue enemies for spawning with delays for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); // 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; // Spawn enemies from the 3rd column (index 2) sequentially, 185 pixels to the right var spawnX = 2; // Always spawn from 3rd column (index 2) 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; // Move spawn position 185 pixels to the right enemy.pathX = (enemy.pathX || grid.x + enemy.currentCellX * CELL_SIZE) + 185; enemy.pathY = enemy.pathY || grid.y + enemy.currentCellY * CELL_SIZE; enemy.waveNumber = currentWave; // Add to spawn queue instead of spawning immediately enemySpawnQueue.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; } } // Process enemy spawn queue with delays if (enemySpawnQueue.length > 0 && LK.ticks - lastEnemySpawnTime >= enemySpawnDelay) { var enemy = enemySpawnQueue.shift(); // Take first enemy from queue // 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); } enemies.push(enemy); lastEnemySpawnTime = LK.ticks; // Record spawn time } for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; if (enemy.health <= 0) { for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } // Boss enemies give more gold and score var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5); var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); // Give more score for defeating a boss var scoreValue = enemy.isBoss ? 100 : 5; score += scoreValue; // Add a notification for boss defeat if (enemy.isBoss) { var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } updateUI(); // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { 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 && bullets[i].targetEnemy.bulletsTargetingThis) { var targetEnemy = bullets[i].targetEnemy; var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } bullets.splice(i, 1); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -2389,10 +2389,10 @@
var waveType = "normal";
var enemyType = "normal";
var enemyCount = 10;
var isBossWave = (i + 1) % 10 === 0;
- // Use original spawn pattern: flying, normal, fast, immune (flying comes first)
- var wavePattern = ['flying', 'normal', 'fast', 'immune'];
+ // Use original pattern: normal, fast, immune, flying
+ var wavePattern = ['normal', 'fast', 'immune', 'flying'];
if (!isBossWave) {
var patternIndex = i % wavePattern.length;
enemyType = wavePattern[patternIndex];
switch (enemyType) {
@@ -3421,11 +3421,11 @@
if (waveInProgress) {
if (!waveSpawned) {
waveSpawned = true;
// Get wave type and enemy count from the wave indicator
- // Use original pattern: flying, normal, fast, immune (flying comes first)
- var wavePattern = ['flying', 'normal', 'fast', 'immune'];
+ // Use original spawn pattern: normal, fast, immune, flying
var waveType;
+ var wavePattern = ['normal', 'fast', 'immune', 'flying'];
// For non-boss waves, use the repeating pattern
if (currentWave % 10 !== 0 || currentWave === 0) {
var patternIndex = (currentWave - 1) % wavePattern.length;
waveType = wavePattern[patternIndex];
@@ -3456,15 +3456,18 @@
enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier);
enemy.health = enemy.maxHealth;
// Increment speed slightly with wave number
//enemy.speed = enemy.speed + currentWave * 0.002;
- // Spawn enemies from the 3rd column (index 2) sequentially
+ // Spawn enemies from the 3rd column (index 2) sequentially, 185 pixels to the right
var spawnX = 2; // Always spawn from 3rd column (index 2)
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;
+ // Move spawn position 185 pixels to the right
+ enemy.pathX = (enemy.pathX || grid.x + enemy.currentCellX * CELL_SIZE) + 185;
+ enemy.pathY = enemy.pathY || grid.y + enemy.currentCellY * CELL_SIZE;
enemy.waveNumber = currentWave;
// Add to spawn queue instead of spawning immediately
enemySpawnQueue.push(enemy);
}
White circle with black outline. Blue background.. In-Game asset. 2d. High contrast. No shadows
düz çimen resmi. In-Game asset. 2d. High contrast. No shadows
düz çimen resmi çoz az otlu olsun In-Game asset. 2d. High contrast. No shadows
düz gri beton In-Game asset. 2d. High contrast. No shadows
kuş bakışı açısından 3 tekerlekli kırmızı siyah motor. In-Game asset. 2d. High contrast. No shadows
Kuş bakışı acısından bir karargah binası ve çatısımda tank resmi In-Game asset. 2d. High contrast. No shadows
kuş bakışı açısından yeşil çimen üzerinde tanksavar çelik çapraz demir. In-Game asset. 2d. High contrast. No shadows
düz toprak resmi. In-Game asset. 2d. High contrast. No shadows
kuş bakışı açısından B-2 Spirit uçağı kırmızı renkte. In-Game asset. 2d. High contrast. No shadows