User prompt
store a boolean hasPlayedTutorial at the end of tutorial. Only play tutorial if it is false at game start. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
OK, currently in UpgradeMenu of HQ, we have 1 button to change mode (Generator/Defense) and this is ok. Currently in UpgradeMenu of normal Tower we have 2 buttons : a green 'Upgrade: 5 gold' and a red 'Sell: +5 gold'. => Change the buttons in UpgradeMenu for nomal towers: - green 'Upgrade: 5 gold' => green 'Generator mode'/'Defense mode' with same logic as HQ - red 'Sell: +5 gold' => red 'Move out' that evacuates the 10 units and make the tower return to empty state Implement step by step with caution without breaking anything
Code edit (6 edits merged)
Please save this source code
User prompt
it's not an upgrade issue as it is to used. Analyze and explain why ?
User prompt
despite `self.fireRate = self.isHeadquarters ? 20 : 60;`, HQ is practically the same as Towers one; Analyze and explain why ?
Code edit (2 edits merged)
Please save this source code
User prompt
HQ towers turret base and barrel (gunGraphics) are now centered horizontally but not vertically on the HQ tower. Analyze then explain why ?
User prompt
normal towers turrets work properly but HQ towers turret base and barrel (gunGraphics) aren't centered. Analyze then explain why ?
User prompt
make HQ range, the double of the one of towers
User prompt
make HQs fire ratio the double of the towers one
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
in step 9, keep the overlayshidden and show the text "Now take your revenge!" during 2s, then end the tutorial (remove steps 10 and 11) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
in step 8, hide the overlays and clear the text and set tutorialBlockTowerUpdates and tutorialBlockUnitUpdates to false; then switch to step 9 after 3000ms
User prompt
there is a missing logic to switch from tutorial step 6 to step 7. Player is asked to select his HQ, when he does it, the Upgrade menu is displayed but tutorial doesn't switch to step 7 which asks to switch to Defense mode. Analyze and fix
Code edit (5 edits merged)
Please save this source code
User prompt
Add a hook to handle navigation to step 8 when player taps Defense mode button
User prompt
On step 5, hide overlays and enable units and tower updates for 1 sec then switch to step 6
User prompt
add a global enemyMovementSpeedRatio = 0.8; to control enemy only Units speed. Remember that the active class is Unit, the Enemy class is deprecated
User prompt
add a global enemyMovementSpeedRatio = 0.8; to control enemy only units speed
Code edit (6 edits merged)
Please save this source code
User prompt
When on tutorial step 4, implement a hook that enables tutorialBlockUnitUpdates during 1s if player taps on an empty tower (while 10 units selected) then switch to step 5
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Update findFirstEmptyTower to find the empty tower closest to the player's HQ for tutorial phase 3. ✅ Advance tutorial from step 4 to 5 when the player taps on an empty tower on row 29.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var BackgroundManager = Container.expand(function () { var self = Container.call(this); self.backgrounds = []; self.currentBackground = null; self.animationActive = false; self.addBackground = function (assetId, config) { config = config || {}; var background = new Container(); var bgGraphics = background.attachAsset(assetId, { anchorX: config.anchorX || 0.5, anchorY: config.anchorY || 0.5, x: config.x || 0, y: config.y || 0, scaleX: config.scaleX || 1, scaleY: config.scaleY || 1, alpha: config.alpha !== undefined ? config.alpha : 1, tint: config.tint || 0xFFFFFF }); background.config = config; background.graphics = bgGraphics; background.assetId = assetId; self.addChild(background); self.backgrounds.push(background); return background; }; self.removeBackground = function (background) { var index = self.backgrounds.indexOf(background); if (index !== -1) { self.backgrounds.splice(index, 1); self.removeChild(background); } }; self.animateBackground = function (background, properties, config) { if (!background || !background.graphics) { return; } config = config || {}; var duration = config.duration || 1000; var easing = config.easing || tween.linear; var loop = config.loop || false; var _onFinish = config.onFinish; var _animateFunc = function animateFunc() { tween(background.graphics, properties, { duration: duration, easing: easing, onFinish: function onFinish() { if (loop && self.animationActive) { if (config.resetOnLoop) { for (var prop in config.resetOnLoop) { background.graphics[prop] = config.resetOnLoop[prop]; } } _animateFunc(); } else if (_onFinish) { _onFinish(); } } }); }; self.animationActive = true; _animateFunc(); }; self.stopAnimations = function () { self.animationActive = false; for (var i = 0; i < self.backgrounds.length; i++) { if (self.backgrounds[i].graphics) { tween.stop(self.backgrounds[i].graphics); } } }; self.fadeToBackground = function (newBackground, duration, onComplete) { duration = duration || 1000; if (self.currentBackground && self.currentBackground !== newBackground) { tween(self.currentBackground.graphics, { alpha: 0 }, { duration: duration / 2, easing: tween.easeInOut, onFinish: function onFinish() { self.currentBackground.visible = false; } }); } newBackground.visible = true; newBackground.graphics.alpha = 0; tween(newBackground.graphics, { alpha: 1 }, { duration: duration / 2, easing: tween.easeInOut, onFinish: function onFinish() { self.currentBackground = newBackground; if (onComplete) { onComplete(); } } }); }; self.createParallaxEffect = function (backgrounds, baseSpeed) { baseSpeed = baseSpeed || 10; for (var i = 0; i < backgrounds.length; i++) { (function (index) { var bg1 = backgrounds[index]; var speed = baseSpeed * (index + 1) * 0.5; var bg2 = self.addBackground(bg1.assetId, { anchorX: bg1.config.anchorX || 0.5, anchorY: bg1.config.anchorY || 0.5, x: bg1.graphics.x + bg1.graphics.width, y: bg1.config.y || 0, scaleX: -1, scaleY: bg1.config.scaleY || 1, alpha: bg1.config.alpha !== undefined ? bg1.config.alpha : 1, tint: bg1.config.tint || 0xFFFFFF }); var moveDistance = bg1.graphics.width; var duration = 80000 / speed; var _animate = function animate(bg, initialX) { self.animateBackground(bg, { x: bg.graphics.x - moveDistance }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { if (self.animationActive) { if (bg.graphics.x <= -moveDistance / 2) { bg.graphics.x = bg.graphics.x + moveDistance * 2; } _animate(bg, initialX); } } }); }; if (index > 0) { var _animateAlpha = function animateAlpha(bg) { tween(bg.graphics, { alpha: 1 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { tween(bg.graphics, { alpha: 0.5 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { if (self.animationActive) { _animateAlpha(bg); } } }); } }); }; _animateAlpha(bg1); _animateAlpha(bg2); } _animate(bg1, bg1.graphics.x); _animate(bg2, bg2.graphics.x); })(i); } }; return self; }); var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) { var self = Container.call(this); self.targetEnemy = targetEnemy; self.damage = damage || 10; self.speed = speed || 5; self.x = startX; self.y = startY; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); bulletGraphics.rotation = angle; if (distance < self.speed) { self.targetEnemy.health -= self.damage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } if (self.type === 'splash') { var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); game.addChild(splashEffect); var splashRadius = CELL_SIZE * 1.5; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self.targetEnemy) { var splashDx = otherEnemy.x - self.targetEnemy.x; var splashDy = otherEnemy.y - self.targetEnemy.y; var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy); if (splashDistance <= splashRadius) { otherEnemy.health -= self.damage * 0.5; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } } } } } else if (self.type === 'slow') { if (!self.targetEnemy.isImmune) { var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); var slowPct = 0.5; if (self.sourceTowerLevel !== undefined) { var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8]; var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1)); slowPct = slowLevels[idx]; } if (!self.targetEnemy.slowed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; self.targetEnemy.speed *= 1 - slowPct; self.targetEnemy.slowed = true; self.targetEnemy.slowDuration = 180; } else { self.targetEnemy.slowDuration = 180; } } } else if (self.type === 'poison') { if (!self.targetEnemy.isImmune) { var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; self.targetEnemy.poisonDuration = 300; } } else if (self.type === 'sniper') { var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper'); game.addChild(sniperEffect); } self.destroy(); } else { self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); var DebugCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = null; if (isDebug) { cellGraphics = self.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5, alpha: 1 }); cellGraphics.tint = Math.random() * 0xffffff; } var debugArrows = []; var numberLabel = new Text2("-", { size: 20, fill: 0xFFFFFF }); self.addChild(numberLabel); self.update = function () {}; self.down = function () { return; if (self.cell.type == 0 || self.cell.type == 1) { self.cell.type = self.cell.type == 1 ? 0 : 1; if (grid.pathFind()) { self.cell.type = self.cell.type == 1 ? 0 : 1; grid.pathFind(); var notification = game.addChild(new Notification("Path is blocked!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } grid.renderDebug(); } }; self.removeArrows = function () { while (debugArrows.length) { self.removeChild(debugArrows.pop()); } }; self.render = function (data) { if (!isDebug) { numberLabel.visible = false; numberLabel.alpha = 0; numberLabel.setText(""); if (data.type === 1 && !data.isVisuallyEmptyTower) { if (self.wallInstance) { self.removeChild(self.wallInstance); self.wallInstance = null; } self.wallInstance = new Wall(); self.addChild(self.wallInstance); } else { if (self.wallInstance) { self.removeChild(self.wallInstance); self.wallInstance = null; } } return; } if (cellGraphics) { cellGraphics.visible = true; cellGraphics.alpha = 1; } switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); if (cellGraphics) { cellGraphics.tint = 0x880000; } return; } var tint = Math.floor(data.score / maxScore * 0x88); var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; if (cellGraphics) { cellGraphics.tint = 0x0088ff; } } else { if (cellGraphics) { cellGraphics.tint = 0x88 - tint << 8 | tint; } } break; } case 1: { self.removeArrows(); if (cellGraphics) { cellGraphics.tint = 0xaaaaaa; } numberLabel.visible = false; break; } case 3: { self.removeArrows(); if (cellGraphics) { cellGraphics.tint = 0x008800; } numberLabel.visible = false; break; } case 4: { self.removeArrows(); if (cellGraphics) { cellGraphics.tint = 0x888888; } numberLabel.visible = false; break; } } }; }); var EffectIndicator = Container.expand(function (x, y, type) { var self = Container.call(this); self.x = x; self.y = y; var effectGraphics = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.blendMode = 1; switch (type) { case 'splash': effectGraphics.tint = 0x33CC00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5; break; case 'slow': effectGraphics.tint = 0x9900FF; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'poison': effectGraphics.tint = 0x00FFAA; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'sniper': effectGraphics.tint = 0xFF5500; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; } effectGraphics.alpha = 0.7; self.alpha = 0; tween(self, { alpha: 0.8, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Enemy = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'normal'; self.speed = .01; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 1; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.isFlying = false; self.isImmune = false; self.isBoss = false; if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') { self.isBoss = true; self.maxHealth *= 20; self.speed = self.speed * 0.7; } self.health = self.maxHealth; var assetId = 'enemy'; if (self.type !== 'normal') { assetId = 'enemy_' + self.type; } var enemyGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); if (self.isBoss) { enemyGraphics.scaleX = 1.8; enemyGraphics.scaleY = 1.8; } /*switch (self.type) { case 'fast': enemyGraphics.tint = 0x00AAFF; break; case 'immune': enemyGraphics.tint = 0xAA0000; break; case 'flying': enemyGraphics.tint = 0xFFFF00; break; case 'swarm': enemyGraphics.tint = 0xFF00FF; break; }*/ if (self.isFlying) { self.shadow = new Container(); var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', { anchorX: 0.5, anchorY: 0.5 }); shadowGraphics.tint = 0x000000; shadowGraphics.alpha = 0.4; if (self.isBoss) { shadowGraphics.scaleX = 1.8; shadowGraphics.scaleY = 1.8; } self.shadow.x = 20; self.shadow.y = 20; shadowGraphics.rotation = enemyGraphics.rotation; } self.update = function () { if (self.health <= 0) { self.health = 0; } if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; if (ox !== 0 || oy !== 0) { var angle = Math.atan2(oy, ox); if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) { tween.stop(enemyGraphics, { rotation: true }); var currentRotation = enemyGraphics.rotation; var angleDiff = angle - currentRotation; while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemyGraphics.targetRotation = angle; tween(enemyGraphics, { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } } } }; return self; }); var GoldIndicator = Container.expand(function (value, x, y) { var self = Container.call(this); var shadowText = new Text2("+" + value, { size: 45, fill: 0x000000, weight: 800 }); shadowText.anchor.set(0.5, 0.5); shadowText.x = 2; shadowText.y = 2; self.addChild(shadowText); var goldText = new Text2("+" + value, { size: 45, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); self.x = x; self.y = y; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2, y: y - 40 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: y - 80 }, { duration: 600, easing: tween.easeIn, delay: 800, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; self.spawns = []; self.goals = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { score: 0, pathId: 0, towersInRange: [] }; } } /* Cell Types 0: Transparent floor 1: Wall 2: Spawn 3: Goal 4: Empty Tower 9: Wall (from map) */ var currentMap = MAPS[0]; var mapHeight = currentMap.length; var mapWidth = currentMap[0].length; for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; var cellType = 0; if (j < mapHeight && i < mapWidth) { var mapValue = currentMap[j][i]; switch (mapValue) { case 0: cellType = 0; break; case 11: cell.orientation = Math.PI; case 1: cellType = 3; self.goals.push(cell); break; case 22: case 33: cell.orientation = Math.PI; case 2: case 3: cellType = 2; self.spawns.push(cell); break; case 8: cellType = 4; break; case 9: cellType = 1; break; default: cellType = 0; } } else { cellType = 1; } cell.type = cellType; cell.x = i; cell.y = j; cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1]; cell.up = self.cells[i - 1] && self.cells[i - 1][j]; cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1]; cell.left = self.cells[i][j - 1]; cell.right = self.cells[i][j + 1]; cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1]; cell.down = self.cells[i + 1] && self.cells[i + 1][j]; cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1]; cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left]; cell.targets = []; if (j >= 0 && j <= gridHeight - 5) { var debugCell = new DebugCell(); self.addChild(debugCell); debugCell.cell = cell; debugCell.x = i * CELL_SIZE; debugCell.y = (j + mapGridOffset) * CELL_SIZE; cell.debugCell = debugCell; } } } self.getCell = function (x, y) { return self.cells[x] && self.cells[x][y]; }; self.pathFind = function () { var before = new Date().getTime(); var toProcess = self.goals.concat([]); maxScore = 0; pathId += 1; for (var a = 0; a < toProcess.length; a++) { toProcess[a].pathId = pathId; } function processNode(node, targetValue, targetNode) { if (node && node.type != 1) { if (node.pathId < pathId || targetValue < node.score) { node.targets = [targetNode]; } else if (node.pathId == pathId && targetValue == node.score) { node.targets.push(targetNode); } if (node.pathId < pathId || targetValue < node.score) { node.score = targetValue; if (node.pathId != pathId) { toProcess.push(node); } node.pathId = pathId; if (targetValue > maxScore) { maxScore = targetValue; } } } } while (toProcess.length) { var nodes = toProcess; toProcess = []; for (var a = 0; a < nodes.length; a++) { var node = nodes[a]; var targetScore = node.score + 14142; if (node.up && node.left && node.up.type != 1 && node.left.type != 1) { processNode(node.upLeft, targetScore, node); } if (node.up && node.right && node.up.type != 1 && node.right.type != 1) { processNode(node.upRight, targetScore, node); } if (node.down && node.right && node.down.type != 1 && node.right.type != 1) { processNode(node.downRight, targetScore, node); } if (node.down && node.left && node.down.type != 1 && node.left.type != 1) { processNode(node.downLeft, targetScore, node); } targetScore = node.score + 10000; processNode(node.up, targetScore, node); processNode(node.right, targetScore, node); processNode(node.down, targetScore, node); processNode(node.left, targetScore, node); } } for (var a = 0; a < self.spawns.length; a++) { if (self.spawns[a].pathId != pathId) { console.warn("Spawn blocked"); return true; } } for (var a = 0; a < enemies.length; a++) { var enemy = enemies[a]; if (enemy.currentCellY < mapGridOffset) { continue; } if (enemy.isFlying) { continue; } var target = self.getCell(enemy.cellX, enemy.cellY); if (enemy.currentTarget) { if (enemy.currentTarget.pathId != pathId) { if (!target || target.pathId != pathId) { console.warn("Enemy blocked 1 "); return true; } } } else if (!target || target.pathId != pathId) { console.warn("Enemy blocked 2"); return true; } } console.log("Speed", new Date().getTime() - before); }; self.renderDebug = function () { for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var debugCell = self.cells[i][j].debugCell; if (debugCell) { debugCell.render(self.cells[i][j]); } } } }; self.updateEnemy = function (enemy) { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell.type == 3) { return true; } if (enemy.isFlying && enemy.shadow) { enemy.shadow.x = enemy.x + 20; enemy.shadow.y = enemy.y + 20; if (enemy.children[0] && enemy.shadow.children[0]) { enemy.shadow.children[0].rotation = enemy.children[0].rotation; } } var hasReachedEntryArea = enemy.currentCellY >= mapGridOffset; if (!hasReachedEntryArea) { enemy.currentCellY += enemy.speed; var angle = Math.PI / 2; if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; if (enemy.currentCellY >= mapGridOffset) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); } return false; } if (enemy.isFlying) { if (!enemy.flyingTarget) { enemy.flyingTarget = self.goals[0]; if (self.goals.length > 1) { var closestDist = Infinity; for (var i = 0; i < self.goals.length; i++) { var goal = self.goals[i]; var dx = goal.x - enemy.cellX; var dy = goal.y - enemy.cellY; var dist = dx * dx + dy * dy; if (dist < closestDist) { closestDist = dist; enemy.flyingTarget = goal; } } } } var ox = enemy.flyingTarget.x - enemy.currentCellX; var oy = enemy.flyingTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { return true; } var angle = Math.atan2(oy, ox); if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; return false; } if (!enemy.currentTarget) { enemy.currentTarget = cell.targets[0]; } if (enemy.currentTarget) { if (cell.score < enemy.currentTarget.score) { enemy.currentTarget = cell; } var ox = enemy.currentTarget.x - enemy.currentCellX; var oy = enemy.currentTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentTarget = undefined; return; } var angle = Math.atan2(oy, ox); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; }; }); var NextWaveButton = Container.expand(function () { var self = Container.call(this); var buttonBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 300; buttonBackground.height = 100; buttonBackground.tint = 0x0088FF; var buttonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.enabled = false; self.visible = false; self.update = function () { if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) { self.enabled = true; self.visible = true; buttonBackground.tint = 0x0088FF; self.alpha = 1; } else { self.enabled = false; self.visible = false; buttonBackground.tint = 0x888888; self.alpha = 0.7; } }; self.down = function () { if (!self.enabled) { return; } if (waveIndicator.gameStarted && currentWave < totalWaves) { currentWave++; waveTimer = 0; waveInProgress = true; waveSpawned = false; var waveType = waveIndicator.getWaveTypeName(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 30; self.addChild(notificationText); self.alpha = 1; var fadeOutTime = 120; self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var SelectionCircle = Container.expand(function () { var self = Container.call(this); self.startX = 0; self.startY = 0; self.currentRadius = 0; var circleGraphics = self.attachAsset('circleSelector', { anchorX: 0.5, anchorY: 0.5 }); circleGraphics.alpha = 0.3; circleGraphics.tint = 0x00FF00; circleGraphics.width = 1; circleGraphics.height = 1; self.updateCircle = function (endX, endY) { var dx = endX - self.startX; var dy = endY - self.startY; self.currentRadius = Math.sqrt(dx * dx + dy * dy); circleGraphics.width = self.currentRadius * 2; circleGraphics.height = self.currentRadius * 2; }; self.setPosition = function (x, y) { self.startX = x; self.startY = y; self.x = x; self.y = y; }; return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); baseGraphics.tint = 0xAAAAAA; var towerCost = getTowerCost(self.towerType); var typeLabelShadow = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), { size: 50, fill: 0x000000, weight: 800 }); typeLabelShadow.anchor.set(0.5, 0.5); typeLabelShadow.x = 4; typeLabelShadow.y = -20 + 4; self.addChild(typeLabelShadow); var typeLabel = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), { size: 50, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.y = -20; self.addChild(typeLabel); var costLabelShadow = new Text2(towerCost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; costLabelShadow.y = 24 + 12; self.addChild(costLabelShadow); var costLabel = new Text2(towerCost, { size: 50, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.y = 20 + 12; self.addChild(costLabel); self.update = function () { var canAfford = gold >= getTowerCost(self.towerType); self.alpha = canAfford ? 1 : 0.5; }; return self; }); var Tower = Container.expand(function (headquarters, orientation, playerIndex) { var self = Container.call(this); self.isHeadquarters = headquarters || false; self.baseOrientation = orientation; self.playerIndex = playerIndex !== undefined ? playerIndex : 0; self.isActivated = self.isHeadquarters; self.activationThreshold = 10; self.currentShares = 0; self.capturingPlayer = -1; self.activationGauge = null; self.id = self.isHeadquarters ? 'headquarters' : 'default'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.isProducing = false; self.range = 3 * CELL_SIZE; self.health = isDebug ? self.isHeadquarters ? 4 : 2 : self.isHeadquarters ? 30 : 10; self.maxHealth = isDebug ? self.isHeadquarters ? 4 : 2 : self.isHeadquarters ? 30 : 10; self.getRange = function () { if (self.isHeadquarters) { return (3 + (self.level - 1) * 0.5) * CELL_SIZE * 2; } switch (self.id) { case 'sniper': if (self.level === self.maxLevel) { return 12 * CELL_SIZE; } return (5 + (self.level - 1) * 0.8) * CELL_SIZE; case 'splash': return (2 + (self.level - 1) * 0.2) * CELL_SIZE; case 'rapid': return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'slow': return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'poison': return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; default: return (3 + (self.level - 1) * 0.5) * CELL_SIZE; } }; self.cellsInRange = []; self.fireRate = self.isHeadquarters ? 20 : 60; self.bulletSpeed = 5; self.damage = 1; self.lastFired = 0; self.targetEnemy = null; var baseGraphics; if (self.isHeadquarters) { baseGraphics = self.attachAsset('headquarterBase', { anchorX: 0.5, anchorY: 0.5, width: 150, height: 150 }); baseGraphics.rotation = orientation || 0; } else { baseGraphics = self.attachAsset('towerBase', { anchorX: 0.5, anchorY: 0.5 }); } switch (self.id) { case 'rapid': baseGraphics.tint = 0x00AAFF; break; case 'sniper': baseGraphics.tint = 0xFF5500; break; case 'splash': baseGraphics.tint = 0x33CC00; break; case 'slow': baseGraphics.tint = 0x9900FF; break; case 'poison': baseGraphics.tint = 0x00FFAA; break; default: baseGraphics.tint = 0xAAAAAA; } var levelIndicators = []; var maxDots = 0; var dotSpacing = baseGraphics.width / (maxDots + 1); var dotSize = CELL_SIZE / 6; for (var i = 0; i < maxDots; i++) { var dot = new Container(); var outlineCircle = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); outlineCircle.width = dotSize + 4; outlineCircle.height = dotSize + 4; outlineCircle.tint = 0x000000; var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); towerLevelIndicator.width = dotSize; towerLevelIndicator.height = dotSize; towerLevelIndicator.tint = 0xCCCCCC; dot.x = -CELL_SIZE * 2 + dotSpacing * (i + 1); dot.y = CELL_SIZE * 1.3; if (self.playerIndex !== 0) { dot.visible = false; } self.addChild(dot); levelIndicators.push(dot); } var activationGaugeContainer = new Container(); self.addChild(activationGaugeContainer); activationGaugeContainer.y = CELL_SIZE * 0.9; self.activationGauge = activationGaugeContainer; self.activationGauge.visible = false; var gunContainer = new Container(); self.addChild(gunContainer); var turretGraphics; var gunGraphics; var prismGraphics; if (self.isHeadquarters) { self.isProducing = true; turretGraphics = gunContainer.attachAsset('turret', { anchorX: 0.5, anchorY: 0.5, width: 100, height: 100 }); gunGraphics = gunContainer.attachAsset('barrel2', { anchorX: 0, anchorY: 0.5, width: 100, height: 20 }); prismGraphics = gunContainer.attachAsset('prism', { anchorX: 0.5, anchorY: 0.5, width: 100, height: 100 }); } else { turretGraphics = gunContainer.attachAsset('turret', { anchorX: 0.5, anchorY: 0.5, width: 50, height: 50 }); gunGraphics = gunContainer.attachAsset('barrel', { anchorX: 0, anchorY: 0.5, width: 50, height: 10 }); prismGraphics = gunContainer.attachAsset('prism', { anchorX: 0.5, anchorY: 0.5, width: 50, height: 50 }); } self.turretGraphics = turretGraphics; self.gunGraphics = gunGraphics; self.prismGraphics = prismGraphics; prismGraphics.visible = self.isProducing; turretGraphics.visible = !self.isProducing; gunGraphics.visible = !self.isProducing; if (self.playerIndex === 0) { gunGraphics.tint = teamColors[0]; turretGraphics.tint = teamColors[0]; prismGraphics.tint = teamColors[0]; } else if (self.playerIndex === 1) { gunGraphics.tint = teamColors[1]; turretGraphics.tint = teamColors[1]; prismGraphics.tint = teamColors[1]; } else { gunGraphics.tint = 0xAAAAAA; turretGraphics.tint = 0xAAAAAA; prismGraphics.tint = 0xAAAAAA; } self.updateLevelIndicators = function () { for (var i = 0; i < maxDots; i++) { var dot = levelIndicators[i]; var towerLevelIndicator = dot.children[1]; if (i < self.level) { towerLevelIndicator.tint = 0xFFFFFF; } else { towerLevelIndicator.tint = 0xAAAAAA; } } }; self.updateLevelIndicators(); self.updateActivation = function (fromPlayerIndex) { if (self.isActivated && self.playerIndex === fromPlayerIndex) { return false; } if (self.capturingPlayer === -1) { self.capturingPlayer = fromPlayerIndex; } if (self.capturingPlayer === fromPlayerIndex) { if (self.currentShares >= self.activationThreshold) { if (!self.isActivated) { self.activate(self.capturingPlayer); } return false; } self.currentShares++; } else { self.currentShares--; } if (self.currentShares <= 0) { self.currentShares = 0; if (self.isActivated) { self.deactivate(); } self.capturingPlayer = -1; } else if (self.currentShares >= self.activationThreshold) { self.currentShares = self.activationThreshold; if (!self.isActivated) { self.activate(self.capturingPlayer); } } self.updateActivationGaugeVisuals(); return true; }; self.activate = function (newPlayerIndex) { self.isActivated = true; self.playerIndex = newPlayerIndex; gunContainer.visible = true; var baseGraphics = self.children[0]; if (self.playerIndex == 0) { baseGraphics.tint = teamColors[0]; self.turretGraphics.tint = teamColors[0]; self.gunGraphics.tint = teamColors[0]; self.prismGraphics.tint = teamColors[0]; } else if (self.playerIndex == 1) { baseGraphics.tint = teamColors[1]; self.turretGraphics.tint = teamColors[1]; self.gunGraphics.tint = teamColors[1]; self.prismGraphics.tint = teamColors[1]; } if (self.activationGauge) { self.activationGauge.visible = false; } }; self.deactivate = function () { self.isActivated = false; self.playerIndex = -1; gunContainer.visible = false; var baseGraphics = self.children[0]; baseGraphics.tint = 0xAAAAAA; self.gunGraphics.tint = 0xAAAAAA; self.turretGraphics.tint = 0xAAAAAA; self.prismGraphics.tint = 0xAAAAAA; self.updateActivationGaugeVisuals(); }; self.updateActivationGaugeVisuals = function () { if (!self.activationGauge) { return; } self.activationGauge.y = 0; self.activationGauge.removeChildren(); if (self.currentShares > 0 && !self.isActivated) { self.activationGauge.visible = true; var totalDots = 12; var radius = CELL_SIZE / 2.5; var dotsToShow = Math.ceil(self.currentShares / self.activationThreshold * totalDots); var playerColor = 0x0000FF; if (self.capturingPlayer === 0) { playerColor = teamColors[0]; } else if (self.capturingPlayer === 1) { playerColor = teamColors[1]; } for (var i = 0; i < totalDots; i++) { var angle = i / totalDots * Math.PI * 2 - Math.PI / 2; var dot = self.activationGauge.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5, width: 12, height: 12 }); dot.x = Math.cos(angle) * radius; dot.y = Math.sin(angle) * radius; if (i < dotsToShow) { dot.tint = playerColor; dot.alpha = 1.0; } else { dot.tint = 0x666666; dot.alpha = 0.4; } } } else { self.activationGauge.visible = false; } }; self.refreshCellsInRange = function () { for (var i = 0; i < self.cellsInRange.length; i++) { var cell = self.cellsInRange[i]; var towerIndex = cell.towersInRange.indexOf(self); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } self.cellsInRange = []; var rangeRadius = self.getRange() / CELL_SIZE; var centerX = self.gridX + 1; var centerY = self.gridY + 1; var minI = Math.floor(centerX - rangeRadius - 0.5); var maxI = Math.ceil(centerX + rangeRadius + 0.5); var minJ = Math.floor(centerY - rangeRadius - 0.5); var maxJ = Math.ceil(centerY + rangeRadius + 0.5); for (var i = minI; i <= maxI; i++) { for (var j = minJ; j <= maxJ; j++) { var closestX = Math.max(i, Math.min(centerX, i + 1)); var closestY = Math.max(j, Math.min(centerY, j + 1)); var deltaX = closestX - centerX; var deltaY = closestY - centerY; var distanceSquared = deltaX * deltaX + deltaY * deltaY; if (distanceSquared <= rangeRadius * rangeRadius) { var cell = grid.getCell(i, j); if (cell) { self.cellsInRange.push(cell); cell.towersInRange.push(self); } } } } grid.renderDebug(); }; self.getTotalValue = function () { var baseTowerCost = getTowerCost(self.id); var totalInvestment = baseTowerCost; var baseUpgradeCost = baseTowerCost; for (var i = 1; i < self.level; i++) { totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1)); } return totalInvestment; }; self.upgrade = function () { /* if (self.level < self.maxLevel) { var baseUpgradeCost = getTowerCost(self.id); var upgradeCost; if (self.level === self.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1)); } if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; if (self.id === 'rapid') { if (self.level === self.maxLevel) { self.fireRate = Math.max(4, 30 - self.level * 9); self.damage = 5 + self.level * 10; self.bulletSpeed = 7 + self.level * 2.4; } else { self.fireRate = Math.max(15, 30 - self.level * 3); self.damage = 5 + self.level * 3; self.bulletSpeed = 7 + self.level * 0.7; } } else { if (self.level === self.maxLevel) { self.fireRate = Math.max(5, 60 - self.level * 24); self.damage = 10 + self.level * 20; self.bulletSpeed = 5 + self.level * 2.4; } else { self.fireRate = Math.max(20, 60 - self.level * 8); self.damage = 10 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.5; } } self.refreshCellsInRange(); self.updateLevelIndicators(); if (self.level > 1) { var levelDot = levelIndicators[self.level - 1].children[1]; tween(levelDot, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut, onFinish: function onFinish() { tween(levelDot, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); } return true; } else { var notification = game.addChild(new Notification("Not enough gold to upgrade!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } */ return false; }; self.findTarget = function () { if (unitManager && unitManager.units) { var closestUnit = null; var closestUnitDistSq = Infinity; var range = self.getRange(); var rangeSq = range * range; for (var i = 0; i < unitManager.units.length; i++) { var unit = unitManager.units[i]; if (unit.ownerIndex !== self.playerIndex && unit.parent) { var dx = unit.x - self.x; var dy = unit.y - self.y; var distSq = dx * dx + dy * dy; if (distSq <= rangeSq && distSq < closestUnitDistSq) { closestUnitDistSq = distSq; closestUnit = unit; } } } if (closestUnit) { return closestUnit; } } var closestEnemy = null; var closestScore = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.getRange()) { if (enemy.isFlying) { if (enemy.flyingTarget) { var goalX = enemy.flyingTarget.x; var goalY = enemy.flyingTarget.y; var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY)); if (distToGoal < closestScore) { closestScore = distToGoal; closestEnemy = enemy; } } else { if (distance < closestScore) { closestScore = distance; closestEnemy = enemy; } } } else { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell && cell.pathId === pathId) { if (cell.score < closestScore) { closestScore = cell.score; closestEnemy = enemy; } } } } } if (!closestEnemy) { self.targetEnemy = null; } return closestEnemy; }; self.update = function () { if (!self.isActivated) { return; } if (!self.isProducing) { self.targetEnemy = self.findTarget(); if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); gunContainer.rotation = angle; if (LK.ticks - self.lastFired >= self.fireRate) { self.fire(); self.lastFired = LK.ticks; } } } }; self.down = function (x, y, obj) { if (tutorialActive && self.isHeadquarters && self.playerIndex === 0 && tutorialStep < 6) { return; } if (!self.isActivated) { return; } if (self.playerIndex !== 0) { return; } var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); var hasOwnMenu = false; var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self) { rangeCircle = game.children[i]; break; } } for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hasOwnMenu = true; break; } } if (hasOwnMenu) { for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } if (rangeCircle) { game.removeChild(rangeCircle); } selectedTower = null; grid.renderDebug(); return; } for (var i = 0; i < existingMenus.length; i++) { existingMenus[i].destroy(); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = self; var rangeIndicator = new Container(); rangeIndicator.isTowerRange = true; rangeIndicator.tower = self; game.addChild(rangeIndicator); rangeIndicator.x = self.x; rangeIndicator.y = self.y; var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.getRange() * 2; rangeGraphics.alpha = 0.3; var upgradeMenu = new UpgradeMenu(self); game.addChild(upgradeMenu); upgradeMenu.x = 2048 / 2; tween(upgradeMenu, { y: 2732 - 225 }, { duration: 200, easing: tween.backOut, onFinish: function onFinish() { if (tutorialActive && tutorialStep === 6 && self.isHeadquarters && self.playerIndex === 0) { executeTutorialStep(7); } } }); grid.renderDebug(); }; self.isInRange = function (enemy) { if (!enemy) { return false; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance <= self.getRange(); }; self.fire = function () { if (self.targetEnemy) { var potentialDamage = 0; for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) { potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage; } if (self.targetEnemy.health > 0) { //potentialDamage) { var bulletX = self.x + Math.cos(gunContainer.rotation) * 40; var bulletY = self.y + Math.sin(gunContainer.rotation) * 40; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); bullet.type = self.id; /* if (self.id === 'slow') { bullet.sourceTowerLevel = self.level; } switch (self.id) { case 'rapid': bullet.children[0].tint = 0x00AAFF; bullet.children[0].width = 20; bullet.children[0].height = 20; break; case 'sniper': bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; case 'splash': bullet.children[0].tint = 0x33CC00; bullet.children[0].width = 40; bullet.children[0].height = 40; break; case 'slow': bullet.children[0].tint = 0x9900FF; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'poison': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; } */ game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); if (gunContainer._restX === undefined) { gunContainer._restX = 0; } if (gunContainer._restY === undefined) { gunContainer._restY = 0; } if (gunContainer._restScaleX === undefined) { gunContainer._restScaleX = 1; } if (gunContainer._restScaleY === undefined) { gunContainer._restScaleY = 1; } gunContainer.x = gunContainer._restX; gunContainer.y = gunContainer._restY; gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; var recoilDistance = 8; var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance; var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance; tween(gunContainer, { x: gunContainer._restX + recoilX, y: gunContainer._restY + recoilY }, { duration: self.isHeadquarters ? 30 : 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: self.isHeadquarters ? 45 : 60, easing: tween.cubicIn }); } }); } } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 1; } } } self.refreshCellsInRange(); }; self.changeTowerMode = function () { self.isProducing = !self.isProducing; if (self.isProducing) { self.activateGeneratorMode(); } else { self.activateDefenseMode(); } }; self.animatePrism = function () { if (!self.isProducing) { return; } var prismGraphics = self.prismGraphics; tween(prismGraphics, { rotation: prismGraphics.rotation + Math.PI * 2 }, { duration: 8000, easing: tween.linear, onFinish: function onFinish() { self.animatePrism(); } }); var tintColors = [0xFFFFFF, 0xFFE0E0, 0xE0FFFF, 0xE0FFE0, 0xFFFFE0, 0xF0E0FF]; var currentTintIndex = 0; function animateTint() { var nextIndex = (currentTintIndex + 1) % tintColors.length; tween(prismGraphics, { tint: tintColors[nextIndex] }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { currentTintIndex = nextIndex; animateTint(); } }); } }; self.animatePrism(); self.activateGeneratorMode = function () { console.log("Tower activating generator mode at", self.gridX, self.gridY); self.prismGraphics.visible = true; self.gunGraphics.visible = false; self.turretGraphics.visible = false; if (self.playerIndex === 0) { self.prismGraphics.tint = teamColors[0]; self.turretGraphics.tint = teamColors[0]; self.gunGraphics.tint = teamColors[0]; } else if (self.playerIndex === 1) { self.prismGraphics.tint = teamColors[1]; self.turretGraphics.tint = teamColors[1]; self.gunGraphics.tint = teamColors[1]; } else { self.prismGraphics.tint = 0xAAAAAA; self.turretGraphics.tint = 0xAAAAAA; self.gunGraphics.tint = 0xAAAAAA; } self.animatePrism(); var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu && child.tower === self; }); for (var i = 0; i < upgradeMenus.length; i++) { if (upgradeMenus[i].updateButtonText) { upgradeMenus[i].updateButtonText('Defense mode'); } } }; self.activateDefenseMode = function () { self.prismGraphics.visible = false; self.turretGraphics.visible = true; self.gunGraphics.visible = true; if (self.playerIndex === 0) { self.turretGraphics.tint = teamColors[0]; self.gunGraphics.tint = teamColors[0]; self.prismGraphics.tint = teamColors[0]; } else if (self.playerIndex === 1) { self.turretGraphics.tint = teamColors[1]; self.gunGraphics.tint = teamColors[1]; self.prismGraphics.tint = teamColors[1]; } else { self.turretGraphics.tint = 0xAAAAAA; self.gunGraphics.tint = 0xAAAAAA; self.prismGraphics.tint = 0xAAAAAA; } tween.stop(self.prismGraphics, { rotation: true, tint: true }); self.prismGraphics.rotation = 0; var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu && child.tower === self; }); for (var i = 0; i < upgradeMenus.length; i++) { if (upgradeMenus[i].updateButtonText) { upgradeMenus[i].updateButtonText('Generator mode'); } } }; return self; }); var TowerPreview = Container.expand(function () { var self = Container.call(this); var towerRange = 3; var rangeInPixels = towerRange * CELL_SIZE; self.towerType = 'default'; self.hasEnoughGold = true; var rangeIndicator = new Container(); self.addChild(rangeIndicator); var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.alpha = 0.3; var previewGraphics = self.attachAsset('towerpreview', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; self.canPlace = false; self.gridX = 0; self.gridY = 0; self.blockedByEnemy = false; self.update = function () { var previousHasEnoughGold = self.hasEnoughGold; self.hasEnoughGold = gold >= getTowerCost(self.towerType); if (previousHasEnoughGold !== self.hasEnoughGold) { self.updateAppearance(); } }; self.updateAppearance = function () { var tempTower = new Tower(false, undefined, 0); var previewRange = tempTower.getRange(); if (tempTower && tempTower.destroy) { tempTower.destroy(); } rangeGraphics.width = rangeGraphics.height = previewRange * 2; previewGraphics.tint = 0xAAAAAA; if (!self.canPlace || !self.hasEnoughGold) { previewGraphics.tint = 0xFF0000; } }; self.updatePlacementStatus = function () { var validGridPlacement = true; if (self.gridY <= mapGridOffset || self.gridY + 1 >= grid.cells[0].length - mapGridOffset) { validGridPlacement = false; } else { for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(self.gridX + i, self.gridY + j); if (!cell || cell.type !== 0) { validGridPlacement = false; break; } } if (!validGridPlacement) { break; } } } self.blockedByEnemy = false; if (validGridPlacement) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.currentCellY < mapGridOffset) { continue; } if (!enemy.isFlying) { if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) { self.blockedByEnemy = true; break; } if (enemy.currentTarget) { var targetX = enemy.currentTarget.x; var targetY = enemy.currentTarget.y; if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) { self.blockedByEnemy = true; break; } } } } } self.canPlace = validGridPlacement && !self.blockedByEnemy; self.hasEnoughGold = gold >= getTowerCost(self.towerType); self.updateAppearance(); }; self.checkPlacement = function () { self.updatePlacementStatus(); }; self.snapToGrid = function (x, y) { var gridPosX = x - grid.x; var gridPosY = y - grid.y; self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; self.checkPlacement(); }; return self; }); var TrailParticle = Container.expand(function (x, y, tint, scale) { var self = Container.call(this); self.x = x; self.y = y; var particleGraphics = self.attachAsset('unit', { anchorX: 0.5, anchorY: 0.5 }); particleGraphics.tint = tint; self.alpha = 0.75; self.scaleX = (scale || 1) * 0.75; self.scaleY = (scale || 1) * 0.75; tween(self, { alpha: 0, scaleX: self.scaleX * 0.1, scaleY: self.scaleY * 0.1 }, { duration: 300 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); return self; }); var Unit = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'normal'; self.speed = .01; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 1; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.health = self.maxHealth; self.enemyHQ = null; self.isIdle = false; self.idleBaseX = 0; self.idleBaseY = 0; self.idleAnimationActive = false; self.isSelected = false; self.selectionIndicator = null; self.targetCellX = null; self.targetCellY = null; self.movementSpeed = 0.05; self.lastTrailTime = 0; var assetId = 'unit'; if (self.type !== 'normal') { assetId = 'unit_' + self.type; } var unitGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); for (var i = 0; i < towers.length; i++) { if (towers[i].isHeadquarters && towers[i].playerIndex === 1) { self.enemyHQ = towers[i]; break; } } self.setSelected = function (selected) { self.isSelected = selected; if (selected && !self.selectionIndicator) { self.selectionIndicator = new Container(); var indicator = self.selectionIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = 50; indicator.height = 50; indicator.alpha = 0.5; indicator.tint = 0x00FF00; self.addChild(self.selectionIndicator); } else if (!selected && self.selectionIndicator) { self.removeChild(self.selectionIndicator); self.selectionIndicator = null; } }; self.setTarget = function (cellX, cellY) { self.targetCellX = cellX; self.targetCellY = cellY; self.isIdle = false; self.idleAnimationActive = false; tween.stop(self); self.targetTower = null; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.playerIndex !== self.ownerIndex) { var towerGridX, towerGridY; var towerSize = tower.isHeadquarters ? 4 : 2; if (tower.isHeadquarters) { towerGridX = Math.round((tower.x - grid.x) / CELL_SIZE) - towerSize / 2; towerGridY = Math.round((tower.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset; } else { towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE); towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset; } if (cellX >= towerGridX && cellX < towerGridX + towerSize && cellY >= towerGridY && cellY < towerGridY + towerSize) { self.targetTower = tower; break; } } } }; self.update = function () { if (tutorialActive && tutorialBlockUnitUpdates) { return; } if (self.health <= 0) { self.health = 0; } if (self.targetCellX !== null && self.targetCellY !== null) { var targetX = grid.x + self.targetCellX * CELL_SIZE + CELL_SIZE / 2; var targetY = grid.y + (self.targetCellY + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2; if (self.targetTower && self.targetTower.parent) { targetX = self.targetTower.x; targetY = self.targetTower.y; } var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var attackRange = CELL_SIZE; if (self.targetTower && distance <= attackRange) { if (!self.targetTower.isHeadquarters && self.targetTower.updateActivation) { var consumed = self.targetTower.updateActivation(self.ownerIndex); if (consumed) { self.health = 0; } else { var tower = self.targetTower; var towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE); var towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE - mapGridOffset); var availableCells = []; var searchRadius = 1; var maxSearchRadius = 10; while (availableCells.length === 0 && searchRadius <= maxSearchRadius) { for (var dx = -searchRadius; dx <= searchRadius; dx++) { for (var dy = -searchRadius; dy <= searchRadius; dy++) { if (Math.abs(dx) !== searchRadius && Math.abs(dy) !== searchRadius) { continue; } var checkX = towerGridX + dx; var checkY = towerGridY + dy; var cell = grid.getCell(checkX, checkY); if (cell && cell.type === 0) { availableCells.push(cell); } } } searchRadius++; } if (availableCells.length > 0) { var randomCell = availableCells[Math.floor(Math.random() * availableCells.length)]; self.setTarget(randomCell.x, randomCell.y); } else { self.health = 0; } } return; } if (!self.targetTower.parent) { var tower = self.targetTower; var towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE); var towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE - mapGridOffset); var availableCells = []; var searchRadius = 1; var maxSearchRadius = 10; while (availableCells.length === 0 && searchRadius <= maxSearchRadius) { for (var dx = -searchRadius; dx <= searchRadius; dx++) { for (var dy = -searchRadius; dy <= searchRadius; dy++) { if (Math.abs(dx) !== searchRadius && Math.abs(dy) !== searchRadius) { continue; } var checkX = towerGridX + dx; var checkY = towerGridY + dy; var cell = grid.getCell(checkX, checkY); if (cell && cell.type === 0) { availableCells.push(cell); } } } searchRadius++; } if (availableCells.length > 0) { var randomCell = availableCells[Math.floor(Math.random() * availableCells.length)]; self.setTarget(randomCell.x, randomCell.y); } else { self.health = 0; } } else { var isEnemyTower = self.targetTower.playerIndex !== self.ownerIndex; console.log("Unit reached tower. Enemy tower:", isEnemyTower, "Tower health:", self.targetTower.health); self.targetTower.health -= 1; self.health = 0; if (self.targetTower.health <= 0) { var towerIndex = towers.indexOf(self.targetTower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } game.removeChild(self.targetTower); var towerGridX, towerGridY; var towerSize = self.targetTower.isHeadquarters ? 4 : 2; if (self.targetTower.isHeadquarters) { towerGridX = Math.round((self.targetTower.x - grid.x) / CELL_SIZE) - towerSize / 2; towerGridY = Math.round((self.targetTower.y - grid.y) / CELL_SIZE) - towerSize / 2; } else { towerGridX = Math.floor((self.targetTower.x - grid.x) / CELL_SIZE); towerGridY = Math.floor((self.targetTower.y - grid.y) / CELL_SIZE); } for (var i = 0; i < towerSize; i++) { for (var j = 0; j < towerSize; j++) { var cell = grid.getCell(towerGridX + i, towerGridY + j); if (cell) { cell.type = 0; } } } grid.pathFind(); grid.renderDebug(); } } return; } if (distance > 2) { var angle = Math.atan2(dy, dx); if (LK.ticks - (self.lastTrailTime || 0) > 3) { self.lastTrailTime = LK.ticks; var trail = new TrailParticle(self.x, self.y, self.children[0].tint, self.scaleX); enemyLayerMiddle.addChild(trail); } self.x += Math.cos(angle) * self.movementSpeed * CELL_SIZE; self.y += Math.sin(angle) * self.movementSpeed * CELL_SIZE; self.currentCellX = (self.x - grid.x) / CELL_SIZE - 0.5; self.currentCellY = (self.y - grid.y) / CELL_SIZE - 0.5; self.cellX = Math.round(self.currentCellX); self.cellY = Math.round(self.currentCellY); } else { self.targetCellX = null; self.targetCellY = null; self.targetTower = null; self.isIdle = true; self.idleBaseX = targetX; self.idleBaseY = targetY; if (unitManager) { unitManager.startIdleAnimation(self); } } } }; return self; }); var UnitManager = Container.expand(function () { var self = Container.call(this); self.units = []; self.spawnInterval = 180; self.lastSpawnTime = 0; self.maxUnits = 100; self.spawnUnitsAroundTower = function (tower) { var actualGridX = Math.floor((tower.x - grid.x) / CELL_SIZE); var actualGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset; if (tower.isHeadquarters) { actualGridX -= 1; actualGridY -= 1; } var totalUnits = self.units.length; if (totalUnits >= self.maxUnits) { return; } var emptySpawnPositions = []; var unitOccupiedSpawnPositions = []; var towerSize = 2; var maxSearchRadius = 2; function checkCell(checkX, checkY) { var cell = grid.getCell(checkX, checkY); if (!cell || cell.type !== 0) { return; } for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === checkX && enemies[e].cellY === checkY) { return; } } var isOccupiedByUnit = false; for (var u = 0; u < self.units.length; u++) { if (self.units[u].cellX === checkX && self.units[u].cellY === checkY) { isOccupiedByUnit = true; break; } } var pos = { x: checkX, y: checkY }; if (isOccupiedByUnit) { unitOccupiedSpawnPositions.push(pos); } else { emptySpawnPositions.push(pos); } } for (var r = 1; r <= maxSearchRadius; r++) { for (var dx = -r; dx < towerSize + r; dx++) { checkCell(actualGridX + dx, actualGridY - r); checkCell(actualGridX + dx, actualGridY + towerSize - 1 + r); } for (var dy = -r + 1; dy < towerSize + r - 1; dy++) { checkCell(actualGridX - r, actualGridY + dy); checkCell(actualGridX + towerSize - 1 + r, actualGridY + dy); } if (emptySpawnPositions.length > 0) { break; } } var spawnPositions = emptySpawnPositions; if (spawnPositions.length === 0) { spawnPositions = unitOccupiedSpawnPositions; } if (spawnPositions.length > 0) { var spawnPos = spawnPositions[Math.floor(Math.random() * spawnPositions.length)]; var unit = new Unit('normal'); unit.cellX = spawnPos.x; unit.cellY = spawnPos.y; unit.currentCellX = spawnPos.x; unit.currentCellY = spawnPos.y; var finalX = grid.x + spawnPos.x * CELL_SIZE + CELL_SIZE / 2; var finalY = grid.y + (spawnPos.y + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2; unit.x = tower.x; unit.y = tower.y; unit.sourceTower = tower; unit.ownerIndex = tower.playerIndex; unit.movementSpeed *= unit.ownerIndex == 0 ? 1 : enemyMovementSpeedRatio; enemyLayerTop.addChild(unit); self.units.push(unit); unit.alpha = 0; unit.scaleX = 0.1; unit.scaleY = 0.1; if (tower.playerIndex === 0) { unit.children[0].tint = teamColors[0]; } else if (tower.playerIndex === 1) { unit.children[0].tint = teamColors[1]; } else { var playerColors = [0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF]; unit.children[0].tint = playerColors[(tower.playerIndex - 2) % playerColors.length]; } tween(unit, { scaleX: 1, scaleY: 1, alpha: 1, x: finalX, y: finalY }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { unit.isIdle = true; unit.idleBaseX = finalX; unit.idleBaseY = finalY; self.startIdleAnimation(unit); } }); } }; self.spawnUnitsAtRow = function (playerIndex, rowIndex) { var gridWidth = grid.cells.length; for (var col = 0; col < gridWidth; col++) { if (self.units.length >= self.maxUnits) { break; } var cell = grid.getCell(col, rowIndex); if (cell && cell.type === 0) { var unit = new Unit('normal'); unit.cellX = col; unit.cellY = rowIndex; unit.currentCellX = col; unit.currentCellY = rowIndex; var finalX = grid.x + col * CELL_SIZE + CELL_SIZE / 2; var finalY = grid.y + (rowIndex + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2; unit.x = finalX; unit.y = finalY; unit.ownerIndex = playerIndex; unit.movementSpeed *= unit.ownerIndex == 0 ? 1 : enemyMovementSpeedRatio; enemyLayerTop.addChild(unit); self.units.push(unit); unit.alpha = 0; unit.scaleX = 0.1; unit.scaleY = 0.1; if (playerIndex === 0) { unit.children[0].tint = teamColors[0]; } else if (playerIndex === 1) { unit.children[0].tint = teamColors[1]; } else { var playerColors = [0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF]; unit.children[0].tint = playerColors[(playerIndex - 2) % playerColors.length]; } (function (currentUnit, finalUnitX, finalUnitY) { tween(currentUnit, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { currentUnit.isIdle = true; currentUnit.idleBaseX = finalUnitX; currentUnit.idleBaseY = finalUnitY; self.startIdleAnimation(currentUnit); } }); })(unit, finalX, finalY); } } }; self.startIdleAnimation = function (unit) { if (!unit.isIdle || unit.idleAnimationActive) { return; } unit.idleAnimationActive = true; var _animateFloat = function animateFloat() { if (!unit.isIdle || !unit.parent) { unit.idleAnimationActive = false; return; } var radius = 8; var angle = Math.random() * Math.PI * 2; var offsetX = Math.cos(angle) * radius; var offsetY = Math.sin(angle) * radius; tween(unit, { x: unit.idleBaseX + offsetX, y: unit.idleBaseY + offsetY }, { duration: 800 + Math.random() * 400, easing: tween.easeInOut, onFinish: function onFinish() { if (unit.isIdle && unit.parent) { _animateFloat(); } else { unit.idleAnimationActive = false; } } }); }; _animateFloat(); }; self.update = function () { if (LK.ticks - self.lastSpawnTime >= self.spawnInterval) { self.lastSpawnTime = LK.ticks; var generatorTowerCount = 0; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.isProducing && !tutorialBlockUnitProduction) { generatorTowerCount++; self.spawnUnitsAroundTower(tower); } } } for (var i = self.units.length - 1; i >= 0; i--) { var unit = self.units[i]; if (unit.health <= 0) { enemyLayerTop.removeChild(unit); self.units.splice(i, 1); continue; } } }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 500; menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; var towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' Tower', { size: 80, fill: 0xFFFFFF, weight: 800 }); towerTypeText.anchor.set(0, 0); towerTypeText.x = -840; towerTypeText.y = -160; self.addChild(towerTypeText); var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', { size: 70, fill: 0xFFFFFF, weight: 400 }); statsText.anchor.set(0, 0.5); statsText.x = -840; statsText.y = 50; self.addChild(statsText); var buttonsContainer = new Container(); buttonsContainer.x = 500; self.addChild(buttonsContainer); var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; buttonBackground.height = 150; buttonBackground.tint = 0x00AA00; var buttonText = new Text2(self.tower.isProducing ? 'Defense mode' : 'Generator mode', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; sellButtonBackground.height = 150; sellButtonBackground.tint = 0xCC0000; var sellButtonText = new Text2('Move out', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); if (self.tower.isHeadquarters) { sellButton.visible = false; } upgradeButton.y = -85; sellButton.y = 85; var closeButton = new Container(); self.addChild(closeButton); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; upgradeButton.down = function (x, y, obj) { self.tower.changeTowerMode(); if (self.tower.isHeadquarters) { if (tutorialActive && tutorialStep === 7 && self.tower.playerIndex === 0 && !self.tower.isProducing) { executeTutorialStep(8); } } hideUpgradeMenu(self); for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } selectedTower = null; grid.renderDebug(); }; sellButton.down = function (x, y, obj) { if (self.tower.isHeadquarters) { return; } if (unitManager) { for (var i = 0; i < 10; i++) { unitManager.spawnUnitsAroundTower(self.tower); } } self.tower.isProducing = false; self.tower.deactivate(); hideUpgradeMenu(self); for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } if (selectedTower === self.tower) { selectedTower = null; } grid.renderDebug(); }; closeButton.down = function (x, y, obj) { hideUpgradeMenu(self); selectedTower = null; grid.renderDebug(); }; self.update = function () { // This method is now empty as the buttons are static within the menu's lifecycle. }; self.updateButtonText = function (newText) { buttonText.setText(newText); }; return self; }); var Wall = Container.expand(function () { var self = Container.call(this); var wallGraphics = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); wallGraphics.alpha = 0.5; self.startPulseAnimation = function () { var _pulseAnimation2 = function _pulseAnimation() { tween(wallGraphics, { alpha: 1 }, { duration: 4000, easing: tween.easeInOut, onFinish: function onFinish() { tween(wallGraphics, { alpha: 0.5 }, { duration: 4000, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent) { _pulseAnimation2(); } } }); } }); }; _pulseAnimation2(); }; self.startPulseAnimation(); return self; }); var WaveIndicator = Container.expand(function () { var self = Container.call(this); self.gameStarted = false; self.waveMarkers = []; self.waveTypes = []; self.enemyCounts = []; self.indicatorWidth = 0; self.lastBossType = null; var blockWidth = 400; var totalBlocksWidth = blockWidth * totalWaves; var startMarker = new Container(); var startBlock = startMarker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); startBlock.width = blockWidth - 10; startBlock.height = 70 * 2; startBlock.tint = 0x00AA00; var startTextShadow = new Text2("Start Game", { size: 50, fill: 0x000000, weight: 800 }); startTextShadow.anchor.set(0.5, 0.5); startTextShadow.x = 4; startTextShadow.y = 4; startMarker.addChild(startTextShadow); var startText = new Text2("Start Game", { size: 50, fill: 0xFFFFFF, weight: 800 }); startText.anchor.set(0.5, 0.5); startMarker.addChild(startText); startMarker.x = -self.indicatorWidth; self.addChild(startMarker); self.waveMarkers.push(startMarker); startMarker.down = function () { if (!self.gameStarted) { self.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; startBlock.tint = 0x00FF00; startText.setText("Started!"); startTextShadow.setText("Started!"); startTextShadow.x = 4; startTextShadow.y = 4; var notification = game.addChild(new Notification("Game started! Wave 1 incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; for (var i = 0; i < totalWaves; i++) { var marker = new Container(); var block = marker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth - 10; block.height = 70 * 2; var waveType = "normal"; var enemyType = "normal"; var enemyCount = 10; var isBossWave = (i + 1) % 10 === 0; if (i === 0) { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } else if (isBossWave) { enemyType = 'normal'; waveType = "Boss Normal"; block.tint = 0xAAAAAA; enemyCount = 1; } else { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } if (isBossWave && enemyType !== 'swarm') { var bossIndicator = marker.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); bossIndicator.width = 30; bossIndicator.height = 30; bossIndicator.tint = 0xFFD700; bossIndicator.y = -block.height / 2 - 15; waveType = "BOSS"; } self.waveTypes[i] = enemyType; self.enemyCounts[i] = enemyCount; var waveTypeShadow = new Text2(waveType, { size: 56, fill: 0x000000, weight: 800 }); waveTypeShadow.anchor.set(0.5, 0.5); waveTypeShadow.x = 4; waveTypeShadow.y = 4; marker.addChild(waveTypeShadow); var waveTypeText = new Text2(waveType, { size: 56, fill: 0xFFFFFF, weight: 800 }); waveTypeText.anchor.set(0.5, 0.5); waveTypeText.y = 0; marker.addChild(waveTypeText); var waveNumShadow = new Text2((i + 1).toString(), { size: 48, fill: 0x000000, weight: 800 }); waveNumShadow.anchor.set(1.0, 1.0); waveNumShadow.x = blockWidth / 2 - 16 + 5; waveNumShadow.y = block.height / 2 - 12 + 5; marker.addChild(waveNumShadow); var waveNum = new Text2((i + 1).toString(), { size: 48, fill: 0xFFFFFF, weight: 800 }); waveNum.anchor.set(1.0, 1.0); waveNum.x = blockWidth / 2 - 16; waveNum.y = block.height / 2 - 12; marker.addChild(waveNum); marker.x = -self.indicatorWidth + (i + 1) * blockWidth; self.addChild(marker); self.waveMarkers.push(marker); } self.getWaveType = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return "normal"; } var waveType = self.waveTypes[waveNumber - 1]; return waveType; }; self.getEnemyCount = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return 10; } return self.enemyCounts[waveNumber - 1]; }; self.getWaveTypeName = function (waveNumber) { var type = self.getWaveType(waveNumber); var typeName = type.charAt(0).toUpperCase() + type.slice(1); if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') { typeName = "BOSS"; } return typeName; }; self.positionIndicator = new Container(); var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = blockWidth - 10; indicator.height = 16; indicator.tint = 0xffad0e; indicator.y = -65; var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator2.width = blockWidth - 10; indicator2.height = 16; indicator2.tint = 0xffad0e; indicator2.y = 65; var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); leftWall.width = 16; leftWall.height = 146; leftWall.tint = 0xffad0e; leftWall.x = -(blockWidth - 16) / 2; var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); rightWall.width = 16; rightWall.height = 146; rightWall.tint = 0xffad0e; rightWall.x = (blockWidth - 16) / 2; self.addChild(self.positionIndicator); self.update = function () { var progress = waveTimer / nextWaveTime; var moveAmount = (progress + currentWave) * blockWidth; for (var i = 0; i < self.waveMarkers.length; i++) { var marker = self.waveMarkers[i]; marker.x = -moveAmount + i * blockWidth; } self.positionIndicator.x = 0; for (var i = 0; i < totalWaves + 1; i++) { var marker = self.waveMarkers[i]; if (i === 0) { continue; } var block = marker.children[0]; if (i - 1 < currentWave) { block.alpha = .5; } } self.handleWaveProgression = function () { if (!self.gameStarted) { return; } if (currentWave < totalWaves) { waveTimer++; if (waveTimer >= nextWaveTime) { waveTimer = 0; currentWave++; waveInProgress = true; waveSpawned = false; if (currentWave != 1) { var waveType = self.getWaveTypeName(currentWave); var enemyCount = self.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } }; self.handleWaveProgression(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x333333 }); /**** * Game Code ****/ var teamColors = [0x00AA00, 0xAA0000]; var isHidingUpgradeMenu = false; var isSelectingUnits = false; var selectionStartX = 0; var selectionStartY = 0; var selectionCircle = null; var selectedUnits = []; var isCommandMode = false; function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; } }); } var isDebug = false; var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var mapGridOffset = 4; var enemies = []; var towers = []; var bullets = []; var defenses = []; var selectedTower = null; var unitManager = null; var gold = 80; var lives = 20; var score = 0; var currentWave = 0; var totalWaves = 50; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 12000 / 2; var sourceTower = null; var enemiesToSpawn = 10; var enemyMovementSpeedRatio = 0.6; // Map cell values: 0 = empty cell, 1 = player base, 2 = enemy 1 base, 3 = enemy 2 base, // 4 = player tower, 5 = enemy 1 tower, 6 = enemy 2 tower, 8 = empty tower, 9 = wall, // 11 = player base (rotated), 22 = enemy 1 base (rotated), 33 = enemy 2 base (rotated) var MAPS = [ // Level 1: Basic tower defense layout (24x31) [[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 22, 22, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9], // Row 0 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 5 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 6 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 7 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 8 [9, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 9 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 10 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 11 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 12 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 13 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 14 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 15 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 16 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 17 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 18 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 19 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 20 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 21 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 22 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 23 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 24 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 25 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 26 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 27 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 28 [9, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 29 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 30 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 31 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 32 [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], // Row 33 [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9] // Row 34 - Player base area ]]; var goldText = new Text2('Gold: ' + gold, { size: 60, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); var livesText = new Text2('Lives: ' + lives, { size: 60, fill: 0x00FF00, weight: 800 }); livesText.anchor.set(0.5, 0.5); var scoreText = new Text2('Score: ' + score, { size: 60, fill: 0xFF0000, weight: 800 }); scoreText.anchor.set(0.5, 0.5); var topMargin = 50; var centerX = 2048 / 2; var spacing = 400; LK.gui.top.addChild(goldText); LK.gui.top.addChild(livesText); LK.gui.top.addChild(scoreText); livesText.x = 0; livesText.y = topMargin; goldText.x = -spacing; goldText.y = topMargin; scoreText.x = spacing; scoreText.y = topMargin; function updateUI() { goldText.setText('Gold: ' + gold); livesText.setText('Lives: ' + lives); scoreText.setText('Score: ' + score); } function setGold(value) { gold = value; updateUI(); } var backgroundManager = new BackgroundManager(); game.addChild(backgroundManager); var background1 = backgroundManager.addBackground('background_1', { x: 1024, y: 1366, scaleX: 1, scaleY: 1, alpha: 0.8, tint: 0xCCCCCC }); var background2 = backgroundManager.addBackground('background_2', { x: 1024, y: 1366, scaleX: 1, scaleY: 1, alpha: 1 }); var background3 = backgroundManager.addBackground('background_3', { x: 1024, y: 1366, scaleX: 1, scaleY: 1, alpha: 1 }); backgroundManager.createParallaxEffect([background1, background2, background3], 1); var debugLayer = new Container(); var enemyLayerBottom = new Container(); var enemyLayerMiddle = new Container(); var enemyLayerTop = new Container(); var enemyLayer = new Container(); enemyLayer.addChild(enemyLayerBottom); enemyLayer.addChild(enemyLayerMiddle); enemyLayer.addChild(enemyLayerTop); var grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * mapGridOffset; grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); game.addChild(debugLayer); unitManager = new UnitManager(); game.addChild(unitManager); function addHeadquarters() { var playerBaseCells = []; var enemyBaseCells = []; for (var i = 0; i < grid.cells.length; i++) { for (var j = 0; j < grid.cells[i].length; j++) { var cell = grid.cells[i][j]; if (cell.type === 3) { playerBaseCells.push(cell); } else if (cell.type === 2) { enemyBaseCells.push(cell); } } } if (playerBaseCells.length > 0) { var playerOrientation = 0; for (var i = 0; i < playerBaseCells.length; i++) { if (playerBaseCells[i].orientation) { playerOrientation = playerBaseCells[i].orientation; break; } } var playerHQ = new Tower(true, playerOrientation, 0); var playerAnchorX = 10; var playerAnchorY = 31; playerHQ.x = grid.x + (playerAnchorX + 1.5) * CELL_SIZE; playerHQ.y = grid.y + (playerAnchorY + 2) * CELL_SIZE; playerHQ.children[0].tint = teamColors[0]; game.addChild(playerHQ); towers.push(playerHQ); } if (enemyBaseCells.length > 0) { var enemyOrientation = 0; for (var i = 0; i < enemyBaseCells.length; i++) { if (enemyBaseCells[i].orientation) { enemyOrientation = enemyBaseCells[i].orientation; break; } } var enemyHQ = new Tower(true, enemyOrientation, 1); var enemyAnchorX = 10; var enemyAnchorY = 0; enemyHQ.x = grid.x + (enemyAnchorX + 1.5) * CELL_SIZE; if (enemyOrientation !== 0) { enemyHQ.y = grid.y + (enemyAnchorY + 5) * CELL_SIZE; } else { enemyHQ.y = grid.y + (enemyAnchorY + 2) * CELL_SIZE; } enemyHQ.children[0].tint = teamColors[1]; game.addChild(enemyHQ); towers.push(enemyHQ); } } function addEmptyTowers() { var emptyTowerCells = []; for (var i = 0; i < grid.cells.length; i++) { for (var j = 0; j < grid.cells[i].length; j++) { var cell = grid.cells[i][j]; if (cell.type === 4) { emptyTowerCells.push(cell); } } } for (var i = 0; i < emptyTowerCells.length; i++) { var cell = emptyTowerCells[i]; var emptyTower = new Tower(false, 0, -1); emptyTower.x = grid.x + cell.x * CELL_SIZE + CELL_SIZE / 2; emptyTower.y = grid.y + (cell.y + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2; var baseGraphics = emptyTower.children[0]; baseGraphics.width = CELL_SIZE; baseGraphics.height = CELL_SIZE; baseGraphics.tint = 0xAAAAAA; var gunContainer = emptyTower.children[emptyTower.children.length - 1]; gunContainer.visible = false; game.addChild(emptyTower); towers.push(emptyTower); cell.type = 1; cell.isVisuallyEmptyTower = true; } if (emptyTowerCells.length > 0) { grid.pathFind(); grid.renderDebug(); } } addHeadquarters(); addEmptyTowers(); var tutorialActive = false; var tutorialStep = 0; var tutorialText = null; var tutorialOverlayTop = null; var tutorialOverlayBottom = null; var tutorialWaitForTap = false; var tutorialBlockUnitProduction = true; var tutorialBlockUnitUpdates = true; var tutorialBlockTowerUpdates = true; function getPlayerHQ() { for (var i = 0; i < towers.length; i++) { if (towers[i].isHeadquarters && towers[i].playerIndex === 0) { return towers[i]; } } return null; } function findFirstEmptyTower() { var potentialTowers = []; for (var i = 0; i < towers.length; i++) { if (!towers[i].isHeadquarters && towers[i].playerIndex === -1) { potentialTowers.push(towers[i]); } } if (potentialTowers.length === 0) { return null; } var playerHQ = getPlayerHQ(); if (!playerHQ) { return potentialTowers[0]; } var closestTower = null; var closestDistSq = Infinity; for (var i = 0; i < potentialTowers.length; i++) { var tower = potentialTowers[i]; var dx = tower.x - playerHQ.x; var dy = tower.y - playerHQ.y; var distSq = dx * dx + dy * dy; if (distSq < closestDistSq) { closestDistSq = distSq; closestTower = tower; } } return closestTower; } function executeTutorialStep(step) { tutorialStep = step; tutorialOverlayTop.visible = false; tutorialOverlayBottom.visible = false; tutorialText.setText(''); tutorialWaitForTap = false; tutorialBlockUnitProduction = true; tutorialBlockUnitUpdates = true; tutorialBlockTowerUpdates = true; switch (step) { case 1: // Enemy Movement var playerHQ = getPlayerHQ(); if (playerHQ && unitManager) { var towerSize = playerHQ.isHeadquarters ? 4 : 2; var hqGridX = Math.round((playerHQ.x - grid.x) / CELL_SIZE) - towerSize / 2; var hqGridY = Math.round((playerHQ.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset; for (var i = 0; i < unitManager.units.length; i++) { var unit = unitManager.units[i]; if (unit.ownerIndex === 1) { unit.setTarget(hqGridX, hqGridY); } } } tutorialBlockUnitUpdates = false; LK.setTimeout(function () { executeTutorialStep(2); }, 3000); break; case 2: // Enemy Alert var enemyRowY = 700 + (2 + mapGridOffset) * CELL_SIZE; tutorialOverlayTop.visible = true; var enemyHQ = null; for (var i = 0; i < towers.length; i++) { if (towers[i].isHeadquarters && towers[i].playerIndex === 1) { enemyHQ = towers[i]; break; } } if (enemyHQ) { tutorialOverlayTop.height = enemyHQ.y - 150; } else { tutorialOverlayTop.height = 150; } tutorialOverlayBottom.visible = true; tutorialOverlayBottom.y = enemyRowY + CELL_SIZE / 2; tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y; tutorialText.setText("Enemy Wave Incoming!\n(tap to continue)"); tutorialWaitForTap = true; break; case 3: // Unit Selection var playerHQ = getPlayerHQ(); if (playerHQ) { var visibleTopY = playerHQ.y - 300; tutorialOverlayTop.visible = true; tutorialOverlayTop.height = visibleTopY; } tutorialText.setText("Select 10 of your units!"); break; case 4: // Tower Capture console.log("Tuto step 4: Tower capture", tutorialBlockUnitUpdates); var emptyTower = findFirstEmptyTower(); if (emptyTower) { var towerY = emptyTower.y; tutorialOverlayTop.visible = true; tutorialOverlayTop.height = towerY - 150; tutorialOverlayBottom.visible = true; tutorialOverlayBottom.y = towerY + 150; tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y; tutorialText.setText("Capture an empty tower."); /* tutorialBlockUnitUpdates = false; LK.setTimeout(function () { executeTutorialStep(5); }, 1000); */ } break; case 5: // Unit Movement for Capture tutorialBlockUnitUpdates = false; tutorialBlockTowerUpdates = false; LK.setTimeout(function () { executeTutorialStep(6); }, 1000); break; case 6: // HQ Selection console.log("Tuto step 6: HQ Selection", tutorialBlockUnitUpdates); var playerHQ = getPlayerHQ(); if (playerHQ) { var hqY = playerHQ.y; tutorialOverlayTop.visible = true; tutorialOverlayTop.height = hqY - 200; tutorialOverlayBottom.visible = true; tutorialOverlayBottom.y = hqY + 200; tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y; tutorialText.setText("Select your HQ."); } break; case 7: // Switch to Defense var visibleTopY = 2732 - 500; tutorialOverlayTop.visible = true; tutorialOverlayTop.height = visibleTopY; tutorialText.setText("Switch to Defense mode..."); break; case 8: // HQ Attacking tutorialOverlayTop.visible = false; tutorialOverlayBottom.visible = false; tutorialText.setText(''); tutorialBlockTowerUpdates = false; tutorialBlockUnitUpdates = false; LK.setTimeout(function () { executeTutorialStep(9); }, 10000); break; case 9: tutorialOverlayTop.visible = false; tutorialOverlayBottom.visible = false; tutorialBlockTowerUpdates = false; tutorialBlockUnitUpdates = false; tutorialText.setText("Wave cleared!"); LK.setTimeout(function () { executeTutorialStep(10); }, 2000); break; case 10: // "Now take your revenge!" tutorialText.setText("Now take your revenge!"); tutorialBlockUnitUpdates = false; tutorialBlockTowerUpdates = false; LK.setTimeout(function () { // End Tutorial tutorialActive = false; tutorialBlockUnitProduction = false; tutorialBlockUnitUpdates = false; tutorialBlockTowerUpdates = false; storage.hasPlayedTutorial = true; tutorialText.setText(''); var notification = game.addChild(new Notification("Tutorial Complete!")); notification.x = 2048 / 2; notification.y = grid.height - 250; }, 2000); break; } } function playTutorial() { tutorialActive = true; tutorialStep = 0; tutorialOverlayTop = game.addChild(LK.getAsset('notification', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 100 })); tutorialOverlayTop.tint = 0x000000; tutorialOverlayTop.alpha = 0.8; tutorialOverlayTop.visible = false; tutorialOverlayBottom = game.addChild(LK.getAsset('notification', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 100 })); tutorialOverlayBottom.tint = 0x000000; tutorialOverlayBottom.alpha = 0.8; tutorialOverlayBottom.visible = false; tutorialText = new Text2('', { size: 70, fill: 0xFFFFFF, weight: "bold", align: 'center', wordWrap: true, wordWrapWidth: 1200 }); tutorialText.anchor.set(0.5, 0.5); LK.gui.center.addChild(tutorialText); executeTutorialStep(1); } function initLevel0Units() { unitManager.spawnUnitsAtRow(1, 2); var playerHQ = null; for (var i = 0; i < towers.length; i++) { if (towers[i].isHeadquarters && towers[i].playerIndex === 0) { playerHQ = towers[i]; break; } } if (playerHQ) { for (var i = 0; i < 10; i++) { unitManager.spawnUnitsAroundTower(playerHQ); } } } var offset = 0; game.addChild(enemyLayer); var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; var isDragging = false; initLevel0Units(); var hasPlayedTutorial = storage.hasPlayedTutorial || false; if (!hasPlayedTutorial) { LK.setTimeout(playTutorial, 1000); } else { tutorialActive = false; tutorialBlockUnitProduction = false; tutorialBlockUnitUpdates = false; tutorialBlockTowerUpdates = false; } function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); return blocked; } function getTowerCost(towerType) { var cost = 5; cost = 5; return cost; } function getTowerSellValue(totalValue) { return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue; } function placeTower(gridX, gridY, towerType) { var towerCost = getTowerCost(towerType); if (gold >= towerCost) { var tower = new Tower(false, undefined, 0); tower.placeOnGrid(gridX, gridY); game.addChild(tower); towers.push(tower); setGold(gold - towerCost); grid.pathFind(); grid.renderDebug(); return true; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } game.down = function (x, y, obj) { if (tutorialActive && tutorialWaitForTap) { executeTutorialStep(tutorialStep + 1); return; } var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenuVisible) { return; } var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerLeft = tower.x - CELL_SIZE; var towerRight = tower.x + CELL_SIZE; var towerTop = tower.y - CELL_SIZE; var towerBottom = tower.y + CELL_SIZE; if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } if (sourceTower) { var sourceLeft = sourceTower.x - sourceTower.width / 2; var sourceRight = sourceTower.x + sourceTower.width / 2; var sourceTop = sourceTower.y - sourceTower.height / 2; var sourceBottom = sourceTower.y + sourceTower.height / 2; if (x >= sourceLeft && x <= sourceRight && y >= sourceTop && y <= sourceBottom) { clickedOnTower = true; } } if (!clickedOnTower && !isDragging && !isCommandMode) { if (selectionCircle) { game.removeChild(selectionCircle); selectionCircle = null; } isSelectingUnits = true; selectionStartX = x; selectionStartY = y; selectionCircle = new SelectionCircle(); selectionCircle.setPosition(x, y); game.addChild(selectionCircle); } }; game.move = function (x, y, obj) { if (isDragging) { towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); } else if (isSelectingUnits && selectionCircle) { var screenMargin = 50; if (x < -screenMargin || x > 2048 + screenMargin || y < -screenMargin || y > 2732 + screenMargin) { isSelectingUnits = false; if (selectionCircle) { game.removeChild(selectionCircle); selectionCircle = null; } } else { selectionCircle.updateCircle(x, y); } } }; game.up = function (x, y, obj) { if (isSelectingUnits && selectionCircle) { isSelectingUnits = false; for (var i = 0; i < selectedUnits.length; i++) { selectedUnits[i].setSelected(false); } selectedUnits = []; var centerX = selectionCircle.startX; var centerY = selectionCircle.startY; var radius = selectionCircle.currentRadius; if (unitManager && unitManager.units) { for (var i = 0; i < unitManager.units.length; i++) { var unit = unitManager.units[i]; if (unit.ownerIndex === 0) { var dx = unit.x - centerX; var dy = unit.y - centerY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= radius) { unit.setSelected(true); selectedUnits.push(unit); } } } } game.removeChild(selectionCircle); selectionCircle = null; if (selectedUnits.length > 0) { var notification = game.addChild(new Notification(selectedUnits.length + " units selected")); notification.x = 2048 / 2; notification.y = grid.height - 250; isCommandMode = true; if (tutorialActive && tutorialStep === 3 && selectedUnits.length === 10) { executeTutorialStep(4); } } return; } if (isCommandMode && selectedUnits.length > 0) { var gridPosX = x - grid.x; var gridPosY = y - grid.y; var cellX = Math.floor(gridPosX / CELL_SIZE); var cellY = Math.floor(gridPosY / CELL_SIZE) - mapGridOffset; var targetCell = grid.getCell(cellX, cellY); if (!targetCell || targetCell.type === 1 || targetCell.type === 9) { var closestCell = null; var closestDistance = Infinity; var searchRadius = 1; var maxSearchRadius = 10; while (!closestCell && searchRadius <= maxSearchRadius) { for (var dx = -searchRadius; dx <= searchRadius; dx++) { for (var dy = -searchRadius; dy <= searchRadius; dy++) { if (Math.abs(dx) === searchRadius || Math.abs(dy) === searchRadius) { var checkX = cellX + dx; var checkY = cellY + dy; var checkCell = grid.getCell(checkX, checkY); if (checkCell && checkCell.type === 0) { var dist = dx * dx + dy * dy; if (dist < closestDistance) { closestDistance = dist; closestCell = checkCell; } } } } } searchRadius++; } targetCell = closestCell; } var targetHasTower = false; var enemyTower = null; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.playerIndex !== 0) { var towerGridX, towerGridY; var towerSize = tower.isHeadquarters ? 4 : 2; if (tower.isHeadquarters) { towerGridX = Math.round((tower.x - grid.x) / CELL_SIZE) - towerSize / 2; towerGridY = Math.round((tower.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset; } else { towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE); towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset; } if (cellX >= towerGridX && cellX < towerGridX + towerSize && cellY >= towerGridY && cellY < towerGridY + towerSize) { targetHasTower = true; enemyTower = tower; console.log("Empty tower tapped", tutorialActive, tutorialStep === 4, tower.playerIndex === -1, towerGridY, selectedUnits.length === 10); if (tutorialActive && tutorialStep === 4 && tower.playerIndex === -1 && towerGridY === 25 && selectedUnits.length === 10) { tutorialBlockUnitUpdates = false; console.log("Tuto Step 4.5 : Ok units capturing tower", tutorialBlockUnitUpdates); LK.setTimeout(function () { executeTutorialStep(5); }, 2000); } break; } } } if (targetCell && targetCell.type === 0 || targetHasTower) { for (var i = 0; i < selectedUnits.length; i++) { var unit = selectedUnits[i]; if (targetHasTower) { unit.setTarget(cellX, cellY); } else { unit.setTarget(targetCell.x, targetCell.y); } } var message; if (targetHasTower) { if (enemyTower.playerIndex === -1) { message = "Units capturing tower!"; } else { message = "Units attacking enemy tower!"; } } else { message = "Units moving to target"; } if (!(tutorialActive && tutorialStep === 3 && selectedUnits.length < 10)) { var notification = game.addChild(new Notification(message)); notification.x = 2048 / 2; notification.y = grid.height - 250; } for (var i = 0; i < selectedUnits.length; i++) { selectedUnits[i].setSelected(false); } selectedUnits = []; isCommandMode = false; } else { var notification = game.addChild(new Notification("Invalid target location")); notification.x = 2048 / 2; notification.y = grid.height - 250; } return; } var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerLeft = tower.x - tower.width / 2; var towerRight = tower.x + tower.width / 2; var towerTop = tower.y - tower.height / 2; var towerBottom = tower.y + tower.height / 2; if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) { var clickedOnMenu = false; for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; var menuWidth = 2048; var menuHeight = 450; var menuLeft = menu.x - menuWidth / 2; var menuRight = menu.x + menuWidth / 2; var menuTop = menu.y - menuHeight / 2; var menuBottom = menu.y + menuHeight / 2; if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) { clickedOnMenu = true; break; } } if (!clickedOnMenu) { for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; hideUpgradeMenu(menu); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = null; grid.renderDebug(); } } if (isDragging) { isDragging = false; if (towerPreview.canPlace) { if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) { placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType); } else { var notification = game.addChild(new Notification("Tower would block the path!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } else if (towerPreview.blockedByEnemy) { var notification = game.addChild(new Notification("Cannot build: Enemy in the way!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (towerPreview.visible) { var notification = game.addChild(new Notification("Cannot build here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } towerPreview.visible = false; if (isDragging) { var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } } } }; var waveIndicator = new WaveIndicator(); waveIndicator.x = 2048 / 2; waveIndicator.y = 2732 - 80; game.addChild(waveIndicator); var nextWaveButtonContainer = new Container(); var nextWaveButton = new NextWaveButton(); nextWaveButton.x = 2048 - 200; nextWaveButton.y = 2732 - 100 + 20; nextWaveButtonContainer.addChild(nextWaveButton); game.addChild(nextWaveButtonContainer); sourceTower = null; enemiesToSpawn = 10; game.update = function () { if (waveInProgress) { if (!waveSpawned) { waveSpawned = true; var waveType = waveIndicator.getWaveType(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); var isBossWave = currentWave % 10 === 0 && currentWave > 0; if (isBossWave) { enemyCount = 1; var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️")); notification.x = 2048 / 2; notification.y = grid.height - 200; } for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); if (enemy.isFlying) { enemyLayerTop.addChild(enemy); if (enemy.shadow) { enemyLayerMiddle.addChild(enemy.shadow); } } else { enemyLayerBottom.addChild(enemy); } var healthMultiplier = Math.pow(1.12, currentWave); enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier); enemy.health = enemy.maxHealth; var gridWidth = 24; var midPoint = Math.floor(gridWidth / 2); var availableColumns = []; for (var col = midPoint - 3; col < midPoint + 3; col++) { var columnOccupied = false; for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === col && enemies[e].currentCellY < 4) { columnOccupied = true; break; } } if (!columnOccupied) { availableColumns.push(col); } } var spawnX; if (availableColumns.length > 0) { spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)]; } else { spawnX = midPoint - 3 + Math.floor(Math.random() * 6); } var spawnY = -1 - Math.random() * 5; enemy.cellX = spawnX; enemy.cellY = mapGridOffset; enemy.currentCellX = spawnX; enemy.currentCellY = spawnY; enemy.waveNumber = currentWave; enemies.push(enemy); } } var currentWaveEnemiesRemaining = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].waveNumber === currentWave) { currentWaveEnemiesRemaining = true; break; } } if (waveSpawned && !currentWaveEnemiesRemaining) { waveInProgress = false; waveSpawned = false; } } for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; if (enemy.health <= 0) { for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5); var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); var scoreValue = enemy.isBoss ? 100 : 5; score += scoreValue; if (enemy.isBoss) { var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } updateUI(); if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); continue; } if (grid.updateEnemy(enemy)) { if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); lives = Math.max(0, lives - 1); updateUI(); if (lives <= 0) { LK.showGameOver(); } } } for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent) { if (bullets[i].targetEnemy) { var targetEnemy = bullets[i].targetEnemy; var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } bullets.splice(i, 1); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -1,8 +1,9 @@
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
+var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
@@ -3263,8 +3264,9 @@
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;
@@ -3330,9 +3332,17 @@
game.addChild(towerPreview);
towerPreview.visible = false;
var isDragging = false;
initLevel0Units();
-LK.setTimeout(playTutorial, 1000);
+var hasPlayedTutorial = storage.hasPlayedTutorial || false;
+if (!hasPlayedTutorial) {
+ LK.setTimeout(playTutorial, 1000);
+} else {
+ tutorialActive = false;
+ tutorialBlockUnitProduction = false;
+ tutorialBlockUnitUpdates = false;
+ tutorialBlockTowerUpdates = false;
+}
function wouldBlockPath(gridX, gridY) {
var cells = [];
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {