User prompt
I think enemies spawn to far away from the entry point now, E.g. they take to long to appear on screen after they have been spawned
Code edit (1 edits merged)
Please save this source code
User prompt
You should be able to press the next wave button at anytime to spawn the next wave
User prompt
Add a next wave button that allows me to spawn a wave whenever I want. (Except before the game has been started by pressing the start game button)
User prompt
Waves should be able to spawn concurrently. E.g. it should be possible for multiple waves to be active at the same time. Update the game to support this. As an example as soon as we hit wave 2 in the wave indicator it should spawn regardless of the state of previous waves
User prompt
When wave 2 is ready, the wave does not seem to spawn as wave 1 is still active. Please fix this
Code edit (1 edits merged)
Please save this source code
User prompt
Update enemy spawn code such that next wave spawn time is only based on spawn timeout. While you do this refactor the code such that multipe waves can be spawned in short order such that enemies from each multiple waves are active at the same time.
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'spawnWaveEnemies(currentWave);' Line Number: 1391
User prompt
You should always be able to spawn a new wave by pressing the next wave button. Regardless of what state the game is in (Except in the case where we have not started the game yet) Doing so, should make the next wave spawn instantly. Similar to how a wave is spawned now. Refactor the spawn code to support this and implement this feature. The wave should exist along with the current wave. E.g. enemies from multipe waves can be active at the same time. It might make sense to remove the code that automatically spawn the next wave when all enemies are dead as this conflicts a bit with this setup
User prompt
You should always be able to spawn a new wave by pressing the next wave button. Regardless of what state the game is in (Except in the case where we have not started the game yet) Doing so, should make the next wave spawn instantly. Similar to how a wave is spawned now. Refactor the spawn code to support this and implement this feature.
User prompt
Pressing next wave 2-3 times in a row does not seem to instantly spawn 2-3 waves. It should.
User prompt
I can't seem to press "next wave" while a wave is currently spawning. I should be able to. E.g. while a wave is currently active I should be able to spawn the next wave and thereby significantly speed up the game.
User prompt
You should always be able to spawn a new wave by pressing the next wave button. Regardless of what state the game is in (Except in the case where we have not started the game yet)
Code edit (1 edits merged)
Please save this source code
User prompt
Add a button that allows me to spawn the next level right away.
Code edit (4 edits merged)
Please save this source code
User prompt
Update all places where gold is modified to use a single "setGold" method. This is to ensure we never modify gold without updating the UI. Move the updateUI call into that setGold method
Code edit (1 edits merged)
Please save this source code
User prompt
Make enemies appear at a slower rate on the screen by increasing how far away from the entry point they spawn-.
Code edit (1 edits merged)
Please save this source code
User prompt
When spawning a wave, all enemies for that wave should spawn at the same time. However place the enemies randomly outside the screen above play area that they dont arrive on the playing field at the same time.
User prompt
Increase the amount of time it takes for waves of enemies to spawn by 10x. E.g. the speed of the animation at the level indicator at the bottom of the screen.
User prompt
Each wave should have 20 enemies
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Enemy class // Bullet class var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) { var self = Container.call(this); // Bullet properties self.targetEnemy = targetEnemy; self.damage = damage || 10; self.speed = speed || 5; self.x = startX; self.y = startY; // Visual representation of bullet var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); // Update method - called every frame self.update = function () { // If target is gone, destroy this bullet if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } // Move toward target var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If we hit the target if (distance < self.speed) { // Deal damage to enemy self.targetEnemy.health -= self.damage; // Update health bar visual if (self.targetEnemy.health <= 0) { // Enemy is defeated self.targetEnemy.health = 0; } else { // Update health bar width based on remaining health percentage self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; } // Remove this bullet self.destroy(); } else { // Move toward target var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); // DebugCell class var DebugCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = self.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5 }); cellGraphics.tint = Math.random() * 0xffffff; // Add a debug arrow to the debug cells var debugArrows = []; // Add a number label to the debug cells var numberLabel = new Text2('0', { size: 30, fill: 0xFFFFFF, weight: 800 }); numberLabel.anchor.set(.5, .5); self.addChild(numberLabel); self.update = function () {}; self.down = function () { return; if (self.cell.type == 0 || self.cell.type == 1) { self.cell.type = self.cell.type == 1 ? 0 : 1; if (grid.pathFind()) { self.cell.type = self.cell.type == 1 ? 0 : 1; grid.pathFind(); var notification = game.addChild(new Notification("Path is blocked!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } grid.renderDebug(); } }; self.removeArrows = function () { while (debugArrows.length) { self.removeChild(debugArrows.pop()); } }; self.render = function (data) { switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); cellGraphics.tint = 0x880000; return; } numberLabel.visible = true; var tint = Math.floor(data.score / maxScore * 0x88); // Check if we have a selected tower and this cell is in its range var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; cellGraphics.tint = 0x0088ff; // Highlight cells in range of selected tower } else { cellGraphics.tint = 0x88 - tint << 8 | tint; } while (debugArrows.length > data.targets.length) { self.removeChild(debugArrows.pop()); } for (var a = 0; a < data.targets.length; a++) { var destination = data.targets[a]; var ox = destination.x - data.x; var oy = destination.y - data.y; var angle = Math.atan2(oy, ox); if (!debugArrows[a]) { debugArrows[a] = LK.getAsset('arrow', { anchorX: -.5, anchorY: 0.5 }); debugArrows[a].alpha = .5; self.addChildAt(debugArrows[a], 1); // debugArrows = } debugArrows[a].rotation = angle; // debugArrow.rotation = angle; } break; } case 1: { self.removeArrows(); cellGraphics.tint = 0xaaaaaa; numberLabel.visible = false; break; } case 3: { self.removeArrows(); cellGraphics.tint = 0x008800; numberLabel.visible = false; break; } } numberLabel.setText(Math.floor(data.score / 1000) / 10); }; }); var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.speed = .05; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 100; // Starting max health self.health = self.maxHealth; // Current health self.bulletsTargetingThis = []; // Track bullets targeting this enemy // Add health bar to the enemy var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBarBG = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; // Position the health bar above the enemy healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; // Update method - called every frame self.update = function () { // Check if health has depleted if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; } // Calculate direction of travel if we have a current target if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; if (ox !== 0 || oy !== 0) { // Calculate the angle of movement var angle = Math.atan2(oy, ox); // Smoothly rotate the enemy graphics using tween // First, store the current rotation if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { // Only update if the angle has changed significantly if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) { // Stop any existing rotation tweens tween.stop(enemyGraphics, { rotation: true }); // Set the new target rotation enemyGraphics.targetRotation = angle; // Tween to the new rotation tween(enemyGraphics, { rotation: angle }, { duration: 250, easing: tween.easeOut }); } } } } // Update health bar position relative to the enemy healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; }; return self; }); //Class for the gr0x412e2eles all logic related to grid movement and interaction 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: [] // Cache for towers in range }; } } /* Cell Types 0: Transparent floor 1: Wall 2: Spawn 3: Goal */ for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { // Set the type to 1 (wall) if the cell is on the edge of the grid, otherwise set it to 0 (ground) var cell = self.cells[i][j]; var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0; if (i > 11 - 3 && i <= 11 + 3) { if (j === 0) { cellType = 2; self.spawns.push(cell); } else if (j <= 4) { cellType = 0; } else if (j === gridHeight - 1) { cellType = 3; self.goals.push(cell); } else if (j >= gridHeight - 4) { cellType = 0; } } cell.type = cellType; cell.x = i; cell.y = j; /*if (cell.type == 0) { if (Math.random() > .5) { cell.type = 1; } }*/ //Cache neighbours for speed cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1]; cell.up = self.cells[i - 1] && self.cells[i - 1][j]; cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1]; cell.left = self.cells[i][j - 1]; cell.right = self.cells[i][j + 1]; cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1]; cell.down = self.cells[i + 1] && self.cells[i + 1][j]; cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1]; cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left]; cell.targets = []; if (j > 3 && j <= gridHeight - 4) { var debugCell = new DebugCell(); self.addChild(debugCell); debugCell.cell = cell; debugCell.x = i * CELL_SIZE; debugCell.y = j * CELL_SIZE; cell.debugCell = debugCell; } } } self.getCell = function (x, y) { return self.cells[x] && self.cells[x][y]; }; self.pathFind = function () { var before = new Date().getTime(); var toProcess = self.goals.concat([]); maxScore = 0; pathId += 1; for (var a = 0; a < toProcess.length; a++) { toProcess[a].pathId = pathId; } function processNode(node, targetValue, targetNode) { if (node && node.type != 1) { if (node.pathId < pathId || targetValue < node.score) { node.targets = [targetNode]; } else if (node.pathId == pathId && targetValue == node.score) { node.targets.push(targetNode); } if (node.pathId < pathId || targetValue < node.score) { node.score = targetValue; if (node.pathId != pathId) { toProcess.push(node); } node.pathId = pathId; if (targetValue > maxScore) { maxScore = targetValue; } } } } while (toProcess.length) { var nodes = toProcess; toProcess = []; for (var a = 0; a < nodes.length; a++) { var node = nodes[a]; var targetScore = node.score + 14142; // Check for diagonal movement and ensure both adjacent cells are not walls 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]; 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"); return true; } } } else if (!target || target.pathId != pathId) { console.warn("Enemy blocked"); 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.currentTarget) { enemy.currentTarget = cell.targets[0]; } if (enemy.currentTarget) { //Allows enemies to change direction right away. 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; }; }); // Notification class 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; // 2 seconds at 60FPS self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); // SourceTower class var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; // Create the tower base var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); // Customize visuals based on tower type switch (self.towerType) { case 'rapid': baseGraphics.tint = 0x00AAFF; // Blue break; case 'sniper': baseGraphics.tint = 0xFF5500; // Orange break; case 'splash': baseGraphics.tint = 0x33CC00; // Bright Green break; case 'slow': baseGraphics.tint = 0x9900FF; // Deep Purple break; case 'poison': baseGraphics.tint = 0x00FFAA; // Aqua break; default: baseGraphics.tint = 0xAAAAAA; // Gray } // Calculate cost based on tower type var towerCost = getTowerCost(self.towerType); // Add a label to indicate tower cost var costLabel = new Text2('Cost: ' + towerCost, { size: 40, fill: 0xFFFFFF, weight: 800 }); costLabel.anchor.set(0.5, 0); costLabel.y = baseGraphics.height / 2 + 5; self.addChild(costLabel); return self; }); // Tower class that can be placed on the grid var Tower = Container.expand(function (id) { var self = Container.call(this); // Tower attributes self.id = id || 'default'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.range = 3 * CELL_SIZE; // initial range (3 grid cells) self.cellsInRange = []; // Cache for cells in range self.fireRate = 60; // frames between shots self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; // Set tower attributes based on type switch (self.id) { case 'rapid': self.fireRate = 30; // Faster firing rate self.damage = 5; // Lower damage self.range = 2.5 * CELL_SIZE; // Slightly shorter range self.bulletSpeed = 7; // Faster bullets break; case 'sniper': self.fireRate = 90; // Slower firing rate self.damage = 25; // Higher damage self.range = 5 * CELL_SIZE; // Longer range self.bulletSpeed = 8; // Fast bullets break; case 'splash': self.fireRate = 75; // Medium firing rate self.damage = 15; // Medium damage self.range = 3 * CELL_SIZE; // Medium range self.bulletSpeed = 4; // Slower bullets break; case 'slow': self.fireRate = 50; // Medium firing rate self.damage = 8; // Lower damage self.range = 3.5 * CELL_SIZE; // Medium-high range self.bulletSpeed = 5; // Medium bullets break; case 'poison': self.fireRate = 70; // Medium firing rate self.damage = 12; // Medium damage self.range = 3.2 * CELL_SIZE; // Medium range self.bulletSpeed = 5; // Medium bullets break; } // Create the tower base (a square) var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); // Set tower color based on type switch (self.id) { case 'rapid': baseGraphics.tint = 0x00AAFF; // Blue break; case 'sniper': baseGraphics.tint = 0xFF5500; // Orange break; case 'splash': baseGraphics.tint = 0x33CC00; // Bright Green break; case 'slow': baseGraphics.tint = 0x9900FF; // Deep Purple break; case 'poison': baseGraphics.tint = 0x00FFAA; // Aqua break; default: baseGraphics.tint = 0xAAAAAA; // Gray } // Level indicators (dots at bottom of tower) var levelIndicators = []; var maxDots = self.maxLevel; var dotSpacing = baseGraphics.width / (maxDots + 1); var dotSize = CELL_SIZE / 6; for (var i = 0; i < maxDots; i++) { var dot = new Container(); // Create black outline circle behind the indicator var outlineCircle = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); outlineCircle.width = dotSize + 4; // Slightly larger for outline outlineCircle.height = dotSize + 4; outlineCircle.tint = 0x000000; // Black outline // Add the colored indicator on top var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); towerLevelIndicator.width = dotSize; towerLevelIndicator.height = dotSize; towerLevelIndicator.tint = 0xCCCCCC; // Gray dots by default // Position dot at bottom of tower dot.x = -CELL_SIZE + dotSpacing * (i + 1); dot.y = CELL_SIZE * 0.7; self.addChild(dot); levelIndicators.push(dot); } // Create the gun turret that will rotate var gunContainer = new Container(); self.addChild(gunContainer); var gunGraphics = gunContainer.attachAsset('defense', { anchorX: 0.5, anchorY: 0.5 }); //gunGraphics.y = -CELL_SIZE * 0.3; // Position the gun slightly up from center // Update the level indicators self.updateLevelIndicators = function () { for (var i = 0; i < maxDots; i++) { var dot = levelIndicators[i]; var towerLevelIndicator = dot.children[1]; // Index 1 is the colored indicator (index 0 is the black outline) // Light up dots up to current level with white color if (i < self.level) { // All upgraded levels should be white towerLevelIndicator.tint = 0xFFFFFF; // White for active levels } else { // Levels not yet unlocked should match the tower's background color switch (self.id) { case 'rapid': towerLevelIndicator.tint = 0x00AAFF; // Blue break; case 'sniper': towerLevelIndicator.tint = 0xFF5500; // Orange break; case 'splash': towerLevelIndicator.tint = 0x33CC00; // Bright Green break; case 'slow': towerLevelIndicator.tint = 0x9900FF; // Deep Purple break; case 'poison': towerLevelIndicator.tint = 0x00FFAA; // Aqua break; default: towerLevelIndicator.tint = 0xAAAAAA; // Gray for default } } } }; // Initialize level indicators self.updateLevelIndicators(); // Method to refresh which cells are in range of this tower self.refreshCellsInRange = function () { // First clear current cells in range 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); } } // Recalculate cells in range with the new range using a circle approach self.cellsInRange = []; var rangeRadius = self.range / CELL_SIZE; // Convert range to grid cell units // Get the exact center coordinates of the tower in grid space var centerX = self.gridX + 1; // Add .5 as our cells are offset with half a width var centerY = self.gridY + 1; // Add .5 as our cells are offset with half a width // Expand the search area to catch all cells that might intersect 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); // Iterate through all cells in the bounding box for (var i = minI; i <= maxI; i++) { for (var j = minJ; j <= maxJ; j++) { // Find the closest point on the cell to the circle center var closestX = Math.max(i, Math.min(centerX, i + 1)); var closestY = Math.max(j, Math.min(centerY, j + 1)); // Calculate distance from circle center to closest point on the cell var deltaX = closestX - centerX; var deltaY = closestY - centerY; var distanceSquared = deltaX * deltaX + deltaY * deltaY; // If any part of the cell is within the range radius, include this cell if (distanceSquared <= rangeRadius * rangeRadius) { var cell = grid.getCell(i, j); if (cell) { self.cellsInRange.push(cell); cell.towersInRange.push(self); } } } } // Refresh debug cell rendering to update visuals for tower range grid.renderDebug(); }; // Method to calculate the total value of the tower including all upgrades self.getTotalValue = function () { // Base tower cost based on tower type var baseTowerCost = getTowerCost(self.id); // Calculate total investment (base cost + upgrade costs) var totalInvestment = baseTowerCost; for (var i = 1; i < self.level; i++) { totalInvestment += 10 + i * 5; } return totalInvestment; }; // Method to upgrade the tower self.upgrade = function () { if (self.level < self.maxLevel) { // Calculate upgrade cost based on level var upgradeCost = 10 + self.level * 5; // Check if player has enough gold if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; // Increase stats based on level self.range = (3 + self.level * 0.5) * CELL_SIZE; self.fireRate = Math.max(20, 60 - self.level * 8); // Faster firing rate self.damage = 10 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.5; // Update targeting cells based on new range self.refreshCellsInRange(); // Update level indicators with animation self.updateLevelIndicators(); // Add a flash animation to the newly activated level indicator if (self.level > 1) { var levelDot = levelIndicators[self.level - 1].children[1]; // Get the newly activated indicator // Flash the indicator by scaling it up and back 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 { // Show notification for not enough gold var notification = game.addChild(new Notification("Not enough gold to upgrade!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } return false; }; // Method to find the nearest enemy within range self.findTarget = function () { var closestEnemy = null; var closestScore = Infinity; // Use pathfinding score to determine proximity to goal for (var i = 0; i < self.cellsInRange.length; i++) { var cell = self.cellsInRange[i]; for (var j = 0; j < enemies.length; j++) { var enemy = enemies[j]; if (enemy.cellX === cell.x && enemy.cellY === cell.y) { // Double-check actual distance to ensure enemy is within range var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Only consider enemies that are actually within the tower's range if (distance <= self.range) { // Check if this enemy is closer to the goal based on pathfinding score if (cell.score < closestScore) { closestScore = cell.score; closestEnemy = enemy; } } } } } // Reset targetEnemy if no valid target is found if (!closestEnemy) { self.targetEnemy = null; } return closestEnemy; }; // Update method self.update = function () { // Always find the target every frame to ensure state consistency self.targetEnemy = self.findTarget(); // Rotate gun toward target if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); // Rotate the gun container gunContainer.rotation = angle; // Fire at target if cooldown is complete if (LK.ticks - self.lastFired >= self.fireRate) { self.fire(); self.lastFired = LK.ticks; } } }; // Add click event handler to open upgrade menu self.down = function (x, y, obj) { // Check if there's already an upgrade menu open var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); // Check if this tower already has an upgrade menu open var hasOwnMenu = false; var rangeCircle = null; // Find this tower's range indicator if it exists 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 this tower already has a menu open, close it and return if (hasOwnMenu) { // Find and animate/destroy this tower's upgrade menu for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } // Remove range circle if it exists if (rangeCircle) { game.removeChild(rangeCircle); } // Clear selected tower selectedTower = null; // Update grid render to remove highlights grid.renderDebug(); return; } // Remove any existing menus and range circles 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]); } } // Set this tower as the selected tower selectedTower = self; // Show the tower's range circle first (add it before the upgrade menu so it appears underneath) var rangeIndicator = new Container(); rangeIndicator.isTowerRange = true; // Mark this as a tower range indicator rangeIndicator.tower = self; // Reference to the tower it belongs to game.addChild(rangeIndicator); // Position at tower center rangeIndicator.x = self.x; rangeIndicator.y = self.y; // Create a range circle with the tower's current range var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.range * 2; // Diameter is twice the radius rangeGraphics.alpha = 0.3; // Make it semi-transparent // Create new upgrade menu (after adding range indicator so menu appears on top) var upgradeMenu = new UpgradeMenu(self); game.addChild(upgradeMenu); // Position menu horizontally centered upgradeMenu.x = 2048 / 2; // Horizontally centered // Animate the upgrade menu from below the screen to its position tween(upgradeMenu, { y: 2732 - 225 // Move to final position (bottom of screen with menu centered) }, { duration: 200, // Twice as fast (400 -> 200) easing: tween.backOut }); // Update grid render to highlight cells in range grid.renderDebug(); }; // Check if enemy is in range 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.range; }; // Fire at the current target self.fire = function () { if (self.targetEnemy) { // Verify the target is alive and calculate damage including future damage var potentialDamage = 0; for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) { potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage; } // Only shoot if enemy would still be alive after all current bullets hit if (self.targetEnemy.health > potentialDamage) { // Calculate bullet starting position var bulletX = self.x + Math.cos(gunContainer.rotation) * 40; var bulletY = self.y + Math.sin(gunContainer.rotation) * 40; // Create a new bullet var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); game.addChild(bullet); // Add bullet to global bullets array and to enemy's tracking array bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); } } }; // Place tower on the grid self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; // Adjust by half a cell size to align with grid cell centers self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; // Mark these grid cells as occupied 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; // Mark as wall/occupied } } } // Calculate which cells are in range self.refreshCellsInRange(); }; return self; }); // TowerPreview class for showing where towers can be placed var TowerPreview = Container.expand(function () { var self = Container.call(this); // Define tower range (in grid cells) var towerRange = 3; var rangeInPixels = towerRange * CELL_SIZE; // Tower type being dragged self.towerType = 'default'; // Create the range indicator (circle) var rangeIndicator = new Container(); self.addChild(rangeIndicator); // Since we don't have direct access to Graphics, // we'll create a white semi-transparent circle shape asset var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.alpha = 0.3; // Make it semi-transparent // Create the preview square (2x2 grid cells) var previewGraphics = self.attachAsset('towerpreview', { anchorX: 0.5, anchorY: 0.5 }); // Set the size to cover 2x2 grid cells previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; // Initial state self.canPlace = false; self.gridX = 0; self.gridY = 0; self.blockedByEnemy = false; // Track if placement is blocked by enemy // Update appearance based on tower type self.updateAppearance = function () { // Update range size based on tower type switch (self.towerType) { case 'rapid': rangeGraphics.width = rangeGraphics.height = 2.5 * CELL_SIZE * 2; previewGraphics.tint = 0x00AAFF; // Blue break; case 'sniper': rangeGraphics.width = rangeGraphics.height = 5 * CELL_SIZE * 2; previewGraphics.tint = 0xFF5500; // Orange break; case 'splash': rangeGraphics.width = rangeGraphics.height = 3 * CELL_SIZE * 2; previewGraphics.tint = 0x33CC00; // Bright Green break; case 'slow': rangeGraphics.width = rangeGraphics.height = 3.5 * CELL_SIZE * 2; previewGraphics.tint = 0x9900FF; // Deep Purple break; case 'poison': rangeGraphics.width = rangeGraphics.height = 3.2 * CELL_SIZE * 2; previewGraphics.tint = 0x00FFAA; // Aqua break; default: rangeGraphics.width = rangeGraphics.height = 3 * CELL_SIZE * 2; previewGraphics.tint = 0xAAAAAA; // Gray } // Reset to original preview color based on placement possibility if (!self.canPlace) { previewGraphics.tint = 0xFF0000; // Red if can't place } }; // Update the preview color based on placement possibility self.updatePlacementStatus = function () { // First check if placement is valid from a grid perspective var validGridPlacement = true; // Check if we're in the valid placement area (main square of the board) if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) { validGridPlacement = false; } else { // Check each of the 2x2 cells for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(self.gridX + i, self.gridY + j); // Can't place if any cell is undefined or not of type 0 (empty ground) if (!cell || cell.type !== 0) { validGridPlacement = false; break; } } if (!validGridPlacement) { break; } } } // Now check if any enemy is occupying the space where the tower would be placed // or if any cell is the current target of an enemy self.blockedByEnemy = false; if (validGridPlacement) { // Check for enemies occupying cells for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; // Check if enemy is on this cell if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) { self.blockedByEnemy = true; break; } // Check if enemy's current target is one of these cells 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; } } } } // Set final placement status self.canPlace = validGridPlacement && !self.blockedByEnemy; // Update appearance based on placement and tower type self.updateAppearance(); }; // Check if the tower can be placed at the current position self.checkPlacement = function () { self.updatePlacementStatus(); }; // Snap the preview to the grid self.snapToGrid = function (x, y) { // Convert screen coordinates to grid coordinates // Offset by the grid's position var gridPosX = x - grid.x; var gridPosY = y - grid.y; // Convert to grid coordinates and round down to get the top-left cell self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); // Position the preview at the intersection of the four cells // Adding CELL_SIZE to gridX and gridY gives us the center point // Adjust by half a cell size to align with grid cell centers self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; // Center of the 2x2 grid self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; // Center of the 2x2 grid // Check if we can place the tower here self.checkPlacement(); }; return self; }); // Function to handle closing/hiding upgrade menus with animation // UpgradeMenu class var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; // Set initial position below the screen self.y = 2732 + 225; // Position below the screen (half the menu height) // Create background - wider to accommodate horizontal layout var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; // Full screen width menuBackground.height = 500; // Shorter for horizontal layout menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; // More opaque for bottom toolbar // Tower name/type header 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; // Align with stats towerTypeText.y = -160; // Higher up to make room for stats self.addChild(towerTypeText); // Tower stats - positioned on the left side 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; // Keep the left alignment statsText.y = 50; // Move down to make room for headline self.addChild(statsText); // Create buttons container for the right side var buttonsContainer = new Container(); buttonsContainer.x = 500; // More to the right self.addChild(buttonsContainer); // Create upgrade button var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; // Reduced width for side-by-side layout buttonBackground.height = 150; // Reduced height // Check if tower is already at max level var isMaxLevel = self.tower.level >= self.tower.maxLevel; // Calculate upgrade cost if not at max level var upgradeCost = isMaxLevel ? 0 : 10 + self.tower.level * 5; // Set button color based on max level or affordability buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888; // Create button text var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); // Create sell button var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; // Reduced width for side-by-side layout sellButtonBackground.height = 150; // Reduced height sellButtonBackground.tint = 0xCC0000; // Red color for sell button // Calculate sell value (60% of total value) using tower's properties directly var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); // 60% of total value // Create sell button text var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); // Position buttons side by side upgradeButton.y = -85; // Position upgrade button on top sellButton.y = 85; // Position sell button below // Tower indicator removed // Close button var closeButton = new Container(); self.addChild(closeButton); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; // 50% larger than current: 60 * 1.5 closeBackground.height = 90; // 50% larger than current: 60 * 1.5 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; // 50% larger than current: 38 * 1.5 closeButton.y = -menuBackground.height / 2 + 57; // 50% larger than current: 38 * 1.5 // Event handlers upgradeButton.down = function (x, y, obj) { // First check if tower is already at max level if (self.tower.level >= self.tower.maxLevel) { // Just show a notification about max level var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } // Then check if upgrade succeeds if (self.tower.upgrade()) { // Update stats text after upgrade upgradeCost = 10 + self.tower.level * 5; 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'); // Update sell value after upgrade - use the tower's properties directly var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); // 60% of total value // Update sell button text sellButtonText.setText('Sell: +' + sellValue + ' gold'); // If tower is max level, disable button if (self.tower.level >= self.tower.maxLevel) { buttonBackground.tint = 0x888888; buttonText.setText('Max Level'); } // Find and update range indicator if it exists 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; } } // Always ensure we have a range indicator when upgrading if (rangeCircle) { // Update the existing range indicator with new range var rangeGraphics = rangeCircle.children[0]; rangeGraphics.width = rangeGraphics.height = self.tower.range * 2; // Update diameter to reflect new range } else { // Create a new range indicator var newRangeIndicator = new Container(); newRangeIndicator.isTowerRange = true; newRangeIndicator.tower = self.tower; // Add it to the beginning of the children array so it appears at the bottom game.addChildAt(newRangeIndicator, 0); // Position at tower center newRangeIndicator.x = self.tower.x; newRangeIndicator.y = self.tower.y; // Create a range circle with the tower's updated range var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.tower.range * 2; // Diameter is twice the radius rangeGraphics.alpha = 0.3; // Make it semi-transparent } // Add a nice animation effect 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 }); } }); } }; // Sell button handler sellButton.down = function (x, y, obj) { // Calculate sell value (60% of total value) using tower's properties directly var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); // 60% of total value // Award gold to player setGold(gold + sellValue); // Show sale notification var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; // Mark cells as empty again 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; // Mark as empty ground // Remove this tower from cell's towersInRange array var towerIndex = cell.towersInRange.indexOf(self.tower); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } // If the tower being sold is the selected tower, clear the selected tower if (selectedTower === self.tower) { selectedTower = null; } // Remove tower from towers array var towerIndex = towers.indexOf(self.tower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } // Remove tower from the game towerLayer.removeChild(self.tower); // Update pathing grid.pathFind(); grid.renderDebug(); // Close upgrade menu self.destroy(); // Remove range indicator 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); // Clear selected tower selectedTower = null; // Update grid render to remove highlights grid.renderDebug(); }; // Add update method to check affordability in real-time self.update = function () { // Check if at max level if (self.tower.level >= self.tower.maxLevel) { // Ensure button shows max level state if (buttonText.text !== 'Max Level') { buttonText.setText('Max Level'); buttonBackground.tint = 0x888888; } return; } // Calculate current upgrade cost var currentUpgradeCost = 10 + self.tower.level * 5; // Update button color based on current gold var canAfford = gold >= currentUpgradeCost; buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888; // Update text to reflect current status if needed var newText = 'Upgrade: ' + currentUpgradeCost + ' gold'; if (buttonText.text !== newText) { buttonText.setText(newText); } }; return self; }); // WaveIndicator class var WaveIndicator = Container.expand(function () { var self = Container.call(this); // Track if game has started self.gameStarted = false; // Create wave markers - now as blocks instead of dots self.waveMarkers = []; self.indicatorWidth = 0; //background.width - 400; // Leave space on edges var blockWidth = 400; // Width of each wave block // Calculate total content width needed (50 blocks) var totalBlocksWidth = blockWidth * totalWaves; // Create the start game block first (level 0) var startMarker = new Container(); // Start block marker with different styling var startBlock = startMarker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); startBlock.width = blockWidth - 10; startBlock.height = 70 * 2; startBlock.tint = 0x00AA00; // Green for start button // Add "Start Game" text var startText = new Text2("Start Game", { size: 50, fill: 0xFFFFFF, weight: 800 }); startText.anchor.set(0.5, 0.5); startMarker.addChild(startText); // Position start marker at level 0 position startMarker.x = -self.indicatorWidth; self.addChild(startMarker); self.waveMarkers.push(startMarker); // Add click event to start marker startMarker.down = function () { if (!self.gameStarted) { self.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; // This will trigger the first wave immediately // Change appearance to show it's been activated startBlock.tint = 0x00FF00; startText.setText("Started!"); // Show notification var notification = game.addChild(new Notification("Game started! Wave 1 incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; // Position blocks further apart so they don't all fit on screen for (var i = 0; i < totalWaves; i++) { var marker = new Container(); // Block marker var block = marker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth - 10; // Slight gap between blocks, scaled up by 100% block.height = 70 * 2; // Scale height by 100% // Set block color if (i % 10 === 0) { // Special waves (every 10th) block.tint = 0xFF5500; // Orange for major waves } else if (i % 5 === 0) { // Medium waves (every 5th) block.tint = 0xFFFF00; // Yellow for medium waves } else { // Regular waves block.tint = 0xAAAAAA; // Gray for regular waves } // Add wave number text to every block var waveNum = new Text2((i + 1).toString(), { size: 40, fill: 0xFFFFFF, weight: 800 }); waveNum.anchor.set(0.5, 0.5); marker.addChild(waveNum); // Position marker horizontally - spread blocks further apart than screen width marker.x = -self.indicatorWidth + (i + 1) * blockWidth; // Add 1 to account for start button self.addChild(marker); self.waveMarkers.push(marker); } // Create current position indicator 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; // Blue for current position 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; // Blue for current position indicator2.y = 65; // Add wave number text self.addChild(self.positionIndicator); // Update method to move the position indicator and scroll the wave blocks self.update = function () { // Update position indicator based on current wave progress var progress = waveTimer / nextWaveTime; // Calculate position for the wave markers // Move all markers to position the current wave at the center of the screen var moveAmount = (progress + currentWave) * blockWidth; // Center current wave block // Move all markers (creates scrolling effect) // Adjust so that the level is centered on the screen for (var i = 0; i < self.waveMarkers.length; i++) { var marker = self.waveMarkers[i]; marker.x = -moveAmount + i * blockWidth; } // Position the indicator at the center of the screen self.positionIndicator.x = 0; // Update completed wave markers for (var i = 0; i < totalWaves + 1; i++) { var marker = self.waveMarkers[i]; if (i === 0) { // This is the start button continue; } var block = marker.children[0]; if (i - 1 < currentWave) { // Subtract 1 to account for start button // Completed waves block.tint = 0x00AA00; // Green for completed waves } } // Handle wave progression self.handleWaveProgression = function () { // Don't start any waves until the game has been started if (!self.gameStarted) { return; } // Only process if we haven't reached max waves if (currentWave < totalWaves) { waveTimer++; // Check if it's time to start a new wave if (waveTimer >= nextWaveTime) { waveTimer = 0; currentWave++; waveInProgress = true; // Show wave notification if (currentWave != 1) { var notification = game.addChild(new Notification("Wave " + currentWave + " incoming with 20 enemies!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } }; // Call wave progression handler self.handleWaveProgression(); }; return self; }); /**** * Initialize Game ****/ // Add update method to check affordability in real-time var game = new LK.Game({ backgroundColor: 0x333333 //Init game with black background }); /**** * Game Code ****/ // Import tween plugin for animations // Define cell size as a global constant // Variable to track if an upgrade menu is being hidden var isHidingUpgradeMenu = false; // Function to handle closing/hiding upgrade menus with animation function hideUpgradeMenu(menu) { // Prevent double calls to hide the same menu if (isHidingUpgradeMenu) { return; } // Set flag to indicate we're hiding a menu isHidingUpgradeMenu = true; // Animate menu out before destroying tween(menu, { y: 2732 + 225 // Animate below the screen }, { duration: 150, // Twice as fast as before easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; // Reset flag after animation completes } }); } var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var enemies = []; var towers = []; var bullets = []; // Array to store active bullets var defenses = []; var selectedTower = null; // Keep track of the currently selected tower // Game resources and scores 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 = 3000; // Time between waves (in frames), increased by 10x // Source tower that players drag from to create new towers var sourceTower = null; // Create UI elements for resources 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); // Position UI elements at the top of the screen // Avoid top left corner (pause button area) // Center the UI elements with proper spacing var topMargin = 50; // Reduced by 30px from original 80px var centerX = 2048 / 2; var spacing = 400; // Adjusted spacing for larger text // Position the elements centered on the screen // First, attach to the top center container LK.gui.top.addChild(goldText); LK.gui.top.addChild(livesText); LK.gui.top.addChild(scoreText); // Position lives text in the center livesText.x = 0; // Center position (relative to top center) livesText.y = topMargin; // Position gold text to the left of lives goldText.x = -spacing; goldText.y = topMargin; // Position score text to the right of lives scoreText.x = spacing; scoreText.y = topMargin; // Helper function to update UI function updateUI() { goldText.setText('Gold: ' + gold); livesText.setText('Lives: ' + lives); scoreText.setText('Score: ' + score); } // Centralized function to modify gold with UI updates function setGold(value) { gold = value; updateUI(); } // Create separate layers for debug tiles, towers, and enemies var debugLayer = new Container(); var towerLayer = new Container(); var enemyLayer = new Container(); var grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Add layers to the game in the correct order game.addChild(debugLayer); game.addChild(towerLayer); game.addChild(enemyLayer); var offset = 0; // Create tower preview var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; // Handle drag events for tower placement var isDragging = false; // Check if placing a tower would block the path function wouldBlockPath(gridX, gridY) { // Temporarily mark cells as walls 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; // Mark as wall } } } // Check if path is still valid var blocked = grid.pathFind(); // Restore original cell types for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } // Re-find path with original cells grid.pathFind(); grid.renderDebug(); return blocked; } // Helper function to get tower cost based on type function getTowerCost(towerType) { var cost = 5; // Default tower cost switch (towerType) { case 'rapid': cost = 15; break; case 'sniper': cost = 25; break; case 'splash': cost = 35; break; case 'slow': cost = 45; break; case 'poison': cost = 55; break; } return cost; } // Place a tower at the grid position function placeTower(gridX, gridY, towerType) { // Tower cost based on tower type var towerCost = getTowerCost(towerType); // Check if player has enough gold if (gold >= towerCost) { var tower = new Tower(towerType || 'default'); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); // Deduct gold setGold(gold - towerCost); // Update grid pathing grid.pathFind(); grid.renderDebug(); return true; } else { // Not enough gold 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) { // Check if an upgrade menu is currently visible - prevent tower drag when menu is showing var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); // Exit early if upgrade menu is visible if (upgradeMenuVisible) { return; } // Check if click is on any source tower for (var i = 0; i < sourceTowers.length; i++) { var tower = sourceTowers[i]; if (x >= tower.x - tower.width / 2 && x <= tower.x + tower.width / 2 && y >= tower.y - tower.height / 2 && y <= tower.y + tower.height / 2) { towerPreview.visible = true; isDragging = true; // Store which tower type we're dragging towerPreview.towerType = tower.towerType; // Update preview appearance based on tower type towerPreview.updateAppearance(); towerPreview.snapToGrid(x, y); break; } } }; game.move = function (x, y, obj) { if (isDragging) { towerPreview.snapToGrid(x, y); } }; game.up = function (x, y, obj) { // Track if we clicked on a tower to show the upgrade menu var clickedOnTower = false; // Check if click was on a tower for (var i = 0; i < towers.length; i++) { var tower = towers[i]; // Calculate tower bounds 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; // Check if click is inside tower bounds if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } // Check for clicking outside upgrade menu var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); // If there are upgrade menus open, check if we clicked outside if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) { var clickedOnMenu = false; // Check if the click is inside any upgrade menu for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; // Calculate menu bounds var menuWidth = 2048; // Full screen width var menuHeight = 450; // Menu height var menuLeft = menu.x - menuWidth / 2; var menuRight = menu.x + menuWidth / 2; var menuTop = menu.y - menuHeight / 2; var menuBottom = menu.y + menuHeight / 2; // Check if click is inside menu bounds if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) { clickedOnMenu = true; break; } } // If clicked outside all menus, close them if (!clickedOnMenu) { // Animate menu out before destroying for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; hideUpgradeMenu(menu); } // Also remove any range indicators for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } // Clear selected tower selectedTower = null; // Update grid render to remove highlights grid.renderDebug(); } } if (isDragging) { isDragging = false; // Check placement possibilities if (towerPreview.canPlace) { // Check if placing here would block the path if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) { // Place the tower (placeTower will handle gold check internally) placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType); } else { // Show notification that we can't place here var notification = game.addChild(new Notification("Tower would block the path!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } else if (towerPreview.blockedByEnemy) { // Show notification that an enemy is in the way 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) { // Generic notification for other reasons var notification = game.addChild(new Notification("Cannot build here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } towerPreview.visible = false; // Close any upgrade menus when placing towers if (isDragging) { var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } } } }; // Create wave indicator at the bottom of the screen var waveIndicator = new WaveIndicator(); waveIndicator.x = 2048 / 2; waveIndicator.y = 2732 - 80; // Adjust initial position to ensure first wave block is visible game.addChild(waveIndicator); // Note: Wave progression is now fully handled by the WaveIndicator class // Create a row of source towers var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison']; var sourceTowers = []; var towerSpacing = 250; // Increased spacing from 170 to 250 var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2; var towerY = 2732 - CELL_SIZE * 3 - 90; for (var i = 0; i < towerTypes.length; i++) { var tower = new SourceTower(towerTypes[i]); tower.x = startX + i * towerSpacing; tower.y = towerY; towerLayer.addChild(tower); sourceTowers.push(tower); } // Set sourceTower to null as we'll use the array instead sourceTower = null; enemiesToSpawn = 20; game.update = function () { // Wave progress is now handled by the WaveIndicator class // Spawn enemies when wave is in progress if (waveInProgress) { // Spawn all enemies at once when the wave starts if (!waveSpawned) { waveSpawned = true; for (var i = 0; i < enemiesToSpawn; i++) { var target = grid.spawns[Math.random() * grid.spawns.length >> 0]; var enemy = enemyLayer.addChild(new Enemy()); // Make enemies stronger in later waves enemy.maxHealth = 100 + currentWave * 10; enemy.health = enemy.maxHealth; enemy.speed = 0.05 + currentWave * 0.002; // Calculate random starting position farther outside the screen (above) var randomOffsetX = Math.random() * 2048; // Random X position var randomOffsetY = -300 - Math.random() * 1800; // Random Y position much farther above screen // Set cell position to target spawn enemy.cellX = target.x; enemy.cellY = target.y; // Set current cell position to be outside screen // We'll convert from screen position to grid position enemy.currentCellX = (randomOffsetX - grid.x) / CELL_SIZE; enemy.currentCellY = (randomOffsetY - grid.y) / CELL_SIZE; enemies.push(enemy); } } // Check if all enemies have either been killed or reached the goal if (waveSpawned && enemies.length === 0) { waveInProgress = false; waveSpawned = false; } } // Update enemies for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; // Check if enemy is defeated if (enemy.health <= 0) { // Clear bullet targeting references for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; // This will cause bullet to destroy itself on next update } // Award gold and score for defeating enemy setGold(gold + 1); score += 5; updateUI(); // Remove enemy enemyLayer.removeChild(enemy); enemies.splice(a, 1); continue; } // Update enemy movement if (grid.updateEnemy(enemy)) { // Enemy reached goal enemyLayer.removeChild(enemy); enemies.splice(a, 1); // Reduce lives when enemy reaches goal lives = Math.max(0, lives - 1); updateUI(); // Check if game over (no lives left) if (lives <= 0) { LK.showGameOver(); } } } // Update towers for (var i = 0; i < towers.length; i++) { towers[i].update(); } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { bullets[i].update(); // Remove destroyed bullets if (!bullets[i].parent) { // If this bullet has a target, remove the bullet from the target's tracking array if (bullets[i].targetEnemy) { var targetEnemy = bullets[i].targetEnemy; var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } // Ensure bullet is removed from global bullets array bullets.splice(i, 1); } } // Update tower preview placement status on every frame // This ensures the placement status updates in real-time as enemies move if (towerPreview.visible) { towerPreview.checkPlacement(); } // Wave indicator update handles the wave progression if (waveIndicator) { waveIndicator.update(); } // Check win condition if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -1341,49 +1341,8 @@
self.indicatorWidth = 0; //background.width - 400; // Leave space on edges
var blockWidth = 400; // Width of each wave block
// Calculate total content width needed (50 blocks)
var totalBlocksWidth = blockWidth * totalWaves;
- // Create next wave button
- var nextWaveButton = new Container();
- // Next wave button background
- var nextWaveButtonBg = nextWaveButton.attachAsset('notification', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- nextWaveButtonBg.width = 250;
- nextWaveButtonBg.height = 70;
- nextWaveButtonBg.tint = 0xFF5500; // Orange
- // Next wave button text
- var nextWaveText = new Text2("Next Wave", {
- size: 40,
- fill: 0xFFFFFF,
- weight: 800
- });
- nextWaveText.anchor.set(0.5, 0.5);
- nextWaveButton.addChild(nextWaveText);
- // Position next wave button on the right side
- nextWaveButton.x = 800;
- nextWaveButton.y = 0;
- nextWaveButton.visible = false;
- // Add click event to next wave button
- nextWaveButton.down = function () {
- if (self.gameStarted) {
- // If there's a wave in progress, spawn an additional wave
- if (waveInProgress) {
- // Increment current wave for the new spawn but don't change the timer
- currentWave++;
- // Call spawn function directly
- spawnWaveEnemies(currentWave);
- } else {
- // No wave in progress, trigger next wave immediately
- waveTimer = nextWaveTime; // This will trigger the next wave immediately
- }
- // Show notification
- var notification = game.addChild(new Notification("Spawning next wave!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 150;
- }
- };
// Create the start game block first (level 0)
var startMarker = new Container();
// Start block marker with different styling
var startBlock = startMarker.attachAsset('notification', {
@@ -1417,10 +1376,8 @@
// Show notification
var notification = game.addChild(new Notification("Game started! Wave 1 incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
- // Make next wave button visible after game starts
- nextWaveButton.visible = true;
}
};
// Position blocks further apart so they don't all fit on screen
for (var i = 0; i < totalWaves; i++) {
@@ -1504,57 +1461,33 @@
// Completed waves
block.tint = 0x00AA00; // Green for completed waves
}
}
- // Update next wave button state
- if (self.gameStarted) {
- nextWaveButton.visible = true;
- // Only disable button if we've reached maximum waves
- if (currentWave >= totalWaves) {
- nextWaveButtonBg.tint = 0x888888; // Gray when all waves completed
- nextWaveText.setText("Max Wave");
- } else {
- // Button is always active, whether wave is in progress or not
- nextWaveButtonBg.tint = 0xFF5500; // Orange when available
- // Update button text based on whether a wave is in progress
- if (waveInProgress) {
- nextWaveText.setText("Add Wave");
- } else {
- nextWaveText.setText("Next Wave");
- }
- }
- }
// Handle wave progression
self.handleWaveProgression = function () {
// Don't start any waves until the game has been started
if (!self.gameStarted) {
return;
}
- // If we're in the middle of a wave, don't start a new one
- if (waveInProgress) {
- return;
- }
// Only process if we haven't reached max waves
if (currentWave < totalWaves) {
waveTimer++;
// Check if it's time to start a new wave
if (waveTimer >= nextWaveTime) {
waveTimer = 0;
currentWave++;
waveInProgress = true;
- waveSpawned = false; // Reset waveSpawned flag
// Show wave notification
if (currentWave != 1) {
- var notification = game.addChild(new Notification("Wave " + currentWave + " incoming!"));
+ var notification = game.addChild(new Notification("Wave " + currentWave + " incoming with 20 enemies!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
}
}
};
// Call wave progression handler
self.handleWaveProgression();
- self.addChild(nextWaveButton);
};
return self;
});
@@ -1904,49 +1837,42 @@
sourceTowers.push(tower);
}
// Set sourceTower to null as we'll use the array instead
sourceTower = null;
-enemiesToSpawn = 10;
-// Function to spawn enemies for a specific wave
-// Moved outside of game.update to make it globally accessible
-function spawnWaveEnemies(waveNumber) {
- if (!waveSpawned) {
- waveSpawned = true;
- waveInProgress = true;
- // Calculate number of enemies based on wave number
- var enemiesToSpawnForWave = 10 + Math.floor(waveNumber / 5) * 2;
- for (var i = 0; i < enemiesToSpawnForWave; i++) {
- var target = grid.spawns[Math.random() * grid.spawns.length >> 0];
- var enemy = enemyLayer.addChild(new Enemy());
- // Make enemies stronger in later waves
- enemy.maxHealth = 100 + waveNumber * 10;
- enemy.health = enemy.maxHealth;
- enemy.speed = 0.05 + waveNumber * 0.002;
- // Calculate random starting position farther outside the screen (above)
- var randomOffsetX = Math.random() * 2048; // Random X position
- var randomOffsetY = -300 - Math.random() * 1200; // Random Y position much farther above screen
- // Set cell position to target spawn
- enemy.cellX = target.x;
- enemy.cellY = target.y;
- // Set current cell position to be outside screen
- // We'll convert from screen position to grid position
- enemy.currentCellX = (randomOffsetX - grid.x) / CELL_SIZE;
- enemy.currentCellY = (randomOffsetY - grid.y) / CELL_SIZE;
- enemies.push(enemy);
- }
- }
-}
+enemiesToSpawn = 20;
game.update = function () {
// Wave progress is now handled by the WaveIndicator class
- // Check if we should spawn a wave
- if (waveInProgress && !waveSpawned) {
- spawnWaveEnemies(currentWave);
+ // Spawn enemies when wave is in progress
+ if (waveInProgress) {
+ // Spawn all enemies at once when the wave starts
+ if (!waveSpawned) {
+ waveSpawned = true;
+ for (var i = 0; i < enemiesToSpawn; i++) {
+ var target = grid.spawns[Math.random() * grid.spawns.length >> 0];
+ var enemy = enemyLayer.addChild(new Enemy());
+ // Make enemies stronger in later waves
+ enemy.maxHealth = 100 + currentWave * 10;
+ enemy.health = enemy.maxHealth;
+ enemy.speed = 0.05 + currentWave * 0.002;
+ // Calculate random starting position farther outside the screen (above)
+ var randomOffsetX = Math.random() * 2048; // Random X position
+ var randomOffsetY = -300 - Math.random() * 1800; // Random Y position much farther above screen
+ // Set cell position to target spawn
+ enemy.cellX = target.x;
+ enemy.cellY = target.y;
+ // Set current cell position to be outside screen
+ // We'll convert from screen position to grid position
+ enemy.currentCellX = (randomOffsetX - grid.x) / CELL_SIZE;
+ enemy.currentCellY = (randomOffsetY - grid.y) / CELL_SIZE;
+ enemies.push(enemy);
+ }
+ }
+ // Check if all enemies have either been killed or reached the goal
+ if (waveSpawned && enemies.length === 0) {
+ waveInProgress = false;
+ waveSpawned = false;
+ }
}
- // Check if all enemies have either been killed or reached the goal
- if (waveSpawned && enemies.length === 0) {
- waveInProgress = false;
- waveSpawned = false;
- }
// Update enemies
for (var a = enemies.length - 1; a >= 0; a--) {
var enemy = enemies[a];
// Check if enemy is defeated
White circle with two eyes, seen from above.. In-Game asset. 2d. High contrast. No shadows
White simple circular enemy seen from above, black outline. Black eyes, with a single shield in-font of it. Black and white only. Blue background.
White circle with black outline. Blue background.. In-Game asset. 2d. High contrast. No shadows