User prompt
sisler ve dumanlar şuan oval şekilde duruyor böyle istemiyorum. gerçek bir sis ve duman görüntüsü olsun ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
sisler ve dumanlar daha gerçekçi, yanan bir yerden çıkar gibi olsun ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
kulelerin düşmanları takibi daha uzun mesafe sürsün
User prompt
20 saniyede bir zemin titresin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
dumanlar bir sis bulutu gibi gerçekçi ve ilerleyen şekilde olsun.korku unsuruna uygun olsun ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ekran kararırken kenarlardan gri ve siyah dumanlar gelsin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ekran karalması 2 saniye sürsün ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
oyun başlandıktan sonra her 15 saniyede bir ekran karalsın ve yıldırım sesi gelsin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
on saniyede bir oyunda ekran kararsın
User prompt
başlangıç ekranında asıl oyuna geçmeden önce heycanlı bir şarkı çalsın
User prompt
hogwarts begins yazısını kaldır
User prompt
the battle for yazısını kaldır
User prompt
protect yazısını alttaki yazıyla uygun temaya çevir
User prompt
hogwart defense yazısını kaldır
User prompt
ready to battle tuşunu kaldır ekranda herhangi bir yere dokununca oyun ekranına geçsin
User prompt
giriş ekranındaki resimde beary to battle yazıyor onu kaldır
User prompt
ready to battle tuşu otantik olsun
User prompt
ready to battle butonu daha üstte olsun
User prompt
başlangıç ekranındaki resmi şununla değiş: harry potter evreninde hogwarts saldırı altında. bir tarafta iyi büyücüler diğer tarafta canavarlar ve kötü büyücülerin olduğu resim. Bu resmi ekranın tamamına sığdır ve görüntü kalitesi düşmesin. Resmin üstünde harry potter temasına uygun ready to battle tuşu olsun
User prompt
başlangıç ekranındaki resmi ekranın tamamına sığdıracak şekilde genişlet ve kalitesini bozma
User prompt
başlangıçtaki resimde bir genç erkek bir genç kız bir erkek daha ve yaşlıca bir erkek ayakta duruyorlar. büyücüler ellerinde asa var
User prompt
başlangıç ekranındaki resimi baştan yap net ve görünür olsun
User prompt
başlangıç ekranında harry potter harmony ve ron un ayakta olduğu ve ellerinde asa tuttuğu bir resim olsun. harry potter temasında olsun. tüm ekranı kaplasın
User prompt
savaşmaya hazırım yerine ready to battle yazsın
User prompt
oyunun girişinde tüm ekranı kapsayacak şekilde harry potter fotoğrafınının olacağı şekilde başlangıç girişi yap altta savaşmaya hazırım butonu olsun
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var BackgroundFlame = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; var flameGraphics = self.attachAsset('flame', { anchorX: 0.5, anchorY: 1.0 }); // Random flame colors (orange to red) var flameColors = [0xff6b35, 0xff4500, 0xff6347, 0xdc143c, 0xb22222]; flameGraphics.tint = flameColors[Math.floor(Math.random() * flameColors.length)]; // Random size variation var sizeVariation = 0.5 + Math.random() * 0.8; // 0.5 to 1.3 scale flameGraphics.scaleX = sizeVariation; flameGraphics.scaleY = sizeVariation; // Start invisible self.alpha = 0; self.scaleX = 0.1; self.scaleY = 0.1; // Animate appearance var appearDuration = 800 + Math.random() * 600; // 800-1400ms tween(self, { alpha: 0.3 + Math.random() * 0.4, // 0.3 to 0.7 alpha scaleX: sizeVariation, scaleY: sizeVariation }, { duration: appearDuration, easing: tween.easeOut, onFinish: function onFinish() { // Flicker for a while self.startFlickering(); } }); self.startFlickering = function () { if (!self.parent) return; // Stop if destroyed // Random flicker duration var flickerTime = 2000 + Math.random() * 3000; // 2-5 seconds var flickerInterval = 80 + Math.random() * 120; // Flicker every 80-200ms var flickerTimer = LK.setInterval(function () { if (!self.parent) { LK.clearInterval(flickerTimer); return; } // Quick flicker animation var currentAlpha = self.alpha; var flickerAlpha = Math.max(0.1, currentAlpha - 0.15 + Math.random() * 0.3); tween(self, { alpha: flickerAlpha }, { duration: 60, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent) return; tween(self, { alpha: currentAlpha }, { duration: 60, easing: tween.easeInOut }); } }); }, flickerInterval); // Start disappearing after flicker time LK.setTimeout(function () { if (!self.parent) return; LK.clearInterval(flickerTimer); self.startDisappearing(); }, flickerTime); }; self.startDisappearing = function () { if (!self.parent) return; // Stop if destroyed var disappearDuration = 600 + Math.random() * 400; // 600-1000ms tween(self, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: disappearDuration, easing: tween.easeIn, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); }; return self; }); var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) { var self = Container.call(this); self.targetEnemy = targetEnemy; self.damage = damage || 10; self.speed = speed || 5; self.x = startX; self.y = startY; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { // Apply damage to target enemy self.targetEnemy.health -= self.damage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } else { self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; } // Apply special effects based on bullet type if (self.type === 'splash') { // Create visual splash effect var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); game.addChild(splashEffect); // Splash damage to nearby enemies var splashRadius = CELL_SIZE * 1.5; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self.targetEnemy) { var splashDx = otherEnemy.x - self.targetEnemy.x; var splashDy = otherEnemy.y - self.targetEnemy.y; var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy); if (splashDistance <= splashRadius) { // Apply splash damage (50% of original damage) otherEnemy.health -= self.damage * 0.5; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } else { otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70; } } } } } else if (self.type === 'slow') { // Prevent slow effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual slow effect var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); // Apply slow effect // Make slow percentage scale with tower level (default 50%, up to 80% at max level) var slowPct = 0.5; if (self.sourceTowerLevel !== undefined) { // Scale: 50% at level 1, 60% at 2, 65% at 3, 70% at 4, 75% at 5, 80% at 6 var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8]; var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1)); slowPct = slowLevels[idx]; } if (!self.targetEnemy.slowed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; self.targetEnemy.speed *= 1 - slowPct; // Slow by X% self.targetEnemy.slowed = true; self.targetEnemy.slowDuration = 180; // 3 seconds at 60 FPS } else { self.targetEnemy.slowDuration = 180; // Reset duration } } } else if (self.type === 'poison') { // Prevent poison effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual poison effect var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Apply poison effect self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS } } else if (self.type === 'sniper') { // Create visual critical hit effect for sniper var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper'); game.addChild(sniperEffect); } self.destroy(); } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); var DebugCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = self.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5 }); // Remove the random tint to show the stone texture properly cellGraphics.tint = 0xFFFFFF; cellGraphics.alpha = 0.8; var debugArrows = []; var numberLabel = new Text2('0', { size: 30, fill: 0xFFFFFF, weight: 800 }); numberLabel.anchor.set(.5, .5); numberLabel.visible = false; self.addChild(numberLabel); self.update = function () {}; self.down = function () { return; if (self.cell.type == 0 || self.cell.type == 1) { self.cell.type = self.cell.type == 1 ? 0 : 1; if (grid.pathFind()) { self.cell.type = self.cell.type == 1 ? 0 : 1; grid.pathFind(); var notification = game.addChild(new Notification("Path is blocked!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } grid.renderDebug(); } }; self.removeArrows = function () { while (debugArrows.length) { self.removeChild(debugArrows.pop()); } }; self.render = function (data) { switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); // Dark reddish stone for non-path cells cellGraphics.tint = 0x8B4513; cellGraphics.alpha = 0.5; return; } numberLabel.visible = false; var tint = Math.floor(data.score / maxScore * 0x88); var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; cellGraphics.tint = 0x0088ff; cellGraphics.alpha = 0.6; } else { // Apply subtle tinting to the stone texture based on path score var pathTint = 0x88 - tint << 8 | tint; // Blend the tint with the stone texture color cellGraphics.tint = 0xD2B48C; // Light tan/stone color cellGraphics.alpha = 0.7 + tint / 0x88 * 0.3; // Vary alpha based on path score } // Arrows removed - no visual indicators break; } case 1: { self.removeArrows(); // Darker stone for walls cellGraphics.tint = 0x654321; cellGraphics.alpha = 0.9; numberLabel.visible = false; break; } case 3: { self.removeArrows(); // Goal cells with greenish stone tint cellGraphics.tint = 0x8FBC8F; cellGraphics.alpha = 0.8; numberLabel.visible = false; break; } } // Number labels removed }; }); // This update method was incorrectly placed here and should be removed var EffectIndicator = Container.expand(function (x, y, type) { var self = Container.call(this); self.x = x; self.y = y; var effectGraphics = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.blendMode = 1; switch (type) { case 'splash': effectGraphics.tint = 0x33CC00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5; break; case 'slow': effectGraphics.tint = 0x9900FF; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'poison': effectGraphics.tint = 0x00FFAA; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'sniper': effectGraphics.tint = 0xFF5500; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; } effectGraphics.alpha = 0.7; self.alpha = 0; // Animate the effect tween(self, { alpha: 0.8, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); // Base enemy class for common functionality var Enemy = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'normal'; self.speed = .01; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 100; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.isFlying = false; self.isImmune = false; self.isBoss = false; // Check if this is a boss wave // Check if this is a boss wave // Apply different stats based on enemy type switch (self.type) { case 'fast': self.speed *= 2; // Twice as fast self.maxHealth = 100; break; case 'immune': self.isImmune = true; self.maxHealth = 80; break; case 'flying': self.isFlying = true; self.maxHealth = 80; break; case 'swarm': self.maxHealth = 50; // Weaker enemies break; case 'normal': default: // Normal enemy uses default values break; } if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') { self.isBoss = true; // Boss enemies have 20x health and are larger self.maxHealth *= 20; // Slower speed for bosses self.speed = self.speed * 0.7; } // Make all enemies 25% faster self.speed = self.speed * 1.25; self.health = self.maxHealth; // Get appropriate asset for this enemy type var assetId = 'enemy'; if (self.type !== 'normal') { assetId = 'enemy_' + self.type; } var enemyGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.enemyGraphics = enemyGraphics; // Store reference for walking animation // Scale up boss enemies if (self.isBoss) { enemyGraphics.scaleX = 1.8; enemyGraphics.scaleY = 1.8; } // Dark wizard enemies with magical effects // Different wizard types with distinct magical auras /*switch (self.type) { case 'fast': enemyGraphics.tint = 0x3742fa; // Blue aura for fast dark wizards break; case 'immune': enemyGraphics.tint = 0xff3838; // Red aura for immune dark wizards break; case 'flying': enemyGraphics.tint = 0xf1c40f; // Golden aura for broom-riding wizards break; case 'swarm': enemyGraphics.tint = 0x8c7ae6; // Purple aura for swarm dark wizards break; }*/ // Create shadow for flying enemies if (self.isFlying) { // Create a shadow container that will be added to the shadow layer self.shadow = new Container(); // Clone the enemy graphics for the shadow var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow effect shadowGraphics.tint = 0x000000; // Black shadow shadowGraphics.alpha = 0.4; // Semi-transparent // If this is a boss, scale up the shadow to match if (self.isBoss) { shadowGraphics.scaleX = 1.8; shadowGraphics.scaleY = 1.8; } // Position shadow slightly offset self.shadow.x = 20; // Offset right self.shadow.y = 20; // Offset down // Ensure shadow has the same rotation as the enemy shadowGraphics.rotation = enemyGraphics.rotation; } var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBarBG = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; self.update = function () { if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; } // Handle slow effect if (self.isImmune) { // Immune enemies cannot be slowed or poisoned, clear any such effects self.slowed = false; self.slowEffect = false; self.poisoned = false; self.poisonEffect = false; // Reset speed to original if needed if (self.originalSpeed !== undefined) { self.speed = self.originalSpeed; } } else { // Handle slow effect if (self.slowed) { // Visual indication of slowed status if (!self.slowEffect) { self.slowEffect = true; } self.slowDuration--; if (self.slowDuration <= 0) { self.speed = self.originalSpeed; self.slowed = false; self.slowEffect = false; // Only reset tint if not poisoned if (!self.poisoned) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } // Handle poison effect if (self.poisoned) { // Visual indication of poisoned status if (!self.poisonEffect) { self.poisonEffect = true; } // Apply poison damage every 30 frames (twice per second) if (LK.ticks % 30 === 0) { self.health -= self.poisonDamage; if (self.health <= 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; } self.poisonDuration--; if (self.poisonDuration <= 0) { self.poisoned = false; self.poisonEffect = false; // Only reset tint if not slowed if (!self.slowed) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } } // Set tint based on effect status if (self.isImmune) { enemyGraphics.tint = 0xFFFFFF; } else if (self.poisoned && self.slowed) { // Combine poison (0x00FFAA) and slow (0x9900FF) colors // Simple average: R: (0+153)/2=76, G: (255+0)/2=127, B: (170+255)/2=212 enemyGraphics.tint = 0x4C7FD4; } else if (self.poisoned) { enemyGraphics.tint = 0x00FFAA; } else if (self.slowed) { enemyGraphics.tint = 0x9900FF; } else { enemyGraphics.tint = 0xFFFFFF; } // Add walking animation effect if (!self.walkingAnimation && self.speed > 0) { // Create a subtle bobbing effect for walking var _createWalkingAnimation = function createWalkingAnimation() { if (!self.parent) return; // Stop if enemy is destroyed // Adjust animation speed based on enemy speed var animDuration = self.slowed ? 400 : 200; if (self.type === 'fast') animDuration = 100; // Vertical bobbing motion tween(enemyGraphics, { y: -5 }, { duration: animDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent) return; tween(enemyGraphics, { y: 0 }, { duration: animDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent) return; _createWalkingAnimation(); // Continue the animation loop } }); } }); }; self.walkingAnimation = true; _createWalkingAnimation(); } if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; if (ox !== 0 || oy !== 0) { var angle = Math.atan2(oy, ox); if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) { tween.stop(enemyGraphics, { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemyGraphics.rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemyGraphics.targetRotation = angle; tween(enemyGraphics, { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } } } healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; }; return self; }); var GoldIndicator = Container.expand(function (value, x, y) { var self = Container.call(this); var shadowText = new Text2("+" + value, { size: 45, fill: 0x000000, weight: 800 }); shadowText.anchor.set(0.5, 0.5); shadowText.x = 2; shadowText.y = 2; self.addChild(shadowText); var goldText = new Text2("+" + value, { size: 45, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); self.x = x; self.y = y; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2, y: y - 40 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: y - 80 }, { duration: 600, easing: tween.easeIn, delay: 800, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; self.spawns = []; self.goals = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { score: 0, pathId: 0, towersInRange: [] }; } } /* Cell Types 0: Transparent floor 1: Wall 2: Spawn 3: Goal */ for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0; if (i > 11 - 3 && i <= 11 + 3) { if (j === 0) { cellType = 2; self.spawns.push(cell); } else if (j <= 4) { cellType = 0; } else if (j === gridHeight - 1) { cellType = 3; self.goals.push(cell); } else if (j >= gridHeight - 4) { cellType = 0; } } cell.type = cellType; cell.x = i; cell.y = j; cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1]; cell.up = self.cells[i - 1] && self.cells[i - 1][j]; cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1]; cell.left = self.cells[i][j - 1]; cell.right = self.cells[i][j + 1]; cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1]; cell.down = self.cells[i + 1] && self.cells[i + 1][j]; cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1]; cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left]; cell.targets = []; if (j > 3 && j <= gridHeight - 4) { var debugCell = new DebugCell(); self.addChild(debugCell); debugCell.cell = cell; debugCell.x = i * CELL_SIZE; debugCell.y = j * CELL_SIZE; cell.debugCell = debugCell; } } } self.getCell = function (x, y) { return self.cells[x] && self.cells[x][y]; }; self.pathFind = function () { var before = new Date().getTime(); var toProcess = self.goals.concat([]); maxScore = 0; pathId += 1; for (var a = 0; a < toProcess.length; a++) { toProcess[a].pathId = pathId; } function processNode(node, targetValue, targetNode) { if (node && node.type != 1) { if (node.pathId < pathId || targetValue < node.score) { node.targets = [targetNode]; } else if (node.pathId == pathId && targetValue == node.score) { node.targets.push(targetNode); } if (node.pathId < pathId || targetValue < node.score) { node.score = targetValue; if (node.pathId != pathId) { toProcess.push(node); } node.pathId = pathId; if (targetValue > maxScore) { maxScore = targetValue; } } } } while (toProcess.length) { var nodes = toProcess; toProcess = []; for (var a = 0; a < nodes.length; a++) { var node = nodes[a]; var targetScore = node.score + 14142; if (node.up && node.left && node.up.type != 1 && node.left.type != 1) { processNode(node.upLeft, targetScore, node); } if (node.up && node.right && node.up.type != 1 && node.right.type != 1) { processNode(node.upRight, targetScore, node); } if (node.down && node.right && node.down.type != 1 && node.right.type != 1) { processNode(node.downRight, targetScore, node); } if (node.down && node.left && node.down.type != 1 && node.left.type != 1) { processNode(node.downLeft, targetScore, node); } targetScore = node.score + 10000; processNode(node.up, targetScore, node); processNode(node.right, targetScore, node); processNode(node.down, targetScore, node); processNode(node.left, targetScore, node); } } for (var a = 0; a < self.spawns.length; a++) { if (self.spawns[a].pathId != pathId) { console.warn("Spawn blocked"); return true; } } for (var a = 0; a < enemies.length; a++) { var enemy = enemies[a]; // Skip enemies that haven't entered the viewable area yet if (enemy.currentCellY < 4) { continue; } // Skip flying enemies from path check as they can fly over obstacles if (enemy.isFlying) { continue; } var target = self.getCell(enemy.cellX, enemy.cellY); if (enemy.currentTarget) { if (enemy.currentTarget.pathId != pathId) { if (!target || target.pathId != pathId) { console.warn("Enemy blocked 1 "); return true; } } } else if (!target || target.pathId != pathId) { console.warn("Enemy blocked 2"); return true; } } console.log("Speed", new Date().getTime() - before); }; self.renderDebug = function () { for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var debugCell = self.cells[i][j].debugCell; if (debugCell) { debugCell.render(self.cells[i][j]); } } } }; self.updateEnemy = function (enemy) { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell.type == 3) { return true; } if (enemy.isFlying && enemy.shadow) { enemy.shadow.x = enemy.x + 20; // Match enemy x-position + offset enemy.shadow.y = enemy.y + 20; // Match enemy y-position + offset // Match shadow rotation with enemy rotation if (enemy.children[0] && enemy.shadow.children[0]) { enemy.shadow.children[0].rotation = enemy.children[0].rotation; } } // Check if the enemy has reached the entry area (y position is at least 5) var hasReachedEntryArea = enemy.currentCellY >= 4; // If enemy hasn't reached the entry area yet, just move down vertically if (!hasReachedEntryArea) { // Move directly downward enemy.currentCellY += enemy.speed; // Rotate enemy graphic to face downward (PI/2 radians = 90 degrees) var angle = Math.PI / 2; if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update enemy's position enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // If enemy has now reached the entry area, update cell coordinates if (enemy.currentCellY >= 4) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); } return false; } // After reaching entry area, handle flying enemies differently if (enemy.isFlying) { // Flying enemies head straight to the closest goal if (!enemy.flyingTarget) { // Set flying target to the closest goal enemy.flyingTarget = self.goals[0]; // Find closest goal if there are multiple if (self.goals.length > 1) { var closestDist = Infinity; for (var i = 0; i < self.goals.length; i++) { var goal = self.goals[i]; var dx = goal.x - enemy.cellX; var dy = goal.y - enemy.cellY; var dist = dx * dx + dy * dy; if (dist < closestDist) { closestDist = dist; enemy.flyingTarget = goal; } } } } // Move directly toward the goal var ox = enemy.flyingTarget.x - enemy.currentCellX; var oy = enemy.flyingTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { // Reached the goal return true; } var angle = Math.atan2(oy, ox); // Rotate enemy graphic to match movement direction if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update the cell position to track where the flying enemy is enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // Update shadow position if this is a flying enemy return false; } // Handle normal pathfinding enemies if (!enemy.currentTarget) { enemy.currentTarget = cell.targets[0]; } if (enemy.currentTarget) { if (cell.score < enemy.currentTarget.score) { enemy.currentTarget = cell; } var ox = enemy.currentTarget.x - enemy.currentCellX; var oy = enemy.currentTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentTarget = undefined; return; } var angle = Math.atan2(oy, ox); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; }; }); var NextWaveButton = Container.expand(function () { var self = Container.call(this); var buttonBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 300; buttonBackground.height = 100; buttonBackground.tint = 0x0088FF; var buttonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.enabled = false; self.visible = false; self.update = function () { if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) { self.enabled = true; self.visible = true; buttonBackground.tint = 0x0088FF; self.alpha = 1; } else { self.enabled = false; self.visible = false; buttonBackground.tint = 0x888888; self.alpha = 0.7; } }; self.down = function () { if (!self.enabled) { return; } if (waveIndicator.gameStarted && currentWave < totalWaves) { currentWave++; // Increment to the next wave directly waveTimer = 0; // Reset wave timer waveInProgress = true; waveSpawned = false; // Get the type of the current wave (which is now the next wave) var waveType = waveIndicator.getWaveTypeName(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 30; self.addChild(notificationText); self.alpha = 1; self.fadeOutTime = 120; // Default fade out time self.update = function () { if (self.fadeOutTime > 0) { self.fadeOutTime--; self.alpha = Math.min(self.fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var SmokeParticle = Container.expand(function (startX, startY, targetX, targetY, color) { var self = Container.call(this); self.x = startX; self.y = startY; // Create multiple smoke layers for depth with irregular shapes var smokeLayers = []; var numLayers = 6; // More layers for better depth and realism for (var i = 0; i < numLayers; i++) { var smokeLayer = new Container(); self.addChild(smokeLayer); // Create multiple small circles to form irregular smoke shape var numCircles = 3 + Math.floor(Math.random() * 3); // 3-5 circles per layer for (var j = 0; j < numCircles; j++) { var circleContainer = new Container(); smokeLayer.addChild(circleContainer); var smokeGraphics = circleContainer.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); // Varied sizes for each circle to create irregular shape var baseSize = 200 + Math.random() * 300 + i * 50; var circleSize = baseSize * (0.6 + Math.random() * 0.8); smokeGraphics.width = circleSize; smokeGraphics.height = circleSize * (0.7 + Math.random() * 0.3); // Slight vertical variation // Position circles with overlap to create cloud-like formation var angle = j / numCircles * Math.PI * 2 + Math.random() * 0.5; var radius = baseSize * 0.3 * (0.5 + Math.random() * 0.5); circleContainer.x = Math.cos(angle) * radius; circleContainer.y = Math.sin(angle) * radius * 0.7; // Flatten vertically // Fire-like smoke colors - mix of dark greys with orange/red hints var smokeType = Math.random(); if (smokeType < 0.3) { // Dark smoke with red tint (30% chance) var redValue = 0x30 + Math.floor(Math.random() * 0x20); var grayValue = 0x10 + Math.floor(Math.random() * 0x10); smokeGraphics.tint = redValue << 16 | grayValue << 8 | grayValue; } else if (smokeType < 0.5) { // Orangish smoke (20% chance) var orangeRed = 0x40 + Math.floor(Math.random() * 0x20); var orangeGreen = 0x20 + Math.floor(Math.random() * 0x10); smokeGraphics.tint = orangeRed << 16 | orangeGreen << 8 | 0x08; } else { // Dark grey smoke (50% chance) var grayValue = 0x18 + Math.floor(Math.random() * 0x28); smokeGraphics.tint = grayValue << 16 | grayValue << 8 | grayValue; } // Vary alpha for depth and realism smokeGraphics.alpha = (0.3 - i * 0.04) * (0.7 + Math.random() * 0.3); smokeGraphics.blendMode = 1; // Additive blend for glowing effect } // Add some distortion to the layer smokeLayer.scaleX = 0.9 + Math.random() * 0.2; smokeLayer.scaleY = 0.8 + Math.random() * 0.3; // Add initial rotation for more organic look smokeLayer.rotation = (Math.random() - 0.5) * 0.4; smokeLayers.push(smokeLayer); } // Add wispy tendrils on top layers for more realistic smoke if (i < 2) { var tendrilCount = 2 + Math.floor(Math.random() * 2); for (var t = 0; t < tendrilCount; t++) { var tendril = new Container(); smokeLayer.addChild(tendril); var tendrilGraphics = tendril.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); tendrilGraphics.width = 80 + Math.random() * 60; tendrilGraphics.height = 200 + Math.random() * 100; tendrilGraphics.rotation = Math.random() * Math.PI; tendril.x = (Math.random() - 0.5) * 200; tendril.y = (Math.random() - 0.5) * 150; tendrilGraphics.alpha = 0.15 + Math.random() * 0.15; tendrilGraphics.tint = 0x202020; tendrilGraphics.blendMode = 1; } } // Add ember particles occasionally if (Math.random() < 0.2 && i < 3) { var emberCount = 1 + Math.floor(Math.random() * 2); for (var e = 0; e < emberCount; e++) { var emberLayer = new Container(); self.addChild(emberLayer); var ember = emberLayer.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); ember.width = 20 + Math.random() * 25; ember.height = ember.width; // Bright orange/red ember color ember.tint = Math.random() < 0.5 ? 0xFF4500 : 0xFF6347; ember.alpha = 0.9; ember.blendMode = 1; emberLayer.x = (Math.random() - 0.5) * 150; emberLayer.y = (Math.random() - 0.5) * 100; smokeLayers.push(emberLayer); } } // Start with 0 alpha and scale self.alpha = 0; self.scaleX = 0.3; self.scaleY = 0.3; // Calculate movement direction with organic variation var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); // Add perpendicular drift and upward bias (smoke rises) var perpX = -dy / distance * (Math.random() - 0.5) * 400; var perpY = dx / distance * (Math.random() - 0.5) * 300 - 80; // Strong upward bias // Add wind effect var windX = 50 + Math.random() * 50; // Slight rightward wind // Animate appearance with creeping fog effect tween(self, { alpha: 0.85, scaleX: 1.0, scaleY: 0.9, x: startX + dx * 0.1 + perpX * 0.15 + windX * 0.1, y: startY + dy * 0.1 + perpY * 0.15 - 30 // Rising motion }, { duration: 1800, easing: tween.easeOut, onFinish: function onFinish() { // Continue creeping forward with undulation tween(self, { alpha: 0.6, scaleX: 1.5, scaleY: 1.3, x: startX + dx * 0.3 + perpX * 0.4 + windX * 0.3, y: startY + dy * 0.3 + perpY * 0.4 - 80 // Continue rising }, { duration: 1500, easing: tween.linear, onFinish: function onFinish() { // Final fade out tween(self, { alpha: 0, scaleX: 2.0, scaleY: 1.7, x: startX + dx * 0.5 + perpX * 0.7 + windX * 0.5, y: startY + dy * 0.5 + perpY * 0.7 - 150 // Final rise }, { duration: 1200, easing: tween.easeIn, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); } }); } }); // Animate individual layers for undulating fog effect for (var i = 0; i < smokeLayers.length; i++) { (function (layer, index) { var _animateLayer = function animateLayer() { if (!self.parent) return; // More turbulent movement for fire smoke var turbulenceX = (Math.random() - 0.5) * 80; var turbulenceY = (Math.random() - 0.5) * 60 - 20; // Strong upward bias var rotationAmount = (Math.random() - 0.5) * 0.3; var scaleChange = 0.9 + Math.random() * 0.2; tween(layer, { x: layer.x + turbulenceX, y: layer.y + turbulenceY, rotation: layer.rotation + rotationAmount, scaleX: layer.scaleX * scaleChange, scaleY: layer.scaleY * scaleChange }, { duration: 2000 + index * 200, easing: tween.easeInOut, onFinish: _animateLayer }); }; // Start with slight delay for each layer LK.setTimeout(_animateLayer, index * 100); })(smokeLayers[i], i); } return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; // Increase size of base for easier touch var baseGraphics; if (self.towerType === 'default') { baseGraphics = self.attachAsset('1', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else if (self.towerType === 'rapid') { baseGraphics = self.attachAsset('2', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else if (self.towerType === 'sniper') { baseGraphics = self.attachAsset('3', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else if (self.towerType === 'splash') { baseGraphics = self.attachAsset('4', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else if (self.towerType === 'slow') { baseGraphics = self.attachAsset('5', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else if (self.towerType === 'poison') { baseGraphics = self.attachAsset('6', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } else { baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); } switch (self.towerType) { case 'rapid': // Don't tint if using image asset for rapid tower break; case 'sniper': // Don't tint if using image asset for sniper tower break; case 'splash': // Don't tint if using image asset for splash tower break; case 'slow': // Don't tint if using image asset for slow tower break; case 'poison': // Don't tint if using image asset for poison tower break; default: // Don't tint if using image asset for default tower break; } var towerCost = getTowerCost(self.towerType); // Add shadow for tower type label var towerDisplayNameShadow = self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1); if (self.towerType === 'rapid') { towerDisplayNameShadow = 'Twins'; } if (self.towerType === 'default') { towerDisplayNameShadow = 'Students'; } if (self.towerType === 'sniper') { towerDisplayNameShadow = 'Harry'; } if (self.towerType === 'splash') { towerDisplayNameShadow = 'Harmony'; } if (self.towerType === 'slow') { towerDisplayNameShadow = 'Ron'; } if (self.towerType === 'poison') { towerDisplayNameShadow = 'Snape'; } var typeLabelShadow = new Text2(towerDisplayNameShadow, { size: 50, fill: 0x000000, weight: 800 }); typeLabelShadow.anchor.set(0.5, 0.5); typeLabelShadow.x = 4; typeLabelShadow.y = -20 + 4; self.addChild(typeLabelShadow); // Add tower type label var towerDisplayName = self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1); if (self.towerType === 'rapid') { towerDisplayName = 'Twins'; } if (self.towerType === 'default') { towerDisplayName = 'Students'; } if (self.towerType === 'sniper') { towerDisplayName = 'Harry'; } if (self.towerType === 'splash') { towerDisplayName = 'Harmony'; } if (self.towerType === 'slow') { towerDisplayName = 'Ron'; } if (self.towerType === 'poison') { towerDisplayName = 'Snape'; } var typeLabel = new Text2(towerDisplayName, { size: 50, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.y = -20; // Position above center of tower self.addChild(typeLabel); // Add cost shadow var costLabelShadow = new Text2(towerCost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; costLabelShadow.y = 24 + 12; self.addChild(costLabelShadow); // Add cost label var costLabel = new Text2(towerCost, { size: 50, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.y = 20 + 12; self.addChild(costLabel); self.update = function () { // Check if player can afford this tower var canAfford = gold >= getTowerCost(self.towerType); // Set opacity based on affordability self.alpha = canAfford ? 1 : 0.5; }; return self; }); var Tower = Container.expand(function (id) { var self = Container.call(this); self.id = id || 'default'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.range = 3 * CELL_SIZE; // Standardized method to get the current range of the tower self.getRange = function () { // Always calculate range based on tower type and level switch (self.id) { case 'sniper': // Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost if (self.level === self.maxLevel) { return 12 * CELL_SIZE; // Significantly increased range for max level } return (5 + (self.level - 1) * 0.8) * CELL_SIZE; case 'splash': // Splash: base 2, +0.2 per level (max ~4 blocks at max level) return (2 + (self.level - 1) * 0.2) * CELL_SIZE; case 'rapid': // Rapid: base 2.5, +0.5 per level return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'slow': // Slow: base 3.5, +0.5 per level return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'poison': // Poison: base 3.2, +0.5 per level return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; default: // Default: base 3, +0.5 per level return (3 + (self.level - 1) * 0.5) * CELL_SIZE; } }; self.cellsInRange = []; self.fireRate = 60; self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; switch (self.id) { case 'rapid': self.fireRate = 30; self.damage = 5; self.range = 2.5 * CELL_SIZE; self.bulletSpeed = 7; break; case 'sniper': self.fireRate = 90; self.damage = 25; self.range = 5 * CELL_SIZE; self.bulletSpeed = 25; break; case 'splash': self.fireRate = 75; self.damage = 15; self.range = 2 * CELL_SIZE; self.bulletSpeed = 4; break; case 'slow': self.fireRate = 50; self.damage = 8; self.range = 3.5 * CELL_SIZE; self.bulletSpeed = 5; break; case 'poison': self.fireRate = 70; self.damage = 12; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 5; break; } var baseGraphics; if (self.id === 'default') { baseGraphics = self.attachAsset('1', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else if (self.id === 'rapid') { baseGraphics = self.attachAsset('2', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else if (self.id === 'sniper') { baseGraphics = self.attachAsset('3', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else if (self.id === 'splash') { baseGraphics = self.attachAsset('4', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else if (self.id === 'slow') { baseGraphics = self.attachAsset('5', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else if (self.id === 'poison') { baseGraphics = self.attachAsset('6', { anchorX: 0.5, anchorY: 0.5 }); // Ensure proper sizing for image asset baseGraphics.width = CELL_SIZE * 2.5; baseGraphics.height = CELL_SIZE * 2.5; } else { baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); } // Store reference to base graphics self.baseGraphics = baseGraphics; switch (self.id) { case 'rapid': // Don't tint if using image asset for rapid tower break; case 'sniper': // Don't tint if using image asset for sniper tower break; case 'splash': // Don't tint if using image asset for splash tower break; case 'slow': // Don't tint if using image asset for slow tower break; case 'poison': // Don't tint if using image asset for poison tower break; default: // Don't tint if using image asset for default tower // Ensure default tower image is not tinted if (self.id === 'default') { baseGraphics.tint = 0xFFFFFF; // Pure white (no tint) } break; } var levelIndicators = []; var maxDots = self.maxLevel; var dotSpacing = baseGraphics.width / (maxDots + 1); var dotSize = CELL_SIZE / 6; for (var i = 0; i < maxDots; i++) { var dot = new Container(); var outlineCircle = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); outlineCircle.width = dotSize + 4; outlineCircle.height = dotSize + 4; outlineCircle.tint = 0x000000; var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); towerLevelIndicator.width = dotSize; towerLevelIndicator.height = dotSize; towerLevelIndicator.tint = 0xCCCCCC; dot.x = -CELL_SIZE + dotSpacing * (i + 1); dot.y = CELL_SIZE * 0.7; self.addChild(dot); levelIndicators.push(dot); } var gunContainer = new Container(); self.addChild(gunContainer); var gunGraphics = gunContainer.attachAsset('defense', { anchorX: 0.5, anchorY: 0.5 }); // Make gun slightly smaller for default towers to not obscure the base if (self.id === 'default') { gunGraphics.scaleX = 0.8; gunGraphics.scaleY = 0.8; } self.updateLevelIndicators = function () { for (var i = 0; i < maxDots; i++) { var dot = levelIndicators[i]; var towerLevelIndicator = dot.children[1]; if (i < self.level) { towerLevelIndicator.tint = 0xFFFFFF; } else { switch (self.id) { case 'rapid': towerLevelIndicator.tint = 0x00AAFF; break; case 'sniper': towerLevelIndicator.tint = 0xFF5500; break; case 'splash': towerLevelIndicator.tint = 0x33CC00; break; case 'slow': towerLevelIndicator.tint = 0x9900FF; break; case 'poison': towerLevelIndicator.tint = 0x00FFAA; break; default: towerLevelIndicator.tint = 0xAAAAAA; } } } }; self.updateLevelIndicators(); self.refreshCellsInRange = function () { for (var i = 0; i < self.cellsInRange.length; i++) { var cell = self.cellsInRange[i]; var towerIndex = cell.towersInRange.indexOf(self); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } self.cellsInRange = []; var rangeRadius = self.getRange() / CELL_SIZE; var centerX = self.gridX + 1; var centerY = self.gridY + 1; var minI = Math.floor(centerX - rangeRadius - 0.5); var maxI = Math.ceil(centerX + rangeRadius + 0.5); var minJ = Math.floor(centerY - rangeRadius - 0.5); var maxJ = Math.ceil(centerY + rangeRadius + 0.5); for (var i = minI; i <= maxI; i++) { for (var j = minJ; j <= maxJ; j++) { var closestX = Math.max(i, Math.min(centerX, i + 1)); var closestY = Math.max(j, Math.min(centerY, j + 1)); var deltaX = closestX - centerX; var deltaY = closestY - centerY; var distanceSquared = deltaX * deltaX + deltaY * deltaY; if (distanceSquared <= rangeRadius * rangeRadius) { var cell = grid.getCell(i, j); if (cell) { self.cellsInRange.push(cell); cell.towersInRange.push(self); } } } } grid.renderDebug(); }; self.getTotalValue = function () { var baseTowerCost = getTowerCost(self.id); var totalInvestment = baseTowerCost; var baseUpgradeCost = baseTowerCost; // Upgrade cost now scales with base tower cost for (var i = 1; i < self.level; i++) { totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1)); } return totalInvestment; }; self.upgrade = function () { if (self.level < self.maxLevel) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.id); var upgradeCost; // Make last upgrade level extra expensive if (self.level === self.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); // Half the cost for final upgrade } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1)); } if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; // No need to update self.range here; getRange() is now the source of truth // Apply tower-specific upgrades based on type if (self.id === 'rapid') { if (self.level === self.maxLevel) { // Extra powerful last upgrade (double the effect) self.fireRate = Math.max(4, 30 - self.level * 9); // double the effect self.damage = 5 + self.level * 10; // double the effect self.bulletSpeed = 7 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(15, 30 - self.level * 3); // Fast tower gets faster with upgrades self.damage = 5 + self.level * 3; self.bulletSpeed = 7 + self.level * 0.7; } } else { if (self.level === self.maxLevel) { // Extra powerful last upgrade for all other towers (double the effect) self.fireRate = Math.max(5, 60 - self.level * 24); // double the effect self.damage = 10 + self.level * 20; // double the effect self.bulletSpeed = 5 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(20, 60 - self.level * 8); self.damage = 10 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.5; } } self.refreshCellsInRange(); self.updateLevelIndicators(); if (self.level > 1) { var levelDot = levelIndicators[self.level - 1].children[1]; tween(levelDot, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut, onFinish: function onFinish() { tween(levelDot, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); } return true; } else { var notification = game.addChild(new Notification("Not enough gold to upgrade!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } return false; }; self.findTarget = function () { var closestEnemy = null; var closestScore = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if enemy is in range if (distance <= self.getRange()) { // Handle flying enemies differently - they can be targeted regardless of path if (enemy.isFlying) { // For flying enemies, prioritize by distance to the goal if (enemy.flyingTarget) { var goalX = enemy.flyingTarget.x; var goalY = enemy.flyingTarget.y; var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY)); // Use distance to goal as score if (distToGoal < closestScore) { closestScore = distToGoal; closestEnemy = enemy; } } else { // If no flying target yet (shouldn't happen), prioritize by distance to tower if (distance < closestScore) { closestScore = distance; closestEnemy = enemy; } } } else { // For ground enemies, use the original path-based targeting // Get the cell for this enemy var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell && cell.pathId === pathId) { // Use the cell's score (distance to exit) for prioritization // Lower score means closer to exit if (cell.score < closestScore) { closestScore = cell.score; closestEnemy = enemy; } } } } } if (!closestEnemy) { self.targetEnemy = null; } return closestEnemy; }; self.update = function () { self.targetEnemy = self.findTarget(); if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); gunContainer.rotation = angle; if (LK.ticks - self.lastFired >= self.fireRate) { self.fire(); self.lastFired = LK.ticks; } } // Tower following behavior - move towards enemies periodically if (!self.isMoving && !self.moveTimer) { self.moveTimer = LK.ticks + Math.random() * 300 + 180; // Random interval 3-8 seconds } if (!self.isMoving && LK.ticks >= self.moveTimer) { // Find closest enemy in range to follow var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.originalX; var dy = enemy.y - self.originalY; var distance = Math.sqrt(dx * dx + dy * dy); // Increase following range to 2.5x the attack range if (distance <= self.getRange() * 2.5 && distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } if (closestEnemy) { self.isMoving = true; // Calculate movement towards enemy (but not too close) var enemyDx = closestEnemy.x - self.originalX; var enemyDy = closestEnemy.y - self.originalY; var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy); // Move 40% of the way towards the enemy (increased from 30%) var moveDistance = Math.min(CELL_SIZE * 3, enemyDistance * 0.4); var moveAngle = Math.atan2(enemyDy, enemyDx); var newX = self.originalX + Math.cos(moveAngle) * moveDistance; var newY = self.originalY + Math.sin(moveAngle) * moveDistance; // Animate towards enemy tween(self, { x: newX, y: newY }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { // Stay at new position for a moment LK.setTimeout(function () { // Return to original position tween(self, { x: self.originalX, y: self.originalY }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { self.isMoving = false; self.moveTimer = LK.ticks + Math.random() * 300 + 180; // Reset timer } }); }, 1000); } }); } else { // No enemy found, reset timer self.moveTimer = LK.ticks + Math.random() * 180 + 120; // Shorter wait if no target } } }; self.down = function (x, y, obj) { var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); var hasOwnMenu = false; var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self) { rangeCircle = game.children[i]; break; } } for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hasOwnMenu = true; break; } } if (hasOwnMenu) { for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } if (rangeCircle) { game.removeChild(rangeCircle); } selectedTower = null; grid.renderDebug(); return; } for (var i = 0; i < existingMenus.length; i++) { existingMenus[i].destroy(); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = self; var rangeIndicator = new Container(); rangeIndicator.isTowerRange = true; rangeIndicator.tower = self; game.addChild(rangeIndicator); rangeIndicator.x = self.x; rangeIndicator.y = self.y; var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.getRange() * 2; rangeGraphics.alpha = 0.3; var upgradeMenu = new UpgradeMenu(self); game.addChild(upgradeMenu); upgradeMenu.x = 2048 / 2; tween(upgradeMenu, { y: 2732 - 225 }, { duration: 200, easing: tween.backOut }); grid.renderDebug(); }; self.isInRange = function (enemy) { if (!enemy) { return false; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance <= self.getRange(); }; self.fire = function () { if (self.targetEnemy) { var potentialDamage = 0; for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) { potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage; } if (self.targetEnemy.health > potentialDamage) { var bulletX = self.x + Math.cos(gunContainer.rotation) * 40; var bulletY = self.y + Math.sin(gunContainer.rotation) * 40; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); // Set bullet type based on tower type bullet.type = self.id; // For slow tower, pass level for scaling slow effect if (self.id === 'slow') { bullet.sourceTowerLevel = self.level; } // Customize bullet appearance based on tower type switch (self.id) { case 'default': bullet.children[0].tint = 0xd2b48c; // Default tower bullet color bullet.children[0].width = 30; bullet.children[0].height = 30; // Randomly play either 111 or abrakadabra sound when default tower fires if (Math.random() < 0.5) { LK.getSound('111').play(); } else { LK.getSound('abrakadabra').play(); } // Change default tower to alternate between image assets 11 and 1 after firing if (self.baseGraphics) { // Check current asset and switch var currentAssetId = self.baseGraphics.texture ? '1' : '11'; if (self.lastUsedAsset === '1' || self.lastUsedAsset === undefined) { // Switch to asset 11 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('11', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '11'; } else { // Switch to asset 1 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('1', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '1'; } self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; case 'rapid': bullet.children[0].tint = 0x87ceeb; // Bright sky blue bolt bullet.children[0].width = 20; bullet.children[0].height = 20; // Play 222 sound when rapid tower fires LK.getSound('222').play(); // Change rapid tower to alternate between image assets 22 and 2 after firing if (self.baseGraphics) { // Check current asset and switch if (self.lastUsedAsset === '2' || self.lastUsedAsset === undefined) { // Switch to asset 22 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('22', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '22'; } else { // Switch to asset 2 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('2', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '2'; } self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; case 'sniper': bullet.children[0].tint = 0xff6347; // Bright orange-red spell bullet.children[0].width = 15; bullet.children[0].height = 15; // Play 33 sound when sniper tower fires LK.getSound('33').play(); // Change sniper tower to alternate between image assets 3 and 32 after firing if (self.baseGraphics) { // Check current asset and switch if (self.lastUsedAsset === '3' || self.lastUsedAsset === undefined) { // Switch to asset 32 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('32', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '32'; } else { // Switch to asset 3 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('3', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '3'; } self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; case 'splash': bullet.children[0].tint = 0x90ee90; // Bright light green spell bullet.children[0].width = 40; bullet.children[0].height = 40; // Play harmony sound when splash tower fires LK.getSound('harmony').play(); // Change splash tower to use image asset 42 after firing if (self.baseGraphics) { self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('42', { anchorX: 0.5, anchorY: 0.5 }); self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; case 'slow': bullet.children[0].tint = 0xdda0dd; // Bright plum spell bullet.children[0].width = 35; bullet.children[0].height = 35; // Play ron sound when slow tower fires LK.getSound('ron').play(); // Change slow tower to alternate between image assets 5 and 52 after firing if (self.baseGraphics) { // Check current asset and switch if (self.lastUsedAsset === '5' || self.lastUsedAsset === undefined) { // Switch to asset 52 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('52', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '52'; } else { // Switch to asset 5 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('5', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '5'; } self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; case 'poison': bullet.children[0].tint = 0x98fb98; // Bright pale green spell bullet.children[0].width = 35; bullet.children[0].height = 35; // Play snape sound when poison tower fires LK.getSound('snape').play(); // Change poison tower to alternate between image assets 6 and 62 after firing if (self.baseGraphics) { // Check current asset and switch if (self.lastUsedAsset === '6' || self.lastUsedAsset === undefined) { // Switch to asset 62 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('62', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '62'; } else { // Switch to asset 6 self.removeChild(self.baseGraphics); self.baseGraphics = self.attachAsset('6', { anchorX: 0.5, anchorY: 0.5 }); self.lastUsedAsset = '6'; } self.baseGraphics.width = CELL_SIZE * 2.5; self.baseGraphics.height = CELL_SIZE * 2.5; // Move base graphics to correct position in display list (before gun container) self.setChildIndex(self.baseGraphics, 0); } break; } game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); // --- Fire recoil effect for gunContainer --- // Stop any ongoing recoil tweens before starting a new one tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); // Always use the original resting position for recoil, never accumulate offset if (gunContainer._restX === undefined) { gunContainer._restX = 0; } if (gunContainer._restY === undefined) { gunContainer._restY = 0; } if (gunContainer._restScaleX === undefined) { gunContainer._restScaleX = 1; } if (gunContainer._restScaleY === undefined) { gunContainer._restScaleY = 1; } // Reset to resting position before animating (in case of interrupted tweens) gunContainer.x = gunContainer._restX; gunContainer.y = gunContainer._restY; gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; // Calculate recoil offset (recoil back along the gun's rotation) var recoilDistance = 8; var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance; var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance; // Animate recoil back from the resting position tween(gunContainer, { x: gunContainer._restX + recoilX, y: gunContainer._restY + recoilY }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { // Animate return to original position/scale tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: 90, easing: tween.cubicIn }); } }); } } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; // Store original position for movement behavior self.originalX = self.x; self.originalY = self.y; self.isMoving = false; self.moveTimer = null; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 1; } } } self.refreshCellsInRange(); }; return self; }); var TowerPreview = Container.expand(function () { var self = Container.call(this); var towerRange = 3; var rangeInPixels = towerRange * CELL_SIZE; self.towerType = 'default'; self.hasEnoughGold = true; var rangeIndicator = new Container(); self.addChild(rangeIndicator); var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.alpha = 0.3; var previewGraphics = self.attachAsset('towerpreview', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; self.canPlace = false; self.gridX = 0; self.gridY = 0; self.blockedByEnemy = false; self.update = function () { var previousHasEnoughGold = self.hasEnoughGold; self.hasEnoughGold = gold >= getTowerCost(self.towerType); // Only update appearance if the affordability status has changed if (previousHasEnoughGold !== self.hasEnoughGold) { self.updateAppearance(); } }; self.updateAppearance = function () { // Use Tower class to get the source of truth for range var tempTower = new Tower(self.towerType); var previewRange = tempTower.getRange(); // Clean up tempTower to avoid memory leaks if (tempTower && tempTower.destroy) { tempTower.destroy(); } // Set range indicator using unified range logic rangeGraphics.width = rangeGraphics.height = previewRange * 2; // For default tower type, replace the preview graphics with image asset 1 if (self.towerType === 'default') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 1 as preview previewGraphics = self.attachAsset('1', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else if (self.towerType === 'rapid') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 2 as preview previewGraphics = self.attachAsset('2', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else if (self.towerType === 'sniper') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 3 as preview previewGraphics = self.attachAsset('3', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else if (self.towerType === 'splash') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 4 as preview previewGraphics = self.attachAsset('4', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else if (self.towerType === 'slow') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 5 as preview previewGraphics = self.attachAsset('5', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else if (self.towerType === 'poison') { // Remove existing preview graphics self.removeChild(previewGraphics); // Add image asset 5 as preview previewGraphics = self.attachAsset('5', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Move preview graphics to correct position in display list (before range indicator) self.setChildIndex(previewGraphics, 1); } else { // For non-default towers, ensure we're using the shape asset if (previewGraphics.texture && previewGraphics.texture.baseTexture) { // Already using an image, need to switch back to shape self.removeChild(previewGraphics); previewGraphics = self.attachAsset('towerpreview', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; self.setChildIndex(previewGraphics, 1); } } switch (self.towerType) { case 'rapid': // Don't tint if using image asset for rapid tower break; case 'sniper': // Don't tint if using image asset for sniper tower break; case 'splash': // Don't tint if using image asset for splash tower break; case 'slow': // Don't tint if using image asset for slow tower break; case 'poison': // Don't tint if using image asset for poison tower break; default: // Don't tint if using image asset for default tower if (self.towerType !== 'default' && self.towerType !== 'rapid' && self.towerType !== 'sniper' && self.towerType !== 'splash' && self.towerType !== 'slow' && self.towerType !== 'poison') { previewGraphics.tint = 0xd2b48c; // Light tan } } if (!self.canPlace || !self.hasEnoughGold) { previewGraphics.tint = 0xFF0000; } }; self.updatePlacementStatus = function () { var validGridPlacement = true; if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) { validGridPlacement = false; } else { for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(self.gridX + i, self.gridY + j); if (!cell || cell.type !== 0) { validGridPlacement = false; break; } } if (!validGridPlacement) { break; } } } self.blockedByEnemy = false; if (validGridPlacement) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.currentCellY < 4) { continue; } // Only check non-flying enemies, flying enemies can pass over towers if (!enemy.isFlying) { if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) { self.blockedByEnemy = true; break; } if (enemy.currentTarget) { var targetX = enemy.currentTarget.x; var targetY = enemy.currentTarget.y; if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) { self.blockedByEnemy = true; break; } } } } } self.canPlace = validGridPlacement && !self.blockedByEnemy; self.hasEnoughGold = gold >= getTowerCost(self.towerType); self.updateAppearance(); }; self.checkPlacement = function () { self.updatePlacementStatus(); }; self.snapToGrid = function (x, y) { var gridPosX = x - grid.x; var gridPosY = y - grid.y; self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; self.checkPlacement(); }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 500; menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; var towerTypeName = self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1); if (self.tower.id === 'rapid') { towerTypeName = 'Twins'; } if (self.tower.id === 'default') { towerTypeName = 'Students'; } if (self.tower.id === 'sniper') { towerTypeName = 'Harry'; } if (self.tower.id === 'splash') { towerTypeName = 'Harmony'; } if (self.tower.id === 'slow') { towerTypeName = 'Ron'; } if (self.tower.id === 'poison') { towerTypeName = 'Snape'; } var towerTypeText = new Text2(towerTypeName + ' Tower', { size: 80, fill: 0xFFFFFF, weight: 800 }); towerTypeText.anchor.set(0, 0); towerTypeText.x = -840; towerTypeText.y = -160; self.addChild(towerTypeText); var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', { size: 70, fill: 0xFFFFFF, weight: 400 }); statsText.anchor.set(0, 0.5); statsText.x = -840; statsText.y = 50; self.addChild(statsText); var buttonsContainer = new Container(); buttonsContainer.x = 500; self.addChild(buttonsContainer); var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; buttonBackground.height = 150; var isMaxLevel = self.tower.level >= self.tower.maxLevel; // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var upgradeCost; if (isMaxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888; var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; sellButtonBackground.height = 150; sellButtonBackground.tint = 0xCC0000; var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); upgradeButton.y = -85; sellButton.y = 85; var closeButton = new Container(); self.addChild(closeButton); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; upgradeButton.down = function (x, y, obj) { if (self.tower.level >= self.tower.maxLevel) { var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (self.tower.upgrade()) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); if (self.tower.level >= self.tower.maxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s'); buttonText.setText('Upgrade: ' + upgradeCost + ' gold'); var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); sellButtonText.setText('Sell: +' + sellValue + ' gold'); if (self.tower.level >= self.tower.maxLevel) { buttonBackground.tint = 0x888888; buttonText.setText('Max Level'); } var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { rangeCircle = game.children[i]; break; } } if (rangeCircle) { var rangeGraphics = rangeCircle.children[0]; rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; } else { var newRangeIndicator = new Container(); newRangeIndicator.isTowerRange = true; newRangeIndicator.tower = self.tower; game.addChildAt(newRangeIndicator, 0); newRangeIndicator.x = self.tower.x; newRangeIndicator.y = self.tower.y; var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; rangeGraphics.alpha = 0.3; } tween(self, { scaleX: 1.05, scaleY: 1.05 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn }); } }); } }; sellButton.down = function (x, y, obj) { var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); setGold(gold + sellValue); var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; var gridX = self.tower.gridX; var gridY = self.tower.gridY; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 0; var towerIndex = cell.towersInRange.indexOf(self.tower); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } if (selectedTower === self.tower) { selectedTower = null; } var towerIndex = towers.indexOf(self.tower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } towerLayer.removeChild(self.tower); grid.pathFind(); grid.renderDebug(); self.destroy(); for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } }; closeButton.down = function (x, y, obj) { hideUpgradeMenu(self); selectedTower = null; grid.renderDebug(); }; self.update = function () { if (self.tower.level >= self.tower.maxLevel) { if (buttonText.text !== 'Max Level') { buttonText.setText('Max Level'); buttonBackground.tint = 0x888888; } return; } // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var currentUpgradeCost; if (self.tower.level >= self.tower.maxLevel) { currentUpgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } var canAfford = gold >= currentUpgradeCost; buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888; var newText = 'Upgrade: ' + currentUpgradeCost + ' gold'; if (buttonText.text !== newText) { buttonText.setText(newText); } }; return self; }); var WaveIndicator = Container.expand(function () { var self = Container.call(this); self.gameStarted = false; self.waveMarkers = []; self.waveTypes = []; self.enemyCounts = []; self.indicatorWidth = 0; self.lastBossType = null; // Track the last boss type to avoid repeating var blockWidth = 400; var totalBlocksWidth = blockWidth * totalWaves; var startMarker = new Container(); var startBlock = startMarker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); startBlock.width = blockWidth - 10; startBlock.height = 70 * 2; startBlock.tint = 0x00AA00; // Add shadow for start text var startTextShadow = new Text2("Start Game", { size: 50, fill: 0x000000, weight: 800 }); startTextShadow.anchor.set(0.5, 0.5); startTextShadow.x = 4; startTextShadow.y = 4; startMarker.addChild(startTextShadow); var startText = new Text2("Start Game", { size: 50, fill: 0xFFFFFF, weight: 800 }); startText.anchor.set(0.5, 0.5); startMarker.addChild(startText); startMarker.x = -self.indicatorWidth; self.addChild(startMarker); self.waveMarkers.push(startMarker); startMarker.down = function () { if (!self.gameStarted) { self.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; startBlock.tint = 0x00FF00; startText.setText("Started!"); startTextShadow.setText("Started!"); // Make sure shadow position remains correct after text change startTextShadow.x = 4; startTextShadow.y = 4; var notification = game.addChild(new Notification("Game started! Wave 1 incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; for (var i = 0; i < totalWaves; i++) { var marker = new Container(); var block = marker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth - 10; block.height = 70 * 2; // --- Begin new unified wave logic --- var waveType = "normal"; var enemyType = "normal"; var enemyCount = 10; var isBossWave = (i + 1) % 10 === 0; // Ensure all types appear in early waves if (i === 0) { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } else if (i === 1) { block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if (i === 2) { block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; enemyCount = 10; } else if (i === 3) { block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if (i === 4) { block.tint = 0xFF00FF; waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else if (isBossWave) { // Boss waves: cycle through all boss types, last boss is always flying var bossTypes = ['normal', 'fast', 'immune', 'flying']; var bossTypeIndex = Math.floor((i + 1) / 10) - 1; if (i === totalWaves - 1) { // Last boss is always flying enemyType = 'flying'; waveType = "Boss Flying"; block.tint = 0xFFFF00; } else { enemyType = bossTypes[bossTypeIndex % bossTypes.length]; switch (enemyType) { case 'normal': block.tint = 0xAAAAAA; waveType = "Boss Normal"; break; case 'fast': block.tint = 0x00AAFF; waveType = "Boss Fast"; break; case 'immune': block.tint = 0xAA0000; waveType = "Boss Immune"; break; case 'flying': block.tint = 0xFFFF00; waveType = "Boss Flying"; break; } } enemyCount = 1; // Make the wave indicator for boss waves stand out // Set boss wave color to the color of the wave type switch (enemyType) { case 'normal': block.tint = 0xAAAAAA; break; case 'fast': block.tint = 0x00AAFF; break; case 'immune': block.tint = 0xAA0000; break; case 'flying': block.tint = 0xFFFF00; break; default: block.tint = 0xFF0000; break; } } else if ((i + 1) % 5 === 0) { // Every 5th non-boss wave is fast block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if ((i + 1) % 4 === 0) { // Every 4th non-boss wave is immune block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; enemyCount = 10; } else if ((i + 1) % 7 === 0) { // Every 7th non-boss wave is flying block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if ((i + 1) % 3 === 0) { // Every 3rd non-boss wave is swarm block.tint = 0xFF00FF; waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } // --- End new unified wave logic --- // Mark boss waves with a special visual indicator if (isBossWave && enemyType !== 'swarm') { // Add a crown or some indicator to the wave marker for boss waves var bossIndicator = marker.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); bossIndicator.width = 30; bossIndicator.height = 30; bossIndicator.tint = 0xFFD700; // Gold color bossIndicator.y = -block.height / 2 - 15; // Change the wave type text to indicate boss waveType = "BOSS"; } // Store the wave type and enemy count self.waveTypes[i] = enemyType; self.enemyCounts[i] = enemyCount; // Add shadow for wave type - 30% smaller than before var waveTypeShadow = new Text2(waveType, { size: 56, fill: 0x000000, weight: 800 }); waveTypeShadow.anchor.set(0.5, 0.5); waveTypeShadow.x = 4; waveTypeShadow.y = 4; marker.addChild(waveTypeShadow); // Add wave type text - 30% smaller than before var waveTypeText = new Text2(waveType, { size: 56, fill: 0xFFFFFF, weight: 800 }); waveTypeText.anchor.set(0.5, 0.5); waveTypeText.y = 0; marker.addChild(waveTypeText); // Add shadow for wave number - 20% larger than before var waveNumShadow = new Text2((i + 1).toString(), { size: 48, fill: 0x000000, weight: 800 }); waveNumShadow.anchor.set(1.0, 1.0); waveNumShadow.x = blockWidth / 2 - 16 + 5; waveNumShadow.y = block.height / 2 - 12 + 5; marker.addChild(waveNumShadow); // Main wave number text - 20% larger than before var waveNum = new Text2((i + 1).toString(), { size: 48, fill: 0xFFFFFF, weight: 800 }); waveNum.anchor.set(1.0, 1.0); waveNum.x = blockWidth / 2 - 16; waveNum.y = block.height / 2 - 12; marker.addChild(waveNum); marker.x = -self.indicatorWidth + (i + 1) * blockWidth; self.addChild(marker); self.waveMarkers.push(marker); } // Get wave type for a specific wave number self.getWaveType = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return "normal"; } // If this is a boss wave (waveNumber % 10 === 0), and the type is the same as lastBossType // then we should return a different boss type var waveType = self.waveTypes[waveNumber - 1]; return waveType; }; // Get enemy count for a specific wave number self.getEnemyCount = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return 10; } return self.enemyCounts[waveNumber - 1]; }; // Get display name for a wave type self.getWaveTypeName = function (waveNumber) { var type = self.getWaveType(waveNumber); var typeName = type.charAt(0).toUpperCase() + type.slice(1); // Add boss prefix for boss waves (every 10th wave) if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') { typeName = "BOSS"; } return typeName; }; self.positionIndicator = new Container(); var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = blockWidth - 10; indicator.height = 16; indicator.tint = 0xffad0e; indicator.y = -65; var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator2.width = blockWidth - 10; indicator2.height = 16; indicator2.tint = 0xffad0e; indicator2.y = 65; var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); leftWall.width = 16; leftWall.height = 146; leftWall.tint = 0xffad0e; leftWall.x = -(blockWidth - 16) / 2; var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); rightWall.width = 16; rightWall.height = 146; rightWall.tint = 0xffad0e; rightWall.x = (blockWidth - 16) / 2; self.addChild(self.positionIndicator); self.update = function () { var progress = waveTimer / nextWaveTime; var moveAmount = (progress + currentWave) * blockWidth; for (var i = 0; i < self.waveMarkers.length; i++) { var marker = self.waveMarkers[i]; marker.x = -moveAmount + i * blockWidth; } self.positionIndicator.x = 0; for (var i = 0; i < totalWaves + 1; i++) { var marker = self.waveMarkers[i]; if (i === 0) { continue; } var block = marker.children[0]; if (i - 1 < currentWave) { block.alpha = .5; } } self.handleWaveProgression = function () { if (!self.gameStarted) { return; } if (currentWave < totalWaves) { waveTimer++; if (waveTimer >= nextWaveTime) { waveTimer = 0; currentWave++; waveInProgress = true; waveSpawned = false; if (currentWave != 1) { var waveType = self.getWaveTypeName(currentWave); var enemyCount = self.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } }; self.handleWaveProgression(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x8B7355 }); /**** * Game Code ****/ var isHidingUpgradeMenu = false; var gameStarted = false; var startScreen = new Container(); function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; } }); } var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var enemies = []; var towers = []; var bullets = []; var defenses = []; var selectedTower = null; var gold = 200; var lives = 5; var score = 0; var currentWave = 0; var totalWaves = 50; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 2000; var sourceTower = null; var enemiesToSpawn = 10; // Default number of enemies per wave var backgroundFlames = []; var flameSpawnTimer = 0; var flameSpawnInterval = 180 + Math.random() * 240; // Spawn flame every 3-7 seconds var lightningTimer = 0; var lightningInterval = 900; // 15 seconds at 60 FPS var groundShakeTimer = 0; var groundShakeInterval = 1200; // 20 seconds at 60 FPS var flameLayer = new Container(); var smokeLayer = new Container(); var smokeParticles = []; var goldText = new Text2('Gold: ' + gold, { size: 60, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); var livesText = new Text2('Lives: ' + lives, { size: 60, fill: 0x00FF00, weight: 800 }); livesText.anchor.set(0.5, 0.5); var scoreText = new Text2('Score: ' + score, { size: 60, fill: 0xFF0000, weight: 800 }); scoreText.anchor.set(0.5, 0.5); var 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 debugLayer = new Container(); var towerLayer = new Container(); // Create three separate layers for enemy hierarchy var enemyLayerBottom = new Container(); // For normal enemies var enemyLayerMiddle = new Container(); // For shadows var enemyLayerTop = new Container(); // For flying enemies var enemyLayer = new Container(); // Main container to hold all enemy layers // Add layers in correct order (bottom first, then middle for shadows, then top) enemyLayer.addChild(enemyLayerBottom); enemyLayer.addChild(enemyLayerMiddle); enemyLayer.addChild(enemyLayerTop); var grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); // Add background image var backgroundImage = LK.getAsset('arkaplan', { anchorX: 0, anchorY: 0, scaleX: 1, scaleY: 1 }); backgroundImage.x = 0; backgroundImage.y = 0; game.addChild(backgroundImage); debugLayer.addChild(grid); game.addChild(debugLayer); game.addChild(flameLayer); game.addChild(smokeLayer); game.addChild(towerLayer); game.addChild(enemyLayer); var offset = 0; var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; var isDragging = false; var draggingTower = null; var dragOffset = { x: 0, y: 0 }; function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); return blocked; } function getTowerCost(towerType) { var cost = 5; switch (towerType) { case 'rapid': cost = 15; break; case 'sniper': cost = 25; break; case 'splash': cost = 35; break; case 'slow': cost = 45; break; case 'poison': cost = 55; break; } return cost; } function getTowerSellValue(totalValue) { return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue; } function placeTower(gridX, gridY, towerType) { var towerCost = getTowerCost(towerType); if (gold >= towerCost) { // Ensure towerType is 'default' if not specified var actualTowerType = towerType || 'default'; var tower = new Tower(actualTowerType); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); grid.pathFind(); grid.renderDebug(); return true; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } game.down = function (x, y, obj) { if (!gameStarted) { return; } var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenuVisible) { return; } // Check if clicking on an existing tower first for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (x >= tower.x - tower.baseGraphics.width / 2 && x <= tower.x + tower.baseGraphics.width / 2 && y >= tower.y - tower.baseGraphics.height / 2 && y <= tower.y + tower.baseGraphics.height / 2) { // Start dragging the existing tower draggingTower = tower; isDragging = true; dragOffset.x = x - tower.x; dragOffset.y = y - tower.y; // Clear the tower's grid cells for (var j = 0; j < 2; j++) { for (var k = 0; k < 2; k++) { var cell = grid.getCell(tower.gridX + j, tower.gridY + k); if (cell) { cell.type = 0; } } } // Show visual feedback that tower is being dragged tower.alpha = 0.7; // Remove tower from towers in range for all cells tower.refreshCellsInRange(); grid.pathFind(); grid.renderDebug(); return; } } // Check source towers for creating new towers for (var i = 0; i < sourceTowers.length; i++) { var tower = sourceTowers[i]; if (x >= tower.x - tower.width / 2 && x <= tower.x + tower.width / 2 && y >= tower.y - tower.height / 2 && y <= tower.y + tower.height / 2) { towerPreview.visible = true; isDragging = true; towerPreview.towerType = tower.towerType; towerPreview.updateAppearance(); // Apply the same offset as in move handler to ensure consistency when starting drag towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); break; } } }; game.move = function (x, y, obj) { if (!gameStarted) { return; } if (isDragging) { if (draggingTower) { // Move the existing tower draggingTower.x = x - dragOffset.x; draggingTower.y = y - dragOffset.y; } else { // Shift the y position upward by 1.5 tiles to show preview above finger towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); } } }; game.up = function (x, y, obj) { if (!gameStarted) { return; } var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerLeft = tower.x - tower.width / 2; var towerRight = tower.x + tower.width / 2; var towerTop = tower.y - tower.height / 2; var towerBottom = tower.y + tower.height / 2; if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) { var clickedOnMenu = false; for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; var menuWidth = 2048; var menuHeight = 450; var menuLeft = menu.x - menuWidth / 2; var menuRight = menu.x + menuWidth / 2; var menuTop = menu.y - menuHeight / 2; var menuBottom = menu.y + menuHeight / 2; if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) { clickedOnMenu = true; break; } } if (!clickedOnMenu) { for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; hideUpgradeMenu(menu); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = null; grid.renderDebug(); } } if (isDragging) { isDragging = false; if (draggingTower) { // Handle existing tower repositioning var gridPosX = draggingTower.x - grid.x; var gridPosY = draggingTower.y - grid.y; var newGridX = Math.floor(gridPosX / CELL_SIZE); var newGridY = Math.floor(gridPosY / CELL_SIZE); // Check if the new position is valid var validPlacement = true; var blockedByEnemy = false; // Check bounds if (newGridY <= 4 || newGridY + 1 >= grid.cells[0].length - 4) { validPlacement = false; } else { // Check if cells are available for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(newGridX + i, newGridY + j); if (!cell || cell.type !== 0) { validPlacement = false; break; } } if (!validPlacement) break; } // Check for enemies in the way if (validPlacement) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.currentCellY < 4) continue; if (!enemy.isFlying) { if (enemy.cellX >= newGridX && enemy.cellX < newGridX + 2 && enemy.cellY >= newGridY && enemy.cellY < newGridY + 2) { blockedByEnemy = true; break; } } } } } // Check if placement would block path var wouldBlock = false; if (validPlacement && !blockedByEnemy) { wouldBlock = wouldBlockPath(newGridX, newGridY); } if (validPlacement && !blockedByEnemy && !wouldBlock) { // Place tower in new position draggingTower.placeOnGrid(newGridX, newGridY); draggingTower.alpha = 1; var notification = game.addChild(new Notification("Tower moved!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else { // Return tower to original position draggingTower.x = grid.x + draggingTower.gridX * CELL_SIZE + CELL_SIZE / 2; draggingTower.y = grid.y + draggingTower.gridY * CELL_SIZE + CELL_SIZE / 2; draggingTower.alpha = 1; // Restore grid cells for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(draggingTower.gridX + i, draggingTower.gridY + j); if (cell) { cell.type = 1; } } } draggingTower.refreshCellsInRange(); if (!validPlacement) { var notification = game.addChild(new Notification("Cannot move tower here!")); } else if (blockedByEnemy) { var notification = game.addChild(new Notification("Cannot move: Enemy in the way!")); } else if (wouldBlock) { var notification = game.addChild(new Notification("Cannot move: Would block path!")); } notification.x = 2048 / 2; notification.y = grid.height - 50; } draggingTower = null; grid.pathFind(); grid.renderDebug(); } else { // Handle new tower placement if (towerPreview.canPlace) { if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) { placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType); } else { var notification = game.addChild(new Notification("Tower would block the path!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } else if (towerPreview.blockedByEnemy) { var notification = game.addChild(new Notification("Cannot build: Enemy in the way!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (towerPreview.visible) { var notification = game.addChild(new Notification("Cannot build here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } towerPreview.visible = false; } if (isDragging) { var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } } } }; var waveIndicator = new WaveIndicator(); waveIndicator.x = 2048 / 2; waveIndicator.y = 2732 - 80; game.addChild(waveIndicator); var nextWaveButtonContainer = new Container(); var nextWaveButton = new NextWaveButton(); nextWaveButton.x = 2048 - 200; nextWaveButton.y = 2732 - 100 + 20; nextWaveButtonContainer.addChild(nextWaveButton); game.addChild(nextWaveButtonContainer); // Create start screen with Hogwarts under attack image var startScreenBackground = startScreen.attachAsset('hogwartsAttack', { anchorX: 0.5, anchorY: 0.5 }); startScreenBackground.x = 1024; startScreenBackground.y = 1366; // Center vertically // Image is already 2048x2732, perfect fit startScreenBackground.scaleX = 1; startScreenBackground.scaleY = 1; // Title text removed as requested // Subtitle removed as requested // Add instruction text to tap anywhere var tapInstructionShadow = new Text2("Tap anywhere to start", { size: 80, fill: 0x000000, weight: 600 }); tapInstructionShadow.anchor.set(0.5, 0.5); tapInstructionShadow.x = 1024 + 4; tapInstructionShadow.y = 2732 - 600 + 4; startScreen.addChild(tapInstructionShadow); var tapInstruction = new Text2("Tap anywhere to start", { size: 80, fill: 0xFFD700, weight: 600 }); tapInstruction.anchor.set(0.5, 0.5); tapInstruction.x = 1024; tapInstruction.y = 2732 - 600; startScreen.addChild(tapInstruction); // Add pulsing animation to instruction text var _pulseAnimation = function pulseAnimation() { if (tapInstruction.parent) { tween(tapInstruction, { alpha: 0.4, scaleX: 0.95, scaleY: 0.95 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (tapInstruction.parent) { tween(tapInstruction, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 1000, easing: tween.easeInOut, onFinish: _pulseAnimation }); } } }); } }; _pulseAnimation(); // Make entire start screen clickable startScreen.down = function () { // Start the game gameStarted = true; game.removeChild(startScreen); // Add wizard movement warning var movementWarning = game.addChild(new Notification("Wizards can be moved by dragging them!")); movementWarning.x = 2048 / 2; movementWarning.y = 2732 / 2; // Make the warning last longer than normal notifications movementWarning.fadeOutTime = 300; // 5 seconds at 60 FPS // Music is already playing from start screen, no need to restart }; // Add start screen to game game.addChild(startScreen); // Play exciting music on start screen LK.playMusic('g1', { loop: true }); // Wizard movement warning and music are now handled in the start screen button handler // Create restart button var restartButton = new Container(); var restartBackground = restartButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); restartBackground.width = 180; restartBackground.height = 80; restartBackground.tint = 0xFF6B6B; var restartText = new Text2("Restart", { size: 40, fill: 0xFFFFFF, weight: 800 }); restartText.anchor.set(0.5, 0.5); restartButton.addChild(restartText); // Position in top left area but outside the reserved 100x100 zone restartButton.x = 200; // Outside the 100px restricted zone restartButton.y = 120; // Below the top UI elements restartButton.down = function () { // Reset all game variables to initial state gold = 200; lives = 20; score = 0; currentWave = 0; waveTimer = 0; waveInProgress = false; waveSpawned = false; selectedTower = null; isDragging = false; draggingTower = null; // Clear all enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); } if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } } enemies = []; // Clear all bullets for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i].parent) { bullets[i].destroy(); } } bullets = []; // Clear all towers for (var i = towers.length - 1; i >= 0; i--) { var tower = towers[i]; // Reset grid cells for (var j = 0; j < 2; j++) { for (var k = 0; k < 2; k++) { var cell = grid.getCell(tower.gridX + j, tower.gridY + k); if (cell) { cell.type = 0; } } } towerLayer.removeChild(tower); } towers = []; // Clear any upgrade menus var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } // Clear any range indicators for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } // Clear background flames for (var i = backgroundFlames.length - 1; i >= 0; i--) { if (backgroundFlames[i].parent) { backgroundFlames[i].destroy(); } } backgroundFlames = []; // Clear smoke particles for (var i = smokeParticles.length - 1; i >= 0; i--) { if (smokeParticles[i].parent) { smokeParticles[i].destroy(); } } smokeParticles = []; flameSpawnTimer = 0; lightningTimer = 0; groundShakeTimer = 0; // Reset wave indicator waveIndicator.gameStarted = false; var startMarker = waveIndicator.waveMarkers[0]; if (startMarker && startMarker.children[0]) { startMarker.children[0].tint = 0x00AA00; } if (startMarker && startMarker.children[1]) { startMarker.children[1].setText("Start Game"); } if (startMarker && startMarker.children[2]) { startMarker.children[2].setText("Start Game"); } // Reset grid grid.pathFind(); grid.renderDebug(); // Update UI updateUI(); // Hide tower preview towerPreview.visible = false; var notification = game.addChild(new Notification("Game restarted!")); notification.x = 2048 / 2; notification.y = grid.height - 150; }; game.addChild(restartButton); var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison']; var sourceTowers = []; var towerSpacing = 300; // Increase spacing for larger towers var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2; var towerY = 2732 - CELL_SIZE * 3 - 90; for (var i = 0; i < towerTypes.length; i++) { var tower = new SourceTower(towerTypes[i]); tower.x = startX + i * towerSpacing; tower.y = towerY; towerLayer.addChild(tower); sourceTowers.push(tower); } sourceTower = null; enemiesToSpawn = 10; game.update = function () { if (!gameStarted) { return; } if (waveInProgress) { if (!waveSpawned) { waveSpawned = true; // Get wave type and enemy count from the wave indicator var waveType = waveIndicator.getWaveType(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); // Check if this is a boss wave var isBossWave = currentWave % 10 === 0 && currentWave > 0; if (isBossWave && waveType !== 'swarm') { // Boss waves have just 1 enemy regardless of what the wave indicator says enemyCount = 1; // Show boss announcement var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️")); notification.x = 2048 / 2; notification.y = grid.height - 200; } // Spawn the appropriate number of enemies for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); // Play monster sound when enemy spawns LK.getSound('ron').play(); // Add enemy to the appropriate layer based on type if (enemy.isFlying) { // Add flying enemy to the top layer enemyLayerTop.addChild(enemy); // If it's a flying enemy, add its shadow to the middle layer if (enemy.shadow) { enemyLayerMiddle.addChild(enemy.shadow); } } else { // Add normal/ground enemies to the bottom layer enemyLayerBottom.addChild(enemy); } // Scale difficulty with wave number but don't apply to boss // as bosses already have their health multiplier // Use exponential scaling for health var healthMultiplier = Math.pow(1.12, currentWave); // ~20% increase per wave enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier); enemy.health = enemy.maxHealth; // Increment speed slightly with wave number //enemy.speed = enemy.speed + currentWave * 0.002; // All enemy types now spawn in the middle 6 tiles at the top spacing var gridWidth = 24; var midPoint = Math.floor(gridWidth / 2); // 12 // Find a column that isn't occupied by another enemy that's not yet in view var availableColumns = []; for (var col = midPoint - 3; col < midPoint + 3; col++) { var columnOccupied = false; // Check if any enemy is already in this column but not yet in view for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === col && enemies[e].currentCellY < 4) { columnOccupied = true; break; } } if (!columnOccupied) { availableColumns.push(col); } } // If all columns are occupied, use original random method var spawnX; if (availableColumns.length > 0) { // Choose a random unoccupied column spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)]; } else { // Fallback to random if all columns are occupied spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 9 to 14 } var spawnY = -1 - Math.random() * 5; // Random distance above the grid for spreading enemy.cellX = spawnX; enemy.cellY = 5; // Position after entry enemy.currentCellX = spawnX; enemy.currentCellY = spawnY; enemy.waveNumber = currentWave; enemies.push(enemy); } } var currentWaveEnemiesRemaining = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].waveNumber === currentWave) { currentWaveEnemiesRemaining = true; break; } } if (waveSpawned && !currentWaveEnemiesRemaining) { waveInProgress = false; waveSpawned = false; } } for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; if (enemy.health <= 0) { for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } // Boss enemies give more gold and score var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : 5; var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); // Give more score for defeating a boss var scoreValue = enemy.isBoss ? 100 : 5; score += scoreValue; // Add a notification for boss defeat if (enemy.isBoss) { var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } updateUI(); // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); continue; } if (grid.updateEnemy(enemy)) { // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); lives = Math.max(0, lives - 1); updateUI(); if (lives <= 0) { LK.showGameOver(); } } } for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent) { if (bullets[i].targetEnemy) { var targetEnemy = bullets[i].targetEnemy; var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } bullets.splice(i, 1); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } // Background flame spawning logic if (gameStarted) { flameSpawnTimer++; if (flameSpawnTimer >= flameSpawnInterval) { flameSpawnTimer = 0; flameSpawnInterval = 180 + Math.random() * 240; // Reset interval // Spawn a new flame at random position var flameX = 100 + Math.random() * (2048 - 200); // Avoid edges var flameY = 300 + Math.random() * (2200 - 300); // Avoid UI areas var flame = new BackgroundFlame(flameX, flameY); flameLayer.addChild(flame); backgroundFlames.push(flame); } // Clean up destroyed flames from array for (var i = backgroundFlames.length - 1; i >= 0; i--) { if (!backgroundFlames[i].parent) { backgroundFlames.splice(i, 1); } } } // Lightning effect every 15 seconds if (gameStarted) { lightningTimer++; if (lightningTimer >= lightningInterval) { lightningTimer = 0; // Play lightning sound LK.getSound('lightning').play(); // Create dark overlay for lightning effect var darkOverlay = new Container(); var overlayGraphics = darkOverlay.attachAsset('notification', { anchorX: 0, anchorY: 0 }); overlayGraphics.width = 2048; overlayGraphics.height = 2732; overlayGraphics.tint = 0x000000; darkOverlay.alpha = 0; game.addChild(darkOverlay); // Create smoke particles from edges var smokeCount = 40; // Fewer but larger, more complex particles var centerX = 1024; var centerY = 1366; // Define fire source locations for more realistic smoke origins var fireSources = [ // Top edge fire sources { x: 300, y: -100 }, { x: 700, y: -120 }, { x: 1200, y: -100 }, { x: 1700, y: -110 }, // Right edge fire sources { x: 2148, y: 400 }, { x: 2168, y: 900 }, { x: 2158, y: 1500 }, { x: 2148, y: 2200 }, // Bottom edge fire sources { x: 300, y: 2832 }, { x: 800, y: 2852 }, { x: 1300, y: 2842 }, { x: 1700, y: 2832 }, // Left edge fire sources { x: -100, y: 400 }, { x: -120, y: 1000 }, { x: -110, y: 1600 }, { x: -100, y: 2200 }]; // Create initial wave of smoke from fire sources for (var i = 0; i < smokeCount; i++) { // Pick a random fire source var sourceIndex = Math.floor(Math.random() * fireSources.length); var source = fireSources[sourceIndex]; // Create smoke originating from near the fire source with variation var startX = source.x + (Math.random() - 0.5) * 100; var startY = source.y + (Math.random() - 0.5) * 80; // Create smoke with varied delays for waves of fog LK.setTimeout(function (x, y) { return function () { // Smoke spreads inward and upward from fire sources var inwardAngle = Math.atan2(centerY - y, centerX - x); // Add variation to the angle for more natural spread var angleVariation = (Math.random() - 0.5) * Math.PI / 3; var finalAngle = inwardAngle + angleVariation; var spreadDistance = 400 + Math.random() * 600; var targetX = x + Math.cos(finalAngle) * spreadDistance; var targetY = y + Math.sin(finalAngle) * spreadDistance - 200; // Strong upward bias var smoke = new SmokeParticle(x, y, targetX, targetY); smokeLayer.addChild(smoke); smokeParticles.push(smoke); }; }(startX, startY), Math.random() * 1000); // Staggered over 1000ms } // Create second wave of smoke for continuous burning effect LK.setTimeout(function () { var secondWaveCount = 30; // Fewer particles for second wave for (var i = 0; i < secondWaveCount; i++) { // Use the same fire sources but with more intensity var sourceIndex = Math.floor(Math.random() * fireSources.length); var source = fireSources[sourceIndex]; // Create larger spread from fire sources for second wave var startX = source.x + (Math.random() - 0.5) * 150; var startY = source.y + (Math.random() - 0.5) * 120; // Smoke rises and spreads in a more realistic pattern // Create smoke columns that rise and billow outward var baseAngle = Math.atan2(centerY - startY, centerX - startX); var riseAngle = baseAngle - Math.PI / 2; // Perpendicular upward // Add swirl to the smoke for more realistic movement var swirlFactor = (Math.random() - 0.5) * Math.PI / 4; var finalAngle = riseAngle + swirlFactor; var riseDistance = 500 + Math.random() * 700; var targetX = startX + Math.cos(finalAngle) * riseDistance * 0.6 + Math.cos(baseAngle) * 200; var targetY = startY + Math.sin(finalAngle) * riseDistance - 300; // Strong upward movement var smoke = new SmokeParticle(startX, startY, targetX, targetY); smokeLayer.addChild(smoke); smokeParticles.push(smoke); } }, 600); // Animate the darkness effect with slower fade-in for creeping horror tween(darkOverlay, { alpha: 0.85 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { // Hold the dark state for most of the 2 seconds LK.setTimeout(function () { tween(darkOverlay, { alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (darkOverlay.parent) { darkOverlay.destroy(); } // Clean up smoke particles with delay LK.setTimeout(function () { for (var i = smokeParticles.length - 1; i >= 0; i--) { if (smokeParticles[i].parent) { smokeParticles[i].destroy(); } } smokeParticles = []; }, 500); } }); }, 1000); // Hold darkness for 1 second } }); } } // Ground shake effect every 20 seconds if (gameStarted) { groundShakeTimer++; if (groundShakeTimer >= groundShakeInterval) { groundShakeTimer = 0; // Shake the ground (game container) var shakeIntensity = 15; var originalX = game.x; var originalY = game.y; // Create shake sequence var _shakeSequence = function shakeSequence(count) { if (count <= 0) { // Return to original position tween(game, { x: originalX, y: originalY }, { duration: 100, easing: tween.easeOut }); return; } // Random shake offset var shakeX = originalX + (Math.random() - 0.5) * shakeIntensity; var shakeY = originalY + (Math.random() - 0.5) * shakeIntensity; tween(game, { x: shakeX, y: shakeY }, { duration: 50, easing: tween.easeInOut, onFinish: function onFinish() { _shakeSequence(count - 1); } }); }; // Start shake sequence with 8 shakes _shakeSequence(8); } } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -1044,110 +1044,146 @@
var SmokeParticle = Container.expand(function (startX, startY, targetX, targetY, color) {
var self = Container.call(this);
self.x = startX;
self.y = startY;
- // Create multiple smoke layers for depth
+ // Create multiple smoke layers for depth with irregular shapes
var smokeLayers = [];
- var numLayers = 4; // More layers for better depth
+ var numLayers = 6; // More layers for better depth and realism
for (var i = 0; i < numLayers; i++) {
var smokeLayer = new Container();
self.addChild(smokeLayer);
- // Create smoke shape
- var smokeGraphics = smokeLayer.attachAsset('rangeCircle', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- // Larger, more varied sizes for fog effect
- var size = 300 + Math.random() * 500 + i * 120; // Larger smoke clouds
- smokeGraphics.width = size;
- smokeGraphics.height = size * 0.6; // Flatten for more fog-like appearance
- // Fire-like smoke colors - mix of dark greys with orange/red hints
- var smokeType = Math.random();
- if (smokeType < 0.3) {
- // Dark smoke with red tint (30% chance)
- var redValue = 0x40 + Math.floor(Math.random() * 0x30);
- var grayValue = 0x10 + Math.floor(Math.random() * 0x20);
- smokeGraphics.tint = redValue << 16 | grayValue << 8 | grayValue;
- } else if (smokeType < 0.5) {
- // Orangish smoke (20% chance)
- var orangeRed = 0x60 + Math.floor(Math.random() * 0x40);
- var orangeGreen = 0x30 + Math.floor(Math.random() * 0x20);
- smokeGraphics.tint = orangeRed << 16 | orangeGreen << 8 | 0x10;
- } else {
- // Dark grey smoke (50% chance)
- var grayValue = 0x20 + Math.floor(Math.random() * 0x40);
- smokeGraphics.tint = grayValue << 16 | grayValue << 8 | grayValue;
+ // Create multiple small circles to form irregular smoke shape
+ var numCircles = 3 + Math.floor(Math.random() * 3); // 3-5 circles per layer
+ for (var j = 0; j < numCircles; j++) {
+ var circleContainer = new Container();
+ smokeLayer.addChild(circleContainer);
+ var smokeGraphics = circleContainer.attachAsset('rangeCircle', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ // Varied sizes for each circle to create irregular shape
+ var baseSize = 200 + Math.random() * 300 + i * 50;
+ var circleSize = baseSize * (0.6 + Math.random() * 0.8);
+ smokeGraphics.width = circleSize;
+ smokeGraphics.height = circleSize * (0.7 + Math.random() * 0.3); // Slight vertical variation
+ // Position circles with overlap to create cloud-like formation
+ var angle = j / numCircles * Math.PI * 2 + Math.random() * 0.5;
+ var radius = baseSize * 0.3 * (0.5 + Math.random() * 0.5);
+ circleContainer.x = Math.cos(angle) * radius;
+ circleContainer.y = Math.sin(angle) * radius * 0.7; // Flatten vertically
+ // Fire-like smoke colors - mix of dark greys with orange/red hints
+ var smokeType = Math.random();
+ if (smokeType < 0.3) {
+ // Dark smoke with red tint (30% chance)
+ var redValue = 0x30 + Math.floor(Math.random() * 0x20);
+ var grayValue = 0x10 + Math.floor(Math.random() * 0x10);
+ smokeGraphics.tint = redValue << 16 | grayValue << 8 | grayValue;
+ } else if (smokeType < 0.5) {
+ // Orangish smoke (20% chance)
+ var orangeRed = 0x40 + Math.floor(Math.random() * 0x20);
+ var orangeGreen = 0x20 + Math.floor(Math.random() * 0x10);
+ smokeGraphics.tint = orangeRed << 16 | orangeGreen << 8 | 0x08;
+ } else {
+ // Dark grey smoke (50% chance)
+ var grayValue = 0x18 + Math.floor(Math.random() * 0x28);
+ smokeGraphics.tint = grayValue << 16 | grayValue << 8 | grayValue;
+ }
+ // Vary alpha for depth and realism
+ smokeGraphics.alpha = (0.3 - i * 0.04) * (0.7 + Math.random() * 0.3);
+ smokeGraphics.blendMode = 1; // Additive blend for glowing effect
}
- // Vary alpha based on layer and type
- smokeGraphics.alpha = 0.5 - i * 0.08; // Front layers more opaque
- smokeGraphics.blendMode = 1; // Additive blend for glowing effect
- // Offset layers for depth with more variation
- smokeLayer.x = (Math.random() - 0.5) * 80;
- smokeLayer.y = (Math.random() - 0.5) * 50;
+ // Add some distortion to the layer
+ smokeLayer.scaleX = 0.9 + Math.random() * 0.2;
+ smokeLayer.scaleY = 0.8 + Math.random() * 0.3;
// Add initial rotation for more organic look
- smokeLayer.rotation = (Math.random() - 0.5) * 0.3;
+ smokeLayer.rotation = (Math.random() - 0.5) * 0.4;
smokeLayers.push(smokeLayer);
}
+ // Add wispy tendrils on top layers for more realistic smoke
+ if (i < 2) {
+ var tendrilCount = 2 + Math.floor(Math.random() * 2);
+ for (var t = 0; t < tendrilCount; t++) {
+ var tendril = new Container();
+ smokeLayer.addChild(tendril);
+ var tendrilGraphics = tendril.attachAsset('rangeCircle', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ tendrilGraphics.width = 80 + Math.random() * 60;
+ tendrilGraphics.height = 200 + Math.random() * 100;
+ tendrilGraphics.rotation = Math.random() * Math.PI;
+ tendril.x = (Math.random() - 0.5) * 200;
+ tendril.y = (Math.random() - 0.5) * 150;
+ tendrilGraphics.alpha = 0.15 + Math.random() * 0.15;
+ tendrilGraphics.tint = 0x202020;
+ tendrilGraphics.blendMode = 1;
+ }
+ }
// Add ember particles occasionally
- if (Math.random() < 0.3) {
- var emberLayer = new Container();
- self.addChild(emberLayer);
- var ember = emberLayer.attachAsset('rangeCircle', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- ember.width = 50 + Math.random() * 30;
- ember.height = ember.width;
- // Bright orange/red ember color
- ember.tint = Math.random() < 0.5 ? 0xFF4500 : 0xFF6347;
- ember.alpha = 0.8;
- ember.blendMode = 1;
- emberLayer.x = (Math.random() - 0.5) * 100;
- emberLayer.y = (Math.random() - 0.5) * 100;
- smokeLayers.push(emberLayer);
+ if (Math.random() < 0.2 && i < 3) {
+ var emberCount = 1 + Math.floor(Math.random() * 2);
+ for (var e = 0; e < emberCount; e++) {
+ var emberLayer = new Container();
+ self.addChild(emberLayer);
+ var ember = emberLayer.attachAsset('rangeCircle', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ ember.width = 20 + Math.random() * 25;
+ ember.height = ember.width;
+ // Bright orange/red ember color
+ ember.tint = Math.random() < 0.5 ? 0xFF4500 : 0xFF6347;
+ ember.alpha = 0.9;
+ ember.blendMode = 1;
+ emberLayer.x = (Math.random() - 0.5) * 150;
+ emberLayer.y = (Math.random() - 0.5) * 100;
+ smokeLayers.push(emberLayer);
+ }
}
// Start with 0 alpha and scale
self.alpha = 0;
- self.scaleX = 0.1;
- self.scaleY = 0.1;
+ self.scaleX = 0.3;
+ self.scaleY = 0.3;
// Calculate movement direction with organic variation
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Add perpendicular drift and upward bias (smoke rises)
- var perpX = -dy / distance * (Math.random() - 0.5) * 300;
- var perpY = dx / distance * (Math.random() - 0.5) * 300 - 50; // Slight upward bias
+ var perpX = -dy / distance * (Math.random() - 0.5) * 400;
+ var perpY = dx / distance * (Math.random() - 0.5) * 300 - 80; // Strong upward bias
+ // Add wind effect
+ var windX = 50 + Math.random() * 50; // Slight rightward wind
// Animate appearance with creeping fog effect
tween(self, {
- alpha: 0.9,
- scaleX: 1.3,
- scaleY: 1.1,
- x: startX + dx * 0.15 + perpX * 0.2,
- y: startY + dy * 0.15 + perpY * 0.2 - 20 // Rising motion
+ alpha: 0.85,
+ scaleX: 1.0,
+ scaleY: 0.9,
+ x: startX + dx * 0.1 + perpX * 0.15 + windX * 0.1,
+ y: startY + dy * 0.1 + perpY * 0.15 - 30 // Rising motion
}, {
- duration: 1500,
+ duration: 1800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Continue creeping forward with undulation
tween(self, {
- alpha: 0.7,
- scaleX: 1.8,
- scaleY: 1.6,
- x: startX + dx * 0.35 + perpX * 0.5,
- y: startY + dy * 0.35 + perpY * 0.5 - 60 // Continue rising
+ alpha: 0.6,
+ scaleX: 1.5,
+ scaleY: 1.3,
+ x: startX + dx * 0.3 + perpX * 0.4 + windX * 0.3,
+ y: startY + dy * 0.3 + perpY * 0.4 - 80 // Continue rising
}, {
- duration: 1200,
+ duration: 1500,
easing: tween.linear,
onFinish: function onFinish() {
// Final fade out
tween(self, {
alpha: 0,
- scaleX: 2.3,
- scaleY: 2.0,
- x: startX + dx * 0.5 + perpX * 0.8,
- y: startY + dy * 0.5 + perpY * 0.8 - 120 // Final rise
+ scaleX: 2.0,
+ scaleY: 1.7,
+ x: startX + dx * 0.5 + perpX * 0.7 + windX * 0.5,
+ y: startY + dy * 0.5 + perpY * 0.7 - 150 // Final rise
}, {
- duration: 1000,
+ duration: 1200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
@@ -1163,23 +1199,26 @@
(function (layer, index) {
var _animateLayer = function animateLayer() {
if (!self.parent) return;
// More turbulent movement for fire smoke
- var turbulenceX = (Math.random() - 0.5) * 60;
- var turbulenceY = (Math.random() - 0.5) * 40 - 10; // Upward bias
- var rotationAmount = (Math.random() - 0.5) * 0.2;
+ var turbulenceX = (Math.random() - 0.5) * 80;
+ var turbulenceY = (Math.random() - 0.5) * 60 - 20; // Strong upward bias
+ var rotationAmount = (Math.random() - 0.5) * 0.3;
+ var scaleChange = 0.9 + Math.random() * 0.2;
tween(layer, {
x: layer.x + turbulenceX,
y: layer.y + turbulenceY,
- rotation: layer.rotation + rotationAmount
+ rotation: layer.rotation + rotationAmount,
+ scaleX: layer.scaleX * scaleChange,
+ scaleY: layer.scaleY * scaleChange
}, {
- duration: 1500 + index * 300,
+ duration: 2000 + index * 200,
easing: tween.easeInOut,
onFinish: _animateLayer
});
};
// Start with slight delay for each layer
- LK.setTimeout(_animateLayer, index * 80);
+ LK.setTimeout(_animateLayer, index * 100);
})(smokeLayers[i], i);
}
return self;
});
@@ -3812,85 +3851,114 @@
overlayGraphics.tint = 0x000000;
darkOverlay.alpha = 0;
game.addChild(darkOverlay);
// Create smoke particles from edges
- var smokeCount = 60; // More particles for denser, more realistic fog
+ var smokeCount = 40; // Fewer but larger, more complex particles
var centerX = 1024;
var centerY = 1366;
- // Create initial wave of smoke with more focused origin points (like fire sources)
+ // Define fire source locations for more realistic smoke origins
+ var fireSources = [
+ // Top edge fire sources
+ {
+ x: 300,
+ y: -100
+ }, {
+ x: 700,
+ y: -120
+ }, {
+ x: 1200,
+ y: -100
+ }, {
+ x: 1700,
+ y: -110
+ },
+ // Right edge fire sources
+ {
+ x: 2148,
+ y: 400
+ }, {
+ x: 2168,
+ y: 900
+ }, {
+ x: 2158,
+ y: 1500
+ }, {
+ x: 2148,
+ y: 2200
+ },
+ // Bottom edge fire sources
+ {
+ x: 300,
+ y: 2832
+ }, {
+ x: 800,
+ y: 2852
+ }, {
+ x: 1300,
+ y: 2842
+ }, {
+ x: 1700,
+ y: 2832
+ },
+ // Left edge fire sources
+ {
+ x: -100,
+ y: 400
+ }, {
+ x: -120,
+ y: 1000
+ }, {
+ x: -110,
+ y: 1600
+ }, {
+ x: -100,
+ y: 2200
+ }];
+ // Create initial wave of smoke from fire sources
for (var i = 0; i < smokeCount; i++) {
- var side = Math.floor(Math.random() * 4); // 0: top, 1: right, 2: bottom, 3: left
- var startX, startY;
- // Create clusters of smoke from specific "burning" points
- var clusterOffset = Math.floor(i / 5) * 200; // Group smoke into clusters
- switch (side) {
- case 0:
- // Top edge - create fire source clusters
- startX = (clusterOffset + Math.random() * 150) % 2048;
- startY = -150 - Math.random() * 50;
- break;
- case 1:
- // Right edge - create fire source clusters
- startX = 2198 + Math.random() * 50;
- startY = (clusterOffset + Math.random() * 150) % 2732;
- break;
- case 2:
- // Bottom edge - create fire source clusters
- startX = (clusterOffset + Math.random() * 150) % 2048;
- startY = 2882 + Math.random() * 50;
- break;
- case 3:
- // Left edge - create fire source clusters
- startX = -150 - Math.random() * 50;
- startY = (clusterOffset + Math.random() * 150) % 2732;
- break;
- }
+ // Pick a random fire source
+ var sourceIndex = Math.floor(Math.random() * fireSources.length);
+ var source = fireSources[sourceIndex];
+ // Create smoke originating from near the fire source with variation
+ var startX = source.x + (Math.random() - 0.5) * 100;
+ var startY = source.y + (Math.random() - 0.5) * 80;
// Create smoke with varied delays for waves of fog
LK.setTimeout(function (x, y) {
return function () {
- // Vary the target point for more organic movement
- // Smoke rises and spreads outward from fire sources
- var spreadAngle = Math.random() * Math.PI * 2;
- var spreadDistance = 200 + Math.random() * 400;
- var targetX = centerX + Math.cos(spreadAngle) * spreadDistance;
- var targetY = centerY + Math.sin(spreadAngle) * spreadDistance - 100; // Upward bias
+ // Smoke spreads inward and upward from fire sources
+ var inwardAngle = Math.atan2(centerY - y, centerX - x);
+ // Add variation to the angle for more natural spread
+ var angleVariation = (Math.random() - 0.5) * Math.PI / 3;
+ var finalAngle = inwardAngle + angleVariation;
+ var spreadDistance = 400 + Math.random() * 600;
+ var targetX = x + Math.cos(finalAngle) * spreadDistance;
+ var targetY = y + Math.sin(finalAngle) * spreadDistance - 200; // Strong upward bias
var smoke = new SmokeParticle(x, y, targetX, targetY);
smokeLayer.addChild(smoke);
smokeParticles.push(smoke);
};
- }(startX, startY), Math.random() * 800); // Staggered over 800ms
+ }(startX, startY), Math.random() * 1000); // Staggered over 1000ms
}
// Create second wave of smoke for continuous burning effect
LK.setTimeout(function () {
- for (var i = 0; i < smokeCount; i++) {
- var side = Math.floor(Math.random() * 4);
- var startX, startY;
- // More concentrated fire sources for second wave
- var fireSourceX = Math.floor(Math.random() * 4) * 512 + 256; // 4 main fire sources
- var fireSourceY = Math.floor(Math.random() * 4) * 683 + 341;
- switch (side) {
- case 0:
- startX = fireSourceX + (Math.random() - 0.5) * 200;
- startY = -100 - Math.random() * 50;
- break;
- case 1:
- startX = 2148 + Math.random() * 50;
- startY = fireSourceY + (Math.random() - 0.5) * 200;
- break;
- case 2:
- startX = fireSourceX + (Math.random() - 0.5) * 200;
- startY = 2832 + Math.random() * 50;
- break;
- case 3:
- startX = -100 - Math.random() * 50;
- startY = fireSourceY + (Math.random() - 0.5) * 200;
- break;
- }
+ var secondWaveCount = 30; // Fewer particles for second wave
+ for (var i = 0; i < secondWaveCount; i++) {
+ // Use the same fire sources but with more intensity
+ var sourceIndex = Math.floor(Math.random() * fireSources.length);
+ var source = fireSources[sourceIndex];
+ // Create larger spread from fire sources for second wave
+ var startX = source.x + (Math.random() - 0.5) * 150;
+ var startY = source.y + (Math.random() - 0.5) * 120;
// Smoke rises and spreads in a more realistic pattern
- var riseAngle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 3; // Mostly upward
- var riseDistance = 300 + Math.random() * 500;
- var targetX = startX + Math.cos(riseAngle) * riseDistance * 0.5;
- var targetY = startY + Math.sin(riseAngle) * riseDistance;
+ // Create smoke columns that rise and billow outward
+ var baseAngle = Math.atan2(centerY - startY, centerX - startX);
+ var riseAngle = baseAngle - Math.PI / 2; // Perpendicular upward
+ // Add swirl to the smoke for more realistic movement
+ var swirlFactor = (Math.random() - 0.5) * Math.PI / 4;
+ var finalAngle = riseAngle + swirlFactor;
+ var riseDistance = 500 + Math.random() * 700;
+ var targetX = startX + Math.cos(finalAngle) * riseDistance * 0.6 + Math.cos(baseAngle) * 200;
+ var targetY = startY + Math.sin(finalAngle) * riseDistance - 300; // Strong upward movement
var smoke = new SmokeParticle(startX, startY, targetX, targetY);
smokeLayer.addChild(smoke);
smokeParticles.push(smoke);
}
ışıklı büyü ışıltısı. In-Game asset. 2d. High contrast. No shadows
3 boyutlu alev
elinde asa olsun ve sinirli olsun
harry potter evreninde kötü bir yaratık. In-Game asset. 2d. High contrast. No shadows
harry potter temasında korkutucu bir yılan yeşil renkte. In-Game asset. 2d. High contrast. No shadows