/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var AIPlayer = Container.expand(function (playerIndex) { var self = Container.call(this); self.playerIndex = playerIndex; self.state = 'EXPLORING'; self.hq = null; self.updateCooldown = 0; self.attackWaveSize = 15; self.exploreGroupSize = 10; self.findHq = function () { if (self.hq && self.hq.parent) { return; } for (var i = 0; i < towers.length; i++) { if (towers[i].isHeadquarters && towers[i].playerIndex === self.playerIndex) { self.hq = towers[i]; break; } } }; self.getUnits = function (isIdleOnly) { var myUnits = []; for (var i = 0; i < unitManager.units.length; i++) { var unit = unitManager.units[i]; if (unit.ownerIndex === self.playerIndex) { if (isIdleOnly && !unit.isIdle) { continue; } myUnits.push(unit); } } return myUnits; }; self.getTowers = function (ownerIndex) { var foundTowers = []; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.isHeadquarters) { continue; } if (ownerIndex === -1 && tower.playerIndex === -1) { foundTowers.push(tower); } else if (tower.playerIndex === ownerIndex) { foundTowers.push(tower); } } return foundTowers; }; self.isHqUnderAttack = function () { if (!self.hq) { return false; } if (self.hq.lastHealth === undefined) { self.hq.lastHealth = self.hq.health; } if (self.hq.health < self.hq.lastHealth) { self.hq.lastHealth = self.hq.health; return true; } self.hq.lastHealth = self.hq.health; var playerUnits = unitManager.units.filter(function (u) { return u.ownerIndex === 0; }); var attackRangeSq = self.hq.getRange() * self.hq.getRange(); for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var dx = unit.x - self.hq.x; var dy = unit.y - self.hq.y; if (dx * dx + dy * dy < attackRangeSq) { return true; } } return false; }; self.findClosestTarget = function (source, targets) { var closestTarget = null; var closestDistSq = Infinity; for (var i = 0; i < targets.length; i++) { var target = targets[i]; var dx = target.x - source.x; var dy = target.y - source.y; var distSq = dx * dx + dy * dy; if (distSq < closestDistSq) { closestDistSq = distSq; closestTarget = target; } } return closestTarget; }; self.commandUnits = function (units, target) { if (!target) { return; } var targetGridX = Math.floor((target.x - grid.x) / CELL_SIZE); var targetGridY = Math.floor((target.y - grid.y) / CELL_SIZE) - mapGridOffset; for (var i = 0; i < units.length; i++) { var unit = units[i]; unit.setTarget(targetGridX, targetGridY); unit.isIdle = false; } }; self.update = function () { if (tutorialActive || !self.hq) { self.findHq(); return; } self.updateCooldown--; if (self.updateCooldown > 0) { return; } self.updateCooldown = 120; var idleUnits = self.getUnits(true); if (self.isHqUnderAttack()) { self.state = 'DEFENDING'; } else { var neutralTowers = self.getTowers(-1); if (neutralTowers.length > 0) { self.state = 'EXPLORING'; } else { self.state = 'ATTACKING'; } } if (self.state === 'DEFENDING') { if (self.hq.isProducing) { self.hq.changeTowerMode(); } } else if (self.state === 'EXPLORING') { if (!self.hq.isProducing) { self.hq.changeTowerMode(); } if (idleUnits.length >= self.exploreGroupSize) { var neutralTowers = self.getTowers(-1); if (neutralTowers.length > 0) { var targetTower = self.findClosestTarget(self.hq, neutralTowers); if (targetTower) { var unitsToCommand = idleUnits.slice(0, self.exploreGroupSize); self.commandUnits(unitsToCommand, targetTower); } } } } else if (self.state === 'ATTACKING') { if (!self.hq.isProducing) { self.hq.changeTowerMode(); } if (idleUnits.length >= self.attackWaveSize) { var playerTowers = self.getTowers(0); var target; if (playerTowers.length > 0) { target = self.findClosestTarget(self.hq, playerTowers); } else { for (var i = 0; i < towers.length; i++) { if (towers[i].isHeadquarters && towers[i].playerIndex === 0) { target = towers[i]; break; } } } if (target) { self.commandUnits(idleUnits, target); } } } }; return self; }); 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 currentLevel = storage.currentLevel || 1; // FOR DEBUGGING: //currentLevel = 1; //storage.currentLevel = 1; // FOR DEBUGGING var currentMap; if (storage.hasPlayedTutorial) { currentMap = initMapForLevel(currentLevel); } else { 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) { 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: 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: 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) { if (self.targetTower.isHeadquarters) { if (self.targetTower.playerIndex === 1) { handleGameEnd(true); } else if (self.targetTower.playerIndex === 0) { handleGameEnd(false); } } 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 aiPlayer = null; var teamColors = [0x00AA00, 0xAA0000]; var isHidingUpgradeMenu = false; var isSelectingUnits = false; var selectionStartX = 0; var selectionStartY = 0; var selectionCircle = null; var selectedUnits = []; var isCommandMode = false; var isEndGame = false; function handleGameEnd(isVictory) { if (isEndGame) { return; } isEndGame = true; var currentLevel = storage.currentLevel || 1; if (isVictory && currentLevel <= 20) { storage.currentLevel = currentLevel + 1; var message = "Level " + (storage.currentLevel - 1) + " Complete!"; var endText = new Text2(message, { size: 150, fill: 0x00FF00, weight: "bold", align: 'center' }); endText.anchor.set(0.5, 0.5); LK.gui.center.addChild(endText); LK.setTimeout(initNewLevel, 3000); return; } var message = isVictory ? "Victory!" : "Defeat!"; var color = isVictory ? 0x00FF00 : 0xFF0000; var endText = new Text2(message, { size: 150, fill: color, weight: "bold", align: 'center' }); endText.anchor.set(0.5, 0.5); LK.gui.center.addChild(endText); LK.setTimeout(function () { if (isVictory) { LK.showYouWin(); } else { LK.showGameOver(); } }, 3000); } function initNewLevel() { // 1. Clean up from previous level for (var i = towers.length - 1; i >= 0; i--) { towers[i].destroy(); } for (var i = bullets.length - 1; i >= 0; i--) { bullets[i].destroy(); } enemyLayerTop.removeChildren(); enemyLayerMiddle.removeChildren(); enemyLayerBottom.removeChildren(); if (grid && grid.parent) { grid.destroy(); } if (unitManager && unitManager.parent) { unitManager.destroy(); } if (aiPlayer && aiPlayer.parent) { aiPlayer.destroy(); } if (towerPreview && towerPreview.parent) { towerPreview.destroy(); } var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } if (selectionCircle) { selectionCircle.destroy(); } LK.gui.center.removeChildren(); // 2. Reset state variables pathId = 1; maxScore = 0; enemies = []; towers = []; bullets = []; defenses = []; selectedTower = null; gold = 80; currentWave = 0; waveTimer = 0; waveInProgress = false; waveSpawned = false; sourceTower = null; isSelectingUnits = false; isCommandMode = false; selectionCircle = null; selectedUnits = []; // 3. Initialize new level from scratch grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * mapGridOffset; debugLayer.addChild(grid); unitManager = new UnitManager(); game.addChild(unitManager); addHeadquarters(); addEmptyTowers(); grid.pathFind(); grid.renderDebug(); aiPlayer = new AIPlayer(1); game.addChild(aiPlayer); towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; updateUI(); isEndGame = 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 0: Tutorial 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 ], // 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, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 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 ]]; function initMapForLevel(level) { // Level 0 is tutorial, actual levels start at 1 if (level <= 0) { return MAPS[0]; // Return tutorial map } // Use level 1 base map (MAPS[1]) as template var baseMap = MAPS[1]; var mapHeight = baseMap.length; var mapWidth = baseMap[0].length; // Create a deep copy of the base map var newMap = []; for (var i = 0; i < mapHeight; i++) { newMap[i] = []; for (var j = 0; j < mapWidth; j++) { newMap[i][j] = baseMap[i][j]; } } // Calculate number of towers to add: 2x(3+level-1) = 2x(2+level) var towersToAdd = 2 * (2 + level); // Find all empty cells between rows 10 and 26 (excluding walls and bases) var availableCells = []; for (var row = 10; row <= 26 - mapGridOffset; row++) { for (var col = 1; col < mapWidth - 1; col++) { // Exclude wall boundaries if (newMap[row][col] === 0) { // Empty cell availableCells.push({ row: row, col: col }); } } } // Shuffle available cells for random placement for (var i = availableCells.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = availableCells[i]; availableCells[i] = availableCells[j]; availableCells[j] = temp; } // Place towers symmetrically around the center line (column 12) var centerCol = Math.floor(mapWidth / 2); var towersPlaced = 0; for (var i = 0; i < availableCells.length && towersPlaced < towersToAdd; i++) { var cell = availableCells[i]; var row = cell.row; var col = cell.col; // Calculate the symmetric position var symmetricCol = centerCol + (centerCol - col); // Check if both positions are valid and empty if (symmetricCol >= 1 && symmetricCol < mapWidth - 1 && newMap[row][col] === 0 && newMap[row][symmetricCol] === 0) { // Place towers at both symmetric positions newMap[row][col] = 8; // Empty tower if (col !== symmetricCol) { // Don't place twice if it's on center line newMap[row][symmetricCol] = 8; // Empty tower towersPlaced += 2; } else { towersPlaced += 1; } } } return newMap; } 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 = -75; var centerX = 2048 / 2; var spacing = 400; /* LK.gui.bottom.addChild(goldText); LK.gui.bottom.addChild(livesText); LK.gui.bottom.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; var hasPlayedTutorial = storage.hasPlayedTutorial || false; // FOR DEBUGGING //hasPlayedTutorial = false; //storage.hasPlayedTutorial = false; // FOR DEBUGGING if (!hasPlayedTutorial) { initLevel0Units(); LK.setTimeout(playTutorial, 1000); } else { tutorialActive = false; tutorialBlockUnitProduction = false; tutorialBlockUnitUpdates = false; tutorialBlockTowerUpdates = false; } aiPlayer = new AIPlayer(1); game.addChild(aiPlayer); 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 (isEndGame) { return; } if (aiPlayer) { aiPlayer.update(); } 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(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AIPlayer = Container.expand(function (playerIndex) {
var self = Container.call(this);
self.playerIndex = playerIndex;
self.state = 'EXPLORING';
self.hq = null;
self.updateCooldown = 0;
self.attackWaveSize = 15;
self.exploreGroupSize = 10;
self.findHq = function () {
if (self.hq && self.hq.parent) {
return;
}
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === self.playerIndex) {
self.hq = towers[i];
break;
}
}
};
self.getUnits = function (isIdleOnly) {
var myUnits = [];
for (var i = 0; i < unitManager.units.length; i++) {
var unit = unitManager.units[i];
if (unit.ownerIndex === self.playerIndex) {
if (isIdleOnly && !unit.isIdle) {
continue;
}
myUnits.push(unit);
}
}
return myUnits;
};
self.getTowers = function (ownerIndex) {
var foundTowers = [];
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.isHeadquarters) {
continue;
}
if (ownerIndex === -1 && tower.playerIndex === -1) {
foundTowers.push(tower);
} else if (tower.playerIndex === ownerIndex) {
foundTowers.push(tower);
}
}
return foundTowers;
};
self.isHqUnderAttack = function () {
if (!self.hq) {
return false;
}
if (self.hq.lastHealth === undefined) {
self.hq.lastHealth = self.hq.health;
}
if (self.hq.health < self.hq.lastHealth) {
self.hq.lastHealth = self.hq.health;
return true;
}
self.hq.lastHealth = self.hq.health;
var playerUnits = unitManager.units.filter(function (u) {
return u.ownerIndex === 0;
});
var attackRangeSq = self.hq.getRange() * self.hq.getRange();
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var dx = unit.x - self.hq.x;
var dy = unit.y - self.hq.y;
if (dx * dx + dy * dy < attackRangeSq) {
return true;
}
}
return false;
};
self.findClosestTarget = function (source, targets) {
var closestTarget = null;
var closestDistSq = Infinity;
for (var i = 0; i < targets.length; i++) {
var target = targets[i];
var dx = target.x - source.x;
var dy = target.y - source.y;
var distSq = dx * dx + dy * dy;
if (distSq < closestDistSq) {
closestDistSq = distSq;
closestTarget = target;
}
}
return closestTarget;
};
self.commandUnits = function (units, target) {
if (!target) {
return;
}
var targetGridX = Math.floor((target.x - grid.x) / CELL_SIZE);
var targetGridY = Math.floor((target.y - grid.y) / CELL_SIZE) - mapGridOffset;
for (var i = 0; i < units.length; i++) {
var unit = units[i];
unit.setTarget(targetGridX, targetGridY);
unit.isIdle = false;
}
};
self.update = function () {
if (tutorialActive || !self.hq) {
self.findHq();
return;
}
self.updateCooldown--;
if (self.updateCooldown > 0) {
return;
}
self.updateCooldown = 120;
var idleUnits = self.getUnits(true);
if (self.isHqUnderAttack()) {
self.state = 'DEFENDING';
} else {
var neutralTowers = self.getTowers(-1);
if (neutralTowers.length > 0) {
self.state = 'EXPLORING';
} else {
self.state = 'ATTACKING';
}
}
if (self.state === 'DEFENDING') {
if (self.hq.isProducing) {
self.hq.changeTowerMode();
}
} else if (self.state === 'EXPLORING') {
if (!self.hq.isProducing) {
self.hq.changeTowerMode();
}
if (idleUnits.length >= self.exploreGroupSize) {
var neutralTowers = self.getTowers(-1);
if (neutralTowers.length > 0) {
var targetTower = self.findClosestTarget(self.hq, neutralTowers);
if (targetTower) {
var unitsToCommand = idleUnits.slice(0, self.exploreGroupSize);
self.commandUnits(unitsToCommand, targetTower);
}
}
}
} else if (self.state === 'ATTACKING') {
if (!self.hq.isProducing) {
self.hq.changeTowerMode();
}
if (idleUnits.length >= self.attackWaveSize) {
var playerTowers = self.getTowers(0);
var target;
if (playerTowers.length > 0) {
target = self.findClosestTarget(self.hq, playerTowers);
} else {
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 0) {
target = towers[i];
break;
}
}
}
if (target) {
self.commandUnits(idleUnits, target);
}
}
}
};
return self;
});
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 currentLevel = storage.currentLevel || 1;
// FOR DEBUGGING:
//currentLevel = 1;
//storage.currentLevel = 1;
// FOR DEBUGGING
var currentMap;
if (storage.hasPlayedTutorial) {
currentMap = initMapForLevel(currentLevel);
} else {
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) {
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: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(gunContainer, {
x: gunContainer._restX,
y: gunContainer._restY
}, {
duration: 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) {
if (self.targetTower.isHeadquarters) {
if (self.targetTower.playerIndex === 1) {
handleGameEnd(true);
} else if (self.targetTower.playerIndex === 0) {
handleGameEnd(false);
}
}
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 aiPlayer = null;
var teamColors = [0x00AA00, 0xAA0000];
var isHidingUpgradeMenu = false;
var isSelectingUnits = false;
var selectionStartX = 0;
var selectionStartY = 0;
var selectionCircle = null;
var selectedUnits = [];
var isCommandMode = false;
var isEndGame = false;
function handleGameEnd(isVictory) {
if (isEndGame) {
return;
}
isEndGame = true;
var currentLevel = storage.currentLevel || 1;
if (isVictory && currentLevel <= 20) {
storage.currentLevel = currentLevel + 1;
var message = "Level " + (storage.currentLevel - 1) + " Complete!";
var endText = new Text2(message, {
size: 150,
fill: 0x00FF00,
weight: "bold",
align: 'center'
});
endText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(endText);
LK.setTimeout(initNewLevel, 3000);
return;
}
var message = isVictory ? "Victory!" : "Defeat!";
var color = isVictory ? 0x00FF00 : 0xFF0000;
var endText = new Text2(message, {
size: 150,
fill: color,
weight: "bold",
align: 'center'
});
endText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(endText);
LK.setTimeout(function () {
if (isVictory) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}, 3000);
}
function initNewLevel() {
// 1. Clean up from previous level
for (var i = towers.length - 1; i >= 0; i--) {
towers[i].destroy();
}
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
enemyLayerTop.removeChildren();
enemyLayerMiddle.removeChildren();
enemyLayerBottom.removeChildren();
if (grid && grid.parent) {
grid.destroy();
}
if (unitManager && unitManager.parent) {
unitManager.destroy();
}
if (aiPlayer && aiPlayer.parent) {
aiPlayer.destroy();
}
if (towerPreview && towerPreview.parent) {
towerPreview.destroy();
}
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
for (var i = 0; i < upgradeMenus.length; i++) {
upgradeMenus[i].destroy();
}
if (selectionCircle) {
selectionCircle.destroy();
}
LK.gui.center.removeChildren();
// 2. Reset state variables
pathId = 1;
maxScore = 0;
enemies = [];
towers = [];
bullets = [];
defenses = [];
selectedTower = null;
gold = 80;
currentWave = 0;
waveTimer = 0;
waveInProgress = false;
waveSpawned = false;
sourceTower = null;
isSelectingUnits = false;
isCommandMode = false;
selectionCircle = null;
selectedUnits = [];
// 3. Initialize new level from scratch
grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * mapGridOffset;
debugLayer.addChild(grid);
unitManager = new UnitManager();
game.addChild(unitManager);
addHeadquarters();
addEmptyTowers();
grid.pathFind();
grid.renderDebug();
aiPlayer = new AIPlayer(1);
game.addChild(aiPlayer);
towerPreview = new TowerPreview();
game.addChild(towerPreview);
towerPreview.visible = false;
updateUI();
isEndGame = 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 0: Tutorial 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
],
// 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, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 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
]];
function initMapForLevel(level) {
// Level 0 is tutorial, actual levels start at 1
if (level <= 0) {
return MAPS[0]; // Return tutorial map
}
// Use level 1 base map (MAPS[1]) as template
var baseMap = MAPS[1];
var mapHeight = baseMap.length;
var mapWidth = baseMap[0].length;
// Create a deep copy of the base map
var newMap = [];
for (var i = 0; i < mapHeight; i++) {
newMap[i] = [];
for (var j = 0; j < mapWidth; j++) {
newMap[i][j] = baseMap[i][j];
}
}
// Calculate number of towers to add: 2x(3+level-1) = 2x(2+level)
var towersToAdd = 2 * (2 + level);
// Find all empty cells between rows 10 and 26 (excluding walls and bases)
var availableCells = [];
for (var row = 10; row <= 26 - mapGridOffset; row++) {
for (var col = 1; col < mapWidth - 1; col++) {
// Exclude wall boundaries
if (newMap[row][col] === 0) {
// Empty cell
availableCells.push({
row: row,
col: col
});
}
}
}
// Shuffle available cells for random placement
for (var i = availableCells.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = availableCells[i];
availableCells[i] = availableCells[j];
availableCells[j] = temp;
}
// Place towers symmetrically around the center line (column 12)
var centerCol = Math.floor(mapWidth / 2);
var towersPlaced = 0;
for (var i = 0; i < availableCells.length && towersPlaced < towersToAdd; i++) {
var cell = availableCells[i];
var row = cell.row;
var col = cell.col;
// Calculate the symmetric position
var symmetricCol = centerCol + (centerCol - col);
// Check if both positions are valid and empty
if (symmetricCol >= 1 && symmetricCol < mapWidth - 1 && newMap[row][col] === 0 && newMap[row][symmetricCol] === 0) {
// Place towers at both symmetric positions
newMap[row][col] = 8; // Empty tower
if (col !== symmetricCol) {
// Don't place twice if it's on center line
newMap[row][symmetricCol] = 8; // Empty tower
towersPlaced += 2;
} else {
towersPlaced += 1;
}
}
}
return newMap;
}
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 = -75;
var centerX = 2048 / 2;
var spacing = 400;
/*
LK.gui.bottom.addChild(goldText);
LK.gui.bottom.addChild(livesText);
LK.gui.bottom.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;
var hasPlayedTutorial = storage.hasPlayedTutorial || false;
// FOR DEBUGGING
//hasPlayedTutorial = false;
//storage.hasPlayedTutorial = false;
// FOR DEBUGGING
if (!hasPlayedTutorial) {
initLevel0Units();
LK.setTimeout(playTutorial, 1000);
} else {
tutorialActive = false;
tutorialBlockUnitProduction = false;
tutorialBlockUnitUpdates = false;
tutorialBlockTowerUpdates = false;
}
aiPlayer = new AIPlayer(1);
game.addChild(aiPlayer);
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 (isEndGame) {
return;
}
if (aiPlayer) {
aiPlayer.update();
}
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();
}
};