/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Enemy = Container.expand(function (path, enemyType) { var self = Container.call(this); var config = enemyConfigs[enemyType] || enemyConfigs['serpent1']; var enemyGraphics = self.attachAsset(config.asset, { anchorX: 0.5, anchorY: 0.5 }); if (config.scale) { enemyGraphics.scaleX = config.scale; enemyGraphics.scaleY = config.scale; } if (config.tint) { enemyGraphics.tint = config.tint; } self.health = config.health; self.maxHealth = config.health; self.speed = config.speed; // Increase speed for small serpents by 0.1 each wave if (enemyType === 'serpent1' || enemyType === 'serpent2' || enemyType === 'serpent3' || enemyType === 'serpent4') { self.speed += (currentWave - 1) * 0.1; } self.path = path; self.currentWaypointIndex = 1; self.value = config.value; // Score awarded on defeat self.enemyType = enemyType; // Store enemy type for boss tracking // Create health bar only if config says to show it if (config.showHealthBar) { self.healthBar = self.addChild(LK.getAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 })); self.healthBar.y = -80; // Position above enemy } self.update = function () { if (self.currentWaypointIndex >= self.path.length) { // Reached the end LK.showGameOver(); self.destroy(); // Self-destruct to avoid multiple game overs return; } var targetWaypoint = self.path[self.currentWaypointIndex]; var dx = targetWaypoint.x - self.x; var dy = targetWaypoint.y - self.y; if (config.asset === 'Serpent1' || config.asset === 'Serpent2' || config.asset === 'Serpent3' || config.asset === 'Serpent4') { var scale = config.scale || 1; if (dx > 0.1) { // moving right enemyGraphics.scaleX = -scale; } else if (dx < -0.1) { // moving left enemyGraphics.scaleX = scale; } } var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { self.currentWaypointIndex++; } }; self.takeDamage = function (amount) { self.health -= amount; // Update health bar width based on remaining health if it exists if (self.healthBar) { var healthPercentage = self.health / self.maxHealth; self.healthBar.scaleX = healthPercentage; } if (self.health <= 0) { return true; // Is dead } LK.effects.flashObject(self, 0xffffff, 100); return false; // Is alive }; return self; }); var Projectile = Container.expand(function (start, target, damage, projectileType) { var self = Container.call(this); projectileType = projectileType || 'projectile'; self.attachAsset(projectileType, { anchorX: 0.5, anchorY: 0.5 }); self.x = start.x; self.y = start.y; self.target = target; self.damage = damage; self.speed = 12; self.update = function () { if (!self.target || self.target.health <= 0) { // Target is gone projectiles.splice(projectiles.indexOf(self), 1); self.destroy(); return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { // Hit target if (self.target.takeDamage(self.damage)) { // Enemy died LK.getSound('enemy_die').play(); LK.setScore(LK.getScore() + self.target.value); scoreTxt.setText('SCORE: ' + LK.getScore()); // Check if this was a boss - just mark it as killed, don't progress wave immediately if (self.target.enemyType && (self.target.enemyType === 'boss1' || self.target.enemyType === 'boss2' || self.target.enemyType === 'boss3' || self.target.enemyType === 'boss4')) { // For wave 3 and 6, we need both bosses killed if (currentWave === 3 || currentWave === 6) { // Check if all bosses are killed (excluding the one we just killed) var bossesRemaining = 0; for (var j = 0; j < enemies.length; j++) { if (enemies[j] !== self.target && (enemies[j].enemyType === 'boss1' || enemies[j].enemyType === 'boss2' || enemies[j].enemyType === 'boss3' || enemies[j].enemyType === 'boss4')) { bossesRemaining++; } } if (bossesRemaining === 0) { waveBossKilled = true; } } else { // For waves 1, 2, 4, 5 - single boss per wave waveBossKilled = true; } } enemies.splice(enemies.indexOf(self.target), 1); self.target.destroy(); } projectiles.splice(projectiles.indexOf(self), 1); self.destroy(); } else { // Move towards target var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); var Tower = Container.expand(function (config) { var self = Container.call(this); self.config = config; self.attachAsset(config.asset, { anchorX: 0.5, anchorY: 0.5 }); self.cost = config.cost; self.damage = config.damage; self.range = 400; // All towers have same range for now self.fireRate = config.fireRate; // shots per second self.cooldown = 0; self.isTower = true; self.down = function (x, y, obj) { // This makes the tower a "button" console.log("Tower clicked! Damage: " + self.damage); LK.effects.flashObject(self, 0xffffff, 200); }; self.update = function () { if (self.cooldown > 0) { self.cooldown--; return; } var target = findClosestEnemy(self.x, self.y, self.range); if (target) { fireProjectile(self, target); self.cooldown = 60 / self.fireRate; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1e272e }); /**** * Game Code ****/ // The engine will automatically create these assets based on their usage below. // --- Constants & Game State --- var MAP_WIDTH = 4000; var MAP_HEIGHT = 4800; var CELL_SIZE = 200; var GRID_WIDTH = MAP_WIDTH / CELL_SIZE; var GRID_HEIGHT = MAP_HEIGHT / CELL_SIZE; // Grid states: 0=empty, 1=path, 2=buildable, 3=occupied var grid = []; // Wave system var currentWave = 1; var enemiesSpawnedInWave = 0; var waveSpawnTimer = null; var waveBossKilled = false; var isShowingWaveText = false; var enemyConfigs = { 'serpent1': { asset: 'Serpent1', health: 250, speed: 0.7, value: 10, showHealthBar: true }, 'serpent2': { asset: 'Serpent2', health: 300, speed: 0.6, value: 15, showHealthBar: true }, 'serpent1_big': { asset: 'Serpent1', health: 600, speed: 0.5, value: 30, scale: 1.5, showHealthBar: true }, 'serpent2_big': { asset: 'Serpent2', health: 750, speed: 0.4, value: 40, scale: 1.5, showHealthBar: true }, 'boss1': { asset: 'Serpent1', health: 2000, speed: 0.3, value: 100, scale: 2, tint: 0xff0000, showHealthBar: true }, 'boss2': { asset: 'Serpent2', health: 2500, speed: 0.3, value: 120, scale: 2, tint: 0xff0000, showHealthBar: true }, 'serpent3': { asset: 'Serpent3', health: 400, speed: 0.6, value: 20, showHealthBar: true }, 'serpent4': { asset: 'Serpent4', health: 500, speed: 0.5, value: 25, showHealthBar: true }, 'serpent3_big': { asset: 'Serpent3', health: 900, speed: 0.4, value: 50, scale: 1.5, showHealthBar: true }, 'serpent4_big': { asset: 'Serpent4', health: 1100, speed: 0.4, value: 60, scale: 1.5, showHealthBar: true }, 'boss3': { asset: 'Serpent3', health: 3000, speed: 0.3, value: 150, scale: 2, tint: 0xff0000, showHealthBar: true }, 'boss4': { asset: 'Serpent4', health: 3500, speed: 0.3, value: 180, scale: 2, tint: 0xff0000, showHealthBar: true } }; var pathWaypoints = [{ x: 2000 + CELL_SIZE / 2, y: -100 }, { x: 2000 + CELL_SIZE / 2, y: 800 + CELL_SIZE / 2 }, { x: 800 + CELL_SIZE / 2, y: 800 + CELL_SIZE / 2 }, { x: 800 + CELL_SIZE / 2, y: 1600 + CELL_SIZE / 2 }, { x: 3200 + CELL_SIZE / 2, y: 1600 + CELL_SIZE / 2 }, { x: 3200 + CELL_SIZE / 2, y: 2400 + CELL_SIZE / 2 }, { x: 1200 + CELL_SIZE / 2, y: 2400 + CELL_SIZE / 2 }, { x: 1200 + CELL_SIZE / 2, y: 3200 + CELL_SIZE / 2 }, { x: 2800 + CELL_SIZE / 2, y: 3200 + CELL_SIZE / 2 }, { x: 2800 + CELL_SIZE / 2, y: 4000 + CELL_SIZE / 2 }, { x: 2800 + CELL_SIZE / 2, y: 4900 }]; var enemies = []; var towers = []; var projectiles = []; var mapContainer = game.addChild(new Container()); mapContainer.width = MAP_WIDTH; mapContainer.height = MAP_HEIGHT; // Add background var gameBackground = mapContainer.addChild(LK.getAsset('gameBackground', { anchorX: 0, anchorY: 0 })); gameBackground.x = 0; gameBackground.y = 0; gameBackground.width = MAP_WIDTH; gameBackground.height = MAP_HEIGHT; var placingTowerConfig = null; var ghostTower = null; var isDraggingMap = false; var dragStart = { x: 0, y: 0 }; var mapStart = { x: 0, y: 0 }; // --- Tower Configurations --- var towerTypes = { '1': { asset: 'tower_1', cost: 50, damage: 15, fireRate: 1.5, projectileTypes: ['star'] }, '2': { asset: 'tower_2', cost: 100, damage: 35, fireRate: 1, projectileTypes: ['arrow'] }, '3': { asset: 'tower_3', cost: 150, damage: 60, fireRate: 0.75, projectileTypes: ['star', 'arrow'] } }; // --- UI --- LK.setScore(200); // Starting cash var scoreTxt = new Text2('SCORE: ' + LK.getScore(), { size: 70, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); scoreTxt.y = 20; LK.gui.top.addChild(scoreTxt); var uiContainer = LK.gui.bottom.addChild(new Container()); uiContainer.y = -20; // Move it up slightly from the absolute bottom // Create tower 1 group var towerButton1 = uiContainer.addChild(LK.getAsset('uiButton', { anchorX: 0.5, anchorY: 1 })); towerButton1.x = -300; var towerIcon1 = uiContainer.addChild(LK.getAsset('tower_1', { scaleX: 0.8, scaleY: 0.8, anchorX: 0.5, anchorY: 0.5 })); towerIcon1.x = -305; towerIcon1.y = -115; towerIcon1.down = function (x, y, obj) { startPlacingTower('1'); if (placingTowerConfig) { towerIcon1.isDragging = true; game.move(x, y, obj); } }; var towerText1 = uiContainer.addChild(new Text2('50', { size: 50, fill: '#fff', anchorX: 0.5, anchorY: 0.5 })); towerText1.x = -305 - 25; towerText1.y = -55; // Create tower 2 group var towerButton2 = uiContainer.addChild(LK.getAsset('uiButton', { anchorX: 0.5, anchorY: 1 })); var towerIcon2 = uiContainer.addChild(LK.getAsset('tower_2', { scaleX: 0.8, scaleY: 0.8, anchorX: 0.5, anchorY: 0.5 })); towerIcon2.x = -5; towerIcon2.y = -115; towerIcon2.down = function (x, y, obj) { startPlacingTower('2'); if (placingTowerConfig) { towerIcon2.isDragging = true; game.move(x, y, obj); } }; var towerText2 = uiContainer.addChild(new Text2('100', { size: 50, fill: '#fff', anchorX: 0.5, anchorY: 0.5 })); towerText2.x = -15 - 25; towerText2.y = -55; // Create tower 3 group var towerButton3 = uiContainer.addChild(LK.getAsset('uiButton', { anchorX: 0.5, anchorY: 1 })); towerButton3.x = 300; var towerIcon3 = uiContainer.addChild(LK.getAsset('tower_3', { scaleX: 0.8, scaleY: 0.8, anchorX: 0.5, anchorY: 0.5 })); towerIcon3.x = 300; towerIcon3.y = -115; towerIcon3.down = function (x, y, obj) { startPlacingTower('3'); if (placingTowerConfig) { towerIcon3.isDragging = true; game.move(x, y, obj); } }; var towerText3 = uiContainer.addChild(new Text2('150', { size: 50, fill: '#fff', anchorX: 0.5, anchorY: 0.5 })); towerText3.x = 285 - 25; towerText3.y = -55; // --- Initialization Functions --- function initializeGrid() { for (var i = 0; i < GRID_WIDTH; i++) { grid[i] = []; for (var j = 0; j < GRID_HEIGHT; j++) { grid[i][j] = 0; // Empty } } // Mark path tiles for (var k = 0; k < pathWaypoints.length - 1; k++) { var start = pathWaypoints[k]; var end = pathWaypoints[k + 1]; var p1 = { x: Math.floor(start.x / CELL_SIZE), y: Math.floor(start.y / CELL_SIZE) }; var p2 = { x: Math.floor(end.x / CELL_SIZE), y: Math.floor(end.y / CELL_SIZE) }; var x = p1.x; var y = p1.y; var dx = Math.abs(p2.x - x); var dy = Math.abs(p2.y - y); var sx = x < p2.x ? 1 : -1; var sy = y < p2.y ? 1 : -1; var err = dx - dy; while (true) { if (x >= 0 && x < GRID_WIDTH && y >= 0 && y < GRID_HEIGHT) { grid[x][y] = 1; } if (x === p2.x && y === p2.y) { break; } var e2 = 2 * err; if (e2 > -dy) { err -= dy; x += sx; } if (e2 < dx) { err += dx; y += sy; } } } // Mark buildable tiles - only those on the sides of the road segments for (var k = 0; k < pathWaypoints.length - 1; k++) { var start = pathWaypoints[k]; var end = pathWaypoints[k + 1]; var p1 = { x: Math.floor(start.x / CELL_SIZE), y: Math.floor(start.y / CELL_SIZE) }; var p2 = { x: Math.floor(end.x / CELL_SIZE), y: Math.floor(end.y / CELL_SIZE) }; // Determine if this segment is horizontal or vertical var isHorizontal = p1.y === p2.y; var isVertical = p1.x === p2.x; // Determine direction of movement to know which side to place tiles var movingRight = p2.x > p1.x; var movingDown = p2.y > p1.y; var movingLeft = p2.x < p1.x; var movingUp = p2.y < p1.y; if (isVertical) { // For vertical segments, place buildable tiles on left and right var x = p1.x; var minY = Math.min(p1.y, p2.y); var maxY = Math.max(p1.y, p2.y); for (var y = minY; y <= maxY; y++) { // Only place on sides, avoiding corners where segments meet var isCorner = false; // Check if this position is a corner (where path changes direction) for (var w = 0; w < pathWaypoints.length; w++) { var wp = pathWaypoints[w]; if (Math.floor(wp.x / CELL_SIZE) === x && Math.floor(wp.y / CELL_SIZE) === y) { isCorner = true; break; } } if (!isCorner) { // Left side if (x - 1 >= 0 && grid[x - 1][y] === 0) { grid[x - 1][y] = 2; } // Right side if (x + 1 < GRID_WIDTH && grid[x + 1][y] === 0) { grid[x + 1][y] = 2; } } } } else if (isHorizontal) { // For horizontal segments, only place on one side to avoid doubles var y = p1.y; var minX = Math.min(p1.x, p2.x); var maxX = Math.max(p1.x, p2.x); for (var x = minX; x <= maxX; x++) { // Only place on one side (top) to avoid double placement if (y - 1 >= 0 && grid[x][y - 1] === 0) { grid[x][y - 1] = 2; } } } } // Draw visual grid - buildable tiles first (background layer) for (var i = 0; i < GRID_WIDTH; i++) { for (var j = 0; j < GRID_HEIGHT; j++) { if (grid[i][j] === 2) { var buildableTile = mapContainer.addChild(LK.getAsset('buildableTile', {})); buildableTile.alpha = 0.5; buildableTile.x = i * CELL_SIZE; buildableTile.y = j * CELL_SIZE; } } } // Draw path tiles on top for (var i = 0; i < GRID_WIDTH; i++) { for (var j = 0; j < GRID_HEIGHT; j++) { if (grid[i][j] === 1) { var pathTile = mapContainer.addChild(LK.getAsset('pathTile', {})); pathTile.x = i * CELL_SIZE; pathTile.y = j * CELL_SIZE; } } } } initializeGrid(); function startPlacingTower(type) { var config = towerTypes[type]; if (LK.getScore() >= config.cost) { placingTowerConfig = config; ghostTower = mapContainer.addChild(LK.getAsset(config.asset, { anchorX: 0, anchorY: 0 })); ghostTower.alpha = 0.7; } } // --- Event Handlers --- game.down = function (x, y, obj) { var localPoint = uiContainer.toLocal({ x: x, y: y }); // If the clicked object is a tower on the map, duplicate it for placement if (obj && obj.target && (obj.target.isTower || obj.target.parent && obj.target.parent.isTower)) { var clickedTower = obj.target.isTower ? obj.target : obj.target.parent; // Find tower type from its config var myType = null; if (clickedTower.config) { for (var key in towerTypes) { if (towerTypes[key].asset === clickedTower.config.asset) { myType = key; break; } } } if (myType) { startPlacingTower(myType); if (placingTowerConfig) { game.move(x, y, obj); } } return; // Stop further processing (like map dragging) } if (!placingTowerConfig) { isDraggingMap = true; dragStart.x = x; dragStart.y = y; mapStart.x = mapContainer.x; mapStart.y = mapContainer.y; } }; game.move = function (x, y, obj) { if (placingTowerConfig) { var mapPos = mapContainer.toLocal({ x: x, y: y }); // Position ghost tower directly at cursor position without anchor offset ghostTower.x = mapPos.x; ghostTower.y = mapPos.y; var gridX = Math.floor(mapPos.x / CELL_SIZE); var gridY = Math.floor(mapPos.y / CELL_SIZE); if (gridX >= 0 && gridX < GRID_WIDTH && gridY >= 0 && gridY < GRID_HEIGHT && grid[gridX][gridY] === 2) { ghostTower.tint = 0x00ff00; // Green for valid } else { ghostTower.tint = 0xff0000; // Red for invalid } } else if (isDraggingMap) { var dx = x - dragStart.x; var dy = y - dragStart.y; // Clamp map position var newX = mapStart.x + dx; var newY = mapStart.y + dy; var gameWidth = 2048; var gameHeight = 2732; if (newX > 0) { newX = 0; } if (newY > 0) { newY = 0; } if (newX < gameWidth - MAP_WIDTH) { newX = gameWidth - MAP_WIDTH; } if (newY < gameHeight - MAP_HEIGHT) { newY = gameHeight - MAP_HEIGHT; } mapContainer.x = newX; mapContainer.y = newY; } }; game.up = function (x, y, obj) { if (placingTowerConfig) { var mapPos = mapContainer.toLocal({ x: x, y: y }); var gridX = Math.floor(mapPos.x / CELL_SIZE); var gridY = Math.floor(mapPos.y / CELL_SIZE); if (gridX >= 0 && gridX < GRID_WIDTH && gridY >= 0 && gridY < GRID_HEIGHT && grid[gridX][gridY] === 2) { if (LK.getScore() >= placingTowerConfig.cost) { var newTower = new Tower(placingTowerConfig); newTower.x = gridX * CELL_SIZE + CELL_SIZE / 2; newTower.y = gridY * CELL_SIZE + CELL_SIZE / 2; mapContainer.addChild(newTower); towers.push(newTower); grid[gridX][gridY] = 3; // Mark as occupied LK.setScore(LK.getScore() - placingTowerConfig.cost); scoreTxt.setText('SCORE: ' + LK.getScore()); LK.getSound('place_tower').play(); } } ghostTower.destroy(); ghostTower = null; placingTowerConfig = null; // Reset drag states towerIcon1.isDragging = false; towerIcon2.isDragging = false; towerIcon3.isDragging = false; } isDraggingMap = false; }; // --- Game Logic Functions --- function findClosestEnemy(x, y, range) { var closestEnemy = null; var minDistanceSq = range * range; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - x; var dy = enemy.y - y; var distanceSq = dx * dx + dy * dy; if (distanceSq < minDistanceSq) { minDistanceSq = distanceSq; closestEnemy = enemy; } } return closestEnemy; } function fireProjectile(tower, target) { var projectileTypes = tower.config.projectileTypes; for (var i = 0; i < projectileTypes.length; i++) { var projectile = new Projectile({ x: tower.x, y: tower.y }, target, tower.damage, projectileTypes[i]); // Flip arrow if enemy is to the left of tower_2 and projectile is arrow if (tower.config.asset === 'tower_2' && projectileTypes[i] === 'arrow' && target.x < tower.x) { projectile.scaleX = -1; } // Add rotation animation to star projectiles if (projectileTypes[i] === 'star') { tween(projectile, { rotation: Math.PI * 4 }, { duration: 2000 }); } projectiles.push(projectile); mapContainer.addChild(projectile); } LK.getSound('shoot').play(); } function startWave() { enemiesSpawnedInWave = 0; waveBossKilled = false; waveSpawnTimer = LK.setInterval(function () { if (enemiesSpawnedInWave >= 20) { LK.clearInterval(waveSpawnTimer); waveSpawnTimer = null; // Check for next wave after all enemies are defeated return; } enemiesSpawnedInWave++; var enemyType; // Determine enemy type based on position in wave if (currentWave === 3) { // Wave 3: Mix of serpent1 and serpent2 if (enemiesSpawnedInWave === 19) { // First boss - serpent1 boss enemyType = 'boss1'; } else if (enemiesSpawnedInWave === 20) { // Second boss - serpent2 boss enemyType = 'boss2'; } else if (enemiesSpawnedInWave === 15 || enemiesSpawnedInWave === 17) { // Big serpent1 enemies enemyType = 'serpent1_big'; } else if (enemiesSpawnedInWave === 16 || enemiesSpawnedInWave === 18) { // Big serpent2 enemies enemyType = 'serpent2_big'; } else { // Regular enemies - alternate between serpent1 and serpent2 enemyType = enemiesSpawnedInWave % 2 === 1 ? 'serpent1' : 'serpent2'; } } else if (currentWave === 4) { // Wave 4: serpent3 based wave if (enemiesSpawnedInWave === 20) { // Boss - serpent3 boss enemyType = 'boss3'; } else if (enemiesSpawnedInWave === 18 || enemiesSpawnedInWave === 19) { // Big serpent3 enemies enemyType = 'serpent3_big'; } else { // Regular serpent3 enemies enemyType = 'serpent3'; } } else if (currentWave === 5) { // Wave 5: serpent4 based wave if (enemiesSpawnedInWave === 20) { // Boss - serpent4 boss enemyType = 'boss4'; } else if (enemiesSpawnedInWave === 18 || enemiesSpawnedInWave === 19) { // Big serpent4 enemies enemyType = 'serpent4_big'; } else { // Regular serpent4 enemies enemyType = 'serpent4'; } } else if (currentWave === 6) { // Wave 6: Mix of all serpent types with two bosses if (enemiesSpawnedInWave === 19) { // First boss - serpent3 boss enemyType = 'boss3'; } else if (enemiesSpawnedInWave === 20) { // Second boss - serpent4 boss enemyType = 'boss4'; } else if (enemiesSpawnedInWave === 15) { // Big serpent1 enemy enemyType = 'serpent1_big'; } else if (enemiesSpawnedInWave === 16) { // Big serpent2 enemy enemyType = 'serpent2_big'; } else if (enemiesSpawnedInWave === 17) { // Big serpent3 enemy enemyType = 'serpent3_big'; } else if (enemiesSpawnedInWave === 18) { // Big serpent4 enemy enemyType = 'serpent4_big'; } else { // Regular enemies - cycle through all serpent types var serpentTypes = ['serpent1', 'serpent2', 'serpent3', 'serpent4']; enemyType = serpentTypes[(enemiesSpawnedInWave - 1) % 4]; } } else { // Fallback for other waves - single boss at enemy 20 if (enemiesSpawnedInWave === 20) { // Boss - use wave-specific boss type if (currentWave === 1) { enemyType = 'boss1'; } else if (currentWave === 2) { enemyType = 'boss2'; } } else if (enemiesSpawnedInWave === 18 || enemiesSpawnedInWave === 19) { // Big enemies - use wave-specific serpent type if (currentWave === 1) { enemyType = 'serpent1_big'; } else if (currentWave === 2) { enemyType = 'serpent2_big'; } } else { // Regular enemy based on wave if (currentWave === 1) { enemyType = 'serpent1'; } else if (currentWave === 2) { enemyType = 'serpent2'; } } } var enemy = new Enemy(pathWaypoints, enemyType); enemy.x = pathWaypoints[0].x; enemy.y = pathWaypoints[0].y; enemies.push(enemy); mapContainer.addChild(enemy); }, 800); } // Wave text will be created dynamically when needed function showWaveText(waveNumber) { // Set flag to indicate wave text is showing isShowingWaveText = true; // Create wave text dynamically var waveText = new Text2('WAVE ' + waveNumber, { size: 120, fill: 0xFFA500 // Orange color }); waveText.anchor.set(0.5, 0.5); waveText.x = 2048 / 2; waveText.y = 2732 / 2; game.addChild(waveText); // Create blinking animation with proper recursion control var blinkCount = 0; var maxBlinks = 6; // 3 complete blink cycles (fade out + fade in = 1 cycle, so 6 total for 3 cycles) function blink() { if (blinkCount >= maxBlinks) { // Stop blinking and fade out tween(waveText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { waveText.destroy(); // Clear flag when wave text is destroyed isShowingWaveText = false; // Start spawning enemies after wave text disappears startWave(); } }); return; } tween(waveText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { tween(waveText, { alpha: 1 }, { duration: 500, onFinish: function onFinish() { blinkCount++; blink(); } }); } }); } blink(); } // Show initial wave text (startWave will be called after text animation) showWaveText(1); game.update = function () { for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].update(); } for (var i = towers.length - 1; i >= 0; i--) { towers[i].update(); } for (var i = projectiles.length - 1; i >= 0; i--) { projectiles[i].update(); } // Check if wave is complete and start next wave if (enemiesSpawnedInWave === 20 && waveBossKilled && !waveSpawnTimer && !isShowingWaveText && enemies.length === 0) { currentWave++; if (currentWave <= 6) { // Reset wave state for next wave enemiesSpawnedInWave = 0; waveBossKilled = false; // Show wave text for new wave (startWave will be called after text animation) showWaveText(currentWave); } else { // All waves completed, show you win screen LK.showYouWin(); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Enemy = Container.expand(function (path, enemyType) {
var self = Container.call(this);
var config = enemyConfigs[enemyType] || enemyConfigs['serpent1'];
var enemyGraphics = self.attachAsset(config.asset, {
anchorX: 0.5,
anchorY: 0.5
});
if (config.scale) {
enemyGraphics.scaleX = config.scale;
enemyGraphics.scaleY = config.scale;
}
if (config.tint) {
enemyGraphics.tint = config.tint;
}
self.health = config.health;
self.maxHealth = config.health;
self.speed = config.speed;
// Increase speed for small serpents by 0.1 each wave
if (enemyType === 'serpent1' || enemyType === 'serpent2' || enemyType === 'serpent3' || enemyType === 'serpent4') {
self.speed += (currentWave - 1) * 0.1;
}
self.path = path;
self.currentWaypointIndex = 1;
self.value = config.value; // Score awarded on defeat
self.enemyType = enemyType; // Store enemy type for boss tracking
// Create health bar only if config says to show it
if (config.showHealthBar) {
self.healthBar = self.addChild(LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
}));
self.healthBar.y = -80; // Position above enemy
}
self.update = function () {
if (self.currentWaypointIndex >= self.path.length) {
// Reached the end
LK.showGameOver();
self.destroy(); // Self-destruct to avoid multiple game overs
return;
}
var targetWaypoint = self.path[self.currentWaypointIndex];
var dx = targetWaypoint.x - self.x;
var dy = targetWaypoint.y - self.y;
if (config.asset === 'Serpent1' || config.asset === 'Serpent2' || config.asset === 'Serpent3' || config.asset === 'Serpent4') {
var scale = config.scale || 1;
if (dx > 0.1) {
// moving right
enemyGraphics.scaleX = -scale;
} else if (dx < -0.1) {
// moving left
enemyGraphics.scaleX = scale;
}
}
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.speed) {
self.currentWaypointIndex++;
}
};
self.takeDamage = function (amount) {
self.health -= amount;
// Update health bar width based on remaining health if it exists
if (self.healthBar) {
var healthPercentage = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercentage;
}
if (self.health <= 0) {
return true; // Is dead
}
LK.effects.flashObject(self, 0xffffff, 100);
return false; // Is alive
};
return self;
});
var Projectile = Container.expand(function (start, target, damage, projectileType) {
var self = Container.call(this);
projectileType = projectileType || 'projectile';
self.attachAsset(projectileType, {
anchorX: 0.5,
anchorY: 0.5
});
self.x = start.x;
self.y = start.y;
self.target = target;
self.damage = damage;
self.speed = 12;
self.update = function () {
if (!self.target || self.target.health <= 0) {
// Target is gone
projectiles.splice(projectiles.indexOf(self), 1);
self.destroy();
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.speed) {
// Hit target
if (self.target.takeDamage(self.damage)) {
// Enemy died
LK.getSound('enemy_die').play();
LK.setScore(LK.getScore() + self.target.value);
scoreTxt.setText('SCORE: ' + LK.getScore());
// Check if this was a boss - just mark it as killed, don't progress wave immediately
if (self.target.enemyType && (self.target.enemyType === 'boss1' || self.target.enemyType === 'boss2' || self.target.enemyType === 'boss3' || self.target.enemyType === 'boss4')) {
// For wave 3 and 6, we need both bosses killed
if (currentWave === 3 || currentWave === 6) {
// Check if all bosses are killed (excluding the one we just killed)
var bossesRemaining = 0;
for (var j = 0; j < enemies.length; j++) {
if (enemies[j] !== self.target && (enemies[j].enemyType === 'boss1' || enemies[j].enemyType === 'boss2' || enemies[j].enemyType === 'boss3' || enemies[j].enemyType === 'boss4')) {
bossesRemaining++;
}
}
if (bossesRemaining === 0) {
waveBossKilled = true;
}
} else {
// For waves 1, 2, 4, 5 - single boss per wave
waveBossKilled = true;
}
}
enemies.splice(enemies.indexOf(self.target), 1);
self.target.destroy();
}
projectiles.splice(projectiles.indexOf(self), 1);
self.destroy();
} else {
// Move towards target
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
};
return self;
});
var Tower = Container.expand(function (config) {
var self = Container.call(this);
self.config = config;
self.attachAsset(config.asset, {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = config.cost;
self.damage = config.damage;
self.range = 400; // All towers have same range for now
self.fireRate = config.fireRate; // shots per second
self.cooldown = 0;
self.isTower = true;
self.down = function (x, y, obj) {
// This makes the tower a "button"
console.log("Tower clicked! Damage: " + self.damage);
LK.effects.flashObject(self, 0xffffff, 200);
};
self.update = function () {
if (self.cooldown > 0) {
self.cooldown--;
return;
}
var target = findClosestEnemy(self.x, self.y, self.range);
if (target) {
fireProjectile(self, target);
self.cooldown = 60 / self.fireRate;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1e272e
});
/****
* Game Code
****/
// The engine will automatically create these assets based on their usage below.
// --- Constants & Game State ---
var MAP_WIDTH = 4000;
var MAP_HEIGHT = 4800;
var CELL_SIZE = 200;
var GRID_WIDTH = MAP_WIDTH / CELL_SIZE;
var GRID_HEIGHT = MAP_HEIGHT / CELL_SIZE;
// Grid states: 0=empty, 1=path, 2=buildable, 3=occupied
var grid = [];
// Wave system
var currentWave = 1;
var enemiesSpawnedInWave = 0;
var waveSpawnTimer = null;
var waveBossKilled = false;
var isShowingWaveText = false;
var enemyConfigs = {
'serpent1': {
asset: 'Serpent1',
health: 250,
speed: 0.7,
value: 10,
showHealthBar: true
},
'serpent2': {
asset: 'Serpent2',
health: 300,
speed: 0.6,
value: 15,
showHealthBar: true
},
'serpent1_big': {
asset: 'Serpent1',
health: 600,
speed: 0.5,
value: 30,
scale: 1.5,
showHealthBar: true
},
'serpent2_big': {
asset: 'Serpent2',
health: 750,
speed: 0.4,
value: 40,
scale: 1.5,
showHealthBar: true
},
'boss1': {
asset: 'Serpent1',
health: 2000,
speed: 0.3,
value: 100,
scale: 2,
tint: 0xff0000,
showHealthBar: true
},
'boss2': {
asset: 'Serpent2',
health: 2500,
speed: 0.3,
value: 120,
scale: 2,
tint: 0xff0000,
showHealthBar: true
},
'serpent3': {
asset: 'Serpent3',
health: 400,
speed: 0.6,
value: 20,
showHealthBar: true
},
'serpent4': {
asset: 'Serpent4',
health: 500,
speed: 0.5,
value: 25,
showHealthBar: true
},
'serpent3_big': {
asset: 'Serpent3',
health: 900,
speed: 0.4,
value: 50,
scale: 1.5,
showHealthBar: true
},
'serpent4_big': {
asset: 'Serpent4',
health: 1100,
speed: 0.4,
value: 60,
scale: 1.5,
showHealthBar: true
},
'boss3': {
asset: 'Serpent3',
health: 3000,
speed: 0.3,
value: 150,
scale: 2,
tint: 0xff0000,
showHealthBar: true
},
'boss4': {
asset: 'Serpent4',
health: 3500,
speed: 0.3,
value: 180,
scale: 2,
tint: 0xff0000,
showHealthBar: true
}
};
var pathWaypoints = [{
x: 2000 + CELL_SIZE / 2,
y: -100
}, {
x: 2000 + CELL_SIZE / 2,
y: 800 + CELL_SIZE / 2
}, {
x: 800 + CELL_SIZE / 2,
y: 800 + CELL_SIZE / 2
}, {
x: 800 + CELL_SIZE / 2,
y: 1600 + CELL_SIZE / 2
}, {
x: 3200 + CELL_SIZE / 2,
y: 1600 + CELL_SIZE / 2
}, {
x: 3200 + CELL_SIZE / 2,
y: 2400 + CELL_SIZE / 2
}, {
x: 1200 + CELL_SIZE / 2,
y: 2400 + CELL_SIZE / 2
}, {
x: 1200 + CELL_SIZE / 2,
y: 3200 + CELL_SIZE / 2
}, {
x: 2800 + CELL_SIZE / 2,
y: 3200 + CELL_SIZE / 2
}, {
x: 2800 + CELL_SIZE / 2,
y: 4000 + CELL_SIZE / 2
}, {
x: 2800 + CELL_SIZE / 2,
y: 4900
}];
var enemies = [];
var towers = [];
var projectiles = [];
var mapContainer = game.addChild(new Container());
mapContainer.width = MAP_WIDTH;
mapContainer.height = MAP_HEIGHT;
// Add background
var gameBackground = mapContainer.addChild(LK.getAsset('gameBackground', {
anchorX: 0,
anchorY: 0
}));
gameBackground.x = 0;
gameBackground.y = 0;
gameBackground.width = MAP_WIDTH;
gameBackground.height = MAP_HEIGHT;
var placingTowerConfig = null;
var ghostTower = null;
var isDraggingMap = false;
var dragStart = {
x: 0,
y: 0
};
var mapStart = {
x: 0,
y: 0
};
// --- Tower Configurations ---
var towerTypes = {
'1': {
asset: 'tower_1',
cost: 50,
damage: 15,
fireRate: 1.5,
projectileTypes: ['star']
},
'2': {
asset: 'tower_2',
cost: 100,
damage: 35,
fireRate: 1,
projectileTypes: ['arrow']
},
'3': {
asset: 'tower_3',
cost: 150,
damage: 60,
fireRate: 0.75,
projectileTypes: ['star', 'arrow']
}
};
// --- UI ---
LK.setScore(200); // Starting cash
var scoreTxt = new Text2('SCORE: ' + LK.getScore(), {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.y = 20;
LK.gui.top.addChild(scoreTxt);
var uiContainer = LK.gui.bottom.addChild(new Container());
uiContainer.y = -20; // Move it up slightly from the absolute bottom
// Create tower 1 group
var towerButton1 = uiContainer.addChild(LK.getAsset('uiButton', {
anchorX: 0.5,
anchorY: 1
}));
towerButton1.x = -300;
var towerIcon1 = uiContainer.addChild(LK.getAsset('tower_1', {
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
}));
towerIcon1.x = -305;
towerIcon1.y = -115;
towerIcon1.down = function (x, y, obj) {
startPlacingTower('1');
if (placingTowerConfig) {
towerIcon1.isDragging = true;
game.move(x, y, obj);
}
};
var towerText1 = uiContainer.addChild(new Text2('50', {
size: 50,
fill: '#fff',
anchorX: 0.5,
anchorY: 0.5
}));
towerText1.x = -305 - 25;
towerText1.y = -55;
// Create tower 2 group
var towerButton2 = uiContainer.addChild(LK.getAsset('uiButton', {
anchorX: 0.5,
anchorY: 1
}));
var towerIcon2 = uiContainer.addChild(LK.getAsset('tower_2', {
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
}));
towerIcon2.x = -5;
towerIcon2.y = -115;
towerIcon2.down = function (x, y, obj) {
startPlacingTower('2');
if (placingTowerConfig) {
towerIcon2.isDragging = true;
game.move(x, y, obj);
}
};
var towerText2 = uiContainer.addChild(new Text2('100', {
size: 50,
fill: '#fff',
anchorX: 0.5,
anchorY: 0.5
}));
towerText2.x = -15 - 25;
towerText2.y = -55;
// Create tower 3 group
var towerButton3 = uiContainer.addChild(LK.getAsset('uiButton', {
anchorX: 0.5,
anchorY: 1
}));
towerButton3.x = 300;
var towerIcon3 = uiContainer.addChild(LK.getAsset('tower_3', {
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
}));
towerIcon3.x = 300;
towerIcon3.y = -115;
towerIcon3.down = function (x, y, obj) {
startPlacingTower('3');
if (placingTowerConfig) {
towerIcon3.isDragging = true;
game.move(x, y, obj);
}
};
var towerText3 = uiContainer.addChild(new Text2('150', {
size: 50,
fill: '#fff',
anchorX: 0.5,
anchorY: 0.5
}));
towerText3.x = 285 - 25;
towerText3.y = -55;
// --- Initialization Functions ---
function initializeGrid() {
for (var i = 0; i < GRID_WIDTH; i++) {
grid[i] = [];
for (var j = 0; j < GRID_HEIGHT; j++) {
grid[i][j] = 0; // Empty
}
}
// Mark path tiles
for (var k = 0; k < pathWaypoints.length - 1; k++) {
var start = pathWaypoints[k];
var end = pathWaypoints[k + 1];
var p1 = {
x: Math.floor(start.x / CELL_SIZE),
y: Math.floor(start.y / CELL_SIZE)
};
var p2 = {
x: Math.floor(end.x / CELL_SIZE),
y: Math.floor(end.y / CELL_SIZE)
};
var x = p1.x;
var y = p1.y;
var dx = Math.abs(p2.x - x);
var dy = Math.abs(p2.y - y);
var sx = x < p2.x ? 1 : -1;
var sy = y < p2.y ? 1 : -1;
var err = dx - dy;
while (true) {
if (x >= 0 && x < GRID_WIDTH && y >= 0 && y < GRID_HEIGHT) {
grid[x][y] = 1;
}
if (x === p2.x && y === p2.y) {
break;
}
var e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x += sx;
}
if (e2 < dx) {
err += dx;
y += sy;
}
}
}
// Mark buildable tiles - only those on the sides of the road segments
for (var k = 0; k < pathWaypoints.length - 1; k++) {
var start = pathWaypoints[k];
var end = pathWaypoints[k + 1];
var p1 = {
x: Math.floor(start.x / CELL_SIZE),
y: Math.floor(start.y / CELL_SIZE)
};
var p2 = {
x: Math.floor(end.x / CELL_SIZE),
y: Math.floor(end.y / CELL_SIZE)
};
// Determine if this segment is horizontal or vertical
var isHorizontal = p1.y === p2.y;
var isVertical = p1.x === p2.x;
// Determine direction of movement to know which side to place tiles
var movingRight = p2.x > p1.x;
var movingDown = p2.y > p1.y;
var movingLeft = p2.x < p1.x;
var movingUp = p2.y < p1.y;
if (isVertical) {
// For vertical segments, place buildable tiles on left and right
var x = p1.x;
var minY = Math.min(p1.y, p2.y);
var maxY = Math.max(p1.y, p2.y);
for (var y = minY; y <= maxY; y++) {
// Only place on sides, avoiding corners where segments meet
var isCorner = false;
// Check if this position is a corner (where path changes direction)
for (var w = 0; w < pathWaypoints.length; w++) {
var wp = pathWaypoints[w];
if (Math.floor(wp.x / CELL_SIZE) === x && Math.floor(wp.y / CELL_SIZE) === y) {
isCorner = true;
break;
}
}
if (!isCorner) {
// Left side
if (x - 1 >= 0 && grid[x - 1][y] === 0) {
grid[x - 1][y] = 2;
}
// Right side
if (x + 1 < GRID_WIDTH && grid[x + 1][y] === 0) {
grid[x + 1][y] = 2;
}
}
}
} else if (isHorizontal) {
// For horizontal segments, only place on one side to avoid doubles
var y = p1.y;
var minX = Math.min(p1.x, p2.x);
var maxX = Math.max(p1.x, p2.x);
for (var x = minX; x <= maxX; x++) {
// Only place on one side (top) to avoid double placement
if (y - 1 >= 0 && grid[x][y - 1] === 0) {
grid[x][y - 1] = 2;
}
}
}
}
// Draw visual grid - buildable tiles first (background layer)
for (var i = 0; i < GRID_WIDTH; i++) {
for (var j = 0; j < GRID_HEIGHT; j++) {
if (grid[i][j] === 2) {
var buildableTile = mapContainer.addChild(LK.getAsset('buildableTile', {}));
buildableTile.alpha = 0.5;
buildableTile.x = i * CELL_SIZE;
buildableTile.y = j * CELL_SIZE;
}
}
}
// Draw path tiles on top
for (var i = 0; i < GRID_WIDTH; i++) {
for (var j = 0; j < GRID_HEIGHT; j++) {
if (grid[i][j] === 1) {
var pathTile = mapContainer.addChild(LK.getAsset('pathTile', {}));
pathTile.x = i * CELL_SIZE;
pathTile.y = j * CELL_SIZE;
}
}
}
}
initializeGrid();
function startPlacingTower(type) {
var config = towerTypes[type];
if (LK.getScore() >= config.cost) {
placingTowerConfig = config;
ghostTower = mapContainer.addChild(LK.getAsset(config.asset, {
anchorX: 0,
anchorY: 0
}));
ghostTower.alpha = 0.7;
}
}
// --- Event Handlers ---
game.down = function (x, y, obj) {
var localPoint = uiContainer.toLocal({
x: x,
y: y
});
// If the clicked object is a tower on the map, duplicate it for placement
if (obj && obj.target && (obj.target.isTower || obj.target.parent && obj.target.parent.isTower)) {
var clickedTower = obj.target.isTower ? obj.target : obj.target.parent;
// Find tower type from its config
var myType = null;
if (clickedTower.config) {
for (var key in towerTypes) {
if (towerTypes[key].asset === clickedTower.config.asset) {
myType = key;
break;
}
}
}
if (myType) {
startPlacingTower(myType);
if (placingTowerConfig) {
game.move(x, y, obj);
}
}
return; // Stop further processing (like map dragging)
}
if (!placingTowerConfig) {
isDraggingMap = true;
dragStart.x = x;
dragStart.y = y;
mapStart.x = mapContainer.x;
mapStart.y = mapContainer.y;
}
};
game.move = function (x, y, obj) {
if (placingTowerConfig) {
var mapPos = mapContainer.toLocal({
x: x,
y: y
});
// Position ghost tower directly at cursor position without anchor offset
ghostTower.x = mapPos.x;
ghostTower.y = mapPos.y;
var gridX = Math.floor(mapPos.x / CELL_SIZE);
var gridY = Math.floor(mapPos.y / CELL_SIZE);
if (gridX >= 0 && gridX < GRID_WIDTH && gridY >= 0 && gridY < GRID_HEIGHT && grid[gridX][gridY] === 2) {
ghostTower.tint = 0x00ff00; // Green for valid
} else {
ghostTower.tint = 0xff0000; // Red for invalid
}
} else if (isDraggingMap) {
var dx = x - dragStart.x;
var dy = y - dragStart.y;
// Clamp map position
var newX = mapStart.x + dx;
var newY = mapStart.y + dy;
var gameWidth = 2048;
var gameHeight = 2732;
if (newX > 0) {
newX = 0;
}
if (newY > 0) {
newY = 0;
}
if (newX < gameWidth - MAP_WIDTH) {
newX = gameWidth - MAP_WIDTH;
}
if (newY < gameHeight - MAP_HEIGHT) {
newY = gameHeight - MAP_HEIGHT;
}
mapContainer.x = newX;
mapContainer.y = newY;
}
};
game.up = function (x, y, obj) {
if (placingTowerConfig) {
var mapPos = mapContainer.toLocal({
x: x,
y: y
});
var gridX = Math.floor(mapPos.x / CELL_SIZE);
var gridY = Math.floor(mapPos.y / CELL_SIZE);
if (gridX >= 0 && gridX < GRID_WIDTH && gridY >= 0 && gridY < GRID_HEIGHT && grid[gridX][gridY] === 2) {
if (LK.getScore() >= placingTowerConfig.cost) {
var newTower = new Tower(placingTowerConfig);
newTower.x = gridX * CELL_SIZE + CELL_SIZE / 2;
newTower.y = gridY * CELL_SIZE + CELL_SIZE / 2;
mapContainer.addChild(newTower);
towers.push(newTower);
grid[gridX][gridY] = 3; // Mark as occupied
LK.setScore(LK.getScore() - placingTowerConfig.cost);
scoreTxt.setText('SCORE: ' + LK.getScore());
LK.getSound('place_tower').play();
}
}
ghostTower.destroy();
ghostTower = null;
placingTowerConfig = null;
// Reset drag states
towerIcon1.isDragging = false;
towerIcon2.isDragging = false;
towerIcon3.isDragging = false;
}
isDraggingMap = false;
};
// --- Game Logic Functions ---
function findClosestEnemy(x, y, range) {
var closestEnemy = null;
var minDistanceSq = range * range;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - x;
var dy = enemy.y - y;
var distanceSq = dx * dx + dy * dy;
if (distanceSq < minDistanceSq) {
minDistanceSq = distanceSq;
closestEnemy = enemy;
}
}
return closestEnemy;
}
function fireProjectile(tower, target) {
var projectileTypes = tower.config.projectileTypes;
for (var i = 0; i < projectileTypes.length; i++) {
var projectile = new Projectile({
x: tower.x,
y: tower.y
}, target, tower.damage, projectileTypes[i]);
// Flip arrow if enemy is to the left of tower_2 and projectile is arrow
if (tower.config.asset === 'tower_2' && projectileTypes[i] === 'arrow' && target.x < tower.x) {
projectile.scaleX = -1;
}
// Add rotation animation to star projectiles
if (projectileTypes[i] === 'star') {
tween(projectile, {
rotation: Math.PI * 4
}, {
duration: 2000
});
}
projectiles.push(projectile);
mapContainer.addChild(projectile);
}
LK.getSound('shoot').play();
}
function startWave() {
enemiesSpawnedInWave = 0;
waveBossKilled = false;
waveSpawnTimer = LK.setInterval(function () {
if (enemiesSpawnedInWave >= 20) {
LK.clearInterval(waveSpawnTimer);
waveSpawnTimer = null;
// Check for next wave after all enemies are defeated
return;
}
enemiesSpawnedInWave++;
var enemyType;
// Determine enemy type based on position in wave
if (currentWave === 3) {
// Wave 3: Mix of serpent1 and serpent2
if (enemiesSpawnedInWave === 19) {
// First boss - serpent1 boss
enemyType = 'boss1';
} else if (enemiesSpawnedInWave === 20) {
// Second boss - serpent2 boss
enemyType = 'boss2';
} else if (enemiesSpawnedInWave === 15 || enemiesSpawnedInWave === 17) {
// Big serpent1 enemies
enemyType = 'serpent1_big';
} else if (enemiesSpawnedInWave === 16 || enemiesSpawnedInWave === 18) {
// Big serpent2 enemies
enemyType = 'serpent2_big';
} else {
// Regular enemies - alternate between serpent1 and serpent2
enemyType = enemiesSpawnedInWave % 2 === 1 ? 'serpent1' : 'serpent2';
}
} else if (currentWave === 4) {
// Wave 4: serpent3 based wave
if (enemiesSpawnedInWave === 20) {
// Boss - serpent3 boss
enemyType = 'boss3';
} else if (enemiesSpawnedInWave === 18 || enemiesSpawnedInWave === 19) {
// Big serpent3 enemies
enemyType = 'serpent3_big';
} else {
// Regular serpent3 enemies
enemyType = 'serpent3';
}
} else if (currentWave === 5) {
// Wave 5: serpent4 based wave
if (enemiesSpawnedInWave === 20) {
// Boss - serpent4 boss
enemyType = 'boss4';
} else if (enemiesSpawnedInWave === 18 || enemiesSpawnedInWave === 19) {
// Big serpent4 enemies
enemyType = 'serpent4_big';
} else {
// Regular serpent4 enemies
enemyType = 'serpent4';
}
} else if (currentWave === 6) {
// Wave 6: Mix of all serpent types with two bosses
if (enemiesSpawnedInWave === 19) {
// First boss - serpent3 boss
enemyType = 'boss3';
} else if (enemiesSpawnedInWave === 20) {
// Second boss - serpent4 boss
enemyType = 'boss4';
} else if (enemiesSpawnedInWave === 15) {
// Big serpent1 enemy
enemyType = 'serpent1_big';
} else if (enemiesSpawnedInWave === 16) {
// Big serpent2 enemy
enemyType = 'serpent2_big';
} else if (enemiesSpawnedInWave === 17) {
// Big serpent3 enemy
enemyType = 'serpent3_big';
} else if (enemiesSpawnedInWave === 18) {
// Big serpent4 enemy
enemyType = 'serpent4_big';
} else {
// Regular enemies - cycle through all serpent types
var serpentTypes = ['serpent1', 'serpent2', 'serpent3', 'serpent4'];
enemyType = serpentTypes[(enemiesSpawnedInWave - 1) % 4];
}
} else {
// Fallback for other waves - single boss at enemy 20
if (enemiesSpawnedInWave === 20) {
// Boss - use wave-specific boss type
if (currentWave === 1) {
enemyType = 'boss1';
} else if (currentWave === 2) {
enemyType = 'boss2';
}
} else if (enemiesSpawnedInWave === 18 || enemiesSpawnedInWave === 19) {
// Big enemies - use wave-specific serpent type
if (currentWave === 1) {
enemyType = 'serpent1_big';
} else if (currentWave === 2) {
enemyType = 'serpent2_big';
}
} else {
// Regular enemy based on wave
if (currentWave === 1) {
enemyType = 'serpent1';
} else if (currentWave === 2) {
enemyType = 'serpent2';
}
}
}
var enemy = new Enemy(pathWaypoints, enemyType);
enemy.x = pathWaypoints[0].x;
enemy.y = pathWaypoints[0].y;
enemies.push(enemy);
mapContainer.addChild(enemy);
}, 800);
}
// Wave text will be created dynamically when needed
function showWaveText(waveNumber) {
// Set flag to indicate wave text is showing
isShowingWaveText = true;
// Create wave text dynamically
var waveText = new Text2('WAVE ' + waveNumber, {
size: 120,
fill: 0xFFA500 // Orange color
});
waveText.anchor.set(0.5, 0.5);
waveText.x = 2048 / 2;
waveText.y = 2732 / 2;
game.addChild(waveText);
// Create blinking animation with proper recursion control
var blinkCount = 0;
var maxBlinks = 6; // 3 complete blink cycles (fade out + fade in = 1 cycle, so 6 total for 3 cycles)
function blink() {
if (blinkCount >= maxBlinks) {
// Stop blinking and fade out
tween(waveText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
waveText.destroy();
// Clear flag when wave text is destroyed
isShowingWaveText = false;
// Start spawning enemies after wave text disappears
startWave();
}
});
return;
}
tween(waveText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
tween(waveText, {
alpha: 1
}, {
duration: 500,
onFinish: function onFinish() {
blinkCount++;
blink();
}
});
}
});
}
blink();
}
// Show initial wave text (startWave will be called after text animation)
showWaveText(1);
game.update = function () {
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].update();
}
for (var i = towers.length - 1; i >= 0; i--) {
towers[i].update();
}
for (var i = projectiles.length - 1; i >= 0; i--) {
projectiles[i].update();
}
// Check if wave is complete and start next wave
if (enemiesSpawnedInWave === 20 && waveBossKilled && !waveSpawnTimer && !isShowingWaveText && enemies.length === 0) {
currentWave++;
if (currentWave <= 6) {
// Reset wave state for next wave
enemiesSpawnedInWave = 0;
waveBossKilled = false;
// Show wave text for new wave (startWave will be called after text animation)
showWaveText(currentWave);
} else {
// All waves completed, show you win screen
LK.showYouWin();
}
}
};