/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) { var self = Container.call(this); self.targetEnemy = targetEnemy; self.damage = damage || 10; self.speed = speed || 5; self.x = startX; self.y = startY; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { // Apply damage to target enemy var actualDamage = self.damage; // In Hot Potato challenge, multiply CapyBomb and CapyDemolition damage by 20x against bosses if (challengeMode && window.challengeName === 'Hot Potato') { if ((self.type === 'bomb' || self.type === 'demolition') && self.targetEnemy.isBoss) { actualDamage = self.damage * 20; } } self.targetEnemy.health -= actualDamage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } else { self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; } // Apply special effects based on bullet type if (self.type === 'splash') { // Create visual splash effect var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); game.addChild(splashEffect); // Splash damage to nearby enemies var splashRadius = CELL_SIZE * 1.5; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self.targetEnemy) { var splashDx = otherEnemy.x - self.targetEnemy.x; var splashDy = otherEnemy.y - self.targetEnemy.y; var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy); if (splashDistance <= splashRadius) { // Apply splash damage (50% of original damage) otherEnemy.health -= self.damage * 0.5; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } else { otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70; } } } } } else if (self.type === 'slow') { // Prevent slow effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual slow effect var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); // Apply slow effect // Make slow percentage scale with tower level (default 50%, up to 80% at max level) var slowPct = 0.5; if (self.sourceTowerLevel !== undefined) { // Scale: 50% at level 1, 60% at 2, 65% at 3, 70% at 4, 75% at 5, 80% at 6 var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8]; var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1)); slowPct = slowLevels[idx]; } if (!self.targetEnemy.slowed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; self.targetEnemy.speed *= 1 - slowPct; // Slow by X% self.targetEnemy.slowed = true; self.targetEnemy.slowDuration = 180; // 3 seconds at 60 FPS } else { self.targetEnemy.slowDuration = 180; // Reset duration } } } else if (self.type === 'queen') { // Prevent slow effect on immune enemies (Queen tower with enhanced effects) if (!self.targetEnemy.isImmune) { // Create visual slow effect var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); // Apply enhanced slow effect - double the reduction and duration // Make slow percentage scale with tower level (default 100%, up to 160% at max level) var slowPct = 1.0; // Double the base slow effect if (self.sourceTowerLevel !== undefined) { // Scale: 100% at level 1, 120% at 2, 130% at 3, 140% at 4, 150% at 5, 160% at 6 var slowLevels = [1.0, 1.2, 1.3, 1.4, 1.5, 1.6]; var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1)); slowPct = slowLevels[idx]; } // Cap slow percentage to prevent negative speeds slowPct = Math.min(slowPct, 0.95); // Maximum 95% slow to prevent stopping if (!self.targetEnemy.slowed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; self.targetEnemy.speed *= 1 - slowPct; // Slow by X% self.targetEnemy.slowed = true; self.targetEnemy.slowDuration = 360; // 6 seconds at 60 FPS (double duration) } else { self.targetEnemy.slowDuration = 360; // Reset duration (double) } } } else if (self.type === 'poison') { // Prevent poison effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual poison effect var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Apply poison effect self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS } } else if (self.type === 'bomb') { // Prevent poison effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual poison effect var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Apply poison effect self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS } } else if (self.type === 'demolition') { // Prevent poison effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual poison effect var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Apply poison effect self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS } } else if (self.type === 'prince') { // Create visual splash effect var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); game.addChild(splashEffect); // Splash damage to nearby enemies var splashRadius = CELL_SIZE * 1.5; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self.targetEnemy) { var splashDx = otherEnemy.x - self.targetEnemy.x; var splashDy = otherEnemy.y - self.targetEnemy.y; var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy); if (splashDistance <= splashRadius) { // Apply splash damage (50% of original damage) otherEnemy.health -= self.damage * 0.5; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } else { otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70; } } } } } else if (self.type === 'princess') { // Prevent poison effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual poison effect var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Apply poison effect self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS } } else if (self.type === 'sniper') { // Create visual critical hit effect for sniper var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper'); game.addChild(sniperEffect); } self.destroy(); } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); var CapyChallengesScreen = Container.expand(function () { var self = Container.call(this); // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Title shadow var titleShadow = new Text2("Capy Challenges", { size: 120, fill: 0x000000, weight: 800 }); titleShadow.anchor.set(0.5, 0.5); titleShadow.x = 4; titleShadow.y = -300 + 4; self.addChild(titleShadow); // Title var title = new Text2("Capy Challenges", { size: 120, fill: 0xFF6600, weight: 800 }); title.anchor.set(0.5, 0.5); title.y = -300; self.addChild(title); // Cold Blast button var coldBlastButton = new Container(); var coldBlastBg = coldBlastButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); coldBlastBg.width = 600; coldBlastBg.height = 150; coldBlastBg.tint = 0x0088FF; var coldBlastTextShadow = new Text2("Cold Blast", { size: 70, fill: 0x000000, weight: 800 }); coldBlastTextShadow.anchor.set(0.5, 0.5); coldBlastTextShadow.x = 2; coldBlastTextShadow.y = 2; coldBlastButton.addChild(coldBlastTextShadow); var coldBlastText = new Text2("Cold Blast", { size: 70, fill: 0xFFFFFF, weight: 800 }); coldBlastText.anchor.set(0.5, 0.5); coldBlastButton.addChild(coldBlastText); coldBlastButton.x = 0; coldBlastButton.y = -100; self.addChild(coldBlastButton); // Hot Potato button var hotPotatoButton = new Container(); var hotPotatoBg = hotPotatoButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); hotPotatoBg.width = 600; hotPotatoBg.height = 150; hotPotatoBg.tint = 0xFF4400; var hotPotatoTextShadow = new Text2("Hot Potato", { size: 70, fill: 0x000000, weight: 800 }); hotPotatoTextShadow.anchor.set(0.5, 0.5); hotPotatoTextShadow.x = 2; hotPotatoTextShadow.y = 2; hotPotatoButton.addChild(hotPotatoTextShadow); var hotPotatoText = new Text2("Hot Potato", { size: 70, fill: 0xFFFFFF, weight: 800 }); hotPotatoText.anchor.set(0.5, 0.5); hotPotatoButton.addChild(hotPotatoText); hotPotatoButton.x = 0; hotPotatoButton.y = 100; self.addChild(hotPotatoButton); // Back button var backButton = new Container(); var backBg = backButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); backBg.width = 400; backBg.height = 100; backBg.tint = 0xCC0000; var backTextShadow = new Text2("Back", { size: 60, fill: 0x000000, weight: 800 }); backTextShadow.anchor.set(0.5, 0.5); backTextShadow.x = 2; backTextShadow.y = 2; backButton.addChild(backTextShadow); var backText = new Text2("Back", { size: 60, fill: 0xFFFFFF, weight: 800 }); backText.anchor.set(0.5, 0.5); backButton.addChild(backText); backButton.x = 0; backButton.y = 660; self.addChild(backButton); // Cold Blast button handler coldBlastButton.down = function (x, y, obj) { // Start Cold Blast challenge - only CapyFreeze, CapyBomb and CapyDemolition towers self.startChallenge('Cold Blast', ['slow', 'bomb', 'demolition']); }; // Hot Potato button handler hotPotatoButton.down = function (x, y, obj) { // Start Hot Potato challenge - only CapySniper, CapyBomb and CapyDemolition towers self.startChallenge('Hot Potato', ['sniper', 'bomb', 'demolition']); }; // Queen's Walk button var queensWalkButton = new Container(); var queensWalkBg = queensWalkButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); queensWalkBg.width = 600; queensWalkBg.height = 150; queensWalkBg.tint = 0x9900FF; var queensWalkTextShadow = new Text2("Queen's Walk", { size: 70, fill: 0x000000, weight: 800 }); queensWalkTextShadow.anchor.set(0.5, 0.5); queensWalkTextShadow.x = 2; queensWalkTextShadow.y = 2; queensWalkButton.addChild(queensWalkTextShadow); var queensWalkText = new Text2("Queen's Walk", { size: 70, fill: 0xFFFFFF, weight: 800 }); queensWalkText.anchor.set(0.5, 0.5); queensWalkButton.addChild(queensWalkText); queensWalkButton.x = 0; queensWalkButton.y = 300; self.addChild(queensWalkButton); // King's Gold button var kingsGoldButton = new Container(); var kingsGoldBg = kingsGoldButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); kingsGoldBg.width = 600; kingsGoldBg.height = 150; kingsGoldBg.tint = 0xFFD700; var kingsGoldTextShadow = new Text2("King's Gold", { size: 70, fill: 0x000000, weight: 800 }); kingsGoldTextShadow.anchor.set(0.5, 0.5); kingsGoldTextShadow.x = 2; kingsGoldTextShadow.y = 2; kingsGoldButton.addChild(kingsGoldTextShadow); var kingsGoldText = new Text2("King's Gold", { size: 70, fill: 0xFFFFFF, weight: 800 }); kingsGoldText.anchor.set(0.5, 0.5); kingsGoldButton.addChild(kingsGoldText); kingsGoldButton.x = 0; kingsGoldButton.y = 480; self.addChild(kingsGoldButton); // Queen's Walk button handler queensWalkButton.down = function (x, y, obj) { // Start Queen's Walk challenge - only CapyQueen, CapyPult, and CapyPrincess towers self.startChallenge("Queen's Walk", ['queen', 'splash', 'princess']); }; // King's Gold button handler kingsGoldButton.down = function (x, y, obj) { // Start King's Gold challenge - only CapyKing, CapyFreeze, and CapyPrince towers self.startChallenge("King's Gold", ['king', 'slow', 'prince']); }; // Challenge starter function self.startChallenge = function (challengeName, allowedTowers) { // Set challenge mode variables tutorialMode = false; selectedLevel = 'lobby'; // Use lobby level as base difficultyMultiplier = 1.0; // Normal difficulty totalWaves = 25; // Shorter challenge nextWaveTime = 12000 / 2 * 0.8; // Faster waves // Set challenge-specific starting gold if (challengeName === "King's Gold") { setGold(240); // King's Gold specific starting gold } else { setGold(270); // More starting gold for other challenges } challengeMode = true; challengeAllowedTowers = allowedTowers; // Set global challengeName variable properly window.challengeName = challengeName; // Regenerate grid with lobby configuration // Regenerate grid based on challenge type debugLayer.removeChild(grid); grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; var wallSquares; if (challengeName === 'Cold Blast') { // Define positions for 4 wall squares (4x4 each) arranged in 2 rows, 2 columns wallSquares = [{ startX: 6, startY: 8 }, { startX: 14, startY: 8 }, { startX: 6, startY: 18 }, { startX: 14, startY: 18 }]; // Create the wall squares for Cold Blast for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { var cell = grid.getCell(square.startX + i, square.startY + j); if (cell && cell.type === 0) { cell.type = 1; // Set to wall type } } } } } else if (challengeName === 'Hot Potato') { // Hot Potato challenge has no wall squares - keep grid clear // Paint floor cells blue in bottom third of map for Hot Potatoe challenge if (challengeName === 'Hot Potato') { var gridWidth = 24; var gridHeight = 29 + 6; var bottomThirdStart = Math.floor(gridHeight * 2 / 3); // Start of bottom third // Paint floor cells blue in bottom third for (var i = 0; i < gridWidth; i++) { for (var j = bottomThirdStart; j < gridHeight - 4; j++) { var cell = grid.getCell(i, j); if (cell && cell.type === 0) { // Only paint floor cells // Mark cell for blue painting - this will be handled in the render debug cell.hotPotatoeBlue = true; } } } } } else if (challengeName === "Queen's Walk") { // Queen's Walk: no wall squares, keep garden level clear } else if (challengeName === "King's Gold") { // King's Gold: Create 4 wall cell squares for Great Hall level var gridHeight = 29 + 6; var wallSquares = [{ startX: 1, startY: gridHeight - 10 // Left border square, moved up 2 cells }, { startX: 19, startY: gridHeight - 10 // Right border square, moved up 2 cells }, { startX: 6, startY: 12 // Middle left square }, { startX: 14, startY: 12 // Middle right square }]; // Create the wall squares for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { var cell = grid.getCell(square.startX + i, square.startY + j); if (cell && cell.type === 0) { cell.type = 1; // Set to wall type } } } } } else { // Define positions for the 3 wall squares (4x4 each) - same as lobby for other challenges wallSquares = [{ startX: 4, startY: 10 }, { startX: 10, startY: 16 }, { startX: 16, startY: 10 }]; // Create the wall squares for other challenges for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { var cell = grid.getCell(square.startX + i, square.startY + j); if (cell && cell.type === 0) { cell.type = 1; // Set to wall type } } } } } grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Force re-render after Hot Potatoe challenge setup to apply blue painting if (challengeName === 'Hot Potato') { // Wait a frame then force re-render to ensure blue floor cells are painted LK.setTimeout(function () { grid.renderDebug(); }, 100); } // Create images over the 4x4 wall squares only for challenges that have them if (challengeName === 'Cold Blast') { // Use independent coldblast square assets for the 4 squares var squareAssets = ['coldblast_square_1', 'coldblast_square_2', 'coldblast_square_3', 'coldblast_square_4']; for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; var squareImage = game.attachAsset(squareAssets[s], { anchorX: 0.5, anchorY: 0.5 }); // Apply cold blue tint to distinguish from lobby assets squareImage.tint = 0x88CCFF; var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE; var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE; squareImage.x = squareCenterX - CELL_SIZE / 2; squareImage.y = squareCenterY - CELL_SIZE / 2; towerLayer.addChild(squareImage); lobbySquareImages.push(squareImage); } } else if (challengeName === 'Hot Potato') { // Hot Potato: no square images, keep grid clear // Define positions for the 3 wall squares (4x4 each) - same as lobby for Hot Potato challenges var hotPotatoWallSquares = [{ startX: 4, startY: 10 }, { startX: 10, startY: 16 }, { startX: 16, startY: 10 }]; // No square images created for Hot Potato challenge } else if (challengeName === "Queen's Walk") { // Queen's Walk: no square images, keep garden level clear } else if (challengeName === "King's Gold") { // King's Gold: Create images over the 4 great hall wall squares var squareAssets = ['greathall_square_1', 'greathall_square_2', 'greathall_square_1', 'greathall_square_2']; var gridHeight = 29 + 6; var wallSquares = [{ startX: 1, startY: gridHeight - 10 }, { startX: 19, startY: gridHeight - 10 }, { startX: 6, startY: 12 }, { startX: 14, startY: 12 }]; for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; var squareImage = game.attachAsset(squareAssets[s], { anchorX: 0.5, anchorY: 0.5 }); var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE; var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE; squareImage.x = squareCenterX - CELL_SIZE / 2; squareImage.y = squareCenterY - CELL_SIZE / 2; towerLayer.addChild(squareImage); lobbySquareImages.push(squareImage); } } else { // Other challenges use 3 lobby square assets var squareAssets = ['lobby_square_1', 'lobby_square_2', 'lobby_square_3']; for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; var squareImage = game.attachAsset(squareAssets[s], { anchorX: 0.5, anchorY: 0.5 }); var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE; var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE; squareImage.x = squareCenterX - CELL_SIZE / 2; squareImage.y = squareCenterY - CELL_SIZE / 2; towerLayer.addChild(squareImage); lobbySquareImages.push(squareImage); } } // Update background based on challenge var newBackgroundAssetName; if (challengeName === 'Cold Blast') { newBackgroundAssetName = 'background_coldblast'; } else if (challengeName === 'Hot Potato') { newBackgroundAssetName = 'background_hotpotato'; } else if (challengeName === "Queen's Walk") { newBackgroundAssetName = 'background_queens_walk'; } else if (challengeName === "King's Gold") { newBackgroundAssetName = 'background_greathall'; } else { newBackgroundAssetName = 'background_lobby'; } game.removeChild(backgroundImage); backgroundImage = game.attachAsset(newBackgroundAssetName, { anchorX: 0.5, anchorY: 0.5 }); // Apply cold blue tint for Cold Blast challenge if (challengeName === 'Cold Blast') { backgroundImage.tint = 0xCCDDFF; } backgroundImage.x = 2048 / 2; backgroundImage.y = 2732 / 2; backgroundImage.width = 2048; backgroundImage.height = 2732; game.addChildAt(backgroundImage, 0); // Clear existing source towers and recreate them for challenge mode for (var i = sourceTowers.length - 1; i >= 0; i--) { var sourceTower = sourceTowers[i]; if (sourceTower.parent) { sourceTower.parent.removeChild(sourceTower); } } sourceTowers = []; // Create source towers with challenge-specific types ordered by challenge var challengeTowerTypes; if (challengeName === 'Cold Blast') { // Cold Blast: CapyFreeze, CapyBomb, CapyDemolition challengeTowerTypes = ['slow', 'bomb', 'demolition']; } else if (challengeName === 'Hot Potato') { // Hot Potato: CapySniper, CapyBomb, CapyDemolition challengeTowerTypes = ['sniper', 'bomb', 'demolition']; } else if (challengeName === "Queen's Walk") { // Queen's Walk: CapyQueen, CapyPult, CapyPrincess challengeTowerTypes = ['queen', 'splash', 'princess']; } else if (challengeName === "King's Gold") { // King's Gold: CapyKing, CapyFreeze, CapyPrince challengeTowerTypes = ['king', 'slow', 'prince']; } else { // Fallback to all types if challenge name doesn't match challengeTowerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison', 'bomb', 'demolition', 'queen', 'king']; } var towerSpacing = 320; var startX = 2048 / 2 - challengeTowerTypes.length * towerSpacing / 2 + towerSpacing / 2; var towerY = 2732 - CELL_SIZE * 3 - 90; for (var t = 0; t < challengeTowerTypes.length; t++) { var tower = new SourceTower(challengeTowerTypes[t]); tower.x = startX + t * towerSpacing; tower.y = towerY; tower.scalingInitialized = false; // Show/hide based on allowed towers for this challenge if (allowedTowers.indexOf(tower.towerType) === -1) { tower.visible = false; tower.alpha = 0.3; } else { tower.visible = true; tower.alpha = 1; } towerLayer.addChild(tower); sourceTowers.push(tower); } // Show start game button if (waveIndicator) { waveIndicator.showStartButton(); } // Show challenge-specific instruction screen if (challengeName === 'Cold Blast') { var instructionsScreen = new ColdBlastInstructionsScreen(); instructionsScreen.x = 2048 / 2; instructionsScreen.y = 2732 / 2; game.addChild(instructionsScreen); } else if (challengeName === 'Hot Potato') { var instructionsScreen = new HotPotatoInstructionsScreen(); instructionsScreen.x = 2048 / 2; instructionsScreen.y = 2732 / 2; game.addChild(instructionsScreen); } else if (challengeName === "Queen's Walk") { var instructionsScreen = new QueensWalkInstructionsScreen(); instructionsScreen.x = 2048 / 2; instructionsScreen.y = 2732 / 2; game.addChild(instructionsScreen); } else if (challengeName === "King's Gold") { var instructionsScreen = new KingsGoldInstructionsScreen(); instructionsScreen.x = 2048 / 2; instructionsScreen.y = 2732 / 2; game.addChild(instructionsScreen); } else { // Show selection feedback for other challenges var notification = game.addChild(new Notification(challengeName + " Challenge Started!")); notification.x = 2048 / 2; notification.y = 2732 / 2; } // Remove challenges screen self.destroy(); }; // Back button handler - return to mode selector backButton.down = function (x, y, obj) { // Remove challenges screen self.destroy(); // Show mode selector var modeSelector = new ModeSelector(); modeSelector.x = 2048 / 2; modeSelector.y = 2732 / 2; game.addChild(modeSelector); }; return self; }); var CapybarasInfoScreen = Container.expand(function () { var self = Container.call(this); // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Title shadow var titleShadow = new Text2("Capybara Towers", { size: 120, fill: 0x000000, weight: 800 }); titleShadow.anchor.set(0.5, 0.5); titleShadow.x = 4; titleShadow.y = -1200 + 4; self.addChild(titleShadow); // Title var title = new Text2("Capybara Towers", { size: 120, fill: 0x8B4513, weight: 800 }); title.anchor.set(0.5, 0.5); title.y = -1200; self.addChild(title); // Tower information data var towerData = [{ name: "CapyShot", type: "default", cost: "5 gold", description: "Basic balanced tower with moderate damage and range" }, { name: "CapyBlast", type: "rapid", cost: "15 gold", description: "Fast firing tower with low damage per shot" }, { name: "CapySniper", type: "sniper", cost: "25 gold", description: "Long range tower with high damage" }, { name: "CapyPult", type: "splash", cost: "35 gold", description: "Area damage tower that hits multiple enemies" }, { name: "CapyFreeze", type: "slow", cost: "45 gold", description: "Slows down enemies with special effects" }, { name: "CapyPoison", type: "poison", cost: "55 gold", description: "Applies poison damage over time" }, { name: "CapyBomb", type: "bomb", cost: "80 gold", description: "Explodes after 2 seconds dealing massive area damage" }, { name: "CapyQueen", type: "queen", cost: "200 gold", description: "Enhanced freeze tower with increased effects" }, { name: "CapyKing", type: "king", cost: "250 gold", description: "Ultimate sniper tower with devastating damage" }, { name: "CapyDemolition", type: "demolition", cost: "150 gold", description: "Explodes after 2 seconds dealing massive area damage" }, { name: "CapyPrince", type: "prince", cost: "130 gold", description: "Enhanced splash tower with double range and double damage" }, { name: "CapyPrincess", type: "princess", cost: "130 gold", description: "Enhanced poison tower with double range effects" }]; // Create scrollable content container var contentContainer = new Container(); self.addChild(contentContainer); // Create tower information entries var startY = -1000; var entryHeight = 200; for (var i = 0; i < towerData.length; i++) { var tower = towerData[i]; var entryContainer = new Container(); contentContainer.addChild(entryContainer); // Tower image var towerImage = entryContainer.attachAsset('tower_' + tower.type, { anchorX: 0.5, anchorY: 0.5 }); towerImage.x = -700; towerImage.y = startY + i * entryHeight; towerImage.width = 138; // Increased by 15% (120 * 1.15 = 138) towerImage.height = 138; // Increased by 15% (120 * 1.15 = 138) // Start continuous scaling animation for tower image (function (image) { function scaleUp() { tween(image, { scaleX: 1.2, scaleY: 1.2 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleDown(); } }); } function scaleDown() { tween(image, { scaleX: 1.0, scaleY: 1.0 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleUp(); } }); } scaleUp(); })(towerImage); // Tower name shadow var nameShadow = new Text2(tower.name, { size: 60, fill: 0x000000, weight: 800 }); nameShadow.anchor.set(0, 0.5); nameShadow.x = -500 + 2; nameShadow.y = startY + i * entryHeight - 30 + 2; entryContainer.addChild(nameShadow); // Tower name var nameText = new Text2(tower.name, { size: 60, fill: 0xFFD700, weight: 800 }); nameText.anchor.set(0, 0.5); nameText.x = -500; nameText.y = startY + i * entryHeight - 30; entryContainer.addChild(nameText); // Cost shadow var costShadow = new Text2(tower.cost, { size: 45, fill: 0x000000, weight: 600 }); costShadow.anchor.set(0, 0.5); costShadow.x = -500 + 2; costShadow.y = startY + i * entryHeight + 10 + 2; entryContainer.addChild(costShadow); // Cost text var costText = new Text2(tower.cost, { size: 45, fill: 0x00FF00, weight: 600 }); costText.anchor.set(0, 0.5); costText.x = -500; costText.y = startY + i * entryHeight + 10; entryContainer.addChild(costText); // Description shadow var descShadow = new Text2(tower.description, { size: 40, fill: 0x000000, weight: 400 }); descShadow.anchor.set(0, 0.5); descShadow.x = -500 + 2; descShadow.y = startY + i * entryHeight + 50 + 2; descShadow.wordWrap = true; descShadow.wordWrapWidth = 1000; entryContainer.addChild(descShadow); // Description text var descText = new Text2(tower.description, { size: 40, fill: 0xFFFFFF, weight: 400 }); descText.anchor.set(0, 0.5); descText.x = -500; descText.y = startY + i * entryHeight + 50; descText.wordWrap = true; descText.wordWrapWidth = 1000; entryContainer.addChild(descText); } // Back button var backButton = new Container(); var backBg = backButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); backBg.width = 400; backBg.height = 100; backBg.tint = 0xCC0000; var backTextShadow = new Text2("Back", { size: 60, fill: 0x000000, weight: 800 }); backTextShadow.anchor.set(0.5, 0.5); backTextShadow.x = 2; backTextShadow.y = 2; backButton.addChild(backTextShadow); var backText = new Text2("Back", { size: 60, fill: 0xFFFFFF, weight: 800 }); backText.anchor.set(0.5, 0.5); backButton.addChild(backText); backButton.x = -800; backButton.y = -1200; self.addChild(backButton); // Back button handler - return to mode selector backButton.down = function (x, y, obj) { // Remove capybaras info screen self.destroy(); // Show mode selector var modeSelector = new ModeSelector(); modeSelector.x = 2048 / 2; modeSelector.y = 2732 / 2; game.addChild(modeSelector); }; return self; }); var ColdBlastInstructionsScreen = Container.expand(function () { var self = Container.call(this); // Pause the game when instructions screen is shown self.gamePaused = true; // Hide UI labels when instructions screen is shown goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Cold Blast instruction image at the bottom var instructionsImage = self.attachAsset('coldblast_instructions_image', { anchorX: 0.5, anchorY: 1.0 }); instructionsImage.x = 0; instructionsImage.y = 2732 / 2 - 100; // Position higher up instructionsImage.width = 800; instructionsImage.height = 800; // Instructions text label in the center of the screen var instructionsTextShadow = new Text2("Welcome to Cold Blast Challenge!\nBrr! The weather is freezing!\nOnly CapyFreeze, CapyBomb and CapyDemolition towers work in this cold.\nUse the icy environment to your advantage.\nGood luck, defender!\n", { size: 55, fill: 0x000000, weight: 800, align: 'center' }); instructionsTextShadow.anchor.set(0.5, 0.5); instructionsTextShadow.x = 4; // Shadow offset instructionsTextShadow.y = 4; // Centered vertically with shadow offset // Set maximum width to ensure text wraps properly instructionsTextShadow.wordWrap = true; instructionsTextShadow.wordWrapWidth = 1800; self.addChild(instructionsTextShadow); var instructionsText = new Text2("Welcome to Cold Blast Challenge!\nBrr! The weather is freezing!\nOnly CapyFreeze, CapyBomb and CapyDemolition towers work in this cold.\nUse the icy environment to your advantage.\nGood luck, defender!\n", { size: 55, fill: 0xFFFFFF, weight: 800, align: 'center' }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 0; instructionsText.y = 0; // Centered vertically in screen // Set maximum width to ensure text wraps properly instructionsText.wordWrap = true; instructionsText.wordWrapWidth = 1800; self.addChild(instructionsText); // Continue button at the top var continueButton = new Container(); var buttonBg = continueButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 150; buttonBg.tint = 0x0088FF; var buttonTextShadow = new Text2("Continue", { size: 80, fill: 0x000000, weight: 800 }); buttonTextShadow.anchor.set(0.5, 0.5); buttonTextShadow.x = 2; buttonTextShadow.y = 2; continueButton.addChild(buttonTextShadow); var buttonText = new Text2("Continue", { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); continueButton.addChild(buttonText); continueButton.x = 0; continueButton.y = -2732 / 2 + 300; // Position at top self.addChild(continueButton); // Continue button handler - close the screen continueButton.down = function (x, y, obj) { // Unpause the game self.gamePaused = false; // Show UI labels when continue button is pressed goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; // Remove instructions screen self.destroy(); }; return self; }); var DebugCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = self.attachAsset(getCellAssetName(), { anchorX: 0.5, anchorY: 0.5 }); cellGraphics.tint = Math.random() * 0xffffff; var debugArrows = []; 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) { // Check if we need to update the cell asset based on current level var currentAssetName = getCellAssetName(); if (cellGraphics.assetName !== currentAssetName) { // Remove old graphics self.removeChild(cellGraphics); // Create new graphics with correct asset for current level cellGraphics = self.attachAsset(currentAssetName, { anchorX: 0.5, anchorY: 0.5 }); cellGraphics.assetName = currentAssetName; } switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); cellGraphics.tint = 0x880000; return; } numberLabel.visible = false; var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; cellGraphics.tint = 0x0088ff; } else { // Check if this is Great Hall level and we're in the middle path from entrance to exit if (selectedLevel === 'greathall') { var gridWidth = 24; var gridHeight = 29 + 6; var centerX = Math.floor(gridWidth / 2); // Center column (12) // Check if current cell is in the center column path or adjacent cells from entrance (top) to exit (bottom) if (data.x >= centerX - 3 && data.x <= centerX + 2 && data.y >= 5 && data.y <= gridHeight - 5) { cellGraphics.tint = 0xFF0000; // Red color for the middle path and adjacent cells } else { cellGraphics.tint = 0xFFFFFF; // Remove blue background - set to white/transparent } } else if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) { // Paint floor cells blue in bottom third of map for Hot Potatoe challenge var gridWidth = 24; var gridHeight = 29 + 6; var bottomThirdStart = Math.floor(gridHeight * 2 / 3); // Start of bottom third if (data.y >= bottomThirdStart && data.y < gridHeight - 4) { tween(cellGraphics, { tint: 0x0088FF }, { duration: 500, easing: tween.easeOut }); } else { cellGraphics.tint = 0xFFFFFF; // Remove blue background - set to white/transparent } } else { cellGraphics.tint = 0xFFFFFF; // Remove blue background - set to white/transparent } } 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 = 0; self.addChildAt(debugArrows[a], 1); } debugArrows[a].rotation = angle; debugArrows[a].alpha = 0; } break; } case 1: { self.removeArrows(); // Check if this is tutorial mode and make wall cells invisible if (tutorialMode) { cellGraphics.alpha = 0; // Make wall cells invisible in tutorial mode } else if (selectedLevel === 'pool') { var gridWidth = 24; var gridHeight = 29 + 6; var centerX = Math.floor(gridWidth / 2); // Center column (12) var centerY = Math.floor(gridHeight / 2); // Center row var rectWidth = 6; // Rectangle width (6 cells) var rectHeight = 6; // Rectangle height (6 cells) var rectLeft = centerX - Math.floor(rectWidth / 2); var rectRight = centerX + Math.floor(rectWidth / 2); var rectTop = centerY - Math.floor(rectHeight / 2); var rectBottom = centerY + Math.floor(rectHeight / 2); // Check if current cell is within the rectangular pool zone if (data.x >= rectLeft && data.x < rectRight && data.y >= rectTop && data.y < rectBottom) { // Use gray color for the rectangular pool area tween(cellGraphics, { tint: 0x888888 }, { duration: 500, easing: tween.easeOut }); } else { cellGraphics.tint = 0xaaaaaa; // Regular wall color } } else { cellGraphics.tint = 0xaaaaaa; // Regular wall color for other levels } numberLabel.visible = false; break; } case 3: { self.removeArrows(); cellGraphics.tint = 0x008800; numberLabel.visible = false; break; } } numberLabel.setText(Math.floor(data.score / 1000) / 10); }; }); var Diamond = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; // Use independent diamond asset based on challenge var assetName = 'diamond_queens_walk'; // Default to Queen's Walk diamond if (challengeMode && (challengeName === "Queen's Walk" || window.challengeName === "Queen's Walk")) { assetName = 'diamond_queens_walk'; } var diamondGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Make diamonds a bit smaller by setting width and height directly diamondGraphics.width = 100; // Reduced from 120px to 100px diamondGraphics.height = 83.2; // Reduced from 99.84px to 83.2px (maintaining aspect ratio) // Remove rotation to keep diamonds straight // Add pulsing animation self.pulseTimer = 0; self.update = function () { self.pulseTimer += 0.1; var pulseScale = 1 + 0.2 * Math.sin(self.pulseTimer); diamondGraphics.width = 100 * pulseScale; diamondGraphics.height = 83.2 * pulseScale; }; return self; }); var DifficultySelector = Container.expand(function () { var self = Container.call(this); // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Title var titleShadow = new Text2("Select Difficulty", { size: 120, fill: 0x000000, weight: 800 }); titleShadow.anchor.set(0.5, 0.5); titleShadow.x = 4; titleShadow.y = -800; self.addChild(titleShadow); var title = new Text2("Select Difficulty", { size: 120, fill: 0xFFFFFF, weight: 800 }); title.anchor.set(0.5, 0.5); title.y = -800; self.addChild(title); // Difficulty buttons var difficulties = [{ name: "Easy", color: 0x00AA00, multiplier: 0.7, description: "Enemies have 30% less health" }, { name: "Normal", color: 0x0088FF, multiplier: 1.0, description: "Standard difficulty" }, { name: "Hard", color: 0xFFFF00, multiplier: 1.5, description: "Enemies have 50% more health" }, { name: "Insane", color: 0xFF0000, multiplier: 2.0, description: "Enemies have 100% more health" }]; var buttonSpacing = 300; var startY = -200; for (var i = 0; i < difficulties.length; i++) { var difficulty = difficulties[i]; var button = new Container(); var buttonBg = button.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 200; buttonBg.tint = difficulty.color; // Button title shadow var buttonTitleShadow = new Text2(difficulty.name, { size: 80, fill: 0x000000, weight: 800 }); buttonTitleShadow.anchor.set(0.5, 0.5); buttonTitleShadow.x = 4; buttonTitleShadow.y = -20; button.addChild(buttonTitleShadow); // Button title var buttonTitle = new Text2(difficulty.name, { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonTitle.anchor.set(0.5, 0.5); buttonTitle.y = -20; button.addChild(buttonTitle); // Description shadow var descShadow = new Text2(difficulty.description, { size: 40, fill: 0x000000, weight: 400 }); descShadow.anchor.set(0.5, 0.5); descShadow.x = 2; descShadow.y = 30; button.addChild(descShadow); // Description var desc = new Text2(difficulty.description, { size: 40, fill: 0xFFFFFF, weight: 400 }); desc.anchor.set(0.5, 0.5); desc.y = 28; button.addChild(desc); button.y = startY + i * buttonSpacing; button.difficulty = difficulty; self.addChild(button); // Button click handler button.down = function (x, y, obj) { var selectedDifficulty = this.difficulty; difficultyMultiplier = selectedDifficulty.multiplier; // Set initial gold based on difficulty switch (selectedDifficulty.name) { case "Easy": setGold(80); break; case "Normal": setGold(85); break; case "Hard": setGold(100); break; case "Insane": setGold(150); break; } // Show start game button after difficulty selection if (waveIndicator) { waveIndicator.showStartButton(); } // Show selection feedback var notification = game.addChild(new Notification("Difficulty set to " + selectedDifficulty.name + "!")); notification.x = 2048 / 2; notification.y = 2732 / 2; // Remove difficulty selector self.destroy(); }; } return self; }); // This update method was incorrectly placed here and should be removed var EffectIndicator = Container.expand(function (x, y, type) { var self = Container.call(this); self.x = x; self.y = y; var effectGraphics = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.blendMode = 1; switch (type) { case 'splash': effectGraphics.tint = 0x33CC00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5; break; case 'slow': effectGraphics.tint = 0x9900FF; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'poison': effectGraphics.tint = 0x00FFAA; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'sniper': effectGraphics.tint = 0xFF5500; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'explosion': effectGraphics.tint = 0xFF4500; effectGraphics.width = effectGraphics.height = CELL_SIZE * 3; // Larger explosion effect break; } effectGraphics.alpha = 0.7; self.alpha = 0; // Animate the effect tween(self, { alpha: 0.8, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); // Base enemy class for common functionality var Enemy = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'normal'; self.speed = .01; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 100; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.isFlying = false; self.isImmune = false; self.isBoss = false; // Check if this is a boss wave // Check if this is a boss wave // Apply different stats based on enemy type switch (self.type) { case 'fast': self.speed *= 2; // Twice as fast self.maxHealth = 100; break; case 'immune': self.isImmune = true; self.maxHealth = 80; break; case 'flying': self.isFlying = true; self.maxHealth = 80; break; case 'swarm': self.maxHealth = 50; // Weaker enemies break; case 'normal': default: // Normal enemy uses default values break; } if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') { self.isBoss = true; // Boss enemies have 20x health and are larger self.maxHealth *= 20; // Slower speed for bosses self.speed = self.speed * 0.7; } self.health = self.maxHealth; // Get appropriate asset for this enemy type var assetId = 'enemy'; if (self.type !== 'normal') { assetId = 'enemy_' + self.type; } var enemyGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Apply 50% size increase to all enemies (25% increase on top of previous 20%) enemyGraphics.scaleX = 1.5; enemyGraphics.scaleY = 1.5; // Scale up boss enemies (additional scaling on top of base 50% increase) if (self.isBoss) { enemyGraphics.scaleX = 1.8 * 1.5; // 1.8 * 1.5 = 2.7 enemyGraphics.scaleY = 1.8 * 1.5; } // Fall back to regular enemy asset if specific type asset not found // Apply tint to differentiate enemy types /*switch (self.type) { case 'fast': enemyGraphics.tint = 0x00AAFF; // Blue for fast enemies break; case 'immune': enemyGraphics.tint = 0xAA0000; // Red for immune enemies break; case 'flying': enemyGraphics.tint = 0xFFFF00; // Yellow for flying enemies break; case 'swarm': enemyGraphics.tint = 0xFF00FF; // Pink for swarm enemies break; }*/ // Create shadow for flying enemies if (self.isFlying) { // Create a shadow container that will be added to the shadow layer self.shadow = new Container(); // Clone the enemy graphics for the shadow var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow effect shadowGraphics.tint = 0x000000; // Black shadow shadowGraphics.alpha = 0.4; // Semi-transparent // Apply 50% size increase to shadow to match enemy shadowGraphics.scaleX = 1.5; shadowGraphics.scaleY = 1.5; // If this is a boss, scale up the shadow to match if (self.isBoss) { shadowGraphics.scaleX = 1.8 * 1.5; // 1.8 * 1.5 = 2.7 shadowGraphics.scaleY = 1.8 * 1.5; } // Position shadow slightly offset self.shadow.x = 20; // Offset right self.shadow.y = 20; // Offset down // Ensure shadow has the same rotation as the enemy shadowGraphics.rotation = enemyGraphics.rotation; } 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; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; // Add scaling animation function self.startScalingAnimation = function () { // Function to animate to larger scale (108%) function scaleUp() { tween(enemyGraphics, { scaleX: 1.08, scaleY: 1.08 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleDown(); } }); } // Function to animate to smaller scale (92%) function scaleDown() { tween(enemyGraphics, { scaleX: 0.92, scaleY: 0.92 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleUp(); } }); } // Start the animation cycle scaleUp(); }; self.update = function () { if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; } // Handle slow effect if (self.isImmune) { // Immune enemies cannot be slowed or poisoned, clear any such effects self.slowed = false; self.slowEffect = false; self.poisoned = false; self.poisonEffect = false; // Reset speed to original if needed if (self.originalSpeed !== undefined) { self.speed = self.originalSpeed; } } else { // Handle slow effect if (self.slowed) { // Visual indication of slowed status if (!self.slowEffect) { self.slowEffect = true; } self.slowDuration--; if (self.slowDuration <= 0) { self.speed = self.originalSpeed; self.slowed = false; self.slowEffect = false; // Only reset tint if not poisoned if (!self.poisoned) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } // Handle poison effect if (self.poisoned) { // Visual indication of poisoned status if (!self.poisonEffect) { self.poisonEffect = true; } // Apply poison damage every 30 frames (twice per second) if (LK.ticks % 30 === 0) { self.health -= self.poisonDamage; if (self.health <= 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; } self.poisonDuration--; if (self.poisonDuration <= 0) { self.poisoned = false; self.poisonEffect = false; // Only reset tint if not slowed if (!self.slowed) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } } // Set tint based on effect status if (self.isImmune) { enemyGraphics.tint = 0xFFFFFF; } else if (self.poisoned && self.slowed) { // Combine poison (0x00FFAA) and slow (0x9900FF) colors // Simple average: R: (0+153)/2=76, G: (255+0)/2=127, B: (170+255)/2=212 enemyGraphics.tint = 0x4C7FD4; } else if (self.poisoned) { enemyGraphics.tint = 0x00FFAA; } else if (self.slowed) { enemyGraphics.tint = 0x9900FF; } else { enemyGraphics.tint = 0xFFFFFF; } // Initialize scaling animation on first update if (!self.scalingInitialized) { self.scalingInitialized = true; // Start the continuous scaling animation self.startScalingAnimation(); } if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; if (ox !== 0 || oy !== 0) { var angle = Math.atan2(oy, ox); if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) { tween.stop(enemyGraphics, { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemyGraphics.rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemyGraphics.targetRotation = angle; tween(enemyGraphics, { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } } } healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; }; return self; }); var GoldIndicator = Container.expand(function (value, x, y) { var self = Container.call(this); var shadowText = new Text2("+" + value, { size: 45, fill: 0x000000, weight: 800 }); shadowText.anchor.set(0.5, 0.5); shadowText.x = 2; shadowText.y = 2; self.addChild(shadowText); var goldText = new Text2("+" + value, { size: 45, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); self.x = x; self.y = y; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2, y: y - 40 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: y - 80 }, { duration: 600, easing: tween.easeIn, delay: 800, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; self.spawns = []; self.goals = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { score: 0, pathId: 0, towersInRange: [] }; } } /* Cell Types 0: Transparent floor 1: Wall 2: Spawn 3: Goal */ for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; // Check if we're in Hot Potato challenge mode var isHotPotato = challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato'); // For Hot Potato, only set side walls and bottom walls, not top walls var cellType; if (isHotPotato) { // Hot Potato: only side walls (left/right) and bottom walls, no top walls cellType = i === 0 || i === gridWidth - 1 || j >= gridHeight - 4 ? 1 : 0; } else { // Normal behavior: top, bottom, and side walls 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; } } // Tutorial mode grid modification - fill sides with walls, leave only 6 middle rows as floor if (tutorialMode) { // For tutorial, fill all side areas with walls, only leave 6 middle columns (9-14) as walkable if (i < 9 || i > 14) { // Set side areas to walls (except for spawn/goal areas which are already handled above) if (cellType === 0) { cellType = 1; // Convert floor to wall } } } // Add rectangular no-tower zone for Pool level in the middle area if (selectedLevel === 'pool') { // Create a large rectangular area in the center where towers cannot be placed var centerX = Math.floor(gridWidth / 2); // Center column (12) var centerY = Math.floor(gridHeight / 2); // Center row var rectWidth = 6; // Rectangle width (6 cells) var rectHeight = 16; // Rectangle height (16 cells) var rectLeft = centerX - Math.floor(rectWidth / 2); var rectRight = centerX + Math.floor(rectWidth / 2); var rectTop = centerY - Math.floor(rectHeight / 2); var rectBottom = centerY + Math.floor(rectHeight / 2); // Check if current cell is within the rectangular zone if (i >= rectLeft && i < rectRight && j >= rectTop && j < rectBottom) { // Only block tower placement if it's a walkable area (not spawn/goal/wall) if (cellType === 0) { cellType = 1; // Set to wall type to prevent tower placement } } } cell.type = cellType; cell.x = i; cell.y = j; cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1]; cell.up = self.cells[i - 1] && self.cells[i - 1][j]; cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1]; cell.left = self.cells[i][j - 1]; cell.right = self.cells[i][j + 1]; cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1]; cell.down = self.cells[i + 1] && self.cells[i + 1][j]; cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1]; cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left]; cell.targets = []; if (j > 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; 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]; // Skip enemies that haven't entered the viewable area yet if (enemy.currentCellY < 4) { continue; } // Skip flying enemies from path check as they can fly over obstacles if (enemy.isFlying) { continue; } var target = self.getCell(enemy.cellX, enemy.cellY); if (enemy.currentTarget) { if (enemy.currentTarget.pathId != pathId) { if (!target || target.pathId != pathId) { console.warn("Enemy blocked 1 "); return true; } } } else if (!target || target.pathId != pathId) { console.warn("Enemy blocked 2"); return true; } } console.log("Speed", new Date().getTime() - before); }; self.renderDebug = function () { for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var debugCell = self.cells[i][j].debugCell; if (debugCell) { debugCell.render(self.cells[i][j]); } } } }; self.updateEnemy = function (enemy) { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell.type == 3) { return true; } if (enemy.isFlying && enemy.shadow) { enemy.shadow.x = enemy.x + 20; // Match enemy x-position + offset enemy.shadow.y = enemy.y + 20; // Match enemy y-position + offset // Match shadow rotation with enemy rotation if (enemy.children[0] && enemy.shadow.children[0]) { enemy.shadow.children[0].rotation = enemy.children[0].rotation; } } // Check if the enemy has reached the entry area (y position is at least 5) var hasReachedEntryArea = enemy.currentCellY >= 4; // If enemy hasn't reached the entry area yet, just move down vertically if (!hasReachedEntryArea) { // Move directly downward enemy.currentCellY += enemy.speed; // Rotate enemy graphic to face downward (PI/2 radians = 90 degrees) var angle = Math.PI / 2; if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update enemy's position enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // If enemy has now reached the entry area, update cell coordinates if (enemy.currentCellY >= 4) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); } return false; } // After reaching entry area, handle flying enemies differently if (enemy.isFlying) { // Flying enemies head straight to the closest goal if (!enemy.flyingTarget) { // Set flying target to the closest goal enemy.flyingTarget = self.goals[0]; // Find closest goal if there are multiple if (self.goals.length > 1) { var closestDist = Infinity; for (var i = 0; i < self.goals.length; i++) { var goal = self.goals[i]; var dx = goal.x - enemy.cellX; var dy = goal.y - enemy.cellY; var dist = dx * dx + dy * dy; if (dist < closestDist) { closestDist = dist; enemy.flyingTarget = goal; } } } } // Move directly toward the goal var ox = enemy.flyingTarget.x - enemy.currentCellX; var oy = enemy.flyingTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { // Reached the goal return true; } var angle = Math.atan2(oy, ox); // Rotate enemy graphic to match movement direction if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update the cell position to track where the flying enemy is enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // Update shadow position if this is a flying enemy return false; } // Handle normal pathfinding enemies if (!enemy.currentTarget) { enemy.currentTarget = cell.targets[0]; } if (enemy.currentTarget) { if (cell.score < enemy.currentTarget.score) { enemy.currentTarget = cell; } var ox = enemy.currentTarget.x - enemy.currentCellX; var oy = enemy.currentTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentTarget = undefined; return; } var angle = Math.atan2(oy, ox); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; }; }); var HotPotatoInstructionsScreen = Container.expand(function () { var self = Container.call(this); // Pause the game when instructions screen is shown self.gamePaused = true; // Hide UI labels when instructions screen is shown goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Hot Potato instruction image at the bottom var instructionsImage = self.attachAsset('hotpotato_instructions_image', { anchorX: 0.5, anchorY: 1.0 }); instructionsImage.x = 0; instructionsImage.y = 2732 / 2 - 100; // Position higher up instructionsImage.width = 800; instructionsImage.height = 800; // Instructions text label in the center of the screen var instructionsTextShadow = new Text2("Welcome to Hot Potato Challenge!\nThings are heating up!\nOnly CapySniper, CapyBomb and CapyDemolition towers are available.\nPlace towers on the blue floor area.\nBombs launch upward!\nReceive 250 gold every time you beat a wave\n", { size: 55, fill: 0x000000, weight: 800, align: 'center' }); instructionsTextShadow.anchor.set(0.5, 0.5); instructionsTextShadow.x = 4; // Shadow offset instructionsTextShadow.y = 4; // Centered vertically with shadow offset // Set maximum width to ensure text wraps properly instructionsTextShadow.wordWrap = true; instructionsTextShadow.wordWrapWidth = 1800; self.addChild(instructionsTextShadow); var instructionsText = new Text2("Welcome to Hot Potato Challenge!\nThings are heating up!\nOnly CapySniper, CapyBomb and CapyDemolition towers are available.\nPlace towers on the blue floor area.\nBombs launch upward!\nReceive 250 gold every time you beat a wave\n", { size: 55, fill: 0xFFFFFF, weight: 800, align: 'center' }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 0; instructionsText.y = 0; // Centered vertically in screen // Set maximum width to ensure text wraps properly instructionsText.wordWrap = true; instructionsText.wordWrapWidth = 1800; self.addChild(instructionsText); // Continue button at the top var continueButton = new Container(); var buttonBg = continueButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 150; buttonBg.tint = 0xFF4400; var buttonTextShadow = new Text2("Continue", { size: 80, fill: 0x000000, weight: 800 }); buttonTextShadow.anchor.set(0.5, 0.5); buttonTextShadow.x = 2; buttonTextShadow.y = 2; continueButton.addChild(buttonTextShadow); var buttonText = new Text2("Continue", { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); continueButton.addChild(buttonText); continueButton.x = 0; continueButton.y = -2732 / 2 + 300; // Position at top self.addChild(continueButton); // Continue button handler - close the screen continueButton.down = function (x, y, obj) { // Unpause the game self.gamePaused = false; // Show UI labels when continue button is pressed goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; // Remove instructions screen self.destroy(); }; return self; }); var Instructions2Screen = Container.expand(function () { var self = Container.call(this); // Pause the game when instructions2 screen is shown self.gamePaused = true; // Hide UI labels when instructions2 screen is shown goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Instructions2 image at the bottom using separate asset var instructionsImage = self.attachAsset('instructions2_image', { anchorX: 0.5, anchorY: 1.0 }); instructionsImage.x = 0; instructionsImage.y = 2732 / 2 - 100; // Position higher up instructionsImage.width = 800; instructionsImage.height = 800; // Instructions text label in the center of the screen var instructionsTextShadow = new Text2("Hi I´m the CapyQueen!\nGreat job defending the castle!\nYou've learned the basics of placing towers.\nYou can skip waves using the next wave button.\nTake this Gold to deploy the CapyQueen on the field \nand test the skill of the kings.\n", { size: 55, fill: 0x000000, weight: 800, align: 'center' }); instructionsTextShadow.anchor.set(0.5, 0.5); instructionsTextShadow.x = 4; // Shadow offset instructionsTextShadow.y = 4; // Centered vertically with shadow offset // Set maximum width to ensure text wraps properly instructionsTextShadow.wordWrap = true; instructionsTextShadow.wordWrapWidth = 1800; self.addChild(instructionsTextShadow); var instructionsText = new Text2("Hi I´m the CapyQueen!\nGreat job defending the castle!\nYou've learned the basics of placing towers.\nYou can skip waves using the next wave button.\nTake this Gold to deploy the CapyQueen on the field \nand test the skill of the kings.\n", { size: 55, fill: 0xFFFFFF, weight: 800, align: 'center' }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 0; instructionsText.y = 0; // Centered vertically in screen // Set maximum width to ensure text wraps properly instructionsText.wordWrap = true; instructionsText.wordWrapWidth = 1800; self.addChild(instructionsText); // Continue button at the top var continueButton = new Container(); var buttonBg = continueButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 150; buttonBg.tint = 0x00AA00; var buttonTextShadow = new Text2("Continue", { size: 80, fill: 0x000000, weight: 800 }); buttonTextShadow.anchor.set(0.5, 0.5); buttonTextShadow.x = 2; buttonTextShadow.y = 2; continueButton.addChild(buttonTextShadow); var buttonText = new Text2("Continue", { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); continueButton.addChild(buttonText); continueButton.x = 0; continueButton.y = -2732 / 2 + 300; // Position at top self.addChild(continueButton); // Continue button handler - unpause game and close the screen continueButton.down = function (x, y, obj) { // Unpause the game self.gamePaused = false; // Add 200 gold when continuing from Instructions2Screen setGold(gold + 200); // Show UI labels when continue button is pressed goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; // In tutorial mode, advance to wave 4 if (tutorialMode) { currentWave = 4; waveTimer = 0; // Reset wave timer waveInProgress = true; waveSpawned = false; // Create new wave indicator for wave 4 if (waveIndicator) { waveIndicator.createCurrentWaveIndicator(); } // Show Next Wave button in tutorial mode if (nextWaveButton) { nextWaveButton.enabled = true; nextWaveButton.visible = true; } // Remove notification for wave 4 in tutorial mode } // Remove instructions2 screen self.destroy(); }; return self; }); var Instructions3Screen = Container.expand(function () { var self = Container.call(this); // Pause the game when instructions3 screen is shown self.gamePaused = true; // Hide UI labels when instructions3 screen is shown goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Instructions3 image at the bottom using independent asset var instructionsImage = self.attachAsset('instructions3_image', { anchorX: 0.5, anchorY: 1.0 }); instructionsImage.x = 0; instructionsImage.y = 2732 / 2 - 100; // Position higher up instructionsImage.width = 800; instructionsImage.height = 800; // Instructions text label in the center of the screen var instructionsTextShadow = new Text2("Well done!\nThe mighty Boss approaches the castle!\nTake this gold to deploy the CapyKing.\nYou can also use the gold you earn to \nupgrade troops in the field.\n", { size: 55, fill: 0x000000, weight: 800, align: 'center' }); instructionsTextShadow.anchor.set(0.5, 0.5); instructionsTextShadow.x = 4; // Shadow offset instructionsTextShadow.y = 4; // Centered vertically with shadow offset // Set maximum width to ensure text wraps properly instructionsTextShadow.wordWrap = true; instructionsTextShadow.wordWrapWidth = 1800; self.addChild(instructionsTextShadow); var instructionsText = new Text2("Well done!\nThe mighty Boss approaches the castle!\nTake this gold to deploy the CapyKing.\nYou can also use the gold you earn to \nupgrade troops in the field.\n", { size: 55, fill: 0xFFFFFF, weight: 800, align: 'center' }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 0; instructionsText.y = 0; // Centered vertically in screen // Set maximum width to ensure text wraps properly instructionsText.wordWrap = true; instructionsText.wordWrapWidth = 1800; self.addChild(instructionsText); // Continue button at the top var continueButton = new Container(); var buttonBg = continueButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 150; buttonBg.tint = 0x00AA00; var buttonTextShadow = new Text2("Continue", { size: 80, fill: 0x000000, weight: 800 }); buttonTextShadow.anchor.set(0.5, 0.5); buttonTextShadow.x = 2; buttonTextShadow.y = 2; continueButton.addChild(buttonTextShadow); var buttonText = new Text2("Continue", { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); continueButton.addChild(buttonText); continueButton.x = 0; continueButton.y = -2732 / 2 + 300; // Position at top self.addChild(continueButton); // Continue button handler - unpause game and close the screen continueButton.down = function (x, y, obj) { // Unpause the game self.gamePaused = false; // Add 250 gold when continuing from Instructions3Screen setGold(gold + 250); // Show UI labels when continue button is pressed goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; // Remove instructions3 screen self.destroy(); }; return self; }); var KingsGoldInstructionsScreen = Container.expand(function () { var self = Container.call(this); // Pause the game when instructions screen is shown self.gamePaused = true; // Hide UI labels when instructions screen is shown goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // King's Gold instruction image at the bottom var instructionsImage = self.attachAsset('kings_gold_instructions_image', { anchorX: 0.5, anchorY: 1.0 }); instructionsImage.x = 0; instructionsImage.y = 2732 / 2 - 100; // Position higher up instructionsImage.width = 800; instructionsImage.height = 800; // Instructions text label in the center of the screen var instructionsTextShadow = new Text2("Welcome to King's Gold Challenge!\nEnter the mighty Great Hall!\nOnly CapyKing, CapyFreeze, and CapyPrince towers are available.\nTouch gold coins dropped by enemies to collect them.\nGold coins grant 50 gold each!\nGood luck, defender!\n", { size: 55, fill: 0x000000, weight: 800, align: 'center' }); instructionsTextShadow.anchor.set(0.5, 0.5); instructionsTextShadow.x = 4; // Shadow offset instructionsTextShadow.y = 4; // Centered vertically with shadow offset // Set maximum width to ensure text wraps properly instructionsTextShadow.wordWrap = true; instructionsTextShadow.wordWrapWidth = 1800; self.addChild(instructionsTextShadow); var instructionsText = new Text2("Welcome to King's Gold Challenge!\nEnter the mighty Great Hall!\nOnly CapyKing, CapyFreeze, and CapyPrince towers are available.\nTouch gold coins dropped by enemies to collect them.\nGold coins grant 50 gold each!\nGood luck, defender!\n", { size: 55, fill: 0xFFFFFF, weight: 800, align: 'center' }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 0; instructionsText.y = 0; // Centered vertically in screen // Set maximum width to ensure text wraps properly instructionsText.wordWrap = true; instructionsText.wordWrapWidth = 1800; self.addChild(instructionsText); // Continue button at the top var continueButton = new Container(); var buttonBg = continueButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 150; buttonBg.tint = 0xFFD700; var buttonTextShadow = new Text2("Continue", { size: 80, fill: 0x000000, weight: 800 }); buttonTextShadow.anchor.set(0.5, 0.5); buttonTextShadow.x = 2; buttonTextShadow.y = 2; continueButton.addChild(buttonTextShadow); var buttonText = new Text2("Continue", { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); continueButton.addChild(buttonText); continueButton.x = 0; continueButton.y = -2732 / 2 + 300; // Position at top self.addChild(continueButton); // Continue button handler - close the screen continueButton.down = function (x, y, obj) { // Unpause the game self.gamePaused = false; // Show UI labels when continue button is pressed goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; // Remove instructions screen self.destroy(); }; return self; }); var LevelConfirmation = Container.expand(function (levelData, levelSelector) { var self = Container.call(this); // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.9; // Confirmation box var confirmBox = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); confirmBox.width = 800; confirmBox.height = 400; confirmBox.tint = 0x444444; // Title shadow var titleShadow = new Text2("Confirm Level Selection", { size: 70, fill: 0x000000, weight: 800 }); titleShadow.anchor.set(0.5, 0.5); titleShadow.x = 4; titleShadow.y = -120; self.addChild(titleShadow); // Title var title = new Text2("Confirm Level Selection", { size: 70, fill: 0xFFFFFF, weight: 800 }); title.anchor.set(0.5, 0.5); title.y = -120; self.addChild(title); // Level name shadow var levelNameShadow = new Text2("Level: " + levelData.name, { size: 60, fill: 0x000000, weight: 600 }); levelNameShadow.anchor.set(0.5, 0.5); levelNameShadow.x = 4; levelNameShadow.y = -30; self.addChild(levelNameShadow); // Level name var levelName = new Text2("Level: " + levelData.name, { size: 60, fill: levelData.color, weight: 600 }); levelName.anchor.set(0.5, 0.5); levelName.y = -30; self.addChild(levelName); // Description shadow var descShadow = new Text2(levelData.description, { size: 45, fill: 0x000000, weight: 400 }); descShadow.anchor.set(0.5, 0.5); descShadow.x = 2; descShadow.y = 30; self.addChild(descShadow); // Description var desc = new Text2(levelData.description, { size: 45, fill: 0xFFFFFF, weight: 400 }); desc.anchor.set(0.5, 0.5); desc.y = 28; self.addChild(desc); // Confirm button var confirmButton = new Container(); var confirmBg = confirmButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); confirmBg.width = 250; confirmBg.height = 100; confirmBg.tint = 0x00AA00; var confirmTextShadow = new Text2("Confirm", { size: 50, fill: 0x000000, weight: 800 }); confirmTextShadow.anchor.set(0.5, 0.5); confirmTextShadow.x = 2; confirmTextShadow.y = 2; confirmButton.addChild(confirmTextShadow); var confirmText = new Text2("Confirm", { size: 50, fill: 0xFFFFFF, weight: 800 }); confirmText.anchor.set(0.5, 0.5); confirmButton.addChild(confirmText); confirmButton.x = -150; confirmButton.y = 120; self.addChild(confirmButton); // Cancel button var cancelButton = new Container(); var cancelBg = cancelButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); cancelBg.width = 250; cancelBg.height = 100; cancelBg.tint = 0xCC0000; var cancelTextShadow = new Text2("Cancel", { size: 50, fill: 0x000000, weight: 800 }); cancelTextShadow.anchor.set(0.5, 0.5); cancelTextShadow.x = 2; cancelTextShadow.y = 2; cancelButton.addChild(cancelTextShadow); var cancelText = new Text2("Cancel", { size: 50, fill: 0xFFFFFF, weight: 800 }); cancelText.anchor.set(0.5, 0.5); cancelButton.addChild(cancelText); cancelButton.x = 150; cancelButton.y = 120; self.addChild(cancelButton); // Confirm button handler confirmButton.down = function (x, y, obj) { // Store the selected level globally selectedLevel = levelData.name.toLowerCase().replace(' ', ''); // Clean up existing lobby square images first for (var i = 0; i < lobbySquareImages.length; i++) { towerLayer.removeChild(lobbySquareImages[i]); } lobbySquareImages = []; // Regenerate grid for Pool level to add rectangular pool area if (selectedLevel === 'pool') { // Remove existing grid debugLayer.removeChild(grid); // Create new grid with pool configuration grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; // Set selectedLevel based on challenge if (challengeName === "Queen's Walk") { selectedLevel = 'garden'; // Copy garden level } else if (challengeName === "King's Gold") { selectedLevel = 'greathall'; // Copy great hall level } grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); } // Create 3 wall cell squares for Lobby level if (selectedLevel === 'lobby') { // Remove existing grid debugLayer.removeChild(grid); // Create new grid with lobby configuration grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; // Define positions for the 3 wall squares (4x4 each) var wallSquares = [{ startX: 4, startY: 10 }, // Left square { startX: 10, startY: 16 }, // Center square { startX: 16, startY: 10 } // Right square ]; // Create the wall squares for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { var cell = grid.getCell(square.startX + i, square.startY + j); if (cell && cell.type === 0) { // Only modify walkable cells cell.type = 1; // Set to wall type } } } } grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Create images over the 4x4 wall squares var squareAssets = ['lobby_square_1', 'lobby_square_2', 'lobby_square_3']; for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; var squareImage = game.attachAsset(squareAssets[s], { anchorX: 0.5, anchorY: 0.5 }); // Position image over the center of the 4x4 square, moved half a cell left and up var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE; var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE; squareImage.x = squareCenterX - CELL_SIZE / 2; squareImage.y = squareCenterY - CELL_SIZE / 2; // Add to tower layer so it appears above the grid cells towerLayer.addChild(squareImage); lobbySquareImages.push(squareImage); } } // Create 2 wall cell squares for Great Hall level at the bottom if (selectedLevel === 'greathall') { // Remove existing grid debugLayer.removeChild(grid); // Create new grid with great hall configuration grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; // Define positions for the 2 wall squares (4x4 each) at the bottom var gridHeight = 29 + 6; var wallSquares = [{ startX: 4, startY: gridHeight - 10 // Bottom left square, moved up 2 cells }, { startX: 16, startY: gridHeight - 10 // Bottom right square, moved up 2 cells }]; // Create the wall squares for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { var cell = grid.getCell(square.startX + i, square.startY + j); if (cell && cell.type === 0) { // Only modify walkable cells cell.type = 1; // Set to wall type } } } } grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Create images over the 4x4 wall squares using separate assets var squareAssets = ['greathall_square_1', 'greathall_square_2']; for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; var squareImage = game.attachAsset(squareAssets[s], { anchorX: 0.5, anchorY: 0.5 }); // Position image over the center of the 4x4 square, moved half a cell left and up var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE; var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE; squareImage.x = squareCenterX - CELL_SIZE / 2; squareImage.y = squareCenterY - CELL_SIZE / 2; // Add to tower layer so it appears above the grid cells towerLayer.addChild(squareImage); lobbySquareImages.push(squareImage); } } // Update background image immediately when level is selected var newBackgroundAssetName = getBackgroundAssetName(); game.removeChild(backgroundImage); backgroundImage = game.attachAsset(newBackgroundAssetName, { anchorX: 0.5, anchorY: 0.5 }); backgroundImage.x = 2048 / 2; backgroundImage.y = 2732 / 2; backgroundImage.width = 2048; backgroundImage.height = 2732; // Add background to the back of the game game.addChildAt(backgroundImage, 0); // Add or remove pool rectangle image based on selected level if (poolRectangleImage) { game.removeChild(poolRectangleImage); poolRectangleImage = null; } if (selectedLevel === 'pool') { poolRectangleImage = game.attachAsset('pool_rectangle', { anchorX: 0.5, anchorY: 0.5 }); // Position over the rectangular pool area (center of grid + pool area center offset) var gridWidth = 24; var gridHeight = 35; var centerX = Math.floor(gridWidth / 2); // Center column (12) var centerY = Math.floor(gridHeight / 2); // Center row var rectWidth = 6; // Rectangle width (6 cells) var rectHeight = 16; // Rectangle height (16 cells) var rectLeft = centerX - Math.floor(rectWidth / 2); var rectTop = centerY - Math.floor(rectHeight / 2); // Calculate the actual center of the rectangular pool area in pixels var poolCenterX = grid.x + (rectLeft + rectWidth / 2) * CELL_SIZE; var poolCenterY = grid.y + (rectTop + rectHeight / 2) * CELL_SIZE; poolRectangleImage.x = poolCenterX - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2; poolRectangleImage.y = poolCenterY - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2; // Keep pool rectangle image at normal tint (not gray) so it appears above the gray cells poolRectangleImage.tint = 0xFFFFFF; // Keep original white/normal tint // Add to tower layer so it appears above the grid cells towerLayer.addChild(poolRectangleImage); } // Trigger grid debug render to update cell assets immediately grid.renderDebug(); // Show selection feedback var notification = game.addChild(new Notification("Level set to " + levelData.name + "!")); notification.x = 2048 / 2; //{i2} notification.y = 2732 / 2; // Remove confirmation dialog and level selector self.destroy(); levelSelector.destroy(); // Show difficulty selector var difficultySelector = new DifficultySelector(); difficultySelector.x = 2048 / 2; difficultySelector.y = 2732 / 2; game.addChild(difficultySelector); }; // Cancel button handler cancelButton.down = function (x, y, obj) { // Unblock level selector buttons levelSelector.buttonsBlocked = false; // Just close the confirmation dialog, return to level selector self.destroy(); }; return self; }); var LevelSelector = Container.expand(function () { var self = Container.call(this); // Track if buttons should be blocked self.buttonsBlocked = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Title var titleShadow = new Text2("Select Level", { size: 120, fill: 0x000000, weight: 800 }); titleShadow.anchor.set(0.5, 0.5); titleShadow.x = 4; titleShadow.y = -900; self.addChild(titleShadow); var title = new Text2("Select Level", { size: 120, fill: 0xFFFFFF, weight: 800 }); title.anchor.set(0.5, 0.5); title.y = -900; self.addChild(title); // Level buttons var levels = [{ name: "Garden", color: 0x00AA00, description: "A peaceful garden setting" }, { name: "Pool", color: 0x0088FF, description: "Battle by the swimming pool" }, { name: "Lobby", color: 0xFFAA00, description: "Defend the castle lobby" }, { name: "Great Hall", color: 0xAA00AA, description: "Epic battles in the great hall" }]; var buttonSpacing = 300; var startY = -200; // Store button references for blocking/unblocking self.buttons = []; for (var i = 0; i < levels.length; i++) { var level = levels[i]; var button = new Container(); var buttonBg = button.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 200; buttonBg.tint = level.color; // Button title shadow var buttonTitleShadow = new Text2(level.name, { size: 80, fill: 0x000000, weight: 800 }); buttonTitleShadow.anchor.set(0.5, 0.5); buttonTitleShadow.x = 4; buttonTitleShadow.y = -20; button.addChild(buttonTitleShadow); // Button title var buttonTitle = new Text2(level.name, { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonTitle.anchor.set(0.5, 0.5); buttonTitle.y = -20; button.addChild(buttonTitle); // Description shadow var descShadow = new Text2(level.description, { size: 40, fill: 0x000000, weight: 400 }); descShadow.anchor.set(0.5, 0.5); descShadow.x = 2; descShadow.y = 30; button.addChild(descShadow); // Description var desc = new Text2(level.description, { size: 40, fill: 0xFFFFFF, weight: 400 }); desc.anchor.set(0.5, 0.5); desc.y = 28; button.addChild(desc); button.y = startY + i * buttonSpacing; button.level = level; self.addChild(button); self.buttons.push(button); // Button click handler button.down = function (x, y, obj) { // Don't handle clicks if buttons are blocked if (self.buttonsBlocked) { return; } var levelData = this.level; // Block buttons when showing confirmation window self.buttonsBlocked = true; // Show confirmation window var levelConfirmation = new LevelConfirmation(levelData, self); levelConfirmation.x = 2048 / 2; levelConfirmation.y = 2732 / 2; game.addChild(levelConfirmation); }; } // Back button var backButton = new Container(); var backBg = backButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); backBg.width = 400; backBg.height = 100; backBg.tint = 0xCC0000; var backTextShadow = new Text2("Back", { size: 60, fill: 0x000000, weight: 800 }); backTextShadow.anchor.set(0.5, 0.5); backTextShadow.x = 2; backTextShadow.y = 2; backButton.addChild(backTextShadow); var backText = new Text2("Back", { size: 60, fill: 0xFFFFFF, weight: 800 }); backText.anchor.set(0.5, 0.5); backButton.addChild(backText); backButton.x = 0; backButton.y = 1000; self.addChild(backButton); // Back button handler - return to mode selector backButton.down = function (x, y, obj) { // Don't handle clicks if buttons are blocked if (self.buttonsBlocked) { return; } // Remove level selector self.destroy(); // Show mode selector var modeSelector = new ModeSelector(); modeSelector.x = 2048 / 2; modeSelector.y = 2732 / 2; game.addChild(modeSelector); }; return self; }); var ModeSelector = Container.expand(function () { var self = Container.call(this); // Hide UI labels when ModeSelector is shown goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Main container for content var contentContainer = new Container(); self.addChild(contentContainer); // Title shadow var titleShadow = new Text2("Select Mode", { size: 140, fill: 0x000000, weight: 800 }); titleShadow.anchor.set(0.5, 0.5); titleShadow.x = 4; titleShadow.y = -300 + 4; contentContainer.addChild(titleShadow); // Title var title = new Text2("Select Mode", { size: 140, fill: 0xFFD700, weight: 800 }); title.anchor.set(0.5, 0.5); title.y = -300; contentContainer.addChild(title); // Tutorial button var tutorialButton = new Container(); var tutorialBg = tutorialButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); tutorialBg.width = 600; tutorialBg.height = 150; tutorialBg.tint = 0x00AA00; var tutorialTextShadow = new Text2("Tutorial", { size: 70, fill: 0x000000, weight: 800 }); tutorialTextShadow.anchor.set(0.5, 0.5); tutorialTextShadow.x = 2; tutorialTextShadow.y = 2; tutorialButton.addChild(tutorialTextShadow); var tutorialText = new Text2("Tutorial", { size: 70, fill: 0xFFFFFF, weight: 800 }); tutorialText.anchor.set(0.5, 0.5); tutorialButton.addChild(tutorialText); tutorialButton.x = 0; tutorialButton.y = 50; contentContainer.addChild(tutorialButton); // Normal Mode button var normalButton = new Container(); var normalBg = normalButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); normalBg.width = 600; normalBg.height = 150; normalBg.tint = 0x0088FF; var normalTextShadow = new Text2("Normal Mode", { size: 70, fill: 0x000000, weight: 800 }); normalTextShadow.anchor.set(0.5, 0.5); normalTextShadow.x = 2; normalTextShadow.y = 2; normalButton.addChild(normalTextShadow); var normalText = new Text2("Normal Mode", { size: 70, fill: 0xFFFFFF, weight: 800 }); normalText.anchor.set(0.5, 0.5); normalButton.addChild(normalText); normalButton.x = 0; normalButton.y = 220; contentContainer.addChild(normalButton); // Capy Challenges button var challengesButton = new Container(); var challengesBg = challengesButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); challengesBg.width = 600; challengesBg.height = 150; challengesBg.tint = 0xFF6600; var challengesTextShadow = new Text2("Capy Challenges", { size: 70, fill: 0x000000, weight: 800 }); challengesTextShadow.anchor.set(0.5, 0.5); challengesTextShadow.x = 2; challengesTextShadow.y = 2; challengesButton.addChild(challengesTextShadow); var challengesText = new Text2("Capy Challenges", { size: 70, fill: 0xFFFFFF, weight: 800 }); challengesText.anchor.set(0.5, 0.5); challengesButton.addChild(challengesText); challengesButton.x = 0; challengesButton.y = 390; contentContainer.addChild(challengesButton); // Capybaras button var capybarasButton = new Container(); var capybarasBg = capybarasButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); capybarasBg.width = 600; capybarasBg.height = 150; capybarasBg.tint = 0x8B4513; var capybarasTextShadow = new Text2("Capybaras", { size: 70, fill: 0x000000, weight: 800 }); capybarasTextShadow.anchor.set(0.5, 0.5); capybarasTextShadow.x = 2; capybarasTextShadow.y = 2; capybarasButton.addChild(capybarasTextShadow); var capybarasText = new Text2("Capybaras", { size: 70, fill: 0xFFFFFF, weight: 800 }); capybarasText.anchor.set(0.5, 0.5); capybarasButton.addChild(capybarasText); capybarasButton.x = 0; capybarasButton.y = 560; contentContainer.addChild(capybarasButton); // Tutorial button handler - go to tutorial tutorialButton.down = function (x, y, obj) { // Block start game button by checking if the click is targeting it var startGameButtonClicked = false; // Check if waveIndicator exists and has a start marker if (waveIndicator && waveIndicator.waveMarkers && waveIndicator.waveMarkers.length > 0) { var startMarker = waveIndicator.waveMarkers[0]; if (startMarker && !waveIndicator.gameStarted) { // Get start marker bounds (assuming it's centered at waveIndicator position) var startMarkerX = waveIndicator.x + startMarker.x; var startMarkerY = waveIndicator.y + startMarker.y; var startMarkerWidth = 400; // blockWidth from WaveIndicator var startMarkerHeight = 120; // blockHeight from WaveIndicator var startMarkerLeft = startMarkerX - startMarkerWidth / 2; var startMarkerRight = startMarkerX + startMarkerWidth / 2; var startMarkerTop = startMarkerY - startMarkerHeight / 2; var startMarkerBottom = startMarkerY + startMarkerHeight / 2; // Check if click coordinates overlap with start game button if (x >= startMarkerLeft && x <= startMarkerRight && y >= startMarkerTop && y <= startMarkerBottom) { startGameButtonClicked = true; } } } // Block start game button interaction if (startGameButtonClicked) { return; // Prevent start game button from working } // Set tutorial mode variables tutorialMode = true; // Enable tutorial mode selectedLevel = 'garden'; // Tutorial uses garden level difficultyMultiplier = 0.5; // Make tutorial easier totalWaves = 10; // Limit tutorial to 10 waves nextWaveTime = 12000 / 2 * 0.25 * 0.7; // Reduce wave time by 75% + 30% for tutorial setGold(65); // Tutorial starting gold // Regenerate grid with tutorial configuration and activate wall cells debugLayer.removeChild(grid); grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Update background immediately to tutorial assets var newBackgroundAssetName = getBackgroundAssetName(); game.removeChild(backgroundImage); backgroundImage = game.attachAsset(newBackgroundAssetName, { anchorX: 0.5, anchorY: 0.5 }); backgroundImage.x = 2048 / 2; backgroundImage.y = 2732 / 2; backgroundImage.width = 2048; backgroundImage.height = 2732; // Add background to the back of the game game.addChildAt(backgroundImage, 0); // Show start game button after tutorial selection if (waveIndicator) { waveIndicator.showStartButton(); } // Show tutorial intro screen instead of notification var tutorialIntroScreen = new TutorialIntroScreen(); tutorialIntroScreen.x = 2048 / 2; tutorialIntroScreen.y = 2732 / 2; game.addChild(tutorialIntroScreen); // Remove mode selector self.destroy(); }; // Normal Mode button handler - go to level selection normalButton.down = function (x, y, obj) { // Remove mode selector self.destroy(); // Show level selector var levelSelector = new LevelSelector(); levelSelector.x = 2048 / 2; levelSelector.y = 2732 / 2; game.addChild(levelSelector); }; // Capy Challenges button handler - go to empty challenges screen challengesButton.down = function (x, y, obj) { // Remove mode selector self.destroy(); // Show empty Capy Challenges screen var capyChallengesScreen = new CapyChallengesScreen(); capyChallengesScreen.x = 2048 / 2; capyChallengesScreen.y = 2732 / 2; game.addChild(capyChallengesScreen); }; // Capybaras button handler - show tower information screen capybarasButton.down = function (x, y, obj) { // Remove mode selector self.destroy(); // Show Capybaras information screen var capybarasInfoScreen = new CapybarasInfoScreen(); capybarasInfoScreen.x = 2048 / 2; capybarasInfoScreen.y = 2732 / 2; game.addChild(capybarasInfoScreen); }; return self; }); var NextWaveButton = Container.expand(function () { var self = Container.call(this); var buttonBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 300; buttonBackground.height = 100; buttonBackground.tint = 0x0088FF; var buttonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.enabled = false; self.visible = false; self.update = function () { // In tutorial mode, only show button after instructions2 continue is pressed if (tutorialMode && !self.enabled) { self.visible = false; buttonBackground.tint = 0x888888; self.alpha = 0.7; return; } if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) { self.enabled = true; self.visible = true; buttonBackground.tint = 0x0088FF; self.alpha = 1; } else { self.enabled = false; self.visible = false; buttonBackground.tint = 0x888888; self.alpha = 0.7; } }; self.down = function () { // Block next wave button when ModeSelector or tutorial instruction screens are active var modeSelectorActive = game.children.some(function (child) { return child instanceof ModeSelector; }); var instructions2Active = game.children.some(function (child) { return child instanceof Instructions2Screen; }); var instructions3Active = game.children.some(function (child) { return child instanceof Instructions3Screen; }); if (modeSelectorActive || instructions2Active || instructions3Active) { return; } if (!self.enabled) { return; } if (waveIndicator.gameStarted && currentWave < totalWaves) { currentWave++; // Increment to the next wave directly waveTimer = 0; // Reset wave timer waveInProgress = true; waveSpawned = false; // Create new wave indicator for the manually advanced wave waveIndicator.createCurrentWaveIndicator(); // Get the type of the current wave (which is now the next wave) var waveType = waveIndicator.getWaveTypeName(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); // Only show notification if not in tutorial mode if (!tutorialMode) { var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 30; self.addChild(notificationText); self.alpha = 1; var fadeOutTime = 120; self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var ProjectileTower = Container.expand(function (type, x, y) { var self = Container.call(this); self.towerType = type; // 'bomb' or 'demolition' self.x = x; self.y = y; self.launched = false; self.targetEnemy = null; // Create tower graphics var assetName = 'tower_' + self.towerType; var towerGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Find target enemy when launched self.findTarget = function () { var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } return closestEnemy; }; // Launch the tower toward target enemy self.launch = function () { if (self.launched) return; self.launched = true; // In Hot Potato challenge, move straight up in same column if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) { // Launch straight up until hitting an enemy in the same column var startX = self.x; // Keep same X position (same column) // Start rotation animation - continuous right rotation tween(self, { rotation: self.rotation + Math.PI * 8 // Rotate right (8 full rotations) }, { duration: 5000, // 5 seconds duration easing: tween.linear }); var checkInterval = LK.setInterval(function () { // Move upward self.y -= 8; // Move up 8 pixels per frame // Check for collision with enemies in same column var hitEnemy = null; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; // Check if enemy is in roughly the same column (within cell width) var dx = Math.abs(enemy.x - startX); var dy = Math.abs(enemy.y - self.y); if (dx <= CELL_SIZE && dy <= CELL_SIZE / 2) { hitEnemy = enemy; break; } } // If hit enemy or moved too far up, explode if (hitEnemy || self.y < -100) { LK.clearInterval(checkInterval); // Stop rotation animation when exploding tween.stop(self, { rotation: true }); self.explode(); } }, 16); // Check every 16ms (roughly 60fps) return; } // Original behavior for non-Hot Potato challenges self.targetEnemy = self.findTarget(); if (!self.targetEnemy) return; // Calculate trajectory to enemy var targetX = self.targetEnemy.x; var targetY = self.targetEnemy.y; // Launch upward first, then toward enemy var midY = self.y - 300; // Go up 300 pixels first // First tween: launch upward tween(self, { y: midY }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { // Second tween: arc toward enemy tween(self, { x: targetX, y: targetY }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { self.explode(); } }); } }); }; // Explode when hitting enemy self.explode = function () { // Create explosion visual effect var explosionEffect = new EffectIndicator(self.x, self.y, 'explosion'); game.addChild(explosionEffect); // Determine damage and range based on tower type var explosionDamage = self.towerType === 'demolition' ? 200 : 100; var explosionRange = CELL_SIZE * 4; // Large explosion range // Deal area damage to all enemies in range for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRange) { // Apply explosion damage with Hot Potato challenge multiplier var actualDamage = explosionDamage; // In Hot Potato challenge, multiply CapyBomb and CapyDemolition damage by 20x against bosses if (challengeMode && window.challengeName === 'Hot Potato') { if ((self.towerType === 'bomb' || self.towerType === 'demolition') && enemy.isBoss) { actualDamage = explosionDamage * 20; } } enemy.health -= actualDamage; if (enemy.health <= 0) { enemy.health = 0; } else { enemy.healthBar.width = enemy.health / enemy.maxHealth * 70; } // Create damage indicator for each hit enemy var damageEffect = new EffectIndicator(enemy.x, enemy.y, 'explosion'); game.addChild(damageEffect); } } // Remove projectile tower self.destroy(); }; self.update = function () { // Track target enemy position if launched but not yet exploded if (self.launched && self.targetEnemy && !self.targetEnemy.health <= 0) { // Update target position during flight for moving enemies // This could be enhanced with predictive targeting } }; return self; }); var QueensWalkInstructionsScreen = Container.expand(function () { var self = Container.call(this); // Pause the game when instructions screen is shown self.gamePaused = true; // Hide UI labels when instructions screen is shown goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Queen's Walk instruction image at the bottom var instructionsImage = self.attachAsset('queens_walk_instructions_image', { anchorX: 0.5, anchorY: 1.0 }); instructionsImage.x = 0; instructionsImage.y = 2732 / 2 - 100; // Position higher up instructionsImage.width = 800; instructionsImage.height = 800; // Instructions text label in the center of the screen var instructionsTextShadow = new Text2("Welcome to Queen's Walk Challenge!\nBehold the royal garden path!\nOnly CapyQueen, CapyPult, and CapyPrincess towers are available.\nCollect diamonds dropped by defeated enemies.\nDiamonds grant 25 gold each!\nGood luck, defender!\n", { size: 55, fill: 0x000000, weight: 800, align: 'center' }); instructionsTextShadow.anchor.set(0.5, 0.5); instructionsTextShadow.x = 4; // Shadow offset instructionsTextShadow.y = 4; // Centered vertically with shadow offset // Set maximum width to ensure text wraps properly instructionsTextShadow.wordWrap = true; instructionsTextShadow.wordWrapWidth = 1800; self.addChild(instructionsTextShadow); var instructionsText = new Text2("Welcome to Queen's Walk Challenge!\nBehold the royal garden path!\nOnly CapyQueen, CapyPult, and CapyPrincess towers are available.\nCollect diamonds dropped by defeated enemies.\nDiamonds grant 25 gold each!\nGood luck, defender!\n", { size: 55, fill: 0xFFFFFF, weight: 800, align: 'center' }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 0; instructionsText.y = 0; // Centered vertically in screen // Set maximum width to ensure text wraps properly instructionsText.wordWrap = true; instructionsText.wordWrapWidth = 1800; self.addChild(instructionsText); // Continue button at the top var continueButton = new Container(); var buttonBg = continueButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 150; buttonBg.tint = 0x9900FF; var buttonTextShadow = new Text2("Continue", { size: 80, fill: 0x000000, weight: 800 }); buttonTextShadow.anchor.set(0.5, 0.5); buttonTextShadow.x = 2; buttonTextShadow.y = 2; continueButton.addChild(buttonTextShadow); var buttonText = new Text2("Continue", { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); continueButton.addChild(buttonText); continueButton.x = 0; continueButton.y = -2732 / 2 + 300; // Position at top self.addChild(continueButton); // Continue button handler - close the screen continueButton.down = function (x, y, obj) { // Unpause the game self.gamePaused = false; // Show UI labels when continue button is pressed goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; // Remove instructions screen self.destroy(); }; return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; // Increase size of base for easier touch var assetName = 'tower_' + self.towerType; var baseGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.99, scaleY: 0.99, y: -40 }); var towerCost = getTowerCost(self.towerType); // Add shadow for tower type label below the tower var towerNames = { 'default': 'CapyShot', 'rapid': 'CapyBlast', 'sniper': 'CapySniper', 'splash': 'CapyPult', 'slow': 'CapyFreeze', 'poison': 'CapyPoison', 'bomb': 'CapyBomb', 'demolition': 'CapyDemolition', 'queen': 'CapyQueen', 'king': 'CapyKing', 'prince': 'CapyPrince', 'princess': 'CapyPrincess' }; var displayName = towerNames[self.towerType] || self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1); var typeLabelShadow = new Text2(displayName, { size: 40, fill: 0x000000, weight: 800 }); typeLabelShadow.anchor.set(0.5, 0.5); typeLabelShadow.x = 4; // Shadow offset but still centered relative to tower typeLabelShadow.y = 75 + 4; // Position higher up with shadow offset self.addChild(typeLabelShadow); // Add tower type label below the tower var typeLabel = new Text2(displayName, { size: 40, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.x = 0; // Explicitly center horizontally typeLabel.y = 75; // Position higher up self.addChild(typeLabel); // Add cost shadow var costLabelShadow = new Text2(towerCost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; // Shadow offset but centered horizontally costLabelShadow.y = 115 + 4; // Position higher up with shadow offset self.addChild(costLabelShadow); // Add cost label var costLabel = new Text2(towerCost, { size: 50, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.x = 0; // Explicitly center horizontally costLabel.y = 115; // Position higher up self.addChild(costLabel); // Add scaling animation function self.startScalingAnimation = function () { // Function to animate to larger scale (108%) function scaleUp() { tween(baseGraphics, { scaleX: 1.07, // Slightly smaller than tower scaling to 1.07 scaleY: 1.07 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleDown(); } }); } // Function to animate to smaller scale (93%) function scaleDown() { tween(baseGraphics, { scaleX: 0.93, // Slightly smaller than tower scaling to 0.93 scaleY: 0.93 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleUp(); } }); } // Start the animation cycle scaleUp(); }; self.update = function () { // Initialize scaling animation on first update if (!self.scalingInitialized) { self.scalingInitialized = true; // Start the continuous scaling animation self.startScalingAnimation(); } // Check if player can afford this tower var canAfford = gold >= getTowerCost(self.towerType); // Check if this is a single-use tower that has already been placed var isBlocked = self.towerType === 'king' && placedTowers.king || self.towerType === 'queen' && placedTowers.queen; // Check if this tower is allowed in challenge mode var isChallengeBlocked = challengeMode && challengeAllowedTowers.indexOf(self.towerType) === -1; // Set opacity based on affordability, placement status, and challenge restrictions if (isBlocked || isChallengeBlocked) { self.alpha = 0.3; // Very dim for blocked towers } else { self.alpha = canAfford ? 1 : 0.5; } }; return self; }); var StoryScreen = Container.expand(function () { var self = Container.call(this); // Pause the game when story screen is shown self.gamePaused = true; // Hide UI labels when story screen is shown goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 1.0; // Create 6 independent story images var storyImages = []; var imageAssets = ['tutorial_image1', 'tutorial_image2', 'tutorial_image3', 'tutorial_image4', 'tutorial_image5', 'tutorial_image6']; // Define positions for ordered 2x3 grid layout var positions = [{ x: -512, y: -900 }, // Top-left { x: 512, y: -900 }, // Top-right { x: -512, y: 0 }, // Middle-left { x: 512, y: 0 }, // Middle-right { x: -512, y: 900 }, // Bottom-left { x: 512, y: 900 } // Bottom-right ]; for (var i = 0; i < 6; i++) { var storyImage = self.attachAsset(imageAssets[i], { anchorX: 0.5, anchorY: 0.5 }); storyImage.x = positions[i].x; storyImage.y = positions[i].y; storyImage.width = 816; // 960 * 0.85 = 816 (15% smaller than current size) storyImage.height = 816; // 960 * 0.85 = 816 (15% smaller than current size) storyImage.alpha = 0; // Start hidden storyImage.imageIndex = i; // Store index for reference storyImages.push(storyImage); } // Start independent animations for each image self.startIndependentAnimations = function () { for (var i = 0; i < storyImages.length; i++) { (function (imageIndex) { var image = storyImages[imageIndex]; var delay = imageIndex * 1000; // Each image starts 1 second after the previous // Start animation after delay LK.setTimeout(function () { tween(image, { alpha: 1 }, { duration: 500, easing: tween.easeInOut }); }, delay); })(i); } // Remove automatic timeout - only allow manual skip by clicking }; // Continue to normal game flow self.continueToGame = function () { // Show UI labels when continuing goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; // Remove story screen and show mode selector self.destroy(); var modeSelector = new ModeSelector(); modeSelector.x = 2048 / 2; modeSelector.y = 2732 / 2; game.addChild(modeSelector); }; // Allow skipping by tapping self.down = function (x, y, obj) { self.continueToGame(); }; // Start independent animations when screen is created LK.setTimeout(function () { self.startIndependentAnimations(); }, 500); return self; }); /** * TOWER SYSTEM ARCHITECTURE * * The tower system consists of 4 main components: * 1. SourceTower - Menu towers at bottom for selecting tower types * 2. TowerPreview - Visual preview when dragging to place towers * 3. Tower - Functional placed towers that attack enemies * 4. UpgradeMenu - UI for upgrading/selling towers * * TOWER TYPES (6 total): * - default: Balanced tower (gray, 5 gold) * - rapid: Fast firing, low damage (blue, 15 gold) * - sniper: Long range, high damage (orange, 25 gold) * - splash: Area damage (green, 35 gold) * - slow: Slows enemies (purple, 45 gold) * - poison: Damage over time (teal, 55 gold) * * UPGRADE SYSTEM: * - 6 levels maximum per tower * - Exponential cost scaling: base_cost * (2^(level-1)) * - Final upgrade costs 1.75x but gives 2x effect * - Visual level indicators (dots) below each tower * * TARGETING SYSTEM: * - Ground enemies: Prioritized by path score (closer to exit) * - Flying enemies: Prioritized by distance to goal * - Range calculated dynamically based on tower type and level * * SPECIAL MECHANICS: * - Towers occupy 2x2 grid cells * - Cannot block enemy paths * - Recoil animation when firing * - Type-specific bullet effects and visuals */ var Tower = Container.expand(function (id) { var self = Container.call(this); self.id = id || 'default'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.range = 3 * CELL_SIZE; // RANGE CALCULATION SYSTEM // Each tower type has unique range scaling: // - Sniper: Starts at 5 blocks, huge boost at max level (12 blocks) // - Splash: Short range (2-4 blocks) for balance // - Others: Standard scaling with incremental increases per level self.getRange = function () { // Always calculate range based on tower type and level switch (self.id) { case 'sniper': // Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost if (self.level === self.maxLevel) { return 12 * CELL_SIZE; // Significantly increased range for max level } return (5 + (self.level - 1) * 0.8) * CELL_SIZE; case 'splash': // Splash: base 2, +0.2 per level (max ~4 blocks at max level) return (2 + (self.level - 1) * 0.2) * CELL_SIZE; case 'rapid': // Rapid: base 2.5, +0.5 per level return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'slow': // Slow: base 3.5, +0.5 per level return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'poison': // Poison: base 3.2, +0.5 per level return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; case 'bomb': // Bomb: base 3.2, +0.5 per level (same as poison) return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; case 'queen': // Queen: base 7.875, +1.125 per level (125% increase over slow/CapyFreeze - additional 50% boost) return (7.875 + (self.level - 1) * 1.125) * CELL_SIZE; case 'king': // King: base 8.5, +1.36 per level, huge boost at max level (70% increase over sniper/CapySniper - reduced by 30%) if (self.level === self.maxLevel) { return 20.4 * CELL_SIZE; // Significantly increased range for max level (70% increase) } return (8.5 + (self.level - 1) * 1.36) * CELL_SIZE; // 70% increase over sniper base case 'demolition': // Demolition: base 3.2, +0.5 per level (same as bomb) return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; case 'prince': // Prince: base 7.0, +1.36 per level (50% reduced range) return (7.0 + (self.level - 1) * 1.36) * CELL_SIZE; case 'princess': // Princess: base 6.4, +1.0 per level (double poison range) return (6.4 + (self.level - 1) * 1.0) * CELL_SIZE; default: // Default: base 3, +0.5 per level return (3 + (self.level - 1) * 0.5) * CELL_SIZE; } }; self.cellsInRange = []; self.fireRate = 60; self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; switch (self.id) { case 'rapid': self.fireRate = 30; self.damage = 5; self.range = 2.5 * CELL_SIZE; self.bulletSpeed = 7; break; case 'sniper': self.fireRate = 90; self.damage = 25; self.range = 5 * CELL_SIZE; self.bulletSpeed = 25; break; case 'splash': self.fireRate = 75; self.damage = 15; self.range = 2 * CELL_SIZE; self.bulletSpeed = 4; break; case 'slow': self.fireRate = 50; self.damage = 8; self.range = 3.5 * CELL_SIZE; self.bulletSpeed = 5; break; case 'poison': self.fireRate = 70; self.damage = 12; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 5; break; case 'bomb': self.fireRate = 70; self.damage = 50; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 5; // Special bomb tower behavior - explodes after 2 seconds self.isBombTower = true; self.explosionTimer = 120; // 2 seconds at 60fps self.hasExploded = false; break; case 'queen': // Queen tower (enhanced version of slow/CapyFreeze) self.fireRate = 50; self.damage = 24; self.range = 5.25 * CELL_SIZE; self.bulletSpeed = 5; break; case 'king': // King tower (5x damage multiplier over sniper/CapySniper) self.fireRate = 90; self.damage = 125; // 25 * 5 = 125 self.range = 5 * CELL_SIZE; self.bulletSpeed = 25; break; case 'demolition': self.fireRate = 70; self.damage = 100; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 5; // Special bomb tower behavior - explodes after 2 seconds self.isBombTower = true; self.explosionTimer = 120; // 2 seconds at 60fps self.hasExploded = false; break; case 'prince': self.fireRate = 75; self.damage = 30; self.range = 14.0 * CELL_SIZE; self.bulletSpeed = 4; break; case 'princess': self.fireRate = 70; self.damage = 12; self.range = 6.4 * CELL_SIZE; self.bulletSpeed = 5; break; } var assetName = 'tower_' + self.id; var baseGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); 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(); var outlineCircle = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); outlineCircle.width = dotSize + 4; outlineCircle.height = dotSize + 4; outlineCircle.tint = 0x000000; var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); towerLevelIndicator.width = dotSize; towerLevelIndicator.height = dotSize; towerLevelIndicator.tint = 0xCCCCCC; dot.x = -CELL_SIZE + dotSpacing * (i + 1); dot.y = CELL_SIZE * 0.7; self.addChild(dot); levelIndicators.push(dot); } var gunContainer = new Container(); self.addChild(gunContainer); var gunGraphics = gunContainer.attachAsset('defense', { anchorX: 0.5, anchorY: 0.5 }); gunGraphics.alpha = 0; self.updateLevelIndicators = function () { for (var i = 0; i < maxDots; i++) { var dot = levelIndicators[i]; var towerLevelIndicator = dot.children[1]; if (i < self.level) { towerLevelIndicator.tint = 0xFFFFFF; } else { towerLevelIndicator.tint = 0xCCCCCC; } } }; self.updateLevelIndicators(); // Add scaling animation function self.startScalingAnimation = function () { // Function to animate to larger scale (108%) function scaleUp() { tween(baseGraphics, { scaleX: 1.08, scaleY: 1.08 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleDown(); } }); } // Function to animate to smaller scale (92%) function scaleDown() { tween(baseGraphics, { scaleX: 0.92, scaleY: 0.92 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleUp(); } }); } // Start the animation cycle scaleUp(); }; self.refreshCellsInRange = function () { for (var i = 0; i < self.cellsInRange.length; i++) { var cell = self.cellsInRange[i]; var towerIndex = cell.towersInRange.indexOf(self); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } self.cellsInRange = []; var rangeRadius = self.getRange() / CELL_SIZE; var centerX = self.gridX + 1; var centerY = self.gridY + 1; var minI = Math.floor(centerX - rangeRadius - 0.5); var maxI = Math.ceil(centerX + rangeRadius + 0.5); var minJ = Math.floor(centerY - rangeRadius - 0.5); var maxJ = Math.ceil(centerY + rangeRadius + 0.5); for (var i = minI; i <= maxI; i++) { for (var j = minJ; j <= maxJ; j++) { var closestX = Math.max(i, Math.min(centerX, i + 1)); var closestY = Math.max(j, Math.min(centerY, j + 1)); var deltaX = closestX - centerX; var deltaY = closestY - centerY; var distanceSquared = deltaX * deltaX + deltaY * deltaY; if (distanceSquared <= rangeRadius * rangeRadius) { var cell = grid.getCell(i, j); if (cell) { self.cellsInRange.push(cell); cell.towersInRange.push(self); } } } } grid.renderDebug(); }; self.getTotalValue = function () { var baseTowerCost = getTowerCost(self.id); var totalInvestment = baseTowerCost; var baseUpgradeCost = baseTowerCost; // Upgrade cost now scales with base tower cost for (var i = 1; i < self.level; i++) { totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1)); } return totalInvestment; }; self.upgrade = function () { if (self.level < self.maxLevel) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.id); var upgradeCost; // Make last upgrade level extra expensive if (self.level === self.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); // Half the cost for final upgrade } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1)); } if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; // No need to update self.range here; getRange() is now the source of truth // Apply tower-specific upgrades based on type if (self.id === 'rapid') { if (self.level === self.maxLevel) { // Extra powerful last upgrade (double the effect) self.fireRate = Math.max(4, 30 - self.level * 9); // double the effect self.damage = 5 + self.level * 10; // double the effect self.bulletSpeed = 7 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(15, 30 - self.level * 3); // Fast tower gets faster with upgrades self.damage = 5 + self.level * 3; self.bulletSpeed = 7 + self.level * 0.7; } } else { if (self.level === self.maxLevel) { // Extra powerful last upgrade for all other towers (double the effect) self.fireRate = Math.max(5, 60 - self.level * 24); // double the effect self.damage = 10 + self.level * 20; // double the effect self.bulletSpeed = 5 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(20, 60 - self.level * 8); self.damage = 10 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.5; } } self.refreshCellsInRange(); self.updateLevelIndicators(); if (self.level > 1) { var levelDot = levelIndicators[self.level - 1].children[1]; tween(levelDot, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut, onFinish: function onFinish() { tween(levelDot, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); } return true; } else { var notification = game.addChild(new Notification("Not enough gold to upgrade!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } return false; }; // TARGETING PRIORITY SYSTEM // Ground enemies: Use pathfinding score (lower = closer to exit) // Flying enemies: Use distance to their flight goal // This ensures towers target the most dangerous enemies first self.findTarget = function () { var closestEnemy = null; var closestScore = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if enemy is in range if (distance <= self.getRange()) { // Handle flying enemies differently - they can be targeted regardless of path if (enemy.isFlying) { // For flying enemies, prioritize by distance to the goal if (enemy.flyingTarget) { var goalX = enemy.flyingTarget.x; var goalY = enemy.flyingTarget.y; var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY)); // Use distance to goal as score if (distToGoal < closestScore) { closestScore = distToGoal; closestEnemy = enemy; } } else { // If no flying target yet (shouldn't happen), prioritize by distance to tower if (distance < closestScore) { closestScore = distance; closestEnemy = enemy; } } } else { // For ground enemies, use the original path-based targeting // Get the cell for this enemy var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell && cell.pathId === pathId) { // Use the cell's score (distance to exit) for prioritization // Lower score means closer to exit if (cell.score < closestScore) { closestScore = cell.score; closestEnemy = enemy; } } } } } if (!closestEnemy) { self.targetEnemy = null; } return closestEnemy; }; self.update = function () { // Initialize scaling animation on first update if (!self.scalingInitialized) { self.scalingInitialized = true; // Start the continuous scaling animation self.startScalingAnimation(); } // Handle bomb tower special behavior if (self.isBombTower && !self.hasExploded) { self.explosionTimer--; if (self.explosionTimer <= 0) { self.explode(); return; // Don't continue with normal tower behavior after exploding } // Visual warning when close to explosion if (self.explosionTimer <= 60) { // Last second warning var warningIntensity = Math.sin(self.explosionTimer * 0.5) * 0.5 + 0.5; baseGraphics.tint = 0xFF0000 * warningIntensity + 0xFFFFFF * (1 - warningIntensity); } } // Normal tower behavior for non-bomb towers only if (!self.isBombTower) { self.targetEnemy = self.findTarget(); if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); gunContainer.rotation = angle; // Apply game speed to fire rate - higher speed means faster firing var adjustedFireRate = self.fireRate / gameSpeed; if (LK.ticks - self.lastFired >= adjustedFireRate) { self.fire(); self.lastFired = LK.ticks; } } } }; self.down = function (x, y, obj) { // Block tower interactions when ModeSelector or tutorial instruction screens are active var modeSelectorActive = game.children.some(function (child) { return child instanceof ModeSelector; }); var instructions2Active = game.children.some(function (child) { return child instanceof Instructions2Screen; }); var instructions3Active = game.children.some(function (child) { return child instanceof Instructions3Screen; }); if (modeSelectorActive || instructions2Active || instructions3Active) { return; } // Don't show upgrade menu for bomb towers since they explode if (self.isBombTower) { var notification = game.addChild(new Notification("Bomb towers cannot be upgraded - they explode!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); var hasOwnMenu = false; var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self) { rangeCircle = game.children[i]; break; } } for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hasOwnMenu = true; break; } } if (hasOwnMenu) { for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } if (rangeCircle) { game.removeChild(rangeCircle); } selectedTower = null; grid.renderDebug(); return; } for (var i = 0; i < existingMenus.length; i++) { existingMenus[i].destroy(); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = self; var rangeIndicator = new Container(); rangeIndicator.isTowerRange = true; rangeIndicator.tower = self; game.addChild(rangeIndicator); rangeIndicator.x = self.x; rangeIndicator.y = self.y; var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.getRange() * 2; rangeGraphics.alpha = 0.2; var upgradeMenu = new UpgradeMenu(self); game.addChild(upgradeMenu); upgradeMenu.x = 2048 / 2; tween(upgradeMenu, { y: 2732 - 225 }, { duration: 200, easing: tween.backOut }); grid.renderDebug(); }; self.isInRange = function (enemy) { if (!enemy) { return false; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance <= self.getRange(); }; // BULLET EFFECT SYSTEM // Each tower type creates specialized bullets: // - splash: Area damage to nearby enemies // - slow: Reduces enemy speed (scales with tower level) // - poison: Damage over time effect // - sniper: High damage with visual critical effect // Bullets track their type and apply effects on impact self.fire = function () { if (self.targetEnemy) { var potentialDamage = 0; for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) { potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage; } if (self.targetEnemy.health > potentialDamage) { var bulletX = self.x + Math.cos(gunContainer.rotation) * 40; var bulletY = self.y + Math.sin(gunContainer.rotation) * 40; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed * gameSpeed); // Set bullet type based on tower type bullet.type = self.id; // For slow and queen towers, pass level for scaling slow effect if (self.id === 'slow' || self.id === 'queen') { bullet.sourceTowerLevel = self.level; } // Customize bullet appearance based on tower type switch (self.id) { case 'rapid': bullet.children[0].tint = 0x00AAFF; bullet.children[0].width = 20; bullet.children[0].height = 20; break; case 'sniper': bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; case 'splash': bullet.children[0].tint = 0x33CC00; bullet.children[0].width = 40; bullet.children[0].height = 40; break; case 'slow': bullet.children[0].tint = 0x9900FF; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'poison': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'bomb': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'queen': // Queen bullets (similar to slow) bullet.children[0].tint = 0x9900FF; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'king': // King bullets (similar to sniper) bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; case 'demolition': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'prince': bullet.children[0].tint = 0x33CC00; bullet.children[0].width = 40; bullet.children[0].height = 40; break; case 'princess': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; } game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); // --- Fire recoil effect for gunContainer --- // Stop any ongoing recoil tweens before starting a new one tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); // Always use the original resting position for recoil, never accumulate offset if (gunContainer._restX === undefined) { gunContainer._restX = 0; } if (gunContainer._restY === undefined) { gunContainer._restY = 0; } if (gunContainer._restScaleX === undefined) { gunContainer._restScaleX = 1; } if (gunContainer._restScaleY === undefined) { gunContainer._restScaleY = 1; } // Reset to resting position before animating (in case of interrupted tweens) gunContainer.x = gunContainer._restX; gunContainer.y = gunContainer._restY; gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; // Calculate recoil offset (recoil back along the gun's rotation) var recoilDistance = 8; var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance; var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance; // Animate recoil back from the resting position tween(gunContainer, { x: gunContainer._restX + recoilX, y: gunContainer._restY + recoilY }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { // Animate return to original position/scale tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: 90, easing: tween.cubicIn }); } }); } } }; self.explode = function () { if (self.hasExploded) return; self.hasExploded = true; // Create explosion visual effect var explosionEffect = new EffectIndicator(self.x, self.y, 'explosion'); game.addChild(explosionEffect); // Deal area damage to all enemies in range var explosionRange = self.getRange() * 3.0; // Double the explosion range multiplier var explosionDamage = self.damage * (self.level + 2) * 2; // Double the explosion damage scaling for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRange) { // Apply explosion damage enemy.health -= explosionDamage; if (enemy.health <= 0) { enemy.health = 0; } else { enemy.healthBar.width = enemy.health / enemy.maxHealth * 70; } // Create damage indicator for each hit enemy var damageEffect = new EffectIndicator(enemy.x, enemy.y, 'explosion'); game.addChild(damageEffect); } } // Remove tower from grid and clean up var gridX = self.gridX; var gridY = self.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; // Clear cell var towerIndex = cell.towersInRange.indexOf(self); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } // Remove from towers array var towerIndex = towers.indexOf(self); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } // Close any upgrade menu for this tower var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } // Remove range indicator if exists for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange && game.children[i].tower === self) { game.removeChild(game.children[i]); } } // Clear selected tower if this was selected if (selectedTower === self) { selectedTower = null; } // Animate tower destruction tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { towerLayer.removeChild(self); grid.pathFind(); grid.renderDebug(); } }); }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; // Special behavior for Hot Potato challenge if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) { if (self.id === 'bomb' || self.id === 'demolition') { // Create projectile tower instead of regular tower var projectile = new ProjectileTower(self.id, self.x, self.y); game.addChild(projectile); // Launch immediately after short delay LK.setTimeout(function () { projectile.launch(); }, 500); // Don't place regular tower, just return return; } } 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; // Set alpha to 0.5 for wall cells in tutorial mode if (tutorialMode && cell.debugCell) { cell.debugCell.children[0].alpha = 0.5; } } } } self.refreshCellsInRange(); }; return self; }); var TowerPreview = Container.expand(function () { var self = Container.call(this); var towerRange = 3; var rangeInPixels = towerRange * CELL_SIZE; self.towerType = 'default'; self.hasEnoughGold = true; var rangeIndicator = new Container(); self.addChild(rangeIndicator); var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.alpha = 0.3; var assetName = 'towerpreview_' + self.towerType; var previewGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; self.canPlace = false; self.gridX = 0; self.gridY = 0; self.blockedByEnemy = false; self.update = function () { var previousHasEnoughGold = self.hasEnoughGold; self.hasEnoughGold = gold >= getTowerCost(self.towerType); // Only update appearance if the affordability status has changed if (previousHasEnoughGold !== self.hasEnoughGold) { self.updateAppearance(); } }; self.updateAppearance = function () { // Update the preview graphics asset to match the tower type var newAssetName = 'towerpreview_' + self.towerType; if (previewGraphics.assetName !== newAssetName) { // Remove old graphics self.removeChild(previewGraphics); // Create new graphics with correct asset previewGraphics = self.attachAsset(newAssetName, { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; previewGraphics.assetName = newAssetName; } // Use Tower class to get the source of truth for range var tempTower = new Tower(self.towerType); var previewRange = tempTower.getRange(); // Clean up tempTower to avoid memory leaks if (tempTower && tempTower.destroy) { tempTower.destroy(); } // Set range indicator using unified range logic rangeGraphics.width = rangeGraphics.height = previewRange * 2; if (!self.canPlace) { previewGraphics.tint = 0xFF0000; // Red tint when cannot place } else if (!self.hasEnoughGold) { previewGraphics.tint = 0xFFFFFF; // Keep white when insufficient gold } else { previewGraphics.tint = 0xFFFFFF; // White when can place } }; self.updatePlacementStatus = function () { var validGridPlacement = true; if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) { validGridPlacement = false; } else { for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(self.gridX + i, self.gridY + j); if (!cell || cell.type !== 0) { validGridPlacement = false; break; } // Check if Hot Potato challenge is active and prevent placement on non-blue floor cells if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) { var gridWidth = 24; var gridHeight = 29 + 6; var bottomThirdStart = Math.floor(gridHeight * 2 / 3); // Start of bottom third var cellY = self.gridY + j; // Only allow placement on blue floor cells (bottom third) if (cellY < bottomThirdStart || cellY >= gridHeight - 4) { validGridPlacement = false; break; } } } if (!validGridPlacement) { break; } } } self.blockedByEnemy = false; if (validGridPlacement) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.currentCellY < 4) { continue; } // Only check non-flying enemies, flying enemies can pass over towers if (!enemy.isFlying) { if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) { self.blockedByEnemy = true; break; } if (enemy.currentTarget) { var targetX = enemy.currentTarget.x; var targetY = enemy.currentTarget.y; if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) { self.blockedByEnemy = true; break; } } } } } self.canPlace = validGridPlacement && !self.blockedByEnemy; self.hasEnoughGold = gold >= getTowerCost(self.towerType); self.updateAppearance(); }; self.checkPlacement = function () { self.updatePlacementStatus(); }; self.snapToGrid = function (x, y) { var gridPosX = x - grid.x; var gridPosY = y - grid.y; self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; self.checkPlacement(); }; return self; }); var TutorialCompleteScreen = Container.expand(function () { var self = Container.call(this); // Pause the game when tutorial complete screen is shown self.gamePaused = true; // Hide UI labels when tutorial complete screen is shown goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Instructions3 image at the bottom using independent asset var instructionsImage = self.attachAsset('tutorialcomplete_image1', { anchorX: 0.5, anchorY: 1.0 }); instructionsImage.x = 300; // Move away from center instructionsImage.y = 2732 / 2 - 100; // Position higher up instructionsImage.width = 600; // Reduce size to fit better instructionsImage.height = 600; // Reduce size to fit better // Duplicate Instructions3 image positioned independently var instructionsImageDuplicate = self.attachAsset('tutorialcomplete_image2', { anchorX: 0.5, anchorY: 1.0 }); instructionsImageDuplicate.x = -300; // Position independently to the left instructionsImageDuplicate.y = 2732 / 2 - 100; // Same vertical position instructionsImageDuplicate.width = 600; // Match the other image size instructionsImageDuplicate.height = 600; // Match the other image size // Instructions text label in the center of the screen var instructionsTextShadow = new Text2("Congratulations!\nYou have completed the tutorial!\nYou are now ready to defend the castle\nin the full game. Go forth, defender!\n", { size: 55, fill: 0x000000, weight: 800, align: 'center' }); instructionsTextShadow.anchor.set(0.5, 0.5); instructionsTextShadow.x = 4; // Shadow offset instructionsTextShadow.y = 4; // Centered vertically with shadow offset // Set maximum width to ensure text wraps properly instructionsTextShadow.wordWrap = true; instructionsTextShadow.wordWrapWidth = 1800; self.addChild(instructionsTextShadow); var instructionsText = new Text2("Congratulations!\nYou have completed the tutorial!\nYou are now ready to defend the castle\nin the full game. Go forth, defender!\n", { size: 55, fill: 0xFFFFFF, weight: 800, align: 'center' }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 0; instructionsText.y = 0; // Centered vertically in screen // Set maximum width to ensure text wraps properly instructionsText.wordWrap = true; instructionsText.wordWrapWidth = 1800; self.addChild(instructionsText); // Continue button at the top var continueButton = new Container(); var buttonBg = continueButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 150; buttonBg.tint = 0x00AA00; var buttonTextShadow = new Text2("Continue", { size: 80, fill: 0x000000, weight: 800 }); buttonTextShadow.anchor.set(0.5, 0.5); buttonTextShadow.x = 2; buttonTextShadow.y = 2; continueButton.addChild(buttonTextShadow); var buttonText = new Text2("Continue", { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); continueButton.addChild(buttonText); continueButton.x = 0; continueButton.y = -2732 / 2 + 300; // Position at top self.addChild(continueButton); // Continue button handler - restart game and show level selection continueButton.down = function (x, y, obj) { // Restart the game by resetting all game state tutorialMode = false; selectedLevel = 'garden'; difficultyMultiplier = 1.0; totalWaves = 50; nextWaveTime = 12000 / 2; currentWave = 0; waveTimer = 0; waveInProgress = false; waveSpawned = false; gold = 80; lives = 20; score = 0; enemies = []; towers = []; bullets = []; diamonds = []; gameSpeed = 1.0; currentSpeedIndex = 0; placedTowers = { king: false, queen: false }; instructions2Shown = false; selectedTower = null; pathId = 1; maxScore = 0; // Stop all tween animations before resetting tween.stop(game); for (var i = 0; i < enemies.length; i++) { if (enemies[i]) { tween.stop(enemies[i]); if (enemies[i].children && enemies[i].children[0]) { tween.stop(enemies[i].children[0]); } } } for (var i = 0; i < towers.length; i++) { if (towers[i] && towers[i].children) { for (var j = 0; j < towers[i].children.length; j++) { if (towers[i].children[j]) { tween.stop(towers[i].children[j]); } } } } for (var i = 0; i < sourceTowers.length; i++) { if (sourceTowers[i] && sourceTowers[i].children) { for (var j = 0; j < sourceTowers[i].children.length; j++) { if (sourceTowers[i].children[j]) { tween.stop(sourceTowers[i].children[j]); } } } } // Clear and destroy all enemies from all enemy layers for (var i = enemyLayerBottom.children.length - 1; i >= 0; i--) { var enemy = enemyLayerBottom.children[i]; if (enemy && enemy.destroy) { enemy.destroy(); } else { enemyLayerBottom.removeChild(enemy); } } for (var i = enemyLayerMiddle.children.length - 1; i >= 0; i--) { var shadow = enemyLayerMiddle.children[i]; if (shadow && shadow.destroy) { shadow.destroy(); } else { enemyLayerMiddle.removeChild(shadow); } } for (var i = enemyLayerTop.children.length - 1; i >= 0; i--) { var flyingEnemy = enemyLayerTop.children[i]; if (flyingEnemy && flyingEnemy.destroy) { flyingEnemy.destroy(); } else { enemyLayerTop.removeChild(flyingEnemy); } } // Clear all towers from tower layer and reset their grid cells for (var i = towerLayer.children.length - 1; i >= 0; i--) { var child = towerLayer.children[i]; if (child instanceof Tower) { // Reset grid cells occupied by this tower for (var x = 0; x < 2; x++) { for (var y = 0; y < 2; y++) { var cell = grid.getCell(child.gridX + x, child.gridY + y); if (cell) { cell.type = 0; // Reset to walkable var towerIndex = cell.towersInRange.indexOf(child); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } towerLayer.removeChild(child); } else if (child instanceof SourceTower) { // Keep source towers but don't remove them } } // Reset grid by removing and recreating it completely if (grid) { debugLayer.removeChild(grid); } grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Clear all bullets from the game for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i].parent) { bullets[i].parent.removeChild(bullets[i]); } } bullets = []; // Clear all game children except essential elements and recreate background for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; if (child !== debugLayer && child !== towerLayer && child !== enemyLayer) { if (child && child.destroy) { child.destroy(); } else { game.removeChild(child); } } } // Recreate background with proper asset var newBackgroundAssetName = getBackgroundAssetName(); backgroundImage = game.attachAsset(newBackgroundAssetName, { anchorX: 0.5, anchorY: 0.5 }); backgroundImage.x = 2048 / 2; backgroundImage.y = 2732 / 2; backgroundImage.width = 2048; backgroundImage.height = 2732; game.addChildAt(backgroundImage, 0); // Remove and recreate wave indicator completely if (waveIndicator) { game.removeChild(waveIndicator); } waveIndicator = new WaveIndicator(); waveIndicator.x = 2048 / 2; waveIndicator.y = 2732 - 80; game.addChild(waveIndicator); // Clear existing source towers and recreate them for (var i = sourceTowers.length - 1; i >= 0; i--) { var sourceTower = sourceTowers[i]; if (sourceTower.parent) { sourceTower.parent.removeChild(sourceTower); } } sourceTowers = []; var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison', 'bomb', 'queen', 'king']; var towerSpacing = 220; var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2; var towerY = 2732 - CELL_SIZE * 3 - 90; for (var t = 0; t < towerTypes.length; t++) { var tower = new SourceTower(towerTypes[t]); tower.x = startX + t * towerSpacing; tower.y = towerY; // Reset scaling animation state tower.scalingInitialized = false; // Make sure tower is visible and enabled tower.visible = true; tower.alpha = 1.0; towerLayer.addChild(tower); sourceTowers.push(tower); } // Remove and recreate next wave button completely if (nextWaveButton) { if (nextWaveButton.parent) { nextWaveButton.parent.removeChild(nextWaveButton); } } if (nextWaveButtonContainer) { if (nextWaveButtonContainer.parent) { nextWaveButtonContainer.parent.removeChild(nextWaveButtonContainer); } } // Reset tower preview functionality if (towerPreview) { game.removeChild(towerPreview); } towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; // Recreate next wave button container and button nextWaveButtonContainer = new Container(); nextWaveButton = new NextWaveButton(); nextWaveButton.x = 2048 - 200; nextWaveButton.y = 2732 - 100 + 20; nextWaveButton.enabled = false; nextWaveButton.visible = false; nextWaveButtonContainer.addChild(nextWaveButton); game.addChild(nextWaveButtonContainer); // Recreate velocity button if (velocityButton && velocityButton.parent) { velocityButton.parent.removeChild(velocityButton); } velocityButton = new Container(); var velocityButtonBackground = velocityButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); velocityButtonBackground.width = 280; velocityButtonBackground.height = 120; velocityButtonBackground.tint = 0x00AA88; var velocityButtonText = new Text2("Velocity", { size: 45, fill: 0xFFFFFF, weight: 800 }); velocityButtonText.anchor.set(0.5, 0.5); velocityButtonText.y = -15; var velocitySpeedText = new Text2("x" + gameSpeed.toFixed(1), { size: 40, fill: 0xFFD700, weight: 800 }); velocitySpeedText.anchor.set(0.5, 0.5); velocitySpeedText.y = 32; velocityButton.addChild(velocityButtonText); velocityButton.addChild(velocitySpeedText); velocityButton.x = 200; velocityButton.y = 2732 - 80; velocityButton.visible = false; velocityButton.enabled = false; // Add velocity button functionality velocityButton.down = function () { if (!velocityButton.enabled) { return; } currentSpeedIndex = (currentSpeedIndex + 1) % speedMultipliers.length; gameSpeed = speedMultipliers[currentSpeedIndex]; velocitySpeedText.setText("x" + gameSpeed.toFixed(1)); // Visual feedback tween(velocityButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(velocityButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeIn }); } }); var notification = game.addChild(new Notification("Game speed set to x" + gameSpeed.toFixed(1) + "!")); notification.x = 2048 / 2; notification.y = grid.height - 100; }; velocityButton.update = function () { if (waveIndicator && waveIndicator.gameStarted) { velocityButton.enabled = true; velocityButton.visible = true; velocityButton.alpha = 1; } else { velocityButton.enabled = false; velocityButton.visible = false; velocityButton.alpha = 0.7; } }; game.addChild(velocityButton); // Reset dragging state and other variables isDragging = false; sourceTower = null; // Reset UI visibility goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; updateUI(); // Hide start game button again since we're going back to level selection if (waveIndicator) { waveIndicator.waveMarkers[0].visible = false; } // Show level selector for fresh level selection var levelSelector = new LevelSelector(); levelSelector.x = 2048 / 2; levelSelector.y = 2732 / 2; game.addChild(levelSelector); // Remove tutorial complete screen self.destroy(); }; return self; }); var TutorialIntroScreen = Container.expand(function () { var self = Container.call(this); // Hide UI labels when tutorial intro screen is shown goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Tutorial image at the bottom var tutorialImage = self.attachAsset('tutorial_image', { anchorX: 0.5, anchorY: 1.0 }); tutorialImage.x = 0; tutorialImage.y = 2732 / 2 - 100; // Position higher up tutorialImage.width = 800; tutorialImage.height = 800; // Welcome text label in the center of the screen var welcomeTextShadow = new Text2("Welcome to the castle!\nI am The Capy King.\nWe need your help to build the castle defenses.\nOur CapyWarriors are ready to deploy.\nUse the gold to place your first CapyWarrior.\n", { size: 55, fill: 0x000000, weight: 800, align: 'center' }); welcomeTextShadow.anchor.set(0.5, 0.5); welcomeTextShadow.x = 4; // Shadow offset welcomeTextShadow.y = 4; // Centered vertically with shadow offset // Set maximum width to ensure text wraps properly welcomeTextShadow.wordWrap = true; welcomeTextShadow.wordWrapWidth = 1800; self.addChild(welcomeTextShadow); var welcomeText = new Text2("Welcome to the castle!\nI am The Capy King.\nWe need your help to build the castle defenses.\nOur CapyWarriors are ready to deploy.\nUse the gold to place your first CapyWarrior.\n", { size: 55, fill: 0xFFFFFF, weight: 800, align: 'center' }); welcomeText.anchor.set(0.5, 0.5); welcomeText.x = 0; welcomeText.y = 0; // Centered vertically in screen // Set maximum width to ensure text wraps properly welcomeText.wordWrap = true; welcomeText.wordWrapWidth = 1800; self.addChild(welcomeText); // Continue button at the top var continueButton = new Container(); var buttonBg = continueButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 150; buttonBg.tint = 0x00AA00; var buttonTextShadow = new Text2("Continue", { size: 80, fill: 0x000000, weight: 800 }); buttonTextShadow.anchor.set(0.5, 0.5); buttonTextShadow.x = 2; buttonTextShadow.y = 2; continueButton.addChild(buttonTextShadow); var buttonText = new Text2("Continue", { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); continueButton.addChild(buttonText); continueButton.x = 0; continueButton.y = -2732 / 2 + 300; // Position at top self.addChild(continueButton); // Continue button handler - only close the screen continueButton.down = function (x, y, obj) { // Show UI labels when continue button is pressed goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; // Remove tutorial intro screen self.destroy(); }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 500; menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; var towerNames = { 'default': 'CapyShot', 'rapid': 'CapyBlast', 'sniper': 'CapySniper', 'splash': 'CapyPult', 'slow': 'CapyFreeze', 'poison': 'CapyPoison', 'bomb': 'CapyBomb', 'demolition': 'CapyDemolition', 'queen': 'CapyQueen', 'king': 'CapyKing', 'prince': 'CapyPrince', 'princess': 'CapyPrincess' }; var displayName = towerNames[self.tower.id] || self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1); var towerTypeText = new Text2(displayName + ' Tower', { size: 70, fill: 0xFFFFFF, weight: 800 }); towerTypeText.anchor.set(0, 0); towerTypeText.x = -840; towerTypeText.y = -160; self.addChild(towerTypeText); var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', { size: 70, fill: 0xFFFFFF, weight: 400 }); statsText.anchor.set(0, 0.5); statsText.x = -840; statsText.y = 50; self.addChild(statsText); var buttonsContainer = new Container(); buttonsContainer.x = 500; self.addChild(buttonsContainer); var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; buttonBackground.height = 150; var isMaxLevel = self.tower.level >= self.tower.maxLevel; // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var upgradeCost; if (isMaxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888; var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; sellButtonBackground.height = 150; sellButtonBackground.tint = 0xCC0000; var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); upgradeButton.y = -85; sellButton.y = 85; var closeButton = new Container(); self.addChild(closeButton); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; upgradeButton.down = function (x, y, obj) { if (self.tower.level >= self.tower.maxLevel) { var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (self.tower.upgrade()) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); if (self.tower.level >= self.tower.maxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s'); buttonText.setText('Upgrade: ' + upgradeCost + ' gold'); var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); sellButtonText.setText('Sell: +' + sellValue + ' gold'); if (self.tower.level >= self.tower.maxLevel) { buttonBackground.tint = 0x888888; buttonText.setText('Max Level'); } var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { rangeCircle = game.children[i]; break; } } if (rangeCircle) { var rangeGraphics = rangeCircle.children[0]; rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; } else { var newRangeIndicator = new Container(); newRangeIndicator.isTowerRange = true; newRangeIndicator.tower = self.tower; game.addChildAt(newRangeIndicator, 0); newRangeIndicator.x = self.tower.x; newRangeIndicator.y = self.tower.y; var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; rangeGraphics.alpha = 0.3; } tween(self, { scaleX: 1.05, scaleY: 1.05 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn }); } }); } }; sellButton.down = function (x, y, obj) { var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); setGold(gold + sellValue); var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; // Reset placement tracking for single-use towers when sold if (self.tower.id === 'king') { placedTowers.king = false; } else if (self.tower.id === 'queen') { placedTowers.queen = false; } var gridX = self.tower.gridX; var gridY = self.tower.gridY; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 0; var towerIndex = cell.towersInRange.indexOf(self.tower); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } if (selectedTower === self.tower) { selectedTower = null; } var towerIndex = towers.indexOf(self.tower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } towerLayer.removeChild(self.tower); grid.pathFind(); grid.renderDebug(); self.destroy(); for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } }; closeButton.down = function (x, y, obj) { hideUpgradeMenu(self); selectedTower = null; grid.renderDebug(); }; self.update = function () { if (self.tower.level >= self.tower.maxLevel) { if (buttonText.text !== 'Max Level') { buttonText.setText('Max Level'); buttonBackground.tint = 0x888888; } return; } // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var currentUpgradeCost; if (self.tower.level >= self.tower.maxLevel) { currentUpgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } var canAfford = gold >= currentUpgradeCost; buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888; var newText = 'Upgrade: ' + currentUpgradeCost + ' gold'; if (buttonText.text !== newText) { buttonText.setText(newText); } }; return self; }); var WaveIndicator = Container.expand(function () { var self = Container.call(this); self.gameStarted = false; self.waveMarkers = []; self.waveTypes = []; self.enemyCounts = []; self.lastBossType = null; // Track the last boss type to avoid repeating var blockWidth = 400; var blockHeight = 120; var currentWaveIndicator = null; var progressBar = null; // Create start marker var startMarker = new Container(); var startBlock = startMarker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); startBlock.width = blockWidth; startBlock.height = blockHeight; startBlock.tint = 0x00AA00; // Add shadow for start text var startTextShadow = new Text2("Start Game", { size: 60, fill: 0x000000, weight: 800 }); startTextShadow.anchor.set(0.5, 0.5); startTextShadow.x = 2; startTextShadow.y = 2; startMarker.addChild(startTextShadow); var startText = new Text2("Start Game", { size: 60, fill: 0xFFFFFF, weight: 800 }); startText.anchor.set(0.5, 0.5); startMarker.addChild(startText); // Position start marker in center startMarker.x = 0; // Hide start marker by default - will be shown after level/tutorial selection startMarker.visible = false; self.addChild(startMarker); self.waveMarkers.push(startMarker); startMarker.down = function () { // Block start game button when tutorial instruction screens are active var instructions2Active = game.children.some(function (child) { return child instanceof Instructions2Screen; }); var instructions3Active = game.children.some(function (child) { return child instanceof Instructions3Screen; }); var coldBlastInstructionsActive = game.children.some(function (child) { return child instanceof ColdBlastInstructionsScreen; }); var hotPotatoInstructionsActive = game.children.some(function (child) { return child instanceof HotPotatoInstructionsScreen; }); var queensWalkInstructionsActive = game.children.some(function (child) { return child instanceof QueensWalkInstructionsScreen; }); var kingsGoldInstructionsActive = game.children.some(function (child) { return child instanceof KingsGoldInstructionsScreen; }); if (instructions2Active || instructions3Active || coldBlastInstructionsActive || hotPotatoInstructionsActive || queensWalkInstructionsActive || kingsGoldInstructionsActive) { return; // Prevent start game button from working } if (!self.gameStarted) { // Start game immediately without story screen self.gameStarted = true; currentWave = 1; // Start with wave 1, not 0 waveTimer = 0; // Start timer at 0 so first wave starts immediately waveInProgress = true; // Set wave in progress to trigger spawning waveSpawned = false; // Ensure enemies will spawn startBlock.tint = 0x00FF00; startText.setText("Started!"); startTextShadow.setText("Started!"); // Make sure shadow position remains correct after text change startTextShadow.x = 2; startTextShadow.y = 2; // Show UI labels when normal mode game starts if (!tutorialMode) { goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; } // Hide Next Wave button in tutorial mode when starting game if (tutorialMode && nextWaveButton) { nextWaveButton.enabled = false; nextWaveButton.visible = false; } // Remove start marker since game has started self.removeChild(startMarker); var waveType = self.getWaveTypeName(1); var enemyCount = self.getEnemyCount(1); // Only show notification if not in tutorial mode if (!tutorialMode) { var notification = game.addChild(new Notification("Wave 1 (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } // Create current wave indicator and progress bar for wave 1 self.createCurrentWaveIndicator(); } }; // In tutorial mode, only create indicators for the first 10 waves var wavesToShow = tutorialMode ? 10 : totalWaves; // Initialize arrays with the correct size self.waveTypes = []; self.enemyCounts = []; // Prepare wave data for (var i = 0; i < wavesToShow; i++) { // --- Begin new unified wave logic --- var waveType = "normal"; var enemyType = "normal"; var enemyCount = 10; var isBossWave = (i + 1) % 10 === 0; // Ensure all types appear in early waves if (i === 0) { waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } else if (i === 1) { waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if (i === 2) { waveType = "Immune"; enemyType = "immune"; enemyCount = 10; } else if (i === 3) { waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if (i === 4) { waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else if (isBossWave) { // Boss waves: cycle through all boss types, last boss is always flying var bossTypes = ['normal', 'fast', 'immune', 'flying']; var bossTypeIndex = Math.floor((i + 1) / 10) - 1; if (i === wavesToShow - 1) { // Last boss is always flying enemyType = 'flying'; waveType = "Boss Flying"; } else { enemyType = bossTypes[bossTypeIndex % bossTypes.length]; switch (enemyType) { case 'normal': waveType = "Boss Normal"; break; case 'fast': waveType = "Boss Fast"; break; case 'immune': waveType = "Boss Immune"; break; case 'flying': waveType = "Boss Flying"; break; } } enemyCount = 1; } else if ((i + 1) % 5 === 0) { // Every 5th non-boss wave is fast waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if ((i + 1) % 4 === 0) { // Every 4th non-boss wave is immune waveType = "Immune"; enemyType = "immune"; enemyCount = 10; } else if ((i + 1) % 7 === 0) { // Every 7th non-boss wave is flying waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if ((i + 1) % 3 === 0) { // Every 3rd non-boss wave is swarm waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else { waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } // --- End new unified wave logic --- // Store the wave type and enemy count self.waveTypes[i] = enemyType; self.enemyCounts[i] = enemyCount; } // Create current wave indicator with progress bar self.createCurrentWaveIndicator = function () { // Remove previous current wave indicator if it exists if (currentWaveIndicator) { self.removeChild(currentWaveIndicator); } // Only create indicator if we haven't completed all waves if (currentWave >= totalWaves) { return; } currentWaveIndicator = new Container(); // Main block for current wave var block = currentWaveIndicator.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth; block.height = blockHeight; // Set color based on wave type - use currentWave (not currentWave + 1) since we want the wave that's about to start var waveType = self.getWaveType(currentWave); var isBossWave = currentWave % 10 === 0 && currentWave > 0; switch (waveType) { case 'normal': block.tint = 0xAAAAAA; break; case 'fast': block.tint = 0x00AAFF; break; case 'immune': block.tint = 0xAA0000; break; case 'flying': block.tint = 0xFFFF00; break; case 'swarm': block.tint = 0xFF00FF; break; default: block.tint = 0xAAAAAA; break; } // Add boss indicator if needed if (isBossWave && waveType !== 'swarm') { var bossIndicator = currentWaveIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); bossIndicator.width = 20; bossIndicator.height = 20; bossIndicator.tint = 0xFFD700; // Gold color bossIndicator.y = -blockHeight / 2 - 12; } // Wave type text - use currentWave since that's the wave we're displaying var displayType = self.getWaveTypeName(currentWave); var waveTypeText = new Text2(displayType, { size: 45, fill: 0xFFFFFF, weight: 800 }); waveTypeText.anchor.set(0.5, 0.5); waveTypeText.y = -20; currentWaveIndicator.addChild(waveTypeText); // Wave number text - use currentWave since that's the wave we're displaying var waveNumText = new Text2("Wave " + currentWave, { size: 38, fill: 0xFFFFFF, weight: 600 }); waveNumText.anchor.set(0.5, 0.5); waveNumText.y = 25; currentWaveIndicator.addChild(waveNumText); // Create progress bar background var progressBg = currentWaveIndicator.attachAsset('notification', { anchorX: 0, anchorY: 0.5 }); progressBg.width = blockWidth - 30; progressBg.height = 12; progressBg.tint = 0x333333; progressBg.x = -blockWidth / 2 + 15; progressBg.y = blockHeight / 2 + 5; // Create progress bar fill progressBar = currentWaveIndicator.attachAsset('notification', { anchorX: 0, anchorY: 0.5 }); progressBar.width = 0; progressBar.height = 10; progressBar.tint = 0x00FF00; progressBar.x = -blockWidth / 2 + 16; progressBar.y = blockHeight / 2 + 5; currentWaveIndicator.x = 0; self.addChild(currentWaveIndicator); }; // Get wave type for a specific wave number self.getWaveType = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return "normal"; } var waveType = self.waveTypes[waveNumber - 1]; return waveType; }; // Get enemy count for a specific wave number self.getEnemyCount = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return 10; } return self.enemyCounts[waveNumber - 1]; }; // Get display name for a wave type self.getWaveTypeName = function (waveNumber) { var type = self.getWaveType(waveNumber); var typeName = type.charAt(0).toUpperCase() + type.slice(1); // Add boss prefix for boss waves (every 10th wave) if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') { typeName = "BOSS"; } return typeName; }; self.update = function () { // Handle wave progression self.handleWaveProgression = function () { if (!self.gameStarted) { return; } // Check if instructions2 or instructions3 screen is active and pause wave progression var instructionsActive = false; for (var i = 0; i < game.children.length; i++) { if (game.children[i] instanceof Instructions2Screen || game.children[i] instanceof Instructions3Screen) { instructionsActive = true; break; } } // If instructions screen is active, don't advance waves if (instructionsActive) { return; } if (currentWave <= totalWaves) { waveTimer++; if (waveTimer >= nextWaveTime) { waveTimer = 0; // Only advance if we haven't reached the end if (currentWave < totalWaves) { // In tutorial mode, reduce time by 90% when transitioning from wave 2 to wave 3 if (tutorialMode && currentWave === 2) { // Reduce the next wave time by 90% for wave 2 to wave 3 transition var originalNextWaveTime = nextWaveTime; nextWaveTime = nextWaveTime * 0.1; // 90% reduction currentWave++; waveInProgress = true; waveSpawned = false; // Reset the wave time back to original after this transition nextWaveTime = originalNextWaveTime; } else if (tutorialMode && currentWave === 3) { // Stop wave progression at wave 3 in tutorial mode // The instructions2 Continue button will handle advancing to wave 4 return; } else { currentWave++; waveInProgress = true; waveSpawned = false; } // Create new wave indicator for the new current wave self.createCurrentWaveIndicator(); var waveType = self.getWaveTypeName(currentWave); var enemyCount = self.getEnemyCount(currentWave); // Only show notification if not in tutorial mode if (!tutorialMode) { // Only show notification if not in tutorial mode if (!tutorialMode) { var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } } } }; // Update progress bar if game started and current wave indicator exists if (self.gameStarted && progressBar && currentWave < totalWaves) { var progress = waveTimer / nextWaveTime; var maxWidth = blockWidth - 32; progressBar.width = maxWidth * progress; } self.handleWaveProgression(); }; // Method to show start game button after level/tutorial selection self.showStartButton = function () { if (self.waveMarkers && self.waveMarkers.length > 0) { self.waveMarkers[0].visible = true; } }; return self; }); var WelcomeScreen = Container.expand(function () { var self = Container.call(this); // Background image var backgroundImage = self.attachAsset('welcome_background', { anchorX: 0.5, anchorY: 0.5 }); backgroundImage.x = 0; backgroundImage.y = 0; backgroundImage.width = 2048; backgroundImage.height = 2732; // Main instruction text shadow - positioned at bottom var instructionShadow = new Text2("Press anywhere to continue", { size: 80, fill: 0x000000, weight: 800 }); instructionShadow.anchor.set(0.5, 1.0); instructionShadow.x = 4; instructionShadow.y = 2732 / 2 - 200 + 4; self.addChild(instructionShadow); // Main instruction text - positioned at bottom var instructionText = new Text2("Press anywhere to continue", { size: 80, fill: 0xFFFFFF, weight: 800 }); instructionText.anchor.set(0.5, 1.0); instructionText.x = 0; instructionText.y = 2732 / 2 - 200; self.addChild(instructionText); // Pulsing animation for the text self.animationTimer = 0; self.update = function () { self.animationTimer += 0.05; var alpha = 0.7 + 0.3 * Math.sin(self.animationTimer); instructionText.alpha = alpha; instructionShadow.alpha = alpha; }; // Handle any touch/click to continue self.down = function (x, y, obj) { // Show UI labels when leaving welcome screen goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; // Remove welcome screen and show story screen self.destroy(); var storyScreen = new StoryScreen(); storyScreen.x = 2048 / 2; storyScreen.y = 2732 / 2; game.addChild(storyScreen); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x333333 }); /**** * Game Code ****/ // Add background image based on selected level function getBackgroundAssetName() { // Use tutorial background if in tutorial mode if (tutorialMode) { return 'background_tutorial'; } // Use Queen's Walk specific background in Queen's Walk challenge if (challengeMode && (challengeName === "Queen's Walk" || window.challengeName === "Queen's Walk")) { return 'background_queens_walk'; } switch (selectedLevel) { case 'garden': return 'background_garden'; case 'pool': return 'background_pool'; case 'lobby': return 'background_lobby'; case 'greathall': return 'background_greathall'; default: return 'background_garden'; } } function getCellAssetName() { // Use tutorial cell if in tutorial mode if (tutorialMode) { return 'cell_tutorial'; } // Use Cold Blast specific assets in Cold Blast challenge if (challengeMode && (challengeName === 'Cold Blast' || window.challengeName === 'Cold Blast')) { return 'cell_coldblast'; } // Use Hot Potato specific assets in Hot Potato challenge if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) { return 'cell_hotpotato'; } // Use Queen's Walk specific assets in Queen's Walk challenge if (challengeMode && (challengeName === "Queen's Walk" || window.challengeName === "Queen's Walk")) { return 'cell_queens_walk'; } switch (selectedLevel) { case 'garden': return 'cell_garden'; case 'pool': return 'cell_pool'; case 'lobby': return 'cell_lobby'; case 'greathall': return 'cell_greathall'; default: return 'cell_garden'; } } var backgroundImage = game.attachAsset(getBackgroundAssetName(), { anchorX: 0.5, anchorY: 0.5 }); backgroundImage.x = 2048 / 2; backgroundImage.y = 2732 / 2; // Scale background to cover the entire screen backgroundImage.width = 2048; backgroundImage.height = 2732; var isHidingUpgradeMenu = false; function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; } }); } var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var enemies = []; var towers = []; var bullets = []; var diamonds = []; var defenses = []; var selectedTower = null; var gold = 80; var difficultyMultiplier = 1.0; // Will be set by difficulty selector var selectedLevel = 'garden'; // Default level, will be set by level selector var tutorialMode = false; // Track if we're in tutorial mode var instructions2Shown = false; // Track if instructions2 screen has been shown var challengeMode = false; // Track if we're in challenge mode var challengeAllowedTowers = []; // Track which towers are allowed in challenge mode var challengeName = ''; // Track current challenge name var lives = 20; var score = 0; var currentWave = 0; var totalWaves = tutorialMode ? 10 : 50; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = tutorialMode ? 12000 / 2 * 0.25 * 0.7 * 0.7 : 12000 / 2; var sourceTower = null; var enemiesToSpawn = 10; // Default number of enemies per wave var goldTextShadow = new Text2('Gold: ' + gold, { size: 60, fill: 0x000000, weight: 800 }); goldTextShadow.anchor.set(0.5, 0.5); var goldText = new Text2('Gold: ' + gold, { size: 60, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); var livesTextShadow = new Text2('Lives: ' + lives, { size: 60, fill: 0x000000, weight: 800 }); livesTextShadow.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 scoreTextShadow = new Text2('Score: ' + score, { size: 60, fill: 0x000000, weight: 800 }); scoreTextShadow.anchor.set(0.5, 0.5); var scoreText = new Text2('Score: ' + score, { size: 60, fill: 0xFF0000, weight: 800 }); scoreText.anchor.set(0.5, 0.5); var topMargin = 50; var centerX = 2048 / 2; var spacing = 400; LK.gui.top.addChild(goldTextShadow); LK.gui.top.addChild(livesTextShadow); LK.gui.top.addChild(scoreTextShadow); LK.gui.top.addChild(goldText); LK.gui.top.addChild(livesText); LK.gui.top.addChild(scoreText); // Position shadow texts with offset goldTextShadow.x = -spacing + 2; goldTextShadow.y = topMargin + 2; livesTextShadow.x = 0 + 2; livesTextShadow.y = topMargin + 2; scoreTextShadow.x = spacing + 2; scoreTextShadow.y = topMargin + 2; // Position main texts livesText.x = 0; livesText.y = topMargin; goldText.x = -spacing; goldText.y = topMargin; scoreText.x = spacing; scoreText.y = topMargin; function updateUI() { goldTextShadow.setText('Gold: ' + gold); livesTextShadow.setText('Lives: ' + lives); scoreTextShadow.setText('Score: ' + score); goldText.setText('Gold: ' + gold); livesText.setText('Lives: ' + lives); scoreText.setText('Score: ' + score); } function setGold(value) { gold = value; updateUI(); } var debugLayer = new Container(); var towerLayer = new Container(); // Create three separate layers for enemy hierarchy var enemyLayerBottom = new Container(); // For normal enemies var enemyLayerMiddle = new Container(); // For shadows var enemyLayerTop = new Container(); // For flying enemies var enemyLayer = new Container(); // Main container to hold all enemy layers // Add layers in correct order (bottom first, then middle for shadows, then top) enemyLayer.addChild(enemyLayerBottom); enemyLayer.addChild(enemyLayerMiddle); enemyLayer.addChild(enemyLayerTop); var grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Add pool rectangle image if on pool level var poolRectangleImage = null; // Add lobby square images if on lobby level var lobbySquareImages = []; if (selectedLevel === 'pool') { poolRectangleImage = game.attachAsset('pool_rectangle', { anchorX: 0.5, anchorY: 0.5 }); // Position over the rectangular pool area (center of grid + pool area center offset) var gridWidth = 24; var gridHeight = 35; var centerX = Math.floor(gridWidth / 2); // Center column (12) var centerY = Math.floor(gridHeight / 2); // Center row var rectWidth = 6; // Rectangle width (6 cells) var rectHeight = 16; // Rectangle height (16 cells) var rectLeft = centerX - Math.floor(rectWidth / 2); var rectTop = centerY - Math.floor(rectHeight / 2); // Calculate the actual center of the rectangular pool area in pixels var poolCenterX = grid.x + (rectLeft + rectWidth / 2) * CELL_SIZE; var poolCenterY = grid.y + (rectTop + rectHeight / 2) * CELL_SIZE; poolRectangleImage.x = poolCenterX - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2; poolRectangleImage.y = poolCenterY - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2; // Keep pool rectangle image at normal tint (not gray) so it appears above the gray cells poolRectangleImage.tint = 0xFFFFFF; // Keep original white/normal tint // Add to tower layer so it appears above the grid cells towerLayer.addChild(poolRectangleImage); } game.addChild(debugLayer); game.addChild(towerLayer); game.addChild(enemyLayer); var offset = 0; var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; var isDragging = false; function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); return blocked; } function getTowerCost(towerType) { var cost = 5; 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; case 'bomb': cost = 80; break; case 'demolition': cost = 150; break; case 'queen': cost = 200; break; case 'king': cost = 250; break; case 'prince': cost = 130; break; case 'princess': cost = 130; break; } return cost; } function getTowerSellValue(totalValue) { return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue; } function placeTower(gridX, gridY, towerType) { var towerCost = getTowerCost(towerType); if (gold >= towerCost) { // Check if this tower type is allowed in challenge mode if (challengeMode && challengeAllowedTowers.indexOf(towerType) === -1) { var notification = game.addChild(new Notification("This tower is not allowed in " + challengeName + " challenge!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } // Check if this is a single-use tower that has already been placed if (towerType === 'king' && placedTowers.king || towerType === 'queen' && placedTowers.queen) { var notification = game.addChild(new Notification("This tower can only be placed once!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } // Special handling for Hot Potato challenge if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) { if (towerType === 'bomb' || towerType === 'demolition') { // Create projectile tower directly instead of regular tower var projectileX = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; var projectileY = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; var projectile = new ProjectileTower(towerType, projectileX, projectileY); game.addChild(projectile); // Launch immediately after short delay LK.setTimeout(function () { projectile.launch(); }, 500); setGold(gold - towerCost); var notification = game.addChild(new Notification("Capy" + (towerType === 'bomb' ? 'Bomb' : 'Demolition') + " launched!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return true; } } var tower = new Tower(towerType || 'default'); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); // Mark single-use towers as placed if (towerType === 'king') { placedTowers.king = true; } else if (towerType === 'queen') { placedTowers.queen = true; } grid.pathFind(); grid.renderDebug(); return true; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } game.down = function (x, y, obj) { var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenuVisible) { return; } // Check if level selector or difficulty selector is active var levelSelectorActive = game.children.some(function (child) { return child instanceof LevelSelector; }); var difficultySelectorActive = game.children.some(function (child) { return child instanceof DifficultySelector; }); var tutorialIntroActive = game.children.some(function (child) { return child instanceof TutorialIntroScreen; }); var instructions2Active = game.children.some(function (child) { return child instanceof Instructions2Screen; }); var instructions3Active = game.children.some(function (child) { return child instanceof Instructions3Screen; }); var modeSelectorActive = game.children.some(function (child) { return child instanceof ModeSelector; }); var storyScreenActive = game.children.some(function (child) { return child instanceof StoryScreen; }); // Block all interactions when tutorial instruction screens are active if (instructions2Active || instructions3Active) { return; } // Block start game button when ModeSelector is active if (modeSelectorActive && waveIndicator && waveIndicator.waveMarkers && waveIndicator.waveMarkers.length > 0) { var startMarker = waveIndicator.waveMarkers[0]; if (startMarker && !waveIndicator.gameStarted) { // Get start marker bounds var startMarkerX = waveIndicator.x + startMarker.x; var startMarkerY = waveIndicator.y + startMarker.y; var startMarkerWidth = 400; // blockWidth from WaveIndicator var startMarkerHeight = 120; // blockHeight from WaveIndicator var startMarkerLeft = startMarkerX - startMarkerWidth / 2; var startMarkerRight = startMarkerX + startMarkerWidth / 2; var startMarkerTop = startMarkerY - startMarkerHeight / 2; var startMarkerBottom = startMarkerY + startMarkerHeight / 2; // Check if click coordinates overlap with start game button if (x >= startMarkerLeft && x <= startMarkerRight && y >= startMarkerTop && y <= startMarkerBottom) { return; // Block start game button interaction } } } // Don't allow tower selection if level selector or difficulty selector is active if (levelSelectorActive || difficultySelectorActive || tutorialIntroActive || instructions2Active || instructions3Active || modeSelectorActive || storyScreenActive) { return; } // Block all source tower interactions when ModeSelector is active if (modeSelectorActive || storyScreenActive) { return; } // Check for diamond collection by player touch if (challengeMode && (challengeName === "Queen's Walk" || window.challengeName === "Queen's Walk")) { for (var i = diamonds.length - 1; i >= 0; i--) { var diamond = diamonds[i]; if (!diamond.parent) continue; // Check if click/touch is close to diamond var dx = diamond.x - x; var dy = diamond.y - y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= 50) { // Collection radius of 50 pixels // Diamond collected by player! setGold(gold + 25); var goldIndicator = new GoldIndicator(25, diamond.x, diamond.y); game.addChild(goldIndicator); diamond.destroy(); diamonds.splice(i, 1); return; // Exit early after collecting diamond } } } // Check for gold coin collection by player touch in King's Gold challenge if (challengeMode && (challengeName === "King's Gold" || window.challengeName === "King's Gold")) { for (var i = diamonds.length - 1; i >= 0; i--) { var goldCoin = diamonds[i]; if (!goldCoin.parent || !goldCoin.isGoldCoin) continue; // Check if click/touch is close to gold coin var dx = goldCoin.x - x; var dy = goldCoin.y - y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= 75) { // Collection radius of 75 pixels for easier collection // Gold coin collected by player! setGold(gold + 50); var goldIndicator = new GoldIndicator(50, goldCoin.x, goldCoin.y); game.addChild(goldIndicator); goldCoin.destroy(); diamonds.splice(i, 1); return; // Exit early after collecting gold coin } } } 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; towerPreview.towerType = tower.towerType; towerPreview.updateAppearance(); // Apply the same offset as in move handler to ensure consistency when starting drag towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); break; } } }; game.move = function (x, y, obj) { // Check if level selector or difficulty selector is active var levelSelectorActive = game.children.some(function (child) { return child instanceof LevelSelector; }); var difficultySelectorActive = game.children.some(function (child) { return child instanceof DifficultySelector; }); var tutorialIntroActive = game.children.some(function (child) { return child instanceof TutorialIntroScreen; }); var instructions2Active = game.children.some(function (child) { return child instanceof Instructions2Screen; }); var instructions3Active = game.children.some(function (child) { return child instanceof Instructions3Screen; }); var modeSelectorActive = game.children.some(function (child) { return child instanceof ModeSelector; }); var storyScreenActive = game.children.some(function (child) { return child instanceof StoryScreen; }); // Don't allow tower dragging if level selector, difficulty selector, or tutorial instruction screens are active if (levelSelectorActive || difficultySelectorActive || tutorialIntroActive || instructions2Active || instructions3Active || modeSelectorActive || storyScreenActive) { return; } if (isDragging) { // Shift the y position upward by 1.5 tiles to show preview above finger towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); } }; game.up = function (x, y, obj) { var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerLeft = tower.x - tower.width / 2; var towerRight = tower.x + tower.width / 2; var towerTop = tower.y - tower.height / 2; var towerBottom = tower.y + tower.height / 2; if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) { var clickedOnMenu = false; for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; var menuWidth = 2048; var menuHeight = 450; var menuLeft = menu.x - menuWidth / 2; var menuRight = menu.x + menuWidth / 2; var menuTop = menu.y - menuHeight / 2; var menuBottom = menu.y + menuHeight / 2; if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) { clickedOnMenu = true; break; } } if (!clickedOnMenu) { for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; hideUpgradeMenu(menu); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = null; grid.renderDebug(); } } if (isDragging) { // Check if level selector or difficulty selector is active var levelSelectorActive = game.children.some(function (child) { return child instanceof LevelSelector; }); var difficultySelectorActive = game.children.some(function (child) { return child instanceof DifficultySelector; }); var tutorialIntroActive = game.children.some(function (child) { return child instanceof TutorialIntroScreen; }); var instructions2Active = game.children.some(function (child) { return child instanceof Instructions2Screen; }); var instructions3Active = game.children.some(function (child) { return child instanceof Instructions3Screen; }); var modeSelectorActive = game.children.some(function (child) { return child instanceof ModeSelector; }); var storyScreenActive = game.children.some(function (child) { return child instanceof StoryScreen; }); isDragging = false; // Don't allow tower placement if level selector, difficulty selector, or tutorial instruction screens are active if (levelSelectorActive || difficultySelectorActive || tutorialIntroActive || instructions2Active || instructions3Active || modeSelectorActive || storyScreenActive) { towerPreview.visible = false; return; } if (towerPreview.canPlace) { if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) { placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType); } else { var notification = game.addChild(new Notification("Tower would block the path!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } else if (towerPreview.blockedByEnemy) { var notification = game.addChild(new Notification("Cannot build: Enemy in the way!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (towerPreview.visible) { var notification = game.addChild(new Notification("Cannot build here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } towerPreview.visible = false; if (isDragging) { var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } } } }; var waveIndicator = new WaveIndicator(); waveIndicator.x = 2048 / 2; waveIndicator.y = 2732 - 80; game.addChild(waveIndicator); var nextWaveButtonContainer = new Container(); var nextWaveButton = new NextWaveButton(); nextWaveButton.x = 2048 - 200; nextWaveButton.y = 2732 - 100 + 20; // Hide Next Wave button in tutorial mode until instructions2 continue is pressed if (tutorialMode) { nextWaveButton.enabled = false; nextWaveButton.visible = false; } nextWaveButtonContainer.addChild(nextWaveButton); game.addChild(nextWaveButtonContainer); // Velocity control system var gameSpeed = 1.0; var speedMultipliers = [1.0, 1.3, 1.5, 2.0]; var currentSpeedIndex = 0; // Track tower placement for single-use towers var placedTowers = { king: false, queen: false }; var velocityButton = new Container(); var velocityButtonBackground = velocityButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); velocityButtonBackground.width = 280; velocityButtonBackground.height = 120; velocityButtonBackground.tint = 0x00AA88; var velocityButtonText = new Text2("Velocity", { size: 45, fill: 0xFFFFFF, weight: 800 }); velocityButtonText.anchor.set(0.5, 0.5); velocityButtonText.y = -15; // Move velocity text up slightly var velocitySpeedText = new Text2("x" + gameSpeed.toFixed(1), { size: 40, fill: 0xFFD700, weight: 800 }); velocitySpeedText.anchor.set(0.5, 0.5); velocitySpeedText.y = 32; // Increase spacing by 2 pixels (was 30, now 32) velocityButton.addChild(velocityButtonText); velocityButton.addChild(velocitySpeedText); velocityButton.x = 200; velocityButton.y = 2732 - 80; // Move button lower (was -120, now -80) velocityButton.visible = false; velocityButton.enabled = false; game.addChild(velocityButton); var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison', 'bomb', 'queen', 'king']; var sourceTowers = []; var towerSpacing = 220; // Adjusted spacing for 9 towers 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); } sourceTower = null; enemiesToSpawn = 10; velocityButton.down = function () { // Block velocity button when ModeSelector or tutorial instruction screens are active var modeSelectorActive = game.children.some(function (child) { return child instanceof ModeSelector; }); var instructions2Active = game.children.some(function (child) { return child instanceof Instructions2Screen; }); var instructions3Active = game.children.some(function (child) { return child instanceof Instructions3Screen; }); if (modeSelectorActive || instructions2Active || instructions3Active) { return; } if (!velocityButton.enabled) { return; } currentSpeedIndex = (currentSpeedIndex + 1) % speedMultipliers.length; gameSpeed = speedMultipliers[currentSpeedIndex]; velocitySpeedText.setText("x" + gameSpeed.toFixed(1)); // Visual feedback tween(velocityButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(velocityButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeIn }); } }); var notification = game.addChild(new Notification("Game speed set to x" + gameSpeed.toFixed(1) + "!")); notification.x = 2048 / 2; notification.y = grid.height - 100; }; velocityButton.update = function () { if (waveIndicator && waveIndicator.gameStarted) { velocityButton.enabled = true; velocityButton.visible = true; velocityButton.alpha = 1; } else { velocityButton.enabled = false; velocityButton.visible = false; velocityButton.alpha = 0.7; } }; // Show welcome screen at start var welcomeScreen = new WelcomeScreen(); welcomeScreen.x = 2048 / 2; welcomeScreen.y = 2732 / 2; game.addChild(welcomeScreen); // Start background music LK.playMusic('Song', { loop: true }); // Hide UI labels from the start until start game is pressed goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; game.update = function () { // Check if game is paused by instructions2 or instructions3 screen var instructionsActive = false; for (var i = 0; i < game.children.length; i++) { if (game.children[i] instanceof Instructions2Screen || game.children[i] instanceof Instructions3Screen) { instructionsActive = true; break; } } // If instructions screen is active and pausing, skip all game logic if (instructionsActive) { return; } // Apply game speed to wave progression if (waveInProgress) { if (!waveSpawned) { waveSpawned = true; // Get wave type and enemy count from the wave indicator var waveType = waveIndicator.getWaveType(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); // Check if this is a boss wave var isBossWave = currentWave % 10 === 0 && currentWave > 0; if (isBossWave && waveType !== 'swarm') { // Boss waves have just 1 enemy regardless of what the wave indicator says enemyCount = 1; // Show Instructions3Screen for boss wave (tutorial mode only) if (tutorialMode) { var instructions3Screen = new Instructions3Screen(); instructions3Screen.x = 2048 / 2; instructions3Screen.y = 2732 / 2; game.addChild(instructions3Screen); } // Show boss announcement var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️")); notification.x = 2048 / 2; notification.y = grid.height - 200; } // In tutorial mode, limit to 1 enemy per wave for waves 1-3, then 3 enemies for wave 4 onwards, except for boss waves which should remain at 1 if (tutorialMode) { // Boss waves should always have 1 enemy, even in tutorial mode if (!(isBossWave && waveType !== 'swarm')) { enemyCount = currentWave >= 4 ? 3 : 1; } } // Spawn the appropriate number of enemies for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); // Add enemy to the appropriate layer based on type if (enemy.isFlying) { // Add flying enemy to the top layer enemyLayerTop.addChild(enemy); // If it's a flying enemy, add its shadow to the middle layer if (enemy.shadow) { enemyLayerMiddle.addChild(enemy.shadow); } } else { // Add normal/ground enemies to the bottom layer enemyLayerBottom.addChild(enemy); } // Scale difficulty with wave number but don't apply to boss // as bosses already have their health multiplier // Use exponential scaling for health var healthMultiplier = Math.pow(1.12, currentWave); // ~20% increase per wave // Apply difficulty multiplier enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier * difficultyMultiplier); enemy.health = enemy.maxHealth; // Increment speed slightly with wave number //enemy.speed = enemy.speed + currentWave * 0.002; // Check if Hot Potato challenge is active for different spawning behavior var isHotPotatoChallenge = challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato'); var gridWidth = 24; var midPoint = Math.floor(gridWidth / 2); // 12 if (isHotPotatoChallenge) { // Hot Potato: spawn across entire top width (excluding walls at x=0 and x=23) var availableColumns = []; for (var col = 1; col < gridWidth - 1; col++) { // x from 1 to 22 var columnOccupied = false; // Check if any enemy is already in this column but not yet in view for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === col && enemies[e].currentCellY < 4) { columnOccupied = true; break; } } if (!columnOccupied) { availableColumns.push(col); } } // If all columns are occupied, use random across entire width var spawnX; if (availableColumns.length > 0) { // Choose a random unoccupied column spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)]; } else { // Fallback to random across entire width if all columns are occupied spawnX = 1 + Math.floor(Math.random() * (gridWidth - 2)); // x from 1 to 22 } } else { // Normal behavior: spawn in middle 6 tiles // Find a column that isn't occupied by another enemy that's not yet in view var availableColumns = []; for (var col = midPoint - 3; col < midPoint + 3; col++) { var columnOccupied = false; // Check if any enemy is already in this column but not yet in view for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === col && enemies[e].currentCellY < 4) { columnOccupied = true; break; } } if (!columnOccupied) { availableColumns.push(col); } } // If all columns are occupied, use original random method var spawnX; if (availableColumns.length > 0) { // Choose a random unoccupied column spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)]; } else { // Fallback to random if all columns are occupied spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 9 to 14 } } var spawnY = -1 - Math.random() * 5; // Random distance above the grid for spreading enemy.cellX = spawnX; enemy.cellY = 5; // Position after entry enemy.currentCellX = spawnX; enemy.currentCellY = spawnY; enemy.waveNumber = currentWave; // Apply speed multiplier to enemy enemy.speed = enemy.speed * gameSpeed; enemies.push(enemy); } } var currentWaveEnemiesRemaining = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].waveNumber === currentWave) { currentWaveEnemiesRemaining = true; break; } } if (waveSpawned && !currentWaveEnemiesRemaining) { waveInProgress = false; waveSpawned = false; // Add 250 gold for completing a wave in Hot Potato challenge if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) { setGold(gold + 250); var notification = game.addChild(new Notification("Wave completed! +250 gold bonus!")); notification.x = 2048 / 2; notification.y = grid.height - 100; } } } // Apply speed multiplier to existing enemies for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; // Apply speed multiplier to enemy movement (but only update if speed has changed) if (enemy.lastGameSpeed === undefined || enemy.lastGameSpeed !== gameSpeed) { // Reset speed to base and reapply multiplier if (enemy.lastGameSpeed !== undefined) { enemy.speed = enemy.speed / enemy.lastGameSpeed; } enemy.speed = enemy.speed * gameSpeed; enemy.lastGameSpeed = gameSpeed; } if (enemy.health <= 0) { for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } // Check if this is wave 3 and we haven't shown instructions2 yet (tutorial mode only) if (tutorialMode && enemy.waveNumber === 3 && !instructions2Shown) { // Show instructions2 screen var instructions2Screen = new Instructions2Screen(); instructions2Screen.x = 2048 / 2; instructions2Screen.y = 2732 / 2; game.addChild(instructions2Screen); instructions2Shown = true; } // 10% chance to drop a diamond in Queen's Walk challenge if (challengeMode && (challengeName === "Queen's Walk" || window.challengeName === "Queen's Walk")) { if (Math.random() < 0.1) { var diamond = new Diamond(enemy.x, enemy.y); game.addChild(diamond); diamonds.push(diamond); } } // 30% chance to drop a static gold coin in King's Gold challenge if (challengeMode && (challengeName === "King's Gold" || window.challengeName === "King's Gold")) { if (Math.random() < 0.3) { // Create static gold coin var goldCoin = game.attachAsset('gold_coin_kings_gold', { anchorX: 0.5, anchorY: 0.5 }); goldCoin.x = enemy.x; goldCoin.y = enemy.y; goldCoin.width = 100; goldCoin.height = 100; goldCoin.lifetime = 0; goldCoin.isGoldCoin = true; // Mark as gold coin for identification // Gold coin is now static and doesn't spin diamonds.push(goldCoin); } } // Boss enemies give more gold and score var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5); // Triple gold for Cold Blast and Hot Potatoe challenges if (challengeMode && (challengeName === 'Cold Blast' || challengeName === 'Hot Potatoe')) { goldEarned *= 3; } var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); // Give more score for defeating a boss var scoreValue = enemy.isBoss ? 100 : 5; score += scoreValue; // Add a notification for boss defeat if (enemy.isBoss) { var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; // In tutorial mode, show tutorial complete screen if (tutorialMode) { var tutorialCompleteScreen = new TutorialCompleteScreen(); tutorialCompleteScreen.x = 2048 / 2; tutorialCompleteScreen.y = 2732 / 2; game.addChild(tutorialCompleteScreen); } } updateUI(); // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); continue; } if (grid.updateEnemy(enemy)) { // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); lives = Math.max(0, lives - 1); updateUI(); if (lives <= 0) { LK.showGameOver(); } } } for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent) { if (bullets[i].targetEnemy) { var targetEnemy = bullets[i].targetEnemy; var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } bullets.splice(i, 1); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } // Update and handle diamond and gold coin collection for (var i = diamonds.length - 1; i >= 0; i--) { var diamond = diamonds[i]; // Check if diamond still exists in game if (!diamond.parent) { diamonds.splice(i, 1); continue; } // Check if any tower is close enough to collect the diamond/gold coin var collected = false; for (var t = 0; t < towers.length; t++) { var tower = towers[t]; var dx = tower.x - diamond.x; var dy = tower.y - diamond.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= CELL_SIZE * 2) { // Diamond or gold coin collected! var goldValue = diamond.isGoldCoin ? 50 : 25; setGold(gold + goldValue); var goldIndicator = new GoldIndicator(goldValue, diamond.x, diamond.y); game.addChild(goldIndicator); diamond.destroy(); diamonds.splice(i, 1); collected = true; break; } } // Auto-collect after 10 seconds if not collected if (!collected) { diamond.lifetime = (diamond.lifetime || 0) + 1; if (diamond.lifetime > 600) { // 10 seconds at 60fps var goldValue = diamond.isGoldCoin ? 50 : 25; setGold(gold + goldValue); var goldIndicator = new GoldIndicator(goldValue, diamond.x, diamond.y); game.addChild(goldIndicator); diamond.destroy(); diamonds.splice(i, 1); } } } // Only end the game if not in tutorial mode, or if in tutorial mode but boss hasn't been defeated yet if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress && !tutorialMode) { LK.showYouWin(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) {
var self = Container.call(this);
self.targetEnemy = targetEnemy;
self.damage = damage || 10;
self.speed = speed || 5;
self.x = startX;
self.y = startY;
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.targetEnemy || !self.targetEnemy.parent) {
self.destroy();
return;
}
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.speed) {
// Apply damage to target enemy
var actualDamage = self.damage;
// In Hot Potato challenge, multiply CapyBomb and CapyDemolition damage by 20x against bosses
if (challengeMode && window.challengeName === 'Hot Potato') {
if ((self.type === 'bomb' || self.type === 'demolition') && self.targetEnemy.isBoss) {
actualDamage = self.damage * 20;
}
}
self.targetEnemy.health -= actualDamage;
if (self.targetEnemy.health <= 0) {
self.targetEnemy.health = 0;
} else {
self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70;
}
// Apply special effects based on bullet type
if (self.type === 'splash') {
// Create visual splash effect
var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash');
game.addChild(splashEffect);
// Splash damage to nearby enemies
var splashRadius = CELL_SIZE * 1.5;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self.targetEnemy) {
var splashDx = otherEnemy.x - self.targetEnemy.x;
var splashDy = otherEnemy.y - self.targetEnemy.y;
var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy);
if (splashDistance <= splashRadius) {
// Apply splash damage (50% of original damage)
otherEnemy.health -= self.damage * 0.5;
if (otherEnemy.health <= 0) {
otherEnemy.health = 0;
} else {
otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70;
}
}
}
}
} else if (self.type === 'slow') {
// Prevent slow effect on immune enemies
if (!self.targetEnemy.isImmune) {
// Create visual slow effect
var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow');
game.addChild(slowEffect);
// Apply slow effect
// Make slow percentage scale with tower level (default 50%, up to 80% at max level)
var slowPct = 0.5;
if (self.sourceTowerLevel !== undefined) {
// Scale: 50% at level 1, 60% at 2, 65% at 3, 70% at 4, 75% at 5, 80% at 6
var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8];
var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1));
slowPct = slowLevels[idx];
}
if (!self.targetEnemy.slowed) {
self.targetEnemy.originalSpeed = self.targetEnemy.speed;
self.targetEnemy.speed *= 1 - slowPct; // Slow by X%
self.targetEnemy.slowed = true;
self.targetEnemy.slowDuration = 180; // 3 seconds at 60 FPS
} else {
self.targetEnemy.slowDuration = 180; // Reset duration
}
}
} else if (self.type === 'queen') {
// Prevent slow effect on immune enemies (Queen tower with enhanced effects)
if (!self.targetEnemy.isImmune) {
// Create visual slow effect
var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow');
game.addChild(slowEffect);
// Apply enhanced slow effect - double the reduction and duration
// Make slow percentage scale with tower level (default 100%, up to 160% at max level)
var slowPct = 1.0; // Double the base slow effect
if (self.sourceTowerLevel !== undefined) {
// Scale: 100% at level 1, 120% at 2, 130% at 3, 140% at 4, 150% at 5, 160% at 6
var slowLevels = [1.0, 1.2, 1.3, 1.4, 1.5, 1.6];
var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1));
slowPct = slowLevels[idx];
}
// Cap slow percentage to prevent negative speeds
slowPct = Math.min(slowPct, 0.95); // Maximum 95% slow to prevent stopping
if (!self.targetEnemy.slowed) {
self.targetEnemy.originalSpeed = self.targetEnemy.speed;
self.targetEnemy.speed *= 1 - slowPct; // Slow by X%
self.targetEnemy.slowed = true;
self.targetEnemy.slowDuration = 360; // 6 seconds at 60 FPS (double duration)
} else {
self.targetEnemy.slowDuration = 360; // Reset duration (double)
}
}
} else if (self.type === 'poison') {
// Prevent poison effect on immune enemies
if (!self.targetEnemy.isImmune) {
// Create visual poison effect
var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison');
game.addChild(poisonEffect);
// Apply poison effect
self.targetEnemy.poisoned = true;
self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick
self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS
}
} else if (self.type === 'bomb') {
// Prevent poison effect on immune enemies
if (!self.targetEnemy.isImmune) {
// Create visual poison effect
var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison');
game.addChild(poisonEffect);
// Apply poison effect
self.targetEnemy.poisoned = true;
self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick
self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS
}
} else if (self.type === 'demolition') {
// Prevent poison effect on immune enemies
if (!self.targetEnemy.isImmune) {
// Create visual poison effect
var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison');
game.addChild(poisonEffect);
// Apply poison effect
self.targetEnemy.poisoned = true;
self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick
self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS
}
} else if (self.type === 'prince') {
// Create visual splash effect
var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash');
game.addChild(splashEffect);
// Splash damage to nearby enemies
var splashRadius = CELL_SIZE * 1.5;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self.targetEnemy) {
var splashDx = otherEnemy.x - self.targetEnemy.x;
var splashDy = otherEnemy.y - self.targetEnemy.y;
var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy);
if (splashDistance <= splashRadius) {
// Apply splash damage (50% of original damage)
otherEnemy.health -= self.damage * 0.5;
if (otherEnemy.health <= 0) {
otherEnemy.health = 0;
} else {
otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70;
}
}
}
}
} else if (self.type === 'princess') {
// Prevent poison effect on immune enemies
if (!self.targetEnemy.isImmune) {
// Create visual poison effect
var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison');
game.addChild(poisonEffect);
// Apply poison effect
self.targetEnemy.poisoned = true;
self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick
self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS
}
} else if (self.type === 'sniper') {
// Create visual critical hit effect for sniper
var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper');
game.addChild(sniperEffect);
}
self.destroy();
} else {
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
};
return self;
});
var CapyChallengesScreen = Container.expand(function () {
var self = Container.call(this);
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Title shadow
var titleShadow = new Text2("Capy Challenges", {
size: 120,
fill: 0x000000,
weight: 800
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -300 + 4;
self.addChild(titleShadow);
// Title
var title = new Text2("Capy Challenges", {
size: 120,
fill: 0xFF6600,
weight: 800
});
title.anchor.set(0.5, 0.5);
title.y = -300;
self.addChild(title);
// Cold Blast button
var coldBlastButton = new Container();
var coldBlastBg = coldBlastButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
coldBlastBg.width = 600;
coldBlastBg.height = 150;
coldBlastBg.tint = 0x0088FF;
var coldBlastTextShadow = new Text2("Cold Blast", {
size: 70,
fill: 0x000000,
weight: 800
});
coldBlastTextShadow.anchor.set(0.5, 0.5);
coldBlastTextShadow.x = 2;
coldBlastTextShadow.y = 2;
coldBlastButton.addChild(coldBlastTextShadow);
var coldBlastText = new Text2("Cold Blast", {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
coldBlastText.anchor.set(0.5, 0.5);
coldBlastButton.addChild(coldBlastText);
coldBlastButton.x = 0;
coldBlastButton.y = -100;
self.addChild(coldBlastButton);
// Hot Potato button
var hotPotatoButton = new Container();
var hotPotatoBg = hotPotatoButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
hotPotatoBg.width = 600;
hotPotatoBg.height = 150;
hotPotatoBg.tint = 0xFF4400;
var hotPotatoTextShadow = new Text2("Hot Potato", {
size: 70,
fill: 0x000000,
weight: 800
});
hotPotatoTextShadow.anchor.set(0.5, 0.5);
hotPotatoTextShadow.x = 2;
hotPotatoTextShadow.y = 2;
hotPotatoButton.addChild(hotPotatoTextShadow);
var hotPotatoText = new Text2("Hot Potato", {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
hotPotatoText.anchor.set(0.5, 0.5);
hotPotatoButton.addChild(hotPotatoText);
hotPotatoButton.x = 0;
hotPotatoButton.y = 100;
self.addChild(hotPotatoButton);
// Back button
var backButton = new Container();
var backBg = backButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
backBg.width = 400;
backBg.height = 100;
backBg.tint = 0xCC0000;
var backTextShadow = new Text2("Back", {
size: 60,
fill: 0x000000,
weight: 800
});
backTextShadow.anchor.set(0.5, 0.5);
backTextShadow.x = 2;
backTextShadow.y = 2;
backButton.addChild(backTextShadow);
var backText = new Text2("Back", {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
backText.anchor.set(0.5, 0.5);
backButton.addChild(backText);
backButton.x = 0;
backButton.y = 660;
self.addChild(backButton);
// Cold Blast button handler
coldBlastButton.down = function (x, y, obj) {
// Start Cold Blast challenge - only CapyFreeze, CapyBomb and CapyDemolition towers
self.startChallenge('Cold Blast', ['slow', 'bomb', 'demolition']);
};
// Hot Potato button handler
hotPotatoButton.down = function (x, y, obj) {
// Start Hot Potato challenge - only CapySniper, CapyBomb and CapyDemolition towers
self.startChallenge('Hot Potato', ['sniper', 'bomb', 'demolition']);
};
// Queen's Walk button
var queensWalkButton = new Container();
var queensWalkBg = queensWalkButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
queensWalkBg.width = 600;
queensWalkBg.height = 150;
queensWalkBg.tint = 0x9900FF;
var queensWalkTextShadow = new Text2("Queen's Walk", {
size: 70,
fill: 0x000000,
weight: 800
});
queensWalkTextShadow.anchor.set(0.5, 0.5);
queensWalkTextShadow.x = 2;
queensWalkTextShadow.y = 2;
queensWalkButton.addChild(queensWalkTextShadow);
var queensWalkText = new Text2("Queen's Walk", {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
queensWalkText.anchor.set(0.5, 0.5);
queensWalkButton.addChild(queensWalkText);
queensWalkButton.x = 0;
queensWalkButton.y = 300;
self.addChild(queensWalkButton);
// King's Gold button
var kingsGoldButton = new Container();
var kingsGoldBg = kingsGoldButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
kingsGoldBg.width = 600;
kingsGoldBg.height = 150;
kingsGoldBg.tint = 0xFFD700;
var kingsGoldTextShadow = new Text2("King's Gold", {
size: 70,
fill: 0x000000,
weight: 800
});
kingsGoldTextShadow.anchor.set(0.5, 0.5);
kingsGoldTextShadow.x = 2;
kingsGoldTextShadow.y = 2;
kingsGoldButton.addChild(kingsGoldTextShadow);
var kingsGoldText = new Text2("King's Gold", {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
kingsGoldText.anchor.set(0.5, 0.5);
kingsGoldButton.addChild(kingsGoldText);
kingsGoldButton.x = 0;
kingsGoldButton.y = 480;
self.addChild(kingsGoldButton);
// Queen's Walk button handler
queensWalkButton.down = function (x, y, obj) {
// Start Queen's Walk challenge - only CapyQueen, CapyPult, and CapyPrincess towers
self.startChallenge("Queen's Walk", ['queen', 'splash', 'princess']);
};
// King's Gold button handler
kingsGoldButton.down = function (x, y, obj) {
// Start King's Gold challenge - only CapyKing, CapyFreeze, and CapyPrince towers
self.startChallenge("King's Gold", ['king', 'slow', 'prince']);
};
// Challenge starter function
self.startChallenge = function (challengeName, allowedTowers) {
// Set challenge mode variables
tutorialMode = false;
selectedLevel = 'lobby'; // Use lobby level as base
difficultyMultiplier = 1.0; // Normal difficulty
totalWaves = 25; // Shorter challenge
nextWaveTime = 12000 / 2 * 0.8; // Faster waves
// Set challenge-specific starting gold
if (challengeName === "King's Gold") {
setGold(240); // King's Gold specific starting gold
} else {
setGold(270); // More starting gold for other challenges
}
challengeMode = true;
challengeAllowedTowers = allowedTowers;
// Set global challengeName variable properly
window.challengeName = challengeName;
// Regenerate grid with lobby configuration
// Regenerate grid based on challenge type
debugLayer.removeChild(grid);
grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * 4;
var wallSquares;
if (challengeName === 'Cold Blast') {
// Define positions for 4 wall squares (4x4 each) arranged in 2 rows, 2 columns
wallSquares = [{
startX: 6,
startY: 8
}, {
startX: 14,
startY: 8
}, {
startX: 6,
startY: 18
}, {
startX: 14,
startY: 18
}];
// Create the wall squares for Cold Blast
for (var s = 0; s < wallSquares.length; s++) {
var square = wallSquares[s];
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
var cell = grid.getCell(square.startX + i, square.startY + j);
if (cell && cell.type === 0) {
cell.type = 1; // Set to wall type
}
}
}
}
} else if (challengeName === 'Hot Potato') {
// Hot Potato challenge has no wall squares - keep grid clear
// Paint floor cells blue in bottom third of map for Hot Potatoe challenge
if (challengeName === 'Hot Potato') {
var gridWidth = 24;
var gridHeight = 29 + 6;
var bottomThirdStart = Math.floor(gridHeight * 2 / 3); // Start of bottom third
// Paint floor cells blue in bottom third
for (var i = 0; i < gridWidth; i++) {
for (var j = bottomThirdStart; j < gridHeight - 4; j++) {
var cell = grid.getCell(i, j);
if (cell && cell.type === 0) {
// Only paint floor cells
// Mark cell for blue painting - this will be handled in the render debug
cell.hotPotatoeBlue = true;
}
}
}
}
} else if (challengeName === "Queen's Walk") {
// Queen's Walk: no wall squares, keep garden level clear
} else if (challengeName === "King's Gold") {
// King's Gold: Create 4 wall cell squares for Great Hall level
var gridHeight = 29 + 6;
var wallSquares = [{
startX: 1,
startY: gridHeight - 10 // Left border square, moved up 2 cells
}, {
startX: 19,
startY: gridHeight - 10 // Right border square, moved up 2 cells
}, {
startX: 6,
startY: 12 // Middle left square
}, {
startX: 14,
startY: 12 // Middle right square
}];
// Create the wall squares
for (var s = 0; s < wallSquares.length; s++) {
var square = wallSquares[s];
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
var cell = grid.getCell(square.startX + i, square.startY + j);
if (cell && cell.type === 0) {
cell.type = 1; // Set to wall type
}
}
}
}
} else {
// Define positions for the 3 wall squares (4x4 each) - same as lobby for other challenges
wallSquares = [{
startX: 4,
startY: 10
}, {
startX: 10,
startY: 16
}, {
startX: 16,
startY: 10
}];
// Create the wall squares for other challenges
for (var s = 0; s < wallSquares.length; s++) {
var square = wallSquares[s];
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
var cell = grid.getCell(square.startX + i, square.startY + j);
if (cell && cell.type === 0) {
cell.type = 1; // Set to wall type
}
}
}
}
}
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
// Force re-render after Hot Potatoe challenge setup to apply blue painting
if (challengeName === 'Hot Potato') {
// Wait a frame then force re-render to ensure blue floor cells are painted
LK.setTimeout(function () {
grid.renderDebug();
}, 100);
}
// Create images over the 4x4 wall squares only for challenges that have them
if (challengeName === 'Cold Blast') {
// Use independent coldblast square assets for the 4 squares
var squareAssets = ['coldblast_square_1', 'coldblast_square_2', 'coldblast_square_3', 'coldblast_square_4'];
for (var s = 0; s < wallSquares.length; s++) {
var square = wallSquares[s];
var squareImage = game.attachAsset(squareAssets[s], {
anchorX: 0.5,
anchorY: 0.5
});
// Apply cold blue tint to distinguish from lobby assets
squareImage.tint = 0x88CCFF;
var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE;
var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE;
squareImage.x = squareCenterX - CELL_SIZE / 2;
squareImage.y = squareCenterY - CELL_SIZE / 2;
towerLayer.addChild(squareImage);
lobbySquareImages.push(squareImage);
}
} else if (challengeName === 'Hot Potato') {
// Hot Potato: no square images, keep grid clear
// Define positions for the 3 wall squares (4x4 each) - same as lobby for Hot Potato challenges
var hotPotatoWallSquares = [{
startX: 4,
startY: 10
}, {
startX: 10,
startY: 16
}, {
startX: 16,
startY: 10
}];
// No square images created for Hot Potato challenge
} else if (challengeName === "Queen's Walk") {
// Queen's Walk: no square images, keep garden level clear
} else if (challengeName === "King's Gold") {
// King's Gold: Create images over the 4 great hall wall squares
var squareAssets = ['greathall_square_1', 'greathall_square_2', 'greathall_square_1', 'greathall_square_2'];
var gridHeight = 29 + 6;
var wallSquares = [{
startX: 1,
startY: gridHeight - 10
}, {
startX: 19,
startY: gridHeight - 10
}, {
startX: 6,
startY: 12
}, {
startX: 14,
startY: 12
}];
for (var s = 0; s < wallSquares.length; s++) {
var square = wallSquares[s];
var squareImage = game.attachAsset(squareAssets[s], {
anchorX: 0.5,
anchorY: 0.5
});
var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE;
var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE;
squareImage.x = squareCenterX - CELL_SIZE / 2;
squareImage.y = squareCenterY - CELL_SIZE / 2;
towerLayer.addChild(squareImage);
lobbySquareImages.push(squareImage);
}
} else {
// Other challenges use 3 lobby square assets
var squareAssets = ['lobby_square_1', 'lobby_square_2', 'lobby_square_3'];
for (var s = 0; s < wallSquares.length; s++) {
var square = wallSquares[s];
var squareImage = game.attachAsset(squareAssets[s], {
anchorX: 0.5,
anchorY: 0.5
});
var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE;
var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE;
squareImage.x = squareCenterX - CELL_SIZE / 2;
squareImage.y = squareCenterY - CELL_SIZE / 2;
towerLayer.addChild(squareImage);
lobbySquareImages.push(squareImage);
}
}
// Update background based on challenge
var newBackgroundAssetName;
if (challengeName === 'Cold Blast') {
newBackgroundAssetName = 'background_coldblast';
} else if (challengeName === 'Hot Potato') {
newBackgroundAssetName = 'background_hotpotato';
} else if (challengeName === "Queen's Walk") {
newBackgroundAssetName = 'background_queens_walk';
} else if (challengeName === "King's Gold") {
newBackgroundAssetName = 'background_greathall';
} else {
newBackgroundAssetName = 'background_lobby';
}
game.removeChild(backgroundImage);
backgroundImage = game.attachAsset(newBackgroundAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply cold blue tint for Cold Blast challenge
if (challengeName === 'Cold Blast') {
backgroundImage.tint = 0xCCDDFF;
}
backgroundImage.x = 2048 / 2;
backgroundImage.y = 2732 / 2;
backgroundImage.width = 2048;
backgroundImage.height = 2732;
game.addChildAt(backgroundImage, 0);
// Clear existing source towers and recreate them for challenge mode
for (var i = sourceTowers.length - 1; i >= 0; i--) {
var sourceTower = sourceTowers[i];
if (sourceTower.parent) {
sourceTower.parent.removeChild(sourceTower);
}
}
sourceTowers = [];
// Create source towers with challenge-specific types ordered by challenge
var challengeTowerTypes;
if (challengeName === 'Cold Blast') {
// Cold Blast: CapyFreeze, CapyBomb, CapyDemolition
challengeTowerTypes = ['slow', 'bomb', 'demolition'];
} else if (challengeName === 'Hot Potato') {
// Hot Potato: CapySniper, CapyBomb, CapyDemolition
challengeTowerTypes = ['sniper', 'bomb', 'demolition'];
} else if (challengeName === "Queen's Walk") {
// Queen's Walk: CapyQueen, CapyPult, CapyPrincess
challengeTowerTypes = ['queen', 'splash', 'princess'];
} else if (challengeName === "King's Gold") {
// King's Gold: CapyKing, CapyFreeze, CapyPrince
challengeTowerTypes = ['king', 'slow', 'prince'];
} else {
// Fallback to all types if challenge name doesn't match
challengeTowerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison', 'bomb', 'demolition', 'queen', 'king'];
}
var towerSpacing = 320;
var startX = 2048 / 2 - challengeTowerTypes.length * towerSpacing / 2 + towerSpacing / 2;
var towerY = 2732 - CELL_SIZE * 3 - 90;
for (var t = 0; t < challengeTowerTypes.length; t++) {
var tower = new SourceTower(challengeTowerTypes[t]);
tower.x = startX + t * towerSpacing;
tower.y = towerY;
tower.scalingInitialized = false;
// Show/hide based on allowed towers for this challenge
if (allowedTowers.indexOf(tower.towerType) === -1) {
tower.visible = false;
tower.alpha = 0.3;
} else {
tower.visible = true;
tower.alpha = 1;
}
towerLayer.addChild(tower);
sourceTowers.push(tower);
}
// Show start game button
if (waveIndicator) {
waveIndicator.showStartButton();
}
// Show challenge-specific instruction screen
if (challengeName === 'Cold Blast') {
var instructionsScreen = new ColdBlastInstructionsScreen();
instructionsScreen.x = 2048 / 2;
instructionsScreen.y = 2732 / 2;
game.addChild(instructionsScreen);
} else if (challengeName === 'Hot Potato') {
var instructionsScreen = new HotPotatoInstructionsScreen();
instructionsScreen.x = 2048 / 2;
instructionsScreen.y = 2732 / 2;
game.addChild(instructionsScreen);
} else if (challengeName === "Queen's Walk") {
var instructionsScreen = new QueensWalkInstructionsScreen();
instructionsScreen.x = 2048 / 2;
instructionsScreen.y = 2732 / 2;
game.addChild(instructionsScreen);
} else if (challengeName === "King's Gold") {
var instructionsScreen = new KingsGoldInstructionsScreen();
instructionsScreen.x = 2048 / 2;
instructionsScreen.y = 2732 / 2;
game.addChild(instructionsScreen);
} else {
// Show selection feedback for other challenges
var notification = game.addChild(new Notification(challengeName + " Challenge Started!"));
notification.x = 2048 / 2;
notification.y = 2732 / 2;
}
// Remove challenges screen
self.destroy();
};
// Back button handler - return to mode selector
backButton.down = function (x, y, obj) {
// Remove challenges screen
self.destroy();
// Show mode selector
var modeSelector = new ModeSelector();
modeSelector.x = 2048 / 2;
modeSelector.y = 2732 / 2;
game.addChild(modeSelector);
};
return self;
});
var CapybarasInfoScreen = Container.expand(function () {
var self = Container.call(this);
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Title shadow
var titleShadow = new Text2("Capybara Towers", {
size: 120,
fill: 0x000000,
weight: 800
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -1200 + 4;
self.addChild(titleShadow);
// Title
var title = new Text2("Capybara Towers", {
size: 120,
fill: 0x8B4513,
weight: 800
});
title.anchor.set(0.5, 0.5);
title.y = -1200;
self.addChild(title);
// Tower information data
var towerData = [{
name: "CapyShot",
type: "default",
cost: "5 gold",
description: "Basic balanced tower with moderate damage and range"
}, {
name: "CapyBlast",
type: "rapid",
cost: "15 gold",
description: "Fast firing tower with low damage per shot"
}, {
name: "CapySniper",
type: "sniper",
cost: "25 gold",
description: "Long range tower with high damage"
}, {
name: "CapyPult",
type: "splash",
cost: "35 gold",
description: "Area damage tower that hits multiple enemies"
}, {
name: "CapyFreeze",
type: "slow",
cost: "45 gold",
description: "Slows down enemies with special effects"
}, {
name: "CapyPoison",
type: "poison",
cost: "55 gold",
description: "Applies poison damage over time"
}, {
name: "CapyBomb",
type: "bomb",
cost: "80 gold",
description: "Explodes after 2 seconds dealing massive area damage"
}, {
name: "CapyQueen",
type: "queen",
cost: "200 gold",
description: "Enhanced freeze tower with increased effects"
}, {
name: "CapyKing",
type: "king",
cost: "250 gold",
description: "Ultimate sniper tower with devastating damage"
}, {
name: "CapyDemolition",
type: "demolition",
cost: "150 gold",
description: "Explodes after 2 seconds dealing massive area damage"
}, {
name: "CapyPrince",
type: "prince",
cost: "130 gold",
description: "Enhanced splash tower with double range and double damage"
}, {
name: "CapyPrincess",
type: "princess",
cost: "130 gold",
description: "Enhanced poison tower with double range effects"
}];
// Create scrollable content container
var contentContainer = new Container();
self.addChild(contentContainer);
// Create tower information entries
var startY = -1000;
var entryHeight = 200;
for (var i = 0; i < towerData.length; i++) {
var tower = towerData[i];
var entryContainer = new Container();
contentContainer.addChild(entryContainer);
// Tower image
var towerImage = entryContainer.attachAsset('tower_' + tower.type, {
anchorX: 0.5,
anchorY: 0.5
});
towerImage.x = -700;
towerImage.y = startY + i * entryHeight;
towerImage.width = 138; // Increased by 15% (120 * 1.15 = 138)
towerImage.height = 138; // Increased by 15% (120 * 1.15 = 138)
// Start continuous scaling animation for tower image
(function (image) {
function scaleUp() {
tween(image, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
scaleDown();
}
});
}
function scaleDown() {
tween(image, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
scaleUp();
}
});
}
scaleUp();
})(towerImage);
// Tower name shadow
var nameShadow = new Text2(tower.name, {
size: 60,
fill: 0x000000,
weight: 800
});
nameShadow.anchor.set(0, 0.5);
nameShadow.x = -500 + 2;
nameShadow.y = startY + i * entryHeight - 30 + 2;
entryContainer.addChild(nameShadow);
// Tower name
var nameText = new Text2(tower.name, {
size: 60,
fill: 0xFFD700,
weight: 800
});
nameText.anchor.set(0, 0.5);
nameText.x = -500;
nameText.y = startY + i * entryHeight - 30;
entryContainer.addChild(nameText);
// Cost shadow
var costShadow = new Text2(tower.cost, {
size: 45,
fill: 0x000000,
weight: 600
});
costShadow.anchor.set(0, 0.5);
costShadow.x = -500 + 2;
costShadow.y = startY + i * entryHeight + 10 + 2;
entryContainer.addChild(costShadow);
// Cost text
var costText = new Text2(tower.cost, {
size: 45,
fill: 0x00FF00,
weight: 600
});
costText.anchor.set(0, 0.5);
costText.x = -500;
costText.y = startY + i * entryHeight + 10;
entryContainer.addChild(costText);
// Description shadow
var descShadow = new Text2(tower.description, {
size: 40,
fill: 0x000000,
weight: 400
});
descShadow.anchor.set(0, 0.5);
descShadow.x = -500 + 2;
descShadow.y = startY + i * entryHeight + 50 + 2;
descShadow.wordWrap = true;
descShadow.wordWrapWidth = 1000;
entryContainer.addChild(descShadow);
// Description text
var descText = new Text2(tower.description, {
size: 40,
fill: 0xFFFFFF,
weight: 400
});
descText.anchor.set(0, 0.5);
descText.x = -500;
descText.y = startY + i * entryHeight + 50;
descText.wordWrap = true;
descText.wordWrapWidth = 1000;
entryContainer.addChild(descText);
}
// Back button
var backButton = new Container();
var backBg = backButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
backBg.width = 400;
backBg.height = 100;
backBg.tint = 0xCC0000;
var backTextShadow = new Text2("Back", {
size: 60,
fill: 0x000000,
weight: 800
});
backTextShadow.anchor.set(0.5, 0.5);
backTextShadow.x = 2;
backTextShadow.y = 2;
backButton.addChild(backTextShadow);
var backText = new Text2("Back", {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
backText.anchor.set(0.5, 0.5);
backButton.addChild(backText);
backButton.x = -800;
backButton.y = -1200;
self.addChild(backButton);
// Back button handler - return to mode selector
backButton.down = function (x, y, obj) {
// Remove capybaras info screen
self.destroy();
// Show mode selector
var modeSelector = new ModeSelector();
modeSelector.x = 2048 / 2;
modeSelector.y = 2732 / 2;
game.addChild(modeSelector);
};
return self;
});
var ColdBlastInstructionsScreen = Container.expand(function () {
var self = Container.call(this);
// Pause the game when instructions screen is shown
self.gamePaused = true;
// Hide UI labels when instructions screen is shown
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Cold Blast instruction image at the bottom
var instructionsImage = self.attachAsset('coldblast_instructions_image', {
anchorX: 0.5,
anchorY: 1.0
});
instructionsImage.x = 0;
instructionsImage.y = 2732 / 2 - 100; // Position higher up
instructionsImage.width = 800;
instructionsImage.height = 800;
// Instructions text label in the center of the screen
var instructionsTextShadow = new Text2("Welcome to Cold Blast Challenge!\nBrr! The weather is freezing!\nOnly CapyFreeze, CapyBomb and CapyDemolition towers work in this cold.\nUse the icy environment to your advantage.\nGood luck, defender!\n", {
size: 55,
fill: 0x000000,
weight: 800,
align: 'center'
});
instructionsTextShadow.anchor.set(0.5, 0.5);
instructionsTextShadow.x = 4; // Shadow offset
instructionsTextShadow.y = 4; // Centered vertically with shadow offset
// Set maximum width to ensure text wraps properly
instructionsTextShadow.wordWrap = true;
instructionsTextShadow.wordWrapWidth = 1800;
self.addChild(instructionsTextShadow);
var instructionsText = new Text2("Welcome to Cold Blast Challenge!\nBrr! The weather is freezing!\nOnly CapyFreeze, CapyBomb and CapyDemolition towers work in this cold.\nUse the icy environment to your advantage.\nGood luck, defender!\n", {
size: 55,
fill: 0xFFFFFF,
weight: 800,
align: 'center'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 0;
instructionsText.y = 0; // Centered vertically in screen
// Set maximum width to ensure text wraps properly
instructionsText.wordWrap = true;
instructionsText.wordWrapWidth = 1800;
self.addChild(instructionsText);
// Continue button at the top
var continueButton = new Container();
var buttonBg = continueButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 600;
buttonBg.height = 150;
buttonBg.tint = 0x0088FF;
var buttonTextShadow = new Text2("Continue", {
size: 80,
fill: 0x000000,
weight: 800
});
buttonTextShadow.anchor.set(0.5, 0.5);
buttonTextShadow.x = 2;
buttonTextShadow.y = 2;
continueButton.addChild(buttonTextShadow);
var buttonText = new Text2("Continue", {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
continueButton.addChild(buttonText);
continueButton.x = 0;
continueButton.y = -2732 / 2 + 300; // Position at top
self.addChild(continueButton);
// Continue button handler - close the screen
continueButton.down = function (x, y, obj) {
// Unpause the game
self.gamePaused = false;
// Show UI labels when continue button is pressed
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
// Remove instructions screen
self.destroy();
};
return self;
});
var DebugCell = Container.expand(function () {
var self = Container.call(this);
var cellGraphics = self.attachAsset(getCellAssetName(), {
anchorX: 0.5,
anchorY: 0.5
});
cellGraphics.tint = Math.random() * 0xffffff;
var debugArrows = [];
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) {
// Check if we need to update the cell asset based on current level
var currentAssetName = getCellAssetName();
if (cellGraphics.assetName !== currentAssetName) {
// Remove old graphics
self.removeChild(cellGraphics);
// Create new graphics with correct asset for current level
cellGraphics = self.attachAsset(currentAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
cellGraphics.assetName = currentAssetName;
}
switch (data.type) {
case 0:
case 2:
{
if (data.pathId != pathId) {
self.removeArrows();
numberLabel.setText("-");
cellGraphics.tint = 0x880000;
return;
}
numberLabel.visible = false;
var towerInRangeHighlight = false;
if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) {
towerInRangeHighlight = true;
cellGraphics.tint = 0x0088ff;
} else {
// Check if this is Great Hall level and we're in the middle path from entrance to exit
if (selectedLevel === 'greathall') {
var gridWidth = 24;
var gridHeight = 29 + 6;
var centerX = Math.floor(gridWidth / 2); // Center column (12)
// Check if current cell is in the center column path or adjacent cells from entrance (top) to exit (bottom)
if (data.x >= centerX - 3 && data.x <= centerX + 2 && data.y >= 5 && data.y <= gridHeight - 5) {
cellGraphics.tint = 0xFF0000; // Red color for the middle path and adjacent cells
} else {
cellGraphics.tint = 0xFFFFFF; // Remove blue background - set to white/transparent
}
} else if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) {
// Paint floor cells blue in bottom third of map for Hot Potatoe challenge
var gridWidth = 24;
var gridHeight = 29 + 6;
var bottomThirdStart = Math.floor(gridHeight * 2 / 3); // Start of bottom third
if (data.y >= bottomThirdStart && data.y < gridHeight - 4) {
tween(cellGraphics, {
tint: 0x0088FF
}, {
duration: 500,
easing: tween.easeOut
});
} else {
cellGraphics.tint = 0xFFFFFF; // Remove blue background - set to white/transparent
}
} else {
cellGraphics.tint = 0xFFFFFF; // Remove blue background - set to white/transparent
}
}
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 = 0;
self.addChildAt(debugArrows[a], 1);
}
debugArrows[a].rotation = angle;
debugArrows[a].alpha = 0;
}
break;
}
case 1:
{
self.removeArrows();
// Check if this is tutorial mode and make wall cells invisible
if (tutorialMode) {
cellGraphics.alpha = 0; // Make wall cells invisible in tutorial mode
} else if (selectedLevel === 'pool') {
var gridWidth = 24;
var gridHeight = 29 + 6;
var centerX = Math.floor(gridWidth / 2); // Center column (12)
var centerY = Math.floor(gridHeight / 2); // Center row
var rectWidth = 6; // Rectangle width (6 cells)
var rectHeight = 6; // Rectangle height (6 cells)
var rectLeft = centerX - Math.floor(rectWidth / 2);
var rectRight = centerX + Math.floor(rectWidth / 2);
var rectTop = centerY - Math.floor(rectHeight / 2);
var rectBottom = centerY + Math.floor(rectHeight / 2);
// Check if current cell is within the rectangular pool zone
if (data.x >= rectLeft && data.x < rectRight && data.y >= rectTop && data.y < rectBottom) {
// Use gray color for the rectangular pool area
tween(cellGraphics, {
tint: 0x888888
}, {
duration: 500,
easing: tween.easeOut
});
} else {
cellGraphics.tint = 0xaaaaaa; // Regular wall color
}
} else {
cellGraphics.tint = 0xaaaaaa; // Regular wall color for other levels
}
numberLabel.visible = false;
break;
}
case 3:
{
self.removeArrows();
cellGraphics.tint = 0x008800;
numberLabel.visible = false;
break;
}
}
numberLabel.setText(Math.floor(data.score / 1000) / 10);
};
});
var Diamond = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
// Use independent diamond asset based on challenge
var assetName = 'diamond_queens_walk'; // Default to Queen's Walk diamond
if (challengeMode && (challengeName === "Queen's Walk" || window.challengeName === "Queen's Walk")) {
assetName = 'diamond_queens_walk';
}
var diamondGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Make diamonds a bit smaller by setting width and height directly
diamondGraphics.width = 100; // Reduced from 120px to 100px
diamondGraphics.height = 83.2; // Reduced from 99.84px to 83.2px (maintaining aspect ratio)
// Remove rotation to keep diamonds straight
// Add pulsing animation
self.pulseTimer = 0;
self.update = function () {
self.pulseTimer += 0.1;
var pulseScale = 1 + 0.2 * Math.sin(self.pulseTimer);
diamondGraphics.width = 100 * pulseScale;
diamondGraphics.height = 83.2 * pulseScale;
};
return self;
});
var DifficultySelector = Container.expand(function () {
var self = Container.call(this);
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Title
var titleShadow = new Text2("Select Difficulty", {
size: 120,
fill: 0x000000,
weight: 800
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -800;
self.addChild(titleShadow);
var title = new Text2("Select Difficulty", {
size: 120,
fill: 0xFFFFFF,
weight: 800
});
title.anchor.set(0.5, 0.5);
title.y = -800;
self.addChild(title);
// Difficulty buttons
var difficulties = [{
name: "Easy",
color: 0x00AA00,
multiplier: 0.7,
description: "Enemies have 30% less health"
}, {
name: "Normal",
color: 0x0088FF,
multiplier: 1.0,
description: "Standard difficulty"
}, {
name: "Hard",
color: 0xFFFF00,
multiplier: 1.5,
description: "Enemies have 50% more health"
}, {
name: "Insane",
color: 0xFF0000,
multiplier: 2.0,
description: "Enemies have 100% more health"
}];
var buttonSpacing = 300;
var startY = -200;
for (var i = 0; i < difficulties.length; i++) {
var difficulty = difficulties[i];
var button = new Container();
var buttonBg = button.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 600;
buttonBg.height = 200;
buttonBg.tint = difficulty.color;
// Button title shadow
var buttonTitleShadow = new Text2(difficulty.name, {
size: 80,
fill: 0x000000,
weight: 800
});
buttonTitleShadow.anchor.set(0.5, 0.5);
buttonTitleShadow.x = 4;
buttonTitleShadow.y = -20;
button.addChild(buttonTitleShadow);
// Button title
var buttonTitle = new Text2(difficulty.name, {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
buttonTitle.anchor.set(0.5, 0.5);
buttonTitle.y = -20;
button.addChild(buttonTitle);
// Description shadow
var descShadow = new Text2(difficulty.description, {
size: 40,
fill: 0x000000,
weight: 400
});
descShadow.anchor.set(0.5, 0.5);
descShadow.x = 2;
descShadow.y = 30;
button.addChild(descShadow);
// Description
var desc = new Text2(difficulty.description, {
size: 40,
fill: 0xFFFFFF,
weight: 400
});
desc.anchor.set(0.5, 0.5);
desc.y = 28;
button.addChild(desc);
button.y = startY + i * buttonSpacing;
button.difficulty = difficulty;
self.addChild(button);
// Button click handler
button.down = function (x, y, obj) {
var selectedDifficulty = this.difficulty;
difficultyMultiplier = selectedDifficulty.multiplier;
// Set initial gold based on difficulty
switch (selectedDifficulty.name) {
case "Easy":
setGold(80);
break;
case "Normal":
setGold(85);
break;
case "Hard":
setGold(100);
break;
case "Insane":
setGold(150);
break;
}
// Show start game button after difficulty selection
if (waveIndicator) {
waveIndicator.showStartButton();
}
// Show selection feedback
var notification = game.addChild(new Notification("Difficulty set to " + selectedDifficulty.name + "!"));
notification.x = 2048 / 2;
notification.y = 2732 / 2;
// Remove difficulty selector
self.destroy();
};
}
return self;
});
// This update method was incorrectly placed here and should be removed
var EffectIndicator = Container.expand(function (x, y, type) {
var self = Container.call(this);
self.x = x;
self.y = y;
var effectGraphics = self.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
effectGraphics.blendMode = 1;
switch (type) {
case 'splash':
effectGraphics.tint = 0x33CC00;
effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5;
break;
case 'slow':
effectGraphics.tint = 0x9900FF;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
case 'poison':
effectGraphics.tint = 0x00FFAA;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
case 'sniper':
effectGraphics.tint = 0xFF5500;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
case 'explosion':
effectGraphics.tint = 0xFF4500;
effectGraphics.width = effectGraphics.height = CELL_SIZE * 3; // Larger explosion effect
break;
}
effectGraphics.alpha = 0.7;
self.alpha = 0;
// Animate the effect
tween(self, {
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
return self;
});
// Base enemy class for common functionality
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'normal';
self.speed = .01;
self.cellX = 0;
self.cellY = 0;
self.currentCellX = 0;
self.currentCellY = 0;
self.currentTarget = undefined;
self.maxHealth = 100;
self.health = self.maxHealth;
self.bulletsTargetingThis = [];
self.waveNumber = currentWave;
self.isFlying = false;
self.isImmune = false;
self.isBoss = false;
// Check if this is a boss wave
// Check if this is a boss wave
// Apply different stats based on enemy type
switch (self.type) {
case 'fast':
self.speed *= 2; // Twice as fast
self.maxHealth = 100;
break;
case 'immune':
self.isImmune = true;
self.maxHealth = 80;
break;
case 'flying':
self.isFlying = true;
self.maxHealth = 80;
break;
case 'swarm':
self.maxHealth = 50; // Weaker enemies
break;
case 'normal':
default:
// Normal enemy uses default values
break;
}
if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') {
self.isBoss = true;
// Boss enemies have 20x health and are larger
self.maxHealth *= 20;
// Slower speed for bosses
self.speed = self.speed * 0.7;
}
self.health = self.maxHealth;
// Get appropriate asset for this enemy type
var assetId = 'enemy';
if (self.type !== 'normal') {
assetId = 'enemy_' + self.type;
}
var enemyGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply 50% size increase to all enemies (25% increase on top of previous 20%)
enemyGraphics.scaleX = 1.5;
enemyGraphics.scaleY = 1.5;
// Scale up boss enemies (additional scaling on top of base 50% increase)
if (self.isBoss) {
enemyGraphics.scaleX = 1.8 * 1.5; // 1.8 * 1.5 = 2.7
enemyGraphics.scaleY = 1.8 * 1.5;
}
// Fall back to regular enemy asset if specific type asset not found
// Apply tint to differentiate enemy types
/*switch (self.type) {
case 'fast':
enemyGraphics.tint = 0x00AAFF; // Blue for fast enemies
break;
case 'immune':
enemyGraphics.tint = 0xAA0000; // Red for immune enemies
break;
case 'flying':
enemyGraphics.tint = 0xFFFF00; // Yellow for flying enemies
break;
case 'swarm':
enemyGraphics.tint = 0xFF00FF; // Pink for swarm enemies
break;
}*/
// Create shadow for flying enemies
if (self.isFlying) {
// Create a shadow container that will be added to the shadow layer
self.shadow = new Container();
// Clone the enemy graphics for the shadow
var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply shadow effect
shadowGraphics.tint = 0x000000; // Black shadow
shadowGraphics.alpha = 0.4; // Semi-transparent
// Apply 50% size increase to shadow to match enemy
shadowGraphics.scaleX = 1.5;
shadowGraphics.scaleY = 1.5;
// If this is a boss, scale up the shadow to match
if (self.isBoss) {
shadowGraphics.scaleX = 1.8 * 1.5; // 1.8 * 1.5 = 2.7
shadowGraphics.scaleY = 1.8 * 1.5;
}
// Position shadow slightly offset
self.shadow.x = 20; // Offset right
self.shadow.y = 20; // Offset down
// Ensure shadow has the same rotation as the enemy
shadowGraphics.rotation = enemyGraphics.rotation;
}
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;
healthBarOutline.x = -healthBarOutline.width / 2;
healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5;
healthBar.tint = 0x00ff00;
healthBarBG.tint = 0xff0000;
self.healthBar = healthBar;
// Add scaling animation function
self.startScalingAnimation = function () {
// Function to animate to larger scale (108%)
function scaleUp() {
tween(enemyGraphics, {
scaleX: 1.08,
scaleY: 1.08
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
scaleDown();
}
});
}
// Function to animate to smaller scale (92%)
function scaleDown() {
tween(enemyGraphics, {
scaleX: 0.92,
scaleY: 0.92
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
scaleUp();
}
});
}
// Start the animation cycle
scaleUp();
};
self.update = function () {
if (self.health <= 0) {
self.health = 0;
self.healthBar.width = 0;
}
// Handle slow effect
if (self.isImmune) {
// Immune enemies cannot be slowed or poisoned, clear any such effects
self.slowed = false;
self.slowEffect = false;
self.poisoned = false;
self.poisonEffect = false;
// Reset speed to original if needed
if (self.originalSpeed !== undefined) {
self.speed = self.originalSpeed;
}
} else {
// Handle slow effect
if (self.slowed) {
// Visual indication of slowed status
if (!self.slowEffect) {
self.slowEffect = true;
}
self.slowDuration--;
if (self.slowDuration <= 0) {
self.speed = self.originalSpeed;
self.slowed = false;
self.slowEffect = false;
// Only reset tint if not poisoned
if (!self.poisoned) {
enemyGraphics.tint = 0xFFFFFF; // Reset tint
}
}
}
// Handle poison effect
if (self.poisoned) {
// Visual indication of poisoned status
if (!self.poisonEffect) {
self.poisonEffect = true;
}
// Apply poison damage every 30 frames (twice per second)
if (LK.ticks % 30 === 0) {
self.health -= self.poisonDamage;
if (self.health <= 0) {
self.health = 0;
}
self.healthBar.width = self.health / self.maxHealth * 70;
}
self.poisonDuration--;
if (self.poisonDuration <= 0) {
self.poisoned = false;
self.poisonEffect = false;
// Only reset tint if not slowed
if (!self.slowed) {
enemyGraphics.tint = 0xFFFFFF; // Reset tint
}
}
}
}
// Set tint based on effect status
if (self.isImmune) {
enemyGraphics.tint = 0xFFFFFF;
} else if (self.poisoned && self.slowed) {
// Combine poison (0x00FFAA) and slow (0x9900FF) colors
// Simple average: R: (0+153)/2=76, G: (255+0)/2=127, B: (170+255)/2=212
enemyGraphics.tint = 0x4C7FD4;
} else if (self.poisoned) {
enemyGraphics.tint = 0x00FFAA;
} else if (self.slowed) {
enemyGraphics.tint = 0x9900FF;
} else {
enemyGraphics.tint = 0xFFFFFF;
}
// Initialize scaling animation on first update
if (!self.scalingInitialized) {
self.scalingInitialized = true;
// Start the continuous scaling animation
self.startScalingAnimation();
}
if (self.currentTarget) {
var ox = self.currentTarget.x - self.currentCellX;
var oy = self.currentTarget.y - self.currentCellY;
if (ox !== 0 || oy !== 0) {
var angle = Math.atan2(oy, ox);
if (enemyGraphics.targetRotation === undefined) {
enemyGraphics.targetRotation = angle;
enemyGraphics.rotation = angle;
} else {
if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) {
tween.stop(enemyGraphics, {
rotation: true
});
// Calculate the shortest angle to rotate
var currentRotation = enemyGraphics.rotation;
var angleDiff = angle - currentRotation;
// Normalize angle difference to -PI to PI range for shortest path
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
enemyGraphics.targetRotation = angle;
tween(enemyGraphics, {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
}
}
healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10;
};
return self;
});
var GoldIndicator = Container.expand(function (value, x, y) {
var self = Container.call(this);
var shadowText = new Text2("+" + value, {
size: 45,
fill: 0x000000,
weight: 800
});
shadowText.anchor.set(0.5, 0.5);
shadowText.x = 2;
shadowText.y = 2;
self.addChild(shadowText);
var goldText = new Text2("+" + value, {
size: 45,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
self.addChild(goldText);
self.x = x;
self.y = y;
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
tween(self, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2,
y: y - 40
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5,
y: y - 80
}, {
duration: 600,
easing: tween.easeIn,
delay: 800,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
return self;
});
var Grid = Container.expand(function (gridWidth, gridHeight) {
var self = Container.call(this);
self.cells = [];
self.spawns = [];
self.goals = [];
for (var i = 0; i < gridWidth; i++) {
self.cells[i] = [];
for (var j = 0; j < gridHeight; j++) {
self.cells[i][j] = {
score: 0,
pathId: 0,
towersInRange: []
};
}
}
/*
Cell Types
0: Transparent floor
1: Wall
2: Spawn
3: Goal
*/
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
var cell = self.cells[i][j];
// Check if we're in Hot Potato challenge mode
var isHotPotato = challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato');
// For Hot Potato, only set side walls and bottom walls, not top walls
var cellType;
if (isHotPotato) {
// Hot Potato: only side walls (left/right) and bottom walls, no top walls
cellType = i === 0 || i === gridWidth - 1 || j >= gridHeight - 4 ? 1 : 0;
} else {
// Normal behavior: top, bottom, and side walls
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;
}
}
// Tutorial mode grid modification - fill sides with walls, leave only 6 middle rows as floor
if (tutorialMode) {
// For tutorial, fill all side areas with walls, only leave 6 middle columns (9-14) as walkable
if (i < 9 || i > 14) {
// Set side areas to walls (except for spawn/goal areas which are already handled above)
if (cellType === 0) {
cellType = 1; // Convert floor to wall
}
}
}
// Add rectangular no-tower zone for Pool level in the middle area
if (selectedLevel === 'pool') {
// Create a large rectangular area in the center where towers cannot be placed
var centerX = Math.floor(gridWidth / 2); // Center column (12)
var centerY = Math.floor(gridHeight / 2); // Center row
var rectWidth = 6; // Rectangle width (6 cells)
var rectHeight = 16; // Rectangle height (16 cells)
var rectLeft = centerX - Math.floor(rectWidth / 2);
var rectRight = centerX + Math.floor(rectWidth / 2);
var rectTop = centerY - Math.floor(rectHeight / 2);
var rectBottom = centerY + Math.floor(rectHeight / 2);
// Check if current cell is within the rectangular zone
if (i >= rectLeft && i < rectRight && j >= rectTop && j < rectBottom) {
// Only block tower placement if it's a walkable area (not spawn/goal/wall)
if (cellType === 0) {
cellType = 1; // Set to wall type to prevent tower placement
}
}
}
cell.type = cellType;
cell.x = i;
cell.y = j;
cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1];
cell.up = self.cells[i - 1] && self.cells[i - 1][j];
cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1];
cell.left = self.cells[i][j - 1];
cell.right = self.cells[i][j + 1];
cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1];
cell.down = self.cells[i + 1] && self.cells[i + 1][j];
cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1];
cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left];
cell.targets = [];
if (j > 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;
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];
// Skip enemies that haven't entered the viewable area yet
if (enemy.currentCellY < 4) {
continue;
}
// Skip flying enemies from path check as they can fly over obstacles
if (enemy.isFlying) {
continue;
}
var target = self.getCell(enemy.cellX, enemy.cellY);
if (enemy.currentTarget) {
if (enemy.currentTarget.pathId != pathId) {
if (!target || target.pathId != pathId) {
console.warn("Enemy blocked 1 ");
return true;
}
}
} else if (!target || target.pathId != pathId) {
console.warn("Enemy blocked 2");
return true;
}
}
console.log("Speed", new Date().getTime() - before);
};
self.renderDebug = function () {
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
var debugCell = self.cells[i][j].debugCell;
if (debugCell) {
debugCell.render(self.cells[i][j]);
}
}
}
};
self.updateEnemy = function (enemy) {
var cell = grid.getCell(enemy.cellX, enemy.cellY);
if (cell.type == 3) {
return true;
}
if (enemy.isFlying && enemy.shadow) {
enemy.shadow.x = enemy.x + 20; // Match enemy x-position + offset
enemy.shadow.y = enemy.y + 20; // Match enemy y-position + offset
// Match shadow rotation with enemy rotation
if (enemy.children[0] && enemy.shadow.children[0]) {
enemy.shadow.children[0].rotation = enemy.children[0].rotation;
}
}
// Check if the enemy has reached the entry area (y position is at least 5)
var hasReachedEntryArea = enemy.currentCellY >= 4;
// If enemy hasn't reached the entry area yet, just move down vertically
if (!hasReachedEntryArea) {
// Move directly downward
enemy.currentCellY += enemy.speed;
// Rotate enemy graphic to face downward (PI/2 radians = 90 degrees)
var angle = Math.PI / 2;
if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
enemy.children[0].targetRotation = angle;
enemy.children[0].rotation = angle;
} else if (enemy.children[0]) {
if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) {
tween.stop(enemy.children[0], {
rotation: true
});
// Calculate the shortest angle to rotate
var currentRotation = enemy.children[0].rotation;
var angleDiff = angle - currentRotation;
// Normalize angle difference to -PI to PI range for shortest path
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
// Set target rotation and animate to it
enemy.children[0].targetRotation = angle;
tween(enemy.children[0], {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
// Update enemy's position
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
// If enemy has now reached the entry area, update cell coordinates
if (enemy.currentCellY >= 4) {
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
}
return false;
}
// After reaching entry area, handle flying enemies differently
if (enemy.isFlying) {
// Flying enemies head straight to the closest goal
if (!enemy.flyingTarget) {
// Set flying target to the closest goal
enemy.flyingTarget = self.goals[0];
// Find closest goal if there are multiple
if (self.goals.length > 1) {
var closestDist = Infinity;
for (var i = 0; i < self.goals.length; i++) {
var goal = self.goals[i];
var dx = goal.x - enemy.cellX;
var dy = goal.y - enemy.cellY;
var dist = dx * dx + dy * dy;
if (dist < closestDist) {
closestDist = dist;
enemy.flyingTarget = goal;
}
}
}
}
// Move directly toward the goal
var ox = enemy.flyingTarget.x - enemy.currentCellX;
var oy = enemy.flyingTarget.y - enemy.currentCellY;
var dist = Math.sqrt(ox * ox + oy * oy);
if (dist < enemy.speed) {
// Reached the goal
return true;
}
var angle = Math.atan2(oy, ox);
// Rotate enemy graphic to match movement direction
if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
enemy.children[0].targetRotation = angle;
enemy.children[0].rotation = angle;
} else if (enemy.children[0]) {
if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) {
tween.stop(enemy.children[0], {
rotation: true
});
// Calculate the shortest angle to rotate
var currentRotation = enemy.children[0].rotation;
var angleDiff = angle - currentRotation;
// Normalize angle difference to -PI to PI range for shortest path
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
// Set target rotation and animate to it
enemy.children[0].targetRotation = angle;
tween(enemy.children[0], {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
// Update the cell position to track where the flying enemy is
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentCellX += Math.cos(angle) * enemy.speed;
enemy.currentCellY += Math.sin(angle) * enemy.speed;
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
// Update shadow position if this is a flying enemy
return false;
}
// Handle normal pathfinding enemies
if (!enemy.currentTarget) {
enemy.currentTarget = cell.targets[0];
}
if (enemy.currentTarget) {
if (cell.score < enemy.currentTarget.score) {
enemy.currentTarget = cell;
}
var ox = enemy.currentTarget.x - enemy.currentCellX;
var oy = enemy.currentTarget.y - enemy.currentCellY;
var dist = Math.sqrt(ox * ox + oy * oy);
if (dist < enemy.speed) {
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentTarget = undefined;
return;
}
var angle = Math.atan2(oy, ox);
enemy.currentCellX += Math.cos(angle) * enemy.speed;
enemy.currentCellY += Math.sin(angle) * enemy.speed;
}
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
};
});
var HotPotatoInstructionsScreen = Container.expand(function () {
var self = Container.call(this);
// Pause the game when instructions screen is shown
self.gamePaused = true;
// Hide UI labels when instructions screen is shown
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Hot Potato instruction image at the bottom
var instructionsImage = self.attachAsset('hotpotato_instructions_image', {
anchorX: 0.5,
anchorY: 1.0
});
instructionsImage.x = 0;
instructionsImage.y = 2732 / 2 - 100; // Position higher up
instructionsImage.width = 800;
instructionsImage.height = 800;
// Instructions text label in the center of the screen
var instructionsTextShadow = new Text2("Welcome to Hot Potato Challenge!\nThings are heating up!\nOnly CapySniper, CapyBomb and CapyDemolition towers are available.\nPlace towers on the blue floor area.\nBombs launch upward!\nReceive 250 gold every time you beat a wave\n", {
size: 55,
fill: 0x000000,
weight: 800,
align: 'center'
});
instructionsTextShadow.anchor.set(0.5, 0.5);
instructionsTextShadow.x = 4; // Shadow offset
instructionsTextShadow.y = 4; // Centered vertically with shadow offset
// Set maximum width to ensure text wraps properly
instructionsTextShadow.wordWrap = true;
instructionsTextShadow.wordWrapWidth = 1800;
self.addChild(instructionsTextShadow);
var instructionsText = new Text2("Welcome to Hot Potato Challenge!\nThings are heating up!\nOnly CapySniper, CapyBomb and CapyDemolition towers are available.\nPlace towers on the blue floor area.\nBombs launch upward!\nReceive 250 gold every time you beat a wave\n", {
size: 55,
fill: 0xFFFFFF,
weight: 800,
align: 'center'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 0;
instructionsText.y = 0; // Centered vertically in screen
// Set maximum width to ensure text wraps properly
instructionsText.wordWrap = true;
instructionsText.wordWrapWidth = 1800;
self.addChild(instructionsText);
// Continue button at the top
var continueButton = new Container();
var buttonBg = continueButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 600;
buttonBg.height = 150;
buttonBg.tint = 0xFF4400;
var buttonTextShadow = new Text2("Continue", {
size: 80,
fill: 0x000000,
weight: 800
});
buttonTextShadow.anchor.set(0.5, 0.5);
buttonTextShadow.x = 2;
buttonTextShadow.y = 2;
continueButton.addChild(buttonTextShadow);
var buttonText = new Text2("Continue", {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
continueButton.addChild(buttonText);
continueButton.x = 0;
continueButton.y = -2732 / 2 + 300; // Position at top
self.addChild(continueButton);
// Continue button handler - close the screen
continueButton.down = function (x, y, obj) {
// Unpause the game
self.gamePaused = false;
// Show UI labels when continue button is pressed
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
// Remove instructions screen
self.destroy();
};
return self;
});
var Instructions2Screen = Container.expand(function () {
var self = Container.call(this);
// Pause the game when instructions2 screen is shown
self.gamePaused = true;
// Hide UI labels when instructions2 screen is shown
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Instructions2 image at the bottom using separate asset
var instructionsImage = self.attachAsset('instructions2_image', {
anchorX: 0.5,
anchorY: 1.0
});
instructionsImage.x = 0;
instructionsImage.y = 2732 / 2 - 100; // Position higher up
instructionsImage.width = 800;
instructionsImage.height = 800;
// Instructions text label in the center of the screen
var instructionsTextShadow = new Text2("Hi I´m the CapyQueen!\nGreat job defending the castle!\nYou've learned the basics of placing towers.\nYou can skip waves using the next wave button.\nTake this Gold to deploy the CapyQueen on the field \nand test the skill of the kings.\n", {
size: 55,
fill: 0x000000,
weight: 800,
align: 'center'
});
instructionsTextShadow.anchor.set(0.5, 0.5);
instructionsTextShadow.x = 4; // Shadow offset
instructionsTextShadow.y = 4; // Centered vertically with shadow offset
// Set maximum width to ensure text wraps properly
instructionsTextShadow.wordWrap = true;
instructionsTextShadow.wordWrapWidth = 1800;
self.addChild(instructionsTextShadow);
var instructionsText = new Text2("Hi I´m the CapyQueen!\nGreat job defending the castle!\nYou've learned the basics of placing towers.\nYou can skip waves using the next wave button.\nTake this Gold to deploy the CapyQueen on the field \nand test the skill of the kings.\n", {
size: 55,
fill: 0xFFFFFF,
weight: 800,
align: 'center'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 0;
instructionsText.y = 0; // Centered vertically in screen
// Set maximum width to ensure text wraps properly
instructionsText.wordWrap = true;
instructionsText.wordWrapWidth = 1800;
self.addChild(instructionsText);
// Continue button at the top
var continueButton = new Container();
var buttonBg = continueButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 600;
buttonBg.height = 150;
buttonBg.tint = 0x00AA00;
var buttonTextShadow = new Text2("Continue", {
size: 80,
fill: 0x000000,
weight: 800
});
buttonTextShadow.anchor.set(0.5, 0.5);
buttonTextShadow.x = 2;
buttonTextShadow.y = 2;
continueButton.addChild(buttonTextShadow);
var buttonText = new Text2("Continue", {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
continueButton.addChild(buttonText);
continueButton.x = 0;
continueButton.y = -2732 / 2 + 300; // Position at top
self.addChild(continueButton);
// Continue button handler - unpause game and close the screen
continueButton.down = function (x, y, obj) {
// Unpause the game
self.gamePaused = false;
// Add 200 gold when continuing from Instructions2Screen
setGold(gold + 200);
// Show UI labels when continue button is pressed
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
// In tutorial mode, advance to wave 4
if (tutorialMode) {
currentWave = 4;
waveTimer = 0; // Reset wave timer
waveInProgress = true;
waveSpawned = false;
// Create new wave indicator for wave 4
if (waveIndicator) {
waveIndicator.createCurrentWaveIndicator();
}
// Show Next Wave button in tutorial mode
if (nextWaveButton) {
nextWaveButton.enabled = true;
nextWaveButton.visible = true;
}
// Remove notification for wave 4 in tutorial mode
}
// Remove instructions2 screen
self.destroy();
};
return self;
});
var Instructions3Screen = Container.expand(function () {
var self = Container.call(this);
// Pause the game when instructions3 screen is shown
self.gamePaused = true;
// Hide UI labels when instructions3 screen is shown
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Instructions3 image at the bottom using independent asset
var instructionsImage = self.attachAsset('instructions3_image', {
anchorX: 0.5,
anchorY: 1.0
});
instructionsImage.x = 0;
instructionsImage.y = 2732 / 2 - 100; // Position higher up
instructionsImage.width = 800;
instructionsImage.height = 800;
// Instructions text label in the center of the screen
var instructionsTextShadow = new Text2("Well done!\nThe mighty Boss approaches the castle!\nTake this gold to deploy the CapyKing.\nYou can also use the gold you earn to \nupgrade troops in the field.\n", {
size: 55,
fill: 0x000000,
weight: 800,
align: 'center'
});
instructionsTextShadow.anchor.set(0.5, 0.5);
instructionsTextShadow.x = 4; // Shadow offset
instructionsTextShadow.y = 4; // Centered vertically with shadow offset
// Set maximum width to ensure text wraps properly
instructionsTextShadow.wordWrap = true;
instructionsTextShadow.wordWrapWidth = 1800;
self.addChild(instructionsTextShadow);
var instructionsText = new Text2("Well done!\nThe mighty Boss approaches the castle!\nTake this gold to deploy the CapyKing.\nYou can also use the gold you earn to \nupgrade troops in the field.\n", {
size: 55,
fill: 0xFFFFFF,
weight: 800,
align: 'center'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 0;
instructionsText.y = 0; // Centered vertically in screen
// Set maximum width to ensure text wraps properly
instructionsText.wordWrap = true;
instructionsText.wordWrapWidth = 1800;
self.addChild(instructionsText);
// Continue button at the top
var continueButton = new Container();
var buttonBg = continueButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 600;
buttonBg.height = 150;
buttonBg.tint = 0x00AA00;
var buttonTextShadow = new Text2("Continue", {
size: 80,
fill: 0x000000,
weight: 800
});
buttonTextShadow.anchor.set(0.5, 0.5);
buttonTextShadow.x = 2;
buttonTextShadow.y = 2;
continueButton.addChild(buttonTextShadow);
var buttonText = new Text2("Continue", {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
continueButton.addChild(buttonText);
continueButton.x = 0;
continueButton.y = -2732 / 2 + 300; // Position at top
self.addChild(continueButton);
// Continue button handler - unpause game and close the screen
continueButton.down = function (x, y, obj) {
// Unpause the game
self.gamePaused = false;
// Add 250 gold when continuing from Instructions3Screen
setGold(gold + 250);
// Show UI labels when continue button is pressed
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
// Remove instructions3 screen
self.destroy();
};
return self;
});
var KingsGoldInstructionsScreen = Container.expand(function () {
var self = Container.call(this);
// Pause the game when instructions screen is shown
self.gamePaused = true;
// Hide UI labels when instructions screen is shown
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// King's Gold instruction image at the bottom
var instructionsImage = self.attachAsset('kings_gold_instructions_image', {
anchorX: 0.5,
anchorY: 1.0
});
instructionsImage.x = 0;
instructionsImage.y = 2732 / 2 - 100; // Position higher up
instructionsImage.width = 800;
instructionsImage.height = 800;
// Instructions text label in the center of the screen
var instructionsTextShadow = new Text2("Welcome to King's Gold Challenge!\nEnter the mighty Great Hall!\nOnly CapyKing, CapyFreeze, and CapyPrince towers are available.\nTouch gold coins dropped by enemies to collect them.\nGold coins grant 50 gold each!\nGood luck, defender!\n", {
size: 55,
fill: 0x000000,
weight: 800,
align: 'center'
});
instructionsTextShadow.anchor.set(0.5, 0.5);
instructionsTextShadow.x = 4; // Shadow offset
instructionsTextShadow.y = 4; // Centered vertically with shadow offset
// Set maximum width to ensure text wraps properly
instructionsTextShadow.wordWrap = true;
instructionsTextShadow.wordWrapWidth = 1800;
self.addChild(instructionsTextShadow);
var instructionsText = new Text2("Welcome to King's Gold Challenge!\nEnter the mighty Great Hall!\nOnly CapyKing, CapyFreeze, and CapyPrince towers are available.\nTouch gold coins dropped by enemies to collect them.\nGold coins grant 50 gold each!\nGood luck, defender!\n", {
size: 55,
fill: 0xFFFFFF,
weight: 800,
align: 'center'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 0;
instructionsText.y = 0; // Centered vertically in screen
// Set maximum width to ensure text wraps properly
instructionsText.wordWrap = true;
instructionsText.wordWrapWidth = 1800;
self.addChild(instructionsText);
// Continue button at the top
var continueButton = new Container();
var buttonBg = continueButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 600;
buttonBg.height = 150;
buttonBg.tint = 0xFFD700;
var buttonTextShadow = new Text2("Continue", {
size: 80,
fill: 0x000000,
weight: 800
});
buttonTextShadow.anchor.set(0.5, 0.5);
buttonTextShadow.x = 2;
buttonTextShadow.y = 2;
continueButton.addChild(buttonTextShadow);
var buttonText = new Text2("Continue", {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
continueButton.addChild(buttonText);
continueButton.x = 0;
continueButton.y = -2732 / 2 + 300; // Position at top
self.addChild(continueButton);
// Continue button handler - close the screen
continueButton.down = function (x, y, obj) {
// Unpause the game
self.gamePaused = false;
// Show UI labels when continue button is pressed
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
// Remove instructions screen
self.destroy();
};
return self;
});
var LevelConfirmation = Container.expand(function (levelData, levelSelector) {
var self = Container.call(this);
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.9;
// Confirmation box
var confirmBox = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
confirmBox.width = 800;
confirmBox.height = 400;
confirmBox.tint = 0x444444;
// Title shadow
var titleShadow = new Text2("Confirm Level Selection", {
size: 70,
fill: 0x000000,
weight: 800
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -120;
self.addChild(titleShadow);
// Title
var title = new Text2("Confirm Level Selection", {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
title.anchor.set(0.5, 0.5);
title.y = -120;
self.addChild(title);
// Level name shadow
var levelNameShadow = new Text2("Level: " + levelData.name, {
size: 60,
fill: 0x000000,
weight: 600
});
levelNameShadow.anchor.set(0.5, 0.5);
levelNameShadow.x = 4;
levelNameShadow.y = -30;
self.addChild(levelNameShadow);
// Level name
var levelName = new Text2("Level: " + levelData.name, {
size: 60,
fill: levelData.color,
weight: 600
});
levelName.anchor.set(0.5, 0.5);
levelName.y = -30;
self.addChild(levelName);
// Description shadow
var descShadow = new Text2(levelData.description, {
size: 45,
fill: 0x000000,
weight: 400
});
descShadow.anchor.set(0.5, 0.5);
descShadow.x = 2;
descShadow.y = 30;
self.addChild(descShadow);
// Description
var desc = new Text2(levelData.description, {
size: 45,
fill: 0xFFFFFF,
weight: 400
});
desc.anchor.set(0.5, 0.5);
desc.y = 28;
self.addChild(desc);
// Confirm button
var confirmButton = new Container();
var confirmBg = confirmButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
confirmBg.width = 250;
confirmBg.height = 100;
confirmBg.tint = 0x00AA00;
var confirmTextShadow = new Text2("Confirm", {
size: 50,
fill: 0x000000,
weight: 800
});
confirmTextShadow.anchor.set(0.5, 0.5);
confirmTextShadow.x = 2;
confirmTextShadow.y = 2;
confirmButton.addChild(confirmTextShadow);
var confirmText = new Text2("Confirm", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
confirmText.anchor.set(0.5, 0.5);
confirmButton.addChild(confirmText);
confirmButton.x = -150;
confirmButton.y = 120;
self.addChild(confirmButton);
// Cancel button
var cancelButton = new Container();
var cancelBg = cancelButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
cancelBg.width = 250;
cancelBg.height = 100;
cancelBg.tint = 0xCC0000;
var cancelTextShadow = new Text2("Cancel", {
size: 50,
fill: 0x000000,
weight: 800
});
cancelTextShadow.anchor.set(0.5, 0.5);
cancelTextShadow.x = 2;
cancelTextShadow.y = 2;
cancelButton.addChild(cancelTextShadow);
var cancelText = new Text2("Cancel", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
cancelText.anchor.set(0.5, 0.5);
cancelButton.addChild(cancelText);
cancelButton.x = 150;
cancelButton.y = 120;
self.addChild(cancelButton);
// Confirm button handler
confirmButton.down = function (x, y, obj) {
// Store the selected level globally
selectedLevel = levelData.name.toLowerCase().replace(' ', '');
// Clean up existing lobby square images first
for (var i = 0; i < lobbySquareImages.length; i++) {
towerLayer.removeChild(lobbySquareImages[i]);
}
lobbySquareImages = [];
// Regenerate grid for Pool level to add rectangular pool area
if (selectedLevel === 'pool') {
// Remove existing grid
debugLayer.removeChild(grid);
// Create new grid with pool configuration
grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * 4;
// Set selectedLevel based on challenge
if (challengeName === "Queen's Walk") {
selectedLevel = 'garden'; // Copy garden level
} else if (challengeName === "King's Gold") {
selectedLevel = 'greathall'; // Copy great hall level
}
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
}
// Create 3 wall cell squares for Lobby level
if (selectedLevel === 'lobby') {
// Remove existing grid
debugLayer.removeChild(grid);
// Create new grid with lobby configuration
grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * 4;
// Define positions for the 3 wall squares (4x4 each)
var wallSquares = [{
startX: 4,
startY: 10
},
// Left square
{
startX: 10,
startY: 16
},
// Center square
{
startX: 16,
startY: 10
} // Right square
];
// Create the wall squares
for (var s = 0; s < wallSquares.length; s++) {
var square = wallSquares[s];
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
var cell = grid.getCell(square.startX + i, square.startY + j);
if (cell && cell.type === 0) {
// Only modify walkable cells
cell.type = 1; // Set to wall type
}
}
}
}
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
// Create images over the 4x4 wall squares
var squareAssets = ['lobby_square_1', 'lobby_square_2', 'lobby_square_3'];
for (var s = 0; s < wallSquares.length; s++) {
var square = wallSquares[s];
var squareImage = game.attachAsset(squareAssets[s], {
anchorX: 0.5,
anchorY: 0.5
});
// Position image over the center of the 4x4 square, moved half a cell left and up
var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE;
var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE;
squareImage.x = squareCenterX - CELL_SIZE / 2;
squareImage.y = squareCenterY - CELL_SIZE / 2;
// Add to tower layer so it appears above the grid cells
towerLayer.addChild(squareImage);
lobbySquareImages.push(squareImage);
}
}
// Create 2 wall cell squares for Great Hall level at the bottom
if (selectedLevel === 'greathall') {
// Remove existing grid
debugLayer.removeChild(grid);
// Create new grid with great hall configuration
grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * 4;
// Define positions for the 2 wall squares (4x4 each) at the bottom
var gridHeight = 29 + 6;
var wallSquares = [{
startX: 4,
startY: gridHeight - 10 // Bottom left square, moved up 2 cells
}, {
startX: 16,
startY: gridHeight - 10 // Bottom right square, moved up 2 cells
}];
// Create the wall squares
for (var s = 0; s < wallSquares.length; s++) {
var square = wallSquares[s];
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
var cell = grid.getCell(square.startX + i, square.startY + j);
if (cell && cell.type === 0) {
// Only modify walkable cells
cell.type = 1; // Set to wall type
}
}
}
}
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
// Create images over the 4x4 wall squares using separate assets
var squareAssets = ['greathall_square_1', 'greathall_square_2'];
for (var s = 0; s < wallSquares.length; s++) {
var square = wallSquares[s];
var squareImage = game.attachAsset(squareAssets[s], {
anchorX: 0.5,
anchorY: 0.5
});
// Position image over the center of the 4x4 square, moved half a cell left and up
var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE;
var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE;
squareImage.x = squareCenterX - CELL_SIZE / 2;
squareImage.y = squareCenterY - CELL_SIZE / 2;
// Add to tower layer so it appears above the grid cells
towerLayer.addChild(squareImage);
lobbySquareImages.push(squareImage);
}
}
// Update background image immediately when level is selected
var newBackgroundAssetName = getBackgroundAssetName();
game.removeChild(backgroundImage);
backgroundImage = game.attachAsset(newBackgroundAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
backgroundImage.x = 2048 / 2;
backgroundImage.y = 2732 / 2;
backgroundImage.width = 2048;
backgroundImage.height = 2732;
// Add background to the back of the game
game.addChildAt(backgroundImage, 0);
// Add or remove pool rectangle image based on selected level
if (poolRectangleImage) {
game.removeChild(poolRectangleImage);
poolRectangleImage = null;
}
if (selectedLevel === 'pool') {
poolRectangleImage = game.attachAsset('pool_rectangle', {
anchorX: 0.5,
anchorY: 0.5
});
// Position over the rectangular pool area (center of grid + pool area center offset)
var gridWidth = 24;
var gridHeight = 35;
var centerX = Math.floor(gridWidth / 2); // Center column (12)
var centerY = Math.floor(gridHeight / 2); // Center row
var rectWidth = 6; // Rectangle width (6 cells)
var rectHeight = 16; // Rectangle height (16 cells)
var rectLeft = centerX - Math.floor(rectWidth / 2);
var rectTop = centerY - Math.floor(rectHeight / 2);
// Calculate the actual center of the rectangular pool area in pixels
var poolCenterX = grid.x + (rectLeft + rectWidth / 2) * CELL_SIZE;
var poolCenterY = grid.y + (rectTop + rectHeight / 2) * CELL_SIZE;
poolRectangleImage.x = poolCenterX - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2;
poolRectangleImage.y = poolCenterY - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2;
// Keep pool rectangle image at normal tint (not gray) so it appears above the gray cells
poolRectangleImage.tint = 0xFFFFFF; // Keep original white/normal tint
// Add to tower layer so it appears above the grid cells
towerLayer.addChild(poolRectangleImage);
}
// Trigger grid debug render to update cell assets immediately
grid.renderDebug();
// Show selection feedback
var notification = game.addChild(new Notification("Level set to " + levelData.name + "!"));
notification.x = 2048 / 2; //{i2}
notification.y = 2732 / 2;
// Remove confirmation dialog and level selector
self.destroy();
levelSelector.destroy();
// Show difficulty selector
var difficultySelector = new DifficultySelector();
difficultySelector.x = 2048 / 2;
difficultySelector.y = 2732 / 2;
game.addChild(difficultySelector);
};
// Cancel button handler
cancelButton.down = function (x, y, obj) {
// Unblock level selector buttons
levelSelector.buttonsBlocked = false;
// Just close the confirmation dialog, return to level selector
self.destroy();
};
return self;
});
var LevelSelector = Container.expand(function () {
var self = Container.call(this);
// Track if buttons should be blocked
self.buttonsBlocked = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Title
var titleShadow = new Text2("Select Level", {
size: 120,
fill: 0x000000,
weight: 800
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -900;
self.addChild(titleShadow);
var title = new Text2("Select Level", {
size: 120,
fill: 0xFFFFFF,
weight: 800
});
title.anchor.set(0.5, 0.5);
title.y = -900;
self.addChild(title);
// Level buttons
var levels = [{
name: "Garden",
color: 0x00AA00,
description: "A peaceful garden setting"
}, {
name: "Pool",
color: 0x0088FF,
description: "Battle by the swimming pool"
}, {
name: "Lobby",
color: 0xFFAA00,
description: "Defend the castle lobby"
}, {
name: "Great Hall",
color: 0xAA00AA,
description: "Epic battles in the great hall"
}];
var buttonSpacing = 300;
var startY = -200;
// Store button references for blocking/unblocking
self.buttons = [];
for (var i = 0; i < levels.length; i++) {
var level = levels[i];
var button = new Container();
var buttonBg = button.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 600;
buttonBg.height = 200;
buttonBg.tint = level.color;
// Button title shadow
var buttonTitleShadow = new Text2(level.name, {
size: 80,
fill: 0x000000,
weight: 800
});
buttonTitleShadow.anchor.set(0.5, 0.5);
buttonTitleShadow.x = 4;
buttonTitleShadow.y = -20;
button.addChild(buttonTitleShadow);
// Button title
var buttonTitle = new Text2(level.name, {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
buttonTitle.anchor.set(0.5, 0.5);
buttonTitle.y = -20;
button.addChild(buttonTitle);
// Description shadow
var descShadow = new Text2(level.description, {
size: 40,
fill: 0x000000,
weight: 400
});
descShadow.anchor.set(0.5, 0.5);
descShadow.x = 2;
descShadow.y = 30;
button.addChild(descShadow);
// Description
var desc = new Text2(level.description, {
size: 40,
fill: 0xFFFFFF,
weight: 400
});
desc.anchor.set(0.5, 0.5);
desc.y = 28;
button.addChild(desc);
button.y = startY + i * buttonSpacing;
button.level = level;
self.addChild(button);
self.buttons.push(button);
// Button click handler
button.down = function (x, y, obj) {
// Don't handle clicks if buttons are blocked
if (self.buttonsBlocked) {
return;
}
var levelData = this.level;
// Block buttons when showing confirmation window
self.buttonsBlocked = true;
// Show confirmation window
var levelConfirmation = new LevelConfirmation(levelData, self);
levelConfirmation.x = 2048 / 2;
levelConfirmation.y = 2732 / 2;
game.addChild(levelConfirmation);
};
}
// Back button
var backButton = new Container();
var backBg = backButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
backBg.width = 400;
backBg.height = 100;
backBg.tint = 0xCC0000;
var backTextShadow = new Text2("Back", {
size: 60,
fill: 0x000000,
weight: 800
});
backTextShadow.anchor.set(0.5, 0.5);
backTextShadow.x = 2;
backTextShadow.y = 2;
backButton.addChild(backTextShadow);
var backText = new Text2("Back", {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
backText.anchor.set(0.5, 0.5);
backButton.addChild(backText);
backButton.x = 0;
backButton.y = 1000;
self.addChild(backButton);
// Back button handler - return to mode selector
backButton.down = function (x, y, obj) {
// Don't handle clicks if buttons are blocked
if (self.buttonsBlocked) {
return;
}
// Remove level selector
self.destroy();
// Show mode selector
var modeSelector = new ModeSelector();
modeSelector.x = 2048 / 2;
modeSelector.y = 2732 / 2;
game.addChild(modeSelector);
};
return self;
});
var ModeSelector = Container.expand(function () {
var self = Container.call(this);
// Hide UI labels when ModeSelector is shown
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Main container for content
var contentContainer = new Container();
self.addChild(contentContainer);
// Title shadow
var titleShadow = new Text2("Select Mode", {
size: 140,
fill: 0x000000,
weight: 800
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -300 + 4;
contentContainer.addChild(titleShadow);
// Title
var title = new Text2("Select Mode", {
size: 140,
fill: 0xFFD700,
weight: 800
});
title.anchor.set(0.5, 0.5);
title.y = -300;
contentContainer.addChild(title);
// Tutorial button
var tutorialButton = new Container();
var tutorialBg = tutorialButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
tutorialBg.width = 600;
tutorialBg.height = 150;
tutorialBg.tint = 0x00AA00;
var tutorialTextShadow = new Text2("Tutorial", {
size: 70,
fill: 0x000000,
weight: 800
});
tutorialTextShadow.anchor.set(0.5, 0.5);
tutorialTextShadow.x = 2;
tutorialTextShadow.y = 2;
tutorialButton.addChild(tutorialTextShadow);
var tutorialText = new Text2("Tutorial", {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
tutorialText.anchor.set(0.5, 0.5);
tutorialButton.addChild(tutorialText);
tutorialButton.x = 0;
tutorialButton.y = 50;
contentContainer.addChild(tutorialButton);
// Normal Mode button
var normalButton = new Container();
var normalBg = normalButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
normalBg.width = 600;
normalBg.height = 150;
normalBg.tint = 0x0088FF;
var normalTextShadow = new Text2("Normal Mode", {
size: 70,
fill: 0x000000,
weight: 800
});
normalTextShadow.anchor.set(0.5, 0.5);
normalTextShadow.x = 2;
normalTextShadow.y = 2;
normalButton.addChild(normalTextShadow);
var normalText = new Text2("Normal Mode", {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
normalText.anchor.set(0.5, 0.5);
normalButton.addChild(normalText);
normalButton.x = 0;
normalButton.y = 220;
contentContainer.addChild(normalButton);
// Capy Challenges button
var challengesButton = new Container();
var challengesBg = challengesButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
challengesBg.width = 600;
challengesBg.height = 150;
challengesBg.tint = 0xFF6600;
var challengesTextShadow = new Text2("Capy Challenges", {
size: 70,
fill: 0x000000,
weight: 800
});
challengesTextShadow.anchor.set(0.5, 0.5);
challengesTextShadow.x = 2;
challengesTextShadow.y = 2;
challengesButton.addChild(challengesTextShadow);
var challengesText = new Text2("Capy Challenges", {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
challengesText.anchor.set(0.5, 0.5);
challengesButton.addChild(challengesText);
challengesButton.x = 0;
challengesButton.y = 390;
contentContainer.addChild(challengesButton);
// Capybaras button
var capybarasButton = new Container();
var capybarasBg = capybarasButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
capybarasBg.width = 600;
capybarasBg.height = 150;
capybarasBg.tint = 0x8B4513;
var capybarasTextShadow = new Text2("Capybaras", {
size: 70,
fill: 0x000000,
weight: 800
});
capybarasTextShadow.anchor.set(0.5, 0.5);
capybarasTextShadow.x = 2;
capybarasTextShadow.y = 2;
capybarasButton.addChild(capybarasTextShadow);
var capybarasText = new Text2("Capybaras", {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
capybarasText.anchor.set(0.5, 0.5);
capybarasButton.addChild(capybarasText);
capybarasButton.x = 0;
capybarasButton.y = 560;
contentContainer.addChild(capybarasButton);
// Tutorial button handler - go to tutorial
tutorialButton.down = function (x, y, obj) {
// Block start game button by checking if the click is targeting it
var startGameButtonClicked = false;
// Check if waveIndicator exists and has a start marker
if (waveIndicator && waveIndicator.waveMarkers && waveIndicator.waveMarkers.length > 0) {
var startMarker = waveIndicator.waveMarkers[0];
if (startMarker && !waveIndicator.gameStarted) {
// Get start marker bounds (assuming it's centered at waveIndicator position)
var startMarkerX = waveIndicator.x + startMarker.x;
var startMarkerY = waveIndicator.y + startMarker.y;
var startMarkerWidth = 400; // blockWidth from WaveIndicator
var startMarkerHeight = 120; // blockHeight from WaveIndicator
var startMarkerLeft = startMarkerX - startMarkerWidth / 2;
var startMarkerRight = startMarkerX + startMarkerWidth / 2;
var startMarkerTop = startMarkerY - startMarkerHeight / 2;
var startMarkerBottom = startMarkerY + startMarkerHeight / 2;
// Check if click coordinates overlap with start game button
if (x >= startMarkerLeft && x <= startMarkerRight && y >= startMarkerTop && y <= startMarkerBottom) {
startGameButtonClicked = true;
}
}
}
// Block start game button interaction
if (startGameButtonClicked) {
return; // Prevent start game button from working
}
// Set tutorial mode variables
tutorialMode = true; // Enable tutorial mode
selectedLevel = 'garden'; // Tutorial uses garden level
difficultyMultiplier = 0.5; // Make tutorial easier
totalWaves = 10; // Limit tutorial to 10 waves
nextWaveTime = 12000 / 2 * 0.25 * 0.7; // Reduce wave time by 75% + 30% for tutorial
setGold(65); // Tutorial starting gold
// Regenerate grid with tutorial configuration and activate wall cells
debugLayer.removeChild(grid);
grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * 4;
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
// Update background immediately to tutorial assets
var newBackgroundAssetName = getBackgroundAssetName();
game.removeChild(backgroundImage);
backgroundImage = game.attachAsset(newBackgroundAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
backgroundImage.x = 2048 / 2;
backgroundImage.y = 2732 / 2;
backgroundImage.width = 2048;
backgroundImage.height = 2732;
// Add background to the back of the game
game.addChildAt(backgroundImage, 0);
// Show start game button after tutorial selection
if (waveIndicator) {
waveIndicator.showStartButton();
}
// Show tutorial intro screen instead of notification
var tutorialIntroScreen = new TutorialIntroScreen();
tutorialIntroScreen.x = 2048 / 2;
tutorialIntroScreen.y = 2732 / 2;
game.addChild(tutorialIntroScreen);
// Remove mode selector
self.destroy();
};
// Normal Mode button handler - go to level selection
normalButton.down = function (x, y, obj) {
// Remove mode selector
self.destroy();
// Show level selector
var levelSelector = new LevelSelector();
levelSelector.x = 2048 / 2;
levelSelector.y = 2732 / 2;
game.addChild(levelSelector);
};
// Capy Challenges button handler - go to empty challenges screen
challengesButton.down = function (x, y, obj) {
// Remove mode selector
self.destroy();
// Show empty Capy Challenges screen
var capyChallengesScreen = new CapyChallengesScreen();
capyChallengesScreen.x = 2048 / 2;
capyChallengesScreen.y = 2732 / 2;
game.addChild(capyChallengesScreen);
};
// Capybaras button handler - show tower information screen
capybarasButton.down = function (x, y, obj) {
// Remove mode selector
self.destroy();
// Show Capybaras information screen
var capybarasInfoScreen = new CapybarasInfoScreen();
capybarasInfoScreen.x = 2048 / 2;
capybarasInfoScreen.y = 2732 / 2;
game.addChild(capybarasInfoScreen);
};
return self;
});
var NextWaveButton = Container.expand(function () {
var self = Container.call(this);
var buttonBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBackground.width = 300;
buttonBackground.height = 100;
buttonBackground.tint = 0x0088FF;
var buttonText = new Text2("Next Wave", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.enabled = false;
self.visible = false;
self.update = function () {
// In tutorial mode, only show button after instructions2 continue is pressed
if (tutorialMode && !self.enabled) {
self.visible = false;
buttonBackground.tint = 0x888888;
self.alpha = 0.7;
return;
}
if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) {
self.enabled = true;
self.visible = true;
buttonBackground.tint = 0x0088FF;
self.alpha = 1;
} else {
self.enabled = false;
self.visible = false;
buttonBackground.tint = 0x888888;
self.alpha = 0.7;
}
};
self.down = function () {
// Block next wave button when ModeSelector or tutorial instruction screens are active
var modeSelectorActive = game.children.some(function (child) {
return child instanceof ModeSelector;
});
var instructions2Active = game.children.some(function (child) {
return child instanceof Instructions2Screen;
});
var instructions3Active = game.children.some(function (child) {
return child instanceof Instructions3Screen;
});
if (modeSelectorActive || instructions2Active || instructions3Active) {
return;
}
if (!self.enabled) {
return;
}
if (waveIndicator.gameStarted && currentWave < totalWaves) {
currentWave++; // Increment to the next wave directly
waveTimer = 0; // Reset wave timer
waveInProgress = true;
waveSpawned = false;
// Create new wave indicator for the manually advanced wave
waveIndicator.createCurrentWaveIndicator();
// Get the type of the current wave (which is now the next wave)
var waveType = waveIndicator.getWaveTypeName(currentWave);
var enemyCount = waveIndicator.getEnemyCount(currentWave);
// Only show notification if not in tutorial mode
if (!tutorialMode) {
var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
}
};
return self;
});
var Notification = Container.expand(function (message) {
var self = Container.call(this);
var notificationGraphics = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
var notificationText = new Text2(message, {
size: 50,
fill: 0x000000,
weight: 800
});
notificationText.anchor.set(0.5, 0.5);
notificationGraphics.width = notificationText.width + 30;
self.addChild(notificationText);
self.alpha = 1;
var fadeOutTime = 120;
self.update = function () {
if (fadeOutTime > 0) {
fadeOutTime--;
self.alpha = Math.min(fadeOutTime / 120 * 2, 1);
} else {
self.destroy();
}
};
return self;
});
var ProjectileTower = Container.expand(function (type, x, y) {
var self = Container.call(this);
self.towerType = type; // 'bomb' or 'demolition'
self.x = x;
self.y = y;
self.launched = false;
self.targetEnemy = null;
// Create tower graphics
var assetName = 'tower_' + self.towerType;
var towerGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Find target enemy when launched
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
return closestEnemy;
};
// Launch the tower toward target enemy
self.launch = function () {
if (self.launched) return;
self.launched = true;
// In Hot Potato challenge, move straight up in same column
if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) {
// Launch straight up until hitting an enemy in the same column
var startX = self.x; // Keep same X position (same column)
// Start rotation animation - continuous right rotation
tween(self, {
rotation: self.rotation + Math.PI * 8 // Rotate right (8 full rotations)
}, {
duration: 5000,
// 5 seconds duration
easing: tween.linear
});
var checkInterval = LK.setInterval(function () {
// Move upward
self.y -= 8; // Move up 8 pixels per frame
// Check for collision with enemies in same column
var hitEnemy = null;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Check if enemy is in roughly the same column (within cell width)
var dx = Math.abs(enemy.x - startX);
var dy = Math.abs(enemy.y - self.y);
if (dx <= CELL_SIZE && dy <= CELL_SIZE / 2) {
hitEnemy = enemy;
break;
}
}
// If hit enemy or moved too far up, explode
if (hitEnemy || self.y < -100) {
LK.clearInterval(checkInterval);
// Stop rotation animation when exploding
tween.stop(self, {
rotation: true
});
self.explode();
}
}, 16); // Check every 16ms (roughly 60fps)
return;
}
// Original behavior for non-Hot Potato challenges
self.targetEnemy = self.findTarget();
if (!self.targetEnemy) return;
// Calculate trajectory to enemy
var targetX = self.targetEnemy.x;
var targetY = self.targetEnemy.y;
// Launch upward first, then toward enemy
var midY = self.y - 300; // Go up 300 pixels first
// First tween: launch upward
tween(self, {
y: midY
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second tween: arc toward enemy
tween(self, {
x: targetX,
y: targetY
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
self.explode();
}
});
}
});
};
// Explode when hitting enemy
self.explode = function () {
// Create explosion visual effect
var explosionEffect = new EffectIndicator(self.x, self.y, 'explosion');
game.addChild(explosionEffect);
// Determine damage and range based on tower type
var explosionDamage = self.towerType === 'demolition' ? 200 : 100;
var explosionRange = CELL_SIZE * 4; // Large explosion range
// Deal area damage to all enemies in range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRange) {
// Apply explosion damage with Hot Potato challenge multiplier
var actualDamage = explosionDamage;
// In Hot Potato challenge, multiply CapyBomb and CapyDemolition damage by 20x against bosses
if (challengeMode && window.challengeName === 'Hot Potato') {
if ((self.towerType === 'bomb' || self.towerType === 'demolition') && enemy.isBoss) {
actualDamage = explosionDamage * 20;
}
}
enemy.health -= actualDamage;
if (enemy.health <= 0) {
enemy.health = 0;
} else {
enemy.healthBar.width = enemy.health / enemy.maxHealth * 70;
}
// Create damage indicator for each hit enemy
var damageEffect = new EffectIndicator(enemy.x, enemy.y, 'explosion');
game.addChild(damageEffect);
}
}
// Remove projectile tower
self.destroy();
};
self.update = function () {
// Track target enemy position if launched but not yet exploded
if (self.launched && self.targetEnemy && !self.targetEnemy.health <= 0) {
// Update target position during flight for moving enemies
// This could be enhanced with predictive targeting
}
};
return self;
});
var QueensWalkInstructionsScreen = Container.expand(function () {
var self = Container.call(this);
// Pause the game when instructions screen is shown
self.gamePaused = true;
// Hide UI labels when instructions screen is shown
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Queen's Walk instruction image at the bottom
var instructionsImage = self.attachAsset('queens_walk_instructions_image', {
anchorX: 0.5,
anchorY: 1.0
});
instructionsImage.x = 0;
instructionsImage.y = 2732 / 2 - 100; // Position higher up
instructionsImage.width = 800;
instructionsImage.height = 800;
// Instructions text label in the center of the screen
var instructionsTextShadow = new Text2("Welcome to Queen's Walk Challenge!\nBehold the royal garden path!\nOnly CapyQueen, CapyPult, and CapyPrincess towers are available.\nCollect diamonds dropped by defeated enemies.\nDiamonds grant 25 gold each!\nGood luck, defender!\n", {
size: 55,
fill: 0x000000,
weight: 800,
align: 'center'
});
instructionsTextShadow.anchor.set(0.5, 0.5);
instructionsTextShadow.x = 4; // Shadow offset
instructionsTextShadow.y = 4; // Centered vertically with shadow offset
// Set maximum width to ensure text wraps properly
instructionsTextShadow.wordWrap = true;
instructionsTextShadow.wordWrapWidth = 1800;
self.addChild(instructionsTextShadow);
var instructionsText = new Text2("Welcome to Queen's Walk Challenge!\nBehold the royal garden path!\nOnly CapyQueen, CapyPult, and CapyPrincess towers are available.\nCollect diamonds dropped by defeated enemies.\nDiamonds grant 25 gold each!\nGood luck, defender!\n", {
size: 55,
fill: 0xFFFFFF,
weight: 800,
align: 'center'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 0;
instructionsText.y = 0; // Centered vertically in screen
// Set maximum width to ensure text wraps properly
instructionsText.wordWrap = true;
instructionsText.wordWrapWidth = 1800;
self.addChild(instructionsText);
// Continue button at the top
var continueButton = new Container();
var buttonBg = continueButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 600;
buttonBg.height = 150;
buttonBg.tint = 0x9900FF;
var buttonTextShadow = new Text2("Continue", {
size: 80,
fill: 0x000000,
weight: 800
});
buttonTextShadow.anchor.set(0.5, 0.5);
buttonTextShadow.x = 2;
buttonTextShadow.y = 2;
continueButton.addChild(buttonTextShadow);
var buttonText = new Text2("Continue", {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
continueButton.addChild(buttonText);
continueButton.x = 0;
continueButton.y = -2732 / 2 + 300; // Position at top
self.addChild(continueButton);
// Continue button handler - close the screen
continueButton.down = function (x, y, obj) {
// Unpause the game
self.gamePaused = false;
// Show UI labels when continue button is pressed
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
// Remove instructions screen
self.destroy();
};
return self;
});
var SourceTower = Container.expand(function (towerType) {
var self = Container.call(this);
self.towerType = towerType || 'default';
// Increase size of base for easier touch
var assetName = 'tower_' + self.towerType;
var baseGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.99,
scaleY: 0.99,
y: -40
});
var towerCost = getTowerCost(self.towerType);
// Add shadow for tower type label below the tower
var towerNames = {
'default': 'CapyShot',
'rapid': 'CapyBlast',
'sniper': 'CapySniper',
'splash': 'CapyPult',
'slow': 'CapyFreeze',
'poison': 'CapyPoison',
'bomb': 'CapyBomb',
'demolition': 'CapyDemolition',
'queen': 'CapyQueen',
'king': 'CapyKing',
'prince': 'CapyPrince',
'princess': 'CapyPrincess'
};
var displayName = towerNames[self.towerType] || self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1);
var typeLabelShadow = new Text2(displayName, {
size: 40,
fill: 0x000000,
weight: 800
});
typeLabelShadow.anchor.set(0.5, 0.5);
typeLabelShadow.x = 4; // Shadow offset but still centered relative to tower
typeLabelShadow.y = 75 + 4; // Position higher up with shadow offset
self.addChild(typeLabelShadow);
// Add tower type label below the tower
var typeLabel = new Text2(displayName, {
size: 40,
fill: 0xFFFFFF,
weight: 800
});
typeLabel.anchor.set(0.5, 0.5);
typeLabel.x = 0; // Explicitly center horizontally
typeLabel.y = 75; // Position higher up
self.addChild(typeLabel);
// Add cost shadow
var costLabelShadow = new Text2(towerCost, {
size: 50,
fill: 0x000000,
weight: 800
});
costLabelShadow.anchor.set(0.5, 0.5);
costLabelShadow.x = 4; // Shadow offset but centered horizontally
costLabelShadow.y = 115 + 4; // Position higher up with shadow offset
self.addChild(costLabelShadow);
// Add cost label
var costLabel = new Text2(towerCost, {
size: 50,
fill: 0xFFD700,
weight: 800
});
costLabel.anchor.set(0.5, 0.5);
costLabel.x = 0; // Explicitly center horizontally
costLabel.y = 115; // Position higher up
self.addChild(costLabel);
// Add scaling animation function
self.startScalingAnimation = function () {
// Function to animate to larger scale (108%)
function scaleUp() {
tween(baseGraphics, {
scaleX: 1.07,
// Slightly smaller than tower scaling to 1.07
scaleY: 1.07
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
scaleDown();
}
});
}
// Function to animate to smaller scale (93%)
function scaleDown() {
tween(baseGraphics, {
scaleX: 0.93,
// Slightly smaller than tower scaling to 0.93
scaleY: 0.93
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
scaleUp();
}
});
}
// Start the animation cycle
scaleUp();
};
self.update = function () {
// Initialize scaling animation on first update
if (!self.scalingInitialized) {
self.scalingInitialized = true;
// Start the continuous scaling animation
self.startScalingAnimation();
}
// Check if player can afford this tower
var canAfford = gold >= getTowerCost(self.towerType);
// Check if this is a single-use tower that has already been placed
var isBlocked = self.towerType === 'king' && placedTowers.king || self.towerType === 'queen' && placedTowers.queen;
// Check if this tower is allowed in challenge mode
var isChallengeBlocked = challengeMode && challengeAllowedTowers.indexOf(self.towerType) === -1;
// Set opacity based on affordability, placement status, and challenge restrictions
if (isBlocked || isChallengeBlocked) {
self.alpha = 0.3; // Very dim for blocked towers
} else {
self.alpha = canAfford ? 1 : 0.5;
}
};
return self;
});
var StoryScreen = Container.expand(function () {
var self = Container.call(this);
// Pause the game when story screen is shown
self.gamePaused = true;
// Hide UI labels when story screen is shown
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 1.0;
// Create 6 independent story images
var storyImages = [];
var imageAssets = ['tutorial_image1', 'tutorial_image2', 'tutorial_image3', 'tutorial_image4', 'tutorial_image5', 'tutorial_image6'];
// Define positions for ordered 2x3 grid layout
var positions = [{
x: -512,
y: -900
},
// Top-left
{
x: 512,
y: -900
},
// Top-right
{
x: -512,
y: 0
},
// Middle-left
{
x: 512,
y: 0
},
// Middle-right
{
x: -512,
y: 900
},
// Bottom-left
{
x: 512,
y: 900
} // Bottom-right
];
for (var i = 0; i < 6; i++) {
var storyImage = self.attachAsset(imageAssets[i], {
anchorX: 0.5,
anchorY: 0.5
});
storyImage.x = positions[i].x;
storyImage.y = positions[i].y;
storyImage.width = 816; // 960 * 0.85 = 816 (15% smaller than current size)
storyImage.height = 816; // 960 * 0.85 = 816 (15% smaller than current size)
storyImage.alpha = 0; // Start hidden
storyImage.imageIndex = i; // Store index for reference
storyImages.push(storyImage);
}
// Start independent animations for each image
self.startIndependentAnimations = function () {
for (var i = 0; i < storyImages.length; i++) {
(function (imageIndex) {
var image = storyImages[imageIndex];
var delay = imageIndex * 1000; // Each image starts 1 second after the previous
// Start animation after delay
LK.setTimeout(function () {
tween(image, {
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut
});
}, delay);
})(i);
}
// Remove automatic timeout - only allow manual skip by clicking
};
// Continue to normal game flow
self.continueToGame = function () {
// Show UI labels when continuing
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
// Remove story screen and show mode selector
self.destroy();
var modeSelector = new ModeSelector();
modeSelector.x = 2048 / 2;
modeSelector.y = 2732 / 2;
game.addChild(modeSelector);
};
// Allow skipping by tapping
self.down = function (x, y, obj) {
self.continueToGame();
};
// Start independent animations when screen is created
LK.setTimeout(function () {
self.startIndependentAnimations();
}, 500);
return self;
});
/**
* TOWER SYSTEM ARCHITECTURE
*
* The tower system consists of 4 main components:
* 1. SourceTower - Menu towers at bottom for selecting tower types
* 2. TowerPreview - Visual preview when dragging to place towers
* 3. Tower - Functional placed towers that attack enemies
* 4. UpgradeMenu - UI for upgrading/selling towers
*
* TOWER TYPES (6 total):
* - default: Balanced tower (gray, 5 gold)
* - rapid: Fast firing, low damage (blue, 15 gold)
* - sniper: Long range, high damage (orange, 25 gold)
* - splash: Area damage (green, 35 gold)
* - slow: Slows enemies (purple, 45 gold)
* - poison: Damage over time (teal, 55 gold)
*
* UPGRADE SYSTEM:
* - 6 levels maximum per tower
* - Exponential cost scaling: base_cost * (2^(level-1))
* - Final upgrade costs 1.75x but gives 2x effect
* - Visual level indicators (dots) below each tower
*
* TARGETING SYSTEM:
* - Ground enemies: Prioritized by path score (closer to exit)
* - Flying enemies: Prioritized by distance to goal
* - Range calculated dynamically based on tower type and level
*
* SPECIAL MECHANICS:
* - Towers occupy 2x2 grid cells
* - Cannot block enemy paths
* - Recoil animation when firing
* - Type-specific bullet effects and visuals
*/
var Tower = Container.expand(function (id) {
var self = Container.call(this);
self.id = id || 'default';
self.level = 1;
self.maxLevel = 6;
self.gridX = 0;
self.gridY = 0;
self.range = 3 * CELL_SIZE;
// RANGE CALCULATION SYSTEM
// Each tower type has unique range scaling:
// - Sniper: Starts at 5 blocks, huge boost at max level (12 blocks)
// - Splash: Short range (2-4 blocks) for balance
// - Others: Standard scaling with incremental increases per level
self.getRange = function () {
// Always calculate range based on tower type and level
switch (self.id) {
case 'sniper':
// Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost
if (self.level === self.maxLevel) {
return 12 * CELL_SIZE; // Significantly increased range for max level
}
return (5 + (self.level - 1) * 0.8) * CELL_SIZE;
case 'splash':
// Splash: base 2, +0.2 per level (max ~4 blocks at max level)
return (2 + (self.level - 1) * 0.2) * CELL_SIZE;
case 'rapid':
// Rapid: base 2.5, +0.5 per level
return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'slow':
// Slow: base 3.5, +0.5 per level
return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'poison':
// Poison: base 3.2, +0.5 per level
return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'bomb':
// Bomb: base 3.2, +0.5 per level (same as poison)
return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'queen':
// Queen: base 7.875, +1.125 per level (125% increase over slow/CapyFreeze - additional 50% boost)
return (7.875 + (self.level - 1) * 1.125) * CELL_SIZE;
case 'king':
// King: base 8.5, +1.36 per level, huge boost at max level (70% increase over sniper/CapySniper - reduced by 30%)
if (self.level === self.maxLevel) {
return 20.4 * CELL_SIZE; // Significantly increased range for max level (70% increase)
}
return (8.5 + (self.level - 1) * 1.36) * CELL_SIZE;
// 70% increase over sniper base
case 'demolition':
// Demolition: base 3.2, +0.5 per level (same as bomb)
return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'prince':
// Prince: base 7.0, +1.36 per level (50% reduced range)
return (7.0 + (self.level - 1) * 1.36) * CELL_SIZE;
case 'princess':
// Princess: base 6.4, +1.0 per level (double poison range)
return (6.4 + (self.level - 1) * 1.0) * CELL_SIZE;
default:
// Default: base 3, +0.5 per level
return (3 + (self.level - 1) * 0.5) * CELL_SIZE;
}
};
self.cellsInRange = [];
self.fireRate = 60;
self.bulletSpeed = 5;
self.damage = 10;
self.lastFired = 0;
self.targetEnemy = null;
switch (self.id) {
case 'rapid':
self.fireRate = 30;
self.damage = 5;
self.range = 2.5 * CELL_SIZE;
self.bulletSpeed = 7;
break;
case 'sniper':
self.fireRate = 90;
self.damage = 25;
self.range = 5 * CELL_SIZE;
self.bulletSpeed = 25;
break;
case 'splash':
self.fireRate = 75;
self.damage = 15;
self.range = 2 * CELL_SIZE;
self.bulletSpeed = 4;
break;
case 'slow':
self.fireRate = 50;
self.damage = 8;
self.range = 3.5 * CELL_SIZE;
self.bulletSpeed = 5;
break;
case 'poison':
self.fireRate = 70;
self.damage = 12;
self.range = 3.2 * CELL_SIZE;
self.bulletSpeed = 5;
break;
case 'bomb':
self.fireRate = 70;
self.damage = 50;
self.range = 3.2 * CELL_SIZE;
self.bulletSpeed = 5;
// Special bomb tower behavior - explodes after 2 seconds
self.isBombTower = true;
self.explosionTimer = 120; // 2 seconds at 60fps
self.hasExploded = false;
break;
case 'queen':
// Queen tower (enhanced version of slow/CapyFreeze)
self.fireRate = 50;
self.damage = 24;
self.range = 5.25 * CELL_SIZE;
self.bulletSpeed = 5;
break;
case 'king':
// King tower (5x damage multiplier over sniper/CapySniper)
self.fireRate = 90;
self.damage = 125; // 25 * 5 = 125
self.range = 5 * CELL_SIZE;
self.bulletSpeed = 25;
break;
case 'demolition':
self.fireRate = 70;
self.damage = 100;
self.range = 3.2 * CELL_SIZE;
self.bulletSpeed = 5;
// Special bomb tower behavior - explodes after 2 seconds
self.isBombTower = true;
self.explosionTimer = 120; // 2 seconds at 60fps
self.hasExploded = false;
break;
case 'prince':
self.fireRate = 75;
self.damage = 30;
self.range = 14.0 * CELL_SIZE;
self.bulletSpeed = 4;
break;
case 'princess':
self.fireRate = 70;
self.damage = 12;
self.range = 6.4 * CELL_SIZE;
self.bulletSpeed = 5;
break;
}
var assetName = 'tower_' + self.id;
var baseGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
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();
var outlineCircle = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
outlineCircle.width = dotSize + 4;
outlineCircle.height = dotSize + 4;
outlineCircle.tint = 0x000000;
var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
towerLevelIndicator.width = dotSize;
towerLevelIndicator.height = dotSize;
towerLevelIndicator.tint = 0xCCCCCC;
dot.x = -CELL_SIZE + dotSpacing * (i + 1);
dot.y = CELL_SIZE * 0.7;
self.addChild(dot);
levelIndicators.push(dot);
}
var gunContainer = new Container();
self.addChild(gunContainer);
var gunGraphics = gunContainer.attachAsset('defense', {
anchorX: 0.5,
anchorY: 0.5
});
gunGraphics.alpha = 0;
self.updateLevelIndicators = function () {
for (var i = 0; i < maxDots; i++) {
var dot = levelIndicators[i];
var towerLevelIndicator = dot.children[1];
if (i < self.level) {
towerLevelIndicator.tint = 0xFFFFFF;
} else {
towerLevelIndicator.tint = 0xCCCCCC;
}
}
};
self.updateLevelIndicators();
// Add scaling animation function
self.startScalingAnimation = function () {
// Function to animate to larger scale (108%)
function scaleUp() {
tween(baseGraphics, {
scaleX: 1.08,
scaleY: 1.08
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
scaleDown();
}
});
}
// Function to animate to smaller scale (92%)
function scaleDown() {
tween(baseGraphics, {
scaleX: 0.92,
scaleY: 0.92
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
scaleUp();
}
});
}
// Start the animation cycle
scaleUp();
};
self.refreshCellsInRange = function () {
for (var i = 0; i < self.cellsInRange.length; i++) {
var cell = self.cellsInRange[i];
var towerIndex = cell.towersInRange.indexOf(self);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
self.cellsInRange = [];
var rangeRadius = self.getRange() / CELL_SIZE;
var centerX = self.gridX + 1;
var centerY = self.gridY + 1;
var minI = Math.floor(centerX - rangeRadius - 0.5);
var maxI = Math.ceil(centerX + rangeRadius + 0.5);
var minJ = Math.floor(centerY - rangeRadius - 0.5);
var maxJ = Math.ceil(centerY + rangeRadius + 0.5);
for (var i = minI; i <= maxI; i++) {
for (var j = minJ; j <= maxJ; j++) {
var closestX = Math.max(i, Math.min(centerX, i + 1));
var closestY = Math.max(j, Math.min(centerY, j + 1));
var deltaX = closestX - centerX;
var deltaY = closestY - centerY;
var distanceSquared = deltaX * deltaX + deltaY * deltaY;
if (distanceSquared <= rangeRadius * rangeRadius) {
var cell = grid.getCell(i, j);
if (cell) {
self.cellsInRange.push(cell);
cell.towersInRange.push(self);
}
}
}
}
grid.renderDebug();
};
self.getTotalValue = function () {
var baseTowerCost = getTowerCost(self.id);
var totalInvestment = baseTowerCost;
var baseUpgradeCost = baseTowerCost; // Upgrade cost now scales with base tower cost
for (var i = 1; i < self.level; i++) {
totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1));
}
return totalInvestment;
};
self.upgrade = function () {
if (self.level < self.maxLevel) {
// Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.id);
var upgradeCost;
// Make last upgrade level extra expensive
if (self.level === self.maxLevel - 1) {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); // Half the cost for final upgrade
} else {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1));
}
if (gold >= upgradeCost) {
setGold(gold - upgradeCost);
self.level++;
// No need to update self.range here; getRange() is now the source of truth
// Apply tower-specific upgrades based on type
if (self.id === 'rapid') {
if (self.level === self.maxLevel) {
// Extra powerful last upgrade (double the effect)
self.fireRate = Math.max(4, 30 - self.level * 9); // double the effect
self.damage = 5 + self.level * 10; // double the effect
self.bulletSpeed = 7 + self.level * 2.4; // double the effect
} else {
self.fireRate = Math.max(15, 30 - self.level * 3); // Fast tower gets faster with upgrades
self.damage = 5 + self.level * 3;
self.bulletSpeed = 7 + self.level * 0.7;
}
} else {
if (self.level === self.maxLevel) {
// Extra powerful last upgrade for all other towers (double the effect)
self.fireRate = Math.max(5, 60 - self.level * 24); // double the effect
self.damage = 10 + self.level * 20; // double the effect
self.bulletSpeed = 5 + self.level * 2.4; // double the effect
} else {
self.fireRate = Math.max(20, 60 - self.level * 8);
self.damage = 10 + self.level * 5;
self.bulletSpeed = 5 + self.level * 0.5;
}
}
self.refreshCellsInRange();
self.updateLevelIndicators();
if (self.level > 1) {
var levelDot = levelIndicators[self.level - 1].children[1];
tween(levelDot, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(levelDot, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
return true;
} else {
var notification = game.addChild(new Notification("Not enough gold to upgrade!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
return false;
};
// TARGETING PRIORITY SYSTEM
// Ground enemies: Use pathfinding score (lower = closer to exit)
// Flying enemies: Use distance to their flight goal
// This ensures towers target the most dangerous enemies first
self.findTarget = function () {
var closestEnemy = null;
var closestScore = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if enemy is in range
if (distance <= self.getRange()) {
// Handle flying enemies differently - they can be targeted regardless of path
if (enemy.isFlying) {
// For flying enemies, prioritize by distance to the goal
if (enemy.flyingTarget) {
var goalX = enemy.flyingTarget.x;
var goalY = enemy.flyingTarget.y;
var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY));
// Use distance to goal as score
if (distToGoal < closestScore) {
closestScore = distToGoal;
closestEnemy = enemy;
}
} else {
// If no flying target yet (shouldn't happen), prioritize by distance to tower
if (distance < closestScore) {
closestScore = distance;
closestEnemy = enemy;
}
}
} else {
// For ground enemies, use the original path-based targeting
// Get the cell for this enemy
var cell = grid.getCell(enemy.cellX, enemy.cellY);
if (cell && cell.pathId === pathId) {
// Use the cell's score (distance to exit) for prioritization
// Lower score means closer to exit
if (cell.score < closestScore) {
closestScore = cell.score;
closestEnemy = enemy;
}
}
}
}
}
if (!closestEnemy) {
self.targetEnemy = null;
}
return closestEnemy;
};
self.update = function () {
// Initialize scaling animation on first update
if (!self.scalingInitialized) {
self.scalingInitialized = true;
// Start the continuous scaling animation
self.startScalingAnimation();
}
// Handle bomb tower special behavior
if (self.isBombTower && !self.hasExploded) {
self.explosionTimer--;
if (self.explosionTimer <= 0) {
self.explode();
return; // Don't continue with normal tower behavior after exploding
}
// Visual warning when close to explosion
if (self.explosionTimer <= 60) {
// Last second warning
var warningIntensity = Math.sin(self.explosionTimer * 0.5) * 0.5 + 0.5;
baseGraphics.tint = 0xFF0000 * warningIntensity + 0xFFFFFF * (1 - warningIntensity);
}
}
// Normal tower behavior for non-bomb towers only
if (!self.isBombTower) {
self.targetEnemy = self.findTarget();
if (self.targetEnemy) {
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var angle = Math.atan2(dy, dx);
gunContainer.rotation = angle;
// Apply game speed to fire rate - higher speed means faster firing
var adjustedFireRate = self.fireRate / gameSpeed;
if (LK.ticks - self.lastFired >= adjustedFireRate) {
self.fire();
self.lastFired = LK.ticks;
}
}
}
};
self.down = function (x, y, obj) {
// Block tower interactions when ModeSelector or tutorial instruction screens are active
var modeSelectorActive = game.children.some(function (child) {
return child instanceof ModeSelector;
});
var instructions2Active = game.children.some(function (child) {
return child instanceof Instructions2Screen;
});
var instructions3Active = game.children.some(function (child) {
return child instanceof Instructions3Screen;
});
if (modeSelectorActive || instructions2Active || instructions3Active) {
return;
}
// Don't show upgrade menu for bomb towers since they explode
if (self.isBombTower) {
var notification = game.addChild(new Notification("Bomb towers cannot be upgraded - they explode!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return;
}
var existingMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
var hasOwnMenu = false;
var rangeCircle = null;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self) {
rangeCircle = game.children[i];
break;
}
}
for (var i = 0; i < existingMenus.length; i++) {
if (existingMenus[i].tower === self) {
hasOwnMenu = true;
break;
}
}
if (hasOwnMenu) {
for (var i = 0; i < existingMenus.length; i++) {
if (existingMenus[i].tower === self) {
hideUpgradeMenu(existingMenus[i]);
}
}
if (rangeCircle) {
game.removeChild(rangeCircle);
}
selectedTower = null;
grid.renderDebug();
return;
}
for (var i = 0; i < existingMenus.length; i++) {
existingMenus[i].destroy();
}
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange) {
game.removeChild(game.children[i]);
}
}
selectedTower = self;
var rangeIndicator = new Container();
rangeIndicator.isTowerRange = true;
rangeIndicator.tower = self;
game.addChild(rangeIndicator);
rangeIndicator.x = self.x;
rangeIndicator.y = self.y;
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.width = rangeGraphics.height = self.getRange() * 2;
rangeGraphics.alpha = 0.2;
var upgradeMenu = new UpgradeMenu(self);
game.addChild(upgradeMenu);
upgradeMenu.x = 2048 / 2;
tween(upgradeMenu, {
y: 2732 - 225
}, {
duration: 200,
easing: tween.backOut
});
grid.renderDebug();
};
self.isInRange = function (enemy) {
if (!enemy) {
return false;
}
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance <= self.getRange();
};
// BULLET EFFECT SYSTEM
// Each tower type creates specialized bullets:
// - splash: Area damage to nearby enemies
// - slow: Reduces enemy speed (scales with tower level)
// - poison: Damage over time effect
// - sniper: High damage with visual critical effect
// Bullets track their type and apply effects on impact
self.fire = function () {
if (self.targetEnemy) {
var potentialDamage = 0;
for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) {
potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage;
}
if (self.targetEnemy.health > potentialDamage) {
var bulletX = self.x + Math.cos(gunContainer.rotation) * 40;
var bulletY = self.y + Math.sin(gunContainer.rotation) * 40;
var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed * gameSpeed);
// Set bullet type based on tower type
bullet.type = self.id;
// For slow and queen towers, pass level for scaling slow effect
if (self.id === 'slow' || self.id === 'queen') {
bullet.sourceTowerLevel = self.level;
}
// Customize bullet appearance based on tower type
switch (self.id) {
case 'rapid':
bullet.children[0].tint = 0x00AAFF;
bullet.children[0].width = 20;
bullet.children[0].height = 20;
break;
case 'sniper':
bullet.children[0].tint = 0xFF5500;
bullet.children[0].width = 15;
bullet.children[0].height = 15;
break;
case 'splash':
bullet.children[0].tint = 0x33CC00;
bullet.children[0].width = 40;
bullet.children[0].height = 40;
break;
case 'slow':
bullet.children[0].tint = 0x9900FF;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
case 'poison':
bullet.children[0].tint = 0x00FFAA;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
case 'bomb':
bullet.children[0].tint = 0x00FFAA;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
case 'queen':
// Queen bullets (similar to slow)
bullet.children[0].tint = 0x9900FF;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
case 'king':
// King bullets (similar to sniper)
bullet.children[0].tint = 0xFF5500;
bullet.children[0].width = 15;
bullet.children[0].height = 15;
break;
case 'demolition':
bullet.children[0].tint = 0x00FFAA;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
case 'prince':
bullet.children[0].tint = 0x33CC00;
bullet.children[0].width = 40;
bullet.children[0].height = 40;
break;
case 'princess':
bullet.children[0].tint = 0x00FFAA;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
}
game.addChild(bullet);
bullets.push(bullet);
self.targetEnemy.bulletsTargetingThis.push(bullet);
// --- Fire recoil effect for gunContainer ---
// Stop any ongoing recoil tweens before starting a new one
tween.stop(gunContainer, {
x: true,
y: true,
scaleX: true,
scaleY: true
});
// Always use the original resting position for recoil, never accumulate offset
if (gunContainer._restX === undefined) {
gunContainer._restX = 0;
}
if (gunContainer._restY === undefined) {
gunContainer._restY = 0;
}
if (gunContainer._restScaleX === undefined) {
gunContainer._restScaleX = 1;
}
if (gunContainer._restScaleY === undefined) {
gunContainer._restScaleY = 1;
}
// Reset to resting position before animating (in case of interrupted tweens)
gunContainer.x = gunContainer._restX;
gunContainer.y = gunContainer._restY;
gunContainer.scaleX = gunContainer._restScaleX;
gunContainer.scaleY = gunContainer._restScaleY;
// Calculate recoil offset (recoil back along the gun's rotation)
var recoilDistance = 8;
var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance;
var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance;
// Animate recoil back from the resting position
tween(gunContainer, {
x: gunContainer._restX + recoilX,
y: gunContainer._restY + recoilY
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Animate return to original position/scale
tween(gunContainer, {
x: gunContainer._restX,
y: gunContainer._restY
}, {
duration: 90,
easing: tween.cubicIn
});
}
});
}
}
};
self.explode = function () {
if (self.hasExploded) return;
self.hasExploded = true;
// Create explosion visual effect
var explosionEffect = new EffectIndicator(self.x, self.y, 'explosion');
game.addChild(explosionEffect);
// Deal area damage to all enemies in range
var explosionRange = self.getRange() * 3.0; // Double the explosion range multiplier
var explosionDamage = self.damage * (self.level + 2) * 2; // Double the explosion damage scaling
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRange) {
// Apply explosion damage
enemy.health -= explosionDamage;
if (enemy.health <= 0) {
enemy.health = 0;
} else {
enemy.healthBar.width = enemy.health / enemy.maxHealth * 70;
}
// Create damage indicator for each hit enemy
var damageEffect = new EffectIndicator(enemy.x, enemy.y, 'explosion');
game.addChild(damageEffect);
}
}
// Remove tower from grid and clean up
var gridX = self.gridX;
var gridY = self.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; // Clear cell
var towerIndex = cell.towersInRange.indexOf(self);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
}
}
// Remove from towers array
var towerIndex = towers.indexOf(self);
if (towerIndex !== -1) {
towers.splice(towerIndex, 1);
}
// Close any upgrade menu for this tower
var existingMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
for (var i = 0; i < existingMenus.length; i++) {
if (existingMenus[i].tower === self) {
hideUpgradeMenu(existingMenus[i]);
}
}
// Remove range indicator if exists
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange && game.children[i].tower === self) {
game.removeChild(game.children[i]);
}
}
// Clear selected tower if this was selected
if (selectedTower === self) {
selectedTower = null;
}
// Animate tower destruction
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
towerLayer.removeChild(self);
grid.pathFind();
grid.renderDebug();
}
});
};
self.placeOnGrid = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2;
// Special behavior for Hot Potato challenge
if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) {
if (self.id === 'bomb' || self.id === 'demolition') {
// Create projectile tower instead of regular tower
var projectile = new ProjectileTower(self.id, self.x, self.y);
game.addChild(projectile);
// Launch immediately after short delay
LK.setTimeout(function () {
projectile.launch();
}, 500);
// Don't place regular tower, just return
return;
}
}
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;
// Set alpha to 0.5 for wall cells in tutorial mode
if (tutorialMode && cell.debugCell) {
cell.debugCell.children[0].alpha = 0.5;
}
}
}
}
self.refreshCellsInRange();
};
return self;
});
var TowerPreview = Container.expand(function () {
var self = Container.call(this);
var towerRange = 3;
var rangeInPixels = towerRange * CELL_SIZE;
self.towerType = 'default';
self.hasEnoughGold = true;
var rangeIndicator = new Container();
self.addChild(rangeIndicator);
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.alpha = 0.3;
var assetName = 'towerpreview_' + self.towerType;
var previewGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
previewGraphics.width = CELL_SIZE * 2;
previewGraphics.height = CELL_SIZE * 2;
self.canPlace = false;
self.gridX = 0;
self.gridY = 0;
self.blockedByEnemy = false;
self.update = function () {
var previousHasEnoughGold = self.hasEnoughGold;
self.hasEnoughGold = gold >= getTowerCost(self.towerType);
// Only update appearance if the affordability status has changed
if (previousHasEnoughGold !== self.hasEnoughGold) {
self.updateAppearance();
}
};
self.updateAppearance = function () {
// Update the preview graphics asset to match the tower type
var newAssetName = 'towerpreview_' + self.towerType;
if (previewGraphics.assetName !== newAssetName) {
// Remove old graphics
self.removeChild(previewGraphics);
// Create new graphics with correct asset
previewGraphics = self.attachAsset(newAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
previewGraphics.width = CELL_SIZE * 2;
previewGraphics.height = CELL_SIZE * 2;
previewGraphics.assetName = newAssetName;
}
// Use Tower class to get the source of truth for range
var tempTower = new Tower(self.towerType);
var previewRange = tempTower.getRange();
// Clean up tempTower to avoid memory leaks
if (tempTower && tempTower.destroy) {
tempTower.destroy();
}
// Set range indicator using unified range logic
rangeGraphics.width = rangeGraphics.height = previewRange * 2;
if (!self.canPlace) {
previewGraphics.tint = 0xFF0000; // Red tint when cannot place
} else if (!self.hasEnoughGold) {
previewGraphics.tint = 0xFFFFFF; // Keep white when insufficient gold
} else {
previewGraphics.tint = 0xFFFFFF; // White when can place
}
};
self.updatePlacementStatus = function () {
var validGridPlacement = true;
if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) {
validGridPlacement = false;
} else {
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(self.gridX + i, self.gridY + j);
if (!cell || cell.type !== 0) {
validGridPlacement = false;
break;
}
// Check if Hot Potato challenge is active and prevent placement on non-blue floor cells
if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) {
var gridWidth = 24;
var gridHeight = 29 + 6;
var bottomThirdStart = Math.floor(gridHeight * 2 / 3); // Start of bottom third
var cellY = self.gridY + j;
// Only allow placement on blue floor cells (bottom third)
if (cellY < bottomThirdStart || cellY >= gridHeight - 4) {
validGridPlacement = false;
break;
}
}
}
if (!validGridPlacement) {
break;
}
}
}
self.blockedByEnemy = false;
if (validGridPlacement) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.currentCellY < 4) {
continue;
}
// Only check non-flying enemies, flying enemies can pass over towers
if (!enemy.isFlying) {
if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
}
if (enemy.currentTarget) {
var targetX = enemy.currentTarget.x;
var targetY = enemy.currentTarget.y;
if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
}
}
}
}
}
self.canPlace = validGridPlacement && !self.blockedByEnemy;
self.hasEnoughGold = gold >= getTowerCost(self.towerType);
self.updateAppearance();
};
self.checkPlacement = function () {
self.updatePlacementStatus();
};
self.snapToGrid = function (x, y) {
var gridPosX = x - grid.x;
var gridPosY = y - grid.y;
self.gridX = Math.floor(gridPosX / CELL_SIZE);
self.gridY = Math.floor(gridPosY / CELL_SIZE);
self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2;
self.checkPlacement();
};
return self;
});
var TutorialCompleteScreen = Container.expand(function () {
var self = Container.call(this);
// Pause the game when tutorial complete screen is shown
self.gamePaused = true;
// Hide UI labels when tutorial complete screen is shown
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Instructions3 image at the bottom using independent asset
var instructionsImage = self.attachAsset('tutorialcomplete_image1', {
anchorX: 0.5,
anchorY: 1.0
});
instructionsImage.x = 300; // Move away from center
instructionsImage.y = 2732 / 2 - 100; // Position higher up
instructionsImage.width = 600; // Reduce size to fit better
instructionsImage.height = 600; // Reduce size to fit better
// Duplicate Instructions3 image positioned independently
var instructionsImageDuplicate = self.attachAsset('tutorialcomplete_image2', {
anchorX: 0.5,
anchorY: 1.0
});
instructionsImageDuplicate.x = -300; // Position independently to the left
instructionsImageDuplicate.y = 2732 / 2 - 100; // Same vertical position
instructionsImageDuplicate.width = 600; // Match the other image size
instructionsImageDuplicate.height = 600; // Match the other image size
// Instructions text label in the center of the screen
var instructionsTextShadow = new Text2("Congratulations!\nYou have completed the tutorial!\nYou are now ready to defend the castle\nin the full game. Go forth, defender!\n", {
size: 55,
fill: 0x000000,
weight: 800,
align: 'center'
});
instructionsTextShadow.anchor.set(0.5, 0.5);
instructionsTextShadow.x = 4; // Shadow offset
instructionsTextShadow.y = 4; // Centered vertically with shadow offset
// Set maximum width to ensure text wraps properly
instructionsTextShadow.wordWrap = true;
instructionsTextShadow.wordWrapWidth = 1800;
self.addChild(instructionsTextShadow);
var instructionsText = new Text2("Congratulations!\nYou have completed the tutorial!\nYou are now ready to defend the castle\nin the full game. Go forth, defender!\n", {
size: 55,
fill: 0xFFFFFF,
weight: 800,
align: 'center'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 0;
instructionsText.y = 0; // Centered vertically in screen
// Set maximum width to ensure text wraps properly
instructionsText.wordWrap = true;
instructionsText.wordWrapWidth = 1800;
self.addChild(instructionsText);
// Continue button at the top
var continueButton = new Container();
var buttonBg = continueButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 600;
buttonBg.height = 150;
buttonBg.tint = 0x00AA00;
var buttonTextShadow = new Text2("Continue", {
size: 80,
fill: 0x000000,
weight: 800
});
buttonTextShadow.anchor.set(0.5, 0.5);
buttonTextShadow.x = 2;
buttonTextShadow.y = 2;
continueButton.addChild(buttonTextShadow);
var buttonText = new Text2("Continue", {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
continueButton.addChild(buttonText);
continueButton.x = 0;
continueButton.y = -2732 / 2 + 300; // Position at top
self.addChild(continueButton);
// Continue button handler - restart game and show level selection
continueButton.down = function (x, y, obj) {
// Restart the game by resetting all game state
tutorialMode = false;
selectedLevel = 'garden';
difficultyMultiplier = 1.0;
totalWaves = 50;
nextWaveTime = 12000 / 2;
currentWave = 0;
waveTimer = 0;
waveInProgress = false;
waveSpawned = false;
gold = 80;
lives = 20;
score = 0;
enemies = [];
towers = [];
bullets = [];
diamonds = [];
gameSpeed = 1.0;
currentSpeedIndex = 0;
placedTowers = {
king: false,
queen: false
};
instructions2Shown = false;
selectedTower = null;
pathId = 1;
maxScore = 0;
// Stop all tween animations before resetting
tween.stop(game);
for (var i = 0; i < enemies.length; i++) {
if (enemies[i]) {
tween.stop(enemies[i]);
if (enemies[i].children && enemies[i].children[0]) {
tween.stop(enemies[i].children[0]);
}
}
}
for (var i = 0; i < towers.length; i++) {
if (towers[i] && towers[i].children) {
for (var j = 0; j < towers[i].children.length; j++) {
if (towers[i].children[j]) {
tween.stop(towers[i].children[j]);
}
}
}
}
for (var i = 0; i < sourceTowers.length; i++) {
if (sourceTowers[i] && sourceTowers[i].children) {
for (var j = 0; j < sourceTowers[i].children.length; j++) {
if (sourceTowers[i].children[j]) {
tween.stop(sourceTowers[i].children[j]);
}
}
}
}
// Clear and destroy all enemies from all enemy layers
for (var i = enemyLayerBottom.children.length - 1; i >= 0; i--) {
var enemy = enemyLayerBottom.children[i];
if (enemy && enemy.destroy) {
enemy.destroy();
} else {
enemyLayerBottom.removeChild(enemy);
}
}
for (var i = enemyLayerMiddle.children.length - 1; i >= 0; i--) {
var shadow = enemyLayerMiddle.children[i];
if (shadow && shadow.destroy) {
shadow.destroy();
} else {
enemyLayerMiddle.removeChild(shadow);
}
}
for (var i = enemyLayerTop.children.length - 1; i >= 0; i--) {
var flyingEnemy = enemyLayerTop.children[i];
if (flyingEnemy && flyingEnemy.destroy) {
flyingEnemy.destroy();
} else {
enemyLayerTop.removeChild(flyingEnemy);
}
}
// Clear all towers from tower layer and reset their grid cells
for (var i = towerLayer.children.length - 1; i >= 0; i--) {
var child = towerLayer.children[i];
if (child instanceof Tower) {
// Reset grid cells occupied by this tower
for (var x = 0; x < 2; x++) {
for (var y = 0; y < 2; y++) {
var cell = grid.getCell(child.gridX + x, child.gridY + y);
if (cell) {
cell.type = 0; // Reset to walkable
var towerIndex = cell.towersInRange.indexOf(child);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
}
}
towerLayer.removeChild(child);
} else if (child instanceof SourceTower) {
// Keep source towers but don't remove them
}
}
// Reset grid by removing and recreating it completely
if (grid) {
debugLayer.removeChild(grid);
}
grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * 4;
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
// Clear all bullets from the game
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i].parent) {
bullets[i].parent.removeChild(bullets[i]);
}
}
bullets = [];
// Clear all game children except essential elements and recreate background
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child !== debugLayer && child !== towerLayer && child !== enemyLayer) {
if (child && child.destroy) {
child.destroy();
} else {
game.removeChild(child);
}
}
}
// Recreate background with proper asset
var newBackgroundAssetName = getBackgroundAssetName();
backgroundImage = game.attachAsset(newBackgroundAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
backgroundImage.x = 2048 / 2;
backgroundImage.y = 2732 / 2;
backgroundImage.width = 2048;
backgroundImage.height = 2732;
game.addChildAt(backgroundImage, 0);
// Remove and recreate wave indicator completely
if (waveIndicator) {
game.removeChild(waveIndicator);
}
waveIndicator = new WaveIndicator();
waveIndicator.x = 2048 / 2;
waveIndicator.y = 2732 - 80;
game.addChild(waveIndicator);
// Clear existing source towers and recreate them
for (var i = sourceTowers.length - 1; i >= 0; i--) {
var sourceTower = sourceTowers[i];
if (sourceTower.parent) {
sourceTower.parent.removeChild(sourceTower);
}
}
sourceTowers = [];
var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison', 'bomb', 'queen', 'king'];
var towerSpacing = 220;
var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2;
var towerY = 2732 - CELL_SIZE * 3 - 90;
for (var t = 0; t < towerTypes.length; t++) {
var tower = new SourceTower(towerTypes[t]);
tower.x = startX + t * towerSpacing;
tower.y = towerY;
// Reset scaling animation state
tower.scalingInitialized = false;
// Make sure tower is visible and enabled
tower.visible = true;
tower.alpha = 1.0;
towerLayer.addChild(tower);
sourceTowers.push(tower);
}
// Remove and recreate next wave button completely
if (nextWaveButton) {
if (nextWaveButton.parent) {
nextWaveButton.parent.removeChild(nextWaveButton);
}
}
if (nextWaveButtonContainer) {
if (nextWaveButtonContainer.parent) {
nextWaveButtonContainer.parent.removeChild(nextWaveButtonContainer);
}
}
// Reset tower preview functionality
if (towerPreview) {
game.removeChild(towerPreview);
}
towerPreview = new TowerPreview();
game.addChild(towerPreview);
towerPreview.visible = false;
// Recreate next wave button container and button
nextWaveButtonContainer = new Container();
nextWaveButton = new NextWaveButton();
nextWaveButton.x = 2048 - 200;
nextWaveButton.y = 2732 - 100 + 20;
nextWaveButton.enabled = false;
nextWaveButton.visible = false;
nextWaveButtonContainer.addChild(nextWaveButton);
game.addChild(nextWaveButtonContainer);
// Recreate velocity button
if (velocityButton && velocityButton.parent) {
velocityButton.parent.removeChild(velocityButton);
}
velocityButton = new Container();
var velocityButtonBackground = velocityButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
velocityButtonBackground.width = 280;
velocityButtonBackground.height = 120;
velocityButtonBackground.tint = 0x00AA88;
var velocityButtonText = new Text2("Velocity", {
size: 45,
fill: 0xFFFFFF,
weight: 800
});
velocityButtonText.anchor.set(0.5, 0.5);
velocityButtonText.y = -15;
var velocitySpeedText = new Text2("x" + gameSpeed.toFixed(1), {
size: 40,
fill: 0xFFD700,
weight: 800
});
velocitySpeedText.anchor.set(0.5, 0.5);
velocitySpeedText.y = 32;
velocityButton.addChild(velocityButtonText);
velocityButton.addChild(velocitySpeedText);
velocityButton.x = 200;
velocityButton.y = 2732 - 80;
velocityButton.visible = false;
velocityButton.enabled = false;
// Add velocity button functionality
velocityButton.down = function () {
if (!velocityButton.enabled) {
return;
}
currentSpeedIndex = (currentSpeedIndex + 1) % speedMultipliers.length;
gameSpeed = speedMultipliers[currentSpeedIndex];
velocitySpeedText.setText("x" + gameSpeed.toFixed(1));
// Visual feedback
tween(velocityButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(velocityButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
var notification = game.addChild(new Notification("Game speed set to x" + gameSpeed.toFixed(1) + "!"));
notification.x = 2048 / 2;
notification.y = grid.height - 100;
};
velocityButton.update = function () {
if (waveIndicator && waveIndicator.gameStarted) {
velocityButton.enabled = true;
velocityButton.visible = true;
velocityButton.alpha = 1;
} else {
velocityButton.enabled = false;
velocityButton.visible = false;
velocityButton.alpha = 0.7;
}
};
game.addChild(velocityButton);
// Reset dragging state and other variables
isDragging = false;
sourceTower = null;
// Reset UI visibility
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
updateUI();
// Hide start game button again since we're going back to level selection
if (waveIndicator) {
waveIndicator.waveMarkers[0].visible = false;
}
// Show level selector for fresh level selection
var levelSelector = new LevelSelector();
levelSelector.x = 2048 / 2;
levelSelector.y = 2732 / 2;
game.addChild(levelSelector);
// Remove tutorial complete screen
self.destroy();
};
return self;
});
var TutorialIntroScreen = Container.expand(function () {
var self = Container.call(this);
// Hide UI labels when tutorial intro screen is shown
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
// Background overlay
var overlay = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 2048;
overlay.height = 2732;
overlay.tint = 0x000000;
overlay.alpha = 0.8;
// Tutorial image at the bottom
var tutorialImage = self.attachAsset('tutorial_image', {
anchorX: 0.5,
anchorY: 1.0
});
tutorialImage.x = 0;
tutorialImage.y = 2732 / 2 - 100; // Position higher up
tutorialImage.width = 800;
tutorialImage.height = 800;
// Welcome text label in the center of the screen
var welcomeTextShadow = new Text2("Welcome to the castle!\nI am The Capy King.\nWe need your help to build the castle defenses.\nOur CapyWarriors are ready to deploy.\nUse the gold to place your first CapyWarrior.\n", {
size: 55,
fill: 0x000000,
weight: 800,
align: 'center'
});
welcomeTextShadow.anchor.set(0.5, 0.5);
welcomeTextShadow.x = 4; // Shadow offset
welcomeTextShadow.y = 4; // Centered vertically with shadow offset
// Set maximum width to ensure text wraps properly
welcomeTextShadow.wordWrap = true;
welcomeTextShadow.wordWrapWidth = 1800;
self.addChild(welcomeTextShadow);
var welcomeText = new Text2("Welcome to the castle!\nI am The Capy King.\nWe need your help to build the castle defenses.\nOur CapyWarriors are ready to deploy.\nUse the gold to place your first CapyWarrior.\n", {
size: 55,
fill: 0xFFFFFF,
weight: 800,
align: 'center'
});
welcomeText.anchor.set(0.5, 0.5);
welcomeText.x = 0;
welcomeText.y = 0; // Centered vertically in screen
// Set maximum width to ensure text wraps properly
welcomeText.wordWrap = true;
welcomeText.wordWrapWidth = 1800;
self.addChild(welcomeText);
// Continue button at the top
var continueButton = new Container();
var buttonBg = continueButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 600;
buttonBg.height = 150;
buttonBg.tint = 0x00AA00;
var buttonTextShadow = new Text2("Continue", {
size: 80,
fill: 0x000000,
weight: 800
});
buttonTextShadow.anchor.set(0.5, 0.5);
buttonTextShadow.x = 2;
buttonTextShadow.y = 2;
continueButton.addChild(buttonTextShadow);
var buttonText = new Text2("Continue", {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
continueButton.addChild(buttonText);
continueButton.x = 0;
continueButton.y = -2732 / 2 + 300; // Position at top
self.addChild(continueButton);
// Continue button handler - only close the screen
continueButton.down = function (x, y, obj) {
// Show UI labels when continue button is pressed
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
// Remove tutorial intro screen
self.destroy();
};
return self;
});
var UpgradeMenu = Container.expand(function (tower) {
var self = Container.call(this);
self.tower = tower;
self.y = 2732 + 225;
var menuBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
menuBackground.width = 2048;
menuBackground.height = 500;
menuBackground.tint = 0x444444;
menuBackground.alpha = 0.9;
var towerNames = {
'default': 'CapyShot',
'rapid': 'CapyBlast',
'sniper': 'CapySniper',
'splash': 'CapyPult',
'slow': 'CapyFreeze',
'poison': 'CapyPoison',
'bomb': 'CapyBomb',
'demolition': 'CapyDemolition',
'queen': 'CapyQueen',
'king': 'CapyKing',
'prince': 'CapyPrince',
'princess': 'CapyPrincess'
};
var displayName = towerNames[self.tower.id] || self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1);
var towerTypeText = new Text2(displayName + ' Tower', {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
towerTypeText.anchor.set(0, 0);
towerTypeText.x = -840;
towerTypeText.y = -160;
self.addChild(towerTypeText);
var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', {
size: 70,
fill: 0xFFFFFF,
weight: 400
});
statsText.anchor.set(0, 0.5);
statsText.x = -840;
statsText.y = 50;
self.addChild(statsText);
var buttonsContainer = new Container();
buttonsContainer.x = 500;
self.addChild(buttonsContainer);
var upgradeButton = new Container();
buttonsContainer.addChild(upgradeButton);
var buttonBackground = upgradeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBackground.width = 500;
buttonBackground.height = 150;
var isMaxLevel = self.tower.level >= self.tower.maxLevel;
// Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.tower.id);
var upgradeCost;
if (isMaxLevel) {
upgradeCost = 0;
} else if (self.tower.level === self.tower.maxLevel - 1) {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2);
} else {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1));
}
buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888;
var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
upgradeButton.addChild(buttonText);
var sellButton = new Container();
buttonsContainer.addChild(sellButton);
var sellButtonBackground = sellButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
sellButtonBackground.width = 500;
sellButtonBackground.height = 150;
sellButtonBackground.tint = 0xCC0000;
var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
var sellValue = getTowerSellValue(totalInvestment);
var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
sellButtonText.anchor.set(0.5, 0.5);
sellButton.addChild(sellButtonText);
upgradeButton.y = -85;
sellButton.y = 85;
var closeButton = new Container();
self.addChild(closeButton);
var closeBackground = closeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
closeBackground.width = 90;
closeBackground.height = 90;
closeBackground.tint = 0xAA0000;
var closeText = new Text2('X', {
size: 68,
fill: 0xFFFFFF,
weight: 800
});
closeText.anchor.set(0.5, 0.5);
closeButton.addChild(closeText);
closeButton.x = menuBackground.width / 2 - 57;
closeButton.y = -menuBackground.height / 2 + 57;
upgradeButton.down = function (x, y, obj) {
if (self.tower.level >= self.tower.maxLevel) {
var notification = game.addChild(new Notification("Tower is already at max level!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return;
}
if (self.tower.upgrade()) {
// Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.tower.id);
if (self.tower.level >= self.tower.maxLevel) {
upgradeCost = 0;
} else if (self.tower.level === self.tower.maxLevel - 1) {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2);
} else {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1));
}
statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s');
buttonText.setText('Upgrade: ' + upgradeCost + ' gold');
var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
var sellValue = Math.floor(totalInvestment * 0.6);
sellButtonText.setText('Sell: +' + sellValue + ' gold');
if (self.tower.level >= self.tower.maxLevel) {
buttonBackground.tint = 0x888888;
buttonText.setText('Max Level');
}
var rangeCircle = null;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
rangeCircle = game.children[i];
break;
}
}
if (rangeCircle) {
var rangeGraphics = rangeCircle.children[0];
rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2;
} else {
var newRangeIndicator = new Container();
newRangeIndicator.isTowerRange = true;
newRangeIndicator.tower = self.tower;
game.addChildAt(newRangeIndicator, 0);
newRangeIndicator.x = self.tower.x;
newRangeIndicator.y = self.tower.y;
var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2;
rangeGraphics.alpha = 0.3;
}
tween(self, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeIn
});
}
});
}
};
sellButton.down = function (x, y, obj) {
var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
var sellValue = getTowerSellValue(totalInvestment);
setGold(gold + sellValue);
var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
// Reset placement tracking for single-use towers when sold
if (self.tower.id === 'king') {
placedTowers.king = false;
} else if (self.tower.id === 'queen') {
placedTowers.queen = false;
}
var gridX = self.tower.gridX;
var gridY = self.tower.gridY;
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cell.type = 0;
var towerIndex = cell.towersInRange.indexOf(self.tower);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
}
}
if (selectedTower === self.tower) {
selectedTower = null;
}
var towerIndex = towers.indexOf(self.tower);
if (towerIndex !== -1) {
towers.splice(towerIndex, 1);
}
towerLayer.removeChild(self.tower);
grid.pathFind();
grid.renderDebug();
self.destroy();
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
game.removeChild(game.children[i]);
break;
}
}
};
closeButton.down = function (x, y, obj) {
hideUpgradeMenu(self);
selectedTower = null;
grid.renderDebug();
};
self.update = function () {
if (self.tower.level >= self.tower.maxLevel) {
if (buttonText.text !== 'Max Level') {
buttonText.setText('Max Level');
buttonBackground.tint = 0x888888;
}
return;
}
// Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.tower.id);
var currentUpgradeCost;
if (self.tower.level >= self.tower.maxLevel) {
currentUpgradeCost = 0;
} else if (self.tower.level === self.tower.maxLevel - 1) {
currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2);
} else {
currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1));
}
var canAfford = gold >= currentUpgradeCost;
buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888;
var newText = 'Upgrade: ' + currentUpgradeCost + ' gold';
if (buttonText.text !== newText) {
buttonText.setText(newText);
}
};
return self;
});
var WaveIndicator = Container.expand(function () {
var self = Container.call(this);
self.gameStarted = false;
self.waveMarkers = [];
self.waveTypes = [];
self.enemyCounts = [];
self.lastBossType = null; // Track the last boss type to avoid repeating
var blockWidth = 400;
var blockHeight = 120;
var currentWaveIndicator = null;
var progressBar = null;
// Create start marker
var startMarker = new Container();
var startBlock = startMarker.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
startBlock.width = blockWidth;
startBlock.height = blockHeight;
startBlock.tint = 0x00AA00;
// Add shadow for start text
var startTextShadow = new Text2("Start Game", {
size: 60,
fill: 0x000000,
weight: 800
});
startTextShadow.anchor.set(0.5, 0.5);
startTextShadow.x = 2;
startTextShadow.y = 2;
startMarker.addChild(startTextShadow);
var startText = new Text2("Start Game", {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
startText.anchor.set(0.5, 0.5);
startMarker.addChild(startText);
// Position start marker in center
startMarker.x = 0;
// Hide start marker by default - will be shown after level/tutorial selection
startMarker.visible = false;
self.addChild(startMarker);
self.waveMarkers.push(startMarker);
startMarker.down = function () {
// Block start game button when tutorial instruction screens are active
var instructions2Active = game.children.some(function (child) {
return child instanceof Instructions2Screen;
});
var instructions3Active = game.children.some(function (child) {
return child instanceof Instructions3Screen;
});
var coldBlastInstructionsActive = game.children.some(function (child) {
return child instanceof ColdBlastInstructionsScreen;
});
var hotPotatoInstructionsActive = game.children.some(function (child) {
return child instanceof HotPotatoInstructionsScreen;
});
var queensWalkInstructionsActive = game.children.some(function (child) {
return child instanceof QueensWalkInstructionsScreen;
});
var kingsGoldInstructionsActive = game.children.some(function (child) {
return child instanceof KingsGoldInstructionsScreen;
});
if (instructions2Active || instructions3Active || coldBlastInstructionsActive || hotPotatoInstructionsActive || queensWalkInstructionsActive || kingsGoldInstructionsActive) {
return; // Prevent start game button from working
}
if (!self.gameStarted) {
// Start game immediately without story screen
self.gameStarted = true;
currentWave = 1; // Start with wave 1, not 0
waveTimer = 0; // Start timer at 0 so first wave starts immediately
waveInProgress = true; // Set wave in progress to trigger spawning
waveSpawned = false; // Ensure enemies will spawn
startBlock.tint = 0x00FF00;
startText.setText("Started!");
startTextShadow.setText("Started!");
// Make sure shadow position remains correct after text change
startTextShadow.x = 2;
startTextShadow.y = 2;
// Show UI labels when normal mode game starts
if (!tutorialMode) {
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
}
// Hide Next Wave button in tutorial mode when starting game
if (tutorialMode && nextWaveButton) {
nextWaveButton.enabled = false;
nextWaveButton.visible = false;
}
// Remove start marker since game has started
self.removeChild(startMarker);
var waveType = self.getWaveTypeName(1);
var enemyCount = self.getEnemyCount(1);
// Only show notification if not in tutorial mode
if (!tutorialMode) {
var notification = game.addChild(new Notification("Wave 1 (" + waveType + " - " + enemyCount + " enemies) incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
// Create current wave indicator and progress bar for wave 1
self.createCurrentWaveIndicator();
}
};
// In tutorial mode, only create indicators for the first 10 waves
var wavesToShow = tutorialMode ? 10 : totalWaves;
// Initialize arrays with the correct size
self.waveTypes = [];
self.enemyCounts = [];
// Prepare wave data
for (var i = 0; i < wavesToShow; i++) {
// --- Begin new unified wave logic ---
var waveType = "normal";
var enemyType = "normal";
var enemyCount = 10;
var isBossWave = (i + 1) % 10 === 0;
// Ensure all types appear in early waves
if (i === 0) {
waveType = "Normal";
enemyType = "normal";
enemyCount = 10;
} else if (i === 1) {
waveType = "Fast";
enemyType = "fast";
enemyCount = 10;
} else if (i === 2) {
waveType = "Immune";
enemyType = "immune";
enemyCount = 10;
} else if (i === 3) {
waveType = "Flying";
enemyType = "flying";
enemyCount = 10;
} else if (i === 4) {
waveType = "Swarm";
enemyType = "swarm";
enemyCount = 30;
} else if (isBossWave) {
// Boss waves: cycle through all boss types, last boss is always flying
var bossTypes = ['normal', 'fast', 'immune', 'flying'];
var bossTypeIndex = Math.floor((i + 1) / 10) - 1;
if (i === wavesToShow - 1) {
// Last boss is always flying
enemyType = 'flying';
waveType = "Boss Flying";
} else {
enemyType = bossTypes[bossTypeIndex % bossTypes.length];
switch (enemyType) {
case 'normal':
waveType = "Boss Normal";
break;
case 'fast':
waveType = "Boss Fast";
break;
case 'immune':
waveType = "Boss Immune";
break;
case 'flying':
waveType = "Boss Flying";
break;
}
}
enemyCount = 1;
} else if ((i + 1) % 5 === 0) {
// Every 5th non-boss wave is fast
waveType = "Fast";
enemyType = "fast";
enemyCount = 10;
} else if ((i + 1) % 4 === 0) {
// Every 4th non-boss wave is immune
waveType = "Immune";
enemyType = "immune";
enemyCount = 10;
} else if ((i + 1) % 7 === 0) {
// Every 7th non-boss wave is flying
waveType = "Flying";
enemyType = "flying";
enemyCount = 10;
} else if ((i + 1) % 3 === 0) {
// Every 3rd non-boss wave is swarm
waveType = "Swarm";
enemyType = "swarm";
enemyCount = 30;
} else {
waveType = "Normal";
enemyType = "normal";
enemyCount = 10;
}
// --- End new unified wave logic ---
// Store the wave type and enemy count
self.waveTypes[i] = enemyType;
self.enemyCounts[i] = enemyCount;
}
// Create current wave indicator with progress bar
self.createCurrentWaveIndicator = function () {
// Remove previous current wave indicator if it exists
if (currentWaveIndicator) {
self.removeChild(currentWaveIndicator);
}
// Only create indicator if we haven't completed all waves
if (currentWave >= totalWaves) {
return;
}
currentWaveIndicator = new Container();
// Main block for current wave
var block = currentWaveIndicator.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
block.width = blockWidth;
block.height = blockHeight;
// Set color based on wave type - use currentWave (not currentWave + 1) since we want the wave that's about to start
var waveType = self.getWaveType(currentWave);
var isBossWave = currentWave % 10 === 0 && currentWave > 0;
switch (waveType) {
case 'normal':
block.tint = 0xAAAAAA;
break;
case 'fast':
block.tint = 0x00AAFF;
break;
case 'immune':
block.tint = 0xAA0000;
break;
case 'flying':
block.tint = 0xFFFF00;
break;
case 'swarm':
block.tint = 0xFF00FF;
break;
default:
block.tint = 0xAAAAAA;
break;
}
// Add boss indicator if needed
if (isBossWave && waveType !== 'swarm') {
var bossIndicator = currentWaveIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
bossIndicator.width = 20;
bossIndicator.height = 20;
bossIndicator.tint = 0xFFD700; // Gold color
bossIndicator.y = -blockHeight / 2 - 12;
}
// Wave type text - use currentWave since that's the wave we're displaying
var displayType = self.getWaveTypeName(currentWave);
var waveTypeText = new Text2(displayType, {
size: 45,
fill: 0xFFFFFF,
weight: 800
});
waveTypeText.anchor.set(0.5, 0.5);
waveTypeText.y = -20;
currentWaveIndicator.addChild(waveTypeText);
// Wave number text - use currentWave since that's the wave we're displaying
var waveNumText = new Text2("Wave " + currentWave, {
size: 38,
fill: 0xFFFFFF,
weight: 600
});
waveNumText.anchor.set(0.5, 0.5);
waveNumText.y = 25;
currentWaveIndicator.addChild(waveNumText);
// Create progress bar background
var progressBg = currentWaveIndicator.attachAsset('notification', {
anchorX: 0,
anchorY: 0.5
});
progressBg.width = blockWidth - 30;
progressBg.height = 12;
progressBg.tint = 0x333333;
progressBg.x = -blockWidth / 2 + 15;
progressBg.y = blockHeight / 2 + 5;
// Create progress bar fill
progressBar = currentWaveIndicator.attachAsset('notification', {
anchorX: 0,
anchorY: 0.5
});
progressBar.width = 0;
progressBar.height = 10;
progressBar.tint = 0x00FF00;
progressBar.x = -blockWidth / 2 + 16;
progressBar.y = blockHeight / 2 + 5;
currentWaveIndicator.x = 0;
self.addChild(currentWaveIndicator);
};
// Get wave type for a specific wave number
self.getWaveType = function (waveNumber) {
if (waveNumber < 1 || waveNumber > totalWaves) {
return "normal";
}
var waveType = self.waveTypes[waveNumber - 1];
return waveType;
};
// Get enemy count for a specific wave number
self.getEnemyCount = function (waveNumber) {
if (waveNumber < 1 || waveNumber > totalWaves) {
return 10;
}
return self.enemyCounts[waveNumber - 1];
};
// Get display name for a wave type
self.getWaveTypeName = function (waveNumber) {
var type = self.getWaveType(waveNumber);
var typeName = type.charAt(0).toUpperCase() + type.slice(1);
// Add boss prefix for boss waves (every 10th wave)
if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') {
typeName = "BOSS";
}
return typeName;
};
self.update = function () {
// Handle wave progression
self.handleWaveProgression = function () {
if (!self.gameStarted) {
return;
}
// Check if instructions2 or instructions3 screen is active and pause wave progression
var instructionsActive = false;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i] instanceof Instructions2Screen || game.children[i] instanceof Instructions3Screen) {
instructionsActive = true;
break;
}
}
// If instructions screen is active, don't advance waves
if (instructionsActive) {
return;
}
if (currentWave <= totalWaves) {
waveTimer++;
if (waveTimer >= nextWaveTime) {
waveTimer = 0;
// Only advance if we haven't reached the end
if (currentWave < totalWaves) {
// In tutorial mode, reduce time by 90% when transitioning from wave 2 to wave 3
if (tutorialMode && currentWave === 2) {
// Reduce the next wave time by 90% for wave 2 to wave 3 transition
var originalNextWaveTime = nextWaveTime;
nextWaveTime = nextWaveTime * 0.1; // 90% reduction
currentWave++;
waveInProgress = true;
waveSpawned = false;
// Reset the wave time back to original after this transition
nextWaveTime = originalNextWaveTime;
} else if (tutorialMode && currentWave === 3) {
// Stop wave progression at wave 3 in tutorial mode
// The instructions2 Continue button will handle advancing to wave 4
return;
} else {
currentWave++;
waveInProgress = true;
waveSpawned = false;
}
// Create new wave indicator for the new current wave
self.createCurrentWaveIndicator();
var waveType = self.getWaveTypeName(currentWave);
var enemyCount = self.getEnemyCount(currentWave);
// Only show notification if not in tutorial mode
if (!tutorialMode) {
// Only show notification if not in tutorial mode
if (!tutorialMode) {
var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
}
}
}
}
};
// Update progress bar if game started and current wave indicator exists
if (self.gameStarted && progressBar && currentWave < totalWaves) {
var progress = waveTimer / nextWaveTime;
var maxWidth = blockWidth - 32;
progressBar.width = maxWidth * progress;
}
self.handleWaveProgression();
};
// Method to show start game button after level/tutorial selection
self.showStartButton = function () {
if (self.waveMarkers && self.waveMarkers.length > 0) {
self.waveMarkers[0].visible = true;
}
};
return self;
});
var WelcomeScreen = Container.expand(function () {
var self = Container.call(this);
// Background image
var backgroundImage = self.attachAsset('welcome_background', {
anchorX: 0.5,
anchorY: 0.5
});
backgroundImage.x = 0;
backgroundImage.y = 0;
backgroundImage.width = 2048;
backgroundImage.height = 2732;
// Main instruction text shadow - positioned at bottom
var instructionShadow = new Text2("Press anywhere to continue", {
size: 80,
fill: 0x000000,
weight: 800
});
instructionShadow.anchor.set(0.5, 1.0);
instructionShadow.x = 4;
instructionShadow.y = 2732 / 2 - 200 + 4;
self.addChild(instructionShadow);
// Main instruction text - positioned at bottom
var instructionText = new Text2("Press anywhere to continue", {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
instructionText.anchor.set(0.5, 1.0);
instructionText.x = 0;
instructionText.y = 2732 / 2 - 200;
self.addChild(instructionText);
// Pulsing animation for the text
self.animationTimer = 0;
self.update = function () {
self.animationTimer += 0.05;
var alpha = 0.7 + 0.3 * Math.sin(self.animationTimer);
instructionText.alpha = alpha;
instructionShadow.alpha = alpha;
};
// Handle any touch/click to continue
self.down = function (x, y, obj) {
// Show UI labels when leaving welcome screen
goldText.visible = true;
goldTextShadow.visible = true;
livesText.visible = true;
livesTextShadow.visible = true;
scoreText.visible = true;
scoreTextShadow.visible = true;
// Remove welcome screen and show story screen
self.destroy();
var storyScreen = new StoryScreen();
storyScreen.x = 2048 / 2;
storyScreen.y = 2732 / 2;
game.addChild(storyScreen);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x333333
});
/****
* Game Code
****/
// Add background image based on selected level
function getBackgroundAssetName() {
// Use tutorial background if in tutorial mode
if (tutorialMode) {
return 'background_tutorial';
}
// Use Queen's Walk specific background in Queen's Walk challenge
if (challengeMode && (challengeName === "Queen's Walk" || window.challengeName === "Queen's Walk")) {
return 'background_queens_walk';
}
switch (selectedLevel) {
case 'garden':
return 'background_garden';
case 'pool':
return 'background_pool';
case 'lobby':
return 'background_lobby';
case 'greathall':
return 'background_greathall';
default:
return 'background_garden';
}
}
function getCellAssetName() {
// Use tutorial cell if in tutorial mode
if (tutorialMode) {
return 'cell_tutorial';
}
// Use Cold Blast specific assets in Cold Blast challenge
if (challengeMode && (challengeName === 'Cold Blast' || window.challengeName === 'Cold Blast')) {
return 'cell_coldblast';
}
// Use Hot Potato specific assets in Hot Potato challenge
if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) {
return 'cell_hotpotato';
}
// Use Queen's Walk specific assets in Queen's Walk challenge
if (challengeMode && (challengeName === "Queen's Walk" || window.challengeName === "Queen's Walk")) {
return 'cell_queens_walk';
}
switch (selectedLevel) {
case 'garden':
return 'cell_garden';
case 'pool':
return 'cell_pool';
case 'lobby':
return 'cell_lobby';
case 'greathall':
return 'cell_greathall';
default:
return 'cell_garden';
}
}
var backgroundImage = game.attachAsset(getBackgroundAssetName(), {
anchorX: 0.5,
anchorY: 0.5
});
backgroundImage.x = 2048 / 2;
backgroundImage.y = 2732 / 2;
// Scale background to cover the entire screen
backgroundImage.width = 2048;
backgroundImage.height = 2732;
var isHidingUpgradeMenu = false;
function hideUpgradeMenu(menu) {
if (isHidingUpgradeMenu) {
return;
}
isHidingUpgradeMenu = true;
tween(menu, {
y: 2732 + 225
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
menu.destroy();
isHidingUpgradeMenu = false;
}
});
}
var CELL_SIZE = 76;
var pathId = 1;
var maxScore = 0;
var enemies = [];
var towers = [];
var bullets = [];
var diamonds = [];
var defenses = [];
var selectedTower = null;
var gold = 80;
var difficultyMultiplier = 1.0; // Will be set by difficulty selector
var selectedLevel = 'garden'; // Default level, will be set by level selector
var tutorialMode = false; // Track if we're in tutorial mode
var instructions2Shown = false; // Track if instructions2 screen has been shown
var challengeMode = false; // Track if we're in challenge mode
var challengeAllowedTowers = []; // Track which towers are allowed in challenge mode
var challengeName = ''; // Track current challenge name
var lives = 20;
var score = 0;
var currentWave = 0;
var totalWaves = tutorialMode ? 10 : 50;
var waveTimer = 0;
var waveInProgress = false;
var waveSpawned = false;
var nextWaveTime = tutorialMode ? 12000 / 2 * 0.25 * 0.7 * 0.7 : 12000 / 2;
var sourceTower = null;
var enemiesToSpawn = 10; // Default number of enemies per wave
var goldTextShadow = new Text2('Gold: ' + gold, {
size: 60,
fill: 0x000000,
weight: 800
});
goldTextShadow.anchor.set(0.5, 0.5);
var goldText = new Text2('Gold: ' + gold, {
size: 60,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
var livesTextShadow = new Text2('Lives: ' + lives, {
size: 60,
fill: 0x000000,
weight: 800
});
livesTextShadow.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 scoreTextShadow = new Text2('Score: ' + score, {
size: 60,
fill: 0x000000,
weight: 800
});
scoreTextShadow.anchor.set(0.5, 0.5);
var scoreText = new Text2('Score: ' + score, {
size: 60,
fill: 0xFF0000,
weight: 800
});
scoreText.anchor.set(0.5, 0.5);
var topMargin = 50;
var centerX = 2048 / 2;
var spacing = 400;
LK.gui.top.addChild(goldTextShadow);
LK.gui.top.addChild(livesTextShadow);
LK.gui.top.addChild(scoreTextShadow);
LK.gui.top.addChild(goldText);
LK.gui.top.addChild(livesText);
LK.gui.top.addChild(scoreText);
// Position shadow texts with offset
goldTextShadow.x = -spacing + 2;
goldTextShadow.y = topMargin + 2;
livesTextShadow.x = 0 + 2;
livesTextShadow.y = topMargin + 2;
scoreTextShadow.x = spacing + 2;
scoreTextShadow.y = topMargin + 2;
// Position main texts
livesText.x = 0;
livesText.y = topMargin;
goldText.x = -spacing;
goldText.y = topMargin;
scoreText.x = spacing;
scoreText.y = topMargin;
function updateUI() {
goldTextShadow.setText('Gold: ' + gold);
livesTextShadow.setText('Lives: ' + lives);
scoreTextShadow.setText('Score: ' + score);
goldText.setText('Gold: ' + gold);
livesText.setText('Lives: ' + lives);
scoreText.setText('Score: ' + score);
}
function setGold(value) {
gold = value;
updateUI();
}
var debugLayer = new Container();
var towerLayer = new Container();
// Create three separate layers for enemy hierarchy
var enemyLayerBottom = new Container(); // For normal enemies
var enemyLayerMiddle = new Container(); // For shadows
var enemyLayerTop = new Container(); // For flying enemies
var enemyLayer = new Container(); // Main container to hold all enemy layers
// Add layers in correct order (bottom first, then middle for shadows, then top)
enemyLayer.addChild(enemyLayerBottom);
enemyLayer.addChild(enemyLayerMiddle);
enemyLayer.addChild(enemyLayerTop);
var grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * 4;
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
// Add pool rectangle image if on pool level
var poolRectangleImage = null;
// Add lobby square images if on lobby level
var lobbySquareImages = [];
if (selectedLevel === 'pool') {
poolRectangleImage = game.attachAsset('pool_rectangle', {
anchorX: 0.5,
anchorY: 0.5
});
// Position over the rectangular pool area (center of grid + pool area center offset)
var gridWidth = 24;
var gridHeight = 35;
var centerX = Math.floor(gridWidth / 2); // Center column (12)
var centerY = Math.floor(gridHeight / 2); // Center row
var rectWidth = 6; // Rectangle width (6 cells)
var rectHeight = 16; // Rectangle height (16 cells)
var rectLeft = centerX - Math.floor(rectWidth / 2);
var rectTop = centerY - Math.floor(rectHeight / 2);
// Calculate the actual center of the rectangular pool area in pixels
var poolCenterX = grid.x + (rectLeft + rectWidth / 2) * CELL_SIZE;
var poolCenterY = grid.y + (rectTop + rectHeight / 2) * CELL_SIZE;
poolRectangleImage.x = poolCenterX - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2;
poolRectangleImage.y = poolCenterY - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2;
// Keep pool rectangle image at normal tint (not gray) so it appears above the gray cells
poolRectangleImage.tint = 0xFFFFFF; // Keep original white/normal tint
// Add to tower layer so it appears above the grid cells
towerLayer.addChild(poolRectangleImage);
}
game.addChild(debugLayer);
game.addChild(towerLayer);
game.addChild(enemyLayer);
var offset = 0;
var towerPreview = new TowerPreview();
game.addChild(towerPreview);
towerPreview.visible = false;
var isDragging = false;
function wouldBlockPath(gridX, gridY) {
var cells = [];
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cells.push({
cell: cell,
originalType: cell.type
});
cell.type = 1;
}
}
}
var blocked = grid.pathFind();
for (var i = 0; i < cells.length; i++) {
cells[i].cell.type = cells[i].originalType;
}
grid.pathFind();
grid.renderDebug();
return blocked;
}
function getTowerCost(towerType) {
var cost = 5;
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;
case 'bomb':
cost = 80;
break;
case 'demolition':
cost = 150;
break;
case 'queen':
cost = 200;
break;
case 'king':
cost = 250;
break;
case 'prince':
cost = 130;
break;
case 'princess':
cost = 130;
break;
}
return cost;
}
function getTowerSellValue(totalValue) {
return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue;
}
function placeTower(gridX, gridY, towerType) {
var towerCost = getTowerCost(towerType);
if (gold >= towerCost) {
// Check if this tower type is allowed in challenge mode
if (challengeMode && challengeAllowedTowers.indexOf(towerType) === -1) {
var notification = game.addChild(new Notification("This tower is not allowed in " + challengeName + " challenge!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
// Check if this is a single-use tower that has already been placed
if (towerType === 'king' && placedTowers.king || towerType === 'queen' && placedTowers.queen) {
var notification = game.addChild(new Notification("This tower can only be placed once!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
// Special handling for Hot Potato challenge
if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) {
if (towerType === 'bomb' || towerType === 'demolition') {
// Create projectile tower directly instead of regular tower
var projectileX = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2;
var projectileY = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2;
var projectile = new ProjectileTower(towerType, projectileX, projectileY);
game.addChild(projectile);
// Launch immediately after short delay
LK.setTimeout(function () {
projectile.launch();
}, 500);
setGold(gold - towerCost);
var notification = game.addChild(new Notification("Capy" + (towerType === 'bomb' ? 'Bomb' : 'Demolition') + " launched!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return true;
}
}
var tower = new Tower(towerType || 'default');
tower.placeOnGrid(gridX, gridY);
towerLayer.addChild(tower);
towers.push(tower);
setGold(gold - towerCost);
// Mark single-use towers as placed
if (towerType === 'king') {
placedTowers.king = true;
} else if (towerType === 'queen') {
placedTowers.queen = true;
}
grid.pathFind();
grid.renderDebug();
return true;
} else {
var notification = game.addChild(new Notification("Not enough gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
game.down = function (x, y, obj) {
var upgradeMenuVisible = game.children.some(function (child) {
return child instanceof UpgradeMenu;
});
if (upgradeMenuVisible) {
return;
}
// Check if level selector or difficulty selector is active
var levelSelectorActive = game.children.some(function (child) {
return child instanceof LevelSelector;
});
var difficultySelectorActive = game.children.some(function (child) {
return child instanceof DifficultySelector;
});
var tutorialIntroActive = game.children.some(function (child) {
return child instanceof TutorialIntroScreen;
});
var instructions2Active = game.children.some(function (child) {
return child instanceof Instructions2Screen;
});
var instructions3Active = game.children.some(function (child) {
return child instanceof Instructions3Screen;
});
var modeSelectorActive = game.children.some(function (child) {
return child instanceof ModeSelector;
});
var storyScreenActive = game.children.some(function (child) {
return child instanceof StoryScreen;
});
// Block all interactions when tutorial instruction screens are active
if (instructions2Active || instructions3Active) {
return;
}
// Block start game button when ModeSelector is active
if (modeSelectorActive && waveIndicator && waveIndicator.waveMarkers && waveIndicator.waveMarkers.length > 0) {
var startMarker = waveIndicator.waveMarkers[0];
if (startMarker && !waveIndicator.gameStarted) {
// Get start marker bounds
var startMarkerX = waveIndicator.x + startMarker.x;
var startMarkerY = waveIndicator.y + startMarker.y;
var startMarkerWidth = 400; // blockWidth from WaveIndicator
var startMarkerHeight = 120; // blockHeight from WaveIndicator
var startMarkerLeft = startMarkerX - startMarkerWidth / 2;
var startMarkerRight = startMarkerX + startMarkerWidth / 2;
var startMarkerTop = startMarkerY - startMarkerHeight / 2;
var startMarkerBottom = startMarkerY + startMarkerHeight / 2;
// Check if click coordinates overlap with start game button
if (x >= startMarkerLeft && x <= startMarkerRight && y >= startMarkerTop && y <= startMarkerBottom) {
return; // Block start game button interaction
}
}
}
// Don't allow tower selection if level selector or difficulty selector is active
if (levelSelectorActive || difficultySelectorActive || tutorialIntroActive || instructions2Active || instructions3Active || modeSelectorActive || storyScreenActive) {
return;
}
// Block all source tower interactions when ModeSelector is active
if (modeSelectorActive || storyScreenActive) {
return;
}
// Check for diamond collection by player touch
if (challengeMode && (challengeName === "Queen's Walk" || window.challengeName === "Queen's Walk")) {
for (var i = diamonds.length - 1; i >= 0; i--) {
var diamond = diamonds[i];
if (!diamond.parent) continue;
// Check if click/touch is close to diamond
var dx = diamond.x - x;
var dy = diamond.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 50) {
// Collection radius of 50 pixels
// Diamond collected by player!
setGold(gold + 25);
var goldIndicator = new GoldIndicator(25, diamond.x, diamond.y);
game.addChild(goldIndicator);
diamond.destroy();
diamonds.splice(i, 1);
return; // Exit early after collecting diamond
}
}
}
// Check for gold coin collection by player touch in King's Gold challenge
if (challengeMode && (challengeName === "King's Gold" || window.challengeName === "King's Gold")) {
for (var i = diamonds.length - 1; i >= 0; i--) {
var goldCoin = diamonds[i];
if (!goldCoin.parent || !goldCoin.isGoldCoin) continue;
// Check if click/touch is close to gold coin
var dx = goldCoin.x - x;
var dy = goldCoin.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 75) {
// Collection radius of 75 pixels for easier collection
// Gold coin collected by player!
setGold(gold + 50);
var goldIndicator = new GoldIndicator(50, goldCoin.x, goldCoin.y);
game.addChild(goldIndicator);
goldCoin.destroy();
diamonds.splice(i, 1);
return; // Exit early after collecting gold coin
}
}
}
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;
towerPreview.towerType = tower.towerType;
towerPreview.updateAppearance();
// Apply the same offset as in move handler to ensure consistency when starting drag
towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5);
break;
}
}
};
game.move = function (x, y, obj) {
// Check if level selector or difficulty selector is active
var levelSelectorActive = game.children.some(function (child) {
return child instanceof LevelSelector;
});
var difficultySelectorActive = game.children.some(function (child) {
return child instanceof DifficultySelector;
});
var tutorialIntroActive = game.children.some(function (child) {
return child instanceof TutorialIntroScreen;
});
var instructions2Active = game.children.some(function (child) {
return child instanceof Instructions2Screen;
});
var instructions3Active = game.children.some(function (child) {
return child instanceof Instructions3Screen;
});
var modeSelectorActive = game.children.some(function (child) {
return child instanceof ModeSelector;
});
var storyScreenActive = game.children.some(function (child) {
return child instanceof StoryScreen;
});
// Don't allow tower dragging if level selector, difficulty selector, or tutorial instruction screens are active
if (levelSelectorActive || difficultySelectorActive || tutorialIntroActive || instructions2Active || instructions3Active || modeSelectorActive || storyScreenActive) {
return;
}
if (isDragging) {
// Shift the y position upward by 1.5 tiles to show preview above finger
towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5);
}
};
game.up = function (x, y, obj) {
var clickedOnTower = false;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var towerLeft = tower.x - tower.width / 2;
var towerRight = tower.x + tower.width / 2;
var towerTop = tower.y - tower.height / 2;
var towerBottom = tower.y + tower.height / 2;
if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) {
clickedOnTower = true;
break;
}
}
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) {
var clickedOnMenu = false;
for (var i = 0; i < upgradeMenus.length; i++) {
var menu = upgradeMenus[i];
var menuWidth = 2048;
var menuHeight = 450;
var menuLeft = menu.x - menuWidth / 2;
var menuRight = menu.x + menuWidth / 2;
var menuTop = menu.y - menuHeight / 2;
var menuBottom = menu.y + menuHeight / 2;
if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) {
clickedOnMenu = true;
break;
}
}
if (!clickedOnMenu) {
for (var i = 0; i < upgradeMenus.length; i++) {
var menu = upgradeMenus[i];
hideUpgradeMenu(menu);
}
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange) {
game.removeChild(game.children[i]);
}
}
selectedTower = null;
grid.renderDebug();
}
}
if (isDragging) {
// Check if level selector or difficulty selector is active
var levelSelectorActive = game.children.some(function (child) {
return child instanceof LevelSelector;
});
var difficultySelectorActive = game.children.some(function (child) {
return child instanceof DifficultySelector;
});
var tutorialIntroActive = game.children.some(function (child) {
return child instanceof TutorialIntroScreen;
});
var instructions2Active = game.children.some(function (child) {
return child instanceof Instructions2Screen;
});
var instructions3Active = game.children.some(function (child) {
return child instanceof Instructions3Screen;
});
var modeSelectorActive = game.children.some(function (child) {
return child instanceof ModeSelector;
});
var storyScreenActive = game.children.some(function (child) {
return child instanceof StoryScreen;
});
isDragging = false;
// Don't allow tower placement if level selector, difficulty selector, or tutorial instruction screens are active
if (levelSelectorActive || difficultySelectorActive || tutorialIntroActive || instructions2Active || instructions3Active || modeSelectorActive || storyScreenActive) {
towerPreview.visible = false;
return;
}
if (towerPreview.canPlace) {
if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) {
placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType);
} else {
var notification = game.addChild(new Notification("Tower would block the path!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
} else if (towerPreview.blockedByEnemy) {
var notification = game.addChild(new Notification("Cannot build: Enemy in the way!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
} else if (towerPreview.visible) {
var notification = game.addChild(new Notification("Cannot build here!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
towerPreview.visible = false;
if (isDragging) {
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
for (var i = 0; i < upgradeMenus.length; i++) {
upgradeMenus[i].destroy();
}
}
}
};
var waveIndicator = new WaveIndicator();
waveIndicator.x = 2048 / 2;
waveIndicator.y = 2732 - 80;
game.addChild(waveIndicator);
var nextWaveButtonContainer = new Container();
var nextWaveButton = new NextWaveButton();
nextWaveButton.x = 2048 - 200;
nextWaveButton.y = 2732 - 100 + 20;
// Hide Next Wave button in tutorial mode until instructions2 continue is pressed
if (tutorialMode) {
nextWaveButton.enabled = false;
nextWaveButton.visible = false;
}
nextWaveButtonContainer.addChild(nextWaveButton);
game.addChild(nextWaveButtonContainer);
// Velocity control system
var gameSpeed = 1.0;
var speedMultipliers = [1.0, 1.3, 1.5, 2.0];
var currentSpeedIndex = 0;
// Track tower placement for single-use towers
var placedTowers = {
king: false,
queen: false
};
var velocityButton = new Container();
var velocityButtonBackground = velocityButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
velocityButtonBackground.width = 280;
velocityButtonBackground.height = 120;
velocityButtonBackground.tint = 0x00AA88;
var velocityButtonText = new Text2("Velocity", {
size: 45,
fill: 0xFFFFFF,
weight: 800
});
velocityButtonText.anchor.set(0.5, 0.5);
velocityButtonText.y = -15; // Move velocity text up slightly
var velocitySpeedText = new Text2("x" + gameSpeed.toFixed(1), {
size: 40,
fill: 0xFFD700,
weight: 800
});
velocitySpeedText.anchor.set(0.5, 0.5);
velocitySpeedText.y = 32; // Increase spacing by 2 pixels (was 30, now 32)
velocityButton.addChild(velocityButtonText);
velocityButton.addChild(velocitySpeedText);
velocityButton.x = 200;
velocityButton.y = 2732 - 80; // Move button lower (was -120, now -80)
velocityButton.visible = false;
velocityButton.enabled = false;
game.addChild(velocityButton);
var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison', 'bomb', 'queen', 'king'];
var sourceTowers = [];
var towerSpacing = 220; // Adjusted spacing for 9 towers
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);
}
sourceTower = null;
enemiesToSpawn = 10;
velocityButton.down = function () {
// Block velocity button when ModeSelector or tutorial instruction screens are active
var modeSelectorActive = game.children.some(function (child) {
return child instanceof ModeSelector;
});
var instructions2Active = game.children.some(function (child) {
return child instanceof Instructions2Screen;
});
var instructions3Active = game.children.some(function (child) {
return child instanceof Instructions3Screen;
});
if (modeSelectorActive || instructions2Active || instructions3Active) {
return;
}
if (!velocityButton.enabled) {
return;
}
currentSpeedIndex = (currentSpeedIndex + 1) % speedMultipliers.length;
gameSpeed = speedMultipliers[currentSpeedIndex];
velocitySpeedText.setText("x" + gameSpeed.toFixed(1));
// Visual feedback
tween(velocityButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(velocityButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
var notification = game.addChild(new Notification("Game speed set to x" + gameSpeed.toFixed(1) + "!"));
notification.x = 2048 / 2;
notification.y = grid.height - 100;
};
velocityButton.update = function () {
if (waveIndicator && waveIndicator.gameStarted) {
velocityButton.enabled = true;
velocityButton.visible = true;
velocityButton.alpha = 1;
} else {
velocityButton.enabled = false;
velocityButton.visible = false;
velocityButton.alpha = 0.7;
}
};
// Show welcome screen at start
var welcomeScreen = new WelcomeScreen();
welcomeScreen.x = 2048 / 2;
welcomeScreen.y = 2732 / 2;
game.addChild(welcomeScreen);
// Start background music
LK.playMusic('Song', {
loop: true
});
// Hide UI labels from the start until start game is pressed
goldText.visible = false;
goldTextShadow.visible = false;
livesText.visible = false;
livesTextShadow.visible = false;
scoreText.visible = false;
scoreTextShadow.visible = false;
game.update = function () {
// Check if game is paused by instructions2 or instructions3 screen
var instructionsActive = false;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i] instanceof Instructions2Screen || game.children[i] instanceof Instructions3Screen) {
instructionsActive = true;
break;
}
}
// If instructions screen is active and pausing, skip all game logic
if (instructionsActive) {
return;
}
// Apply game speed to wave progression
if (waveInProgress) {
if (!waveSpawned) {
waveSpawned = true;
// Get wave type and enemy count from the wave indicator
var waveType = waveIndicator.getWaveType(currentWave);
var enemyCount = waveIndicator.getEnemyCount(currentWave);
// Check if this is a boss wave
var isBossWave = currentWave % 10 === 0 && currentWave > 0;
if (isBossWave && waveType !== 'swarm') {
// Boss waves have just 1 enemy regardless of what the wave indicator says
enemyCount = 1;
// Show Instructions3Screen for boss wave (tutorial mode only)
if (tutorialMode) {
var instructions3Screen = new Instructions3Screen();
instructions3Screen.x = 2048 / 2;
instructions3Screen.y = 2732 / 2;
game.addChild(instructions3Screen);
}
// Show boss announcement
var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️"));
notification.x = 2048 / 2;
notification.y = grid.height - 200;
}
// In tutorial mode, limit to 1 enemy per wave for waves 1-3, then 3 enemies for wave 4 onwards, except for boss waves which should remain at 1
if (tutorialMode) {
// Boss waves should always have 1 enemy, even in tutorial mode
if (!(isBossWave && waveType !== 'swarm')) {
enemyCount = currentWave >= 4 ? 3 : 1;
}
}
// Spawn the appropriate number of enemies
for (var i = 0; i < enemyCount; i++) {
var enemy = new Enemy(waveType);
// Add enemy to the appropriate layer based on type
if (enemy.isFlying) {
// Add flying enemy to the top layer
enemyLayerTop.addChild(enemy);
// If it's a flying enemy, add its shadow to the middle layer
if (enemy.shadow) {
enemyLayerMiddle.addChild(enemy.shadow);
}
} else {
// Add normal/ground enemies to the bottom layer
enemyLayerBottom.addChild(enemy);
}
// Scale difficulty with wave number but don't apply to boss
// as bosses already have their health multiplier
// Use exponential scaling for health
var healthMultiplier = Math.pow(1.12, currentWave); // ~20% increase per wave
// Apply difficulty multiplier
enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier * difficultyMultiplier);
enemy.health = enemy.maxHealth;
// Increment speed slightly with wave number
//enemy.speed = enemy.speed + currentWave * 0.002;
// Check if Hot Potato challenge is active for different spawning behavior
var isHotPotatoChallenge = challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato');
var gridWidth = 24;
var midPoint = Math.floor(gridWidth / 2); // 12
if (isHotPotatoChallenge) {
// Hot Potato: spawn across entire top width (excluding walls at x=0 and x=23)
var availableColumns = [];
for (var col = 1; col < gridWidth - 1; col++) {
// x from 1 to 22
var columnOccupied = false;
// Check if any enemy is already in this column but not yet in view
for (var e = 0; e < enemies.length; e++) {
if (enemies[e].cellX === col && enemies[e].currentCellY < 4) {
columnOccupied = true;
break;
}
}
if (!columnOccupied) {
availableColumns.push(col);
}
}
// If all columns are occupied, use random across entire width
var spawnX;
if (availableColumns.length > 0) {
// Choose a random unoccupied column
spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)];
} else {
// Fallback to random across entire width if all columns are occupied
spawnX = 1 + Math.floor(Math.random() * (gridWidth - 2)); // x from 1 to 22
}
} else {
// Normal behavior: spawn in middle 6 tiles
// Find a column that isn't occupied by another enemy that's not yet in view
var availableColumns = [];
for (var col = midPoint - 3; col < midPoint + 3; col++) {
var columnOccupied = false;
// Check if any enemy is already in this column but not yet in view
for (var e = 0; e < enemies.length; e++) {
if (enemies[e].cellX === col && enemies[e].currentCellY < 4) {
columnOccupied = true;
break;
}
}
if (!columnOccupied) {
availableColumns.push(col);
}
}
// If all columns are occupied, use original random method
var spawnX;
if (availableColumns.length > 0) {
// Choose a random unoccupied column
spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)];
} else {
// Fallback to random if all columns are occupied
spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 9 to 14
}
}
var spawnY = -1 - Math.random() * 5; // Random distance above the grid for spreading
enemy.cellX = spawnX;
enemy.cellY = 5; // Position after entry
enemy.currentCellX = spawnX;
enemy.currentCellY = spawnY;
enemy.waveNumber = currentWave;
// Apply speed multiplier to enemy
enemy.speed = enemy.speed * gameSpeed;
enemies.push(enemy);
}
}
var currentWaveEnemiesRemaining = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].waveNumber === currentWave) {
currentWaveEnemiesRemaining = true;
break;
}
}
if (waveSpawned && !currentWaveEnemiesRemaining) {
waveInProgress = false;
waveSpawned = false;
// Add 250 gold for completing a wave in Hot Potato challenge
if (challengeMode && (challengeName === 'Hot Potato' || window.challengeName === 'Hot Potato')) {
setGold(gold + 250);
var notification = game.addChild(new Notification("Wave completed! +250 gold bonus!"));
notification.x = 2048 / 2;
notification.y = grid.height - 100;
}
}
}
// Apply speed multiplier to existing enemies
for (var a = enemies.length - 1; a >= 0; a--) {
var enemy = enemies[a];
// Apply speed multiplier to enemy movement (but only update if speed has changed)
if (enemy.lastGameSpeed === undefined || enemy.lastGameSpeed !== gameSpeed) {
// Reset speed to base and reapply multiplier
if (enemy.lastGameSpeed !== undefined) {
enemy.speed = enemy.speed / enemy.lastGameSpeed;
}
enemy.speed = enemy.speed * gameSpeed;
enemy.lastGameSpeed = gameSpeed;
}
if (enemy.health <= 0) {
for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) {
var bullet = enemy.bulletsTargetingThis[i];
bullet.targetEnemy = null;
}
// Check if this is wave 3 and we haven't shown instructions2 yet (tutorial mode only)
if (tutorialMode && enemy.waveNumber === 3 && !instructions2Shown) {
// Show instructions2 screen
var instructions2Screen = new Instructions2Screen();
instructions2Screen.x = 2048 / 2;
instructions2Screen.y = 2732 / 2;
game.addChild(instructions2Screen);
instructions2Shown = true;
}
// 10% chance to drop a diamond in Queen's Walk challenge
if (challengeMode && (challengeName === "Queen's Walk" || window.challengeName === "Queen's Walk")) {
if (Math.random() < 0.1) {
var diamond = new Diamond(enemy.x, enemy.y);
game.addChild(diamond);
diamonds.push(diamond);
}
}
// 30% chance to drop a static gold coin in King's Gold challenge
if (challengeMode && (challengeName === "King's Gold" || window.challengeName === "King's Gold")) {
if (Math.random() < 0.3) {
// Create static gold coin
var goldCoin = game.attachAsset('gold_coin_kings_gold', {
anchorX: 0.5,
anchorY: 0.5
});
goldCoin.x = enemy.x;
goldCoin.y = enemy.y;
goldCoin.width = 100;
goldCoin.height = 100;
goldCoin.lifetime = 0;
goldCoin.isGoldCoin = true; // Mark as gold coin for identification
// Gold coin is now static and doesn't spin
diamonds.push(goldCoin);
}
}
// Boss enemies give more gold and score
var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5);
// Triple gold for Cold Blast and Hot Potatoe challenges
if (challengeMode && (challengeName === 'Cold Blast' || challengeName === 'Hot Potatoe')) {
goldEarned *= 3;
}
var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y);
game.addChild(goldIndicator);
setGold(gold + goldEarned);
// Give more score for defeating a boss
var scoreValue = enemy.isBoss ? 100 : 5;
score += scoreValue;
// Add a notification for boss defeat
if (enemy.isBoss) {
var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
// In tutorial mode, show tutorial complete screen
if (tutorialMode) {
var tutorialCompleteScreen = new TutorialCompleteScreen();
tutorialCompleteScreen.x = 2048 / 2;
tutorialCompleteScreen.y = 2732 / 2;
game.addChild(tutorialCompleteScreen);
}
}
updateUI();
// Clean up shadow if it's a flying enemy
if (enemy.isFlying && enemy.shadow) {
enemyLayerMiddle.removeChild(enemy.shadow);
enemy.shadow = null;
}
// Remove enemy from the appropriate layer
if (enemy.isFlying) {
enemyLayerTop.removeChild(enemy);
} else {
enemyLayerBottom.removeChild(enemy);
}
enemies.splice(a, 1);
continue;
}
if (grid.updateEnemy(enemy)) {
// Clean up shadow if it's a flying enemy
if (enemy.isFlying && enemy.shadow) {
enemyLayerMiddle.removeChild(enemy.shadow);
enemy.shadow = null;
}
// Remove enemy from the appropriate layer
if (enemy.isFlying) {
enemyLayerTop.removeChild(enemy);
} else {
enemyLayerBottom.removeChild(enemy);
}
enemies.splice(a, 1);
lives = Math.max(0, lives - 1);
updateUI();
if (lives <= 0) {
LK.showGameOver();
}
}
}
for (var i = bullets.length - 1; i >= 0; i--) {
if (!bullets[i].parent) {
if (bullets[i].targetEnemy) {
var targetEnemy = bullets[i].targetEnemy;
var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]);
if (bulletIndex !== -1) {
targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1);
}
}
bullets.splice(i, 1);
}
}
if (towerPreview.visible) {
towerPreview.checkPlacement();
}
// Update and handle diamond and gold coin collection
for (var i = diamonds.length - 1; i >= 0; i--) {
var diamond = diamonds[i];
// Check if diamond still exists in game
if (!diamond.parent) {
diamonds.splice(i, 1);
continue;
}
// Check if any tower is close enough to collect the diamond/gold coin
var collected = false;
for (var t = 0; t < towers.length; t++) {
var tower = towers[t];
var dx = tower.x - diamond.x;
var dy = tower.y - diamond.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= CELL_SIZE * 2) {
// Diamond or gold coin collected!
var goldValue = diamond.isGoldCoin ? 50 : 25;
setGold(gold + goldValue);
var goldIndicator = new GoldIndicator(goldValue, diamond.x, diamond.y);
game.addChild(goldIndicator);
diamond.destroy();
diamonds.splice(i, 1);
collected = true;
break;
}
}
// Auto-collect after 10 seconds if not collected
if (!collected) {
diamond.lifetime = (diamond.lifetime || 0) + 1;
if (diamond.lifetime > 600) {
// 10 seconds at 60fps
var goldValue = diamond.isGoldCoin ? 50 : 25;
setGold(gold + goldValue);
var goldIndicator = new GoldIndicator(goldValue, diamond.x, diamond.y);
game.addChild(goldIndicator);
diamond.destroy();
diamonds.splice(i, 1);
}
}
}
// Only end the game if not in tutorial mode, or if in tutorial mode but boss hasn't been defeated yet
if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress && !tutorialMode) {
LK.showYouWin();
}
};
White circle with black outline. Blue background.. In-Game asset. 2d. High contrast. No shadows
Quitale el fondo blanco
Cambia la resortera por una espada.
Cambia la resortera por un arco. Añade una bufanda y has el casco mas pequeño
Elimina la espada. Añade una catapulta en la parte de atras de la cabeza con una piedra. La catapulta debe de tener la piedra al lado izquierdo simulando se va a lanzar a la derecha. Haz la cabeza un poco mas pequeña.
Elimina la resortera. Añadele un arco y una fleca con la punta de hielo. Dale unos colores mas frios a la ilsutracion. Adorna el casco con piedras birllantes celestes.
Elimina la resortera. Añadele un arco y una flecha con la punta verde que suelta un humo verde. Adorna el casco con piedras birllantes verdes.
Quitale el casco y la resortera. Utiliza una paleta de colores grises. Has una expresion seria. Ojos rojos sin pupila. Utiliza los rasgos y formas de un capibara.
Agregale un sombrero rojo de robinhood, cambia la explesion a sonrisa malvada sin mostrar los dientes. Recuerda usar los rasgos y formas de un capibara.
Ponle un escudo de metal delante de la cabeza. Tapando la boca y el cuello. Recuerda usar los rasgos y formas de un capibara. El escudo debe de ser de un plomo claro
Has este capibara mas joven y pequeño. Recuerda usar los rasgos y formas de un capibara. Añadele una sonrisa sin mostrar los dientes. Sin fondo
Textura de cesped. Recuerda la escala 1:1. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows
Deja plano. Que no se nore las lineas de la cuadricula
Quitale la espada. Ahora has una bomba con la forma del capibara y su cabeza.
Haz aun mas pequeñas a las piedras, mucho mas
Por favor haz la textura de piedras cuadradas desde arriba, las piedras color plomo
Haz mas pequeñas las piedras mucho mas
Mejora la paleta a un gris mas claro, no cambies nada mas
Quitale la resortera y el casco. Añadele una corona de reina con gemas. Añade tambein un arco dorado y una fleca con una punta morada brillante. Dame la imagen sin fondo. Ponle pestañas pequeñas en los ojos.
Quitale la resortera y el casco. Añadele una corona de rey con gemas. Añade tambein una capa roja. Agrega en la parte de atras un cañon. Dame la imagen sin fondo.
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises.
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo. Tambien quita el arco y la flecha
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo, Quita el cañon de fondo
Utilizando este ejemplo. Hay un castillo al fondo. Al frente esta el rey capybara y la reina capybara tranquilos y los capibaras habitantes estan rodeandolos felices. Elimina el cañon de detras. Agrega el titulo de juego en la parte superior: Capybara Castle. In-Game asset. 2d. High contrast.
Elimina el cañon y ponle un cetro de rey. Dame sin fondo.
Has mas verde el centro del cesped.
Elimina arco y la flecha, ponle un cetro de reina con gemas. Dame sin fondo.
Piscina vista desde arriba con lineas suaves. un borde celeste igual al agua de proporciones 4:16. In-Game asset. 2d. High contrast. No shadows
Ponle un gorro helicoptero dorado, no le quites las gafas.
Utilizando este rey. Pon de fondo un castillo. Al rey feliz con su reina. Los capybaras subditos alrededor felices.
Ponles sonrisas malvadas a los capibaras, recuerda que los ojos son rojos y usa lineas mas gruesas.
El rey ve al fondo del bosque a los capybaras negros. El esta de espaldas y a su lado sale un signo de exclamacion.
El rey orgulloso y confiado usando su cetro manda a defender desde el castillo.
El rey tambien esta listo para pelear en el castillo de fondo.
Los capybaras estan listo para defender el castillo.
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises azulados. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo
Haz los bloques de hielo ya no de piedra.
Transformalo todo en metal. Recuerda los rasgos de capibara. Hazlo redondo y sin fondo.
Agrega manchas de explosiones.
Agrega unas flores sobre los arbustos.
Quitale el casco y ponle una tiara de princesa. Has el arco y la flecha de oro. Recuerda que es un capybara.
Has el casco dorado con gemas. Quitale la espada. Has un arco dorado y ponle 3 flechas doradas. Damela si fondo.