User prompt
Me parece bien puedes trabajar en eso porfavor
User prompt
Mis amigos me dijeron que no les carga el juego sera por el facekit?
User prompt
Y has que los enemigos ataquen un poco más a las torres además los efectos de las torres pon les probabilidades que también se mejoran con el nivel de 5% a 50% o 55% como maximo has que el crecimiento por cada mejora sea natural
User prompt
Cuando mejoro una torre no ve que su salud mejore y ademas su ultima mejora les aumenta demasiado el ataque esta muy roto
User prompt
Oh si y recuerda que estos digimon invocados deben moverse correctamente por el camino hacia los enemigos, además creo que algunos sonidos no se estan usando y no entiendo por que
User prompt
Debes arreglar todos los digimon que pueden ser invocados estos son los mismos que se invocan por el microfono
User prompt
2 ERRORES A TOMAR EN CUENTA, el next wave no quiere funcionar en el desierto y los botones solo deberían mostrar las invocaciones que se pueden hacer además ni se mueven o toman los assets que les colocamos
User prompt
Por cierto añadiremos 3 botones llamados C, B y A estos estaran al lado del boton de la tienda y cada unos nos mostrara los digimon para invocar
User prompt
Acabo de ver un error con los mundos, estaba en el nivel 1 del desierto y al empezarlo me llevo al mundo 1 del bosque
User prompt
Si porfavor
User prompt
Un error presente es que cuando abres la tienda o el menú de mejora puedes terminar interactuando con la parte de colocar torres
User prompt
Y seria bueno agregar un botón en la parte de abajo para ver los digimon que puedes invocar por si la persona no le funciona el microfono,
User prompt
Sabes tal vez seria mejor que cuando se termine el nivel diga lo de win y play again así no sucedería el bug
User prompt
Tenemos un bug muy malo cuando derrotamos al jefe y se devuelve al menú principal, toda la música se mezcla y se pega, las opciones se quedan en un bucle que lagea el juego
User prompt
Oye si todas esas ideas son muy buenas puedes hacerlo
User prompt
Puedes arreglar todo lo referente al audio
User prompt
La musica se esta mezclando tenemos un problema
User prompt
Si me parecen muy bien, recuerda que hay que decir el nombre de ese Digimon para invocarlo ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
User prompt
Puedes crear los assets que faltan recargue la pagina sin poner algunos
User prompt
Si puedes crear todo eso
User prompt
Oh sin querer la pagina antes de colocar la musica del final boss puedes crear de nuevo su assets
User prompt
Puedes crear esos assets, y por cierto cuando se acaba el nivel 10 debe llevarte al menu principal en lugar de lanzar eso de ganaste play again
User prompt
Muy bien nos falta la musica de jefe y jefe final
User prompt
Puedes crear los assets de cada musica para cada mundo
User prompt
Tenemos 2 errores, el primer es que Snorlax no aparece como jefe en el ultimo nivel del primer mundo y además se cambia el mundo sin llegar hasta ese otro mundo, el segundo problema es que el boton de digievolucionar no deberia aparecer en los digimon si no que al comprar el item nos aparezca una flecha en donde seleccionamos las torres para cambiar de sección
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var facekit = LK.import("@upit/facekit.v1"); var storage = LK.import("@upit/storage.v1", { unlockedWorlds: 1, completedWaves: 0, digiviceC: false, digiviceB: false, digiviceA: false, worldLevels: {}, language: "en", securityPoints: 0 }); /**** * Classes ****/ var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) { var self = Container.call(this); self.targetEnemy = targetEnemy; self.damage = damage || 10; self.speed = speed || 5; self.x = startX; self.y = startY; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { // Apply damage to target enemy 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; } // Apply special effects based on bullet type if (self.type === 'splash') { // Create visual splash effect var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); game.addChild(splashEffect); // Play splash attack sound LK.getSound('splashAttack').play(); // Splash damage to nearby enemies var splashRadius = CELL_SIZE * 1.5; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self.targetEnemy) { var splashDx = otherEnemy.x - self.targetEnemy.x; var splashDy = otherEnemy.y - self.targetEnemy.y; var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy); if (splashDistance <= splashRadius) { // Apply splash damage (50% of original damage) otherEnemy.health -= self.damage * 0.5; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } else { otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70; } } } } } else if (self.type === 'slow') { // Prevent slow effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual slow effect var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); // Play freeze effect sound for slow attacks LK.getSound('freezeEffect').play(); // Apply slow effect // Make slow percentage scale with tower level (default 50%, up to 80% at max level) var slowPct = 0.5; if (self.sourceTowerLevel !== undefined) { // Scale: 50% at level 1, 60% at 2, 65% at 3, 70% at 4, 75% at 5, 80% at 6 var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8]; var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1)); slowPct = slowLevels[idx]; } if (!self.targetEnemy.slowed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; self.targetEnemy.speed *= 1 - slowPct; // Slow by X% self.targetEnemy.slowed = true; self.targetEnemy.slowDuration = 180; // 3 seconds at 60 FPS } else { self.targetEnemy.slowDuration = 180; // Reset duration } } } else if (self.type === 'poison') { // Prevent poison effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual poison effect var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Play poison effect sound LK.getSound('poisonEffect').play(); // Apply poison effect self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS } } else if (self.type === 'sniper') { // Create visual critical hit effect for sniper var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper'); game.addChild(sniperEffect); } // Special Digimon evolutionary line effects if (self.type === 'agumon' && self.burnChance) { // Agumon line: Fire/burn effects if (!self.targetEnemy.isImmune && Math.random() < self.burnChance) { self.targetEnemy.burning = true; self.targetEnemy.burnDamage = self.damage * 0.15; self.targetEnemy.burnDuration = 240; // 4 seconds var burnEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'burn'); game.addChild(burnEffect); // Play burn effect sound LK.getSound('burnEffect').play(); } } else if (self.type === 'gabumon' && self.freezeChance) { // Gabumon line: Ice/freeze effects if (!self.targetEnemy.isImmune && Math.random() < self.freezeChance) { self.targetEnemy.frozen = true; self.targetEnemy.frozenDuration = 120; // 2 seconds if (!self.targetEnemy.originalSpeed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; } self.targetEnemy.speed = 0; // Completely frozen var freezeEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'freeze'); game.addChild(freezeEffect); // Play freeze effect sound LK.getSound('freezeEffect').play(); } } else if (self.type === 'tentomon' && self.paralyzeChance) { // Tentomon line: Electric paralysis affecting multiple enemies if (!self.targetEnemy.isImmune && Math.random() < self.paralyzeChance) { // Paralyze main target self.targetEnemy.paralyzed = true; self.targetEnemy.paralyzeDuration = 180; // 3 seconds if (!self.targetEnemy.originalSpeed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; } self.targetEnemy.speed *= 0.1; // Nearly stopped var paralyzeEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'paralyze'); game.addChild(paralyzeEffect); // Spread paralysis to nearby enemies for (var i = 0; i < enemies.length; i++) { var nearbyEnemy = enemies[i]; if (nearbyEnemy !== self.targetEnemy && !nearbyEnemy.isImmune) { var dx = nearbyEnemy.x - self.targetEnemy.x; var dy = nearbyEnemy.y - self.targetEnemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.paralyzeArea) { nearbyEnemy.paralyzed = true; nearbyEnemy.paralyzeDuration = 120; // Shorter for spread effect if (!nearbyEnemy.originalSpeed) { nearbyEnemy.originalSpeed = nearbyEnemy.speed; } nearbyEnemy.speed *= 0.3; var spreadEffect = new EffectIndicator(nearbyEnemy.x, nearbyEnemy.y, 'paralyze'); game.addChild(spreadEffect); } } } } } else if (self.type === 'palmon' && self.poisonSpread) { // Palmon line: Spreading poison effects if (!self.targetEnemy.isImmune) { // Apply poison to main target self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.25; self.targetEnemy.poisonDuration = 360; // 6 seconds var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Spread poison to nearby enemies for (var i = 0; i < enemies.length; i++) { var nearbyEnemy = enemies[i]; if (nearbyEnemy !== self.targetEnemy && !nearbyEnemy.isImmune) { var dx = nearbyEnemy.x - self.targetEnemy.x; var dy = nearbyEnemy.y - self.targetEnemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.poisonRadius) { nearbyEnemy.poisoned = true; nearbyEnemy.poisonDamage = self.damage * 0.15; // Weaker spread poison nearbyEnemy.poisonDuration = 240; var spreadPoisonEffect = new EffectIndicator(nearbyEnemy.x, nearbyEnemy.y, 'poison'); game.addChild(spreadPoisonEffect); } } } } } else if (self.type === 'gomamon' && self.moistureEffect) { // Gomamon line: Moisture effects that affect fire/ice interactions if (!self.targetEnemy.isImmune) { self.targetEnemy.moist = true; self.targetEnemy.moistDuration = self.moistureDuration; self.targetEnemy.fireResistance = 0.5; // Reduced fire damage self.targetEnemy.freezeVulnerability = 1.5; // Increased freeze chance var moistEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'moist'); game.addChild(moistEffect); } } self.destroy(); } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); var Coin = Container.expand(function (x, y, value) { var self = Container.call(this); self.value = value || 5; self.x = x; self.y = y; self.collected = false; self.walkSpeed = 0.5; self.direction = Math.random() * Math.PI * 2; self.changeDirectionTimer = 0; var coinGraphics = self.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); coinGraphics.width = 30; coinGraphics.height = 30; coinGraphics.tint = 0xFFD700; // Add coin value text var valueText = new Text2(self.value.toString(), { size: 20, fill: 0x000000, weight: 800 }); valueText.anchor.set(0.5, 0.5); self.addChild(valueText); self.update = function () { if (self.collected) return; // Change direction occasionally self.changeDirectionTimer++; if (self.changeDirectionTimer > 120) { // Change direction every 2 seconds self.direction = Math.random() * Math.PI * 2; self.changeDirectionTimer = 0; } // Move in current direction var newX = self.x + Math.cos(self.direction) * self.walkSpeed; var newY = self.y + Math.sin(self.direction) * self.walkSpeed; // Keep within playable bounds var minX = grid.x + CELL_SIZE; var maxX = grid.x + grid.cells.length * CELL_SIZE - CELL_SIZE; var minY = grid.y + CELL_SIZE; var maxY = grid.y + grid.cells[0].length * CELL_SIZE - CELL_SIZE; if (newX < minX || newX > maxX) { self.direction = Math.PI - self.direction; // Bounce horizontally } else { self.x = newX; } if (newY < minY || newY > maxY) { self.direction = -self.direction; // Bounce vertically } else { self.y = newY; } // Check if player clicks on coin }; self.down = function () { if (!self.collected) { self.collected = true; setGold(gold + self.value); // Add 10 security score points score += 10; // Save security points to storage storage.securityPoints = score; updateUI(); // Play coin collect sound LK.getSound('coinCollect').play(); var goldIndicator = new GoldIndicator(self.value, self.x, self.y); game.addChild(goldIndicator); // Remove coin from coins array var coinIndex = coins.indexOf(self); if (coinIndex !== -1) { coins.splice(coinIndex, 1); } self.destroy(); } }; return self; }); var ComicPanel = Container.expand(function (imagePath, text) { var self = Container.call(this); // Panel background - larger panel var panelBg = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); panelBg.width = 700; panelBg.height = 500; panelBg.tint = 0x222222; panelBg.alpha = 0.9; // Add border effect var border = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); border.width = 710; border.height = 510; border.tint = 0x000000; border.alpha = 0.8; self.addChildAt(border, 0); // Text area - larger and repositioned var textBg = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); textBg.width = 660; textBg.height = 160; textBg.tint = 0xffffff; textBg.alpha = 0.95; textBg.y = 170; self.addChild(textBg); // Character image if provided - repositioned if (imagePath) { var characterImage = self.attachAsset(imagePath, { anchorX: 0.5, anchorY: 0.5 }); characterImage.width = 200; characterImage.height = 200; characterImage.y = -80; self.addChild(characterImage); } // Story text - improved sizing and positioning var storyText = new Text2(text, { size: 28, fill: 0x000000, weight: 600 }); storyText.anchor.set(0.5, 0.5); storyText.y = 170; storyText.wordWrap = true; storyText.wordWrapWidth = 600; storyText.maxWidth = 600; self.addChild(storyText); // Scale animation entrance self.scaleX = 0.3; self.scaleY = 0.3; self.alpha = 0; self.show = function () { tween(self, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 400, easing: tween.backOut }); }; self.hide = function (callback) { tween(self, { scaleX: 0.8, scaleY: 0.8, alpha: 0 }, { duration: 300, easing: tween.easeIn, onFinish: callback }); }; 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 = []; // Removed number label to improve performance // Numbers were causing lag due to text rendering overhead 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) { // Renderizado visual desactivado para evitar interferencia con tiles del mundo // Solo mantiene la lógica de pathfinding sin elementos visuales // Los tiles del mundo se renderizan a través de WorldRenderer // Actualizar las flechas solo si hay una torre seleccionada para debug if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { // Mostrar flechas solo para torres seleccionadas 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; } } else { // Remover flechas si no hay torre seleccionada self.removeArrows(); } // Hacer el gráfico de celda invisible para no interferir con tiles cellGraphics.alpha = 0; }; }); var DigimonShop = Container.expand(function () { var self = Container.call(this); self.visible = false; self.y = 2732; var shopBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); shopBackground.width = 2048; shopBackground.height = 600; shopBackground.tint = 0x222222; shopBackground.alpha = 0.95; var titleText = new Text2('Digimon Firewall Shop', { size: 80, fill: 0xFFFFFF, weight: 800 }); titleText.anchor.set(0.5, 0.5); titleText.y = -250; self.addChild(titleText); // Create shop items container var itemsContainer = new Container(); itemsContainer.y = -50; self.addChild(itemsContainer); // Digivice items data var digiviceItems = [{ id: 'digiviceC', name: 'Digivice C', cost: 500, description: 'Unlocks Champion Level' }, { id: 'digiviceB', name: 'Digivice B', cost: 2000, description: 'Unlocks Ultimate Level' }, { id: 'digiviceA', name: 'Digivice A', cost: 8000, description: 'Unlocks Mega Level' }]; // Create shop item buttons for (var i = 0; i < digiviceItems.length; i++) { var item = digiviceItems[i]; var itemButton = new Container(); itemButton.x = -600 + i * 400; itemButton.y = 0; itemsContainer.addChild(itemButton); var itemBg = itemButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); itemBg.width = 350; itemBg.height = 200; var itemNameText = new Text2(item.name, { size: 50, fill: 0xFFFFFF, weight: 800 }); itemNameText.anchor.set(0.5, 0.5); itemNameText.y = -50; itemButton.addChild(itemNameText); var itemDescText = new Text2(item.description, { size: 35, fill: 0xCCCCCC, weight: 400 }); itemDescText.anchor.set(0.5, 0.5); itemDescText.y = -10; itemButton.addChild(itemDescText); var itemCostText = new Text2(item.cost + ' points', { size: 45, fill: 0xFFD700, weight: 800 }); itemCostText.anchor.set(0.5, 0.5); itemCostText.y = 40; itemButton.addChild(itemCostText); // Create purchase functionality (function (itemData, button, background, costText) { button.update = function () { var owned = storage[itemData.id] || false; var canAfford = score >= itemData.cost; if (owned) { background.tint = 0x00AA00; costText.setText('OWNED'); button.alpha = 0.7; } else if (canAfford) { background.tint = 0x4444FF; costText.setText(itemData.cost + ' points'); button.alpha = 1.0; } else { background.tint = 0x666666; costText.setText(itemData.cost + ' points'); button.alpha = 0.5; } }; button.down = function () { var owned = storage[itemData.id] || false; var canAfford = score >= itemData.cost; if (owned) { var notification = game.addChild(new Notification("You already own " + itemData.name + "!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (canAfford) { score -= itemData.cost; updateUI(); storage[itemData.id] = true; var notification = game.addChild(new Notification("Purchased " + itemData.name + "!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else { var notification = game.addChild(new Notification("Not enough security points for " + itemData.name + "!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } }; })(item, itemButton, itemBg, itemCostText); } var closeButton = new Container(); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = shopBackground.width / 2 - 57; closeButton.y = -shopBackground.height / 2 + 57; self.addChild(closeButton); closeButton.down = function () { self.hide(); }; self.show = function () { self.visible = true; tween(self, { y: 2732 - 300 }, { duration: 300, easing: tween.backOut }); }; self.hide = function () { tween(self, { y: 2732 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.visible = false; } }); }; return self; }); var DigimonSummonMenu = Container.expand(function () { var self = Container.call(this); self.visible = false; self.y = 2732; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 600; menuBackground.tint = 0x222222; menuBackground.alpha = 0.95; var titleText = new Text2('Summon Digimon Allies', { size: 80, fill: 0xFFFFFF, weight: 800 }); titleText.anchor.set(0.5, 0.5); titleText.y = -250; self.addChild(titleText); // Create summon items container var itemsContainer = new Container(); itemsContainer.y = -50; self.addChild(itemsContainer); // Available Digimon based on unlocked Digivices function getAvailableDigimon() { var available = [{ id: 'koromon', name: 'Koromon', cost: 50, description: 'Rookie Melee Fighter', reqDigivice: null }, { id: 'tsunomon', name: 'Tsunomon', cost: 60, description: 'Rookie Ranged Fighter', reqDigivice: null }]; if (storage.digiviceC) { available.push({ id: 'greymon', name: 'Greymon', cost: 150, description: 'Champion Area Fighter', reqDigivice: 'digiviceC' }, { id: 'garurumon', name: 'Garurumon', cost: 180, description: 'Champion Fast Ranger', reqDigivice: 'digiviceC' }); } if (storage.digiviceB) { available.push({ id: 'metalgreymon', name: 'MetalGreymon', cost: 400, description: 'Ultimate Heavy Fighter', reqDigivice: 'digiviceB' }); } if (storage.digiviceA) { available.push({ id: 'wargreymon', name: 'WarGreymon', cost: 800, description: 'Mega Destroyer', reqDigivice: 'digiviceA' }); } return available; } function updateDigimonButtons() { // Clear existing buttons while (itemsContainer.children.length > 0) { itemsContainer.removeChild(itemsContainer.children[0]); } // Only show Digimon that can actually be summoned (requirements met) var availableDigimon = getAvailableDigimon().filter(function (d) { // Check digivice requirement if (d.reqDigivice && !storage[d.reqDigivice]) return false; // Check cost if (score < d.cost) return false; // Check maxAlliedUnits if (alliedUnits.length >= maxAlliedUnits) return false; return true; }); // If a filterLevel is set (from C, B, or A button), filter Digimon by evolution level var filterLevel = self._filterLevel; if (filterLevel) { // Map Digimon id to evolution level var digimonLevelMap = { koromon: "rookie", tsunomon: "rookie", greymon: "champion", garurumon: "champion", metalgreymon: "ultimate", wargreymon: "mega" }; availableDigimon = availableDigimon.filter(function (d) { return (digimonLevelMap[d.id] || "").toLowerCase() === filterLevel.toLowerCase(); }); } // Layout: 3 per row, center horizontally var buttonsPerRow = Math.min(3, availableDigimon.length); var buttonSpacing = 400; var startX = -((buttonsPerRow - 1) * buttonSpacing) / 2; for (var i = 0; i < availableDigimon.length; i++) { var digimon = availableDigimon[i]; var row = Math.floor(i / buttonsPerRow); var col = i % buttonsPerRow; var digimonButton = new Container(); digimonButton.x = startX + col * buttonSpacing; digimonButton.y = row * 180; itemsContainer.addChild(digimonButton); // Use correct asset for Digimon var digimonAssetMap = { koromon: 'agumon', tsunomon: 'gabumon', greymon: 'greymon', garurumon: 'garurumon', metalgreymon: 'metalgreymon', wargreymon: 'wargreymon' }; var assetId = digimonAssetMap[digimon.id] || 'agumon'; var digimonArt = digimonButton.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); digimonArt.width = 90; digimonArt.height = 90; digimonArt.y = -60; var buttonBg = digimonButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 350; buttonBg.height = 160; var nameText = new Text2(digimon.name, { size: 45, fill: 0xFFFFFF, weight: 800 }); nameText.anchor.set(0.5, 0.5); nameText.y = -40; digimonButton.addChild(nameText); var descText = new Text2(digimon.description, { size: 28, fill: 0xCCCCCC, weight: 400 }); descText.anchor.set(0.5, 0.5); descText.y = -10; digimonButton.addChild(descText); var costText = new Text2(digimon.cost + ' points', { size: 35, fill: 0xFFD700, weight: 800 }); costText.anchor.set(0.5, 0.5); costText.y = 30; digimonButton.addChild(costText); // Create functionality for each button (function (digimonData, button, background, costText) { button.update = function () { var canAfford = score >= digimonData.cost; var hasSpace = alliedUnits.length < maxAlliedUnits; var isAvailable = !summonCooldown || LK.ticks - lastSummonTime > summonCooldown; if (canAfford && hasSpace && isAvailable) { background.tint = 0x4444FF; button.alpha = 1.0; } else { background.tint = 0x666666; button.alpha = 0.7; } }; button.down = function () { var canAfford = score >= digimonData.cost; var hasSpace = alliedUnits.length < maxAlliedUnits; var isAvailable = !summonCooldown || LK.ticks - lastSummonTime > summonCooldown; if (!canAfford) { var notification = game.addChild(new Notification("Not enough security points for " + digimonData.name + "!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (!hasSpace) { var notification = game.addChild(new Notification("¡Límite de aliados alcanzado!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (!isAvailable) { var remainingCooldown = Math.ceil((summonCooldown - (LK.ticks - lastSummonTime)) / 60); var notification = game.addChild(new Notification("Summon cooldown: " + remainingCooldown + "s")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else { // Summon the Digimon score -= digimonData.cost; updateUI(); var newUnit = new DigimonUnit(digimonData.id, 1); enemyLayerTop.addChild(newUnit); // Add to top layer so they appear above enemies alliedUnits.push(newUnit); lastSummonTime = LK.ticks; var notification = game.addChild(new Notification(digimonData.name + " summoned!")); notification.x = 2048 / 2; notification.y = grid.height - 50; self.hide(); } }; })(digimon, digimonButton, buttonBg, costText); } } // Close button var closeButton = new Container(); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; self.addChild(closeButton); closeButton.down = function () { self.hide(); }; self.show = function (filterLevel) { // If a filterLevel is provided, filter Digimon by evolution level self._filterLevel = filterLevel || null; updateDigimonButtons(); self.visible = true; tween(self, { y: 2732 - 300 }, { duration: 300, easing: tween.backOut }); }; self.hide = function () { self._filterLevel = null; // Reset filter when closing tween(self, { y: 2732 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.visible = false; } }); }; self.update = function () { // Update all button states for (var i = 0; i < itemsContainer.children.length; i++) { var button = itemsContainer.children[i]; if (button.update) button.update(); } }; return self; }); var DigimonUnit = Container.expand(function (type, level) { var self = Container.call(this); self.type = type || 'koromon'; self.level = level || 1; self.health = 100 + (self.level - 1) * 50; self.maxHealth = self.health; self.damage = 20 + (self.level - 1) * 10; self.speed = 0.8; self.range = CELL_SIZE * 1.5; self.attackType = 'melee'; // 'melee', 'ranged', 'area' self.lastAttacked = 0; self.attackRate = 90; // Frames between attacks self.targetEnemy = null; self.isDead = false; // --- Digimon UI contextual elements --- var digimonNames = { koromon: "Koromon", tsunomon: "Tsunomon", greymon: "Greymon", garurumon: "Garurumon", metalgreymon: "MetalGreymon", wargreymon: "WarGreymon" }; var digimonLevels = { koromon: "Rookie", tsunomon: "Rookie", greymon: "Champion", garurumon: "Champion", metalgreymon: "Ultimate", wargreymon: "Mega" }; var digimonDescriptions = { koromon: "Rookie Melee Fighter", tsunomon: "Rookie Ranged Fighter", greymon: "Champion Area Fighter", garurumon: "Champion Fast Ranger", metalgreymon: "Ultimate Heavy Fighter", wargreymon: "Mega Destroyer" }; var digimonColor = { koromon: 0xFFAAAA, tsunomon: 0xAAAAFF, greymon: 0xFF6600, garurumon: 0x0066FF, metalgreymon: 0xFF3300, wargreymon: 0xFFCC00 }; // Set properties based on Digimon type and level switch (self.type) { case 'koromon': self.attackType = 'melee'; self.damage = 15 + (self.level - 1) * 8; break; case 'tsunomon': self.attackType = 'ranged'; self.damage = 12 + (self.level - 1) * 6; self.range = CELL_SIZE * 2; break; case 'greymon': self.attackType = 'area'; self.damage = 25 + (self.level - 1) * 15; self.health = 150 + (self.level - 1) * 75; break; case 'garurumon': self.attackType = 'ranged'; self.damage = 20 + (self.level - 1) * 12; self.range = CELL_SIZE * 2.5; self.speed = 1.2; break; case 'metalgreymon': self.attackType = 'area'; self.damage = 40 + (self.level - 1) * 25; self.health = 250 + (self.level - 1) * 100; self.range = CELL_SIZE * 2; break; case 'wargreymon': self.attackType = 'area'; self.damage = 60 + (self.level - 1) * 40; self.health = 400 + (self.level - 1) * 150; self.range = CELL_SIZE * 2.5; self.speed = 1.0; break; } self.maxHealth = self.health; // Use agumon asset as placeholder for all allied units var unitGraphics = self.attachAsset('agumon', { anchorX: 0.5, anchorY: 0.5 }); // Tint based on type switch (self.type) { case 'koromon': unitGraphics.tint = 0xFFAAAA; break; case 'tsunomon': unitGraphics.tint = 0xAAAAFF; break; case 'greymon': unitGraphics.tint = 0xFF6600; unitGraphics.scaleX = 1.3; unitGraphics.scaleY = 1.3; break; case 'garurumon': unitGraphics.tint = 0x0066FF; unitGraphics.scaleX = 1.3; unitGraphics.scaleY = 1.3; break; case 'metalgreymon': unitGraphics.tint = 0xFF3300; unitGraphics.scaleX = 1.6; unitGraphics.scaleY = 1.6; break; case 'wargreymon': unitGraphics.tint = 0xFFCC00; unitGraphics.scaleX = 2.0; unitGraphics.scaleY = 2.0; break; } // --- Level badge (top left of unit) --- var badgeColors = { Rookie: 0x00BFFF, Champion: 0x32CD32, Ultimate: 0xFFD700, Mega: 0xFF4500 }; var levelName = digimonLevels[self.type] || "Rookie"; var badgeColor = badgeColors[levelName] || 0x00BFFF; var badge = self.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); badge.width = 44; badge.height = 44; badge.x = -unitGraphics.width / 2 + 22; badge.y = -unitGraphics.height / 2 + 22; badge.tint = badgeColor; var badgeText = new Text2(levelName.charAt(0), { size: 32, fill: 0xffffff, weight: 800 }); badgeText.anchor.set(0.5, 0.5); badgeText.x = badge.x; badgeText.y = badge.y; self.addChild(badge); self.addChild(badgeText); // --- Name popup (on summon, above unit) --- var namePopup = new Text2(digimonNames[self.type] || self.type, { size: 60, fill: 0xffffff, weight: 800 }); namePopup.anchor.set(0.5, 1.0); namePopup.x = 0; namePopup.y = -unitGraphics.height / 2 - 30; namePopup.alpha = 0; self.addChild(namePopup); // Animate name popup on spawn tween(namePopup, { alpha: 1, y: namePopup.y - 30 }, { duration: 350, easing: tween.easeOut, onFinish: function onFinish() { tween(namePopup, { alpha: 0, y: namePopup.y - 60 }, { duration: 700, delay: 700, easing: tween.easeIn, onFinish: function onFinish() { namePopup.visible = false; } }); } }); // --- Tooltip on touch (shows name, level, description) --- var tooltip = new Container(); tooltip.visible = false; var tooltipBg = tooltip.attachAsset('notification', { anchorX: 0.5, anchorY: 1.0 }); tooltipBg.width = 340; tooltipBg.height = 120; tooltipBg.tint = 0x222222; tooltipBg.alpha = 0.95; var tooltipName = new Text2(digimonNames[self.type] || self.type, { size: 38, fill: 0xffffff, weight: 800 }); tooltipName.anchor.set(0.5, 0); tooltipName.y = -50; var tooltipLevel = new Text2(levelName, { size: 28, fill: badgeColor, weight: 800 }); tooltipLevel.anchor.set(0.5, 0); tooltipLevel.y = -15; var tooltipDesc = new Text2(digimonDescriptions[self.type] || "", { size: 24, fill: 0xcccccc, weight: 400 }); tooltipDesc.anchor.set(0.5, 0); tooltipDesc.y = 15; tooltip.addChild(tooltipBg); tooltip.addChild(tooltipName); tooltip.addChild(tooltipLevel); tooltip.addChild(tooltipDesc); tooltip.x = 0; tooltip.y = -unitGraphics.height / 2 - 10; self.addChild(tooltip); // Health bar 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 = -unitGraphics.height / 2 - 15; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - 0.5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; // Movement properties self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = null; self.pathTargets = []; // Initialize at goal positions (moving towards spawns) if (grid.goals && grid.goals.length > 0) { var startGoal = grid.goals[Math.floor(Math.random() * grid.goals.length)]; self.cellX = startGoal.x; self.cellY = startGoal.y; self.currentCellX = startGoal.x; self.currentCellY = startGoal.y; self.x = grid.x + self.currentCellX * CELL_SIZE; self.y = grid.y + self.currentCellY * CELL_SIZE; } // --- Touch interaction for tooltip --- self.down = function () { tooltip.visible = true; tooltip.alpha = 0; tween(tooltip, { alpha: 1 }, { duration: 120, easing: tween.easeOut }); // Hide after 2 seconds LK.setTimeout(function () { tween(tooltip, { alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { tooltip.visible = false; } }); }, 2000); }; self.findNearbyEnemies = function () { var nearbyEnemies = []; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range) { nearbyEnemies.push(enemy); } } return nearbyEnemies; }; self.attack = function (targetEnemy) { if (LK.ticks - self.lastAttacked < self.attackRate) return; self.lastAttacked = LK.ticks; switch (self.attackType) { case 'melee': // Direct damage targetEnemy.health -= self.damage; if (targetEnemy.health <= 0) targetEnemy.health = 0;else targetEnemy.healthBar.width = targetEnemy.health / targetEnemy.maxHealth * 70; // Visual effect var effect = new EffectIndicator(targetEnemy.x, targetEnemy.y, 'sniper'); game.addChild(effect); break; case 'ranged': // Create projectile var bullet = new Bullet(self.x, self.y, targetEnemy, self.damage, 8); bullet.type = 'sniper'; bullet.children[0].tint = 0x00AAFF; game.addChild(bullet); bullets.push(bullet); targetEnemy.bulletsTargetingThis.push(bullet); break; case 'area': // Area damage to all enemies in range var nearbyEnemies = self.findNearbyEnemies(); for (var i = 0; i < nearbyEnemies.length; i++) { var enemy = nearbyEnemies[i]; enemy.health -= self.damage; if (enemy.health <= 0) enemy.health = 0;else enemy.healthBar.width = enemy.health / enemy.maxHealth * 70; } // Visual splash effect var splashEffect = new EffectIndicator(self.x, self.y, 'splash'); game.addChild(splashEffect); break; } }; self.update = function () { if (self.isDead) return; // Update health bar if (self.health <= 0) { self.isDead = true; self.healthBar.width = 0; // Show disappearance animation (fade out and scale up) tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 600, easing: tween.easeIn, onFinish: function onFinish() { // Remove from allied units array and destroy after delay var unitIndex = alliedUnits.indexOf(self); if (unitIndex !== -1) alliedUnits.splice(unitIndex, 1); self.destroy(); } }); // Show notification of defeat var defeatNote = game.addChild(new Notification((digimonNames[self.type] || self.type) + " defeated!")); defeatNote.x = 2048 / 2; defeatNote.y = grid.height - 120; return; } self.healthBar.width = self.health / self.maxHealth * 70; // Find and attack nearby enemies var nearbyEnemies = self.findNearbyEnemies(); if (nearbyEnemies.length > 0) { // Stop and attack closest enemy var closestEnemy = nearbyEnemies[0]; var closestDistance = Infinity; for (var i = 0; i < nearbyEnemies.length; i++) { var enemy = nearbyEnemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } self.targetEnemy = closestEnemy; self.attack(closestEnemy); // Face the enemy var dx = closestEnemy.x - self.x; var dy = closestEnemy.y - self.y; var angle = Math.atan2(dy, dx); unitGraphics.rotation = angle; return; // Don't move while attacking } self.targetEnemy = null; // Move towards spawn points (reverse pathfinding) if (!self.currentTarget) { // Find path towards spawns using reverse pathfinding var cell = grid.getCell(self.cellX, self.cellY); if (cell && cell.targets && cell.targets.length > 0) { // Move towards higher score (away from goals, towards spawns) var bestTarget = null; var highestScore = -1; for (var i = 0; i < cell.targets.length; i++) { var target = cell.targets[i]; if (target.score > highestScore) { highestScore = target.score; bestTarget = target; } } self.currentTarget = bestTarget; } } // Move towards current target if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < self.speed) { self.cellX = Math.round(self.currentCellX); self.cellY = Math.round(self.currentCellY); self.currentTarget = null; return; } var angle = Math.atan2(oy, ox); self.currentCellX += Math.cos(angle) * self.speed * gameSpeed; self.currentCellY += Math.sin(angle) * self.speed * gameSpeed; // Face movement direction unitGraphics.rotation = angle; } // Update position self.x = grid.x + self.currentCellX * CELL_SIZE; self.y = grid.y + self.currentCellY * CELL_SIZE; // Check if reached spawn area (remove unit) if (self.currentCellY <= 3) { var unitIndex = alliedUnits.indexOf(self); if (unitIndex !== -1) alliedUnits.splice(unitIndex, 1); self.destroy(); } }; return self; }); // This update method was incorrectly placed here and should be removed var EffectIndicator = Container.expand(function (x, y, type) { var self = Container.call(this); self.x = x; self.y = y; var effectGraphics = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.blendMode = 1; switch (type) { case 'splash': effectGraphics.tint = 0x33CC00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5; break; case 'slow': effectGraphics.tint = 0x9900FF; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'poison': effectGraphics.tint = 0x00FFAA; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'sniper': effectGraphics.tint = 0xFF5500; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'burn': effectGraphics.tint = 0xFF4400; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.2; break; case 'freeze': effectGraphics.tint = 0x66CCFF; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.3; break; case 'paralyze': effectGraphics.tint = 0xFFFF00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.1; break; case 'moist': effectGraphics.tint = 0x0099CC; effectGraphics.width = effectGraphics.height = CELL_SIZE * 0.9; break; case 'heal': effectGraphics.tint = 0x00FF88; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.4; break; } effectGraphics.alpha = 0.7; self.alpha = 0; // Animate the effect tween(self, { alpha: 0.8, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); // Base enemy class for common functionality var Enemy = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'normal'; self.speed = .01; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 100; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.isFlying = false; self.isImmune = false; self.isBoss = false; self.worldNumber = currentWorld; // Track which world this enemy belongs to // Apply different stats based on enemy type switch (self.type) { case 'fast': self.speed *= 2; // Twice as fast self.maxHealth = 100; break; case 'immune': self.isImmune = true; self.maxHealth = 80; break; case 'flying': self.isFlying = true; self.maxHealth = 80; break; case 'swarm': self.maxHealth = 50; // Weaker enemies break; case 'normal': default: // Normal enemy uses default values break; } // Apply world-based scaling to all enemy types self.applyWorldScaling = function () { var worldMultiplier = 1; var speedMultiplier = 1; // Scale stats based on world progression switch (self.worldNumber) { case 1: // Forest - Base stats worldMultiplier = 1; speedMultiplier = 1; break; case 2: // Desert - 25% more health, 10% faster worldMultiplier = 1.25; speedMultiplier = 1.1; break; case 3: // Glacier - 50% more health, slightly slower worldMultiplier = 1.5; speedMultiplier = 0.95; break; case 4: // Village - 75% more health, 15% faster worldMultiplier = 1.75; speedMultiplier = 1.15; break; case 5: // Tech Lab - Double health, 20% faster worldMultiplier = 2.0; speedMultiplier = 1.2; break; case 6: // Inferno - Triple health, 25% faster worldMultiplier = 3.0; speedMultiplier = 1.25; break; default: worldMultiplier = 1; speedMultiplier = 1; } // Apply scaling self.maxHealth = Math.floor(self.maxHealth * worldMultiplier); self.speed *= speedMultiplier; }; // Apply world scaling self.applyWorldScaling(); if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') { self.isBoss = true; // Boss enemies have 20x health and are larger self.maxHealth *= 20; // Slower speed for bosses self.speed = self.speed * 0.7; } self.health = self.maxHealth; // Get appropriate asset for this virus type var assetId = 'virus'; if (self.isBoss) { // Use world-specific boss assets switch (self.worldNumber) { case 1: assetId = 'boss_snorlax'; break; case 2: assetId = 'boss_rhydon'; break; case 3: assetId = 'boss_articuno'; break; case 4: assetId = 'boss_machamp'; break; case 5: assetId = 'boss_groudon'; break; case 6: assetId = 'boss_mewtwo'; break; default: assetId = 'virus'; } } else if (self.type !== 'normal') { assetId = 'virus_' + self.type; } var enemyGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Scale up boss enemies (but less since they already have larger base assets) if (self.isBoss) { enemyGraphics.scaleX = 1.2; enemyGraphics.scaleY = 1.2; } // Apply world-based color tinting system self.getWorldTint = function () { var baseTint = 0xFFFFFF; // Default white tint // Get base color based on world switch (self.worldNumber) { case 1: // Forest - Green tints baseTint = 0x90EE90; break; case 2: // Desert - Sand/yellow tints baseTint = 0xF4A460; break; case 3: // Glacier - Blue/ice tints baseTint = 0xADD8E6; break; case 4: // Village - Brown/earth tints baseTint = 0xD2B48C; break; case 5: // Tech Lab - Metallic/silver tints baseTint = 0xC0C0C0; break; case 6: // Inferno - Red/fire tints baseTint = 0xFF6B6B; break; default: baseTint = 0xFFFFFF; } // Modify base tint slightly based on enemy type while keeping world theme switch (self.type) { case 'fast': // Make it slightly more blue-ish while keeping world color baseTint = tween.linear(baseTint, 0x0080FF, 0.3); break; case 'immune': // Make it slightly more red-ish while keeping world color baseTint = tween.linear(baseTint, 0xFF4444, 0.3); break; case 'flying': // Make it brighter while keeping world color baseTint = Math.min(0xFFFFFF, baseTint + 0x202020); break; case 'swarm': // Make it slightly darker while keeping world color baseTint = Math.max(0x404040, baseTint - 0x202020); break; } return baseTint; }; // Apply world-based tinting var worldTint = self.getWorldTint(); enemyGraphics.tint = worldTint; // Create shadow for flying enemies if (self.isFlying) { // Create a shadow container that will be added to the shadow layer self.shadow = new Container(); // Clone the enemy graphics for the shadow var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow effect shadowGraphics.tint = 0x000000; // Black shadow shadowGraphics.alpha = 0.4; // Semi-transparent // If this is a boss, scale up the shadow to match if (self.isBoss) { shadowGraphics.scaleX = 1.8; shadowGraphics.scaleY = 1.8; } // Position shadow slightly offset self.shadow.x = 20; // Offset right self.shadow.y = 20; // Offset down // Ensure shadow has the same rotation as the enemy shadowGraphics.rotation = enemyGraphics.rotation; } var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBarBG = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; self.update = function () { if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; } // Handle slow effect if (self.isImmune) { // Immune enemies cannot be slowed or poisoned, clear any such effects self.slowed = false; self.slowEffect = false; self.poisoned = false; self.poisonEffect = false; // Reset speed to original if needed if (self.originalSpeed !== undefined) { self.speed = self.originalSpeed; } } else { // Handle slow effect if (self.slowed) { // Visual indication of slowed status if (!self.slowEffect) { self.slowEffect = true; } self.slowDuration--; if (self.slowDuration <= 0) { self.speed = self.originalSpeed; self.slowed = false; self.slowEffect = false; // Only reset tint if not poisoned if (!self.poisoned) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } // Handle poison effect if (self.poisoned) { // Visual indication of poisoned status if (!self.poisonEffect) { self.poisonEffect = true; } // Apply poison damage every 30 frames (twice per second) if (LK.ticks % 30 === 0) { self.health -= self.poisonDamage; if (self.health <= 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; } self.poisonDuration--; if (self.poisonDuration <= 0) { self.poisoned = false; self.poisonEffect = false; // Only reset tint if not slowed if (!self.slowed) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } // Handle burn effect (Agumon line) if (self.burning) { if (LK.ticks % 45 === 0) { // Every 0.75 seconds var burnDamage = self.burnDamage; // Reduce burn damage if enemy is moist if (self.moist && self.fireResistance) { burnDamage *= self.fireResistance; } self.health -= burnDamage; if (self.health <= 0) self.health = 0;else self.healthBar.width = self.health / self.maxHealth * 70; } self.burnDuration--; if (self.burnDuration <= 0) { self.burning = false; } } // Handle freeze effect (Gabumon line) if (self.frozen) { self.frozenDuration--; if (self.frozenDuration <= 0) { self.frozen = false; if (self.originalSpeed !== undefined) { self.speed = self.originalSpeed; } } } // Handle paralysis effect (Tentomon line) if (self.paralyzed) { self.paralyzeDuration--; if (self.paralyzeDuration <= 0) { self.paralyzed = false; if (self.originalSpeed !== undefined) { self.speed = self.originalSpeed; } } } // Handle moisture effect (Gomamon line) if (self.moist) { self.moistDuration--; if (self.moistDuration <= 0) { self.moist = false; self.fireResistance = 1.0; self.freezeVulnerability = 1.0; } } } // Set tint based on effect status if (self.isImmune) { enemyGraphics.tint = 0xFFFFFF; } else if (self.poisoned && self.slowed) { // Combine poison (0x00FFAA) and slow (0x9900FF) colors // Simple average: R: (0+153)/2=76, G: (255+0)/2=127, B: (170+255)/2=212 enemyGraphics.tint = 0x4C7FD4; } else if (self.poisoned) { enemyGraphics.tint = 0x00FFAA; } else if (self.slowed) { enemyGraphics.tint = 0x9900FF; } else { enemyGraphics.tint = 0xFFFFFF; } if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; if (ox !== 0 || oy !== 0) { var angle = Math.atan2(oy, ox); if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) { tween.stop(enemyGraphics, { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemyGraphics.rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemyGraphics.targetRotation = angle; tween(enemyGraphics, { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } } } healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; }; return self; }); var GoldIndicator = Container.expand(function (value, x, y) { var self = Container.call(this); var shadowText = new Text2("+" + value, { size: 45, fill: 0x000000, weight: 800 }); shadowText.anchor.set(0.5, 0.5); shadowText.x = 2; shadowText.y = 2; self.addChild(shadowText); var goldText = new Text2("+" + value, { size: 45, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); self.x = x; self.y = y; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2, y: y - 40 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: y - 80 }, { duration: 600, easing: tween.easeIn, delay: 800, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; self.spawns = []; self.goals = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { score: 0, pathId: 0, towersInRange: [] }; } } /* Cell Types 0: Transparent floor 1: Wall 2: Spawn 3: Goal */ // Create world-based maze layout self.generateMazeForWorld = function (worldNumber) { // Clear existing maze for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { self.cells[i][j].type = 1; // Default to wall } } self.spawns = []; self.goals = []; // Always create entry area (top 5 rows) with 3-block wide path for (var i = 0; i < gridWidth; i++) { for (var j = 0; j <= 4; j++) { if (i >= 11 && i <= 13) { self.cells[i][j].type = 0; // Open path in center (3 blocks wide) if (j === 0) { self.cells[i][j].type = 2; // Spawn points self.spawns.push(self.cells[i][j]); } } else { self.cells[i][j].type = 1; // Walls on sides } } } // Always create exit area (bottom 5 rows) with 3-block wide path for (var i = 0; i < gridWidth; i++) { for (var j = gridHeight - 5; j < gridHeight; j++) { if (i >= 11 && i <= 13) { self.cells[i][j].type = 0; // Open path in center (3 blocks wide) if (j === gridHeight - 1) { self.cells[i][j].type = 3; // Goal points self.goals.push(self.cells[i][j]); } } else { self.cells[i][j].type = 1; // Walls on sides } } } // Create classic square-cornered labyrinth with generous tower placement areas // Simple pattern: Create a winding path with 90-degree turns and wide wall corridors // Path layout: Start center-top, go down, turn right, down, turn left, repeat creating a serpentine pattern // Main path coordinates (center of 3-block wide paths) // Reduced to only 2 curves for a simpler path var pathCenterX = 12; // Center column var pathPattern = [ // Start: go down from entry { x: pathCenterX, startY: 5, endY: 12, type: 'vertical' }, // First curve: turn left { y: 12, startX: pathCenterX, endX: 4, type: 'horizontal' }, // Go down on left side { x: 4, startY: 12, endY: 20, type: 'vertical' }, // Second curve: turn right to center { y: 20, startX: 4, endX: pathCenterX, type: 'horizontal' }, // Final path to exit { x: pathCenterX, startY: 20, endY: gridHeight - 5, type: 'vertical' }]; // Crear los segmentos del camino con 3 bloques de grosor (camino) y dejar espacio extra de 1 bloque a cada lado para asegurar zonas de 2x2 para torretas for (var p = 0; p < pathPattern.length; p++) { var segment = pathPattern[p]; if (segment.type === 'vertical') { var startY = Math.min(segment.startY, segment.endY); var endY = Math.max(segment.startY, segment.endY); for (var y = startY; y <= endY; y++) { // Camino de 3 bloques de ancho for (var offset = -1; offset <= 1; offset++) { var pathX = segment.x + offset; if (pathX >= 0 && pathX < gridWidth && y >= 0 && y < gridHeight) { self.cells[pathX][y].type = 0; } } // Dejar espacio extra de 1 bloque a cada lado del camino para permitir torretas 2x2 for (var extra = -2; extra <= 2; extra += 4) { var sideX = segment.x + extra; if (sideX >= 0 && sideX < gridWidth && y >= 0 && y < gridHeight) { // Solo marcar como espacio de torre si no es camino ni spawn/goal if (self.cells[sideX][y].type === 1) { // No cambiar si ya es camino/spawn/goal self.cells[sideX][y].type = 1; // Mantener como muro, pero dejarlo para posible torre } } } // Asegurar espacio de 2x2 para torretas a la izquierda y derecha del camino for (var offsetY = 0; offsetY <= 1; offsetY++) { for (var extra = -2; extra <= 2; extra += 4) { var baseX = segment.x + extra; var baseY = y + offsetY; if (baseX >= 0 && baseX + 1 < gridWidth && baseY >= 0 && baseY + 1 < gridHeight) { // Solo marcar como espacio de torre si ambos son muro if (self.cells[baseX][baseY].type === 1 && self.cells[baseX + 1][baseY].type === 1 && self.cells[baseX][baseY + 1].type === 1 && self.cells[baseX + 1][baseY + 1].type === 1) { // Marcar como espacio de torre (type 1) para permitir torretas 2x2 self.cells[baseX][baseY].type = 1; self.cells[baseX + 1][baseY].type = 1; self.cells[baseX][baseY + 1].type = 1; self.cells[baseX + 1][baseY + 1].type = 1; } } } } } } else if (segment.type === 'horizontal') { var startX = Math.min(segment.startX, segment.endX); var endX = Math.max(segment.startX, segment.endX); for (var x = startX; x <= endX; x++) { // Camino de 3 bloques de alto for (var offset = -1; offset <= 1; offset++) { var pathY = segment.y + offset; if (x >= 0 && x < gridWidth && pathY >= 0 && pathY < gridHeight) { self.cells[x][pathY].type = 0; } } // Dejar espacio extra de 1 bloque arriba y abajo del camino para permitir torretas 2x2 for (var extra = -2; extra <= 2; extra += 4) { var sideY = segment.y + extra; if (x >= 0 && x < gridWidth && sideY >= 0 && sideY < gridHeight) { if (self.cells[x][sideY].type === 1) { self.cells[x][sideY].type = 1; } } } // Asegurar espacio de 2x2 para torretas arriba y abajo del camino for (var offsetX = 0; offsetX <= 1; offsetX++) { for (var extra = -2; extra <= 2; extra += 4) { var baseX = x + offsetX; var baseY = segment.y + extra; if (baseX >= 0 && baseX + 1 < gridWidth && baseY >= 0 && baseY + 1 < gridHeight) { if (self.cells[baseX][baseY].type === 1 && self.cells[baseX + 1][baseY].type === 1 && self.cells[baseX][baseY + 1].type === 1 && self.cells[baseX + 1][baseY + 1].type === 1) { // Marcar como espacio de torre (type 1) para permitir torretas 2x2 self.cells[baseX][baseY].type = 1; self.cells[baseX + 1][baseY].type = 1; self.cells[baseX][baseY + 1].type = 1; self.cells[baseX + 1][baseY + 1].type = 1; } } } } } } } // Asegurar conexiones suaves en las intersecciones y dejar espacio de 2x2 para torret }; // Generate maze for current world var world = Math.ceil(currentWave / 9); if (world < 1) world = 1; if (world > 6) world = 6; self.generateMazeForWorld(world); // Apply the maze layout to cells for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; var cellType = cell.type; // Use the type set by generateMazeForWorld 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 = []; // Only create debug cells for visible areas and reduce frequency if (j > 3 && j <= gridHeight - 4 && (i + j) % 2 === 0) { var debugCell = new DebugCell(); self.addChild(debugCell); debugCell.cell = cell; debugCell.x = i * CELL_SIZE; debugCell.y = j * CELL_SIZE; cell.debugCell = debugCell; } } } self.getCell = function (x, y) { return self.cells[x] && self.cells[x][y]; }; self.pathFind = function () { var before = new Date().getTime(); var toProcess = self.goals.concat([]); maxScore = 0; pathId += 1; for (var a = 0; a < toProcess.length; a++) { toProcess[a].pathId = pathId; } function processNode(node, targetValue, targetNode) { if (node && node.type != 1) { if (node.pathId < pathId || targetValue < node.score) { node.targets = [targetNode]; } else if (node.pathId == pathId && targetValue == node.score) { node.targets.push(targetNode); } if (node.pathId < pathId || targetValue < node.score) { node.score = targetValue; if (node.pathId != pathId) { toProcess.push(node); } node.pathId = pathId; if (targetValue > maxScore) { maxScore = targetValue; } } } } while (toProcess.length) { var nodes = toProcess; toProcess = []; for (var a = 0; a < nodes.length; a++) { var node = nodes[a]; // Simplified pathfinding - only check cardinal directions for better performance var targetScore = node.score + 10000; processNode(node.up, targetScore, node); processNode(node.right, targetScore, node); processNode(node.down, targetScore, node); processNode(node.left, targetScore, node); } } for (var a = 0; a < self.spawns.length; a++) { if (self.spawns[a].pathId != pathId) { console.warn("Spawn blocked"); return true; } } for (var a = 0; a < enemies.length; a++) { var enemy = enemies[a]; // Skip enemies that haven't entered the viewable area yet if (enemy.currentCellY < 4) { continue; } // Skip flying enemies from path check as they can fly over obstacles if (enemy.isFlying) { continue; } var target = self.getCell(enemy.cellX, enemy.cellY); if (enemy.currentTarget) { if (enemy.currentTarget.pathId != pathId) { if (!target || target.pathId != pathId) { console.warn("Enemy blocked 1 "); return true; } } } else if (!target || target.pathId != pathId) { console.warn("Enemy blocked 2"); return true; } } console.log("Speed", new Date().getTime() - before); }; self.renderDebug = function () { for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var debugCell = self.cells[i][j].debugCell; if (debugCell) { debugCell.render(self.cells[i][j]); } } } }; self.updateEnemy = function (enemy) { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell.type == 3) { return true; } if (enemy.isFlying && enemy.shadow) { enemy.shadow.x = enemy.x + 20; // Match enemy x-position + offset enemy.shadow.y = enemy.y + 20; // Match enemy y-position + offset // Match shadow rotation with enemy rotation if (enemy.children[0] && enemy.shadow.children[0]) { enemy.shadow.children[0].rotation = enemy.children[0].rotation; } } // Check if the enemy has reached the entry area (y position is at least 5) var hasReachedEntryArea = enemy.currentCellY >= 4; // If enemy hasn't reached the entry area yet, just move down vertically if (!hasReachedEntryArea) { // Move directly downward with speed multiplier enemy.currentCellY += enemy.speed * gameSpeed; // Ensure enemy moves towards the center of the 3-block wide path (x=12) var pathCenterX = 12; if (enemy.currentCellX !== pathCenterX) { var xDiff = pathCenterX - enemy.currentCellX; var moveSpeed = Math.min(Math.abs(xDiff), enemy.speed * 0.5 * gameSpeed); enemy.currentCellX += xDiff > 0 ? moveSpeed : -moveSpeed; } // Rotate enemy graphic to face downward (PI/2 radians = 90 degrees) var angle = Math.PI / 2; if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update enemy's position enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // If enemy has now reached the entry area, update cell coordinates if (enemy.currentCellY >= 4) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); } return false; } // After reaching entry area, handle flying enemies differently if (enemy.isFlying) { // Flying enemies head straight to the closest goal if (!enemy.flyingTarget) { // Set flying target to the closest goal enemy.flyingTarget = self.goals[0]; // Find closest goal if there are multiple if (self.goals.length > 1) { var closestDist = Infinity; for (var i = 0; i < self.goals.length; i++) { var goal = self.goals[i]; var dx = goal.x - enemy.cellX; var dy = goal.y - enemy.cellY; var dist = dx * dx + dy * dy; if (dist < closestDist) { closestDist = dist; enemy.flyingTarget = goal; } } } } // Move directly toward the goal var ox = enemy.flyingTarget.x - enemy.currentCellX; var oy = enemy.flyingTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { // Reached the goal return true; } var angle = Math.atan2(oy, ox); // Rotate enemy graphic to match movement direction if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update the cell position to track where the flying enemy is enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentCellX += Math.cos(angle) * enemy.speed * gameSpeed; enemy.currentCellY += Math.sin(angle) * enemy.speed * gameSpeed; enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // Update shadow position if this is a flying enemy return false; } // Handle normal pathfinding enemies if (!enemy.currentTarget) { enemy.currentTarget = cell.targets[0]; } // Initialize stuck tracking for enemy if (enemy.lastPosition === undefined) { enemy.lastPosition = { x: enemy.currentCellX, y: enemy.currentCellY }; enemy.stuckCounter = 0; enemy.lastMovementTime = LK.ticks; } // Check if enemy is stuck (hasn't moved significantly in a while) var currentPos = { x: enemy.currentCellX, y: enemy.currentCellY }; var distanceMoved = Math.sqrt(Math.pow(currentPos.x - enemy.lastPosition.x, 2) + Math.pow(currentPos.y - enemy.lastPosition.y, 2)); // If enemy hasn't moved much in the last 60 ticks (1 second), consider it stuck if (distanceMoved < 0.1 && LK.ticks - enemy.lastMovementTime > 60) { enemy.stuckCounter++; enemy.lastMovementTime = LK.ticks; // If stuck for too long, try to find alternative path if (enemy.stuckCounter > 3) { // Reset stuck counter to prevent infinite loops enemy.stuckCounter = 0; // Try to find alternative targets from current cell if (cell.targets && cell.targets.length > 1) { // Find a different target than the current one for (var i = 0; i < cell.targets.length; i++) { var alternativeTarget = cell.targets[i]; if (alternativeTarget !== enemy.currentTarget) { enemy.currentTarget = alternativeTarget; break; } } } else { // If no alternative targets, try neighboring cells var neighbors = [cell.up, cell.right, cell.down, cell.left]; var validNeighbors = []; for (var i = 0; i < neighbors.length; i++) { var neighbor = neighbors[i]; if (neighbor && neighbor.type !== 1 && neighbor.pathId === pathId && neighbor.targets && neighbor.targets.length > 0) { validNeighbors.push(neighbor); } } if (validNeighbors.length > 0) { // Choose a random valid neighbor and use its target var randomNeighbor = validNeighbors[Math.floor(Math.random() * validNeighbors.length)]; enemy.currentTarget = randomNeighbor.targets[0]; // Move slightly towards the chosen neighbor to unstuck var neighborX = randomNeighbor.x; var neighborY = randomNeighbor.y; var unstuckAngle = Math.atan2(neighborY - enemy.currentCellY, neighborX - enemy.currentCellX); enemy.currentCellX += Math.cos(unstuckAngle) * enemy.speed * 0.5; enemy.currentCellY += Math.sin(unstuckAngle) * enemy.speed * 0.5; } } } } else if (distanceMoved >= 0.1) { // Enemy is moving, reset stuck tracking enemy.stuckCounter = 0; enemy.lastMovementTime = LK.ticks; enemy.lastPosition = { x: currentPos.x, y: currentPos.y }; } if (enemy.currentTarget) { if (cell.score < enemy.currentTarget.score) { enemy.currentTarget = cell; } var ox = enemy.currentTarget.x - enemy.currentCellX; var oy = enemy.currentTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentTarget = undefined; // Reset position tracking when reaching target enemy.lastPosition = { x: enemy.currentCellX, y: enemy.currentCellY }; return; } var angle = Math.atan2(oy, ox); enemy.currentCellX += Math.cos(angle) * enemy.speed * gameSpeed; enemy.currentCellY += Math.sin(angle) * enemy.speed * gameSpeed; } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; }; }); var LevelSelectionMenu = Container.expand(function (worldNumber) { var self = Container.call(this); self.worldNumber = worldNumber; // Position the menu at center of screen self.x = 2048 / 2; self.y = 2732 / 2; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 1800; menuBackground.height = 1200; menuBackground.tint = 0x333333; menuBackground.alpha = 0.9; var worldNames = ["", "Forest", "Desert", "Glacier", "Village", "Tech Lab", "Inferno"]; var titleText = new Text2('Select Level - ' + worldNames[worldNumber], { size: 80, fill: 0xFFFFFF, weight: 800 }); titleText.anchor.set(0.5, 0.5); titleText.y = -400; self.addChild(titleText); var worldLevels = storage.worldLevels || { 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1 }; var unlockedLevel = worldLevels[worldNumber] || 1; // Create level buttons in a grid var buttonsPerRow = 5; var buttonWidth = 120; var buttonHeight = 80; var buttonSpacing = 160; var startX = -((buttonsPerRow - 1) * buttonSpacing) / 2; var startY = -200; for (var i = 1; i <= 10; i++) { var levelButton = new Container(); var levelButtonBg = levelButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); levelButtonBg.width = buttonWidth; levelButtonBg.height = buttonHeight; var isUnlocked = i <= unlockedLevel; levelButtonBg.tint = isUnlocked ? 0x4444FF : 0x666666; var levelButtonText = new Text2(isUnlocked ? i.toString() : "🔒", { size: 40, fill: isUnlocked ? 0xFFFFFF : 0x999999, weight: 800 }); levelButtonText.anchor.set(0.5, 0.5); levelButton.addChild(levelButtonText); // Position buttons in grid var row = Math.floor((i - 1) / buttonsPerRow); var col = (i - 1) % buttonsPerRow; levelButton.x = startX + col * buttonSpacing; levelButton.y = startY + row * 120; self.addChild(levelButton); (function (levelIndex, unlocked) { levelButton.down = function () { if (unlocked) { self.destroy(); game.startWorldLevel(self.worldNumber, levelIndex); } else { var notification = game.addChild(new Notification("Complete previous level to unlock!")); notification.x = 2048 / 2; notification.y = 2732 / 2 + 200; } }; })(i, isUnlocked); } // Back button var backButton = new Container(); var backButtonBg = backButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); backButtonBg.width = 200; backButtonBg.height = 80; backButtonBg.tint = 0x666666; var backButtonText = new Text2('Back', { size: 40, fill: 0xFFFFFF, weight: 800 }); backButtonText.anchor.set(0.5, 0.5); backButton.addChild(backButtonText); backButton.x = 0; backButton.y = 400; self.addChild(backButton); backButton.down = function () { self.destroy(); var worldSelectionMenu = new WorldSelectionMenu(); game.addChild(worldSelectionMenu); }; return self; }); var MainMenu = Container.expand(function () { var self = Container.call(this); // Position the menu at center of screen self.x = 2048 / 2; self.y = 2732 / 2; // Stop any currently playing music first LK.stopMusic(); // Start main menu music LK.playMusic('mainMenuMusic', { fade: { start: 0, end: 0.8, duration: 1500 } }); var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 1800; menuBackground.height = 1200; menuBackground.tint = 0x333333; menuBackground.alpha = 0.9; var titleText = new Text2(getText('firewallDefensors'), { size: 100, fill: 0xFFFFFF, weight: 800 }); titleText.anchor.set(0.5, 0.5); titleText.y = -300; self.addChild(titleText); // Add version number below title var versionText = new Text2('v0.1', { size: 40, fill: 0xCCCCCC, weight: 400 }); versionText.anchor.set(0.5, 0.5); versionText.y = -220; self.addChild(versionText); var startButton = new Container(); var startButtonBg = startButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); startButtonBg.width = 400; startButtonBg.height = 120; startButtonBg.tint = 0x00AA00; var startButtonText = new Text2(getText('startGame'), { size: 50, fill: 0xFFFFFF, weight: 800 }); startButtonText.anchor.set(0.5, 0.5); startButton.addChild(startButtonText); startButton.y = 50; self.addChild(startButton); startButton.down = function () { self.destroy(); game.startGame(); }; // Add tutorial button var tutorialButton = new Container(); var tutorialButtonBg = tutorialButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); tutorialButtonBg.width = 400; tutorialButtonBg.height = 120; tutorialButtonBg.tint = 0x00AAAA; var tutorialButtonText = new Text2(getText('tutorial'), { size: 50, fill: 0xFFFFFF, weight: 800 }); tutorialButtonText.anchor.set(0.5, 0.5); tutorialButton.addChild(tutorialButtonText); tutorialButton.y = 180; self.addChild(tutorialButton); tutorialButton.down = function () { self.destroy(); game.startTutorial(); }; // Add language button var languageButton = new Container(); var languageButtonBg = languageButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); languageButtonBg.width = 400; languageButtonBg.height = 120; languageButtonBg.tint = 0xFF6600; var languageButtonText = new Text2(getText('language'), { size: 50, fill: 0xFFFFFF, weight: 800 }); languageButtonText.anchor.set(0.5, 0.5); languageButton.addChild(languageButtonText); languageButton.y = 310; self.addChild(languageButton); languageButton.down = function () { // Toggle language between English and Spanish var newLang = currentLanguage === 'en' ? 'es' : 'en'; setLanguage(newLang); // Recreate main menu with new language self.destroy(); var newMainMenu = new MainMenu(); game.addChild(newMainMenu); }; return self; }); var NextWaveButton = Container.expand(function () { var self = Container.call(this); var buttonBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 300; buttonBackground.height = 100; buttonBackground.tint = 0x0088FF; var buttonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.enabled = false; self.visible = false; self.update = function () { // Check if we can start next wave (10 waves max per world) var worldWave = (currentWave - 1) % 10 + 1; if (currentWave === 0) worldWave = 0; // Show button during tutorial or when can start next wave var showForTutorial = waveIndicator && waveIndicator.gameStarted && currentWave === 0; var showForNextWave = waveIndicator && waveIndicator.gameStarted && worldWave < 10 && !waveInProgress && enemies.length === 0; if (showForTutorial || showForNextWave) { self.enabled = true; self.visible = true; buttonBackground.tint = 0x0088FF; self.alpha = 1; } else { self.enabled = false; self.visible = false; buttonBackground.tint = 0x888888; self.alpha = 0.7; } }; self.down = function () { if (!self.enabled) { return; } if (waveIndicator.gameStarted && currentWave < totalWaves && !waveInProgress && enemies.length === 0) { currentWave++; // Increment to the next wave directly // Calculate current world and level for 10-wave system currentWorld = Math.ceil(currentWave / 10); currentLevel = (currentWave - 1) % 10 + 1; waveTimer = 0; // Reset wave timer waveInProgress = true; waveSpawned = false; // Get the type of the current wave (which is now the next wave) var waveType = waveIndicator.getWaveTypeName(currentLevel); var enemyCount = waveIndicator.getEnemyCount(currentLevel); // Update wave counter display updateWaveCounter(); var notification = game.addChild(new Notification("Wave " + currentLevel + " (" + waveType + " - " + enemyCount + " enemies) activated!")); notification.x = 2048 / 2; notification.y = grid.height - 150; // Play wave start sound LK.getSound('waveStart').play(); } }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 30; self.addChild(notificationText); self.alpha = 1; var fadeOutTime = 120; self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; // Increase size of base for easier touch var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); switch (self.towerType) { case 'rapid': baseGraphics.tint = 0x00AAFF; break; case 'sniper': baseGraphics.tint = 0xFF5500; break; case 'splash': baseGraphics.tint = 0x33CC00; break; case 'slow': baseGraphics.tint = 0x9900FF; break; case 'poison': baseGraphics.tint = 0x00FFAA; break; default: baseGraphics.tint = 0xAAAAAA; } var towerCost = getTowerCost(self.towerType); // Add shadow for tower type label var typeLabelShadow = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), { size: 50, fill: 0x000000, weight: 800 }); typeLabelShadow.anchor.set(0.5, 0.5); typeLabelShadow.x = 4; typeLabelShadow.y = -20 + 4; self.addChild(typeLabelShadow); // Add tower type label var typeLabel = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), { size: 60, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.y = -25; // Position above center of tower self.addChild(typeLabel); // Add cost shadow var costLabelShadow = new Text2(towerCost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; costLabelShadow.y = 24 + 12; self.addChild(costLabelShadow); // Add cost label var costLabel = new Text2(towerCost + ' bits', { size: 60, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.y = 25 + 12; self.addChild(costLabel); self.update = function () { // Check if player can afford this tower var canAfford = gold >= getTowerCost(self.towerType); // Set opacity based on affordability self.alpha = canAfford ? 1 : 0.5; }; return self; }); var StorySequence = Container.expand(function (worldNumber) { var self = Container.call(this); self.worldNumber = worldNumber; self.currentPanel = 0; self.panels = []; self.onComplete = null; // Position at center of screen self.x = 2048 / 2; self.y = 2732 / 2; // Semi-transparent background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.7; self.getWorldStory = function (worldNumber) { switch (worldNumber) { case 1: return [{ text: "ALERT! Pokémon infiltrators have breached\nthe Digital Forest servers! They're attempting\nto steal Digimon data files!", image: 'agumon' }, { text: "These Pokémon spies are using advanced\nstealth protocols to access our core database.\nDeploy Digimon guardians immediately!", image: 'gabumon' }, { text: "The future of the Digimon franchise\ndepends on you! Stop Pokémon from\ncorrupting our digital ecosystem!", image: 'tentomon' }]; case 2: return [{ text: "Pokémon agents have infiltrated the Desert\nData Center! They're planting malicious code\nto corrupt our systems!", image: 'palmon' }, { text: "Fire-type Pokémon are overheating our\nservers while others steal precious\nDigimon evolution data!", image: 'gomamon' }, { text: "Their coordinated attack is more sophisticated\nthan before. Pokémon want to monopolize\nthe children's entertainment industry!", image: 'patamon' }]; case 3: return [{ text: "Ice-type Pokémon have frozen our Glacier\nservers to slow down our defenses\nwhile they extract data!", image: null }, { text: "Flying Pokémon are bypassing our security\nwalls! They're trying to reach the core\nDigimon genetic database!", image: null }, { text: "Critical system temperatures detected!\nPokémon are trying to cause a complete\nserver meltdown!", image: null }]; case 4: return [{ text: "Pokémon sleeper agents hidden in the Village\nNetwork have activated! They've been\ngathering intelligence for months!", image: null }, { text: "Multiple Pokémon strike teams are attacking\nsimultaneously, trying to overwhelm\nour Digimon defenders!", image: null }, { text: "This is corporate espionage on a massive\nscale! Pokémon Company wants to steal\nour digital creature technology!", image: null }]; case 5: return [{ text: "MAXIMUM THREAT LEVEL! Elite Pokémon\nhackers have breached our most secure\nTechnology Labs!", image: null }, { text: "They're using legendary Pokémon abilities\nto bypass our quantum encryption!\nOur most sensitive data is at risk!", image: null }, { text: "Deploy our strongest Mega-level Digimon!\nOnly they can stop this corporate\ncyber warfare!", image: null }]; case 6: return [{ text: "FINAL ASSAULT! Pokémon's master plan\nis revealed - they want to delete ALL\nDigimon data permanently!", image: null }, { text: "Legendary Pokémon themselves are leading\nthis final attack on our core servers!\nThis is the ultimate battle for supremacy!", image: null }, { text: "The children's hearts are at stake!\nDefeat Pokémon's invasion and save\nthe future of digital monsters forever!", image: null }]; default: return [{ text: "Pokémon infiltrators detected! Protect the Digimon database!", image: null }, { text: "Deploy your Digimon to stop the corporate espionage!", image: null }, { text: "Save the Digital World from Pokémon's takeover!", image: null }]; } }; // World-specific story content var storyData = self.getWorldStory(worldNumber); // Create panels for (var i = 0; i < storyData.length; i++) { var panel = new ComicPanel(storyData[i].image, storyData[i].text); panel.x = (i - 1) * 650; // Position panels side by side self.addChild(panel); self.panels.push(panel); } // Navigation indicators var indicatorContainer = new Container(); indicatorContainer.y = 250; self.addChild(indicatorContainer); for (var i = 0; i < self.panels.length; i++) { var indicator = indicatorContainer.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = 20; indicator.height = 20; indicator.tint = i === 0 ? 0xffffff : 0x666666; indicator.x = (i - (self.panels.length - 1) / 2) * 40; } // Skip button var skipButton = new Container(); var skipBg = skipButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); skipBg.width = 150; skipBg.height = 60; skipBg.tint = 0x666666; var skipText = new Text2('Skip', { size: 40, fill: 0xffffff, weight: 800 }); skipText.anchor.set(0.5, 0.5); skipButton.addChild(skipText); skipButton.x = 400; skipButton.y = -300; self.addChild(skipButton); skipButton.down = function () { self.complete(); }; self.showPanel = function (index) { if (index < 0 || index >= self.panels.length) return; // Hide all panels for (var i = 0; i < self.panels.length; i++) { self.panels[i].alpha = 0; indicatorContainer.children[i].tint = 0x666666; } // Show current panel self.panels[index].show(); indicatorContainer.children[index].tint = 0xffffff; self.currentPanel = index; }; self.nextPanel = function () { if (self.currentPanel < self.panels.length - 1) { self.showPanel(self.currentPanel + 1); } else { self.complete(); } }; self.complete = function () { if (self.onComplete) { self.onComplete(); } self.destroy(); }; // Show first panel self.showPanel(0); // Auto-advance after 4 seconds or click to advance var autoAdvanceTimer = LK.setTimeout(function () { self.nextPanel(); }, 4000); self.down = function () { LK.clearTimeout(autoAdvanceTimer); autoAdvanceTimer = LK.setTimeout(function () { self.nextPanel(); }, 4000); self.nextPanel(); }; return self; }); var Tower = Container.expand(function (id) { var self = Container.call(this); self.id = id || 'default'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.range = 3 * CELL_SIZE; // Tower health system self.maxHealth = 100; self.health = self.maxHealth; // Standardized method to get the current range of the tower self.getRange = function () { // Always calculate range based on tower type and level switch (self.id) { case 'sniper': // Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost if (self.level === self.maxLevel) { return 12 * CELL_SIZE; // Significantly increased range for max level } return (5 + (self.level - 1) * 0.8) * CELL_SIZE; case 'splash': // Splash: base 2, +0.2 per level (max ~4 blocks at max level) return (2 + (self.level - 1) * 0.2) * CELL_SIZE; case 'rapid': // Rapid: base 2.5, +0.5 per level return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'slow': // Slow: base 3.5, +0.5 per level return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'poison': // Poison: base 3.2, +0.5 per level return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; default: // Default: base 3, +0.5 per level return (3 + (self.level - 1) * 0.5) * CELL_SIZE; } }; self.cellsInRange = []; self.fireRate = 60; self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; switch (self.id) { case 'agumon': // Fire-based attacks, burns enemies self.fireRate = 60; self.damage = 12; self.range = 3 * CELL_SIZE; self.bulletSpeed = 6; self.maxHealth = 120; // Tanky fire dragon break; case 'gabumon': // Ice-based attacks, freezes enemies self.fireRate = 55; self.damage = 10; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 6; self.maxHealth = 110; // Ice wolf durability break; case 'tentomon': // Electric paralysis attacks self.fireRate = 70; self.damage = 14; self.range = 3.5 * CELL_SIZE; self.bulletSpeed = 8; self.maxHealth = 90; // Electric insect, moderate health break; case 'palmon': // Poison attacks that spread self.fireRate = 65; self.damage = 11; self.range = 3 * CELL_SIZE; self.bulletSpeed = 5; self.maxHealth = 100; // Plant creature, balanced break; case 'gomamon': // Water/moisture attacks, weakens fire resistance self.fireRate = 60; self.damage = 9; self.range = 3.3 * CELL_SIZE; self.bulletSpeed = 5; self.maxHealth = 105; // Aquatic mammal, decent health break; case 'patamon': // Healing and support abilities self.fireRate = 80; self.damage = 8; self.range = 4 * CELL_SIZE; // Larger healing range self.bulletSpeed = 7; self.maxHealth = 85; // Angel creature, less physical but supportive break; } self.health = self.maxHealth; var baseGraphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); switch (self.id) { case 'gabumon': //{93} // Blue colors for Gabumon baseGraphics.tint = 0x00AAFF; break; case 'tentomon': //{96} // Red colors for Tentomon baseGraphics.tint = 0xFF5500; break; case 'palmon': //{99} // Green colors for Palmon baseGraphics.tint = 0x33CC00; break; case 'gomamon': //{9c} // Purple colors for Gomamon baseGraphics.tint = 0x9900FF; break; case 'patamon': //{9f} // Cyan colors for Patamon baseGraphics.tint = 0x00FFAA; break; default: //{9i} // Agumon default baseGraphics.tint = 0xAAAAAA; } // Tower health bar var towerHealthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0.5, anchorY: 0.5 }); var towerHealthBar = self.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); towerHealthBarOutline.width = 80; towerHealthBarOutline.height = 8; towerHealthBarOutline.tint = 0x000000; towerHealthBar.width = 76; towerHealthBar.height = 6; towerHealthBar.tint = 0x00ff00; towerHealthBarOutline.y = towerHealthBar.y = -CELL_SIZE - 15; self.towerHealthBar = towerHealthBar; var levelIndicators = []; var maxDots = self.maxLevel; var dotSpacing = baseGraphics.width / (maxDots + 1); var dotSize = CELL_SIZE / 6; for (var i = 0; i < maxDots; i++) { var dot = new Container(); var outlineCircle = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); outlineCircle.width = dotSize + 4; outlineCircle.height = dotSize + 4; outlineCircle.tint = 0x000000; var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); towerLevelIndicator.width = dotSize; towerLevelIndicator.height = dotSize; towerLevelIndicator.tint = 0xCCCCCC; dot.x = -CELL_SIZE + dotSpacing * (i + 1); dot.y = CELL_SIZE * 0.7; self.addChild(dot); levelIndicators.push(dot); } var gunContainer = new Container(); self.addChild(gunContainer); var gunGraphics = gunContainer.attachAsset(self.id, { anchorX: 0.5, anchorY: 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 = 0xFFFFFF; } else { switch (self.id) { case 'rapid': towerLevelIndicator.tint = 0x00AAFF; break; case 'sniper': towerLevelIndicator.tint = 0xFF5500; break; case 'splash': towerLevelIndicator.tint = 0x33CC00; break; case 'slow': towerLevelIndicator.tint = 0x9900FF; break; case 'poison': towerLevelIndicator.tint = 0x00FFAA; break; default: towerLevelIndicator.tint = 0xAAAAAA; } } } }; self.updateLevelIndicators(); self.refreshCellsInRange = function () { for (var i = 0; i < self.cellsInRange.length; i++) { var cell = self.cellsInRange[i]; var towerIndex = cell.towersInRange.indexOf(self); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } self.cellsInRange = []; var rangeRadius = self.getRange() / CELL_SIZE; var centerX = self.gridX + 1; var centerY = self.gridY + 1; var minI = Math.floor(centerX - rangeRadius - 0.5); var maxI = Math.ceil(centerX + rangeRadius + 0.5); var minJ = Math.floor(centerY - rangeRadius - 0.5); var maxJ = Math.ceil(centerY + rangeRadius + 0.5); for (var i = minI; i <= maxI; i++) { for (var j = minJ; j <= maxJ; j++) { var closestX = Math.max(i, Math.min(centerX, i + 1)); var closestY = Math.max(j, Math.min(centerY, j + 1)); var deltaX = closestX - centerX; var deltaY = closestY - centerY; var distanceSquared = deltaX * deltaX + deltaY * deltaY; if (distanceSquared <= rangeRadius * rangeRadius) { var cell = grid.getCell(i, j); if (cell) { self.cellsInRange.push(cell); cell.towersInRange.push(self); } } } } grid.renderDebug(); }; self.getTotalValue = function () { var baseTowerCost = getTowerCost(self.id); var totalInvestment = baseTowerCost; var baseUpgradeCost = baseTowerCost; // Upgrade cost now scales with base tower cost for (var i = 1; i < self.level; i++) { totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1)); } return totalInvestment; }; self.upgrade = function () { if (self.level < self.maxLevel) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.id); var upgradeCost; // Make last upgrade level extra expensive if (self.level === self.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); // Half the cost for final upgrade } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1)); } if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; // Play tower upgrade sound LK.getSound('towerUpgrade').play(); // No need to update self.range here; getRange() is now the source of truth // Apply tower-specific upgrades based on type if (self.id === 'rapid') { if (self.level === self.maxLevel) { // Extra powerful last upgrade (double the effect) self.fireRate = Math.max(4, 30 - self.level * 9); // double the effect self.damage = 5 + self.level * 10; // double the effect self.bulletSpeed = 7 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(15, 30 - self.level * 3); // Fast tower gets faster with upgrades self.damage = 5 + self.level * 3; self.bulletSpeed = 7 + self.level * 0.7; } } else { if (self.level === self.maxLevel) { // Extra powerful last upgrade for all other towers (double the effect) self.fireRate = Math.max(5, 60 - self.level * 24); // double the effect self.damage = 10 + self.level * 20; // double the effect self.bulletSpeed = 5 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(20, 60 - self.level * 8); self.damage = 10 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.5; } } self.refreshCellsInRange(); self.updateLevelIndicators(); if (self.level > 1) { var levelDot = levelIndicators[self.level - 1].children[1]; tween(levelDot, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut, onFinish: function onFinish() { tween(levelDot, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); } // Check if tower reached maximum level and award bonus if (self.level === self.maxLevel) { // Award 300 bits and security points for reaching max level setGold(gold + 300); score += 300; // Save security points to storage storage.securityPoints = score; updateUI(); var notification = game.addChild(new Notification("Max level reached! +300 bits & security points!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } return true; } else { var notification = game.addChild(new Notification("Not enough bits to upgrade!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } return false; }; self.findTarget = function () { var closestEnemy = null; var closestScore = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if enemy is in range if (distance <= self.getRange()) { // Handle flying enemies differently - they can be targeted regardless of path if (enemy.isFlying) { // For flying enemies, prioritize by distance to the goal if (enemy.flyingTarget) { var goalX = enemy.flyingTarget.x; var goalY = enemy.flyingTarget.y; var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY)); // Use distance to goal as score if (distToGoal < closestScore) { closestScore = distToGoal; closestEnemy = enemy; } } else { // If no flying target yet (shouldn't happen), prioritize by distance to tower if (distance < closestScore) { closestScore = distance; closestEnemy = enemy; } } } else { // For ground enemies, use the original path-based targeting // Get the cell for this enemy var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell && cell.pathId === pathId) { // Use the cell's score (distance to exit) for prioritization // Lower score means closer to exit if (cell.score < closestScore) { closestScore = cell.score; closestEnemy = enemy; } } } } } if (!closestEnemy) { self.targetEnemy = null; } return closestEnemy; }; self.update = function () { // Update tower health bar self.towerHealthBar.width = self.health / self.maxHealth * 76; if (self.health / self.maxHealth > 0.6) { self.towerHealthBar.tint = 0x00ff00; // Green } else if (self.health / self.maxHealth > 0.3) { self.towerHealthBar.tint = 0xffff00; // Yellow } else { self.towerHealthBar.tint = 0xff0000; // Red } self.targetEnemy = self.findTarget(); if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); gunContainer.rotation = angle; var effectiveFireRate = gameSpeed > 1 ? Math.max(1, Math.floor(self.fireRate / gameSpeed)) : self.fireRate; if (LK.ticks - self.lastFired >= effectiveFireRate) { self.fire(); self.lastFired = LK.ticks; } } }; self.down = function (x, y, obj) { var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); var hasOwnMenu = false; var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self) { rangeCircle = game.children[i]; break; } } for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hasOwnMenu = true; break; } } if (hasOwnMenu) { for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } if (rangeCircle) { game.removeChild(rangeCircle); } selectedTower = null; grid.renderDebug(); return; } for (var i = 0; i < existingMenus.length; i++) { existingMenus[i].destroy(); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = self; var rangeIndicator = new Container(); rangeIndicator.isTowerRange = true; rangeIndicator.tower = self; game.addChild(rangeIndicator); rangeIndicator.x = self.x; rangeIndicator.y = self.y; var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.getRange() * 2; rangeGraphics.alpha = 0.3; // Add evolution arrow if digivice is available and tower level is appropriate function canDigivolve() { if (self.level < 2) return false; // Need at least level 2 var hasDigivice = false; if (self.level >= 2 && self.level <= 3 && storage.digiviceC) hasDigivice = true; if (self.level >= 4 && self.level <= 5 && storage.digiviceB) hasDigivice = true; if (self.level >= 6 && storage.digiviceA) hasDigivice = true; return hasDigivice; } if (canDigivolve()) { var evolutionArrow = rangeIndicator.attachAsset('arrow', { anchorX: 0.5, anchorY: 0.5 }); evolutionArrow.width = 60; evolutionArrow.height = 60; evolutionArrow.tint = 0xFF6600; evolutionArrow.x = self.getRange() + 40; evolutionArrow.y = -40; evolutionArrow.rotation = -Math.PI / 4; // Point diagonally up-right // Add glow effect evolutionArrow.alpha = 0.8; var _glowTween = function glowTween() { tween(evolutionArrow, { alpha: 1.0, scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(evolutionArrow, { alpha: 0.8, scaleX: 1.0, scaleY: 1.0 }, { duration: 500, easing: tween.easeInOut, onFinish: _glowTween }); } }); }; _glowTween(); } // Check if clicked on evolution arrow var clickedOnEvolutionArrow = false; if (rangeIndicator.children.length > 1) { // Has evolution arrow var evolutionArrow = rangeIndicator.children[1]; var arrowGlobalPos = rangeIndicator.toGlobal(evolutionArrow.position); var arrowX = arrowGlobalPos.x; var arrowY = arrowGlobalPos.y; if (Math.abs(x - arrowX) < 40 && Math.abs(y - arrowY) < 40) { clickedOnEvolutionArrow = true; // Trigger digivolution var evolutionCost = getTowerCost(self.id) * 2; if (gold >= evolutionCost) { setGold(gold - evolutionCost); // Apply evolution effects self.damage *= 1.5; self.fireRate = Math.max(5, Math.floor(self.fireRate * 0.8)); var notification = game.addChild(new Notification(self.id.toUpperCase() + " DIGIVOLVED!")); notification.x = 2048 / 2; notification.y = grid.height - 50; // Visual evolution effect LK.effects.flashObject(self, 0xFF6600, 1000); // Remove evolution arrow after use rangeIndicator.removeChild(evolutionArrow); } else { var notification = game.addChild(new Notification("Not enough bits to digivolve!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } } if (!clickedOnEvolutionArrow) { var upgradeMenu = new UpgradeMenu(self); game.addChild(upgradeMenu); upgradeMenu.x = 2048 / 2; tween(upgradeMenu, { y: 2732 - 225 }, { duration: 200, easing: tween.backOut }); } grid.renderDebug(); }; self.isInRange = function (enemy) { if (!enemy) { return false; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance <= self.getRange(); }; self.fire = function () { // Patamon healing ability - heal nearby towers instead of attacking if (self.id === 'patamon' && LK.ticks % 120 === 0) { // Every 2 seconds var healingRange = self.getRange(); var healAmount = 5 + self.level * 2; var towersHealed = 0; for (var i = 0; i < towers.length; i++) { var otherTower = towers[i]; if (otherTower !== self && otherTower.health < otherTower.maxHealth) { var dx = otherTower.x - self.x; var dy = otherTower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= healingRange) { otherTower.health = Math.min(otherTower.maxHealth, otherTower.health + healAmount); otherTower.towerHealthBar.width = otherTower.health / otherTower.maxHealth * 76; towersHealed++; // Visual healing effect var healEffect = new EffectIndicator(otherTower.x, otherTower.y, 'heal'); game.addChild(healEffect); } } } if (towersHealed > 0) { // Show healing indicator on Patamon var healSelfEffect = new EffectIndicator(self.x, self.y, 'heal'); game.addChild(healSelfEffect); } } if (self.targetEnemy) { var potentialDamage = 0; for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) { potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage; } if (self.targetEnemy.health > potentialDamage) { var bulletX = self.x + Math.cos(gunContainer.rotation) * 40; var bulletY = self.y + Math.sin(gunContainer.rotation) * 40; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed); // Set bullet type based on tower type and add special effects bullet.type = self.id; bullet.sourceTowerLevel = self.level; // Special attack patterns for different evolutionary lines switch (self.id) { case 'agumon': // Fire attacks that can burn enemies bullet.burnChance = 0.3 + self.level * 0.1; break; case 'gabumon': // Ice attacks that can freeze enemies bullet.freezeChance = 0.25 + self.level * 0.08; break; case 'tentomon': // Electric attacks that can paralyze multiple enemies bullet.paralyzeChance = 0.4 + self.level * 0.1; bullet.paralyzeArea = CELL_SIZE * (1 + self.level * 0.2); break; case 'palmon': // Poison attacks that spread to nearby enemies bullet.poisonSpread = true; bullet.poisonRadius = CELL_SIZE * (0.8 + self.level * 0.15); break; case 'gomamon': // Water attacks that make enemies wet (vulnerable to freeze, resistant to burn) bullet.moistureEffect = true; bullet.moistureDuration = 180 + self.level * 30; break; } // Customize bullet appearance based on tower type switch (self.id) { case 'rapid': bullet.children[0].tint = 0x00AAFF; bullet.children[0].width = 20; bullet.children[0].height = 20; break; case 'sniper': bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; case 'splash': bullet.children[0].tint = 0x33CC00; bullet.children[0].width = 40; bullet.children[0].height = 40; break; case 'slow': bullet.children[0].tint = 0x9900FF; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'poison': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; } game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); // --- Fire recoil effect for gunContainer --- // Stop any ongoing recoil tweens before starting a new one tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); // Always use the original resting position for recoil, never accumulate offset if (gunContainer._restX === undefined) { gunContainer._restX = 0; } if (gunContainer._restY === undefined) { gunContainer._restY = 0; } if (gunContainer._restScaleX === undefined) { gunContainer._restScaleX = 1; } if (gunContainer._restScaleY === undefined) { gunContainer._restScaleY = 1; } // Reset to resting position before animating (in case of interrupted tweens) gunContainer.x = gunContainer._restX; gunContainer.y = gunContainer._restY; gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; // Calculate recoil offset (recoil back along the gun's rotation) var recoilDistance = 8; var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance; var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance; // Animate recoil back from the resting position tween(gunContainer, { x: gunContainer._restX + recoilX, y: gunContainer._restY + recoilY }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { // Animate return to original position/scale tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: 90, easing: tween.cubicIn }); } }); } } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; // Mark cells as occupied by tower (type 1 = wall/occupied) for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 1; } } } 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(self.towerType); // Only update appearance if the affordability status has changed if (previousHasEnoughGold !== self.hasEnoughGold) { self.updateAppearance(); } }; self.updateAppearance = function () { // Use Tower class to get the source of truth for range var tempTower = new Tower(self.towerType); var previewRange = tempTower.getRange(); // Clean up tempTower to avoid memory leaks if (tempTower && tempTower.destroy) { tempTower.destroy(); } // Set range indicator using unified range logic rangeGraphics.width = rangeGraphics.height = previewRange * 2; switch (self.towerType) { case 'rapid': previewGraphics.tint = 0x00AAFF; break; case 'sniper': previewGraphics.tint = 0xFF5500; break; case 'splash': previewGraphics.tint = 0x33CC00; break; case 'slow': previewGraphics.tint = 0x9900FF; break; case 'poison': previewGraphics.tint = 0x00FFAA; break; default: previewGraphics.tint = 0xAAAAAA; } if (!self.canPlace || !self.hasEnoughGold) { previewGraphics.tint = 0xFF0000; } }; self.updatePlacementStatus = function () { var validGridPlacement = true; // Check if tower would be placed within valid grid bounds if (self.gridX < 0 || self.gridY < 0 || self.gridX + 1 >= grid.cells.length || self.gridY + 1 >= grid.cells[0].length) { validGridPlacement = false; } else { // Check if all 4 cells for the 2x2 tower are available (not on enemy path) for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(self.gridX + i, self.gridY + j); // Only allow placement on wall cells (type 1) - not on path, spawn, or goal if (!cell || cell.type !== 1) { validGridPlacement = false; break; } } if (!validGridPlacement) { break; } } } self.blockedByEnemy = false; // Remove enemy blocking detection since towers can only be placed on wall tiles // which enemies cannot occupy anyway self.blockedByEnemy = false; self.canPlace = validGridPlacement; self.hasEnoughGold = gold >= getTowerCost(self.towerType); self.updateAppearance(); }; self.checkPlacement = function () { self.updatePlacementStatus(); }; self.snapToGrid = function (x, y) { var gridPosX = x - grid.x; var gridPosY = y - grid.y; self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; self.checkPlacement(); }; return self; }); var TutorialSequence = Container.expand(function () { var self = Container.call(this); console.log("Starting interactive tutorial sequence"); self.currentStep = 0; self.onComplete = null; self.tutorialActive = true; self.waitingForUserAction = false; self.stepCompleted = false; // Tutorial steps data self.tutorialSteps = [{ textKey: 'tutorialWelcome', action: 'intro' }, { textKey: 'tutorialStep1', action: 'placeTower', completeTextKey: 'tutorialStep1Complete' }, { textKey: 'tutorialStep2', action: 'startWave', completeTextKey: 'tutorialStep2Complete' }, { textKey: 'tutorialStep3', action: 'upgradeTower', completeTextKey: 'tutorialStep3Complete' }, { textKey: 'tutorialCompleted', action: 'finish' }]; // Semi-transparent background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.4; // Instruction panel var instructionBg = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); instructionBg.width = 1600; instructionBg.height = 400; instructionBg.tint = 0x222222; instructionBg.alpha = 0.95; instructionBg.x = 2048 / 2; instructionBg.y = 400; var instructionText = new Text2(getText(self.tutorialSteps[0].textKey), { size: 50, fill: 0xFFFFFF, weight: 600 }); instructionText.anchor.set(0.5, 0.5); instructionText.wordWrap = true; instructionText.wordWrapWidth = 1500; instructionText.x = 2048 / 2; instructionText.y = 400; self.addChild(instructionText); // Next button var nextButton = new Container(); var nextBg = nextButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); nextBg.width = 250; nextBg.height = 80; nextBg.tint = 0x00AA00; var nextText = new Text2(getText('nextStep'), { size: 45, fill: 0xFFFFFF, weight: 800 }); nextText.anchor.set(0.5, 0.5); nextButton.addChild(nextText); nextButton.x = 2048 / 2; nextButton.y = 580; self.addChild(nextButton); // Skip button var skipButton = new Container(); var skipBg = skipButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); skipBg.width = 200; skipBg.height = 80; skipBg.tint = 0x666666; var skipText = new Text2(getText('skipTutorial'), { size: 35, fill: 0xffffff, weight: 800 }); skipText.anchor.set(0.5, 0.5); skipButton.addChild(skipText); skipButton.x = 2048 - 150; skipButton.y = 150; self.addChild(skipButton); // Button handlers nextButton.down = function () { self.nextStep(); }; skipButton.down = function () { self.complete(); }; // Update button state self.updateButtonState = function () { if (self.waitingForUserAction && !self.stepCompleted) { nextBg.tint = 0x666666; nextButton.alpha = 0.5; nextText.setText('Complete Action'); } else { nextBg.tint = 0x00AA00; nextButton.alpha = 1.0; nextText.setText(getText('nextStep')); } }; self.nextStep = function () { // Don't advance if waiting for user action and step not completed if (self.waitingForUserAction && !self.stepCompleted) { return; } self.currentStep++; if (self.currentStep >= self.tutorialSteps.length) { self.complete(); return; } self.waitingForUserAction = false; self.stepCompleted = false; var step = self.tutorialSteps[self.currentStep]; instructionText.setText(getText(step.textKey)); // Handle specific step actions switch (step.action) { case 'placeTower': self.waitingForUserAction = true; // Give enough gold for tutorial setGold(200); updateUI(); break; case 'startWave': self.waitingForUserAction = true; waveIndicator.gameStarted = true; break; case 'upgradeTower': self.waitingForUserAction = true; // Give extra gold for upgrades setGold(gold + 200); updateUI(); break; } self.updateButtonState(); }; // Check for step completion self.update = function () { if (!self.waitingForUserAction || self.stepCompleted) { return; } var step = self.tutorialSteps[self.currentStep]; switch (step.action) { case 'placeTower': if (towers.length > 0) { self.stepCompleted = true; if (step.completeTextKey) { instructionText.setText(getText(step.completeTextKey)); } self.updateButtonState(); } break; case 'startWave': if (waveInProgress || currentWave > 0) { self.stepCompleted = true; if (step.completeTextKey) { instructionText.setText(getText(step.completeTextKey)); } self.updateButtonState(); } break; case 'upgradeTower': // Check if any tower has been upgraded or if upgrade menu was closed var hasUpgradedTower = false; for (var i = 0; i < towers.length; i++) { if (towers[i].level > 1) { hasUpgradedTower = true; break; } } // Also check if upgrade menu was opened and closed var upgradeMenuExists = false; for (var i = 0; i < game.children.length; i++) { if (game.children[i] instanceof UpgradeMenu) { upgradeMenuExists = true; break; } } // If tower was upgraded OR if menu was opened and then closed if (hasUpgradedTower || !upgradeMenuExists && self.menuWasOpened) { self.stepCompleted = true; if (step.completeTextKey) { instructionText.setText(getText(step.completeTextKey)); } self.updateButtonState(); } // Track if menu was opened if (upgradeMenuExists) { self.menuWasOpened = true; } break; } }; // Initialize first step self.updateButtonState(); self.complete = function () { self.tutorialActive = false; // Mark tutorial as completed globally tutorialCompleted = true; tutorialInProgress = false; // Stop all music first to prevent mixing LK.stopMusic(); // Clear tutorial game state currentWave = 0; currentWorld = 1; currentLevel = 1; waveInProgress = false; waveSpawned = false; waveIndicator.gameStarted = false; gold = 80; lives = 20; score = 0; enemiesKilledInWave = 0; // Clear all game entities while (enemies.length > 0) { var enemy = enemies.pop(); if (enemy.parent) { enemy.parent.removeChild(enemy); } if (enemy.shadow && enemy.shadow.parent) { enemy.shadow.parent.removeChild(enemy.shadow); } } while (towers.length > 0) { var tower = towers.pop(); if (tower.parent) { tower.parent.removeChild(tower); } } while (bullets.length > 0) { var bullet = bullets.pop(); if (bullet.parent) { bullet.parent.removeChild(bullet); } } while (alliedUnits.length > 0) { var unit = alliedUnits.pop(); if (unit.parent) { unit.parent.removeChild(unit); } } // Clear grid state for (var i = 0; i < 24; i++) { for (var j = 0; j < 35; j++) { if (grid.cells[i] && grid.cells[i][j]) { grid.cells[i][j].towersInRange = []; } } } // Reset UI updateUI(); updateWaveCounter(); if (self.onComplete) { self.onComplete(); } self.destroy(); // Wait before creating main menu to ensure clean transition LK.setTimeout(function () { // Return to main menu after tutorial var mainMenu = new MainMenu(); game.addChild(mainMenu); }, 500); }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 600; menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; var towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' Tower', { size: 80, fill: 0xFFFFFF, weight: 800 }); towerTypeText.anchor.set(0, 0); towerTypeText.x = -840; towerTypeText.y = -160; self.addChild(towerTypeText); var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nHealth: ' + self.tower.health + '/' + self.tower.maxHealth + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', { size: 60, fill: 0xFFFFFF, weight: 400 }); statsText.anchor.set(0, 0.5); statsText.x = -840; statsText.y = 50; self.addChild(statsText); var buttonsContainer = new Container(); buttonsContainer.x = 500; self.addChild(buttonsContainer); var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; buttonBackground.height = 150; var isMaxLevel = self.tower.level >= self.tower.maxLevel; // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var upgradeCost; if (isMaxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888; var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' bits', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; sellButtonBackground.height = 150; sellButtonBackground.tint = 0xCC0000; var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); var sellButtonText = new Text2('Sell: +' + sellValue + ' bits', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); upgradeButton.y = -75; sellButton.y = 75; var closeButton = new Container(); self.addChild(closeButton); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; upgradeButton.down = function (x, y, obj) { if (self.tower.level >= self.tower.maxLevel) { var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (self.tower.upgrade()) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); if (self.tower.level >= self.tower.maxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s'); buttonText.setText('Upgrade: ' + upgradeCost + ' bits'); var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); sellButtonText.setText('Sell: +' + sellValue + ' bits'); if (self.tower.level >= self.tower.maxLevel) { buttonBackground.tint = 0x888888; buttonText.setText('Max Level'); } var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { rangeCircle = game.children[i]; break; } } if (rangeCircle) { var rangeGraphics = rangeCircle.children[0]; rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; } else { var newRangeIndicator = new Container(); newRangeIndicator.isTowerRange = true; newRangeIndicator.tower = self.tower; game.addChildAt(newRangeIndicator, 0); newRangeIndicator.x = self.tower.x; newRangeIndicator.y = self.tower.y; var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; rangeGraphics.alpha = 0.3; } tween(self, { scaleX: 1.05, scaleY: 1.05 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn }); } }); } }; sellButton.down = function (x, y, obj) { var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); setGold(gold + sellValue); var notification = game.addChild(new Notification("Tower sold for " + sellValue + " bits!")); notification.x = 2048 / 2; notification.y = grid.height - 50; var gridX = self.tower.gridX; var gridY = self.tower.gridY; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 0; var towerIndex = cell.towersInRange.indexOf(self.tower); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } if (selectedTower === self.tower) { selectedTower = null; } var towerIndex = towers.indexOf(self.tower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } towerLayer.removeChild(self.tower); grid.pathFind(); grid.renderDebug(); // Sincronizar el mapa de visualización después de vender torre syncVisualizationMap(); worldRenderer.updateWorldGraphics(currentWorld, mapVisualization); self.destroy(); for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } }; closeButton.down = function (x, y, obj) { hideUpgradeMenu(self); selectedTower = null; grid.renderDebug(); }; self.update = function () { if (self.tower.level >= self.tower.maxLevel) { if (buttonText.text !== 'Max Level') { buttonText.setText('Max Level'); buttonBackground.tint = 0x888888; } return; } // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var currentUpgradeCost; if (self.tower.level >= self.tower.maxLevel) { currentUpgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } var canAfford = gold >= currentUpgradeCost; buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888; var newText = 'Upgrade: ' + currentUpgradeCost + ' bits'; if (buttonText.text !== newText) { buttonText.setText(newText); } }; return self; }); var WaveIndicator = Container.expand(function () { var self = Container.call(this); self.gameStarted = false; self.waveMarkers = []; self.waveTypes = []; self.enemyCounts = []; self.indicatorWidth = 0; self.lastBossType = null; // Track the last boss type to avoid repeating var blockWidth = 400; var totalBlocksWidth = blockWidth * totalWaves; var startMarker = new Container(); var startBlock = startMarker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); startBlock.width = blockWidth - 10; startBlock.height = 70 * 2; startBlock.tint = 0x00AA00; // Add shadow for start text var startTextShadow = new Text2("Start Game", { size: 50, fill: 0x000000, weight: 800 }); startTextShadow.anchor.set(0.5, 0.5); startTextShadow.x = 4; startTextShadow.y = 4; startMarker.addChild(startTextShadow); var startText = new Text2("Start Game", { size: 50, fill: 0xFFFFFF, weight: 800 }); startText.anchor.set(0.5, 0.5); startMarker.addChild(startText); startMarker.x = -self.indicatorWidth; self.addChild(startMarker); self.waveMarkers.push(startMarker); startMarker.down = function () { if (!self.gameStarted) { self.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; startBlock.tint = 0x00FF00; startText.setText("Started!"); startTextShadow.setText("Started!"); // Make sure shadow position remains correct after text change startTextShadow.x = 4; startTextShadow.y = 4; var notification = game.addChild(new Notification("Game started! Wave 1 incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; for (var i = 0; i < totalWaves; i++) { var marker = new Container(); var block = marker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth - 10; block.height = 70 * 2; // --- 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 = "Immune"; enemyType = "immune"; 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: cycle through all boss types, last boss is always flying var bossTypes = ['normal', 'fast', 'immune', 'flying']; var bossTypeIndex = Math.floor((i + 1) / 10) - 1; if (i === totalWaves - 1) { // Last boss is always flying enemyType = 'flying'; waveType = "Boss Flying"; block.tint = 0xFFFF00; } else { enemyType = bossTypes[bossTypeIndex % bossTypes.length]; switch (enemyType) { case 'normal': block.tint = 0xAAAAAA; waveType = "Boss Normal"; break; case 'fast': block.tint = 0x00AAFF; waveType = "Boss Fast"; break; case 'immune': block.tint = 0xAA0000; waveType = "Boss Immune"; break; case 'flying': block.tint = 0xFFFF00; waveType = "Boss Flying"; break; } } enemyCount = 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 'immune': 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 immune block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; 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.update = function () { var progress = waveTimer / nextWaveTime; // Fix positioning to properly show first wave when currentWave is 0 var displayWave = Math.max(0, currentWave); var moveAmount = (progress + displayWave) * blockWidth; for (var i = 0; i < self.waveMarkers.length; i++) { var marker = self.waveMarkers[i]; marker.x = -moveAmount + i * blockWidth; } self.positionIndicator.x = 0; for (var i = 0; i < totalWaves + 1; i++) { var marker = self.waveMarkers[i]; if (i === 0) { continue; } var block = marker.children[0]; // Fix comparison to properly handle wave 0 and 1 if (i - 1 < displayWave) { block.alpha = .5; } } self.handleWaveProgression = function () { if (!self.gameStarted) { return; } // Waves no longer advance automatically - they must be triggered manually via NextWaveButton // This function now only handles the initial game start }; self.handleWaveProgression(); }; return self; }); var WorldRenderer = Container.expand(function () { var self = Container.call(this); self.currentWorld = 1; self.backgroundTiles = []; self.pathTiles = []; self.wallTiles = []; self.sceneryElements = []; self.getWorldAssets = function (worldNumber) { switch (worldNumber) { case 1: return { background: 'forestBg', path: 'forestPath', wall: 'forestWall', scenery: 'forestScenery', ambient: 0x90ee90 }; case 2: return { background: 'desertBg', path: 'desertPath', wall: 'desertWall', scenery: 'desertScenery', ambient: 0xffd700 }; case 3: return { background: 'glacierBg', path: 'glacierPath', wall: 'glacierWall', scenery: 'glacierScenery', ambient: 0xe6f3ff }; case 4: return { background: 'villageBg', path: 'villagePath', wall: 'villageWall', scenery: 'villageScenery', ambient: 0xf0e68c }; case 5: return { background: 'techLabBg', path: 'techLabPath', wall: 'techLabWall', scenery: 'techLabScenery', ambient: 0x87ceeb }; case 6: return { background: 'infernoBg', path: 'infernoPath', wall: 'infernoWall', scenery: 'infernoScenery', ambient: 0xff6347 }; default: return { background: 'forestBg', path: 'forestPath', wall: 'forestWall', scenery: 'forestScenery', ambient: 0x90ee90 }; } }; self.updateWorldGraphics = function (worldNumber, gridInstance) { // Forzar renderizado siempre, incluso si el mundo no cambia self.currentWorld = worldNumber; var worldAssets = self.getWorldAssets(worldNumber); // Clear existing tiles while (self.backgroundTiles.length) { self.removeChild(self.backgroundTiles.pop()); } while (self.pathTiles.length) { self.removeChild(self.pathTiles.pop()); } while (self.wallTiles.length) { self.removeChild(self.wallTiles.pop()); } while (self.sceneryElements.length) { self.removeChild(self.sceneryElements.pop()); } // Create new tiles based on current world using the correct assets var gridWidth = 24; var gridHeight = 35; for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { // Create background tile using world-specific assets var bgTile = self.attachAsset(worldAssets.background, { anchorX: 0, anchorY: 0 }); bgTile.x = i * CELL_SIZE; bgTile.y = j * CELL_SIZE; self.backgroundTiles.push(bgTile); // Add world-specific scenery elements randomly if (Math.random() < 0.15) { // 15% chance for scenery - use world-specific scenery assets var scenery = self.attachAsset(worldAssets.scenery, { anchorX: 0.5, anchorY: 0.5 }); scenery.x = i * CELL_SIZE + CELL_SIZE / 2; scenery.y = j * CELL_SIZE + CELL_SIZE / 2; // World-specific scenery shapes switch (worldNumber) { case 1: // Forest - trees/bushes (ellipses) scenery.scaleY = 1.2 + Math.random() * 0.8; break; case 2: // Desert - cacti/rocks (tall thin or wide) if (Math.random() < 0.5) { scenery.scaleX = 0.6; scenery.scaleY = 1.8; // Tall cactus } else { scenery.scaleX = 1.4; scenery.scaleY = 0.7; // Wide rock } break; case 3: // Glacier - ice crystals scenery.scaleX = 0.8 + Math.random() * 0.4; scenery.scaleY = 0.8 + Math.random() * 0.4; scenery.alpha = 0.7; break; case 4: // Village - small structures scenery.scaleX = 1.2; scenery.scaleY = 1.0; break; case 5: // Technology - machinery scenery.scaleX = 0.9; scenery.scaleY = 0.9; scenery.alpha = 0.8; break; case 6: // Hell - lava bubbles/flames scenery.scaleX = 1.1 + Math.random() * 0.6; scenery.scaleY = 1.1 + Math.random() * 0.6; scenery.alpha = 0.9; break; } self.sceneryElements.push(scenery); } } } // Overlay path and wall tiles on top of scenery if (gridInstance && gridInstance.cells) { for (var i = 0; i < Math.min(gridWidth, gridInstance.cells.length); i++) { for (var j = 0; j < Math.min(gridHeight, gridInstance.cells[i].length); j++) { var cell = gridInstance.cells[i][j]; if (cell.type === 0 || cell.type === 2 || cell.type === 3) { // Path, spawn, or goal - use world-specific path assets var pathTile = self.attachAsset(worldAssets.path, { anchorX: 0, anchorY: 0 }); pathTile.x = i * CELL_SIZE; pathTile.y = j * CELL_SIZE; pathTile.alpha = 0.95; self.pathTiles.push(pathTile); } else if (cell.type === 1) { // Wall - use appropriate wall tile based on world var wallTile = self.attachAsset(worldAssets.wall, { anchorX: 0, anchorY: 0 }); wallTile.x = i * CELL_SIZE; wallTile.y = j * CELL_SIZE; wallTile.alpha = 0.98; self.wallTiles.push(wallTile); } } } } // Asegurar que WorldRenderer esté en el fondo, debajo de todo if (self.parent) { self.parent.addChildAt(self, 0); } }; return self; }); var WorldSelectionMenu = Container.expand(function () { var self = Container.call(this); // Position the menu at center of screen self.x = 2048 / 2; self.y = 2732 / 2; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 1800; menuBackground.height = 1200; menuBackground.tint = 0x333333; menuBackground.alpha = 0.9; var titleText = new Text2('Select World', { size: 80, fill: 0xFFFFFF, weight: 800 }); titleText.anchor.set(0.5, 0.5); titleText.y = -400; self.addChild(titleText); var worldNames = ["Forest", "Desert", "Glacier", "Village", "Tech Lab", "Inferno"]; var unlockedWorlds = storage.unlockedWorlds || 1; for (var i = 0; i < worldNames.length; i++) { var worldButton = new Container(); var worldButtonBg = worldButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); worldButtonBg.width = 350; worldButtonBg.height = 80; var isUnlocked = i + 1 <= unlockedWorlds; worldButtonBg.tint = isUnlocked ? 0x4444FF : 0x666666; var worldButtonText = new Text2(isUnlocked ? worldNames[i] : "Locked", { size: 40, fill: isUnlocked ? 0xFFFFFF : 0x999999, weight: 800 }); worldButtonText.anchor.set(0.5, 0.5); worldButton.addChild(worldButtonText); worldButton.y = -200 + i * 100; self.addChild(worldButton); (function (worldIndex, unlocked) { worldButton.down = function () { if (unlocked) { self.destroy(); var levelSelectionMenu = new LevelSelectionMenu(worldIndex + 1); game.addChild(levelSelectionMenu); } else { var notification = game.addChild(new Notification("Complete the previous world to unlock!")); notification.x = 2048 / 2; notification.y = 2732 / 2 + 200; } }; })(i, isUnlocked); } return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x333333 }); /**** * Game Code ****/ // This ensures the browser only requests microphone access, not camera. // Only use microphone features from facekit. Do NOT call any camera-related methods. // NOTE: We only use facekit for microphone/voice features (facekit.volume). // We never call any camera or face detection methods, so the browser will only request microphone access. // Voice & Summoning // Boss & Special Events // UI & Feedback // Special Effects // Combat & Action Sounds // Music system for world progression // Notify user about microphone usage for voice commands // Digimon Evolution Assets for Voice Summoning // Agumon Evolution Line (Fire/Dragon) // Gabumon Evolution Line (Ice/Wolf) // Tentomon Evolution Line (Electric/Insect) // Palmon Evolution Line (Plant/Poison) // Gomamon Evolution Line (Water) // Patamon Evolution Line (Sacred/Angel) console.log("This game uses microphone for voice summoning commands, not camera."); function playWorldMusic(worldNumber, isBoss, isFinalBoss) { // Stop any currently playing music first to prevent mixing LK.stopMusic(); // Play world-specific music with fade in effect var musicId; // Check for boss music first if (isFinalBoss) { musicId = 'finalBossMusic'; } else if (isBoss) { musicId = 'bossMusic'; } else { switch (worldNumber) { case 1: musicId = 'forestMusic'; break; case 2: musicId = 'desertMusic'; break; case 3: musicId = 'glacierMusic'; break; case 4: musicId = 'villageMusic'; break; case 5: musicId = 'techLabMusic'; break; case 6: musicId = 'infernoMusic'; break; default: musicId = 'forestMusic'; } } console.log("Playing music for world " + worldNumber + ": " + musicId); LK.playMusic(musicId, { fade: { start: 0, end: isBoss || isFinalBoss ? 1.0 : 0.8, duration: 1500 } }); } // Language system var currentLanguage = storage.language || 'en'; var translations = { en: { firewallDefensors: 'Firewall Defensors', startGame: 'Start Game', tutorial: 'Tutorial', language: 'Language', selectWorld: 'Select World', selectLevel: 'Select Level', back: 'Back', locked: 'Locked', completeLevel: 'Complete previous level to unlock!', completeWorld: 'Complete the previous world to unlock!', nextWave: 'Next Wave', shop: 'Shop', bits: 'Bits', systemHealth: 'System Health', securityScore: 'Security Score', wave: 'Wave', upgrade: 'Upgrade', sell: 'Sell', maxLevel: 'Max Level', digivolve: 'Digivolve', owned: 'OWNED', notEnoughBits: 'Not enough bits!', cannotBuild: 'Cannot build here!', pathBlocked: 'Tower would block the path!', tutorialWelcome: 'Welcome to the Tutorial!\n\nThis game uses microphone for voice commands (not camera).\nLearn to play step by step...', tutorialStep1: 'Step 1: Tower Placement\n\nDrag a Digimon tower from the bottom of the screen to a valid position on the battlefield.', tutorialStep2: 'Step 2: Starting Waves\n\nClick the "Next Wave" button to start sending enemies. Towers will attack automatically.', tutorialStep3: 'Step 3: Upgrading Towers\n\nClick on a placed tower and then press the "Upgrade" button to increase its power. Close the menu when done!', tutorialCompleted: 'Tutorial completed!\n\nNow you know how to:\n• Place towers\n• Start waves\n• Upgrade towers\n• Shout Digimon names to summon allies (requires microphone)\n\nDefend the digital world!', tutorialStep1Complete: 'Excellent! You placed your first tower.', tutorialStep2Complete: 'Well done! The wave has started.', tutorialStep3Complete: 'Perfect! You upgraded the tower.', nextStep: 'Next', skipTutorial: 'Skip Tutorial' }, es: { firewallDefensors: 'Defensores del Firewall', startGame: 'Iniciar Juego', tutorial: 'Tutorial', language: 'Idioma', selectWorld: 'Seleccionar Mundo', selectLevel: 'Seleccionar Nivel', back: 'Atrás', locked: 'Bloqueado', completeLevel: '¡Completa el nivel anterior para desbloquear!', completeWorld: '¡Completa el mundo anterior para desbloquear!', nextWave: 'Siguiente Oleada', shop: 'Tienda', bits: 'Bits', systemHealth: 'Salud del Sistema', securityScore: 'Puntuación de Seguridad', wave: 'Oleada', upgrade: 'Mejorar', sell: 'Vender', maxLevel: 'Nivel Máximo', digivolve: 'Digievolucionar', owned: 'POSEÍDO', notEnoughBits: '¡No tienes suficientes bits!', cannotBuild: '¡No se puede construir aquí!', pathBlocked: '¡La torreta bloquearía el camino!', tutorialWelcome: '¡Bienvenido al Tutorial!\n\nEste juego usa el micrófono para comandos de voz (no la cámara).\nAprende a jugar paso a paso...', tutorialStep1: 'Paso 1: Colocación de torretas\n\nArrastra una torreta Digimon desde la parte inferior de la pantalla hasta una posición válida en el campo de batalla.', tutorialStep2: 'Paso 2: Iniciar oleadas\n\nHaz clic en el botón "Siguiente Oleada" para comenzar a enviar enemigos. Las torretas atacarán automáticamente.', tutorialStep3: 'Paso 3: Mejorar torretas\n\nHaz clic en una torreta colocada y luego presiona el botón "Mejorar" para aumentar su poder. ¡Cierra el menú cuando termines!', tutorialCompleted: '¡Tutorial completado!\n\nAhora ya sabes:\n• Colocar torretas\n• Iniciar oleadas\n• Mejorar torretas\n• Gritar nombres de Digimon para invocar aliados (requiere micrófono)\n\n¡Defiende el mundo digital!', tutorialStep1Complete: 'Excelente! Has colocado tu primera torreta.', tutorialStep2Complete: 'Bien hecho! La oleada ha comenzado.', tutorialStep3Complete: 'Perfecto! Has mejorado la torreta.', nextStep: 'Siguiente', skipTutorial: 'Saltar Tutorial' } }; function getText(key) { return translations[currentLanguage][key] || translations.en[key] || key; } function setLanguage(lang) { currentLanguage = lang; storage.language = lang; } // Assets adicionales para tiles específicos por mundo var isHidingUpgradeMenu = false; function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; } }); } var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var enemies = []; var towers = []; var bullets = []; var defenses = []; var alliedUnits = []; // Array to track summoned allied Digimon units var selectedTower = null; var gold = 80; var lives = 20; var score = storage.securityPoints || 0; var currentWave = 0; var totalWaves = 10; // 10 waves per world var currentWorld = 1; var currentLevel = 1; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 12000 / 2; var sourceTower = null; var enemiesToSpawn = 10; // Default number of enemies per wave var enemiesKilledInWave = 0; var coins = []; // Allied units management var maxAlliedUnits = 5; // Maximum number of allied units at once var summonCooldown = 300; // 5 seconds at 60 FPS var lastSummonTime = 0; // Voice summoning system var voiceSummonCooldown = {}; var voiceDetectionActive = false; var lastVoiceDetectionTime = 0; var voiceDetectionCooldown = 60; // 1 second between voice detections var coinSpawnTimer = 0; var goldText = new Text2(getText('bits') + ': ' + gold, { size: 60, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); // Create health bar container var healthBarContainer = new Container(); var healthBarBg = healthBarContainer.attachAsset('healthBarOutline', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.width = 300; healthBarBg.height = 20; healthBarBg.tint = 0x000000; var healthBarFill = healthBarContainer.attachAsset('healthBar', { anchorX: 0.5, anchorY: 0.5 }); healthBarFill.width = 296; healthBarFill.height = 16; healthBarFill.tint = 0x00FF00; // Add health text label var livesText = new Text2(getText('systemHealth'), { size: 45, fill: 0xFFFFFF, weight: 800 }); livesText.anchor.set(0.5, 0.5); livesText.y = -35; healthBarContainer.addChild(livesText); var scoreText = new Text2(getText('securityScore') + ': ' + score, { size: currentLanguage === 'es' ? 45 : 60, fill: 0xFF0000, weight: 800 }); scoreText.anchor.set(0.5, 0.5); var topMargin = 50; var centerX = 2048 / 2; var spacing = 400; // Create speed control button var speedButton = new Container(); var speedButtonBg = speedButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); speedButtonBg.width = 150; speedButtonBg.height = 80; speedButtonBg.tint = 0x00AA00; var speedButtonText = new Text2('1x', { size: 50, fill: 0xFFFFFF, weight: 800 }); speedButtonText.anchor.set(0.5, 0.5); speedButton.addChild(speedButtonText); var gameSpeed = 1; var speedLevels = [1, 2]; var currentSpeedIndex = 0; speedButton.down = function () { currentSpeedIndex = (currentSpeedIndex + 1) % speedLevels.length; gameSpeed = speedLevels[currentSpeedIndex]; speedButtonText.setText(gameSpeed + 'x'); // Update button color based on speed if (gameSpeed === 1) { speedButtonBg.tint = 0x00AA00; // Green for normal speed } else { speedButtonBg.tint = 0xFFAA00; // Orange for 2x speed } }; LK.gui.top.addChild(goldText); LK.gui.bottom.addChild(healthBarContainer); LK.gui.top.addChild(scoreText); LK.gui.top.addChild(speedButton); healthBarContainer.x = 0; healthBarContainer.y = -300; goldText.x = -spacing; goldText.y = topMargin; scoreText.x = spacing; scoreText.y = topMargin; speedButton.x = 0; speedButton.y = topMargin; function updateUI() { goldText.setText(getText('bits') + ': ' + gold); scoreText.setText(getText('securityScore') + ': ' + score); // Save security points to storage storage.securityPoints = score; // Update health bar var healthPercent = lives / 20; // Assuming max lives is 20 healthBarFill.width = 296 * healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBarFill.tint = 0x00FF00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xFFFF00; // Yellow } else { healthBarFill.tint = 0xFF0000; // Red } } function setGold(value) { gold = value; updateUI(); } var debugLayer = new Container(); var towerLayer = new Container(); // Create three separate layers for enemy hierarchy var enemyLayerBottom = new Container(); // For normal enemies var enemyLayerMiddle = new Container(); // For shadows var enemyLayerTop = new Container(); // For flying enemies var enemyLayer = new Container(); // Main container to hold all enemy layers // Add layers in correct order (bottom first, then middle for shadows, then top) enemyLayer.addChild(enemyLayerBottom); enemyLayer.addChild(enemyLayerMiddle); enemyLayer.addChild(enemyLayerTop); // Add world renderer for background graphics var worldRenderer = new WorldRenderer(); var backgroundLayer = new Container(); backgroundLayer.addChild(worldRenderer); var grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; worldRenderer.x = grid.x; worldRenderer.y = grid.y; grid.pathFind(); // Crear una segunda copia del mapa específicamente para visualización var mapVisualization = new Grid(24, 29 + 6); mapVisualization.x = 150; mapVisualization.y = 200 - CELL_SIZE * 4; // Sincronizar el mapa de visualización con el mapa principal function syncVisualizationMap() { for (var i = 0; i < 24; i++) { for (var j = 0; j < 35; j++) { if (grid.cells[i] && grid.cells[i][j] && mapVisualization.cells[i] && mapVisualization.cells[i][j]) { mapVisualization.cells[i][j].type = grid.cells[i][j].type; } } } } // Forzar actualización visual del mapa cada vez que se sincroniza worldRenderer.updateWorldGraphics(currentWorld, mapVisualization); // Sincronizar inicialmente syncVisualizationMap(); // Render initial world graphics worldRenderer.updateWorldGraphics(currentWorld, mapVisualization); // Only render debug on game start, not every frame if (LK.ticks === 0) { grid.renderDebug(); } debugLayer.addChild(grid); game.addChildAt(backgroundLayer, 0); game.addChild(debugLayer); game.addChild(towerLayer); game.addChild(enemyLayer); var offset = 0; var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; var isDragging = false; function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); // Sincronizar el mapa de visualización después de las verificaciones syncVisualizationMap(); return blocked; } function getTowerCost(towerType) { var cost = 5; switch (towerType) { case 'gabumon': //{ju} // Rapid fire Digimon cost = 15; break; case 'tentomon': //{jw} // Long range Digimon cost = 25; break; case 'palmon': //{jy} // Area damage Digimon cost = 35; break; case 'gomamon': //{jA} // Slowing Digimon cost = 45; break; case 'patamon': //{jC} // Poison/status Digimon cost = 55; break; } return cost; } function getTowerSellValue(totalValue) { return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue; } function placeTower(gridX, gridY, towerType) { var towerCost = getTowerCost(towerType); if (gold >= towerCost) { var tower = new Tower(towerType || 'default'); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); // Play tower placement sound LK.getSound('towerPlace').play(); grid.pathFind(); grid.renderDebug(); // Sincronizar el mapa de visualización después de colocar torre syncVisualizationMap(); worldRenderer.updateWorldGraphics(currentWorld, mapVisualization); return true; } else { var notification = game.addChild(new Notification("Not enough bits!")); notification.x = 2048 / 2; notification.y = grid.height - 50; // Play purchase fail sound LK.getSound('purchaseFail').play(); return false; } } game.down = function (x, y, obj) { var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); var shopVisible = digimonShop && digimonShop.visible; var summonMenuVisible = digimonSummonMenu && digimonSummonMenu.visible; if (upgradeMenuVisible || shopVisible || summonMenuVisible) { return; } for (var i = 0; i < sourceTowers.length; i++) { var tower = sourceTowers[i]; if (x >= tower.x - tower.width / 2 && x <= tower.x + tower.width / 2 && y >= tower.y - tower.height / 2 && y <= tower.y + tower.height / 2) { towerPreview.visible = true; isDragging = true; towerPreview.towerType = tower.towerType; towerPreview.updateAppearance(); // Apply the same offset as in move handler to ensure consistency when starting drag towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); break; } } }; game.move = function (x, y, obj) { var shopVisible = digimonShop && digimonShop.visible; var summonMenuVisible = digimonSummonMenu && digimonSummonMenu.visible; if (shopVisible || summonMenuVisible) { return; } if (isDragging) { // Shift the y position upward by 1.5 tiles to show preview above finger towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); } }; game.up = function (x, y, obj) { var shopVisible = digimonShop && digimonShop.visible; var summonMenuVisible = digimonSummonMenu && digimonSummonMenu.visible; if (shopVisible || summonMenuVisible) { // Still allow dragging to be reset even when menus are open if (isDragging) { isDragging = false; towerPreview.visible = false; } return; } var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerLeft = tower.x - tower.width / 2; var towerRight = tower.x + tower.width / 2; var towerTop = tower.y - tower.height / 2; var towerBottom = tower.y + tower.height / 2; if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) { var clickedOnMenu = false; for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; var menuWidth = 2048; var menuHeight = 450; var menuLeft = menu.x - menuWidth / 2; var menuRight = menu.x + menuWidth / 2; var menuTop = menu.y - menuHeight / 2; var menuBottom = menu.y + menuHeight / 2; if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) { clickedOnMenu = true; break; } } if (!clickedOnMenu) { for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; hideUpgradeMenu(menu); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = null; grid.renderDebug(); } } if (isDragging) { isDragging = false; if (towerPreview.canPlace) { if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) { placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType); } else { var notification = game.addChild(new Notification("Tower would block the path!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } else if (towerPreview.visible) { var notification = game.addChild(new Notification("Cannot build here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } towerPreview.visible = false; if (isDragging) { var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } } } }; // Simple wave counter instead of full wave indicator var waveCounterText = new Text2('Wave: 0/9', { size: 60, fill: 0xFFFFFF, weight: 800 }); waveCounterText.anchor.set(0.5, 0.5); waveCounterText.x = 2048 / 2; waveCounterText.y = 2732 - 80; game.addChild(waveCounterText); // Create a simple wave counter object to replace waveIndicator var waveIndicator = { gameStarted: false, getWaveType: function getWaveType(waveNumber) { if (waveNumber < 1 || waveNumber > 10) return "normal"; // Simple wave type logic for 10 waves var waveTypes = ['normal', 'fast', 'immune', 'flying', 'swarm', 'normal', 'fast', 'immune', 'flying', 'boss']; return waveTypes[waveNumber - 1]; }, getEnemyCount: function getEnemyCount(waveNumber) { if (waveNumber < 1 || waveNumber > 10) return 10; // Wave 10 is boss with 1 giant enemy, others have 10-30 enemies if (waveNumber === 10) return 1; if (waveNumber === 5) return 30; // Swarm wave return 10; }, getWaveTypeName: function getWaveTypeName(waveNumber) { var type = this.getWaveType(waveNumber); var typeName = type.charAt(0).toUpperCase() + type.slice(1); if (waveNumber === 10) typeName = "BOSS"; return typeName; } }; // Function to update wave counter display function updateWaveCounter() { var currentWorldWave; if (currentWave === 0) { currentWorldWave = 0; } else { // Calculate the wave within the current world (1-10) currentWorldWave = (currentWave - 1) % 10 + 1; } waveCounterText.setText(getText('wave') + ': ' + currentWorldWave + '/10'); } var nextWaveButtonContainer = new Container(); var nextWaveButton = new NextWaveButton(); nextWaveButton.x = 2048 - 200; nextWaveButton.y = 2732 - 100 + 20; nextWaveButtonContainer.addChild(nextWaveButton); game.addChild(nextWaveButtonContainer); // Add shop button var shopButton = new Container(); var shopButtonBg = shopButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); shopButtonBg.width = 300; shopButtonBg.height = 100; shopButtonBg.tint = 0x4444FF; var shopButtonText = new Text2('Shop', { size: 50, fill: 0xFFFFFF, weight: 800 }); shopButtonText.anchor.set(0.5, 0.5); shopButton.addChild(shopButtonText); shopButton.x = 200; shopButton.y = 2732 - 100 + 20; game.addChild(shopButton); var digimonShop = new DigimonShop(); digimonShop.x = 2048 / 2; game.addChild(digimonShop); shopButton.down = function () { digimonShop.show(); }; // --- Add C, B, and A buttons for Digimon summon filtering --- var summonButtonSpacing = 120; var summonButtonStartX = shopButton.x + 220; // Place to the right of shop button function createSummonLevelButton(label, color, filterLevel, offset) { var btn = new Container(); var btnBg = btn.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); btnBg.width = 100; btnBg.height = 100; btnBg.tint = color; var btnText = new Text2(label, { size: 60, fill: 0xFFFFFF, weight: 800 }); btnText.anchor.set(0.5, 0.5); btn.addChild(btnText); btn.x = summonButtonStartX + offset * summonButtonSpacing; btn.y = shopButton.y; btn.down = function () { // Show summon menu filtered by evolution level if (digimonSummonMenu && digimonSummonMenu.show) { digimonSummonMenu.show(filterLevel); } }; return btn; } // C = Champion, B = Ultimate, A = Mega var buttonC = createSummonLevelButton('C', 0x32CD32, 'champion', 0); var buttonB = createSummonLevelButton('B', 0xFFD700, 'ultimate', 1); var buttonA = createSummonLevelButton('A', 0xFF4500, 'mega', 2); game.addChild(buttonC); game.addChild(buttonB); game.addChild(buttonA); var towerTypes = ['agumon', 'gabumon', 'tentomon', 'palmon', 'gomamon', 'patamon']; var sourceTowers = []; var towerSpacing = 320; // Increase spacing for larger towers var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2; var towerY = 2732 - CELL_SIZE * 3 - 90; for (var i = 0; i < towerTypes.length; i++) { var tower = new SourceTower(towerTypes[i]); tower.x = startX + i * towerSpacing; tower.y = towerY; towerLayer.addChild(tower); sourceTowers.push(tower); } // Add summon arrow button at the end of source towers var summonArrowButton = new Container(); var arrowBg = summonArrowButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); arrowBg.width = 200; arrowBg.height = 200; arrowBg.tint = 0xFF6600; var arrowGraphics = summonArrowButton.attachAsset('arrow', { anchorX: 0.5, anchorY: 0.5 }); arrowGraphics.width = 80; arrowGraphics.height = 80; arrowGraphics.tint = 0xFFFFFF; arrowGraphics.rotation = Math.PI / 2; // Point upward var summonText = new Text2('SUMMON', { size: 35, fill: 0xFFFFFF, weight: 800 }); summonText.anchor.set(0.5, 0.5); summonText.y = 50; summonArrowButton.addChild(summonText); var summonSubText = new Text2('(No Mic)', { size: 25, fill: 0xCCCCCC, weight: 600 }); summonSubText.anchor.set(0.5, 0.5); summonSubText.y = 75; summonArrowButton.addChild(summonSubText); summonArrowButton.x = startX + towerTypes.length * towerSpacing; summonArrowButton.y = towerY; towerLayer.addChild(summonArrowButton); // Create summon menu var digimonSummonMenu = new DigimonSummonMenu(); digimonSummonMenu.x = 2048 / 2; game.addChild(digimonSummonMenu); summonArrowButton.down = function () { digimonSummonMenu.show(); }; sourceTower = null; enemiesToSpawn = 10; game.update = function () { // Update background color based on current world var worldAssets = worldRenderer.getWorldAssets(currentWorld); if (worldAssets) { game.setBackgroundColor(worldAssets.ambient); } // Apply speed multiplier to frame-dependent updates var effectiveSpeed = gameSpeed; // Note: Wave progression is now manual only via NextWaveButton, no automatic timer if (waveInProgress) { if (!waveSpawned) { waveSpawned = true; enemiesKilledInWave = 0; // Reset kill counter for new wave // Calculate correct world and level for this wave var levelsPerWorld = 10; var newWorld = Math.ceil(currentWave / levelsPerWorld); var newLevel = (currentWave - 1) % levelsPerWorld + 1; var previousWorld = currentWorld; currentWorld = newWorld; currentLevel = newLevel; if (currentWorld > 6) currentWorld = 6; // Regenerate maze and world graphics if world changed or first wave if (currentWorld !== previousWorld || currentWave === 1) { grid.generateMazeForWorld(currentWorld); mapVisualization.generateMazeForWorld(currentWorld); // Regenerar también el mapa de visualización grid.pathFind(); grid.renderDebug(); // Sincronizar mapas después de regenerar syncVisualizationMap(); // Update world graphics usando el mapa de visualización worldRenderer.updateWorldGraphics(currentWorld, mapVisualization); // Change music when entering new world playWorldMusic(currentWorld); // World-specific notification messages var worldNames = ["", "Forest", "Desert", "Glacier", "Village", "Tech Lab", "Inferno"]; var worldNotification = game.addChild(new Notification("Welcome to Digital World " + currentWorld + ": " + worldNames[currentWorld] + "!")); worldNotification.x = 2048 / 2; worldNotification.y = grid.height - 100; } // Get wave type and enemy count from the wave indicator (based on current world/level) var worldWave = currentLevel; var waveType = waveIndicator.getWaveType(worldWave); var enemyCount = waveIndicator.getEnemyCount(worldWave); // Update wave counter display updateWaveCounter(); // Check if this is a boss wave (wave 10 of each world) var isBossWave = worldWave === 10; if (isBossWave) { // Boss waves have just 1 giant enemy enemyCount = 1; // Get boss name based on current world var bossNames = ["", "SNORLAX", "RHYDON", "ARTICUNO", "MACHAMP", "GROUDON", "MEWTWO"]; var bossName = bossNames[currentWorld] || "BOSS"; // Check if this is the final boss (Mewtwo in world 6) var isFinalBoss = currentWorld === 6; // Play boss music playWorldMusic(currentWorld, true, isFinalBoss); // Show boss announcement with specific name var notification = game.addChild(new Notification("⚠️ " + bossName + " APPEARS! ⚠️")); notification.x = 2048 / 2; notification.y = grid.height - 200; } // Spawn the appropriate number of enemies for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); // Make wave 10 boss giant with much more health if (isBossWave) { enemy.isBoss = true; enemy.maxHealth *= 50; // 50x more health for boss enemy.health = enemy.maxHealth; // Make boss visually larger if (enemy.children[0]) { enemy.children[0].scaleX = 3.0; enemy.children[0].scaleY = 3.0; } // Make boss slower but more intimidating enemy.speed *= 0.5; } // 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; // All enemy types now spawn in the middle 6 tiles at the top spacing var gridWidth = 24; var midPoint = Math.floor(gridWidth / 2); // 12 // Find a column that isn't occupied by another enemy that's not yet in view var availableColumns = []; for (var col = midPoint - 3; col < midPoint + 3; col++) { var columnOccupied = false; // Check if any enemy is already in this column but not yet in view for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === col && enemies[e].currentCellY < 4) { columnOccupied = true; break; } } if (!columnOccupied) { availableColumns.push(col); } } // If all columns are occupied, use original random method var spawnX; if (availableColumns.length > 0) { // Choose a random unoccupied column spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)]; } else { // Fallback to random if all columns are occupied spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 9 to 14 } var spawnY = -1 - Math.random() * 5; // Random distance above the grid for spreading enemy.cellX = spawnX; enemy.cellY = 5; // Position after entry enemy.currentCellX = spawnX; enemy.currentCellY = spawnY; enemy.waveNumber = currentWave; 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; } } // Apply speed multiplier to enemy updates for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; // Update enemy with speed multiplier applied if (enemy.update) { for (var speedTick = 0; speedTick < effectiveSpeed; speedTick++) { enemy.update(); } } if (enemy.health <= 0) { // Play enemy death sound if (enemy.isBoss) { LK.getSound('bossDeath').play(); } else { LK.getSound('enemyDestroyed').play(); } for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } // Track enemy kills for wave progression if (enemy.waveNumber === currentWave) { enemiesKilledInWave++; // Give bonus gold every 5 enemies killed if (enemiesKilledInWave % 5 === 0) { var bonusGold = enemy.isBoss ? 15 : 10; var bonusIndicator = new GoldIndicator(bonusGold, enemy.x, enemy.y - 50); game.addChild(bonusIndicator); setGold(gold + bonusGold); var notification = game.addChild(new Notification("Kill streak bonus! +" + bonusGold + " bits!")); notification.x = 2048 / 2; notification.y = grid.height - 200; } } // Boss enemies give more gold and score var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5); var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); // Give more score for defeating a boss var scoreValue = enemy.isBoss ? 100 : 5; score += scoreValue; // Save security points to storage storage.securityPoints = score; // Add a notification for boss defeat if (enemy.isBoss) { // Get boss name based on world var bossNames = ["", "Snorlax", "Rhydon", "Articuno", "Machamp", "Groudon", "Mewtwo"]; var bossName = bossNames[enemy.worldNumber] || "Boss"; var notification = game.addChild(new Notification(bossName + " defeated! +" + goldEarned + " bits!")); notification.x = 2048 / 2; notification.y = grid.height - 150; // Return to normal world music after boss defeat LK.setTimeout(function () { LK.stopMusic(); LK.setTimeout(function () { playWorldMusic(enemy.worldNumber, false, false); }, 500); // Add delay to ensure clean music transition }, 2000); // Wait 2 seconds before switching back to normal music } updateUI(); // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); continue; } if (grid.updateEnemy(enemy)) { // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); // Calculate damage based on enemy world and type var damageDealt = 1; // Base damage var worldDamageMultiplier = 1; // Scale damage based on world switch (enemy.worldNumber) { case 1: worldDamageMultiplier = 1; break; case 2: worldDamageMultiplier = 1.2; break; case 3: worldDamageMultiplier = 1.4; break; case 4: worldDamageMultiplier = 1.6; break; case 5: worldDamageMultiplier = 1.8; break; case 6: worldDamageMultiplier = 2.0; break; } // Scale damage based on enemy type switch (enemy.type) { case 'normal': damageDealt = 1; break; case 'fast': damageDealt = 1; break; // Fast but same damage case 'immune': damageDealt = 2; break; // Tanky and hits hard case 'flying': damageDealt = 1.5; break; // Aerial advantage case 'swarm': damageDealt = 1; break; // Weak individual damage } // Boss enemies deal significantly more damage if (enemy.isBoss) { damageDealt *= 5; } // Apply world multiplier and round damageDealt = Math.ceil(damageDealt * worldDamageMultiplier); lives = Math.max(0, lives - damageDealt); // Play system damage sound LK.getSound('systemDamage').play(); // Check for critical health warning if (lives <= 5 && lives > 0) { LK.getSound('criticalHealth').play(); } updateUI(); // Show damage indicator var damageIndicator = new Notification("-" + damageDealt + " System Health!"); damageIndicator.x = 2048 / 2; damageIndicator.y = 2732 - 200; game.addChild(damageIndicator); if (lives <= 0) { LK.showGameOver(); } } } for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent) { if (bullets[i].targetEnemy) { var targetEnemy = bullets[i].targetEnemy; var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } bullets.splice(i, 1); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } // Check for level completion and world progression var levelsPerWorld = 10; var completedWaves = storage.completedWaves || 0; if (currentWave > completedWaves) { storage.completedWaves = currentWave; // Calculate current world and level var currentWorldNum = Math.ceil(currentWave / levelsPerWorld); var currentLevelNum = (currentWave - 1) % levelsPerWorld + 1; // Update world levels in storage var worldLevels = storage.worldLevels || { 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1 }; if (currentWorldNum >= 1 && currentWorldNum <= 6) { worldLevels[currentWorldNum] = Math.max(worldLevels[currentWorldNum], currentLevelNum); storage.worldLevels = worldLevels; } // Check if we completed a world (every 10 waves) var worldsCompleted = Math.floor(currentWave / levelsPerWorld); var unlockedWorlds = storage.unlockedWorlds || 1; if (worldsCompleted + 1 > unlockedWorlds && worldsCompleted + 1 <= 6) { storage.unlockedWorlds = worldsCompleted + 1; var notification = game.addChild(new Notification("New world unlocked!")); notification.x = 2048 / 2; notification.y = grid.height - 100; } } // Spawn coins randomly on the field coinSpawnTimer++; if (coinSpawnTimer > 10800 && coins.length < 3) { // Spawn every 3 minutes (180 seconds * 60 FPS = 10800 ticks), max 3 coins coinSpawnTimer = 0; // Find a random walkable position var attempts = 0; var spawnX, spawnY; do { var gridX = Math.floor(Math.random() * 24); var gridY = Math.floor(Math.random() * 35); var cell = grid.getCell(gridX, gridY); if (cell && cell.type === 0) { // Spawn on path tiles spawnX = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; spawnY = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; break; } attempts++; } while (attempts < 50); if (attempts < 50) { var coinValue = 5; // Fixed 5 bits for security store currency var coin = new Coin(spawnX, spawnY, coinValue); game.addChild(coin); coins.push(coin); } } // Update existing coins for (var i = coins.length - 1; i >= 0; i--) { var coin = coins[i]; if (coin.update) coin.update(); if (coin.collected) { coins.splice(i, 1); } } // Update allied units with speed multiplier for (var i = alliedUnits.length - 1; i >= 0; i--) { var unit = alliedUnits[i]; if (unit.update) { for (var speedTick = 0; speedTick < effectiveSpeed; speedTick++) { unit.update(); } } // Remove dead or destroyed units if (unit.isDead || !unit.parent) { alliedUnits.splice(i, 1); } } // Voice summoning system handleVoiceSummoning(); // Check for completion of current world (10 waves) or all worlds var worldWave = (currentWave - 1) % 10 + 1; if (worldWave >= 10 && enemies.length === 0 && !waveInProgress) { // Play level complete sound LK.getSound('levelComplete').play(); // Stop all music first to prevent mixing LK.stopMusic(); // Clear all timeouts to prevent conflicts // (No direct timeout clearing method available, but stopping music helps) // Show win screen instead of immediately returning to main menu LK.showYouWin(); // This will handle the game state reset and return to main menu properly } }; // Show microphone usage notification var micNotification = game.addChild(new Notification("Microphone access may be requested for voice summoning features")); micNotification.x = 2048 / 2; micNotification.y = 200; var mainMenu = new MainMenu(); game.addChild(mainMenu); // Execute the game console.log("Game initialized and running!"); game.startGame = function () { var worldSelectionMenu = new WorldSelectionMenu(); game.addChild(worldSelectionMenu); }; game.startWorld = function (worldNumber) { currentWorld = worldNumber; grid.generateMazeForWorld(currentWorld); mapVisualization.generateMazeForWorld(currentWorld); // Regenerar también el mapa de visualización grid.pathFind(); grid.renderDebug(); // Sincronizar mapas syncVisualizationMap(); worldRenderer.updateWorldGraphics(currentWorld, mapVisualization); // Change to world-specific music playWorldMusic(worldNumber); // Show story sequence before starting the world var storySequence = new StorySequence(worldNumber); game.addChild(storySequence); storySequence.onComplete = function () { waveIndicator.gameStarted = true; currentWave = (currentWorld - 1) * 10; waveTimer = nextWaveTime; }; }; game.startWorldLevel = function (worldNumber, levelNumber) { currentWorld = worldNumber; currentLevel = levelNumber; // Set currentWave to the correct absolute wave number for this world/level // Each world has 10 levels, so world 1: 1-10, world 2: 11-20, etc. currentWave = (worldNumber - 1) * 10 + levelNumber; grid.generateMazeForWorld(currentWorld); mapVisualization.generateMazeForWorld(currentWorld); grid.pathFind(); grid.renderDebug(); // Sincronizar mapas syncVisualizationMap(); worldRenderer.updateWorldGraphics(currentWorld, mapVisualization); // Change to world-specific music playWorldMusic(worldNumber); // Show story sequence before starting the level var storySequence = new StorySequence(worldNumber); game.addChild(storySequence); storySequence.onComplete = function () { waveIndicator.gameStarted = true; // Set waveTimer so the first wave button click starts at the selected level waveTimer = nextWaveTime; }; }; // Add tutorial state tracking var tutorialCompleted = false; var tutorialInProgress = false; // Voice summoning system handler // This function only uses facekit.volume (microphone input). // No camera or face detection features are used, so only microphone permission is requested. function handleVoiceSummoning() { // Only process voice commands during active gameplay if (!waveIndicator.gameStarted || LK.ticks - lastVoiceDetectionTime < voiceDetectionCooldown) { return; } // Check for loud voice input (shouting level) if (facekit.volume > 0.7) { lastVoiceDetectionTime = LK.ticks; // Define Digimon voice commands and their requirements var digimonVoiceCommands = { // Champion level (requires Digivice C and base tower) 'greymon': { baseTower: 'agumon', requiredDigivice: 'digiviceC', level: 'champion', cost: 150, description: 'Champion Fire Dragon' }, 'garurumon': { baseTower: 'gabumon', requiredDigivice: 'digiviceC', level: 'champion', cost: 180, description: 'Champion Ice Wolf' }, 'kabuterimon': { baseTower: 'tentomon', requiredDigivice: 'digiviceC', level: 'champion', cost: 160, description: 'Champion Electric Insect' }, // Ultimate level (requires Digivice B and base tower) 'metalgreymon': { baseTower: 'agumon', requiredDigivice: 'digiviceB', level: 'ultimate', cost: 400, description: 'Ultimate Cyborg Dragon' }, 'weregarurumon': { baseTower: 'gabumon', requiredDigivice: 'digiviceB', level: 'ultimate', cost: 450, description: 'Ultimate Beast Warrior' }, 'megakabuterimon': { baseTower: 'tentomon', requiredDigivice: 'digiviceB', level: 'ultimate', cost: 420, description: 'Ultimate Giant Insect' }, // Mega level (requires Digivice A and base tower) 'wargreymon': { baseTower: 'agumon', requiredDigivice: 'digiviceA', level: 'mega', cost: 800, description: 'Mega Dragon Warrior' } }; // Check each voice command for (var digimonName in digimonVoiceCommands) { var command = digimonVoiceCommands[digimonName]; // Check if player has the required Digivice if (!storage[command.requiredDigivice]) { continue; } // Count base towers of the required type var baseTowerCount = 0; for (var i = 0; i < towers.length; i++) { if (towers[i].id === command.baseTower) { baseTowerCount++; } } // Need at least one base tower to summon evolution if (baseTowerCount === 0) { continue; } // Calculate cooldown based on number of base towers var cooldownReduction = Math.max(1, baseTowerCount); var effectiveCooldown = Math.floor(summonCooldown / cooldownReduction); // Check if this Digimon is off cooldown var lastSummonTime = voiceSummonCooldown[digimonName] || 0; if (LK.ticks - lastSummonTime < effectiveCooldown) { continue; } // Check if player can afford the summon if (score < command.cost) { continue; } // Check if there's space for more allied units if (alliedUnits.length >= maxAlliedUnits) { continue; } // Voice detection successful - summon the Digimon! score -= command.cost; updateUI(); // Create a more powerful allied unit based on evolution level var summonedUnit = new DigimonUnit(digimonName, getEvolutionLevel(command.level)); // Apply evolution bonuses switch (command.level) { case 'champion': summonedUnit.damage *= 2; summonedUnit.health *= 2; summonedUnit.maxHealth = summonedUnit.health; break; case 'ultimate': summonedUnit.damage *= 3; summonedUnit.health *= 3; summonedUnit.maxHealth = summonedUnit.health; summonedUnit.range *= 1.5; break; case 'mega': summonedUnit.damage *= 5; summonedUnit.health *= 5; summonedUnit.maxHealth = summonedUnit.health; summonedUnit.range *= 2; summonedUnit.attackRate = Math.floor(summonedUnit.attackRate * 0.7); break; } // Add to game world enemyLayerTop.addChild(summonedUnit); alliedUnits.push(summonedUnit); // Set cooldown for this specific Digimon voiceSummonCooldown[digimonName] = LK.ticks; // Show success notification var notification = game.addChild(new Notification(digimonName.toUpperCase() + " summoned by voice!")); notification.x = 2048 / 2; notification.y = grid.height - 50; // Show microphone icon animation above the summoned Digimon var micIcon = summonedUnit.attachAsset('notification', { anchorX: 0.5, anchorY: 1.0 }); micIcon.width = 60; micIcon.height = 60; micIcon.x = 0; micIcon.y = -summonedUnit.children[0].height / 2 - 10; micIcon.tint = 0x00AAFF; micIcon.alpha = 0; tween(micIcon, { alpha: 1, y: micIcon.y - 20 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(micIcon, { alpha: 0, y: micIcon.y - 40 }, { duration: 600, delay: 600, easing: tween.easeIn, onFinish: function onFinish() { micIcon.visible = false; } }); } }); // Visual and audio feedback LK.effects.flashScreen(0x00FF00, 500); // Play voice summon and digivolve sounds LK.getSound('voiceSummon').play(); LK.setTimeout(function () { LK.getSound('digivolveSound').play(); }, 200); // Only summon one Digimon per voice command break; } } } // Helper function to convert evolution level to numeric level function getEvolutionLevel(levelName) { switch (levelName) { case 'champion': return 3; case 'ultimate': return 5; case 'mega': return 6; default: return 1; } } game.startTutorial = function () { // Clear any existing game state first while (enemies.length > 0) { var enemy = enemies.pop(); if (enemy.parent) { enemy.parent.removeChild(enemy); } if (enemy.shadow && enemy.shadow.parent) { enemy.shadow.parent.removeChild(enemy.shadow); } } while (towers.length > 0) { var tower = towers.pop(); if (tower.parent) { tower.parent.removeChild(tower); } } while (bullets.length > 0) { var bullet = bullets.pop(); if (bullet.parent) { bullet.parent.removeChild(bullet); } } // IMPORTANT: Reset tutorial state BEFORE creating the sequence tutorialCompleted = false; tutorialInProgress = false; // Reset game state for tutorial currentWorld = 1; currentLevel = 1; currentWave = 0; // Reset to 0 so tutorial starts at wave 0/9 waveInProgress = false; waveSpawned = false; gold = 80; lives = 20; score = 0; enemiesKilledInWave = 0; // Update wave counter for tutorial - explicitly set to show 0/10 waveCounterText.setText('Wave: 0/10'); updateUI(); // Generate tutorial world grid.generateMazeForWorld(currentWorld); mapVisualization.generateMazeForWorld(currentWorld); grid.pathFind(); grid.renderDebug(); syncVisualizationMap(); worldRenderer.updateWorldGraphics(currentWorld, mapVisualization); // Start tutorial with forest world music playWorldMusic(1); // Create tutorial sequence immediately after state reset var tutorialSequence = new TutorialSequence(); game.addChild(tutorialSequence); // Set tutorial in progress AFTER creation tutorialInProgress = true; tutorialSequence.onComplete = function () { // Mark tutorial as completed tutorialCompleted = true; tutorialInProgress = false; // After tutorial, ensure game is ready to start from wave 1 waveIndicator.gameStarted = true; // Tutorial complete - show next wave button var notification = game.addChild(new Notification("Tutorial complete! Use Next Wave button to start your first wave!")); notification.x = 2048 / 2; notification.y = grid.height - 150; }; };
===================================================================
--- original.js
+++ change.js
@@ -679,9 +679,18 @@
// Clear existing buttons
while (itemsContainer.children.length > 0) {
itemsContainer.removeChild(itemsContainer.children[0]);
}
- var availableDigimon = getAvailableDigimon();
+ // Only show Digimon that can actually be summoned (requirements met)
+ var availableDigimon = getAvailableDigimon().filter(function (d) {
+ // Check digivice requirement
+ if (d.reqDigivice && !storage[d.reqDigivice]) return false;
+ // Check cost
+ if (score < d.cost) return false;
+ // Check maxAlliedUnits
+ if (alliedUnits.length >= maxAlliedUnits) return false;
+ return true;
+ });
// If a filterLevel is set (from C, B, or A button), filter Digimon by evolution level
var filterLevel = self._filterLevel;
if (filterLevel) {
// Map Digimon id to evolution level
@@ -696,8 +705,9 @@
availableDigimon = availableDigimon.filter(function (d) {
return (digimonLevelMap[d.id] || "").toLowerCase() === filterLevel.toLowerCase();
});
}
+ // Layout: 3 per row, center horizontally
var buttonsPerRow = Math.min(3, availableDigimon.length);
var buttonSpacing = 400;
var startX = -((buttonsPerRow - 1) * buttonSpacing) / 2;
for (var i = 0; i < availableDigimon.length; i++) {
@@ -707,9 +717,25 @@
var digimonButton = new Container();
digimonButton.x = startX + col * buttonSpacing;
digimonButton.y = row * 180;
itemsContainer.addChild(digimonButton);
- // ... rest of function unchanged
+ // Use correct asset for Digimon
+ var digimonAssetMap = {
+ koromon: 'agumon',
+ tsunomon: 'gabumon',
+ greymon: 'greymon',
+ garurumon: 'garurumon',
+ metalgreymon: 'metalgreymon',
+ wargreymon: 'wargreymon'
+ };
+ var assetId = digimonAssetMap[digimon.id] || 'agumon';
+ var digimonArt = digimonButton.attachAsset(assetId, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ digimonArt.width = 90;
+ digimonArt.height = 90;
+ digimonArt.y = -60;
var buttonBg = digimonButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
@@ -4798,26 +4824,26 @@
/****
* Game Code
****/
-// Patamon Evolution Line (Sacred/Angel)
-// Gomamon Evolution Line (Water)
-// Palmon Evolution Line (Plant/Poison)
-// Tentomon Evolution Line (Electric/Insect)
-// Gabumon Evolution Line (Ice/Wolf)
-// Agumon Evolution Line (Fire/Dragon)
-// Digimon Evolution Assets for Voice Summoning
-// Notify user about microphone usage for voice commands
-// Music system for world progression
-// Combat & Action Sounds
-// Special Effects
-// UI & Feedback
-// Boss & Special Events
-// Voice & Summoning
-// We never call any camera or face detection methods, so the browser will only request microphone access.
-// NOTE: We only use facekit for microphone/voice features (facekit.volume).
-// Only use microphone features from facekit. Do NOT call any camera-related methods.
// This ensures the browser only requests microphone access, not camera.
+// Only use microphone features from facekit. Do NOT call any camera-related methods.
+// NOTE: We only use facekit for microphone/voice features (facekit.volume).
+// We never call any camera or face detection methods, so the browser will only request microphone access.
+// Voice & Summoning
+// Boss & Special Events
+// UI & Feedback
+// Special Effects
+// Combat & Action Sounds
+// Music system for world progression
+// Notify user about microphone usage for voice commands
+// Digimon Evolution Assets for Voice Summoning
+// Agumon Evolution Line (Fire/Dragon)
+// Gabumon Evolution Line (Ice/Wolf)
+// Tentomon Evolution Line (Electric/Insect)
+// Palmon Evolution Line (Plant/Poison)
+// Gomamon Evolution Line (Water)
+// Patamon Evolution Line (Sacred/Angel)
console.log("This game uses microphone for voice summoning commands, not camera.");
function playWorldMusic(worldNumber, isBoss, isFinalBoss) {
// Stop any currently playing music first to prevent mixing
LK.stopMusic();
@@ -5526,14 +5552,18 @@
if (waveInProgress) {
if (!waveSpawned) {
waveSpawned = true;
enemiesKilledInWave = 0; // Reset kill counter for new wave
- // Check if we entered a new world and regenerate maze (10 waves per world)
- var newWorld = Math.ceil(currentWave / 10);
- if (newWorld > currentWorld || currentWave === 1) {
- var previousWorld = currentWorld;
- currentWorld = newWorld;
- if (currentWorld > 6) currentWorld = 6;
+ // Calculate correct world and level for this wave
+ var levelsPerWorld = 10;
+ var newWorld = Math.ceil(currentWave / levelsPerWorld);
+ var newLevel = (currentWave - 1) % levelsPerWorld + 1;
+ var previousWorld = currentWorld;
+ currentWorld = newWorld;
+ currentLevel = newLevel;
+ if (currentWorld > 6) currentWorld = 6;
+ // Regenerate maze and world graphics if world changed or first wave
+ if (currentWorld !== previousWorld || currentWave === 1) {
grid.generateMazeForWorld(currentWorld);
mapVisualization.generateMazeForWorld(currentWorld); // Regenerar también el mapa de visualización
grid.pathFind();
grid.renderDebug();
@@ -5541,27 +5571,22 @@
syncVisualizationMap();
// Update world graphics usando el mapa de visualización
worldRenderer.updateWorldGraphics(currentWorld, mapVisualization);
// Change music when entering new world
- if (currentWorld !== previousWorld) {
- playWorldMusic(currentWorld);
- }
+ playWorldMusic(currentWorld);
// World-specific notification messages
var worldNames = ["", "Forest", "Desert", "Glacier", "Village", "Tech Lab", "Inferno"];
var worldNotification = game.addChild(new Notification("Welcome to Digital World " + currentWorld + ": " + worldNames[currentWorld] + "!"));
worldNotification.x = 2048 / 2;
worldNotification.y = grid.height - 100;
}
- // Get wave type and enemy count from the wave indicator (based on current world wave)
- var worldWave = (currentWave - 1) % 10 + 1;
+ // Get wave type and enemy count from the wave indicator (based on current world/level)
+ var worldWave = currentLevel;
var waveType = waveIndicator.getWaveType(worldWave);
var enemyCount = waveIndicator.getEnemyCount(worldWave);
// Update wave counter display
updateWaveCounter();
// Check if this is a boss wave (wave 10 of each world)
- var worldWave = (currentWave - 1) % 10 + 1;
- // Handle special case for tutorial/first world where currentWave might be 10
- if (currentWave === 10) worldWave = 10;
var isBossWave = worldWave === 10;
if (isBossWave) {
// Boss waves have just 1 giant enemy
enemyCount = 1;
@@ -5578,12 +5603,11 @@
notification.y = grid.height - 200;
}
// Spawn the appropriate number of enemies
for (var i = 0; i < enemyCount; i++) {
- var worldWave = (currentWave - 1) % 9 + 1;
var enemy = new Enemy(waveType);
// Make wave 10 boss giant with much more health
- if (worldWave === 10) {
+ if (isBossWave) {
enemy.isBoss = true;
enemy.maxHealth *= 50; // 50x more health for boss
enemy.health = enemy.maxHealth;
// Make boss visually larger
@@ -5611,10 +5635,8 @@
// 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 = 24;
var midPoint = Math.floor(gridWidth / 2); // 12
// Find a column that isn't occupied by another enemy that's not yet in view
Gabumon viendo a la derecha, perspectiva isométrica. In-Game asset. 2d. High contrast. No shadows
Gomamon. In-Game asset. 2d. High contrast. No shadows
Agumon viendo a la derecha, perspectiva isometrica. In-Game asset. 2d. High contrast. No shadows
Tentomon viendo a la derecha, perspectiva isometrica. In-Game asset. 2d. High contrast. No shadows
Patamon viendo a la derecha, perspectiva isometrica. In-Game asset. 2d. High contrast. No shadows
Pikachu visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Pidgeot visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Beedrill visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Gengar visto desde arriba. In-Game asset. 2d. High contrast. No shadows
forestScenaryElement. In-Game asset. 2d. High contrast. No shadows
Cell. In-Game asset. 2d. High contrast. No shadows
desertbg. In-Game asset. 2d. High contrast. No shadows
glacierbg block. In-Game asset. 2d. High contrast. No shadows
Rattata, visto desde arriba, viendo a la derecha. In-Game asset. 2d. High contrast. No shadows
boton con contorno mecanico. In-Game asset. 2d. High contrast. No shadows
Bloque de pasto verde, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Articuno visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Bloque de tierra sin césped, visto desde arriba, con efecto repetido para unirse a otros bloques, ocupar el cuadro completo de imagen In-Game asset. 2d. High contrast. No shadows
Snorlax, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Quitale el fondo blanco pero cuidado con las garras
Machamp, visto desde arriba, bien detallado. In-Game asset. 2d. High contrast. No shadows
Groudon, visto desde arriba, detalles altos. In-Game asset. 2d. High contrast. No shadows
Mewtwo, visto desde arriba, altos detalles. In-Game asset. 2d. High contrast. No shadows
Greymon, visto desde arriba, detalles altos sin errores. In-Game asset. 2d. High contrast. No shadows
Metalgreymon, visto desde arriba, detalles altos, sin errores debe ser identico. In-Game asset. 2d. High contrast. No shadows
Wargreymon, visto desde arriba, detalles muy altos, sin errores idéntico. In-Game asset. 2d. High contrast. No shadows
Garurumon, visto desde arriba, detalles muy altos, sin errores idéntico. In-Game asset. 2d. High contrast. No shadows
WereGarurumon, visto desde arriba, detalles muy altos, sin errores, original In-Game asset. 2d. High contrast. No shadows
Metalgarurumon, visto desde arriba, detalles muy épicos. In-Game asset. 2d. High contrast. No shadows
Kabuterimon. visto desde arriba. calidad y detalles altos. In-Game asset. 2d. High contrast. No shadows
MegaKabuterimon. visto desde arriba. calidad y detalles altos. In-Game asset. 2d. High contrast. No shadows
Herculeskabuterimon, visto desde arriba, calidad y detalles altamente épicos. In-Game asset. 2d. High contrast. No shadows
Rosemon, vista desde arriba, calidad y detalles altos, busto grande sexy, cuerpo completo. In-Game asset. 2d. High contrast. No shadows
Togemon, visto desde arriba, calidad y detalles altamente épicos. In-Game asset. 2d. High contrast. No shadows
Lillymon, visto desde arriba, calidad y detalles altamente épicos. In-Game asset. 2d. High contrast. No shadows
Ikkakumon, visto desde arriba, calidad y detalles altamente épicos. In-Game asset. 2d. High contrast. No shadows
Angemon, visto desde arriba, calidad y detalles altamente épicos.. In-Game asset. 2d. High contrast. No shadows
Zudomon, visto desde arriba, calidad y detalles altamente épicos, fidelidad al concepto original In-Game asset. 2d. High contrast. No shadows
Sin fondo
hada digital. In-Game asset. 2d. High contrast. No shadows
MagnaAngemon, visto desde arriba, calidad y detalles altamente épicos.. In-Game asset. 2d. High contrast. No shadows
Seraphimon, visto desde arriba, calidad y detalles altamente épicos, Digimon. In-Game asset. 2d. High contrast. No shadows
Vikemon, visto desde arriba, calidad y detalles altamente épicos, Digimon. In-Game asset. 2d. High contrast. No shadows
Hielo oscuro, ocupar toda la imagen, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Hielo, ocupar toda la imagen, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Tierras con piedras, ocupar toda la imagen, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Aldeas, ocupar toda la imagen, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Alfombra roja, ocupar toda la imagen, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Metal con pequeños componentes de electrónica, ocupar toda la imagen, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Magma oscura, ocupar toda la imagen, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Rocas volcanicas, ocupar toda la imagen, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
Arena del desierto, ocupar toda la imagen, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
UlforceVeedramon, visto desde arriba. In-Game asset. 2d. High contrast. No shadows
mainMenuMusic
Music
forestMusic
Music
desertMusic
Music
glacierMusic
Music
villageMusic
Music
techLabMusic
Music
infernoMusic
Music
bossMusic
Music
finalBossMusic
Music
digimonAttack
Sound effect
enemyHit
Sound effect
enemyDestroyed
Sound effect
towerPlace
Sound effect
towerUpgrade
Sound effect
digivolveSound
Sound effect
splashAttack
Sound effect
poisonEffect
Sound effect
freezeEffect
Sound effect
burnEffect
Sound effect
buttonClick
Sound effect
coinCollect
Sound effect
bossWarning
Sound effect
criticalHealth
Sound effect
levelComplete
Sound effect
purchaseFail
Sound effect
purchaseSuccess
Sound effect
systemDamage
Sound effect
voiceSummon
Sound effect
waveStart
Sound effect
bossDeath
Sound effect