User prompt
add spell trail ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que la pantalla se agite bastante cuando el jugador recibe damage ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
aplica todas la mejoras que mencionaste ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
puedes implementar todas las opciones del 1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que tiemble mas ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que la pantalla tiemble cuando recibes damage ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz efectos de impactos cuando golpeas a los enemigos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que el jugador empieze con 1000 monedas y la tienda se active cada 10 enemigos
User prompt
reduce la hitbox de los orbes
User prompt
haz que el orbe sea el doble de lento
User prompt
haz que los orbes esten el doble de lejos del jugador
User prompt
haz que el orbe no aumente velocidad con las mejoras
User prompt
haz que la lv2 del orbe no sea tan rapido ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que el jugador empieze con 1000 monedas y la tienda salga cada 10 enemigos
User prompt
haz que cada vez que se compra una mejora salga una version mejorada despues en la tienda
User prompt
haz un nuevo menu de mejora al llegar al enemigo 70
User prompt
haz que la musica no se pause con el menu de emjora
User prompt
pon el orbe mas arriba ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
pon el orbe mas arriba
User prompt
separa la orbe el doble de espacio del jugador
User prompt
haz que el orbe este mas separado del jugador
User prompt
cambia el mana shield por una mejora que sea unos orbes que dan vueltas alrededor del jugador ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
cambia el nomre de fire bag a fire ball
User prompt
cambia la mejora de multi shot por una que lanze una bolsa de fuego cada 3s ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que la mejora de spell power ahora haga el doble de damage
/**** * 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++; totalCoinsEarned++; coinText.setText('Coins: ' + coinCounter); // Create coin collection effect var collectEffect = game.addChild(LK.getAsset('coin', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 1.5, scaleY: 1.5 })); collectEffect.tint = 0xFFFFFF; tween(collectEffect, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { collectEffect.destroy(); } }); // Save progress saveGameData(); checkAchievements(); // 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; }); var DamageNumber = Container.expand(function () { var self = Container.call(this); self.init = function (damage, x, y, color) { var damageText = new Text2(damage.toString(), { size: 80, fill: color || 0xFFFFFF, font: "monospace" }); damageText.anchor.set(0.5, 0.5); self.addChild(damageText); self.x = x; self.y = y; // Animate damage number floating up and fading tween(self, { y: self.y - 100, alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); // Add slight random horizontal drift var drift = (Math.random() - 0.5) * 60; tween(self, { x: self.x + drift }, { duration: 1000, easing: tween.easeOut }); }; return self; }); var DeathAnimation = Container.expand(function () { var self = Container.call(this); self.init = function (x, y, enemyType) { self.x = x; self.y = y; // Create explosion particles for (var i = 0; i < 8; i++) { var particle = self.attachAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); var angle = Math.PI * 2 / 8 * i; var distance = 60 + Math.random() * 40; particle.tint = enemyType === 'knight' ? 0xFFD700 : enemyType === 'ogre' ? 0xFF4500 : 0xFF6B6B; tween(particle, { x: Math.cos(angle) * distance, y: Math.sin(angle) * distance, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 800, easing: tween.easeOut }); } // Remove after animation tween({}, {}, { duration: 800, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Game arrays to track objects var Enemy = Container.expand(function () { var self = Container.call(this); // Animation system for skeleton self.currentFrame = 1; self.animationTimer = 0; self.animationSpeed = 15; // Change frame every 15 ticks (250ms at 60fps) self.animationState = 'walking'; // walking, attacking, dying, idle // Create all skeleton graphics frames and store them self.skeletonFrames = []; for (var i = 1; i <= 4; i++) { var frameGraphics = self.attachAsset('esqueleto' + i, { anchorX: 0.5, anchorY: 1.0, scaleX: 2.0, scaleY: 2.0 }); frameGraphics.visible = i === 1; // Only show first frame initially self.skeletonFrames.push(frameGraphics); } // Create invisible hitbox with double size var hitbox = self.attachAsset('esqueleto1', { anchorX: 0.5, anchorY: 1.0, scaleX: 4, scaleY: 4 }); hitbox.alpha = 0; // Make hitbox invisible self.health = 1; self.maxHealth = 1; self.speed = 7; // Note: health, maxHealth and speed will be overridden by difficulty system self.lastX = 0; self.frozen = false; self.frozenTimer = 0; self.update = function () { // Animation system - cycle through skeleton frames based on state self.animationTimer++; var frameSpeed = self.animationSpeed; if (self.animationState === 'attacking') { frameSpeed = 8; // Faster animation for attacking } else if (self.animationState === 'dying') { frameSpeed = 20; // Slower animation for dying } else if (self.animationState === 'idle') { frameSpeed = 25; // Very slow animation for idle } if (self.animationTimer >= frameSpeed) { self.animationTimer = 0; // Hide current frame self.skeletonFrames[self.currentFrame - 1].visible = false; // Move to next frame based on animation state if (self.animationState === 'walking') { self.currentFrame++; if (self.currentFrame > 4) { self.currentFrame = 1; } } else if (self.animationState === 'attacking') { // Cycle through frames 2-4 for attacking self.currentFrame++; if (self.currentFrame > 4) { self.currentFrame = 2; } } else if (self.animationState === 'dying') { // Play frames 3-4 once for dying if (self.currentFrame < 4) { self.currentFrame++; } } else if (self.animationState === 'idle') { // Cycle between frames 1-2 for idle self.currentFrame = self.currentFrame === 1 ? 2 : 1; } // Show new frame self.skeletonFrames[self.currentFrame - 1].visible = true; } // Progressive speed increase - increase speed by 50% over 10 seconds if (!self.speedTweenStarted) { self.speedTweenStarted = true; var targetSpeed = self.speed * 2.5; // Increase speed by 150% tween(self, { speed: targetSpeed }, { duration: 10000, // 10 seconds easing: tween.easeOut }); } // Handle frozen state if (self.frozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.frozen = false; } return; // Skip movement when frozen } // Always move directly toward the wizard to ensure enemies reach the player if (wizard) { // Calculate direction from enemy to wizard var dx = wizard.x - self.x; var dy = wizard.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // Normalize direction and apply speed self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } // Face toward the wizard - flip horizontally based on wizard position if (dx < 0) { // Wizard is to the left - flip to face left for (var frameIdx = 0; frameIdx < self.skeletonFrames.length; frameIdx++) { self.skeletonFrames[frameIdx].scaleX = -2.0; // Flip to face left } } else { // Wizard is to the right - face right for (var frameIdx = 0; frameIdx < self.skeletonFrames.length; frameIdx++) { self.skeletonFrames[frameIdx].scaleX = 2.0; // Normal orientation to face right } } } else { // Fallback: move down if wizard doesn't exist self.y += self.speed; } }; self.takeDamage = function (damage) { self.health -= damage; // Create floating damage number var damageNumber = game.addChild(new DamageNumber()); damageNumber.init(damage, self.x, self.y - 50, 0xFFFF00); // Screen shake on hit if (!game.screenShaker) { game.screenShaker = new ScreenShake(); } game.screenShaker.shake(5, 200); // Change animation state to attacking when hit self.animationState = 'attacking'; // Flash red when hit LK.effects.flashObject(self, 0xFF0000, 200); // Enhanced impact effect with sparks var impactEffect = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 0.5, scaleY: 0.5 })); impactEffect.tint = 0xFFFFFF; impactEffect.alpha = 0.9; // Create spark particles for (var i = 0; i < 6; i++) { var spark = game.addChild(LK.getAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 0.2, scaleY: 0.2 })); spark.tint = 0xFFFFFF; var sparkAngle = Math.random() * Math.PI * 2; var sparkDistance = 50 + Math.random() * 30; tween(spark, { x: self.x + Math.cos(sparkAngle) * sparkDistance, y: self.y + Math.sin(sparkAngle) * sparkDistance, alpha: 0, scaleX: 0.05, scaleY: 0.05 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { spark.destroy(); } }); } // Animate impact effect tween(impactEffect, { scaleX: 2.5, scaleY: 2.5, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { impactEffect.destroy(); } }); // Return to walking animation after brief attacking animation tween({}, {}, { duration: 300, onFinish: function onFinish() { if (self.animationState === 'attacking') { self.animationState = 'walking'; } } }); // Check if enemy should die based on health if (self.health <= 0) { self.die(); } }; self.down = function (x, y, obj) { // Set this enemy as the selected target selectedEnemy = self; // Visual feedback for selection LK.effects.flashObject(self, 0xFFFF00, 500); // Yellow flash to indicate selection // Create projectile from wizard to enemy when enemy is tapped if (wizard && projectiles.length < 10) { // Limit projectiles to prevent spam var projectile = game.addChild(new Projectile()); projectile.x = wizard.x; projectile.y = wizard.y; // Calculate direction from wizard to enemy 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; // Set the target for this projectile projectiles.push(projectile); LK.getSound('spellCast').play(); } }; self.die = function () { // Change animation state to dying self.animationState = 'dying'; self.currentFrame = 3; // Start dying animation from frame 3 // Create death animation var deathAnim = game.addChild(new DeathAnimation()); deathAnim.init(self.x, self.y, 'skeleton'); // Screen shake on death if (!game.screenShaker) { game.screenShaker = new ScreenShake(); } game.screenShaker.shake(8, 250); // Play pain sound when enemy dies LK.getSound('painSound').play(); // Drop coin var coin = game.addChild(new Coin()); coin.x = self.x; coin.y = self.y - 50; coin.isAnimating = true; coins.push(coin); // Animate coin moving toward coin counter 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, easing: tween.easeOut, onFinish: function onFinish() { // Increment coin counter after animation coinCounter++; coinText.setText('Coins: ' + coinCounter); // Remove coin from array and destroy for (var i = coins.length - 1; i >= 0; i--) { if (coins[i] === coin) { coins.splice(i, 1); break; } } coin.destroy(); } }); // Increment enemy kill counter enemyKillCounter++; killCountText.setText('Puntuacion: ' + enemyKillCounter); // Add experience to wizard wizard.gainExperience(25); // Life drain upgrade - improved chance and healing if (wizard.lifeDrainLevel && wizard.lifeDrainLevel > 0) { var drainChance = Math.random(); var chanceThreshold = upgradeLevels.lifeDrain > 1 ? 0.10 : 0.05; // 10% vs 5% var healAmount = upgradeLevels.lifeDrain > 1 ? 20 : 10; // 20 vs 10 HP if (drainChance < chanceThreshold) { wizard.health = Math.min(wizard.health + healAmount, wizard.maxHealth); updateHealthBar(); // Visual feedback for life drain LK.effects.flashObject(wizard, 0x00FF00, 300); } } // Clear selected enemy if this was the target if (selectedEnemy === self) { selectedEnemy = null; } // Remove from enemies array for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); LK.setScore(LK.getScore() + 10); }; return self; }); var EnergyBeam = Container.expand(function () { var self = Container.call(this); // Create beam visual using projectile glow var beamGraphics = self.attachAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0 }); beamGraphics.tint = 0x00ffff; // Cyan color beamGraphics.alpha = 0.9; self.speed = 60; self.direction = { x: 0, y: 0 }; self.targetEnemy = null; self.lastIntersecting = false; self.update = function () { // Move toward target self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Rotate beam to point toward movement direction var angle = Math.atan2(self.direction.y, self.direction.x); beamGraphics.rotation = angle; // Remove if off screen if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.destroy(); return; } // Check collision with target enemy if (self.targetEnemy && self.targetEnemy.parent) { var currentIntersecting = self.intersects(self.targetEnemy); if (!self.lastIntersecting && currentIntersecting) { // Hit the target enemy // Calculate damage based on spell power upgrade var damage = 100; if (wizard && wizard.spellPowerLevel && wizard.spellPowerLevel > 0) { // Check if target is an ogre and we're before enemy 35 if (self.targetEnemy && self.targetEnemy.ogreFrames && enemyKillCounter < 35) { // Always kill ogres in one hit until enemy 35 damage = self.targetEnemy.health + 1; // Ensure one-hit kill } else { damage = 200; // Double damage when spell power is active } } self.targetEnemy.takeDamage(damage); self.destroy(); return; } self.lastIntersecting = currentIntersecting; } else { // Target destroyed, remove beam self.destroy(); } }; 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 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); 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 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 FireBall = Container.expand(function () { var self = Container.call(this); // Create fire ball visual using projectile glow with fire colors var fireBallGraphics = self.attachAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); fireBallGraphics.tint = 0xFF4500; // Orange-red fire color fireBallGraphics.alpha = 0.9; self.speed = 40; self.direction = { x: 0, y: 0 }; self.lastIntersecting = {}; self.update = function () { // Move fire ball self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Add fire flickering effect var flicker = 1 + Math.sin(LK.ticks * 0.4) * 0.3; fireBallGraphics.scaleX = 1.5 * flicker; fireBallGraphics.scaleY = 1.5 * flicker; // Rotate fire ball fireBallGraphics.rotation += 0.2; // Remove if off screen if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.destroy(); return; } // Check collision with all enemy types var allEnemies = enemies.concat(ogres).concat(knights); for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[i]; if (!self.lastIntersecting[i]) { self.lastIntersecting[i] = false; } var currentIntersecting = self.intersects(enemy); if (!self.lastIntersecting[i] && currentIntersecting) { // Hit enemy - deal fire damage var damage = 150; // High damage for fire ball enemy.takeDamage(damage); // Create fire explosion effect LK.effects.flashObject(enemy, 0xFF4500, 400); // Create additional fire visual 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; // Animate fire effect tween(fireEffect, { scaleX: 6, scaleY: 6, alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { fireEffect.destroy(); } }); self.destroy(); return; } self.lastIntersecting[i] = currentIntersecting; } }; 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: 4.0, scaleY: 4.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 PATHS TO CAST SPELLS\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: 1600, 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 = 1700; self.addChild(startButtonText); // Button interaction self.down = function (x, y, obj) { // Start the game by hiding menu self.startGame(); }; self.startGame = function () { // Hide menu and start game 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 LK.playMusic('medievalTheme', { volume: 0.7, fade: { start: 0, end: 0.7, duration: 2000 } }); }; return self; }); var Knight = Container.expand(function () { var self = Container.call(this); // Animation system for knight self.currentFrame = 1; self.animationTimer = 0; self.animationSpeed = 22; // Change frame every 22 ticks (367ms at 60fps) self.animationState = 'walking'; // walking, attacking, dying, idle // Create all knight graphics frames and store them self.knightFrames = []; for (var i = 1; i <= 4; i++) { var frameGraphics = self.attachAsset('knight' + i, { anchorX: 0.5, anchorY: 1.0, scaleX: 2.0, scaleY: 2.0 }); frameGraphics.visible = i === 1; // Only show first frame initially self.knightFrames.push(frameGraphics); } // Create invisible hitbox with double size var hitbox = self.attachAsset('knight1', { anchorX: 0.5, anchorY: 1.0, scaleX: 4, scaleY: 4 }); hitbox.alpha = 0; // Make hitbox invisible self.health = 3; self.maxHealth = 3; self.speed = 7; // Note: health, maxHealth and speed will be overridden by difficulty system self.lastX = 0; self.frozen = false; self.frozenTimer = 0; self.update = function () { // Animation system - cycle through knight frames based on state self.animationTimer++; var frameSpeed = self.animationSpeed; if (self.animationState === 'attacking') { frameSpeed = 12; // Faster animation for attacking } else if (self.animationState === 'dying') { frameSpeed = 28; // Slower animation for dying } else if (self.animationState === 'idle') { frameSpeed = 35; // Very slow animation for idle } if (self.animationTimer >= frameSpeed) { self.animationTimer = 0; // Hide current frame self.knightFrames[self.currentFrame - 1].visible = false; // Move to next frame based on animation state if (self.animationState === 'walking') { self.currentFrame++; if (self.currentFrame > 4) { self.currentFrame = 1; } } else if (self.animationState === 'attacking') { // Cycle through frames 2-4 for attacking self.currentFrame++; if (self.currentFrame > 4) { self.currentFrame = 2; } } else if (self.animationState === 'dying') { // Play frames 3-4 once for dying if (self.currentFrame < 4) { self.currentFrame++; } } else if (self.animationState === 'idle') { // Cycle between frames 1-2 for idle self.currentFrame = self.currentFrame === 1 ? 2 : 1; } // Show new frame self.knightFrames[self.currentFrame - 1].visible = true; } // Progressive speed increase - increase speed by 150% over 10 seconds if (!self.speedTweenStarted) { self.speedTweenStarted = true; var targetSpeed = self.speed * 2.5; // Increase speed by 150% tween(self, { speed: targetSpeed }, { duration: 10000, // 10 seconds easing: tween.easeOut }); } // Handle frozen state if (self.frozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.frozen = false; } return; // Skip movement when frozen } // Always move directly toward the wizard to ensure knights reach the player if (wizard) { // Calculate direction from knight to wizard var dx = wizard.x - self.x; var dy = wizard.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // Normalize direction and apply speed self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } // Face toward the wizard - flip horizontally based on wizard position if (dx < 0) { // Wizard is to the left - flip to face left for (var frameIdx = 0; frameIdx < self.knightFrames.length; frameIdx++) { self.knightFrames[frameIdx].scaleX = -2.0; // Flip to face left } } else { // Wizard is to the right - face right for (var frameIdx = 0; frameIdx < self.knightFrames.length; frameIdx++) { self.knightFrames[frameIdx].scaleX = 2.0; // Normal orientation to face right } } } else { // Fallback: move down if wizard doesn't exist self.y += self.speed; } }; self.takeDamage = function (damage) { self.health -= damage; // Change animation state to attacking when hit self.animationState = 'attacking'; // Flash red when hit LK.effects.flashObject(self, 0xFF0000, 200); // Create impact effect var impactEffect = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 0.8, scaleY: 0.8 })); impactEffect.tint = 0xFFD700; impactEffect.alpha = 0.9; // Animate impact effect with sparks tween(impactEffect, { scaleX: 3.0, scaleY: 3.0, alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { impactEffect.destroy(); } }); // Return to walking animation after brief attacking animation tween({}, {}, { duration: 500, onFinish: function onFinish() { if (self.animationState === 'attacking') { self.animationState = 'walking'; } } }); // Check if knight should die based on health if (self.health <= 0) { self.die(); } }; self.down = function (x, y, obj) { // Set this knight as the selected target selectedEnemy = self; // Visual feedback for selection LK.effects.flashObject(self, 0xFFFF00, 500); // Yellow flash to indicate selection // Create projectile from wizard to knight when knight is tapped if (wizard && projectiles.length < 10) { // Limit projectiles to prevent spam var projectile = game.addChild(new Projectile()); projectile.x = wizard.x; projectile.y = wizard.y; // Calculate direction from wizard to knight 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; // Set the target for this projectile projectiles.push(projectile); LK.getSound('spellCast').play(); } }; self.die = function () { // Change animation state to dying self.animationState = 'dying'; self.currentFrame = 3; // Start dying animation from frame 3 // Play pain sound when knight dies LK.getSound('painSound').play(); // Drop coin var coin = game.addChild(new Coin()); coin.x = self.x; coin.y = self.y - 50; coin.isAnimating = true; coins.push(coin); // Animate coin moving toward coin counter 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, easing: tween.easeOut, onFinish: function onFinish() { // Increment coin counter after animation coinCounter++; coinText.setText('Coins: ' + coinCounter); // Remove coin from array and destroy for (var i = coins.length - 1; i >= 0; i--) { if (coins[i] === coin) { coins.splice(i, 1); break; } } coin.destroy(); } }); // Increment enemy kill counter enemyKillCounter++; killCountText.setText('Puntuacion: ' + enemyKillCounter); // Add experience to wizard wizard.gainExperience(25); // Life drain upgrade - 5% chance to restore 10 health if (wizard.lifeDrainLevel && wizard.lifeDrainLevel > 0) { var drainChance = Math.random(); if (drainChance < 0.05) { // 5% chance wizard.health = Math.min(wizard.health + 10, wizard.maxHealth); updateHealthBar(); // Visual feedback for life drain LK.effects.flashObject(wizard, 0x00FF00, 300); } } // Clear selected enemy if this was the target if (selectedEnemy === self) { selectedEnemy = null; } // Remove from knights array for (var i = knights.length - 1; i >= 0; i--) { if (knights[i] === self) { knights.splice(i, 1); break; } } self.destroy(); LK.setScore(LK.getScore() + 20); }; return self; }); var Ogre = Container.expand(function () { var self = Container.call(this); // Animation system for ogre self.currentFrame = 1; self.animationTimer = 0; self.animationSpeed = 20; // Change frame every 20 ticks (333ms at 60fps) self.animationState = 'walking'; // walking, attacking, dying, idle // Create all ogre graphics frames and store them self.ogreFrames = []; for (var i = 1; i <= 4; i++) { var frameGraphics = self.attachAsset('ogre' + i, { anchorX: 0.5, anchorY: 1.0, scaleX: 2.0, scaleY: 2.0 }); frameGraphics.visible = i === 1; // Only show first frame initially self.ogreFrames.push(frameGraphics); } // Create invisible hitbox with double size var hitbox = self.attachAsset('ogre1', { anchorX: 0.5, anchorY: 1.0, scaleX: 4, scaleY: 4 }); hitbox.alpha = 0; // Make hitbox invisible self.health = 2; self.maxHealth = 2; self.speed = 7; // Note: health, maxHealth and speed will be overridden by difficulty system self.lastX = 0; self.frozen = false; self.frozenTimer = 0; self.update = function () { // Animation system - cycle through ogre frames based on state self.animationTimer++; var frameSpeed = self.animationSpeed; if (self.animationState === 'attacking') { frameSpeed = 10; // Faster animation for attacking } else if (self.animationState === 'dying') { frameSpeed = 25; // Slower animation for dying } else if (self.animationState === 'idle') { frameSpeed = 30; // Very slow animation for idle } if (self.animationTimer >= frameSpeed) { self.animationTimer = 0; // Hide current frame self.ogreFrames[self.currentFrame - 1].visible = false; // Move to next frame based on animation state if (self.animationState === 'walking') { self.currentFrame++; if (self.currentFrame > 4) { self.currentFrame = 1; } } else if (self.animationState === 'attacking') { // Cycle through frames 2-4 for attacking self.currentFrame++; if (self.currentFrame > 4) { self.currentFrame = 2; } } else if (self.animationState === 'dying') { // Play frames 3-4 once for dying if (self.currentFrame < 4) { self.currentFrame++; } } else if (self.animationState === 'idle') { // Cycle between frames 1-2 for idle self.currentFrame = self.currentFrame === 1 ? 2 : 1; } // Show new frame self.ogreFrames[self.currentFrame - 1].visible = true; } // Progressive speed increase - increase speed by 150% over 10 seconds if (!self.speedTweenStarted) { self.speedTweenStarted = true; var targetSpeed = self.speed * 2.5; // Increase speed by 150% tween(self, { speed: targetSpeed }, { duration: 10000, // 10 seconds easing: tween.easeOut }); } // Handle frozen state if (self.frozen) { self.frozenTimer--; if (self.frozenTimer <= 0) { self.frozen = false; } return; // Skip movement when frozen } // Always move directly toward the wizard to ensure ogres reach the player if (wizard) { // Calculate direction from ogre to wizard var dx = wizard.x - self.x; var dy = wizard.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // Normalize direction and apply speed self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } // Face toward the wizard - flip horizontally based on wizard position if (dx < 0) { // Wizard is to the left - flip to face left for (var frameIdx = 0; frameIdx < self.ogreFrames.length; frameIdx++) { self.ogreFrames[frameIdx].scaleX = -2.0; // Flip to face left } } else { // Wizard is to the right - face right for (var frameIdx = 0; frameIdx < self.ogreFrames.length; frameIdx++) { self.ogreFrames[frameIdx].scaleX = 2.0; // Normal orientation to face right } } } else { // Fallback: move down if wizard doesn't exist self.y += self.speed; } }; self.takeDamage = function (damage) { self.health -= damage; // Change animation state to attacking when hit self.animationState = 'attacking'; // Flash red when hit LK.effects.flashObject(self, 0xFF0000, 200); // Create impact effect var impactEffect = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 1.0, scaleY: 1.0 })); impactEffect.tint = 0xFF4500; impactEffect.alpha = 1.0; // Animate impact effect with explosion tween(impactEffect, { scaleX: 4.0, scaleY: 4.0, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { impactEffect.destroy(); } }); // Return to walking animation after brief attacking animation tween({}, {}, { duration: 400, onFinish: function onFinish() { if (self.animationState === 'attacking') { self.animationState = 'walking'; } } }); // Check if ogre should die based on health if (self.health <= 0) { self.die(); } }; self.down = function (x, y, obj) { // Set this ogre as the selected target selectedEnemy = self; // Visual feedback for selection LK.effects.flashObject(self, 0xFFFF00, 500); // Yellow flash to indicate selection // Create projectile from wizard to ogre when ogre is tapped if (wizard && projectiles.length < 10) { // Limit projectiles to prevent spam var projectile = game.addChild(new Projectile()); projectile.x = wizard.x; projectile.y = wizard.y; // Calculate direction from wizard to ogre 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; // Set the target for this projectile projectiles.push(projectile); LK.getSound('spellCast').play(); } }; self.die = function () { // Change animation state to dying self.animationState = 'dying'; self.currentFrame = 3; // Start dying animation from frame 3 // Play pain sound when ogre dies LK.getSound('painSound').play(); // Drop coin var coin = game.addChild(new Coin()); coin.x = self.x; coin.y = self.y - 50; coin.isAnimating = true; coins.push(coin); // Animate coin moving toward coin counter 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, easing: tween.easeOut, onFinish: function onFinish() { // Increment coin counter after animation coinCounter++; coinText.setText('Coins: ' + coinCounter); // Remove coin from array and destroy for (var i = coins.length - 1; i >= 0; i--) { if (coins[i] === coin) { coins.splice(i, 1); break; } } coin.destroy(); } }); // Increment enemy kill counter enemyKillCounter++; killCountText.setText('Puntuacion: ' + enemyKillCounter); // Add experience to wizard wizard.gainExperience(25); // Life drain upgrade - 5% chance to restore 10 health if (wizard.lifeDrainLevel && wizard.lifeDrainLevel > 0) { var drainChance = Math.random(); if (drainChance < 0.05) { // 5% chance wizard.health = Math.min(wizard.health + 10, wizard.maxHealth); updateHealthBar(); // Visual feedback for life drain LK.effects.flashObject(wizard, 0x00FF00, 300); } } // Clear selected enemy if this was the target if (selectedEnemy === self) { selectedEnemy = null; } // Remove from ogres array for (var i = ogres.length - 1; i >= 0; i--) { if (ogres[i] === self) { ogres.splice(i, 1); break; } } self.destroy(); LK.setScore(LK.getScore() + 15); }; return self; }); var Orb = Container.expand(function () { var self = Container.call(this); // Create orb visual using energy sphere var orbGraphics = self.attachAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4 }); orbGraphics.tint = 0xFFD700; // Golden color for orbs orbGraphics.alpha = 0.9; self.orbitalAngle = 0; // Starting angle for this orb self.orbitalRadius = 880; // Distance from wizard - doubled again for even more separation self.rotationSpeed = 0.025; // How fast orbs rotate - halved for slower movement self.update = function () { // 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 var allEnemies = enemies.concat(ogres).concat(knights); for (var i = 0; i < allEnemies.length; i++) { var enemy = allEnemies[i]; if (self.intersects(enemy)) { // Deal damage to enemy on contact enemy.takeDamage(50); // 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(); } }); } } }; return self; }); var Projectile = Container.expand(function () { var self = Container.call(this); // Create glowing light effect behind projectile var lightGlow = self.attachAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 2.0 }); lightGlow.alpha = 0.15; lightGlow.tint = 0x44aaff; // Blue-white glow // 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 }); var projectileGraphics = self.attachAsset('projectile', { anchorX: 0.5, anchorY: 0.5, scaleX: 3.0, scaleY: 3.0 }); self.speed = 50; self.direction = { x: 0, y: 0 }; self.lastIntersecting = {}; self.targetEnemy = null; // The specific enemy this projectile should damage self.update = function () { // Move projectile self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Rotate projectile to point towards movement direction var angle = Math.atan2(self.direction.y, self.direction.x); projectileGraphics.rotation = angle + Math.PI / 2; // Add 90 degrees so top points forward // Add pulsing light effect if (lightGlow && lightGlow.parent) { var pulse = 1 + Math.sin(LK.ticks * 0.3) * 0.4; lightGlow.scaleX = 2.0 * pulse; lightGlow.scaleY = 2.0 * pulse; var alphaFlicker = 0.1 + Math.sin(LK.ticks * 0.5) * 0.08; lightGlow.alpha = alphaFlicker; } // Remove if off screen if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { self.removeFromGame(); return; } // Only check collision with the target enemy if one is set if (self.targetEnemy && self.targetEnemy.parent) { if (!self.lastIntersecting.target) { self.lastIntersecting.target = false; } var currentTargetIntersecting = self.intersects(self.targetEnemy); if (!self.lastIntersecting.target && currentTargetIntersecting) { // Hit the target enemy // Calculate damage based on spell power upgrade var damage = 100; if (wizard && wizard.spellPowerLevel && wizard.spellPowerLevel > 0) { // Check if target is an ogre and we're before enemy 35 if (self.targetEnemy && self.targetEnemy.ogreFrames && enemyKillCounter < 35) { // Always kill ogres in one hit until enemy 35 damage = self.targetEnemy.health + 1; // Ensure one-hit kill } else { damage = 200; // Double damage when spell power is active } } self.targetEnemy.takeDamage(damage); self.removeFromGame(); return; } self.lastIntersecting.target = currentTargetIntersecting; } }; self.removeFromGame = function () { // 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 ScreenShake = Container.expand(function () { var self = Container.call(this); self.shake = function (intensity, duration) { intensity = intensity || 10; duration = duration || 300; var originalX = game.x; var originalY = game.y; var shakeTimer = 0; var shakeInterval = LK.setInterval(function () { shakeTimer += 16; // Approximate frame time if (shakeTimer >= duration) { game.x = originalX; game.y = originalY; LK.clearInterval(shakeInterval); return; } var fadeIntensity = intensity * (1 - shakeTimer / duration); game.x = originalX + (Math.random() - 0.5) * fadeIntensity; game.y = originalY + (Math.random() - 0.5) * fadeIntensity; }, 16); }; return self; }); var SpellTrail = Container.expand(function () { var self = Container.call(this); self.init = function (startX, startY, endX, endY, color) { self.x = startX; self.y = startY; // Create trail segments var segments = 10; for (var i = 0; i < segments; i++) { var segment = self.attachAsset('energySphere', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.2 - i * 0.02, scaleY: 0.2 - i * 0.02 }); segment.tint = color || 0x44aaff; segment.alpha = 1 - i * 0.1; var progress = i / segments; segment.x = (endX - startX) * progress; segment.y = (endY - startY) * progress; // Fade out segments with delay tween(segment, { alpha: 0, scaleX: 0, scaleY: 0 }, { duration: 300, delay: i * 30, easing: tween.easeOut }); } // Clean up after animation tween({}, {}, { duration: 600, onFinish: function onFinish() { 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 = []; // Upgrade options - expanded to 10 upgrades var upgradeOptions = [{ name: 'SHIELD', description: 'Block 1 attack' }, { name: 'HEALTH BOOST', description: 'More health' }, { name: 'LIFE DRAIN', description: '5% chance +10 HP' }, { name: 'ENERGY SPHERE', description: 'Auto-attack every 3s' }, { name: 'FORCE PUSH', description: 'Push enemies back' }, { name: 'SPELL POWER', description: 'Double damage' }, { name: 'REGENERATION', description: 'Heal 5 HP/sec' }, { name: 'FIRE BALL', description: 'Launch fire ball every 3s' }, { name: 'FREEZE PULSE', description: 'Freeze all enemies' }, { name: 'ORBS', description: 'Orbs rotate around player' }]; // Create upgrade buttons in 2x5 grid layout for (var i = 0; i < upgradeOptions.length; i++) { var upgrade = upgradeOptions[i]; // Calculate position in 2-row, 5-column grid var col = i % 5; // Column (0-4) var row = Math.floor(i / 5); // Row (0-1) var xPos = 2048 / 2 - 800 + col * 400; // Space 5 upgrades across screen width var yPos = 1200 + row * 300; // Two rows with vertical spacing // Upgrade button background var upgradeBtn = self.attachAsset('pathSelector', { anchorX: 0.5, anchorY: 0.5, x: xPos, y: yPos, scaleX: 5, scaleY: 3.5 }); upgradeBtn.upgradeIndex = i; upgradeBtn.tint = 0x8B008B; // Upgrade text var upgradeText = new Text2(upgrade.name + '\n' + upgrade.description + '\nCost: ' + self.upgradeCost + ' coins', { size: 35, fill: 0xFFFFFF, font: "monospace" }); upgradeText.anchor.set(0.5, 0.5); upgradeText.x = xPos; upgradeText.y = yPos; upgradeText.upgradeIndex = i; // Store upgrade index for reference 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 update upgrade text with new costs and show improved versions self.updateUpgradeCosts = function () { var upgradeOptions = [{ name: 'SHIELD', key: 'shield', baseDesc: 'Block 1 attack', improvedDesc: 'Block 2 attacks + regen' }, { name: 'HEALTH BOOST', key: 'healthBoost', baseDesc: 'More health', improvedDesc: 'Much more health' }, { name: 'LIFE DRAIN', key: 'lifeDrain', baseDesc: '5% chance +10 HP', improvedDesc: '10% chance +20 HP' }, { name: 'ENERGY SPHERE', key: 'energySphere', baseDesc: 'Auto-attack every 3s', improvedDesc: 'Auto-attack every 2s' }, { name: 'FORCE PUSH', key: 'forcePush', baseDesc: 'Push enemies back', improvedDesc: 'Stronger push + damage' }, { name: 'SPELL POWER', key: 'spellPower', baseDesc: 'Double damage', improvedDesc: 'Triple damage' }, { name: 'REGENERATION', key: 'regeneration', baseDesc: 'Heal 5 HP/sec', improvedDesc: 'Heal 10 HP/sec' }, { name: 'FIRE BALL', key: 'fireBall', baseDesc: 'Launch fire ball every 3s', improvedDesc: 'Launch fire ball every 2s' }, { name: 'FREEZE PULSE', key: 'freezePulse', baseDesc: 'Freeze all enemies', improvedDesc: 'Freeze longer + damage' }, { name: 'ORBS', key: 'orbs', baseDesc: 'Orbs rotate around player', improvedDesc: 'More orbs + faster rotation' }]; for (var i = 0; i < self.upgradeTextElements.length; i++) { var textElement = self.upgradeTextElements[i]; var upgrade = upgradeOptions[i]; var currentLevel = upgradeLevels[upgrade.key]; // Show improved version if already purchased var description = currentLevel > 0 ? upgrade.improvedDesc : upgrade.baseDesc; var levelText = currentLevel > 0 ? ' (LV' + (currentLevel + 1) + ')' : ''; textElement.setText(upgrade.name + levelText + '\n' + description + '\nCost: ' + self.upgradeCost + ' coins'); textElement.upgradeName = upgrade.name; textElement.upgradeDescription = description; } }; // 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 grid position (2x5 layout) var upgradeIndex = -1; // Check which grid cell was clicked for (var i = 0; i < 10; i++) { var col = i % 5; // Column (0-4) var row = Math.floor(i / 5); // Row (0-1) var cellX = 2048 / 2 - 800 + col * 400; // Calculate cell center X var cellY = 1200 + row * 300; // Calculate cell center Y var cellWidth = 300; // Touch area width var cellHeight = 200; // 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 = i; break; } } // Check if upgrade button was clicked if (upgradeIndex !== -1) { // Close menu immediately when any upgrade option is selected self.closeMenu(); var currentCost = self.upgradeCost || 5; // Use dynamic cost or default to 5 if (coinCounter >= currentCost) { coinCounter -= currentCost; coinText.setText('Coins: ' + coinCounter); // Apply upgrade based on index switch (upgradeIndex) { case 0: // Shield upgrade upgradeLevels.shield++; if (upgradeLevels.shield === 1) { // First level: Basic shield wizard.shieldActive = true; wizard.maxShieldHits = 1; wizard.currentShieldHits = 0; } else { // Improved level: Double shield + regeneration wizard.shieldActive = true; wizard.maxShieldHits = 2; wizard.currentShieldHits = 0; wizard.shieldRegen = true; // Enable shield regeneration } // Visual feedback for shield activation LK.effects.flashObject(wizard, 0x00BFFF, 500); // Add shield activation animation effect 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 }); } }); break; case 1: // Health Boost upgrade upgradeLevels.healthBoost++; var healthIncrease = upgradeLevels.healthBoost === 1 ? 30 : 50; wizard.maxHealth += healthIncrease; wizard.health = Math.min(wizard.health + healthIncrease, wizard.maxHealth); updateHealthBar(); break; case 2: // Life Drain upgrade upgradeLevels.lifeDrain++; if (!wizard.lifeDrainLevel) { wizard.lifeDrainLevel = 0; } wizard.lifeDrainLevel = upgradeLevels.lifeDrain; break; case 3: // Energy Sphere upgrade upgradeLevels.energySphere++; if (upgradeLevels.energySphere === 1) { // First level: Create energy sphere if (!wizard.energySphere) { wizard.energySphere = game.addChild(new EnergyOrb()); } } // Visual feedback for energy sphere activation LK.effects.flashObject(wizard, 0x00FFFF, 500); break; case 4: // Force Push upgrade upgradeLevels.forcePush++; if (!wizard.forcePushLevel) { wizard.forcePushLevel = 0; wizard.forcePushTimer = 0; } wizard.forcePushLevel = upgradeLevels.forcePush; // Visual feedback for force push LK.effects.flashObject(wizard, 0xFFFF00, 500); break; case 5: // Spell Power upgrade upgradeLevels.spellPower++; if (!wizard.spellPowerLevel) { wizard.spellPowerLevel = 0; } wizard.spellPowerLevel = upgradeLevels.spellPower; // Visual feedback for spell power LK.effects.flashObject(wizard, 0xFF4500, 500); break; case 6: // Regeneration upgrade upgradeLevels.regeneration++; if (!wizard.regenerationLevel) { wizard.regenerationLevel = 0; } wizard.regenerationLevel = upgradeLevels.regeneration; // Visual feedback for regeneration LK.effects.flashObject(wizard, 0x90EE90, 500); break; case 7: // Fire Ball upgrade upgradeLevels.fireBall++; if (!wizard.fireBallLevel) { wizard.fireBallLevel = 0; wizard.fireBallTimer = 0; } wizard.fireBallLevel = upgradeLevels.fireBall; // Visual feedback for fire ball LK.effects.flashObject(wizard, 0xFF4500, 500); break; case 8: // Freeze Pulse upgrade upgradeLevels.freezePulse++; if (!wizard.freezePulseLevel) { wizard.freezePulseLevel = 0; wizard.freezePulseTimer = 0; } wizard.freezePulseLevel = upgradeLevels.freezePulse; // Visual feedback for freeze pulse LK.effects.flashObject(wizard, 0x87CEEB, 500); break; case 9: // Orbs upgrade upgradeLevels.orbs++; if (!wizard.orbLevel) { wizard.orbLevel = 0; wizard.orbs = []; // Array to store orb instances } wizard.orbLevel++; // Create new orb var newOrb = game.addChild(new Orb()); // Set starting angle based on number of orbs for even distribution newOrb.orbitalAngle = wizard.orbs.length * (Math.PI * 2) / (wizard.orbLevel + 1); wizard.orbs.push(newOrb); // Redistribute existing orbs evenly for (var orbIdx = 0; orbIdx < wizard.orbs.length; orbIdx++) { wizard.orbs[orbIdx].orbitalAngle = orbIdx * (Math.PI * 2) / wizard.orbs.length; } // Visual feedback for orb upgrade LK.effects.flashObject(wizard, 0xFFD700, 500); break; } // Visual feedback - flash the upgrade area green LK.effects.flashScreen(0x00FF00, 300); // Save upgrade progress saveGameData(); checkAchievements(); } 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 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 self.update = function () { // 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(); } } // Regeneration upgrade - heal over time with improved rate if (self.regenerationLevel && self.regenerationLevel > 0) { if (!self.regenTimer) { self.regenTimer = 0; } self.regenTimer++; if (self.regenTimer >= 60) { // Every second self.regenTimer = 0; var healAmount = upgradeLevels.regeneration > 1 ? 10 : 5; // Improved healing self.health = Math.min(self.health + healAmount, self.maxHealth); updateHealthBar(); // Visual feedback for regeneration LK.effects.flashObject(self, 0x90EE90, 200); } } // 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 trail effect var spellTrail = game.addChild(new SpellTrail()); spellTrail.init(self.x, self.y, spellX, spellY, 0xFF69B4); // 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 })); // Enhanced spell effect with multiple layers var spellGlow = game.addChild(LK.getAsset('projectileGlow', { anchorX: 0.5, anchorY: 0.5, x: spellX, y: spellY, scaleX: 1.5, scaleY: 1.5 })); spellGlow.tint = 0xFF69B4; spellGlow.alpha = 0.6; // Animate spell with magical effects tween(spell, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { spell.destroy(); } }); // Animate glow effect tween(spellGlow, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { spellGlow.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; // Game over when health reaches 0 LK.effects.flashScreen(0xFF0000, 1000); // Save final game data gamesPlayed++; saveGameData(); LK.showGameOver(); } // Update health bar updateHealthBar(); // 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); 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 // Freeze all enemies with improved effects var allEnemies = enemies.concat(ogres).concat(knights); 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 }); // 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.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 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); 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(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 // Black background for pixel art }); /**** * Game Code ****/ // 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 // Load persistent data from storage var persistentData = storage || {}; var totalCoinsEarned = persistentData.totalCoinsEarned || 0; var gamesPlayed = persistentData.gamesPlayed || 0; var highScore = persistentData.highScore || 0; var achievements = persistentData.achievements || {}; // Upgrade level tracking system with persistence var upgradeLevels = persistentData.upgradeLevels || { shield: 0, healthBoost: 0, lifeDrain: 0, energySphere: 0, forcePush: 0, spellPower: 0, regeneration: 0, fireBall: 0, freezePulse: 0, orbs: 0 }; // Achievement system var achievementList = [{ id: 'firstKill', name: 'First Blood', description: 'Kill your first enemy', threshold: 1 }, { id: 'survivor', name: 'Survivor', description: 'Kill 50 enemies', threshold: 50 }, { id: 'slayer', name: 'Slayer', description: 'Kill 100 enemies', threshold: 100 }, { id: 'coinCollector', name: 'Coin Collector', description: 'Collect 1000 coins total', threshold: 1000 }, { id: 'upgraded', name: 'Upgraded', description: 'Purchase your first upgrade', threshold: 1 }]; function checkAchievements() { for (var i = 0; i < achievementList.length; i++) { var achievement = achievementList[i]; if (!achievements[achievement.id]) { var completed = false; switch (achievement.id) { case 'firstKill': case 'survivor': case 'slayer': completed = enemyKillCounter >= achievement.threshold; break; case 'coinCollector': completed = totalCoinsEarned >= achievement.threshold; break; case 'upgraded': var hasUpgrade = false; for (var key in upgradeLevels) { if (upgradeLevels[key] > 0) { hasUpgrade = true; break; } } completed = hasUpgrade; break; } if (completed) { achievements[achievement.id] = true; // Show achievement notification var achievementText = new Text2('ACHIEVEMENT: ' + achievement.name, { size: 80, fill: 0xFFD700, font: "monospace" }); achievementText.anchor.set(0.5, 0.5); achievementText.x = 2048 / 2; achievementText.y = 400; LK.gui.center.addChild(achievementText); // Animate achievement tween(achievementText, { y: 300, alpha: 0 }, { duration: 3000, easing: tween.easeOut, onFinish: function onFinish() { achievementText.destroy(); } }); // Save achievement storage.achievements = achievements; } } } } function saveGameData() { storage.upgradeLevels = upgradeLevels; storage.totalCoinsEarned = totalCoinsEarned; storage.gamesPlayed = gamesPlayed; storage.highScore = Math.max(highScore, enemyKillCounter); storage.achievements = achievements; } // Game arrays to track objects var enemies = []; var ogres = []; var knights = []; var coins = []; var projectiles = []; // Create and show game menu gameMenu = game.addChild(new GameMenu()); // Create upgrade menu (initially hidden) upgradeMenu = game.addChild(new UpgradeMenu()); upgradeMenu.visible = false; // Create 5 stone paths leading toward the wizard position var paths = []; var knightX = 2048 / 2; var knightY = 2732 - 250; // Create paths: all pointing toward the wizard position var pathAngles = [-Math.PI / 2, // Center (straight down toward wizard) -Math.PI / 3, // Diagonal right toward wizard -2 * Math.PI / 3, // Diagonal left toward wizard Math.PI / 6, // Path 4: opposite to path 2 (diagonal left) 5 * Math.PI / 6 // Path 5: opposite to path 3 (diagonal right) ]; // 5 paths: all directed toward wizard position // Create multiple stone segments for each path to form visible stone roads for (var p = 0; p < 5; p++) { var angle = pathAngles[p]; // Calculate distance from spawn point to wizard position var wizardX = knightX; var wizardY = 2732 - 600; // Wizard position var spawnDistance = p === 0 ? 2000 : p === 3 || p === 4 ? 1200 : 1800; // Distance from wizard to spawn point // Set spawn points to screen edges for all paths var spawnX, spawnY; if (p === 0) { // Center path - spawn at top edge spawnX = 2048 / 2; spawnY = -100; } else if (p === 1) { // Path 2 - spawn at top right edge spawnX = 2048 + 50; spawnY = -50; } else if (p === 2) { // Path 3 - spawn at top left edge spawnX = -50; spawnY = -50; } else if (p === 3) { // Path 4 - spawn at left edge spawnX = -100; spawnY = 2732 / 2 + 400; } else if (p === 4) { // Path 5 - spawn at right edge spawnX = 2048 + 100; spawnY = 2732 / 2 + 400; } // Calculate actual path length from spawn point to wizard var actualPathLength = Math.sqrt((spawnX - wizardX) * (spawnX - wizardX) + (spawnY - wizardY) * (spawnY - wizardY)); var segmentSize = 120; // Size of each stone segment var numSegments = Math.floor(actualPathLength / segmentSize); // Create individual stone segments along the path from spawn point toward wizard for (var s = 0; s < numSegments; s++) { var segmentDistance = s * segmentSize + segmentSize / 2; var segmentX = spawnX - Math.cos(angle) * segmentDistance; var segmentY = spawnY - Math.sin(angle) * segmentDistance; // Only create segments that are within the visible game area if (segmentX >= -100 && segmentX <= 2148 && segmentY >= -100 && segmentY <= 2832) { // Create stone path segment 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 // Rotate stone to align with path })); // Make stone paths completely invisible stoneSegment.alpha = 0; // Make completely invisible stoneSegment.visible = false; // Initially hidden, will be shown when game starts stoneSegment.pathIndex = p; } } } // Create invisible path collision areas for touch detection for (var p = 0; p < 5; p++) { var angle = pathAngles[p]; // Calculate distance from spawn point to wizard position var wizardX = knightX; var wizardY = 2732 - 600; // Wizard position var spawnDistance = p === 0 ? 3000 : p === 3 || p === 4 ? 1200 : 1800; // Distance from wizard to spawn point var spawnX = wizardX + Math.cos(angle) * spawnDistance; var spawnY = wizardY + Math.sin(angle) * spawnDistance; // Move all spawn points to screen edges if (p === 0) { // Center path - spawn at top edge spawnX = 2048 / 2; spawnY = -100; } else if (p === 1) { // Path 2 - spawn at top right edge spawnX = 2048 + 50; spawnY = -50; } else if (p === 2) { // Path 3 - spawn at top left edge spawnX = -50; spawnY = -50; } else if (p === 3) { // Path 4 - spawn at left edge spawnX = -100; spawnY = 2732 / 2 + 400; } else if (p === 4) { // Path 5 - spawn at right edge spawnX = 2048 + 100; spawnY = 2732 / 2 + 400; } // Calculate actual path length from spawn point to wizard var actualPathLength = Math.sqrt((spawnX - wizardX) * (spawnX - wizardX) + (spawnY - wizardY) * (spawnY - wizardY)); var centerX = (spawnX + wizardX) / 2; var centerY = (spawnY + wizardY) / 2; // Create invisible collision path 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 })); // Make collision path completely invisible path.alpha = 0; path.visible = false; path.pathIndex = p; // Add numbered text label for each path var pathNumber = new Text2((p + 1).toString(), { size: 120, fill: 0xFFD700, font: "monospace" }); pathNumber.anchor.set(0.5, 0.5); // Position number directly at spawn point pathNumber.x = spawnX; pathNumber.y = spawnY - 80; // Position slightly above spawn point pathNumber.visible = false; // Initially hidden, will be shown when game starts pathNumber.pathIndex = p; game.addChild(pathNumber); // Add touch handler for directional attacks path.down = function (x, y, obj) { // Attack in this path's direction wizard.attack(obj.pathIndex); // Visual feedback - no flash for invisible paths // Paths remain invisible when tapped }; paths.push(path); } // 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: 60, fill: 0xFFD700, font: "monospace" }); coinText.anchor.set(0, 0); LK.gui.topLeft.addChild(coinText); coinText.x = 120; coinText.y = 90; coinText.visible = false; var killCountText = new Text2('Puntuacion: 0', { size: 60, fill: 0xFF6B6B, font: "monospace" }); killCountText.anchor.set(0, 0); LK.gui.topLeft.addChild(killCountText); killCountText.x = 120; killCountText.y = 150; killCountText.visible = false; var tapText = new Text2('TAP TO CAST SPELLS!', { size: 100, fill: 0xFF6B6B, font: "monospace" }); tapText.anchor.set(0.5, 0.5); LK.gui.center.addChild(tapText); tapText.y = -200; tapText.visible = false; // Health bar UI var healthBarBg = LK.getAsset('healthBarBg', { anchorX: 0, anchorY: 0 }); LK.gui.topLeft.addChild(healthBarBg); healthBarBg.x = 120; healthBarBg.y = 20; healthBarBg.visible = false; var healthBar = LK.getAsset('healthBar', { anchorX: 0, anchorY: 0 }); LK.gui.topLeft.addChild(healthBar); healthBar.x = 120; healthBar.y = 20; healthBar.visible = false; var healthText = new Text2('Health: 100/100', { size: 50, fill: 0xFFFFFF, font: "monospace" }); healthText.anchor.set(0, 0); LK.gui.topLeft.addChild(healthText); healthText.x = 120; healthText.y = 50; 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 // Game input handling game.down = function (x, y, obj) { // Check if a path was tapped for directional attack var pathTapped = false; for (var p = 0; p < paths.length; p++) { var path = paths[p]; // Convert tap position to path's local coordinates var localPos = path.toLocal({ x: x, y: y }); // Check if tap is within path bounds if (Math.abs(localPos.x) < path.width / 2 && Math.abs(localPos.y) < path.height / 2) { // Attack in this path's direction wizard.attack(path.pathIndex); pathTapped = true; break; } } // No default attack - player must tap a path to attack }; // 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); }); // Only update game logic if game has started if (!gameStarted) { return; } // Dynamic music system based on intensity var currentEnemyCount = enemies.length + ogres.length + knights.length; var intensity = Math.min(currentEnemyCount / 10, 1); // 0 to 1 based on enemy count // Change music to epic battle theme at 10 enemies killed if (enemyKillCounter === 10) { LK.playMusic('epicBattle', { volume: 0.8 + intensity * 0.2, fade: { start: 0, end: 0.8 + intensity * 0.2, duration: 1500 } }); } // Change to mystical ambient music at 25 enemies killed if (enemyKillCounter === 25) { LK.playMusic('mysticalAmbient', { volume: 0.6 + intensity * 0.3, fade: { start: 0, end: 0.6 + intensity * 0.3, duration: 2000 } }); } // Check achievements periodically if (LK.ticks % 60 === 0) { // Every second checkAchievements(); } // 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; } } // Progressive difficulty system based on kills var difficultyLevel = Math.floor(enemyKillCounter / 10); // Every 10 kills increases difficulty var currentSpawnRate = Math.max(30, 90 - difficultyLevel * 8); // Faster spawning over time var enemyHealthMultiplier = 1 + difficultyLevel * 0.3; // 30% more health per difficulty level var enemySpeedMultiplier = 1 + difficultyLevel * 0.35; // 35% faster per difficulty level // Spawn enemies enemySpawnTimer++; if (enemySpawnTimer >= currentSpawnRate) { enemySpawnTimer = 0; var enemy = game.addChild(new Enemy()); // Apply difficulty scaling to enemy stats enemy.health = 100; enemy.maxHealth = enemy.health; enemy.speed = 3 * enemySpeedMultiplier; // Spawn enemy at a random path entrance var randomPathIndex; if (enemyKillCounter < 5) { // First 5 enemies spawn from center path only randomPathIndex = 0; // Center path } else { // After 5 enemies, spawn from any path but limit consecutive spawns with cooldown system // Build available paths list var availablePaths = []; for (var pathIdx = 0; pathIdx < 5; pathIdx++) { // If path has spawned less than 2 consecutive times, it's available if (pathConsecutiveSpawns[pathIdx] < 2) { availablePaths.push(pathIdx); } } // If no paths available, reset ALL paths to ensure balanced distribution if (availablePaths.length === 0) { // Reset all paths' consecutive spawn counters for (var pathIdx = 0; pathIdx < 5; pathIdx++) { pathConsecutiveSpawns[pathIdx] = 0; availablePaths.push(pathIdx); } } randomPathIndex = availablePaths[Math.floor(Math.random() * availablePaths.length)]; } // Update path-specific spawn tracking // Always increment consecutive spawns for the selected path pathConsecutiveSpawns[randomPathIndex]++; // Update path spawn time and global tracking pathLastSpawnTime[randomPathIndex] = LK.ticks; lastSpawnedPath = randomPathIndex; consecutiveSpawns = pathConsecutiveSpawns[randomPathIndex]; var pathAngle = pathAngles[randomPathIndex]; // Store the path information on the enemy enemy.pathIndex = randomPathIndex; enemy.pathAngle = pathAngle; // Calculate spawn position at screen edges if (randomPathIndex === 0) { // Center path - spawn at top edge enemy.x = 2048 / 2; enemy.y = -100; } else if (randomPathIndex === 1) { // Path 2 - spawn at top right edge enemy.x = 2048 + 50; enemy.y = -50; } else if (randomPathIndex === 2) { // Path 3 - spawn at top left edge enemy.x = -50; enemy.y = -50; } else if (randomPathIndex === 3) { // Path 4 - spawn at left edge enemy.x = -100; enemy.y = 2732 / 2 + 400; } else if (randomPathIndex === 4) { // Path 5 - spawn at right edge enemy.x = 2048 + 100; enemy.y = 2732 / 2 + 400; } // Make sure enemies spawn within screen bounds 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; enemies.push(enemy); } // Spawn ogres after 15 seconds (900 ticks at 60fps) if (LK.ticks >= 900 && LK.ticks % 180 === 0) { // Every 3 seconds after 15 seconds var ogre = game.addChild(new Ogre()); // Apply difficulty scaling to ogre stats ogre.health = 200; // Ogres have more base health ogre.maxHealth = ogre.health; ogre.speed = 2.5 * enemySpeedMultiplier; // Ogres are slightly slower // Spawn ogre at a random path entrance var ogrePathIndex = Math.floor(Math.random() * 5); var ogrePathAngle = pathAngles[ogrePathIndex]; // Store the path information on the ogre ogre.pathIndex = ogrePathIndex; ogre.pathAngle = ogrePathAngle; // Calculate spawn position at screen edges if (ogrePathIndex === 0) { // Center path - spawn at top edge ogre.x = 2048 / 2; ogre.y = -100; } else if (ogrePathIndex === 1) { // Path 2 - spawn at top right edge ogre.x = 2048 + 50; ogre.y = -50; } else if (ogrePathIndex === 2) { // Path 3 - spawn at top left edge ogre.x = -50; ogre.y = -50; } else if (ogrePathIndex === 3) { // Path 4 - spawn at left edge ogre.x = -100; ogre.y = 2732 / 2 + 400; } else if (ogrePathIndex === 4) { // Path 5 - spawn at right edge ogre.x = 2048 + 100; ogre.y = 2732 / 2 + 400; } // Make sure ogres spawn within screen bounds ogre.x = Math.max(50, Math.min(1998, ogre.x)); ogre.y = Math.max(-200, Math.min(2732 + 100, ogre.y)); ogre.lastX = ogre.x; ogres.push(ogre); } // Spawn knights after 30 enemies killed if (enemyKillCounter >= 30 && LK.ticks % 300 === 0) { // Every 5 seconds after 30th enemy var knight = game.addChild(new Knight()); // Apply difficulty scaling to knight stats knight.health = 300; // Knights have highest base health knight.maxHealth = knight.health; knight.speed = 2 * enemySpeedMultiplier; // Knights are slowest // Spawn knight at a random path entrance var knightPathIndex = Math.floor(Math.random() * 5); var knightPathAngle = pathAngles[knightPathIndex]; // Store the path information on the knight knight.pathIndex = knightPathIndex; knight.pathAngle = knightPathAngle; // Calculate spawn position at screen edges if (knightPathIndex === 0) { // Center path - spawn at top edge knight.x = 2048 / 2; knight.y = -100; } else if (knightPathIndex === 1) { // Path 2 - spawn at top right edge knight.x = 2048 + 50; knight.y = -50; } else if (knightPathIndex === 2) { // Path 3 - spawn at top left edge knight.x = -50; knight.y = -50; } else if (knightPathIndex === 3) { // Path 4 - spawn at left edge knight.x = -100; knight.y = 2732 / 2 + 400; } else if (knightPathIndex === 4) { // Path 5 - spawn at right edge knight.x = 2048 + 100; knight.y = 2732 / 2 + 400; } // Make sure knights spawn within screen bounds knight.x = Math.max(50, Math.min(1998, knight.x)); knight.y = Math.max(-200, Math.min(2732 + 100, knight.y)); knight.lastX = knight.x; knights.push(knight); } // Check enemy-knight collisions and cleanup off-screen enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; // Remove enemies that went off-screen at bottom if (enemy.y > 2732 + 100) { enemies.splice(i, 1); enemy.destroy(); continue; } // Initialize lastIntersecting if not set if (enemy.lastIntersecting === undefined) { enemy.lastIntersecting = false; } // Check for collision transition var currentIntersecting = enemy.intersects(wizard); if (!enemy.lastIntersecting && currentIntersecting) { // Damage wizard when enemy touches for the first time wizard.takeDamage(20); // Remove enemy after dealing damage enemies.splice(i, 1); enemy.destroy(); continue; } // Update last intersecting state enemy.lastIntersecting = currentIntersecting; } // Check ogre-wizard collisions and cleanup off-screen ogres for (var i = ogres.length - 1; i >= 0; i--) { var ogre = ogres[i]; // Remove ogres that went off-screen at bottom if (ogre.y > 2732 + 100) { ogres.splice(i, 1); ogre.destroy(); continue; } // Initialize lastIntersecting if not set if (ogre.lastIntersecting === undefined) { ogre.lastIntersecting = false; } // Check for collision transition var currentOgreIntersecting = ogre.intersects(wizard); if (!ogre.lastIntersecting && currentOgreIntersecting) { // Damage wizard when ogre touches for the first time wizard.takeDamage(30); // Ogres deal 30 damage // Remove ogre after dealing damage ogres.splice(i, 1); ogre.destroy(); continue; } // Update last intersecting state ogre.lastIntersecting = currentOgreIntersecting; } // Check knight-wizard collisions and cleanup off-screen knights for (var i = knights.length - 1; i >= 0; i--) { var knight = knights[i]; // Remove knights that went off-screen at bottom if (knight.y > 2732 + 100) { knights.splice(i, 1); knight.destroy(); continue; } // Initialize lastIntersecting if not set if (knight.lastIntersecting === undefined) { knight.lastIntersecting = false; } // Check for collision transition var currentKnightIntersecting = knight.intersects(wizard); if (!knight.lastIntersecting && currentKnightIntersecting) { // Damage wizard when knight touches for the first time wizard.takeDamage(40); // Knights deal 40 damage // Remove knight after dealing damage knights.splice(i, 1); knight.destroy(); continue; } // Update last intersecting state knight.lastIntersecting = currentKnightIntersecting; } // 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
@@ -32,9 +32,34 @@
self.collect = function () {
LK.getSound('coinCollect').play();
LK.setScore(LK.getScore() + 5);
coinCounter++;
+ totalCoinsEarned++;
coinText.setText('Coins: ' + coinCounter);
+ // Create coin collection effect
+ var collectEffect = game.addChild(LK.getAsset('coin', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: self.x,
+ y: self.y,
+ scaleX: 1.5,
+ scaleY: 1.5
+ }));
+ collectEffect.tint = 0xFFFFFF;
+ tween(collectEffect, {
+ scaleX: 3,
+ scaleY: 3,
+ alpha: 0
+ }, {
+ duration: 400,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ collectEffect.destroy();
+ }
+ });
+ // Save progress
+ saveGameData();
+ checkAchievements();
// Remove from coins array
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i] === self) {
coins.splice(i, 1);
@@ -44,49 +69,77 @@
self.destroy();
};
return self;
});
-var DamageNumber = Container.expand(function (damageAmount, x, y) {
+var DamageNumber = Container.expand(function () {
var self = Container.call(this);
- // Create damage text
- var damageText = new Text2(damageAmount.toString(), {
- size: 80,
- fill: 0xFF4444,
- font: "monospace"
- });
- damageText.anchor.set(0.5, 0.5);
- self.addChild(damageText);
- // Position at impact location
- self.x = x;
- self.y = y;
- // Animate floating upward with fade
- tween(self, {
- y: self.y - 120,
- alpha: 0
- }, {
- duration: 1000,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- self.destroy();
- }
- });
- // Add slight scale animation
- tween(damageText, {
- scaleX: 1.5,
- scaleY: 1.5
- }, {
- duration: 200,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- tween(damageText, {
- scaleX: 1.0,
- scaleY: 1.0
+ self.init = function (damage, x, y, color) {
+ var damageText = new Text2(damage.toString(), {
+ size: 80,
+ fill: color || 0xFFFFFF,
+ font: "monospace"
+ });
+ damageText.anchor.set(0.5, 0.5);
+ self.addChild(damageText);
+ self.x = x;
+ self.y = y;
+ // Animate damage number floating up and fading
+ tween(self, {
+ y: self.y - 100,
+ alpha: 0
+ }, {
+ duration: 1000,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ self.destroy();
+ }
+ });
+ // Add slight random horizontal drift
+ var drift = (Math.random() - 0.5) * 60;
+ tween(self, {
+ x: self.x + drift
+ }, {
+ duration: 1000,
+ easing: tween.easeOut
+ });
+ };
+ return self;
+});
+var DeathAnimation = Container.expand(function () {
+ var self = Container.call(this);
+ self.init = function (x, y, enemyType) {
+ self.x = x;
+ self.y = y;
+ // Create explosion particles
+ for (var i = 0; i < 8; i++) {
+ var particle = self.attachAsset('energySphere', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 0.3,
+ scaleY: 0.3
+ });
+ var angle = Math.PI * 2 / 8 * i;
+ var distance = 60 + Math.random() * 40;
+ particle.tint = enemyType === 'knight' ? 0xFFD700 : enemyType === 'ogre' ? 0xFF4500 : 0xFF6B6B;
+ tween(particle, {
+ x: Math.cos(angle) * distance,
+ y: Math.sin(angle) * distance,
+ alpha: 0,
+ scaleX: 0.1,
+ scaleY: 0.1
}, {
- duration: 300,
- easing: tween.easeIn
+ duration: 800,
+ easing: tween.easeOut
});
}
- });
+ // Remove after animation
+ tween({}, {}, {
+ duration: 800,
+ onFinish: function onFinish() {
+ self.destroy();
+ }
+ });
+ };
return self;
});
// Game arrays to track objects
var Enemy = Container.expand(function () {
@@ -211,15 +264,21 @@
}
};
self.takeDamage = function (damage) {
self.health -= damage;
+ // Create floating damage number
+ var damageNumber = game.addChild(new DamageNumber());
+ damageNumber.init(damage, self.x, self.y - 50, 0xFFFF00);
+ // Screen shake on hit
+ if (!game.screenShaker) {
+ game.screenShaker = new ScreenShake();
+ }
+ game.screenShaker.shake(5, 200);
// Change animation state to attacking when hit
self.animationState = 'attacking';
// Flash red when hit
LK.effects.flashObject(self, 0xFF0000, 200);
- // Create damage number effect
- var damageNumber = game.addChild(new DamageNumber(damage, self.x, self.y - 50));
- // Create impact effect
+ // Enhanced impact effect with sparks
var impactEffect = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
@@ -228,8 +287,35 @@
scaleY: 0.5
}));
impactEffect.tint = 0xFFFFFF;
impactEffect.alpha = 0.9;
+ // Create spark particles
+ for (var i = 0; i < 6; i++) {
+ var spark = game.addChild(LK.getAsset('energySphere', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: self.x,
+ y: self.y,
+ scaleX: 0.2,
+ scaleY: 0.2
+ }));
+ spark.tint = 0xFFFFFF;
+ var sparkAngle = Math.random() * Math.PI * 2;
+ var sparkDistance = 50 + Math.random() * 30;
+ tween(spark, {
+ x: self.x + Math.cos(sparkAngle) * sparkDistance,
+ y: self.y + Math.sin(sparkAngle) * sparkDistance,
+ alpha: 0,
+ scaleX: 0.05,
+ scaleY: 0.05
+ }, {
+ duration: 400,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ spark.destroy();
+ }
+ });
+ }
// Animate impact effect
tween(impactEffect, {
scaleX: 2.5,
scaleY: 2.5,
@@ -282,43 +368,16 @@
self.die = function () {
// Change animation state to dying
self.animationState = 'dying';
self.currentFrame = 3; // Start dying animation from frame 3
- // Add death animation effects
- tween(self, {
- alpha: 0,
- scaleX: 0.5,
- scaleY: 0.5,
- rotation: Math.PI
- }, {
- duration: 800,
- easing: tween.easeIn
- });
- // Add death particle effect
- for (var p = 0; p < 5; p++) {
- var particle = game.addChild(LK.getAsset('coin', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: self.x,
- y: self.y,
- scaleX: 0.3,
- scaleY: 0.3
- }));
- particle.tint = 0xFF6666;
- var angle = p / 5 * Math.PI * 2;
- var distance = 100 + Math.random() * 50;
- tween(particle, {
- x: self.x + Math.cos(angle) * distance,
- y: self.y + Math.sin(angle) * distance,
- alpha: 0
- }, {
- duration: 600 + Math.random() * 400,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- particle.destroy();
- }
- });
+ // Create death animation
+ var deathAnim = game.addChild(new DeathAnimation());
+ deathAnim.init(self.x, self.y, 'skeleton');
+ // Screen shake on death
+ if (!game.screenShaker) {
+ game.screenShaker = new ScreenShake();
}
+ game.screenShaker.shake(8, 250);
// Play pain sound when enemy dies
LK.getSound('painSound').play();
// Drop coin
var coin = game.addChild(new Coin());
@@ -833,10 +892,8 @@
// Change animation state to attacking when hit
self.animationState = 'attacking';
// Flash red when hit
LK.effects.flashObject(self, 0xFF0000, 200);
- // Create damage number effect
- var damageNumber = game.addChild(new DamageNumber(damage, self.x, self.y - 60));
// Create impact effect
var impactEffect = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
@@ -900,44 +957,8 @@
self.die = function () {
// Change animation state to dying
self.animationState = 'dying';
self.currentFrame = 3; // Start dying animation from frame 3
- // Add death animation effects
- tween(self, {
- alpha: 0,
- scaleX: 0.3,
- scaleY: 0.3,
- rotation: -Math.PI * 0.5
- }, {
- duration: 1000,
- easing: tween.easeIn
- });
- // Add death particle effect with golden particles
- for (var p = 0; p < 7; p++) {
- var particle = game.addChild(LK.getAsset('coin', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: self.x,
- y: self.y,
- scaleX: 0.4,
- scaleY: 0.4
- }));
- particle.tint = 0xFFD700;
- var angle = p / 7 * Math.PI * 2;
- var distance = 120 + Math.random() * 60;
- tween(particle, {
- x: self.x + Math.cos(angle) * distance,
- y: self.y + Math.sin(angle) * distance,
- alpha: 0,
- rotation: Math.PI * 2
- }, {
- duration: 800 + Math.random() * 400,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- particle.destroy();
- }
- });
- }
// Play pain sound when knight dies
LK.getSound('painSound').play();
// Drop coin
var coin = game.addChild(new Coin());
@@ -1128,10 +1149,8 @@
// Change animation state to attacking when hit
self.animationState = 'attacking';
// Flash red when hit
LK.effects.flashObject(self, 0xFF0000, 200);
- // Create damage number effect
- var damageNumber = game.addChild(new DamageNumber(damage, self.x, self.y - 70));
// Create impact effect
var impactEffect = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
@@ -1195,45 +1214,8 @@
self.die = function () {
// Change animation state to dying
self.animationState = 'dying';
self.currentFrame = 3; // Start dying animation from frame 3
- // Add death animation effects
- tween(self, {
- alpha: 0,
- scaleX: 0.2,
- scaleY: 0.2,
- rotation: Math.PI * 1.5
- }, {
- duration: 1200,
- easing: tween.easeIn
- });
- // Add death particle effect with fire particles
- for (var p = 0; p < 8; p++) {
- var particle = game.addChild(LK.getAsset('projectileGlow', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: self.x,
- y: self.y,
- scaleX: 0.5,
- scaleY: 0.5
- }));
- particle.tint = 0xFF4500;
- var angle = p / 8 * Math.PI * 2;
- var distance = 140 + Math.random() * 70;
- tween(particle, {
- x: self.x + Math.cos(angle) * distance,
- y: self.y + Math.sin(angle) * distance,
- alpha: 0,
- scaleX: 1.5,
- scaleY: 1.5
- }, {
- duration: 1000 + Math.random() * 500,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- particle.destroy();
- }
- });
- }
// Play pain sound when ogre dies
LK.getSound('painSound').play();
// Drop coin
var coin = game.addChild(new Coin());
@@ -1417,33 +1399,8 @@
lightGlow.scaleY = 2.0 * pulse;
var alphaFlicker = 0.1 + Math.sin(LK.ticks * 0.5) * 0.08;
lightGlow.alpha = alphaFlicker;
}
- // Create spell trail effect every few frames
- if (LK.ticks % 3 === 0) {
- var trailParticle = game.addChild(LK.getAsset('projectileGlow', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: self.x,
- y: self.y,
- scaleX: 0.8,
- scaleY: 0.8
- }));
- trailParticle.tint = 0x44AAFF;
- trailParticle.alpha = 0.6;
- // Animate trail particle fading and shrinking
- tween(trailParticle, {
- scaleX: 0.2,
- scaleY: 0.2,
- alpha: 0
- }, {
- duration: 400,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- trailParticle.destroy();
- }
- });
- }
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.removeFromGame();
return;
@@ -1485,8 +1442,71 @@
self.destroy();
};
return self;
});
+var ScreenShake = Container.expand(function () {
+ var self = Container.call(this);
+ self.shake = function (intensity, duration) {
+ intensity = intensity || 10;
+ duration = duration || 300;
+ var originalX = game.x;
+ var originalY = game.y;
+ var shakeTimer = 0;
+ var shakeInterval = LK.setInterval(function () {
+ shakeTimer += 16; // Approximate frame time
+ if (shakeTimer >= duration) {
+ game.x = originalX;
+ game.y = originalY;
+ LK.clearInterval(shakeInterval);
+ return;
+ }
+ var fadeIntensity = intensity * (1 - shakeTimer / duration);
+ game.x = originalX + (Math.random() - 0.5) * fadeIntensity;
+ game.y = originalY + (Math.random() - 0.5) * fadeIntensity;
+ }, 16);
+ };
+ return self;
+});
+var SpellTrail = Container.expand(function () {
+ var self = Container.call(this);
+ self.init = function (startX, startY, endX, endY, color) {
+ self.x = startX;
+ self.y = startY;
+ // Create trail segments
+ var segments = 10;
+ for (var i = 0; i < segments; i++) {
+ var segment = self.attachAsset('energySphere', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 0.2 - i * 0.02,
+ scaleY: 0.2 - i * 0.02
+ });
+ segment.tint = color || 0x44aaff;
+ segment.alpha = 1 - i * 0.1;
+ var progress = i / segments;
+ segment.x = (endX - startX) * progress;
+ segment.y = (endY - startY) * progress;
+ // Fade out segments with delay
+ tween(segment, {
+ alpha: 0,
+ scaleX: 0,
+ scaleY: 0
+ }, {
+ duration: 300,
+ delay: i * 30,
+ easing: tween.easeOut
+ });
+ }
+ // Clean up after animation
+ tween({}, {}, {
+ duration: 600,
+ onFinish: function onFinish() {
+ 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;
@@ -1843,8 +1863,11 @@
break;
}
// Visual feedback - flash the upgrade area green
LK.effects.flashScreen(0x00FF00, 300);
+ // Save upgrade progress
+ saveGameData();
+ checkAchievements();
} else {
// Not enough coins - flash red
LK.effects.flashScreen(0xFF0000, 300);
}
@@ -2022,8 +2045,11 @@
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 trail effect
+ var spellTrail = game.addChild(new SpellTrail());
+ spellTrail.init(self.x, self.y, spellX, spellY, 0xFF69B4);
// Create spell effect
var spell = game.addChild(LK.getAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
@@ -2031,8 +2057,19 @@
y: spellY,
scaleX: 0.5,
scaleY: 0.5
}));
+ // Enhanced spell effect with multiple layers
+ var spellGlow = game.addChild(LK.getAsset('projectileGlow', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: spellX,
+ y: spellY,
+ scaleX: 1.5,
+ scaleY: 1.5
+ }));
+ spellGlow.tint = 0xFF69B4;
+ spellGlow.alpha = 0.6;
// Animate spell with magical effects
tween(spell, {
scaleX: 2,
scaleY: 2,
@@ -2043,8 +2080,20 @@
onFinish: function onFinish() {
spell.destroy();
}
});
+ // Animate glow effect
+ tween(spellGlow, {
+ scaleX: 3,
+ scaleY: 3,
+ alpha: 0
+ }, {
+ duration: 600,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ spellGlow.destroy();
+ }
+ });
// Add rotation animation to spell
tween(spell, {
rotation: Math.PI * 2
}, {
@@ -2052,24 +2101,8 @@
easing: tween.linear
});
self.attackCooldown = 30; // 0.5 seconds at 60fps
LK.getSound('spellCast').play();
- // Add slight screen shake for spell casting
- var shakeIntensity = 5;
- var originalX = game.x;
- var originalY = game.y;
- var shakeCount = 0;
- var shakeInterval = LK.setInterval(function () {
- if (shakeCount < 3) {
- game.x = originalX + (Math.random() - 0.5) * shakeIntensity;
- game.y = originalY + (Math.random() - 0.5) * shakeIntensity;
- shakeCount++;
- } else {
- game.x = originalX;
- game.y = originalY;
- LK.clearInterval(shakeInterval);
- }
- }, 40);
// 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--) {
@@ -2166,31 +2199,17 @@
if (self.health <= 0) {
self.health = 0;
// Game over when health reaches 0
LK.effects.flashScreen(0xFF0000, 1000);
+ // Save final game data
+ gamesPlayed++;
+ saveGameData();
LK.showGameOver();
}
// Update health bar
updateHealthBar();
// Flash red when hit
LK.effects.flashObject(self, 0xFF0000, 200);
- // Add screen shake effect
- var shakeIntensity = 15;
- var originalX = game.x;
- var originalY = game.y;
- // Create shake effect
- var shakeCount = 0;
- var shakeInterval = LK.setInterval(function () {
- if (shakeCount < 8) {
- game.x = originalX + (Math.random() - 0.5) * shakeIntensity;
- game.y = originalY + (Math.random() - 0.5) * shakeIntensity;
- shakeCount++;
- } else {
- game.x = originalX;
- game.y = originalY;
- LK.clearInterval(shakeInterval);
- }
- }, 50);
};
self.activateForcePush = function () {
// Visual effect for force push activation
LK.effects.flashScreen(0x8A2BE2, 300); // Purple flash
@@ -2332,10 +2351,16 @@
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 = {
+// Load persistent data from storage
+var persistentData = storage || {};
+var totalCoinsEarned = persistentData.totalCoinsEarned || 0;
+var gamesPlayed = persistentData.gamesPlayed || 0;
+var highScore = persistentData.highScore || 0;
+var achievements = persistentData.achievements || {};
+// Upgrade level tracking system with persistence
+var upgradeLevels = persistentData.upgradeLevels || {
shield: 0,
healthBoost: 0,
lifeDrain: 0,
energySphere: 0,
@@ -2345,8 +2370,96 @@
fireBall: 0,
freezePulse: 0,
orbs: 0
};
+// Achievement system
+var achievementList = [{
+ id: 'firstKill',
+ name: 'First Blood',
+ description: 'Kill your first enemy',
+ threshold: 1
+}, {
+ id: 'survivor',
+ name: 'Survivor',
+ description: 'Kill 50 enemies',
+ threshold: 50
+}, {
+ id: 'slayer',
+ name: 'Slayer',
+ description: 'Kill 100 enemies',
+ threshold: 100
+}, {
+ id: 'coinCollector',
+ name: 'Coin Collector',
+ description: 'Collect 1000 coins total',
+ threshold: 1000
+}, {
+ id: 'upgraded',
+ name: 'Upgraded',
+ description: 'Purchase your first upgrade',
+ threshold: 1
+}];
+function checkAchievements() {
+ for (var i = 0; i < achievementList.length; i++) {
+ var achievement = achievementList[i];
+ if (!achievements[achievement.id]) {
+ var completed = false;
+ switch (achievement.id) {
+ case 'firstKill':
+ case 'survivor':
+ case 'slayer':
+ completed = enemyKillCounter >= achievement.threshold;
+ break;
+ case 'coinCollector':
+ completed = totalCoinsEarned >= achievement.threshold;
+ break;
+ case 'upgraded':
+ var hasUpgrade = false;
+ for (var key in upgradeLevels) {
+ if (upgradeLevels[key] > 0) {
+ hasUpgrade = true;
+ break;
+ }
+ }
+ completed = hasUpgrade;
+ break;
+ }
+ if (completed) {
+ achievements[achievement.id] = true;
+ // Show achievement notification
+ var achievementText = new Text2('ACHIEVEMENT: ' + achievement.name, {
+ size: 80,
+ fill: 0xFFD700,
+ font: "monospace"
+ });
+ achievementText.anchor.set(0.5, 0.5);
+ achievementText.x = 2048 / 2;
+ achievementText.y = 400;
+ LK.gui.center.addChild(achievementText);
+ // Animate achievement
+ tween(achievementText, {
+ y: 300,
+ alpha: 0
+ }, {
+ duration: 3000,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ achievementText.destroy();
+ }
+ });
+ // Save achievement
+ storage.achievements = achievements;
+ }
+ }
+ }
+}
+function saveGameData() {
+ storage.upgradeLevels = upgradeLevels;
+ storage.totalCoinsEarned = totalCoinsEarned;
+ storage.gamesPlayed = gamesPlayed;
+ storage.highScore = Math.max(highScore, enemyKillCounter);
+ storage.achievements = achievements;
+}
// Game arrays to track objects
var enemies = [];
var ogres = [];
var knights = [];
@@ -2632,30 +2745,38 @@
// Only update game logic if game has started
if (!gameStarted) {
return;
}
+ // Dynamic music system based on intensity
+ var currentEnemyCount = enemies.length + ogres.length + knights.length;
+ var intensity = Math.min(currentEnemyCount / 10, 1); // 0 to 1 based on enemy count
// Change music to epic battle theme at 10 enemies killed
if (enemyKillCounter === 10) {
LK.playMusic('epicBattle', {
- volume: 0.8,
+ volume: 0.8 + intensity * 0.2,
fade: {
start: 0,
- end: 0.8,
+ end: 0.8 + intensity * 0.2,
duration: 1500
}
});
}
// Change to mystical ambient music at 25 enemies killed
if (enemyKillCounter === 25) {
LK.playMusic('mysticalAmbient', {
- volume: 0.6,
+ volume: 0.6 + intensity * 0.3,
fade: {
start: 0,
- end: 0.6,
+ end: 0.6 + intensity * 0.3,
duration: 2000
}
});
}
+ // Check achievements periodically
+ if (LK.ticks % 60 === 0) {
+ // Every second
+ checkAchievements();
+ }
// Show upgrade menu after 12 enemies are killed
if (enemyKillCounter >= 12 && !upgradeMenuShown) {
upgradeMenuShown = true;
// Increase upgrade costs by 5 coins when menu is shown