Code edit (2 edits merged)
Please save this source code
User prompt
When !hasPlayedTutorial don't use initMapForLevel but use the dedicated MAPS index 0 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
Code edit (2 edits merged)
Please save this source code
User prompt
Bug is still present !! Now we See "Level 1 complete!" and then after a few seconds "Level 2 complete!" without playing level 2!! Please Analyze deeply and fix quickly!
User prompt
There is a bug in victory timing : currently on victory initNewLevel is call in loop; we can see levels initializing in loop with each time more empty towers until reaching level 20 and then showing youwin under a few seconds! Make sure to reset game properly and add necesseary delays to ensure normal robust game init and end ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Create a new function initNewLevel() that calls initMapForLevel() and reset/prepare everything for the currentLevel
User prompt
You still did not understand : GAME SHOULD CONTINUE TO NEXT LEVEL AFTER A VICTORY! if level is <=20 , call a function that init a game for next level not showGameOver not showYouWin ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
There is a problem : showYouWin is called in all victory cases making it impossible to play level 2; review the logic ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
In case of victory, only show showYouWin if level is > 20, else init the next level; ensure all elements are properly reset to start a clean new game ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
Please verify the level map generator: it just generated a map with empty towers at rows above 26 that make them too close to player HQ! maybe mapGridOffset was not taking into account?
User prompt
when enemy HQ is destroyed display a Vicrory! message (center screen like tutorial text) then after 3s, switch to next level; when player HQ is destroyed display a Defeat! message (center screen like tutorial text) then after 3s, switch same level; ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
initLevel0Units should be called only if tutorial wasn't played. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
create a new function to initMapForLevel; it starts at levl 1 (level 0 is the tutorial) it uses the map with index 1 (MAPS[1]) as a basis and adds an increasing number of empty towers between row 10 and row 26 at random empty cells but symetrically; ie. 2x3 empty towers for level 1, 2x4 empty towers for level 2,... to create original balanced maps
User prompt
Check player's HQ fire/bullets and find why player's HQ bullets have a shorter livespan than turret's or enemy's HQ bullets ?
Code edit (5 edits merged)
Please save this source code
User prompt
Now implement an AI player with these rules : - Defend : activates HQ defense mode if HQ under attack - Explore : when not under attack, activates HQ Genrator mode to create units then find empty towers and capture them - Attack : prepare waves of units and attack player towers then player HQ AI should be disabled during tutorial
Code edit (1 edits merged)
Please save this source code
User prompt
when units move out from a tower, the tower shares indicator should be hidden
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var BackgroundManager = Container.expand(function () { var self = Container.call(this); self.backgrounds = []; self.currentBackground = null; self.animationActive = false; self.addBackground = function (assetId, config) { config = config || {}; var background = new Container(); var bgGraphics = background.attachAsset(assetId, { anchorX: config.anchorX || 0.5, anchorY: config.anchorY || 0.5, x: config.x || 0, y: config.y || 0, scaleX: config.scaleX || 1, scaleY: config.scaleY || 1, alpha: config.alpha !== undefined ? config.alpha : 1, tint: config.tint || 0xFFFFFF }); background.config = config; background.graphics = bgGraphics; background.assetId = assetId; self.addChild(background); self.backgrounds.push(background); return background; }; self.removeBackground = function (background) { var index = self.backgrounds.indexOf(background); if (index !== -1) { self.backgrounds.splice(index, 1); self.removeChild(background); } }; self.animateBackground = function (background, properties, config) { if (!background || !background.graphics) { return; } config = config || {}; var duration = config.duration || 1000; var easing = config.easing || tween.linear; var loop = config.loop || false; var _onFinish = config.onFinish; var _animateFunc = function animateFunc() { tween(background.graphics, properties, { duration: duration, easing: easing, onFinish: function onFinish() { if (loop && self.animationActive) { if (config.resetOnLoop) { for (var prop in config.resetOnLoop) { background.graphics[prop] = config.resetOnLoop[prop]; } } _animateFunc(); } else if (_onFinish) { _onFinish(); } } }); }; self.animationActive = true; _animateFunc(); }; self.stopAnimations = function () { self.animationActive = false; for (var i = 0; i < self.backgrounds.length; i++) { if (self.backgrounds[i].graphics) { tween.stop(self.backgrounds[i].graphics); } } }; self.fadeToBackground = function (newBackground, duration, onComplete) { duration = duration || 1000; if (self.currentBackground && self.currentBackground !== newBackground) { tween(self.currentBackground.graphics, { alpha: 0 }, { duration: duration / 2, easing: tween.easeInOut, onFinish: function onFinish() { self.currentBackground.visible = false; } }); } newBackground.visible = true; newBackground.graphics.alpha = 0; tween(newBackground.graphics, { alpha: 1 }, { duration: duration / 2, easing: tween.easeInOut, onFinish: function onFinish() { self.currentBackground = newBackground; if (onComplete) { onComplete(); } } }); }; self.createParallaxEffect = function (backgrounds, baseSpeed) { baseSpeed = baseSpeed || 10; for (var i = 0; i < backgrounds.length; i++) { (function (index) { var bg1 = backgrounds[index]; var speed = baseSpeed * (index + 1) * 0.5; var bg2 = self.addBackground(bg1.assetId, { anchorX: bg1.config.anchorX || 0.5, anchorY: bg1.config.anchorY || 0.5, x: bg1.graphics.x + bg1.graphics.width, y: bg1.config.y || 0, scaleX: -1, scaleY: bg1.config.scaleY || 1, alpha: bg1.config.alpha !== undefined ? bg1.config.alpha : 1, tint: bg1.config.tint || 0xFFFFFF }); var moveDistance = bg1.graphics.width; var duration = 80000 / speed; var _animate = function animate(bg, initialX) { self.animateBackground(bg, { x: bg.graphics.x - moveDistance }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { if (self.animationActive) { if (bg.graphics.x <= -moveDistance / 2) { bg.graphics.x = bg.graphics.x + moveDistance * 2; } _animate(bg, initialX); } } }); }; if (index > 0) { var _animateAlpha = function animateAlpha(bg) { tween(bg.graphics, { alpha: 1 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { tween(bg.graphics, { alpha: 0.5 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { if (self.animationActive) { _animateAlpha(bg); } } }); } }); }; _animateAlpha(bg1); _animateAlpha(bg2); } _animate(bg1, bg1.graphics.x); _animate(bg2, bg2.graphics.x); })(i); } }; 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); var angle = Math.atan2(dy, dx); bulletGraphics.rotation = angle; if (distance < self.speed) { self.targetEnemy.health -= self.damage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } if (self.type === 'splash') { var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); game.addChild(splashEffect); 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) { otherEnemy.health -= self.damage * 0.5; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } } } } } else if (self.type === 'slow') { if (!self.targetEnemy.isImmune) { var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); var slowPct = 0.5; if (self.sourceTowerLevel !== undefined) { 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; self.targetEnemy.slowed = true; self.targetEnemy.slowDuration = 180; } else { self.targetEnemy.slowDuration = 180; } } } else if (self.type === 'poison') { if (!self.targetEnemy.isImmune) { var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; self.targetEnemy.poisonDuration = 300; } } else if (self.type === 'sniper') { var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper'); game.addChild(sniperEffect); } self.destroy(); } else { 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 = null; if (isDebug) { cellGraphics = self.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5, alpha: 1 }); cellGraphics.tint = Math.random() * 0xffffff; } var debugArrows = []; var numberLabel = new Text2("-", { size: 20, fill: 0xFFFFFF }); 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) { if (!isDebug) { numberLabel.visible = false; numberLabel.alpha = 0; numberLabel.setText(""); if (data.type === 1 && !data.isVisuallyEmptyTower) { if (self.wallInstance) { self.removeChild(self.wallInstance); self.wallInstance = null; } self.wallInstance = new Wall(); self.addChild(self.wallInstance); } else { if (self.wallInstance) { self.removeChild(self.wallInstance); self.wallInstance = null; } } return; } if (cellGraphics) { cellGraphics.visible = true; cellGraphics.alpha = 1; } switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); if (cellGraphics) { cellGraphics.tint = 0x880000; } return; } var tint = Math.floor(data.score / maxScore * 0x88); var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; if (cellGraphics) { cellGraphics.tint = 0x0088ff; } } else { if (cellGraphics) { cellGraphics.tint = 0x88 - tint << 8 | tint; } } break; } case 1: { self.removeArrows(); if (cellGraphics) { cellGraphics.tint = 0xaaaaaa; } numberLabel.visible = false; break; } case 3: { self.removeArrows(); if (cellGraphics) { cellGraphics.tint = 0x008800; } numberLabel.visible = false; break; } case 4: { self.removeArrows(); if (cellGraphics) { cellGraphics.tint = 0x888888; } numberLabel.visible = false; break; } } }; }); 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; 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; }); 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 = 1; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.isFlying = false; self.isImmune = false; self.isBoss = false; if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') { self.isBoss = true; self.maxHealth *= 20; self.speed = self.speed * 0.7; } self.health = self.maxHealth; var assetId = 'enemy'; if (self.type !== 'normal') { assetId = 'enemy_' + self.type; } var enemyGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); if (self.isBoss) { enemyGraphics.scaleX = 1.8; enemyGraphics.scaleY = 1.8; } /*switch (self.type) { case 'fast': enemyGraphics.tint = 0x00AAFF; break; case 'immune': enemyGraphics.tint = 0xAA0000; break; case 'flying': enemyGraphics.tint = 0xFFFF00; break; case 'swarm': enemyGraphics.tint = 0xFF00FF; break; }*/ if (self.isFlying) { self.shadow = new Container(); var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', { anchorX: 0.5, anchorY: 0.5 }); shadowGraphics.tint = 0x000000; shadowGraphics.alpha = 0.4; if (self.isBoss) { shadowGraphics.scaleX = 1.8; shadowGraphics.scaleY = 1.8; } self.shadow.x = 20; self.shadow.y = 20; shadowGraphics.rotation = enemyGraphics.rotation; } self.update = function () { if (self.health <= 0) { self.health = 0; } 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 }); var currentRotation = enemyGraphics.rotation; var angleDiff = angle - currentRotation; 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 }); } } } } }; 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 4: Empty Tower 9: Wall (from map) */ var currentMap = MAPS[0]; var mapHeight = currentMap.length; var mapWidth = currentMap[0].length; for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; var cellType = 0; if (j < mapHeight && i < mapWidth) { var mapValue = currentMap[j][i]; switch (mapValue) { case 0: cellType = 0; break; case 11: cell.orientation = Math.PI; case 1: cellType = 3; self.goals.push(cell); break; case 22: case 33: cell.orientation = Math.PI; case 2: case 3: cellType = 2; self.spawns.push(cell); break; case 8: cellType = 4; break; case 9: cellType = 1; break; default: cellType = 0; } } else { cellType = 1; } 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 >= 0 && j <= gridHeight - 5) { var debugCell = new DebugCell(); self.addChild(debugCell); debugCell.cell = cell; debugCell.x = i * CELL_SIZE; debugCell.y = (j + mapGridOffset) * 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]; if (enemy.currentCellY < mapGridOffset) { continue; } 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; enemy.shadow.y = enemy.y + 20; if (enemy.children[0] && enemy.shadow.children[0]) { enemy.shadow.children[0].rotation = enemy.children[0].rotation; } } var hasReachedEntryArea = enemy.currentCellY >= mapGridOffset; if (!hasReachedEntryArea) { enemy.currentCellY += enemy.speed; 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 }); var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; if (enemy.currentCellY >= mapGridOffset) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); } return false; } if (enemy.isFlying) { if (!enemy.flyingTarget) { enemy.flyingTarget = self.goals[0]; 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; } } } } var ox = enemy.flyingTarget.x - enemy.currentCellX; var oy = enemy.flyingTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { return true; } var angle = Math.atan2(oy, ox); 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 }); var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } 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; return false; } 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++; waveTimer = 0; waveInProgress = true; waveSpawned = false; var waveType = waveIndicator.getWaveTypeName(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 30; self.addChild(notificationText); self.alpha = 1; var fadeOutTime = 120; self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var SelectionCircle = Container.expand(function () { var self = Container.call(this); self.startX = 0; self.startY = 0; self.currentRadius = 0; var circleGraphics = self.attachAsset('circleSelector', { anchorX: 0.5, anchorY: 0.5 }); circleGraphics.alpha = 0.3; circleGraphics.tint = 0x00FF00; circleGraphics.width = 1; circleGraphics.height = 1; self.updateCircle = function (endX, endY) { var dx = endX - self.startX; var dy = endY - self.startY; self.currentRadius = Math.sqrt(dx * dx + dy * dy); circleGraphics.width = self.currentRadius * 2; circleGraphics.height = self.currentRadius * 2; }; self.setPosition = function (x, y) { self.startX = x; self.startY = y; self.x = x; self.y = y; }; return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); baseGraphics.tint = 0xAAAAAA; var towerCost = getTowerCost(self.towerType); var typeLabelShadow = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), { size: 50, fill: 0x000000, weight: 800 }); typeLabelShadow.anchor.set(0.5, 0.5); typeLabelShadow.x = 4; typeLabelShadow.y = -20 + 4; self.addChild(typeLabelShadow); var typeLabel = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), { size: 50, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.y = -20; self.addChild(typeLabel); 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); 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 () { var canAfford = gold >= getTowerCost(self.towerType); self.alpha = canAfford ? 1 : 0.5; }; return self; }); var Tower = Container.expand(function (headquarters, orientation, playerIndex) { var self = Container.call(this); self.isHeadquarters = headquarters || false; self.baseOrientation = orientation; self.playerIndex = playerIndex !== undefined ? playerIndex : 0; self.isActivated = self.isHeadquarters; self.activationThreshold = 10; self.currentShares = 0; self.capturingPlayer = -1; self.activationGauge = null; self.id = self.isHeadquarters ? 'headquarters' : 'default'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.isProducing = false; self.range = 3 * CELL_SIZE; self.health = isDebug ? self.isHeadquarters ? 4 : 2 : self.isHeadquarters ? 30 : 10; self.maxHealth = isDebug ? self.isHeadquarters ? 4 : 2 : self.isHeadquarters ? 30 : 10; self.getRange = function () { if (self.isHeadquarters) { return (3 + (self.level - 1) * 0.5) * CELL_SIZE * 2; } switch (self.id) { case 'sniper': if (self.level === self.maxLevel) { return 12 * CELL_SIZE; } return (5 + (self.level - 1) * 0.8) * CELL_SIZE; case 'splash': return (2 + (self.level - 1) * 0.2) * CELL_SIZE; case 'rapid': return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'slow': return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'poison': return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; default: return (3 + (self.level - 1) * 0.5) * CELL_SIZE; } }; self.cellsInRange = []; self.fireRate = self.isHeadquarters ? 20 : 60; self.bulletSpeed = 5; self.damage = 1; self.lastFired = 0; self.targetEnemy = null; var baseGraphics; if (self.isHeadquarters) { baseGraphics = self.attachAsset('headquarterBase', { anchorX: 0.5, anchorY: 0.5, width: 150, height: 150 }); baseGraphics.rotation = orientation || 0; } else { baseGraphics = self.attachAsset('towerBase', { anchorX: 0.5, anchorY: 0.5 }); } switch (self.id) { case 'rapid': baseGraphics.tint = 0x00AAFF; break; case 'sniper': baseGraphics.tint = 0xFF5500; break; case 'splash': baseGraphics.tint = 0x33CC00; break; case 'slow': baseGraphics.tint = 0x9900FF; break; case 'poison': baseGraphics.tint = 0x00FFAA; break; default: baseGraphics.tint = 0xAAAAAA; } var levelIndicators = []; var maxDots = 0; 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 * 2 + dotSpacing * (i + 1); dot.y = CELL_SIZE * 1.3; if (self.playerIndex !== 0) { dot.visible = false; } self.addChild(dot); levelIndicators.push(dot); } var activationGaugeContainer = new Container(); self.addChild(activationGaugeContainer); activationGaugeContainer.y = CELL_SIZE * 0.9; self.activationGauge = activationGaugeContainer; self.activationGauge.visible = false; var gunContainer = new Container(); self.addChild(gunContainer); var turretGraphics; var gunGraphics; var prismGraphics; if (self.isHeadquarters) { self.isProducing = true; turretGraphics = gunContainer.attachAsset('turret', { anchorX: 0.5, anchorY: 0.5, width: 100, height: 100 }); gunGraphics = gunContainer.attachAsset('barrel2', { anchorX: 0, anchorY: 0.5, width: 100, height: 20 }); prismGraphics = gunContainer.attachAsset('prism', { anchorX: 0.5, anchorY: 0.5, width: 100, height: 100 }); } else { turretGraphics = gunContainer.attachAsset('turret', { anchorX: 0.5, anchorY: 0.5, width: 50, height: 50 }); gunGraphics = gunContainer.attachAsset('barrel', { anchorX: 0, anchorY: 0.5, width: 50, height: 10 }); prismGraphics = gunContainer.attachAsset('prism', { anchorX: 0.5, anchorY: 0.5, width: 50, height: 50 }); } self.turretGraphics = turretGraphics; self.gunGraphics = gunGraphics; self.prismGraphics = prismGraphics; prismGraphics.visible = self.isProducing; turretGraphics.visible = !self.isProducing; gunGraphics.visible = !self.isProducing; if (self.playerIndex === 0) { gunGraphics.tint = teamColors[0]; turretGraphics.tint = teamColors[0]; prismGraphics.tint = teamColors[0]; } else if (self.playerIndex === 1) { gunGraphics.tint = teamColors[1]; turretGraphics.tint = teamColors[1]; prismGraphics.tint = teamColors[1]; } else { gunGraphics.tint = 0xAAAAAA; turretGraphics.tint = 0xAAAAAA; prismGraphics.tint = 0xAAAAAA; } 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 { towerLevelIndicator.tint = 0xAAAAAA; } } }; self.updateLevelIndicators(); self.updateActivation = function (fromPlayerIndex) { if (self.isActivated && self.playerIndex === fromPlayerIndex) { return false; } if (self.capturingPlayer === -1) { self.capturingPlayer = fromPlayerIndex; } if (self.capturingPlayer === fromPlayerIndex) { if (self.currentShares >= self.activationThreshold) { if (!self.isActivated) { self.activate(self.capturingPlayer); } return false; } self.currentShares++; } else { self.currentShares--; } if (self.currentShares <= 0) { self.currentShares = 0; if (self.isActivated) { self.deactivate(); } self.capturingPlayer = -1; } else if (self.currentShares >= self.activationThreshold) { self.currentShares = self.activationThreshold; if (!self.isActivated) { self.activate(self.capturingPlayer); } } self.updateActivationGaugeVisuals(); return true; }; self.activate = function (newPlayerIndex) { self.isActivated = true; self.playerIndex = newPlayerIndex; gunContainer.visible = true; var baseGraphics = self.children[0]; if (self.playerIndex == 0) { baseGraphics.tint = teamColors[0]; self.turretGraphics.tint = teamColors[0]; self.gunGraphics.tint = teamColors[0]; self.prismGraphics.tint = teamColors[0]; } else if (self.playerIndex == 1) { baseGraphics.tint = teamColors[1]; self.turretGraphics.tint = teamColors[1]; self.gunGraphics.tint = teamColors[1]; self.prismGraphics.tint = teamColors[1]; } if (self.activationGauge) { self.activationGauge.visible = false; } }; self.deactivate = function () { self.isActivated = false; self.playerIndex = -1; gunContainer.visible = false; var baseGraphics = self.children[0]; baseGraphics.tint = 0xAAAAAA; self.gunGraphics.tint = 0xAAAAAA; self.turretGraphics.tint = 0xAAAAAA; self.prismGraphics.tint = 0xAAAAAA; self.updateActivationGaugeVisuals(); }; self.updateActivationGaugeVisuals = function () { if (!self.activationGauge) { return; } self.activationGauge.y = 0; self.activationGauge.removeChildren(); if (self.currentShares > 0 && !self.isActivated) { self.activationGauge.visible = true; var totalDots = 12; var radius = CELL_SIZE / 2.5; var dotsToShow = Math.ceil(self.currentShares / self.activationThreshold * totalDots); var playerColor = 0x0000FF; if (self.capturingPlayer === 0) { playerColor = teamColors[0]; } else if (self.capturingPlayer === 1) { playerColor = teamColors[1]; } for (var i = 0; i < totalDots; i++) { var angle = i / totalDots * Math.PI * 2 - Math.PI / 2; var dot = self.activationGauge.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5, width: 12, height: 12 }); dot.x = Math.cos(angle) * radius; dot.y = Math.sin(angle) * radius; if (i < dotsToShow) { dot.tint = playerColor; dot.alpha = 1.0; } else { dot.tint = 0x666666; dot.alpha = 0.4; } } } else { self.activationGauge.visible = false; } }; 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; 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) { var baseUpgradeCost = getTowerCost(self.id); var upgradeCost; if (self.level === self.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1)); } if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; if (self.id === 'rapid') { if (self.level === self.maxLevel) { self.fireRate = Math.max(4, 30 - self.level * 9); self.damage = 5 + self.level * 10; self.bulletSpeed = 7 + self.level * 2.4; } else { self.fireRate = Math.max(15, 30 - self.level * 3); self.damage = 5 + self.level * 3; self.bulletSpeed = 7 + self.level * 0.7; } } else { if (self.level === self.maxLevel) { self.fireRate = Math.max(5, 60 - self.level * 24); self.damage = 10 + self.level * 20; self.bulletSpeed = 5 + self.level * 2.4; } 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 () { if (unitManager && unitManager.units) { var closestUnit = null; var closestUnitDistSq = Infinity; var range = self.getRange(); var rangeSq = range * range; for (var i = 0; i < unitManager.units.length; i++) { var unit = unitManager.units[i]; if (unit.ownerIndex !== self.playerIndex && unit.parent) { var dx = unit.x - self.x; var dy = unit.y - self.y; var distSq = dx * dx + dy * dy; if (distSq <= rangeSq && distSq < closestUnitDistSq) { closestUnitDistSq = distSq; closestUnit = unit; } } } if (closestUnit) { return closestUnit; } } 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); if (distance <= self.getRange()) { if (enemy.isFlying) { 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)); if (distToGoal < closestScore) { closestScore = distToGoal; closestEnemy = enemy; } } else { if (distance < closestScore) { closestScore = distance; closestEnemy = enemy; } } } else { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell && cell.pathId === pathId) { if (cell.score < closestScore) { closestScore = cell.score; closestEnemy = enemy; } } } } } if (!closestEnemy) { self.targetEnemy = null; } return closestEnemy; }; self.update = function () { if (!self.isActivated) { return; } if (!self.isProducing) { 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; } } } }; self.down = function (x, y, obj) { if (tutorialActive && self.isHeadquarters && self.playerIndex === 0 && tutorialStep < 6) { return; } if (!self.isActivated) { return; } if (self.playerIndex !== 0) { return; } 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, onFinish: function onFinish() { if (tutorialActive && tutorialStep === 6 && self.isHeadquarters && self.playerIndex === 0) { executeTutorialStep(7); } } }); 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 > 0) { //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); bullet.type = self.id; /* if (self.id === 'slow') { bullet.sourceTowerLevel = self.level; } switch (self.id) { case 'rapid': bullet.children[0].tint = 0x00AAFF; bullet.children[0].width = 20; bullet.children[0].height = 20; break; case 'sniper': bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; case 'splash': bullet.children[0].tint = 0x33CC00; bullet.children[0].width = 40; bullet.children[0].height = 40; break; case 'slow': bullet.children[0].tint = 0x9900FF; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'poison': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; } */ game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); 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; } gunContainer.x = gunContainer._restX; gunContainer.y = gunContainer._restY; gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; var recoilDistance = 8; var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance; var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance; tween(gunContainer, { x: gunContainer._restX + recoilX, y: gunContainer._restY + recoilY }, { duration: self.isHeadquarters ? 30 : 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: self.isHeadquarters ? 45 : 60, easing: tween.cubicIn }); } }); } } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 1; } } } self.refreshCellsInRange(); }; self.changeTowerMode = function () { self.isProducing = !self.isProducing; if (self.isProducing) { self.activateGeneratorMode(); } else { self.activateDefenseMode(); } }; self.animatePrism = function () { if (!self.isProducing) { return; } var prismGraphics = self.prismGraphics; tween(prismGraphics, { rotation: prismGraphics.rotation + Math.PI * 2 }, { duration: 8000, easing: tween.linear, onFinish: function onFinish() { self.animatePrism(); } }); var tintColors = [0xFFFFFF, 0xFFE0E0, 0xE0FFFF, 0xE0FFE0, 0xFFFFE0, 0xF0E0FF]; var currentTintIndex = 0; function animateTint() { var nextIndex = (currentTintIndex + 1) % tintColors.length; tween(prismGraphics, { tint: tintColors[nextIndex] }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { currentTintIndex = nextIndex; animateTint(); } }); } }; self.animatePrism(); self.activateGeneratorMode = function () { console.log("Tower activating generator mode at", self.gridX, self.gridY); self.prismGraphics.visible = true; self.gunGraphics.visible = false; self.turretGraphics.visible = false; if (self.playerIndex === 0) { self.prismGraphics.tint = teamColors[0]; self.turretGraphics.tint = teamColors[0]; self.gunGraphics.tint = teamColors[0]; } else if (self.playerIndex === 1) { self.prismGraphics.tint = teamColors[1]; self.turretGraphics.tint = teamColors[1]; self.gunGraphics.tint = teamColors[1]; } else { self.prismGraphics.tint = 0xAAAAAA; self.turretGraphics.tint = 0xAAAAAA; self.gunGraphics.tint = 0xAAAAAA; } self.animatePrism(); var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu && child.tower === self; }); for (var i = 0; i < upgradeMenus.length; i++) { if (upgradeMenus[i].updateButtonText) { upgradeMenus[i].updateButtonText('Defense mode'); } } }; self.activateDefenseMode = function () { self.prismGraphics.visible = false; self.turretGraphics.visible = true; self.gunGraphics.visible = true; if (self.playerIndex === 0) { self.turretGraphics.tint = teamColors[0]; self.gunGraphics.tint = teamColors[0]; self.prismGraphics.tint = teamColors[0]; } else if (self.playerIndex === 1) { self.turretGraphics.tint = teamColors[1]; self.gunGraphics.tint = teamColors[1]; self.prismGraphics.tint = teamColors[1]; } else { self.turretGraphics.tint = 0xAAAAAA; self.gunGraphics.tint = 0xAAAAAA; self.prismGraphics.tint = 0xAAAAAA; } tween.stop(self.prismGraphics, { rotation: true, tint: true }); self.prismGraphics.rotation = 0; var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu && child.tower === self; }); for (var i = 0; i < upgradeMenus.length; i++) { if (upgradeMenus[i].updateButtonText) { upgradeMenus[i].updateButtonText('Generator mode'); } } }; 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); if (previousHasEnoughGold !== self.hasEnoughGold) { self.updateAppearance(); } }; self.updateAppearance = function () { var tempTower = new Tower(false, undefined, 0); var previewRange = tempTower.getRange(); if (tempTower && tempTower.destroy) { tempTower.destroy(); } rangeGraphics.width = rangeGraphics.height = previewRange * 2; previewGraphics.tint = 0xAAAAAA; if (!self.canPlace || !self.hasEnoughGold) { previewGraphics.tint = 0xFF0000; } }; self.updatePlacementStatus = function () { var validGridPlacement = true; if (self.gridY <= mapGridOffset || self.gridY + 1 >= grid.cells[0].length - mapGridOffset) { 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 < mapGridOffset) { continue; } 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 TrailParticle = Container.expand(function (x, y, tint, scale) { var self = Container.call(this); self.x = x; self.y = y; var particleGraphics = self.attachAsset('unit', { anchorX: 0.5, anchorY: 0.5 }); particleGraphics.tint = tint; self.alpha = 0.75; self.scaleX = (scale || 1) * 0.75; self.scaleY = (scale || 1) * 0.75; tween(self, { alpha: 0, scaleX: self.scaleX * 0.1, scaleY: self.scaleY * 0.1 }, { duration: 300 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); return self; }); var Unit = 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 = 1; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.health = self.maxHealth; self.enemyHQ = null; self.isIdle = false; self.idleBaseX = 0; self.idleBaseY = 0; self.idleAnimationActive = false; self.isSelected = false; self.selectionIndicator = null; self.targetCellX = null; self.targetCellY = null; self.movementSpeed = 0.05; self.lastTrailTime = 0; var assetId = 'unit'; if (self.type !== 'normal') { assetId = 'unit_' + self.type; } var unitGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); for (var i = 0; i < towers.length; i++) { if (towers[i].isHeadquarters && towers[i].playerIndex === 1) { self.enemyHQ = towers[i]; break; } } self.setSelected = function (selected) { self.isSelected = selected; if (selected && !self.selectionIndicator) { self.selectionIndicator = new Container(); var indicator = self.selectionIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = 50; indicator.height = 50; indicator.alpha = 0.5; indicator.tint = 0x00FF00; self.addChild(self.selectionIndicator); } else if (!selected && self.selectionIndicator) { self.removeChild(self.selectionIndicator); self.selectionIndicator = null; } }; self.setTarget = function (cellX, cellY) { self.targetCellX = cellX; self.targetCellY = cellY; self.isIdle = false; self.idleAnimationActive = false; tween.stop(self); self.targetTower = null; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.playerIndex !== self.ownerIndex) { var towerGridX, towerGridY; var towerSize = tower.isHeadquarters ? 4 : 2; if (tower.isHeadquarters) { towerGridX = Math.round((tower.x - grid.x) / CELL_SIZE) - towerSize / 2; towerGridY = Math.round((tower.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset; } else { towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE); towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset; } if (cellX >= towerGridX && cellX < towerGridX + towerSize && cellY >= towerGridY && cellY < towerGridY + towerSize) { self.targetTower = tower; break; } } } }; self.update = function () { if (tutorialActive && tutorialBlockUnitUpdates) { return; } if (self.health <= 0) { self.health = 0; } if (self.targetCellX !== null && self.targetCellY !== null) { var targetX = grid.x + self.targetCellX * CELL_SIZE + CELL_SIZE / 2; var targetY = grid.y + (self.targetCellY + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2; if (self.targetTower && self.targetTower.parent) { targetX = self.targetTower.x; targetY = self.targetTower.y; } var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var attackRange = CELL_SIZE; if (self.targetTower && distance <= attackRange) { if (!self.targetTower.isHeadquarters && self.targetTower.updateActivation) { var consumed = self.targetTower.updateActivation(self.ownerIndex); if (consumed) { self.health = 0; } else { var tower = self.targetTower; var towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE); var towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE - mapGridOffset); var availableCells = []; var searchRadius = 1; var maxSearchRadius = 10; while (availableCells.length === 0 && searchRadius <= maxSearchRadius) { for (var dx = -searchRadius; dx <= searchRadius; dx++) { for (var dy = -searchRadius; dy <= searchRadius; dy++) { if (Math.abs(dx) !== searchRadius && Math.abs(dy) !== searchRadius) { continue; } var checkX = towerGridX + dx; var checkY = towerGridY + dy; var cell = grid.getCell(checkX, checkY); if (cell && cell.type === 0) { availableCells.push(cell); } } } searchRadius++; } if (availableCells.length > 0) { var randomCell = availableCells[Math.floor(Math.random() * availableCells.length)]; self.setTarget(randomCell.x, randomCell.y); } else { self.health = 0; } } return; } if (!self.targetTower.parent) { var tower = self.targetTower; var towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE); var towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE - mapGridOffset); var availableCells = []; var searchRadius = 1; var maxSearchRadius = 10; while (availableCells.length === 0 && searchRadius <= maxSearchRadius) { for (var dx = -searchRadius; dx <= searchRadius; dx++) { for (var dy = -searchRadius; dy <= searchRadius; dy++) { if (Math.abs(dx) !== searchRadius && Math.abs(dy) !== searchRadius) { continue; } var checkX = towerGridX + dx; var checkY = towerGridY + dy; var cell = grid.getCell(checkX, checkY); if (cell && cell.type === 0) { availableCells.push(cell); } } } searchRadius++; } if (availableCells.length > 0) { var randomCell = availableCells[Math.floor(Math.random() * availableCells.length)]; self.setTarget(randomCell.x, randomCell.y); } else { self.health = 0; } } else { var isEnemyTower = self.targetTower.playerIndex !== self.ownerIndex; console.log("Unit reached tower. Enemy tower:", isEnemyTower, "Tower health:", self.targetTower.health); self.targetTower.health -= 1; self.health = 0; if (self.targetTower.health <= 0) { var towerIndex = towers.indexOf(self.targetTower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } game.removeChild(self.targetTower); var towerGridX, towerGridY; var towerSize = self.targetTower.isHeadquarters ? 4 : 2; if (self.targetTower.isHeadquarters) { towerGridX = Math.round((self.targetTower.x - grid.x) / CELL_SIZE) - towerSize / 2; towerGridY = Math.round((self.targetTower.y - grid.y) / CELL_SIZE) - towerSize / 2; } else { towerGridX = Math.floor((self.targetTower.x - grid.x) / CELL_SIZE); towerGridY = Math.floor((self.targetTower.y - grid.y) / CELL_SIZE); } for (var i = 0; i < towerSize; i++) { for (var j = 0; j < towerSize; j++) { var cell = grid.getCell(towerGridX + i, towerGridY + j); if (cell) { cell.type = 0; } } } grid.pathFind(); grid.renderDebug(); } } return; } if (distance > 2) { var angle = Math.atan2(dy, dx); if (LK.ticks - (self.lastTrailTime || 0) > 3) { self.lastTrailTime = LK.ticks; var trail = new TrailParticle(self.x, self.y, self.children[0].tint, self.scaleX); enemyLayerMiddle.addChild(trail); } self.x += Math.cos(angle) * self.movementSpeed * CELL_SIZE; self.y += Math.sin(angle) * self.movementSpeed * CELL_SIZE; self.currentCellX = (self.x - grid.x) / CELL_SIZE - 0.5; self.currentCellY = (self.y - grid.y) / CELL_SIZE - 0.5; self.cellX = Math.round(self.currentCellX); self.cellY = Math.round(self.currentCellY); } else { self.targetCellX = null; self.targetCellY = null; self.targetTower = null; self.isIdle = true; self.idleBaseX = targetX; self.idleBaseY = targetY; if (unitManager) { unitManager.startIdleAnimation(self); } } } }; return self; }); var UnitManager = Container.expand(function () { var self = Container.call(this); self.units = []; self.spawnInterval = 180; self.lastSpawnTime = 0; self.maxUnits = 100; self.spawnUnitsAroundTower = function (tower) { var actualGridX = Math.floor((tower.x - grid.x) / CELL_SIZE); var actualGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset; if (tower.isHeadquarters) { actualGridX -= 1; actualGridY -= 1; } var totalUnits = self.units.length; if (totalUnits >= self.maxUnits) { return; } var emptySpawnPositions = []; var unitOccupiedSpawnPositions = []; var towerSize = 2; var maxSearchRadius = 2; function checkCell(checkX, checkY) { var cell = grid.getCell(checkX, checkY); if (!cell || cell.type !== 0) { return; } for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === checkX && enemies[e].cellY === checkY) { return; } } var isOccupiedByUnit = false; for (var u = 0; u < self.units.length; u++) { if (self.units[u].cellX === checkX && self.units[u].cellY === checkY) { isOccupiedByUnit = true; break; } } var pos = { x: checkX, y: checkY }; if (isOccupiedByUnit) { unitOccupiedSpawnPositions.push(pos); } else { emptySpawnPositions.push(pos); } } for (var r = 1; r <= maxSearchRadius; r++) { for (var dx = -r; dx < towerSize + r; dx++) { checkCell(actualGridX + dx, actualGridY - r); checkCell(actualGridX + dx, actualGridY + towerSize - 1 + r); } for (var dy = -r + 1; dy < towerSize + r - 1; dy++) { checkCell(actualGridX - r, actualGridY + dy); checkCell(actualGridX + towerSize - 1 + r, actualGridY + dy); } if (emptySpawnPositions.length > 0) { break; } } var spawnPositions = emptySpawnPositions; if (spawnPositions.length === 0) { spawnPositions = unitOccupiedSpawnPositions; } if (spawnPositions.length > 0) { var spawnPos = spawnPositions[Math.floor(Math.random() * spawnPositions.length)]; var unit = new Unit('normal'); unit.cellX = spawnPos.x; unit.cellY = spawnPos.y; unit.currentCellX = spawnPos.x; unit.currentCellY = spawnPos.y; var finalX = grid.x + spawnPos.x * CELL_SIZE + CELL_SIZE / 2; var finalY = grid.y + (spawnPos.y + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2; unit.x = tower.x; unit.y = tower.y; unit.sourceTower = tower; unit.ownerIndex = tower.playerIndex; unit.movementSpeed *= unit.ownerIndex == 0 ? 1 : enemyMovementSpeedRatio; enemyLayerTop.addChild(unit); self.units.push(unit); unit.alpha = 0; unit.scaleX = 0.1; unit.scaleY = 0.1; if (tower.playerIndex === 0) { unit.children[0].tint = teamColors[0]; } else if (tower.playerIndex === 1) { unit.children[0].tint = teamColors[1]; } else { var playerColors = [0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF]; unit.children[0].tint = playerColors[(tower.playerIndex - 2) % playerColors.length]; } tween(unit, { scaleX: 1, scaleY: 1, alpha: 1, x: finalX, y: finalY }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { unit.isIdle = true; unit.idleBaseX = finalX; unit.idleBaseY = finalY; self.startIdleAnimation(unit); } }); } }; self.spawnUnitsAtRow = function (playerIndex, rowIndex) { var gridWidth = grid.cells.length; for (var col = 0; col < gridWidth; col++) { if (self.units.length >= self.maxUnits) { break; } var cell = grid.getCell(col, rowIndex); if (cell && cell.type === 0) { var unit = new Unit('normal'); unit.cellX = col; unit.cellY = rowIndex; unit.currentCellX = col; unit.currentCellY = rowIndex; var finalX = grid.x + col * CELL_SIZE + CELL_SIZE / 2; var finalY = grid.y + (rowIndex + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2; unit.x = finalX; unit.y = finalY; unit.ownerIndex = playerIndex; unit.movementSpeed *= unit.ownerIndex == 0 ? 1 : enemyMovementSpeedRatio; enemyLayerTop.addChild(unit); self.units.push(unit); unit.alpha = 0; unit.scaleX = 0.1; unit.scaleY = 0.1; if (playerIndex === 0) { unit.children[0].tint = teamColors[0]; } else if (playerIndex === 1) { unit.children[0].tint = teamColors[1]; } else { var playerColors = [0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF]; unit.children[0].tint = playerColors[(playerIndex - 2) % playerColors.length]; } (function (currentUnit, finalUnitX, finalUnitY) { tween(currentUnit, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { currentUnit.isIdle = true; currentUnit.idleBaseX = finalUnitX; currentUnit.idleBaseY = finalUnitY; self.startIdleAnimation(currentUnit); } }); })(unit, finalX, finalY); } } }; self.startIdleAnimation = function (unit) { if (!unit.isIdle || unit.idleAnimationActive) { return; } unit.idleAnimationActive = true; var _animateFloat = function animateFloat() { if (!unit.isIdle || !unit.parent) { unit.idleAnimationActive = false; return; } var radius = 8; var angle = Math.random() * Math.PI * 2; var offsetX = Math.cos(angle) * radius; var offsetY = Math.sin(angle) * radius; tween(unit, { x: unit.idleBaseX + offsetX, y: unit.idleBaseY + offsetY }, { duration: 800 + Math.random() * 400, easing: tween.easeInOut, onFinish: function onFinish() { if (unit.isIdle && unit.parent) { _animateFloat(); } else { unit.idleAnimationActive = false; } } }); }; _animateFloat(); }; self.update = function () { if (LK.ticks - self.lastSpawnTime >= self.spawnInterval) { self.lastSpawnTime = LK.ticks; var generatorTowerCount = 0; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.isProducing && !tutorialBlockUnitProduction) { generatorTowerCount++; self.spawnUnitsAroundTower(tower); } } } for (var i = self.units.length - 1; i >= 0; i--) { var unit = self.units[i]; if (unit.health <= 0) { enemyLayerTop.removeChild(unit); self.units.splice(i, 1); continue; } } }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 500; menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; var towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' Tower', { size: 80, fill: 0xFFFFFF, weight: 800 }); towerTypeText.anchor.set(0, 0); towerTypeText.x = -840; towerTypeText.y = -160; self.addChild(towerTypeText); var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', { size: 70, fill: 0xFFFFFF, weight: 400 }); statsText.anchor.set(0, 0.5); statsText.x = -840; statsText.y = 50; self.addChild(statsText); var buttonsContainer = new Container(); buttonsContainer.x = 500; self.addChild(buttonsContainer); var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; buttonBackground.height = 150; buttonBackground.tint = 0x00AA00; var buttonText = new Text2(self.tower.isProducing ? 'Defense mode' : 'Generator mode', { 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 sellButtonText = new Text2('Move out', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); if (self.tower.isHeadquarters) { sellButton.visible = false; } 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) { self.tower.changeTowerMode(); if (self.tower.isHeadquarters) { if (tutorialActive && tutorialStep === 7 && self.tower.playerIndex === 0 && !self.tower.isProducing) { executeTutorialStep(8); } } hideUpgradeMenu(self); for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } selectedTower = null; grid.renderDebug(); }; sellButton.down = function (x, y, obj) { if (self.tower.isHeadquarters) { return; } if (unitManager) { for (var i = 0; i < 10; i++) { unitManager.spawnUnitsAroundTower(self.tower); } } self.tower.isProducing = false; self.tower.currentShares = 0; self.tower.capturingPlayer = -1; self.tower.deactivate(); hideUpgradeMenu(self); for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } if (selectedTower === self.tower) { selectedTower = null; } grid.renderDebug(); }; closeButton.down = function (x, y, obj) { hideUpgradeMenu(self); selectedTower = null; grid.renderDebug(); }; self.update = function () { // This method is now empty as the buttons are static within the menu's lifecycle. }; self.updateButtonText = function (newText) { buttonText.setText(newText); }; return self; }); var Wall = Container.expand(function () { var self = Container.call(this); var wallGraphics = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); wallGraphics.alpha = 0.5; self.startPulseAnimation = function () { var _pulseAnimation2 = function _pulseAnimation() { tween(wallGraphics, { alpha: 1 }, { duration: 4000, easing: tween.easeInOut, onFinish: function onFinish() { tween(wallGraphics, { alpha: 0.5 }, { duration: 4000, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent) { _pulseAnimation2(); } } }); } }); }; _pulseAnimation2(); }; self.startPulseAnimation(); 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; 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; 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!"); 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; var waveType = "normal"; var enemyType = "normal"; var enemyCount = 10; var isBossWave = (i + 1) % 10 === 0; if (i === 0) { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } else if (isBossWave) { enemyType = 'normal'; waveType = "Boss Normal"; block.tint = 0xAAAAAA; enemyCount = 1; } else { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } if (isBossWave && enemyType !== 'swarm') { var bossIndicator = marker.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); bossIndicator.width = 30; bossIndicator.height = 30; bossIndicator.tint = 0xFFD700; bossIndicator.y = -block.height / 2 - 15; waveType = "BOSS"; } self.waveTypes[i] = enemyType; self.enemyCounts[i] = enemyCount; 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); var waveTypeText = new Text2(waveType, { size: 56, fill: 0xFFFFFF, weight: 800 }); waveTypeText.anchor.set(0.5, 0.5); waveTypeText.y = 0; marker.addChild(waveTypeText); 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); 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); } self.getWaveType = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return "normal"; } var waveType = self.waveTypes[waveNumber - 1]; return waveType; }; self.getEnemyCount = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return 10; } return self.enemyCounts[waveNumber - 1]; }; self.getWaveTypeName = function (waveNumber) { var type = self.getWaveType(waveNumber); var typeName = type.charAt(0).toUpperCase() + type.slice(1); if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') { typeName = "BOSS"; } return typeName; }; self.positionIndicator = new Container(); var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = blockWidth - 10; indicator.height = 16; indicator.tint = 0xffad0e; indicator.y = -65; var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator2.width = blockWidth - 10; indicator2.height = 16; indicator2.tint = 0xffad0e; indicator2.y = 65; var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); leftWall.width = 16; leftWall.height = 146; leftWall.tint = 0xffad0e; leftWall.x = -(blockWidth - 16) / 2; var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); rightWall.width = 16; rightWall.height = 146; rightWall.tint = 0xffad0e; rightWall.x = (blockWidth - 16) / 2; self.addChild(self.positionIndicator); self.update = function () { var progress = waveTimer / nextWaveTime; var moveAmount = (progress + currentWave) * blockWidth; for (var i = 0; i < self.waveMarkers.length; i++) { var marker = self.waveMarkers[i]; marker.x = -moveAmount + i * blockWidth; } self.positionIndicator.x = 0; for (var i = 0; i < totalWaves + 1; i++) { var marker = self.waveMarkers[i]; if (i === 0) { continue; } var block = marker.children[0]; if (i - 1 < currentWave) { block.alpha = .5; } } self.handleWaveProgression = function () { if (!self.gameStarted) { return; } if (currentWave < totalWaves) { waveTimer++; if (waveTimer >= nextWaveTime) { waveTimer = 0; currentWave++; waveInProgress = true; waveSpawned = false; if (currentWave != 1) { var waveType = self.getWaveTypeName(currentWave); var enemyCount = self.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } }; self.handleWaveProgression(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x333333 }); /**** * Game Code ****/ var teamColors = [0x00AA00, 0xAA0000]; var isHidingUpgradeMenu = false; var isSelectingUnits = false; var selectionStartX = 0; var selectionStartY = 0; var selectionCircle = null; var selectedUnits = []; var isCommandMode = false; function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; } }); } var isDebug = false; var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var mapGridOffset = 4; var enemies = []; var towers = []; var bullets = []; var defenses = []; var selectedTower = null; var unitManager = null; var gold = 80; var lives = 20; var score = 0; var currentWave = 0; var totalWaves = 50; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 12000 / 2; var sourceTower = null; var enemiesToSpawn = 10; var enemyMovementSpeedRatio = 0.6; // Map cell values: 0 = empty cell, 1 = player base, 2 = enemy 1 base, 3 = enemy 2 base, // 4 = player tower, 5 = enemy 1 tower, 6 = enemy 2 tower, 8 = empty tower, 9 = wall, // 11 = player base (rotated), 22 = enemy 1 base (rotated), 33 = enemy 2 base (rotated) var MAPS = [ // Level 1: Basic tower defense layout (24x31) [[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 22, 22, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9], // Row 0 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 5 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 6 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 7 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 8 [9, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 9 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 10 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 11 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 12 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 13 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 14 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 15 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 16 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 17 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 18 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 19 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 20 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 21 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 22 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 23 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 24 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 25 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 26 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 27 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 28 [9, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 29 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 30 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 31 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 32 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 33 [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9] // Row 34 - Player base area ]]; 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 backgroundManager = new BackgroundManager(); game.addChild(backgroundManager); var background1 = backgroundManager.addBackground('background_1', { x: 1024, y: 1366, scaleX: 1, scaleY: 1, alpha: 0.8, tint: 0xCCCCCC }); var background2 = backgroundManager.addBackground('background_2', { x: 1024, y: 1366, scaleX: 1, scaleY: 1, alpha: 1 }); var background3 = backgroundManager.addBackground('background_3', { x: 1024, y: 1366, scaleX: 1, scaleY: 1, alpha: 1 }); backgroundManager.createParallaxEffect([background1, background2, background3], 1); var debugLayer = new Container(); var enemyLayerBottom = new Container(); var enemyLayerMiddle = new Container(); var enemyLayerTop = new Container(); var enemyLayer = new Container(); enemyLayer.addChild(enemyLayerBottom); enemyLayer.addChild(enemyLayerMiddle); enemyLayer.addChild(enemyLayerTop); var grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * mapGridOffset; grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); game.addChild(debugLayer); unitManager = new UnitManager(); game.addChild(unitManager); function addHeadquarters() { var playerBaseCells = []; var enemyBaseCells = []; for (var i = 0; i < grid.cells.length; i++) { for (var j = 0; j < grid.cells[i].length; j++) { var cell = grid.cells[i][j]; if (cell.type === 3) { playerBaseCells.push(cell); } else if (cell.type === 2) { enemyBaseCells.push(cell); } } } if (playerBaseCells.length > 0) { var playerOrientation = 0; for (var i = 0; i < playerBaseCells.length; i++) { if (playerBaseCells[i].orientation) { playerOrientation = playerBaseCells[i].orientation; break; } } var playerHQ = new Tower(true, playerOrientation, 0); var playerAnchorX = 10; var playerAnchorY = 31; playerHQ.x = grid.x + (playerAnchorX + 1.5) * CELL_SIZE; playerHQ.y = grid.y + (playerAnchorY + 2) * CELL_SIZE; playerHQ.children[0].tint = teamColors[0]; game.addChild(playerHQ); towers.push(playerHQ); } if (enemyBaseCells.length > 0) { var enemyOrientation = 0; for (var i = 0; i < enemyBaseCells.length; i++) { if (enemyBaseCells[i].orientation) { enemyOrientation = enemyBaseCells[i].orientation; break; } } var enemyHQ = new Tower(true, enemyOrientation, 1); var enemyAnchorX = 10; var enemyAnchorY = 0; enemyHQ.x = grid.x + (enemyAnchorX + 1.5) * CELL_SIZE; if (enemyOrientation !== 0) { enemyHQ.y = grid.y + (enemyAnchorY + 5) * CELL_SIZE; } else { enemyHQ.y = grid.y + (enemyAnchorY + 2) * CELL_SIZE; } enemyHQ.children[0].tint = teamColors[1]; game.addChild(enemyHQ); towers.push(enemyHQ); } } function addEmptyTowers() { var emptyTowerCells = []; for (var i = 0; i < grid.cells.length; i++) { for (var j = 0; j < grid.cells[i].length; j++) { var cell = grid.cells[i][j]; if (cell.type === 4) { emptyTowerCells.push(cell); } } } for (var i = 0; i < emptyTowerCells.length; i++) { var cell = emptyTowerCells[i]; var emptyTower = new Tower(false, 0, -1); emptyTower.x = grid.x + cell.x * CELL_SIZE + CELL_SIZE / 2; emptyTower.y = grid.y + (cell.y + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2; var baseGraphics = emptyTower.children[0]; baseGraphics.width = CELL_SIZE; baseGraphics.height = CELL_SIZE; baseGraphics.tint = 0xAAAAAA; var gunContainer = emptyTower.children[emptyTower.children.length - 1]; gunContainer.visible = false; game.addChild(emptyTower); towers.push(emptyTower); cell.type = 1; cell.isVisuallyEmptyTower = true; } if (emptyTowerCells.length > 0) { grid.pathFind(); grid.renderDebug(); } } addHeadquarters(); addEmptyTowers(); var tutorialActive = false; var tutorialStep = 0; var tutorialText = null; var tutorialOverlayTop = null; var tutorialOverlayBottom = null; var tutorialWaitForTap = false; var tutorialBlockUnitProduction = true; var tutorialBlockUnitUpdates = true; var tutorialBlockTowerUpdates = true; function getPlayerHQ() { for (var i = 0; i < towers.length; i++) { if (towers[i].isHeadquarters && towers[i].playerIndex === 0) { return towers[i]; } } return null; } function findFirstEmptyTower() { var potentialTowers = []; for (var i = 0; i < towers.length; i++) { if (!towers[i].isHeadquarters && towers[i].playerIndex === -1) { potentialTowers.push(towers[i]); } } if (potentialTowers.length === 0) { return null; } var playerHQ = getPlayerHQ(); if (!playerHQ) { return potentialTowers[0]; } var closestTower = null; var closestDistSq = Infinity; for (var i = 0; i < potentialTowers.length; i++) { var tower = potentialTowers[i]; var dx = tower.x - playerHQ.x; var dy = tower.y - playerHQ.y; var distSq = dx * dx + dy * dy; if (distSq < closestDistSq) { closestDistSq = distSq; closestTower = tower; } } return closestTower; } function executeTutorialStep(step) { tutorialStep = step; tutorialOverlayTop.visible = false; tutorialOverlayBottom.visible = false; tutorialText.setText(''); tutorialWaitForTap = false; tutorialBlockUnitProduction = true; tutorialBlockUnitUpdates = true; tutorialBlockTowerUpdates = true; switch (step) { case 1: // Enemy Movement var playerHQ = getPlayerHQ(); if (playerHQ && unitManager) { var towerSize = playerHQ.isHeadquarters ? 4 : 2; var hqGridX = Math.round((playerHQ.x - grid.x) / CELL_SIZE) - towerSize / 2; var hqGridY = Math.round((playerHQ.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset; for (var i = 0; i < unitManager.units.length; i++) { var unit = unitManager.units[i]; if (unit.ownerIndex === 1) { unit.setTarget(hqGridX, hqGridY); } } } tutorialBlockUnitUpdates = false; LK.setTimeout(function () { executeTutorialStep(2); }, 3000); break; case 2: // Enemy Alert var enemyRowY = 700 + (2 + mapGridOffset) * CELL_SIZE; tutorialOverlayTop.visible = true; var enemyHQ = null; for (var i = 0; i < towers.length; i++) { if (towers[i].isHeadquarters && towers[i].playerIndex === 1) { enemyHQ = towers[i]; break; } } if (enemyHQ) { tutorialOverlayTop.height = enemyHQ.y - 150; } else { tutorialOverlayTop.height = 150; } tutorialOverlayBottom.visible = true; tutorialOverlayBottom.y = enemyRowY + CELL_SIZE / 2; tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y; tutorialText.setText("Enemy Wave Incoming!\n(tap to continue)"); tutorialWaitForTap = true; break; case 3: // Unit Selection var playerHQ = getPlayerHQ(); if (playerHQ) { var visibleTopY = playerHQ.y - 300; tutorialOverlayTop.visible = true; tutorialOverlayTop.height = visibleTopY; } tutorialText.setText("Select 10 of your units!"); break; case 4: // Tower Capture console.log("Tuto step 4: Tower capture", tutorialBlockUnitUpdates); var emptyTower = findFirstEmptyTower(); if (emptyTower) { var towerY = emptyTower.y; tutorialOverlayTop.visible = true; tutorialOverlayTop.height = towerY - 150; tutorialOverlayBottom.visible = true; tutorialOverlayBottom.y = towerY + 150; tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y; tutorialText.setText("Capture an empty tower."); /* tutorialBlockUnitUpdates = false; LK.setTimeout(function () { executeTutorialStep(5); }, 1000); */ } break; case 5: // Unit Movement for Capture tutorialBlockUnitUpdates = false; tutorialBlockTowerUpdates = false; LK.setTimeout(function () { executeTutorialStep(6); }, 1000); break; case 6: // HQ Selection console.log("Tuto step 6: HQ Selection", tutorialBlockUnitUpdates); var playerHQ = getPlayerHQ(); if (playerHQ) { var hqY = playerHQ.y; tutorialOverlayTop.visible = true; tutorialOverlayTop.height = hqY - 200; tutorialOverlayBottom.visible = true; tutorialOverlayBottom.y = hqY + 200; tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y; tutorialText.setText("Select your HQ."); } break; case 7: // Switch to Defense var visibleTopY = 2732 - 500; tutorialOverlayTop.visible = true; tutorialOverlayTop.height = visibleTopY; tutorialText.setText("Switch to Defense mode..."); break; case 8: // HQ Attacking tutorialOverlayTop.visible = false; tutorialOverlayBottom.visible = false; tutorialText.setText(''); tutorialBlockTowerUpdates = false; tutorialBlockUnitUpdates = false; LK.setTimeout(function () { executeTutorialStep(9); }, 10000); break; case 9: tutorialOverlayTop.visible = false; tutorialOverlayBottom.visible = false; tutorialBlockTowerUpdates = false; tutorialBlockUnitUpdates = false; tutorialText.setText("Wave cleared!"); LK.setTimeout(function () { executeTutorialStep(10); }, 2000); break; case 10: // "Now take your revenge!" tutorialText.setText("Now take your revenge!"); tutorialBlockUnitUpdates = false; tutorialBlockTowerUpdates = false; LK.setTimeout(function () { // End Tutorial tutorialActive = false; tutorialBlockUnitProduction = false; tutorialBlockUnitUpdates = false; tutorialBlockTowerUpdates = false; storage.hasPlayedTutorial = true; tutorialText.setText(''); var notification = game.addChild(new Notification("Tutorial Complete!")); notification.x = 2048 / 2; notification.y = grid.height - 250; }, 2000); break; } } function playTutorial() { tutorialActive = true; tutorialStep = 0; tutorialOverlayTop = game.addChild(LK.getAsset('notification', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 100 })); tutorialOverlayTop.tint = 0x000000; tutorialOverlayTop.alpha = 0.8; tutorialOverlayTop.visible = false; tutorialOverlayBottom = game.addChild(LK.getAsset('notification', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 100 })); tutorialOverlayBottom.tint = 0x000000; tutorialOverlayBottom.alpha = 0.8; tutorialOverlayBottom.visible = false; tutorialText = new Text2('', { size: 70, fill: 0xFFFFFF, weight: "bold", align: 'center', wordWrap: true, wordWrapWidth: 1200 }); tutorialText.anchor.set(0.5, 0.5); LK.gui.center.addChild(tutorialText); executeTutorialStep(1); } function initLevel0Units() { unitManager.spawnUnitsAtRow(1, 2); var playerHQ = null; for (var i = 0; i < towers.length; i++) { if (towers[i].isHeadquarters && towers[i].playerIndex === 0) { playerHQ = towers[i]; break; } } if (playerHQ) { for (var i = 0; i < 10; i++) { unitManager.spawnUnitsAroundTower(playerHQ); } } } var offset = 0; game.addChild(enemyLayer); var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; var isDragging = false; initLevel0Units(); var hasPlayedTutorial = storage.hasPlayedTutorial || false; if (!hasPlayedTutorial) { LK.setTimeout(playTutorial, 1000); } else { tutorialActive = false; tutorialBlockUnitProduction = false; tutorialBlockUnitUpdates = false; tutorialBlockTowerUpdates = false; } function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); return blocked; } function getTowerCost(towerType) { var cost = 5; cost = 5; return cost; } function getTowerSellValue(totalValue) { return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue; } function placeTower(gridX, gridY, towerType) { var towerCost = getTowerCost(towerType); if (gold >= towerCost) { var tower = new Tower(false, undefined, 0); tower.placeOnGrid(gridX, gridY); game.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 (tutorialActive && tutorialWaitForTap) { executeTutorialStep(tutorialStep + 1); return; } var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenuVisible) { return; } var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerLeft = tower.x - CELL_SIZE; var towerRight = tower.x + CELL_SIZE; var towerTop = tower.y - CELL_SIZE; var towerBottom = tower.y + CELL_SIZE; if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } if (sourceTower) { var sourceLeft = sourceTower.x - sourceTower.width / 2; var sourceRight = sourceTower.x + sourceTower.width / 2; var sourceTop = sourceTower.y - sourceTower.height / 2; var sourceBottom = sourceTower.y + sourceTower.height / 2; if (x >= sourceLeft && x <= sourceRight && y >= sourceTop && y <= sourceBottom) { clickedOnTower = true; } } if (!clickedOnTower && !isDragging && !isCommandMode) { if (selectionCircle) { game.removeChild(selectionCircle); selectionCircle = null; } isSelectingUnits = true; selectionStartX = x; selectionStartY = y; selectionCircle = new SelectionCircle(); selectionCircle.setPosition(x, y); game.addChild(selectionCircle); } }; game.move = function (x, y, obj) { if (isDragging) { towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); } else if (isSelectingUnits && selectionCircle) { var screenMargin = 50; if (x < -screenMargin || x > 2048 + screenMargin || y < -screenMargin || y > 2732 + screenMargin) { isSelectingUnits = false; if (selectionCircle) { game.removeChild(selectionCircle); selectionCircle = null; } } else { selectionCircle.updateCircle(x, y); } } }; game.up = function (x, y, obj) { if (isSelectingUnits && selectionCircle) { isSelectingUnits = false; for (var i = 0; i < selectedUnits.length; i++) { selectedUnits[i].setSelected(false); } selectedUnits = []; var centerX = selectionCircle.startX; var centerY = selectionCircle.startY; var radius = selectionCircle.currentRadius; if (unitManager && unitManager.units) { for (var i = 0; i < unitManager.units.length; i++) { var unit = unitManager.units[i]; if (unit.ownerIndex === 0) { var dx = unit.x - centerX; var dy = unit.y - centerY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= radius) { unit.setSelected(true); selectedUnits.push(unit); } } } } game.removeChild(selectionCircle); selectionCircle = null; if (selectedUnits.length > 0) { var notification = game.addChild(new Notification(selectedUnits.length + " units selected")); notification.x = 2048 / 2; notification.y = grid.height - 250; isCommandMode = true; if (tutorialActive && tutorialStep === 3 && selectedUnits.length === 10) { executeTutorialStep(4); } } return; } if (isCommandMode && selectedUnits.length > 0) { var gridPosX = x - grid.x; var gridPosY = y - grid.y; var cellX = Math.floor(gridPosX / CELL_SIZE); var cellY = Math.floor(gridPosY / CELL_SIZE) - mapGridOffset; var targetCell = grid.getCell(cellX, cellY); if (!targetCell || targetCell.type === 1 || targetCell.type === 9) { var closestCell = null; var closestDistance = Infinity; var searchRadius = 1; var maxSearchRadius = 10; while (!closestCell && searchRadius <= maxSearchRadius) { for (var dx = -searchRadius; dx <= searchRadius; dx++) { for (var dy = -searchRadius; dy <= searchRadius; dy++) { if (Math.abs(dx) === searchRadius || Math.abs(dy) === searchRadius) { var checkX = cellX + dx; var checkY = cellY + dy; var checkCell = grid.getCell(checkX, checkY); if (checkCell && checkCell.type === 0) { var dist = dx * dx + dy * dy; if (dist < closestDistance) { closestDistance = dist; closestCell = checkCell; } } } } } searchRadius++; } targetCell = closestCell; } var targetHasTower = false; var enemyTower = null; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.playerIndex !== 0) { var towerGridX, towerGridY; var towerSize = tower.isHeadquarters ? 4 : 2; if (tower.isHeadquarters) { towerGridX = Math.round((tower.x - grid.x) / CELL_SIZE) - towerSize / 2; towerGridY = Math.round((tower.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset; } else { towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE); towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset; } if (cellX >= towerGridX && cellX < towerGridX + towerSize && cellY >= towerGridY && cellY < towerGridY + towerSize) { targetHasTower = true; enemyTower = tower; console.log("Empty tower tapped", tutorialActive, tutorialStep === 4, tower.playerIndex === -1, towerGridY, selectedUnits.length === 10); if (tutorialActive && tutorialStep === 4 && tower.playerIndex === -1 && towerGridY === 25 && selectedUnits.length === 10) { tutorialBlockUnitUpdates = false; console.log("Tuto Step 4.5 : Ok units capturing tower", tutorialBlockUnitUpdates); LK.setTimeout(function () { executeTutorialStep(5); }, 2000); } break; } } } if (targetCell && targetCell.type === 0 || targetHasTower) { for (var i = 0; i < selectedUnits.length; i++) { var unit = selectedUnits[i]; if (targetHasTower) { unit.setTarget(cellX, cellY); } else { unit.setTarget(targetCell.x, targetCell.y); } } var message; if (targetHasTower) { if (enemyTower.playerIndex === -1) { message = "Units capturing tower!"; } else { message = "Units attacking enemy tower!"; } } else { message = "Units moving to target"; } if (!(tutorialActive && tutorialStep === 3 && selectedUnits.length < 10)) { var notification = game.addChild(new Notification(message)); notification.x = 2048 / 2; notification.y = grid.height - 250; } for (var i = 0; i < selectedUnits.length; i++) { selectedUnits[i].setSelected(false); } selectedUnits = []; isCommandMode = false; } else { var notification = game.addChild(new Notification("Invalid target location")); notification.x = 2048 / 2; notification.y = grid.height - 250; } 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 (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 = null; /* 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); sourceTower = null; enemiesToSpawn = 10; game.update = function () { if (waveInProgress) { if (!waveSpawned) { waveSpawned = true; var waveType = waveIndicator.getWaveType(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); var isBossWave = currentWave % 10 === 0 && currentWave > 0; if (isBossWave) { enemyCount = 1; var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️")); notification.x = 2048 / 2; notification.y = grid.height - 200; } for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); if (enemy.isFlying) { enemyLayerTop.addChild(enemy); if (enemy.shadow) { enemyLayerMiddle.addChild(enemy.shadow); } } else { enemyLayerBottom.addChild(enemy); } var healthMultiplier = Math.pow(1.12, currentWave); enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier); enemy.health = enemy.maxHealth; var gridWidth = 24; var midPoint = Math.floor(gridWidth / 2); var availableColumns = []; for (var col = midPoint - 3; col < midPoint + 3; col++) { var columnOccupied = false; 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); } } var spawnX; if (availableColumns.length > 0) { spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)]; } else { spawnX = midPoint - 3 + Math.floor(Math.random() * 6); } var spawnY = -1 - Math.random() * 5; enemy.cellX = spawnX; enemy.cellY = mapGridOffset; 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; } var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5); var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); var scoreValue = enemy.isBoss ? 100 : 5; score += scoreValue; if (enemy.isBoss) { var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } updateUI(); if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); continue; } if (grid.updateEnemy(enemy)) { if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } 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(); } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -3660,12 +3660,15 @@
}
}
}
};
+var waveIndicator = null;
+/*
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;