User prompt
implementa el 2C
User prompt
implementa 2B
User prompt
implementa 2B
User prompt
implementa 2A
User prompt
implementa 1B
User prompt
implementa 1A
User prompt
unifica efectos visuales de hechizos
User prompt
consolida clases de enemigos
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'tween(readyAura, {' Line Number: 6781 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el paso 1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
puedes solucionar el problema si te lo pido?
User prompt
procede con el paso 8 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ahora implementa el paso 7 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ahora implementa el paso 6 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ahora implementa el paso 5 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ahora implementa el paso 4 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ahora aplica el paso 3 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ahora aplica el paso 2 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
aplica el paso 1
User prompt
implementa el paso 5
User prompt
ahora implementa el paso 4
User prompt
ahora implementa el paso 3
User prompt
ahora implementa el paso 2 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
solucionar el problema que esta sucediendo cuando se inicia el juego
User prompt
Please fix the bug: 'Uncaught ReferenceError: cardToggleButton is not defined' in or related to this line: 'cardToggleButton.visible = true;' Line Number: 1576
/**** * 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; } // Unified spell visual effects system var SpellVisualEffects = { // Create pre-cast buildup effects for any spell createPreCastEffects: function createPreCastEffects(spellType, wizard) { if (spellType === 'fireball') { this.createFireBuildupEffects(wizard); } else if (spellType === 'lightning') { this.createLightningBuildupEffects(wizard); } else if (spellType === 'heal') { this.createHealBuildupEffects(wizard); } }, // Create generic particle effect createParticle: function createParticle(config) { var particle = game.addChild(LK.getAsset(config.asset || 'projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: config.x, y: config.y, scaleX: config.scaleX || 1, scaleY: config.scaleY || 1 })); particle.tint = config.tint; particle.alpha = config.alpha || 0.8; particle.zIndex = config.zIndex || 1600; if (config.rotation !== undefined) particle.rotation = config.rotation; return particle; }, // Animate particle with standard patterns animateParticle: function animateParticle(particle, animConfig) { tween(particle, animConfig.to, { duration: animConfig.duration, delay: animConfig.delay || 0, easing: animConfig.easing || tween.easeOut, onFinish: function onFinish() { if (particle.parent) particle.destroy(); } }); }, // Create fire buildup effects createFireBuildupEffects: function createFireBuildupEffects(wizard) { // Fire particles rising for (var f = 0; f < 12; f++) { var flameParticle = this.createParticle({ x: wizard.x + (Math.random() - 0.5) * 200, y: wizard.y + 150 + Math.random() * 100, scaleX: 0.4 + Math.random() * 0.6, scaleY: 0.8 + Math.random() * 0.8, tint: Math.random() > 0.5 ? 0xFF4500 : 0xFF6600, alpha: 0.9 }); this.animateParticle(flameParticle, { to: { x: wizard.x + (Math.random() - 0.5) * 40, y: wizard.y - 50, scaleX: (0.4 + Math.random() * 0.6) * 1.8, scaleY: (0.8 + Math.random() * 0.8) * 1.5, alpha: 0.3, rotation: Math.PI * (Math.random() - 0.5) }, duration: 800 + f * 60, delay: f * 40 }); } // Heat waves for (var h = 0; h < 6; h++) { var heatWave = this.createParticle({ asset: 'energySphere', x: wizard.x, y: wizard.y, scaleX: 1.5 + h * 0.3, scaleY: 0.8 + h * 0.2, tint: 0xFF8800, alpha: 0.15 - h * 0.02, zIndex: 1590 }); this.animateParticle(heatWave, { to: { scaleX: (1.5 + h * 0.3) * 2.5, scaleY: (0.8 + h * 0.2) * 1.8, alpha: 0, x: wizard.x + (Math.random() - 0.5) * 30, y: wizard.y - 40 }, duration: 1200 + h * 100, delay: h * 80, easing: tween.easeInOut }); } }, // Create lightning buildup effects createLightningBuildupEffects: function createLightningBuildupEffects(wizard) { // Electric sparks with closure fix for (var e = 0; e < 16; e++) { (function (sparkIndex) { var electricSpark = SpellVisualEffects.createParticle({ x: wizard.x + (Math.random() - 0.5) * 300, y: wizard.y + (Math.random() - 0.5) * 300, scaleX: 0.3 + Math.random() * 0.4, scaleY: 0.1 + Math.random() * 0.2, tint: Math.random() > 0.7 ? 0xFFFFFF : 0x00FFFF, rotation: Math.random() * Math.PI * 2 }); // Create spark path var sparkPath = []; for (var pathPoint = 0; pathPoint < 5; pathPoint++) { sparkPath.push({ x: wizard.x + (Math.random() - 0.5) * 60, y: wizard.y + (Math.random() - 0.5) * 60 }); } function animateSparkPath(spark, pathIndex, path) { if (pathIndex >= path.length || !spark.parent) { if (spark.parent) spark.destroy(); return; } tween(spark, { x: path[pathIndex].x, y: path[pathIndex].y, scaleX: (0.3 + Math.random() * 0.4) * (1 + pathIndex * 0.2), scaleY: (0.1 + Math.random() * 0.2) * (1 + pathIndex * 0.3), alpha: 0.9 - pathIndex * 0.15, rotation: spark.rotation + Math.PI * 0.5 }, { duration: 80 + Math.random() * 40, easing: tween.linear, onFinish: function onFinish() { animateSparkPath(spark, pathIndex + 1, path); } }); } animateSparkPath(electricSpark, 0, sparkPath); })(e); } // Static electricity for (var s = 0; s < 12; s++) { var staticElectricity = this.createParticle({ asset: 'energySphere', x: wizard.x + Math.cos(s * Math.PI * 2 / 12) * (80 + Math.random() * 40), y: wizard.y + Math.sin(s * Math.PI * 2 / 12) * (80 + Math.random() * 40), scaleX: 0.4 + Math.random() * 0.3, scaleY: 2.0 + Math.random() * 1.0, tint: 0x88FFFF, alpha: 0.7, rotation: Math.random() * Math.PI * 2, zIndex: 1595 }); this.animateParticle(staticElectricity, { to: { x: wizard.x + Math.cos((s + 3) * Math.PI * 2 / 12) * (120 + Math.random() * 60), y: wizard.y + Math.sin((s + 3) * Math.PI * 2 / 12) * (120 + Math.random() * 60), scaleX: 0.1, scaleY: 0.3, alpha: 0, rotation: staticElectricity.rotation + Math.PI * 4 }, duration: 400 + s * 50, delay: s * 30 }); } }, // Create heal buildup effects createHealBuildupEffects: function createHealBuildupEffects(wizard) { // Light rays for (var r = 0; r < 8; r++) { var lightRay = this.createParticle({ asset: 'energySphere', x: wizard.x, y: wizard.y, scaleX: 0.5, scaleY: 4 + Math.random() * 2, tint: 0x88FF88, alpha: 0.6, rotation: r * Math.PI * 2 / 8 + Math.random() * 0.3, zIndex: 1580 }); this.animateParticle(lightRay, { to: { scaleX: 1.5, scaleY: (4 + Math.random() * 2) * 1.8, alpha: 0, rotation: lightRay.rotation + Math.PI * 0.2 }, duration: 1200 + r * 100, delay: r * 80 }); } // Healing mist for (var m = 0; m < 15; m++) { var healingMist = this.createParticle({ x: wizard.x + (Math.random() - 0.5) * 200, y: wizard.y + Math.random() * 150 + 50, scaleX: 1.0 + Math.random() * 1.5, scaleY: 0.6 + Math.random() * 0.8, tint: Math.random() > 0.5 ? 0x00FF88 : 0x66FFAA, alpha: 0.4, zIndex: 1590 }); this.animateParticle(healingMist, { to: { x: wizard.x + (Math.random() - 0.5) * 300, y: wizard.y - 200 - Math.random() * 100, alpha: 0, scaleX: (1.0 + Math.random() * 1.5) * 0.3, scaleY: (0.6 + Math.random() * 0.8) * 0.4, rotation: Math.PI * 0.5 * (Math.random() - 0.5) }, duration: 2500 + m * 150, delay: m * 80 }); } }, // Create casting aura createCastingAura: function createCastingAura(wizard, color) { var castingAura = this.createParticle({ x: wizard.x, y: wizard.y, scaleX: 4, scaleY: 4, tint: color, alpha: 0.7 }); this.animateParticle(castingAura, { to: { scaleX: 8, scaleY: 8, alpha: 0, rotation: Math.PI * 2 }, duration: 1000 }); return castingAura; }, // Create spell ring createSpellRing: function createSpellRing(wizard, color) { var spellRing = this.createParticle({ asset: 'spellCardBg', x: wizard.x, y: wizard.y, scaleX: 2, scaleY: 2, tint: color, alpha: 0.5, zIndex: 1595 }); this.animateParticle(spellRing, { to: { scaleX: 6, scaleY: 6, alpha: 0, rotation: -Math.PI * 1.5 }, duration: 1200 }); return spellRing; } }; // 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; } } // Use unified visual effects system SpellVisualEffects.createPreCastEffects('fireball', wizard); // Enhanced wizard casting animation LK.effects.flashObject(wizard, 0xFF4500, 800); // Create casting aura and ring SpellVisualEffects.createCastingAura(wizard, 0xFF4500); SpellVisualEffects.createSpellRing(wizard, 0xFF6600); 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'); // Simplified lightning casting effect LK.effects.flashObject(wizard, 0x00FFFF, 600); LK.effects.flashScreen(0x00FFFF, 400); 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(); // Use unified visual effects system SpellVisualEffects.createPreCastEffects('heal', wizard); // Simplified healing effects - basic green flash and simple visual feedback LK.effects.flashScreen(0x00FF00, 500); LK.effects.flashObject(wizard, 0x00FF00, 1000); // 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(); } } });
===================================================================
--- original.js
+++ change.js
@@ -4272,110 +4272,11 @@
var actualHealing = wizard.health - healthBefore;
updateHealthBar();
// Use unified visual effects system
SpellVisualEffects.createPreCastEffects('heal', wizard);
- // Floating plus symbols with closure fix
- for (var plus = 0; plus < 6; plus++) {
- (function (currentPlus) {
- var plusSymbol = new Text2('+', {
- size: 80 + Math.random() * 40,
- fill: 0x00FF88,
- font: "monospace"
- });
- plusSymbol.anchor.set(0.5, 0.5);
- plusSymbol.x = wizard.x + Math.cos(currentPlus * Math.PI * 2 / 6) * (100 + Math.random() * 50);
- plusSymbol.y = wizard.y + Math.sin(currentPlus * Math.PI * 2 / 6) * (100 + Math.random() * 50);
- plusSymbol.alpha = 0.8;
- plusSymbol.zIndex = 1600;
- game.addChild(plusSymbol);
- tween(plusSymbol, {
- x: wizard.x + Math.cos((currentPlus + 1.5) * Math.PI * 2 / 6) * (150 + Math.random() * 60),
- y: wizard.y + Math.sin((currentPlus + 1.5) * Math.PI * 2 / 6) * (150 + Math.random() * 60) - 120,
- alpha: 0,
- scaleX: 1.5,
- scaleY: 1.5,
- rotation: Math.PI * (Math.random() - 0.5)
- }, {
- duration: 2000 + currentPlus * 200,
- delay: currentPlus * 120,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- if (plusSymbol.parent) plusSymbol.destroy();
- }
- });
- })(plus);
- }
- // Enhanced healing prep with closure fix
- for (var p = 0; p < 12; p++) {
- (function (prepIndex) {
- var healPrep = SpellVisualEffects.createParticle({
- x: wizard.x + (Math.random() - 0.5) * 250,
- y: wizard.y + (Math.random() - 0.5) * 250,
- scaleX: 0.6 + Math.random() * 0.4,
- scaleY: 0.6 + Math.random() * 0.4,
- tint: Math.random() > 0.6 ? 0x00FF88 : 0x66FF66,
- alpha: 0.7,
- zIndex: 1585
- });
- SpellVisualEffects.animateParticle(healPrep, {
- to: {
- x: wizard.x + (Math.random() - 0.5) * 40,
- y: wizard.y + (Math.random() - 0.5) * 40,
- scaleX: 2.2,
- scaleY: 2.2,
- alpha: 0,
- rotation: Math.PI * 1.5 * (Math.random() - 0.5)
- },
- duration: 1000 + Math.random() * 400,
- delay: prepIndex * 60
- });
- })(p);
- }
- // Enhanced healing effects
+ // Simplified healing effects - basic green flash and simple visual feedback
LK.effects.flashScreen(0x00FF00, 500);
LK.effects.flashObject(wizard, 0x00FF00, 1000);
- // Soft environmental lighting
- var healingLight = SpellVisualEffects.createParticle({
- asset: 'spellCardBg',
- x: wizard.x,
- y: wizard.y,
- scaleX: 8,
- scaleY: 8,
- tint: 0x88FF88,
- alpha: 0.25,
- zIndex: 1575
- });
- SpellVisualEffects.animateParticle(healingLight, {
- to: {
- scaleX: 16,
- scaleY: 16,
- alpha: 0
- },
- duration: 2500
- });
- // Multiple healing aura rings with closure fix
- for (var h = 0; h < 7; h++) {
- (function (ringIndex) {
- var healingAura = SpellVisualEffects.createParticle({
- x: wizard.x,
- y: wizard.y,
- scaleX: 1 + ringIndex * 0.4,
- scaleY: 1 + ringIndex * 0.4,
- tint: ringIndex % 2 === 0 ? 0x00FF88 : 0x66FFAA,
- alpha: 0.6 - ringIndex * 0.08,
- zIndex: 1595
- });
- SpellVisualEffects.animateParticle(healingAura, {
- to: {
- scaleX: 9 + ringIndex * 1.5,
- scaleY: 9 + ringIndex * 1.5,
- alpha: 0
- },
- duration: 1800 + ringIndex * 250,
- delay: ringIndex * 120
- });
- })(h);
- }
// Create floating heal text
var healText = new Text2('+' + actualHealing + ' HP', {
size: 120,
fill: 0x00FF00,