User prompt
haz el 1D
User prompt
si haz el paso 1C
User prompt
haz el paso 1B
User prompt
perfecto haz el paso 1A
User prompt
elimina las upgrades
User prompt
elimina la tienda mejora
User prompt
implementa todas las mejoras del juego en el deck de hechizos
User prompt
el deck de hechizos no se entiende
User prompt
crea un deck de hechizos en el menu principal
User prompt
simplifica el sistema de mejoras
User prompt
haz mas eficiente los sistemas de spawn
User prompt
Please fix the bug: 'ReferenceError: enemyType is not defined' in or related to this line: 'if (enemyType === 'miniBoss') {' Line Number: 146
User prompt
Please fix the bug: 'ReferenceError: enemyType is not defined' in or related to this line: 'if (enemyType === 'miniBoss') {' Line Number: 146
User prompt
crea un sistema unificado de configuracion de enemigos y usa patrones de herencia para cada clase
User prompt
bueno haz el tutorial mas basico y confiable
User prompt
mejora el tutorial con ejemplos interactivos
User prompt
implementa tap directo al enemigo
User prompt
quita los combos
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'speed')' in or related to this line: 'self.speed = config.speed;' Line Number: 1699
User prompt
Please fix the bug: 'ReferenceError: ENEMY_SPAWN_CONFIG is not defined' in or related to this line: 'var config = ENEMY_SPAWN_CONFIG[enemyType];' Line Number: 4101
User prompt
si empieza con la consolidacion de configuraciones
User prompt
haz que la creacion de paths sean considerados un solo paths para reducir codigo
User prompt
haz que el menu backgronf=d cubra toda la pantalla
User prompt
haz que el orb quite 200 puntos de vida
User prompt
elimina las clases separadas de enemigos y crea una clase base mas simple
/**** * 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 knight if (knight && self.intersects(knight)) { 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; } } self.destroy(); }; return self; }); // Game arrays to track objects var Enemy = Container.expand(function (type, config) { var self = Container.call(this); // Default configuration if none provided if (!config) config = {}; var enemyType = type || 'skeleton'; // Animation system self.currentFrame = 1; self.animationTimer = 0; self.animationSpeed = config.animationSpeed || 15; self.animationState = 'walking'; // Get enemy configuration from unified config var enemyConfig = GAME_CONFIG.enemies[enemyType]; if (!enemyConfig) { console.error('Unknown enemy type:', enemyType); enemyConfig = GAME_CONFIG.enemies.skeleton; // fallback } // Create type config with overrides from constructor var typeConfigs = { skeleton: { assetPrefix: enemyConfig.assetPrefix, scale: enemyConfig.scale, health: config.health || enemyConfig.baseHealth, speed: config.speed || enemyConfig.baseSpeed, vibration: enemyConfig.vibration, damageTextSize: enemyConfig.damageTextSize, damageTextColor: enemyConfig.damageTextColor, impactScale: enemyConfig.impactScale, deathRotation: enemyConfig.deathRotation, scoreReward: enemyConfig.scoreReward, tint: enemyConfig.tint }, ogre: { assetPrefix: GAME_CONFIG.enemies.ogre.assetPrefix, scale: GAME_CONFIG.enemies.ogre.scale, health: config.health || GAME_CONFIG.enemies.ogre.baseHealth, speed: config.speed || GAME_CONFIG.enemies.ogre.baseSpeed, vibration: GAME_CONFIG.enemies.ogre.vibration, damageTextSize: GAME_CONFIG.enemies.ogre.damageTextSize, damageTextColor: GAME_CONFIG.enemies.ogre.damageTextColor, impactScale: GAME_CONFIG.enemies.ogre.impactScale, deathRotation: GAME_CONFIG.enemies.ogre.deathRotation, scoreReward: GAME_CONFIG.enemies.ogre.scoreReward, tint: GAME_CONFIG.enemies.ogre.tint }, knight: { assetPrefix: GAME_CONFIG.enemies.knight.assetPrefix, scale: GAME_CONFIG.enemies.knight.scale, health: config.health || GAME_CONFIG.enemies.knight.baseHealth, speed: config.speed || GAME_CONFIG.enemies.knight.baseSpeed, vibration: GAME_CONFIG.enemies.knight.vibration, damageTextSize: GAME_CONFIG.enemies.knight.damageTextSize, damageTextColor: GAME_CONFIG.enemies.knight.damageTextColor, impactScale: GAME_CONFIG.enemies.knight.impactScale, deathRotation: GAME_CONFIG.enemies.knight.deathRotation, scoreReward: GAME_CONFIG.enemies.knight.scoreReward, tint: GAME_CONFIG.enemies.knight.tint }, miniBoss: { assetPrefix: GAME_CONFIG.enemies.miniBoss.assetPrefix, scale: GAME_CONFIG.enemies.miniBoss.scale, health: config.health || GAME_CONFIG.enemies.miniBoss.baseHealth, speed: config.speed || GAME_CONFIG.enemies.miniBoss.baseSpeed, vibration: GAME_CONFIG.enemies.miniBoss.vibration, damageTextSize: GAME_CONFIG.enemies.miniBoss.damageTextSize, damageTextColor: GAME_CONFIG.enemies.miniBoss.damageTextColor, impactScale: GAME_CONFIG.enemies.miniBoss.impactScale, deathRotation: GAME_CONFIG.enemies.miniBoss.deathRotation, scoreReward: GAME_CONFIG.enemies.miniBoss.scoreReward, tint: GAME_CONFIG.enemies.miniBoss.tint } }; self.typeConfig = typeConfigs[enemyType]; self.enemyType = enemyType; // Create animation frames self.animationFrames = []; for (var i = 1; i <= 4; i++) { var frameGraphics = self.attachAsset(self.typeConfig.assetPrefix + i, { anchorX: 0.5, anchorY: 1.0, scaleX: self.typeConfig.scale, scaleY: self.typeConfig.scale }); frameGraphics.visible = i === 1; if (self.typeConfig.tint) frameGraphics.tint = self.typeConfig.tint; self.animationFrames.push(frameGraphics); } // Create hitbox var hitbox = self.attachAsset(self.typeConfig.assetPrefix + '1', { anchorX: 0.5, anchorY: 1.0, scaleX: self.typeConfig.scale * 2, scaleY: self.typeConfig.scale * 2 }); hitbox.alpha = 0; // Initialize stats self.health = self.typeConfig.health; self.maxHealth = self.typeConfig.health; self.speed = self.typeConfig.speed; self.lastX = 0; self.frozen = false; self.frozenTimer = 0; // Define createHealthBar method before it's used self.createHealthBar = function () { if (enemyType !== 'miniBoss') return; self.healthBarBg = game.addChild(LK.getAsset('miniBossHealthBarBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 200, scaleX: 1.0, scaleY: 1.0 })); self.healthBarFg = game.addChild(LK.getAsset('miniBossHealthBar', { anchorX: 0.0, anchorY: 0.5, x: 2048 / 2 - 200, y: 200, scaleX: 1.0, scaleY: 1.0 })); self.healthText = new Text2('Boss Health: ' + self.health + '/' + self.maxHealth, { size: 40, fill: 0xFFFFFF, font: "monospace" }); self.healthText.anchor.set(0.5, 0.5); self.healthText.x = 2048 / 2; self.healthText.y = 150; game.addChild(self.healthText); }; // Special properties for mini boss if (enemyType === 'miniBoss') { self.createHealthBar(); } self.updateHealthBar = function () { if (enemyType !== 'miniBoss' || !self.healthBarFg) return; var healthPercent = self.health / self.maxHealth; self.healthBarFg.scaleX = healthPercent; 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; } }; self.update = function () { if (tutorial && tutorial.isActive) { return; } if (self.isDying) { return; } var speedMultiplier = 1.0; if (upgradeMenu && upgradeMenu.visible) { speedMultiplier = 0.3; } // Animation system 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; } // Speed progression if (!self.speedTweenStarted) { self.speedTweenStarted = true; var targetSpeed = self.speed * 1.5; tween(self, { speed: targetSpeed }, { duration: 10000, easing: tween.easeOut }); } // Handle frozen state if (self.frozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.frozen = false; } return; } // 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) { self.x += dx / distance * self.speed * speedMultiplier; self.y += dy / distance * self.speed * speedMultiplier; } // Face direction var flipScale = dx < 0 ? -self.typeConfig.scale : self.typeConfig.scale; for (var frameIdx = 0; frameIdx < self.animationFrames.length; frameIdx++) { self.animationFrames[frameIdx].scaleX = flipScale; } } else { self.y += self.speed * speedMultiplier; } }; self.takeDamage = function (damage) { self.health -= damage; self.animationState = 'attacking'; LK.effects.flashObject(self, 0xFF0000, 50); // Update health bar for mini boss if (enemyType === 'miniBoss') { self.updateHealthBar(); } // Create damage text var damageText = new Text2('-' + damage, { size: self.typeConfig.damageTextSize, fill: self.typeConfig.damageTextColor, 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); tween(damageText, { y: damageText.y - 120, alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { damageText.destroy(); } }); // Create impact effect using unified function createImpactEffect(self); // Return to walking after attack animation tween({}, {}, { duration: 300, onFinish: function onFinish() { if (self.animationState === 'attacking') { self.animationState = 'walking'; } } }); if (self.health <= 0) { self.die(); } }; self.down = function (x, y, obj) { // Don't allow attacking during tutorial if (tutorial && tutorial.isActive) { return; } // Don't allow attacking during upgrade menu if (upgradeMenu && upgradeMenu.visible) { return; } // Don't allow attacking dying enemies if (self.isDying) { return; } // Check wizard's attack cooldown if (wizard && wizard.attackCooldown > 0) { // Flash red to indicate cooldown LK.effects.flashObject(self, 0xFF0000, 200); return; } // Vibration feedback if (typeof LK.vibrate === 'function') { if (Array.isArray(self.typeConfig.vibration)) { LK.vibrate(self.typeConfig.vibration); } else { LK.vibrate(self.typeConfig.vibration); } } // Visual feedback for successful tap selectedEnemy = self; LK.effects.flashObject(self, 0xFFFF00, 500); // Create projectile from wizard to this enemy if (wizard && projectiles.length < 10) { var projectile = game.addChild(new UnifiedProjectile('projectile')); projectile.x = wizard.x; projectile.y = wizard.y; var dx = self.x - wizard.x; var dy = self.y - wizard.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { projectile.direction.x = dx / distance; projectile.direction.y = dy / distance; } projectile.targetEnemy = self; projectiles.push(projectile); LK.getSound('spellCast').play(); // Set wizard's attack cooldown wizard.attackCooldown = 30; // 0.5 seconds at 60fps } }; self.die = function () { // Determine appropriate enemy array based on type var arrayToUpdate = null; if (enemyType === 'skeleton') arrayToUpdate = enemies;else if (enemyType === 'ogre') arrayToUpdate = ogres;else if (enemyType === 'knight') arrayToUpdate = knights;else if (enemyType === 'miniBoss') arrayToUpdate = miniBosses; // Call unified death animation function createEnemyDeathAnimation(self, enemyType, arrayToUpdate); }; 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; } // Pause energy orb when upgrade menu is visible if (upgradeMenu && upgradeMenu.visible) { 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 = enemies.concat(ogres).concat(knights).concat(miniBosses); 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 var energyBeam = game.addChild(new UnifiedProjectile('energyBeam')); energyBeam.x = self.x; energyBeam.y = self.y; energyBeam.targetEnemy = closestEnemy; // Calculate direction to target var dx = closestEnemy.x - self.x; var dy = closestEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { energyBeam.direction.x = dx / distance; energyBeam.direction.y = dy / distance; } // 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); // 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: 2000, 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 = 2000; self.addChild(shopButtonText); // 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: 2200, 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 = 2200; 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 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 >= 1900 && y <= 2100 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) { // Show shop menu self.showShop(); } else if (storage.tutorialCompleted && y >= 2100 && y <= 2300 && 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 () { // Create configuration overlay if (!self.configOverlay) { self.configOverlay = self.addChild(LK.getAsset('startMenuBackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 1.0, scaleY: 1.0 })); self.configOverlay.alpha = 1.0; self.configOverlay.tint = 0x000000; // Config title var configTitle = new Text2('CONFIGURACION', { size: 120, fill: 0xFFD700, font: "monospace" }); configTitle.anchor.set(0.5, 0.5); configTitle.x = 2048 / 2; configTitle.y = 800; self.addChild(configTitle); self.configTitle = configTitle; // Music volume setting var musicVolumeText = new Text2('VOLUMEN MUSICA: ' + Math.round((storage.musicVolume || 0.7) * 100) + '%', { size: 80, fill: 0xFFFFFF, font: "monospace" }); musicVolumeText.anchor.set(0.5, 0.5); musicVolumeText.x = 2048 / 2; musicVolumeText.y = 1200; self.addChild(musicVolumeText); self.musicVolumeText = musicVolumeText; // Sound volume setting var soundVolumeText = new Text2('VOLUMEN SONIDO: ' + Math.round((storage.soundVolume || 1.0) * 100) + '%', { size: 80, fill: 0xFFFFFF, font: "monospace" }); soundVolumeText.anchor.set(0.5, 0.5); soundVolumeText.x = 2048 / 2; soundVolumeText.y = 1400; self.addChild(soundVolumeText); self.soundVolumeText = soundVolumeText; // Difficulty setting var difficultyText = new Text2('DIFICULTAD: ' + (storage.difficulty || 'NORMAL'), { size: 80, fill: 0xFFFFFF, font: "monospace" }); difficultyText.anchor.set(0.5, 0.5); difficultyText.x = 2048 / 2; difficultyText.y = 1600; self.addChild(difficultyText); self.difficultyText = difficultyText; // Back button var backButton = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2000, scaleX: 2, scaleY: 2 }); backButton.tint = 0x00FF00; self.backButton = backButton; var backText = new Text2('VOLVER', { size: 80, fill: 0xFFFFFF, font: "monospace" }); backText.anchor.set(0.5, 0.5); backText.x = 2048 / 2; backText.y = 2000; self.addChild(backText); self.backText = backText; } 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 () { // Create shop overlay if (!self.shopOverlay) { self.shopOverlay = self.addChild(LK.getAsset('startMenuBackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 1.0, scaleY: 1.0 })); self.shopOverlay.alpha = 1.0; self.shopOverlay.tint = 0x000033; // Shop title var shopTitle = new Text2('TIENDA', { size: 120, fill: 0xFFD700, font: "monospace" }); shopTitle.anchor.set(0.5, 0.5); shopTitle.x = 2048 / 2; shopTitle.y = 800; self.addChild(shopTitle); self.shopTitle = shopTitle; // Shop items var shopItems = [{ 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' }]; // Initialize shop elements arrays if not exists if (!self.shopIcons) self.shopIcons = []; if (!self.shopTexts) self.shopTexts = []; if (!self.shopBuyButtons) self.shopBuyButtons = []; if (!self.shopBuyTexts) self.shopBuyTexts = []; // Display shop items for (var i = 0; i < shopItems.length; i++) { var item = shopItems[i]; var yPos = 1100 + i * 200; // Item icon 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); // Item text 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); // Buy button 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); } // Back button var shopBackButton = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2000, scaleX: 2, scaleY: 2 }); shopBackButton.tint = 0x00FF00; self.shopBackButton = shopBackButton; var shopBackText = new Text2('VOLVER', { size: 80, fill: 0xFFFFFF, font: "monospace" }); shopBackText.anchor.set(0.5, 0.5); shopBackText.x = 2048 / 2; shopBackText.y = 2000; self.addChild(shopBackText); self.shopBackText = shopBackText; } 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.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; } // 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; // 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; }); // All enemy types now use the unified Enemy class with type parameter 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 self.update = function () { // Pause orb when tutorial is active if (tutorial && tutorial.isActive) { return; } // Pause orb when upgrade menu is visible if (upgradeMenu && upgradeMenu.visible) { 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; // Check collision with enemies using collision state tracking (but never with wizard) var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses); 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; } var 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; } }; return self; }); var Tutorial = Container.expand(function () { var self = Container.call(this); // Simple tutorial state self.currentStep = 0; self.isActive = false; self.skipped = false; self.tutorialTexts = []; self.practiceEnemy = null; self.stepCompleted = false; // 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; // Simple tutorial steps self.tutorialSteps = [{ title: 'BIENVENIDO A WIZARD DEFENDER!', description: 'Toca enemigos para atacarlos.\n\nToca para continuar.', showSkip: true }, { title: 'PRACTICA DE ATAQUE', description: 'Toca el enemigo brillante para atacarlo.', spawnEnemy: true, waitForKill: true }, { title: 'SISTEMA DE SALUD', description: 'Tu salud está arriba a la izquierda.\nEvita que los enemigos te toquen.\n\nToca para continuar.' }, { title: 'TUTORIAL COMPLETADO', description: '¡Listo para jugar!\n\nToca para empezar.', startGame: true }]; // Start tutorial self.startTutorial = function () { self.isActive = true; self.currentStep = 0; self.stepCompleted = false; self.skipped = false; // Hide game elements if (gameMenu) gameMenu.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; } // Show tutorial self.visible = true; tutorialOverlay.visible = true; self.showCurrentStep(); return true; }; // Show current step self.showCurrentStep = function () { self.clearStepElements(); var step = self.tutorialSteps[self.currentStep]; if (!step) { self.completeTutorial(); return; } // Show title var titleText = new Text2(step.title, { size: 80, fill: 0xFFD700, font: "monospace" }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 800; self.addChild(titleText); self.tutorialTexts.push(titleText); // Show description var descText = new Text2(step.description, { size: 60, fill: 0xFFFFFF, font: "monospace" }); descText.anchor.set(0.5, 0.5); descText.x = 2048 / 2; descText.y = 1200; self.addChild(descText); self.tutorialTexts.push(descText); // 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; 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); } // Handle special steps if (step.spawnEnemy) { self.spawnPracticeEnemy(); } self.stepCompleted = !step.waitForKill; }; // Spawn practice enemy self.spawnPracticeEnemy = function () { // Show game background and wizard if (backgroundMap) backgroundMap.visible = true; if (wizard) { wizard.visible = true; wizard.x = 2048 / 2; wizard.y = 2732 - 600; } // Create practice enemy self.practiceEnemy = game.addChild(new Enemy('skeleton', { health: 30, speed: 1, animationSpeed: 15 })); self.practiceEnemy.x = 2048 / 2; self.practiceEnemy.y = 1000; self.practiceEnemy.pathIndex = 0; self.practiceEnemy.isPracticeEnemy = true; // Make enemy glow for (var frameIdx = 0; frameIdx < self.practiceEnemy.animationFrames.length; frameIdx++) { self.practiceEnemy.animationFrames[frameIdx].tint = 0xFFFF00; } enemies.push(self.practiceEnemy); }; // Clear step elements self.clearStepElements = function () { // Clear text elements for (var i = 0; i < self.tutorialTexts.length; i++) { if (self.tutorialTexts[i] && self.tutorialTexts[i].parent) { self.tutorialTexts[i].destroy(); } } self.tutorialTexts = []; // Clear practice enemy if (self.practiceEnemy && self.practiceEnemy.parent) { var enemyIndex = enemies.indexOf(self.practiceEnemy); if (enemyIndex !== -1) { enemies.splice(enemyIndex, 1); } self.practiceEnemy.destroy(); self.practiceEnemy = null; } }; // Update tutorial self.update = function () { if (!self.isActive) return; // Check if practice enemy was killed if (self.practiceEnemy && !self.practiceEnemy.parent) { self.stepCompleted = true; self.practiceEnemy = null; // Update description var step = self.tutorialSteps[self.currentStep]; if (step.waitForKill && self.tutorialTexts.length > 1) { self.tutorialTexts[1].setText('¡Excelente!\n\nToca para continuar.'); self.tutorialTexts[1].tint = 0x00FF00; } } }; // Next step self.nextStep = function () { self.currentStep++; if (self.currentStep < self.tutorialSteps.length) { self.showCurrentStep(); } else { self.completeTutorial(); } }; // Complete tutorial self.completeTutorial = function () { // Mark as completed storage.tutorialCompleted = true; // Clear elements self.clearStepElements(); // Hide tutorial tutorialOverlay.visible = false; self.visible = false; self.isActive = false; // Hide wizard if (wizard) wizard.visible = false; // Auto-start game if completed naturally if (!self.skipped) { if (gameMenu) gameMenu.startGame(); } else { if (gameMenu) gameMenu.visible = true; } self.skipped = false; }; // Handle input self.down = function (x, y, obj) { if (!self.isActive) return; var step = self.tutorialSteps[self.currentStep]; // Check skip button if (step.showSkip && x >= 2048 - 300 && x <= 2048 - 100 && y >= 100 && y <= 300) { self.skipped = true; self.completeTutorial(); return; } // Advance if step is completed if (self.stepCompleted) { self.nextStep(); } }; return self; }); var UnifiedProjectile = Container.expand(function (type) { var self = Container.call(this); // Default type self.projectileType = type || 'projectile'; // Common properties self.speed = 50; self.direction = { x: 0, y: 0 }; self.lastIntersecting = {}; self.targetEnemy = null; self.trailSegments = []; // Get projectile configuration from unified config var config = GAME_CONFIG.projectiles[self.projectileType]; if (!config) { console.error('Unknown projectile type:', self.projectileType); config = GAME_CONFIG.projectiles.projectile; // fallback } self.speed = config.speed; // Create main projectile graphics if (self.projectileType === 'projectile') { self.mainGraphics = self.attachAsset(config.assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: config.scale, scaleY: config.scale }); } else { self.mainGraphics = self.attachAsset(config.glowAsset, { anchorX: 0.5, anchorY: 0.5, scaleX: config.scale, scaleY: config.scale }); } self.mainGraphics.tint = config.tint; self.mainGraphics.alpha = 0.9; // Create glow effect for projectile type if (self.projectileType === 'projectile') { var lightGlow = self.attachAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 2.0 }); lightGlow.alpha = 0.15; lightGlow.tint = config.glowTint; // Animate the glow effect tween(lightGlow, { scaleX: 3.5, scaleY: 3.5, alpha: 0.05 }, { duration: 800, easing: tween.easeInOut }); tween(lightGlow, { rotation: Math.PI * 4 }, { duration: 1000, easing: tween.linear }); } self.update = function () { // Pause when tutorial is active if (tutorial && tutorial.isActive) { return; } // Move projectile self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Type-specific visual effects if (config.hasRotation) { var angle = Math.atan2(self.direction.y, self.direction.x); self.mainGraphics.rotation = angle + Math.PI / 2; } if (config.hasFlicker) { var flicker = 1 + Math.sin(LK.ticks * 0.4) * 0.3; self.mainGraphics.scaleX = config.scale * flicker; self.mainGraphics.scaleY = config.scale * flicker; self.mainGraphics.rotation += 0.2; } // Remove if off screen if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.removeFromGame(); return; } // Handle collisions based on type if (self.projectileType === 'fireBall') { self.handleFireBallCollisions(); } else { self.handleSingleTargetCollision(); } }; self.handleSingleTargetCollision = function () { if (self.targetEnemy && self.targetEnemy.parent && self.targetEnemy !== wizard) { if (!self.lastIntersecting.target) { self.lastIntersecting.target = false; } var currentIntersecting = self.intersects(self.targetEnemy); if (!self.lastIntersecting.target && currentIntersecting) { var damage = config.damage; if (wizard && wizard.spellPowerLevel && wizard.spellPowerLevel > 0) { if (self.targetEnemy && self.targetEnemy.ogreFrames && enemyKillCounter < 35) { damage = self.targetEnemy.health + 1; } else { damage = 200; } } // Removed combo damage logic self.targetEnemy.takeDamage(damage); self.removeFromGame(); return; } self.lastIntersecting.target = currentIntersecting; } else { self.removeFromGame(); } }; self.handleFireBallCollisions = function () { var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses); for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[i]; if (enemy === wizard) { continue; } if (!self.lastIntersecting[i]) { self.lastIntersecting[i] = false; } var currentIntersecting = self.intersects(enemy); if (!self.lastIntersecting[i] && currentIntersecting) { enemy.takeDamage(config.damage); LK.effects.flashObject(enemy, 0xFF4500, 400); // Create fire explosion effect var fireEffect = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y, scaleX: 3, scaleY: 3 })); fireEffect.tint = 0xFF6600; fireEffect.alpha = 0.8; tween(fireEffect, { scaleX: 6, scaleY: 6, alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { fireEffect.destroy(); } }); // Fire particles for (var fireIdx = 0; fireIdx < 6; fireIdx++) { var fireParticle = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y, scaleX: 0.6, scaleY: 0.6 })); fireParticle.tint = 0xFF4500; fireParticle.alpha = 0.9; var fireAngle = fireIdx / 6 * Math.PI * 2; var fireSpeed = 50; var fireTargetX = enemy.x + Math.cos(fireAngle) * fireSpeed; var fireTargetY = enemy.y + Math.sin(fireAngle) * fireSpeed; tween(fireParticle, { x: fireTargetX, y: fireTargetY, scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { fireParticle.destroy(); } }); } self.removeFromGame(); return; } self.lastIntersecting[i] = currentIntersecting; } }; self.removeFromGame = function () { // Clean up trail segments for (var i = 0; i < self.trailSegments.length; i++) { var segment = self.trailSegments[i]; if (segment && segment.parent) { segment.destroy(); } } self.trailSegments = []; // Remove from projectiles array for (var i = projectiles.length - 1; i >= 0; i--) { if (projectiles[i] === self) { projectiles.splice(i, 1); break; } } self.destroy(); }; return self; }); var UpgradeMenu = Container.expand(function () { var self = Container.call(this); // Set high z-index to ensure menu appears above all game elements self.zIndex = 1000; // Cooldown system for upgrade menu self.cooldownDuration = 120; // 2 seconds at 60fps self.cooldownTimer = 0; // Background removed - no overlay needed // Title text var titleText = new Text2('UPGRADES', { size: 120, fill: 0xFFD700, font: "monospace" }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 1050; self.addChild(titleText); // Initialize upgrade cost self.upgradeCost = 5; // Store references to upgrade text elements for updates self.upgradeTextElements = []; // Use unified upgrade configuration from GAME_CONFIG var UPGRADE_CONFIG = GAME_CONFIG.upgrades; // Convert to array format for compatibility var upgradeOptions = Object.keys(UPGRADE_CONFIG).map(function (key) { var config = UPGRADE_CONFIG[key]; return { name: config.name, description: config.baseDesc, key: key }; }); // Select 3 alternating upgrades from the available options var selectedUpgrades = []; var availableIndices = []; for (var i = 0; i < upgradeOptions.length; i++) { availableIndices.push(i); } // Track which upgrades were shown last time if (!self.lastShownUpgrades) { self.lastShownUpgrades = []; } // Remove previously shown upgrades from available options if possible if (self.lastShownUpgrades.length > 0 && availableIndices.length > 3) { for (var lastIdx = 0; lastIdx < self.lastShownUpgrades.length; lastIdx++) { var indexToRemove = availableIndices.indexOf(self.lastShownUpgrades[lastIdx]); if (indexToRemove !== -1) { availableIndices.splice(indexToRemove, 1); } } } // If we don't have enough unique options, add back some previously shown ones if (availableIndices.length < 3) { availableIndices = []; for (var i = 0; i < upgradeOptions.length; i++) { availableIndices.push(i); } } // Randomly select 3 unique upgrades from available options for (var selection = 0; selection < 3; selection++) { var randomIndex = Math.floor(Math.random() * availableIndices.length); selectedUpgrades.push(availableIndices[randomIndex]); availableIndices.splice(randomIndex, 1); } // Store current selection for next time self.lastShownUpgrades = selectedUpgrades.slice(); // Copy array self.selectedUpgradeIndices = selectedUpgrades; // Store for reference // Create upgrade buttons in single row layout for 3 upgrades for (var i = 0; i < selectedUpgrades.length; i++) { var upgradeIndex = selectedUpgrades[i]; var upgrade = upgradeOptions[upgradeIndex]; // Calculate position in single row, 3-column grid var xPos = 2048 / 2 - 500 + i * 500; // Space 3 upgrades across screen width with more separation var yPos = 1400; // Single row positioned lower // Upgrade button background var upgradeBtn = self.attachAsset('pathSelector', { anchorX: 0.5, anchorY: 0.5, x: xPos, y: yPos, scaleX: 6, scaleY: 4.5 }); upgradeBtn.upgradeIndex = upgradeIndex; upgradeBtn.displayIndex = i; // Store display index for touch detection upgradeBtn.tint = 0x8B008B; // Upgrade text var upgradeText = new Text2(upgrade.name + '\n' + upgrade.description + '\nCost: ' + self.upgradeCost + ' coins', { size: 40, fill: 0xFFFFFF, font: "monospace" }); upgradeText.anchor.set(0.5, 0.5); upgradeText.x = xPos; upgradeText.y = yPos; upgradeText.upgradeIndex = upgradeIndex; // Store actual upgrade index for reference upgradeText.displayIndex = i; // Store display index for touch detection upgradeText.upgradeName = upgrade.name; upgradeText.upgradeDescription = upgrade.description; self.addChild(upgradeText); self.upgradeTextElements.push(upgradeText); // Store reference for updates } // Close button var closeBtn = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1900, scaleX: 2, scaleY: 2 }); closeBtn.tint = 0xFF0000; var closeText = new Text2('CLOSE', { size: 80, fill: 0xFFFFFF, font: "monospace" }); closeText.anchor.set(0.5, 0.5); closeText.x = 2048 / 2; closeText.y = 1900; self.addChild(closeText); // Method to refresh upgrade selection for alternating self.refreshUpgradeSelection = function () { // Convert UPGRADE_CONFIG to array format var upgradeOptions = Object.keys(UPGRADE_CONFIG).map(function (key) { var config = UPGRADE_CONFIG[key]; return { name: config.name, key: key, baseDesc: config.baseDesc, improvedDesc: config.improvedDesc, index: config.index }; }); // Select new alternating upgrades using existing logic var selectedUpgrades = []; var availableIndices = []; for (var i = 0; i < upgradeOptions.length; i++) { availableIndices.push(i); } // Remove previously shown upgrades from available options if possible if (self.lastShownUpgrades && self.lastShownUpgrades.length > 0 && availableIndices.length > 3) { for (var lastIdx = 0; lastIdx < self.lastShownUpgrades.length; lastIdx++) { var indexToRemove = availableIndices.indexOf(self.lastShownUpgrades[lastIdx]); if (indexToRemove !== -1) { availableIndices.splice(indexToRemove, 1); } } } // If we don't have enough unique options, add back some previously shown ones if (availableIndices.length < 3) { availableIndices = []; for (var i = 0; i < upgradeOptions.length; i++) { availableIndices.push(i); } } // Select 3 unique upgrades from available options for (var selection = 0; selection < 3; selection++) { var randomIndex = Math.floor(Math.random() * availableIndices.length); selectedUpgrades.push(availableIndices[randomIndex]); availableIndices.splice(randomIndex, 1); } // Store current selection for next time self.lastShownUpgrades = selectedUpgrades.slice(); self.selectedUpgradeIndices = selectedUpgrades; // Update upgrade text elements with new selections for (var i = 0; i < self.upgradeTextElements.length && i < selectedUpgrades.length; i++) { var upgradeIndex = selectedUpgrades[i]; var upgrade = upgradeOptions[upgradeIndex]; var textElement = self.upgradeTextElements[i]; // Update the text element properties textElement.upgradeIndex = upgradeIndex; textElement.displayIndex = i; textElement.upgradeName = upgrade.name; textElement.upgradeDescription = upgrade.baseDesc; } }; // Method to update upgrade text with new costs and show improved versions self.updateUpgradeCosts = function () { // First refresh the upgrade selection to ensure alternating upgrades self.refreshUpgradeSelection(); // Randomly select one upgrade to have modified cost (20% chance) var hasModifiedCost = Math.random() < 0.20; var modifiedCostIndex = hasModifiedCost ? Math.floor(Math.random() * 3) : -1; for (var i = 0; i < self.upgradeTextElements.length; i++) { var textElement = self.upgradeTextElements[i]; var upgradeIndex = textElement.upgradeIndex; var upgradeKey = Object.keys(UPGRADE_CONFIG)[upgradeIndex]; var config = UPGRADE_CONFIG[upgradeKey]; var currentLevel = upgradeLevels[upgradeKey]; // Calculate cost for this upgrade var upgradeCost = self.upgradeCost; if (i === modifiedCostIndex) { upgradeCost = Math.floor(self.upgradeCost * 0.95); // 5% decrease, rounded down } // Show improved version if already purchased var description = currentLevel > 0 ? config.improvedDesc : config.baseDesc; var levelText = currentLevel > 0 ? ' (LV' + (currentLevel + 1) + ')' : ''; textElement.setText(config.name + levelText + '\n' + description + '\nCost: ' + upgradeCost + ' coins'); textElement.upgradeName = config.name; textElement.upgradeDescription = description; textElement.actualCost = upgradeCost; // Store the actual cost for purchase logic } }; // Update method for cooldown timer self.update = function () { if (self.cooldownTimer > 0) { self.cooldownTimer--; // Apply visual feedback during cooldown var cooldownAlpha = 0.3 + self.cooldownTimer / self.cooldownDuration * 0.7; self.alpha = cooldownAlpha; } else { self.alpha = 1.0; // Full opacity when cooldown is over } }; // Handle upgrade purchases self.down = function (x, y, obj) { // Check if cooldown is active if (self.cooldownTimer > 0) { // Flash red to indicate cooldown is active LK.effects.flashScreen(0xFF0000, 200); return; // Exit early if cooldown is active } // Determine which upgrade was clicked based on 3-upgrade layout var upgradeIndex = -1; // Check which upgrade cell was clicked (single row, 3 columns) for (var i = 0; i < 3; i++) { var cellX = 2048 / 2 - 500 + i * 500; // Calculate cell center X for 3-upgrade layout with more separation var cellY = 1400; // Single row Y position var cellWidth = 400; // Touch area width - increased to match new spacing var cellHeight = 250; // Touch area height // Check if click is within this cell if (x >= cellX - cellWidth / 2 && x <= cellX + cellWidth / 2 && y >= cellY - cellHeight / 2 && y <= cellY + cellHeight / 2) { upgradeIndex = self.selectedUpgradeIndices[i]; // Get actual upgrade index from selected list break; } } // Check if upgrade button was clicked if (upgradeIndex !== -1) { // Close menu immediately when any upgrade option is selected self.closeMenu(); // Get the actual cost from the text element var actualCost = self.upgradeTextElements[0].actualCost || self.upgradeCost || 5; // Default fallback for (var textIdx = 0; textIdx < self.upgradeTextElements.length; textIdx++) { if (self.upgradeTextElements[textIdx].displayIndex === Math.floor(upgradeIndex / 1)) { actualCost = self.upgradeTextElements[textIdx].actualCost || self.upgradeCost || 5; break; } } // Find the correct text element by matching the upgrade selection for (var cellIdx = 0; cellIdx < 3; cellIdx++) { var cellX = 2048 / 2 - 500 + cellIdx * 500; var cellY = 1400; var cellWidth = 400; var cellHeight = 250; if (x >= cellX - cellWidth / 2 && x <= cellX + cellWidth / 2 && y >= cellY - cellHeight / 2 && y <= cellY + cellHeight / 2) { actualCost = self.upgradeTextElements[cellIdx].actualCost || self.upgradeCost || 5; break; } } if (coinCounter >= actualCost) { var applyUpgradeEffect = function applyUpgradeEffect(upgradeKey, config) { var currentLevel = upgradeLevels[upgradeKey] || 0; var effectFunction = UPGRADE_EFFECTS[upgradeKey]; if (effectFunction) { effectFunction(currentLevel + 1); } }; var createUpgradeVisualEffect = function createUpgradeVisualEffect(upgradeType, color) { LK.effects.flashObject(wizard, color, 500); }; var animateShieldActivation = function animateShieldActivation() { for (var shieldIdx = 0; shieldIdx < 10; shieldIdx++) { var shieldParticle = game.addChild(LK.getAsset('shield', { anchorX: 0.5, anchorY: 0.5, x: wizard.x + (Math.random() - 0.5) * 200, y: wizard.y + (Math.random() - 0.5) * 200, scaleX: 0.5, scaleY: 0.5 })); shieldParticle.tint = 0x00BFFF; shieldParticle.alpha = 0.8; tween(shieldParticle, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 500 + Math.random() * 300, easing: tween.easeOut, onFinish: function onFinish() { shieldParticle.destroy(); } }); } tween(wizard.shieldGraphics, { scaleX: 5, scaleY: 5, alpha: 1.0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { tween(wizard.shieldGraphics, { scaleX: 3, scaleY: 3, alpha: 0.7 }, { duration: 300, easing: tween.easeIn }); } }); }; // Update medal statistics coinCounter -= actualCost; coinText.setText('Coins: ' + coinCounter); // Apply upgrade using unified system var upgradeKey = Object.keys(UPGRADE_CONFIG)[upgradeIndex]; var config = UPGRADE_CONFIG[upgradeKey]; // Apply upgrade effect applyUpgradeEffect(upgradeKey, config); // Visual feedback - flash the upgrade area green LK.effects.flashScreen(0x00FF00, 300); // Visual effects for upgrades createUpgradeVisualEffect(upgradeKey, 0x00FF00); // Add visual effects based on upgrade type if (upgradeKey === 'shield') { createUpgradeVisualEffect('shield', 0x00BFFF); animateShieldActivation(); } else if (upgradeKey === 'healthBoost') { createUpgradeVisualEffect('healthBoost', 0x00FF00); } else if (upgradeKey === 'lifeDrain') { createUpgradeVisualEffect('lifeDrain', 0xFF69B4); } else if (upgradeKey === 'energySphere') { createUpgradeVisualEffect('energySphere', 0x00FFFF); } else if (upgradeKey === 'forcePush') { createUpgradeVisualEffect('forcePush', 0xFFFF00); } else if (upgradeKey === 'spellPower') { createUpgradeVisualEffect('spellPower', 0xFF4500); } else if (upgradeKey === 'thorns') { createUpgradeVisualEffect('thorns', 0x8B4513); } else if (upgradeKey === 'fireBall') { createUpgradeVisualEffect('fireBall', 0xFF4500); } else if (upgradeKey === 'freezePulse') { createUpgradeVisualEffect('freezePulse', 0x87CEEB); } else if (upgradeKey === 'orbs') { createUpgradeVisualEffect('orbs', 0xFFD700); } } else { // Not enough coins - flash red LK.effects.flashScreen(0xFF0000, 300); } } else if (y >= 1750 && y <= 2050) { // Close button area clicked self.closeMenu(); } }; self.closeMenu = function () { self.visible = false; gameStarted = true; // Resume appropriate music based on game progress if (enemyKillCounter >= 25) { LK.playMusic('mysticalAmbient', { volume: 0.6, fade: { start: 0, end: 0.6, duration: 1000 } }); } else if (enemyKillCounter >= 10) { LK.playMusic('epicBattle', { volume: 0.8, fade: { start: 0, end: 0.8, duration: 1000 } }); } else { LK.playMusic('medievalTheme', { volume: 0.7, fade: { start: 0, end: 0.7, duration: 1000 } }); } }; return self; }); 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.experience = 0; self.health = 100; self.maxHealth = 100; self.spellPowerLevel = 0; // Track spell power upgrades self.shieldActive = false; // Track shield status self.orbLevel = 0; // Track orb upgrades self.orbs = []; // Array to store orb instances // 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; } // Pause all upgrades when upgrade menu is visible if (upgradeMenu && upgradeMenu.visible) { 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; } // Force Push upgrade - push all enemies backward every 4 seconds if (self.forcePushLevel && self.forcePushLevel > 0) { if (!self.forcePushTimer) { self.forcePushTimer = 0; } self.forcePushTimer++; if (self.forcePushTimer >= 240) { // 4 seconds at 60fps self.forcePushTimer = 0; self.activateForcePush(); } } // Freeze Pulse upgrade - freeze all enemies every 4 seconds if (self.freezePulseLevel && self.freezePulseLevel > 0) { if (!self.freezePulseTimer) { self.freezePulseTimer = 0; } self.freezePulseTimer++; if (self.freezePulseTimer >= 240) { // 4 seconds at 60fps self.freezePulseTimer = 0; self.activateFreezePulse(); } } // Fire Ball upgrade - launch fire ball with improved timing if (self.fireBallLevel && self.fireBallLevel > 0) { if (!self.fireBallTimer) { self.fireBallTimer = 0; } self.fireBallTimer++; var fireBallInterval = upgradeLevels.fireBall > 1 ? 120 : 180; // 2s vs 3s if (self.fireBallTimer >= fireBallInterval) { self.fireBallTimer = 0; self.launchFireBall(); } } // Thorns upgrade - spawn spikes along all paths every 3 seconds if (self.thornsLevel && self.thornsLevel > 0) { if (!self.thornsTimer) { self.thornsTimer = 0; } self.thornsTimer++; if (self.thornsTimer >= 180) { // Always 3 seconds (180 ticks at 60fps) self.thornsTimer = 0; self.activateThorns(); } } // Animation system - cycle through wizard frames 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 new 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 without spell power upgrades 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 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 LK.effects.flashObject(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 LK.effects.flashObject(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; } self.health -= damage; 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 = enemies.concat(ogres).concat(knights).concat(miniBosses); for (var enemyIdx = allEnemies.length - 1; enemyIdx >= 0; enemyIdx--) { var enemy = allEnemies[enemyIdx]; // Create destruction effect for each enemy LK.effects.flashObject(enemy, 0xFFD700, 500); // Create golden explosion particles for (var particleIdx = 0; particleIdx < 8; particleIdx++) { var destructionParticle = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y, scaleX: 0.5, scaleY: 0.5 })); destructionParticle.tint = 0xFFD700; destructionParticle.alpha = 0.9; // Random explosion direction var explosionAngle = particleIdx / 8 * Math.PI * 2; var explosionSpeed = 60 + Math.random() * 40; var explosionTargetX = enemy.x + Math.cos(explosionAngle) * explosionSpeed; var explosionTargetY = enemy.y + Math.sin(explosionAngle) * explosionSpeed; // Animate explosion particle tween(destructionParticle, { x: explosionTargetX, y: explosionTargetY, scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 400 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { destructionParticle.destroy(); } }); } // Kill ALL enemies instantly by calling die() method enemy.die(); } // Visual effects for revival LK.effects.flashScreen(0x00FF00, 1500); // Green flash for revival LK.effects.flashObject(self, 0xFFD700, 1000); // Golden flash on wizard // Reduced revival particles for better performance for (var reviveIdx = 0; reviveIdx < 8; reviveIdx++) { var reviveParticle = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: self.x + (Math.random() - 0.5) * 200, y: self.y + (Math.random() - 0.5) * 200, scaleX: 1.0, scaleY: 1.0 })); reviveParticle.tint = 0x00FF00; reviveParticle.alpha = 0.9; // Animate revival particles toward wizard tween(reviveParticle, { x: self.x, y: self.y, scaleX: 0.3, scaleY: 0.3, alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { reviveParticle.destroy(); } }); } // Create healing aura effect var healingAura = game.addChild(LK.getAsset('shield', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 1, scaleY: 1 })); healingAura.tint = 0x00FF00; healingAura.alpha = 0.8; // Animate healing aura expansion tween(healingAura, { scaleX: 8, scaleY: 8, alpha: 0 }, { duration: 1200, easing: tween.easeOut, onFinish: function onFinish() { healingAura.destroy(); } }); // 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 }); } }); // Flash red when hit LK.effects.flashObject(self, 0xFF0000, 200); }; self.activateForcePush = function () { // Visual effect for force push activation LK.effects.flashScreen(0x8A2BE2, 300); // Purple flash LK.effects.flashObject(self, 0x8A2BE2, 500); // Purple flash on wizard // Push back all enemies with improved effects var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses); for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[i]; // Calculate direction from wizard to enemy var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // Improved force push: stronger push and damage var pushDistance = upgradeLevels.forcePush > 1 ? 300 : 200; // Stronger push var pushX = dx / distance * pushDistance; var pushY = dy / distance * pushDistance; // Calculate new position var newX = enemy.x + pushX; var newY = enemy.y + pushY; // Ensure enemies don't go off screen newX = Math.max(50, Math.min(1998, newX)); newY = Math.max(-100, Math.min(2732, newY)); // Animate the push effect tween(enemy, { x: newX, y: newY }, { duration: 300, easing: tween.easeOut }); // Improved force push: deal damage if (upgradeLevels.forcePush > 1) { enemy.takeDamage(50); } // Visual effect on each enemy LK.effects.flashObject(enemy, 0x8A2BE2, 200); } } }; self.activateFreezePulse = function () { // Visual effect for freeze pulse activation LK.effects.flashScreen(0x87CEEB, 500); // Light blue flash LK.effects.flashObject(self, 0x87CEEB, 700); // Light blue flash on wizard // Play freeze sound effect LK.getSound('iceFreeze').play(); // Freeze all enemies with improved effects var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses); for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[i]; // Improved freeze: longer duration and damage var freezeDuration = upgradeLevels.freezePulse > 1 ? 120 : 60; // 2s vs 1s enemy.frozen = true; enemy.frozenTimer = freezeDuration; // Improved freeze: deal damage if (upgradeLevels.freezePulse > 1) { enemy.takeDamage(30); } // Visual freeze effect - tint enemy light blue tween(enemy, { tint: 0x87CEEB }, { duration: 100, easing: tween.easeOut }); // Reduced ice crystal particles for better performance if (i % 2 === 0) { // Only create effects for every other enemy for (var iceIdx = 0; iceIdx < 3; iceIdx++) { var iceCrystal = game.addChild(LK.getAsset('spell', { anchorX: 0.5, anchorY: 0.5, x: enemy.x + (Math.random() - 0.5) * 60, y: enemy.y + (Math.random() - 0.5) * 60, scaleX: 1.0, scaleY: 1.0 })); iceCrystal.tint = 0x87CEEB; iceCrystal.alpha = 0.9; // Create floating ice effect tween(iceCrystal, { y: iceCrystal.y - 30, scaleX: 0.3, scaleY: 0.3, alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { iceCrystal.destroy(); } }); } } // Remove freeze tint after frozen state ends var visualDuration = upgradeLevels.freezePulse > 1 ? 2000 : 1000; tween({}, {}, { duration: visualDuration, onFinish: function onFinish() { if (enemy && enemy.parent) { // Remove freeze tint after frozen effect ends tween(enemy, { tint: 0xFFFFFF }, { duration: 200, easing: tween.easeIn }); } } }); } }; self.activateThorns = function () { // Visual effect for thorns activation LK.effects.flashScreen(0x8B4513, 300); // Brown flash LK.effects.flashObject(self, 0x8B4513, 500); // Brown flash on wizard // Find the closest enemy to the wizard var closestEnemy = null; var closestDistance = Infinity; var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses); 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; } } // Only create spikes if there is a closest enemy if (!closestEnemy) { LK.getSound('spellCast').play(); return; } // Create spikes only along the closest enemy's path var pathIdx = closestEnemy.pathIndex; var pathAngle = pathAngles[pathIdx]; // Calculate spawn position for this path (same as enemy spawning) var spawnX, spawnY; if (pathIdx === 0) { // Center path - spawn at top edge spawnX = 2048 / 2; spawnY = -100; } else if (pathIdx === 1) { // Path 2 - spawn at top right edge spawnX = 2048 + 50; spawnY = -50; } else if (pathIdx === 2) { // Path 3 - spawn at top left edge spawnX = -50; spawnY = -50; } else if (pathIdx === 3) { // Path 4 - spawn at left edge spawnX = -100; spawnY = 2732 / 2 + 400; } else if (pathIdx === 4) { // Path 5 - spawn at right edge spawnX = 2048 + 100; spawnY = 2732 / 2 + 400; } // Calculate wizard position (same as enemy targeting) var wizardX = self.x; var wizardY = self.y; // Calculate path distance and divide into 3 sections with gaps var pathDistance = Math.sqrt((spawnX - wizardX) * (spawnX - wizardX) + (spawnY - wizardY) * (spawnY - wizardY)); var sectionLength = pathDistance / 5; // Each section is 1/5 of total path var gapLength = pathDistance / 10; // Gaps are 1/10 of total path // Define 3 sections along the path with gaps between them var sections = [{ start: 0.1, end: 0.3 }, // First section: 10% to 30% along path { start: 0.45, end: 0.65 }, // Second section: 45% to 65% along path { start: 0.8, end: 1.0 } // Third section: 80% to 100% along path ]; // Create spikes in reverse sequential order: last section, then middle, then first var sectionOrder = [2, 1, 0]; // Create sections starting from farthest outward toward wizard for (var orderIdx = 0; orderIdx < sectionOrder.length; orderIdx++) { var sectionIdx = sectionOrder[orderIdx]; var section = sections[sectionIdx]; var sectionStartDistance = pathDistance * section.start; var sectionEndDistance = pathDistance * section.end; var spikeSpacing = 150; // Distance between spikes within each section // Calculate number of spikes in this section var sectionLength = sectionEndDistance - sectionStartDistance; var numSpikesInSection = Math.floor(sectionLength / spikeSpacing); // Calculate delay for sequential appearance var baseDelay = orderIdx * 300; // 300ms delay between sections // Create spikes within this section with sequential timing for (var s = 0; s < numSpikesInSection; s++) { var spikeDistanceInSection = s * spikeSpacing + spikeSpacing / 2; var totalSpikeDistance = sectionStartDistance + spikeDistanceInSection; var progress = totalSpikeDistance / pathDistance; var spikeX = spawnX + (wizardX - spawnX) * progress; var spikeY = spawnY + (wizardY - spawnY) * progress; // Only create spike if position is within game bounds if (spikeX >= 0 && spikeX <= 2048 && spikeY >= 0 && spikeY <= 2732) { // Create spike with delay (function (delayTime, spikeX, spikeY, pathIdx) { tween({}, {}, { duration: delayTime, onFinish: function onFinish() { var spike = game.addChild(LK.getAsset('spell', { anchorX: 0.5, anchorY: 0.5, x: spikeX, y: spikeY, scaleX: 0.1, scaleY: 0.1 })); spike.tint = 0x8B4513; // Brown color for thorns spike.pathIndex = pathIdx; // Initialize hit tracking for this spike spike.hitEnemies = []; // Set spike to be visible immediately spike.alpha = 1.0; spike.scaleX = 1.5; spike.scaleY = 1.5; // Animate spike emerging from ground tween(spike, { scaleX: 2.5, scaleY: 2.5, alpha: 1.0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Spike stays for a moment then disappears tween(spike, { scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { spike.destroy(); } }); } }); } }); })(baseDelay + s * 50, spikeX, spikeY, pathIdx); // 50ms delay between spikes in same section } } } LK.getSound('spellCast').play(); }; self.launchFireBall = function () { // Visual effect for fire ball launch LK.effects.flashScreen(0xFF4500, 300); // Orange flash LK.effects.flashObject(self, 0xFF4500, 500); // Orange flash on wizard // Create fire ball projectile at wizard position var fireBall = game.addChild(new UnifiedProjectile('fireBall')); fireBall.x = self.x; fireBall.y = self.y; // Find closest enemy to target var closestEnemy = null; var closestDistance = Infinity; var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses); 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; } } // Set fire ball direction toward closest enemy or default direction if (closestEnemy) { var dx = closestEnemy.x - self.x; var dy = closestEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { fireBall.direction.x = dx / distance; fireBall.direction.y = dy / distance; } } else { // Default direction upward if no enemies fireBall.direction.x = 0; fireBall.direction.y = -1; } LK.getSound('spellCast').play(); LK.getSound('fireWhoosh').play(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 // Black background for pixel art }); /**** * Game Code ****/ // Unified game configuration object var GAME_CONFIG = { // Enemy configurations enemies: { skeleton: { assetPrefix: 'esqueleto', scale: 3.0, baseHealth: 100, baseSpeed: 3, animationSpeed: 15, vibration: 50, damageTextSize: 120, damageTextColor: 0xFF4444, impactScale: 1.5, deathRotation: Math.PI * 0.5, scoreReward: 10, startThreshold: 0, coinReward: 1, damage: 20, spawnInterval: function spawnInterval(difficulty, difficultyLevel) { var baseRate = difficulty === 'FACIL' ? 120 : difficulty === 'DIFICIL' ? 90 : 90; return Math.max(difficulty === 'FACIL' ? 60 : 40, baseRate - difficultyLevel * (difficulty === 'DIFICIL' ? 4 : 6)); }, arrayName: 'enemies' }, ogre: { assetPrefix: 'ogre', scale: 3.0, baseHealth: 200, baseSpeed: 2.5, animationSpeed: 20, vibration: 75, damageTextSize: 130, damageTextColor: 0xFF6600, impactScale: 2.0, deathRotation: Math.PI * 1.2, scoreReward: 15, startThreshold: 900, coinReward: 1, damage: 30, spawnInterval: function spawnInterval(difficulty) { return difficulty === 'FACIL' ? 240 : difficulty === 'DIFICIL' ? 120 : 180; }, arrayName: 'ogres' }, knight: { assetPrefix: 'knight', scale: 3.0, baseHealth: 300, baseSpeed: 2, animationSpeed: 22, vibration: 100, damageTextSize: 140, damageTextColor: 0xFFD700, impactScale: 1.8, deathRotation: Math.PI * 0.8, scoreReward: 20, startThreshold: function startThreshold(difficulty) { return difficulty === 'FACIL' ? 40 : difficulty === 'DIFICIL' ? 20 : 30; }, coinReward: 1, damage: 40, spawnInterval: function spawnInterval(difficulty) { return difficulty === 'FACIL' ? 420 : difficulty === 'DIFICIL' ? 240 : 300; }, arrayName: 'knights' }, miniBoss: { assetPrefix: 'knight', scale: 5.0, baseHealth: 3000, baseSpeed: 4, animationSpeed: 12, vibration: [100, 50, 100], damageTextSize: 160, damageTextColor: 0xFF0000, impactScale: 2.5, deathRotation: Math.PI * 2, scoreReward: 100, tint: 0x8B0000, startThreshold: 80, endThreshold: 85, spawnChance: 0.02, coinReward: 5, damage: 75, arrayName: 'miniBosses' } }, // Upgrade configurations upgrades: { shield: { name: 'SHIELD', baseDesc: 'Block 1 attack', improvedDesc: 'Block 2 attacks + regen', baseCost: 5, index: 0 }, healthBoost: { name: 'HEALTH BOOST', baseDesc: 'More health', improvedDesc: 'Much more health', baseCost: 5, index: 1 }, lifeDrain: { name: 'LIFE DRAIN', baseDesc: '20% chance +20 HP', improvedDesc: '20% chance +20 HP', baseCost: 5, index: 2 }, energySphere: { name: 'ENERGY SPHERE', baseDesc: 'Auto-attack every 3s', improvedDesc: 'Auto-attack every 2s', baseCost: 5, index: 3 }, forcePush: { name: 'FORCE PUSH', baseDesc: 'Push enemies back', improvedDesc: 'Stronger push + damage', baseCost: 5, index: 4 }, spellPower: { name: 'SPELL POWER', baseDesc: 'Double damage', improvedDesc: 'Triple damage', baseCost: 5, index: 5 }, thorns: { name: 'THORNS', baseDesc: 'Spikes on all paths', improvedDesc: 'Stronger spikes + faster', baseCost: 5, index: 6 }, fireBall: { name: 'FIRE BALL', baseDesc: 'Launch fire ball every 3s', improvedDesc: 'Launch fire ball every 2s', baseCost: 5, index: 7 }, freezePulse: { name: 'FREEZE PULSE', baseDesc: 'Freeze all enemies', improvedDesc: 'Freeze longer + damage', baseCost: 5, index: 8 }, orbs: { name: 'ORBS', baseDesc: 'Orbs rotate around player', improvedDesc: 'More orbs + faster rotation', baseCost: 5, index: 9 } }, // Path configurations paths: { count: 5, angles: [-Math.PI / 2, -Math.PI / 3, -2 * Math.PI / 3, Math.PI / 6, 5 * Math.PI / 6], spawnPositions: [{ x: 2048 / 2, y: -100 }, // Center path { x: 2048 + 50, y: -50 }, // Top right { x: -50, y: -50 }, // Top left { x: -100, y: 2732 / 2 + 400 }, // Left edge { x: 2048 + 100, y: 2732 / 2 + 400 } // Right edge ] }, // UI configurations ui: { healthBar: { x: 120, y: 20 }, coinText: { x: 120, y: 90, size: 60 }, killText: { x: 120, y: 150, size: 60 }, healthText: { x: 120, y: 50, size: 50 }, tapText: { size: 100, yOffset: -200 } }, // Gameplay configurations gameplay: { maxEnemiesOnScreen: 15, pathCooldown: 300, upgradeMenuTriggers: [12, 35, 50, 70], baseUpgradeCost: 5, upgradeCostIncrease: 5, attackCooldown: 30, projectileSpeed: 50, wizardAnimationSpeed: 18, segmentSize: 120, difficultyScaling: { FACIL: { healthMult: 0.8, speedMult: 0.9, bonusCoins: true, healingChance: 0.15 }, NORMAL: { healthMult: 1.0, speedMult: 1.0, standardRewards: true }, DIFICIL: { healthMult: 1.2, speedMult: 1.1, eliteEnemies: true, reducedCoins: true } } }, // Projectile configurations projectiles: { projectile: { assetId: 'projectile', glowAsset: 'projectileGlow', scale: 1.5, speed: 50, damage: 100, tint: 0x44aaff, glowTint: 0x44aaff, hasTrail: false, hasRotation: true }, energyBeam: { assetId: 'projectileGlow', glowAsset: 'projectileGlow', scale: 1.0, speed: 60, damage: 100, tint: 0x00ffff, glowTint: 0x00ffff, hasTrail: false, hasRotation: true }, fireBall: { assetId: 'projectileGlow', glowAsset: 'projectileGlow', scale: 1.5, speed: 40, damage: 150, tint: 0xFF4500, glowTint: 0xFF6600, hasTrail: true, hasRotation: true, hasFlicker: true } } }; // Game state variables var gameStarted = false; var gameMenu; var upgradeMenu; var upgradeMenuShown = false; var secondUpgradeMenuShown = false; var thirdUpgradeMenuShown = false; var fourthUpgradeMenuShown = false; var selectedEnemy = null; // Track currently selected enemy for projectile targeting var energySphere = null; // Track energy sphere instance // Upgrade level tracking system var upgradeLevels = { shield: 0, healthBoost: 0, lifeDrain: 0, energySphere: 0, forcePush: 0, spellPower: 0, thorns: 0, fireBall: 0, freezePulse: 0, orbs: 0 }; // Upgrade effects system - moved to global scope var UPGRADE_EFFECTS = { shield: function shield(level) { upgradeLevels.shield++; if (level === 1) { wizard.shieldActive = true; wizard.maxShieldHits = 1; wizard.currentShieldHits = 0; } else { wizard.shieldActive = true; wizard.maxShieldHits = 2; wizard.currentShieldHits = 0; wizard.shieldRegen = true; } }, healthBoost: function healthBoost(level) { upgradeLevels.healthBoost++; var healthIncrease = 40; wizard.maxHealth += healthIncrease; wizard.health = Math.min(wizard.health + healthIncrease, wizard.maxHealth); updateHealthBar(); }, lifeDrain: function lifeDrain(level) { upgradeLevels.lifeDrain++; if (!wizard.lifeDrainLevel) wizard.lifeDrainLevel = 0; wizard.lifeDrainLevel = upgradeLevels.lifeDrain; }, energySphere: function energySphere(level) { upgradeLevels.energySphere++; if (level === 1 && !wizard.energySphere) { wizard.energySphere = game.addChild(new EnergyOrb()); } }, forcePush: function forcePush(level) { upgradeLevels.forcePush++; if (!wizard.forcePushLevel) { wizard.forcePushLevel = 0; wizard.forcePushTimer = 0; } wizard.forcePushLevel = upgradeLevels.forcePush; }, spellPower: function spellPower(level) { upgradeLevels.spellPower++; if (!wizard.spellPowerLevel) wizard.spellPowerLevel = 0; wizard.spellPowerLevel = upgradeLevels.spellPower; }, thorns: function thorns(level) { upgradeLevels.thorns++; if (!wizard.thornsLevel) { wizard.thornsLevel = 0; wizard.thornsTimer = 0; } wizard.thornsLevel = upgradeLevels.thorns; }, fireBall: function fireBall(level) { upgradeLevels.fireBall++; if (!wizard.fireBallLevel) { wizard.fireBallLevel = 0; wizard.fireBallTimer = 0; } wizard.fireBallLevel = upgradeLevels.fireBall; }, freezePulse: function freezePulse(level) { upgradeLevels.freezePulse++; if (!wizard.freezePulseLevel) { wizard.freezePulseLevel = 0; wizard.freezePulseTimer = 0; } wizard.freezePulseLevel = upgradeLevels.freezePulse; }, orbs: function orbs(level) { upgradeLevels.orbs++; if (!wizard.orbLevel) { wizard.orbLevel = 0; wizard.orbs = []; } wizard.orbLevel++; var newOrb = game.addChild(new Orb()); newOrb.orbitalAngle = wizard.orbs.length * (Math.PI * 2) / (wizard.orbLevel + 1); wizard.orbs.push(newOrb); for (var orbIdx = 0; orbIdx < wizard.orbs.length; orbIdx++) { wizard.orbs[orbIdx].orbitalAngle = orbIdx * (Math.PI * 2) / wizard.orbs.length; } } }; // Game arrays to track objects var enemies = []; var ogres = []; var knights = []; var miniBosses = []; var coins = []; var projectiles = []; // Removed combo system variables // 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 upgrade menu (initially hidden) upgradeMenu = game.addChild(new UpgradeMenu()); upgradeMenu.visible = false; // 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 = GAME_CONFIG.paths.spawnPositions; for (var p = 0; p < GAME_CONFIG.paths.count; p++) { var angle = GAME_CONFIG.paths.angles[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 = GAME_CONFIG.gameplay.segmentSize; 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 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: GAME_CONFIG.ui.coinText.size, fill: 0xFFD700, font: "monospace" }); coinText.anchor.set(0, 0); LK.gui.topLeft.addChild(coinText); coinText.x = GAME_CONFIG.ui.coinText.x; coinText.y = GAME_CONFIG.ui.coinText.y; coinText.visible = false; var killCountText = new Text2('Puntuacion: 0', { size: GAME_CONFIG.ui.killText.size, fill: 0xFF6B6B, font: "monospace" }); killCountText.anchor.set(0, 0); LK.gui.topLeft.addChild(killCountText); killCountText.x = GAME_CONFIG.ui.killText.x; killCountText.y = GAME_CONFIG.ui.killText.y; killCountText.visible = false; var tapText = new Text2('TAP ENEMIES TO ATTACK!', { size: GAME_CONFIG.ui.tapText.size, fill: 0xFF6B6B, font: "monospace" }); tapText.anchor.set(0.5, 0.5); LK.gui.center.addChild(tapText); tapText.y = GAME_CONFIG.ui.tapText.yOffset; tapText.visible = false; // Health bar UI var healthBarBg = LK.getAsset('healthBarBg', { anchorX: 0, anchorY: 0 }); LK.gui.topLeft.addChild(healthBarBg); healthBarBg.x = GAME_CONFIG.ui.healthBar.x; healthBarBg.y = GAME_CONFIG.ui.healthBar.y; healthBarBg.visible = false; var healthBar = LK.getAsset('healthBar', { anchorX: 0, anchorY: 0 }); LK.gui.topLeft.addChild(healthBar); healthBar.x = GAME_CONFIG.ui.healthBar.x; healthBar.y = GAME_CONFIG.ui.healthBar.y; healthBar.visible = false; var healthText = new Text2('Health: 100/100', { size: GAME_CONFIG.ui.healthText.size, fill: 0xFFFFFF, font: "monospace" }); healthText.anchor.set(0, 0); LK.gui.topLeft.addChild(healthText); healthText.x = GAME_CONFIG.ui.healthText.x; healthText.y = GAME_CONFIG.ui.healthText.y; healthText.visible = false; 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 // Enemy spawn configuration now uses GAME_CONFIG.enemies var ENEMY_SPAWN_CONFIG = { skeleton: { spawnInterval: GAME_CONFIG.enemies.skeleton.spawnInterval, checkSpawn: function checkSpawn(timer, interval) { return timer >= interval; }, arrayName: GAME_CONFIG.enemies.skeleton.arrayName }, ogre: { spawnInterval: GAME_CONFIG.enemies.ogre.spawnInterval, checkSpawn: function checkSpawn(timer, interval) { return timer >= interval && enemyKillCounter >= 15; }, arrayName: GAME_CONFIG.enemies.ogre.arrayName }, knight: { spawnInterval: GAME_CONFIG.enemies.knight.spawnInterval, startThreshold: GAME_CONFIG.enemies.knight.startThreshold, checkSpawn: function checkSpawn(timer, interval, threshold) { return timer >= interval && enemyKillCounter >= threshold; }, arrayName: GAME_CONFIG.enemies.knight.arrayName }, miniBoss: { checkSpawn: function checkSpawn() { return enemyKillCounter >= 80 && enemyKillCounter <= 85 && miniBosses.length === 0 && Math.random() < 0.02; }, arrayName: GAME_CONFIG.enemies.miniBoss.arrayName } }; // Game input handling game.down = function (x, y, obj) { // Tap-to-attack is now handled directly by individual enemies // No need for path-based attacks since enemies handle their own tap events }; // Unified impact effect function for all enemy types function createImpactEffect(enemy, config) { // Use enemy's type config if no custom config provided if (!config) config = enemy.typeConfig || {}; // Create impact effect var impactEffect = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y, scaleX: 0.3, scaleY: 0.3 })); impactEffect.tint = 0xFFAA00; impactEffect.alpha = 1.0; tween(impactEffect, { scaleX: config.impactScale || 1.5, scaleY: config.impactScale || 1.5, alpha: 0 }, { duration: 450, easing: tween.easeOut, onFinish: function onFinish() { impactEffect.destroy(); } }); } // Unified death animation function for all enemy types function createEnemyDeathAnimation(enemy, enemyType, enemyArray) { enemy.animationState = 'dying'; enemy.currentFrame = 3; LK.getSound('painSound').play(); enemy.isDying = true; // Get type-specific configuration var deathRotation = Math.PI * 0.5; // Default rotation var numCoins = 1; // Default coin count if (enemyType === 'skeleton') { deathRotation = Math.PI * 0.5; numCoins = 1; } else if (enemyType === 'ogre') { deathRotation = Math.PI * 1.2; numCoins = 1; } else if (enemyType === 'knight') { deathRotation = Math.PI * 0.8; numCoins = 1; } else if (enemyType === 'miniBoss') { deathRotation = Math.PI * 2; numCoins = 5; // Clean up mini boss health bar 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(); } } // Unified death animation tween(enemy, { alpha: 0, scaleX: 0.3, scaleY: 0.3, rotation: deathRotation }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { // Handle coin drops for (var coinIdx = 0; coinIdx < numCoins; coinIdx++) { var coin = game.addChild(new Coin()); coin.x = enemy.x + (enemyType === 'miniBoss' ? (Math.random() - 0.5) * 200 : 0); coin.y = enemy.y - 50 + (enemyType === 'miniBoss' ? (Math.random() - 0.5) * 100 : 0); coin.isAnimating = true; 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 + (enemyType === 'miniBoss' ? coinIdx * 200 : 0), easing: tween.easeOut, onFinish: function onFinish() { var coinReward = enemyType === 'miniBoss' ? 10 : 1; var selectedDifficulty = storage.difficulty || 'NORMAL'; 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); if (selectedDifficulty === 'FACIL' && Math.random() < 0.15) { wizard.health = Math.min(wizard.health + 5, wizard.maxHealth); updateHealthBar(); LK.effects.flashObject(wizard, 0x00FF00, 200); } for (var i = coins.length - 1; i >= 0; i--) { if (coins[i] === coin) { coins.splice(i, 1); break; } } coin.destroy(); } }); } // Handle kill counter and progression var killIncrement = enemyType === 'miniBoss' ? 10 : 1; enemyKillCounter += killIncrement; killCountText.setText('Puntuacion: ' + enemyKillCounter); // Removed path combo system wizard.gainExperience(enemyType === 'miniBoss' ? 250 : 25); // Life drain if (wizard.lifeDrainLevel && wizard.lifeDrainLevel > 0) { if (Math.random() < 0.20) { wizard.health = Math.min(wizard.health + 20, wizard.maxHealth); updateHealthBar(); LK.effects.flashObject(wizard, 0x00FF00, 300); } } if (selectedEnemy === enemy) { selectedEnemy = null; } // Remove from appropriate array for (var i = enemyArray.length - 1; i >= 0; i--) { if (enemyArray[i] === enemy) { enemyArray.splice(i, 1); break; } } enemy.destroy(); LK.setScore(LK.getScore() + enemy.typeConfig.scoreReward); } }); } // 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 } }); } // Show upgrade menu after 12 enemies are killed if (enemyKillCounter >= 12 && !upgradeMenuShown) { upgradeMenuShown = true; // Increase upgrade costs by 5 coins when menu is shown if (!upgradeMenu.upgradeCost) { upgradeMenu.upgradeCost = 5; // Initial cost } upgradeMenu.upgradeCost += 5; // Increase by 5 coins each time // Update all upgrade text to show new costs upgradeMenu.updateUpgradeCosts(); upgradeMenu.visible = true; upgradeMenu.cooldownTimer = upgradeMenu.cooldownDuration; // Start 2-second cooldown gameStarted = false; // Pause game while menu is open // Keep music playing during upgrade menu // Clear all enemies on screen when upgrade menu is shown for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; enemies.splice(i, 1); enemy.destroy(); } // Clear all ogres on screen when upgrade menu is shown for (var i = ogres.length - 1; i >= 0; i--) { var ogre = ogres[i]; ogres.splice(i, 1); ogre.destroy(); } // Clear all knights on screen when upgrade menu is shown for (var i = knights.length - 1; i >= 0; i--) { var knight = knights[i]; knights.splice(i, 1); knight.destroy(); } } // Show upgrade menu again after 35 enemies are killed if (enemyKillCounter >= 35 && upgradeMenuShown && !secondUpgradeMenuShown) { secondUpgradeMenuShown = true; // Increase upgrade costs by 5 coins when menu is shown if (!upgradeMenu.upgradeCost) { upgradeMenu.upgradeCost = 5; // Initial cost } upgradeMenu.upgradeCost += 5; // Increase by 5 coins each time // Update all upgrade text to show new costs upgradeMenu.updateUpgradeCosts(); upgradeMenu.visible = true; upgradeMenu.cooldownTimer = upgradeMenu.cooldownDuration; // Start 2-second cooldown gameStarted = false; // Pause game while menu is open // Keep music playing during upgrade menu // Clear all enemies on screen when upgrade menu is shown for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; enemies.splice(i, 1); enemy.destroy(); } // Clear all ogres on screen when upgrade menu is shown for (var i = ogres.length - 1; i >= 0; i--) { var ogre = ogres[i]; ogres.splice(i, 1); ogre.destroy(); } // Clear all knights on screen when upgrade menu is shown for (var i = knights.length - 1; i >= 0; i--) { var knight = knights[i]; knights.splice(i, 1); knight.destroy(); } } // Show third upgrade menu after 50 enemies are killed if (enemyKillCounter >= 50 && secondUpgradeMenuShown && !thirdUpgradeMenuShown) { thirdUpgradeMenuShown = true; // Increase upgrade costs by 5 coins when menu is shown if (!upgradeMenu.upgradeCost) { upgradeMenu.upgradeCost = 5; // Initial cost } upgradeMenu.upgradeCost += 5; // Increase by 5 coins each time // Update all upgrade text to show new costs upgradeMenu.updateUpgradeCosts(); upgradeMenu.visible = true; upgradeMenu.cooldownTimer = upgradeMenu.cooldownDuration; // Start 2-second cooldown gameStarted = false; // Pause game while menu is open // Keep music playing during upgrade menu // Clear all enemies on screen when upgrade menu is shown for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; enemies.splice(i, 1); enemy.destroy(); } // Clear all ogres on screen when upgrade menu is shown for (var i = ogres.length - 1; i >= 0; i--) { var ogre = ogres[i]; ogres.splice(i, 1); ogre.destroy(); } // Clear all knights on screen when upgrade menu is shown for (var i = knights.length - 1; i >= 0; i--) { var knight = knights[i]; knights.splice(i, 1); knight.destroy(); } } // Show fourth upgrade menu after 70 enemies are killed if (enemyKillCounter >= 70 && thirdUpgradeMenuShown && !fourthUpgradeMenuShown) { fourthUpgradeMenuShown = true; // Increase upgrade costs by 5 coins when menu is shown if (!upgradeMenu.upgradeCost) { upgradeMenu.upgradeCost = 5; // Initial cost } upgradeMenu.upgradeCost += 5; // Increase by 5 coins each time // Update all upgrade text to show new costs upgradeMenu.updateUpgradeCosts(); upgradeMenu.visible = true; upgradeMenu.cooldownTimer = upgradeMenu.cooldownDuration; // Start 2-second cooldown gameStarted = false; // Pause game while menu is open // Keep music playing during upgrade menu // Clear all enemies on screen when upgrade menu is shown for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; enemies.splice(i, 1); enemy.destroy(); } // Clear all ogres on screen when upgrade menu is shown for (var i = ogres.length - 1; i >= 0; i--) { var ogre = ogres[i]; ogres.splice(i, 1); ogre.destroy(); } // Clear all knights on screen when upgrade menu is shown for (var i = knights.length - 1; i >= 0; i--) { var knight = knights[i]; knights.splice(i, 1); knight.destroy(); } } // 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 // Apply unique difficulty modifiers var currentSpawnRate, enemyHealthMultiplier, enemySpeedMultiplier; var specialMechanics = {}; if (selectedDifficulty === 'FACIL') { // EASY: Slower enemies, less health, longer spawn intervals currentSpawnRate = Math.max(60, 120 - difficultyLevel * 5); // Much slower spawning enemyHealthMultiplier = 1; // No health scaling over time enemySpeedMultiplier = 1 + difficultyLevel * 0.20; // 20% speed increase per level specialMechanics.bonusCoins = true; // 50% more coins specialMechanics.healingChance = 0.15; // 15% chance to heal 5 HP on enemy kill } else if (selectedDifficulty === 'NORMAL') { // NORMAL: Balanced progression currentSpawnRate = Math.max(40, 90 - difficultyLevel * 6); // Standard spawning enemyHealthMultiplier = 1; // No health scaling over time enemySpeedMultiplier = 1 + difficultyLevel * 0.30; // 30% speed increase per level specialMechanics.standardRewards = true; } else if (selectedDifficulty === 'DIFICIL') { // HARD: Faster enemies, more health, shorter spawn intervals, special enemy abilities currentSpawnRate = Math.max(20, 60 - difficultyLevel * 4); // Much faster spawning enemyHealthMultiplier = 1; // No health scaling over time enemySpeedMultiplier = 1 + difficultyLevel * 0.50; // 50% speed increase per level specialMechanics.eliteEnemies = true; // 20% chance for elite enemies with double stats specialMechanics.aggressiveAI = true; // Enemies move more directly toward wizard specialMechanics.reducedCoins = true; // 25% fewer coins } // Helper function to spawn enemy at position function spawnEnemyAtPath(enemyType, pathIndex, config) { var enemy = game.addChild(new Enemy(enemyType, config)); enemy.pathIndex = pathIndex; enemy.pathAngle = pathAngles[pathIndex]; // Calculate spawn position at screen edges if (pathIndex === 0) { enemy.x = 2048 / 2; enemy.y = -100; } else if (pathIndex === 1) { enemy.x = 2048 + 50; enemy.y = -50; } else if (pathIndex === 2) { enemy.x = -50; enemy.y = -50; } else if (pathIndex === 3) { enemy.x = -100; enemy.y = 2732 / 2 + 400; } else if (pathIndex === 4) { enemy.x = 2048 + 100; enemy.y = 2732 / 2 + 400; } enemy.x = Math.max(50, Math.min(1998, enemy.x)); enemy.y = Math.max(-200, Math.min(2732 + 100, enemy.y)); enemy.lastX = enemy.x; return enemy; } // Unified enemy spawning function function spawnEnemyByType(enemyType, difficulty, difficultyLevel, enemyHealthMultiplier, enemySpeedMultiplier) { var config = GAME_CONFIG.enemies[enemyType]; if (!config) return null; // Calculate stats based on difficulty var enemyConfig = { animationSpeed: config.animationSpeed }; if (difficulty === 'FACIL') { enemyConfig.health = Math.floor(config.baseHealth * 0.8 * enemyHealthMultiplier); enemyConfig.speed = config.baseSpeed * 0.9 * enemySpeedMultiplier; } else if (difficulty === 'NORMAL') { enemyConfig.health = Math.floor(config.baseHealth * enemyHealthMultiplier); enemyConfig.speed = config.baseSpeed * enemySpeedMultiplier; } else if (difficulty === 'DIFICIL') { enemyConfig.health = config.baseHealth; enemyConfig.speed = config.baseSpeed * 1.1 * enemySpeedMultiplier; } // Special handling for miniBoss if (enemyType === 'miniBoss') { enemyConfig.health = config.baseHealth; enemyConfig.speed = config.baseSpeed; } // Path selection var pathIndex; if (enemyType === 'skeleton' && enemyKillCounter < 5) { pathIndex = 0; // Center path only } else if (enemyType === 'miniBoss') { pathIndex = 0; // Center path for miniBoss } else { pathIndex = Math.floor(Math.random() * 5); } var enemy = spawnEnemyAtPath(enemyType, pathIndex, enemyConfig); // Apply elite enhancements if (difficulty === 'DIFICIL' && enemyKillCounter >= 20) { var eliteChance = enemyType === 'skeleton' ? 0.20 : enemyType === 'ogre' ? 0.15 : enemyType === 'knight' ? 0.10 : 0; if (Math.random() < eliteChance) { enemy.health *= enemyType === 'ogre' ? 1.5 : 2; enemy.speed *= enemyType === 'ogre' ? 1.4 : enemyType === 'knight' ? 1.5 : 1.3; enemy.maxHealth = enemy.health; var eliteColor = enemyType === 'ogre' ? 0xFF0000 : enemyType === 'knight' ? 0xFFD700 : 0xFF6600; for (var frameIdx = 0; frameIdx < enemy.animationFrames.length; frameIdx++) { enemy.animationFrames[frameIdx].tint = eliteColor; } } } return enemy; } // Unified enemy spawning system enemySpawnTimer++; var totalEnemies = enemies.length + ogres.length + knights.length + miniBosses.length; // Process each enemy type with unified spawning logic var enemyTypes = ['skeleton', 'ogre', 'knight', 'miniBoss']; for (var typeIdx = 0; typeIdx < enemyTypes.length; typeIdx++) { var enemyType = enemyTypes[typeIdx]; var config = ENEMY_SPAWN_CONFIG[enemyType]; var shouldSpawn = false; // Check spawn conditions for each enemy type if (enemyType === 'skeleton') { var skeletonInterval = config.spawnInterval(selectedDifficulty, difficultyLevel); shouldSpawn = config.checkSpawn(enemySpawnTimer, skeletonInterval) && miniBosses.length === 0 && totalEnemies < 15; if (shouldSpawn) { enemySpawnTimer = 0; // Handle path selection for skeletons var randomPathIndex; if (enemyKillCounter < 5) { randomPathIndex = 0; } else { var availablePaths = []; for (var pathIdx = 0; pathIdx < 5; pathIdx++) { if (pathConsecutiveSpawns[pathIdx] < 2) { availablePaths.push(pathIdx); } } if (availablePaths.length === 0) { for (var pathIdx = 0; pathIdx < 5; pathIdx++) { pathConsecutiveSpawns[pathIdx] = 0; availablePaths.push(pathIdx); } } randomPathIndex = availablePaths[Math.floor(Math.random() * availablePaths.length)]; } // Update tracking pathConsecutiveSpawns[randomPathIndex]++; pathLastSpawnTime[randomPathIndex] = LK.ticks; lastSpawnedPath = randomPathIndex; consecutiveSpawns = pathConsecutiveSpawns[randomPathIndex]; } } else if (enemyType === 'ogre') { var ogreInterval = config.spawnInterval(selectedDifficulty); shouldSpawn = config.checkSpawn(enemySpawnTimer, ogreInterval) && miniBosses.length === 0; } else if (enemyType === 'knight') { var knightInterval = config.spawnInterval(selectedDifficulty); var knightThreshold = typeof config.startThreshold === 'function' ? config.startThreshold(selectedDifficulty) : config.startThreshold; shouldSpawn = config.checkSpawn(enemySpawnTimer, knightInterval, knightThreshold) && miniBosses.length === 0; } else if (enemyType === 'miniBoss') { shouldSpawn = config.checkSpawn(); } // Spawn enemy if conditions are met if (shouldSpawn) { var enemy = spawnEnemyByType(enemyType, selectedDifficulty, difficultyLevel, enemyHealthMultiplier, enemySpeedMultiplier); if (enemy) { // Add to appropriate array if (enemyType === 'skeleton') { enemies.push(enemy); if (Math.random() < 0.3) { LK.getSound('enemyGrowl').play(); } } else if (enemyType === 'ogre') { ogres.push(enemy); } else if (enemyType === 'knight') { knights.push(enemy); } else if (enemyType === 'miniBoss') { enemy.y = -200; enemy.createHealthBar(); enemy.updateHealthBar(); miniBosses.push(enemy); LK.effects.flashScreen(0x8B0000, 1000); } } } } // Unified collision detection function for all enemy types function checkAllEnemyCollisions() { // Combine all enemy arrays into one unified array with type information var allEnemies = []; // Add all enemies with their respective damage and behavior enemies.forEach(function (enemy) { allEnemies.push({ enemy: enemy, damage: 20, removeOnHit: true, name: 'skeleton', array: enemies }); }); ogres.forEach(function (enemy) { allEnemies.push({ enemy: enemy, damage: 30, removeOnHit: true, name: 'ogre', array: ogres }); }); knights.forEach(function (enemy) { allEnemies.push({ enemy: enemy, damage: 40, removeOnHit: true, name: 'knight', array: knights }); }); miniBosses.forEach(function (enemy) { allEnemies.push({ enemy: enemy, damage: 75, removeOnHit: false, name: 'miniBoss', array: miniBosses }); }); // Process all enemies in a single loop for (var i = allEnemies.length - 1; i >= 0; i--) { var enemyData = allEnemies[i]; var enemy = enemyData.enemy; var config = enemyData; // Remove enemies that went off-screen at bottom if (enemy.y > 2732 + 100) { // Find and remove from appropriate array var arrayIndex = config.array.indexOf(enemy); if (arrayIndex !== -1) { config.array.splice(arrayIndex, 1); } enemy.destroy(); continue; } // Initialize lastIntersecting if not set if (enemy.lastIntersecting === undefined) { enemy.lastIntersecting = false; } // Check for collision transition (only if enemy is not dying) var currentIntersecting = wizard.intersects(enemy); if (!enemy.lastIntersecting && currentIntersecting && !enemy.isDying) { // Damage wizard when enemy touches for the first time wizard.takeDamage(config.damage); // Remove enemy after dealing damage (except mini bosses) if (config.removeOnHit) { var arrayIndex = config.array.indexOf(enemy); if (arrayIndex !== -1) { config.array.splice(arrayIndex, 1); } enemy.destroy(); continue; } } // Update last intersecting state enemy.lastIntersecting = currentIntersecting; } } // 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 = enemies.concat(ogres).concat(knights).concat(miniBosses); 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); } } } // Make tap text pulse var pulse = 1 + Math.sin(LK.ticks * 0.1) * 0.2; tapText.scale.set(pulse, pulse); }; // Remove tap text after 5 seconds tween({}, {}, { duration: 5000, onFinish: function onFinish() { if (tapText && tapText.parent) { tapText.destroy(); } } });
===================================================================
--- original.js
+++ change.js
@@ -1151,20 +1151,15 @@
return self;
});
var Tutorial = Container.expand(function () {
var self = Container.call(this);
- // Tutorial state variables
+ // Simple tutorial state
self.currentStep = 0;
self.isActive = false;
- self.skipped = false; // Track if tutorial was skipped
- self.tutorialSteps = [];
- self.highlightElements = [];
+ self.skipped = false;
self.tutorialTexts = [];
- self.arrows = [];
- self.demoEnemies = []; // Track demo enemies
- self.interactiveElements = []; // Track interactive elements
- self.practiceMode = false; // Track if in practice mode
- self.practiceCompletedSteps = []; // Track completed practice steps
+ self.practiceEnemy = null;
+ self.stepCompleted = false;
// Tutorial overlay background
var tutorialOverlay = self.attachAsset('startMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
@@ -1174,93 +1169,35 @@
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 interactive tutorial steps
- self.initializeTutorialSteps = function () {
- self.tutorialSteps = [{
- id: 'welcome',
- title: 'BIENVENIDO A WIZARD DEFENDER!',
- description: 'Eres un mago que debe defender su castillo.\n¡Aprendamos jugando!',
- duration: 3000,
- showSkip: true,
- interactive: false,
- waitForTap: true
- }, {
- id: 'basic_attack',
- title: 'PRACTICA: ATAQUE BASICO',
- description: 'Toca el esqueleto brillante para atacarlo.\n¡Inténtalo ahora!',
- interactive: true,
- spawnPracticeEnemy: true,
- waitForEnemyKill: true,
- showWizard: true
- }, {
- id: 'health_system',
- title: 'SISTEMA DE SALUD',
- description: 'Si un enemigo te toca, pierdes salud.\n¡Veamos tu barra de salud!',
- highlightElement: 'healthBar',
- showWizard: true,
- interactive: false,
- waitForTap: true
- }, {
- id: 'practice_defense',
- title: 'PRACTICA: DEFENSA RAPIDA',
- description: 'Defiéndete! Mata 3 enemigos antes de que te toquen.',
- interactive: true,
- spawnMultipleEnemies: 3,
- waitForMultipleKills: 3,
- showWizard: true,
- enableDamage: true
- }, {
- id: 'coins_system',
- title: 'SISTEMA DE MONEDAS',
- description: 'Ganas monedas al matar enemigos.\n¡Observa tu contador!',
- highlightElement: 'coinCounter',
- showWizard: true,
- interactive: false,
- waitForTap: true
- }, {
- id: 'practice_survival',
- title: 'PRACTICA: SUPERVIVENCIA',
- description: 'Sobrevive 10 segundos mientras atacas enemigos.\n¡Usa tus habilidades!',
- interactive: true,
- survivalMode: true,
- survivalTime: 10,
- showWizard: true,
- enableDamage: true
- }, {
- id: 'tutorial_complete',
- title: 'TUTORIAL COMPLETADO!',
- description: '¡Excelente trabajo!\nYa estás listo para el juego completo.',
- interactive: false,
- startGame: true,
- showWizard: true
- }];
- };
- // Start the tutorial
+ tutorialOverlay.visible = false;
+ // Simple tutorial steps
+ self.tutorialSteps = [{
+ title: 'BIENVENIDO A WIZARD DEFENDER!',
+ description: 'Toca enemigos para atacarlos.\n\nToca para continuar.',
+ showSkip: true
+ }, {
+ title: 'PRACTICA DE ATAQUE',
+ description: 'Toca el enemigo brillante para atacarlo.',
+ spawnEnemy: true,
+ waitForKill: true
+ }, {
+ title: 'SISTEMA DE SALUD',
+ description: 'Tu salud está arriba a la izquierda.\nEvita que los enemigos te toquen.\n\nToca para continuar.'
+ }, {
+ title: 'TUTORIAL COMPLETADO',
+ description: '¡Listo para jugar!\n\nToca para empezar.',
+ startGame: true
+ }];
+ // Start tutorial
self.startTutorial = function () {
self.isActive = true;
self.currentStep = 0;
- self.practiceCompletedSteps = [];
- // 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
+ self.stepCompleted = false;
+ self.skipped = false;
+ // Hide game elements
+ if (gameMenu) gameMenu.visible = false;
if (backgroundMap) backgroundMap.visible = false;
coinText.visible = false;
killCountText.visible = false;
tapText.visible = false;
@@ -1269,556 +1206,174 @@
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 tutorial
+ self.visible = true;
+ tutorialOverlay.visible = true;
+ self.showCurrentStep();
+ return true;
};
- // Show a specific tutorial step
- self.showStep = function (stepIndex) {
- if (stepIndex >= self.tutorialSteps.length) {
+ // Show current step
+ self.showCurrentStep = function () {
+ self.clearStepElements();
+ var step = self.tutorialSteps[self.currentStep];
+ if (!step) {
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;
- // Show wizard if step requires it
- if (step.showWizard && wizard) {
- wizard.visible = true;
- wizard.x = 2048 / 2;
- wizard.y = 2732 - 600;
- }
- // Show game background for interactive steps
- if (step.interactive && backgroundMap) {
- backgroundMap.visible = true;
- }
- // Create step title with proper sizing
+ // Show title
var titleText = new Text2(step.title, {
- size: 70,
+ size: 80,
fill: 0xFFD700,
- font: "monospace",
- wordWrap: true,
- wordWrapWidth: 1600
+ font: "monospace"
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
- titleText.y = step.interactive ? 300 : 900; // Higher position for interactive steps
+ titleText.y = 800;
self.addChild(titleText);
self.tutorialTexts.push(titleText);
- // Create step description
+ // Show description
var descText = new Text2(step.description, {
- size: 55,
+ size: 60,
fill: 0xFFFFFF,
- font: "monospace",
- wordWrap: true,
- wordWrapWidth: 1800
+ font: "monospace"
});
descText.anchor.set(0.5, 0.5);
descText.x = 2048 / 2;
- descText.y = step.interactive ? 450 : 1200; // Higher position for interactive steps
+ descText.y = 1200;
self.addChild(descText);
self.tutorialTexts.push(descText);
- // Handle special step behaviors
- if (step.spawnPracticeEnemy) {
- self.spawnPracticeEnemy();
- }
- if (step.spawnMultipleEnemies) {
- self.spawnMultipleEnemies(step.spawnMultipleEnemies);
- }
- if (step.survivalMode) {
- self.startSurvivalMode(step.survivalTime);
- }
- if (step.highlightElement) {
- self.highlightElement(step.highlightElement);
- }
- // Show appropriate prompts
- if (step.interactive) {
- if (step.waitForEnemyKill) {
- self.showInteractivePrompt('¡Toca el enemigo brillante!');
- } else if (step.waitForMultipleKills) {
- self.showInteractivePrompt('Mata ' + step.waitForMultipleKills + ' enemigos: 0/' + step.waitForMultipleKills);
- } else if (step.survivalMode) {
- self.showInteractivePrompt('Sobrevive ' + step.survivalTime + ' segundos!');
- }
- } else if (step.waitForTap) {
- self.showContinuePrompt();
- }
// Show skip button on first step
if (step.showSkip) {
- self.showSkipButton();
+ 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;
+ 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);
}
- };
- // Show interactive prompt
- self.showInteractivePrompt = function (text) {
- var prompt = new Text2(text, {
- size: 50,
- fill: 0x00FF00,
- font: "monospace",
- wordWrap: true,
- wordWrapWidth: 1400
- });
- prompt.anchor.set(0.5, 0.5);
- prompt.x = 2048 / 2;
- prompt.y = 600;
- self.addChild(prompt);
- self.tutorialTexts.push(prompt);
- self.interactivePrompt = prompt;
- // Add pulsing animation
- self.animatePrompt(prompt);
- };
- // Show continue prompt
- self.showContinuePrompt = function () {
- 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 = 1600;
- self.addChild(continuePrompt);
- self.tutorialTexts.push(continuePrompt);
- self.animatePrompt(continuePrompt);
- };
- // Show skip button
- self.showSkipButton = function () {
- 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);
- };
- // Animate prompt text
- self.animatePrompt = function (prompt) {
- tween(prompt, {
- scaleX: 1.2,
- scaleY: 1.2
- }, {
- duration: 800,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- tween(prompt, {
- scaleX: 1.0,
- scaleY: 1.0
- }, {
- duration: 800,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (prompt.parent) {
- self.animatePrompt(prompt); // Loop animation
- }
- }
- });
- }
- });
- };
- // 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: 4,
- scaleY: 2
- }));
- healthGlow.tint = 0x00FF00;
- healthGlow.alpha = 0.8;
- self.highlightElements.push(healthGlow);
- // Create pulsing animation
- self.animateHighlight(healthGlow);
- }
- break;
- case 'coinCounter':
- if (coinText) {
- // Show coin UI temporarily
- coinText.visible = true;
- coinText.setText('Coins: 0');
- // Create highlight glow around coin counter
- var coinGlow = game.addChild(LK.getAsset('projectileGlow', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: 270,
- y: 150,
- scaleX: 4,
- scaleY: 2
- }));
- coinGlow.tint = 0xFFD700;
- coinGlow.alpha = 0.8;
- self.highlightElements.push(coinGlow);
- // Create pulsing animation
- self.animateHighlight(coinGlow);
- }
- break;
+ // Handle special steps
+ if (step.spawnEnemy) {
+ self.spawnPracticeEnemy();
}
+ self.stepCompleted = !step.waitForKill;
};
- // Animate highlight elements
- self.animateHighlight = function (element) {
- tween(element, {
- scaleX: element.scaleX * 1.3,
- scaleY: element.scaleY * 1.3,
- alpha: 0.4
- }, {
- duration: 1000,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- tween(element, {
- scaleX: element.scaleX / 1.3,
- scaleY: element.scaleY / 1.3,
- alpha: 0.8
- }, {
- duration: 1000,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (element.parent) {
- self.animateHighlight(element); // Loop animation
- }
- }
- });
- }
- });
- };
- // Spawn a practice enemy
+ // Spawn practice enemy
self.spawnPracticeEnemy = function () {
- var practiceEnemy = game.addChild(new Enemy('skeleton', {
- health: 50,
- speed: 1.5,
+ // Show game background and wizard
+ if (backgroundMap) backgroundMap.visible = true;
+ if (wizard) {
+ wizard.visible = true;
+ wizard.x = 2048 / 2;
+ wizard.y = 2732 - 600;
+ }
+ // Create practice enemy
+ self.practiceEnemy = game.addChild(new Enemy('skeleton', {
+ health: 30,
+ speed: 1,
animationSpeed: 15
}));
- practiceEnemy.x = 2048 / 2;
- practiceEnemy.y = 1000;
- practiceEnemy.pathIndex = 0;
- practiceEnemy.isPracticeEnemy = true;
- // Make practice enemy more visible with glowing effect
- for (var frameIdx = 0; frameIdx < practiceEnemy.animationFrames.length; frameIdx++) {
- practiceEnemy.animationFrames[frameIdx].tint = 0xFFFF00; // Yellow tint
+ self.practiceEnemy.x = 2048 / 2;
+ self.practiceEnemy.y = 1000;
+ self.practiceEnemy.pathIndex = 0;
+ self.practiceEnemy.isPracticeEnemy = true;
+ // Make enemy glow
+ for (var frameIdx = 0; frameIdx < self.practiceEnemy.animationFrames.length; frameIdx++) {
+ self.practiceEnemy.animationFrames[frameIdx].tint = 0xFFFF00;
}
- self.demoEnemies.push(practiceEnemy);
- enemies.push(practiceEnemy);
- // Create glowing highlight around practice enemy
- var enemyGlow = game.addChild(LK.getAsset('projectileGlow', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: practiceEnemy.x,
- y: practiceEnemy.y,
- scaleX: 5,
- scaleY: 5
- }));
- enemyGlow.tint = 0xFFFF00;
- enemyGlow.alpha = 0.6;
- self.highlightElements.push(enemyGlow);
- // Make glow follow enemy
- var _glowFollower = function glowFollower() {
- if (practiceEnemy && practiceEnemy.parent && enemyGlow && enemyGlow.parent) {
- enemyGlow.x = practiceEnemy.x;
- enemyGlow.y = practiceEnemy.y;
- // Create pulsing effect
- var pulse = 1 + Math.sin(LK.ticks * 0.1) * 0.3;
- enemyGlow.scaleX = 5 * pulse;
- enemyGlow.scaleY = 5 * pulse;
- // Continue following
- tween({}, {}, {
- duration: 60,
- onFinish: _glowFollower
- });
- } else if (enemyGlow && enemyGlow.parent) {
- enemyGlow.destroy();
- }
- };
- _glowFollower();
+ enemies.push(self.practiceEnemy);
};
- // Spawn multiple practice enemies
- self.spawnMultipleEnemies = function (count) {
- self.multipleEnemiesCount = count;
- self.multipleEnemiesKilled = 0;
- for (var i = 0; i < count; i++) {
- var enemy = game.addChild(new Enemy('skeleton', {
- health: 30,
- speed: 1 + i * 0.5,
- animationSpeed: 15
- }));
- // Position enemies at different locations
- var angle = i / count * Math.PI * 2;
- enemy.x = 2048 / 2 + Math.cos(angle) * 300;
- enemy.y = 1200 + Math.sin(angle) * 200;
- enemy.pathIndex = 0;
- enemy.isPracticeEnemy = true;
- enemy.practiceEnemyIndex = i;
- // Make enemies visually distinct
- var colors = [0xFF6600, 0xFF0066, 0x66FF00];
- for (var frameIdx = 0; frameIdx < enemy.animationFrames.length; frameIdx++) {
- enemy.animationFrames[frameIdx].tint = colors[i % colors.length];
- }
- self.demoEnemies.push(enemy);
- enemies.push(enemy);
- }
- };
- // Start survival mode
- self.startSurvivalMode = function (seconds) {
- self.survivalTimeLeft = seconds;
- self.survivalActive = true;
- self.survivalEnemySpawnTimer = 0;
- // Start survival timer
- self.survivalTimer = LK.setInterval(function () {
- self.survivalTimeLeft--;
- if (self.interactivePrompt) {
- self.interactivePrompt.setText('Sobrevive ' + self.survivalTimeLeft + ' segundos!');
- }
- if (self.survivalTimeLeft <= 0) {
- self.completeSurvivalMode();
- }
- }, 1000);
- };
- // Complete survival mode
- self.completeSurvivalMode = function () {
- self.survivalActive = false;
- if (self.survivalTimer) {
- LK.clearInterval(self.survivalTimer);
- self.survivalTimer = null;
- }
- // Clear all practice enemies
- for (var i = 0; i < self.demoEnemies.length; i++) {
- var enemy = self.demoEnemies[i];
- if (enemy && enemy.parent) {
- var enemyIndex = enemies.indexOf(enemy);
- if (enemyIndex !== -1) {
- enemies.splice(enemyIndex, 1);
- }
- enemy.destroy();
- }
- }
- self.demoEnemies = [];
- // Show completion message
- if (self.interactivePrompt) {
- self.interactivePrompt.setText('¡Supervivencia completada! Toca para continuar.');
- self.interactivePrompt.tint = 0x00FF00;
- }
- // Mark step as completed
- self.practiceCompletedSteps.push(self.currentStep);
- };
- // Update method for tutorial logic
- self.update = function () {
- if (!self.isActive) return;
- var step = self.tutorialSteps[self.currentStep];
- if (!step) return;
- // Handle survival mode
- if (self.survivalActive) {
- self.survivalEnemySpawnTimer++;
- // Spawn enemies periodically during survival
- if (self.survivalEnemySpawnTimer >= 120) {
- // Every 2 seconds
- self.survivalEnemySpawnTimer = 0;
- var survivalEnemy = game.addChild(new Enemy('skeleton', {
- health: 20,
- speed: 2,
- animationSpeed: 15
- }));
- survivalEnemy.x = Math.random() * 2048;
- survivalEnemy.y = 800;
- survivalEnemy.pathIndex = 0;
- survivalEnemy.isPracticeEnemy = true;
- for (var frameIdx = 0; frameIdx < survivalEnemy.animationFrames.length; frameIdx++) {
- survivalEnemy.animationFrames[frameIdx].tint = 0xFF3333;
- }
- self.demoEnemies.push(survivalEnemy);
- enemies.push(survivalEnemy);
- }
- }
- // Check for practice enemy kills
- if (step.waitForEnemyKill || step.waitForMultipleKills) {
- var killedCount = 0;
- for (var i = self.demoEnemies.length - 1; i >= 0; i--) {
- var enemy = self.demoEnemies[i];
- if (!enemy || !enemy.parent) {
- self.demoEnemies.splice(i, 1);
- killedCount++;
- }
- }
- // Update kill counter for multiple enemies
- if (step.waitForMultipleKills) {
- self.multipleEnemiesKilled += killedCount;
- if (self.interactivePrompt) {
- self.interactivePrompt.setText('Mata ' + step.waitForMultipleKills + ' enemigos: ' + self.multipleEnemiesKilled + '/' + step.waitForMultipleKills);
- }
- // Check if all enemies killed
- if (self.multipleEnemiesKilled >= step.waitForMultipleKills) {
- if (self.interactivePrompt) {
- self.interactivePrompt.setText('¡Bien hecho! Toca para continuar.');
- self.interactivePrompt.tint = 0x00FF00;
- }
- self.practiceCompletedSteps.push(self.currentStep);
- }
- } else if (step.waitForEnemyKill && killedCount > 0) {
- if (self.interactivePrompt) {
- self.interactivePrompt.setText('¡Excelente! Toca para continuar.');
- self.interactivePrompt.tint = 0x00FF00;
- }
- self.practiceCompletedSteps.push(self.currentStep);
- }
- }
- // Handle practice enemy damage
- if (step.enableDamage) {
- for (var i = 0; i < self.demoEnemies.length; i++) {
- var enemy = self.demoEnemies[i];
- if (enemy && enemy.parent && wizard) {
- // Initialize collision tracking
- if (enemy.lastIntersecting === undefined) {
- enemy.lastIntersecting = false;
- }
- var currentIntersecting = wizard.intersects(enemy);
- if (!enemy.lastIntersecting && currentIntersecting) {
- wizard.takeDamage(10); // Reduced damage for tutorial
- LK.effects.flashObject(wizard, 0xFF0000, 300);
- }
- enemy.lastIntersecting = currentIntersecting;
- }
- }
- }
- };
- // Clear all tutorial step elements
+ // Clear step elements
self.clearStepElements = function () {
- // Clear tutorial texts
+ // Clear text elements
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();
+ // Clear practice enemy
+ if (self.practiceEnemy && self.practiceEnemy.parent) {
+ var enemyIndex = enemies.indexOf(self.practiceEnemy);
+ if (enemyIndex !== -1) {
+ enemies.splice(enemyIndex, 1);
}
+ self.practiceEnemy.destroy();
+ self.practiceEnemy = null;
}
- 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();
+ };
+ // Update tutorial
+ self.update = function () {
+ if (!self.isActive) return;
+ // Check if practice enemy was killed
+ if (self.practiceEnemy && !self.practiceEnemy.parent) {
+ self.stepCompleted = true;
+ self.practiceEnemy = null;
+ // Update description
+ var step = self.tutorialSteps[self.currentStep];
+ if (step.waitForKill && self.tutorialTexts.length > 1) {
+ self.tutorialTexts[1].setText('¡Excelente!\n\nToca para continuar.');
+ self.tutorialTexts[1].tint = 0x00FF00;
}
}
- self.arrows = [];
- // Clear demo enemies
- for (var i = 0; i < self.demoEnemies.length; i++) {
- var enemy = self.demoEnemies[i];
- if (enemy && enemy.parent) {
- var enemyIndex = enemies.indexOf(enemy);
- if (enemyIndex !== -1) {
- enemies.splice(enemyIndex, 1);
- }
- enemy.destroy();
- }
- }
- self.demoEnemies = [];
- // Clear interactive elements
- for (var i = 0; i < self.interactiveElements.length; i++) {
- if (self.interactiveElements[i] && self.interactiveElements[i].parent) {
- self.interactiveElements[i].destroy();
- }
- }
- self.interactiveElements = [];
- // Clear survival timer
- if (self.survivalTimer) {
- LK.clearInterval(self.survivalTimer);
- self.survivalTimer = null;
- }
- self.survivalActive = false;
};
- // Advance to next tutorial step
+ // Next step
self.nextStep = function () {
self.currentStep++;
if (self.currentStep < self.tutorialSteps.length) {
- self.showStep(self.currentStep);
+ self.showCurrentStep();
} else {
- // Tutorial completed naturally (not skipped)
- self.skipped = false;
self.completeTutorial();
}
};
- // Complete the tutorial
+ // Complete tutorial
self.completeTutorial = function () {
- // Mark tutorial as completed
+ // Mark as completed
storage.tutorialCompleted = true;
- // Clear all tutorial elements
+ // Clear elements
self.clearStepElements();
- // Hide tutorial completely
+ // Hide tutorial
tutorialOverlay.visible = false;
- tutorialOverlay.interactive = false; // Remove interactivity
self.visible = false;
self.isActive = false;
// Hide wizard
- if (wizard) {
- wizard.visible = false;
- }
- // Only auto-start game if tutorial was completed naturally (not skipped)
+ if (wizard) wizard.visible = false;
+ // Auto-start game if completed naturally
if (!self.skipped) {
- // Start game immediately without showing menu
- if (gameMenu) {
- gameMenu.startGame();
- }
+ if (gameMenu) gameMenu.startGame();
} else {
- // If skipped, show the menu
- if (gameMenu) {
- gameMenu.visible = true;
- }
+ if (gameMenu) gameMenu.visible = true;
}
- // Reset skipped flag for next time
self.skipped = false;
};
- // Handle tutorial interactions
+ // Handle input
self.down = function (x, y, obj) {
if (!self.isActive) return;
var step = self.tutorialSteps[self.currentStep];
- // Handle skip button (top-right corner)
+ // Check skip button
if (step.showSkip && x >= 2048 - 300 && x <= 2048 - 100 && y >= 100 && y <= 300) {
- self.skipped = true; // Mark as skipped
+ self.skipped = true;
self.completeTutorial();
return;
}
- // Handle interactive steps
- if (step.interactive) {
- // Check if practice step is completed
- if (self.practiceCompletedSteps.indexOf(self.currentStep) !== -1) {
- self.nextStep();
- return;
- }
- // Don't advance if waiting for specific interactions
- return;
+ // Advance if step is completed
+ if (self.stepCompleted) {
+ self.nextStep();
}
- // For non-interactive steps, advance on tap
- self.nextStep();
};
return self;
});
var UnifiedProjectile = Container.expand(function (type) {