User prompt
Maps rules have been updated to handle another player: ``` // 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) ``` Adapt the code accordingly, escpecialy empty tower type is now 8
Code edit (1 edits merged)
Please save this source code
User prompt
in UnitManager, add a new function to spawn Units at row, with 2 parameters : player index and row index; it will spawn an unit of the player index on each empty cell of the row in sent in parameter; then use it in tempInitEnemies with row 2
Code edit (6 edits merged)
Please save this source code
User prompt
when an unit move, it should have a small trail ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (9 edits merged)
Please save this source code
User prompt
In the same way, When sending for example 15 units to attack a tower that needs 10 units to be destroyed, currently the 5 units in excess are destroyed : they shouldn't, they should stay in a closest random empty cell near their initial target
User prompt
Ok, but currently all excess units go to the same cell, they should go to a random cell around the target
User prompt
When sending for example 15 units to capture a tower that needs 10 units to be captures, currently the 5 units in excess are destroyed : they shouldn't, they should stay in closest empty cell near their initial target
User prompt
When sending for example 15 units to capture a tower that needs 10 units to be captures, currently the 5 units in excess are destroyed : they shouldn't, they should stay in closest empty cell near their current location
Code edit (1 edits merged)
Please save this source code
Code edit (19 edits merged)
Please save this source code
User prompt
``` var turretGraphics = gunContainer.children[0]; var gunGraphics = gunContainer.children[1]; var prismGraphics = gunContainer.children[2]; ``` is used multiple times; factorize properly by using `self.`
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'UnitManager is not defined' in or related to this line: 'unitManager = new UnitManager();' Line Number: 3256
Code edit (1 edits merged)
Please save this source code
Code edit (4 edits merged)
Please save this source code
User prompt
tint prism & turret with teamColors in activateGeneratorMode & activateDefenseMode
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Now, make towers in Defense mode (!isProducing) fire bullets (pratically bullet asset is 15x5px horizontal laser beam) on enmies in range. Handle turret & bullet rotation properly Analayze properly current tower and units logic to make a proper integration without breaking existing features. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
no enemy Units are visible. please check again properly
User prompt
add a new function 'tempInitEnemies()' that will be used to spwan enemy Units during early development phases; it will just spawn 10 enemy 1 Units (Unit class, not the old Enemy class) around the enemy HQ
User prompt
add a new function 'tempInitEnemies()' that will be used to spwan enemies during early development phases; it will just spawn 10 enemy 1 units around the enemy HQ
/**** * Plugins ****/ var tween = LK.import("@upit/tween.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 1: cellType = 3; self.goals.push(cell); break; case 22: cell.orientation = Math.PI; case 2: cellType = 2; self.spawns.push(cell); break; case 3: 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 () { 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 = 60; self.bulletSpeed = 5; self.damage = 1; self.lastFired = 0; self.targetEnemy = null; self.fireRate = 60; self.damage = 1; self.range = 3 * CELL_SIZE; self.bulletSpeed = 5; var baseGraphics; if (self.isHeadquarters) { baseGraphics = self.attachAsset('headquarterBase', { anchorX: 0.5, anchorY: 0.25, 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('barrel', { anchorX: 0, anchorY: 0.5, width: 100, height: 20 }); prismGraphics = gunContainer.attachAsset('prism', { anchorX: 0.5, anchorY: 0.5, width: 100, height: 100 }); if (self.baseOrientation) { turretGraphics.y = -turretGraphics.height * 0.375; gunGraphics.y = -turretGraphics.height * 0.375; prismGraphics.y = -prismGraphics.height * 0.375; } else { turretGraphics.y = turretGraphics.height * 0.375; gunGraphics.y = turretGraphics.height * 0.375; prismGraphics.y = prismGraphics.height * 0.375; } } 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 { switch (self.id) { case 'rapid': towerLevelIndicator.tint = 0x00AAFF; break; case 'sniper': towerLevelIndicator.tint = 0xFF5500; break; case 'splash': towerLevelIndicator.tint = 0x33CC00; break; case 'slow': towerLevelIndicator.tint = 0x9900FF; break; case 'poison': towerLevelIndicator.tint = 0x00FFAA; break; default: towerLevelIndicator.tint = 0xAAAAAA; } } } }; self.updateLevelIndicators(); self.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 (!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 }); grid.renderDebug(); }; self.isInRange = function (enemy) { if (!enemy) { return false; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance <= self.getRange(); }; self.fire = function () { if (self.targetEnemy) { var potentialDamage = 0; for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) { potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage; } if (self.targetEnemy.health > potentialDamage) { var bulletX = self.x + Math.cos(gunContainer.rotation) * 40; var bulletY = self.y + Math.sin(gunContainer.rotation) * 40; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); bullet.type = self.id; if (self.id === 'slow') { bullet.sourceTowerLevel = self.level; } switch (self.id) { case 'rapid': bullet.children[0].tint = 0x00AAFF; bullet.children[0].width = 20; bullet.children[0].height = 20; break; case 'sniper': bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; case 'splash': bullet.children[0].tint = 0x33CC00; bullet.children[0].width = 40; bullet.children[0].height = 40; break; case 'slow': bullet.children[0].tint = 0x9900FF; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'poison': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; } game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); if (gunContainer._restX === undefined) { gunContainer._restX = 0; } if (gunContainer._restY === undefined) { gunContainer._restY = 0; } if (gunContainer._restScaleX === undefined) { gunContainer._restScaleX = 1; } if (gunContainer._restScaleY === undefined) { gunContainer._restScaleY = 1; } gunContainer.x = gunContainer._restX; gunContainer.y = gunContainer._restY; gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; var recoilDistance = 8; var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance; var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance; tween(gunContainer, { x: gunContainer._restX + recoilX, y: gunContainer._restY + recoilY }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: 90, easing: tween.cubicIn }); } }); } } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 1; } } } self.refreshCellsInRange(); }; 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; } if (self.baseOrientation) { self.prismGraphics.y = -self.prismGraphics.height * 0.375; } else { self.prismGraphics.y = self.prismGraphics.height * 0.375; } 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.5; self.scaleX = (scale || 1) * 0.5; self.scaleY = (scale || 1) * 0.5; 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 (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; 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.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) { 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; var isMaxLevel = self.tower.level >= self.tower.maxLevel; var baseUpgradeCost = getTowerCost(self.tower.id); var upgradeCost; if (isMaxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888; var buttonText = new Text2(self.tower.isHeadquarters ? 'Generator mode' : isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; sellButtonBackground.height = 150; sellButtonBackground.tint = 0xCC0000; var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); 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) { if (self.tower.isHeadquarters) { self.tower.changeTowerMode(); return; } if (self.tower.level >= self.tower.maxLevel) { var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (self.tower.upgrade()) { var baseUpgradeCost = getTowerCost(self.tower.id); if (self.tower.level >= self.tower.maxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s'); buttonText.setText('Upgrade: ' + upgradeCost + ' gold'); var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); sellButtonText.setText('Sell: +' + sellValue + ' gold'); if (self.tower.level >= self.tower.maxLevel) { buttonBackground.tint = 0x888888; buttonText.setText('Max Level'); } var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { rangeCircle = game.children[i]; break; } } if (rangeCircle) { var rangeGraphics = rangeCircle.children[0]; rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; } else { var newRangeIndicator = new Container(); newRangeIndicator.isTowerRange = true; newRangeIndicator.tower = self.tower; game.addChildAt(newRangeIndicator, 0); newRangeIndicator.x = self.tower.x; newRangeIndicator.y = self.tower.y; var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; rangeGraphics.alpha = 0.3; } tween(self, { scaleX: 1.05, scaleY: 1.05 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn }); } }); } }; sellButton.down = function (x, y, obj) { var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); setGold(gold + sellValue); var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; var gridX = self.tower.gridX; var gridY = self.tower.gridY; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 0; var towerIndex = cell.towersInRange.indexOf(self.tower); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } if (selectedTower === self.tower) { selectedTower = null; } var towerIndex = towers.indexOf(self.tower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } game.removeChild(self.tower); grid.pathFind(); grid.renderDebug(); self.destroy(); for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } }; closeButton.down = function (x, y, obj) { hideUpgradeMenu(self); selectedTower = null; grid.renderDebug(); }; self.update = function () { if (self.tower.level >= self.tower.maxLevel) { if (buttonText.text !== 'Max Level') { buttonText.setText('Max Level'); buttonBackground.tint = 0x888888; } return; } var baseUpgradeCost = getTowerCost(self.tower.id); var currentUpgradeCost; if (self.tower.level >= self.tower.maxLevel) { currentUpgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } var canAfford = gold >= currentUpgradeCost; buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888; /* var newText = 'Upgrade: ' + currentUpgradeCost + ' gold'; if (buttonText.text !== newText) { buttonText.setText(newText); } */ }; 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; // Map cell values: 0 = empty cell, 1 = player base, 2 = enemy base, 3 = empty tower, 9 = wall, 22 = enemy 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, 3, 0, 0, 0, 0, 0, 3, 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, 3, 0, 0, 0, 0, 0, 3, 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(); function tempInitEnemies() { 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) { for (var i = 0; i < 10; i++) { unitManager.spawnUnitsAroundTower(enemyHQ); } } } tempInitEnemies(); var offset = 0; game.addChild(enemyLayer); var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; var isDragging = false; function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); return blocked; } function getTowerCost(towerType) { var cost = 5; 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) { 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; } 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; 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"; } 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 (unitManager) { unitManager.update(); } if (waveInProgress) { if (!waveSpawned) { waveSpawned = true; var waveType = waveIndicator.getWaveType(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); var isBossWave = currentWave % 10 === 0 && currentWave > 0; if (isBossWave) { enemyCount = 1; var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️")); notification.x = 2048 / 2; notification.y = grid.height - 200; } for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); if (enemy.isFlying) { enemyLayerTop.addChild(enemy); if (enemy.shadow) { enemyLayerMiddle.addChild(enemy.shadow); } } else { enemyLayerBottom.addChild(enemy); } var healthMultiplier = Math.pow(1.12, currentWave); enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier); enemy.health = enemy.maxHealth; var gridWidth = 24; var midPoint = Math.floor(gridWidth / 2); var availableColumns = []; for (var col = midPoint - 3; col < midPoint + 3; col++) { var columnOccupied = false; for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === col && enemies[e].currentCellY < 4) { columnOccupied = true; break; } } if (!columnOccupied) { availableColumns.push(col); } } var spawnX; if (availableColumns.length > 0) { spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)]; } else { spawnX = midPoint - 3 + Math.floor(Math.random() * 6); } var spawnY = -1 - Math.random() * 5; enemy.cellX = spawnX; enemy.cellY = mapGridOffset; enemy.currentCellX = spawnX; enemy.currentCellY = spawnY; enemy.waveNumber = currentWave; enemies.push(enemy); } } var currentWaveEnemiesRemaining = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].waveNumber === currentWave) { currentWaveEnemiesRemaining = true; break; } } if (waveSpawned && !currentWaveEnemiesRemaining) { waveInProgress = false; waveSpawned = false; } } for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; if (enemy.health <= 0) { for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5); var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); var scoreValue = enemy.isBoss ? 100 : 5; score += scoreValue; if (enemy.isBoss) { var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } updateUI(); if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); continue; } if (grid.updateEnemy(enemy)) { if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); lives = Math.max(0, lives - 1); updateUI(); if (lives <= 0) { LK.showGameOver(); } } } for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent) { if (bullets[i].targetEnemy) { var targetEnemy = bullets[i].targetEnemy; var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } bullets.splice(i, 1); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -10,9 +10,8 @@
var self = Container.call(this);
self.backgrounds = [];
self.currentBackground = null;
self.animationActive = false;
- // Method to add a background layer
self.addBackground = function (assetId, config) {
config = config || {};
var background = new Container();
var bgGraphics = background.attachAsset(assetId, {
@@ -31,17 +30,15 @@
self.addChild(background);
self.backgrounds.push(background);
return background;
};
- // Method to remove a background
self.removeBackground = function (background) {
var index = self.backgrounds.indexOf(background);
if (index !== -1) {
self.backgrounds.splice(index, 1);
self.removeChild(background);
}
};
- // Method to animate a background
self.animateBackground = function (background, properties, config) {
if (!background || !background.graphics) {
return;
}
@@ -49,16 +46,14 @@
var duration = config.duration || 1000;
var easing = config.easing || tween.linear;
var loop = config.loop || false;
var _onFinish = config.onFinish;
- // Create the animation
var _animateFunc = function animateFunc() {
tween(background.graphics, properties, {
duration: duration,
easing: easing,
onFinish: function onFinish() {
if (loop && self.animationActive) {
- // Reset properties for looping
if (config.resetOnLoop) {
for (var prop in config.resetOnLoop) {
background.graphics[prop] = config.resetOnLoop[prop];
}
@@ -72,22 +67,19 @@
};
self.animationActive = true;
_animateFunc();
};
- // Method to stop all animations
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);
}
}
};
- // Method to fade between backgrounds
self.fadeToBackground = function (newBackground, duration, onComplete) {
duration = duration || 1000;
if (self.currentBackground && self.currentBackground !== newBackground) {
- // Fade out current background
tween(self.currentBackground.graphics, {
alpha: 0
}, {
duration: duration / 2,
@@ -96,9 +88,8 @@
self.currentBackground.visible = false;
}
});
}
- // Fade in new background
newBackground.visible = true;
newBackground.graphics.alpha = 0;
tween(newBackground.graphics, {
alpha: 1
@@ -112,15 +103,14 @@
}
}
});
};
- // Method for parallax scrolling effect
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; // Each layer moves at different speed
+ 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,
@@ -132,36 +122,31 @@
});
var moveDistance = bg1.graphics.width;
var duration = 80000 / speed;
var _animate = function animate(bg, initialX) {
- // Always animate from current position to current - moveDistance
self.animateBackground(bg, {
x: bg.graphics.x - moveDistance
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
if (self.animationActive) {
- // If the background has fully moved out of view to the left, reset to the right
if (bg.graphics.x <= -moveDistance / 2) {
bg.graphics.x = bg.graphics.x + moveDistance * 2;
}
_animate(bg, initialX);
}
}
});
};
- // Also animate alpha for layers with index > 0
if (index > 0) {
var _animateAlpha = function animateAlpha(bg) {
- // Animate alpha from 0.5 to 1
tween(bg.graphics, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
- // Animate back from 1 to 0.5
tween(bg.graphics, {
alpha: 0.5
}, {
duration: 2000,
@@ -206,72 +191,58 @@
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
bulletGraphics.rotation = angle;
if (distance < self.speed) {
- // Apply damage to target enemy
self.targetEnemy.health -= self.damage;
if (self.targetEnemy.health <= 0) {
self.targetEnemy.health = 0;
}
- // Apply special effects based on bullet type
if (self.type === 'splash') {
- // Create visual splash effect
var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash');
game.addChild(splashEffect);
- // Splash damage to nearby enemies
var splashRadius = CELL_SIZE * 1.5;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self.targetEnemy) {
var splashDx = otherEnemy.x - self.targetEnemy.x;
var splashDy = otherEnemy.y - self.targetEnemy.y;
var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy);
if (splashDistance <= splashRadius) {
- // Apply splash damage (50% of original damage)
otherEnemy.health -= self.damage * 0.5;
if (otherEnemy.health <= 0) {
otherEnemy.health = 0;
}
}
}
}
} else if (self.type === 'slow') {
- // Prevent slow effect on immune enemies
if (!self.targetEnemy.isImmune) {
- // Create visual slow effect
var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow');
game.addChild(slowEffect);
- // Apply slow effect
- // Make slow percentage scale with tower level (default 50%, up to 80% at max level)
var slowPct = 0.5;
if (self.sourceTowerLevel !== undefined) {
- // Scale: 50% at level 1, 60% at 2, 65% at 3, 70% at 4, 75% at 5, 80% at 6
var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8];
var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1));
slowPct = slowLevels[idx];
}
if (!self.targetEnemy.slowed) {
self.targetEnemy.originalSpeed = self.targetEnemy.speed;
- self.targetEnemy.speed *= 1 - slowPct; // Slow by X%
+ self.targetEnemy.speed *= 1 - slowPct;
self.targetEnemy.slowed = true;
- self.targetEnemy.slowDuration = 180; // 3 seconds at 60 FPS
+ self.targetEnemy.slowDuration = 180;
} else {
- self.targetEnemy.slowDuration = 180; // Reset duration
+ self.targetEnemy.slowDuration = 180;
}
}
} else if (self.type === 'poison') {
- // Prevent poison effect on immune enemies
if (!self.targetEnemy.isImmune) {
- // Create visual poison effect
var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison');
game.addChild(poisonEffect);
- // Apply poison effect
self.targetEnemy.poisoned = true;
- self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick
- self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS
+ self.targetEnemy.poisonDamage = self.damage * 0.2;
+ self.targetEnemy.poisonDuration = 300;
}
} else if (self.type === 'sniper') {
- // Create visual critical hit effect for sniper
var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper');
game.addChild(sniperEffect);
}
self.destroy();
@@ -284,9 +255,8 @@
});
var DebugCell = Container.expand(function () {
var self = Container.call(this);
var cellGraphics = null;
- // Only create and attach cellGraphics when in debug mode
if (isDebug) {
cellGraphics = self.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
@@ -320,34 +290,27 @@
self.removeChild(debugArrows.pop());
}
};
self.render = function (data) {
- // Hide cell gradient when not in debug mode
if (!isDebug) {
numberLabel.visible = false;
- numberLabel.alpha = 0; // Also set alpha to 0 to ensure it's completely invisible
- numberLabel.setText(""); // Clear text content when not in debug mode
- // cellGraphics doesn't exist when not in debug mode, so no need to hide it
- // Show wall asset for walls, hide cellGraphics for other types
+ numberLabel.alpha = 0;
+ numberLabel.setText("");
if (data.type === 1 && !data.isVisuallyEmptyTower) {
- // Remove existing wall if any
if (self.wallInstance) {
self.removeChild(self.wallInstance);
self.wallInstance = null;
}
- // Create and add wall asset
self.wallInstance = new Wall();
self.addChild(self.wallInstance);
} else {
- // Remove wall if switching away from wall type
if (self.wallInstance) {
self.removeChild(self.wallInstance);
self.wallInstance = null;
}
}
return;
}
- // Show cells in debug mode
if (cellGraphics) {
cellGraphics.visible = true;
cellGraphics.alpha = 1;
}
@@ -406,9 +369,8 @@
}
}
};
});
-// This update method was incorrectly placed here and should be removed
var EffectIndicator = Container.expand(function (x, y, type) {
var self = Container.call(this);
self.x = x;
self.y = y;
@@ -436,9 +398,8 @@
break;
}
effectGraphics.alpha = 0.7;
self.alpha = 0;
- // Animate the effect
tween(self, {
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
@@ -460,9 +421,8 @@
}
});
return self;
});
-// Base enemy class for common functionality
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'normal';
self.speed = .01;
@@ -477,73 +437,56 @@
self.waveNumber = currentWave;
self.isFlying = false;
self.isImmune = false;
self.isBoss = false;
- // Check if this is a boss wave
- // Check if this is a boss wave
- // Normal enemy uses default values
if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') {
self.isBoss = true;
- // Boss enemies have 20x health and are larger
self.maxHealth *= 20;
- // Slower speed for bosses
self.speed = self.speed * 0.7;
}
self.health = self.maxHealth;
- // Get appropriate asset for this enemy type
var assetId = 'enemy';
if (self.type !== 'normal') {
assetId = 'enemy_' + self.type;
}
var enemyGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
- // Scale up boss enemies
if (self.isBoss) {
enemyGraphics.scaleX = 1.8;
enemyGraphics.scaleY = 1.8;
}
- // Fall back to regular enemy asset if specific type asset not found
- // Apply tint to differentiate enemy types
/*switch (self.type) {
case 'fast':
- enemyGraphics.tint = 0x00AAFF; // Blue for fast enemies
+ enemyGraphics.tint = 0x00AAFF;
break;
case 'immune':
- enemyGraphics.tint = 0xAA0000; // Red for immune enemies
+ enemyGraphics.tint = 0xAA0000;
break;
case 'flying':
- enemyGraphics.tint = 0xFFFF00; // Yellow for flying enemies
+ enemyGraphics.tint = 0xFFFF00;
break;
case 'swarm':
- enemyGraphics.tint = 0xFF00FF; // Pink for swarm enemies
+ enemyGraphics.tint = 0xFF00FF;
break;
- }*/
- // Create shadow for flying enemies
+ }*/
if (self.isFlying) {
- // Create a shadow container that will be added to the shadow layer
self.shadow = new Container();
- // Clone the enemy graphics for the shadow
var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', {
anchorX: 0.5,
anchorY: 0.5
});
- // Apply shadow effect
- shadowGraphics.tint = 0x000000; // Black shadow
- shadowGraphics.alpha = 0.4; // Semi-transparent
- // If this is a boss, scale up the shadow to match
+ shadowGraphics.tint = 0x000000;
+ shadowGraphics.alpha = 0.4;
if (self.isBoss) {
shadowGraphics.scaleX = 1.8;
shadowGraphics.scaleY = 1.8;
}
- // Position shadow slightly offset
- self.shadow.x = 20; // Offset right
- self.shadow.y = 20; // Offset down
- // Ensure shadow has the same rotation as the enemy
+ self.shadow.x = 20;
+ self.shadow.y = 20;
shadowGraphics.rotation = enemyGraphics.rotation;
}
- // Health bar graphics removed - health system remains intact
self.update = function () {
if (self.health <= 0) {
self.health = 0;
}
@@ -559,12 +502,10 @@
if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) {
tween.stop(enemyGraphics, {
rotation: true
});
- // Calculate the shortest angle to rotate
var currentRotation = enemyGraphics.rotation;
var angleDiff = angle - currentRotation;
- // Normalize angle difference to -PI to PI range for shortest path
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
@@ -580,9 +521,8 @@
}
}
}
}
- // Health bar positioning removed
};
return self;
});
var GoldIndicator = Container.expand(function (value, x, y) {
@@ -657,51 +597,41 @@
3: Goal
4: Empty Tower
9: Wall (from map)
*/
- // Use first map from MAPS array
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; // Default to empty
- // Check if coordinates are within map bounds
+ var cellType = 0;
if (j < mapHeight && i < mapWidth) {
- var mapValue = currentMap[j][i]; // Note: map is [row][col], grid is [col][row]
- // Convert map values to cell types
+ var mapValue = currentMap[j][i];
switch (mapValue) {
case 0:
- // empty cell
cellType = 0;
break;
case 1:
- // player base
- cellType = 3; // Goal
+ cellType = 3;
self.goals.push(cell);
break;
case 22:
cell.orientation = Math.PI;
- // fallthrough
case 2:
- // enemy base
- cellType = 2; // Spawn
+ cellType = 2;
self.spawns.push(cell);
break;
case 3:
- // empty tower
cellType = 4;
break;
case 9:
- // wall
cellType = 1;
break;
default:
cellType = 0;
}
} else {
- // Outside map bounds, default to wall
cellType = 1;
}
cell.type = cellType;
cell.x = i;
@@ -788,13 +718,11 @@
}
}
for (var a = 0; a < enemies.length; a++) {
var enemy = enemies[a];
- // Skip enemies that haven't entered the viewable area yet
if (enemy.currentCellY < mapGridOffset) {
continue;
}
- // Skip flying enemies from path check as they can fly over obstacles
if (enemy.isFlying) {
continue;
}
var target = self.getCell(enemy.cellX, enemy.cellY);
@@ -827,22 +755,17 @@
if (cell.type == 3) {
return true;
}
if (enemy.isFlying && enemy.shadow) {
- enemy.shadow.x = enemy.x + 20; // Match enemy x-position + offset
- enemy.shadow.y = enemy.y + 20; // Match enemy y-position + offset
- // Match shadow rotation with enemy rotation
+ 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;
}
}
- // Check if the enemy has reached the entry area (y position is at least 5)
var hasReachedEntryArea = enemy.currentCellY >= mapGridOffset;
- // If enemy hasn't reached the entry area yet, just move down vertically
if (!hasReachedEntryArea) {
- // Move directly downward
enemy.currentCellY += enemy.speed;
- // Rotate enemy graphic to face downward (PI/2 radians = 90 degrees)
var angle = Math.PI / 2;
if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
enemy.children[0].targetRotation = angle;
enemy.children[0].rotation = angle;
@@ -850,19 +773,16 @@
if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) {
tween.stop(enemy.children[0], {
rotation: true
});
- // Calculate the shortest angle to rotate
var currentRotation = enemy.children[0].rotation;
var angleDiff = angle - currentRotation;
- // Normalize angle difference to -PI to PI range for shortest path
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
- // Set target rotation and animate to it
enemy.children[0].targetRotation = angle;
tween(enemy.children[0], {
rotation: currentRotation + angleDiff
}, {
@@ -870,25 +790,19 @@
easing: tween.easeOut
});
}
}
- // Update enemy's position
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
- // If enemy has now reached the entry area, update cell coordinates
if (enemy.currentCellY >= mapGridOffset) {
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
}
return false;
}
- // After reaching entry area, handle flying enemies differently
if (enemy.isFlying) {
- // Flying enemies head straight to the closest goal
if (!enemy.flyingTarget) {
- // Set flying target to the closest goal
enemy.flyingTarget = self.goals[0];
- // Find closest goal if there are multiple
if (self.goals.length > 1) {
var closestDist = Infinity;
for (var i = 0; i < self.goals.length; i++) {
var goal = self.goals[i];
@@ -901,37 +815,31 @@
}
}
}
}
- // Move directly toward the goal
var ox = enemy.flyingTarget.x - enemy.currentCellX;
var oy = enemy.flyingTarget.y - enemy.currentCellY;
var dist = Math.sqrt(ox * ox + oy * oy);
if (dist < enemy.speed) {
- // Reached the goal
return true;
}
var angle = Math.atan2(oy, ox);
- // Rotate enemy graphic to match movement direction
if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
enemy.children[0].targetRotation = angle;
enemy.children[0].rotation = angle;
} else if (enemy.children[0]) {
if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) {
tween.stop(enemy.children[0], {
rotation: true
});
- // Calculate the shortest angle to rotate
var currentRotation = enemy.children[0].rotation;
var angleDiff = angle - currentRotation;
- // Normalize angle difference to -PI to PI range for shortest path
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
- // Set target rotation and animate to it
enemy.children[0].targetRotation = angle;
tween(enemy.children[0], {
rotation: currentRotation + angleDiff
}, {
@@ -939,19 +847,16 @@
easing: tween.easeOut
});
}
}
- // Update the cell position to track where the flying enemy is
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentCellX += Math.cos(angle) * enemy.speed;
enemy.currentCellY += Math.sin(angle) * enemy.speed;
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
- // Update shadow position if this is a flying enemy
return false;
}
- // Handle normal pathfinding enemies
if (!enemy.currentTarget) {
enemy.currentTarget = cell.targets[0];
}
if (enemy.currentTarget) {
@@ -1010,13 +915,12 @@
if (!self.enabled) {
return;
}
if (waveIndicator.gameStarted && currentWave < totalWaves) {
- currentWave++; // Increment to the next wave directly
- waveTimer = 0; // Reset wave timer
+ currentWave++;
+ waveTimer = 0;
waveInProgress = true;
waveSpawned = false;
- // Get the type of the current wave (which is now the next wave)
var waveType = waveIndicator.getWaveTypeName(currentWave);
var enemyCount = waveIndicator.getEnemyCount(currentWave);
var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!"));
notification.x = 2048 / 2;
@@ -1081,18 +985,16 @@
});
var SourceTower = Container.expand(function (towerType) {
var self = Container.call(this);
self.towerType = towerType || 'default';
- // Increase size of base for easier touch
var baseGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
baseGraphics.tint = 0xAAAAAA;
var towerCost = getTowerCost(self.towerType);
- // Add shadow for tower type label
var typeLabelShadow = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), {
size: 50,
fill: 0x000000,
weight: 800
@@ -1100,18 +1002,16 @@
typeLabelShadow.anchor.set(0.5, 0.5);
typeLabelShadow.x = 4;
typeLabelShadow.y = -20 + 4;
self.addChild(typeLabelShadow);
- // Add tower type label
var typeLabel = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
typeLabel.anchor.set(0.5, 0.5);
- typeLabel.y = -20; // Position above center of tower
+ typeLabel.y = -20;
self.addChild(typeLabel);
- // Add cost shadow
var costLabelShadow = new Text2(towerCost, {
size: 50,
fill: 0x000000,
weight: 800
@@ -1119,9 +1019,8 @@
costLabelShadow.anchor.set(0.5, 0.5);
costLabelShadow.x = 4;
costLabelShadow.y = 24 + 12;
self.addChild(costLabelShadow);
- // Add cost label
var costLabel = new Text2(towerCost, {
size: 50,
fill: 0xFFD700,
weight: 800
@@ -1129,11 +1028,9 @@
costLabel.anchor.set(0.5, 0.5);
costLabel.y = 20 + 12;
self.addChild(costLabel);
self.update = function () {
- // Check if player can afford this tower
var canAfford = gold >= getTowerCost(self.towerType);
- // Set opacity based on affordability
self.alpha = canAfford ? 1 : 0.5;
};
return self;
});
@@ -1155,32 +1052,24 @@
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;
- // Standardized method to get the current range of the tower
self.getRange = function () {
- // Always calculate range based on tower type and level
switch (self.id) {
case 'sniper':
- // Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost
if (self.level === self.maxLevel) {
- return 12 * CELL_SIZE; // Significantly increased range for max level
+ return 12 * CELL_SIZE;
}
return (5 + (self.level - 1) * 0.8) * CELL_SIZE;
case 'splash':
- // Splash: base 2, +0.2 per level (max ~4 blocks at max level)
return (2 + (self.level - 1) * 0.2) * CELL_SIZE;
case 'rapid':
- // Rapid: base 2.5, +0.5 per level
return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'slow':
- // Slow: base 3.5, +0.5 per level
return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'poison':
- // Poison: base 3.2, +0.5 per level
return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE;
default:
- // Default: base 3, +0.5 per level
return (3 + (self.level - 1) * 0.5) * CELL_SIZE;
}
};
self.cellsInRange = [];
@@ -1192,9 +1081,8 @@
self.fireRate = 60;
self.damage = 1;
self.range = 3 * CELL_SIZE;
self.bulletSpeed = 5;
- // Create appropriate graphics based on tower type
var baseGraphics;
if (self.isHeadquarters) {
baseGraphics = self.attachAsset('headquarterBase', {
anchorX: 0.5,
@@ -1228,9 +1116,9 @@
default:
baseGraphics.tint = 0xAAAAAA;
}
var levelIndicators = [];
- var maxDots = 0; // TEMP DEBUG !! Indicator disabled self.maxLevel;
+ 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();
@@ -1248,10 +1136,9 @@
towerLevelIndicator.width = dotSize;
towerLevelIndicator.height = dotSize;
towerLevelIndicator.tint = 0xCCCCCC;
dot.x = -CELL_SIZE * 2 + dotSpacing * (i + 1);
- dot.y = CELL_SIZE * 1.3; //0.7
- // Hide level indicators for enemy towers (playerIndex != 0)
+ dot.y = CELL_SIZE * 1.3;
if (self.playerIndex !== 0) {
dot.visible = false;
}
self.addChild(dot);
@@ -1278,9 +1165,9 @@
gunGraphics = gunContainer.attachAsset('barrel', {
anchorX: 0,
anchorY: 0.5,
width: 100,
- height: 10
+ height: 20
});
prismGraphics = gunContainer.attachAsset('prism', {
anchorX: 0.5,
anchorY: 0.5,
@@ -1306,9 +1193,9 @@
gunGraphics = gunContainer.attachAsset('barrel', {
anchorX: 0,
anchorY: 0.5,
width: 50,
- height: 5
+ height: 10
});
prismGraphics = gunContainer.attachAsset('prism', {
anchorX: 0.5,
anchorY: 0.5,
@@ -1321,20 +1208,19 @@
self.prismGraphics = prismGraphics;
prismGraphics.visible = self.isProducing;
turretGraphics.visible = !self.isProducing;
gunGraphics.visible = !self.isProducing;
- // Tint turret graphics based on player
if (self.playerIndex === 0) {
- gunGraphics.tint = teamColors[0]; // Player color
- turretGraphics.tint = teamColors[0]; // Player color
+ gunGraphics.tint = teamColors[0];
+ turretGraphics.tint = teamColors[0];
prismGraphics.tint = teamColors[0];
} else if (self.playerIndex === 1) {
- gunGraphics.tint = teamColors[1]; // Enemy color
- turretGraphics.tint = teamColors[1]; // Player color
+ gunGraphics.tint = teamColors[1];
+ turretGraphics.tint = teamColors[1];
prismGraphics.tint = teamColors[1];
} else {
- gunGraphics.tint = 0xAAAAAA; // Gray for neutral
- turretGraphics.tint = 0xAAAAAA; // Player color
+ gunGraphics.tint = 0xAAAAAA;
+ turretGraphics.tint = 0xAAAAAA;
prismGraphics.tint = 0xAAAAAA;
}
self.updateLevelIndicators = function () {
for (var i = 0; i < maxDots; i++) {
@@ -1433,23 +1319,23 @@
self.updateActivationGaugeVisuals = function () {
if (!self.activationGauge) {
return;
}
- self.activationGauge.y = 0; // Center the gauge on the tower
+ 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; // Default/neutral color
+ var playerColor = 0x0000FF;
if (self.capturingPlayer === 0) {
- playerColor = teamColors[0]; // Player color
+ playerColor = teamColors[0];
} else if (self.capturingPlayer === 1) {
- playerColor = teamColors[1]; // Enemy color
+ playerColor = teamColors[1];
}
for (var i = 0; i < totalDots; i++) {
- var angle = i / totalDots * Math.PI * 2 - Math.PI / 2; // Start from top
+ var angle = i / totalDots * Math.PI * 2 - Math.PI / 2;
var dot = self.activationGauge.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
@@ -1505,47 +1391,41 @@
};
self.getTotalValue = function () {
var baseTowerCost = getTowerCost(self.id);
var totalInvestment = baseTowerCost;
- var baseUpgradeCost = baseTowerCost; // Upgrade cost now scales with base tower cost
+ 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) {
- // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.id);
var upgradeCost;
- // Make last upgrade level extra expensive
if (self.level === self.maxLevel - 1) {
- upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); // Half the cost for final upgrade
+ 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++;
- // No need to update self.range here; getRange() is now the source of truth
- // Apply tower-specific upgrades based on type
if (self.id === 'rapid') {
if (self.level === self.maxLevel) {
- // Extra powerful last upgrade (double the effect)
- self.fireRate = Math.max(4, 30 - self.level * 9); // double the effect
- self.damage = 5 + self.level * 10; // double the effect
- self.bulletSpeed = 7 + self.level * 2.4; // double the effect
+ 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); // Fast tower gets faster with upgrades
+ 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) {
- // Extra powerful last upgrade for all other towers (double the effect)
- self.fireRate = Math.max(5, 60 - self.level * 24); // double the effect
- self.damage = 10 + self.level * 20; // double the effect
- self.bulletSpeed = 5 + self.level * 2.4; // double the effect
+ 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;
@@ -1582,9 +1462,8 @@
}
return false;
};
self.findTarget = function () {
- // Prioritize finding the closest enemy Unit in range
if (unitManager && unitManager.units) {
var closestUnit = null;
var closestUnitDistSq = Infinity;
var range = self.getRange();
@@ -1604,44 +1483,34 @@
if (closestUnit) {
return closestUnit;
}
}
- // Fallback to original TD enemy targeting logic if no units are found/targeted
var closestEnemy = null;
var closestScore = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- // Check if enemy is in range
if (distance <= self.getRange()) {
- // Handle flying enemies differently - they can be targeted regardless of path
if (enemy.isFlying) {
- // For flying enemies, prioritize by distance to the goal
if (enemy.flyingTarget) {
var goalX = enemy.flyingTarget.x;
var goalY = enemy.flyingTarget.y;
var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY));
- // Use distance to goal as score
if (distToGoal < closestScore) {
closestScore = distToGoal;
closestEnemy = enemy;
}
} else {
- // If no flying target yet (shouldn't happen), prioritize by distance to tower
if (distance < closestScore) {
closestScore = distance;
closestEnemy = enemy;
}
}
} else {
- // For ground enemies, use the original path-based targeting
- // Get the cell for this enemy
var cell = grid.getCell(enemy.cellX, enemy.cellY);
if (cell && cell.pathId === pathId) {
- // Use the cell's score (distance to exit) for prioritization
- // Lower score means closer to exit
if (cell.score < closestScore) {
closestScore = cell.score;
closestEnemy = enemy;
}
@@ -1675,9 +1544,8 @@
self.down = function (x, y, obj) {
if (!self.isActivated) {
return;
}
- // Don't show upgrade menu for enemy towers
if (self.playerIndex !== 0) {
return;
}
var existingMenus = game.children.filter(function (child) {
@@ -1760,15 +1628,12 @@
if (self.targetEnemy.health > potentialDamage) {
var bulletX = self.x + Math.cos(gunContainer.rotation) * 40;
var bulletY = self.y + Math.sin(gunContainer.rotation) * 40;
var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed);
- // Set bullet type based on tower type
bullet.type = self.id;
- // For slow tower, pass level for scaling slow effect
if (self.id === 'slow') {
bullet.sourceTowerLevel = self.level;
}
- // Customize bullet appearance based on tower type
switch (self.id) {
case 'rapid':
bullet.children[0].tint = 0x00AAFF;
bullet.children[0].width = 20;
@@ -1797,17 +1662,14 @@
}
game.addChild(bullet);
bullets.push(bullet);
self.targetEnemy.bulletsTargetingThis.push(bullet);
- // --- Fire recoil effect for gunContainer ---
- // Stop any ongoing recoil tweens before starting a new one
tween.stop(gunContainer, {
x: true,
y: true,
scaleX: true,
scaleY: true
});
- // Always use the original resting position for recoil, never accumulate offset
if (gunContainer._restX === undefined) {
gunContainer._restX = 0;
}
if (gunContainer._restY === undefined) {
@@ -1818,26 +1680,22 @@
}
if (gunContainer._restScaleY === undefined) {
gunContainer._restScaleY = 1;
}
- // Reset to resting position before animating (in case of interrupted tweens)
gunContainer.x = gunContainer._restX;
gunContainer.y = gunContainer._restY;
gunContainer.scaleX = gunContainer._restScaleX;
gunContainer.scaleY = gunContainer._restScaleY;
- // Calculate recoil offset (recoil back along the gun's rotation)
var recoilDistance = 8;
var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance;
var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance;
- // Animate recoil back from the resting position
tween(gunContainer, {
x: gunContainer._restX + recoilX,
y: gunContainer._restY + recoilY
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
- // Animate return to original position/scale
tween(gunContainer, {
x: gunContainer._restX,
y: gunContainer._restY
}, {
@@ -1864,55 +1722,45 @@
}
self.refreshCellsInRange();
};
self.changeTowerMode = function () {
- // Toggle the tower's producing mode
self.isProducing = !self.isProducing;
if (self.isProducing) {
- // New mode is "Generator" - call generator mode function
self.activateGeneratorMode();
} else {
- // New mode is "Defense" - call defense mode function
self.activateDefenseMode();
}
};
self.animatePrism = function () {
if (!self.isProducing) {
return;
}
- var prismGraphics = self.prismGraphics; // prism is second child
- // Continuous rotation animation
+ var prismGraphics = self.prismGraphics;
tween(prismGraphics, {
rotation: prismGraphics.rotation + Math.PI * 2
}, {
duration: 8000,
- // 8 seconds for full rotation
easing: tween.linear,
onFinish: function onFinish() {
- // Restart rotation when complete
self.animatePrism();
}
});
- // Tint variation animation - cycle through colors
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,
- // 2 seconds per tint transition
easing: tween.easeInOut,
onFinish: function onFinish() {
currentTintIndex = nextIndex;
- animateTint(); // Continue tint animation
+ animateTint();
}
});
}
- //animateTint(); // Start tint animation
};
- // Start prism animation
self.animatePrism();
self.activateGeneratorMode = function () {
console.log("Tower activating generator mode at", self.gridX, self.gridY);
self.prismGraphics.visible = true;
@@ -1935,11 +1783,9 @@
self.prismGraphics.y = -self.prismGraphics.height * 0.375;
} else {
self.prismGraphics.y = self.prismGraphics.height * 0.375;
}
- // Start prism animation
self.animatePrism();
- // Update button text in any open upgrade menu
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu && child.tower === self;
});
for (var i = 0; i < upgradeMenus.length; i++) {
@@ -1964,16 +1810,13 @@
self.turretGraphics.tint = 0xAAAAAA;
self.gunGraphics.tint = 0xAAAAAA;
self.prismGraphics.tint = 0xAAAAAA;
}
- // Stop prism animations
tween.stop(self.prismGraphics, {
rotation: true,
tint: true
});
- // Reset prism to default state
self.prismGraphics.rotation = 0;
- // Update button text in any open upgrade menu
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu && child.tower === self;
});
for (var i = 0; i < upgradeMenus.length; i++) {
@@ -2009,22 +1852,18 @@
self.blockedByEnemy = false;
self.update = function () {
var previousHasEnoughGold = self.hasEnoughGold;
self.hasEnoughGold = gold >= getTowerCost(self.towerType);
- // Only update appearance if the affordability status has changed
if (previousHasEnoughGold !== self.hasEnoughGold) {
self.updateAppearance();
}
};
self.updateAppearance = function () {
- // Use Tower class to get the source of truth for range
- var tempTower = new Tower(false, undefined, 0); // Pass false for headquarters, undefined orientation, playerIndex 0 for player
+ var tempTower = new Tower(false, undefined, 0);
var previewRange = tempTower.getRange();
- // Clean up tempTower to avoid memory leaks
if (tempTower && tempTower.destroy) {
tempTower.destroy();
}
- // Set range indicator using unified range logic
rangeGraphics.width = rangeGraphics.height = previewRange * 2;
previewGraphics.tint = 0xAAAAAA;
if (!self.canPlace || !self.hasEnoughGold) {
previewGraphics.tint = 0xFF0000;
@@ -2054,9 +1893,8 @@
var enemy = enemies[i];
if (enemy.currentCellY < mapGridOffset) {
continue;
}
- // Only check non-flying enemies, flying enemies can pass over towers
if (!enemy.isFlying) {
if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
@@ -2089,8 +1927,33 @@
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.5;
+ self.scaleX = (scale || 1) * 0.5;
+ self.scaleY = (scale || 1) * 0.5;
+ 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;
@@ -2103,28 +1966,27 @@
self.health = self.maxHealth;
self.bulletsTargetingThis = [];
self.waveNumber = currentWave;
self.health = self.maxHealth;
- self.enemyHQ = null; // Store reference to enemy HQ
- self.isIdle = false; // Track if unit is idle
- self.idleBaseX = 0; // Base position for idle animation
- self.idleBaseY = 0; // Base position for idle animation
- self.idleAnimationActive = false; // Track if idle animation is running
- self.isSelected = false; // Track if unit is selected
- self.selectionIndicator = null; // Visual indicator for selection
- self.targetCellX = null; // Target cell X coordinate
- self.targetCellY = null; // Target cell Y coordinate
- self.movementSpeed = 0.05; // Movement speed for units
- // Get appropriate asset for this unit type
+ 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
});
- // Find enemy HQ automatically
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 1) {
self.enemyHQ = towers[i];
break;
@@ -2132,9 +1994,8 @@
}
self.setSelected = function (selected) {
self.isSelected = selected;
if (selected && !self.selectionIndicator) {
- // Create selection indicator
self.selectionIndicator = new Container();
var indicator = self.selectionIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
@@ -2144,28 +2005,22 @@
indicator.alpha = 0.5;
indicator.tint = 0x00FF00;
self.addChild(self.selectionIndicator);
} else if (!selected && self.selectionIndicator) {
- // Remove selection indicator
self.removeChild(self.selectionIndicator);
self.selectionIndicator = null;
}
};
self.setTarget = function (cellX, cellY) {
self.targetCellX = cellX;
self.targetCellY = cellY;
- // Stop idle animation immediately
self.isIdle = false;
self.idleAnimationActive = false;
- // Stop any ongoing idle animation tweens
tween.stop(self);
- // Check if target cell contains an enemy tower
self.targetTower = null;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
- // Check if this tower is an enemy tower (different playerIndex)
if (tower.playerIndex !== self.ownerIndex) {
- // Check if tower occupies the target cell
var towerGridX, towerGridY;
var towerSize = tower.isHeadquarters ? 4 : 2;
if (tower.isHeadquarters) {
towerGridX = Math.round((tower.x - grid.x) / CELL_SIZE) - towerSize / 2;
@@ -2184,29 +2039,25 @@
self.update = function () {
if (self.health <= 0) {
self.health = 0;
}
- // Handle movement to target
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 targeting a tower, adjust target position to tower's actual position
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);
- // Check if close enough to attack (within 1 cell distance)
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 {
- // Unit not consumed, find random empty cell around target
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 = [];
@@ -2228,19 +2079,16 @@
}
searchRadius++;
}
if (availableCells.length > 0) {
- // Choose a random cell from available options
var randomCell = availableCells[Math.floor(Math.random() * availableCells.length)];
self.setTarget(randomCell.x, randomCell.y);
} else {
- // No empty cell found, just destroy the unit
self.health = 0;
}
}
return;
}
- // If tower has been destroyed by other units, find a new spot.
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);
@@ -2269,24 +2117,18 @@
} else {
self.health = 0;
}
} else {
- // Log unit reaching tower
var isEnemyTower = self.targetTower.playerIndex !== self.ownerIndex;
console.log("Unit reached tower. Enemy tower:", isEnemyTower, "Tower health:", self.targetTower.health);
- // Attack the tower
self.targetTower.health -= 1;
- // Destroy unit after attacking
self.health = 0;
- // Check if tower was destroyed
if (self.targetTower.health <= 0) {
- // Remove tower from game
var towerIndex = towers.indexOf(self.targetTower);
if (towerIndex !== -1) {
towers.splice(towerIndex, 1);
}
game.removeChild(self.targetTower);
- // Clear the cells occupied by the tower
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;
@@ -2302,36 +2144,34 @@
cell.type = 0;
}
}
}
- // Update pathfinding
grid.pathFind();
grid.renderDebug();
}
}
return;
}
if (distance > 2) {
- // Not at target yet
- // Move towards target
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;
- // Update current cell position
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 {
- // Reached target
self.targetCellX = null;
self.targetCellY = null;
self.targetTower = null;
- // Set as idle at new position
self.isIdle = true;
self.idleBaseX = targetX;
self.idleBaseY = targetY;
- // Start idle animation at new position
if (unitManager) {
unitManager.startIdleAnimation(self);
}
}
@@ -2341,44 +2181,36 @@
});
var UnitManager = Container.expand(function () {
var self = Container.call(this);
self.units = [];
- self.spawnInterval = 180; // Spawn units every 3 seconds (60 FPS * 3)
+ self.spawnInterval = 180;
self.lastSpawnTime = 0;
- self.maxUnits = 100; // Global maximum units limit
+ self.maxUnits = 100;
self.spawnUnitsAroundTower = function (tower) {
- // Calculate actual grid coordinates from tower position
var actualGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
var actualGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset;
- // For headquarters, adjust to get the top-left corner of the 2x2 area
if (tower.isHeadquarters) {
- actualGridX -= 1; // Headquarters is centered at 1.5 cells from left edge
- actualGridY -= 1; // Adjust for centering
+ actualGridX -= 1;
+ actualGridY -= 1;
}
- // Count existing units globally
var totalUnits = self.units.length;
- // Don't spawn more if we've reached the global limit
if (totalUnits >= self.maxUnits) {
return;
}
- // Find valid spawn positions around the tower, prioritizing closest empty cells
var emptySpawnPositions = [];
var unitOccupiedSpawnPositions = [];
- var towerSize = 2; // Towers occupy a 2x2 logical grid space
+ var towerSize = 2;
var maxSearchRadius = 2;
function checkCell(checkX, checkY) {
var cell = grid.getCell(checkX, checkY);
- // Cell must be valid and of type 0 (empty floor)
if (!cell || cell.type !== 0) {
return;
}
- // Cell must not be occupied by an enemy
for (var e = 0; e < enemies.length; e++) {
if (enemies[e].cellX === checkX && enemies[e].cellY === checkY) {
- return; // Occupied by enemy, invalid
+ return;
}
}
- // Check if occupied by a friendly unit
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;
@@ -2394,65 +2226,51 @@
} else {
emptySpawnPositions.push(pos);
}
}
- // Search in expanding rings for the closest available spot
for (var r = 1; r <= maxSearchRadius; r++) {
- // Top and bottom rows of the ring
for (var dx = -r; dx < towerSize + r; dx++) {
checkCell(actualGridX + dx, actualGridY - r);
checkCell(actualGridX + dx, actualGridY + towerSize - 1 + r);
}
- // Left and right columns of the ring (excluding corners)
for (var dy = -r + 1; dy < towerSize + r - 1; dy++) {
checkCell(actualGridX - r, actualGridY + dy);
checkCell(actualGridX + towerSize - 1 + r, actualGridY + dy);
}
- // If we found completely empty spots, we prioritize them and stop searching wider
if (emptySpawnPositions.length > 0) {
break;
}
}
var spawnPositions = emptySpawnPositions;
- // If no empty positions were found, use positions occupied by other units as a fallback
if (spawnPositions.length === 0) {
spawnPositions = unitOccupiedSpawnPositions;
}
- // Spawn a unit at a random valid position from the chosen list
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;
- // Calculate final position
var finalX = grid.x + spawnPos.x * CELL_SIZE + CELL_SIZE / 2;
var finalY = grid.y + (spawnPos.y + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2;
- // Start unit at tower's center (prism position)
unit.x = tower.x;
unit.y = tower.y;
- unit.sourceTower = tower; // Track which tower spawned this unit
- unit.ownerIndex = tower.playerIndex; // Track owner player index
+ unit.sourceTower = tower;
+ unit.ownerIndex = tower.playerIndex;
enemyLayerTop.addChild(unit);
self.units.push(unit);
- // Start with unit invisible and small
unit.alpha = 0;
unit.scaleX = 0.1;
unit.scaleY = 0.1;
- // Tint unit based on owner (source tower's playerIndex)
if (tower.playerIndex === 0) {
- // Player units
unit.children[0].tint = teamColors[0];
} else if (tower.playerIndex === 1) {
- // Enemy units
unit.children[0].tint = teamColors[1];
} else {
- // Other players - different colors
var playerColors = [0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF];
unit.children[0].tint = playerColors[(tower.playerIndex - 2) % playerColors.length];
}
- // Animate unit emerging from prism center and moving to spawn position
tween(unit, {
scaleX: 1,
scaleY: 1,
alpha: 1,
@@ -2461,44 +2279,37 @@
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
- // Set unit as idle and save base position
unit.isIdle = true;
unit.idleBaseX = finalX;
unit.idleBaseY = finalY;
- // Start idle animation
self.startIdleAnimation(unit);
}
});
}
};
- // Method to start idle animation for a unit
self.startIdleAnimation = function (unit) {
if (!unit.isIdle || unit.idleAnimationActive) {
return;
}
unit.idleAnimationActive = true;
- // Create a gentle floating animation
var _animateFloat = function animateFloat() {
if (!unit.isIdle || !unit.parent) {
unit.idleAnimationActive = false;
return;
}
- // Random offset within a small radius
- var radius = 8; // Small movement radius
+ 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,
- // 0.8-1.2 seconds (faster animation)
easing: tween.easeInOut,
onFinish: function onFinish() {
- // Continue animation if still idle
if (unit.isIdle && unit.parent) {
_animateFloat();
} else {
unit.idleAnimationActive = false;
@@ -2508,12 +2319,10 @@
};
_animateFloat();
};
self.update = function () {
- // Check if it's time to spawn new units
if (LK.ticks - self.lastSpawnTime >= self.spawnInterval) {
self.lastSpawnTime = LK.ticks;
- // Find all towers in generator mode
var generatorTowerCount = 0;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.isProducing) {
@@ -2521,45 +2330,15 @@
self.spawnUnitsAroundTower(tower);
}
}
}
- // Update all units
for (var i = self.units.length - 1; i >= 0; i--) {
var unit = self.units[i];
- // Remove dead units
if (unit.health <= 0) {
enemyLayerTop.removeChild(unit);
self.units.splice(i, 1);
continue;
}
- /*
- // Check if source tower still exists and is in generator mode
- if (!unit.sourceTower || !unit.sourceTower.parent || !unit.sourceTower.isProducing) {
- // Stop idle animation
- unit.isIdle = false;
- // Stop any ongoing tweens
- tween.stop(unit);
- // Fade out and remove unit
- tween(unit, {
- alpha: 0,
- scaleX: 0.1,
- scaleY: 0.1
- }, {
- duration: 300,
- easing: tween.easeIn,
- onFinish: function (u) {
- return function () {
- var idx = self.units.indexOf(u);
- if (idx !== -1) {
- self.units.splice(idx, 1);
- }
- enemyLayerTop.removeChild(u);
- };
- }(unit)
- });
- // Remove from array immediately to prevent multiple tweens
- self.units.splice(i, 1);
- }*/
}
};
return self;
});
@@ -2604,9 +2383,8 @@
});
buttonBackground.width = 500;
buttonBackground.height = 150;
var isMaxLevel = self.tower.level >= self.tower.maxLevel;
- // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.tower.id);
var upgradeCost;
if (isMaxLevel) {
upgradeCost = 0;
@@ -2640,9 +2418,8 @@
weight: 800
});
sellButtonText.anchor.set(0.5, 0.5);
sellButton.addChild(sellButtonText);
- // Hide sell button for headquarters
if (self.tower.isHeadquarters) {
sellButton.visible = false;
}
upgradeButton.y = -85;
@@ -2676,9 +2453,8 @@
notification.y = grid.height - 50;
return;
}
if (self.tower.upgrade()) {
- // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.tower.id);
if (self.tower.level >= self.tower.maxLevel) {
upgradeCost = 0;
} else if (self.tower.level === self.tower.maxLevel - 1) {
@@ -2788,9 +2564,8 @@
buttonBackground.tint = 0x888888;
}
return;
}
- // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.tower.id);
var currentUpgradeCost;
if (self.tower.level >= self.tower.maxLevel) {
currentUpgradeCost = 0;
@@ -2819,26 +2594,22 @@
anchorX: 0.5,
anchorY: 0.5
});
wallGraphics.alpha = 0.5;
- // Start pulsing animation
self.startPulseAnimation = function () {
var _pulseAnimation2 = function _pulseAnimation() {
- // Pulse from 0.5 to 1
tween(wallGraphics, {
alpha: 1
}, {
duration: 4000,
easing: tween.easeInOut,
onFinish: function onFinish() {
- // Pulse back from 1 to 0.5
tween(wallGraphics, {
alpha: 0.5
}, {
duration: 4000,
easing: tween.easeInOut,
onFinish: function onFinish() {
- // Continue the loop if wall still exists
if (self.parent) {
_pulseAnimation2();
}
}
@@ -2847,9 +2618,8 @@
});
};
_pulseAnimation2();
};
- // Start the animation immediately
self.startPulseAnimation();
return self;
});
var WaveIndicator = Container.expand(function () {
@@ -2858,9 +2628,9 @@
self.waveMarkers = [];
self.waveTypes = [];
self.enemyCounts = [];
self.indicatorWidth = 0;
- self.lastBossType = null; // Track the last boss type to avoid repeating
+ self.lastBossType = null;
var blockWidth = 400;
var totalBlocksWidth = blockWidth * totalWaves;
var startMarker = new Container();
var startBlock = startMarker.attachAsset('notification', {
@@ -2869,9 +2639,8 @@
});
startBlock.width = blockWidth - 10;
startBlock.height = 70 * 2;
startBlock.tint = 0x00AA00;
- // Add shadow for start text
var startTextShadow = new Text2("Start Game", {
size: 50,
fill: 0x000000,
weight: 800
@@ -2897,9 +2666,8 @@
waveTimer = nextWaveTime;
startBlock.tint = 0x00FF00;
startText.setText("Started!");
startTextShadow.setText("Started!");
- // Make sure shadow position remains correct after text change
startTextShadow.x = 4;
startTextShadow.y = 4;
var notification = game.addChild(new Notification("Game started! Wave 1 incoming!"));
notification.x = 2048 / 2;
@@ -2913,14 +2681,12 @@
anchorY: 0.5
});
block.width = blockWidth - 10;
block.height = 70 * 2;
- // --- Begin new unified wave logic ---
var waveType = "normal";
var enemyType = "normal";
var enemyCount = 10;
var isBossWave = (i + 1) % 10 === 0;
- // Ensure all types appear in early waves
if (i === 0) {
block.tint = 0xAAAAAA;
waveType = "Normal";
enemyType = "normal";
@@ -2935,27 +2701,21 @@
waveType = "Normal";
enemyType = "normal";
enemyCount = 10;
}
- // --- End new unified wave logic ---
- // Mark boss waves with a special visual indicator
if (isBossWave && enemyType !== 'swarm') {
- // Add a crown or some indicator to the wave marker for boss waves
var bossIndicator = marker.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
bossIndicator.width = 30;
bossIndicator.height = 30;
- bossIndicator.tint = 0xFFD700; // Gold color
+ bossIndicator.tint = 0xFFD700;
bossIndicator.y = -block.height / 2 - 15;
- // Change the wave type text to indicate boss
waveType = "BOSS";
}
- // Store the wave type and enemy count
self.waveTypes[i] = enemyType;
self.enemyCounts[i] = enemyCount;
- // Add shadow for wave type - 30% smaller than before
var waveTypeShadow = new Text2(waveType, {
size: 56,
fill: 0x000000,
weight: 800
@@ -2963,18 +2723,16 @@
waveTypeShadow.anchor.set(0.5, 0.5);
waveTypeShadow.x = 4;
waveTypeShadow.y = 4;
marker.addChild(waveTypeShadow);
- // Add wave type text - 30% smaller than before
var waveTypeText = new Text2(waveType, {
size: 56,
fill: 0xFFFFFF,
weight: 800
});
waveTypeText.anchor.set(0.5, 0.5);
waveTypeText.y = 0;
marker.addChild(waveTypeText);
- // Add shadow for wave number - 20% larger than before
var waveNumShadow = new Text2((i + 1).toString(), {
size: 48,
fill: 0x000000,
weight: 800
@@ -2982,9 +2740,8 @@
waveNumShadow.anchor.set(1.0, 1.0);
waveNumShadow.x = blockWidth / 2 - 16 + 5;
waveNumShadow.y = block.height / 2 - 12 + 5;
marker.addChild(waveNumShadow);
- // Main wave number text - 20% larger than before
var waveNum = new Text2((i + 1).toString(), {
size: 48,
fill: 0xFFFFFF,
weight: 800
@@ -2996,30 +2753,24 @@
marker.x = -self.indicatorWidth + (i + 1) * blockWidth;
self.addChild(marker);
self.waveMarkers.push(marker);
}
- // Get wave type for a specific wave number
self.getWaveType = function (waveNumber) {
if (waveNumber < 1 || waveNumber > totalWaves) {
return "normal";
}
- // If this is a boss wave (waveNumber % 10 === 0), and the type is the same as lastBossType
- // then we should return a different boss type
var waveType = self.waveTypes[waveNumber - 1];
return waveType;
};
- // Get enemy count for a specific wave number
self.getEnemyCount = function (waveNumber) {
if (waveNumber < 1 || waveNumber > totalWaves) {
return 10;
}
return self.enemyCounts[waveNumber - 1];
};
- // Get display name for a wave type
self.getWaveTypeName = function (waveNumber) {
var type = self.getWaveType(waveNumber);
var typeName = type.charAt(0).toUpperCase() + type.slice(1);
- // Add boss prefix for boss waves (every 10th wave)
if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') {
typeName = "BOSS";
}
return typeName;
@@ -3111,16 +2862,16 @@
/****
* Game Code
****/
-var teamColors = [0x00AA00, 0xAA0000]; // Index 0: player color (green), Index 1: enemy color (red)
+var teamColors = [0x00AA00, 0xAA0000];
var isHidingUpgradeMenu = false;
var isSelectingUnits = false;
var selectionStartX = 0;
var selectionStartY = 0;
var selectionCircle = null;
var selectedUnits = [];
-var isCommandMode = false; // Track if we're in command mode after selecting units
+var isCommandMode = false;
function hideUpgradeMenu(menu) {
if (isHidingUpgradeMenu) {
return;
}
@@ -3139,9 +2890,9 @@
var isDebug = false;
var CELL_SIZE = 76;
var pathId = 1;
var maxScore = 0;
-var mapGridOffset = 4; // Global offset for Y-coordinate conversion
+var mapGridOffset = 4;
var enemies = [];
var towers = [];
var bullets = [];
var defenses = [];
@@ -3156,9 +2907,9 @@
var waveInProgress = false;
var waveSpawned = false;
var nextWaveTime = 12000 / 2;
var sourceTower = null;
-var enemiesToSpawn = 10; // Default number of enemies per wave
+var enemiesToSpawn = 10;
// Map cell values: 0 = empty cell, 1 = player base, 2 = enemy base, 3 = empty tower, 9 = wall, 22 = enemy 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],
@@ -3261,12 +3012,10 @@
function setGold(value) {
gold = value;
updateUI();
}
-// Create background manager
var backgroundManager = new BackgroundManager();
game.addChild(backgroundManager);
-// Add background layers for parallax effect
var background1 = backgroundManager.addBackground('background_1', {
x: 1024,
y: 1366,
scaleX: 1,
@@ -3287,17 +3036,14 @@
scaleX: 1,
scaleY: 1,
alpha: 1
});
-// Create a parallax scrolling effect with the three backgrounds
backgroundManager.createParallaxEffect([background1, background2, background3], 1);
var debugLayer = new Container();
-// Create three separate layers for enemy hierarchy
-var enemyLayerBottom = new Container(); // For normal enemies
-var enemyLayerMiddle = new Container(); // For shadows
-var enemyLayerTop = new Container(); // For flying enemies
-var enemyLayer = new Container(); // Main container to hold all enemy layers
-// Add layers in correct order (bottom first, then middle for shadows, then top)
+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);
@@ -3306,78 +3052,63 @@
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
game.addChild(debugLayer);
-// Create and add unit manager
unitManager = new UnitManager();
game.addChild(unitManager);
-// Add headquarters towers for player and enemy bases
function addHeadquarters() {
- // Find player base cells (type 3) and enemy base cells (type 2)
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) {
- // Player base
playerBaseCells.push(cell);
} else if (cell.type === 2) {
- // Enemy base
enemyBaseCells.push(cell);
}
}
}
- // Add player headquarters (center of player base area)
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); // Pass true for headquarters, playerIndex 0 for player
- // Position at center of the 4x3 player base area (anchor at top-left of base area)
- var playerAnchorX = 10; // Top-left of 4x3 player base area
- var playerAnchorY = 31; // Top-left of 4x3 player base area
- // Position the headquarters asset at the center of the 4x3 area
- playerHQ.x = grid.x + (playerAnchorX + 1.5) * CELL_SIZE; // Center horizontally (2 cells from anchor)
- playerHQ.y = grid.y + (playerAnchorY + 2) * CELL_SIZE; // Center vertically (1.5 cells from anchor)
- // Tint the headquarters graphics
- playerHQ.children[0].tint = teamColors[0]; // Player color
+ 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); // Add to towers array so UnitManager can find it
+ towers.push(playerHQ);
}
- // Add enemy headquarters (center of enemy base area)
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); // Pass true for headquarters, playerIndex 1 for first enemy
- // Position at center of the 4x3 enemy base area (anchor at top-left of base area)
- var enemyAnchorX = 10; // Top-left of 4x3 enemy base area
- var enemyAnchorY = 0; // Top-left of 4x3 enemy base area
- // Position the headquarters asset at the center of the 4x3 area
- enemyHQ.x = grid.x + (enemyAnchorX + 1.5) * CELL_SIZE; // Center horizontally (2 cells from anchor)
+ 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) {
- // For a rotated HQ, place it at the bottom edge of its base area to make it visible and appear to be "entering" the map.
enemyHQ.y = grid.y + (enemyAnchorY + 5) * CELL_SIZE;
} else {
- enemyHQ.y = grid.y + (enemyAnchorY + 2) * CELL_SIZE; // Center vertically (1.5 cells from anchor)
+ enemyHQ.y = grid.y + (enemyAnchorY + 2) * CELL_SIZE;
}
- // Tint the headquarters graphics
- enemyHQ.children[0].tint = teamColors[1]; // Enemy color
+ enemyHQ.children[0].tint = teamColors[1];
game.addChild(enemyHQ);
- towers.push(enemyHQ); // Add to towers array
+ towers.push(enemyHQ);
}
}
function addEmptyTowers() {
- // Find empty tower cells (type 4)
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];
@@ -3385,42 +3116,30 @@
emptyTowerCells.push(cell);
}
}
}
- // Add empty towers to the grid
for (var i = 0; i < emptyTowerCells.length; i++) {
var cell = emptyTowerCells[i];
- var emptyTower = new Tower(false, 0, -1); // Not HQ, neutral
- // Position the tower on its cell
+ 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;
- // Customize appearance for an empty, 1x1 tower
var baseGraphics = emptyTower.children[0];
baseGraphics.width = CELL_SIZE;
baseGraphics.height = CELL_SIZE;
- baseGraphics.tint = 0xAAAAAA; // Neutral gray color
- // Hide the gun
+ baseGraphics.tint = 0xAAAAAA;
var gunContainer = emptyTower.children[emptyTower.children.length - 1];
gunContainer.visible = false;
- // Level indicators are already hidden due to playerIndex being -1
- // Behaviors are disabled via the isActivated flag in the Tower class update/down methods.
- // Add to game world
game.addChild(emptyTower);
towers.push(emptyTower);
- // Mark the cell as an obstacle
cell.type = 1;
- // Flag this cell to prevent the renderer from drawing a 'wall' asset on top
cell.isVisuallyEmptyTower = true;
}
- // If any towers were added, update pathfinding
if (emptyTowerCells.length > 0) {
grid.pathFind();
grid.renderDebug();
}
}
-// Call the function to add headquarters
addHeadquarters();
-// Call the function to add empty towers
addEmptyTowers();
function tempInitEnemies() {
var enemyHQ = null;
for (var i = 0; i < towers.length; i++) {
@@ -3429,9 +3148,8 @@
break;
}
}
if (enemyHQ) {
- //enemyHQ.changeTowerMode();
for (var i = 0; i < 10; i++) {
unitManager.spawnUnitsAroundTower(enemyHQ);
}
}
@@ -3475,9 +3193,9 @@
}
function placeTower(gridX, gridY, towerType) {
var towerCost = getTowerCost(towerType);
if (gold >= towerCost) {
- var tower = new Tower(false, undefined, 0); // Pass false for headquarters, undefined orientation, playerIndex 0 for player
+ var tower = new Tower(false, undefined, 0);
tower.placeOnGrid(gridX, gridY);
game.addChild(tower);
towers.push(tower);
setGold(gold - towerCost);
@@ -3497,9 +3215,8 @@
});
if (upgradeMenuVisible) {
return;
}
- // Check if clicking on a tower or source tower
var clickedOnTower = false;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var towerLeft = tower.x - CELL_SIZE;
@@ -3510,9 +3227,8 @@
clickedOnTower = true;
break;
}
}
- // Check if clicking on source tower
if (sourceTower) {
var sourceLeft = sourceTower.x - sourceTower.width / 2;
var sourceRight = sourceTower.x + sourceTower.width / 2;
var sourceTop = sourceTower.y - sourceTower.height / 2;
@@ -3520,61 +3236,50 @@
if (x >= sourceLeft && x <= sourceRight && y >= sourceTop && y <= sourceBottom) {
clickedOnTower = true;
}
}
- // Start unit selection if not clicking on a tower and not in command mode
if (!clickedOnTower && !isDragging && !isCommandMode) {
- // Clean up any existing selection circle first
if (selectionCircle) {
game.removeChild(selectionCircle);
selectionCircle = null;
}
isSelectingUnits = true;
selectionStartX = x;
selectionStartY = y;
- // Create selection circle
selectionCircle = new SelectionCircle();
selectionCircle.setPosition(x, y);
game.addChild(selectionCircle);
}
};
game.move = function (x, y, obj) {
if (isDragging) {
- // Shift the y position upward by 1.5 tiles to show preview above finger
towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5);
} else if (isSelectingUnits && selectionCircle) {
- // Check if cursor went out of screen bounds (with some margin for edge cases)
var screenMargin = 50;
if (x < -screenMargin || x > 2048 + screenMargin || y < -screenMargin || y > 2732 + screenMargin) {
- // Cursor went out of bounds, cancel selection
isSelectingUnits = false;
if (selectionCircle) {
game.removeChild(selectionCircle);
selectionCircle = null;
}
} else {
- // Update selection circle normally
selectionCircle.updateCircle(x, y);
}
}
};
game.up = function (x, y, obj) {
- // Handle unit selection completion
if (isSelectingUnits && selectionCircle) {
isSelectingUnits = false;
- // Clear previous selection
for (var i = 0; i < selectedUnits.length; i++) {
selectedUnits[i].setSelected(false);
}
selectedUnits = [];
- // Check which units are within the selection circle
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];
- // Only select player units (ownerIndex === 0)
if (unit.ownerIndex === 0) {
var dx = unit.x - centerX;
var dy = unit.y - centerY;
var distance = Math.sqrt(dx * dx + dy * dy);
@@ -3584,41 +3289,32 @@
}
}
}
}
- // Remove selection circle
game.removeChild(selectionCircle);
selectionCircle = null;
- // Show feedback if units were selected
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; // Enter command mode
+ isCommandMode = true;
}
return;
}
- // Handle command casting for selected units
if (isCommandMode && selectedUnits.length > 0) {
- // Convert screen coordinates to grid coordinates
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;
- // Get the cell at the tapped position
var targetCell = grid.getCell(cellX, cellY);
- // If tapped outside grid or on a wall, find closest empty cell
if (!targetCell || targetCell.type === 1 || targetCell.type === 9) {
- // Find closest empty cell
var closestCell = null;
var closestDistance = Infinity;
- // Search in expanding rings from the tapped position
var searchRadius = 1;
var maxSearchRadius = 10;
while (!closestCell && searchRadius <= maxSearchRadius) {
for (var dx = -searchRadius; dx <= searchRadius; dx++) {
for (var dy = -searchRadius; dy <= searchRadius; dy++) {
- // Only check cells on the perimeter of the search square
if (Math.abs(dx) === searchRadius || Math.abs(dy) === searchRadius) {
var checkX = cellX + dx;
var checkY = cellY + dy;
var checkCell = grid.getCell(checkX, checkY);
@@ -3635,14 +3331,12 @@
searchRadius++;
}
targetCell = closestCell;
}
- // Check if target cell contains an enemy tower
var targetHasTower = false;
var enemyTower = null;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
- // Check if this is an enemy tower (playerIndex !== 0 for player units)
if (tower.playerIndex !== 0) {
var towerGridX, towerGridY;
var towerSize = tower.isHeadquarters ? 4 : 2;
if (tower.isHeadquarters) {
@@ -3651,29 +3345,24 @@
} else {
towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset;
}
- // Check if the clicked cell is within the tower's area
if (cellX >= towerGridX && cellX < towerGridX + towerSize && cellY >= towerGridY && cellY < towerGridY + towerSize) {
targetHasTower = true;
enemyTower = tower;
break;
}
}
}
- // If we found a valid target cell or an enemy tower
if (targetCell && targetCell.type === 0 || targetHasTower) {
- // Send all selected units to the target
for (var i = 0; i < selectedUnits.length; i++) {
var unit = selectedUnits[i];
if (targetHasTower) {
- // Use the tower's grid position for targeting
unit.setTarget(cellX, cellY);
} else {
unit.setTarget(targetCell.x, targetCell.y);
}
}
- // Show feedback
var message;
if (targetHasTower) {
if (enemyTower.playerIndex === -1) {
message = "Units capturing tower!";
@@ -3685,16 +3374,14 @@
}
var notification = game.addChild(new Notification(message));
notification.x = 2048 / 2;
notification.y = grid.height - 250;
- // Clear selection and exit command mode
for (var i = 0; i < selectedUnits.length; i++) {
selectedUnits[i].setSelected(false);
}
selectedUnits = [];
isCommandMode = false;
} else {
- // No valid target found
var notification = game.addChild(new Notification("Invalid target location"));
notification.x = 2048 / 2;
notification.y = grid.height - 250;
}
@@ -3786,57 +3473,41 @@
game.addChild(nextWaveButtonContainer);
sourceTower = null;
enemiesToSpawn = 10;
game.update = function () {
- // Update unit manager
if (unitManager) {
unitManager.update();
}
if (waveInProgress) {
if (!waveSpawned) {
waveSpawned = true;
- // Get wave type and enemy count from the wave indicator
var waveType = waveIndicator.getWaveType(currentWave);
var enemyCount = waveIndicator.getEnemyCount(currentWave);
- // Check if this is a boss wave
var isBossWave = currentWave % 10 === 0 && currentWave > 0;
if (isBossWave) {
enemyCount = 1;
var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️"));
notification.x = 2048 / 2;
notification.y = grid.height - 200;
}
- // Spawn the appropriate number of enemies
for (var i = 0; i < enemyCount; i++) {
var enemy = new Enemy(waveType);
- // Add enemy to the appropriate layer based on type
if (enemy.isFlying) {
- // Add flying enemy to the top layer
enemyLayerTop.addChild(enemy);
- // If it's a flying enemy, add its shadow to the middle layer
if (enemy.shadow) {
enemyLayerMiddle.addChild(enemy.shadow);
}
} else {
- // Add normal/ground enemies to the bottom layer
enemyLayerBottom.addChild(enemy);
}
- // Scale difficulty with wave number but don't apply to boss
- // as bosses already have their health multiplier
- // Use exponential scaling for health
- var healthMultiplier = Math.pow(1.12, currentWave); // ~20% increase per wave
+ var healthMultiplier = Math.pow(1.12, currentWave);
enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier);
enemy.health = enemy.maxHealth;
- // Increment speed slightly with wave number
- //enemy.speed = enemy.speed + currentWave * 0.002;
- // All enemy types now spawn in the middle 6 tiles at the top spacing
var gridWidth = 24;
- var midPoint = Math.floor(gridWidth / 2); // 12
- // Find a column that isn't occupied by another enemy that's not yet in view
+ var midPoint = Math.floor(gridWidth / 2);
var availableColumns = [];
for (var col = midPoint - 3; col < midPoint + 3; col++) {
var columnOccupied = false;
- // Check if any enemy is already in this column but not yet in view
for (var e = 0; e < enemies.length; e++) {
if (enemies[e].cellX === col && enemies[e].currentCellY < 4) {
columnOccupied = true;
break;
@@ -3845,20 +3516,17 @@
if (!columnOccupied) {
availableColumns.push(col);
}
}
- // If all columns are occupied, use original random method
var spawnX;
if (availableColumns.length > 0) {
- // Choose a random unoccupied column
spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)];
} else {
- // Fallback to random if all columns are occupied
- spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 9 to 14
+ spawnX = midPoint - 3 + Math.floor(Math.random() * 6);
}
- var spawnY = -1 - Math.random() * 5; // Random distance above the grid for spreading
+ var spawnY = -1 - Math.random() * 5;
enemy.cellX = spawnX;
- enemy.cellY = mapGridOffset; // Position at entry area boundary
+ enemy.cellY = mapGridOffset;
enemy.currentCellX = spawnX;
enemy.currentCellY = spawnY;
enemy.waveNumber = currentWave;
enemies.push(enemy);
@@ -3882,29 +3550,24 @@
for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) {
var bullet = enemy.bulletsTargetingThis[i];
bullet.targetEnemy = null;
}
- // Boss enemies give more gold and score
var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5);
var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y);
game.addChild(goldIndicator);
setGold(gold + goldEarned);
- // Give more score for defeating a boss
var scoreValue = enemy.isBoss ? 100 : 5;
score += scoreValue;
- // Add a notification for boss defeat
if (enemy.isBoss) {
var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
updateUI();
- // Clean up shadow if it's a flying enemy
if (enemy.isFlying && enemy.shadow) {
enemyLayerMiddle.removeChild(enemy.shadow);
enemy.shadow = null;
}
- // Remove enemy from the appropriate layer
if (enemy.isFlying) {
enemyLayerTop.removeChild(enemy);
} else {
enemyLayerBottom.removeChild(enemy);
@@ -3912,14 +3575,12 @@
enemies.splice(a, 1);
continue;
}
if (grid.updateEnemy(enemy)) {
- // Clean up shadow if it's a flying enemy
if (enemy.isFlying && enemy.shadow) {
enemyLayerMiddle.removeChild(enemy.shadow);
enemy.shadow = null;
}
- // Remove enemy from the appropriate layer
if (enemy.isFlying) {
enemyLayerTop.removeChild(enemy);
} else {
enemyLayerBottom.removeChild(enemy);