Code edit (1 edits merged)
Please save this source code
Code edit (7 edits merged)
Please save this source code
User prompt
Las balas de las torretas que se crean cuando ya existe la ruleta aparecen sobre ella, necesito que la ruleta siempre aparezca sobre todos los elemetnos incluso si estos fueron creados posterior a que ya existe la ruleta
User prompt
Las flechas que disparan las torretas aparecen sobre la ruleta.
User prompt
Las flechas que disparan las torretas aparecen sobre el asset de la torreta y no deberian
User prompt
Las balas que disparan las torretas aparecen sobre la ruleta
User prompt
Las flechas siguen apareciendo sobre la ruleta
User prompt
Las flechas se pueden ver sobre la ruleta, la ruleta debería verse sobre todos los elementos.
User prompt
Si activo el Skip tutorial despues de que esto ya se activo if (!speedBoostButton.isActive) { tutorialAllowForward = true; showTutorialOverlay(tutorialForwardIconArea, "Press the Forward Icon to Speed up the Game", "down"); } }, 5000); sale aunque ya no sea el tutorial
User prompt
Cuando intentas incrementar la apuesta pero no tienes al menos 10 oros imprime un mensaje que diga "You need at least 10 gold to place a bet"
Code edit (5 edits merged)
Please save this source code
User prompt
Quita todo esto. function highlightGoldButton() { // Add a highlight effect to the red button (e.g. flash or overlay) if (!goldButtonHighlighted && redButton) { goldButtonHighlighted = true; // Simple highlight: flash the button yellow for 1s, then back to normal, repeat var originalTint = 0xFFFFFF; var highlightTint = 0xFFFFFF; if (redButton.children[0]) { originalTint = redButton.children[0].tint; redButton.children[0].tint = highlightTint; tween(redButton.children[0], { tint: originalTint }, { duration: 500, onFinish: function onFinish() { goldButtonHighlighted = false; } }); } } } Solo necesito que se muestra el boton y la flecha sin que esten cambiando el color o el alfa ni nada.
User prompt
En el paso 7 del tutorial cuando se elimina al enemigo volador, la fecha sigue parpadeando
User prompt
En el paso 9 del tutorial la flecha parpadea me imagino que es parte del higlited, no quiero que parpade, solo que este ahi quieta
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.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 }); bulletGraphics.scaleX = 0.5; bulletGraphics.scaleY = 0.5; // --- NUEVO: Calcular radio de colisión basado en tamaño visual --- var bulletRadius = bulletGraphics.width * bulletGraphics.scaleX / 2; // Get actual enemy visual size (enemies are scaled to 50%) var enemyGraphics = self.targetEnemy.children[0]; // First child should be the enemy graphics var enemyRadius = enemyGraphics ? enemyGraphics.width * enemyGraphics.scaleX / 2 : 35; // Dirección fija al momento de disparar if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); self.vx = dx / distance * self.speed; self.vy = dy / distance * self.speed; } else { self.vx = 0; self.vy = 0; } self.update = function () { if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } // Convert enemy position from its scaled layer to game coordinates var enemyGameX = self.targetEnemy.x * gameScale + enemyLayer.x; var enemyGameY = self.targetEnemy.y * gameScale + enemyLayer.y; var dx = enemyGameX - self.x; var dy = enemyGameY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // --- MODIFICADO: comparación de colisión considerando radios --- if (distance < bulletRadius + enemyRadius) { self.targetEnemy.health -= self.damage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } else { self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; } self.destroy(); } else { // Actualizar velocidad para rastrear objetivo var effectiveSpeed = self.speed; if (speedBoostActive) { effectiveSpeed = self.speed * 3; } self.vx = dx / distance * effectiveSpeed; self.vy = dy / distance * effectiveSpeed; // Rotate bullet to point toward target enemy var angle = Math.atan2(self.vy, self.vx); bulletGraphics.rotation = angle; self.x += self.vx; self.y += self.vy; } }; return self; }); var DebugCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = self.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5 }); cellGraphics.tint = Math.random() * 0xffffff; var debugArrows = []; var numberLabel = new Text2('0', { size: 30, fill: 0xFFFFFF, weight: 800 }); numberLabel.anchor.set(.5, .5); self.addChild(numberLabel); // Add cell number label (small red text) var cellNumberLabel = new Text2('1', { size: 20, fill: 0xFF0000, weight: 800 }); cellNumberLabel.anchor.set(0.5, 0.5); self.addChild(cellNumberLabel); self.setCellNumber = function (number) { cellNumberLabel.setText(number.toString()); }; self.update = function () {}; self.down = function () { return; if (self.cell.type == 0 || self.cell.type == 1) { self.cell.type = self.cell.type == 1 ? 0 : 1; if (grid.pathFind()) { self.cell.type = self.cell.type == 1 ? 0 : 1; grid.pathFind(); var notification = game.addChild(new Notification("Path is blocked!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } grid.renderDebug(); } }; self.removeArrows = function () { while (debugArrows.length) { self.removeChild(debugArrows.pop()); } }; self.render = function (data) { switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); cellGraphics.tint = 0x880000; return; } numberLabel.visible = true; var tint = Math.floor(data.score / maxScore * 0x88); var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; cellGraphics.tint = 0x0088ff; } else { cellGraphics.tint = 0x88 - tint << 8 | tint; } while (debugArrows.length > data.targets.length) { self.removeChild(debugArrows.pop()); } for (var a = 0; a < data.targets.length; a++) { var destination = data.targets[a]; var ox = destination.x - data.x; var oy = destination.y - data.y; var angle = Math.atan2(oy, ox); if (!debugArrows[a]) { debugArrows[a] = LK.getAsset('arrow', { anchorX: -.5, anchorY: 0.5 }); debugArrows[a].alpha = .5; self.addChildAt(debugArrows[a], 1); } debugArrows[a].rotation = angle; } break; } case 1: { self.removeArrows(); cellGraphics.tint = 0xaaaaaa; numberLabel.visible = false; break; } case 3: { self.removeArrows(); cellGraphics.tint = 0x008800; numberLabel.visible = false; break; } } numberLabel.setText(Math.floor(data.score / 1000) / 10); }; }); var Door = Container.expand(function (gridX, gridY) { var self = Container.call(this); self.gridX = gridX; self.gridY = gridY; self.enemiesAttacking = []; self.lastHealthLossTime = 0; self.isDestroyed = false; self.originalDoorAsset = selectedDoorAsset; // Store original door asset for this door self.destroyedAsset = 'Destruida' + (Math.floor(Math.random() * 4) + 1); // Random destroyed asset var doorGraphics = self.attachAsset(self.originalDoorAsset, { anchorX: 0.5, anchorY: 0.5 }); self.updateHealthDisplay = function () { // Individual doors no longer have health displays // The global health bar will be updated instead }; self.updateDoorVisual = function () { // Check if door should be destroyed (when global health is 0) var shouldBeDestroyed = globalDoorHealth <= 0; if (shouldBeDestroyed && !self.isDestroyed) { // Change to destroyed visual self.isDestroyed = true; self.removeChild(self.children[0]); // Remove current graphics var destroyedGraphics = self.attachAsset(self.destroyedAsset, { anchorX: 0.5, anchorY: 0.5 }); } else if (!shouldBeDestroyed && self.isDestroyed) { // Change back to original door visual self.isDestroyed = false; self.removeChild(self.children[0]); // Remove destroyed graphics var doorGraphics = self.attachAsset(self.originalDoorAsset, { anchorX: 0.5, anchorY: 0.5 }); } }; self.takeDamage = function () { var effectiveDamageRate = 60; if (speedBoostActive) { effectiveDamageRate = Math.ceil(60 / 3); } if (self.enemiesAttacking.length > 0 && LK.ticks - self.lastHealthLossTime >= effectiveDamageRate) { globalDoorHealth = Math.max(0, globalDoorHealth - self.enemiesAttacking.length); self.lastHealthLossTime = LK.ticks; // Play door hit sound LK.getSound('doorHitSound').play(); // Update global door health bar updateGlobalDoorHealthBar(); // Update door visual state self.updateDoorVisual(); } }; self.repair = function () { if (gold >= 1 && globalDoorHealth < globalDoorMaxHealth) { setGold(gold - 1); globalDoorHealth = Math.min(globalDoorMaxHealth, globalDoorHealth + 1); // Update global door health bar updateGlobalDoorHealthBar(); // Update door visual state self.updateDoorVisual(); return true; } return false; }; self.addAttackingEnemy = function (enemy) { if (self.enemiesAttacking.indexOf(enemy) === -1) { self.enemiesAttacking.push(enemy); } }; self.removeAttackingEnemy = function (enemy) { var index = self.enemiesAttacking.indexOf(enemy); if (index !== -1) { self.enemiesAttacking.splice(index, 1); } }; self.update = function () { self.takeDamage(); }; self.down = function (x, y, obj) { // Prevent door repair until tutorial step 9 or later if (tutorialActive && tutorialStep < 9) { var notification = game.addChild(new Notification("Cannot repair door yet!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (self.repair()) { var notification = game.addChild(new Notification("Door repaired! (-1 gold)")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (gold < 1) { var notification = game.addChild(new Notification("Not enough gold to repair!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else { var notification = game.addChild(new Notification("Door is at full health!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } }; 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; self.animationFrame = 0; self.animationTimer = 0; self.animationSpeed = 15; self.currentAssetIndex = 0; switch (self.type) { case 'fast': self.speed *= 2; self.maxHealth = 100; self.enemyAssets = ['EnemyFast1', 'EnemyFast2', 'EnemyFast3']; break; case 'tank': self.isImmune = true; self.maxHealth = 240; self.speed *= 0.5; // Reduce speed by 0.5x self.enemyAssets = ['EnemyImmune1', 'EnemyImmune2', 'EnemyImmune3']; break; case 'flying': self.isFlying = true; self.maxHealth = 80; self.enemyAssets = ['EnemyFlying1', 'EnemyFlying2', 'EnemyFlying3']; break; case 'swarm': self.maxHealth = 15; self.enemyAssets = ['EnemySwarm1', 'EnemySwarm2', 'EnemySwarm3']; break; case 'normal': default: self.enemyAssets = ['Enemy1', 'Enemy2', 'Enemy3']; break; } // Apply tutorial speed boost for normal enemies only if (tutorialActive && self.type === 'normal') { self.speed *= 2; // Double speed during tutorial } if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') { self.isBoss = true; self.maxHealth *= 20; self.speed = self.speed * 0.7; // Override enemy assets for boss enemies based on wave number if (currentWave === 10) { // Wave 10: Boss Normal self.enemyAssets = ['Enemy1', 'Enemy2', 'Enemy3']; } else if (currentWave === 20) { // Wave 20: Boss Fast self.speed *= 2; self.maxHealth = 100; self.enemyAssets = ['EnemyFast1', 'EnemyFast2', 'EnemyFast3']; } else if (currentWave === 30) { // Wave 30: Boss Flying self.isFlying = true; self.maxHealth = 80; self.enemyAssets = ['EnemyFlying1', 'EnemyFlying2', 'EnemyFlying3']; } else if (currentWave === 40) { // Wave 40: Boss Tank (should NOT be flying) self.isImmune = true; self.isFlying = false; // Explicitly set to false to override any flying behavior self.maxHealth = 240; self.speed *= 0.5; // Reduce speed by 0.5x self.enemyAssets = ['EnemyImmune1', 'EnemyImmune2', 'EnemyImmune3']; } else if (currentWave === 50) { // Wave 50: Boss Swarm - Large bosses with high health self.maxHealth = 500; // Much higher health for wave 50 bosses self.enemyAssets = ['EnemyBoss1', 'EnemyBoss2', 'EnemyBoss3']; // Use boss sprites instead of swarm // Force boss scaling for wave 50 self.isBoss = true; } else { // Default boss sprites for other boss waves self.enemyAssets = ['EnemyBoss1', 'EnemyBoss2', 'EnemyBoss3']; } } self.health = self.maxHealth; // Carga múltiples sprites y alterna visibilidad sin parpadeo self.enemyGraphicsList = []; self.enemyAssets.forEach(function (assetName, index) { var gfx = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); gfx.visible = index === 0; // Solo el primero visible gfx.scaleX = self.isBoss ? 0.9 : 0.5; gfx.scaleY = self.isBoss ? 0.9 : 0.5; self.enemyGraphicsList.push(gfx); }); self.enemyGraphics = self.enemyGraphicsList[0]; // --- HITBOX --- var realWidth = self.enemyGraphics.width * self.enemyGraphics.scaleX; var realHeight = self.enemyGraphics.height * self.enemyGraphics.scaleY; self.hitbox = { x: self.x - realWidth / 2, y: self.y - realHeight / 2, width: realWidth, height: realHeight }; // --------------- if (self.isFlying) { self.shadow = new Container(); var shadowGraphics = self.shadow.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); shadowGraphics.tint = 0x000000; shadowGraphics.alpha = 0; // Start with 0% transparency shadowGraphics.scaleX = self.isBoss ? 0.9 : 0.5; shadowGraphics.scaleY = self.isBoss ? 0.9 : 0.5; // Position shadow outside visible area initially to prevent visible spawn/move effect self.shadow.x = -1000; self.shadow.y = -1000; shadowGraphics.rotation = self.enemyGraphics.rotation; // Tween shadow alpha from 0 to 0.5 over 1 second tween(shadowGraphics, { alpha: 0.5 }, { duration: 1000 }); } 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 = -self.enemyGraphics.height * 0.5 / 2 - 5; healthBarOutline.x = -healthBarOutline.width / 2 + 15; healthBarBG.x = healthBar.x = -healthBar.width / 2 + 15; healthBarOutline.scaleX = healthBarOutline.scaleY = 0.5; healthBarBG.scaleX = healthBarBG.scaleY = 0.5; healthBar.scaleX = healthBar.scaleY = 0.5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; self.update = function () { // Cambio de animación sin parpadeo - now applies to all enemy types self.animationTimer++; if (self.animationTimer >= self.animationSpeed) { self.animationTimer = 0; self.currentAssetIndex = (self.currentAssetIndex + 1) % self.enemyGraphicsList.length; var newGfx = self.enemyGraphicsList[self.currentAssetIndex]; // Copia la rotación del sprite anterior al nuevo newGfx.rotation = self.enemyGraphics.rotation; self.enemyGraphicsList.forEach(function (gfx, index) { gfx.visible = index === self.currentAssetIndex; }); self.enemyGraphics = newGfx; } var visualWidth = self.enemyGraphics.width * self.enemyGraphics.scaleX; var visualHeight = self.enemyGraphics.height * self.enemyGraphics.scaleY; self.hitbox = { x: self.x - visualWidth / 2, y: self.y - visualHeight / 2, width: visualWidth, height: visualHeight }; if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; } if (self.isImmune) { self.slowed = false; self.slowEffect = false; self.poisoned = false; self.poisonEffect = false; if (self.originalSpeed !== undefined) { self.speed = self.originalSpeed; } } else { if (self.slowed) { if (!self.slowEffect) { self.slowEffect = true; } self.slowDuration--; if (self.slowDuration <= 0) { self.speed = self.originalSpeed; self.slowed = false; self.slowEffect = false; if (!self.poisoned) { self.enemyGraphics.tint = 0xFFFFFF; } } } if (self.poisoned) { if (!self.poisonEffect) { self.poisonEffect = true; } 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; if (!self.slowed) { self.enemyGraphics.tint = 0xFFFFFF; } } } } if (self.isImmune) { self.enemyGraphics.tint = 0xFFFFFF; } else if (self.poisoned && self.slowed) { self.enemyGraphics.tint = 0x4C7FD4; } else if (self.poisoned) { self.enemyGraphics.tint = 0x00FFAA; } else if (self.slowed) { self.enemyGraphics.tint = 0x9900FF; } else { self.enemyGraphics.tint = 0xFFFFFF; } 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 (self.enemyGraphics.targetRotation === undefined) { self.enemyGraphics.targetRotation = angle; self.enemyGraphics.rotation = angle; } else { if (Math.abs(angle - self.enemyGraphics.targetRotation) > 0.05) { tween.stop(self.enemyGraphics, { rotation: true }); var currentRotation = self.enemyGraphics.rotation; var angleDiff = angle - currentRotation; while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } self.enemyGraphics.targetRotation = angle; tween(self.enemyGraphics, { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } } } healthBarOutline.y = healthBarBG.y = healthBar.y = -self.enemyGraphics.height * 0.5 / 2 - 5; }; return self; }); var GoldIndicator = Container.expand(function (amount, x, y) { var self = Container.call(this); self.x = x; self.y = y; var goldTextShadow = new Text2('+' + amount, { size: 60, fill: 0x000000, weight: 800 }); goldTextShadow.anchor.set(0.5, 0.5); goldTextShadow.scaleX = 1.1; // Adjusted scale for better visibility goldTextShadow.scaleY = 1.1; // Adjusted scale for better visibility goldTextShadow.x = -2; // Offset for shadow effect goldTextShadow.y = -2; // Offset for shadow effect self.addChild(goldTextShadow); var goldText = new Text2('+' + amount, { size: 60, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); var fadeTime = 60; var moveSpeed = 2; self.update = function () { self.y -= moveSpeed; fadeTime--; if (fadeTime <= 0) { self.destroy(); } else { self.alpha = fadeTime / 60; } }; return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; self.towerCells = []; // Separate tracking for tower placement self.spawns = []; self.goals = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; self.towerCells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { score: 0, pathId: 0, towersInRange: [] }; self.towerCells[i][j] = false; // Track tower placement separately } } /* 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]; var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0; if (i > 5 - 3 && i <= 5 + 3) { if (j === 0) { cellType = 2; self.spawns.push(cell); } else if (j <= 4) { cellType = 0; } else if (j === gridHeight - 1) { cellType = 3; self.goals.push(cell); } else if (j >= gridHeight - 4) { cellType = 0; } } cell.type = cellType; cell.x = i; cell.y = j; 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; // Calculate cell number (1-based indexing) // Only count cells in the playable area (j > 3 && j <= gridHeight - 4) var cellNumber = (j - 4) * gridWidth + i + 1; debugCell.setCellNumber(cellNumber); // Place obstacles on specific cells var obstacleCells = [14, 17, 18, 19, 20, 21, 22, 23, 26, 35, 38, 39, 40, 41, 42, 43, 44, 45, 47, 50, 59, 62, 64, 65, 66, 67, 68, 69, 70, 71, 74, 83, 86, 87, 88, 89, 90, 91, 92, 93, 95, 98, 107, 110, 112, 113, 114, 115, 116, 117, 118, 119, 122, 12, 130, 131, 16]; if (obstacleCells.indexOf(cellNumber) !== -1) { // Create obstacle using randomly selected grass asset var obstacle = new Container(); var obstacleGraphics = obstacle.attachAsset(selectedGrassAsset, { anchorX: 0.5, anchorY: 0.5 }); obstacle.x = i * CELL_SIZE; obstacle.y = j * CELL_SIZE; self.addChild(obstacle); // Mark cell as wall so enemies cannot traverse cell.type = 1; } // Force specific cells to be path regardless of other conditions var pathCells = [3, 4, 5, 6, 7, 8, 9, 10, 15, 27, 28, 29, 30, 31, 32, 33, 34, 46, 51, 52, 53, 54, 55, 56, 57, 58, 63, 75, 76, 77, 78, 79, 80, 81, 82, 94, 99, 100, 101, 102, 103, 104, 105, 106, 111, 123, 124, 125, 126, 127, 128, 129, 141, 136, 137, 138, 139, 140]; if (pathCells.indexOf(cellNumber) !== -1) { cell.type = 0; // Force as path // Create path using randomly selected path asset var path = new Container(); var pathGraphics = path.attachAsset(selectedPathAsset, { anchorX: 0.5, anchorY: 0.5 }); path.x = i * CELL_SIZE; path.y = j * CELL_SIZE; self.addChild(path); } // Place doors on specific cells if (doorCells.indexOf(cellNumber) !== -1) { // Create door instance var door = new Door(i, j); door.x = i * CELL_SIZE; door.y = j * CELL_SIZE; self.addChild(door); doors.push(door); // Mark cell as door type (type 4) cell.type = 4; cell.door = door; } // Place walls on specific cells var wallCells = [1, 2, 11, 12, 13, 24, 25, 36, 37, 48, 49, 60, 61, 72, 73, 84, 85, 96, 97, 108, 109, 120, 121, 132, 133, 134, 135, 142, 143, 144]; if (wallCells.indexOf(cellNumber) !== -1) { // Create wall using randomly selected wall asset var wall = new Container(); var wallGraphics = wall.attachAsset(selectedWallAsset, { anchorX: 0.5, anchorY: 0.5 }); wall.x = i * CELL_SIZE; wall.y = j * CELL_SIZE; self.addChild(wall); // Mark cell as wall so enemies cannot traverse and towers cannot be placed cell.type = 1; } } } } 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 && cell.type == 3) { return true; } // Also check if enemy has reached the bottom of the screen (for enemies moving directly to goals) if (enemy.currentCellY >= 18) { // Grid height is 19 (13+6), so 18 is the bottom row 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 / gameScale + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y / gameScale + 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); } // Check if enemy has moved past the bottom of the playable area if (enemy.currentCellY >= 18) { // Grid height is 19 (13+6), so 18 is the bottom row return true; } 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 / gameScale + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y / gameScale + enemy.currentCellY * CELL_SIZE; // Update shadow position if this is a flying enemy return false; } // Check if enemy has reached cell 124 (position where they should choose a door) var currentCellNumber = (Math.round(enemy.currentCellY) - 4) * 12 + Math.round(enemy.currentCellX) + 1; if (currentCellNumber === 124 && !enemy.selectedDoorCell) { // Enemy reached decision point - randomly select a door cell to attack var doorCellNumbers = [136, 137, 138, 139, 140, 141]; // Use a more robust random selection method with explicit array length var randomIndex = Math.floor(Math.random() * doorCellNumbers.length); var selectedDoorCellNumber = doorCellNumbers[randomIndex]; // Convert cell number back to grid coordinates with explicit calculation var targetGridY = Math.floor((selectedDoorCellNumber - 1) / 12) + 4; var targetGridX = (selectedDoorCellNumber - 1) % 12; // Verify coordinates for cell 140 specifically if (selectedDoorCellNumber === 140) { // Cell 140: (140-1) = 139, 139/12 = 11.58, floor = 11, +4 = 15 // 139 % 12 = 7 targetGridY = 15; targetGridX = 7; } // Set the enemy's target to the selected door cell enemy.selectedDoorCell = { x: targetGridX, y: targetGridY }; enemy.currentTarget = enemy.selectedDoorCell; // Debug logging to verify all cells are being selected console.log("Enemy selected door cell:", selectedDoorCellNumber, "at grid:", targetGridX, targetGridY); } // Check if enemy is trying to move into a door cell var targetCell = grid.getCell(Math.round(enemy.currentCellX), Math.round(enemy.currentCellY)); if (targetCell && targetCell.type === 4 && targetCell.door && globalDoorHealth > 0) { // Enemy encounters a door - start attacking it if (!enemy.attackingDoor) { enemy.attackingDoor = targetCell.door; targetCell.door.addAttackingEnemy(enemy); } // Stop moving and attack the door enemy.currentTarget = undefined; enemy.x = grid.x / gameScale + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y / gameScale + enemy.currentCellY * CELL_SIZE; return false; } // If enemy was attacking a door but door is now destroyed, clear attacking state and continue moving if (enemy.attackingDoor && globalDoorHealth <= 0) { enemy.attackingDoor.removeAttackingEnemy(enemy); enemy.attackingDoor = null; // Instead of resetting target, set enemy to move directly to the goal // Find the closest goal cell for direct movement var closestGoal = self.goals[0]; if (self.goals.length > 1) { var closestDist = Infinity; for (var g = 0; g < self.goals.length; g++) { var goal = self.goals[g]; var dx = goal.x - enemy.currentCellX; var dy = goal.y - enemy.currentCellY; var dist = dx * dx + dy * dy; if (dist < closestDist) { closestDist = dist; closestGoal = goal; } } } enemy.currentTarget = closestGoal; // Set direct target to goal } // Handle normal pathfinding enemies if (!enemy.currentTarget) { // If enemy has a selected door cell, prioritize moving to it if (enemy.selectedDoorCell) { enemy.currentTarget = enemy.selectedDoorCell; } else { enemy.currentTarget = cell.targets[0]; } } if (enemy.currentTarget) { // Don't override target if enemy has selected a specific door cell if (!enemy.selectedDoorCell && 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); // Only clear target if we weren't heading to a specific door if (!enemy.selectedDoorCell) { enemy.currentTarget = undefined; } return; } // Special movement logic for enemies targeting door cells if (enemy.selectedDoorCell) { // First move to correct X position, then move down in Y var targetX = enemy.selectedDoorCell.x; var targetY = enemy.selectedDoorCell.y; var xDistance = Math.abs(targetX - enemy.currentCellX); var yDistance = Math.abs(targetY - enemy.currentCellY); // If not at correct X position, move horizontally first if (xDistance > 0.1) { if (targetX > enemy.currentCellX) { enemy.currentCellX += enemy.speed; } else { enemy.currentCellX -= enemy.speed; } // Rotate enemy to face movement direction var angle = targetX > enemy.currentCellX ? 0 : Math.PI; if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } } else { // At correct X position, now move down in Y enemy.currentCellY += enemy.speed; // Rotate enemy to face downward var angle = Math.PI / 2; if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } } } else { // Normal pathfinding movement for enemies without door target var angle = Math.atan2(oy, ox); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; } } enemy.x = grid.x / gameScale + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y / gameScale + enemy.currentCellY * CELL_SIZE; }; }); 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, style: 'bold' }); 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 SpinningWheel = Container.expand(function () { var self = Container.call(this); self.isSpinning = false; self.finalSection = 0; self.betAmount = 0; // Create wheel base var wheelBase = self.attachAsset('spinningWheel', { anchorX: 0.5, anchorY: 0.5 }); wheelBase.tint = 0xDDDDDD; // Create 8 sections with different colors // Lose All is always red, others are unique bright primaries var sectionNames = ["💀 Lose All", "BET X 10", "💀 Lose All", "BET X 5", "💀 Lose All", "BET X 2", "💀 Lose All", "BET X 1"]; // Assign unique bright primary colors to each section except "Lose All" var brightPrimaries = [0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFFA500, 0x800080]; var sectionColors = []; for (var i = 0; i < sectionNames.length; i++) { if (sectionNames[i] === "Lose All") { sectionColors[i] = 0xFF0000; // Red for Lose All } else { // Assign a unique color from the brightPrimaries array, cycling if needed sectionColors[i] = brightPrimaries[i % brightPrimaries.length]; } } self.sections = []; self.sectionLabels = []; // Create visual sections for (var i = 0; i < 8; i++) { var section = new Container(); // Remove the colored background block - no longer adding sectionGraphics var angle = i * 45 * Math.PI / 180; section.x = Math.cos(angle) * 110; // Move blocks closer to center (was 150) section.y = Math.sin(angle) * 110; // Move blocks closer to center (was 150) section.rotation = angle; // Add text label var label = new Text2(sectionNames[i], { size: 32, fill: 0xFFFFFF, weight: 800 }); label.anchor.set(0.5, 0.5); label.x = 90; // Center label on shorter section block label.y = 0; section.addChild(label); self.addChild(section); self.sections.push(section); self.sectionLabels.push(sectionNames[i]); } // Black separators removed - no longer creating separator lines between sections // Arrow will be created separately and positioned outside the wheel // Remove the arrow from the spinning wheel itself self.spin = function (betAmount) { if (self.isSpinning) { return; } self.isSpinning = true; self.betAmount = betAmount; // Set high zIndex to ensure wheel appears above all other elements, including turret bullets and tutorial arrows self.zIndex = 30000; // Play wheel spin sound LK.getSound('wheelSpinSound').play(); // Random number of full rotations (3-8) plus random section var baseRotations = 3 + Math.random() * 5; var randomSection = Math.floor(Math.random() * 8); // Calculate exact center angle for the selected section // Each section is 45 degrees, so center is at section * 45 var sectionCenterAngle = randomSection * 45; var finalAngle = baseRotations * 360 + sectionCenterAngle; self.finalSection = randomSection; // Spin animation with precise centering tween(self, { rotation: finalAngle * Math.PI / 180 }, { duration: 3000, easing: tween.easeOut, onFinish: function onFinish() { // After main spin, fine-tune to ensure perfect center alignment self.fineTuneToCenter(); } }); }; self.fineTuneToCenter = function () { // Calculate current rotation in degrees var currentRotationDegrees = self.rotation * 180 / Math.PI % 360; while (currentRotationDegrees < 0) { currentRotationDegrees += 360; } while (currentRotationDegrees >= 360) { currentRotationDegrees -= 360; } // Find the nearest section center ahead of current position var nearestSectionAhead = -1; var shortestDistanceAhead = Infinity; // Check all 8 sections to find the nearest one ahead for (var i = 0; i < 8; i++) { var sectionCenter = i * 45; var distanceAhead = sectionCenter - currentRotationDegrees; // If distance is negative, add 360 to get forward distance if (distanceAhead <= 0) { distanceAhead += 360; } // Find the shortest forward distance if (distanceAhead < shortestDistanceAhead) { shortestDistanceAhead = distanceAhead; nearestSectionAhead = i; } } // Update finalSection to the nearest section ahead self.finalSection = nearestSectionAhead; // If we're very close to center, no need to adjust if (Math.abs(shortestDistanceAhead) <= 0.1 || Math.abs(shortestDistanceAhead - 360) <= 0.1) { // Already perfectly centered, proceed with result self.handleResult(); return; } // Calculate final rotation by continuing to spin forward var finalRotation = self.rotation + shortestDistanceAhead * Math.PI / 180; // Use constant slow speed: calculate duration based on angle distance var constantSpeed = 30; // degrees per second var durationMs = shortestDistanceAhead / constantSpeed * 1000; // Continue spinning smoothly at constant speed until perfectly centered tween(self, { rotation: finalRotation }, { duration: durationMs, easing: tween.linear, // Linear easing for constant speed onFinish: function onFinish() { self.handleResult(); } }); }; self.handleResult = function () { // Calculate which section is actually at the arrow position (90 degrees to the right) // The wheel rotates, so we need to find which section is at the 0-degree position (right side) var currentRotationDegrees = self.rotation * 180 / Math.PI % 360; while (currentRotationDegrees < 0) { currentRotationDegrees += 360; } while (currentRotationDegrees >= 360) { currentRotationDegrees -= 360; } // Each section is 45 degrees wide, starting at 0 degrees // The arrow points to the right (0 degrees), so we need to find which section is at 0 degrees // Add 22.5 degrees to center the calculation on section midpoints var sectionAtArrow = Math.floor((currentRotationDegrees + 22.5) / 45) % 8; var sectionName = self.sectionLabels[sectionAtArrow]; var winAmount = 0; var message = ""; switch (sectionAtArrow) { case 0: // Position 1: Lose ALL winAmount = 0; message = "Lost all bet! Better luck next time!"; break; case 1: // Position 2: BET X 10 winAmount = self.betAmount * 1; message = "Won " + winAmount + " gold! (1x)"; break; case 2: // Position 3: Lose ALL winAmount = 0; message = "Lost all bet! Better luck next time!"; break; case 3: // Position 4: BET X 50 (This should award 20X) winAmount = self.betAmount * 2; message = "Won " + winAmount + " gold! (2x)"; break; case 4: // Position 5: Lose ALL winAmount = 0; message = "Lost all bet! Better luck next time!"; break; case 5: // Position 6: BET X 20 (This should award 50X) winAmount = self.betAmount * 5; message = "Won " + winAmount + " gold! (5x)"; break; case 6: // Position 7: Lose ALL winAmount = 0; message = "Lost all bet! Better luck next time!"; break; case 7: // Position 8: BET X 100 (This should award 100X) winAmount = self.betAmount * 10; message = "JACKPOT! Won " + winAmount + " gold! (10x)"; break; } // Play win or lose sound based on result if (winAmount > 0) { LK.getSound('wheelWinSound').play(); } else { LK.getSound('wheelLoseSound').play(); } setGold(gold + winAmount); var notification = game.addChild(new Notification(message)); notification.x = 2048 / 2; notification.y = grid.height - 150; // Keep the wheel visible for at least 1 second after spinning ends, then hide LK.setTimeout(function () { // Clean up the fixed arrow if it exists if (self.wheelArrow) { self.wheelArrow.destroy(); self.wheelArrow = null; } self.destroy(); }, 1000); }; return self; }); var Tower = Container.expand(function () { var self = Container.call(this); self.level = 1; self.maxLevel = 5; self.clicksInvested = 0; // Track total clicks invested in this tower self.clicksNeededForNextLevel = 50; // Clicks needed to reach next level self.gridX = 0; self.gridY = 0; // Method to calculate clicks needed for current level (progressive: 20, 50, 100, 200) self.getClicksNeededForCurrentLevel = function () { if (self.level >= self.maxLevel) { return 0; } // New click requirements per level switch (self.level) { case 1: return 20; // Level 1->2: 20 clicks case 2: return 50; // Level 2->3: 50 clicks case 3: return 100; // Level 3->4: 100 clicks case 4: return 200; // Level 4->5: 200 clicks default: return 0; } }; // Standardized method to get the current range of the tower self.getRange = function () { // Default: base 3, +0.5 per level return (2 + (self.level - 1) * 0.5) * CELL_SIZE; }; self.cellsInRange = []; self.fireRate = 60; self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); baseGraphics.x = -90; baseGraphics.y = 150; baseGraphics.tint = 0xAAAAAA; // Reduce tower base size by 50% baseGraphics.scaleX = 0.5; baseGraphics.scaleY = 0.5; var levelIndicators = []; var maxDots = self.maxLevel; var dotSpacing = baseGraphics.width / (maxDots + 1); var dotSize = CELL_SIZE / 12; // Reduce dot size by 50% 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 = -130 + dotSpacing * (i + 1); dot.y = 125 + CELL_SIZE * 0.7; self.addChild(dot); levelIndicators.push(dot); } var gunContainer = new Container(); self.addChild(gunContainer); gunContainer.x = -90; gunContainer.y = 150; var gunGraphics = gunContainer.attachAsset('defense', { anchorX: 0.5, anchorY: 0.5 }); // Reduce gun size by 50% gunGraphics.scaleX = 0.5; gunGraphics.scaleY = 0.5; 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 = 0x0033CC; } else { towerLevelIndicator.tint = 0xAAAAAA; } } }; self.updateLevelIndicators(); 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 - 200; 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 () { // Use the cost at the time this tower was placed (10 + 10 * number of towers before this one) // Since we don't track individual tower placement order, use current cost as approximation var baseTowerCost = 10; // Original base cost // Calculate total clicks invested with progressive requirements var totalClicksInvested = 0; for (var level = 1; level < self.level; level++) { totalClicksInvested += level * 10; // Sum up all previous level investments } totalClicksInvested += self.clicksInvested; // Add current level progress var totalInvestment = baseTowerCost + totalClicksInvested; return totalInvestment; }; self.upgrade = function () { if (self.level < self.maxLevel) { // Exponential upgrade cost: base cost * (2 ^ (level-1)) var baseUpgradeCost = 5; // Base tower cost 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 upgrades if (self.level === self.maxLevel) { // Extra powerful last upgrade (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; }; self.findTarget = function () { var closestEnemy = null; var closestDistance = Infinity; // Calculamos la posición real del cañón var gunWorldX = self.x + -180 * 0.5; var gunWorldY = self.y + 300 * 0.5; // Debug circle removed - using persistent range display instead for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; // Calcula la distancia desde el cañón, no desde el centro lógico var dx = enemy.x - gunWorldX; var dy = enemy.y - gunWorldY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.getRange()) { if (distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } } self.targetEnemy = closestEnemy; return closestEnemy; }; self.update = function () { self.findTarget(); if (self.targetEnemy) { // Calculate the angle from the gun position to the target enemy // Gun container is at (-95, 150) relative to tower center, scaled by 50% var gunWorldX = self.x + -95 * 0.5; var gunWorldY = self.y + 300 * 0.5; var dx = self.targetEnemy.x - gunWorldX; var dy = self.targetEnemy.y - gunWorldY; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); gunContainer.rotation = angle; // Fire automatically when target is in range and fire rate allows // Apply speed boost multiplier to firing rate var effectiveFireRate = self.fireRate; if (speedBoostActive) { effectiveFireRate = Math.ceil(self.fireRate / 3); } if (LK.ticks - self.lastFired >= effectiveFireRate) { self.fire(); self.lastFired = LK.ticks; } } }; self.down = function (x, y, obj) { // Hide range from previously selected tower if (rangeDisplayTower && rangeDisplayTower !== self) { rangeDisplayTower.hideRangeIndicator(); } // Show range for this tower self.showRange(); rangeDisplayTower = self; // Play tower click sound LK.getSound('towerClickSound').play(); // Track pressed tower and start timer for sell functionality pressedTower = self; pressStartTime = LK.ticks; // Clear any existing timer if (pressTimer) { LK.clearTimeout(pressTimer); } // Set timer for 5 seconds (300 ticks at 60fps) pressTimer = LK.setTimeout(function () { if (pressedTower === self) { // Sell the tower var sellValue = self.getTotalValue(); setGold(gold + sellValue); // Show sell notification var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; // Remove tower from grid tracking if (grid.towerCells[self.gridX]) { grid.towerCells[self.gridX][self.gridY] = false; } // Hide range indicator self.hideRangeIndicator(); if (rangeDisplayTower === self) { rangeDisplayTower = null; } // Remove tower from arrays and destroy var towerIndex = towers.indexOf(self); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } // Decrement towers placed counter when selling if (towersPlaced > 0) { towersPlaced--; } updateTowerPrice(); // Update tower price display self.destroy(); pressedTower = null; } }, 5000); // Check if tower upgrades are allowed during tutorial if (tutorialActive && tutorialStep !== 5) { var notification = game.addChild(new Notification("Cannot upgrade tower yet!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } // Progressive upgrade system - clicks no longer cost gold if (self.level < self.maxLevel) { self.clicksInvested++; // Check if we have enough clicks to level up (progressive requirements) var clicksNeeded = self.getClicksNeededForCurrentLevel(); if (self.clicksInvested >= clicksNeeded) { self.level++; // Reset click counter for next level self.clicksInvested = 0; // Apply upgrades if (self.level === self.maxLevel) { // Extra powerful last upgrade (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(); // Update range display after upgrade self.showRange(); // Animate level indicator 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 }); } }); } // Play tower upgrade sound LK.getSound('towerUpgradeSound').play(); // Show level up notification var notification = game.addChild(new Notification("Tower upgraded to level " + self.level + "!")); notification.x = 2048 / 2; notification.y = grid.height - 50; // Check if this is the tutorial tower reaching level 2 if (tutorialActive && tutorialStep === 5 && self === tutorialTower && self.level >= 2) { advanceTutorialStep(); } } else { // Show progress notification (progressive click requirements) var totalClicksNeeded = self.getClicksNeededForCurrentLevel(); var clicksRemaining = totalClicksNeeded - self.clicksInvested; var notification = game.addChild(new Notification(clicksRemaining + " more clicks to level " + (self.level + 1))); notification.x = 2048 / 2; notification.y = grid.height - 50; } } else if (self.level >= self.maxLevel) { var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } }; self.up = function (x, y, obj) { // Clear the press timer if this tower was being pressed if (pressedTower === self) { pressedTower = null; if (pressTimer) { LK.clearTimeout(pressTimer); pressTimer = null; } } }; self.showRange = function () { // Remove existing range indicator first self.hideRangeIndicator(); // Create range indicator var rangeIndicator = new Container(); var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.getRange() * 2; rangeGraphics.alpha = 0.3; rangeGraphics.tint = 0xffffff; // White color for range (original color) rangeIndicator.isTowerRange = true; // Position at tower center with offset to align with visual elements rangeIndicator.x = -88; // Offset left to center on visual tower rangeIndicator.y = 155; // Offset down to center on visual tower self.addChild(rangeIndicator); }; self.hideRangeIndicator = function () { for (var i = self.children.length - 1; i >= 0; i--) { if (self.children[i].isTowerRange) { self.removeChild(self.children[i]); } } }; self.isInRange = function (enemy) { if (!enemy) { return false; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance <= self.getRange(); }; self.fire = function () { if (self.targetEnemy) { var potentialDamage = 0; for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) { potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage; } if (self.targetEnemy.health > potentialDamage) { // Calculate bullet spawn position relative to gun container position // gunContainer is positioned at (-95, 150) relative to tower center // Apply the same scaling as the tower layer // Define el punto de la punta del cañón localmente en gunContainer (por ejemplo, en el eje X positivo) // Obtener la punta del cañón teniendo en cuenta el anchor // Calcula la posición global de la punta del cañón manualmente var offsetX = 30; // mueve hacia la punta (horizontal) var offsetY = -5; // sube o baja (vertical) // Aplica rotación del cañón a ese offset var cos = Math.cos(gunContainer.rotation); var sin = Math.sin(gunContainer.rotation); var rotatedX = offsetX * cos - offsetY * sin; var rotatedY = offsetX * sin + offsetY * cos; // Posición global final considerando jerarquía y escala de juego var bulletX = (self.x + gunContainer.x + rotatedX) * gameScale; var bulletY = (self.y + gunContainer.y + rotatedY) * gameScale; // Create bullet in that position var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); // Set bullet zIndex below spinning wheel (zIndex 20000), but above most gameplay elements bullet.zIndex = 10000; game.addChild(bullet); // Ensure bullet appears below any existing spinning wheel if (bullet.zIndex) { bullet.zIndex = 10000; } bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); // Play tower fire sound LK.getSound('towerFireSound').play(); // --- Fire recoil effect for gunContainer (rotation only) --- // Stop any ongoing recoil tweens before starting a new one tween.stop(gunContainer, { scaleX: true, scaleY: true }); // Store original scale values if not already stored if (gunContainer._restScaleX === undefined) { gunContainer._restScaleX = 1; } if (gunContainer._restScaleY === undefined) { gunContainer._restScaleY = 1; } // Reset scale to resting values before animating (in case of interrupted tweens) gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; // Animate scale recoil effect (gun briefly shrinks then returns to normal) tween(gunContainer, { scaleX: gunContainer._restScaleX * 0.8, scaleY: gunContainer._restScaleY * 0.8 }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { // Animate return to original scale tween(gunContainer, { scaleX: gunContainer._restScaleX, scaleY: gunContainer._restScaleY }, { duration: 90, easing: tween.cubicIn }); } }); } } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; // ✅ No usar gameScale aquí self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { if (grid.towerCells[gridX] && grid.towerCells[gridX][gridY] !== undefined) { grid.towerCells[gridX][gridY] = true; } } } 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 previewGraphics = self.attachAsset('towerpreview', { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; self.canPlace = false; self.gridX = 0; self.gridY = 0; self.blockedByEnemy = false; self.update = function () { var previousHasEnoughGold = self.hasEnoughGold; self.hasEnoughGold = gold >= getTowerCost(); // Check against current tower cost // Only update appearance if the affordability status has changed if (previousHasEnoughGold !== self.hasEnoughGold) { self.updateAppearance(); } }; self.updateAppearance = function () { // Use Tower class to get the source of truth for range var tempTower = new Tower(); var previewRange = tempTower.getRange(); // Clean up tempTower to avoid memory leaks if (tempTower && tempTower.destroy) { tempTower.destroy(); } // Set range indicator using unified range logic var previewCircleScale = 1.83; // Cambia este valor para agrandar o achicar el círculo rangeGraphics.width = rangeGraphics.height = previewRange * 2 * previewCircleScale; previewGraphics.tint = 0xAAAAAA; if (!self.canPlace || !self.hasEnoughGold) { previewGraphics.tint = 0xFF0000; } }; self.updatePlacementStatus = function () { var cell = grid.getCell(self.gridX, self.gridY); // Check if this cell is specifically an obstacle (not a wall) var cellNumber = (self.gridY - 4) * 12 + self.gridX + 1; var obstacleCells = [14, 17, 18, 19, 20, 21, 22, 23, 26, 35, 38, 39, 40, 41, 42, 43, 44, 45, 47, 50, 59, 62, 64, 65, 66, 67, 68, 69, 70, 71, 74, 83, 86, 87, 88, 89, 90, 91, 92, 93, 95, 98, 107, 110, 112, 113, 114, 115, 116, 117, 118, 119, 122, 130, 131, 16]; var wallCells = [1, 2, 11, 12, 13, 24, 25, 36, 37, 48, 49, 60, 61, 72, 73, 84, 85, 96, 97, 108, 109, 120, 121, 132, 133, 134, 135, 142, 143, 144]; // Check if there's already a tower at this exact position using separate tower tracking var existingTower = grid.towerCells[self.gridX] && grid.towerCells[self.gridX][self.gridY]; // Check if this is a wall cell (towers cannot be placed on walls) var isWallCell = cellNumber >= 1 && cellNumber <= 144 && wallCells.indexOf(cellNumber) !== -1; // Allow placement on obstacle cells that don't have towers and are not walls var isObstacleCell = cellNumber >= 1 && cellNumber <= 144 && obstacleCells.indexOf(cellNumber) !== -1; // Only check the exact cell for tower placement, not neighboring cells var validGridPlacement = cell && !existingTower && isObstacleCell && !isWallCell; self.canPlace = validGridPlacement; self.hasEnoughGold = gold >= getTowerCost(); // Check against current tower cost self.updateAppearance(); }; self.checkPlacement = function () { self.updatePlacementStatus(); }; self.snapToGrid = function (x, y) { var gridPosX = (x - grid.x) / gameScale; var gridPosY = (y - grid.y) / gameScale; // Para X dejamos el redondeo normal self.gridX = Math.round(gridPosX / CELL_SIZE); // Para Y, calculamos la fracción dentro de la celda var yInCell = gridPosY / CELL_SIZE % 1; // Si el mouse pasó más del 90% de la celda, pasamos a la siguiente celda (redondeo hacia arriba) if (yInCell > 0.9) { self.gridY = Math.ceil(gridPosY / CELL_SIZE); } else { self.gridY = Math.ceil(gridPosY / CELL_SIZE); } // Actualizamos la posición visual acorde a gridX, gridY self.x = grid.x + self.gridX * CELL_SIZE * gameScale; self.y = grid.y + self.gridY * CELL_SIZE * gameScale; self.checkPlacement(); }; return self; }); var WaveIndicator = Container.expand(function () { var self = Container.call(this); self.gameStarted = false; self.waveMarkers = []; self.waveTypes = []; self.enemyCounts = []; self.indicatorWidth = 0; self.lastBossType = null; // Track the last boss type to avoid repeating var blockWidth = 400; var totalBlocksWidth = blockWidth * totalWaves; for (var i = 0; i < totalWaves; i++) { var marker = new Container(); var block = marker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth - 10; block.height = 70 * 2; // --- Begin new unified wave logic --- var waveType = "normal"; var enemyType = "normal"; var enemyCount = 10; var isBossWave = (i + 1) % 10 === 0; // Ensure all types appear in early waves if (i === 0) { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } else if (i === 1) { block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if (i === 2) { block.tint = 0xAA0000; waveType = "Tank"; enemyType = "tank"; enemyCount = 10; } else if (i === 3) { block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if (i === 4) { block.tint = 0xFF00FF; waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else if (isBossWave) { // Boss waves: specific boss types for specific waves var waveNumber = i + 1; if (waveNumber === 10) { // Wave 10: Boss Normal enemyType = 'normal'; waveType = "Boss Normal"; block.tint = 0xAAAAAA; } else if (waveNumber === 20) { // Wave 20: Boss Fast enemyType = 'fast'; waveType = "Boss Fast"; block.tint = 0x00AAFF; } else if (waveNumber === 30) { // Wave 30: Boss Tank enemyType = 'tank'; waveType = "Boss Tank"; block.tint = 0xAA0000; } else if (waveNumber === 40) { // Wave 40: Boss Tank enemyType = 'tank'; waveType = "Boss Tank"; block.tint = 0xAA0000; } else if (waveNumber === 50) { // Wave 50: Boss Swarm - 5 large bosses enemyType = 'swarm'; waveType = "Boss Swarm"; block.tint = 0xFF00FF; } else { // Fallback for any other boss waves (shouldn't happen with current totalWaves = 50) enemyType = 'normal'; waveType = "Boss Normal"; block.tint = 0xAAAAAA; } enemyCount = waveNumber === 50 ? 5 : 1; // Wave 50 spawns 5 bosses, others spawn 1 // Make the wave indicator for boss waves stand out // Set boss wave color to the color of the wave type switch (enemyType) { case 'normal': block.tint = 0xAAAAAA; break; case 'fast': block.tint = 0x00AAFF; break; case 'tank': block.tint = 0xAA0000; break; case 'flying': block.tint = 0xFFFF00; break; default: block.tint = 0xFF0000; break; } } else if ((i + 1) % 5 === 0) { // Every 5th non-boss wave is fast block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if ((i + 1) % 4 === 0) { // Every 4th non-boss wave is tank block.tint = 0xAA0000; waveType = "Tank"; enemyType = "tank"; enemyCount = 10; } else if ((i + 1) % 7 === 0) { // Every 7th non-boss wave is flying block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if ((i + 1) % 3 === 0) { // Every 3rd non-boss wave is swarm block.tint = 0xFF00FF; waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } // --- End new unified wave logic --- // Mark boss waves with a special visual indicator if (isBossWave && enemyType !== 'swarm') { // Add a crown or some indicator to the wave marker for boss waves var bossIndicator = marker.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); bossIndicator.width = 30; bossIndicator.height = 30; bossIndicator.tint = 0xFFD700; // Gold color bossIndicator.y = -block.height / 2 - 15; // Change the wave type text to indicate boss waveType = "BOSS"; } // Store the wave type and enemy count self.waveTypes[i] = enemyType; self.enemyCounts[i] = enemyCount; // Add shadow for wave type - 30% smaller than before var waveTypeShadow = new Text2(waveType, { size: 56, fill: 0x000000, weight: 800 }); waveTypeShadow.anchor.set(0.5, 0.5); waveTypeShadow.x = 4; waveTypeShadow.y = 4; marker.addChild(waveTypeShadow); // Add wave type text - 30% smaller than before var waveTypeText = new Text2(waveType, { size: 56, fill: 0xFFFFFF, weight: 800 }); waveTypeText.anchor.set(0.5, 0.5); waveTypeText.y = 0; marker.addChild(waveTypeText); // Add shadow for wave number - 20% larger than before var waveNumShadow = new Text2((i + 1).toString(), { size: 48, fill: 0x000000, weight: 800 }); waveNumShadow.anchor.set(1.0, 1.0); waveNumShadow.x = blockWidth / 2 - 16 + 5; waveNumShadow.y = block.height / 2 - 12 + 5; marker.addChild(waveNumShadow); // Main wave number text - 20% larger than before var waveNum = new Text2((i + 1).toString(), { size: 48, fill: 0xFFFFFF, weight: 800 }); waveNum.anchor.set(1.0, 1.0); waveNum.x = blockWidth / 2 - 16; waveNum.y = block.height / 2 - 12; marker.addChild(waveNum); marker.x = -self.indicatorWidth + (i + 1) * blockWidth; self.addChild(marker); self.waveMarkers.push(marker); } // Get wave type for a specific wave number self.getWaveType = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return "normal"; } // If this is a boss wave (waveNumber % 10 === 0), and the type is the same as lastBossType // then we should return a different boss type var waveType = self.waveTypes[waveNumber - 1]; return waveType; }; // Get enemy count for a specific wave number self.getEnemyCount = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return 10; } return self.enemyCounts[waveNumber - 1]; }; // Get display name for a wave type self.getWaveTypeName = function (waveNumber) { var type = self.getWaveType(waveNumber); var typeName = type.charAt(0).toUpperCase() + type.slice(1); // Add boss prefix for boss waves (every 10th wave) if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') { typeName = "BOSS"; } return typeName; }; self.positionIndicator = new Container(); var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = blockWidth - 10; indicator.height = 16; indicator.tint = 0xffad0e; indicator.y = -65; var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator2.width = blockWidth - 10; indicator2.height = 16; indicator2.tint = 0xffad0e; indicator2.y = 65; var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); leftWall.width = 16; leftWall.height = 146; leftWall.tint = 0xffad0e; leftWall.x = -(blockWidth - 16) / 2; var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); rightWall.width = 16; rightWall.height = 146; rightWall.tint = 0xffad0e; rightWall.x = (blockWidth - 16) / 2; self.addChild(self.positionIndicator); self.handleWaveProgression = function () { if (!self.gameStarted) { return; } if (currentWave < totalWaves) { waveTimer++; if (waveTimer >= nextWaveTime) { waveTimer = 0; currentWave++; waveInProgress = true; waveSpawned = false; if (currentWave != 1) { var waveType = self.getWaveTypeName(currentWave); var enemyCount = self.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } }; self.update = function () { var progress = waveTimer / nextWaveTime; var moveAmount = (progress + currentWave - 1) * blockWidth; for (var i = 0; i < self.waveMarkers.length; i++) { var marker = self.waveMarkers[i]; marker.x = -moveAmount + i * blockWidth; } self.positionIndicator.x = 0; for (var i = 0; i < totalWaves; i++) { var marker = self.waveMarkers[i]; var block = marker.children[0]; if (i < currentWave) { block.alpha = .5; } // Hide markers that are completely outside the left side of the yellow indicator rectangle var leftEdge = self.positionIndicator.x - (blockWidth - 16) / 2; if (marker.x + blockWidth / 2 < leftEdge) { marker.visible = false; } else { marker.visible = true; } } self.handleWaveProgression(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x333333 }); /**** * Game Code ****/ game.sortableChildren = true; // Initialize background music // Initialize sound effects 3; 75; var CELL_SIZE = 76; var selectedWallAsset = 'Muro' + (Math.floor(Math.random() * 4) + 1); // Randomly select Muro1, Muro2, Muro3, or Muro4 var selectedGrassAsset = 'Pasto' + (Math.floor(Math.random() * 4) + 1); // Randomly select Pasto1, Pasto2, Pasto3, or Pasto4 var selectedPathAsset = 'Camino' + (Math.floor(Math.random() * 4) + 1); // Randomly select Camino1, Camino2, Camino3, or Camino4 var selectedDoorAsset = 'Puerta' + (Math.floor(Math.random() * 4) + 1); // Randomly select door asset for this game var pathId = 1; var maxScore = 0; var enemies = []; var towers = []; var bullets = []; var defenses = []; var selectedTower = null; var rangeDisplayTower = null; // Track which tower is showing range var pressedTower = null; // Track which tower is being pressed var pressStartTime = 0; // Track when the press started var pressTimer = null; // Timer for checking press duration var gold = 0; // Start with 0 gold for tutorial var lives = 1; var score = 0; var currentWave = 0; var totalWaves = 50; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 12000 / 4; var sourceTower = null; var enemiesToSpawn = 10; // Default number of enemies per wave var towersPlaced = 0; // Track total towers placed for progressive pricing var doorCells = [136, 137, 138, 139, 140, 141]; // Door cell numbers var doors = []; // Track door instances var globalDoorHealth = 95; // Start door health at 95/100 for tutorial var globalDoorMaxHealth = 100; // Maximum health for all doors var globalDoorHealthBar = null; // Global health bar container var globalDoorHealthBarBg = null; // Global door health bar background var globalDoorHealthBarFill = null; // Global door health bar fill var globalDoorHealthText = null; // Global door health bar text var topMargin = 100; // Define topMargin variable // --- Tutorial System --- // Always show the tutorial at the start of the game, regardless of previous completion var tutorialActive = true; // --- Fix: Define highlightGoldButton globally so it is always available --- var goldButtonHighlighted = false; var tutorialStep = 0; var tutorialOverlay = null; var tutorialArrow = null; var tutorialMessage = null; var tutorialTargetArea = null; var tutorialRedButtonPresses = 0; var tutorialTowerPlaced = false; var tutorialTower = null; var tutorialTowerUpgradeClicks = 0; var tutorialEnemyKilled = false; var tutorialFlyingEnemyKilled = false; var tutorialGateRepaired = false; var tutorialForwardButton = null; var tutorialForwardButtonPressed = false; var tutorialFlyingEnemy = null; var tutorialBasicEnemy = null; var tutorialRepairMessageShown = false; var tutorialGoodLuckShown = false; var tutorialAllowTowerPlacement = false; var tutorialAllowUpgrade = false; var tutorialAllowRepair = false; var tutorialAllowForward = false; var tutorialAllowRedButton = true; var tutorialAllowDragTower = false; var tutorialAllowPlaceCell = 67; // Only allow cell 67 for first tower var tutorialAllowTurretIcon = false; var tutorialAllowGateRepair = false; var tutorialAllowFlyingEnemy = false; var tutorialAllowBasicEnemy = false; var tutorialAllowSpeedBoost = false; var tutorialAllowGameStart = false; var tutorialAllowAll = false; var tutorialCellOffsetX = -85; // Offset for tutorial cell highlighting var tutorialCellOffsetY = -85; // Offset for tutorial cell highlighting var tutorialOffsetStep5X = 530; // Offset for tutorial step 5 highlight var tutorialOffsetStep5Y = 820; // Offset for tutorial step 5 highlight // Betting system variables var currentBet = 0; // Always keep as multiple of 10 var maxBet = 999999; // Remove betting limit var minBet = 0; var tutorialForwardIconArea = { x: 2048 - 150, y: 120, w: 240, h: 180 }; // Area for speed/forward button var tutorialRedButtonArea = { x: 250, y: 2732 - 220, w: 350, h: 350 }; // Area for red button var tutorialTurretIconArea = { x: 700, y: 2732 - 210, w: 350, h: 350 }; // Area for turret icon var tutorialGateArea = { x: 1050, y: 2250, w: 1200, h: 300 }; // Area for gate repair function showTutorialOverlay(area, message, arrowDir) { // Add null check for area parameter if (!area) { console.error("showTutorialOverlay called with null/undefined area parameter"); return; } // Remove previous overlay/arrow/message if (tutorialOverlay) { tutorialOverlay.destroy(); tutorialOverlay = null; } if (tutorialArrow) { tutorialArrow.destroy(); tutorialArrow = null; } if (tutorialMessage) { tutorialMessage.destroy(); tutorialMessage = null; } // Overlay: darken everything except area tutorialOverlay = new Container(); tutorialOverlay.zIndex = 9999; tutorialOverlay.alpha = 0.5; // Four rectangles: top, left, right, bottom var darkColor = 0x000000; var topRect = tutorialOverlay.attachAsset('notification', { anchorX: 0, anchorY: 0 }); topRect.width = 2048; topRect.height = Math.max(0, area.y - area.h / 2); topRect.tint = darkColor; var leftRect = tutorialOverlay.attachAsset('notification', { anchorX: 0, anchorY: 0 }); leftRect.width = Math.max(0, area.x - area.w / 2); leftRect.height = area.h; leftRect.y = area.y - area.h / 2; leftRect.tint = darkColor; var rightRect = tutorialOverlay.attachAsset('notification', { anchorX: 0, anchorY: 0 }); rightRect.width = Math.max(0, 2048 - (area.x + area.w / 2)); rightRect.height = area.h; rightRect.x = area.x + area.w / 2; rightRect.y = area.y - area.h / 2; rightRect.tint = darkColor; var bottomRect = tutorialOverlay.attachAsset('notification', { anchorX: 0, anchorY: 0 }); bottomRect.width = 2048; bottomRect.height = Math.max(0, 2732 - (area.y + area.h / 2)); bottomRect.y = area.y + area.h / 2; bottomRect.tint = darkColor; game.addChild(tutorialOverlay); var fontSize = 80; if (message === "Good Luck") { fontSize = 200; // O incluso 250 si quieres algo más grande } // Message - position below area if it's in the top part of screen (forward button area) tutorialMessage = new Text2(message, { size: fontSize, fill: 0xffffff, weight: 800, align: 'center' }); tutorialMessage.anchor.set(0.5, 0.5); tutorialMessage.x = 2048 / 2; // Check if this is the forward button area (top of screen) and position below instead if (area.y < 300) { tutorialMessage.y = area.y + area.h / 2 + 220; // Position below the area } else { tutorialMessage.y = area.y - area.h / 2 - 220; // Position above the area (normal) } tutorialMessage.zIndex = 10000; // Ensure message appears above all other objects game.addChild(tutorialMessage); // Arrow (optional) - point up if below area, down if above if (arrowDir) { tutorialArrow = new Container(); var arrowGfx = tutorialArrow.attachAsset('arrow', { anchorX: 0.5, anchorY: 0.5 }); arrowGfx.scaleX = 6; arrowGfx.scaleY = 6; // Point up if message is below area, down if above if (area.y < 300) { arrowGfx.rotation = -Math.PI / 2; // Point upward tutorialArrow.y = area.y + area.h / 2 + 60; } else { arrowGfx.rotation = Math.PI / 2; // Point downward tutorialArrow.y = area.y - area.h / 2 - 60; } tutorialArrow.x = area.x; tutorialArrow.zIndex = 10000; // Ensure arrow appears above all other objects tutorialArrow.alpha = 1.0; // Ensure arrow is fully visible without flashing // Stop any existing tweens on the arrow to prevent flashing if (typeof tween !== "undefined") { tween.stop(tutorialArrow, { alpha: true }); } game.addChild(tutorialArrow); } } function clearTutorialOverlay() { if (tutorialOverlay) { tutorialOverlay.destroy(); tutorialOverlay = null; } if (tutorialArrow) { tutorialArrow.destroy(); tutorialArrow = null; } if (tutorialMessage) { tutorialMessage.destroy(); tutorialMessage = null; } } function advanceTutorialStep() { clearTutorialOverlay(); tutorialStep++; console.log("Tutorial Step " + tutorialStep + " started"); // Step logic if (tutorialStep === 1) { // Step 1: Press red button to earn gold tutorialAllowRedButton = true; tutorialAllowDragTower = false; tutorialAllowTurretIcon = false; tutorialAllowUpgrade = false; tutorialAllowRepair = false; tutorialAllowForward = false; tutorialAllowAll = false; showTutorialOverlay(tutorialRedButtonArea, "Press the Red Button to earn Gold", "up"); } else if (tutorialStep === 2) { // Step 2: Keep pressing until 10 gold showTutorialOverlay(tutorialRedButtonArea, "Keep Pressing until you Reach 10 Gold", "up"); } else if (tutorialStep === 3) { // Step 3a: Show turret icon highlight first tutorialAllowRedButton = false; tutorialAllowTurretIcon = true; tutorialAllowDragTower = false; // Don't allow dragging yet tutorialAllowPlaceCell = 67; showTutorialOverlay(tutorialTurretIconArea, "Click the Turret Icon", "up"); } else if (tutorialStep === 4) { // Step 3b: Show placement highlight after clicking turret icon tutorialAllowTurretIcon = false; tutorialAllowDragTower = true; // Calculate cell 67's grid position var cell67GridX = (tutorialAllowPlaceCell - 1) % 12; var cell67GridY = Math.floor((tutorialAllowPlaceCell - 1) / 12) + 4; // Calculate center of cell 67 in game coordinates var cell67CenterX = grid.x + cell67GridX * CELL_SIZE * gameScale + CELL_SIZE * gameScale / 2 + tutorialCellOffsetX; var cell67CenterY = grid.y + cell67GridY * CELL_SIZE * gameScale + CELL_SIZE * gameScale / 2 + tutorialCellOffsetY; showTutorialOverlay({ x: cell67CenterX, y: cell67CenterY, w: CELL_SIZE * gameScale + 20, h: CELL_SIZE * gameScale + 20 }, "Drag the Turret to the Highlighted Area", null); } else if (tutorialStep === 5) { // Step 5: Tap the turret to upgrade to level 2 (10 clicks) tutorialAllowTurretIcon = false; tutorialAllowDragTower = false; tutorialAllowUpgrade = true; showTutorialOverlay({ x: tutorialTower.x + tutorialOffsetStep5X, y: tutorialTower.y + tutorialOffsetStep5Y, w: 200, h: 200 }, "Tap the Turret to Upgrade it to Level 2", "up"); } else if (tutorialStep === 6) { // Step 6: Kill all monsters before they reach the kingdom! tutorialAllowUpgrade = false; tutorialAllowBasicEnemy = true; // No highlight, just clear overlay and wait clearTutorialOverlay(); // Spawn 1 basic enemy spawnTutorialBasicEnemy(); // After 5 seconds, highlight the forward button LK.setTimeout(function () { // Only show highlight if speed boost button is not already active AND tutorial is still active if (!speedBoostButton.isActive && tutorialActive) { tutorialAllowForward = true; showTutorialOverlay(tutorialForwardIconArea, "Press the Forward Icon to Speed up the Game", "down"); } }, 5000); } else if (tutorialStep === 7) { tutorialAllowForward = false; tutorialAllowFlyingEnemy = true; var flyingEnemyArea = { x: 1120, y: 200, w: 300, h: 800 }; showTutorialOverlay(flyingEnemyArea, "Watch out for Flying Enemies!\nThey Fly over the Gate and go straight to the Kingdom", "down"); LK.setTimeout(function () { if (tutorialStep === 7) { clearTutorialOverlay(); } ; }, 4000); spawnTutorialFlyingEnemy(); } else if (tutorialStep === 8) { var checkGoldForStep9 = function checkGoldForStep9() { if (tutorialStep !== 8) { return; } if (gold < 5) { // SOLO dibuja la superposición la primera vez if (!overlayShown) { showTutorialOverlay(tutorialRedButtonArea, "Click again to keep earning more money", "up"); overlayShown = true; } } else { // El jugador llegó a 5 de oro clearTutorialOverlay(); overlayShown = false; if (highlightGoldButtonInterval) { LK.clearInterval(highlightGoldButtonInterval); highlightGoldButtonInterval = null; } advanceTutorialStep(); // pasa al paso 9 } }; // --- NUEVO: Highlight gold button and require 5 gold before step 9 --- // Only proceed to step 9 after player reaches 5 gold var overlayShown = false; // evita que la flecha se redibuje var highlightGoldButtonInterval = null; var goldButtonHighlighted = false; highlightGoldButtonInterval = LK.setInterval(checkGoldForStep9, 300); // Also check immediately in case player already has 5+ gold checkGoldForStep9(); // Do NOT advance to step 9 here. Wait for flying enemy to be killed (handled in game.update). return; } else if (tutorialStep === 9) { // Step 9: Click the gate to repair if damaged tutorialAllowFlyingEnemy = false; tutorialAllowRepair = true; // Clear any existing highlight effects from previous steps goldButtonHighlighted = false; if (typeof highlightGoldButtonInterval !== "undefined" && highlightGoldButtonInterval) { LK.clearInterval(highlightGoldButtonInterval); highlightGoldButtonInterval = null; } showTutorialOverlay(tutorialGateArea, "Click the gate to repair it if it's damaged", "down"); } else if (tutorialStep === 10) { // Step 10: Good luck, start game tutorialAllowRepair = false; // Update existing tutorial message instead of creating new overlay showTutorialOverlay({ x: 2048 / 2, y: 2732 / 2, w: 0, h: 0 }, "Good Luck", null); LK.setTimeout(function () { clearTutorialOverlay(); tutorialActive = false; tutorialAllowAll = true; // Save tutorial completion state storage.tutorialCompleted = true; // Start the game waveIndicator.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; // Hide the skip tutorial button when tutorial ends hideSkipTutorialButton(); }, 1200); // Show Tutorial button removed } } function spawnTutorialBasicEnemy() { // Only spawn if not already present if (tutorialBasicEnemy) { return; } var enemy = new Enemy('normal'); enemy.cellX = 6; enemy.cellY = 1; enemy.currentCellX = 6; enemy.currentCellY = 1; enemy.waveNumber = -1; enemy.maxHealth = 50; enemy.health = 50; enemyLayerBottom.addChild(enemy); enemies.push(enemy); tutorialBasicEnemy = enemy; } function spawnTutorialFlyingEnemy() { if (tutorialFlyingEnemy) { return; } var enemy = new Enemy('flying'); enemy.cellX = 6; enemy.cellY = 1; enemy.currentCellX = 6; enemy.currentCellY = 1; enemy.waveNumber = -2; enemy.maxHealth = 40; enemy.health = 40; enemyLayerTop.addChild(enemy); if (enemy.shadow) { enemyLayerMiddle.addChild(enemy.shadow); } enemies.push(enemy); tutorialFlyingEnemy = enemy; } // --- Skip Tutorial Button Implementation --- var skipTutorialButton = null; var skipTutorialButtonText = null; var showTutorialButton = null; var showTutorialButtonText = null; function showSkipTutorialButton() { if (skipTutorialButton) { return; } skipTutorialButton = new Container(); skipTutorialButton.zIndex = 10001; // Move to bottom right, with minimal margin from the edge (closer to corner) skipTutorialButton.x = 2048 - 40 - 300; // 40px margin, 300 is half width skipTutorialButton.y = 2732 - 40 - 70; // 40px margin, 70 is half height skipTutorialButton.visible = true; var btnBg = skipTutorialButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); btnBg.width = 600; btnBg.height = 140; btnBg.tint = 0x222222; btnBg.alpha = 0.85; skipTutorialButtonText = new Text2("Skip Tutorial", { size: 70, fill: 0xffffff, weight: 800 }); skipTutorialButtonText.anchor.set(0.5, 0.5); skipTutorialButton.addChild(skipTutorialButtonText); skipTutorialButton.down = function (x, y, obj) { // Remove button if (skipTutorialButton) { skipTutorialButton.visible = false; skipTutorialButton.destroy(); skipTutorialButton = null; } // --- Begin: Cancel all pending tutorial processes and remove test monsters --- // Clear any tutorial overlay and arrow/message clearTutorialOverlay(); // Cancel any highlightGoldButtonInterval or tutorial timers if (typeof highlightGoldButtonInterval !== "undefined" && highlightGoldButtonInterval) { LK.clearInterval(highlightGoldButtonInterval); highlightGoldButtonInterval = null; } // Remove any tutorial flying enemy if (typeof tutorialFlyingEnemy !== "undefined" && tutorialFlyingEnemy) { // Remove from enemies array and destroy for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === tutorialFlyingEnemy) { if (tutorialFlyingEnemy.isFlying && tutorialFlyingEnemy.shadow) { if (typeof enemyLayerMiddle !== "undefined") { enemyLayerMiddle.removeChild(tutorialFlyingEnemy.shadow); } tutorialFlyingEnemy.shadow = null; } if (typeof enemyLayerTop !== "undefined") { enemyLayerTop.removeChild(tutorialFlyingEnemy); } enemies.splice(i, 1); tutorialFlyingEnemy.destroy(); break; } } tutorialFlyingEnemy = null; } // Remove any tutorial basic enemy if (typeof tutorialBasicEnemy !== "undefined" && tutorialBasicEnemy) { for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === tutorialBasicEnemy) { if (typeof enemyLayerBottom !== "undefined") { enemyLayerBottom.removeChild(tutorialBasicEnemy); } enemies.splice(i, 1); tutorialBasicEnemy.destroy(); break; } } tutorialBasicEnemy = null; } // --- End: Cancel all pending tutorial processes and remove test monsters --- // End tutorial, start game, give 10 gold, set door health to 100 tutorialActive = false; tutorialAllowAll = true; storage.tutorialCompleted = true; clearTutorialOverlay(); waveIndicator.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; setGold(10); globalDoorHealth = globalDoorMaxHealth; updateGlobalDoorHealthBar(); // Show Tutorial button removed }; game.addChild(skipTutorialButton); } function hideSkipTutorialButton() { if (skipTutorialButton) { skipTutorialButton.visible = false; skipTutorialButton.destroy(); skipTutorialButton = null; } } // Show Tutorial button and its implementation removed // Start tutorial or game based on completion state LK.setTimeout(function () { // Always start tutorial at game start tutorialStep = 0; // Start at step 0 so step 1 shows properly advanceTutorialStep(); showSkipTutorialButton(); // Show Tutorial button removed }, 500); // Start background music LK.playMusic('backgroundMusic', { loop: true }); var goldTextShadow = new Text2('Gold: ' + gold, { size: 90, fill: 0x000000, weight: 800 }); goldTextShadow.anchor.set(0.5, 0.5); LK.gui.top.addChild(goldTextShadow); goldTextShadow.x = 0; goldTextShadow.y = topMargin - 25; var goldText = new Text2('Gold: ' + gold, { size: 90, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); LK.gui.top.addChild(goldText); goldText.x = 0; goldText.y = topMargin - 30; function updateUI() { // Update the text and preserve the stroke styling goldTextShadow.destroy(); // Destroy the old shadow Text2 object goldTextShadow = new Text2('Gold: ' + gold, { size: 90, fill: 0x000000, weight: 800 }); goldTextShadow.anchor.set(0.5, 0.5); LK.gui.top.addChild(goldTextShadow); goldTextShadow.x = 0; goldTextShadow.y = topMargin - 25; goldText.destroy(); // Destroy the old Text2 object goldText = new Text2('Gold: ' + gold, { size: 90, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); LK.gui.top.addChild(goldText); goldText.x = 0; goldText.y = topMargin - 30; updateTowerPrice(); } function setGold(value) { gold = value; updateUI(); } // Create dirt background with multiple smaller tiles var dirtBackground = new Container(); // Create 6 background tiles arranged in a 2x3 grid var tilesX = 2; // 2 tiles horizontally var tilesY = 3; // 3 tiles vertically var tileScale = 0.55; // Make each tile slightly larger to create overlap var tileWidth = 2055 * tileScale; // Use actual asset width var tileHeight = 2738.26 * tileScale; // Use actual asset height // Calculate overlap amount (5% of tile size) var overlapX = tileWidth * 0.05; var overlapY = tileHeight * 0.05; // Calculate total coverage and starting positions with overlap var totalWidth = tilesX * tileWidth - (tilesX - 1) * overlapX; var totalHeight = tilesY * tileHeight - (tilesY - 1) * overlapY; var startX = (2048 - totalWidth) / 2; var startY = (2732 - totalHeight) / 2; for (var i = 0; i < tilesX; i++) { for (var j = 0; j < tilesY; j++) { var backgroundTile = dirtBackground.attachAsset('dirtBackground', { anchorX: 0.5, anchorY: 0.5 }); // Scale each tile with fixed scale (no variation) backgroundTile.scaleX = tileScale; backgroundTile.scaleY = tileScale; // Position tiles with overlap to eliminate gaps backgroundTile.x = startX + i * (tileWidth - overlapX) + tileWidth / 2; backgroundTile.y = startY + j * (tileHeight - overlapY) + tileHeight / 2; // No rotation or variation to maintain consistent orientation } } dirtBackground.x = 0; dirtBackground.y = 0; game.addChild(dirtBackground); 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(12, 13 + 6); // Calculate scale to fill full screen width (2048px) with the 11-cell width var gameScale = 2048 / (12 * CELL_SIZE); // Scale factor to fill screen width grid.scaleX = gameScale; grid.scaleY = gameScale; grid.x = 40 * gameScale; // Move 40 units right (10 + 20 + 10) and scale grid.y = (200 - CELL_SIZE * 4 - 20 - 30) * gameScale; // Move up 50 units total (20 + 30) and scale the Y position accordingly grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Create global door health bar after doors are created createGlobalDoorHealthBar(); game.addChild(debugLayer); // Scale tower layer to match grid towerLayer.scaleX = gameScale; towerLayer.scaleY = gameScale; game.addChild(towerLayer); // Scale enemy layer to match grid enemyLayer.scaleX = gameScale; enemyLayer.scaleY = gameScale; game.addChild(enemyLayer); var offset = 0; var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; // Create simple tower asset next to red button for dragging var buttonTower = new Container(); var buttonTowerGraphics = buttonTower.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); buttonTowerGraphics.scaleX = 3.5; buttonTowerGraphics.scaleY = 3.5; buttonTower.x = 700; buttonTower.y = 2732 - 210; buttonTower.width = buttonTowerGraphics.width * 3.5; buttonTower.height = buttonTowerGraphics.height * 3.5; game.addChild(buttonTower); // Add a shadow text for tower price outline var towerPriceTextShadow = new Text2('Price: ' + getTowerCost(), { size: 60, fill: 0x000000, weight: 800 }); towerPriceTextShadow.anchor.set(0.5, 0.5); towerPriceTextShadow.x = buttonTower.x + 2; towerPriceTextShadow.y = buttonTower.y + buttonTower.height / 2 + 40 + 2; game.addChild(towerPriceTextShadow); // Add a text display for the current tower price below the draggable tower var towerPriceText = new Text2('Price: ' + getTowerCost(), { size: 60, fill: 0xFFFFFF, weight: 800 }); towerPriceText.anchor.set(0.5, 0.5); towerPriceText.x = buttonTower.x; towerPriceText.y = buttonTower.y + buttonTower.height / 2 + 40; // Position below the tower game.addChild(towerPriceText); // Update the tower price text whenever the UI is updated function updateTowerPrice() { towerPriceTextShadow.setText('Price: ' + getTowerCost()); towerPriceText.setText('Price: ' + getTowerCost()); } // Create betting system UI elements var betContainer = new Container(); betContainer.x = 1280; // Move further right betContainer.y = 2732 - 250; // Move down game.addChild(betContainer); // Betting amount display (center) var betAmountTextShadow = new Text2('Bet: ' + currentBet, { size: 60, fill: 0x000000, weight: 800 }); betAmountTextShadow.anchor.set(0.5, 0.5); betAmountTextShadow.x = 2; betAmountTextShadow.y = 2; betContainer.addChild(betAmountTextShadow); var betAmountText = new Text2('Bet: ' + currentBet, { size: 60, fill: 0xFFD700, weight: 800 }); betAmountText.anchor.set(0.5, 0.5); betContainer.addChild(betAmountText); // Decrease bet button (left) var decreaseBetButton = new Container(); var decreaseBetButtonGraphics = decreaseBetButton.attachAsset('decreaseBetButton', { anchorX: 0.5, anchorY: 0.5 }); decreaseBetButtonGraphics.scaleX = 2.0; decreaseBetButtonGraphics.scaleY = 2.0; decreaseBetButton.x = -250; // Left of bet display with more spacing decreaseBetButton.y = 0; betContainer.addChild(decreaseBetButton); // Increase bet button (right) var increaseBetButton = new Container(); var increaseBetButtonGraphics = increaseBetButton.attachAsset('increaseBetButton', { anchorX: 0.5, anchorY: 0.5 }); increaseBetButtonGraphics.scaleX = 2.0; increaseBetButtonGraphics.scaleY = 2.0; increaseBetButton.x = 250; // Right of bet display with more spacing increaseBetButton.y = 0; betContainer.addChild(increaseBetButton); // Bet button (below the amount display) var betButton = new Container(); var betButtonGraphics = betButton.attachAsset('betButton', { anchorX: 0.5, anchorY: 0.5 }); betButtonGraphics.scaleX = 3.0678375; betButtonGraphics.scaleY = 3.0678375; betButton.x = 0; betButton.y = 150; // Increased separation from bet amount display betContainer.addChild(betButton); // Update betting display function function updateBetDisplay() { betAmountTextShadow.setText('Bet: ' + currentBet); betAmountText.setText('Bet: ' + currentBet); } var isDragging = false; function wouldBlockPath(gridX, gridY) { // Use offset coordinates for consistency with placement verification var checkGridX = Math.floor(gridX + 0.5); var checkGridY = Math.floor(gridY + 0.5); var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(checkGridX + i, checkGridY + 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() { return 10 + towersPlaced * 10; // Base cost 10, increases by 10 per tower } function getTowerSellValue(totalValue) { return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue; } function createGlobalDoorHealthBar() { if (doors.length === 0) { return; } // Create health bar container globalDoorHealthBar = new Container(); // Calculate position - center of all doors, slightly below var centerX = 457.5; var centerY = 1040; globalDoorHealthBar.x = centerX; globalDoorHealthBar.y = centerY; // Health bar background globalDoorHealthBarBg = globalDoorHealthBar.attachAsset('doorHealthBg', { anchorX: 0.5, anchorY: 0.5 }); // Health bar fill globalDoorHealthBarFill = globalDoorHealthBar.attachAsset('doorHealthBar', { anchorX: 0.5, anchorY: 0.5 }); // Health text globalDoorHealthText = new Text2(" ", { size: 18, fill: 0xFFFFFF, weight: 800 }); globalDoorHealthText.anchor.set(0.5, 0.5); globalDoorHealthBar.addChild(globalDoorHealthText); // Add to game with proper scaling towerLayer.addChild(globalDoorHealthBar); // Después de 1 segundo, fuerza la actualización del texto a "99/100" LK.setTimeout(function () { globalDoorHealthText.setText(" "); globalDoorHealthText.setText("95/100"); updateGlobalDoorHealthBar(); }, 1000); } function updateGlobalDoorHealthBar() { if (!globalDoorHealthBar) { return; } var healthPercent = globalDoorHealth / globalDoorMaxHealth; // Calculate the actual width based on health percentage - subtract a small amount to make 95% visible var actualWidth = Math.max(0, 450 * healthPercent - 3); // Subtract 3 pixels to ensure visible difference globalDoorHealthBarFill.width = actualWidth; // Change anchor to make it decrease from right to left globalDoorHealthBarFill.anchor.set(0, 0.5); // Position the fill bar so it aligns with the left edge of the background globalDoorHealthBarFill.x = -225; // Half of the background width (450/2) to align left edge globalDoorHealthText.setText(globalDoorHealth + "/" + globalDoorMaxHealth); // Force update the health bar fill width to ensure it reflects the actual health immediately after text update LK.setTimeout(function () { globalDoorHealthBarFill.width = actualWidth; }, 1); // Update all door visuals when global health changes for (var i = 0; i < doors.length; i++) { doors[i].updateDoorVisual(); } } function placeTower(gridX, gridY) { var towerCost = getTowerCost(); if (gold >= towerCost) { // Double-check that the cell is available for placement var cellNumber = (gridY - 4) * 12 + gridX + 1; var obstacleCells = [14, 17, 18, 19, 20, 21, 22, 23, 26, 35, 38, 39, 40, 41, 42, 43, 44, 45, 47, 50, 59, 62, 64, 65, 66, 67, 68, 69, 70, 71, 74, 83, 86, 87, 88, 89, 90, 91, 92, 93, 95, 98, 107, 110, 112, 113, 114, 115, 116, 117, 118, 119, 122, 130, 131, 16]; var wallCells = [1, 2, 11, 12, 13, 24, 25, 36, 37, 48, 49, 60, 61, 72, 73, 84, 85, 96, 97, 108, 109, 120, 121, 132, 133, 134, 135, 142, 143, 144]; var isObstacleCell = cellNumber >= 1 && cellNumber <= 144 && obstacleCells.indexOf(cellNumber) !== -1; var isWallCell = cellNumber >= 1 && cellNumber <= 144 && wallCells.indexOf(cellNumber) !== -1; var existingTower = grid.towerCells[gridX] && grid.towerCells[gridX][gridY]; if (isObstacleCell && !isWallCell && !existingTower) { var tower = new Tower(); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); towersPlaced++; // Increment tower counter updateTowerPrice(); // Update tower price display // Play tower placement sound LK.getSound('placeTowerSound').play(); grid.renderDebug(); return true; } else { var notification = game.addChild(new Notification("Cannot build here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } 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) { // --- Tutorial gating --- if (tutorialActive && !tutorialAllowAll) { // Step 1/2: Only allow red button if ((tutorialStep === 1 || tutorialStep === 2) && tutorialAllowRedButton) { if (x >= tutorialRedButtonArea.x - tutorialRedButtonArea.w / 2 && x <= tutorialRedButtonArea.x + tutorialRedButtonArea.w / 2 && y >= tutorialRedButtonArea.y - tutorialRedButtonArea.h / 2 && y <= tutorialRedButtonArea.y + tutorialRedButtonArea.h / 2) { // Simulate redButton.down if (!redButton.isPressed) { redButton.isPressed = true; redButton.removeChild(redButtonGraphics); redButtonGraphics = redButton.attachAsset('redButtonPressed', { anchorX: 0.5, anchorY: 0.5 }); redButtonGraphics.scaleX = 4.0; redButtonGraphics.scaleY = 4.0; setGold(gold + 1); tutorialRedButtonPresses++; // Show gold indicator var randomXOffset = Math.random() * 450 - 250; var goldIndicator = game.addChild(new GoldIndicator(1, LK.gui.top.x + goldText.x + 350 + randomXOffset, LK.gui.top.y + goldText.y + 140)); goldIndicator.scaleX = 1.5; goldIndicator.scaleY = 1.5; } // Step up only after first press (step 1->2) and at 3 gold (step 2->3) if (tutorialStep === 1 && tutorialRedButtonPresses >= 1) { advanceTutorialStep(); } else if (tutorialStep === 2 && gold >= 10) { advanceTutorialStep(); } return; } else { // Ignore all other clicks return; } } // Step 3: Only allow turret icon click to advance to step 4 if (tutorialStep === 3 && tutorialAllowTurretIcon) { if (x >= tutorialTurretIconArea.x - tutorialTurretIconArea.w / 2 && x <= tutorialTurretIconArea.x + tutorialTurretIconArea.w / 2 && y >= tutorialTurretIconArea.y - tutorialTurretIconArea.h / 2 && y <= tutorialTurretIconArea.y + tutorialTurretIconArea.h / 2) { advanceTutorialStep(); // Advance to step 4 to show placement highlight towerPreview.visible = true; isDragging = true; towerPreview.updateAppearance(); towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); return; } else { return; } } // Step 4: Only allow tower dragging (placement highlight is shown) if (tutorialStep === 4 && tutorialAllowDragTower) { if (x >= tutorialTurretIconArea.x - tutorialTurretIconArea.w / 2 && x <= tutorialTurretIconArea.x + tutorialTurretIconArea.w / 2 && y >= tutorialTurretIconArea.y - tutorialTurretIconArea.h / 2 && y <= tutorialTurretIconArea.y + tutorialTurretIconArea.h / 2) { towerPreview.visible = true; isDragging = true; towerPreview.updateAppearance(); towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); return; } else { return; } } // Step 5: Only allow upgrade on tutorial tower if (tutorialStep === 5 && tutorialAllowUpgrade) { if (tutorialTower) { var tx = tutorialTower.x, ty = tutorialTower.y, tw = 200, th = 200; if (x >= tx - tw / 2 && x <= tx + tw / 2 && y >= ty - th / 2 && y <= ty + th / 2) { // Simulate tower.down for upgrade tutorialTowerUpgradeClicks++; tutorialTower.down(x, y, obj); return; } else { return; } } } // Step 6/7: Allow forward icon (step 6 sets up the highlight with timeout, step 7 handles the press) if (tutorialStep === 6 && tutorialAllowForward) { if (x >= tutorialForwardIconArea.x - tutorialForwardIconArea.w / 2 && x <= tutorialForwardIconArea.x + tutorialForwardIconArea.w / 2 && y >= tutorialForwardIconArea.y - tutorialForwardIconArea.h / 2 && y <= tutorialForwardIconArea.y + tutorialForwardIconArea.h / 2) { // Clear tutorial overlay immediately when forward is pressed clearTutorialOverlay(); // Simulate speedBoostButton.down if (!speedBoostButton.isPressed) { speedBoostButton.isPressed = true; speedBoostButton.isActive = true; speedBoostActive = true; speedBoostButton.removeChild(speedBoostButtonGraphics); speedBoostButtonGraphics = speedBoostButton.attachAsset('speedBoostButtonPressed', { anchorX: 0.5, anchorY: 0.5 }); speedBoostButtonGraphics.scaleX = 4.0; speedBoostButtonGraphics.scaleY = 4.0; // Stop forward loop sound and resume background music if (forwardLoopPlaying) { LK.getSound('forwardLoopSound').stop(); forwardLoopPlaying = false; LK.playMusic('backgroundMusic', { loop: true }); } } return; } else { return; } } // Step 9: Only allow gate repair if (tutorialStep === 9) { console.log("Checking door health:", globalDoorHealth); if (globalDoorHealth >= 99) { console.log("Door fully repaired. Advancing tutorial."); advanceTutorialStep(); } return; } // Block all other actions return; } // --- Normal game code below --- // Check for the normal tower next to red button (buttonTower) if (x >= buttonTower.x - buttonTower.width / 2 && x <= buttonTower.x + buttonTower.width / 2 && y >= buttonTower.y - buttonTower.height / 2 && y <= buttonTower.y + buttonTower.height / 2) { towerPreview.visible = true; isDragging = true; towerPreview.updateAppearance(); // Apply the same offset as in move handler to ensure consistency when starting drag towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); return; } // Check if clicking on any existing tower - if not, hide range 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; } } // If not clicking on a tower, hide the range display if (!clickedOnTower && rangeDisplayTower) { rangeDisplayTower.hideRangeIndicator(); rangeDisplayTower = null; } }; game.move = function (x, y, obj) { if (isDragging) { // Shift the y position upward by 1.5 tiles to show preview above finger towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); } }; 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; } } // Clear press timer when releasing anywhere if (pressedTower) { pressedTower = null; if (pressTimer) { LK.clearTimeout(pressTimer); pressTimer = null; } } if (isDragging) { isDragging = false; // --- Tutorial gating for tower placement --- if (tutorialActive && tutorialStep === 4 && tutorialAllowDragTower) { // Only allow placement on cell 67 var cellNumber = (towerPreview.gridY - 4) * 12 + towerPreview.gridX + 1; if (towerPreview.canPlace && cellNumber === tutorialAllowPlaceCell) { placeTower(towerPreview.gridX, towerPreview.gridY); // Find the just-placed tower and mark as tutorialTower for (var i = 0; i < towers.length; i++) { var t = towers[i]; var tCell = (t.gridY - 4) * 12 + t.gridX + 1; if (tCell === tutorialAllowPlaceCell) { tutorialTower = t; break; } } advanceTutorialStep(); } else { var notification = game.addChild(new Notification("Place the turret on the highlighted cell!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } towerPreview.visible = false; return; } // --- End tutorial gating for placement --- if (towerPreview.canPlace) { placeTower(towerPreview.gridX, towerPreview.gridY); } 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; } }; var waveIndicator = new WaveIndicator(); waveIndicator.scaleX = 1 / 1.5; waveIndicator.scaleY = 1 / 1.5; waveIndicator.x = 2048 - 200; waveIndicator.y = 2732 - 80; game.addChild(waveIndicator); // Do not start the game until tutorial is complete waveIndicator.gameStarted = false; currentWave = 0; waveTimer = nextWaveTime; // Create simple tower asset next to red button for dragging var buttonTower = new Container(); var buttonTowerGraphics = buttonTower.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); buttonTowerGraphics.scaleX = 3.5; buttonTowerGraphics.scaleY = 3.5; buttonTower.x = 700; buttonTower.y = 2732 - 210; buttonTower.width = buttonTowerGraphics.width * 3.5; buttonTower.height = buttonTowerGraphics.height * 3.5; game.addChild(buttonTower); sourceTower = null; enemiesToSpawn = 10; // Create red button for manual firing and gold generation var redButton = new Container(); var redButtonGraphics = redButton.attachAsset('redButton', { anchorX: 0.5, anchorY: 0.5 }); redButtonGraphics.scaleX = 4.0; redButtonGraphics.scaleY = 4.0; redButton.x = 250; redButton.y = 2732 - 220; redButton.isPressed = false; game.addChild(redButton); // Create test button in top-left for adding 1000 gold var testButton = new Container(); var testButtonGraphics = testButton.attachAsset('redButton', { anchorX: 0.5, anchorY: 0.5 }); testButtonGraphics.scaleX = 2.0; testButtonGraphics.scaleY = 2.0; testButton.x = 200; testButton.y = 300; testButton.isPressed = false; testButton.visible = false; // Hide the test button game.addChild(testButton); // Test button functionality testButton.down = function (x, y, obj) { if (!testButton.isPressed) { testButton.isPressed = true; // Change button appearance to pressed state testButton.removeChild(testButtonGraphics); testButtonGraphics = testButton.attachAsset('redButtonPressed', { anchorX: 0.5, anchorY: 0.5 }); testButtonGraphics.scaleX = 2.0; testButtonGraphics.scaleY = 2.0; // Give 1000 gold setGold(gold + 1000); // Upgrade all towers to level 5 var towersUpgraded = 0; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; if (tower.level < 5) { // Set tower to level 5 tower.level = 5; tower.clicksInvested = 0; // Apply level 5 upgrades tower.fireRate = Math.max(5, 60 - tower.level * 24); tower.damage = 10 + tower.level * 20; tower.bulletSpeed = 5 + tower.level * 2.4; // Update visual indicators tower.refreshCellsInRange(); tower.updateLevelIndicators(); towersUpgraded++; } } // Show notification about towers upgraded and gold given if (towersUpgraded > 0) { var notification = game.addChild(new Notification("Upgraded " + towersUpgraded + " towers to level 5! +1000 gold")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else { var notification = game.addChild(new Notification("All towers already at max level! +1000 gold")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } }; testButton.up = function (x, y, obj) { if (testButton.isPressed) { testButton.isPressed = false; // Change button back to normal state testButton.removeChild(testButtonGraphics); testButtonGraphics = testButton.attachAsset('redButton', { anchorX: 0.5, anchorY: 0.5 }); testButtonGraphics.scaleX = 2.0; testButtonGraphics.scaleY = 2.0; } }; // Create speed boost button var speedBoostButton = new Container(); var speedBoostButtonGraphics = speedBoostButton.attachAsset('speedBoostButton', { anchorX: 0.5, anchorY: 0.5 }); speedBoostButtonGraphics.scaleX = 4.0; speedBoostButtonGraphics.scaleY = 4.0; speedBoostButton.x = 2048 - 150; speedBoostButton.y = 120; speedBoostButton.isPressed = false; speedBoostButton.isActive = false; speedBoostButton.cooldown = 0; speedBoostButton.visible = true; // Show the speed boost button game.addChild(speedBoostButton); // Create super speed boost button (10X) var superSpeedBoostButton = new Container(); var superSpeedBoostButtonGraphics = superSpeedBoostButton.attachAsset('speedBoostButton', { anchorX: 0.5, anchorY: 0.5 }); superSpeedBoostButtonGraphics.scaleX = 4.0; superSpeedBoostButtonGraphics.scaleY = 4.0; superSpeedBoostButton.x = 2048 - 150; superSpeedBoostButton.y = 220; // Position below the original button superSpeedBoostButton.isPressed = false; superSpeedBoostButton.isActive = false; superSpeedBoostButton.cooldown = 0; superSpeedBoostButton.visible = false; // Hide the super speed boost button game.addChild(superSpeedBoostButton); // Speed boost variables var speedBoostActive = false; var speedBoostDuration = 0; var speedBoostCooldown = 0; var speedBoostMaxDuration = 600; // 10 seconds at 60fps var speedBoostMaxCooldown = 1800; // 30 seconds at 60fps // Sound control variables var forwardLoopPlaying = false; // Super speed boost variables var superSpeedBoostActive = false; // Speed boost button functionality speedBoostButton.down = function (x, y, obj) { if (!speedBoostButton.isPressed) { speedBoostButton.isPressed = true; speedBoostButton.isActive = true; speedBoostActive = true; // Change button appearance to pressed state speedBoostButton.removeChild(speedBoostButtonGraphics); speedBoostButtonGraphics = speedBoostButton.attachAsset('speedBoostButtonPressed', { anchorX: 0.5, anchorY: 0.5 }); speedBoostButtonGraphics.scaleX = 4.0; speedBoostButtonGraphics.scaleY = 4.0; // Stop background music and play forward loop sound LK.stopMusic(); forwardLoopPlaying = true; LK.getSound('forwardLoopSound').play(); // Show activation notification var notification = game.addChild(new Notification("Speed Boost Active!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; speedBoostButton.up = function (x, y, obj) { if (speedBoostButton.isPressed) { speedBoostButton.isPressed = false; speedBoostButton.isActive = false; speedBoostActive = false; // Change button back to normal state speedBoostButton.removeChild(speedBoostButtonGraphics); speedBoostButtonGraphics = speedBoostButton.attachAsset('speedBoostButton', { anchorX: 0.5, anchorY: 0.5 }); speedBoostButtonGraphics.scaleX = 4.0; speedBoostButtonGraphics.scaleY = 4.0; } }; // Super speed boost button functionality (10X) superSpeedBoostButton.down = function (x, y, obj) { if (!superSpeedBoostButton.isPressed) { superSpeedBoostButton.isPressed = true; superSpeedBoostButton.isActive = true; superSpeedBoostActive = true; // Change button appearance to pressed state superSpeedBoostButton.removeChild(superSpeedBoostButtonGraphics); superSpeedBoostButtonGraphics = superSpeedBoostButton.attachAsset('speedBoostButtonPressed', { anchorX: 0.5, anchorY: 0.5 }); superSpeedBoostButtonGraphics.scaleX = 4.0; superSpeedBoostButtonGraphics.scaleY = 4.0; // Show activation notification var notification = game.addChild(new Notification("Super Speed Boost Active! (50X)")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; // Betting system button functionality decreaseBetButton.down = function (x, y, obj) { // Change to pressed state decreaseBetButton.removeChild(decreaseBetButtonGraphics); decreaseBetButtonGraphics = decreaseBetButton.attachAsset('decreaseBetButtonPressed', { anchorX: 0.5, anchorY: 0.5 }); decreaseBetButtonGraphics.scaleX = 2.0; decreaseBetButtonGraphics.scaleY = 2.0; if (currentBet > minBet) { // Ensure we only bet in multiples of 10 var newBet = currentBet - 10; // Round to nearest multiple of 10 (though it should already be) newBet = Math.round(newBet / 10) * 10; currentBet = Math.max(minBet, newBet); updateBetDisplay(); // Play bet decrease sound LK.getSound('betDecreaseSound').play(); } }; decreaseBetButton.up = function (x, y, obj) { // Change back to normal state decreaseBetButton.removeChild(decreaseBetButtonGraphics); decreaseBetButtonGraphics = decreaseBetButton.attachAsset('decreaseBetButton', { anchorX: 0.5, anchorY: 0.5 }); decreaseBetButtonGraphics.scaleX = 2.0; decreaseBetButtonGraphics.scaleY = 2.0; }; increaseBetButton.down = function (x, y, obj) { // Change to pressed state increaseBetButton.removeChild(increaseBetButtonGraphics); increaseBetButtonGraphics = increaseBetButton.attachAsset('increaseBetButtonPressed', { anchorX: 0.5, anchorY: 0.5 }); increaseBetButtonGraphics.scaleX = 2.0; increaseBetButtonGraphics.scaleY = 2.0; var newBet = currentBet + 10; // Check if player has at least 10 gold to place any bet if (gold < 10) { var notification = game.addChild(new Notification("You need at least 10 gold to place a bet")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } // Only allow increment if player has enough gold for the complete next bet amount if (gold >= newBet) { // Ensure we only bet in multiples of 10 // Round to nearest multiple of 10 (though it should already be) newBet = Math.round(newBet / 10) * 10; currentBet = newBet; updateBetDisplay(); // Play bet increase sound LK.getSound('betIncreaseSound').play(); } }; increaseBetButton.up = function (x, y, obj) { // Change back to normal state increaseBetButton.removeChild(increaseBetButtonGraphics); increaseBetButtonGraphics = increaseBetButton.attachAsset('increaseBetButton', { anchorX: 0.5, anchorY: 0.5 }); increaseBetButtonGraphics.scaleX = 2.0; increaseBetButtonGraphics.scaleY = 2.0; }; betButton.down = function (x, y, obj) { // Ensure bet is a multiple of 10 if (currentBet % 10 !== 0) { currentBet = Math.round(currentBet / 10) * 10; updateBetDisplay(); } if (currentBet > 0 && gold >= currentBet) { setGold(gold - currentBet); // Create spinning wheel in center of screen var spinningWheel = new SpinningWheel(); spinningWheel.x = 2048 / 2; spinningWheel.y = 2732 / 2; spinningWheel.scaleX = 1.5; spinningWheel.scaleY = 1.5; game.addChild(spinningWheel); // Create fixed arrow pointer on the right side var wheelArrow = new Container(); var arrowGraphics = wheelArrow.attachAsset('arrow', { anchorX: 0.5, anchorY: 0.5 }); // Use scale instead of width/height for better quality arrowGraphics.scaleX = 7.0; arrowGraphics.scaleY = 7.0; arrowGraphics.tint = 0xFF0000; // Red color to make it stand out arrowGraphics.rotation = Math.PI; // Point left toward the wheel wheelArrow.x = spinningWheel.x + 600; // Position further to the right of the larger wheel wheelArrow.y = spinningWheel.y; // Same vertical position as wheel center // Set zIndex above all other elements, including turret bullets and tutorial arrows wheelArrow.zIndex = 20001; game.addChild(wheelArrow); // Store reference to arrow for cleanup spinningWheel.wheelArrow = wheelArrow; // Play bet button sound LK.getSound('betButtonSound').play(); spinningWheel.spin(currentBet); var notification = game.addChild(new Notification("Spinning the wheel!")); notification.x = 2048 / 2; notification.y = grid.height - 150; // Reset bet after placing currentBet = 0; updateBetDisplay(); } else if (currentBet <= 0) { var notification = game.addChild(new Notification("Set a bet amount first!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; superSpeedBoostButton.up = function (x, y, obj) { if (superSpeedBoostButton.isPressed) { superSpeedBoostButton.isPressed = false; superSpeedBoostButton.isActive = false; superSpeedBoostActive = false; // Change button back to normal state superSpeedBoostButton.removeChild(superSpeedBoostButtonGraphics); superSpeedBoostButtonGraphics = superSpeedBoostButton.attachAsset('speedBoostButton', { anchorX: 0.5, anchorY: 0.5 }); superSpeedBoostButtonGraphics.scaleX = 4.0; superSpeedBoostButtonGraphics.scaleY = 4.0; } }; // Button functionality redButton.down = function (x, y, obj) { if (!redButton.isPressed) { redButton.isPressed = true; // Change button appearance to pressed state redButton.removeChild(redButtonGraphics); redButtonGraphics = redButton.attachAsset('redButtonPressed', { anchorX: 0.5, anchorY: 0.5 }); redButtonGraphics.scaleX = 4.0; redButtonGraphics.scaleY = 4.0; // Give 1 gold but cap at 10 during tutorial var newGold = gold + 1; if (tutorialActive && newGold > 10) { newGold = 10; } setGold(newGold); // Show gold increment indicator below the gold counter only if gold actually increased if (!(tutorialActive && gold >= 10)) { var randomXOffset = Math.random() * 450 - 250; // Random value between -250 and +200 var goldIndicator = game.addChild(new GoldIndicator(1, LK.gui.top.x + goldText.x + 350 + randomXOffset, LK.gui.top.y + goldText.y + 140)); goldIndicator.scaleX = 1.5; goldIndicator.scaleY = 1.5; } // Play red button sound LK.getSound('redButtonSound').play(); // Towers should not fire when red button is pressed // Red button now only gives gold } }; redButton.up = function (x, y, obj) { if (redButton.isPressed) { redButton.isPressed = false; // Change button back to normal state redButton.removeChild(redButtonGraphics); redButtonGraphics = redButton.attachAsset('redButton', { anchorX: 0.5, anchorY: 0.5 }); redButtonGraphics.scaleX = 4.0; redButtonGraphics.scaleY = 4.0; } }; game.update = function () { // Speed boost is now controlled by button press/release - no duration or cooldown needed // Apply speed boost to game mechanics var speedMultiplier = 1; if (superSpeedBoostActive) { speedMultiplier = 50; } else if (speedBoostActive) { speedMultiplier = 3; } // Process wave progression with speed boost for (var speedTick = 0; speedTick < speedMultiplier; speedTick++) { waveIndicator.handleWaveProgression(); } 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 boss announcement var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️")); notification.x = 2048 / 2; notification.y = grid.height - 200; } // Spawn the appropriate number of enemies // For Swarm waves, spawn 50 enemies instead of 30 and remove spacing var actualEnemyCount = waveType === 'swarm' ? 50 : enemyCount; for (var i = 0; i < actualEnemyCount; 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 enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier); enemy.health = enemy.maxHealth; // Increment speed slightly with wave number //enemy.speed = enemy.speed + currentWave * 0.002; // All enemy types now spawn in the middle 6 tiles at the top spacing var gridWidth = 12; var midPoint = Math.floor(gridWidth / 2); // 6 var spawnX, spawnY; if (waveType === 'swarm') { // For swarm waves, spawn without spacing - enemies spawn closely together spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 3 to 8 spawnY = -1 - i * 0.3; // Minimal spacing for swarm waves } else { // 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 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 } spawnY = -1 - i * 2 - Math.random() * 2; // Increased spacing between enemies } enemy.cellX = spawnX; enemy.cellY = 5; // Position after entry enemy.currentCellX = spawnX; enemy.currentCellY = spawnY; enemy.waveNumber = currentWave; enemies.push(enemy); } } var currentWaveEnemiesRemaining = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].waveNumber === currentWave) { currentWaveEnemiesRemaining = true; break; } } if (waveSpawned && !currentWaveEnemiesRemaining) { waveInProgress = false; waveSpawned = false; } } // Eliminate all enemies while super speed boost button is pressed if (superSpeedBoostActive) { for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; // Set enemy health to 0 to eliminate them enemy.health = 0; } } // Process enemies with speed boost for (var speedTick = 0; speedTick < speedMultiplier; speedTick++) { for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; if (enemy.health <= 0) { // --- Tutorial: check if this is the tutorial basic or flying enemy --- if (tutorialActive) { if (tutorialStep === 6 && tutorialBasicEnemy && enemy === tutorialBasicEnemy) { tutorialBasicEnemy = null; // Immediately spawn flying enemy when basic enemy is killed advanceTutorialStep(); // Don't advance tutorial step here - let forward button press handle it } if (tutorialStep === 7 && tutorialFlyingEnemy && enemy === tutorialFlyingEnemy) { tutorialFlyingEnemy = null; // ← IMPORTANTE: liberar la referencia advanceTutorialStep(); // pasa al paso 8 return; } } for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } // Clean up door attacking if enemy was attacking a door if (enemy.attackingDoor) { enemy.attackingDoor.removeAttackingEnemy(enemy); enemy.attackingDoor = null; } // Enemies no longer give gold when killed // Gold indicator and gold earning removed // Play enemy death sound LK.getSound('enemyDeathSound').play(); // 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!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } updateUI(); // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); continue; } if (grid.updateEnemy(enemy)) { // Clean up door attacking if enemy was attacking a door if (enemy.attackingDoor) { enemy.attackingDoor.removeAttackingEnemy(enemy); enemy.attackingDoor = null; } // 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(); } } } } // Process bullets with speed boost for (var speedTick = 0; speedTick < speedMultiplier; speedTick++) { 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); } } } // Process towers with speed boost (for firing rate) for (var speedTick = 0; speedTick < speedMultiplier; speedTick++) { for (var t = 0; t < towers.length; t++) { var tower = towers[t]; if (tower.update) { tower.update(); } } } if (towerPreview.visible) { towerPreview.checkPlacement(); } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -1202,9 +1202,9 @@
}
self.isSpinning = true;
self.betAmount = betAmount;
// Set high zIndex to ensure wheel appears above all other elements, including turret bullets and tutorial arrows
- self.zIndex = 20000;
+ self.zIndex = 30000;
// Play wheel spin sound
LK.getSound('wheelSpinSound').play();
// Random number of full rotations (3-8) plus random section
var baseRotations = 3 + Math.random() * 5;
White circle with two eyes, seen from above.. In-Game asset. 2d. High contrast. No shadows
Top-down view of a single cartoon-style castle wall tile, seen from above, 2D game style, flat lighting, seamless edges, stone texture, bright colors, square shape, repeatable sprite, pixel-art or hand-drawn style.. In-Game asset. 2d. High contrast. No shadows
Top-down view of a single cartoon-style grass tile, seen from above, 2D game style, seamless and repeatable texture, bright colors, hand-drawn or pixel-art style, soft grass texture, 32x32 sprite.. In-Game asset. 2d. High contrast. No shadows
Top-down view of a single cartoon-style dirt and gravel path tile, seen from above, 2D game style, seamless and repeatable texture, hand-drawn or pixel-art style, natural colors, a mix of packed earth and scattered small stones, 32x32 sprite.. In-Game asset. 2d. High contrast. No shadows
Vista cenital de una sola baldosa de puerta de madera, aislada, vista desde arriba, estilo pixel art 2D para videojuego, textura continua y repetible, sprite de 32x32. Sin manija, sin bisagras, sin marco, solo tablones de madera con clavos decorativos. Textura de vetas estilizadas. Arte estilo hecho a mano. Alto contraste, sin sombras. Sin fondo.
Vista cenital de una baldosa de puerta de madera destruida, aislada, vista desde arriba, estilo pixel art 2D para videojuego, textura continua y repetible, sprite de 32x32. Sin manija, sin marco, sin bisagras. Tablones de madera rotos, astillas y clavos decorativos dispersos. Vetas de madera estilizadas. Alto contraste, arte estilo hecho a mano. Sin sombras. Sin fondo.. In-Game asset. 2d. High contrast. No shadows
Botón presionado rojo 2D, pixel cartoon.
Top-down view of a cartoon-style grass field, seen from above, 2D game style, bright colors, hand-drawn or pixel-art style, soft grass texture, 2000x3000 pixels In-Game asset. 2d. High contrast. No shadows
Un enemigo nivel básico visto desde arriba, pixel cartoon.. In-Game asset. 2d. High contrast. No shadows
Mueve sus pies de posición los de la izquierda más arriba y los de la derecha más abajo
Mueve las alas más abajo
Sin modificar el cuerpo, mueve los pies y las manos como si caminara así de frente a la camara.
Mueve los pies y las manos como si estuviera caminando de frente hacia la camara
Ballesta vista desde arriba, pixel cartoon.. In-Game asset. 2d. High contrast. No shadows
Flecha de arco vista desde arriba, pixel cartoon.. In-Game asset. 2d. High contrast. No shadows
Este mismo pero agrega soldados muy pequeños sobre las torres
Esto mismo pero los pies derechos
Esto mismo pero con los pies derechos
Caminando de frente hacia la camara.
Solo junta los pies, no modifiques nada mas!
Un boton de color banco para delantar el juego de velocidad, dos felchas negras apuntando a la derecha.. In-Game asset. 2d. High contrast. No shadows
White square. In-Game asset. 2d. High contrast. No shadows
Una ruleta, dividida en 8 partes, cada parte separada por una linea negra y cada parte rellena con un color brillante atractivo, sin fondo, sin flecha apuntando al resultado. In-Game asset. 2d. High contrast. No shadows
Un boton grande de apuestas que diga "BET" como en el casino. In-Game asset. 2d. High contrast. No shadows
Un boton azul fuerte tipo Casino que diga "+". In-Game asset. 2d. High contrast. No shadows
Un boton azul fuerte tipo Casino que diga "-". In-Game asset. 2d. High contrast. No shadows