User prompt
elimina sub menus complejos
User prompt
simplifica el gamemenu
User prompt
procede con la simplificacion del tutorial
User prompt
refactorizar tweenmanager ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
simplificar sistemas de hechizos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
simplifica el game menu ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
si empieza con la consolidacion de clases similares ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'tween(readyAura, {' Line Number: 5950 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: '_typeof3 is not defined' in or related to this line: 'console.log('tween plugin available:', _typeof3(tween));' Line Number: 4925 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'setInterval is not a function' in or related to this line: 'setInterval(function () {' Line Number: 4154 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
centraliza el sistema de tween ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 5.1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 4.2 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 4.1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 3.2 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 3.1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 2.2 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementala ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz el 2.1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz el 1.2 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz el 1.1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
dame una lista de pasos con soluciones para el tween ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
si estructura la solucion del tween en pasos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'tween(readyAura, {' Line Number: 5612 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 3
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Coin = Container.expand(function () { var self = Container.call(this); var coinGraphics = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); self.bobOffset = Math.random() * Math.PI * 2; self.initialY = 0; self.update = function () { if (self.initialY === 0) { self.initialY = self.y; } // Only do bobbing animation and collection if not animating to coin counter if (!self.isAnimating) { // Bobbing animation self.y = self.initialY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 10; // Check collection by wizard if (wizard && self.intersects(wizard)) { self.collect(); } } }; self.collect = function () { LK.getSound('coinCollect').play(); LK.setScore(LK.getScore() + 5); coinCounter++; coinText.setText('Coins: ' + coinCounter); // Remove from coins array for (var i = coins.length - 1; i >= 0; i--) { if (coins[i] === self) { coins.splice(i, 1); break; } } // Destroy coin directly without object pool self.destroy(); }; return self; }); // Unified Enemy class that handles all enemy types var Enemy = Container.expand(function (type) { var self = Container.call(this); // Set enemy type and configure based on it self.enemyType = type || 'skeleton'; self.currentFrame = 1; self.animationTimer = 0; self.animationState = 'walking'; self.frozen = false; self.frozenTimer = 0; self.isDying = false; self.lastX = 0; self.speedTweenStarted = false; self.lastIntersecting = false; // Configure enemy based on type var config = { skeleton: { health: 100, speed: 3, damage: 20, animationSpeed: 15, assetPrefix: 'esqueleto', scale: 3.0, vibration: [100], tint: null }, ogre: { health: 200, speed: 2.5, damage: 30, animationSpeed: 18, assetPrefix: 'ogre', scale: 3.5, vibration: [200], tint: null }, knight: { health: 300, speed: 2, damage: 40, animationSpeed: 20, assetPrefix: 'knight', scale: 3.0, vibration: [150], tint: null }, miniBoss: { health: 3000, speed: 4, damage: 75, animationSpeed: 12, assetPrefix: 'ogre', scale: 5.0, vibration: [300, 100, 300], tint: 0x8B0000 } }; var enemyConfig = config[self.enemyType] || config.skeleton; self.health = enemyConfig.health; self.maxHealth = self.health; self.speed = enemyConfig.speed; self.damage = enemyConfig.damage; self.animationSpeed = enemyConfig.animationSpeed; // Create animation frames self.animationFrames = []; for (var i = 1; i <= 4; i++) { var frameGraphics = self.attachAsset(enemyConfig.assetPrefix + i, { anchorX: 0.5, anchorY: 1.0, scaleX: enemyConfig.scale, scaleY: enemyConfig.scale }); frameGraphics.visible = i === 1; if (enemyConfig.tint) { frameGraphics.tint = enemyConfig.tint; } self.animationFrames.push(frameGraphics); } // Create health bar for mini boss if (self.enemyType === 'miniBoss') { self.healthBarBg = game.addChild(LK.getAsset('miniBossHealthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 300, scaleX: 8, scaleY: 2 })); self.healthBarFg = game.addChild(LK.getAsset('miniBossHealthBar', { anchorX: 0.0, anchorY: 0.5, x: 2048 / 2 - 200, y: 300, scaleX: 8, scaleY: 2 })); self.healthText = new Text2('Boss Health: ' + self.health + '/' + self.maxHealth, { size: 80, fill: 0xFFFFFF, font: "monospace" }); self.healthText.anchor.set(0.5, 0.5); self.healthText.x = 2048 / 2; self.healthText.y = 250; game.addChild(self.healthText); } // Update health bar for mini boss self.updateHealthBar = function () { if (self.enemyType !== 'miniBoss' || !self.healthBarFg) return; var healthPercent = self.health / self.maxHealth; self.healthBarFg.scaleX = healthPercent * 8; self.healthText.setText('Boss Health: ' + self.health + '/' + self.maxHealth); if (healthPercent > 0.6) { self.healthBarFg.tint = 0xff0000; } else if (healthPercent > 0.3) { self.healthBarFg.tint = 0xff4500; } else { self.healthBarFg.tint = 0x8B0000; } }; // Optimized animation system self.updateAnimation = function () { self.animationTimer++; var frameSpeed = self.animationSpeed; if (self.animationState === 'attacking') frameSpeed = Math.floor(frameSpeed * 0.5);else if (self.animationState === 'dying') frameSpeed = Math.floor(frameSpeed * 1.3);else if (self.animationState === 'idle') frameSpeed = Math.floor(frameSpeed * 1.7); if (self.animationTimer >= frameSpeed) { self.animationTimer = 0; self.animationFrames[self.currentFrame - 1].visible = false; if (self.animationState === 'walking') { self.currentFrame++; if (self.currentFrame > 4) self.currentFrame = 1; } else if (self.animationState === 'attacking') { self.currentFrame++; if (self.currentFrame > 4) self.currentFrame = 2; } else if (self.animationState === 'dying') { if (self.currentFrame < 4) self.currentFrame++; } else if (self.animationState === 'idle') { self.currentFrame = self.currentFrame === 1 ? 2 : 1; } self.animationFrames[self.currentFrame - 1].visible = true; } }; self.update = function () { if (tutorial && tutorial.isActive || self.isDying) return; // Animation and speed progression self.updateAnimation(); if (!self.speedTweenStarted) { self.speedTweenStarted = true; tween(self, { speed: self.speed * 1.5 }, { duration: 10000, easing: tween.easeOut }); } // Handle frozen state if (self.frozen) { self.frozenTimer--; if (self.frozenTimer <= 0) self.frozen = false; return; } // Enhanced movement toward wizard if (wizard) { var dx = wizard.x - self.x; var dy = wizard.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { var speedMult = self.timeSlowed ? self.timeSlowAmount : 1.0; self.x += dx / distance * self.speed * speedMult; self.y += dy / distance * self.speed * speedMult; var flipScale = dx < 0 ? -enemyConfig.scale : enemyConfig.scale; for (var frameIdx = 0; frameIdx < self.animationFrames.length; frameIdx++) { self.animationFrames[frameIdx].scaleX = flipScale; } } else { self.y += self.speed; } } }; self.takeDamage = function (damage) { self.health -= damage; self.animationState = 'attacking'; // Create damage text var damageText = new Text2('-' + damage, { size: 120, fill: 0xFF4444, font: "monospace" }); damageText.anchor.set(0.5, 0.5); damageText.x = self.x + (Math.random() - 0.5) * 60; damageText.y = self.y - 40; game.addChild(damageText); var startY = damageText.y; tween(damageText, { y: startY - 120, alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { damageText.destroy(); } }); // Flash effect LK.effects.flashObject(self, 0xFF0000, 300); // Return to walking state after brief animation tween({}, {}, { duration: 300, onFinish: function onFinish() { if (self.animationState === 'attacking') self.animationState = 'walking'; } }); if (self.enemyType === 'miniBoss') { self.updateHealthBar(); } if (self.health <= 0) self.die(); }; self.down = function (x, y, obj) { if (tutorial && tutorial.isActive || self.isDying) return; if (wizard && wizard.attackCooldown > 0) { LK.effects.flashObject(self, 0xFF0000, 200); return; } // Vibration feedback if (typeof LK.vibrate === 'function') { LK.vibrate(enemyConfig.vibration); } selectedEnemy = self; LK.effects.flashObject(self, 0xFFFF00, 500); if (wizard && projectiles.length < 10) { var projectile = ProjectileFactory.createBasicAttack(wizard, self); projectile.targetEnemy = self; projectiles.push(projectile); LK.getSound('spellCast').play(); wizard.attackCooldown = 30; } }; self.die = function () { if (globalDeathHandler) { // Get appropriate array based on type var enemyArray = enemies; if (self.enemyType === 'ogre') enemyArray = ogres;else if (self.enemyType === 'knight') enemyArray = knights;else if (self.enemyType === 'miniBoss') enemyArray = miniBosses; globalDeathHandler.executeEnemyDeath(self, enemyArray); } }; return self; }); // Unified EnemyManager for streamlined enemy lifecycle management var EnemyManager = Container.expand(function () { var self = Container.call(this); // Use global arrays directly - no separate collections needed // Unified enemy configuration templates self.enemyTemplates = { skeleton: { baseHealth: 100, baseSpeed: 3, damage: 20, coinReward: 1 }, ogre: { baseHealth: 200, baseSpeed: 2.5, damage: 30, coinReward: 1 }, knight: { baseHealth: 300, baseSpeed: 2, damage: 40, coinReward: 1 }, miniBoss: { baseHealth: 3000, baseSpeed: 4, damage: 75, coinReward: 5 } }; // Streamlined enemy creation with unified Enemy class self.createEnemy = function (type, difficulty, level, pathOverride) { var template = self.enemyTemplates[type]; if (!template) return null; // Create enemy using unified Enemy class var enemy = new Enemy(type); // Apply difficulty scaling enemy.speed *= 1 + level * 0.3; enemy.pathIndex = pathOverride || self.selectOptimalPath(type); self.positionEnemy(enemy, type); self.applyDifficultyModifications(enemy, type, difficulty); return enemy; }; // Unified enemy positioning system self.positionEnemy = function (enemy, type) { // Use direct spawn position values var spawnPositions = [{ x: 2048 / 2, y: -100 }, { x: 2048 + 50, y: -50 }, { x: -50, y: -50 }, { x: -100, y: 2732 / 2 + 400 }, { x: 2048 + 100, y: 2732 / 2 + 400 }]; var spawnPos = spawnPositions[enemy.pathIndex]; if (spawnPos) { enemy.x = Math.max(50, Math.min(1998, spawnPos.x)); enemy.y = type === 'miniBoss' ? -200 : Math.max(-200, Math.min(2732 + 100, spawnPos.y)); enemy.lastX = enemy.x; } }; // Streamlined difficulty modifications self.applyDifficultyModifications = function (enemy, type, difficulty) { if (type === 'miniBoss') { enemy.updateHealthBar(); LK.effects.flashScreen(0x8B0000, 1000); } else if (type === 'skeleton' && Math.random() < 0.3) { LK.getSound('enemyGrowl').play(); } // Elite enemy system for hard difficulty if (difficulty === 'DIFICIL' && enemyKillCounter >= 20 && Math.random() < 0.15) { self.createEliteEnemy(enemy, type); } }; // Unified elite enemy creation self.createEliteEnemy = function (enemy, type) { var eliteMultipliers = { health: 1.8, speed: 1.3, color: 0xFF6600 }; if (type === 'ogre') eliteMultipliers.color = 0xFF0000; if (type === 'knight') eliteMultipliers.color = 0xFFD700; if (type === 'miniBoss') eliteMultipliers.color = 0x8B0000; enemy.health *= eliteMultipliers.health; enemy.speed *= eliteMultipliers.speed; enemy.maxHealth = enemy.health; enemy.isElite = true; for (var i = 0; i < enemy.animationFrames.length; i++) { enemy.animationFrames[i].tint = eliteMultipliers.color; } }; // Optimized path selection system self.selectOptimalPath = function (type) { if (type === 'skeleton' && enemyKillCounter < 5) return 0; if (type === 'miniBoss') return 0; var available = []; for (var i = 0; i < 5; i++) { if (pathConsecutiveSpawns[i] < 2) available.push(i); } if (available.length === 0) { for (var i = 0; i < 5; i++) pathConsecutiveSpawns[i] = 0; available = [0, 1, 2, 3, 4]; } return available[Math.floor(Math.random() * available.length)]; }; // Unified enemy collection management self.addToCollection = function (enemy, type) { if (type === 'skeleton') enemies.push(enemy);else if (type === 'ogre') ogres.push(enemy);else if (type === 'knight') knights.push(enemy);else if (type === 'miniBoss') miniBosses.push(enemy); }; // Streamlined enemy removal system self.removeFromCollection = function (enemy, type) { var collection = []; if (type === 'skeleton') collection = enemies;else if (type === 'ogre') collection = ogres;else if (type === 'knight') collection = knights;else if (type === 'miniBoss') collection = miniBosses; if (collection.length > 0) { for (var i = collection.length - 1; i >= 0; i--) { if (collection[i] === enemy) { collection.splice(i, 1); break; } } } }; // Get all enemies as unified array self.getAllEnemies = function () { var allEnemies = []; allEnemies = allEnemies.concat(enemies); allEnemies = allEnemies.concat(ogres); allEnemies = allEnemies.concat(knights); allEnemies = allEnemies.concat(miniBosses); return allEnemies; }; return self; }); var EnergyOrb = Container.expand(function () { var self = Container.call(this); // Create energy sphere visual var sphereGraphics = self.attachAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // Add glowing effect sphereGraphics.alpha = 0.8; self.attackTimer = 0; self.attackInterval = 180; // 3 seconds at 60fps self.orbitalAngle = 0; self.orbitalRadius = 120; self.update = function () { // Pause energy orb when tutorial is active if (tutorial && tutorial.isActive) { return; } // Keep sphere at wizard's position (stationary relative to wizard) if (wizard) { self.x = wizard.x + 140; // Position further to the right side of wizard self.y = wizard.y - 20; // Position slightly lower relative to wizard } // Pulsing glow effect var pulse = 1 + Math.sin(LK.ticks * 0.2) * 0.3; sphereGraphics.scaleX = 1.5 * pulse; sphereGraphics.scaleY = 1.5 * pulse; // Attack timer - keep original interval regardless of upgrades self.attackTimer++; if (self.attackTimer >= 180) { // Fixed at 3 seconds self.attackTimer = 0; self.attackClosestEnemy(); } }; self.attackClosestEnemy = function () { var closestEnemy = null; var closestDistance = Infinity; // Check all enemy types for closest one var allEnemies = collisionArrayPool.getAllEnemies(); for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[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; } } // Attack closest enemy if found if (closestEnemy) { // Create energy beam projectile using unified factory var energyBeam = ProjectileFactory.createProjectile('energyBeam', self.x, self.y, closestEnemy.x, closestEnemy.y, { targetEnemy: closestEnemy }); // Flash effect on sphere when attacking tween(sphereGraphics, { scaleX: 2.5, scaleY: 2.5, alpha: 1.0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(sphereGraphics, { scaleX: 1.5, scaleY: 1.5, alpha: 0.8 }, { duration: 200, easing: tween.easeIn }); } }); LK.getSound('spellCast').play(); } }; return self; }); var GameMenu = Container.expand(function () { var self = Container.call(this); // Simple spell deck - load from storage or use defaults var currentDeck = storage.spellDeck || ['fireball', 'heal', 'lightning']; storage.spellDeck = currentDeck.slice(); // Menu background image instead of cave background var menuBg = self.attachAsset('menuBackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 25.0, scaleY: 35.0 }); menuBg.alpha = 1.0; // Title text var titleText = new Text2('WIZARD DEFENDER', { size: 150, fill: 0xFFD700, font: "monospace" }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 800; self.addChild(titleText); // Instructions text var instructionsText = new Text2('TAP ENEMIES TO ATTACK\nDEFEND YOUR CASTLE!', { size: 80, fill: 0xFFFFFF, font: "monospace" }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 2048 / 2; instructionsText.y = 1200; self.addChild(instructionsText); // Start button var startButton = self.attachAsset('wizard1', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1500, scaleX: 2, scaleY: 2 }); var startButtonText = new Text2('START GAME', { size: 100, fill: 0x000000, font: "monospace" }); startButtonText.anchor.set(0.5, 0.5); startButtonText.x = 2048 / 2; startButtonText.y = 1600; self.addChild(startButtonText); // Configuration button var configButton = self.attachAsset('pathSelector', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1800, scaleX: 4, scaleY: 2 }); configButton.tint = 0x4169E1; var configButtonText = new Text2('CONFIGURACION', { size: 80, fill: 0xFFFFFF, font: "monospace" }); configButtonText.anchor.set(0.5, 0.5); configButtonText.x = 2048 / 2; configButtonText.y = 1800; self.addChild(configButtonText); // Shop button var shopButton = self.attachAsset('pathSelector', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1950, scaleX: 4, scaleY: 2 }); shopButton.tint = 0xFF6B35; var shopButtonText = new Text2('TIENDA', { size: 80, fill: 0xFFFFFF, font: "monospace" }); shopButtonText.anchor.set(0.5, 0.5); shopButtonText.x = 2048 / 2; shopButtonText.y = 1950; self.addChild(shopButtonText); // Deck button var deckButton = self.attachAsset('pathSelector', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2150, scaleX: 4, scaleY: 2 }); deckButton.tint = 0x8A2BE2; var deckButtonText = new Text2('DECK HECHIZOS', { size: 80, fill: 0xFFFFFF, font: "monospace" }); deckButtonText.anchor.set(0.5, 0.5); deckButtonText.x = 2048 / 2; deckButtonText.y = 2150; self.addChild(deckButtonText); // Tutorial button (only show if tutorial was completed before) if (storage.tutorialCompleted) { var tutorialButton = self.attachAsset('pathSelector', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2350, scaleX: 4, scaleY: 2 }); tutorialButton.tint = 0x2E8B57; var tutorialButtonText = new Text2('TUTORIAL', { size: 80, fill: 0xFFFFFF, font: "monospace" }); tutorialButtonText.anchor.set(0.5, 0.5); tutorialButtonText.x = 2048 / 2; tutorialButtonText.y = 2350; self.addChild(tutorialButtonText); } // Button interaction self.down = function (x, y, obj) { // Handle configuration menu interactions if (self.configMode) { // Music volume adjustment if (y >= 1150 && y <= 1250) { var currentMusicVolume = storage.musicVolume || 0.7; var newMusicVolume = Math.min(1.0, currentMusicVolume + 0.1); if (newMusicVolume > 1.0) newMusicVolume = 0.0; storage.musicVolume = newMusicVolume; self.musicVolumeText.setText('VOLUMEN MUSICA: ' + Math.round(newMusicVolume * 100) + '%'); return; } // Sound volume adjustment if (y >= 1350 && y <= 1450) { var currentSoundVolume = storage.soundVolume || 1.0; var newSoundVolume = Math.min(1.0, currentSoundVolume + 0.1); if (newSoundVolume > 1.0) newSoundVolume = 0.0; storage.soundVolume = newSoundVolume; self.soundVolumeText.setText('VOLUMEN SONIDO: ' + Math.round(newSoundVolume * 100) + '%'); return; } // Difficulty adjustment if (y >= 1550 && y <= 1650) { var currentDifficulty = storage.difficulty || 'NORMAL'; var difficulties = ['FACIL', 'NORMAL', 'DIFICIL']; var currentIndex = difficulties.indexOf(currentDifficulty); var newIndex = (currentIndex + 1) % difficulties.length; var newDifficulty = difficulties[newIndex]; storage.difficulty = newDifficulty; self.difficultyText.setText('DIFICULTAD: ' + newDifficulty); return; } // Back button if (y >= 1950 && y <= 2050) { self.hideConfigMenu(); return; } // Block all other interactions when config menu is active return; } // Handle deck menu interactions if (self.deckMode) { // Check deck card clicks for spell casting (top area) or removal (bottom area) for (var i = 0; i < self.deckElements.length; i++) { var element = self.deckElements[i]; if (element.spellId && element.isDeckCard) { var cardX = element.x; var cardY = element.y; if (x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225) { // Enhanced immediate touch feedback with scaling and ripple effect tween(element, { scaleX: element.scaleX * 0.95, scaleY: element.scaleY * 0.95 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(element, { scaleX: element.scaleX / 0.95, scaleY: element.scaleY / 0.95 }, { duration: 120, easing: tween.bounceOut }); } }); // Create ripple effect at touch point var ripple = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: 0.3, scaleY: 0.3 })); ripple.tint = 0x00FFFF; ripple.alpha = 0.8; ripple.zIndex = 2000; tween(ripple, { scaleX: 3.0, scaleY: 3.0, alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { if (ripple.parent) ripple.destroy(); } }); // Top area of card (above center) - cast spell if (y <= cardY) { // Enhanced validation with visual feedback var canCast = activeSpellDeck && _canCastSpell(element.spellId); var spell = activeSpellDeck ? activeSpellDeck.getSpell(element.spellId) : null; // Check mana requirement with visual feedback if (!canCast && spell && currentMana < spell.manaCost) { // Enhanced mana insufficient feedback with shake and urgent pulsing var originalX = element.x; var shakeIntensity = 8; // Create urgent shake effect tween(element, { x: originalX - shakeIntensity, scaleX: element.scaleX * 1.3, scaleY: element.scaleY * 1.3, tint: 0xFF0000, alpha: 0.8 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(element, { x: originalX + shakeIntensity }, { duration: 80, easing: tween.easeInOut, onFinish: function onFinish() { tween(element, { x: originalX, scaleX: element.scaleX / 1.3, scaleY: element.scaleY / 1.3, tint: 0xFF6666, alpha: 0.6 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Return to normal with fade tween(element, { tint: self.spellDeck.getRarityColor(spell.rarity), alpha: 0.9 }, { duration: 400, easing: tween.easeIn }); } }); } }); } }); // Create warning pulse ring around card var warningRing = game.addChild(LK.getAsset('spellCardBg', { anchorX: 0.5, anchorY: 0.5, x: element.x, y: element.y, scaleX: 4.5, scaleY: 5.5 })); warningRing.tint = 0xFF0000; warningRing.alpha = 0.8; warningRing.zIndex = 1999; tween(warningRing, { scaleX: 6.0, scaleY: 7.0, alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { if (warningRing.parent) warningRing.destroy(); } }); // Show insufficient mana message var manaError = new Text2('MANA INSUFICIENTE!\nRequiere: ' + spell.manaCost + ' Actual: ' + Math.floor(currentMana), { size: 50, fill: 0xFF4444, font: "monospace" }); manaError.anchor.set(0.5, 0.5); manaError.x = 2048 / 2; manaError.y = 2200; self.addChild(manaError); tween(manaError, { alpha: 0, y: manaError.y - 150, scaleX: 1.5, scaleY: 1.5 }, { duration: 2500, easing: tween.easeOut, onFinish: function onFinish() { if (manaError.parent) manaError.destroy(); } }); return; } // Check cooldown with visual feedback var currentTick = LK.ticks || 0; if (cardCooldowns[element.spellId] && currentTick < cardCooldowns[element.spellId]) { // Enhanced cooldown feedback with multi-stage bounce and rotation tween(element, { scaleX: element.scaleX * 0.7, scaleY: element.scaleY * 1.2, tint: 0x4169E1, rotation: -Math.PI / 8 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(element, { scaleX: element.scaleX / 0.7 * 1.1, scaleY: element.scaleY / 1.2 * 0.9, rotation: Math.PI / 8 }, { duration: 140, easing: tween.easeInOut, onFinish: function onFinish() { tween(element, { scaleX: element.scaleX / 1.1, scaleY: element.scaleY / 0.9, tint: self.spellDeck.getRarityColor(spell.rarity), rotation: 0 }, { duration: 200, easing: tween.bounceOut }); } }); } }); // Create cooldown wave effect var cooldownWave = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: element.x, y: element.y, scaleX: 0.5, scaleY: 0.5 })); cooldownWave.tint = 0x4169E1; cooldownWave.alpha = 0.7; cooldownWave.zIndex = 2000; tween(cooldownWave, { scaleX: 4.0, scaleY: 4.0, alpha: 0, rotation: Math.PI * 2 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (cooldownWave.parent) cooldownWave.destroy(); } }); // Show cooldown message var timeRemaining = Math.ceil((cardCooldowns[element.spellId] - currentTick) / 60); var cooldownError = new Text2('EN RECARGA!\nEspera: ' + timeRemaining + ' segundos', { size: 50, fill: 0x4169E1, font: "monospace" }); cooldownError.anchor.set(0.5, 0.5); cooldownError.x = 2048 / 2; cooldownError.y = 2200; self.addChild(cooldownError); tween(cooldownError, { alpha: 0, y: cooldownError.y - 120, rotation: Math.PI * 0.1 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { if (cooldownError.parent) cooldownError.destroy(); } }); return; } // Try to cast the spell if (canCast) { var spell = activeSpellDeck.getSpell(element.spellId); if (spell) { var targetX = wizard.x; var targetY = wizard.y - 100; // Find nearest enemy for targeted spells if (spell.id === 'fireball' || spell.id === 'iceShard' || spell.id === 'lightning') { var allEnemies = collisionArrayPool.getAllEnemies(); var nearestEnemy = null; var nearestDistance = Infinity; for (var e = 0; e < allEnemies.length; e++) { var enemy = allEnemies[e]; var dx = enemy.x - wizard.x; var dy = enemy.y - wizard.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestEnemy = enemy; } } if (nearestEnemy) { targetX = nearestEnemy.x; targetY = nearestEnemy.y; } } // Enhanced successful casting animation with card flip effect tween(element, { scaleX: 0, scaleY: element.scaleY * 1.4, tint: 0x00FF00, alpha: 1.0, rotation: Math.PI / 4 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Flip back with success color tween(element, { scaleX: element.scaleX * 1.2, scaleY: element.scaleY / 1.4, tint: 0xFFD700, alpha: 1.0, rotation: 0 }, { duration: 250, easing: tween.easeOut, onFinish: function onFinish() { // Return to normal state tween(element, { scaleX: element.scaleX / 1.2, tint: self.spellDeck.getRarityColor(spell.rarity), alpha: 0.9 }, { duration: 300, easing: tween.elasticOut }); } }); } }); // Create success burst particles for (var p = 0; p < 8; p++) { var burstParticle = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: element.x, y: element.y, scaleX: 0.6, scaleY: 0.6 })); burstParticle.tint = 0x00FF00; burstParticle.alpha = 0.9; burstParticle.zIndex = 2001; var angle = p * Math.PI * 2 / 8; var distance = 150 + Math.random() * 50; tween(burstParticle, { x: element.x + Math.cos(angle) * distance, y: element.y + Math.sin(angle) * distance, alpha: 0, scaleX: 1.2, scaleY: 1.2, rotation: Math.PI * 3 }, { duration: 800 + p * 100, easing: tween.easeOut, onFinish: function onFinish() { if (burstParticle.parent) burstParticle.destroy(); } }); } // Magical sparkle effects removed for simplification // Cast the spell using specific functions if (element.spellId === 'fireball') { castFireball(); } else if (element.spellId === 'lightning') { castLightning(); } else if (element.spellId === 'heal') { castHeal(); } else { console.log('Unknown spell ID in deck menu:', element.spellId); castFireball(); // Default to fireball } // Show cast message var castText = new Text2('HECHIZO LANZADO!', { size: 60, fill: 0x00FF00, font: "monospace" }); castText.anchor.set(0.5, 0.5); castText.x = 2048 / 2; castText.y = 2200; self.addChild(castText); // Animate and remove message tween(castText, { alpha: 0, y: castText.y - 100 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { if (castText.parent) castText.destroy(); } }); } } else { LK.effects.flashObject(element, 0xFF0000, 200); // Show "cannot cast" message var errorText = new Text2('NO SE PUEDE LANZAR', { size: 50, fill: 0xFF6666, font: "monospace" }); errorText.anchor.set(0.5, 0.5); errorText.x = 2048 / 2; errorText.y = 2200; self.addChild(errorText); // Animate and remove message tween(errorText, { alpha: 0, y: errorText.y - 100 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { if (errorText.parent) errorText.destroy(); } }); } } else { // Bottom area of card (below center) - remove from deck // Visual feedback before removal LK.effects.flashObject(element, 0xFF0000, 300); // Remove from deck if (self.spellDeck.removeFromDeck(element.spellId)) { self.refreshDeckDisplay(); LK.effects.flashScreen(0xFF8800, 200); // Show removal message var removeText = new Text2('HECHIZO REMOVIDO', { size: 60, fill: 0xFF6666, font: "monospace" }); removeText.anchor.set(0.5, 0.5); removeText.x = 2048 / 2; removeText.y = 2200; self.addChild(removeText); // Animate and remove message tween(removeText, { alpha: 0, y: removeText.y - 100 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { if (removeText.parent) removeText.destroy(); } }); } else { LK.effects.flashScreen(0xFF0000, 200); } } return; } } } // Check available card clicks with better hit detection for (var i = 0; i < self.availableElements.length; i++) { var element = self.availableElements[i]; if (element.spellId && !element.isDeckCard) { var cardX = element.x; var cardY = element.y; if (x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225) { // Visual feedback before addition LK.effects.flashObject(element, 0x00FF00, 300); // Add to deck if (self.spellDeck.addToDeck(element.spellId)) { self.refreshDeckDisplay(); LK.effects.flashScreen(0x00FF00, 200); // Show addition message var addText = new Text2('HECHIZO AÑADIDO', { size: 60, fill: 0x66FF66, font: "monospace" }); addText.anchor.set(0.5, 0.5); addText.x = 2048 / 2; addText.y = 2200; self.addChild(addText); // Animate and remove message tween(addText, { alpha: 0, y: addText.y - 100 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { if (addText.parent) addText.destroy(); } }); } else { LK.effects.flashScreen(0xFF0000, 200); // Show error message var errorText = new Text2('DECK LLENO (MAX 5)', { size: 50, fill: 0xFF6666, font: "monospace" }); errorText.anchor.set(0.5, 0.5); errorText.x = 2048 / 2; errorText.y = 2200; self.addChild(errorText); // Animate and remove message tween(errorText, { alpha: 0, y: errorText.y - 100 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { if (errorText.parent) errorText.destroy(); } }); } return; } } } // Deck back button if (y >= 2350 && y <= 2650) { self.hideDeck(); } // Block all other interactions when deck menu is active return; } // Handle shop menu interactions if (self.shopMode) { // Shop item purchase buttons for (var i = 0; i < 3; i++) { var itemY = 1100 + i * 200; if (y >= itemY - 50 && y <= itemY + 50 && x >= 2048 / 2 + 200 && x <= 2048 / 2 + 400) { // Purchase item logic var shopItems = [{ name: 'POCION SALUD', cost: 10 }, { name: 'ESCUDO MAGICO', cost: 15 }, { name: 'ESPADA MALDITA', cost: 20 }]; var item = shopItems[i]; if (coinCounter >= item.cost) { coinCounter -= item.cost; // Apply item effect based on type if (i === 0) { // Health potion if (wizard) { wizard.health = Math.min(wizard.health + 50, wizard.maxHealth); updateHealthBar(); } } else if (i === 1) { // Magic shield if (wizard) { wizard.shieldActive = true; wizard.maxShieldHits = 3; wizard.currentShieldHits = 0; } } else if (i === 2) { // Cursed sword - temporary damage boost if (wizard) { wizard.tempDamageBoost = true; wizard.tempDamageTimer = 1800; // 30 seconds at 60fps } } LK.effects.flashScreen(0x00FF00, 300); } else { LK.effects.flashScreen(0xFF0000, 300); } return; } } // Shop back button if (y >= 1950 && y <= 2050) { self.hideShop(); return; } // Block all other interactions when shop menu is active return; } // Check if configuration button was clicked if (y >= 1700 && y <= 1900 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) { // Show configuration menu self.showConfigMenu(); } else if (y >= 1850 && y <= 2050 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) { // Show shop menu self.showShop(); } else if (y >= 2050 && y <= 2250 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) { // Show deck menu self.showDeck(); } else if (storage.tutorialCompleted && y >= 2250 && y <= 2450 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) { // Show tutorial again if (tutorial) { self.visible = false; tutorial.startTutorial(); } } else if (y >= 1450 && y <= 1650) { // Start the game by hiding menu self.startGame(); } }; self.showConfigMenu = function () { if (!self.configOverlay) { self.configOverlay = self.createMenuOverlay(0x000000); self.configTitle = self.createMenuText('CONFIGURACION', 2048 / 2, 800, 120, 0xFFD700); self.musicVolumeText = self.createMenuText('VOLUMEN MUSICA: ' + Math.round((storage.musicVolume || 0.7) * 100) + '%', 2048 / 2, 1200, 80, 0xFFFFFF); self.soundVolumeText = self.createMenuText('VOLUMEN SONIDO: ' + Math.round((storage.soundVolume || 1.0) * 100) + '%', 2048 / 2, 1400, 80, 0xFFFFFF); self.difficultyText = self.createMenuText('DIFICULTAD: ' + (storage.difficulty || 'NORMAL'), 2048 / 2, 1600, 80, 0xFFFFFF); self.backButton = self.createMenuButton('coin', 2048 / 2, 2000, 0x00FF00); self.backText = self.createMenuText('VOLVER', 2048 / 2, 2000, 80, 0xFFFFFF); } self.configOverlay.visible = true; self.configMode = true; }; self.hideConfigMenu = function () { if (self.configOverlay) { self.configOverlay.destroy(); self.configOverlay = null; } // Remove all configuration text elements if (self.musicVolumeText) { self.musicVolumeText.destroy(); self.musicVolumeText = null; } if (self.soundVolumeText) { self.soundVolumeText.destroy(); self.soundVolumeText = null; } if (self.difficultyText) { self.difficultyText.destroy(); self.difficultyText = null; } // Remove back button elements if (self.backButton) { self.backButton.destroy(); self.backButton = null; } if (self.backText) { self.backText.destroy(); self.backText = null; } // Remove configuration title if (self.configTitle) { self.configTitle.destroy(); self.configTitle = null; } // Remove all configuration children that were added for (var i = self.children.length - 1; i >= 0; i--) { var child = self.children[i]; // Remove config-related elements (title, texts, buttons created in showConfigMenu) if (child.setText && child.text && (child.text.includes('CONFIGURACION') || child.text.includes('VOLUMEN') || child.text.includes('DIFICULTAD') || child.text.includes('VOLVER'))) { child.destroy(); } } self.configMode = false; // Reset to show main menu elements self.visible = true; }; self.showShop = function () { if (!self.shopOverlay) { self.shopOverlay = self.createMenuOverlay(0x000033); self.shopTitle = self.createMenuText('TIENDA', 2048 / 2, 800, 120, 0xFFD700); var shopItems = self.getShopItemsData(); self.initializeShopArrays(); self.createShopItems(shopItems); self.shopBackButton = self.createMenuButton('coin', 2048 / 2, 2000, 0x00FF00); self.shopBackText = self.createMenuText('VOLVER', 2048 / 2, 2000, 80, 0xFFFFFF); } self.shopOverlay.visible = true; self.shopMode = true; }; self.hideShop = function () { if (self.shopOverlay) { self.shopOverlay.destroy(); self.shopOverlay = null; } // Remove shop title if (self.shopTitle) { self.shopTitle.destroy(); self.shopTitle = null; } // Remove shop back button elements if (self.shopBackButton) { self.shopBackButton.destroy(); self.shopBackButton = null; } if (self.shopBackText) { self.shopBackText.destroy(); self.shopBackText = null; } // Remove all shop icons if (self.shopIcons) { for (var i = 0; i < self.shopIcons.length; i++) { if (self.shopIcons[i]) { self.shopIcons[i].destroy(); } } self.shopIcons = []; } // Remove all shop texts if (self.shopTexts) { for (var i = 0; i < self.shopTexts.length; i++) { if (self.shopTexts[i]) { self.shopTexts[i].destroy(); } } self.shopTexts = []; } // Remove all shop buy buttons if (self.shopBuyButtons) { for (var i = 0; i < self.shopBuyButtons.length; i++) { if (self.shopBuyButtons[i]) { self.shopBuyButtons[i].destroy(); } } self.shopBuyButtons = []; } // Remove all shop buy texts if (self.shopBuyTexts) { for (var i = 0; i < self.shopBuyTexts.length; i++) { if (self.shopBuyTexts[i]) { self.shopBuyTexts[i].destroy(); } } self.shopBuyTexts = []; } // Remove all shop children that were added for (var i = self.children.length - 1; i >= 0; i--) { var child = self.children[i]; // Remove shop-related elements if (child.setText && child.text && (child.text.includes('TIENDA') || child.text.includes('POCION') || child.text.includes('ESCUDO') || child.text.includes('ESPADA') || child.text.includes('COMPRAR'))) { child.destroy(); } } self.shopMode = false; // Reset to show main menu elements self.visible = true; }; self.showDeck = function () { if (!self.deckOverlay) { // Ensure spellDeck exists before creating overlay if (!self.spellDeck) self.spellDeck = new SpellDeck(); self.deckOverlay = self.createMenuOverlay(0x1a0a2e); self.deckTitle = self.createMenuText('DECK DE HECHIZOS', 2048 / 2, 600, 100, 0xFFD700); self.initializeDeckArrays(); self.refreshDeckDisplay(); self.deckBackButton = self.createMenuButton('coin', 2048 / 2, 2500, 0x00FF00); self.deckBackText = self.createMenuText('VOLVER', 2048 / 2, 2500, 80, 0xFFFFFF); } self.deckOverlay.visible = true; self.deckMode = true; self.refreshDeckDisplay(); }; self.initializeDeckArrays = function () { if (!self.deckElements) self.deckElements = []; if (!self.availableElements) self.availableElements = []; }; self.refreshDeckDisplay = function () { if (!self.spellDeck) { self.spellDeck = new SpellDeck(); } // Clear existing deck elements for (var i = 0; i < self.deckElements.length; i++) { if (self.deckElements[i] && self.deckElements[i].parent) { self.deckElements[i].destroy(); } } self.deckElements = []; // Clear existing available elements for (var i = 0; i < self.availableElements.length; i++) { if (self.availableElements[i] && self.availableElements[i].parent) { self.availableElements[i].destroy(); } } self.availableElements = []; // Add helpful instructions at the top var instructionText = new Text2('TOCA CARTAS PARA AÑADIR/QUITAR DEL DECK', { size: 50, fill: 0x00FF00, font: "monospace" }); instructionText.anchor.set(0.5, 0.5); instructionText.x = 2048 / 2; instructionText.y = 700; self.addChild(instructionText); self.deckElements.push(instructionText); // Display current deck (top section) var deckLabel = new Text2('MI DECK ACTUAL (' + self.spellDeck.currentDeck.length + '/5):', { size: 70, fill: 0xFFD700, font: "monospace" }); deckLabel.anchor.set(0.5, 0.5); deckLabel.x = 2048 / 2; deckLabel.y = 800; self.addChild(deckLabel); self.deckElements.push(deckLabel); // Add deck instruction var deckInstruction = new Text2('TOCA PARA QUITAR', { size: 40, fill: 0xFF6666, font: "monospace" }); deckInstruction.anchor.set(0.5, 0.5); deckInstruction.x = 2048 / 2; deckInstruction.y = 850; self.addChild(deckInstruction); self.deckElements.push(deckInstruction); // Display deck cards with better spacing for (var i = 0; i < 5; i++) { var cardX = 200 + i * 350; var cardY = 1050; if (i < self.spellDeck.currentDeck.length) { var spell = self.spellDeck.getSpell(self.spellDeck.currentDeck[i]); if (spell) { // Card background with dynamic state visualization var cardBg = self.attachAsset('spellCard', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 3.5, scaleY: 4.5 }); // Check card status for visual feedback var canCast = _canCastSpell(spell.id); var currentTick = LK.ticks || 0; var isOnCooldown = cardCooldowns[spell.id] && currentTick < cardCooldowns[spell.id]; var hasEnoughMana = currentMana >= (spell.manaCost || 0); // Set card appearance based on status if (isOnCooldown) { // Cooldown state - blue tint with reduced opacity cardBg.tint = 0x4169E1; cardBg.alpha = 0.5; // Add pulsing cooldown effect tween(cardBg, { alpha: 0.3 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(cardBg, { alpha: 0.5 }, { duration: 800, easing: tween.easeInOut }); } }); } else if (!hasEnoughMana) { // Enhanced insufficient mana feedback - red tint with warning overlay cardBg.tint = 0xFF4444; cardBg.alpha = 0.6; // Create mana warning overlay var manaWarning = self.attachAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: cardX + 60, y: cardY - 80, scaleX: 1.0, scaleY: 1.0 }); manaWarning.tint = 0xFF0000; manaWarning.alpha = 0.8; self.deckElements.push(manaWarning); // Pulsing warning animation for insufficient mana tween(manaWarning, { scaleX: 1.5, scaleY: 1.5, alpha: 0.4 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { tween(manaWarning, { scaleX: 1.0, scaleY: 1.0, alpha: 0.8 }, { duration: 600, easing: tween.easeInOut }); } }); // Add "X" symbol overlay for insufficient mana var manaBlock = new Text2('✗', { size: 45, fill: 0xFF0000, font: "monospace" }); manaBlock.anchor.set(0.5, 0.5); manaBlock.x = cardX + 60; manaBlock.y = cardY - 80; self.addChild(manaBlock); self.deckElements.push(manaBlock); } else if (canCast) { // Enhanced ready to cast - normal appearance with magical glow effects cardBg.tint = self.spellDeck.getRarityColor(spell.rarity); cardBg.alpha = 0.9; // Create magical glow border around ready cards var glowBorder = self.attachAsset('spellCardBg', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 4.2, scaleY: 5.2 }); glowBorder.tint = 0x00FF00; glowBorder.alpha = 0.4; self.deckElements.push(glowBorder); // Sparkle effects removed for simplification // Simplified ready-to-cast breathing effect tween(cardBg, { alpha: 1.0, scaleX: 3.6 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(cardBg, { alpha: 0.9, scaleX: 3.5 }, { duration: 800, easing: tween.easeInOut }); } }); // Simplified preview mode removed for performance // Simple glow border breathing effect tween(glowBorder, { alpha: 0.5 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(glowBorder, { alpha: 0.4 }, { duration: 1000, easing: tween.easeInOut }); } }); } else { // Default disabled state cardBg.tint = 0x666666; cardBg.alpha = 0.4; } cardBg.spellId = spell.id; cardBg.isDeckCard = true; self.deckElements.push(cardBg); // Add border glow var glowBorder = self.attachAsset('spellCardBg', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 4, scaleY: 5 }); glowBorder.tint = 0x00FF00; glowBorder.alpha = 0.3; self.deckElements.push(glowBorder); // Card name var cardName = new Text2(spell.name, { size: 35, fill: 0xFFFFFF, font: "monospace" }); cardName.anchor.set(0.5, 0.5); cardName.x = cardX; cardName.y = cardY - 60; self.addChild(cardName); self.deckElements.push(cardName); // Card description var description = spell.description || 'Hechizo magico'; var cardDesc = new Text2(description, { size: 25, fill: 0xCCCCCC, font: "monospace", wordWrap: true, wordWrapWidth: 250 }); cardDesc.anchor.set(0.5, 0.5); cardDesc.x = cardX; cardDesc.y = cardY + 20; self.addChild(cardDesc); self.deckElements.push(cardDesc); // Enhanced card stats with status indicators var statsText = ''; if (spell.damage) statsText += 'Daño: ' + spell.damage + '\n'; if (spell.healing) statsText += 'Cura: ' + spell.healing + '\n'; if (spell.manaCost) { var manaColor = currentMana >= spell.manaCost ? '✓' : '✗'; statsText += 'Mana: ' + spell.manaCost + ' ' + manaColor; } if (statsText) { var cardStats = new Text2(statsText, { size: 20, fill: hasEnoughMana ? 0xFFD700 : 0xFF4444, font: "monospace" }); cardStats.anchor.set(0.5, 0.5); cardStats.x = cardX; cardStats.y = cardY + 80; self.addChild(cardStats); self.deckElements.push(cardStats); } // Add enhanced cooldown indicator if on cooldown if (isOnCooldown) { var timeRemaining = Math.ceil((cardCooldowns[spell.id] - currentTick) / 60); // Create cooldown overlay that covers the entire card var cooldownOverlay = self.attachAsset('cooldownOverlay', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 3.5, scaleY: 4.5 }); cooldownOverlay.alpha = 0.8; cooldownOverlay.tint = 0x000000; self.deckElements.push(cooldownOverlay); // Create circular cooldown progress indicator var cooldownProgress = self.attachAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 2.0, scaleY: 2.0 }); cooldownProgress.tint = 0x4169E1; cooldownProgress.alpha = 0.7; self.deckElements.push(cooldownProgress); // Animate progress indicator rotation tween(cooldownProgress, { rotation: Math.PI * 2 }, { duration: 1000, easing: tween.linear, onFinish: function onFinish() { // Continuous rotation while on cooldown if (cardCooldowns[spell.id] && LK.ticks < cardCooldowns[spell.id]) { cooldownProgress.rotation = 0; tween(cooldownProgress, { rotation: Math.PI * 2 }, { duration: 1000, easing: tween.linear }); } } }); var cooldownIndicator = new Text2('RECARGA: ' + timeRemaining + 's', { size: 28, fill: 0xFFFFFF, font: "monospace" }); cooldownIndicator.anchor.set(0.5, 0.5); cooldownIndicator.x = cardX; cooldownIndicator.y = cardY + 110; self.addChild(cooldownIndicator); self.deckElements.push(cooldownIndicator); // Enhanced pulsing animation with color transitions for countdown tween(cooldownIndicator, { alpha: 0.6, scaleX: 1.2, scaleY: 1.2, tint: 0x00FFFF }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(cooldownIndicator, { alpha: 1.0, scaleX: 1.0, scaleY: 1.0, tint: 0xFFFFFF }, { duration: 500, easing: tween.easeInOut }); } }); } } } else { // Empty slot var emptySlot = self.attachAsset('spellCardBg', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 3.5, scaleY: 4.5 }); emptySlot.tint = 0x444444; emptySlot.alpha = 0.5; self.deckElements.push(emptySlot); var emptyText = new Text2('VACIO', { size: 40, fill: 0x666666, font: "monospace" }); emptyText.anchor.set(0.5, 0.5); emptyText.x = cardX; emptyText.y = cardY; self.addChild(emptyText); self.deckElements.push(emptyText); } } // Display available spells (bottom section) var availableLabel = new Text2('HECHIZOS DISPONIBLES PARA AÑADIR:', { size: 60, fill: 0xFFD700, font: "monospace" }); availableLabel.anchor.set(0.5, 0.5); availableLabel.x = 2048 / 2; availableLabel.y = 1350; self.addChild(availableLabel); self.availableElements.push(availableLabel); // Add available instruction var availableInstruction = new Text2('TOCA PARA AÑADIR A TU DECK', { size: 40, fill: 0x66FF66, font: "monospace" }); availableInstruction.anchor.set(0.5, 0.5); availableInstruction.x = 2048 / 2; availableInstruction.y = 1400; self.addChild(availableInstruction); self.availableElements.push(availableInstruction); // Display available spells var availableSpells = []; for (var i = 0; i < self.spellDeck.availableSpells.length; i++) { var spell = self.spellDeck.availableSpells[i]; if (self.spellDeck.currentDeck.indexOf(spell.id) === -1) { availableSpells.push(spell); } } if (availableSpells.length === 0) { var noSpellsText = new Text2('NO HAY HECHIZOS DISPONIBLES\nDESBLOQUEA MAS JUGANDO', { size: 50, fill: 0x888888, font: "monospace" }); noSpellsText.anchor.set(0.5, 0.5); noSpellsText.x = 2048 / 2; noSpellsText.y = 1600; self.addChild(noSpellsText); self.availableElements.push(noSpellsText); } for (var i = 0; i < availableSpells.length && i < 8; i++) { var spell = availableSpells[i]; var cardX = 150 + i % 4 * 450; var cardY = 1550 + Math.floor(i / 4) * 400; // Card background with hover effect var cardBg = self.attachAsset('spellCard', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 3.2, scaleY: 4.2 }); cardBg.tint = self.spellDeck.getRarityColor(spell.rarity); cardBg.spellId = spell.id; cardBg.isDeckCard = false; cardBg.alpha = 0.8; self.availableElements.push(cardBg); // Add selection glow var selectionGlow = self.attachAsset('spellCardBg', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 3.7, scaleY: 4.7 }); selectionGlow.tint = 0x00FFFF; selectionGlow.alpha = 0.2; self.availableElements.push(selectionGlow); // Card name var cardName = new Text2(spell.name, { size: 32, fill: 0xFFFFFF, font: "monospace" }); cardName.anchor.set(0.5, 0.5); cardName.x = cardX; cardName.y = cardY - 60; self.addChild(cardName); self.availableElements.push(cardName); // Card description var cardDesc = new Text2(spell.description, { size: 22, fill: 0xCCCCCC, font: "monospace", wordWrap: true, wordWrapWidth: 280 }); cardDesc.anchor.set(0.5, 0.5); cardDesc.x = cardX; cardDesc.y = cardY + 10; self.addChild(cardDesc); self.availableElements.push(cardDesc); // Card stats var statsText = ''; if (spell.damage) statsText += 'Daño: ' + spell.damage + '\n'; if (spell.healing) statsText += 'Cura: ' + spell.healing + '\n'; if (spell.manaCost) statsText += 'Mana: ' + spell.manaCost; if (statsText) { var cardStats = new Text2(statsText, { size: 18, fill: 0xFFD700, font: "monospace" }); cardStats.anchor.set(0.5, 0.5); cardStats.x = cardX; cardStats.y = cardY + 70; self.addChild(cardStats); self.availableElements.push(cardStats); } // Rarity indicator var rarityText = new Text2(spell.rarity.toUpperCase(), { size: 20, fill: self.spellDeck.getRarityColor(spell.rarity), font: "monospace" }); rarityText.anchor.set(0.5, 0.5); rarityText.x = cardX; rarityText.y = cardY + 100; self.addChild(rarityText); self.availableElements.push(rarityText); } }; self.hideDeck = function () { if (self.deckOverlay) { self.deckOverlay.destroy(); self.deckOverlay = null; } // Remove deck title if (self.deckTitle) { self.deckTitle.destroy(); self.deckTitle = null; } // Remove deck back button elements if (self.deckBackButton) { self.deckBackButton.destroy(); self.deckBackButton = null; } if (self.deckBackText) { self.deckBackText.destroy(); self.deckBackText = null; } // Clear deck elements for (var i = 0; i < self.deckElements.length; i++) { if (self.deckElements[i] && self.deckElements[i].parent) { self.deckElements[i].destroy(); } } self.deckElements = []; // Clear available elements for (var i = 0; i < self.availableElements.length; i++) { if (self.availableElements[i] && self.availableElements[i].parent) { self.availableElements[i].destroy(); } } self.availableElements = []; // Remove all deck-related children for (var i = self.children.length - 1; i >= 0; i--) { var child = self.children[i]; if (child.setText && child.text && (child.text.includes('DECK') || child.text.includes('HECHIZOS') || child.text.includes('ACTUAL') || child.text.includes('DISPONIBLES'))) { child.destroy(); } } self.deckMode = false; self.visible = true; }; // Unified UI factory for all menu elements // Menu overlay creation function self.createMenuOverlay = function (color) { var overlay = self.attachAsset('startMenuBackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 25.0, scaleY: 35.0 }); overlay.alpha = 0.9; overlay.tint = color || 0x000000; overlay.interactive = true; overlay.zIndex = 1000; return overlay; }; // Menu text creation function self.createMenuText = function (text, x, y, size, color) { var textElement = new Text2(text, { size: size, fill: color, font: "monospace" }); textElement.anchor.set(0.5, 0.5); textElement.x = x; textElement.y = y; self.addChild(textElement); return textElement; }; // Menu button creation function self.createMenuButton = function (asset, x, y, color) { var button = self.attachAsset(asset, { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: 2, scaleY: 2 }); button.tint = color; return button; }; self.createUIElement = function (type, config) { if (type === 'overlay') { var overlay = self.addChild(LK.getAsset('startMenuBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 1.0, scaleY: 1.0 })); overlay.alpha = 1.0; overlay.tint = config.tint || 0x000000; return overlay; } else if (type === 'text') { var text = new Text2(config.text, { size: config.size, fill: config.fill, font: "monospace" }); text.anchor.set(0.5, 0.5); text.x = config.x; text.y = config.y; self.addChild(text); return text; } else if (type === 'button') { var button = self.attachAsset(config.asset, { anchorX: 0.5, anchorY: 0.5, x: config.x, y: config.y, scaleX: 2, scaleY: 2 }); button.tint = config.tint; return button; } }; self.getShopItemsData = function () { return [{ name: 'POCION SALUD', description: 'Restaura 50 HP', cost: 10, icon: 'energySphere' }, { name: 'ESCUDO MAGICO', description: 'Bloquea 3 ataques', cost: 15, icon: 'shield' }, { name: 'ESPADA MALDITA', description: 'Daño x2 por 30s', cost: 20, icon: 'spell' }]; }; self.initializeShopArrays = function () { if (!self.shopIcons) self.shopIcons = []; if (!self.shopTexts) self.shopTexts = []; if (!self.shopBuyButtons) self.shopBuyButtons = []; if (!self.shopBuyTexts) self.shopBuyTexts = []; }; self.createShopItems = function (shopItems) { for (var i = 0; i < shopItems.length; i++) { var item = shopItems[i]; var yPos = 1100 + i * 200; var itemIcon = self.attachAsset(item.icon, { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 300, y: yPos, scaleX: 2, scaleY: 2 }); itemIcon.tint = 0xFFD700; self.shopIcons.push(itemIcon); var itemText = new Text2(item.name + '\n' + item.description + '\nCosto: ' + item.cost + ' monedas', { size: 60, fill: 0xFFFFFF, font: "monospace" }); itemText.anchor.set(0, 0.5); itemText.x = 2048 / 2 - 200; itemText.y = yPos; self.addChild(itemText); self.shopTexts.push(itemText); var buyButton = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 + 300, y: yPos, scaleX: 2, scaleY: 2 }); buyButton.tint = 0x00FF00; buyButton.itemIndex = i; self.shopBuyButtons.push(buyButton); var buyText = new Text2('COMPRAR', { size: 50, fill: 0xFFFFFF, font: "monospace" }); buyText.anchor.set(0.5, 0.5); buyText.x = 2048 / 2 + 300; buyText.y = yPos; self.addChild(buyText); self.shopBuyTexts.push(buyText); } }; self.startGame = function () { // Check if this is a new player (no tutorial completed) if (!storage.tutorialCompleted && tutorial) { // Show tutorial for new players self.visible = false; // Small delay to ensure menu is hidden before tutorial starts tween({}, {}, { duration: 100, onFinish: function onFinish() { tutorial.startTutorial(); } }); // Tutorial started successfully return; } // UNIFIED DECK SYNCHRONIZATION: Fix all deck system inconsistencies // Create unified deck reference that all systems will use var unifiedDeck = []; // Priority 1: Use spellDeck from menu if it exists and has content if (self.spellDeck && self.spellDeck.currentDeck && self.spellDeck.currentDeck.length > 0) { unifiedDeck = self.spellDeck.currentDeck.slice(); } // Priority 2: Use storage deck if available else if (storage.spellDeck && storage.spellDeck.length > 0) { unifiedDeck = storage.spellDeck.slice(); } // Priority 3: Use global currentDeck if available else if (currentDeck && currentDeck.length > 0) { unifiedDeck = currentDeck.slice(); } // Priority 4: Use activeSpellDeck if available else if (activeSpellDeck && activeSpellDeck.currentDeck && activeSpellDeck.currentDeck.length > 0) { unifiedDeck = activeSpellDeck.currentDeck.slice(); } // Priority 5: Default deck as last resort else { unifiedDeck = ['fireball', 'heal', 'lightning']; } // SYNCHRONIZE ALL DECK SYSTEMS with unified deck currentDeck = unifiedDeck.slice(); activeSpellDeck.currentDeck = unifiedDeck.slice(); storage.spellDeck = unifiedDeck.slice(); if (self.spellDeck) { self.spellDeck.currentDeck = unifiedDeck.slice(); } // Debug: Log deck synchronization with enhanced details console.log('=== DECK SYNCHRONIZATION ==='); console.log('Unified deck:', unifiedDeck); console.log('ActiveSpellDeck sync:', activeSpellDeck.currentDeck); console.log('Storage sync:', storage.spellDeck); console.log('CurrentDeck sync:', currentDeck); console.log('Available spells IDs:', availableSpells.map(function (s) { return s.id; })); console.log('Spell validation:'); for (var d = 0; d < unifiedDeck.length; d++) { var spellId = unifiedDeck[d]; var spell = _getSpell(spellId); console.log(' - ' + spellId + ':', spell ? spell.name : 'NOT FOUND'); } // Hide menu and start game normally self.visible = false; gameStarted = true; // Show cave background when game starts if (backgroundMap) { backgroundMap.visible = true; } // Show all game elements wizard.visible = true; for (var i = 0; i < paths.length; i++) { paths[i].visible = true; } // Show all stone path segments and make them visible for (var i = 0; i < game.children.length; i++) { var child = game.children[i]; if (child.pathIndex !== undefined && child !== paths[child.pathIndex]) { child.visible = true; // Check if it's a stone path segment or path number if (child.alpha !== undefined && child.setText === undefined) { child.alpha = 0; // Keep stone paths invisible } } } coinText.visible = true; killCountText.visible = true; tapText.visible = true; healthBarBg.visible = true; healthBar.visible = true; healthText.visible = true; // Show mana UI manaBarBg.visible = true; manaBar.visible = true; manaText.visible = true; updateManaDisplay(); // Show card toggle button cardToggleButton.visible = true; cardToggleText.visible = true; // Spell UI system removed - using deck menu only console.log('Spell UI handled through deck menu system'); // SPELL SLOT SYSTEM REMOVED - Using deck menu only for spell casting // Spells can now only be cast from the deck menu interface console.log('Spell slots eliminated - using deck menu system only'); // Mana system simplified - values set directly currentMana = 100; maxMana = 100; if (activeSpellDeck) { activeSpellDeck.currentMana = 100; activeSpellDeck.maxMana = 100; } console.log('=== MANA SYSTEM SIMPLIFIED ==='); console.log('currentMana:', currentMana); console.log('activeSpellDeck.currentMana:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined'); console.log('maxMana:', maxMana); // Mana bar functionality moved to spell deck system console.log('Mana system managed by deck menu'); // DEBUG: Verify mana values after initialization console.log('=== MANA FORCE INITIALIZATION ==='); console.log('currentMana:', currentMana); console.log('activeSpellDeck.currentMana:', activeSpellDeck.currentMana); console.log('maxMana:', maxMana); console.log('Mana bar updated successfully'); // SPELL SLOT SYSTEM REMOVED - Using deck menu only for spell casting // Spells can now only be cast from the deck menu interface console.log('Spell slots eliminated - using deck menu system only'); // ADDITIONAL MANA VERIFICATION - ENSURE MANA STAYS AT 100 tween({}, {}, { duration: 100, // Short delay to ensure all systems are initialized onFinish: function onFinish() { // FORCE MANA TO 100 AGAIN after all initialization currentMana = 100; activeSpellDeck.currentMana = 100; // Mana system handled by deck menu console.log('Mana system initialized through deck menu'); // Final debug verification console.log('=== FINAL MANA VERIFICATION ==='); console.log('Final currentMana:', currentMana); console.log('Final activeSpellDeck.currentMana:', activeSpellDeck.currentMana); console.log('Mana system successfully initialized to 100'); } }); // SPELL SLOTS SYSTEM REMOVED // All spell casting now happens through the deck menu system only console.log('=== SPELL SYSTEM SIMPLIFIED ==='); console.log('Deck menu is the only way to cast spells'); console.log('Spell slots have been eliminated'); // Start medieval music with user's volume setting var musicVolume = storage.musicVolume || 0.7; LK.playMusic('medievalTheme', { volume: musicVolume, fade: { start: 0, end: musicVolume, duration: 2000 } }); }; return self; }); var Orb = Container.expand(function () { var self = Container.call(this); // Create orb visual using energy sphere var orbGraphics = self.attachAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4 }); orbGraphics.tint = 0xFFD700; // Golden color for orbs orbGraphics.alpha = 0.9; self.orbitalAngle = 0; // Starting angle for this orb self.orbitalRadius = 880; // Distance from wizard - doubled again for even more separation self.rotationSpeed = 0.025; // How fast orbs rotate - halved for slower movement // Category 3: Orb-enemy collisions (separate from projectile-enemy and enemy-wizard) self.processOrbEnemyCollisions = function () { var allEnemies = collisionArrayPool.getAllEnemies(); for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[i]; // Skip collision check with wizard if (enemy === wizard) { continue; } // Initialize collision tracking for this enemy if not exists if (!self.lastIntersecting) { self.lastIntersecting = {}; } if (self.lastIntersecting[i] === undefined) { self.lastIntersecting[i] = false; } // 1.1 Distance Culling: Quick distance check before expensive collision detection var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var maxOrbRange = 80; // Orb collision range var currentIntersecting = false; if (distance <= maxOrbRange) { // Only perform expensive intersection test if enemy is close enough currentIntersecting = self.intersects(enemy); } if (!self.lastIntersecting[i] && currentIntersecting) { // Deal damage to enemy on contact transition (first contact only) enemy.takeDamage(200); // Visual effect for orb hit LK.effects.flashObject(self, 0xFFFFFF, 200); // Create orb impact effect var orbImpact = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y, scaleX: 0.3, scaleY: 0.3 })); orbImpact.tint = 0xFFD700; orbImpact.alpha = 0.8; // Animate orb impact tween(orbImpact, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 250, easing: tween.easeOut, onFinish: function onFinish() { orbImpact.destroy(); } }); } // Update collision state for this enemy self.lastIntersecting[i] = currentIntersecting; } }; self.update = function () { // Pause orb when tutorial is active if (tutorial && tutorial.isActive) { return; } // Rotate around wizard if (wizard) { self.orbitalAngle += self.rotationSpeed; self.x = wizard.x + Math.cos(self.orbitalAngle) * self.orbitalRadius; self.y = wizard.y + Math.sin(self.orbitalAngle) * self.orbitalRadius - 240; // Position orb much higher up } // Add pulsing effect var pulse = 1 + Math.sin(LK.ticks * 0.3) * 0.2; orbGraphics.scaleX = 0.4 * pulse; orbGraphics.scaleY = 0.4 * pulse; // Category 3: Orb-enemy collisions (separate from projectile-enemy and enemy-wizard) self.processOrbEnemyCollisions(); }; return self; }); // Create global death handler instance var Projectile = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'projectile'; self.speed = 50; self.direction = { x: 0, y: 0 }; self.targetEnemy = null; self.damage = 100; self.hitEnemy = false; // Define local projectile configurations since GAME_CONFIG is not available yet var projectileConfigs = { projectile: { assetId: 'projectile', scale: 1.5, speed: 50, damage: 100, tint: 0x44aaff, glowTint: 0x44aaff, hasRotation: true }, energyBeam: { assetId: 'projectileGlow', scale: 1.0, speed: 60, damage: 100, tint: 0x00ffff, glowTint: 0x00ffff, hasRotation: true }, fireBall: { assetId: 'projectileGlow', scale: 1.5, speed: 40, damage: 150, tint: 0xFF4500, glowTint: 0xFF6600, hasRotation: true, hasFlicker: true } }; // Get configuration from local configs var config = projectileConfigs[self.type]; if (!config) { // Fallback to projectile config config = projectileConfigs.projectile; } self.speed = config.speed; self.damage = config.damage; // Create graphics based on type var assetId = config.assetId; self.graphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: config.scale, scaleY: config.scale }); self.graphics.tint = config.tint; // Add glow for basic projectiles if (self.type === 'projectile') { var glow = self.attachAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 2.0 }); glow.alpha = 0.3; glow.tint = config.glowTint; } self.update = function () { if (tutorial && tutorial.isActive) return; // Move projectile self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Visual effects if (config.hasRotation) { var angle = Math.atan2(self.direction.y, self.direction.x); self.graphics.rotation = angle + Math.PI / 2; } if (config.hasFlicker) { var flicker = 1 + Math.sin(LK.ticks * 0.4) * 0.3; self.graphics.scaleX = config.scale * flicker; self.graphics.scaleY = config.scale * flicker; } // Remove if off screen if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { // Remove from projectiles array before destroying ProjectileFactory.removeProjectile(self); return; } // Handle collisions self.checkCollisions(); }; self.checkCollisions = function () { if (self.hitEnemy) return; // Category 1: Area damage projectiles (fireball) if (self.type === 'fireBall') { self.processAreaDamageCollisions(); } else { // Category 2: Single target projectiles self.processSingleTargetCollisions(); } }; // Separate collision processing for area damage projectiles self.processAreaDamageCollisions = function () { var allEnemies = collisionArrayPool.getAllEnemies(); for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[i]; if (enemy === wizard || enemy.isDying) continue; // 1.1 Distance Culling: Quick distance check before expensive collision detection var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var maxFireballRange = 120; // Fireball collision range including area effect // Only perform expensive intersection test if enemy is within range if (distance <= maxFireballRange) { if (self.intersects(enemy)) { enemy.takeDamage(self.damage); self.createExplosion(enemy); self.hitEnemy = true; // Remove from projectiles array before destroying ProjectileFactory.removeProjectile(self); return; } } } }; // Separate collision processing for single target projectiles self.processSingleTargetCollisions = function () { if (self.targetEnemy && self.targetEnemy.parent && !self.targetEnemy.isDying) { // 1.1 Distance Culling: Quick distance check before expensive collision detection var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var maxProjectileRange = 100; // Standard projectile collision range // Only perform expensive intersection test if target enemy is within range if (distance <= maxProjectileRange) { if (self.intersects(self.targetEnemy)) { self.targetEnemy.takeDamage(self.damage); self.hitEnemy = true; // Remove from projectiles array before destroying ProjectileFactory.removeProjectile(self); return; } } } else { // Target is invalid, remove projectile self.hitEnemy = true; // Remove from projectiles array before destroying ProjectileFactory.removeProjectile(self); return; } }; self.createExplosion = function (enemy) { LK.effects.flashObject(enemy, 0xFF4500, 400); var explosion = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y, scaleX: 2, scaleY: 2 })); explosion.tint = 0xFF6600; explosion.alpha = 0.8; tween(explosion, { scaleX: 5, scaleY: 5, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { explosion.destroy(); } }); }; return self; }); // Simple effects helper for basic visual feedback // Simple SpellDeck class to replace the complex system var SpellDeck = Container.expand(function () { var self = Container.call(this); // Use the existing simple spell system self.currentDeck = storage.spellDeck || ['fireball', 'heal', 'lightning']; self.availableSpells = availableSpells; // Reference to global availableSpells // Simple methods to match the interface expected by GameMenu self.addToDeck = function (spellId) { if (self.currentDeck.length >= 5) return false; if (self.currentDeck.indexOf(spellId) !== -1) return false; self.currentDeck.push(spellId); storage.spellDeck = self.currentDeck.slice(); return true; }; self.removeFromDeck = function (spellId) { var index = self.currentDeck.indexOf(spellId); if (index === -1) return false; self.currentDeck.splice(index, 1); storage.spellDeck = self.currentDeck.slice(); return true; }; self.getSpell = function (spellId) { return _getSpell(spellId); }; self.getRarityColor = function (rarity) { return _getRarityColor(rarity); }; self.unlockSpell = function (spellId) { // Simple unlock system - spells are always available return true; }; return self; }); // Simple spell system - no complex classes needed var Tutorial = Container.expand(function () { var self = Container.call(this); // Tutorial state variables self.currentStep = 0; self.isActive = false; self.skipped = false; // Track if tutorial was skipped self.tutorialSteps = []; self.highlightElements = []; self.tutorialTexts = []; self.arrows = []; // Tutorial overlay background var tutorialOverlay = self.attachAsset('startMenuBackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 1.0, scaleY: 1.0 }); tutorialOverlay.alpha = 0.8; tutorialOverlay.tint = 0x000000; tutorialOverlay.visible = false; // Initially hidden tutorialOverlay.zIndex = 1999; // Ensure proper layering tutorialOverlay.interactive = true; // Always interactive to block clicks // Define tutorial steps self.initializeTutorialSteps = function () { self.tutorialSteps = [{ id: 'welcome', title: 'BIENVENIDO A WIZARD DEFENDER!', description: 'Eres un poderoso mago que debe defender su castillo. Toca directamente sobre los enemigos para atacarlos con hechizos.', duration: 3000, showSkip: true, highlightElement: 'screen', waitForTap: true }, { id: 'enemies_approach', title: 'ENEMIGOS Y MEJORAS', description: 'Los esqueletos vienen por 5 caminos diferentes. Toca directamente sobre cada enemigo para atacarlo. Gana monedas y desbloquea mejoras cada 12 enemigos.', duration: 3000, spawnDemoEnemy: true, highlightElement: 'coinCounter' }, { id: 'tutorial_complete', title: 'TUTORIAL COMPLETADO!', description: 'Recuerda: toca directamente sobre los enemigos para atacarlos. Cuida tu salud y sobrevive el mayor tiempo posible. ¡Buena suerte!', duration: 3000, startGame: true, highlightElement: 'healthBar' }]; }; // Start the tutorial self.startTutorial = function () { // Always show tutorial when explicitly called self.isActive = true; self.currentStep = 0; // Initialize tutorial steps first self.initializeTutorialSteps(); // Make tutorial visible and properly layered self.visible = true; self.zIndex = 2000; // Configure tutorial overlay properly tutorialOverlay.visible = true; tutorialOverlay.alpha = 0.8; tutorialOverlay.tint = 0x000000; tutorialOverlay.interactive = true; // Block clicks to game below tutorialOverlay.zIndex = 1999; // Ensure proper layering // Hide game menu while tutorial is active if (gameMenu) { gameMenu.visible = false; } // Hide all game elements during tutorial if (wizard) wizard.visible = false; if (backgroundMap) backgroundMap.visible = false; coinText.visible = false; killCountText.visible = false; tapText.visible = false; healthBarBg.visible = false; healthBar.visible = false; healthText.visible = false; for (var i = 0; i < paths.length; i++) { paths[i].visible = false; } // Start first step self.showStep(0); return true; // Tutorial started }; // Show a specific tutorial step self.showStep = function (stepIndex) { if (stepIndex >= self.tutorialSteps.length) { self.completeTutorial(); return; } // Clear previous step elements self.clearStepElements(); var step = self.tutorialSteps[stepIndex]; self.currentStep = stepIndex; // Ensure tutorial is visible and on top self.visible = true; tutorialOverlay.visible = true; self.zIndex = 2000; // Create step title with proper sizing var titleText = new Text2(step.title, { size: 80, fill: 0xFFD700, font: "monospace", wordWrap: true, wordWrapWidth: 1600 }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 900; self.addChild(titleText); self.tutorialTexts.push(titleText); // Create step description with proper text wrapping var descText = new Text2(step.description, { size: 60, fill: 0xFFFFFF, font: "monospace", wordWrap: true, wordWrapWidth: 1800 }); descText.anchor.set(0.5, 0.5); descText.x = 2048 / 2; descText.y = 1300; self.addChild(descText); self.tutorialTexts.push(descText); // Handle special step behaviors if (step.spawnDemoEnemy) { self.spawnDemoEnemy(); } if (step.highlightElement) { self.highlightElement(step.highlightElement); } // Show continue button or wait for specific action if (step.waitForTap) { var tapPrompt = new Text2('TOCA LA PANTALLA PARA CONTINUAR', { size: 50, fill: 0x00FF00, font: "monospace", wordWrap: true, wordWrapWidth: 1400 }); tapPrompt.anchor.set(0.5, 0.5); tapPrompt.x = 2048 / 2; tapPrompt.y = 1800; self.addChild(tapPrompt); self.tutorialTexts.push(tapPrompt); // Add pulsing effect to tap prompt tween(tapPrompt, { scaleX: 1.2, scaleY: 1.2 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(tapPrompt, { scaleX: 1.0, scaleY: 1.0 }, { duration: 800, easing: tween.easeInOut }); } }); } else { // Always show continue prompt - no auto-advance var continuePrompt = new Text2('TOCA LA PANTALLA PARA CONTINUAR', { size: 50, fill: 0x00FF00, font: "monospace", wordWrap: true, wordWrapWidth: 1400 }); continuePrompt.anchor.set(0.5, 0.5); continuePrompt.x = 2048 / 2; continuePrompt.y = 1800; self.addChild(continuePrompt); self.tutorialTexts.push(continuePrompt); // Add pulsing effect to continue prompt tween(continuePrompt, { scaleX: 1.2, scaleY: 1.2 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(continuePrompt, { scaleX: 1.0, scaleY: 1.0 }, { duration: 800, easing: tween.easeInOut }); } }); } // Show skip button on first step if (step.showSkip) { var skipBtn = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: 200, scaleX: 1.5, scaleY: 1.5 }); skipBtn.tint = 0xFF4444; self.highlightElements.push(skipBtn); var skipText = new Text2('OMITIR', { size: 50, fill: 0xFFFFFF, font: "monospace" }); skipText.anchor.set(0.5, 0.5); skipText.x = 2048 - 200; skipText.y = 200; self.addChild(skipText); self.tutorialTexts.push(skipText); } }; // Highlight specific game elements self.highlightElement = function (elementType) { switch (elementType) { case 'healthBar': if (healthBarBg && healthBar) { // Show health UI temporarily healthBarBg.visible = true; healthBar.visible = true; healthText.visible = true; // Create highlight glow around health bar var healthGlow = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: 270, y: 110, scaleX: 3, scaleY: 1.5 })); healthGlow.tint = 0x00FF00; healthGlow.alpha = 0.6; self.highlightElements.push(healthGlow); // Animate glow tween(healthGlow, { alpha: 0.3 }, { duration: 1000, easing: tween.easeInOut }); } break; case 'coinCounter': if (coinText) { // Show coin UI temporarily coinText.visible = true; coinText.setText('Coins: 15'); // Show example coins // Create highlight glow around coin counter var coinGlow = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: 270, y: 150, scaleX: 3, scaleY: 1.5 })); coinGlow.tint = 0xFFD700; coinGlow.alpha = 0.6; self.highlightElements.push(coinGlow); // Animate glow tween(coinGlow, { alpha: 0.3 }, { duration: 1000, easing: tween.easeInOut }); } break; case 'screen': // Create large highlight overlay var screenGlow = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 20, scaleY: 25 })); screenGlow.tint = 0x00FFFF; screenGlow.alpha = 0.2; self.highlightElements.push(screenGlow); // Animate screen glow tween(screenGlow, { alpha: 0.1 }, { duration: 1500, easing: tween.easeInOut }); // Create arrow pointing to center self.createArrow(2048 / 2, 2732 / 2 - 400, 2048 / 2, 2732 / 2); break; } }; // Create pointing arrow self.createArrow = function (fromX, fromY, toX, toY) { var arrow = game.addChild(LK.getAsset('spell', { anchorX: 0.5, anchorY: 0.5, x: fromX, y: fromY, scaleX: 3, scaleY: 3 })); arrow.tint = 0xFFD700; arrow.alpha = 0.8; // Calculate arrow direction var dx = toX - fromX; var dy = toY - fromY; var angle = Math.atan2(dy, dx); arrow.rotation = angle; // Animate arrow bouncing tween(arrow, { x: toX, y: toY }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(arrow, { x: fromX, y: fromY }, { duration: 1000, easing: tween.easeInOut }); } }); self.arrows.push(arrow); }; // Spawn a demo enemy for tutorial self.spawnDemoEnemy = function () { // Create a slow-moving demo enemy using unified Enemy class var demoEnemy = game.addChild(new Enemy('skeleton')); demoEnemy.x = 2048 / 2; demoEnemy.y = -100; demoEnemy.speed = 1; // Very slow for demo demoEnemy.health = 1; demoEnemy.maxHealth = 1; demoEnemy.pathIndex = 0; // Center path // Make demo enemy more visible for (var frameIdx = 0; frameIdx < demoEnemy.animationFrames.length; frameIdx++) { demoEnemy.animationFrames[frameIdx].tint = 0xFF6600; // Orange tint } enemies.push(demoEnemy); // Create highlight around demo enemy var enemyGlow = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: demoEnemy.x, y: demoEnemy.y, scaleX: 4, scaleY: 4 })); enemyGlow.tint = 0xFF0000; enemyGlow.alpha = 0.5; // Make glow follow enemy var _glowFollower = function glowFollower() { if (demoEnemy && demoEnemy.parent && enemyGlow && enemyGlow.parent) { enemyGlow.x = demoEnemy.x; enemyGlow.y = demoEnemy.y; // Continue following tween({}, {}, { duration: 60, onFinish: _glowFollower }); } else if (enemyGlow && enemyGlow.parent) { enemyGlow.destroy(); } }; _glowFollower(); self.highlightElements.push(enemyGlow); }; // Clear all tutorial step elements self.clearStepElements = function () { // Clear tutorial texts for (var i = 0; i < self.tutorialTexts.length; i++) { if (self.tutorialTexts[i] && self.tutorialTexts[i].parent) { self.tutorialTexts[i].destroy(); } } self.tutorialTexts = []; // Clear highlight elements for (var i = 0; i < self.highlightElements.length; i++) { if (self.highlightElements[i] && self.highlightElements[i].parent) { self.highlightElements[i].destroy(); } } self.highlightElements = []; // Clear arrows for (var i = 0; i < self.arrows.length; i++) { if (self.arrows[i] && self.arrows[i].parent) { self.arrows[i].destroy(); } } self.arrows = []; }; // Advance to next tutorial step self.nextStep = function () { self.currentStep++; if (self.currentStep < self.tutorialSteps.length) { self.showStep(self.currentStep); } else { // Tutorial completed naturally (not skipped) self.skipped = false; self.completeTutorial(); } }; // Complete the tutorial self.completeTutorial = function () { // Mark tutorial as completed storage.tutorialCompleted = true; // Clear all tutorial elements self.clearStepElements(); // Hide tutorial completely tutorialOverlay.visible = false; tutorialOverlay.interactive = false; // Remove interactivity self.visible = false; self.isActive = false; // Only auto-start game if tutorial was completed naturally (not skipped) if (!self.skipped) { // Start game immediately without showing menu if (gameMenu) { gameMenu.startGame(); } } else { // If skipped, show the menu if (gameMenu) { gameMenu.visible = true; } } // Reset skipped flag for next time self.skipped = false; }; // Handle tutorial interactions // Add interaction handler for spell cards in deck view self.down = function (x, y, obj) { // Only handle interactions if deck is active or if this is tutorial if (self.deckMode) { // Check deck card clicks for spell casting (from current deck) for (var i = 0; i < self.deckElements.length; i++) { var element = self.deckElements[i]; if (element.spellId && element.isDeckCard) { var cardX = element.x; var cardY = element.y; if (x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225) { // Cast the spell instead of removing it if (activeSpellDeck && activeSpellDeck.canCastSpell(element.spellId)) { var spell = activeSpellDeck.getSpell(element.spellId); if (spell) { var targetX = wizard.x; var targetY = wizard.y - 100; // Find nearest enemy for targeted spells if (spell.id === 'fireball' || spell.id === 'iceShard' || spell.id === 'lightning') { var allEnemies = collisionArrayPool.getAllEnemies(); var nearestEnemy = null; var nearestDistance = Infinity; for (var e = 0; e < allEnemies.length; e++) { var enemy = allEnemies[e]; var dx = enemy.x - wizard.x; var dy = enemy.y - wizard.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestEnemy = enemy; } } if (nearestEnemy) { targetX = nearestEnemy.x; targetY = nearestEnemy.y; } } // Cast the spell using specific functions if (element.spellId === 'fireball') { castFireball(); } else if (element.spellId === 'lightning') { castLightning(); } else if (element.spellId === 'heal') { castHeal(); } else { console.log('Unknown spell ID in tutorial:', element.spellId); castFireball(); // Default to fireball } LK.effects.flashObject(element, 0x00FF00, 200); // Show cast message var castText = new Text2('HECHIZO LANZADO!', { size: 60, fill: 0x00FF00, font: "monospace" }); castText.anchor.set(0.5, 0.5); castText.x = 2048 / 2; castText.y = 2200; self.addChild(castText); // Animate and remove message tween(castText, { alpha: 0, y: castText.y - 100 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { if (castText.parent) castText.destroy(); } }); } } else { LK.effects.flashObject(element, 0xFF0000, 200); // Show "cannot cast" message var errorText = new Text2('NO SE PUEDE LANZAR', { size: 50, fill: 0xFF6666, font: "monospace" }); errorText.anchor.set(0.5, 0.5); errorText.x = 2048 / 2; errorText.y = 2200; self.addChild(errorText); // Animate and remove message tween(errorText, { alpha: 0, y: errorText.y - 100 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { if (errorText.parent) errorText.destroy(); } }); } return; } } } // Continue with existing available cards handling... // Check available card clicks with better hit detection for (var i = 0; i < self.availableElements.length; i++) { var element = self.availableElements[i]; if (element.spellId && !element.isDeckCard) { var cardX = element.x; var cardY = element.y; if (x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225) { // Visual feedback before addition LK.effects.flashObject(element, 0x00FF00, 300); // Add to deck if (self.spellDeck.addToDeck(element.spellId)) { self.refreshDeckDisplay(); LK.effects.flashScreen(0x00FF00, 200); // Show addition message var addText = new Text2('HECHIZO AÑADIDO', { size: 60, fill: 0x66FF66, font: "monospace" }); addText.anchor.set(0.5, 0.5); addText.x = 2048 / 2; addText.y = 2200; self.addChild(addText); // Animate and remove message tween(addText, { alpha: 0, y: addText.y - 100 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { if (addText.parent) addText.destroy(); } }); } else { LK.effects.flashScreen(0xFF0000, 200); // Show error message var errorText = new Text2('DECK LLENO (MAX 5)', { size: 50, fill: 0xFF6666, font: "monospace" }); errorText.anchor.set(0.5, 0.5); errorText.x = 2048 / 2; errorText.y = 2200; self.addChild(errorText); // Animate and remove message tween(errorText, { alpha: 0, y: errorText.y - 100 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { if (errorText.parent) errorText.destroy(); } }); } return; } } } // Deck back button if (y >= 2350 && y <= 2650) { self.hideDeck(); } // Block all other interactions when deck menu is active return; } // Tutorial handling (moved after deck handling) if (!self.isActive) return; var step = self.tutorialSteps[self.currentStep]; // Handle skip button (top-right corner) if (step.showSkip && x >= 2048 - 300 && x <= 2048 - 100 && y >= 100 && y <= 300) { self.skipped = true; // Mark as skipped self.completeTutorial(); return; } // For any tap anywhere on screen, advance to next step if (step.id === 'tap_to_attack') { // Simulate spell casting for demo if (wizard) { wizard.attack(0); // Attack center path } // Wait a moment then advance tween({}, {}, { duration: 1000, onFinish: function onFinish() { self.nextStep(); } }); } else { // For all other steps, advance immediately on any tap self.nextStep(); } }; return self; }); // Impact effect function now handled by BaseDamageHandler // Unified death handler for all enemy types using enemy configuration var UnifiedDeathHandler = Container.expand(function () { var self = Container.call(this); // Execute enemy death with consolidated coin and reward logic self.executeEnemyDeath = function (enemy, enemyArray) { enemy.animationState = 'dying'; enemy.currentFrame = 3; LK.getSound('painSound').play(); enemy.isDying = true; // Use direct enemy configuration values based on type var deathRotation = Math.PI * 0.5; var numCoins = 1; // Set specific values based on enemy type if (enemy.enemyType === 'miniBoss') { deathRotation = Math.PI * 0.8; numCoins = 5; } else if (enemy.enemyType === 'ogre') { deathRotation = Math.PI * 0.6; numCoins = 1; } else if (enemy.enemyType === 'knight') { deathRotation = Math.PI * 0.7; numCoins = 1; } // Special cleanup for mini boss UI elements if (enemy.enemyType === 'miniBoss') { self.cleanupMiniBossUI(enemy); } // Execute unified death animation tween(enemy, { alpha: 0, scaleX: 0.3, scaleY: 0.3, rotation: deathRotation }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { self.handleDeathRewards(enemy, numCoins); self.updateProgression(enemy); self.cleanupEnemy(enemy, enemyArray); } }); }; // Clean up mini boss UI elements self.cleanupMiniBossUI = function (enemy) { if (enemy.healthBarBg && enemy.healthBarBg.parent) { enemy.healthBarBg.destroy(); } if (enemy.healthBarFg && enemy.healthBarFg.parent) { enemy.healthBarFg.destroy(); } if (enemy.healthText && enemy.healthText.parent) { enemy.healthText.destroy(); } }; // Handle coin drops and difficulty-based rewards using pooled coins self.handleDeathRewards = function (enemy, numCoins) { var selectedDifficulty = storage.difficulty || 'NORMAL'; for (var coinIdx = 0; coinIdx < numCoins; coinIdx++) { // Create coin directly without object pool var coin = new Coin(); coin.x = enemy.x + (enemy.enemyType === 'miniBoss' ? (Math.random() - 0.5) * 200 : 0); coin.y = enemy.y - 50 + (enemy.enemyType === 'miniBoss' ? (Math.random() - 0.5) * 100 : 0); coin.isAnimating = true; coin.bobOffset = Math.random() * Math.PI * 2; coin.initialY = 0; coin.visible = true; coin.alpha = 1.0; game.addChild(coin); coins.push(coin); var coinTargetX = 120 + coinText.width / 2; var coinTargetY = 90 + coinText.height / 2; tween(coin, { x: coinTargetX, y: coinTargetY, scaleX: 0.5, scaleY: 0.5 }, { duration: 1000 + (enemy.enemyType === 'miniBoss' ? coinIdx * 200 : 0), easing: tween.easeOut, onFinish: function onFinish() { self.processCoinReward(enemy, selectedDifficulty, coin); } }); } }; // Process coin rewards with difficulty modifiers self.processCoinReward = function (enemy, selectedDifficulty, coin) { var coinReward = enemy.enemyType === 'miniBoss' ? 10 : 1; if (selectedDifficulty === 'FACIL') { coinReward = Math.floor(coinReward * 1.5); } else if (selectedDifficulty === 'DIFICIL') { coinReward = Math.max(1, Math.floor(coinReward * 0.75)); } coinCounter += coinReward; coinText.setText('Coins: ' + coinCounter); // Easy difficulty healing bonus if (selectedDifficulty === 'FACIL' && Math.random() < 0.15) { wizard.health = Math.min(wizard.health + 5, wizard.maxHealth); updateHealthBar(); LK.effects.flashObject(wizard, 0x00FF00, 200); } // Remove coin from tracking array for (var i = coins.length - 1; i >= 0; i--) { if (coins[i] === coin) { coins.splice(i, 1); break; } } // Destroy coin directly without object pool coin.destroy(); }; // Update kill counter and experience progression self.updateProgression = function (enemy) { var killIncrement = enemy.enemyType === 'miniBoss' ? 10 : 1; enemyKillCounter += killIncrement; killCountText.setText('Puntuacion: ' + enemyKillCounter); wizard.gainExperience(enemy.enemyType === 'miniBoss' ? 250 : 25); if (selectedEnemy === enemy) { selectedEnemy = null; } }; // Clean up enemy from arrays and game self.cleanupEnemy = function (enemy, enemyArray) { // Remove from appropriate array for (var i = enemyArray.length - 1; i >= 0; i--) { if (enemyArray[i] === enemy) { enemyArray.splice(i, 1); break; } } // Also remove from global enemy manager collections globalEnemyManager.removeFromCollection(enemy, enemy.enemyType); // Remove from all legacy arrays to ensure proper cleanup var allArrays = [enemies, ogres, knights, miniBosses]; for (var arrayIdx = 0; arrayIdx < allArrays.length; arrayIdx++) { var array = allArrays[arrayIdx]; for (var i = array.length - 1; i >= 0; i--) { if (array[i] === enemy) { array.splice(i, 1); break; } } } enemy.destroy(); // Set score reward based on enemy type var scoreReward = 10; if (enemy.enemyType === 'miniBoss') { scoreReward = 100; } else if (enemy.enemyType === 'ogre') { scoreReward = 20; } else if (enemy.enemyType === 'knight') { scoreReward = 30; } LK.setScore(LK.getScore() + scoreReward); }; return self; }); // UpgradeMenu class removed - using spell deck system instead // Unified Projectile Factory using consolidated GAME_CONFIG.projectiles var Wizard = Container.expand(function () { var self = Container.call(this); // Animation system for wizard self.currentFrame = 1; self.animationTimer = 0; self.animationSpeed = 18; // Change frame every 18 ticks (300ms at 60fps) // Create all wizard graphics frames and store them self.wizardFrames = []; for (var i = 1; i <= 4; i++) { var frameGraphics = self.attachAsset('wizard' + i, { anchorX: 0.5, anchorY: 1.0, scaleX: 2.5, scaleY: 2.5 }); frameGraphics.visible = i === 1; // Only show first frame initially self.wizardFrames.push(frameGraphics); } // Create invisible hitbox with much smaller size for more precise collision var hitbox = self.attachAsset('wizard1', { anchorX: 0.3, anchorY: 1.0, scaleX: 0.25, // Much smaller size for very precise collision scaleY: 0.3 // Much smaller size for very precise collision }); hitbox.alpha = 0; // Make hitbox invisible // Position hitbox slightly to the right to reduce left side hitbox.x = 15; // Offset hitbox to the right // Create shield visual effect self.shieldGraphics = self.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 3 }); self.shieldGraphics.alpha = 0.7; self.shieldGraphics.visible = false; self.attackCooldown = 0; self.level = 1; self.health = 100; self.maxHealth = 100; self.shieldActive = false; // Track shield status // Upgrade system removed - simplified wizard properties // Override intersects method to use smaller hitbox self.intersects = function (other) { return hitbox.intersects(other); }; self.update = function () { // Pause wizard when tutorial is active if (tutorial && tutorial.isActive) { return; } if (self.attackCooldown > 0) { self.attackCooldown--; } // Update shield visibility based on shield status self.shieldGraphics.visible = self.shieldActive; if (self.shieldActive) { // Animate shield with pulsing effect var pulse = 1 + Math.sin(LK.ticks * 0.15) * 0.2; self.shieldGraphics.scaleX = 3 * pulse; self.shieldGraphics.scaleY = 3 * pulse; // Slowly rotate shield self.shieldGraphics.rotation += 0.03; // Add glowing effect self.shieldGraphics.alpha = 0.6 + Math.sin(LK.ticks * 0.1) * 0.2; } // Upgrade-based abilities removed - using spell deck system instead // Simplified animation system - instant frame switching only self.animationTimer++; if (self.animationTimer >= self.animationSpeed) { self.animationTimer = 0; // Hide current frame self.wizardFrames[self.currentFrame - 1].visible = false; // Move to next frame self.currentFrame++; if (self.currentFrame > 4) { self.currentFrame = 1; } // Show next frame self.wizardFrames[self.currentFrame - 1].visible = true; } }; self.attack = function (direction) { if (self.attackCooldown <= 0) { // Default direction if none specified if (direction === undefined) { direction = 0; // Default to center path } // Get attack angle based on path direction var attackAngle = pathAngles[direction]; var attackDistance = 100; // Calculate spell position based on attack direction var spellX = self.x + Math.cos(attackAngle) * attackDistance; var spellY = self.y + Math.sin(attackAngle) * attackDistance; // Create spell effect var spell = game.addChild(LK.getAsset('spell', { anchorX: 0.5, anchorY: 0.5, x: spellX, y: spellY, scaleX: 0.5, scaleY: 0.5 })); // Animate spell with magical effects tween(spell, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { spell.destroy(); } }); // Add rotation animation to spell tween(spell, { rotation: Math.PI * 2 }, { duration: 500, easing: tween.linear }); self.attackCooldown = 30; // 0.5 seconds at 60fps LK.getSound('spellCast').play(); // Base damage for wizard attack var totalDamage = 1; // Attack enemies in the specified direction/path for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.pathIndex === direction) { // Only hit enemies on exact same path - no distance validation enemy.takeDamage(totalDamage); } } // Attack ogres in the specified direction/path for (var i = ogres.length - 1; i >= 0; i--) { var ogre = ogres[i]; if (ogre.pathIndex === direction) { // Only hit ogres on exact same path - no distance validation ogre.takeDamage(totalDamage); } } // Attack knights in the specified direction/path for (var i = knights.length - 1; i >= 0; i--) { var knight = knights[i]; if (knight.pathIndex === direction) { // Only hit knights on exact same path - no distance validation knight.takeDamage(totalDamage); } } return true; } return false; }; self.gainExperience = function (amount) { self.experience += amount; var expNeeded = self.level * 100; if (self.experience >= expNeeded) { self.levelUp(); } }; self.levelUp = function () { self.level++; self.experience = 0; // Visual level up effect LK.effects.flashObject(self, 0xFFD700, 500); }; self.takeDamage = function (damage) { // Check if teleport invulnerability is active if (self.teleportInvuln) { GameManager.createFlashEffect(self, 0x8000FF, 200); return; } // Check if shield is active if (self.shieldActive) { // Initialize shield properties if not set if (self.maxShieldHits === undefined) { self.maxShieldHits = 1; self.currentShieldHits = 0; } // Increment shield hits self.currentShieldHits++; // Visual feedback for shield use GameManager.createFlashEffect(self, 0x00BFFF, 300); // Check if shield is depleted if (self.currentShieldHits >= self.maxShieldHits) { self.shieldActive = false; // Start shield regeneration timer var regenTime = self.shieldRegen ? 5000 : 10000; // Faster regen if improved tween({}, {}, { duration: regenTime, onFinish: function onFinish() { // Regenerate shield self.shieldActive = true; self.currentShieldHits = 0; // Visual feedback for shield regeneration GameManager.createFlashEffect(self, 0x00BFFF, 500); // Add shield regeneration animation tween(self.shieldGraphics, { scaleX: 5, scaleY: 5, alpha: 1.0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { tween(self.shieldGraphics, { scaleX: 3, scaleY: 3, alpha: 0.7 }, { duration: 400, easing: tween.easeIn }); } }); } }); } // No damage taken, shield absorbed it return; } // Use unified damage handler for core damage logic self.health -= damage; GameManager.createFlashEffect(self, 0xFF0000, 200); if (self.health <= 0) { self.health = 0; // 10% chance to revive when dying var reviveChance = Math.random(); if (reviveChance < 0.10) { // Revival successful! self.health = Math.floor(self.maxHealth * 0.5); // Revive with 50% health // Destroy ALL enemies when revival activates (no distance restriction) var allEnemies = collisionArrayPool.getAllEnemies(); for (var enemyIdx = allEnemies.length - 1; enemyIdx >= 0; enemyIdx--) { var enemy = allEnemies[enemyIdx]; // Create destruction effect for each enemy GameManager.createFlashEffect(enemy, 0xFFD700, 500); // Create golden explosion particles GameManager.createVisualEffect('explosion', enemy, { explosionColor: 0xFFD700, explosionScale: 4.0 }); // Kill ALL enemies instantly by calling die() method enemy.die(); } // Visual effects for revival LK.effects.flashScreen(0x00FF00, 1500); // Green flash for revival GameManager.createFlashEffect(self, 0xFFD700, 1000); // Golden flash on wizard // Create healing aura effect GameManager.createVisualEffect('explosion', self, { explosionColor: 0x00FF00, explosionScale: 8.0 }); // Play spell cast sound for revival LK.getSound('spellCast').play(); // Update health bar to show revival updateHealthBar(); } else { // Game over when health reaches 0 and no revival LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); } } // Update health bar updateHealthBar(); // Simplified screen shake for better performance var shakeIntensity = 8; var originalX = game.x; var originalY = game.y; // Simple single shake effect tween(game, { x: originalX + shakeIntensity, y: originalY + shakeIntensity * 0.5 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(game, { x: originalX, y: originalY }, { duration: 100, easing: tween.easeIn }); } }); }; // Force push ability removed - not used in current spell system // Freeze pulse ability removed - not used in current spell system // Thorns ability removed - not used in current spell system // Fireball launch removed - using spell deck system instead // Frame transition config removed - using simple animation only // Advanced transition removed - using simple frame switching only // Basic transition removed - using instant frame switching only // Sparkle effects removed - using simplified animations only return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 // Black background for pixel art }); /**** * Game Code ****/ // Simple spell system - no complex classes needed /**** * Constants & Configuration ****/ // Unified Game Manager for all simple operations // Import tween plugin to ensure it's available throughout game code function _typeof2(o) { "@babel/helpers - typeof"; return _typeof2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof2(o); } var availableSpells = [{ id: 'fireball', name: 'FIREBALL', damage: 150, manaCost: 30, rarity: 'common', description: 'Lanza bola de fuego explosiva' }, { id: 'heal', name: 'HEAL', healing: 50, manaCost: 25, rarity: 'common', description: 'Restaura puntos de salud' }, { id: 'lightning', name: 'LIGHTNING', damage: 200, manaCost: 40, rarity: 'rare', description: 'Cadena de rayos entre enemigos' }]; // PASO 2: Función de validación de maná mejorada function validateManaSystem() { var manaWasCorrupted = false; console.log('=== VALIDATING MANA SYSTEM ==='); console.log('currentMana (before):', currentMana, _typeof2(currentMana)); console.log('activeSpellDeck.currentMana (before):', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined'); // CRÍTICO: Validar currentMana con detección de corrupción if (typeof currentMana !== 'number' || isNaN(currentMana) || currentMana < 0 || currentMana > 100) { console.log('⚠️ currentMana corrupted, resetting to 100'); currentMana = 100; manaWasCorrupted = true; } // CRÍTICO: Validar activeSpellDeck existe y tiene propiedades válidas if (!activeSpellDeck) { console.log('⚠️ activeSpellDeck missing, creating with full mana'); activeSpellDeck = { currentMana: 100, maxMana: 100 }; manaWasCorrupted = true; } // CRÍTICO: Validar activeSpellDeck.currentMana if (typeof activeSpellDeck.currentMana !== 'number' || isNaN(activeSpellDeck.currentMana) || activeSpellDeck.currentMana < 0 || activeSpellDeck.currentMana > 100) { console.log('⚠️ activeSpellDeck.currentMana corrupted, resetting to 100'); activeSpellDeck.currentMana = 100; activeSpellDeck.maxMana = 100; manaWasCorrupted = true; } // CRÍTICO: Sincronizar si hay diferencias entre las dos variables if (Math.abs(currentMana - activeSpellDeck.currentMana) > 1) { console.log('⚠️ Mana desync detected - currentMana:', currentMana, 'activeSpellDeck.currentMana:', activeSpellDeck.currentMana); // Usar el valor más alto pero válido var validMana = Math.max(currentMana, activeSpellDeck.currentMana); if (validMana < 0 || validMana > 100 || typeof validMana !== 'number' || isNaN(validMana)) { validMana = 100; } currentMana = validMana; activeSpellDeck.currentMana = validMana; manaWasCorrupted = true; console.log('✅ Mana synced to:', validMana); } // CRÍTICO: Actualizar UI si hubo corrupción if (manaWasCorrupted) { console.log('🔧 Mana corruption detected and fixed'); // Mana UI updates handled by deck menu system console.log('✅ Mana system repaired - currentMana:', currentMana, 'activeSpellDeck.currentMana:', activeSpellDeck.currentMana); } else { console.log('✅ Mana system healthy - no corruption detected'); } console.log('=== MANA VALIDATION COMPLETE ==='); return !manaWasCorrupted; } // Simplified spell visual effects system var SpellVisualEffects = { // Simplified pre-cast effects - basic flash only createPreCastEffects: function createPreCastEffects(spellType, wizard) { // Basic flash effect for all spells LK.effects.flashObject(wizard, this.getSpellColor(spellType), 300); }, // Get spell color for basic effects getSpellColor: function getSpellColor(spellType) { if (spellType === 'fireball') return 0xFF4500; if (spellType === 'lightning') return 0x00FFFF; if (spellType === 'heal') return 0x00FF88; return 0xFFFFFF; }, // Simplified casting aura - single flash effect createCastingAura: function createCastingAura(wizard, color) { LK.effects.flashObject(wizard, color, 500); }, // Simplified spell ring - basic visual feedback createSpellRing: function createSpellRing(wizard, color) { // Simple screen flash instead of complex ring LK.effects.flashScreen(color, 200); } }; // Simple spell casting functions function _canCastSpell(spellId) { // PASO 1: Validar sistema de maná antes de verificar hechizos validateManaSystem(); // Validate spell ID and existence if (!spellId) { console.log('_canCastSpell: No spell ID provided'); return false; } var spell = _getSpell(spellId); if (!spell) { console.log('_canCastSpell: Spell not found for ID:', spellId); return false; } // Check mana cost with validated mana system var manaCost = spell.manaCost || 0; if (currentMana < manaCost) { console.log('_canCastSpell: Not enough mana. Required:', manaCost, 'Current:', currentMana); return false; } // Check individual card cooldown var currentTick = LK.ticks || 0; if (cardCooldowns[spellId] && currentTick < cardCooldowns[spellId]) { console.log('_canCastSpell: Card on cooldown:', spellId); return false; } console.log('_canCastSpell: Spell can be cast:', spellId); return true; } function castFireball() { console.log('=== CASTING FIREBALL ==='); console.log('Current mana before cast:', currentMana); console.log('activeSpellDeck.currentMana before cast:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined'); var allEnemies = collisionArrayPool.getAllEnemies(); var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[i]; if (enemy.isDying) continue; var dx = enemy.x - wizard.x; var dy = enemy.y - wizard.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } // Simplified fireball casting effects LK.effects.flashObject(wizard, 0xFF4500, 300); LK.effects.flashScreen(0xFF4500, 200); if (closestEnemy) { // Simplified fireball trail effect - single trail element var simpleTrail = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: wizard.x, y: wizard.y, scaleX: 1.5, scaleY: 1.5 })); simpleTrail.tint = 0xFF4500; simpleTrail.alpha = 0.7; simpleTrail.zIndex = 1610; // Simple trail animation toward enemy tween(simpleTrail, { x: closestEnemy.x, y: closestEnemy.y, alpha: 0, scaleX: 2.5, scaleY: 2.5 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (simpleTrail.parent) simpleTrail.destroy(); } }); // Create physical projectile that travels var fireball = ProjectileFactory.createProjectile('fireBall', wizard.x, wizard.y, closestEnemy.x, closestEnemy.y, { targetEnemy: closestEnemy, damage: 150 }); // Add environmental lighting effect around the fireball path var pathLight = game.addChild(LK.getAsset('spellCardBg', { anchorX: 0.5, anchorY: 0.5, x: wizard.x, y: wizard.y, scaleX: 8, scaleY: 2 })); pathLight.tint = 0xFF4500; pathLight.alpha = 0.2; pathLight.zIndex = 1580; // Light follows the fireball path var pathAngle = Math.atan2(closestEnemy.y - wizard.y, closestEnemy.x - wizard.x); pathLight.rotation = pathAngle; tween(pathLight, { alpha: 0, scaleY: 4 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { if (pathLight.parent) pathLight.destroy(); } }); LK.getSound('fireWhoosh').play(); showSpellDescription('FIREBALL', 'Daño: 150 al enemigo más cercano', 0xFF4500); } else { showSpellDescription('FIREBALL', 'No hay enemigos cerca', 0xFF4500); } // Consume mana var spell = _getSpell('fireball'); var manaCost = spell ? spell.manaCost : 30; currentMana = Math.max(0, currentMana - manaCost); if (activeSpellDeck) { activeSpellDeck.currentMana = currentMana; } updateManaDisplay(); cardCooldowns['fireball'] = LK.ticks + cardCooldownDuration; LK.getSound('spellCast').play(); return true; } function castLightning() { console.log('=== CASTING LIGHTNING CHAIN ==='); console.log('Current mana before cast:', currentMana); console.log('activeSpellDeck.currentMana before cast:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined'); // Basic lightning casting effect LK.effects.flashObject(wizard, 0x00FFFF, 300); LK.effects.flashScreen(0x00FFFF, 200); LK.getSound('iceFreeze').play(); var allEnemies = collisionArrayPool.getAllEnemies(); var livingEnemies = []; for (var e = 0; e < allEnemies.length; e++) { var enemy = allEnemies[e]; if (!enemy.isDying && enemy.health > 0 && enemy.parent) { var dx = enemy.x - wizard.x; var dy = enemy.y - wizard.y; enemy.distanceFromWizard = Math.sqrt(dx * dx + dy * dy); livingEnemies.push(enemy); } } // Sort by distance for logical chaining livingEnemies.sort(function (a, b) { return a.distanceFromWizard - b.distanceFromWizard; }); var targetsHit = Math.min(3, livingEnemies.length); console.log('Lightning chaining to', targetsHit, 'enemies'); if (targetsHit > 0) { // Apply damage to all targets simultaneously for (var i = 0; i < targetsHit; i++) { var enemy = livingEnemies[i]; console.log('Lightning hitting enemy', i + 1, 'with 200 damage'); if (enemy && enemy.parent && !enemy.isDying) { enemy.health -= 200; } } // Process deaths and visual effects after all damage applied for (var i = 0; i < targetsHit; i++) { var enemy = livingEnemies[i]; if (enemy && enemy.parent && !enemy.isDying) { LK.effects.flashObject(enemy, 0x00FFFF, 600); if (enemy.health <= 0) { enemy.die(); } // Create lightning impact on each enemy var lightningImpact = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y, scaleX: 3, scaleY: 3 })); lightningImpact.tint = 0x00FFFF; lightningImpact.alpha = 1.0; tween(lightningImpact, { scaleX: 7, scaleY: 7, alpha: 0, rotation: Math.PI * 3 }, { duration: 800, delay: i * 150, easing: tween.easeOut, onFinish: function onFinish() { if (lightningImpact.parent) lightningImpact.destroy(); } }); } } // Create lightning chain visual effects var chainPoints = [{ x: wizard.x, y: wizard.y }]; for (var i = 0; i < targetsHit; i++) { chainPoints.push({ x: livingEnemies[i].x, y: livingEnemies[i].y }); } for (var i = 0; i < chainPoints.length - 1; i++) { var startPoint = chainPoints[i]; var endPoint = chainPoints[i + 1]; var dx = endPoint.x - startPoint.x; var dy = endPoint.y - startPoint.y; var distance = Math.sqrt(dx * dx + dy * dy); var lightningBolt = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: (startPoint.x + endPoint.x) / 2, y: (startPoint.y + endPoint.y) / 2, scaleX: 2, scaleY: distance / 40 })); lightningBolt.tint = 0x00FFFF; lightningBolt.alpha = 1.0; lightningBolt.rotation = Math.atan2(dy, dx) + Math.PI / 2; tween(lightningBolt, { alpha: 0, scaleX: 0.5 }, { duration: 700, delay: i * 150, easing: tween.easeOut, onFinish: function onFinish() { if (lightningBolt.parent) lightningBolt.destroy(); } }); } showSpellDescription('LIGHTNING', 'Cadena de ' + targetsHit + ' rayos - Daño: 200', 0x00FFFF); } else { showSpellDescription('LIGHTNING', 'No hay enemigos para la cadena', 0x00FFFF); } // Consume mana var spell = _getSpell('lightning'); var manaCost = spell ? spell.manaCost : 40; currentMana = Math.max(0, currentMana - manaCost); if (activeSpellDeck) { activeSpellDeck.currentMana = currentMana; } updateManaDisplay(); cardCooldowns['lightning'] = LK.ticks + cardCooldownDuration; LK.getSound('spellCast').play(); return true; } function castHeal() { console.log('=== CASTING HEAL ==='); console.log('Current mana before cast:', currentMana); console.log('activeSpellDeck.currentMana before cast:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined'); var healthBefore = wizard.health; wizard.health = Math.min(wizard.health + 50, wizard.maxHealth); var actualHealing = wizard.health - healthBefore; updateHealthBar(); // Basic healing effects LK.effects.flashScreen(0x00FF00, 300); LK.effects.flashObject(wizard, 0x00FF00, 500); // Create floating heal text var healText = new Text2('+' + actualHealing + ' HP', { size: 120, fill: 0x00FF00, font: "monospace" }); healText.anchor.set(0.5, 0.5); healText.x = wizard.x; healText.y = wizard.y - 150; game.addChild(healText); tween(healText, { y: healText.y - 300, alpha: 0, scaleX: 2.0, scaleY: 2.0 }, { duration: 2500, easing: tween.easeOut, onFinish: function onFinish() { if (healText.parent) healText.destroy(); } }); if (actualHealing > 0) { showSpellDescription('HEAL', 'Curado: ' + actualHealing + ' HP', 0x00FF00); } else { showSpellDescription('HEAL', 'Salud completa', 0x00FF00); } // Consume mana var spell = _getSpell('heal'); var manaCost = spell ? spell.manaCost : 25; currentMana = Math.max(0, currentMana - manaCost); if (activeSpellDeck) { activeSpellDeck.currentMana = currentMana; } updateManaDisplay(); cardCooldowns['heal'] = LK.ticks + cardCooldownDuration; LK.getSound('spellCast').play(); return true; } function _getSpell(spellId) { for (var i = 0; i < availableSpells.length; i++) { if (availableSpells[i].id === spellId) { return availableSpells[i]; } } return null; } function _getRarityColor(rarity) { return rarity === 'rare' ? 0x0080FF : 0xFFFFFF; } var GameManager = { updateEntity: function updateEntity(entity) { if (entity && entity.update && typeof entity.update === 'function') { entity.update(); } }, createDamageText: function createDamageText(x, y, damage) { var damageText = new Text2('-' + damage, { size: 120, fill: 0xFF4444, font: "monospace" }); damageText.anchor.set(0.5, 0.5); damageText.x = x; damageText.y = y - 40; game.addChild(damageText); tween(damageText, { y: y - 120, alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { damageText.destroy(); } }); }, createFlashEffect: function createFlashEffect(target, color, duration) { LK.effects.flashObject(target, color, duration); }, createObject: function createObject(type, config) { if (type === 'coin') return new Coin(); if (type === 'enemy') return new Enemy(config); if (type === 'projectile') return new Projectile(config); return null; }, createVisualEffect: function createVisualEffect(type, target, config) { // Simple visual effect creation if (type === 'explosion') { var explosion = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: target.x, y: target.y, scaleX: config.explosionScale || 2, scaleY: config.explosionScale || 2 })); explosion.tint = config.explosionColor || 0xFFFFFF; explosion.alpha = 0.8; tween(explosion, { scaleX: (config.explosionScale || 2) * 1.5, scaleY: (config.explosionScale || 2) * 1.5, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { explosion.destroy(); } }); } } }; function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } // Usar valores directos en lugar de configuraciones // Usar valores directos en lugar de configuraciones complejas // Usar valores directos en lugar de configuraciones complejas // Usar valores directos en lugar de configuraciones complejas // Usar valores directos en lugar de configuraciones complejas /**** * Global State Management ****/ // Simplified global state - no gameState object needed var gameStarted = false; var selectedEnemy = null; var coinCounter = 0; var enemyKillCounter = 0; var pathLastSpawnTime = [-1, -1, -1, -1, -1]; var pathConsecutiveSpawns = [0, 0, 0, 0, 0]; var lastSpawnedPath = -1; /**** * Global Systems ****/ // Unified game management system var gameManager = GameManager; // Simple Collision Array Pool var collisionArrayPool = { allEnemies: [], clearArray: function clearArray(arrayName) { if (this[arrayName]) { this[arrayName].length = 0; } return this[arrayName]; }, getAllEnemies: function getAllEnemies() { var array = this.clearArray('allEnemies'); for (var i = 0; i < enemies.length; i++) { array.push(enemies[i]); } for (var i = 0; i < ogres.length; i++) { array.push(ogres[i]); } for (var i = 0; i < knights.length; i++) { array.push(knights[i]); } for (var i = 0; i < miniBosses.length; i++) { array.push(miniBosses[i]); } return array; } }; /**** * Unified Projectile Manager ****/ var ProjectileFactory = { // Simplified projectile creation - all types use same Projectile class createProjectile: function createProjectile(type, startX, startY, targetX, targetY, config) { var projectile = new Projectile(type); projectile.x = startX; projectile.y = startY; if (config) { for (var key in config) projectile[key] = config[key]; } if (targetX !== undefined && targetY !== undefined) { var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { projectile.direction.x = dx / distance; projectile.direction.y = dy / distance; } } game.addChild(projectile); projectiles.push(projectile); return projectile; }, createBasicAttack: function createBasicAttack(wizard, enemy) { return this.createProjectile('projectile', wizard.x, wizard.y, enemy.x, enemy.y, { targetEnemy: enemy, damage: 100 }); }, createSpellProjectile: function createSpellProjectile(spellType, wizard, targetX, targetY) { return this.createProjectile(spellType, wizard.x, wizard.y, targetX, targetY, { damage: 150 }); }, removeProjectile: function removeProjectile(projectile) { for (var i = projectiles.length - 1; i >= 0; i--) { if (projectiles[i] === projectile) { projectiles.splice(i, 1); break; } } if (projectile.parent) projectile.destroy(); } }; // Removed globalDamageHandler and globalEffectManager aliases - use GameManager directly /**** * Game Objects (Legacy compatibility) ****/ // Direct global arrays - no gameState needed var enemies = []; var ogres = []; var knights = []; var miniBosses = []; var coins = []; var projectiles = []; // Single game menu object var gameMenu; // Create tutorial system first (initially hidden) var tutorial = game.addChild(new Tutorial()); tutorial.visible = false; // Create and show game menu gameMenu = game.addChild(new GameMenu()); // Create toggle button for in-game card panel var cardToggleButton = LK.getAsset('spellCard', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0 }); LK.gui.topRight.addChild(cardToggleButton); cardToggleButton.x = -80; cardToggleButton.y = 80; cardToggleButton.tint = 0x4a0e4e; cardToggleButton.visible = false; var cardToggleText = new Text2('CARTAS', { size: 35, fill: 0xFFFFFF, font: "monospace" }); cardToggleText.anchor.set(0.5, 0.5); cardToggleText.x = -80; cardToggleText.y = 80; LK.gui.topRight.addChild(cardToggleText); cardToggleText.visible = false; // Add interaction to toggle button cardToggleButton.down = function (x, y, obj) { // Visual feedback for button press LK.effects.flashObject(obj, 0x4169E1, 200); tween(obj, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(obj, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeIn }); } }); toggleCardPanel(); }; // Simple spell system variables var currentDeck = storage.spellDeck || ['fireball', 'heal', 'lightning']; var maxMana = 100; var currentMana = 100; // Starting mana var manaRegenRate = 1; // Mana regenerated per second (60 ticks) var manaRegenTimer = 0; var spellCooldowns = {}; var cardCooldowns = {}; // Track cooldown for each card var cardCooldownDuration = 300; // 5 seconds at 60fps storage.spellDeck = currentDeck.slice(); // PASO 1: Verificar que activeSpellDeck existe console.log('=== VERIFYING SYSTEMS AT STARTUP ==='); console.log('currentDeck:', currentDeck); console.log('currentMana:', currentMana); console.log('maxMana:', maxMana); // Mana UI variables removed - handled by deck menu system var activeSpellDeck = { currentDeck: currentDeck.slice(), currentMana: maxMana, maxMana: maxMana, availableSpells: availableSpells, getSpell: function getSpell(spellId) { return _getSpell(spellId); }, canCastSpell: function canCastSpell(spellId) { return _canCastSpell(spellId); }, castSpell: function castSpell(spellId, targetX, targetY) { console.log('=== CASTING SPELL FROM ACTIVE DECK ==='); console.log('Spell ID:', spellId); if (spellId === 'fireball') { return castFireball(); } else if (spellId === 'lightning') { return castLightning(); } else if (spellId === 'heal') { return castHeal(); } else { console.log('Unknown spell ID:', spellId); return false; } }, getRarityColor: function getRarityColor(rarity) { return _getRarityColor(rarity); } }; // PASO 1: Verificar activeSpellDeck después de su creación console.log('activeSpellDeck created successfully:'); console.log('- currentDeck:', activeSpellDeck.currentDeck); console.log('- currentMana:', activeSpellDeck.currentMana); console.log('- maxMana:', activeSpellDeck.maxMana); console.log('- availableSpells count:', activeSpellDeck.availableSpells.length); // Initialize spell unlocking system var lastUnlockCheck = 0; function checkSpellUnlocks() { if (!gameMenu.spellDeck) { gameMenu.spellDeck = new SpellDeck(); } // Only check unlocks when kill counter changes if (enemyKillCounter === lastUnlockCheck) return; lastUnlockCheck = enemyKillCounter; // Unlock spells based on achievements with messages if (enemyKillCounter >= 10 && !storage.lightningUnlocked) { storage.lightningUnlocked = true; gameMenu.spellDeck.unlockSpell('lightning'); LK.effects.flashScreen(0x00FFFF, 500); showSpellUnlockMessage('LIGHTNING', 'Cadena de rayos entre enemigos'); } if (enemyKillCounter >= 25 && !storage.shieldUnlocked) { storage.shieldUnlocked = true; gameMenu.spellDeck.unlockSpell('shield'); LK.effects.flashScreen(0x0080FF, 500); showSpellUnlockMessage('MAGIC SHIELD', 'Inmunidad temporal al daño'); } if (enemyKillCounter >= 50 && !storage.teleportUnlocked) { storage.teleportUnlocked = true; gameMenu.spellDeck.unlockSpell('teleport'); LK.effects.flashScreen(0x8000FF, 500); showSpellUnlockMessage('TELEPORT', 'Mueve instantáneamente al mago'); } if (enemyKillCounter >= 75 && !storage.timeSlowUnlocked) { storage.timeSlowUnlocked = true; gameMenu.spellDeck.unlockSpell('timeSlow'); LK.effects.flashScreen(0xFF8000, 500); showSpellUnlockMessage('TIME SLOW', 'Ralentiza todos los enemigos'); } if (enemyKillCounter >= 100 && !storage.meteorUnlocked) { storage.meteorUnlocked = true; gameMenu.spellDeck.unlockSpell('meteor'); LK.effects.flashScreen(0xFF0000, 500); showSpellUnlockMessage('METEOR', 'Daño masivo en área'); } } function showSpellUnlockMessage(spellName, description) { var unlockText = new Text2('NUEVO HECHIZO DESBLOQUEADO!\n' + spellName + '\n' + description, { size: 60, fill: 0xFFD700, font: "monospace" }); unlockText.anchor.set(0.5, 0.5); unlockText.x = 2048 / 2; unlockText.y = 2732 / 2; game.addChild(unlockText); // Animate unlock message tween(unlockText, { scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { tween(unlockText, { alpha: 0, y: unlockText.y - 200 }, { duration: 1000, easing: tween.easeIn, onFinish: function onFinish() { if (unlockText.parent) unlockText.destroy(); } }); } }); } // Function to show spell description when cast function showSpellDescription(spellName, description, color) { var descText = new Text2(spellName + '\n' + description, { size: 50, fill: color, font: "monospace" }); descText.anchor.set(0.5, 0.5); descText.x = wizard.x; descText.y = wizard.y - 200; game.addChild(descText); // Magical sparkles removed for simplification // Animate description tween(descText, { y: descText.y - 80, alpha: 0 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { if (descText.parent) descText.destroy(); } }); } // Create unified path system - all 5 paths at once var paths = []; var knightX = 2048 / 2; var knightY = 2732 - 250; var pathAngles = [-Math.PI / 2, -Math.PI / 3, -2 * Math.PI / 3, Math.PI / 6, 5 * Math.PI / 6]; var wizardX = knightX; var wizardY = 2732 - 600; // Unified path creation function function createUnifiedPaths() { var spawnPositions = [{ x: 2048 / 2, y: -100 }, { x: 2048 + 50, y: -50 }, { x: -50, y: -50 }, { x: -100, y: 2732 / 2 + 400 }, { x: 2048 + 100, y: 2732 / 2 + 400 }]; var pathAngles = [-Math.PI / 2, -Math.PI / 3, -2 * Math.PI / 3, Math.PI / 6, 5 * Math.PI / 6]; for (var p = 0; p < 5; p++) { var angle = pathAngles[p]; var spawnPos = spawnPositions[p]; var actualPathLength = Math.sqrt((spawnPos.x - wizardX) * (spawnPos.x - wizardX) + (spawnPos.y - wizardY) * (spawnPos.y - wizardY)); // Create stone segments var segmentSize = 80; var numSegments = Math.floor(actualPathLength / segmentSize); for (var s = 0; s < numSegments; s++) { var segmentDistance = s * segmentSize + segmentSize / 2; var segmentX = spawnPos.x - Math.cos(angle) * segmentDistance; var segmentY = spawnPos.y - Math.sin(angle) * segmentDistance; if (segmentX >= -100 && segmentX <= 2148 && segmentY >= -100 && segmentY <= 2832) { var stoneSegment = game.addChild(LK.getAsset('stonePath', { anchorX: 0.5, anchorY: 0.5, x: segmentX, y: segmentY, scaleX: 2.0, scaleY: 2.0, rotation: angle + Math.PI / 2 })); stoneSegment.alpha = 0; stoneSegment.visible = false; stoneSegment.pathIndex = p; } } // Create collision area var centerX = (spawnPos.x + wizardX) / 2; var centerY = (spawnPos.y + wizardY) / 2; var path = game.addChild(LK.getAsset('pathSelector', { anchorX: 0.5, anchorY: 0.5, x: centerX, y: centerY, scaleX: 4, scaleY: actualPathLength / 60, rotation: angle + Math.PI / 2 })); path.alpha = 0; path.visible = false; path.pathIndex = p; // Add path number var pathNumber = new Text2((p + 1).toString(), { size: 120, fill: 0xFFD700, font: "monospace" }); pathNumber.anchor.set(0.5, 0.5); pathNumber.x = spawnPos.x; pathNumber.y = spawnPos.y - 80; pathNumber.visible = false; pathNumber.pathIndex = p; game.addChild(pathNumber); // Add touch handler path.down = function (x, y, obj) { wizard.attack(obj.pathIndex); }; paths.push(path); } } // Create all paths createUnifiedPaths(); // Pixel art scaling handled by engine automatically // Set fondodelacueva as the actual game background var backgroundMap = game.addChild(LK.getAsset('fondodelacueva', { anchorX: 0, anchorY: 0, scaleX: 19.5, scaleY: 26.0, x: 0, y: 0 })); // Send background to the back but use a less extreme z-index backgroundMap.zIndex = -100; // Hide background initially during menu backgroundMap.visible = false; backgroundMap.alpha = 1.0; // Create wizard early to ensure it's available for other classes var wizard = game.addChild(new Wizard()); wizard.x = knightX; wizard.y = 2732 - 600; // Position wizard higher on screen wizard.visible = false; // UI Elements // Removed scoreText and levelText to eliminate stray characters in top right var coinCounter = 0; var enemyKillCounter = 0; var coinText = new Text2('Coins: 0', { size: 60, fill: 0xFFD700, font: "monospace" }); coinText.anchor.set(0, 0); LK.gui.topLeft.addChild(coinText); coinText.x = 120; coinText.y = 90; coinText.visible = false; var killCountText = new Text2('Puntuacion: 0', { size: 60, fill: 0xFFFFFF, font: "monospace" }); killCountText.anchor.set(0, 0); LK.gui.topLeft.addChild(killCountText); killCountText.x = 120; killCountText.y = 50; killCountText.visible = false; var tapText = new Text2('TAP ENEMIES TO ATTACK!', { size: 80, fill: 0x00FF00, font: "monospace" }); tapText.anchor.set(0.5, 0.5); LK.gui.center.addChild(tapText); tapText.y = -400; tapText.visible = false; // Health bar UI var healthBarBg = LK.getAsset('healthBarBg', { anchorX: 0, anchorY: 0, scaleX: 2, scaleY: 1 }); LK.gui.topLeft.addChild(healthBarBg); healthBarBg.x = 120; healthBarBg.y = 20; healthBarBg.visible = false; var healthBar = LK.getAsset('healthBar', { anchorX: 0, anchorY: 0, scaleX: 2, scaleY: 1 }); LK.gui.topLeft.addChild(healthBar); healthBar.x = 120; healthBar.y = 20; healthBar.visible = false; var healthText = new Text2('Health: 100/100', { size: 60, fill: 0xFFFFFF, font: "monospace" }); healthText.anchor.set(0, 0); LK.gui.topLeft.addChild(healthText); healthText.x = 120; healthText.y = 65; healthText.visible = false; // Mana bar UI var manaBarBg = LK.getAsset('manaBarBg', { anchorX: 0, anchorY: 0, scaleX: 2, scaleY: 1 }); LK.gui.topLeft.addChild(manaBarBg); manaBarBg.x = 120; manaBarBg.y = 105; manaBarBg.visible = false; var manaBar = LK.getAsset('manaBar', { anchorX: 0, anchorY: 0, scaleX: 2, scaleY: 1 }); LK.gui.topLeft.addChild(manaBar); manaBar.x = 120; manaBar.y = 105; manaBar.visible = false; var manaText = new Text2('Mana: 100/100', { size: 60, fill: 0x4169E1, font: "monospace" }); manaText.anchor.set(0, 0); LK.gui.topLeft.addChild(manaText); manaText.x = 120; manaText.y = 150; manaText.visible = false; function updateManaDisplay() { var manaPercent = currentMana / maxMana; manaBar.scaleX = manaPercent * 2; manaText.setText('Mana: ' + Math.floor(currentMana) + '/' + maxMana); // Sync with activeSpellDeck if (activeSpellDeck) { activeSpellDeck.currentMana = currentMana; } } function updateHealthBar() { var healthPercent = wizard.health / wizard.maxHealth; healthBar.scaleX = healthPercent; healthText.setText('Health: ' + wizard.health + '/' + wizard.maxHealth); // Change color based on health if (healthPercent > 0.6) { healthBar.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBar.tint = 0xffff00; // Yellow } else { healthBar.tint = 0xff0000; // Red } } // Enemy spawning variables var enemySpawnTimer = 0; var lastSpawnedPath = -1; // Track the last spawned path var consecutiveSpawns = 0; // Track consecutive spawns from same path // Cooldown system variables var pathLastSpawnTime = [-1, -1, -1, -1, -1]; // Track last spawn time for each path var pathConsecutiveSpawns = [0, 0, 0, 0, 0]; // Track consecutive spawns per path var pathCooldownDuration = 300; // 5 seconds at 60fps // Unified SpawnManager for streamlined spawn control and enemy lifecycle var SpawnManager = { // Consolidated spawn configuration spawnConfig: { skeleton: { interval: 90, maxCount: 15, startAt: 0 }, ogre: { interval: 180, maxCount: 4, startAt: 15 }, knight: { interval: 300, maxCount: 3, startAt: 30 }, miniBoss: { interval: 60, maxCount: 1, startAt: 80, endAt: 85, chance: 0.02 } }, // Unified spawn timers spawnTimers: { skeleton: 0, ogre: 0, knight: 0, miniBoss: 0 }, // Streamlined spawn validation canSpawnEnemy: function canSpawnEnemy(type, difficulty, totalEnemies) { var config = this.spawnConfig[type]; if (!config) return false; var collection = []; if (type === 'skeleton') collection = enemies;else if (type === 'ogre') collection = ogres;else if (type === 'knight') collection = knights;else if (type === 'miniBoss') collection = miniBosses; var timer = this.spawnTimers[type]; // Usar valores directos para thresholds var threshold = 0; if (type === 'ogre') threshold = 15;else if (type === 'knight') threshold = 30;else if (type === 'miniBoss') threshold = 80; // Universal spawn conditions with type-specific rules var baseConditions = timer >= config.interval && enemyKillCounter >= threshold && collection.length < config.maxCount && totalEnemies < 25; // Special conditions for specific types if (type === 'miniBoss') { return baseConditions && enemyKillCounter <= config.endAt && miniBosses.length === 0 && Math.random() < config.chance; } return baseConditions && miniBosses.length === 0; }, // Unified spawn execution with specific enemy classes executeSpawn: function executeSpawn(type, difficulty, level) { var totalEnemies = globalEnemyManager.getAllEnemies().length; if (!this.canSpawnEnemy(type, difficulty, totalEnemies)) return null; var enemy = globalEnemyManager.createEnemy(type, difficulty, level); if (enemy) { game.addChild(enemy); // Add directly to global arrays if (type === 'skeleton') enemies.push(enemy);else if (type === 'ogre') ogres.push(enemy);else if (type === 'knight') knights.push(enemy);else if (type === 'miniBoss') miniBosses.push(enemy); this.spawnTimers[type] = 0; // Update path tracking for skeleton spawns if (type === 'skeleton') { pathConsecutiveSpawns[enemy.pathIndex]++; pathLastSpawnTime[enemy.pathIndex] = LK.ticks; lastSpawnedPath = enemy.pathIndex; } } return enemy; }, // Streamlined interval updates updateSpawnIntervals: function updateSpawnIntervals(difficulty, level) { // Usar valores directos para skeleton if (difficulty === 'FACIL') { this.spawnConfig.skeleton.interval = Math.max(60, 120 - level * 5); this.spawnConfig.ogre.interval = 240; this.spawnConfig.knight.interval = 400; } else if (difficulty === 'NORMAL') { this.spawnConfig.skeleton.interval = Math.max(40, 90 - level * 6); this.spawnConfig.ogre.interval = 180; this.spawnConfig.knight.interval = 300; } else { // DIFICIL this.spawnConfig.skeleton.interval = Math.max(20, 60 - level * 4); this.spawnConfig.ogre.interval = 120; this.spawnConfig.knight.interval = 200; } }, // Unified timer updates updateTimers: function updateTimers() { for (var type in this.spawnTimers) { this.spawnTimers[type]++; } // Check if all paths are in cooldown and reset if needed var allPathsInCooldown = true; for (var pathIdx = 0; pathIdx < 5; pathIdx++) { if (pathConsecutiveSpawns[pathIdx] < 2) { allPathsInCooldown = false; break; } } if (allPathsInCooldown) { // Reset all path cooldowns to prevent spawn deadlock for (var pathIdx = 0; pathIdx < 5; pathIdx++) { pathConsecutiveSpawns[pathIdx] = 0; pathLastSpawnTime[pathIdx] = -1; } } }, // Streamlined spawn cycle for all enemy types processSpawnCycle: function processSpawnCycle(difficulty, level) { this.updateSpawnIntervals(difficulty, level); this.updateTimers(); // Clean up any null/destroyed enemies from collections first this.cleanupDestroyedEnemies(); var enemyTypes = ['skeleton', 'ogre', 'knight', 'miniBoss']; for (var i = 0; i < enemyTypes.length; i++) { this.executeSpawn(enemyTypes[i], difficulty, level); } }, // Add cleanup method for destroyed enemies cleanupDestroyedEnemies: function cleanupDestroyedEnemies() { // Clean up global arrays directly var allArrays = [enemies, ogres, knights, miniBosses]; for (var arrayIdx = 0; arrayIdx < allArrays.length; arrayIdx++) { var array = allArrays[arrayIdx]; for (var i = array.length - 1; i >= 0; i--) { if (!array[i] || !array[i].parent || array[i].isDying) { array.splice(i, 1); } } } } }; // Game input handling with spell casting support game.down = function (x, y, obj) { // Removed automatic fireball casting - spells are now manual only through spell slots // Tap-to-attack is now handled directly by individual enemies // No need for path-based attacks since enemies handle their own tap events }; // Initialize unified management systems for streamlined game architecture var globalEnemyManager = new EnemyManager(); var globalDeathHandler = new UnifiedDeathHandler(); // Unified death animation function for all enemy types function createEnemyDeathAnimation(enemy, enemyType, enemyArray) { globalDeathHandler.executeEnemyDeath(enemy, enemyArray); } // In-game spell card panel system var inGameCardPanel = null; var cardPanelVisible = false; var cardPanelElements = []; // Create in-game card panel function createInGameCardPanel() { if (inGameCardPanel) return inGameCardPanel; // Create semi-transparent background panel inGameCardPanel = game.addChild(LK.getAsset('spellCardBg', { anchorX: 0.5, anchorY: 1.0, x: 2048 / 2, y: 2732, scaleX: 20, scaleY: 4 })); inGameCardPanel.tint = 0x1a0a2e; inGameCardPanel.alpha = 0.9; inGameCardPanel.zIndex = 1500; inGameCardPanel.visible = false; return inGameCardPanel; } // Toggle card panel visibility function toggleCardPanel() { if (!gameStarted) return; cardPanelVisible = !cardPanelVisible; if (cardPanelVisible) { showInGameCardPanel(); } else { hideInGameCardPanel(); } } // Show in-game card panel function showInGameCardPanel() { if (!inGameCardPanel) createInGameCardPanel(); // Clear existing card elements clearCardPanelElements(); inGameCardPanel.visible = true; // Get current deck from activeSpellDeck var currentDeck = activeSpellDeck ? activeSpellDeck.currentDeck : ['fireball', 'heal', 'lightning']; // Create cards for current deck for (var i = 0; i < currentDeck.length && i < 5; i++) { var spellId = currentDeck[i]; var spell = _getSpell(spellId); if (!spell) continue; var cardX = 300 + i * 300; var cardY = 2732 - 150; // Create card background var cardBg = game.addChild(LK.getAsset('spellCard', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 2.5, scaleY: 3.0 })); cardBg.tint = _getRarityColor(spell.rarity); cardBg.alpha = 0.9; cardBg.zIndex = 1501; cardBg.spellId = spellId; cardPanelElements.push(cardBg); // Enhanced ready-to-cast visual feedback with glow effects for in-game panel if (!isOnCooldown && _canCastSpell(spellId)) { // Create enhanced magical glow border around ready cards var inGameGlowBorder = game.addChild(LK.getAsset('spellCardBg', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 3.2, scaleY: 3.8 })); inGameGlowBorder.tint = 0x00FF88; inGameGlowBorder.alpha = 0.5; inGameGlowBorder.zIndex = 1501; cardPanelElements.push(inGameGlowBorder); // Sparkle effects removed for simplification // Enhanced ready-to-cast card pulsing animation tween(cardBg, { alpha: 1.0, scaleX: 2.7, scaleY: 3.2 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(cardBg, { alpha: 0.9, scaleX: 2.5, scaleY: 3.0 }, { duration: 1000, easing: tween.easeInOut }); } }); // Animate glow border with enhanced breathing effect tween(inGameGlowBorder, { alpha: 0.7, scaleX: 3.6, scaleY: 4.2 }, { duration: 1300, easing: tween.easeInOut, onFinish: function onFinish() { tween(inGameGlowBorder, { alpha: 0.5, scaleX: 3.2, scaleY: 3.8 }, { duration: 1300, easing: tween.easeInOut }); } }); } // Check if spell can be cast (mana + cooldown validation) var canCast = _canCastSpell(spellId); var currentTick = LK.ticks || 0; var isOnCooldown = cardCooldowns[spellId] && currentTick < cardCooldowns[spellId]; var hasEnoughMana = currentMana >= (spell.manaCost || 0); // Enhanced card status visual feedback system if (isOnCooldown) { // Create enhanced cooldown overlay with animated elements var cooldownOverlay = game.addChild(LK.getAsset('cooldownOverlay', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 2.5, scaleY: 3.0 })); cooldownOverlay.alpha = 0.8; cooldownOverlay.zIndex = 1502; cardPanelElements.push(cooldownOverlay); // Create rotating cooldown progress ring var cooldownRing = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 2.8, scaleY: 2.8 })); cooldownRing.tint = 0x4169E1; cooldownRing.alpha = 0.6; cooldownRing.zIndex = 1502; cardPanelElements.push(cooldownRing); // Animate cooldown ring rotation tween(cooldownRing, { rotation: Math.PI * 2 }, { duration: 2000, easing: tween.linear }); // Show enhanced cooldown time remaining with countdown animation var timeRemaining = Math.ceil((cardCooldowns[spellId] - currentTick) / 60); var cooldownText = new Text2(timeRemaining.toString(), { size: 50, fill: 0xFFFFFF, font: "monospace" }); cooldownText.anchor.set(0.5, 0.5); cooldownText.x = cardX; cooldownText.y = cardY; cooldownText.zIndex = 1503; game.addChild(cooldownText); cardPanelElements.push(cooldownText); // Pulsing countdown animation tween(cooldownText, { scaleX: 1.3, scaleY: 1.3, alpha: 0.7 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(cooldownText, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 500, easing: tween.easeInOut }); } }); // Disable card interaction when on cooldown cardBg.alpha = 0.4; cardBg.tint = 0x666666; // Gray out on cooldown } else if (!hasEnoughMana) { // Enhanced insufficient mana feedback with warning indicators cardBg.alpha = 0.5; cardBg.tint = 0xFF4444; // Red tint for insufficient mana // Create mana warning icon var manaWarning = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: cardX + 70, y: cardY - 70, scaleX: 1.2, scaleY: 1.2 })); manaWarning.tint = 0xFF0000; manaWarning.alpha = 0.9; manaWarning.zIndex = 1502; cardPanelElements.push(manaWarning); // Add warning symbol var warningText = new Text2('!', { size: 60, fill: 0xFFFFFF, font: "monospace" }); warningText.anchor.set(0.5, 0.5); warningText.x = cardX + 70; warningText.y = cardY - 70; warningText.zIndex = 1503; game.addChild(warningText); cardPanelElements.push(warningText); // Urgent warning animation for insufficient mana tween(manaWarning, { scaleX: 1.6, scaleY: 1.6, alpha: 0.5 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { tween(manaWarning, { scaleX: 1.2, scaleY: 1.2, alpha: 0.9 }, { duration: 400, easing: tween.easeIn }); } }); } else if (canCast) { // Step 8: Enhanced ready to cast with contextual spell-specific visual effects cardBg.alpha = 0.9; cardBg.tint = _getRarityColor(spell.rarity); // Create contextual spell-specific aura effects around ready cards if (spellId === 'fireball') { // Fireball: Orange/red flame aura with heat distortions var fireAura = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 3.2, scaleY: 3.8 })); fireAura.tint = 0xFF4500; fireAura.alpha = 0.4; fireAura.zIndex = 1501; cardPanelElements.push(fireAura); // Create flame particles around fireball cards for (var f = 0; f < 5; f++) { var flameParticle = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: cardX + (Math.random() - 0.5) * 100, y: cardY + (Math.random() - 0.5) * 70 + 20, scaleX: 0.3 + Math.random() * 0.3, scaleY: 0.6 + Math.random() * 0.4 })); flameParticle.tint = f % 2 === 0 ? 0xFF4500 : 0xFF6600; flameParticle.alpha = 0.7; flameParticle.zIndex = 1504; cardPanelElements.push(flameParticle); // Flames rise upward with flickering tween(flameParticle, { y: flameParticle.y - 120, alpha: 0, scaleX: (0.3 + Math.random() * 0.3) * 1.5, scaleY: (0.6 + Math.random() * 0.4) * 0.3, rotation: Math.PI * (Math.random() - 0.5) }, { duration: 1500 + f * 200, delay: f * 150, easing: tween.easeOut }); } // Heat wave distortion effect var heatWave = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 2.8, scaleY: 1.5 })); heatWave.tint = 0xFF8800; heatWave.alpha = 0.15; heatWave.zIndex = 1500; cardPanelElements.push(heatWave); // Animate heat distortion tween(heatWave, { scaleY: 2.2, alpha: 0.05 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(heatWave, { scaleY: 1.5, alpha: 0.15 }, { duration: 1000, easing: tween.easeInOut }); } }); } else if (spellId === 'lightning') { // Lightning: Blue/cyan electric aura with static effects var lightningAura = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 3.2, scaleY: 3.8 })); lightningAura.tint = 0x00FFFF; lightningAura.alpha = 0.4; lightningAura.zIndex = 1501; cardPanelElements.push(lightningAura); // Create electric sparks around lightning cards for (var l = 0; l < 6; l++) { var electricSpark = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: cardX + (Math.random() - 0.5) * 120, y: cardY + (Math.random() - 0.5) * 90, scaleX: 0.2 + Math.random() * 0.3, scaleY: 1.0 + Math.random() * 0.8 })); electricSpark.tint = l % 3 === 0 ? 0xFFFFFF : 0x00FFFF; electricSpark.alpha = 0.8; electricSpark.rotation = Math.random() * Math.PI * 2; electricSpark.zIndex = 1504; cardPanelElements.push(electricSpark); // Electric sparks crackle and fade tween(electricSpark, { alpha: 0, scaleX: 0.05, scaleY: 0.3, rotation: electricSpark.rotation + Math.PI * 4 }, { duration: 300 + l * 100, delay: l * 80, easing: tween.easeOut }); } } else if (spellId === 'heal') { // Heal: Gentle green glow with floating plus symbols var healAura = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 3.2, scaleY: 3.8 })); healAura.tint = 0x00FF88; healAura.alpha = 0.3; healAura.zIndex = 1501; cardPanelElements.push(healAura); // Create soft healing particles around heal cards for (var h = 0; h < 4; h++) { var healParticle = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: cardX + (Math.random() - 0.5) * 100, y: cardY + (Math.random() - 0.5) * 70, scaleX: 0.5 + Math.random() * 0.4, scaleY: 0.5 + Math.random() * 0.4 })); healParticle.tint = h % 2 === 0 ? 0x00FF88 : 0x66FFAA; healParticle.alpha = 0.6; healParticle.zIndex = 1504; cardPanelElements.push(healParticle); // Gentle floating upward motion tween(healParticle, { y: healParticle.y - 80, alpha: 0, scaleX: (0.5 + Math.random() * 0.4) * 1.3, scaleY: (0.5 + Math.random() * 0.4) * 1.3, rotation: Math.PI * 0.5 * (Math.random() - 0.5) }, { duration: 2000 + h * 200, delay: h * 250, easing: tween.easeOut }); } // Add floating plus symbol for heal cards var plusSymbol = new Text2('+', { size: 60, fill: 0x00FF88, font: "monospace" }); plusSymbol.anchor.set(0.5, 0.5); plusSymbol.x = cardX + 60; plusSymbol.y = cardY - 60; plusSymbol.alpha = 0.7; plusSymbol.zIndex = 1505; game.addChild(plusSymbol); cardPanelElements.push(plusSymbol); // Gentle floating animation for plus symbol tween(plusSymbol, { y: plusSymbol.y - 60, alpha: 0, scaleX: 1.4, scaleY: 1.4, rotation: Math.PI * 0.3 }, { duration: 1800, easing: tween.easeOut }); } else { // Default magical readiness aura for other spells var readyAura = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: cardX, y: cardY, scaleX: 3.2, scaleY: 3.8 })); readyAura.tint = 0x00FF88; readyAura.alpha = 0.3; readyAura.zIndex = 1501; cardPanelElements.push(readyAura); // Create generic floating particles around ready cards for (var p = 0; p < 3; p++) { var particle = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: cardX + (Math.random() - 0.5) * 120, y: cardY + (Math.random() - 0.5) * 80, scaleX: 0.4, scaleY: 0.4 })); particle.tint = 0x00FFAA; particle.alpha = 0.8; particle.zIndex = 1504; cardPanelElements.push(particle); // Animate floating particles tween(particle, { y: particle.y - 100, alpha: 0, scaleX: 0.8, scaleY: 0.8, rotation: Math.PI }, { duration: 1800, delay: p * 400, easing: tween.easeOut }); } } // Add enhanced ready glow animation tween(cardBg, { alpha: 1.0, scaleX: 2.6, scaleY: 3.1 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(cardBg, { alpha: 0.9, scaleX: 2.5, scaleY: 3.0 }, { duration: 1000, easing: tween.easeInOut }); } }); // Animate aura with gentle pulsing tween(readyAura, { alpha: 0.5, scaleX: 3.6, scaleY: 4.2 }, { duration: 1400, easing: tween.easeInOut, onFinish: function onFinish() { tween(readyAura, { alpha: 0.3, scaleX: 3.2, scaleY: 3.8 }, { duration: 1400, easing: tween.easeInOut }); } }); } else { // Default disabled state cardBg.alpha = 0.6; cardBg.tint = 0x888888; // Gray tint for disabled } // Add enhanced card interaction with advanced touch feedback cardBg.touchStartTime = 0; cardBg.touchStartX = 0; cardBg.touchStartY = 0; cardBg.down = function (x, y, obj) { if (obj.spellId) { // Record touch start for gesture recognition obj.touchStartTime = LK.ticks; obj.touchStartX = x; obj.touchStartY = y; // Enhanced immediate haptic-like visual feedback with multiple effects LK.effects.flashObject(obj, 0x00FFFF, 200); // Create touch impact ripple at exact touch point var touchRipple = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: 0.2, scaleY: 0.2 })); touchRipple.tint = 0x00FFFF; touchRipple.alpha = 0.9; touchRipple.zIndex = 2100; tween(touchRipple, { scaleX: 2.5, scaleY: 2.5, alpha: 0, rotation: Math.PI * 1.5 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (touchRipple.parent) touchRipple.destroy(); } }); // Enhanced multi-stage press animation tween(obj, { scaleX: 2.3, scaleY: 2.7, rotation: Math.PI / 16 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(obj, { scaleX: 2.9, scaleY: 3.4, rotation: -Math.PI / 32 }, { duration: 100, easing: tween.easeInOut, onFinish: function onFinish() { tween(obj, { scaleX: 2.5, scaleY: 3.0, rotation: 0 }, { duration: 120, easing: tween.bounceOut }); } }); } }); // Create particle trail following touch movement for (var t = 0; t < 5; t++) { var trailParticle = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: x + (Math.random() - 0.5) * 20, y: y + (Math.random() - 0.5) * 20, scaleX: 0.3, scaleY: 0.3 })); trailParticle.tint = 0x00AAFF; trailParticle.alpha = 0.7; trailParticle.zIndex = 2099; tween(trailParticle, { x: trailParticle.x + (Math.random() - 0.5) * 80, y: trailParticle.y - 60 - Math.random() * 40, alpha: 0, scaleX: 0.8, scaleY: 0.8, rotation: Math.PI * 2 }, { duration: 600 + t * 100, delay: t * 50, easing: tween.easeOut, onFinish: function onFinish() { if (trailParticle.parent) trailParticle.destroy(); } }); } castSpellFromCard(obj.spellId); } }; // Add touch release handler for gesture recognition cardBg.up = function (x, y, obj) { if (obj.spellId && obj.touchStartTime) { var touchDuration = LK.ticks - obj.touchStartTime; var touchDistance = Math.sqrt(Math.pow(x - obj.touchStartX, 2) + Math.pow(y - obj.touchStartY, 2)); // Detect different touch gestures if (touchDuration < 30 && touchDistance < 50) { // Quick tap - create snap feedback var snapEffect = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: obj.x, y: obj.y, scaleX: 3.0, scaleY: 3.0 })); snapEffect.tint = 0xFFFF00; snapEffect.alpha = 0.6; snapEffect.zIndex = 2101; tween(snapEffect, { scaleX: 0.5, scaleY: 0.5, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { if (snapEffect.parent) snapEffect.destroy(); } }); } else if (touchDuration > 60) { // Long press - create charge effect var chargeEffect = game.addChild(LK.getAsset('spellCardBg', { anchorX: 0.5, anchorY: 0.5, x: obj.x, y: obj.y, scaleX: 4.0, scaleY: 5.0 })); chargeEffect.tint = 0xFF6600; chargeEffect.alpha = 0.4; chargeEffect.zIndex = 2101; tween(chargeEffect, { scaleX: 6.0, scaleY: 7.0, alpha: 0, rotation: Math.PI / 4 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { if (chargeEffect.parent) chargeEffect.destroy(); } }); } } }; // Create card name text var nameText = new Text2(spell.name, { size: 30, fill: 0xFFFFFF, font: "monospace" }); nameText.anchor.set(0.5, 0.5); nameText.x = cardX; nameText.y = cardY - 40; nameText.zIndex = 1502; game.addChild(nameText); cardPanelElements.push(nameText); // Create mana cost indicator if (spell.manaCost) { var manaText = new Text2(spell.manaCost.toString(), { size: 40, fill: 0x4169E1, font: "monospace" }); manaText.anchor.set(0.5, 0.5); manaText.x = cardX + 50; manaText.y = cardY - 80; manaText.zIndex = 1502; game.addChild(manaText); cardPanelElements.push(manaText); } // Create damage/healing indicator var effectText = ''; if (spell.damage) effectText = 'DMG: ' + spell.damage; if (spell.healing) effectText = 'HEAL: ' + spell.healing; if (effectText) { var effectLabel = new Text2(effectText, { size: 25, fill: 0xFFD700, font: "monospace" }); effectLabel.anchor.set(0.5, 0.5); effectLabel.x = cardX; effectLabel.y = cardY + 40; effectLabel.zIndex = 1502; game.addChild(effectLabel); cardPanelElements.push(effectLabel); } } // Create toggle button to hide panel var hideButton = game.addChild(LK.getAsset('coin', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 100, y: 2732 - 300, scaleX: 1.5, scaleY: 1.5 })); hideButton.tint = 0xFF4444; hideButton.zIndex = 1502; hideButton.down = function (x, y, obj) { // Visual feedback for hide button LK.effects.flashObject(obj, 0xFF6666, 200); tween(obj, { scaleX: 1.8, scaleY: 1.8 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(obj, { scaleX: 1.5, scaleY: 1.5 }, { duration: 100, easing: tween.easeIn }); } }); toggleCardPanel(); }; cardPanelElements.push(hideButton); var hideText = new Text2('CERRAR', { size: 40, fill: 0xFFFFFF, font: "monospace" }); hideText.anchor.set(0.5, 0.5); hideText.x = 2048 - 100; hideText.y = 2732 - 300; hideText.zIndex = 1503; game.addChild(hideText); cardPanelElements.push(hideText); } // Hide in-game card panel function hideInGameCardPanel() { if (inGameCardPanel) { inGameCardPanel.visible = false; } clearCardPanelElements(); } // Clear card panel elements function clearCardPanelElements() { for (var i = 0; i < cardPanelElements.length; i++) { if (cardPanelElements[i] && cardPanelElements[i].parent) { cardPanelElements[i].destroy(); } } cardPanelElements = []; } // Targeting system variables var targetingMode = false; var targetingSpell = null; var targetingCursor = null; var targetingRange = null; // Create targeting cursor function createTargetingCursor() { if (targetingCursor) return targetingCursor; targetingCursor = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 2.0 })); targetingCursor.tint = 0x00FFFF; targetingCursor.alpha = 0.8; targetingCursor.zIndex = 2000; targetingCursor.visible = false; // Add orbiting particles around cursor for enhanced feedback targetingCursor.particles = []; for (var p = 0; p < 4; p++) { var particle = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4 })); particle.tint = 0x00FFFF; particle.alpha = 0.6; particle.zIndex = 1999; particle.visible = false; particle.orbitAngle = p * Math.PI * 2 / 4; targetingCursor.particles.push(particle); } return targetingCursor; } // Create targeting range indicator function createTargetingRange() { if (targetingRange) return targetingRange; targetingRange = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, scaleX: 8.0, scaleY: 8.0 })); targetingRange.tint = 0x00FF00; targetingRange.alpha = 0.3; targetingRange.zIndex = 1999; targetingRange.visible = false; return targetingRange; } // Enter targeting mode function enterTargetingMode(spellId) { targetingMode = true; targetingSpell = spellId; // Create targeting visuals createTargetingCursor(); createTargetingRange(); targetingCursor.visible = true; targetingRange.visible = true; // Position range indicator around wizard targetingRange.x = wizard.x; targetingRange.y = wizard.y; // Add pulsing animation to cursor tween(targetingCursor, { scaleX: 2.5, scaleY: 2.5 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { if (targetingCursor && targetingCursor.visible) { tween(targetingCursor, { scaleX: 2.0, scaleY: 2.0 }, { duration: 800, easing: tween.easeInOut }); } } }); // Show targeting instructions showTargetingInstructions(spellId); } // Exit targeting mode function exitTargetingMode() { targetingMode = false; targetingSpell = null; if (targetingCursor) { targetingCursor.visible = false; } if (targetingRange) { targetingRange.visible = false; } // Hide card panel after targeting hideInGameCardPanel(); cardPanelVisible = false; } // Show targeting instructions function showTargetingInstructions(spellId) { var spell = _getSpell(spellId); var instructionText = ''; if (spellId === 'fireball') { instructionText = 'TOCA UN ENEMIGO PARA LANZAR FIREBALL'; } else if (spellId === 'lightning') { instructionText = 'TOCA UN ENEMIGO PARA CADENA DE RAYOS'; } else if (spellId === 'heal') { instructionText = 'TOCA PARA CURARTE'; } else { instructionText = 'SELECCIONA OBJETIVO PARA ' + (spell ? spell.name : 'HECHIZO'); } var targetingInstructions = new Text2(instructionText, { size: 50, fill: 0x00FFFF, font: "monospace" }); targetingInstructions.anchor.set(0.5, 0.5); targetingInstructions.x = 2048 / 2; targetingInstructions.y = 400; targetingInstructions.zIndex = 2001; game.addChild(targetingInstructions); // Auto-remove instructions after 3 seconds tween(targetingInstructions, { alpha: 0 }, { duration: 3000, easing: tween.easeOut, onFinish: function onFinish() { if (targetingInstructions.parent) targetingInstructions.destroy(); } }); } // Execute spell at target location function executeSpellAtTarget(spellId, targetX, targetY, targetEnemy) { console.log('Executing spell', spellId, 'at', targetX, targetY); var success = false; if (spellId === 'fireball') { if (targetEnemy) { // Create targeted fireball var fireball = ProjectileFactory.createProjectile('fireBall', wizard.x, wizard.y, targetEnemy.x, targetEnemy.y, { targetEnemy: targetEnemy, damage: 150 }); LK.getSound('fireWhoosh').play(); showSpellDescription('FIREBALL', 'Daño: 150 dirigido', 0xFF4500); success = true; } } else if (spellId === 'lightning') { if (targetEnemy) { // Execute lightning with target as starting point LK.effects.flashScreen(0x00FFFF, 800); LK.getSound('iceFreeze').play(); var allEnemies = collisionArrayPool.getAllEnemies(); var livingEnemies = []; // Start chain from targeted enemy for (var e = 0; e < allEnemies.length; e++) { var enemy = allEnemies[e]; if (!enemy.isDying && enemy.health > 0 && enemy.parent) { var dx = enemy.x - targetEnemy.x; var dy = enemy.y - targetEnemy.y; enemy.distanceFromTarget = Math.sqrt(dx * dx + dy * dy); livingEnemies.push(enemy); } } // Sort by distance from target livingEnemies.sort(function (a, b) { return a.distanceFromTarget - b.distanceFromTarget; }); var targetsHit = Math.min(3, livingEnemies.length); if (targetsHit > 0) { // Apply lightning damage for (var i = 0; i < targetsHit; i++) { var enemy = livingEnemies[i]; if (enemy && enemy.parent && !enemy.isDying) { enemy.health -= 200; LK.effects.flashObject(enemy, 0x00FFFF, 600); if (enemy.health <= 0) enemy.die(); // Create lightning visual var lightningImpact = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y, scaleX: 3, scaleY: 3 })); lightningImpact.tint = 0x00FFFF; lightningImpact.alpha = 1.0; tween(lightningImpact, { scaleX: 7, scaleY: 7, alpha: 0, rotation: Math.PI * 3 }, { duration: 800, delay: i * 150, easing: tween.easeOut, onFinish: function onFinish() { if (lightningImpact.parent) lightningImpact.destroy(); } }); } } showSpellDescription('LIGHTNING', 'Cadena dirigida: ' + targetsHit + ' rayos', 0x00FFFF); } success = true; } } else if (spellId === 'heal') { // Heal can be cast anywhere, always targets wizard var healthBefore = wizard.health; wizard.health = Math.min(wizard.health + 50, wizard.maxHealth); var actualHealing = wizard.health - healthBefore; updateHealthBar(); // Enhanced healing effects at target location LK.effects.flashScreen(0x00FF00, 500); LK.effects.flashObject(wizard, 0x00FF00, 1000); // Create healing aura at clicked location var healingAura = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: targetX, y: targetY, scaleX: 4, scaleY: 4 })); healingAura.tint = 0x00FF00; healingAura.alpha = 0.8; tween(healingAura, { scaleX: 12, scaleY: 12, alpha: 0 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { if (healingAura.parent) healingAura.destroy(); } }); showSpellDescription('HEAL', 'Curado: ' + actualHealing + ' HP', 0x00FF00); success = true; } if (success) { LK.getSound('spellCast').play(); cardCooldowns[spellId] = LK.ticks + cardCooldownDuration; } return success; } // Cast spell from in-game card function castSpellFromCard(spellId) { console.log('=== CASTING SPELL FROM CARD ==='); console.log('Spell ID:', spellId); console.log('Current mana before cast:', currentMana); console.log('activeSpellDeck.currentMana before cast:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined'); // Check if spell can be cast with enhanced validation if (!_canCastSpell(spellId)) { console.log('Cannot cast spell:', spellId); LK.effects.flashScreen(0xFF0000, 200); return; } // Get spell data for mana consumption var spell = _getSpell(spellId); if (!spell) { console.log('Spell not found for ID:', spellId); return; } // Execute the spell directly using specific casting functions var castSuccess = false; console.log('Executing spell cast for:', spellId); if (spellId === 'fireball') { castSuccess = castFireball(); } else if (spellId === 'lightning') { castSuccess = castLightning(); } else if (spellId === 'heal') { castSuccess = castHeal(); } else { console.log('Unknown spell ID in card cast:', spellId); // Default to fireball for unknown spells castSuccess = castFireball(); } if (castSuccess) { console.log('Spell cast successful:', spellId); // Enhanced contextual success feedback based on spell type if (spellId === 'fireball') { LK.effects.flashScreen(0xFF4500, 400); // Create fire burst around wizard for (var f = 0; f < 6; f++) { var fireBurst = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: wizard.x, y: wizard.y, scaleX: 1.5, scaleY: 1.5 })); fireBurst.tint = 0xFF6600; fireBurst.alpha = 0.8; fireBurst.zIndex = 1700; var angle = f * Math.PI * 2 / 6; tween(fireBurst, { x: wizard.x + Math.cos(angle) * 120, y: wizard.y + Math.sin(angle) * 120, alpha: 0, scaleX: 3.0, scaleY: 3.0, rotation: Math.PI * 2 }, { duration: 600, delay: f * 100, easing: tween.easeOut, onFinish: function onFinish() { if (fireBurst.parent) fireBurst.destroy(); } }); } } else if (spellId === 'lightning') { LK.effects.flashScreen(0x00FFFF, 500); // Create electric arcs around wizard for (var l = 0; l < 8; l++) { var lightningArc = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: wizard.x + (Math.random() - 0.5) * 200, y: wizard.y + (Math.random() - 0.5) * 200, scaleX: 0.8, scaleY: 3.0 })); lightningArc.tint = 0x00FFFF; lightningArc.alpha = 0.9; lightningArc.rotation = Math.random() * Math.PI * 2; lightningArc.zIndex = 1700; tween(lightningArc, { alpha: 0, scaleX: 0.3, scaleY: 4.0 }, { duration: 300, delay: l * 50, easing: tween.easeOut, onFinish: function onFinish() { if (lightningArc.parent) lightningArc.destroy(); } }); } } else if (spellId === 'heal') { LK.effects.flashScreen(0x00FF00, 350); // Create healing aura waves for (var h = 0; h < 4; h++) { var healWave = game.addChild(LK.getAsset('spellCardBg', { anchorX: 0.5, anchorY: 0.5, x: wizard.x, y: wizard.y, scaleX: 1.0 + h * 0.5, scaleY: 1.0 + h * 0.5 })); healWave.tint = 0x00FF88; healWave.alpha = 0.6 - h * 0.1; healWave.zIndex = 1700; tween(healWave, { scaleX: 8.0 + h * 2, scaleY: 8.0 + h * 2, alpha: 0 }, { duration: 1200 + h * 200, delay: h * 150, easing: tween.easeOut, onFinish: function onFinish() { if (healWave.parent) healWave.destroy(); } }); } } else { // Default success feedback LK.effects.flashScreen(0x00FF00, 300); } // Create spell success text with enhanced animation var successText = new Text2('SPELL CAST!', { size: 80, fill: 0xFFD700, font: "monospace" }); successText.anchor.set(0.5, 0.5); successText.x = wizard.x; successText.y = wizard.y - 180; successText.zIndex = 1701; game.addChild(successText); tween(successText, { y: successText.y - 100, alpha: 0, scaleX: 1.5, scaleY: 1.5, rotation: Math.PI / 8 }, { duration: 1200, easing: tween.easeOut, onFinish: function onFinish() { if (successText.parent) successText.destroy(); } }); // Hide card panel after successful cast hideInGameCardPanel(); cardPanelVisible = false; } else { console.log('Spell cast failed:', spellId); // Enhanced failure feedback with shake effect LK.effects.flashScreen(0xFF0000, 300); // Create failure warning var failureText = new Text2('CAST FAILED!', { size: 60, fill: 0xFF4444, font: "monospace" }); failureText.anchor.set(0.5, 0.5); failureText.x = wizard.x; failureText.y = wizard.y - 150; failureText.zIndex = 1701; game.addChild(failureText); // Shake and fade failure text var originalFailX = failureText.x; tween(failureText, { x: originalFailX - 10 }, { duration: 100, easing: tween.easeInOut, onFinish: function onFinish() { tween(failureText, { x: originalFailX + 10 }, { duration: 100, easing: tween.easeInOut, onFinish: function onFinish() { tween(failureText, { x: originalFailX, alpha: 0, y: failureText.y - 80 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (failureText.parent) failureText.destroy(); } }); } }); } }); } } // Add targeting system to game mouse/touch handling game.move = function (x, y, obj) { if (targetingMode && targetingCursor) { // Update cursor position to follow mouse/touch targetingCursor.x = x; targetingCursor.y = y; // Calculate if target is in range var dx = x - wizard.x; var dy = y - wizard.y; var distance = Math.sqrt(dx * dx + dy * dy); var maxRange = 400; // Maximum spell range // Change cursor color based on range if (distance <= maxRange) { targetingCursor.tint = 0x00FF00; // Green for in range targetingCursor.alpha = 0.8; } else { targetingCursor.tint = 0xFF0000; // Red for out of range targetingCursor.alpha = 0.5; } } }; game.down = function (x, y, obj) { // Simple tap to attack handling - no spell casting interference // All spell casting is now handled through the in-game card panel system }; // Main game update loop game.update = function () { // Sort children by z-index to ensure proper rendering order game.children.sort(function (a, b) { return (a.zIndex || 0) - (b.zIndex || 0); }); // Pause game when tutorial is active if (tutorial && tutorial.isActive) { return; } // Only update game logic if game has started if (!gameStarted) { return; } // Change music to epic battle theme at 10 enemies killed if (enemyKillCounter === 10) { LK.playMusic('epicBattle', { volume: 0.8, fade: { start: 0, end: 0.8, duration: 1500 } }); } // Change to mystical ambient music at 25 enemies killed if (enemyKillCounter === 25) { LK.playMusic('mysticalAmbient', { volume: 0.6, fade: { start: 0, end: 0.6, duration: 2000 } }); } // Upgrade menus removed - using spell deck system instead // Reset consecutive spawns for paths that have cooled down (runs every frame) for (var pathIdx = 0; pathIdx < 5; pathIdx++) { // Check if enough time has passed since last spawn (cooldown expired) if (pathLastSpawnTime[pathIdx] !== -1 && LK.ticks - pathLastSpawnTime[pathIdx] > pathCooldownDuration) { // Reset consecutive spawns for this path due to cooldown pathConsecutiveSpawns[pathIdx] = 0; } } // Get stored difficulty setting var selectedDifficulty = storage.difficulty || 'NORMAL'; var difficultyLevel = Math.floor(enemyKillCounter / 10); // Every 10 kills increases difficulty // Helper functions now integrated into EnemyFactory // Unified spawn management system SpawnManager.processSpawnCycle(selectedDifficulty, difficultyLevel); // Reset path cooldowns for optimized path management for (var pathIdx = 0; pathIdx < 5; pathIdx++) { if (pathLastSpawnTime[pathIdx] !== -1 && LK.ticks - pathLastSpawnTime[pathIdx] > pathCooldownDuration) { pathConsecutiveSpawns[pathIdx] = 0; } } // Unified CollisionManager for streamlined collision detection and response var CollisionManager = { // Consolidated collision configurations collisionConfig: { skeleton: { damage: 20, removeOnHit: true }, ogre: { damage: 30, removeOnHit: true }, knight: { damage: 40, removeOnHit: true }, miniBoss: { damage: 75, removeOnHit: false } }, // Streamlined enemy collision processing with categorized collision types processEnemyCollisions: function processEnemyCollisions() { var allEnemies = globalEnemyManager.getAllEnemies(); // Category 1: Off-screen cleanup (non-collision processing) this.processOffScreenCleanup(allEnemies); // Category 2: Enemy-wizard collisions this.processEnemyWizardCollisions(allEnemies); }, // Separate processing for off-screen enemy cleanup processOffScreenCleanup: function processOffScreenCleanup(allEnemies) { for (var i = allEnemies.length - 1; i >= 0; i--) { var enemy = allEnemies[i]; if (this.isOffScreen(enemy)) { this.removeEnemyFromGame(enemy); } } }, // Separate processing for enemy-wizard collisions processEnemyWizardCollisions: function processEnemyWizardCollisions(allEnemies) { for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[i]; if (!enemy.isDying && enemy.parent) { this.checkWizardCollision(enemy); } } }, // Efficient off-screen detection isOffScreen: function isOffScreen(enemy) { return enemy.y > 2732 + 100; }, // Unified enemy removal system removeEnemyFromGame: function removeEnemyFromGame(enemy) { // Remove from global arrays directly this.removeFromLegacyArrays(enemy); enemy.destroy(); }, // Legacy array compatibility cleanup removeFromLegacyArrays: function removeFromLegacyArrays(enemy) { var arrays = [enemies, ogres, knights, miniBosses]; for (var a = 0; a < arrays.length; a++) { var array = arrays[a]; for (var i = array.length - 1; i >= 0; i--) { if (array[i] === enemy) { array.splice(i, 1); break; } } } }, // 1.1 Distance Culling: Enhanced wizard collision detection with optimized distance-based culling checkWizardCollision: function checkWizardCollision(enemy) { // Initialize collision tracking if (enemy.lastIntersecting === undefined) { enemy.lastIntersecting = false; } // 1.1 Distance Culling: Skip expensive intersection test if objects are too far apart var dx = enemy.x - wizard.x; var dy = enemy.y - wizard.y; var distance = Math.sqrt(dx * dx + dy * dy); var maxCollisionDistance = 150; // Approximate maximum collision distance based on sprite sizes var currentIntersecting = false; if (distance <= maxCollisionDistance) { // Only perform expensive intersection test if objects are close enough currentIntersecting = wizard.intersects(enemy); } // Check collision transition if (!enemy.lastIntersecting && currentIntersecting && !enemy.isDying) { var config = this.getEnemyConfig(enemy); wizard.takeDamage(config.damage); // Handle enemy removal based on type if (config.removeOnHit) { this.removeEnemyFromGame(enemy); return; } } // Update collision state enemy.lastIntersecting = currentIntersecting; }, // Get enemy configuration by type getEnemyConfig: function getEnemyConfig(enemy) { return this.collisionConfig[enemy.enemyType] || this.collisionConfig.skeleton; } }; // Replace the old collision function call function checkAllEnemyCollisions() { CollisionManager.processEnemyCollisions(); } // Call the unified collision detection function checkAllEnemyCollisions(); // Check thorns spike collisions with all enemies continuously var allSpikes = []; for (var childIdx = 0; childIdx < game.children.length; childIdx++) { var child = game.children[childIdx]; // Check if this child is a spike (has hitEnemies array and brown tint) if (child.hitEnemies && child.tint === 0x8B4513) { allSpikes.push(child); } } for (var spikeIdx = 0; spikeIdx < allSpikes.length; spikeIdx++) { var spike = allSpikes[spikeIdx]; var allEnemies = collisionArrayPool.getAllEnemies(); for (var enemyIdx = 0; enemyIdx < allEnemies.length; enemyIdx++) { var enemy = allEnemies[enemyIdx]; // Only hit enemies that haven't been hit by this spike yet and are not dying if (spike.intersects(enemy) && spike.hitEnemies.indexOf(enemy) === -1 && !enemy.isDying) { var thornDamage = 100; // Always deal 100 damage enemy.takeDamage(thornDamage); LK.effects.flashObject(enemy, 0x8B4513, 300); // Mark this enemy as hit by this spike spike.hitEnemies.push(enemy); } } } // Update spell system if (gameStarted) { // Mana regeneration system manaRegenTimer++; if (manaRegenTimer >= 60) { // Every second (60 ticks at 60fps) manaRegenTimer = 0; currentMana = Math.min(maxMana, currentMana + manaRegenRate); if (activeSpellDeck) { activeSpellDeck.currentMana = currentMana; } updateManaDisplay(); } // Ensure mana bounds are correct currentMana = Math.max(0, Math.min(maxMana, currentMana)); if (activeSpellDeck) { activeSpellDeck.currentMana = currentMana; } } // Simple time slow effects processing var allEnemies = collisionArrayPool.getAllEnemies(); for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[i]; if (enemy.timeSlowed) { enemy.timeSlowTimer--; if (enemy.timeSlowTimer <= 0) { enemy.timeSlowed = false; enemy.timeSlowAmount = 1.0; } } } // Make tap text pulse var pulse = 1 + Math.sin(LK.ticks * 0.1) * 0.2; tapText.scale.set(pulse, pulse); // Update targeting cursor animation if (targetingMode && targetingCursor && targetingCursor.visible) { // Rotate targeting cursor targetingCursor.rotation += 0.05; // Add secondary pulsing animation var targetPulse = 1 + Math.sin(LK.ticks * 0.2) * 0.3; if (targetingCursor.tint === 0x00FF00) { // In-range pulsing targetingCursor.alpha = 0.6 + targetPulse * 0.2; } else { // Out-of-range warning pulse targetingCursor.alpha = 0.3 + targetPulse * 0.4; } // Update orbiting particles around cursor if (targetingCursor.particles) { for (var p = 0; p < targetingCursor.particles.length; p++) { var particle = targetingCursor.particles[p]; if (particle && particle.parent) { particle.orbitAngle += 0.08; var orbitRadius = 40 + Math.sin(LK.ticks * 0.1) * 10; particle.x = targetingCursor.x + Math.cos(particle.orbitAngle) * orbitRadius; particle.y = targetingCursor.y + Math.sin(particle.orbitAngle) * orbitRadius; particle.visible = targetingCursor.visible; particle.tint = targetingCursor.tint; particle.alpha = targetingCursor.alpha * 0.7; } } } } // Update targeting range indicator if (targetingMode && targetingRange && targetingRange.visible) { // Gentle pulsing for range indicator var rangePulse = 1 + Math.sin(LK.ticks * 0.1) * 0.1; targetingRange.scaleX = 8.0 * rangePulse; targetingRange.scaleY = 8.0 * rangePulse; targetingRange.alpha = 0.2 + Math.sin(LK.ticks * 0.15) * 0.1; } // Check for spell unlocks checkSpellUnlocks(); // Clean up any orphaned projectiles that may not have been properly removed for (var i = projectiles.length - 1; i >= 0; i--) { var projectile = projectiles[i]; if (!projectile || !projectile.parent || projectile.hitEnemy) { projectiles.splice(i, 1); } } // ProjectileFactory uses the global projectiles array, no separate activeProjectiles array needed }; // Remove tap text after 5 seconds tween({}, {}, { duration: 5000, onFinish: function onFinish() { if (tapText && tapText.parent) { tapText.destroy(); } } });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.bobOffset = Math.random() * Math.PI * 2;
self.initialY = 0;
self.update = function () {
if (self.initialY === 0) {
self.initialY = self.y;
}
// Only do bobbing animation and collection if not animating to coin counter
if (!self.isAnimating) {
// Bobbing animation
self.y = self.initialY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 10;
// Check collection by wizard
if (wizard && self.intersects(wizard)) {
self.collect();
}
}
};
self.collect = function () {
LK.getSound('coinCollect').play();
LK.setScore(LK.getScore() + 5);
coinCounter++;
coinText.setText('Coins: ' + coinCounter);
// Remove from coins array
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i] === self) {
coins.splice(i, 1);
break;
}
}
// Destroy coin directly without object pool
self.destroy();
};
return self;
});
// Unified Enemy class that handles all enemy types
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
// Set enemy type and configure based on it
self.enemyType = type || 'skeleton';
self.currentFrame = 1;
self.animationTimer = 0;
self.animationState = 'walking';
self.frozen = false;
self.frozenTimer = 0;
self.isDying = false;
self.lastX = 0;
self.speedTweenStarted = false;
self.lastIntersecting = false;
// Configure enemy based on type
var config = {
skeleton: {
health: 100,
speed: 3,
damage: 20,
animationSpeed: 15,
assetPrefix: 'esqueleto',
scale: 3.0,
vibration: [100],
tint: null
},
ogre: {
health: 200,
speed: 2.5,
damage: 30,
animationSpeed: 18,
assetPrefix: 'ogre',
scale: 3.5,
vibration: [200],
tint: null
},
knight: {
health: 300,
speed: 2,
damage: 40,
animationSpeed: 20,
assetPrefix: 'knight',
scale: 3.0,
vibration: [150],
tint: null
},
miniBoss: {
health: 3000,
speed: 4,
damage: 75,
animationSpeed: 12,
assetPrefix: 'ogre',
scale: 5.0,
vibration: [300, 100, 300],
tint: 0x8B0000
}
};
var enemyConfig = config[self.enemyType] || config.skeleton;
self.health = enemyConfig.health;
self.maxHealth = self.health;
self.speed = enemyConfig.speed;
self.damage = enemyConfig.damage;
self.animationSpeed = enemyConfig.animationSpeed;
// Create animation frames
self.animationFrames = [];
for (var i = 1; i <= 4; i++) {
var frameGraphics = self.attachAsset(enemyConfig.assetPrefix + i, {
anchorX: 0.5,
anchorY: 1.0,
scaleX: enemyConfig.scale,
scaleY: enemyConfig.scale
});
frameGraphics.visible = i === 1;
if (enemyConfig.tint) {
frameGraphics.tint = enemyConfig.tint;
}
self.animationFrames.push(frameGraphics);
}
// Create health bar for mini boss
if (self.enemyType === 'miniBoss') {
self.healthBarBg = game.addChild(LK.getAsset('miniBossHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 300,
scaleX: 8,
scaleY: 2
}));
self.healthBarFg = game.addChild(LK.getAsset('miniBossHealthBar', {
anchorX: 0.0,
anchorY: 0.5,
x: 2048 / 2 - 200,
y: 300,
scaleX: 8,
scaleY: 2
}));
self.healthText = new Text2('Boss Health: ' + self.health + '/' + self.maxHealth, {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
self.healthText.anchor.set(0.5, 0.5);
self.healthText.x = 2048 / 2;
self.healthText.y = 250;
game.addChild(self.healthText);
}
// Update health bar for mini boss
self.updateHealthBar = function () {
if (self.enemyType !== 'miniBoss' || !self.healthBarFg) return;
var healthPercent = self.health / self.maxHealth;
self.healthBarFg.scaleX = healthPercent * 8;
self.healthText.setText('Boss Health: ' + self.health + '/' + self.maxHealth);
if (healthPercent > 0.6) {
self.healthBarFg.tint = 0xff0000;
} else if (healthPercent > 0.3) {
self.healthBarFg.tint = 0xff4500;
} else {
self.healthBarFg.tint = 0x8B0000;
}
};
// Optimized animation system
self.updateAnimation = function () {
self.animationTimer++;
var frameSpeed = self.animationSpeed;
if (self.animationState === 'attacking') frameSpeed = Math.floor(frameSpeed * 0.5);else if (self.animationState === 'dying') frameSpeed = Math.floor(frameSpeed * 1.3);else if (self.animationState === 'idle') frameSpeed = Math.floor(frameSpeed * 1.7);
if (self.animationTimer >= frameSpeed) {
self.animationTimer = 0;
self.animationFrames[self.currentFrame - 1].visible = false;
if (self.animationState === 'walking') {
self.currentFrame++;
if (self.currentFrame > 4) self.currentFrame = 1;
} else if (self.animationState === 'attacking') {
self.currentFrame++;
if (self.currentFrame > 4) self.currentFrame = 2;
} else if (self.animationState === 'dying') {
if (self.currentFrame < 4) self.currentFrame++;
} else if (self.animationState === 'idle') {
self.currentFrame = self.currentFrame === 1 ? 2 : 1;
}
self.animationFrames[self.currentFrame - 1].visible = true;
}
};
self.update = function () {
if (tutorial && tutorial.isActive || self.isDying) return;
// Animation and speed progression
self.updateAnimation();
if (!self.speedTweenStarted) {
self.speedTweenStarted = true;
tween(self, {
speed: self.speed * 1.5
}, {
duration: 10000,
easing: tween.easeOut
});
}
// Handle frozen state
if (self.frozen) {
self.frozenTimer--;
if (self.frozenTimer <= 0) self.frozen = false;
return;
}
// Enhanced movement toward wizard
if (wizard) {
var dx = wizard.x - self.x;
var dy = wizard.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var speedMult = self.timeSlowed ? self.timeSlowAmount : 1.0;
self.x += dx / distance * self.speed * speedMult;
self.y += dy / distance * self.speed * speedMult;
var flipScale = dx < 0 ? -enemyConfig.scale : enemyConfig.scale;
for (var frameIdx = 0; frameIdx < self.animationFrames.length; frameIdx++) {
self.animationFrames[frameIdx].scaleX = flipScale;
}
} else {
self.y += self.speed;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.animationState = 'attacking';
// Create damage text
var damageText = new Text2('-' + damage, {
size: 120,
fill: 0xFF4444,
font: "monospace"
});
damageText.anchor.set(0.5, 0.5);
damageText.x = self.x + (Math.random() - 0.5) * 60;
damageText.y = self.y - 40;
game.addChild(damageText);
var startY = damageText.y;
tween(damageText, {
y: startY - 120,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
damageText.destroy();
}
});
// Flash effect
LK.effects.flashObject(self, 0xFF0000, 300);
// Return to walking state after brief animation
tween({}, {}, {
duration: 300,
onFinish: function onFinish() {
if (self.animationState === 'attacking') self.animationState = 'walking';
}
});
if (self.enemyType === 'miniBoss') {
self.updateHealthBar();
}
if (self.health <= 0) self.die();
};
self.down = function (x, y, obj) {
if (tutorial && tutorial.isActive || self.isDying) return;
if (wizard && wizard.attackCooldown > 0) {
LK.effects.flashObject(self, 0xFF0000, 200);
return;
}
// Vibration feedback
if (typeof LK.vibrate === 'function') {
LK.vibrate(enemyConfig.vibration);
}
selectedEnemy = self;
LK.effects.flashObject(self, 0xFFFF00, 500);
if (wizard && projectiles.length < 10) {
var projectile = ProjectileFactory.createBasicAttack(wizard, self);
projectile.targetEnemy = self;
projectiles.push(projectile);
LK.getSound('spellCast').play();
wizard.attackCooldown = 30;
}
};
self.die = function () {
if (globalDeathHandler) {
// Get appropriate array based on type
var enemyArray = enemies;
if (self.enemyType === 'ogre') enemyArray = ogres;else if (self.enemyType === 'knight') enemyArray = knights;else if (self.enemyType === 'miniBoss') enemyArray = miniBosses;
globalDeathHandler.executeEnemyDeath(self, enemyArray);
}
};
return self;
});
// Unified EnemyManager for streamlined enemy lifecycle management
var EnemyManager = Container.expand(function () {
var self = Container.call(this);
// Use global arrays directly - no separate collections needed
// Unified enemy configuration templates
self.enemyTemplates = {
skeleton: {
baseHealth: 100,
baseSpeed: 3,
damage: 20,
coinReward: 1
},
ogre: {
baseHealth: 200,
baseSpeed: 2.5,
damage: 30,
coinReward: 1
},
knight: {
baseHealth: 300,
baseSpeed: 2,
damage: 40,
coinReward: 1
},
miniBoss: {
baseHealth: 3000,
baseSpeed: 4,
damage: 75,
coinReward: 5
}
};
// Streamlined enemy creation with unified Enemy class
self.createEnemy = function (type, difficulty, level, pathOverride) {
var template = self.enemyTemplates[type];
if (!template) return null;
// Create enemy using unified Enemy class
var enemy = new Enemy(type);
// Apply difficulty scaling
enemy.speed *= 1 + level * 0.3;
enemy.pathIndex = pathOverride || self.selectOptimalPath(type);
self.positionEnemy(enemy, type);
self.applyDifficultyModifications(enemy, type, difficulty);
return enemy;
};
// Unified enemy positioning system
self.positionEnemy = function (enemy, type) {
// Use direct spawn position values
var spawnPositions = [{
x: 2048 / 2,
y: -100
}, {
x: 2048 + 50,
y: -50
}, {
x: -50,
y: -50
}, {
x: -100,
y: 2732 / 2 + 400
}, {
x: 2048 + 100,
y: 2732 / 2 + 400
}];
var spawnPos = spawnPositions[enemy.pathIndex];
if (spawnPos) {
enemy.x = Math.max(50, Math.min(1998, spawnPos.x));
enemy.y = type === 'miniBoss' ? -200 : Math.max(-200, Math.min(2732 + 100, spawnPos.y));
enemy.lastX = enemy.x;
}
};
// Streamlined difficulty modifications
self.applyDifficultyModifications = function (enemy, type, difficulty) {
if (type === 'miniBoss') {
enemy.updateHealthBar();
LK.effects.flashScreen(0x8B0000, 1000);
} else if (type === 'skeleton' && Math.random() < 0.3) {
LK.getSound('enemyGrowl').play();
}
// Elite enemy system for hard difficulty
if (difficulty === 'DIFICIL' && enemyKillCounter >= 20 && Math.random() < 0.15) {
self.createEliteEnemy(enemy, type);
}
};
// Unified elite enemy creation
self.createEliteEnemy = function (enemy, type) {
var eliteMultipliers = {
health: 1.8,
speed: 1.3,
color: 0xFF6600
};
if (type === 'ogre') eliteMultipliers.color = 0xFF0000;
if (type === 'knight') eliteMultipliers.color = 0xFFD700;
if (type === 'miniBoss') eliteMultipliers.color = 0x8B0000;
enemy.health *= eliteMultipliers.health;
enemy.speed *= eliteMultipliers.speed;
enemy.maxHealth = enemy.health;
enemy.isElite = true;
for (var i = 0; i < enemy.animationFrames.length; i++) {
enemy.animationFrames[i].tint = eliteMultipliers.color;
}
};
// Optimized path selection system
self.selectOptimalPath = function (type) {
if (type === 'skeleton' && enemyKillCounter < 5) return 0;
if (type === 'miniBoss') return 0;
var available = [];
for (var i = 0; i < 5; i++) {
if (pathConsecutiveSpawns[i] < 2) available.push(i);
}
if (available.length === 0) {
for (var i = 0; i < 5; i++) pathConsecutiveSpawns[i] = 0;
available = [0, 1, 2, 3, 4];
}
return available[Math.floor(Math.random() * available.length)];
};
// Unified enemy collection management
self.addToCollection = function (enemy, type) {
if (type === 'skeleton') enemies.push(enemy);else if (type === 'ogre') ogres.push(enemy);else if (type === 'knight') knights.push(enemy);else if (type === 'miniBoss') miniBosses.push(enemy);
};
// Streamlined enemy removal system
self.removeFromCollection = function (enemy, type) {
var collection = [];
if (type === 'skeleton') collection = enemies;else if (type === 'ogre') collection = ogres;else if (type === 'knight') collection = knights;else if (type === 'miniBoss') collection = miniBosses;
if (collection.length > 0) {
for (var i = collection.length - 1; i >= 0; i--) {
if (collection[i] === enemy) {
collection.splice(i, 1);
break;
}
}
}
};
// Get all enemies as unified array
self.getAllEnemies = function () {
var allEnemies = [];
allEnemies = allEnemies.concat(enemies);
allEnemies = allEnemies.concat(ogres);
allEnemies = allEnemies.concat(knights);
allEnemies = allEnemies.concat(miniBosses);
return allEnemies;
};
return self;
});
var EnergyOrb = Container.expand(function () {
var self = Container.call(this);
// Create energy sphere visual
var sphereGraphics = self.attachAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Add glowing effect
sphereGraphics.alpha = 0.8;
self.attackTimer = 0;
self.attackInterval = 180; // 3 seconds at 60fps
self.orbitalAngle = 0;
self.orbitalRadius = 120;
self.update = function () {
// Pause energy orb when tutorial is active
if (tutorial && tutorial.isActive) {
return;
}
// Keep sphere at wizard's position (stationary relative to wizard)
if (wizard) {
self.x = wizard.x + 140; // Position further to the right side of wizard
self.y = wizard.y - 20; // Position slightly lower relative to wizard
}
// Pulsing glow effect
var pulse = 1 + Math.sin(LK.ticks * 0.2) * 0.3;
sphereGraphics.scaleX = 1.5 * pulse;
sphereGraphics.scaleY = 1.5 * pulse;
// Attack timer - keep original interval regardless of upgrades
self.attackTimer++;
if (self.attackTimer >= 180) {
// Fixed at 3 seconds
self.attackTimer = 0;
self.attackClosestEnemy();
}
};
self.attackClosestEnemy = function () {
var closestEnemy = null;
var closestDistance = Infinity;
// Check all enemy types for closest one
var allEnemies = collisionArrayPool.getAllEnemies();
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[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;
}
}
// Attack closest enemy if found
if (closestEnemy) {
// Create energy beam projectile using unified factory
var energyBeam = ProjectileFactory.createProjectile('energyBeam', self.x, self.y, closestEnemy.x, closestEnemy.y, {
targetEnemy: closestEnemy
});
// Flash effect on sphere when attacking
tween(sphereGraphics, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(sphereGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.8
}, {
duration: 200,
easing: tween.easeIn
});
}
});
LK.getSound('spellCast').play();
}
};
return self;
});
var GameMenu = Container.expand(function () {
var self = Container.call(this);
// Simple spell deck - load from storage or use defaults
var currentDeck = storage.spellDeck || ['fireball', 'heal', 'lightning'];
storage.spellDeck = currentDeck.slice();
// Menu background image instead of cave background
var menuBg = self.attachAsset('menuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 25.0,
scaleY: 35.0
});
menuBg.alpha = 1.0;
// Title text
var titleText = new Text2('WIZARD DEFENDER', {
size: 150,
fill: 0xFFD700,
font: "monospace"
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 800;
self.addChild(titleText);
// Instructions text
var instructionsText = new Text2('TAP ENEMIES TO ATTACK\nDEFEND YOUR CASTLE!', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 2048 / 2;
instructionsText.y = 1200;
self.addChild(instructionsText);
// Start button
var startButton = self.attachAsset('wizard1', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1500,
scaleX: 2,
scaleY: 2
});
var startButtonText = new Text2('START GAME', {
size: 100,
fill: 0x000000,
font: "monospace"
});
startButtonText.anchor.set(0.5, 0.5);
startButtonText.x = 2048 / 2;
startButtonText.y = 1600;
self.addChild(startButtonText);
// Configuration button
var configButton = self.attachAsset('pathSelector', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1800,
scaleX: 4,
scaleY: 2
});
configButton.tint = 0x4169E1;
var configButtonText = new Text2('CONFIGURACION', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
configButtonText.anchor.set(0.5, 0.5);
configButtonText.x = 2048 / 2;
configButtonText.y = 1800;
self.addChild(configButtonText);
// Shop button
var shopButton = self.attachAsset('pathSelector', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1950,
scaleX: 4,
scaleY: 2
});
shopButton.tint = 0xFF6B35;
var shopButtonText = new Text2('TIENDA', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
shopButtonText.anchor.set(0.5, 0.5);
shopButtonText.x = 2048 / 2;
shopButtonText.y = 1950;
self.addChild(shopButtonText);
// Deck button
var deckButton = self.attachAsset('pathSelector', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2150,
scaleX: 4,
scaleY: 2
});
deckButton.tint = 0x8A2BE2;
var deckButtonText = new Text2('DECK HECHIZOS', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
deckButtonText.anchor.set(0.5, 0.5);
deckButtonText.x = 2048 / 2;
deckButtonText.y = 2150;
self.addChild(deckButtonText);
// Tutorial button (only show if tutorial was completed before)
if (storage.tutorialCompleted) {
var tutorialButton = self.attachAsset('pathSelector', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2350,
scaleX: 4,
scaleY: 2
});
tutorialButton.tint = 0x2E8B57;
var tutorialButtonText = new Text2('TUTORIAL', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
tutorialButtonText.anchor.set(0.5, 0.5);
tutorialButtonText.x = 2048 / 2;
tutorialButtonText.y = 2350;
self.addChild(tutorialButtonText);
}
// Button interaction
self.down = function (x, y, obj) {
// Handle configuration menu interactions
if (self.configMode) {
// Music volume adjustment
if (y >= 1150 && y <= 1250) {
var currentMusicVolume = storage.musicVolume || 0.7;
var newMusicVolume = Math.min(1.0, currentMusicVolume + 0.1);
if (newMusicVolume > 1.0) newMusicVolume = 0.0;
storage.musicVolume = newMusicVolume;
self.musicVolumeText.setText('VOLUMEN MUSICA: ' + Math.round(newMusicVolume * 100) + '%');
return;
}
// Sound volume adjustment
if (y >= 1350 && y <= 1450) {
var currentSoundVolume = storage.soundVolume || 1.0;
var newSoundVolume = Math.min(1.0, currentSoundVolume + 0.1);
if (newSoundVolume > 1.0) newSoundVolume = 0.0;
storage.soundVolume = newSoundVolume;
self.soundVolumeText.setText('VOLUMEN SONIDO: ' + Math.round(newSoundVolume * 100) + '%');
return;
}
// Difficulty adjustment
if (y >= 1550 && y <= 1650) {
var currentDifficulty = storage.difficulty || 'NORMAL';
var difficulties = ['FACIL', 'NORMAL', 'DIFICIL'];
var currentIndex = difficulties.indexOf(currentDifficulty);
var newIndex = (currentIndex + 1) % difficulties.length;
var newDifficulty = difficulties[newIndex];
storage.difficulty = newDifficulty;
self.difficultyText.setText('DIFICULTAD: ' + newDifficulty);
return;
}
// Back button
if (y >= 1950 && y <= 2050) {
self.hideConfigMenu();
return;
}
// Block all other interactions when config menu is active
return;
}
// Handle deck menu interactions
if (self.deckMode) {
// Check deck card clicks for spell casting (top area) or removal (bottom area)
for (var i = 0; i < self.deckElements.length; i++) {
var element = self.deckElements[i];
if (element.spellId && element.isDeckCard) {
var cardX = element.x;
var cardY = element.y;
if (x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225) {
// Enhanced immediate touch feedback with scaling and ripple effect
tween(element, {
scaleX: element.scaleX * 0.95,
scaleY: element.scaleY * 0.95
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(element, {
scaleX: element.scaleX / 0.95,
scaleY: element.scaleY / 0.95
}, {
duration: 120,
easing: tween.bounceOut
});
}
});
// Create ripple effect at touch point
var ripple = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.3,
scaleY: 0.3
}));
ripple.tint = 0x00FFFF;
ripple.alpha = 0.8;
ripple.zIndex = 2000;
tween(ripple, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
if (ripple.parent) ripple.destroy();
}
});
// Top area of card (above center) - cast spell
if (y <= cardY) {
// Enhanced validation with visual feedback
var canCast = activeSpellDeck && _canCastSpell(element.spellId);
var spell = activeSpellDeck ? activeSpellDeck.getSpell(element.spellId) : null;
// Check mana requirement with visual feedback
if (!canCast && spell && currentMana < spell.manaCost) {
// Enhanced mana insufficient feedback with shake and urgent pulsing
var originalX = element.x;
var shakeIntensity = 8;
// Create urgent shake effect
tween(element, {
x: originalX - shakeIntensity,
scaleX: element.scaleX * 1.3,
scaleY: element.scaleY * 1.3,
tint: 0xFF0000,
alpha: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(element, {
x: originalX + shakeIntensity
}, {
duration: 80,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(element, {
x: originalX,
scaleX: element.scaleX / 1.3,
scaleY: element.scaleY / 1.3,
tint: 0xFF6666,
alpha: 0.6
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to normal with fade
tween(element, {
tint: self.spellDeck.getRarityColor(spell.rarity),
alpha: 0.9
}, {
duration: 400,
easing: tween.easeIn
});
}
});
}
});
}
});
// Create warning pulse ring around card
var warningRing = game.addChild(LK.getAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: element.x,
y: element.y,
scaleX: 4.5,
scaleY: 5.5
}));
warningRing.tint = 0xFF0000;
warningRing.alpha = 0.8;
warningRing.zIndex = 1999;
tween(warningRing, {
scaleX: 6.0,
scaleY: 7.0,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
if (warningRing.parent) warningRing.destroy();
}
});
// Show insufficient mana message
var manaError = new Text2('MANA INSUFICIENTE!\nRequiere: ' + spell.manaCost + ' Actual: ' + Math.floor(currentMana), {
size: 50,
fill: 0xFF4444,
font: "monospace"
});
manaError.anchor.set(0.5, 0.5);
manaError.x = 2048 / 2;
manaError.y = 2200;
self.addChild(manaError);
tween(manaError, {
alpha: 0,
y: manaError.y - 150,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 2500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (manaError.parent) manaError.destroy();
}
});
return;
}
// Check cooldown with visual feedback
var currentTick = LK.ticks || 0;
if (cardCooldowns[element.spellId] && currentTick < cardCooldowns[element.spellId]) {
// Enhanced cooldown feedback with multi-stage bounce and rotation
tween(element, {
scaleX: element.scaleX * 0.7,
scaleY: element.scaleY * 1.2,
tint: 0x4169E1,
rotation: -Math.PI / 8
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(element, {
scaleX: element.scaleX / 0.7 * 1.1,
scaleY: element.scaleY / 1.2 * 0.9,
rotation: Math.PI / 8
}, {
duration: 140,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(element, {
scaleX: element.scaleX / 1.1,
scaleY: element.scaleY / 0.9,
tint: self.spellDeck.getRarityColor(spell.rarity),
rotation: 0
}, {
duration: 200,
easing: tween.bounceOut
});
}
});
}
});
// Create cooldown wave effect
var cooldownWave = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: element.x,
y: element.y,
scaleX: 0.5,
scaleY: 0.5
}));
cooldownWave.tint = 0x4169E1;
cooldownWave.alpha = 0.7;
cooldownWave.zIndex = 2000;
tween(cooldownWave, {
scaleX: 4.0,
scaleY: 4.0,
alpha: 0,
rotation: Math.PI * 2
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (cooldownWave.parent) cooldownWave.destroy();
}
});
// Show cooldown message
var timeRemaining = Math.ceil((cardCooldowns[element.spellId] - currentTick) / 60);
var cooldownError = new Text2('EN RECARGA!\nEspera: ' + timeRemaining + ' segundos', {
size: 50,
fill: 0x4169E1,
font: "monospace"
});
cooldownError.anchor.set(0.5, 0.5);
cooldownError.x = 2048 / 2;
cooldownError.y = 2200;
self.addChild(cooldownError);
tween(cooldownError, {
alpha: 0,
y: cooldownError.y - 120,
rotation: Math.PI * 0.1
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (cooldownError.parent) cooldownError.destroy();
}
});
return;
}
// Try to cast the spell
if (canCast) {
var spell = activeSpellDeck.getSpell(element.spellId);
if (spell) {
var targetX = wizard.x;
var targetY = wizard.y - 100;
// Find nearest enemy for targeted spells
if (spell.id === 'fireball' || spell.id === 'iceShard' || spell.id === 'lightning') {
var allEnemies = collisionArrayPool.getAllEnemies();
var nearestEnemy = null;
var nearestDistance = Infinity;
for (var e = 0; e < allEnemies.length; e++) {
var enemy = allEnemies[e];
var dx = enemy.x - wizard.x;
var dy = enemy.y - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestEnemy = enemy;
}
}
if (nearestEnemy) {
targetX = nearestEnemy.x;
targetY = nearestEnemy.y;
}
}
// Enhanced successful casting animation with card flip effect
tween(element, {
scaleX: 0,
scaleY: element.scaleY * 1.4,
tint: 0x00FF00,
alpha: 1.0,
rotation: Math.PI / 4
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Flip back with success color
tween(element, {
scaleX: element.scaleX * 1.2,
scaleY: element.scaleY / 1.4,
tint: 0xFFD700,
alpha: 1.0,
rotation: 0
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to normal state
tween(element, {
scaleX: element.scaleX / 1.2,
tint: self.spellDeck.getRarityColor(spell.rarity),
alpha: 0.9
}, {
duration: 300,
easing: tween.elasticOut
});
}
});
}
});
// Create success burst particles
for (var p = 0; p < 8; p++) {
var burstParticle = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: element.x,
y: element.y,
scaleX: 0.6,
scaleY: 0.6
}));
burstParticle.tint = 0x00FF00;
burstParticle.alpha = 0.9;
burstParticle.zIndex = 2001;
var angle = p * Math.PI * 2 / 8;
var distance = 150 + Math.random() * 50;
tween(burstParticle, {
x: element.x + Math.cos(angle) * distance,
y: element.y + Math.sin(angle) * distance,
alpha: 0,
scaleX: 1.2,
scaleY: 1.2,
rotation: Math.PI * 3
}, {
duration: 800 + p * 100,
easing: tween.easeOut,
onFinish: function onFinish() {
if (burstParticle.parent) burstParticle.destroy();
}
});
}
// Magical sparkle effects removed for simplification
// Cast the spell using specific functions
if (element.spellId === 'fireball') {
castFireball();
} else if (element.spellId === 'lightning') {
castLightning();
} else if (element.spellId === 'heal') {
castHeal();
} else {
console.log('Unknown spell ID in deck menu:', element.spellId);
castFireball(); // Default to fireball
}
// Show cast message
var castText = new Text2('HECHIZO LANZADO!', {
size: 60,
fill: 0x00FF00,
font: "monospace"
});
castText.anchor.set(0.5, 0.5);
castText.x = 2048 / 2;
castText.y = 2200;
self.addChild(castText);
// Animate and remove message
tween(castText, {
alpha: 0,
y: castText.y - 100
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (castText.parent) castText.destroy();
}
});
}
} else {
LK.effects.flashObject(element, 0xFF0000, 200);
// Show "cannot cast" message
var errorText = new Text2('NO SE PUEDE LANZAR', {
size: 50,
fill: 0xFF6666,
font: "monospace"
});
errorText.anchor.set(0.5, 0.5);
errorText.x = 2048 / 2;
errorText.y = 2200;
self.addChild(errorText);
// Animate and remove message
tween(errorText, {
alpha: 0,
y: errorText.y - 100
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (errorText.parent) errorText.destroy();
}
});
}
} else {
// Bottom area of card (below center) - remove from deck
// Visual feedback before removal
LK.effects.flashObject(element, 0xFF0000, 300);
// Remove from deck
if (self.spellDeck.removeFromDeck(element.spellId)) {
self.refreshDeckDisplay();
LK.effects.flashScreen(0xFF8800, 200);
// Show removal message
var removeText = new Text2('HECHIZO REMOVIDO', {
size: 60,
fill: 0xFF6666,
font: "monospace"
});
removeText.anchor.set(0.5, 0.5);
removeText.x = 2048 / 2;
removeText.y = 2200;
self.addChild(removeText);
// Animate and remove message
tween(removeText, {
alpha: 0,
y: removeText.y - 100
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (removeText.parent) removeText.destroy();
}
});
} else {
LK.effects.flashScreen(0xFF0000, 200);
}
}
return;
}
}
}
// Check available card clicks with better hit detection
for (var i = 0; i < self.availableElements.length; i++) {
var element = self.availableElements[i];
if (element.spellId && !element.isDeckCard) {
var cardX = element.x;
var cardY = element.y;
if (x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225) {
// Visual feedback before addition
LK.effects.flashObject(element, 0x00FF00, 300);
// Add to deck
if (self.spellDeck.addToDeck(element.spellId)) {
self.refreshDeckDisplay();
LK.effects.flashScreen(0x00FF00, 200);
// Show addition message
var addText = new Text2('HECHIZO AÑADIDO', {
size: 60,
fill: 0x66FF66,
font: "monospace"
});
addText.anchor.set(0.5, 0.5);
addText.x = 2048 / 2;
addText.y = 2200;
self.addChild(addText);
// Animate and remove message
tween(addText, {
alpha: 0,
y: addText.y - 100
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (addText.parent) addText.destroy();
}
});
} else {
LK.effects.flashScreen(0xFF0000, 200);
// Show error message
var errorText = new Text2('DECK LLENO (MAX 5)', {
size: 50,
fill: 0xFF6666,
font: "monospace"
});
errorText.anchor.set(0.5, 0.5);
errorText.x = 2048 / 2;
errorText.y = 2200;
self.addChild(errorText);
// Animate and remove message
tween(errorText, {
alpha: 0,
y: errorText.y - 100
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (errorText.parent) errorText.destroy();
}
});
}
return;
}
}
}
// Deck back button
if (y >= 2350 && y <= 2650) {
self.hideDeck();
}
// Block all other interactions when deck menu is active
return;
}
// Handle shop menu interactions
if (self.shopMode) {
// Shop item purchase buttons
for (var i = 0; i < 3; i++) {
var itemY = 1100 + i * 200;
if (y >= itemY - 50 && y <= itemY + 50 && x >= 2048 / 2 + 200 && x <= 2048 / 2 + 400) {
// Purchase item logic
var shopItems = [{
name: 'POCION SALUD',
cost: 10
}, {
name: 'ESCUDO MAGICO',
cost: 15
}, {
name: 'ESPADA MALDITA',
cost: 20
}];
var item = shopItems[i];
if (coinCounter >= item.cost) {
coinCounter -= item.cost;
// Apply item effect based on type
if (i === 0) {
// Health potion
if (wizard) {
wizard.health = Math.min(wizard.health + 50, wizard.maxHealth);
updateHealthBar();
}
} else if (i === 1) {
// Magic shield
if (wizard) {
wizard.shieldActive = true;
wizard.maxShieldHits = 3;
wizard.currentShieldHits = 0;
}
} else if (i === 2) {
// Cursed sword - temporary damage boost
if (wizard) {
wizard.tempDamageBoost = true;
wizard.tempDamageTimer = 1800; // 30 seconds at 60fps
}
}
LK.effects.flashScreen(0x00FF00, 300);
} else {
LK.effects.flashScreen(0xFF0000, 300);
}
return;
}
}
// Shop back button
if (y >= 1950 && y <= 2050) {
self.hideShop();
return;
}
// Block all other interactions when shop menu is active
return;
}
// Check if configuration button was clicked
if (y >= 1700 && y <= 1900 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) {
// Show configuration menu
self.showConfigMenu();
} else if (y >= 1850 && y <= 2050 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) {
// Show shop menu
self.showShop();
} else if (y >= 2050 && y <= 2250 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) {
// Show deck menu
self.showDeck();
} else if (storage.tutorialCompleted && y >= 2250 && y <= 2450 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) {
// Show tutorial again
if (tutorial) {
self.visible = false;
tutorial.startTutorial();
}
} else if (y >= 1450 && y <= 1650) {
// Start the game by hiding menu
self.startGame();
}
};
self.showConfigMenu = function () {
if (!self.configOverlay) {
self.configOverlay = self.createMenuOverlay(0x000000);
self.configTitle = self.createMenuText('CONFIGURACION', 2048 / 2, 800, 120, 0xFFD700);
self.musicVolumeText = self.createMenuText('VOLUMEN MUSICA: ' + Math.round((storage.musicVolume || 0.7) * 100) + '%', 2048 / 2, 1200, 80, 0xFFFFFF);
self.soundVolumeText = self.createMenuText('VOLUMEN SONIDO: ' + Math.round((storage.soundVolume || 1.0) * 100) + '%', 2048 / 2, 1400, 80, 0xFFFFFF);
self.difficultyText = self.createMenuText('DIFICULTAD: ' + (storage.difficulty || 'NORMAL'), 2048 / 2, 1600, 80, 0xFFFFFF);
self.backButton = self.createMenuButton('coin', 2048 / 2, 2000, 0x00FF00);
self.backText = self.createMenuText('VOLVER', 2048 / 2, 2000, 80, 0xFFFFFF);
}
self.configOverlay.visible = true;
self.configMode = true;
};
self.hideConfigMenu = function () {
if (self.configOverlay) {
self.configOverlay.destroy();
self.configOverlay = null;
}
// Remove all configuration text elements
if (self.musicVolumeText) {
self.musicVolumeText.destroy();
self.musicVolumeText = null;
}
if (self.soundVolumeText) {
self.soundVolumeText.destroy();
self.soundVolumeText = null;
}
if (self.difficultyText) {
self.difficultyText.destroy();
self.difficultyText = null;
}
// Remove back button elements
if (self.backButton) {
self.backButton.destroy();
self.backButton = null;
}
if (self.backText) {
self.backText.destroy();
self.backText = null;
}
// Remove configuration title
if (self.configTitle) {
self.configTitle.destroy();
self.configTitle = null;
}
// Remove all configuration children that were added
for (var i = self.children.length - 1; i >= 0; i--) {
var child = self.children[i];
// Remove config-related elements (title, texts, buttons created in showConfigMenu)
if (child.setText && child.text && (child.text.includes('CONFIGURACION') || child.text.includes('VOLUMEN') || child.text.includes('DIFICULTAD') || child.text.includes('VOLVER'))) {
child.destroy();
}
}
self.configMode = false;
// Reset to show main menu elements
self.visible = true;
};
self.showShop = function () {
if (!self.shopOverlay) {
self.shopOverlay = self.createMenuOverlay(0x000033);
self.shopTitle = self.createMenuText('TIENDA', 2048 / 2, 800, 120, 0xFFD700);
var shopItems = self.getShopItemsData();
self.initializeShopArrays();
self.createShopItems(shopItems);
self.shopBackButton = self.createMenuButton('coin', 2048 / 2, 2000, 0x00FF00);
self.shopBackText = self.createMenuText('VOLVER', 2048 / 2, 2000, 80, 0xFFFFFF);
}
self.shopOverlay.visible = true;
self.shopMode = true;
};
self.hideShop = function () {
if (self.shopOverlay) {
self.shopOverlay.destroy();
self.shopOverlay = null;
}
// Remove shop title
if (self.shopTitle) {
self.shopTitle.destroy();
self.shopTitle = null;
}
// Remove shop back button elements
if (self.shopBackButton) {
self.shopBackButton.destroy();
self.shopBackButton = null;
}
if (self.shopBackText) {
self.shopBackText.destroy();
self.shopBackText = null;
}
// Remove all shop icons
if (self.shopIcons) {
for (var i = 0; i < self.shopIcons.length; i++) {
if (self.shopIcons[i]) {
self.shopIcons[i].destroy();
}
}
self.shopIcons = [];
}
// Remove all shop texts
if (self.shopTexts) {
for (var i = 0; i < self.shopTexts.length; i++) {
if (self.shopTexts[i]) {
self.shopTexts[i].destroy();
}
}
self.shopTexts = [];
}
// Remove all shop buy buttons
if (self.shopBuyButtons) {
for (var i = 0; i < self.shopBuyButtons.length; i++) {
if (self.shopBuyButtons[i]) {
self.shopBuyButtons[i].destroy();
}
}
self.shopBuyButtons = [];
}
// Remove all shop buy texts
if (self.shopBuyTexts) {
for (var i = 0; i < self.shopBuyTexts.length; i++) {
if (self.shopBuyTexts[i]) {
self.shopBuyTexts[i].destroy();
}
}
self.shopBuyTexts = [];
}
// Remove all shop children that were added
for (var i = self.children.length - 1; i >= 0; i--) {
var child = self.children[i];
// Remove shop-related elements
if (child.setText && child.text && (child.text.includes('TIENDA') || child.text.includes('POCION') || child.text.includes('ESCUDO') || child.text.includes('ESPADA') || child.text.includes('COMPRAR'))) {
child.destroy();
}
}
self.shopMode = false;
// Reset to show main menu elements
self.visible = true;
};
self.showDeck = function () {
if (!self.deckOverlay) {
// Ensure spellDeck exists before creating overlay
if (!self.spellDeck) self.spellDeck = new SpellDeck();
self.deckOverlay = self.createMenuOverlay(0x1a0a2e);
self.deckTitle = self.createMenuText('DECK DE HECHIZOS', 2048 / 2, 600, 100, 0xFFD700);
self.initializeDeckArrays();
self.refreshDeckDisplay();
self.deckBackButton = self.createMenuButton('coin', 2048 / 2, 2500, 0x00FF00);
self.deckBackText = self.createMenuText('VOLVER', 2048 / 2, 2500, 80, 0xFFFFFF);
}
self.deckOverlay.visible = true;
self.deckMode = true;
self.refreshDeckDisplay();
};
self.initializeDeckArrays = function () {
if (!self.deckElements) self.deckElements = [];
if (!self.availableElements) self.availableElements = [];
};
self.refreshDeckDisplay = function () {
if (!self.spellDeck) {
self.spellDeck = new SpellDeck();
}
// Clear existing deck elements
for (var i = 0; i < self.deckElements.length; i++) {
if (self.deckElements[i] && self.deckElements[i].parent) {
self.deckElements[i].destroy();
}
}
self.deckElements = [];
// Clear existing available elements
for (var i = 0; i < self.availableElements.length; i++) {
if (self.availableElements[i] && self.availableElements[i].parent) {
self.availableElements[i].destroy();
}
}
self.availableElements = [];
// Add helpful instructions at the top
var instructionText = new Text2('TOCA CARTAS PARA AÑADIR/QUITAR DEL DECK', {
size: 50,
fill: 0x00FF00,
font: "monospace"
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 700;
self.addChild(instructionText);
self.deckElements.push(instructionText);
// Display current deck (top section)
var deckLabel = new Text2('MI DECK ACTUAL (' + self.spellDeck.currentDeck.length + '/5):', {
size: 70,
fill: 0xFFD700,
font: "monospace"
});
deckLabel.anchor.set(0.5, 0.5);
deckLabel.x = 2048 / 2;
deckLabel.y = 800;
self.addChild(deckLabel);
self.deckElements.push(deckLabel);
// Add deck instruction
var deckInstruction = new Text2('TOCA PARA QUITAR', {
size: 40,
fill: 0xFF6666,
font: "monospace"
});
deckInstruction.anchor.set(0.5, 0.5);
deckInstruction.x = 2048 / 2;
deckInstruction.y = 850;
self.addChild(deckInstruction);
self.deckElements.push(deckInstruction);
// Display deck cards with better spacing
for (var i = 0; i < 5; i++) {
var cardX = 200 + i * 350;
var cardY = 1050;
if (i < self.spellDeck.currentDeck.length) {
var spell = self.spellDeck.getSpell(self.spellDeck.currentDeck[i]);
if (spell) {
// Card background with dynamic state visualization
var cardBg = self.attachAsset('spellCard', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.5,
scaleY: 4.5
});
// Check card status for visual feedback
var canCast = _canCastSpell(spell.id);
var currentTick = LK.ticks || 0;
var isOnCooldown = cardCooldowns[spell.id] && currentTick < cardCooldowns[spell.id];
var hasEnoughMana = currentMana >= (spell.manaCost || 0);
// Set card appearance based on status
if (isOnCooldown) {
// Cooldown state - blue tint with reduced opacity
cardBg.tint = 0x4169E1;
cardBg.alpha = 0.5;
// Add pulsing cooldown effect
tween(cardBg, {
alpha: 0.3
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(cardBg, {
alpha: 0.5
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
} else if (!hasEnoughMana) {
// Enhanced insufficient mana feedback - red tint with warning overlay
cardBg.tint = 0xFF4444;
cardBg.alpha = 0.6;
// Create mana warning overlay
var manaWarning = self.attachAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX + 60,
y: cardY - 80,
scaleX: 1.0,
scaleY: 1.0
});
manaWarning.tint = 0xFF0000;
manaWarning.alpha = 0.8;
self.deckElements.push(manaWarning);
// Pulsing warning animation for insufficient mana
tween(manaWarning, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.4
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(manaWarning, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8
}, {
duration: 600,
easing: tween.easeInOut
});
}
});
// Add "X" symbol overlay for insufficient mana
var manaBlock = new Text2('✗', {
size: 45,
fill: 0xFF0000,
font: "monospace"
});
manaBlock.anchor.set(0.5, 0.5);
manaBlock.x = cardX + 60;
manaBlock.y = cardY - 80;
self.addChild(manaBlock);
self.deckElements.push(manaBlock);
} else if (canCast) {
// Enhanced ready to cast - normal appearance with magical glow effects
cardBg.tint = self.spellDeck.getRarityColor(spell.rarity);
cardBg.alpha = 0.9;
// Create magical glow border around ready cards
var glowBorder = self.attachAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 4.2,
scaleY: 5.2
});
glowBorder.tint = 0x00FF00;
glowBorder.alpha = 0.4;
self.deckElements.push(glowBorder);
// Sparkle effects removed for simplification
// Simplified ready-to-cast breathing effect
tween(cardBg, {
alpha: 1.0,
scaleX: 3.6
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(cardBg, {
alpha: 0.9,
scaleX: 3.5
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
// Simplified preview mode removed for performance
// Simple glow border breathing effect
tween(glowBorder, {
alpha: 0.5
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(glowBorder, {
alpha: 0.4
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
} else {
// Default disabled state
cardBg.tint = 0x666666;
cardBg.alpha = 0.4;
}
cardBg.spellId = spell.id;
cardBg.isDeckCard = true;
self.deckElements.push(cardBg);
// Add border glow
var glowBorder = self.attachAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 4,
scaleY: 5
});
glowBorder.tint = 0x00FF00;
glowBorder.alpha = 0.3;
self.deckElements.push(glowBorder);
// Card name
var cardName = new Text2(spell.name, {
size: 35,
fill: 0xFFFFFF,
font: "monospace"
});
cardName.anchor.set(0.5, 0.5);
cardName.x = cardX;
cardName.y = cardY - 60;
self.addChild(cardName);
self.deckElements.push(cardName);
// Card description
var description = spell.description || 'Hechizo magico';
var cardDesc = new Text2(description, {
size: 25,
fill: 0xCCCCCC,
font: "monospace",
wordWrap: true,
wordWrapWidth: 250
});
cardDesc.anchor.set(0.5, 0.5);
cardDesc.x = cardX;
cardDesc.y = cardY + 20;
self.addChild(cardDesc);
self.deckElements.push(cardDesc);
// Enhanced card stats with status indicators
var statsText = '';
if (spell.damage) statsText += 'Daño: ' + spell.damage + '\n';
if (spell.healing) statsText += 'Cura: ' + spell.healing + '\n';
if (spell.manaCost) {
var manaColor = currentMana >= spell.manaCost ? '✓' : '✗';
statsText += 'Mana: ' + spell.manaCost + ' ' + manaColor;
}
if (statsText) {
var cardStats = new Text2(statsText, {
size: 20,
fill: hasEnoughMana ? 0xFFD700 : 0xFF4444,
font: "monospace"
});
cardStats.anchor.set(0.5, 0.5);
cardStats.x = cardX;
cardStats.y = cardY + 80;
self.addChild(cardStats);
self.deckElements.push(cardStats);
}
// Add enhanced cooldown indicator if on cooldown
if (isOnCooldown) {
var timeRemaining = Math.ceil((cardCooldowns[spell.id] - currentTick) / 60);
// Create cooldown overlay that covers the entire card
var cooldownOverlay = self.attachAsset('cooldownOverlay', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.5,
scaleY: 4.5
});
cooldownOverlay.alpha = 0.8;
cooldownOverlay.tint = 0x000000;
self.deckElements.push(cooldownOverlay);
// Create circular cooldown progress indicator
var cooldownProgress = self.attachAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 2.0,
scaleY: 2.0
});
cooldownProgress.tint = 0x4169E1;
cooldownProgress.alpha = 0.7;
self.deckElements.push(cooldownProgress);
// Animate progress indicator rotation
tween(cooldownProgress, {
rotation: Math.PI * 2
}, {
duration: 1000,
easing: tween.linear,
onFinish: function onFinish() {
// Continuous rotation while on cooldown
if (cardCooldowns[spell.id] && LK.ticks < cardCooldowns[spell.id]) {
cooldownProgress.rotation = 0;
tween(cooldownProgress, {
rotation: Math.PI * 2
}, {
duration: 1000,
easing: tween.linear
});
}
}
});
var cooldownIndicator = new Text2('RECARGA: ' + timeRemaining + 's', {
size: 28,
fill: 0xFFFFFF,
font: "monospace"
});
cooldownIndicator.anchor.set(0.5, 0.5);
cooldownIndicator.x = cardX;
cooldownIndicator.y = cardY + 110;
self.addChild(cooldownIndicator);
self.deckElements.push(cooldownIndicator);
// Enhanced pulsing animation with color transitions for countdown
tween(cooldownIndicator, {
alpha: 0.6,
scaleX: 1.2,
scaleY: 1.2,
tint: 0x00FFFF
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(cooldownIndicator, {
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
}
}
} else {
// Empty slot
var emptySlot = self.attachAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.5,
scaleY: 4.5
});
emptySlot.tint = 0x444444;
emptySlot.alpha = 0.5;
self.deckElements.push(emptySlot);
var emptyText = new Text2('VACIO', {
size: 40,
fill: 0x666666,
font: "monospace"
});
emptyText.anchor.set(0.5, 0.5);
emptyText.x = cardX;
emptyText.y = cardY;
self.addChild(emptyText);
self.deckElements.push(emptyText);
}
}
// Display available spells (bottom section)
var availableLabel = new Text2('HECHIZOS DISPONIBLES PARA AÑADIR:', {
size: 60,
fill: 0xFFD700,
font: "monospace"
});
availableLabel.anchor.set(0.5, 0.5);
availableLabel.x = 2048 / 2;
availableLabel.y = 1350;
self.addChild(availableLabel);
self.availableElements.push(availableLabel);
// Add available instruction
var availableInstruction = new Text2('TOCA PARA AÑADIR A TU DECK', {
size: 40,
fill: 0x66FF66,
font: "monospace"
});
availableInstruction.anchor.set(0.5, 0.5);
availableInstruction.x = 2048 / 2;
availableInstruction.y = 1400;
self.addChild(availableInstruction);
self.availableElements.push(availableInstruction);
// Display available spells
var availableSpells = [];
for (var i = 0; i < self.spellDeck.availableSpells.length; i++) {
var spell = self.spellDeck.availableSpells[i];
if (self.spellDeck.currentDeck.indexOf(spell.id) === -1) {
availableSpells.push(spell);
}
}
if (availableSpells.length === 0) {
var noSpellsText = new Text2('NO HAY HECHIZOS DISPONIBLES\nDESBLOQUEA MAS JUGANDO', {
size: 50,
fill: 0x888888,
font: "monospace"
});
noSpellsText.anchor.set(0.5, 0.5);
noSpellsText.x = 2048 / 2;
noSpellsText.y = 1600;
self.addChild(noSpellsText);
self.availableElements.push(noSpellsText);
}
for (var i = 0; i < availableSpells.length && i < 8; i++) {
var spell = availableSpells[i];
var cardX = 150 + i % 4 * 450;
var cardY = 1550 + Math.floor(i / 4) * 400;
// Card background with hover effect
var cardBg = self.attachAsset('spellCard', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.2,
scaleY: 4.2
});
cardBg.tint = self.spellDeck.getRarityColor(spell.rarity);
cardBg.spellId = spell.id;
cardBg.isDeckCard = false;
cardBg.alpha = 0.8;
self.availableElements.push(cardBg);
// Add selection glow
var selectionGlow = self.attachAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.7,
scaleY: 4.7
});
selectionGlow.tint = 0x00FFFF;
selectionGlow.alpha = 0.2;
self.availableElements.push(selectionGlow);
// Card name
var cardName = new Text2(spell.name, {
size: 32,
fill: 0xFFFFFF,
font: "monospace"
});
cardName.anchor.set(0.5, 0.5);
cardName.x = cardX;
cardName.y = cardY - 60;
self.addChild(cardName);
self.availableElements.push(cardName);
// Card description
var cardDesc = new Text2(spell.description, {
size: 22,
fill: 0xCCCCCC,
font: "monospace",
wordWrap: true,
wordWrapWidth: 280
});
cardDesc.anchor.set(0.5, 0.5);
cardDesc.x = cardX;
cardDesc.y = cardY + 10;
self.addChild(cardDesc);
self.availableElements.push(cardDesc);
// Card stats
var statsText = '';
if (spell.damage) statsText += 'Daño: ' + spell.damage + '\n';
if (spell.healing) statsText += 'Cura: ' + spell.healing + '\n';
if (spell.manaCost) statsText += 'Mana: ' + spell.manaCost;
if (statsText) {
var cardStats = new Text2(statsText, {
size: 18,
fill: 0xFFD700,
font: "monospace"
});
cardStats.anchor.set(0.5, 0.5);
cardStats.x = cardX;
cardStats.y = cardY + 70;
self.addChild(cardStats);
self.availableElements.push(cardStats);
}
// Rarity indicator
var rarityText = new Text2(spell.rarity.toUpperCase(), {
size: 20,
fill: self.spellDeck.getRarityColor(spell.rarity),
font: "monospace"
});
rarityText.anchor.set(0.5, 0.5);
rarityText.x = cardX;
rarityText.y = cardY + 100;
self.addChild(rarityText);
self.availableElements.push(rarityText);
}
};
self.hideDeck = function () {
if (self.deckOverlay) {
self.deckOverlay.destroy();
self.deckOverlay = null;
}
// Remove deck title
if (self.deckTitle) {
self.deckTitle.destroy();
self.deckTitle = null;
}
// Remove deck back button elements
if (self.deckBackButton) {
self.deckBackButton.destroy();
self.deckBackButton = null;
}
if (self.deckBackText) {
self.deckBackText.destroy();
self.deckBackText = null;
}
// Clear deck elements
for (var i = 0; i < self.deckElements.length; i++) {
if (self.deckElements[i] && self.deckElements[i].parent) {
self.deckElements[i].destroy();
}
}
self.deckElements = [];
// Clear available elements
for (var i = 0; i < self.availableElements.length; i++) {
if (self.availableElements[i] && self.availableElements[i].parent) {
self.availableElements[i].destroy();
}
}
self.availableElements = [];
// Remove all deck-related children
for (var i = self.children.length - 1; i >= 0; i--) {
var child = self.children[i];
if (child.setText && child.text && (child.text.includes('DECK') || child.text.includes('HECHIZOS') || child.text.includes('ACTUAL') || child.text.includes('DISPONIBLES'))) {
child.destroy();
}
}
self.deckMode = false;
self.visible = true;
};
// Unified UI factory for all menu elements
// Menu overlay creation function
self.createMenuOverlay = function (color) {
var overlay = self.attachAsset('startMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 25.0,
scaleY: 35.0
});
overlay.alpha = 0.9;
overlay.tint = color || 0x000000;
overlay.interactive = true;
overlay.zIndex = 1000;
return overlay;
};
// Menu text creation function
self.createMenuText = function (text, x, y, size, color) {
var textElement = new Text2(text, {
size: size,
fill: color,
font: "monospace"
});
textElement.anchor.set(0.5, 0.5);
textElement.x = x;
textElement.y = y;
self.addChild(textElement);
return textElement;
};
// Menu button creation function
self.createMenuButton = function (asset, x, y, color) {
var button = self.attachAsset(asset, {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 2,
scaleY: 2
});
button.tint = color;
return button;
};
self.createUIElement = function (type, config) {
if (type === 'overlay') {
var overlay = self.addChild(LK.getAsset('startMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.0,
scaleY: 1.0
}));
overlay.alpha = 1.0;
overlay.tint = config.tint || 0x000000;
return overlay;
} else if (type === 'text') {
var text = new Text2(config.text, {
size: config.size,
fill: config.fill,
font: "monospace"
});
text.anchor.set(0.5, 0.5);
text.x = config.x;
text.y = config.y;
self.addChild(text);
return text;
} else if (type === 'button') {
var button = self.attachAsset(config.asset, {
anchorX: 0.5,
anchorY: 0.5,
x: config.x,
y: config.y,
scaleX: 2,
scaleY: 2
});
button.tint = config.tint;
return button;
}
};
self.getShopItemsData = function () {
return [{
name: 'POCION SALUD',
description: 'Restaura 50 HP',
cost: 10,
icon: 'energySphere'
}, {
name: 'ESCUDO MAGICO',
description: 'Bloquea 3 ataques',
cost: 15,
icon: 'shield'
}, {
name: 'ESPADA MALDITA',
description: 'Daño x2 por 30s',
cost: 20,
icon: 'spell'
}];
};
self.initializeShopArrays = function () {
if (!self.shopIcons) self.shopIcons = [];
if (!self.shopTexts) self.shopTexts = [];
if (!self.shopBuyButtons) self.shopBuyButtons = [];
if (!self.shopBuyTexts) self.shopBuyTexts = [];
};
self.createShopItems = function (shopItems) {
for (var i = 0; i < shopItems.length; i++) {
var item = shopItems[i];
var yPos = 1100 + i * 200;
var itemIcon = self.attachAsset(item.icon, {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 300,
y: yPos,
scaleX: 2,
scaleY: 2
});
itemIcon.tint = 0xFFD700;
self.shopIcons.push(itemIcon);
var itemText = new Text2(item.name + '\n' + item.description + '\nCosto: ' + item.cost + ' monedas', {
size: 60,
fill: 0xFFFFFF,
font: "monospace"
});
itemText.anchor.set(0, 0.5);
itemText.x = 2048 / 2 - 200;
itemText.y = yPos;
self.addChild(itemText);
self.shopTexts.push(itemText);
var buyButton = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 300,
y: yPos,
scaleX: 2,
scaleY: 2
});
buyButton.tint = 0x00FF00;
buyButton.itemIndex = i;
self.shopBuyButtons.push(buyButton);
var buyText = new Text2('COMPRAR', {
size: 50,
fill: 0xFFFFFF,
font: "monospace"
});
buyText.anchor.set(0.5, 0.5);
buyText.x = 2048 / 2 + 300;
buyText.y = yPos;
self.addChild(buyText);
self.shopBuyTexts.push(buyText);
}
};
self.startGame = function () {
// Check if this is a new player (no tutorial completed)
if (!storage.tutorialCompleted && tutorial) {
// Show tutorial for new players
self.visible = false;
// Small delay to ensure menu is hidden before tutorial starts
tween({}, {}, {
duration: 100,
onFinish: function onFinish() {
tutorial.startTutorial();
}
});
// Tutorial started successfully
return;
}
// UNIFIED DECK SYNCHRONIZATION: Fix all deck system inconsistencies
// Create unified deck reference that all systems will use
var unifiedDeck = [];
// Priority 1: Use spellDeck from menu if it exists and has content
if (self.spellDeck && self.spellDeck.currentDeck && self.spellDeck.currentDeck.length > 0) {
unifiedDeck = self.spellDeck.currentDeck.slice();
}
// Priority 2: Use storage deck if available
else if (storage.spellDeck && storage.spellDeck.length > 0) {
unifiedDeck = storage.spellDeck.slice();
}
// Priority 3: Use global currentDeck if available
else if (currentDeck && currentDeck.length > 0) {
unifiedDeck = currentDeck.slice();
}
// Priority 4: Use activeSpellDeck if available
else if (activeSpellDeck && activeSpellDeck.currentDeck && activeSpellDeck.currentDeck.length > 0) {
unifiedDeck = activeSpellDeck.currentDeck.slice();
}
// Priority 5: Default deck as last resort
else {
unifiedDeck = ['fireball', 'heal', 'lightning'];
}
// SYNCHRONIZE ALL DECK SYSTEMS with unified deck
currentDeck = unifiedDeck.slice();
activeSpellDeck.currentDeck = unifiedDeck.slice();
storage.spellDeck = unifiedDeck.slice();
if (self.spellDeck) {
self.spellDeck.currentDeck = unifiedDeck.slice();
}
// Debug: Log deck synchronization with enhanced details
console.log('=== DECK SYNCHRONIZATION ===');
console.log('Unified deck:', unifiedDeck);
console.log('ActiveSpellDeck sync:', activeSpellDeck.currentDeck);
console.log('Storage sync:', storage.spellDeck);
console.log('CurrentDeck sync:', currentDeck);
console.log('Available spells IDs:', availableSpells.map(function (s) {
return s.id;
}));
console.log('Spell validation:');
for (var d = 0; d < unifiedDeck.length; d++) {
var spellId = unifiedDeck[d];
var spell = _getSpell(spellId);
console.log(' - ' + spellId + ':', spell ? spell.name : 'NOT FOUND');
}
// Hide menu and start game normally
self.visible = false;
gameStarted = true;
// Show cave background when game starts
if (backgroundMap) {
backgroundMap.visible = true;
}
// Show all game elements
wizard.visible = true;
for (var i = 0; i < paths.length; i++) {
paths[i].visible = true;
}
// Show all stone path segments and make them visible
for (var i = 0; i < game.children.length; i++) {
var child = game.children[i];
if (child.pathIndex !== undefined && child !== paths[child.pathIndex]) {
child.visible = true;
// Check if it's a stone path segment or path number
if (child.alpha !== undefined && child.setText === undefined) {
child.alpha = 0; // Keep stone paths invisible
}
}
}
coinText.visible = true;
killCountText.visible = true;
tapText.visible = true;
healthBarBg.visible = true;
healthBar.visible = true;
healthText.visible = true;
// Show mana UI
manaBarBg.visible = true;
manaBar.visible = true;
manaText.visible = true;
updateManaDisplay();
// Show card toggle button
cardToggleButton.visible = true;
cardToggleText.visible = true;
// Spell UI system removed - using deck menu only
console.log('Spell UI handled through deck menu system');
// SPELL SLOT SYSTEM REMOVED - Using deck menu only for spell casting
// Spells can now only be cast from the deck menu interface
console.log('Spell slots eliminated - using deck menu system only');
// Mana system simplified - values set directly
currentMana = 100;
maxMana = 100;
if (activeSpellDeck) {
activeSpellDeck.currentMana = 100;
activeSpellDeck.maxMana = 100;
}
console.log('=== MANA SYSTEM SIMPLIFIED ===');
console.log('currentMana:', currentMana);
console.log('activeSpellDeck.currentMana:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
console.log('maxMana:', maxMana);
// Mana bar functionality moved to spell deck system
console.log('Mana system managed by deck menu');
// DEBUG: Verify mana values after initialization
console.log('=== MANA FORCE INITIALIZATION ===');
console.log('currentMana:', currentMana);
console.log('activeSpellDeck.currentMana:', activeSpellDeck.currentMana);
console.log('maxMana:', maxMana);
console.log('Mana bar updated successfully');
// SPELL SLOT SYSTEM REMOVED - Using deck menu only for spell casting
// Spells can now only be cast from the deck menu interface
console.log('Spell slots eliminated - using deck menu system only');
// ADDITIONAL MANA VERIFICATION - ENSURE MANA STAYS AT 100
tween({}, {}, {
duration: 100,
// Short delay to ensure all systems are initialized
onFinish: function onFinish() {
// FORCE MANA TO 100 AGAIN after all initialization
currentMana = 100;
activeSpellDeck.currentMana = 100;
// Mana system handled by deck menu
console.log('Mana system initialized through deck menu');
// Final debug verification
console.log('=== FINAL MANA VERIFICATION ===');
console.log('Final currentMana:', currentMana);
console.log('Final activeSpellDeck.currentMana:', activeSpellDeck.currentMana);
console.log('Mana system successfully initialized to 100');
}
});
// SPELL SLOTS SYSTEM REMOVED
// All spell casting now happens through the deck menu system only
console.log('=== SPELL SYSTEM SIMPLIFIED ===');
console.log('Deck menu is the only way to cast spells');
console.log('Spell slots have been eliminated');
// Start medieval music with user's volume setting
var musicVolume = storage.musicVolume || 0.7;
LK.playMusic('medievalTheme', {
volume: musicVolume,
fade: {
start: 0,
end: musicVolume,
duration: 2000
}
});
};
return self;
});
var Orb = Container.expand(function () {
var self = Container.call(this);
// Create orb visual using energy sphere
var orbGraphics = self.attachAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
orbGraphics.tint = 0xFFD700; // Golden color for orbs
orbGraphics.alpha = 0.9;
self.orbitalAngle = 0; // Starting angle for this orb
self.orbitalRadius = 880; // Distance from wizard - doubled again for even more separation
self.rotationSpeed = 0.025; // How fast orbs rotate - halved for slower movement
// Category 3: Orb-enemy collisions (separate from projectile-enemy and enemy-wizard)
self.processOrbEnemyCollisions = function () {
var allEnemies = collisionArrayPool.getAllEnemies();
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
// Skip collision check with wizard
if (enemy === wizard) {
continue;
}
// Initialize collision tracking for this enemy if not exists
if (!self.lastIntersecting) {
self.lastIntersecting = {};
}
if (self.lastIntersecting[i] === undefined) {
self.lastIntersecting[i] = false;
}
// 1.1 Distance Culling: Quick distance check before expensive collision detection
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var maxOrbRange = 80; // Orb collision range
var currentIntersecting = false;
if (distance <= maxOrbRange) {
// Only perform expensive intersection test if enemy is close enough
currentIntersecting = self.intersects(enemy);
}
if (!self.lastIntersecting[i] && currentIntersecting) {
// Deal damage to enemy on contact transition (first contact only)
enemy.takeDamage(200);
// Visual effect for orb hit
LK.effects.flashObject(self, 0xFFFFFF, 200);
// Create orb impact effect
var orbImpact = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 0.3,
scaleY: 0.3
}));
orbImpact.tint = 0xFFD700;
orbImpact.alpha = 0.8;
// Animate orb impact
tween(orbImpact, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
orbImpact.destroy();
}
});
}
// Update collision state for this enemy
self.lastIntersecting[i] = currentIntersecting;
}
};
self.update = function () {
// Pause orb when tutorial is active
if (tutorial && tutorial.isActive) {
return;
}
// Rotate around wizard
if (wizard) {
self.orbitalAngle += self.rotationSpeed;
self.x = wizard.x + Math.cos(self.orbitalAngle) * self.orbitalRadius;
self.y = wizard.y + Math.sin(self.orbitalAngle) * self.orbitalRadius - 240; // Position orb much higher up
}
// Add pulsing effect
var pulse = 1 + Math.sin(LK.ticks * 0.3) * 0.2;
orbGraphics.scaleX = 0.4 * pulse;
orbGraphics.scaleY = 0.4 * pulse;
// Category 3: Orb-enemy collisions (separate from projectile-enemy and enemy-wizard)
self.processOrbEnemyCollisions();
};
return self;
});
// Create global death handler instance
var Projectile = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'projectile';
self.speed = 50;
self.direction = {
x: 0,
y: 0
};
self.targetEnemy = null;
self.damage = 100;
self.hitEnemy = false;
// Define local projectile configurations since GAME_CONFIG is not available yet
var projectileConfigs = {
projectile: {
assetId: 'projectile',
scale: 1.5,
speed: 50,
damage: 100,
tint: 0x44aaff,
glowTint: 0x44aaff,
hasRotation: true
},
energyBeam: {
assetId: 'projectileGlow',
scale: 1.0,
speed: 60,
damage: 100,
tint: 0x00ffff,
glowTint: 0x00ffff,
hasRotation: true
},
fireBall: {
assetId: 'projectileGlow',
scale: 1.5,
speed: 40,
damage: 150,
tint: 0xFF4500,
glowTint: 0xFF6600,
hasRotation: true,
hasFlicker: true
}
};
// Get configuration from local configs
var config = projectileConfigs[self.type];
if (!config) {
// Fallback to projectile config
config = projectileConfigs.projectile;
}
self.speed = config.speed;
self.damage = config.damage;
// Create graphics based on type
var assetId = config.assetId;
self.graphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: config.scale,
scaleY: config.scale
});
self.graphics.tint = config.tint;
// Add glow for basic projectiles
if (self.type === 'projectile') {
var glow = self.attachAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
glow.alpha = 0.3;
glow.tint = config.glowTint;
}
self.update = function () {
if (tutorial && tutorial.isActive) return;
// Move projectile
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Visual effects
if (config.hasRotation) {
var angle = Math.atan2(self.direction.y, self.direction.x);
self.graphics.rotation = angle + Math.PI / 2;
}
if (config.hasFlicker) {
var flicker = 1 + Math.sin(LK.ticks * 0.4) * 0.3;
self.graphics.scaleX = config.scale * flicker;
self.graphics.scaleY = config.scale * flicker;
}
// Remove if off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
// Remove from projectiles array before destroying
ProjectileFactory.removeProjectile(self);
return;
}
// Handle collisions
self.checkCollisions();
};
self.checkCollisions = function () {
if (self.hitEnemy) return;
// Category 1: Area damage projectiles (fireball)
if (self.type === 'fireBall') {
self.processAreaDamageCollisions();
} else {
// Category 2: Single target projectiles
self.processSingleTargetCollisions();
}
};
// Separate collision processing for area damage projectiles
self.processAreaDamageCollisions = function () {
var allEnemies = collisionArrayPool.getAllEnemies();
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
if (enemy === wizard || enemy.isDying) continue;
// 1.1 Distance Culling: Quick distance check before expensive collision detection
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var maxFireballRange = 120; // Fireball collision range including area effect
// Only perform expensive intersection test if enemy is within range
if (distance <= maxFireballRange) {
if (self.intersects(enemy)) {
enemy.takeDamage(self.damage);
self.createExplosion(enemy);
self.hitEnemy = true;
// Remove from projectiles array before destroying
ProjectileFactory.removeProjectile(self);
return;
}
}
}
};
// Separate collision processing for single target projectiles
self.processSingleTargetCollisions = function () {
if (self.targetEnemy && self.targetEnemy.parent && !self.targetEnemy.isDying) {
// 1.1 Distance Culling: Quick distance check before expensive collision detection
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var maxProjectileRange = 100; // Standard projectile collision range
// Only perform expensive intersection test if target enemy is within range
if (distance <= maxProjectileRange) {
if (self.intersects(self.targetEnemy)) {
self.targetEnemy.takeDamage(self.damage);
self.hitEnemy = true;
// Remove from projectiles array before destroying
ProjectileFactory.removeProjectile(self);
return;
}
}
} else {
// Target is invalid, remove projectile
self.hitEnemy = true;
// Remove from projectiles array before destroying
ProjectileFactory.removeProjectile(self);
return;
}
};
self.createExplosion = function (enemy) {
LK.effects.flashObject(enemy, 0xFF4500, 400);
var explosion = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 2,
scaleY: 2
}));
explosion.tint = 0xFF6600;
explosion.alpha = 0.8;
tween(explosion, {
scaleX: 5,
scaleY: 5,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
};
return self;
});
// Simple effects helper for basic visual feedback
// Simple SpellDeck class to replace the complex system
var SpellDeck = Container.expand(function () {
var self = Container.call(this);
// Use the existing simple spell system
self.currentDeck = storage.spellDeck || ['fireball', 'heal', 'lightning'];
self.availableSpells = availableSpells; // Reference to global availableSpells
// Simple methods to match the interface expected by GameMenu
self.addToDeck = function (spellId) {
if (self.currentDeck.length >= 5) return false;
if (self.currentDeck.indexOf(spellId) !== -1) return false;
self.currentDeck.push(spellId);
storage.spellDeck = self.currentDeck.slice();
return true;
};
self.removeFromDeck = function (spellId) {
var index = self.currentDeck.indexOf(spellId);
if (index === -1) return false;
self.currentDeck.splice(index, 1);
storage.spellDeck = self.currentDeck.slice();
return true;
};
self.getSpell = function (spellId) {
return _getSpell(spellId);
};
self.getRarityColor = function (rarity) {
return _getRarityColor(rarity);
};
self.unlockSpell = function (spellId) {
// Simple unlock system - spells are always available
return true;
};
return self;
});
// Simple spell system - no complex classes needed
var Tutorial = Container.expand(function () {
var self = Container.call(this);
// Tutorial state variables
self.currentStep = 0;
self.isActive = false;
self.skipped = false; // Track if tutorial was skipped
self.tutorialSteps = [];
self.highlightElements = [];
self.tutorialTexts = [];
self.arrows = [];
// Tutorial overlay background
var tutorialOverlay = self.attachAsset('startMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 1.0,
scaleY: 1.0
});
tutorialOverlay.alpha = 0.8;
tutorialOverlay.tint = 0x000000;
tutorialOverlay.visible = false; // Initially hidden
tutorialOverlay.zIndex = 1999; // Ensure proper layering
tutorialOverlay.interactive = true; // Always interactive to block clicks
// Define tutorial steps
self.initializeTutorialSteps = function () {
self.tutorialSteps = [{
id: 'welcome',
title: 'BIENVENIDO A WIZARD DEFENDER!',
description: 'Eres un poderoso mago que debe defender su castillo. Toca directamente sobre los enemigos para atacarlos con hechizos.',
duration: 3000,
showSkip: true,
highlightElement: 'screen',
waitForTap: true
}, {
id: 'enemies_approach',
title: 'ENEMIGOS Y MEJORAS',
description: 'Los esqueletos vienen por 5 caminos diferentes. Toca directamente sobre cada enemigo para atacarlo. Gana monedas y desbloquea mejoras cada 12 enemigos.',
duration: 3000,
spawnDemoEnemy: true,
highlightElement: 'coinCounter'
}, {
id: 'tutorial_complete',
title: 'TUTORIAL COMPLETADO!',
description: 'Recuerda: toca directamente sobre los enemigos para atacarlos. Cuida tu salud y sobrevive el mayor tiempo posible. ¡Buena suerte!',
duration: 3000,
startGame: true,
highlightElement: 'healthBar'
}];
};
// Start the tutorial
self.startTutorial = function () {
// Always show tutorial when explicitly called
self.isActive = true;
self.currentStep = 0;
// Initialize tutorial steps first
self.initializeTutorialSteps();
// Make tutorial visible and properly layered
self.visible = true;
self.zIndex = 2000;
// Configure tutorial overlay properly
tutorialOverlay.visible = true;
tutorialOverlay.alpha = 0.8;
tutorialOverlay.tint = 0x000000;
tutorialOverlay.interactive = true; // Block clicks to game below
tutorialOverlay.zIndex = 1999; // Ensure proper layering
// Hide game menu while tutorial is active
if (gameMenu) {
gameMenu.visible = false;
}
// Hide all game elements during tutorial
if (wizard) wizard.visible = false;
if (backgroundMap) backgroundMap.visible = false;
coinText.visible = false;
killCountText.visible = false;
tapText.visible = false;
healthBarBg.visible = false;
healthBar.visible = false;
healthText.visible = false;
for (var i = 0; i < paths.length; i++) {
paths[i].visible = false;
}
// Start first step
self.showStep(0);
return true; // Tutorial started
};
// Show a specific tutorial step
self.showStep = function (stepIndex) {
if (stepIndex >= self.tutorialSteps.length) {
self.completeTutorial();
return;
}
// Clear previous step elements
self.clearStepElements();
var step = self.tutorialSteps[stepIndex];
self.currentStep = stepIndex;
// Ensure tutorial is visible and on top
self.visible = true;
tutorialOverlay.visible = true;
self.zIndex = 2000;
// Create step title with proper sizing
var titleText = new Text2(step.title, {
size: 80,
fill: 0xFFD700,
font: "monospace",
wordWrap: true,
wordWrapWidth: 1600
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 900;
self.addChild(titleText);
self.tutorialTexts.push(titleText);
// Create step description with proper text wrapping
var descText = new Text2(step.description, {
size: 60,
fill: 0xFFFFFF,
font: "monospace",
wordWrap: true,
wordWrapWidth: 1800
});
descText.anchor.set(0.5, 0.5);
descText.x = 2048 / 2;
descText.y = 1300;
self.addChild(descText);
self.tutorialTexts.push(descText);
// Handle special step behaviors
if (step.spawnDemoEnemy) {
self.spawnDemoEnemy();
}
if (step.highlightElement) {
self.highlightElement(step.highlightElement);
}
// Show continue button or wait for specific action
if (step.waitForTap) {
var tapPrompt = new Text2('TOCA LA PANTALLA PARA CONTINUAR', {
size: 50,
fill: 0x00FF00,
font: "monospace",
wordWrap: true,
wordWrapWidth: 1400
});
tapPrompt.anchor.set(0.5, 0.5);
tapPrompt.x = 2048 / 2;
tapPrompt.y = 1800;
self.addChild(tapPrompt);
self.tutorialTexts.push(tapPrompt);
// Add pulsing effect to tap prompt
tween(tapPrompt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(tapPrompt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
} else {
// Always show continue prompt - no auto-advance
var continuePrompt = new Text2('TOCA LA PANTALLA PARA CONTINUAR', {
size: 50,
fill: 0x00FF00,
font: "monospace",
wordWrap: true,
wordWrapWidth: 1400
});
continuePrompt.anchor.set(0.5, 0.5);
continuePrompt.x = 2048 / 2;
continuePrompt.y = 1800;
self.addChild(continuePrompt);
self.tutorialTexts.push(continuePrompt);
// Add pulsing effect to continue prompt
tween(continuePrompt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(continuePrompt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
// Show skip button on first step
if (step.showSkip) {
var skipBtn = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 200,
y: 200,
scaleX: 1.5,
scaleY: 1.5
});
skipBtn.tint = 0xFF4444;
self.highlightElements.push(skipBtn);
var skipText = new Text2('OMITIR', {
size: 50,
fill: 0xFFFFFF,
font: "monospace"
});
skipText.anchor.set(0.5, 0.5);
skipText.x = 2048 - 200;
skipText.y = 200;
self.addChild(skipText);
self.tutorialTexts.push(skipText);
}
};
// Highlight specific game elements
self.highlightElement = function (elementType) {
switch (elementType) {
case 'healthBar':
if (healthBarBg && healthBar) {
// Show health UI temporarily
healthBarBg.visible = true;
healthBar.visible = true;
healthText.visible = true;
// Create highlight glow around health bar
var healthGlow = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: 270,
y: 110,
scaleX: 3,
scaleY: 1.5
}));
healthGlow.tint = 0x00FF00;
healthGlow.alpha = 0.6;
self.highlightElements.push(healthGlow);
// Animate glow
tween(healthGlow, {
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeInOut
});
}
break;
case 'coinCounter':
if (coinText) {
// Show coin UI temporarily
coinText.visible = true;
coinText.setText('Coins: 15'); // Show example coins
// Create highlight glow around coin counter
var coinGlow = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: 270,
y: 150,
scaleX: 3,
scaleY: 1.5
}));
coinGlow.tint = 0xFFD700;
coinGlow.alpha = 0.6;
self.highlightElements.push(coinGlow);
// Animate glow
tween(coinGlow, {
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeInOut
});
}
break;
case 'screen':
// Create large highlight overlay
var screenGlow = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 20,
scaleY: 25
}));
screenGlow.tint = 0x00FFFF;
screenGlow.alpha = 0.2;
self.highlightElements.push(screenGlow);
// Animate screen glow
tween(screenGlow, {
alpha: 0.1
}, {
duration: 1500,
easing: tween.easeInOut
});
// Create arrow pointing to center
self.createArrow(2048 / 2, 2732 / 2 - 400, 2048 / 2, 2732 / 2);
break;
}
};
// Create pointing arrow
self.createArrow = function (fromX, fromY, toX, toY) {
var arrow = game.addChild(LK.getAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: fromX,
y: fromY,
scaleX: 3,
scaleY: 3
}));
arrow.tint = 0xFFD700;
arrow.alpha = 0.8;
// Calculate arrow direction
var dx = toX - fromX;
var dy = toY - fromY;
var angle = Math.atan2(dy, dx);
arrow.rotation = angle;
// Animate arrow bouncing
tween(arrow, {
x: toX,
y: toY
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(arrow, {
x: fromX,
y: fromY
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
self.arrows.push(arrow);
};
// Spawn a demo enemy for tutorial
self.spawnDemoEnemy = function () {
// Create a slow-moving demo enemy using unified Enemy class
var demoEnemy = game.addChild(new Enemy('skeleton'));
demoEnemy.x = 2048 / 2;
demoEnemy.y = -100;
demoEnemy.speed = 1; // Very slow for demo
demoEnemy.health = 1;
demoEnemy.maxHealth = 1;
demoEnemy.pathIndex = 0; // Center path
// Make demo enemy more visible
for (var frameIdx = 0; frameIdx < demoEnemy.animationFrames.length; frameIdx++) {
demoEnemy.animationFrames[frameIdx].tint = 0xFF6600; // Orange tint
}
enemies.push(demoEnemy);
// Create highlight around demo enemy
var enemyGlow = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: demoEnemy.x,
y: demoEnemy.y,
scaleX: 4,
scaleY: 4
}));
enemyGlow.tint = 0xFF0000;
enemyGlow.alpha = 0.5;
// Make glow follow enemy
var _glowFollower = function glowFollower() {
if (demoEnemy && demoEnemy.parent && enemyGlow && enemyGlow.parent) {
enemyGlow.x = demoEnemy.x;
enemyGlow.y = demoEnemy.y;
// Continue following
tween({}, {}, {
duration: 60,
onFinish: _glowFollower
});
} else if (enemyGlow && enemyGlow.parent) {
enemyGlow.destroy();
}
};
_glowFollower();
self.highlightElements.push(enemyGlow);
};
// Clear all tutorial step elements
self.clearStepElements = function () {
// Clear tutorial texts
for (var i = 0; i < self.tutorialTexts.length; i++) {
if (self.tutorialTexts[i] && self.tutorialTexts[i].parent) {
self.tutorialTexts[i].destroy();
}
}
self.tutorialTexts = [];
// Clear highlight elements
for (var i = 0; i < self.highlightElements.length; i++) {
if (self.highlightElements[i] && self.highlightElements[i].parent) {
self.highlightElements[i].destroy();
}
}
self.highlightElements = [];
// Clear arrows
for (var i = 0; i < self.arrows.length; i++) {
if (self.arrows[i] && self.arrows[i].parent) {
self.arrows[i].destroy();
}
}
self.arrows = [];
};
// Advance to next tutorial step
self.nextStep = function () {
self.currentStep++;
if (self.currentStep < self.tutorialSteps.length) {
self.showStep(self.currentStep);
} else {
// Tutorial completed naturally (not skipped)
self.skipped = false;
self.completeTutorial();
}
};
// Complete the tutorial
self.completeTutorial = function () {
// Mark tutorial as completed
storage.tutorialCompleted = true;
// Clear all tutorial elements
self.clearStepElements();
// Hide tutorial completely
tutorialOverlay.visible = false;
tutorialOverlay.interactive = false; // Remove interactivity
self.visible = false;
self.isActive = false;
// Only auto-start game if tutorial was completed naturally (not skipped)
if (!self.skipped) {
// Start game immediately without showing menu
if (gameMenu) {
gameMenu.startGame();
}
} else {
// If skipped, show the menu
if (gameMenu) {
gameMenu.visible = true;
}
}
// Reset skipped flag for next time
self.skipped = false;
};
// Handle tutorial interactions
// Add interaction handler for spell cards in deck view
self.down = function (x, y, obj) {
// Only handle interactions if deck is active or if this is tutorial
if (self.deckMode) {
// Check deck card clicks for spell casting (from current deck)
for (var i = 0; i < self.deckElements.length; i++) {
var element = self.deckElements[i];
if (element.spellId && element.isDeckCard) {
var cardX = element.x;
var cardY = element.y;
if (x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225) {
// Cast the spell instead of removing it
if (activeSpellDeck && activeSpellDeck.canCastSpell(element.spellId)) {
var spell = activeSpellDeck.getSpell(element.spellId);
if (spell) {
var targetX = wizard.x;
var targetY = wizard.y - 100;
// Find nearest enemy for targeted spells
if (spell.id === 'fireball' || spell.id === 'iceShard' || spell.id === 'lightning') {
var allEnemies = collisionArrayPool.getAllEnemies();
var nearestEnemy = null;
var nearestDistance = Infinity;
for (var e = 0; e < allEnemies.length; e++) {
var enemy = allEnemies[e];
var dx = enemy.x - wizard.x;
var dy = enemy.y - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestEnemy = enemy;
}
}
if (nearestEnemy) {
targetX = nearestEnemy.x;
targetY = nearestEnemy.y;
}
}
// Cast the spell using specific functions
if (element.spellId === 'fireball') {
castFireball();
} else if (element.spellId === 'lightning') {
castLightning();
} else if (element.spellId === 'heal') {
castHeal();
} else {
console.log('Unknown spell ID in tutorial:', element.spellId);
castFireball(); // Default to fireball
}
LK.effects.flashObject(element, 0x00FF00, 200);
// Show cast message
var castText = new Text2('HECHIZO LANZADO!', {
size: 60,
fill: 0x00FF00,
font: "monospace"
});
castText.anchor.set(0.5, 0.5);
castText.x = 2048 / 2;
castText.y = 2200;
self.addChild(castText);
// Animate and remove message
tween(castText, {
alpha: 0,
y: castText.y - 100
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (castText.parent) castText.destroy();
}
});
}
} else {
LK.effects.flashObject(element, 0xFF0000, 200);
// Show "cannot cast" message
var errorText = new Text2('NO SE PUEDE LANZAR', {
size: 50,
fill: 0xFF6666,
font: "monospace"
});
errorText.anchor.set(0.5, 0.5);
errorText.x = 2048 / 2;
errorText.y = 2200;
self.addChild(errorText);
// Animate and remove message
tween(errorText, {
alpha: 0,
y: errorText.y - 100
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (errorText.parent) errorText.destroy();
}
});
}
return;
}
}
}
// Continue with existing available cards handling...
// Check available card clicks with better hit detection
for (var i = 0; i < self.availableElements.length; i++) {
var element = self.availableElements[i];
if (element.spellId && !element.isDeckCard) {
var cardX = element.x;
var cardY = element.y;
if (x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225) {
// Visual feedback before addition
LK.effects.flashObject(element, 0x00FF00, 300);
// Add to deck
if (self.spellDeck.addToDeck(element.spellId)) {
self.refreshDeckDisplay();
LK.effects.flashScreen(0x00FF00, 200);
// Show addition message
var addText = new Text2('HECHIZO AÑADIDO', {
size: 60,
fill: 0x66FF66,
font: "monospace"
});
addText.anchor.set(0.5, 0.5);
addText.x = 2048 / 2;
addText.y = 2200;
self.addChild(addText);
// Animate and remove message
tween(addText, {
alpha: 0,
y: addText.y - 100
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (addText.parent) addText.destroy();
}
});
} else {
LK.effects.flashScreen(0xFF0000, 200);
// Show error message
var errorText = new Text2('DECK LLENO (MAX 5)', {
size: 50,
fill: 0xFF6666,
font: "monospace"
});
errorText.anchor.set(0.5, 0.5);
errorText.x = 2048 / 2;
errorText.y = 2200;
self.addChild(errorText);
// Animate and remove message
tween(errorText, {
alpha: 0,
y: errorText.y - 100
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (errorText.parent) errorText.destroy();
}
});
}
return;
}
}
}
// Deck back button
if (y >= 2350 && y <= 2650) {
self.hideDeck();
}
// Block all other interactions when deck menu is active
return;
}
// Tutorial handling (moved after deck handling)
if (!self.isActive) return;
var step = self.tutorialSteps[self.currentStep];
// Handle skip button (top-right corner)
if (step.showSkip && x >= 2048 - 300 && x <= 2048 - 100 && y >= 100 && y <= 300) {
self.skipped = true; // Mark as skipped
self.completeTutorial();
return;
}
// For any tap anywhere on screen, advance to next step
if (step.id === 'tap_to_attack') {
// Simulate spell casting for demo
if (wizard) {
wizard.attack(0); // Attack center path
}
// Wait a moment then advance
tween({}, {}, {
duration: 1000,
onFinish: function onFinish() {
self.nextStep();
}
});
} else {
// For all other steps, advance immediately on any tap
self.nextStep();
}
};
return self;
});
// Impact effect function now handled by BaseDamageHandler
// Unified death handler for all enemy types using enemy configuration
var UnifiedDeathHandler = Container.expand(function () {
var self = Container.call(this);
// Execute enemy death with consolidated coin and reward logic
self.executeEnemyDeath = function (enemy, enemyArray) {
enemy.animationState = 'dying';
enemy.currentFrame = 3;
LK.getSound('painSound').play();
enemy.isDying = true;
// Use direct enemy configuration values based on type
var deathRotation = Math.PI * 0.5;
var numCoins = 1;
// Set specific values based on enemy type
if (enemy.enemyType === 'miniBoss') {
deathRotation = Math.PI * 0.8;
numCoins = 5;
} else if (enemy.enemyType === 'ogre') {
deathRotation = Math.PI * 0.6;
numCoins = 1;
} else if (enemy.enemyType === 'knight') {
deathRotation = Math.PI * 0.7;
numCoins = 1;
}
// Special cleanup for mini boss UI elements
if (enemy.enemyType === 'miniBoss') {
self.cleanupMiniBossUI(enemy);
}
// Execute unified death animation
tween(enemy, {
alpha: 0,
scaleX: 0.3,
scaleY: 0.3,
rotation: deathRotation
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.handleDeathRewards(enemy, numCoins);
self.updateProgression(enemy);
self.cleanupEnemy(enemy, enemyArray);
}
});
};
// Clean up mini boss UI elements
self.cleanupMiniBossUI = function (enemy) {
if (enemy.healthBarBg && enemy.healthBarBg.parent) {
enemy.healthBarBg.destroy();
}
if (enemy.healthBarFg && enemy.healthBarFg.parent) {
enemy.healthBarFg.destroy();
}
if (enemy.healthText && enemy.healthText.parent) {
enemy.healthText.destroy();
}
};
// Handle coin drops and difficulty-based rewards using pooled coins
self.handleDeathRewards = function (enemy, numCoins) {
var selectedDifficulty = storage.difficulty || 'NORMAL';
for (var coinIdx = 0; coinIdx < numCoins; coinIdx++) {
// Create coin directly without object pool
var coin = new Coin();
coin.x = enemy.x + (enemy.enemyType === 'miniBoss' ? (Math.random() - 0.5) * 200 : 0);
coin.y = enemy.y - 50 + (enemy.enemyType === 'miniBoss' ? (Math.random() - 0.5) * 100 : 0);
coin.isAnimating = true;
coin.bobOffset = Math.random() * Math.PI * 2;
coin.initialY = 0;
coin.visible = true;
coin.alpha = 1.0;
game.addChild(coin);
coins.push(coin);
var coinTargetX = 120 + coinText.width / 2;
var coinTargetY = 90 + coinText.height / 2;
tween(coin, {
x: coinTargetX,
y: coinTargetY,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 1000 + (enemy.enemyType === 'miniBoss' ? coinIdx * 200 : 0),
easing: tween.easeOut,
onFinish: function onFinish() {
self.processCoinReward(enemy, selectedDifficulty, coin);
}
});
}
};
// Process coin rewards with difficulty modifiers
self.processCoinReward = function (enemy, selectedDifficulty, coin) {
var coinReward = enemy.enemyType === 'miniBoss' ? 10 : 1;
if (selectedDifficulty === 'FACIL') {
coinReward = Math.floor(coinReward * 1.5);
} else if (selectedDifficulty === 'DIFICIL') {
coinReward = Math.max(1, Math.floor(coinReward * 0.75));
}
coinCounter += coinReward;
coinText.setText('Coins: ' + coinCounter);
// Easy difficulty healing bonus
if (selectedDifficulty === 'FACIL' && Math.random() < 0.15) {
wizard.health = Math.min(wizard.health + 5, wizard.maxHealth);
updateHealthBar();
LK.effects.flashObject(wizard, 0x00FF00, 200);
}
// Remove coin from tracking array
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i] === coin) {
coins.splice(i, 1);
break;
}
}
// Destroy coin directly without object pool
coin.destroy();
};
// Update kill counter and experience progression
self.updateProgression = function (enemy) {
var killIncrement = enemy.enemyType === 'miniBoss' ? 10 : 1;
enemyKillCounter += killIncrement;
killCountText.setText('Puntuacion: ' + enemyKillCounter);
wizard.gainExperience(enemy.enemyType === 'miniBoss' ? 250 : 25);
if (selectedEnemy === enemy) {
selectedEnemy = null;
}
};
// Clean up enemy from arrays and game
self.cleanupEnemy = function (enemy, enemyArray) {
// Remove from appropriate array
for (var i = enemyArray.length - 1; i >= 0; i--) {
if (enemyArray[i] === enemy) {
enemyArray.splice(i, 1);
break;
}
}
// Also remove from global enemy manager collections
globalEnemyManager.removeFromCollection(enemy, enemy.enemyType);
// Remove from all legacy arrays to ensure proper cleanup
var allArrays = [enemies, ogres, knights, miniBosses];
for (var arrayIdx = 0; arrayIdx < allArrays.length; arrayIdx++) {
var array = allArrays[arrayIdx];
for (var i = array.length - 1; i >= 0; i--) {
if (array[i] === enemy) {
array.splice(i, 1);
break;
}
}
}
enemy.destroy();
// Set score reward based on enemy type
var scoreReward = 10;
if (enemy.enemyType === 'miniBoss') {
scoreReward = 100;
} else if (enemy.enemyType === 'ogre') {
scoreReward = 20;
} else if (enemy.enemyType === 'knight') {
scoreReward = 30;
}
LK.setScore(LK.getScore() + scoreReward);
};
return self;
});
// UpgradeMenu class removed - using spell deck system instead
// Unified Projectile Factory using consolidated GAME_CONFIG.projectiles
var Wizard = Container.expand(function () {
var self = Container.call(this);
// Animation system for wizard
self.currentFrame = 1;
self.animationTimer = 0;
self.animationSpeed = 18; // Change frame every 18 ticks (300ms at 60fps)
// Create all wizard graphics frames and store them
self.wizardFrames = [];
for (var i = 1; i <= 4; i++) {
var frameGraphics = self.attachAsset('wizard' + i, {
anchorX: 0.5,
anchorY: 1.0,
scaleX: 2.5,
scaleY: 2.5
});
frameGraphics.visible = i === 1; // Only show first frame initially
self.wizardFrames.push(frameGraphics);
}
// Create invisible hitbox with much smaller size for more precise collision
var hitbox = self.attachAsset('wizard1', {
anchorX: 0.3,
anchorY: 1.0,
scaleX: 0.25,
// Much smaller size for very precise collision
scaleY: 0.3 // Much smaller size for very precise collision
});
hitbox.alpha = 0; // Make hitbox invisible
// Position hitbox slightly to the right to reduce left side
hitbox.x = 15; // Offset hitbox to the right
// Create shield visual effect
self.shieldGraphics = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 3
});
self.shieldGraphics.alpha = 0.7;
self.shieldGraphics.visible = false;
self.attackCooldown = 0;
self.level = 1;
self.health = 100;
self.maxHealth = 100;
self.shieldActive = false; // Track shield status
// Upgrade system removed - simplified wizard properties
// Override intersects method to use smaller hitbox
self.intersects = function (other) {
return hitbox.intersects(other);
};
self.update = function () {
// Pause wizard when tutorial is active
if (tutorial && tutorial.isActive) {
return;
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Update shield visibility based on shield status
self.shieldGraphics.visible = self.shieldActive;
if (self.shieldActive) {
// Animate shield with pulsing effect
var pulse = 1 + Math.sin(LK.ticks * 0.15) * 0.2;
self.shieldGraphics.scaleX = 3 * pulse;
self.shieldGraphics.scaleY = 3 * pulse;
// Slowly rotate shield
self.shieldGraphics.rotation += 0.03;
// Add glowing effect
self.shieldGraphics.alpha = 0.6 + Math.sin(LK.ticks * 0.1) * 0.2;
}
// Upgrade-based abilities removed - using spell deck system instead
// Simplified animation system - instant frame switching only
self.animationTimer++;
if (self.animationTimer >= self.animationSpeed) {
self.animationTimer = 0;
// Hide current frame
self.wizardFrames[self.currentFrame - 1].visible = false;
// Move to next frame
self.currentFrame++;
if (self.currentFrame > 4) {
self.currentFrame = 1;
}
// Show next frame
self.wizardFrames[self.currentFrame - 1].visible = true;
}
};
self.attack = function (direction) {
if (self.attackCooldown <= 0) {
// Default direction if none specified
if (direction === undefined) {
direction = 0; // Default to center path
}
// Get attack angle based on path direction
var attackAngle = pathAngles[direction];
var attackDistance = 100;
// Calculate spell position based on attack direction
var spellX = self.x + Math.cos(attackAngle) * attackDistance;
var spellY = self.y + Math.sin(attackAngle) * attackDistance;
// Create spell effect
var spell = game.addChild(LK.getAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: spellX,
y: spellY,
scaleX: 0.5,
scaleY: 0.5
}));
// Animate spell with magical effects
tween(spell, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
spell.destroy();
}
});
// Add rotation animation to spell
tween(spell, {
rotation: Math.PI * 2
}, {
duration: 500,
easing: tween.linear
});
self.attackCooldown = 30; // 0.5 seconds at 60fps
LK.getSound('spellCast').play();
// Base damage for wizard attack
var totalDamage = 1;
// Attack enemies in the specified direction/path
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.pathIndex === direction) {
// Only hit enemies on exact same path - no distance validation
enemy.takeDamage(totalDamage);
}
}
// Attack ogres in the specified direction/path
for (var i = ogres.length - 1; i >= 0; i--) {
var ogre = ogres[i];
if (ogre.pathIndex === direction) {
// Only hit ogres on exact same path - no distance validation
ogre.takeDamage(totalDamage);
}
}
// Attack knights in the specified direction/path
for (var i = knights.length - 1; i >= 0; i--) {
var knight = knights[i];
if (knight.pathIndex === direction) {
// Only hit knights on exact same path - no distance validation
knight.takeDamage(totalDamage);
}
}
return true;
}
return false;
};
self.gainExperience = function (amount) {
self.experience += amount;
var expNeeded = self.level * 100;
if (self.experience >= expNeeded) {
self.levelUp();
}
};
self.levelUp = function () {
self.level++;
self.experience = 0;
// Visual level up effect
LK.effects.flashObject(self, 0xFFD700, 500);
};
self.takeDamage = function (damage) {
// Check if teleport invulnerability is active
if (self.teleportInvuln) {
GameManager.createFlashEffect(self, 0x8000FF, 200);
return;
}
// Check if shield is active
if (self.shieldActive) {
// Initialize shield properties if not set
if (self.maxShieldHits === undefined) {
self.maxShieldHits = 1;
self.currentShieldHits = 0;
}
// Increment shield hits
self.currentShieldHits++;
// Visual feedback for shield use
GameManager.createFlashEffect(self, 0x00BFFF, 300);
// Check if shield is depleted
if (self.currentShieldHits >= self.maxShieldHits) {
self.shieldActive = false;
// Start shield regeneration timer
var regenTime = self.shieldRegen ? 5000 : 10000; // Faster regen if improved
tween({}, {}, {
duration: regenTime,
onFinish: function onFinish() {
// Regenerate shield
self.shieldActive = true;
self.currentShieldHits = 0;
// Visual feedback for shield regeneration
GameManager.createFlashEffect(self, 0x00BFFF, 500);
// Add shield regeneration animation
tween(self.shieldGraphics, {
scaleX: 5,
scaleY: 5,
alpha: 1.0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.shieldGraphics, {
scaleX: 3,
scaleY: 3,
alpha: 0.7
}, {
duration: 400,
easing: tween.easeIn
});
}
});
}
});
}
// No damage taken, shield absorbed it
return;
}
// Use unified damage handler for core damage logic
self.health -= damage;
GameManager.createFlashEffect(self, 0xFF0000, 200);
if (self.health <= 0) {
self.health = 0;
// 10% chance to revive when dying
var reviveChance = Math.random();
if (reviveChance < 0.10) {
// Revival successful!
self.health = Math.floor(self.maxHealth * 0.5); // Revive with 50% health
// Destroy ALL enemies when revival activates (no distance restriction)
var allEnemies = collisionArrayPool.getAllEnemies();
for (var enemyIdx = allEnemies.length - 1; enemyIdx >= 0; enemyIdx--) {
var enemy = allEnemies[enemyIdx];
// Create destruction effect for each enemy
GameManager.createFlashEffect(enemy, 0xFFD700, 500);
// Create golden explosion particles
GameManager.createVisualEffect('explosion', enemy, {
explosionColor: 0xFFD700,
explosionScale: 4.0
});
// Kill ALL enemies instantly by calling die() method
enemy.die();
}
// Visual effects for revival
LK.effects.flashScreen(0x00FF00, 1500); // Green flash for revival
GameManager.createFlashEffect(self, 0xFFD700, 1000); // Golden flash on wizard
// Create healing aura effect
GameManager.createVisualEffect('explosion', self, {
explosionColor: 0x00FF00,
explosionScale: 8.0
});
// Play spell cast sound for revival
LK.getSound('spellCast').play();
// Update health bar to show revival
updateHealthBar();
} else {
// Game over when health reaches 0 and no revival
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
}
}
// Update health bar
updateHealthBar();
// Simplified screen shake for better performance
var shakeIntensity = 8;
var originalX = game.x;
var originalY = game.y;
// Simple single shake effect
tween(game, {
x: originalX + shakeIntensity,
y: originalY + shakeIntensity * 0.5
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(game, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.easeIn
});
}
});
};
// Force push ability removed - not used in current spell system
// Freeze pulse ability removed - not used in current spell system
// Thorns ability removed - not used in current spell system
// Fireball launch removed - using spell deck system instead
// Frame transition config removed - using simple animation only
// Advanced transition removed - using simple frame switching only
// Basic transition removed - using instant frame switching only
// Sparkle effects removed - using simplified animations only
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 // Black background for pixel art
});
/****
* Game Code
****/
// Simple spell system - no complex classes needed
/****
* Constants & Configuration
****/
// Unified Game Manager for all simple operations
// Import tween plugin to ensure it's available throughout game code
function _typeof2(o) {
"@babel/helpers - typeof";
return _typeof2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof2(o);
}
var availableSpells = [{
id: 'fireball',
name: 'FIREBALL',
damage: 150,
manaCost: 30,
rarity: 'common',
description: 'Lanza bola de fuego explosiva'
}, {
id: 'heal',
name: 'HEAL',
healing: 50,
manaCost: 25,
rarity: 'common',
description: 'Restaura puntos de salud'
}, {
id: 'lightning',
name: 'LIGHTNING',
damage: 200,
manaCost: 40,
rarity: 'rare',
description: 'Cadena de rayos entre enemigos'
}];
// PASO 2: Función de validación de maná mejorada
function validateManaSystem() {
var manaWasCorrupted = false;
console.log('=== VALIDATING MANA SYSTEM ===');
console.log('currentMana (before):', currentMana, _typeof2(currentMana));
console.log('activeSpellDeck.currentMana (before):', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
// CRÍTICO: Validar currentMana con detección de corrupción
if (typeof currentMana !== 'number' || isNaN(currentMana) || currentMana < 0 || currentMana > 100) {
console.log('⚠️ currentMana corrupted, resetting to 100');
currentMana = 100;
manaWasCorrupted = true;
}
// CRÍTICO: Validar activeSpellDeck existe y tiene propiedades válidas
if (!activeSpellDeck) {
console.log('⚠️ activeSpellDeck missing, creating with full mana');
activeSpellDeck = {
currentMana: 100,
maxMana: 100
};
manaWasCorrupted = true;
}
// CRÍTICO: Validar activeSpellDeck.currentMana
if (typeof activeSpellDeck.currentMana !== 'number' || isNaN(activeSpellDeck.currentMana) || activeSpellDeck.currentMana < 0 || activeSpellDeck.currentMana > 100) {
console.log('⚠️ activeSpellDeck.currentMana corrupted, resetting to 100');
activeSpellDeck.currentMana = 100;
activeSpellDeck.maxMana = 100;
manaWasCorrupted = true;
}
// CRÍTICO: Sincronizar si hay diferencias entre las dos variables
if (Math.abs(currentMana - activeSpellDeck.currentMana) > 1) {
console.log('⚠️ Mana desync detected - currentMana:', currentMana, 'activeSpellDeck.currentMana:', activeSpellDeck.currentMana);
// Usar el valor más alto pero válido
var validMana = Math.max(currentMana, activeSpellDeck.currentMana);
if (validMana < 0 || validMana > 100 || typeof validMana !== 'number' || isNaN(validMana)) {
validMana = 100;
}
currentMana = validMana;
activeSpellDeck.currentMana = validMana;
manaWasCorrupted = true;
console.log('✅ Mana synced to:', validMana);
}
// CRÍTICO: Actualizar UI si hubo corrupción
if (manaWasCorrupted) {
console.log('🔧 Mana corruption detected and fixed');
// Mana UI updates handled by deck menu system
console.log('✅ Mana system repaired - currentMana:', currentMana, 'activeSpellDeck.currentMana:', activeSpellDeck.currentMana);
} else {
console.log('✅ Mana system healthy - no corruption detected');
}
console.log('=== MANA VALIDATION COMPLETE ===');
return !manaWasCorrupted;
}
// Simplified spell visual effects system
var SpellVisualEffects = {
// Simplified pre-cast effects - basic flash only
createPreCastEffects: function createPreCastEffects(spellType, wizard) {
// Basic flash effect for all spells
LK.effects.flashObject(wizard, this.getSpellColor(spellType), 300);
},
// Get spell color for basic effects
getSpellColor: function getSpellColor(spellType) {
if (spellType === 'fireball') return 0xFF4500;
if (spellType === 'lightning') return 0x00FFFF;
if (spellType === 'heal') return 0x00FF88;
return 0xFFFFFF;
},
// Simplified casting aura - single flash effect
createCastingAura: function createCastingAura(wizard, color) {
LK.effects.flashObject(wizard, color, 500);
},
// Simplified spell ring - basic visual feedback
createSpellRing: function createSpellRing(wizard, color) {
// Simple screen flash instead of complex ring
LK.effects.flashScreen(color, 200);
}
};
// Simple spell casting functions
function _canCastSpell(spellId) {
// PASO 1: Validar sistema de maná antes de verificar hechizos
validateManaSystem();
// Validate spell ID and existence
if (!spellId) {
console.log('_canCastSpell: No spell ID provided');
return false;
}
var spell = _getSpell(spellId);
if (!spell) {
console.log('_canCastSpell: Spell not found for ID:', spellId);
return false;
}
// Check mana cost with validated mana system
var manaCost = spell.manaCost || 0;
if (currentMana < manaCost) {
console.log('_canCastSpell: Not enough mana. Required:', manaCost, 'Current:', currentMana);
return false;
}
// Check individual card cooldown
var currentTick = LK.ticks || 0;
if (cardCooldowns[spellId] && currentTick < cardCooldowns[spellId]) {
console.log('_canCastSpell: Card on cooldown:', spellId);
return false;
}
console.log('_canCastSpell: Spell can be cast:', spellId);
return true;
}
function castFireball() {
console.log('=== CASTING FIREBALL ===');
console.log('Current mana before cast:', currentMana);
console.log('activeSpellDeck.currentMana before cast:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
var allEnemies = collisionArrayPool.getAllEnemies();
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
if (enemy.isDying) continue;
var dx = enemy.x - wizard.x;
var dy = enemy.y - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
// Simplified fireball casting effects
LK.effects.flashObject(wizard, 0xFF4500, 300);
LK.effects.flashScreen(0xFF4500, 200);
if (closestEnemy) {
// Simplified fireball trail effect - single trail element
var simpleTrail = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: wizard.x,
y: wizard.y,
scaleX: 1.5,
scaleY: 1.5
}));
simpleTrail.tint = 0xFF4500;
simpleTrail.alpha = 0.7;
simpleTrail.zIndex = 1610;
// Simple trail animation toward enemy
tween(simpleTrail, {
x: closestEnemy.x,
y: closestEnemy.y,
alpha: 0,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (simpleTrail.parent) simpleTrail.destroy();
}
});
// Create physical projectile that travels
var fireball = ProjectileFactory.createProjectile('fireBall', wizard.x, wizard.y, closestEnemy.x, closestEnemy.y, {
targetEnemy: closestEnemy,
damage: 150
});
// Add environmental lighting effect around the fireball path
var pathLight = game.addChild(LK.getAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: wizard.x,
y: wizard.y,
scaleX: 8,
scaleY: 2
}));
pathLight.tint = 0xFF4500;
pathLight.alpha = 0.2;
pathLight.zIndex = 1580;
// Light follows the fireball path
var pathAngle = Math.atan2(closestEnemy.y - wizard.y, closestEnemy.x - wizard.x);
pathLight.rotation = pathAngle;
tween(pathLight, {
alpha: 0,
scaleY: 4
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (pathLight.parent) pathLight.destroy();
}
});
LK.getSound('fireWhoosh').play();
showSpellDescription('FIREBALL', 'Daño: 150 al enemigo más cercano', 0xFF4500);
} else {
showSpellDescription('FIREBALL', 'No hay enemigos cerca', 0xFF4500);
}
// Consume mana
var spell = _getSpell('fireball');
var manaCost = spell ? spell.manaCost : 30;
currentMana = Math.max(0, currentMana - manaCost);
if (activeSpellDeck) {
activeSpellDeck.currentMana = currentMana;
}
updateManaDisplay();
cardCooldowns['fireball'] = LK.ticks + cardCooldownDuration;
LK.getSound('spellCast').play();
return true;
}
function castLightning() {
console.log('=== CASTING LIGHTNING CHAIN ===');
console.log('Current mana before cast:', currentMana);
console.log('activeSpellDeck.currentMana before cast:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
// Basic lightning casting effect
LK.effects.flashObject(wizard, 0x00FFFF, 300);
LK.effects.flashScreen(0x00FFFF, 200);
LK.getSound('iceFreeze').play();
var allEnemies = collisionArrayPool.getAllEnemies();
var livingEnemies = [];
for (var e = 0; e < allEnemies.length; e++) {
var enemy = allEnemies[e];
if (!enemy.isDying && enemy.health > 0 && enemy.parent) {
var dx = enemy.x - wizard.x;
var dy = enemy.y - wizard.y;
enemy.distanceFromWizard = Math.sqrt(dx * dx + dy * dy);
livingEnemies.push(enemy);
}
}
// Sort by distance for logical chaining
livingEnemies.sort(function (a, b) {
return a.distanceFromWizard - b.distanceFromWizard;
});
var targetsHit = Math.min(3, livingEnemies.length);
console.log('Lightning chaining to', targetsHit, 'enemies');
if (targetsHit > 0) {
// Apply damage to all targets simultaneously
for (var i = 0; i < targetsHit; i++) {
var enemy = livingEnemies[i];
console.log('Lightning hitting enemy', i + 1, 'with 200 damage');
if (enemy && enemy.parent && !enemy.isDying) {
enemy.health -= 200;
}
}
// Process deaths and visual effects after all damage applied
for (var i = 0; i < targetsHit; i++) {
var enemy = livingEnemies[i];
if (enemy && enemy.parent && !enemy.isDying) {
LK.effects.flashObject(enemy, 0x00FFFF, 600);
if (enemy.health <= 0) {
enemy.die();
}
// Create lightning impact on each enemy
var lightningImpact = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 3,
scaleY: 3
}));
lightningImpact.tint = 0x00FFFF;
lightningImpact.alpha = 1.0;
tween(lightningImpact, {
scaleX: 7,
scaleY: 7,
alpha: 0,
rotation: Math.PI * 3
}, {
duration: 800,
delay: i * 150,
easing: tween.easeOut,
onFinish: function onFinish() {
if (lightningImpact.parent) lightningImpact.destroy();
}
});
}
}
// Create lightning chain visual effects
var chainPoints = [{
x: wizard.x,
y: wizard.y
}];
for (var i = 0; i < targetsHit; i++) {
chainPoints.push({
x: livingEnemies[i].x,
y: livingEnemies[i].y
});
}
for (var i = 0; i < chainPoints.length - 1; i++) {
var startPoint = chainPoints[i];
var endPoint = chainPoints[i + 1];
var dx = endPoint.x - startPoint.x;
var dy = endPoint.y - startPoint.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var lightningBolt = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: (startPoint.x + endPoint.x) / 2,
y: (startPoint.y + endPoint.y) / 2,
scaleX: 2,
scaleY: distance / 40
}));
lightningBolt.tint = 0x00FFFF;
lightningBolt.alpha = 1.0;
lightningBolt.rotation = Math.atan2(dy, dx) + Math.PI / 2;
tween(lightningBolt, {
alpha: 0,
scaleX: 0.5
}, {
duration: 700,
delay: i * 150,
easing: tween.easeOut,
onFinish: function onFinish() {
if (lightningBolt.parent) lightningBolt.destroy();
}
});
}
showSpellDescription('LIGHTNING', 'Cadena de ' + targetsHit + ' rayos - Daño: 200', 0x00FFFF);
} else {
showSpellDescription('LIGHTNING', 'No hay enemigos para la cadena', 0x00FFFF);
}
// Consume mana
var spell = _getSpell('lightning');
var manaCost = spell ? spell.manaCost : 40;
currentMana = Math.max(0, currentMana - manaCost);
if (activeSpellDeck) {
activeSpellDeck.currentMana = currentMana;
}
updateManaDisplay();
cardCooldowns['lightning'] = LK.ticks + cardCooldownDuration;
LK.getSound('spellCast').play();
return true;
}
function castHeal() {
console.log('=== CASTING HEAL ===');
console.log('Current mana before cast:', currentMana);
console.log('activeSpellDeck.currentMana before cast:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
var healthBefore = wizard.health;
wizard.health = Math.min(wizard.health + 50, wizard.maxHealth);
var actualHealing = wizard.health - healthBefore;
updateHealthBar();
// Basic healing effects
LK.effects.flashScreen(0x00FF00, 300);
LK.effects.flashObject(wizard, 0x00FF00, 500);
// Create floating heal text
var healText = new Text2('+' + actualHealing + ' HP', {
size: 120,
fill: 0x00FF00,
font: "monospace"
});
healText.anchor.set(0.5, 0.5);
healText.x = wizard.x;
healText.y = wizard.y - 150;
game.addChild(healText);
tween(healText, {
y: healText.y - 300,
alpha: 0,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 2500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (healText.parent) healText.destroy();
}
});
if (actualHealing > 0) {
showSpellDescription('HEAL', 'Curado: ' + actualHealing + ' HP', 0x00FF00);
} else {
showSpellDescription('HEAL', 'Salud completa', 0x00FF00);
}
// Consume mana
var spell = _getSpell('heal');
var manaCost = spell ? spell.manaCost : 25;
currentMana = Math.max(0, currentMana - manaCost);
if (activeSpellDeck) {
activeSpellDeck.currentMana = currentMana;
}
updateManaDisplay();
cardCooldowns['heal'] = LK.ticks + cardCooldownDuration;
LK.getSound('spellCast').play();
return true;
}
function _getSpell(spellId) {
for (var i = 0; i < availableSpells.length; i++) {
if (availableSpells[i].id === spellId) {
return availableSpells[i];
}
}
return null;
}
function _getRarityColor(rarity) {
return rarity === 'rare' ? 0x0080FF : 0xFFFFFF;
}
var GameManager = {
updateEntity: function updateEntity(entity) {
if (entity && entity.update && typeof entity.update === 'function') {
entity.update();
}
},
createDamageText: function createDamageText(x, y, damage) {
var damageText = new Text2('-' + damage, {
size: 120,
fill: 0xFF4444,
font: "monospace"
});
damageText.anchor.set(0.5, 0.5);
damageText.x = x;
damageText.y = y - 40;
game.addChild(damageText);
tween(damageText, {
y: y - 120,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
damageText.destroy();
}
});
},
createFlashEffect: function createFlashEffect(target, color, duration) {
LK.effects.flashObject(target, color, duration);
},
createObject: function createObject(type, config) {
if (type === 'coin') return new Coin();
if (type === 'enemy') return new Enemy(config);
if (type === 'projectile') return new Projectile(config);
return null;
},
createVisualEffect: function createVisualEffect(type, target, config) {
// Simple visual effect creation
if (type === 'explosion') {
var explosion = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: target.x,
y: target.y,
scaleX: config.explosionScale || 2,
scaleY: config.explosionScale || 2
}));
explosion.tint = config.explosionColor || 0xFFFFFF;
explosion.alpha = 0.8;
tween(explosion, {
scaleX: (config.explosionScale || 2) * 1.5,
scaleY: (config.explosionScale || 2) * 1.5,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
}
};
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
// Usar valores directos en lugar de configuraciones
// Usar valores directos en lugar de configuraciones complejas
// Usar valores directos en lugar de configuraciones complejas
// Usar valores directos en lugar de configuraciones complejas
// Usar valores directos en lugar de configuraciones complejas
/****
* Global State Management
****/
// Simplified global state - no gameState object needed
var gameStarted = false;
var selectedEnemy = null;
var coinCounter = 0;
var enemyKillCounter = 0;
var pathLastSpawnTime = [-1, -1, -1, -1, -1];
var pathConsecutiveSpawns = [0, 0, 0, 0, 0];
var lastSpawnedPath = -1;
/****
* Global Systems
****/
// Unified game management system
var gameManager = GameManager;
// Simple Collision Array Pool
var collisionArrayPool = {
allEnemies: [],
clearArray: function clearArray(arrayName) {
if (this[arrayName]) {
this[arrayName].length = 0;
}
return this[arrayName];
},
getAllEnemies: function getAllEnemies() {
var array = this.clearArray('allEnemies');
for (var i = 0; i < enemies.length; i++) {
array.push(enemies[i]);
}
for (var i = 0; i < ogres.length; i++) {
array.push(ogres[i]);
}
for (var i = 0; i < knights.length; i++) {
array.push(knights[i]);
}
for (var i = 0; i < miniBosses.length; i++) {
array.push(miniBosses[i]);
}
return array;
}
};
/****
* Unified Projectile Manager
****/
var ProjectileFactory = {
// Simplified projectile creation - all types use same Projectile class
createProjectile: function createProjectile(type, startX, startY, targetX, targetY, config) {
var projectile = new Projectile(type);
projectile.x = startX;
projectile.y = startY;
if (config) {
for (var key in config) projectile[key] = config[key];
}
if (targetX !== undefined && targetY !== undefined) {
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
projectile.direction.x = dx / distance;
projectile.direction.y = dy / distance;
}
}
game.addChild(projectile);
projectiles.push(projectile);
return projectile;
},
createBasicAttack: function createBasicAttack(wizard, enemy) {
return this.createProjectile('projectile', wizard.x, wizard.y, enemy.x, enemy.y, {
targetEnemy: enemy,
damage: 100
});
},
createSpellProjectile: function createSpellProjectile(spellType, wizard, targetX, targetY) {
return this.createProjectile(spellType, wizard.x, wizard.y, targetX, targetY, {
damage: 150
});
},
removeProjectile: function removeProjectile(projectile) {
for (var i = projectiles.length - 1; i >= 0; i--) {
if (projectiles[i] === projectile) {
projectiles.splice(i, 1);
break;
}
}
if (projectile.parent) projectile.destroy();
}
};
// Removed globalDamageHandler and globalEffectManager aliases - use GameManager directly
/****
* Game Objects (Legacy compatibility)
****/
// Direct global arrays - no gameState needed
var enemies = [];
var ogres = [];
var knights = [];
var miniBosses = [];
var coins = [];
var projectiles = [];
// Single game menu object
var gameMenu;
// Create tutorial system first (initially hidden)
var tutorial = game.addChild(new Tutorial());
tutorial.visible = false;
// Create and show game menu
gameMenu = game.addChild(new GameMenu());
// Create toggle button for in-game card panel
var cardToggleButton = LK.getAsset('spellCard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
LK.gui.topRight.addChild(cardToggleButton);
cardToggleButton.x = -80;
cardToggleButton.y = 80;
cardToggleButton.tint = 0x4a0e4e;
cardToggleButton.visible = false;
var cardToggleText = new Text2('CARTAS', {
size: 35,
fill: 0xFFFFFF,
font: "monospace"
});
cardToggleText.anchor.set(0.5, 0.5);
cardToggleText.x = -80;
cardToggleText.y = 80;
LK.gui.topRight.addChild(cardToggleText);
cardToggleText.visible = false;
// Add interaction to toggle button
cardToggleButton.down = function (x, y, obj) {
// Visual feedback for button press
LK.effects.flashObject(obj, 0x4169E1, 200);
tween(obj, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(obj, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
toggleCardPanel();
};
// Simple spell system variables
var currentDeck = storage.spellDeck || ['fireball', 'heal', 'lightning'];
var maxMana = 100;
var currentMana = 100; // Starting mana
var manaRegenRate = 1; // Mana regenerated per second (60 ticks)
var manaRegenTimer = 0;
var spellCooldowns = {};
var cardCooldowns = {}; // Track cooldown for each card
var cardCooldownDuration = 300; // 5 seconds at 60fps
storage.spellDeck = currentDeck.slice();
// PASO 1: Verificar que activeSpellDeck existe
console.log('=== VERIFYING SYSTEMS AT STARTUP ===');
console.log('currentDeck:', currentDeck);
console.log('currentMana:', currentMana);
console.log('maxMana:', maxMana);
// Mana UI variables removed - handled by deck menu system
var activeSpellDeck = {
currentDeck: currentDeck.slice(),
currentMana: maxMana,
maxMana: maxMana,
availableSpells: availableSpells,
getSpell: function getSpell(spellId) {
return _getSpell(spellId);
},
canCastSpell: function canCastSpell(spellId) {
return _canCastSpell(spellId);
},
castSpell: function castSpell(spellId, targetX, targetY) {
console.log('=== CASTING SPELL FROM ACTIVE DECK ===');
console.log('Spell ID:', spellId);
if (spellId === 'fireball') {
return castFireball();
} else if (spellId === 'lightning') {
return castLightning();
} else if (spellId === 'heal') {
return castHeal();
} else {
console.log('Unknown spell ID:', spellId);
return false;
}
},
getRarityColor: function getRarityColor(rarity) {
return _getRarityColor(rarity);
}
};
// PASO 1: Verificar activeSpellDeck después de su creación
console.log('activeSpellDeck created successfully:');
console.log('- currentDeck:', activeSpellDeck.currentDeck);
console.log('- currentMana:', activeSpellDeck.currentMana);
console.log('- maxMana:', activeSpellDeck.maxMana);
console.log('- availableSpells count:', activeSpellDeck.availableSpells.length);
// Initialize spell unlocking system
var lastUnlockCheck = 0;
function checkSpellUnlocks() {
if (!gameMenu.spellDeck) {
gameMenu.spellDeck = new SpellDeck();
}
// Only check unlocks when kill counter changes
if (enemyKillCounter === lastUnlockCheck) return;
lastUnlockCheck = enemyKillCounter;
// Unlock spells based on achievements with messages
if (enemyKillCounter >= 10 && !storage.lightningUnlocked) {
storage.lightningUnlocked = true;
gameMenu.spellDeck.unlockSpell('lightning');
LK.effects.flashScreen(0x00FFFF, 500);
showSpellUnlockMessage('LIGHTNING', 'Cadena de rayos entre enemigos');
}
if (enemyKillCounter >= 25 && !storage.shieldUnlocked) {
storage.shieldUnlocked = true;
gameMenu.spellDeck.unlockSpell('shield');
LK.effects.flashScreen(0x0080FF, 500);
showSpellUnlockMessage('MAGIC SHIELD', 'Inmunidad temporal al daño');
}
if (enemyKillCounter >= 50 && !storage.teleportUnlocked) {
storage.teleportUnlocked = true;
gameMenu.spellDeck.unlockSpell('teleport');
LK.effects.flashScreen(0x8000FF, 500);
showSpellUnlockMessage('TELEPORT', 'Mueve instantáneamente al mago');
}
if (enemyKillCounter >= 75 && !storage.timeSlowUnlocked) {
storage.timeSlowUnlocked = true;
gameMenu.spellDeck.unlockSpell('timeSlow');
LK.effects.flashScreen(0xFF8000, 500);
showSpellUnlockMessage('TIME SLOW', 'Ralentiza todos los enemigos');
}
if (enemyKillCounter >= 100 && !storage.meteorUnlocked) {
storage.meteorUnlocked = true;
gameMenu.spellDeck.unlockSpell('meteor');
LK.effects.flashScreen(0xFF0000, 500);
showSpellUnlockMessage('METEOR', 'Daño masivo en área');
}
}
function showSpellUnlockMessage(spellName, description) {
var unlockText = new Text2('NUEVO HECHIZO DESBLOQUEADO!\n' + spellName + '\n' + description, {
size: 60,
fill: 0xFFD700,
font: "monospace"
});
unlockText.anchor.set(0.5, 0.5);
unlockText.x = 2048 / 2;
unlockText.y = 2732 / 2;
game.addChild(unlockText);
// Animate unlock message
tween(unlockText, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.8
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(unlockText, {
alpha: 0,
y: unlockText.y - 200
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (unlockText.parent) unlockText.destroy();
}
});
}
});
}
// Function to show spell description when cast
function showSpellDescription(spellName, description, color) {
var descText = new Text2(spellName + '\n' + description, {
size: 50,
fill: color,
font: "monospace"
});
descText.anchor.set(0.5, 0.5);
descText.x = wizard.x;
descText.y = wizard.y - 200;
game.addChild(descText);
// Magical sparkles removed for simplification
// Animate description
tween(descText, {
y: descText.y - 80,
alpha: 0
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (descText.parent) descText.destroy();
}
});
}
// Create unified path system - all 5 paths at once
var paths = [];
var knightX = 2048 / 2;
var knightY = 2732 - 250;
var pathAngles = [-Math.PI / 2, -Math.PI / 3, -2 * Math.PI / 3, Math.PI / 6, 5 * Math.PI / 6];
var wizardX = knightX;
var wizardY = 2732 - 600;
// Unified path creation function
function createUnifiedPaths() {
var spawnPositions = [{
x: 2048 / 2,
y: -100
}, {
x: 2048 + 50,
y: -50
}, {
x: -50,
y: -50
}, {
x: -100,
y: 2732 / 2 + 400
}, {
x: 2048 + 100,
y: 2732 / 2 + 400
}];
var pathAngles = [-Math.PI / 2, -Math.PI / 3, -2 * Math.PI / 3, Math.PI / 6, 5 * Math.PI / 6];
for (var p = 0; p < 5; p++) {
var angle = pathAngles[p];
var spawnPos = spawnPositions[p];
var actualPathLength = Math.sqrt((spawnPos.x - wizardX) * (spawnPos.x - wizardX) + (spawnPos.y - wizardY) * (spawnPos.y - wizardY));
// Create stone segments
var segmentSize = 80;
var numSegments = Math.floor(actualPathLength / segmentSize);
for (var s = 0; s < numSegments; s++) {
var segmentDistance = s * segmentSize + segmentSize / 2;
var segmentX = spawnPos.x - Math.cos(angle) * segmentDistance;
var segmentY = spawnPos.y - Math.sin(angle) * segmentDistance;
if (segmentX >= -100 && segmentX <= 2148 && segmentY >= -100 && segmentY <= 2832) {
var stoneSegment = game.addChild(LK.getAsset('stonePath', {
anchorX: 0.5,
anchorY: 0.5,
x: segmentX,
y: segmentY,
scaleX: 2.0,
scaleY: 2.0,
rotation: angle + Math.PI / 2
}));
stoneSegment.alpha = 0;
stoneSegment.visible = false;
stoneSegment.pathIndex = p;
}
}
// Create collision area
var centerX = (spawnPos.x + wizardX) / 2;
var centerY = (spawnPos.y + wizardY) / 2;
var path = game.addChild(LK.getAsset('pathSelector', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX,
y: centerY,
scaleX: 4,
scaleY: actualPathLength / 60,
rotation: angle + Math.PI / 2
}));
path.alpha = 0;
path.visible = false;
path.pathIndex = p;
// Add path number
var pathNumber = new Text2((p + 1).toString(), {
size: 120,
fill: 0xFFD700,
font: "monospace"
});
pathNumber.anchor.set(0.5, 0.5);
pathNumber.x = spawnPos.x;
pathNumber.y = spawnPos.y - 80;
pathNumber.visible = false;
pathNumber.pathIndex = p;
game.addChild(pathNumber);
// Add touch handler
path.down = function (x, y, obj) {
wizard.attack(obj.pathIndex);
};
paths.push(path);
}
}
// Create all paths
createUnifiedPaths();
// Pixel art scaling handled by engine automatically
// Set fondodelacueva as the actual game background
var backgroundMap = game.addChild(LK.getAsset('fondodelacueva', {
anchorX: 0,
anchorY: 0,
scaleX: 19.5,
scaleY: 26.0,
x: 0,
y: 0
}));
// Send background to the back but use a less extreme z-index
backgroundMap.zIndex = -100;
// Hide background initially during menu
backgroundMap.visible = false;
backgroundMap.alpha = 1.0;
// Create wizard early to ensure it's available for other classes
var wizard = game.addChild(new Wizard());
wizard.x = knightX;
wizard.y = 2732 - 600; // Position wizard higher on screen
wizard.visible = false;
// UI Elements
// Removed scoreText and levelText to eliminate stray characters in top right
var coinCounter = 0;
var enemyKillCounter = 0;
var coinText = new Text2('Coins: 0', {
size: 60,
fill: 0xFFD700,
font: "monospace"
});
coinText.anchor.set(0, 0);
LK.gui.topLeft.addChild(coinText);
coinText.x = 120;
coinText.y = 90;
coinText.visible = false;
var killCountText = new Text2('Puntuacion: 0', {
size: 60,
fill: 0xFFFFFF,
font: "monospace"
});
killCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(killCountText);
killCountText.x = 120;
killCountText.y = 50;
killCountText.visible = false;
var tapText = new Text2('TAP ENEMIES TO ATTACK!', {
size: 80,
fill: 0x00FF00,
font: "monospace"
});
tapText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(tapText);
tapText.y = -400;
tapText.visible = false;
// Health bar UI
var healthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0,
scaleX: 2,
scaleY: 1
});
LK.gui.topLeft.addChild(healthBarBg);
healthBarBg.x = 120;
healthBarBg.y = 20;
healthBarBg.visible = false;
var healthBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0,
scaleX: 2,
scaleY: 1
});
LK.gui.topLeft.addChild(healthBar);
healthBar.x = 120;
healthBar.y = 20;
healthBar.visible = false;
var healthText = new Text2('Health: 100/100', {
size: 60,
fill: 0xFFFFFF,
font: "monospace"
});
healthText.anchor.set(0, 0);
LK.gui.topLeft.addChild(healthText);
healthText.x = 120;
healthText.y = 65;
healthText.visible = false;
// Mana bar UI
var manaBarBg = LK.getAsset('manaBarBg', {
anchorX: 0,
anchorY: 0,
scaleX: 2,
scaleY: 1
});
LK.gui.topLeft.addChild(manaBarBg);
manaBarBg.x = 120;
manaBarBg.y = 105;
manaBarBg.visible = false;
var manaBar = LK.getAsset('manaBar', {
anchorX: 0,
anchorY: 0,
scaleX: 2,
scaleY: 1
});
LK.gui.topLeft.addChild(manaBar);
manaBar.x = 120;
manaBar.y = 105;
manaBar.visible = false;
var manaText = new Text2('Mana: 100/100', {
size: 60,
fill: 0x4169E1,
font: "monospace"
});
manaText.anchor.set(0, 0);
LK.gui.topLeft.addChild(manaText);
manaText.x = 120;
manaText.y = 150;
manaText.visible = false;
function updateManaDisplay() {
var manaPercent = currentMana / maxMana;
manaBar.scaleX = manaPercent * 2;
manaText.setText('Mana: ' + Math.floor(currentMana) + '/' + maxMana);
// Sync with activeSpellDeck
if (activeSpellDeck) {
activeSpellDeck.currentMana = currentMana;
}
}
function updateHealthBar() {
var healthPercent = wizard.health / wizard.maxHealth;
healthBar.scaleX = healthPercent;
healthText.setText('Health: ' + wizard.health + '/' + wizard.maxHealth);
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
}
// Enemy spawning variables
var enemySpawnTimer = 0;
var lastSpawnedPath = -1; // Track the last spawned path
var consecutiveSpawns = 0; // Track consecutive spawns from same path
// Cooldown system variables
var pathLastSpawnTime = [-1, -1, -1, -1, -1]; // Track last spawn time for each path
var pathConsecutiveSpawns = [0, 0, 0, 0, 0]; // Track consecutive spawns per path
var pathCooldownDuration = 300; // 5 seconds at 60fps
// Unified SpawnManager for streamlined spawn control and enemy lifecycle
var SpawnManager = {
// Consolidated spawn configuration
spawnConfig: {
skeleton: {
interval: 90,
maxCount: 15,
startAt: 0
},
ogre: {
interval: 180,
maxCount: 4,
startAt: 15
},
knight: {
interval: 300,
maxCount: 3,
startAt: 30
},
miniBoss: {
interval: 60,
maxCount: 1,
startAt: 80,
endAt: 85,
chance: 0.02
}
},
// Unified spawn timers
spawnTimers: {
skeleton: 0,
ogre: 0,
knight: 0,
miniBoss: 0
},
// Streamlined spawn validation
canSpawnEnemy: function canSpawnEnemy(type, difficulty, totalEnemies) {
var config = this.spawnConfig[type];
if (!config) return false;
var collection = [];
if (type === 'skeleton') collection = enemies;else if (type === 'ogre') collection = ogres;else if (type === 'knight') collection = knights;else if (type === 'miniBoss') collection = miniBosses;
var timer = this.spawnTimers[type];
// Usar valores directos para thresholds
var threshold = 0;
if (type === 'ogre') threshold = 15;else if (type === 'knight') threshold = 30;else if (type === 'miniBoss') threshold = 80;
// Universal spawn conditions with type-specific rules
var baseConditions = timer >= config.interval && enemyKillCounter >= threshold && collection.length < config.maxCount && totalEnemies < 25;
// Special conditions for specific types
if (type === 'miniBoss') {
return baseConditions && enemyKillCounter <= config.endAt && miniBosses.length === 0 && Math.random() < config.chance;
}
return baseConditions && miniBosses.length === 0;
},
// Unified spawn execution with specific enemy classes
executeSpawn: function executeSpawn(type, difficulty, level) {
var totalEnemies = globalEnemyManager.getAllEnemies().length;
if (!this.canSpawnEnemy(type, difficulty, totalEnemies)) return null;
var enemy = globalEnemyManager.createEnemy(type, difficulty, level);
if (enemy) {
game.addChild(enemy);
// Add directly to global arrays
if (type === 'skeleton') enemies.push(enemy);else if (type === 'ogre') ogres.push(enemy);else if (type === 'knight') knights.push(enemy);else if (type === 'miniBoss') miniBosses.push(enemy);
this.spawnTimers[type] = 0;
// Update path tracking for skeleton spawns
if (type === 'skeleton') {
pathConsecutiveSpawns[enemy.pathIndex]++;
pathLastSpawnTime[enemy.pathIndex] = LK.ticks;
lastSpawnedPath = enemy.pathIndex;
}
}
return enemy;
},
// Streamlined interval updates
updateSpawnIntervals: function updateSpawnIntervals(difficulty, level) {
// Usar valores directos para skeleton
if (difficulty === 'FACIL') {
this.spawnConfig.skeleton.interval = Math.max(60, 120 - level * 5);
this.spawnConfig.ogre.interval = 240;
this.spawnConfig.knight.interval = 400;
} else if (difficulty === 'NORMAL') {
this.spawnConfig.skeleton.interval = Math.max(40, 90 - level * 6);
this.spawnConfig.ogre.interval = 180;
this.spawnConfig.knight.interval = 300;
} else {
// DIFICIL
this.spawnConfig.skeleton.interval = Math.max(20, 60 - level * 4);
this.spawnConfig.ogre.interval = 120;
this.spawnConfig.knight.interval = 200;
}
},
// Unified timer updates
updateTimers: function updateTimers() {
for (var type in this.spawnTimers) {
this.spawnTimers[type]++;
}
// Check if all paths are in cooldown and reset if needed
var allPathsInCooldown = true;
for (var pathIdx = 0; pathIdx < 5; pathIdx++) {
if (pathConsecutiveSpawns[pathIdx] < 2) {
allPathsInCooldown = false;
break;
}
}
if (allPathsInCooldown) {
// Reset all path cooldowns to prevent spawn deadlock
for (var pathIdx = 0; pathIdx < 5; pathIdx++) {
pathConsecutiveSpawns[pathIdx] = 0;
pathLastSpawnTime[pathIdx] = -1;
}
}
},
// Streamlined spawn cycle for all enemy types
processSpawnCycle: function processSpawnCycle(difficulty, level) {
this.updateSpawnIntervals(difficulty, level);
this.updateTimers();
// Clean up any null/destroyed enemies from collections first
this.cleanupDestroyedEnemies();
var enemyTypes = ['skeleton', 'ogre', 'knight', 'miniBoss'];
for (var i = 0; i < enemyTypes.length; i++) {
this.executeSpawn(enemyTypes[i], difficulty, level);
}
},
// Add cleanup method for destroyed enemies
cleanupDestroyedEnemies: function cleanupDestroyedEnemies() {
// Clean up global arrays directly
var allArrays = [enemies, ogres, knights, miniBosses];
for (var arrayIdx = 0; arrayIdx < allArrays.length; arrayIdx++) {
var array = allArrays[arrayIdx];
for (var i = array.length - 1; i >= 0; i--) {
if (!array[i] || !array[i].parent || array[i].isDying) {
array.splice(i, 1);
}
}
}
}
};
// Game input handling with spell casting support
game.down = function (x, y, obj) {
// Removed automatic fireball casting - spells are now manual only through spell slots
// Tap-to-attack is now handled directly by individual enemies
// No need for path-based attacks since enemies handle their own tap events
};
// Initialize unified management systems for streamlined game architecture
var globalEnemyManager = new EnemyManager();
var globalDeathHandler = new UnifiedDeathHandler();
// Unified death animation function for all enemy types
function createEnemyDeathAnimation(enemy, enemyType, enemyArray) {
globalDeathHandler.executeEnemyDeath(enemy, enemyArray);
}
// In-game spell card panel system
var inGameCardPanel = null;
var cardPanelVisible = false;
var cardPanelElements = [];
// Create in-game card panel
function createInGameCardPanel() {
if (inGameCardPanel) return inGameCardPanel;
// Create semi-transparent background panel
inGameCardPanel = game.addChild(LK.getAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048 / 2,
y: 2732,
scaleX: 20,
scaleY: 4
}));
inGameCardPanel.tint = 0x1a0a2e;
inGameCardPanel.alpha = 0.9;
inGameCardPanel.zIndex = 1500;
inGameCardPanel.visible = false;
return inGameCardPanel;
}
// Toggle card panel visibility
function toggleCardPanel() {
if (!gameStarted) return;
cardPanelVisible = !cardPanelVisible;
if (cardPanelVisible) {
showInGameCardPanel();
} else {
hideInGameCardPanel();
}
}
// Show in-game card panel
function showInGameCardPanel() {
if (!inGameCardPanel) createInGameCardPanel();
// Clear existing card elements
clearCardPanelElements();
inGameCardPanel.visible = true;
// Get current deck from activeSpellDeck
var currentDeck = activeSpellDeck ? activeSpellDeck.currentDeck : ['fireball', 'heal', 'lightning'];
// Create cards for current deck
for (var i = 0; i < currentDeck.length && i < 5; i++) {
var spellId = currentDeck[i];
var spell = _getSpell(spellId);
if (!spell) continue;
var cardX = 300 + i * 300;
var cardY = 2732 - 150;
// Create card background
var cardBg = game.addChild(LK.getAsset('spellCard', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 2.5,
scaleY: 3.0
}));
cardBg.tint = _getRarityColor(spell.rarity);
cardBg.alpha = 0.9;
cardBg.zIndex = 1501;
cardBg.spellId = spellId;
cardPanelElements.push(cardBg);
// Enhanced ready-to-cast visual feedback with glow effects for in-game panel
if (!isOnCooldown && _canCastSpell(spellId)) {
// Create enhanced magical glow border around ready cards
var inGameGlowBorder = game.addChild(LK.getAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.2,
scaleY: 3.8
}));
inGameGlowBorder.tint = 0x00FF88;
inGameGlowBorder.alpha = 0.5;
inGameGlowBorder.zIndex = 1501;
cardPanelElements.push(inGameGlowBorder);
// Sparkle effects removed for simplification
// Enhanced ready-to-cast card pulsing animation
tween(cardBg, {
alpha: 1.0,
scaleX: 2.7,
scaleY: 3.2
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(cardBg, {
alpha: 0.9,
scaleX: 2.5,
scaleY: 3.0
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
// Animate glow border with enhanced breathing effect
tween(inGameGlowBorder, {
alpha: 0.7,
scaleX: 3.6,
scaleY: 4.2
}, {
duration: 1300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(inGameGlowBorder, {
alpha: 0.5,
scaleX: 3.2,
scaleY: 3.8
}, {
duration: 1300,
easing: tween.easeInOut
});
}
});
}
// Check if spell can be cast (mana + cooldown validation)
var canCast = _canCastSpell(spellId);
var currentTick = LK.ticks || 0;
var isOnCooldown = cardCooldowns[spellId] && currentTick < cardCooldowns[spellId];
var hasEnoughMana = currentMana >= (spell.manaCost || 0);
// Enhanced card status visual feedback system
if (isOnCooldown) {
// Create enhanced cooldown overlay with animated elements
var cooldownOverlay = game.addChild(LK.getAsset('cooldownOverlay', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 2.5,
scaleY: 3.0
}));
cooldownOverlay.alpha = 0.8;
cooldownOverlay.zIndex = 1502;
cardPanelElements.push(cooldownOverlay);
// Create rotating cooldown progress ring
var cooldownRing = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 2.8,
scaleY: 2.8
}));
cooldownRing.tint = 0x4169E1;
cooldownRing.alpha = 0.6;
cooldownRing.zIndex = 1502;
cardPanelElements.push(cooldownRing);
// Animate cooldown ring rotation
tween(cooldownRing, {
rotation: Math.PI * 2
}, {
duration: 2000,
easing: tween.linear
});
// Show enhanced cooldown time remaining with countdown animation
var timeRemaining = Math.ceil((cardCooldowns[spellId] - currentTick) / 60);
var cooldownText = new Text2(timeRemaining.toString(), {
size: 50,
fill: 0xFFFFFF,
font: "monospace"
});
cooldownText.anchor.set(0.5, 0.5);
cooldownText.x = cardX;
cooldownText.y = cardY;
cooldownText.zIndex = 1503;
game.addChild(cooldownText);
cardPanelElements.push(cooldownText);
// Pulsing countdown animation
tween(cooldownText, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.7
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(cooldownText, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
// Disable card interaction when on cooldown
cardBg.alpha = 0.4;
cardBg.tint = 0x666666; // Gray out on cooldown
} else if (!hasEnoughMana) {
// Enhanced insufficient mana feedback with warning indicators
cardBg.alpha = 0.5;
cardBg.tint = 0xFF4444; // Red tint for insufficient mana
// Create mana warning icon
var manaWarning = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX + 70,
y: cardY - 70,
scaleX: 1.2,
scaleY: 1.2
}));
manaWarning.tint = 0xFF0000;
manaWarning.alpha = 0.9;
manaWarning.zIndex = 1502;
cardPanelElements.push(manaWarning);
// Add warning symbol
var warningText = new Text2('!', {
size: 60,
fill: 0xFFFFFF,
font: "monospace"
});
warningText.anchor.set(0.5, 0.5);
warningText.x = cardX + 70;
warningText.y = cardY - 70;
warningText.zIndex = 1503;
game.addChild(warningText);
cardPanelElements.push(warningText);
// Urgent warning animation for insufficient mana
tween(manaWarning, {
scaleX: 1.6,
scaleY: 1.6,
alpha: 0.5
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(manaWarning, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.9
}, {
duration: 400,
easing: tween.easeIn
});
}
});
} else if (canCast) {
// Step 8: Enhanced ready to cast with contextual spell-specific visual effects
cardBg.alpha = 0.9;
cardBg.tint = _getRarityColor(spell.rarity);
// Create contextual spell-specific aura effects around ready cards
if (spellId === 'fireball') {
// Fireball: Orange/red flame aura with heat distortions
var fireAura = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.2,
scaleY: 3.8
}));
fireAura.tint = 0xFF4500;
fireAura.alpha = 0.4;
fireAura.zIndex = 1501;
cardPanelElements.push(fireAura);
// Create flame particles around fireball cards
for (var f = 0; f < 5; f++) {
var flameParticle = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX + (Math.random() - 0.5) * 100,
y: cardY + (Math.random() - 0.5) * 70 + 20,
scaleX: 0.3 + Math.random() * 0.3,
scaleY: 0.6 + Math.random() * 0.4
}));
flameParticle.tint = f % 2 === 0 ? 0xFF4500 : 0xFF6600;
flameParticle.alpha = 0.7;
flameParticle.zIndex = 1504;
cardPanelElements.push(flameParticle);
// Flames rise upward with flickering
tween(flameParticle, {
y: flameParticle.y - 120,
alpha: 0,
scaleX: (0.3 + Math.random() * 0.3) * 1.5,
scaleY: (0.6 + Math.random() * 0.4) * 0.3,
rotation: Math.PI * (Math.random() - 0.5)
}, {
duration: 1500 + f * 200,
delay: f * 150,
easing: tween.easeOut
});
}
// Heat wave distortion effect
var heatWave = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 2.8,
scaleY: 1.5
}));
heatWave.tint = 0xFF8800;
heatWave.alpha = 0.15;
heatWave.zIndex = 1500;
cardPanelElements.push(heatWave);
// Animate heat distortion
tween(heatWave, {
scaleY: 2.2,
alpha: 0.05
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(heatWave, {
scaleY: 1.5,
alpha: 0.15
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
} else if (spellId === 'lightning') {
// Lightning: Blue/cyan electric aura with static effects
var lightningAura = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.2,
scaleY: 3.8
}));
lightningAura.tint = 0x00FFFF;
lightningAura.alpha = 0.4;
lightningAura.zIndex = 1501;
cardPanelElements.push(lightningAura);
// Create electric sparks around lightning cards
for (var l = 0; l < 6; l++) {
var electricSpark = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX + (Math.random() - 0.5) * 120,
y: cardY + (Math.random() - 0.5) * 90,
scaleX: 0.2 + Math.random() * 0.3,
scaleY: 1.0 + Math.random() * 0.8
}));
electricSpark.tint = l % 3 === 0 ? 0xFFFFFF : 0x00FFFF;
electricSpark.alpha = 0.8;
electricSpark.rotation = Math.random() * Math.PI * 2;
electricSpark.zIndex = 1504;
cardPanelElements.push(electricSpark);
// Electric sparks crackle and fade
tween(electricSpark, {
alpha: 0,
scaleX: 0.05,
scaleY: 0.3,
rotation: electricSpark.rotation + Math.PI * 4
}, {
duration: 300 + l * 100,
delay: l * 80,
easing: tween.easeOut
});
}
} else if (spellId === 'heal') {
// Heal: Gentle green glow with floating plus symbols
var healAura = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.2,
scaleY: 3.8
}));
healAura.tint = 0x00FF88;
healAura.alpha = 0.3;
healAura.zIndex = 1501;
cardPanelElements.push(healAura);
// Create soft healing particles around heal cards
for (var h = 0; h < 4; h++) {
var healParticle = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX + (Math.random() - 0.5) * 100,
y: cardY + (Math.random() - 0.5) * 70,
scaleX: 0.5 + Math.random() * 0.4,
scaleY: 0.5 + Math.random() * 0.4
}));
healParticle.tint = h % 2 === 0 ? 0x00FF88 : 0x66FFAA;
healParticle.alpha = 0.6;
healParticle.zIndex = 1504;
cardPanelElements.push(healParticle);
// Gentle floating upward motion
tween(healParticle, {
y: healParticle.y - 80,
alpha: 0,
scaleX: (0.5 + Math.random() * 0.4) * 1.3,
scaleY: (0.5 + Math.random() * 0.4) * 1.3,
rotation: Math.PI * 0.5 * (Math.random() - 0.5)
}, {
duration: 2000 + h * 200,
delay: h * 250,
easing: tween.easeOut
});
}
// Add floating plus symbol for heal cards
var plusSymbol = new Text2('+', {
size: 60,
fill: 0x00FF88,
font: "monospace"
});
plusSymbol.anchor.set(0.5, 0.5);
plusSymbol.x = cardX + 60;
plusSymbol.y = cardY - 60;
plusSymbol.alpha = 0.7;
plusSymbol.zIndex = 1505;
game.addChild(plusSymbol);
cardPanelElements.push(plusSymbol);
// Gentle floating animation for plus symbol
tween(plusSymbol, {
y: plusSymbol.y - 60,
alpha: 0,
scaleX: 1.4,
scaleY: 1.4,
rotation: Math.PI * 0.3
}, {
duration: 1800,
easing: tween.easeOut
});
} else {
// Default magical readiness aura for other spells
var readyAura = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.2,
scaleY: 3.8
}));
readyAura.tint = 0x00FF88;
readyAura.alpha = 0.3;
readyAura.zIndex = 1501;
cardPanelElements.push(readyAura);
// Create generic floating particles around ready cards
for (var p = 0; p < 3; p++) {
var particle = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX + (Math.random() - 0.5) * 120,
y: cardY + (Math.random() - 0.5) * 80,
scaleX: 0.4,
scaleY: 0.4
}));
particle.tint = 0x00FFAA;
particle.alpha = 0.8;
particle.zIndex = 1504;
cardPanelElements.push(particle);
// Animate floating particles
tween(particle, {
y: particle.y - 100,
alpha: 0,
scaleX: 0.8,
scaleY: 0.8,
rotation: Math.PI
}, {
duration: 1800,
delay: p * 400,
easing: tween.easeOut
});
}
}
// Add enhanced ready glow animation
tween(cardBg, {
alpha: 1.0,
scaleX: 2.6,
scaleY: 3.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(cardBg, {
alpha: 0.9,
scaleX: 2.5,
scaleY: 3.0
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
// Animate aura with gentle pulsing
tween(readyAura, {
alpha: 0.5,
scaleX: 3.6,
scaleY: 4.2
}, {
duration: 1400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(readyAura, {
alpha: 0.3,
scaleX: 3.2,
scaleY: 3.8
}, {
duration: 1400,
easing: tween.easeInOut
});
}
});
} else {
// Default disabled state
cardBg.alpha = 0.6;
cardBg.tint = 0x888888; // Gray tint for disabled
}
// Add enhanced card interaction with advanced touch feedback
cardBg.touchStartTime = 0;
cardBg.touchStartX = 0;
cardBg.touchStartY = 0;
cardBg.down = function (x, y, obj) {
if (obj.spellId) {
// Record touch start for gesture recognition
obj.touchStartTime = LK.ticks;
obj.touchStartX = x;
obj.touchStartY = y;
// Enhanced immediate haptic-like visual feedback with multiple effects
LK.effects.flashObject(obj, 0x00FFFF, 200);
// Create touch impact ripple at exact touch point
var touchRipple = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.2,
scaleY: 0.2
}));
touchRipple.tint = 0x00FFFF;
touchRipple.alpha = 0.9;
touchRipple.zIndex = 2100;
tween(touchRipple, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0,
rotation: Math.PI * 1.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (touchRipple.parent) touchRipple.destroy();
}
});
// Enhanced multi-stage press animation
tween(obj, {
scaleX: 2.3,
scaleY: 2.7,
rotation: Math.PI / 16
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(obj, {
scaleX: 2.9,
scaleY: 3.4,
rotation: -Math.PI / 32
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(obj, {
scaleX: 2.5,
scaleY: 3.0,
rotation: 0
}, {
duration: 120,
easing: tween.bounceOut
});
}
});
}
});
// Create particle trail following touch movement
for (var t = 0; t < 5; t++) {
var trailParticle = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: x + (Math.random() - 0.5) * 20,
y: y + (Math.random() - 0.5) * 20,
scaleX: 0.3,
scaleY: 0.3
}));
trailParticle.tint = 0x00AAFF;
trailParticle.alpha = 0.7;
trailParticle.zIndex = 2099;
tween(trailParticle, {
x: trailParticle.x + (Math.random() - 0.5) * 80,
y: trailParticle.y - 60 - Math.random() * 40,
alpha: 0,
scaleX: 0.8,
scaleY: 0.8,
rotation: Math.PI * 2
}, {
duration: 600 + t * 100,
delay: t * 50,
easing: tween.easeOut,
onFinish: function onFinish() {
if (trailParticle.parent) trailParticle.destroy();
}
});
}
castSpellFromCard(obj.spellId);
}
};
// Add touch release handler for gesture recognition
cardBg.up = function (x, y, obj) {
if (obj.spellId && obj.touchStartTime) {
var touchDuration = LK.ticks - obj.touchStartTime;
var touchDistance = Math.sqrt(Math.pow(x - obj.touchStartX, 2) + Math.pow(y - obj.touchStartY, 2));
// Detect different touch gestures
if (touchDuration < 30 && touchDistance < 50) {
// Quick tap - create snap feedback
var snapEffect = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: obj.x,
y: obj.y,
scaleX: 3.0,
scaleY: 3.0
}));
snapEffect.tint = 0xFFFF00;
snapEffect.alpha = 0.6;
snapEffect.zIndex = 2101;
tween(snapEffect, {
scaleX: 0.5,
scaleY: 0.5,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (snapEffect.parent) snapEffect.destroy();
}
});
} else if (touchDuration > 60) {
// Long press - create charge effect
var chargeEffect = game.addChild(LK.getAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: obj.x,
y: obj.y,
scaleX: 4.0,
scaleY: 5.0
}));
chargeEffect.tint = 0xFF6600;
chargeEffect.alpha = 0.4;
chargeEffect.zIndex = 2101;
tween(chargeEffect, {
scaleX: 6.0,
scaleY: 7.0,
alpha: 0,
rotation: Math.PI / 4
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (chargeEffect.parent) chargeEffect.destroy();
}
});
}
}
};
// Create card name text
var nameText = new Text2(spell.name, {
size: 30,
fill: 0xFFFFFF,
font: "monospace"
});
nameText.anchor.set(0.5, 0.5);
nameText.x = cardX;
nameText.y = cardY - 40;
nameText.zIndex = 1502;
game.addChild(nameText);
cardPanelElements.push(nameText);
// Create mana cost indicator
if (spell.manaCost) {
var manaText = new Text2(spell.manaCost.toString(), {
size: 40,
fill: 0x4169E1,
font: "monospace"
});
manaText.anchor.set(0.5, 0.5);
manaText.x = cardX + 50;
manaText.y = cardY - 80;
manaText.zIndex = 1502;
game.addChild(manaText);
cardPanelElements.push(manaText);
}
// Create damage/healing indicator
var effectText = '';
if (spell.damage) effectText = 'DMG: ' + spell.damage;
if (spell.healing) effectText = 'HEAL: ' + spell.healing;
if (effectText) {
var effectLabel = new Text2(effectText, {
size: 25,
fill: 0xFFD700,
font: "monospace"
});
effectLabel.anchor.set(0.5, 0.5);
effectLabel.x = cardX;
effectLabel.y = cardY + 40;
effectLabel.zIndex = 1502;
game.addChild(effectLabel);
cardPanelElements.push(effectLabel);
}
}
// Create toggle button to hide panel
var hideButton = game.addChild(LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 100,
y: 2732 - 300,
scaleX: 1.5,
scaleY: 1.5
}));
hideButton.tint = 0xFF4444;
hideButton.zIndex = 1502;
hideButton.down = function (x, y, obj) {
// Visual feedback for hide button
LK.effects.flashObject(obj, 0xFF6666, 200);
tween(obj, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(obj, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 100,
easing: tween.easeIn
});
}
});
toggleCardPanel();
};
cardPanelElements.push(hideButton);
var hideText = new Text2('CERRAR', {
size: 40,
fill: 0xFFFFFF,
font: "monospace"
});
hideText.anchor.set(0.5, 0.5);
hideText.x = 2048 - 100;
hideText.y = 2732 - 300;
hideText.zIndex = 1503;
game.addChild(hideText);
cardPanelElements.push(hideText);
}
// Hide in-game card panel
function hideInGameCardPanel() {
if (inGameCardPanel) {
inGameCardPanel.visible = false;
}
clearCardPanelElements();
}
// Clear card panel elements
function clearCardPanelElements() {
for (var i = 0; i < cardPanelElements.length; i++) {
if (cardPanelElements[i] && cardPanelElements[i].parent) {
cardPanelElements[i].destroy();
}
}
cardPanelElements = [];
}
// Targeting system variables
var targetingMode = false;
var targetingSpell = null;
var targetingCursor = null;
var targetingRange = null;
// Create targeting cursor
function createTargetingCursor() {
if (targetingCursor) return targetingCursor;
targetingCursor = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
}));
targetingCursor.tint = 0x00FFFF;
targetingCursor.alpha = 0.8;
targetingCursor.zIndex = 2000;
targetingCursor.visible = false;
// Add orbiting particles around cursor for enhanced feedback
targetingCursor.particles = [];
for (var p = 0; p < 4; p++) {
var particle = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
}));
particle.tint = 0x00FFFF;
particle.alpha = 0.6;
particle.zIndex = 1999;
particle.visible = false;
particle.orbitAngle = p * Math.PI * 2 / 4;
targetingCursor.particles.push(particle);
}
return targetingCursor;
}
// Create targeting range indicator
function createTargetingRange() {
if (targetingRange) return targetingRange;
targetingRange = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 8.0
}));
targetingRange.tint = 0x00FF00;
targetingRange.alpha = 0.3;
targetingRange.zIndex = 1999;
targetingRange.visible = false;
return targetingRange;
}
// Enter targeting mode
function enterTargetingMode(spellId) {
targetingMode = true;
targetingSpell = spellId;
// Create targeting visuals
createTargetingCursor();
createTargetingRange();
targetingCursor.visible = true;
targetingRange.visible = true;
// Position range indicator around wizard
targetingRange.x = wizard.x;
targetingRange.y = wizard.y;
// Add pulsing animation to cursor
tween(targetingCursor, {
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetingCursor && targetingCursor.visible) {
tween(targetingCursor, {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
}
});
// Show targeting instructions
showTargetingInstructions(spellId);
}
// Exit targeting mode
function exitTargetingMode() {
targetingMode = false;
targetingSpell = null;
if (targetingCursor) {
targetingCursor.visible = false;
}
if (targetingRange) {
targetingRange.visible = false;
}
// Hide card panel after targeting
hideInGameCardPanel();
cardPanelVisible = false;
}
// Show targeting instructions
function showTargetingInstructions(spellId) {
var spell = _getSpell(spellId);
var instructionText = '';
if (spellId === 'fireball') {
instructionText = 'TOCA UN ENEMIGO PARA LANZAR FIREBALL';
} else if (spellId === 'lightning') {
instructionText = 'TOCA UN ENEMIGO PARA CADENA DE RAYOS';
} else if (spellId === 'heal') {
instructionText = 'TOCA PARA CURARTE';
} else {
instructionText = 'SELECCIONA OBJETIVO PARA ' + (spell ? spell.name : 'HECHIZO');
}
var targetingInstructions = new Text2(instructionText, {
size: 50,
fill: 0x00FFFF,
font: "monospace"
});
targetingInstructions.anchor.set(0.5, 0.5);
targetingInstructions.x = 2048 / 2;
targetingInstructions.y = 400;
targetingInstructions.zIndex = 2001;
game.addChild(targetingInstructions);
// Auto-remove instructions after 3 seconds
tween(targetingInstructions, {
alpha: 0
}, {
duration: 3000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (targetingInstructions.parent) targetingInstructions.destroy();
}
});
}
// Execute spell at target location
function executeSpellAtTarget(spellId, targetX, targetY, targetEnemy) {
console.log('Executing spell', spellId, 'at', targetX, targetY);
var success = false;
if (spellId === 'fireball') {
if (targetEnemy) {
// Create targeted fireball
var fireball = ProjectileFactory.createProjectile('fireBall', wizard.x, wizard.y, targetEnemy.x, targetEnemy.y, {
targetEnemy: targetEnemy,
damage: 150
});
LK.getSound('fireWhoosh').play();
showSpellDescription('FIREBALL', 'Daño: 150 dirigido', 0xFF4500);
success = true;
}
} else if (spellId === 'lightning') {
if (targetEnemy) {
// Execute lightning with target as starting point
LK.effects.flashScreen(0x00FFFF, 800);
LK.getSound('iceFreeze').play();
var allEnemies = collisionArrayPool.getAllEnemies();
var livingEnemies = [];
// Start chain from targeted enemy
for (var e = 0; e < allEnemies.length; e++) {
var enemy = allEnemies[e];
if (!enemy.isDying && enemy.health > 0 && enemy.parent) {
var dx = enemy.x - targetEnemy.x;
var dy = enemy.y - targetEnemy.y;
enemy.distanceFromTarget = Math.sqrt(dx * dx + dy * dy);
livingEnemies.push(enemy);
}
}
// Sort by distance from target
livingEnemies.sort(function (a, b) {
return a.distanceFromTarget - b.distanceFromTarget;
});
var targetsHit = Math.min(3, livingEnemies.length);
if (targetsHit > 0) {
// Apply lightning damage
for (var i = 0; i < targetsHit; i++) {
var enemy = livingEnemies[i];
if (enemy && enemy.parent && !enemy.isDying) {
enemy.health -= 200;
LK.effects.flashObject(enemy, 0x00FFFF, 600);
if (enemy.health <= 0) enemy.die();
// Create lightning visual
var lightningImpact = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 3,
scaleY: 3
}));
lightningImpact.tint = 0x00FFFF;
lightningImpact.alpha = 1.0;
tween(lightningImpact, {
scaleX: 7,
scaleY: 7,
alpha: 0,
rotation: Math.PI * 3
}, {
duration: 800,
delay: i * 150,
easing: tween.easeOut,
onFinish: function onFinish() {
if (lightningImpact.parent) lightningImpact.destroy();
}
});
}
}
showSpellDescription('LIGHTNING', 'Cadena dirigida: ' + targetsHit + ' rayos', 0x00FFFF);
}
success = true;
}
} else if (spellId === 'heal') {
// Heal can be cast anywhere, always targets wizard
var healthBefore = wizard.health;
wizard.health = Math.min(wizard.health + 50, wizard.maxHealth);
var actualHealing = wizard.health - healthBefore;
updateHealthBar();
// Enhanced healing effects at target location
LK.effects.flashScreen(0x00FF00, 500);
LK.effects.flashObject(wizard, 0x00FF00, 1000);
// Create healing aura at clicked location
var healingAura = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: targetX,
y: targetY,
scaleX: 4,
scaleY: 4
}));
healingAura.tint = 0x00FF00;
healingAura.alpha = 0.8;
tween(healingAura, {
scaleX: 12,
scaleY: 12,
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (healingAura.parent) healingAura.destroy();
}
});
showSpellDescription('HEAL', 'Curado: ' + actualHealing + ' HP', 0x00FF00);
success = true;
}
if (success) {
LK.getSound('spellCast').play();
cardCooldowns[spellId] = LK.ticks + cardCooldownDuration;
}
return success;
}
// Cast spell from in-game card
function castSpellFromCard(spellId) {
console.log('=== CASTING SPELL FROM CARD ===');
console.log('Spell ID:', spellId);
console.log('Current mana before cast:', currentMana);
console.log('activeSpellDeck.currentMana before cast:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
// Check if spell can be cast with enhanced validation
if (!_canCastSpell(spellId)) {
console.log('Cannot cast spell:', spellId);
LK.effects.flashScreen(0xFF0000, 200);
return;
}
// Get spell data for mana consumption
var spell = _getSpell(spellId);
if (!spell) {
console.log('Spell not found for ID:', spellId);
return;
}
// Execute the spell directly using specific casting functions
var castSuccess = false;
console.log('Executing spell cast for:', spellId);
if (spellId === 'fireball') {
castSuccess = castFireball();
} else if (spellId === 'lightning') {
castSuccess = castLightning();
} else if (spellId === 'heal') {
castSuccess = castHeal();
} else {
console.log('Unknown spell ID in card cast:', spellId);
// Default to fireball for unknown spells
castSuccess = castFireball();
}
if (castSuccess) {
console.log('Spell cast successful:', spellId);
// Enhanced contextual success feedback based on spell type
if (spellId === 'fireball') {
LK.effects.flashScreen(0xFF4500, 400);
// Create fire burst around wizard
for (var f = 0; f < 6; f++) {
var fireBurst = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: wizard.x,
y: wizard.y,
scaleX: 1.5,
scaleY: 1.5
}));
fireBurst.tint = 0xFF6600;
fireBurst.alpha = 0.8;
fireBurst.zIndex = 1700;
var angle = f * Math.PI * 2 / 6;
tween(fireBurst, {
x: wizard.x + Math.cos(angle) * 120,
y: wizard.y + Math.sin(angle) * 120,
alpha: 0,
scaleX: 3.0,
scaleY: 3.0,
rotation: Math.PI * 2
}, {
duration: 600,
delay: f * 100,
easing: tween.easeOut,
onFinish: function onFinish() {
if (fireBurst.parent) fireBurst.destroy();
}
});
}
} else if (spellId === 'lightning') {
LK.effects.flashScreen(0x00FFFF, 500);
// Create electric arcs around wizard
for (var l = 0; l < 8; l++) {
var lightningArc = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: wizard.x + (Math.random() - 0.5) * 200,
y: wizard.y + (Math.random() - 0.5) * 200,
scaleX: 0.8,
scaleY: 3.0
}));
lightningArc.tint = 0x00FFFF;
lightningArc.alpha = 0.9;
lightningArc.rotation = Math.random() * Math.PI * 2;
lightningArc.zIndex = 1700;
tween(lightningArc, {
alpha: 0,
scaleX: 0.3,
scaleY: 4.0
}, {
duration: 300,
delay: l * 50,
easing: tween.easeOut,
onFinish: function onFinish() {
if (lightningArc.parent) lightningArc.destroy();
}
});
}
} else if (spellId === 'heal') {
LK.effects.flashScreen(0x00FF00, 350);
// Create healing aura waves
for (var h = 0; h < 4; h++) {
var healWave = game.addChild(LK.getAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: wizard.x,
y: wizard.y,
scaleX: 1.0 + h * 0.5,
scaleY: 1.0 + h * 0.5
}));
healWave.tint = 0x00FF88;
healWave.alpha = 0.6 - h * 0.1;
healWave.zIndex = 1700;
tween(healWave, {
scaleX: 8.0 + h * 2,
scaleY: 8.0 + h * 2,
alpha: 0
}, {
duration: 1200 + h * 200,
delay: h * 150,
easing: tween.easeOut,
onFinish: function onFinish() {
if (healWave.parent) healWave.destroy();
}
});
}
} else {
// Default success feedback
LK.effects.flashScreen(0x00FF00, 300);
}
// Create spell success text with enhanced animation
var successText = new Text2('SPELL CAST!', {
size: 80,
fill: 0xFFD700,
font: "monospace"
});
successText.anchor.set(0.5, 0.5);
successText.x = wizard.x;
successText.y = wizard.y - 180;
successText.zIndex = 1701;
game.addChild(successText);
tween(successText, {
y: successText.y - 100,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5,
rotation: Math.PI / 8
}, {
duration: 1200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (successText.parent) successText.destroy();
}
});
// Hide card panel after successful cast
hideInGameCardPanel();
cardPanelVisible = false;
} else {
console.log('Spell cast failed:', spellId);
// Enhanced failure feedback with shake effect
LK.effects.flashScreen(0xFF0000, 300);
// Create failure warning
var failureText = new Text2('CAST FAILED!', {
size: 60,
fill: 0xFF4444,
font: "monospace"
});
failureText.anchor.set(0.5, 0.5);
failureText.x = wizard.x;
failureText.y = wizard.y - 150;
failureText.zIndex = 1701;
game.addChild(failureText);
// Shake and fade failure text
var originalFailX = failureText.x;
tween(failureText, {
x: originalFailX - 10
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(failureText, {
x: originalFailX + 10
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(failureText, {
x: originalFailX,
alpha: 0,
y: failureText.y - 80
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (failureText.parent) failureText.destroy();
}
});
}
});
}
});
}
}
// Add targeting system to game mouse/touch handling
game.move = function (x, y, obj) {
if (targetingMode && targetingCursor) {
// Update cursor position to follow mouse/touch
targetingCursor.x = x;
targetingCursor.y = y;
// Calculate if target is in range
var dx = x - wizard.x;
var dy = y - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var maxRange = 400; // Maximum spell range
// Change cursor color based on range
if (distance <= maxRange) {
targetingCursor.tint = 0x00FF00; // Green for in range
targetingCursor.alpha = 0.8;
} else {
targetingCursor.tint = 0xFF0000; // Red for out of range
targetingCursor.alpha = 0.5;
}
}
};
game.down = function (x, y, obj) {
// Simple tap to attack handling - no spell casting interference
// All spell casting is now handled through the in-game card panel system
};
// Main game update loop
game.update = function () {
// Sort children by z-index to ensure proper rendering order
game.children.sort(function (a, b) {
return (a.zIndex || 0) - (b.zIndex || 0);
});
// Pause game when tutorial is active
if (tutorial && tutorial.isActive) {
return;
}
// Only update game logic if game has started
if (!gameStarted) {
return;
}
// Change music to epic battle theme at 10 enemies killed
if (enemyKillCounter === 10) {
LK.playMusic('epicBattle', {
volume: 0.8,
fade: {
start: 0,
end: 0.8,
duration: 1500
}
});
}
// Change to mystical ambient music at 25 enemies killed
if (enemyKillCounter === 25) {
LK.playMusic('mysticalAmbient', {
volume: 0.6,
fade: {
start: 0,
end: 0.6,
duration: 2000
}
});
}
// Upgrade menus removed - using spell deck system instead
// Reset consecutive spawns for paths that have cooled down (runs every frame)
for (var pathIdx = 0; pathIdx < 5; pathIdx++) {
// Check if enough time has passed since last spawn (cooldown expired)
if (pathLastSpawnTime[pathIdx] !== -1 && LK.ticks - pathLastSpawnTime[pathIdx] > pathCooldownDuration) {
// Reset consecutive spawns for this path due to cooldown
pathConsecutiveSpawns[pathIdx] = 0;
}
}
// Get stored difficulty setting
var selectedDifficulty = storage.difficulty || 'NORMAL';
var difficultyLevel = Math.floor(enemyKillCounter / 10); // Every 10 kills increases difficulty
// Helper functions now integrated into EnemyFactory
// Unified spawn management system
SpawnManager.processSpawnCycle(selectedDifficulty, difficultyLevel);
// Reset path cooldowns for optimized path management
for (var pathIdx = 0; pathIdx < 5; pathIdx++) {
if (pathLastSpawnTime[pathIdx] !== -1 && LK.ticks - pathLastSpawnTime[pathIdx] > pathCooldownDuration) {
pathConsecutiveSpawns[pathIdx] = 0;
}
}
// Unified CollisionManager for streamlined collision detection and response
var CollisionManager = {
// Consolidated collision configurations
collisionConfig: {
skeleton: {
damage: 20,
removeOnHit: true
},
ogre: {
damage: 30,
removeOnHit: true
},
knight: {
damage: 40,
removeOnHit: true
},
miniBoss: {
damage: 75,
removeOnHit: false
}
},
// Streamlined enemy collision processing with categorized collision types
processEnemyCollisions: function processEnemyCollisions() {
var allEnemies = globalEnemyManager.getAllEnemies();
// Category 1: Off-screen cleanup (non-collision processing)
this.processOffScreenCleanup(allEnemies);
// Category 2: Enemy-wizard collisions
this.processEnemyWizardCollisions(allEnemies);
},
// Separate processing for off-screen enemy cleanup
processOffScreenCleanup: function processOffScreenCleanup(allEnemies) {
for (var i = allEnemies.length - 1; i >= 0; i--) {
var enemy = allEnemies[i];
if (this.isOffScreen(enemy)) {
this.removeEnemyFromGame(enemy);
}
}
},
// Separate processing for enemy-wizard collisions
processEnemyWizardCollisions: function processEnemyWizardCollisions(allEnemies) {
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
if (!enemy.isDying && enemy.parent) {
this.checkWizardCollision(enemy);
}
}
},
// Efficient off-screen detection
isOffScreen: function isOffScreen(enemy) {
return enemy.y > 2732 + 100;
},
// Unified enemy removal system
removeEnemyFromGame: function removeEnemyFromGame(enemy) {
// Remove from global arrays directly
this.removeFromLegacyArrays(enemy);
enemy.destroy();
},
// Legacy array compatibility cleanup
removeFromLegacyArrays: function removeFromLegacyArrays(enemy) {
var arrays = [enemies, ogres, knights, miniBosses];
for (var a = 0; a < arrays.length; a++) {
var array = arrays[a];
for (var i = array.length - 1; i >= 0; i--) {
if (array[i] === enemy) {
array.splice(i, 1);
break;
}
}
}
},
// 1.1 Distance Culling: Enhanced wizard collision detection with optimized distance-based culling
checkWizardCollision: function checkWizardCollision(enemy) {
// Initialize collision tracking
if (enemy.lastIntersecting === undefined) {
enemy.lastIntersecting = false;
}
// 1.1 Distance Culling: Skip expensive intersection test if objects are too far apart
var dx = enemy.x - wizard.x;
var dy = enemy.y - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var maxCollisionDistance = 150; // Approximate maximum collision distance based on sprite sizes
var currentIntersecting = false;
if (distance <= maxCollisionDistance) {
// Only perform expensive intersection test if objects are close enough
currentIntersecting = wizard.intersects(enemy);
}
// Check collision transition
if (!enemy.lastIntersecting && currentIntersecting && !enemy.isDying) {
var config = this.getEnemyConfig(enemy);
wizard.takeDamage(config.damage);
// Handle enemy removal based on type
if (config.removeOnHit) {
this.removeEnemyFromGame(enemy);
return;
}
}
// Update collision state
enemy.lastIntersecting = currentIntersecting;
},
// Get enemy configuration by type
getEnemyConfig: function getEnemyConfig(enemy) {
return this.collisionConfig[enemy.enemyType] || this.collisionConfig.skeleton;
}
};
// Replace the old collision function call
function checkAllEnemyCollisions() {
CollisionManager.processEnemyCollisions();
}
// Call the unified collision detection function
checkAllEnemyCollisions();
// Check thorns spike collisions with all enemies continuously
var allSpikes = [];
for (var childIdx = 0; childIdx < game.children.length; childIdx++) {
var child = game.children[childIdx];
// Check if this child is a spike (has hitEnemies array and brown tint)
if (child.hitEnemies && child.tint === 0x8B4513) {
allSpikes.push(child);
}
}
for (var spikeIdx = 0; spikeIdx < allSpikes.length; spikeIdx++) {
var spike = allSpikes[spikeIdx];
var allEnemies = collisionArrayPool.getAllEnemies();
for (var enemyIdx = 0; enemyIdx < allEnemies.length; enemyIdx++) {
var enemy = allEnemies[enemyIdx];
// Only hit enemies that haven't been hit by this spike yet and are not dying
if (spike.intersects(enemy) && spike.hitEnemies.indexOf(enemy) === -1 && !enemy.isDying) {
var thornDamage = 100; // Always deal 100 damage
enemy.takeDamage(thornDamage);
LK.effects.flashObject(enemy, 0x8B4513, 300);
// Mark this enemy as hit by this spike
spike.hitEnemies.push(enemy);
}
}
}
// Update spell system
if (gameStarted) {
// Mana regeneration system
manaRegenTimer++;
if (manaRegenTimer >= 60) {
// Every second (60 ticks at 60fps)
manaRegenTimer = 0;
currentMana = Math.min(maxMana, currentMana + manaRegenRate);
if (activeSpellDeck) {
activeSpellDeck.currentMana = currentMana;
}
updateManaDisplay();
}
// Ensure mana bounds are correct
currentMana = Math.max(0, Math.min(maxMana, currentMana));
if (activeSpellDeck) {
activeSpellDeck.currentMana = currentMana;
}
}
// Simple time slow effects processing
var allEnemies = collisionArrayPool.getAllEnemies();
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
if (enemy.timeSlowed) {
enemy.timeSlowTimer--;
if (enemy.timeSlowTimer <= 0) {
enemy.timeSlowed = false;
enemy.timeSlowAmount = 1.0;
}
}
}
// Make tap text pulse
var pulse = 1 + Math.sin(LK.ticks * 0.1) * 0.2;
tapText.scale.set(pulse, pulse);
// Update targeting cursor animation
if (targetingMode && targetingCursor && targetingCursor.visible) {
// Rotate targeting cursor
targetingCursor.rotation += 0.05;
// Add secondary pulsing animation
var targetPulse = 1 + Math.sin(LK.ticks * 0.2) * 0.3;
if (targetingCursor.tint === 0x00FF00) {
// In-range pulsing
targetingCursor.alpha = 0.6 + targetPulse * 0.2;
} else {
// Out-of-range warning pulse
targetingCursor.alpha = 0.3 + targetPulse * 0.4;
}
// Update orbiting particles around cursor
if (targetingCursor.particles) {
for (var p = 0; p < targetingCursor.particles.length; p++) {
var particle = targetingCursor.particles[p];
if (particle && particle.parent) {
particle.orbitAngle += 0.08;
var orbitRadius = 40 + Math.sin(LK.ticks * 0.1) * 10;
particle.x = targetingCursor.x + Math.cos(particle.orbitAngle) * orbitRadius;
particle.y = targetingCursor.y + Math.sin(particle.orbitAngle) * orbitRadius;
particle.visible = targetingCursor.visible;
particle.tint = targetingCursor.tint;
particle.alpha = targetingCursor.alpha * 0.7;
}
}
}
}
// Update targeting range indicator
if (targetingMode && targetingRange && targetingRange.visible) {
// Gentle pulsing for range indicator
var rangePulse = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
targetingRange.scaleX = 8.0 * rangePulse;
targetingRange.scaleY = 8.0 * rangePulse;
targetingRange.alpha = 0.2 + Math.sin(LK.ticks * 0.15) * 0.1;
}
// Check for spell unlocks
checkSpellUnlocks();
// Clean up any orphaned projectiles that may not have been properly removed
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
if (!projectile || !projectile.parent || projectile.hitEnemy) {
projectiles.splice(i, 1);
}
}
// ProjectileFactory uses the global projectiles array, no separate activeProjectiles array needed
};
// Remove tap text after 5 seconds
tween({}, {}, {
duration: 5000,
onFinish: function onFinish() {
if (tapText && tapText.parent) {
tapText.destroy();
}
}
});