User prompt
Quiero que a partir de la wave 12 para arriba a parte del oro que ya dan TODOS los enemigos al morir tambien den +2 de oro
User prompt
Al cambiar el idioma del juego quiero que tambien cambie el idioma de las opciones que se encuentran dentro de settings, el juego siempre debe comenzar en idioma ingles
User prompt
Ahora quiero que el boton de idioma funicone para los settings
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'toGlobal')' in or related to this line: 'var localPoint = startButton.toLocal(obj.parent.toGlobal({' Line Number: 1981 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'toGlobal')' in or related to this line: 'var localPoint = tutorialButton.toLocal(obj.parent.toGlobal({' Line Number: 2057
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'toGlobal')' in or related to this line: 'var localPoint = settingsButton.toLocal(obj.parent.toGlobal({' Line Number: 2134
User prompt
Que el lenguaje por el que empiece el jugeo sea ingles ademas quiero que la animacion se active solo cundo se pasa el mouse por icono del objeto ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
El idioma predeterminado del juego debe ser ingles ademas de que luego de 1 segundo la animacion del boton debe desaparecer automaticamente si el jugador no vuelve a pasar el cursor por ahi ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Perfecto, solo te falto la descripcion general del tutorial, ademas quiero Create polished and thematic hover and click animations for menu buttons in a fantasy tower defense game set in a magical concert hall. The animations should apply to the buttons from the following menus: Main Menu ("Start Game") Tutorial Settings 🔁 Hover Animation: On mouse hover, each button should gently grow in size by 5–10% with a subtle glowing outline (gold, blue, or violet, depending on the button’s color theme). Add musical sparkle particles or floating notes briefly rising near the edges of the button. Optionally include a soft note sound or chime when the hover begins (e.g., a plucked harp string or gentle bell). 🎯 Click Animation: On click, the button should quickly shrink slightly and bounce back, as if reacting to the pressure. Add a wave ripple or pulse of light moving outward from the button’s center. Play a short confirmation sound, like a piano keystroke or rhythmic drum tap. Keep all animations under 0.3 seconds for responsiveness, and make transitions smooth and elegant. The style must fit a concert-hall fantasy theme: clean, magical, and refined. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Quiero que cuando el idioma cambie tambien cambie toda la descripcion de el tutorial, tambien en las torres y enemigos (excepto en los nombres de los enemigos sy torres) ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
En el boton de idioma When the player clicks the button, it should display a dropdown menu or popup panel listing all available languages. Requirements: Each language should be listed using either its native name (e.g., "Español", "Français") or accompanied by a small flag icon. Selecting a language should immediately update all in-game UI text (menus, tooltips, wave counters, tower descriptions, etc.) to the chosen language. The menu should remember the selected language and load it automatically on the next game session. Optional: Play a soft confirmation sound (e.g., bell chime or subtle harp) when a new language is selected. The menu must be smooth, intuitive, and consistent with the orchestral fantasy theme. Ensure transitions are seamless, with fade-in/out or scroll animation for dropdowns. The system should support at least 5 languages and allow for easy addition of more later. ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
Lo mismo para el signo de menos y mas de Music volume
User prompt
Ahora quiero que el signo de menos y mas en la opcion "Sound effects Volume" esten a lado de la "S" inicial (el menos) y a lado de la "e" final (el mas)
User prompt
Perfecto, ahora sube un poco el icono de mas y menos en la opcion "Music Volume"
User prompt
El boton de tutorial muevelo un poco mas abajo, por otro lado el de settings mucho mas abajo
User prompt
El boton de start game y el de tutorial devuelvelos a su lugar, el de settings ponlo mas abajo
User prompt
Podrias acomoda las letras e iconos de las configuraciones para que no se tapen entre ellas? ademas quiero que el icono de configuraciones este mas abajo
User prompt
Quiero que los botones de subir y bajar volumen sean otros assets nuevos "mas" "menos", ademas de el boton de autostart waves "BotonW" el show damage numbers "SDN" y tambien el mismo boton de configuraciones "Setting", ademas quiero que añadas un nuevo boton que deje esoger el idioma del juego, que este dentro de las mismas configuraciones y tengan otro asset llamado "Idioma" ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Quiero que añadas un boton de configuraciones mas abajo de tutorial en el que se incluyan cosas de tu criterio ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Ahora quieor que en la pestaña tutorial para poder pasar a la siguiente torre/enemigo se pueda pulsar en cualquier parte de la desscripcion de la torre o enemigo incluyendo su descripcion por niveles
User prompt
Quiero que el titiulo del juego y el numero de wave tengan el mismo tipo de letra que music y wave active
User prompt
Si
User prompt
Quiero que las cortinas ocupen toda la pantalla ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Create a smooth and elegant opening animation for the start of a fantasy tower defense game set in a haunted concert hall. The animation should simulate large red velvet stage curtains (or concert hall blinds) slowly opening from the center outward, revealing the main menu or battlefield behind them. Key details: The curtains should be heavy, theatrical, and rich in texture, with deep red or burgundy tones and golden edges or tassels. The animation should begin with the curtains fully closed, then slowly part to the sides with a natural fabric motion, including slight sway, stretch, and easing at the end. Add a subtle dust or particle glow escaping from behind the curtain as it opens, to enhance the magical atmosphere. Optional: accompany the motion with a soft orchestral swell or harp glissando sound. The animation should last between 2 and 4 seconds, and be loop-free (played once at game start). ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Podrias hacer mejor la animacion de las persianas abriendose al inicio del juego? que se vea mas natural y mejor ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var BuildSpot = Container.expand(function (spotNumber) { var self = Container.call(this); var spotGraphics = self.attachAsset('buildSpot', { anchorX: 0.5, anchorY: 0.5 }); self.occupied = false; self.spotNumber = spotNumber || 0; // Build spot graphic only, no number display self.down = function (x, y, obj) { console.log("Build spot", self.spotNumber, "clicked, occupied:", self.occupied, "showingBuildMenu:", showingBuildMenu); if (!self.occupied && !showingBuildMenu) { console.log("Showing build menu for spot", self.spotNumber, "at:", self.x, self.y); showBuildMenu(self); } }; return self; }); var Bullet = Container.expand(function (startX, startY, target, damage, towerType, specialData) { var self = Container.call(this); // Choose random bullet asset from all available types var bulletTypes = ['bullet', 'drumBullet', 'trumpetBullet', 'guitarBullet', 'violinBullet', 'djBullet']; var bulletAsset = bulletTypes[Math.floor(Math.random() * bulletTypes.length)]; var bulletGraphics = self.attachAsset(bulletAsset, { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 8; self.towerType = towerType || 'drum'; self.specialData = specialData || {}; self.hitTargets = []; // For chain attacks self.update = function () { if (!self.target || self.target.health <= 0) { self.destroy(); return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 20) { // Hit target var died = false; // Handle different tower effects if (self.towerType === 'drum') { // Area damage around target died = self.target.takeDamage(self.damage); // Damage other enemies in area for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy !== self.target) { var dx2 = enemy.x - self.target.x; var dy2 = enemy.y - self.target.y; var areaDist = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (areaDist <= (self.specialData.areaRadius || 60)) { enemy.takeDamage(self.damage); // Level III drum tower slow effect if (self.specialData.slowEffect && Math.random() < (self.specialData.slowChance || 0.1)) { enemy.slowedUntil = LK.ticks + (self.specialData.slowDuration || 90); } } } } } else if (self.towerType === 'trumpet') { // Damage and pushback died = self.target.takeDamage(self.damage); // Apply pushback effect if (!died && self.target.pathIndex < pathPoints.length - 2) { self.target.pathIndex = Math.max(0, self.target.pathIndex - 1); } // Level III stun effect if (!died && self.specialData.stunChance && Math.random() < self.specialData.stunChance) { self.target.stunnedUntil = LK.ticks + (self.specialData.stunDuration || 60); // Visual stun effect var enemyGraphics = self.target.getChildAt(0); if (enemyGraphics) { var originalTint = enemyGraphics.tint || 0xffffff; tween(enemyGraphics, { tint: 0xFFFF00, // Yellow stun effect scaleX: 0.8, scaleY: 0.8 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { LK.setTimeout(function () { if (enemyGraphics && enemyGraphics.parent) { tween(enemyGraphics, { tint: originalTint, scaleX: 1.0, scaleY: 1.0 }, { duration: 300 }); } }, (self.specialData.stunDuration || 60) * 16); } }); } } } else if (self.towerType === 'guitar') { // Chain attack with multiple hits var hitsPerTarget = 3; // Guitar does 3 hits per target for (var hit = 0; hit < hitsPerTarget; hit++) { died = self.target.takeDamage(self.damage) || died; } self.hitTargets.push(self.target); var chainsLeft = (self.specialData.chainCount || 3) - self.hitTargets.length; if (chainsLeft > 0) { // Find next target for chain var nextTarget = null; var closestDist = 150; // Chain range for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (self.hitTargets.indexOf(enemy) === -1 && enemy.health > 0) { var dx2 = enemy.x - self.target.x; var dy2 = enemy.y - self.target.y; var chainDist = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (chainDist < closestDist) { nextTarget = enemy; closestDist = chainDist; } } } if (nextTarget) { self.target = nextTarget; return; // Continue to next target } // Level III extended chaining else if (self.specialData.extendedChain && self.hitTargets.length === self.specialData.chainCount) { // Try to find enemies near the last hit target for extended chain var lastTarget = self.hitTargets[self.hitTargets.length - 1]; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (self.hitTargets.indexOf(enemy) === -1 && enemy.health > 0) { var dx2 = enemy.x - lastTarget.x; var dy2 = enemy.y - lastTarget.y; var chainDist = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (chainDist < 100) { // Shorter range for extended chain self.target = enemy; self.specialData.chainCount++; // Extend the chain return; } } } } } } else if (self.towerType === 'violin') { // Damage with slow and freeze chance died = self.target.takeDamage(self.damage); // Check for freeze effect first (Level III only) var froze = false; if (!died && self.specialData.freezeChance && Math.random() < self.specialData.freezeChance) { self.target.frozenUntil = LK.ticks + (self.specialData.freezeDuration || 90); froze = true; // Apply freeze animation var enemyGraphics = self.target.getChildAt(0); if (enemyGraphics) { var originalTint = enemyGraphics.tint || 0xffffff; tween(enemyGraphics, { tint: 0x00FFFF, // Cyan freeze effect scaleX: 0.9, scaleY: 0.9, alpha: 0.7 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { LK.setTimeout(function () { if (enemyGraphics && enemyGraphics.parent) { tween(enemyGraphics, { tint: originalTint, scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 300 }); } }, (self.specialData.freezeDuration || 90) * 16); } }); } } // Apply slow effect if not frozen else if (!died && !froze && Math.random() < (self.specialData.slowChance || 0.25)) { self.target.slowedUntil = LK.ticks + (self.specialData.slowDuration || 120); // Apply freeze animation - enemy becomes frozen and shimmers var enemyGraphics = self.target.getChildAt(0); if (enemyGraphics) { // Store original values var originalTint = enemyGraphics.tint || 0xffffff; var originalScaleX = enemyGraphics.scaleX || 1.0; var originalScaleY = enemyGraphics.scaleY || 1.0; var originalAlpha = enemyGraphics.alpha || 1.0; // Freeze effect with ice-blue tint and slight scale animation tween(enemyGraphics, { tint: 0x0080FF, scaleX: originalScaleX * 1.1, scaleY: originalScaleY * 1.1, alpha: 0.8 }, { duration: 300, easing: tween.easeOut }); // Shimmer effect during freeze duration var shimmerCount = 0; var maxShimmers = Math.floor((self.specialData.slowDuration || 120) / 30); var _doShimmer = function doShimmer() { if (shimmerCount < maxShimmers && self.target && self.target.slowedUntil && LK.ticks < self.target.slowedUntil) { tween(enemyGraphics, { alpha: 0.5, tint: 0xB3D9FF }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tween(enemyGraphics, { alpha: 0.8, tint: 0x0080FF }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { shimmerCount++; if (shimmerCount < maxShimmers) { LK.setTimeout(_doShimmer, 100); } } }); } }); } }; LK.setTimeout(_doShimmer, 300); // Return to normal after freeze duration LK.setTimeout(function () { if (enemyGraphics && enemyGraphics.parent) { tween(enemyGraphics, { tint: originalTint, scaleX: originalScaleX, scaleY: originalScaleY, alpha: originalAlpha }, { duration: 500 }); } }, (self.specialData.slowDuration || 120) * 16); } } } else if (self.towerType === 'dj') { // Area damage with blind effect and Level III distortion died = self.target.takeDamage(self.damage); // Damage and apply effects to other enemies in large area for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx2 = enemy.x - self.target.x; var dy2 = enemy.y - self.target.y; var areaDist = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (areaDist <= (self.specialData.areaRadius || 120)) { if (enemy !== self.target) { enemy.takeDamage(self.damage * 0.8); // Reduced area damage } // Apply blind effect (reduce speed) enemy.blindedUntil = LK.ticks + (self.specialData.blindDuration || 60); // Level III distortion effect if (self.specialData.distortionEffect) { enemy.distortedUntil = LK.ticks + (self.specialData.distortionDuration || 180); enemy.distortionDamage = self.specialData.distortionDamage || 5; enemy.distortionSlowPercent = self.specialData.distortionSlowPercent || 0.25; // Visual distortion effect var enemyGraphics = enemy.getChildAt(0); if (enemyGraphics) { tween(enemyGraphics, { tint: 0xFF00FF, // Magenta distortion effect rotation: 0.2 }, { duration: 300, easing: tween.easeOut }); } } } } } else { // Default damage died = self.target.takeDamage(self.damage); } if (died) { // Track enemy killed totalEnemiesKilled++; // Calculate gold reward with restrictions var goldReward = self.target.goldValue; // Reduce gold per enemy from wave 6 if (wave >= 6) { goldReward = Math.min(goldReward, 3); } // Ensure minimum gold reward of 1 for all enemies from wave 10+ if (wave >= 10 && goldReward === 0) { goldReward = 1; } // Check wave gold limit (before adding universal bonus) if (lastWaveGoldEarned + goldReward > maxGoldPerWave) { goldReward = Math.max(1, maxGoldPerWave - lastWaveGoldEarned); } // Add universal +3 bonus for ALL enemies from wave 10+ (after all other restrictions) if (wave >= 10) { goldReward += 3; console.log("Wave " + wave + ": Added +3 universal bonus, total gold reward:", goldReward); } // Apply gold reward if (goldReward > 0) { gold += goldReward; lastWaveGoldEarned += goldReward; } updateUI(); LK.getSound('enemyDeath').play(); } self.destroy(); } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; return self; }); var Enemy = Container.expand(function (enemyType, isElite, isSpecial, specialType) { var self = Container.call(this); // Set enemy properties based on type self.enemyType = enemyType || 'notaDesafinada'; self.isElite = isElite || false; self.isSpecial = isSpecial || false; self.specialType = specialType || ''; var assetName = self.isElite ? 'eliteEnemy' : self.enemyType; var enemyGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); if (self.enemyType === 'notaDesafinada') { self.health = 90; self.maxHealth = 90; self.speed = 2; self.goldValue = 5; } else if (self.enemyType === 'ruidoBlanco') { self.health = 144; self.maxHealth = 144; self.speed = 1.2; self.goldValue = 8; } else if (self.enemyType === 'glitchAudio') { self.health = 72; self.maxHealth = 72; self.speed = 2.7; // Further reduced for better balance when empowered self.goldValue = 6; } else if (self.enemyType === 'ecoOscuro') { self.health = 180; self.maxHealth = 180; self.speed = 2; self.goldValue = 12; self.canSplit = true; } else if (self.enemyType === 'autotuneMalicioso') { self.health = 270; self.maxHealth = 270; self.speed = 1.8; self.goldValue = 20; self.debuffRadius = 100; } // Elite enemies have double HP and gold value if (self.isElite) { self.health *= 2; self.maxHealth *= 2; self.goldValue *= 2; enemyGraphics.scaleX = 1.3; enemyGraphics.scaleY = 1.3; } // Special enemy modifications if (self.isSpecial) { if (self.specialType === 'spectral') { self.goldValue = 0; // No gold reward enemyGraphics.tint = 0x8800FF; // Purple tint enemyGraphics.alpha = 0.7; } else if (self.specialType === 'runner') { self.speed *= 2.5; // Very fast self.health *= 0.5; // Low health enemyGraphics.tint = 0x00FFFF; // Cyan tint } else if (self.specialType === 'armored') { self.health *= 1.8; // High health self.speed *= 0.7; // Slower self.damageResistance = 0.3; // 30% damage reduction enemyGraphics.tint = 0x808080; // Gray tint } else if (self.specialType === 'explosive') { self.explosiveDamage = 25; self.explosiveRadius = 80; enemyGraphics.tint = 0xFF4400; // Orange-red tint } else if (self.specialType === 'disruptor') { self.disruptRadius = 100; enemyGraphics.tint = 0xFFFF00; // Yellow tint } } self.pathIndex = 0; self.walkAnimationTimer = 0; self.originalScale = 1.0; self.takeDamage = function (damage) { // Apply damage resistance for armored enemies if (self.isSpecial && self.specialType === 'armored' && self.damageResistance) { damage *= 1 - self.damageResistance; } self.health -= damage; if (self.health <= 0) { self.health = 0; // Special enemy death effects if (self.isSpecial) { if (self.specialType === 'explosive') { // Damage nearby towers for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx = tower.x - self.x; var dy = tower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.explosiveRadius) { tower.disruptedUntil = LK.ticks + 120; // 2 seconds disruption // Visual explosion effect var explosion = game.addChild(LK.getAsset('spotlight', { anchorX: 0.5, anchorY: 0.5 })); explosion.x = self.x; explosion.y = self.y; explosion.tint = 0xFF4400; explosion.alpha = 0.8; tween(explosion, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 500, onFinish: function onFinish() { explosion.destroy(); } }); } } } } // Handle splitting for Eco Oscuro if (self.enemyType === 'ecoOscuro' && self.canSplit) { // Create two Nota Desafinada enemies for (var i = 0; i < 2; i++) { var splitEnemy = new Enemy('notaDesafinada'); splitEnemy.x = self.x + (i === 0 ? -30 : 30); splitEnemy.y = self.y; splitEnemy.pathIndex = self.pathIndex; enemies.push(splitEnemy); game.addChild(splitEnemy); } } return true; // Enemy died } // Flash red when hit but preserve original tint var originalTint = enemyGraphics.tint; tween(enemyGraphics, { tint: 0xff0000 }, { duration: 100, onFinish: function onFinish() { tween(enemyGraphics, { tint: originalTint }, { duration: 100 }); } }); return false; }; self.update = function () { // Enhanced walking animation - more pronounced feet movement and body oscillation self.walkAnimationTimer += 0.4; // Increased animation speed var walkBob = Math.sin(self.walkAnimationTimer) * 0.12; // More pronounced bobbing var walkSway = Math.cos(self.walkAnimationTimer * 0.9) * 0.05; // Increased sway var footStep = Math.sin(self.walkAnimationTimer * 2) * 0.08; // Foot stepping animation // Enhanced scale animation for walking feet effect enemyGraphics.scaleY = self.originalScale + walkBob + footStep; enemyGraphics.scaleX = self.originalScale + walkBob * 0.4 + Math.abs(footStep) * 0.2; // Enhanced rotation for body sway during movement enemyGraphics.rotation = Math.sin(self.walkAnimationTimer * 1.4) * 0.06 + walkSway; // Add slight vertical movement for walking bounce enemyGraphics.y = Math.sin(self.walkAnimationTimer * 1.8) * 3; // Additional movement for floating/spectral enemies if (self.isSpecial && self.specialType === 'spectral') { enemyGraphics.y = Math.sin(self.walkAnimationTimer * 0.6) * 20; // Increased floating range enemyGraphics.rotation += Math.sin(self.walkAnimationTimer * 0.4) * 0.15; // More rotation enemyGraphics.alpha = 0.7 + Math.sin(self.walkAnimationTimer * 0.8) * 0.1; // Ghostly flickering } if (self.pathIndex < pathPoints.length - 1) { var targetPoint = pathPoints[self.pathIndex + 1]; var dx = targetPoint.x - self.x; var dy = targetPoint.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { self.pathIndex++; if (self.pathIndex >= pathPoints.length - 1) { // Reached goal - play portal sound and animate enemy entering portal LK.getSound('portal').play(); // Animate enemy being sucked into portal tween(self, { x: goal.x, y: goal.y, scaleX: 0.2, scaleY: 0.2, rotation: Math.PI * 4 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { // Final disappearing effect tween(self, { alpha: 0, scaleX: 0.05, scaleY: 0.05 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { lives--; updateUI(); if (lives <= 0) { showEnhancedGameOver(); } // Remove enemy from array and destroy var enemyIndex = enemies.indexOf(self); if (enemyIndex > -1) { enemies.splice(enemyIndex, 1); } self.destroy(); } }); } }); return; // Exit update to prevent further movement } } else { // Calculate current speed with effects var currentSpeed = self.speed; // Apply stun effect from trumpet (completely stops movement) if (self.stunnedUntil && LK.ticks < self.stunnedUntil) { currentSpeed = 0; // Can't move when stunned } // Apply Grand Piano stun effect (completely stops movement) else if (self.grandPianoStunnedUntil && LK.ticks < self.grandPianoStunnedUntil) { currentSpeed = 0; // Can't move when stunned by Grand Piano } // Apply freeze effect from violin (completely stops movement) else if (self.frozenUntil && LK.ticks < self.frozenUntil) { currentSpeed = 0; // Can't move when frozen } // Apply slow effect from violin else if (self.slowedUntil && LK.ticks < self.slowedUntil) { currentSpeed *= 0.5; // 50% speed when slowed } // Apply blind effect from DJ (further speed reduction) if (self.blindedUntil && LK.ticks < self.blindedUntil) { currentSpeed *= 0.3; // 30% speed when blinded } // Apply distortion effect from DJ Level III if (self.distortedUntil && LK.ticks < self.distortedUntil) { currentSpeed *= 1 - (self.distortionSlowPercent || 0.25); // Additional slow from distortion // Apply DoT damage every 30 ticks (0.5 seconds) if (LK.ticks % 30 === 0) { self.takeDamage(self.distortionDamage || 5); } } // Apply autotune debuff to nearby towers if (self.enemyType === 'autotuneMalicioso') { for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx2 = tower.x - self.x; var dy2 = tower.y - self.y; var towerDist = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (towerDist <= self.debuffRadius) { tower.debuffedUntil = LK.ticks + 1; // Continuous debuff while in range } } } // Special disruptor enemy abilities if (self.isSpecial && self.specialType === 'disruptor') { for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx2 = tower.x - self.x; var dy2 = tower.y - self.y; var towerDist = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (towerDist <= self.disruptRadius) { tower.disruptedUntil = LK.ticks + 1; // Continuous disruption } } } self.x += dx / distance * currentSpeed; self.y += dy / distance * currentSpeed; } } }; return self; }); var Tower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'drum'; var assetName = self.towerType + 'Tower'; var towerGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Function to update tower graphics based on level self.updateTowerGraphics = function () { var newAssetName; if (self.level === 1) { newAssetName = self.towerType + 'Tower'; } else if (self.level === 2) { if (self.towerType === 'drum') newAssetName = 'Tambor2';else if (self.towerType === 'trumpet') newAssetName = 'Trompeta2';else if (self.towerType === 'guitar') newAssetName = 'Guitarra2';else if (self.towerType === 'violin') newAssetName = 'Violin2';else if (self.towerType === 'dj') newAssetName = 'DJ2'; } else if (self.level === 3) { if (self.towerType === 'drum') newAssetName = 'Tambor3';else if (self.towerType === 'trumpet') newAssetName = 'Trompeta3';else if (self.towerType === 'guitar') newAssetName = 'Guitarra3';else if (self.towerType === 'violin') newAssetName = 'Violin3';else if (self.towerType === 'dj') newAssetName = 'DJ3'; } // Remove old graphics and add new ones if (towerGraphics && towerGraphics.parent) { self.removeChild(towerGraphics); } towerGraphics = self.attachAsset(newAssetName, { anchorX: 0.5, anchorY: 0.5 }); }; // Tower level system self.level = 1; self.totalCost = 0; // Track total investment for removal refund // Tower properties based on type if (self.towerType === 'drum') { self.damage = 15; self.range = 165; // Short range self.fireRate = 30; // Fast (0.5s at 60fps) self.cost = 50; self.areaRadius = 60; // Small area for drum } else if (self.towerType === 'trumpet') { self.damage = 35; self.range = 180; // Medium range self.fireRate = 60; // Medium (1s at 60fps) self.cost = 80; self.pushback = true; } else if (self.towerType === 'guitar') { self.damage = 20; self.range = 220; // Long range self.fireRate = 90; // Slow (1.5s at 60fps) self.cost = 120; self.chainCount = 3; } else if (self.towerType === 'violin') { self.damage = 10; self.range = 220; // Long range self.fireRate = 60; // Medium (1s at 60fps) self.cost = 100; self.slowChance = 0.25; // 25% chance self.slowDuration = 120; // 2s at 60fps } else if (self.towerType === 'dj') { self.damage = 60; // Increased from 50 self.range = 220; // Increased from 200 self.fireRate = 120; // Improved from 150 (2.0s instead of 2.5s) self.cost = 200; self.areaRadius = 140; // Increased from 120 self.blindEffect = true; } // Store base stats for upgrade calculations self.baseDamage = self.damage; self.baseRange = self.range; self.baseFireRate = self.fireRate; self.baseAreaRadius = self.areaRadius || 0; self.totalCost = self.cost; // Track total investment self.lastFire = self.fireRate; // Initialize to fireRate to allow immediate first shot after properties are set self.findTarget = function () { var closestEnemy = null; var closestDistance = self.range; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < closestDistance) { closestEnemy = enemy; closestDistance = distance; } } return closestEnemy; }; self.fire = function (target) { var specialData = { areaRadius: self.areaRadius, chainCount: self.chainCount, slowChance: self.slowChance, slowDuration: self.slowDuration }; // Apply autotune debuff if present var actualDamage = self.damage; if (self.debuffedUntil && LK.ticks < self.debuffedUntil) { actualDamage *= 0.8; // 20% damage reduction } var bullet = new Bullet(self.x, self.y, target, actualDamage, self.towerType, specialData); bullets.push(bullet); game.addChild(bullet); // Play specific sound for different tower types if (self.towerType === 'trumpet') { LK.getSound('proyectilT').play(); } else if (self.towerType === 'drum') { try { LK.getSound('ProyectilTA').play(); } catch (error) { console.log("Error playing drum sound, using fallback"); LK.getSound('hit').play(); } } else if (self.towerType === 'guitar') { LK.getSound('ProyectilG').play(); } else if (self.towerType === 'violin') { LK.getSound('ProyectilV').play(); } else if (self.towerType === 'dj') { LK.getSound('ProyectilDJ').play(); } else { LK.getSound('hit').play(); } }; // Tower upgrade method with specific stats per tower type and level self.upgrade = function () { if (self.level >= 3) return false; // Max level reached // Calculate upgrade cost based on tower type and level var upgradeCost = 0; if (self.towerType === 'drum') { upgradeCost = self.level === 1 ? 90 : 160; } else if (self.towerType === 'trumpet') { upgradeCost = self.level === 1 ? 130 : 200; } else if (self.towerType === 'guitar') { upgradeCost = self.level === 1 ? 180 : 250; } else if (self.towerType === 'violin') { upgradeCost = self.level === 1 ? 160 : 220; } else if (self.towerType === 'dj') { upgradeCost = self.level === 1 ? 280 : 350; } if (gold < upgradeCost) return false; // Not enough gold gold -= upgradeCost; // Track gold spent on upgrades totalGoldSpent += upgradeCost; self.totalCost += upgradeCost; self.level++; // Apply specific stats based on tower type and level if (self.towerType === 'drum') { if (self.level === 2) { self.damage = 20; self.fireRate = 30; // Same as level 1 (0.5s) self.areaRadius = Math.floor(self.baseAreaRadius * 1.25); // 25% larger area } else if (self.level === 3) { self.damage = 25; self.fireRate = 24; // 0.4s at 60fps self.areaRadius = Math.floor(self.baseAreaRadius * 1.25); self.slowEffect = true; // Add 10% slow for 1.5s self.slowChance = 0.1; self.slowDuration = 90; // 1.5s at 60fps } } else if (self.towerType === 'trumpet') { if (self.level === 2) { self.damage = 45; self.fireRate = 54; // 0.9s at 60fps self.strongerPushback = true; } else if (self.level === 3) { self.damage = 60; self.fireRate = 48; // 0.8s at 60fps self.stunChance = 0.1; // 10% stun chance self.stunDuration = 60; // 1s at 60fps } } else if (self.towerType === 'guitar') { if (self.level === 2) { self.damage = 25; // Per hit self.fireRate = 78; // 1.3s at 60fps self.chainCount = 4; } else if (self.level === 3) { self.damage = 30; // Per hit self.fireRate = 72; // 1.2s at 60fps self.chainCount = 4; self.extendedChain = true; // Enhanced chaining } } else if (self.towerType === 'violin') { if (self.level === 2) { self.damage = 14; self.fireRate = 60; // Same as level 1 (1.0s) self.slowChance = 0.35; // 35% slow self.slowDuration = 180; // 3s at 60fps } else if (self.level === 3) { self.damage = 18; self.fireRate = 54; // 0.9s at 60fps self.slowChance = 0.40; // 40% slow self.slowDuration = 180; self.freezeChance = 0.1; // 10% freeze chance self.freezeDuration = 90; // 1.5s at 60fps } } else if (self.towerType === 'dj') { if (self.level === 2) { self.damage = 75; // Increased from 65 self.fireRate = 108; // Improved from 138 (1.8s instead of 2.3s) self.areaRadius = Math.floor(self.baseAreaRadius * 1.3); // Increased from 1.2 self.blindDuration = 90; // Increased from 60 (1.5s blind) } else if (self.level === 3) { self.damage = 95; // Increased from 80 self.fireRate = 96; // Improved from 120 (1.6s instead of 2.0s) self.distortionEffect = true; // Add distortion (25% slow + DoT) self.distortionSlowPercent = 0.3; // Increased from 0.25 self.distortionDamage = 7; // Increased from 5 self.distortionDuration = 210; // Increased from 180 (3.5s duration) } } // Update tower graphics to show new level self.updateTowerGraphics(); // Light animation effect without changing tower color var lightEffect = self.addChild(LK.getAsset('spotlight', { anchorX: 0.5, anchorY: 0.5 })); lightEffect.x = 0; lightEffect.y = 0; lightEffect.scaleX = 2; lightEffect.scaleY = 2; lightEffect.alpha = 0.8; lightEffect.tint = 0xFFD700; // Gold light effect tween(lightEffect, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { lightEffect.destroy(); } }); updateUI(); return true; }; // Tower click handler for upgrade menu self.down = function (x, y, obj) { if (!showingBuildMenu && gameState === 'playing') { // If this tower is already selected and upgrade menu is showing, close it if (showingUpgradeMenu && selectedTower === self) { hideUpgradeMenu(); } else { // Otherwise show upgrade menu for this tower showUpgradeMenu(self); } } }; self.update = function () { self.lastFire++; // Check if tower is disrupted if (self.disruptedUntil && LK.ticks < self.disruptedUntil) { return; // Skip firing if disrupted } if (self.lastFire >= self.fireRate) { var target = self.findTarget(); if (target && target.health > 0) { self.fire(target); self.lastFire = 0; // Add visual firing effect tween(towerGraphics, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, onFinish: function onFinish() { tween(towerGraphics, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100 }); } }); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x8B4513, width: 2048, height: 2732, autoResize: true, scaleMode: 'stretch' }); /**** * Game Code ****/ // Game variables // The scaleMode: 'stretch' and autoResize: true in game initialization ensure proper mobile scaling // LK Game engine handles mobile display automatically through initialization parameters var gameState = 'menu'; // 'menu', 'tutorial', 'playing' var tutorialStep = 0; var menuContainer; var tutorialContainer; // Game statistics tracking var totalEnemiesKilled = 0; var totalGoldSpent = 0; // Difficulty system variables var goldPerEnemy = 5; // Base gold per enemy var maxGoldPerWave = 60; // Base maximum gold per wave var goldAbuseCooldown = 0; // Timer for gold abuse punishment var highGoldWaveCount = 0; // Count of waves with excess gold var lastWaveGoldEarned = 0; // Track gold earned in current wave var difficultyModifier = 1.0; // Applied to enemy stats for punishment var enemySpeedModifier = 1.0; // Speed increase from wave 10+ var forceExpenseEvent = false; // Force expense event flag var nextForceExpenseWave = 5; // Next wave to force expense var currentSpecialWave = null; // Track current special wave type // Simple music system - just track if Melodia is playing var gameplayMusicPlaying = false; // Flag to track if music is currently playing // Game Elements var enemies = []; var towers = []; var bullets = []; var buildSpots = []; var gold = 150; var lives = 40; var wave = 1; var enemiesSpawned = 0; var enemiesPerWave = 10; var spawnTimer = 0; var waveStarted = false; var wavePaused = true; // New: waves start paused between waves var autoStartTimer = 0; // New: timer for auto-starting waves var showingBuildMenu = false; var selectedBuildSpot = null; var backgroundElements = []; var floatingNotes = []; // Tower upgrade system variables var showingUpgradeMenu = false; var selectedTower = null; var upgradeMenuContainer; // Credits screen variables var creditsContainer; var showingCredits = false; // Grand Piano Power System var grandPianoPowerUnlocked = false; var grandPianoCooldownRemaining = 0; var grandPianoPowerButton; var grandPianoPowerText; var grandPianoCooldownText; var grandPianoButtonBackground; var grandPianoPowerActive = false; var grandPianoGhostMode = false; var grandPianoGhost = null; var grandPianoTargetArea = null; var grandPianoUnlockAnimationShown = false; // === GIANT WOODEN TABLE BACKGROUND === // Create a large wooden table that covers the entire game area as the base var giantTable = game.addChild(LK.getAsset('woodPanel', { anchorX: 0.5, anchorY: 0.5 })); giantTable.x = 1024; giantTable.y = 1366; giantTable.scaleX = 5.0; // Scale to cover full width giantTable.scaleY = 6.0; // Scale to cover full height giantTable.tint = 0x8B4513; // Rich wooden brown color giantTable.alpha = 0.3; // Semi-transparent so other elements are visible backgroundElements.push(giantTable); // === STAGE AREA (Top section of concert hall) === // Main stage backdrop var stage = game.addChild(LK.getAsset('stage', { anchorX: 0.5, anchorY: 1 })); stage.x = 1024; stage.y = 350; stage.scaleX = 0.9; stage.scaleY = 0.8; backgroundElements.push(stage); // Stage curtain backdrop var stageBackdrop = game.addChild(LK.getAsset('curtain', { anchorX: 0.5, anchorY: 0 })); stageBackdrop.x = 1024; stageBackdrop.y = 30; stageBackdrop.scaleX = 1.2; stageBackdrop.scaleY = 0.7; backgroundElements.push(stageBackdrop); // Orchestra pit and performance area var orchestraPit = game.addChild(LK.getAsset('orchestraPit', { anchorX: 0.5, anchorY: 1 })); orchestraPit.x = 1024; orchestraPit.y = 550; orchestraPit.scaleX = 0.8; orchestraPit.scaleY = 0.6; backgroundElements.push(orchestraPit); // Grand piano in orchestra pit var grandPiano = game.addChild(LK.getAsset('grandPiano', { anchorX: 0.5, anchorY: 0.5 })); grandPiano.x = 900; grandPiano.y = 400; grandPiano.scaleX = 0.8; grandPiano.scaleY = 0.8; backgroundElements.push(grandPiano); // Conductor podium var conductor = game.addChild(LK.getAsset('conductor', { anchorX: 0.5, anchorY: 1 })); conductor.x = 1024; conductor.y = 420; conductor.scaleX = 0.8; conductor.scaleY = 0.8; backgroundElements.push(conductor); // Music stands arranged in orchestra pit for (var i = 0; i < 6; i++) { var musicStand = game.addChild(LK.getAsset('musicStand', { anchorX: 0.5, anchorY: 1 })); musicStand.x = 750 + i * 90; musicStand.y = 440; musicStand.scaleX = 0.6; musicStand.scaleY = 0.8; backgroundElements.push(musicStand); } // === UPPER BALCONIES AND VIP AREAS === // Main balconies positioned logically for (var i = 0; i < 2; i++) { var balcony = game.addChild(LK.getAsset('balcony', { anchorX: 0.5, anchorY: 0.5 })); balcony.x = 400 + i * 1200; balcony.y = 600; balcony.scaleX = 0.7; balcony.scaleY = 0.6; backgroundElements.push(balcony); // Royal crests for main balconies var crest = game.addChild(LK.getAsset('royalCrest', { anchorX: 0.5, anchorY: 0.5 })); crest.x = balcony.x; crest.y = balcony.y - 60; crest.scaleX = 0.5; crest.scaleY = 0.5; backgroundElements.push(crest); } // VIP boxes on middle level for (var i = 0; i < 3; i++) { var vipBox = game.addChild(LK.getAsset('vipBox', { anchorX: 0.5, anchorY: 0.5 })); vipBox.x = 300 + i * 700; vipBox.y = 800; vipBox.scaleX = 0.6; vipBox.scaleY = 0.5; backgroundElements.push(vipBox); // VIP box seating removed } // === LIGHTING AND ATMOSPHERE === // Chandeliers positioned strategically for (var i = 0; i < 4; i++) { var chandelier = game.addChild(LK.getAsset('chandelier', { anchorX: 0.5, anchorY: 0.5 })); chandelier.x = 400 + i * 400; chandelier.y = 120; chandelier.scaleX = 0.8; chandelier.scaleY = 0.8; backgroundElements.push(chandelier); // Chandelier swaying animation tween(chandelier, { rotation: 0.08 }, { duration: 2500, easing: tween.easeInOut, onFinish: function onFinish() { tween(chandelier, { rotation: -0.08 }, { duration: 2500, easing: tween.easeInOut, onFinish: arguments.callee }); } }); } // === STRUCTURAL ELEMENTS === // Supporting pillars var leftPillar = game.addChild(LK.getAsset('pillar', { anchorX: 0.5, anchorY: 1 })); leftPillar.x = 200; leftPillar.y = 1200; leftPillar.scaleX = 0.6; leftPillar.scaleY = 1.0; backgroundElements.push(leftPillar); var rightPillar = game.addChild(LK.getAsset('pillar', { anchorX: 0.5, anchorY: 1 })); rightPillar.x = 1848; rightPillar.y = 1200; rightPillar.scaleX = 0.6; rightPillar.scaleY = 1.0; backgroundElements.push(rightPillar); // Decorative archways section removed // === AUDIENCE SEATING AREAS === // Classical concert hall seating arrangement // Orchestra Level - Main Floor Seating // Left orchestra section with center aisle for (var row = 0; row < 8; row++) { for (var col = 0; col < 6; col++) { var seat = game.addChild(LK.getAsset('seat', { anchorX: 0.5, anchorY: 0.5 })); seat.x = 300 + col * 65; seat.y = 1800 + row * 70; seat.scaleX = 0.8; seat.scaleY = 0.8; seat.rotation = 0.05; // Slight angle toward stage backgroundElements.push(seat); } } // Right orchestra section with center aisle for (var row = 0; row < 8; row++) { for (var col = 0; col < 6; col++) { var seat = game.addChild(LK.getAsset('seat', { anchorX: 0.5, anchorY: 0.5 })); seat.x = 1400 + col * 65; seat.y = 1800 + row * 70; seat.scaleX = 0.8; seat.scaleY = 0.8; seat.rotation = -0.05; // Slight angle toward stage backgroundElements.push(seat); } } // Center orchestra section - split for main aisle for (var row = 0; row < 6; row++) { // Left side of center section for (var col = 0; col < 4; col++) { var seat = game.addChild(LK.getAsset('seat', { anchorX: 0.5, anchorY: 0.5 })); seat.x = 750 + col * 65; seat.y = 1850 + row * 70; seat.scaleX = 0.9; seat.scaleY = 0.9; backgroundElements.push(seat); } // Right side of center section for (var col = 0; col < 4; col++) { var seat = game.addChild(LK.getAsset('seat', { anchorX: 0.5, anchorY: 0.5 })); seat.x = 1100 + col * 65; seat.y = 1850 + row * 70; seat.scaleX = 0.9; seat.scaleY = 0.9; backgroundElements.push(seat); } } // Premium front rows - closer to orchestra pit for (var row = 0; row < 3; row++) { for (var col = 0; col < 12; col++) { var seat = game.addChild(LK.getAsset('seat', { anchorX: 0.5, anchorY: 0.5 })); seat.x = 650 + col * 75; seat.y = 1650 + row * 70; seat.scaleX = 1.0; seat.scaleY = 1.0; seat.tint = 0xB8860B; // Gold tint for premium seats backgroundElements.push(seat); } } // Mezzanine Level - First Balcony // Left and right mezzanine sections removed // Center mezzanine - elevated premium seating for (var row = 0; row < 4; row++) { for (var col = 0; col < 10; col++) { var seat = game.addChild(LK.getAsset('seat', { anchorX: 0.5, anchorY: 0.5 })); seat.x = 750 + col * 70; seat.y = 1100 + row * 65; seat.scaleX = 0.8; seat.scaleY = 0.8; seat.tint = 0x8B0000; // Deep red for premium balcony seats backgroundElements.push(seat); } } // Upper Gallery - Third Level // Compact seating in upper gallery for (var row = 0; row < 6; row++) { for (var col = 0; col < 16; col++) { var seat = game.addChild(LK.getAsset('seat', { anchorX: 0.5, anchorY: 0.5 })); seat.x = 500 + col * 65; seat.y = 800 + row * 60; seat.scaleX = 0.6; seat.scaleY = 0.6; // Create perspective effect - chairs get smaller toward back var perspectiveScale = 1.0 - row * 0.05; seat.scaleX *= perspectiveScale; seat.scaleY *= perspectiveScale; backgroundElements.push(seat); } } // Side Box Seats - Elevated positions along walls // Rear Orchestra - Additional seating toward entrance for (var row = 0; row < 5; row++) { for (var col = 0; col < 14; col++) { var seat = game.addChild(LK.getAsset('seat', { anchorX: 0.5, anchorY: 0.5 })); seat.x = 600 + col * 70; seat.y = 2100 + row * 70; seat.scaleX = 0.8; seat.scaleY = 0.8; // Create slight curve effect for better sightlines var curveOffset = Math.sin(col / 13 * Math.PI) * 20; seat.y += curveOffset; backgroundElements.push(seat); } } // Standing room areas - simulated with smaller seats for atmosphere // Upper standing gallery for (var i = 0; i < 20; i++) { var standingSpot = game.addChild(LK.getAsset('seat', { anchorX: 0.5, anchorY: 0.5 })); standingSpot.x = 400 + i * 60; standingSpot.y = 650; standingSpot.scaleX = 0.3; standingSpot.scaleY = 0.3; standingSpot.alpha = 0.7; standingSpot.tint = 0x696969; // Gray for standing areas backgroundElements.push(standingSpot); } // === DECORATIVE LIGHTING === // Elegant candelabras positioned throughout for (var i = 0; i < 6; i++) { var candelabra = game.addChild(LK.getAsset('candelabra', { anchorX: 0.5, anchorY: 1 })); candelabra.x = 400 + i * 200; candelabra.y = 1600; candelabra.scaleX = 0.9; candelabra.scaleY = 1.1; backgroundElements.push(candelabra); // Flickering flame effect tween(candelabra, { scaleY: 1.15, alpha: 0.85 }, { duration: 1000 + Math.random() * 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(candelabra, { scaleY: 1.1, alpha: 1.0 }, { duration: 1000 + Math.random() * 500, easing: tween.easeInOut, onFinish: arguments.callee }); } }); } // Wall-mounted lighting for (var i = 0; i < 4; i++) { var wallCandelabra = game.addChild(LK.getAsset('candelabra', { anchorX: 0.5, anchorY: 0.5 })); wallCandelabra.x = 150; wallCandelabra.y = 1000 + i * 300; wallCandelabra.scaleX = 0.7; wallCandelabra.scaleY = 0.8; backgroundElements.push(wallCandelabra); var rightWallCandelabra = game.addChild(LK.getAsset('candelabra', { anchorX: 0.5, anchorY: 0.5 })); rightWallCandelabra.x = 1898; rightWallCandelabra.y = 1000 + i * 300; rightWallCandelabra.scaleX = 0.7; rightWallCandelabra.scaleY = 0.8; backgroundElements.push(rightWallCandelabra); // Elevated box seats along left wall var leftBoxSeat = game.addChild(LK.getAsset('vipBox', { anchorX: 1, anchorY: 0.5 })); leftBoxSeat.x = 300; leftBoxSeat.y = 1000 + i * 300; leftBoxSeat.scaleX = 0.4; leftBoxSeat.scaleY = 0.3; leftBoxSeat.rotation = 0.1; backgroundElements.push(leftBoxSeat); // Premium seats inside left box removed // Elevated box seats along right wall var rightBoxSeat = game.addChild(LK.getAsset('vipBox', { anchorX: 0, anchorY: 0.5 })); rightBoxSeat.x = 1748; rightBoxSeat.y = 1000 + i * 300; rightBoxSeat.scaleX = 0.4; rightBoxSeat.scaleY = 0.3; rightBoxSeat.rotation = -0.1; backgroundElements.push(rightBoxSeat); // Premium seats inside right box removed // Synchronized flickering for wall candelabras tween(wallCandelabra, { rotation: 0.03, alpha: 0.9 }, { duration: 1500 + Math.random() * 700, easing: tween.easeInOut, onFinish: function onFinish() { tween(wallCandelabra, { rotation: -0.03, alpha: 1.0 }, { duration: 1500 + Math.random() * 700, easing: tween.easeInOut, onFinish: arguments.callee }); } }); tween(rightWallCandelabra, { rotation: -0.03, alpha: 0.9 }, { duration: 1500 + Math.random() * 700, easing: tween.easeInOut, onFinish: function onFinish() { tween(rightWallCandelabra, { rotation: 0.03, alpha: 1.0 }, { duration: 1500 + Math.random() * 700, easing: tween.easeInOut, onFinish: arguments.callee }); } }); } // === ENTRANCE AREA === // Grand entrance architecture var entranceArch = game.addChild(LK.getAsset('archDetail', { anchorX: 0.5, anchorY: 1 })); entranceArch.x = 1750; entranceArch.y = 2732; entranceArch.scaleX = 2.5; entranceArch.scaleY = 1.8; backgroundElements.push(entranceArch); // Entrance pillars var leftEntrancePillar = game.addChild(LK.getAsset('pillar', { anchorX: 0.5, anchorY: 1 })); leftEntrancePillar.x = 1600; leftEntrancePillar.y = 2732; leftEntrancePillar.scaleX = 0.7; leftEntrancePillar.scaleY = 1.0; backgroundElements.push(leftEntrancePillar); var rightEntrancePillar = game.addChild(LK.getAsset('pillar', { anchorX: 0.5, anchorY: 1 })); rightEntrancePillar.x = 1900; rightEntrancePillar.y = 2732; rightEntrancePillar.scaleX = 0.7; rightEntrancePillar.scaleY = 1.0; backgroundElements.push(rightEntrancePillar); // Entrance lighting for (var i = 0; i < 3; i++) { var entranceCandelabra = game.addChild(LK.getAsset('candelabra', { anchorX: 0.5, anchorY: 1 })); entranceCandelabra.x = 1600 + i * 150; entranceCandelabra.y = 2550; entranceCandelabra.scaleX = 1.0; entranceCandelabra.scaleY = 1.3; backgroundElements.push(entranceCandelabra); // Flickering effect for entrance lighting tween(entranceCandelabra, { scaleY: 1.35, alpha: 0.95 }, { duration: 1200 + Math.random() * 600, easing: tween.easeInOut, onFinish: function onFinish() { tween(entranceCandelabra, { scaleY: 1.3, alpha: 1.0 }, { duration: 1200 + Math.random() * 600, easing: tween.easeInOut, onFinish: arguments.callee }); } }); } // === CURTAINS AND DRAPING === // Side curtains for proper hall framing var leftCurtain = game.addChild(LK.getAsset('curtain', { anchorX: 0, anchorY: 0 })); leftCurtain.x = 0; leftCurtain.y = 0; leftCurtain.scaleX = 0.5; leftCurtain.scaleY = 1.2; backgroundElements.push(leftCurtain); var rightCurtain = game.addChild(LK.getAsset('curtain', { anchorX: 1, anchorY: 0 })); rightCurtain.x = 2048; rightCurtain.y = 0; rightCurtain.scaleX = 0.5; rightCurtain.scaleY = 1.2; backgroundElements.push(rightCurtain); // === CONDUCTOR ANIMATION === // Conductor movement for liveliness tween(conductor, { rotation: 0.06 }, { duration: 1800, easing: tween.easeInOut, onFinish: function onFinish() { tween(conductor, { rotation: -0.06 }, { duration: 1800, easing: tween.easeInOut, onFinish: arguments.callee }); } }); // Function to create floating musical notes function playMelodia() { // Only start music if not muted if (!musicMuted) { try { LK.stopMusic(); console.log("Successfully stopped current music"); } catch (error) { console.log("Error stopping music:", error); } gameplayMusicPlaying = false; try { LK.playMusic('Melodia', { loop: true }); gameplayMusicPlaying = true; console.log("Successfully started Melodia"); } catch (error) { console.log("Error starting Melodia:", error); gameplayMusicPlaying = false; } } else { console.log("Music not started - muted:", musicMuted); } } function startMelodia() { // Only start music if not muted if (!musicMuted) { try { LK.playMusic('Melodia', { loop: true }); gameplayMusicPlaying = true; console.log("Successfully started Melodia"); } catch (error) { console.log("Error starting Melodia:", error); gameplayMusicPlaying = false; } } else { console.log("Music not started - muted:", musicMuted); } } // Manual music control function - stops current music function stopMusicSystem() { console.log("Stopping music system manually"); try { LK.stopMusic(); } catch (error) { console.log("Error stopping music:", error); } gameplayMusicPlaying = false; console.log("Music system stopped"); } function createFloatingNote() { var note = game.addChild(LK.getAsset('musicNote', { anchorX: 0.5, anchorY: 0.5 })); note.x = Math.random() * 2048; note.y = 2732 + 100; note.alpha = 0.6; floatingNotes.push(note); // Animate note floating upward tween(note, { y: -100, rotation: Math.PI * 2, alpha: 0 }, { duration: 8000 + Math.random() * 4000, easing: tween.easeOut, onFinish: function onFinish() { if (note.parent) { note.destroy(); } var index = floatingNotes.indexOf(note); if (index > -1) { floatingNotes.splice(index, 1); } } }); } // Create initial floating notes for (var i = 0; i < 5; i++) { LK.setTimeout(function () { createFloatingNote(); }, Math.random() * 3000); } // === MAIN MENU === menuContainer = new Container(); // Add menu container after all background elements to ensure it appears on top game.addChild(menuContainer); var menuBackground = menuContainer.addChild(LK.getAsset('woodPanel', { anchorX: 0.5, anchorY: 0.5 })); menuBackground.x = 1024; menuBackground.y = 1366; menuBackground.scaleX = 4.5; menuBackground.scaleY = 6; menuBackground.alpha = 0.95; menuBackground.tint = 0x3D2914; var startButton = menuContainer.addChild(LK.getAsset('BotonStart', { anchorX: 0.5, anchorY: 0.5 })); startButton.x = 1024; startButton.y = 1000; startButton.scaleX = 1.5; startButton.scaleY = 1.5; var tutorialButton = menuContainer.addChild(LK.getAsset('TutorialB', { anchorX: 0.5, anchorY: 0.5 })); tutorialButton.x = 1024; tutorialButton.y = 1300; tutorialButton.scaleX = 1.5; tutorialButton.scaleY = 1.5; startButton.down = function () { // Show title intro with curtains showTitleIntro(); }; tutorialButton.down = function () { gameState = 'tutorial'; tutorialStep = 0; menuContainer.visible = false; showTutorial(); }; // === TUTORIAL SYSTEM === tutorialContainer = new Container(); tutorialContainer.visible = false; // Add tutorial container after all background elements to ensure it appears on top game.addChild(tutorialContainer); // Add click handler to tutorial container for cycling through info tutorialContainer.down = function (x, y, obj) { var localX = x - tutorialContainer.x; var localY = y - tutorialContainer.y; // Check if click is in the main content area (not on buttons) if (localX > 200 && localX < 1800 && localY > 400 && localY < 1200) { if (currentInfoMode === 'towers' && currentInfoIndex < towerInfos.length - 1) { currentInfoIndex++; updateTutorialStep(); } else if (currentInfoMode === 'enemies' && currentInfoIndex < enemyInfos.length - 1) { currentInfoIndex++; updateTutorialStep(); } } }; var tutorialBackground = tutorialContainer.addChild(LK.getAsset('woodPanel', { anchorX: 0.5, anchorY: 0.5 })); tutorialBackground.x = 1024; tutorialBackground.y = 1366; tutorialBackground.scaleX = 4.5; tutorialBackground.scaleY = 5.5; tutorialBackground.alpha = 0.95; tutorialBackground.tint = 0x8B4513; // Add subtle background accent for button area var buttonAreaBackground = tutorialContainer.addChild(LK.getAsset('woodPanel', { anchorX: 0.5, anchorY: 0.5 })); buttonAreaBackground.x = 1024; buttonAreaBackground.y = 1700; // Moved down to match new button positions buttonAreaBackground.scaleX = 4.0; // Slightly wider for increased spacing buttonAreaBackground.scaleY = 2.5; // Taller to accommodate more separation buttonAreaBackground.alpha = 0.3; buttonAreaBackground.tint = 0x654321; var tutorialTitleText = tutorialContainer.addChild(new Text2('♪ TUTORIAL ♪', { size: 100, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); tutorialTitleText.anchor.set(0.5, 0.5); tutorialTitleText.x = 1024; tutorialTitleText.y = 300; var tutorialStepText = tutorialContainer.addChild(new Text2('', { size: 60, fill: 0xF5F5DC, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); tutorialStepText.anchor.set(0.5, 0.5); tutorialStepText.x = 1024; tutorialStepText.y = 1200; var towersButton = tutorialContainer.addChild(LK.getAsset('Towers', { anchorX: 0.5, anchorY: 0.5 })); towersButton.x = 800; towersButton.y = 1400; var enemiesButton = tutorialContainer.addChild(LK.getAsset('Enemiesss', { anchorX: 0.5, anchorY: 0.5 })); enemiesButton.x = 1248; enemiesButton.y = 1400; var startGameButton = tutorialContainer.addChild(LK.getAsset('StartGames', { anchorX: 0.5, anchorY: 0.5 })); startGameButton.x = 1024; startGameButton.y = 1600; startGameButton.scaleX = 1.2; startGameButton.scaleY = 1.2; var backButton = tutorialContainer.addChild(LK.getAsset('BackMenu', { anchorX: 0.5, anchorY: 0.5 })); backButton.x = 1024; backButton.y = 2100; // Add elegant text for all tutorial buttons // Define positioning variables for tutorial buttons var centerX = 1024; var centerY = 1500; var buttonRadius = 280; var towersButtonText = tutorialContainer.addChild(new Text2('♪ TOWERS ♪', { size: 50, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); towersButtonText.anchor.set(0.5, 0.5); towersButtonText.x = centerX - buttonRadius; towersButtonText.y = centerY + 90; // Directly below towers icon var enemiesButtonText = tutorialContainer.addChild(new Text2('♪ ENEMIES ♪', { size: 50, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); enemiesButtonText.anchor.set(0.5, 0.5); enemiesButtonText.x = centerX + buttonRadius; enemiesButtonText.y = centerY + 90; // Directly below enemies icon var startGameButtonText = tutorialContainer.addChild(new Text2('♪ START GAME ♪', { size: 55, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); startGameButtonText.anchor.set(0.5, 0.5); startGameButtonText.x = centerX; startGameButtonText.y = centerY + 370; // Directly below start game icon var backButtonText = tutorialContainer.addChild(new Text2('♪ BACK TO MENU ♪', { size: 50, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); backButtonText.anchor.set(0.5, 0.5); backButtonText.x = centerX; backButtonText.y = centerY + 610; // Directly below back button icon var tutorialSteps = ['Welcome to Concert Hall Defense!\n\nWaves are infinite - survive as long as possible!\nYou start with 1 life.\nEvery 10 waves, you gain 10 additional lives.\n\nChoose your path of learning:']; function showTutorial() { tutorialContainer.visible = true; // Move tutorial container to front to ensure visibility game.removeChild(tutorialContainer); game.addChild(tutorialContainer); updateTutorialStep(); } // Add tower reference image container var tutorialTowerImage = null; // Tower information data var towerInfos = [{ name: 'DRUM TOWER', asset: 'drumTower', info: 'Cost: $50 | Damage: 15 | Range: 165 | Speed: Fast\n\nArea damage tower that hits multiple enemies.\nSpecial: Explosive area damage around target.\n\nLevel 2: Increased damage and area\nLevel 3: Adds slow effect to area damage' }, { name: 'TRUMPET TOWER', asset: 'trumpetTower', info: 'Cost: $80 | Damage: 35 | Range: 180 | Speed: Medium\n\nPushback tower that sends enemies backward.\nSpecial: Forces enemies back down the path.\n\nLevel 2: Stronger pushback effect\nLevel 3: Adds stun chance to attacks' }, { name: 'GUITAR TOWER', asset: 'guitarTower', info: 'Cost: $120 | Damage: 20 | Range: 220 | Speed: Slow\n\nChain lightning tower hitting multiple targets.\nSpecial: Lightning chains between 3 enemies.\n\nLevel 2: Chains to 4 enemies\nLevel 3: Extended chaining capabilities' }, { name: 'VIOLIN TOWER', asset: 'violinTower', info: 'Cost: $100 | Damage: 10 | Range: 220 | Speed: Medium\n\nMagical control tower with slowing effects.\nSpecial: 25% chance to slow enemies significantly.\n\nLevel 2: Higher slow chance and duration\nLevel 3: Adds freeze chance to attacks' }, { name: 'DJ TOWER', asset: 'djTower', info: 'Cost: $200 | Damage: 60 | Range: 220 | Speed: Very Slow\n\nMassive area damage with blinding effects.\nSpecial: Large area damage + blind debuff.\n\nLevel 2: Increased area and blind duration\nLevel 3: Adds distortion DoT effect' }]; // Enemy information data var enemyInfos = [{ name: 'NOTA DESAFINADA', asset: 'notaDesafinada', info: 'Basic enemy with moderate health and speed.\nAppears from wave 1.\n\nHealth: 90 | Speed: Medium | Gold: 5\n\nThe most common musical threat.' }, { name: 'GLITCH AUDIO', asset: 'glitchAudio', info: 'Fast but fragile enemy.\nAppears from wave 5.\n\nHealth: 72 | Speed: Fast | Gold: 6\n\nQuick and unpredictable digital distortion.' }, { name: 'RUIDO BLANCO', asset: 'ruidoBlanco', info: 'Tanky enemy with high health.\nAppears from wave 10.\n\nHealth: 144 | Speed: Slow | Gold: 8\n\nStaticky interference that absorbs damage.' }, { name: 'ECO OSCURO', asset: 'ecoOscuro', info: 'Splits into two smaller enemies when killed.\nAppears from wave 15.\n\nHealth: 180 | Speed: Medium | Gold: 12\n\nDark echo that multiplies upon death.' }, { name: 'AUTOTUNE MALICIOSO', asset: 'autotuneMalicioso', info: 'Debuffs nearby towers, reducing their damage.\nAppears from wave 20.\n\nHealth: 270 | Speed: Medium-Slow | Gold: 20\n\nCorrupts the musical harmony of your defense.' }]; // Current info display mode var currentInfoMode = 'main'; // 'main', 'towers', 'enemies' var currentInfoIndex = 0; function updateTutorialStep() { // Remove previous image if exists if (tutorialTowerImage && tutorialTowerImage.parent) { tutorialContainer.removeChild(tutorialTowerImage); tutorialTowerImage = null; } // Hide/show navigation buttons based on mode towersButton.visible = currentInfoMode === 'main'; enemiesButton.visible = currentInfoMode === 'main'; startGameButton.visible = currentInfoMode === 'main'; towersButtonText.visible = currentInfoMode === 'main'; enemiesButtonText.visible = currentInfoMode === 'main'; startGameButtonText.visible = currentInfoMode === 'main'; backButtonText.visible = true; // Aesthetic positioning for main tutorial mode if (currentInfoMode === 'main') { // Create circular arrangement around center with better spacing, moved much lower var centerX = 1024; var centerY = 1500; // Moved down from 1150 to 1500 var buttonRadius = 280; // Increased separation from 200 to 280 // Position TOWERS button (left) towersButton.x = centerX - buttonRadius; towersButton.y = centerY; towersButton.scaleX = 1.8; towersButton.scaleY = 1.8; // Position ENEMIES button (right) enemiesButton.x = centerX + buttonRadius; enemiesButton.y = centerY; enemiesButton.scaleX = 1.8; enemiesButton.scaleY = 1.8; // Position START GAME button (center, larger, more separated) startGameButton.x = centerX; startGameButton.y = centerY + 280; // Increased separation to create more space startGameButton.scaleX = 1.5; startGameButton.scaleY = 1.5; // Position BACK button (bottom center, more separated) backButton.x = centerX; backButton.y = centerY + 520; // Increased separation for more space backButton.scaleX = 1.2; backButton.scaleY = 1.2; // Add entrance animation cascade effect towersButton.alpha = 0; enemiesButton.alpha = 0; startGameButton.alpha = 0; backButton.alpha = 0; towersButtonText.alpha = 0; enemiesButtonText.alpha = 0; startGameButtonText.alpha = 0; backButtonText.alpha = 0; // Animate buttons appearing with cascade effect tween(towersButton, { alpha: 1, scaleX: 1.8, scaleY: 1.8 }, { duration: 400, easing: tween.easeOut }); tween(towersButtonText, { alpha: 1 }, { duration: 400, easing: tween.easeOut }); LK.setTimeout(function () { tween(enemiesButton, { alpha: 1, scaleX: 1.8, scaleY: 1.8 }, { duration: 400, easing: tween.easeOut }); tween(enemiesButtonText, { alpha: 1 }, { duration: 400, easing: tween.easeOut }); }, 150); LK.setTimeout(function () { tween(startGameButton, { alpha: 1, scaleX: 1.5, scaleY: 1.5 }, { duration: 400, easing: tween.easeOut }); tween(startGameButtonText, { alpha: 1 }, { duration: 400, easing: tween.easeOut }); }, 300); LK.setTimeout(function () { tween(backButton, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 400, easing: tween.easeOut }); tween(backButtonText, { alpha: 1 }, { duration: 400, easing: tween.easeOut }); }, 450); } if (currentInfoMode === 'main') { // Show main tutorial text tutorialStepText.setText(tutorialSteps[0]); // Move tutorial text up for main tutorial to avoid collision tutorialStepText.y = 800; } else if (currentInfoMode === 'towers') { // Reset position for other modes tutorialStepText.y = 1200; // Show tower information if (currentInfoIndex < towerInfos.length) { var towerInfo = towerInfos[currentInfoIndex]; var progressText = '(' + (currentInfoIndex + 1) + '/' + towerInfos.length + ')'; var navigationHint = currentInfoIndex < towerInfos.length - 1 ? '\n\n→ CLICK ANYWHERE TO SEE NEXT TOWER ←' : '\n\n→ ALL TOWERS SHOWN ←'; var mainText = towerInfo.name + ' ' + progressText + '\n\n' + towerInfo.info; tutorialStepText.setText(mainText + navigationHint); tutorialStepText.tint = 0xF5F5DC; // Set main text to original color // Apply cyan color to navigation instruction text if (currentInfoIndex < towerInfos.length - 1) { if (tutorialStepText.text) { var textParts = tutorialStepText.text.split('→ CLICK ANYWHERE TO SEE NEXT TOWER ←'); if (textParts.length === 2) { tutorialStepText.setText(textParts[0]); var navText = tutorialStepText.parent.addChild(new Text2('→ CLICK ANYWHERE TO SEE NEXT TOWER ←', { size: 60, fill: 0x00FFFF, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); navText.anchor.set(0.5, 0.5); navText.x = 1024; navText.y = 1150; } } } // Show tower image tutorialTowerImage = tutorialContainer.addChild(LK.getAsset(towerInfo.asset, { anchorX: 0.5, anchorY: 0.5 })); tutorialTowerImage.x = 1024; tutorialTowerImage.y = 650; tutorialTowerImage.scaleX = 2.0; tutorialTowerImage.scaleY = 2.0; // Show navigation if (currentInfoIndex > 0) { // Previous tower navigation } else { // Back to menu navigation } } else { currentInfoMode = 'main'; currentInfoIndex = 0; updateTutorialStep(); return; } } else if (currentInfoMode === 'enemies') { // Reset position for enemy mode to match main tutorial positioning tutorialStepText.y = 1200; // Show enemy information if (currentInfoIndex < enemyInfos.length) { var enemyInfo = enemyInfos[currentInfoIndex]; var progressText = '(' + (currentInfoIndex + 1) + '/' + enemyInfos.length + ')'; var navigationHint = currentInfoIndex < enemyInfos.length - 1 ? '\n\n→ CLICK ANYWHERE TO SEE NEXT ENEMY ←' : '\n\n→ ALL ENEMIES SHOWN ←'; var mainText = enemyInfo.name + ' ' + progressText + '\n\n' + enemyInfo.info; tutorialStepText.setText(mainText + navigationHint); tutorialStepText.tint = 0xF5F5DC; // Set main text to original color // Apply cyan color to navigation instruction text if (currentInfoIndex < enemyInfos.length - 1) { if (tutorialStepText.text) { var textParts = tutorialStepText.text.split('→ CLICK ANYWHERE TO SEE NEXT ENEMY ←'); if (textParts.length === 2) { tutorialStepText.setText(textParts[0]); var navText = tutorialStepText.parent.addChild(new Text2('→ CLICK ANYWHERE TO SEE NEXT ENEMY ←', { size: 60, fill: 0x00FFFF, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); navText.anchor.set(0.5, 0.5); navText.x = 1024; navText.y = 1150; } } } // Show enemy image tutorialTowerImage = tutorialContainer.addChild(LK.getAsset(enemyInfo.asset, { anchorX: 0.5, anchorY: 0.5 })); tutorialTowerImage.x = 1024; tutorialTowerImage.y = 650; tutorialTowerImage.scaleX = 2.0; tutorialTowerImage.scaleY = 2.0; // Show navigation if (currentInfoIndex > 0) { // Previous enemy navigation } else { // Back to menu navigation } } else { currentInfoMode = 'main'; currentInfoIndex = 0; updateTutorialStep(); return; } } } towersButton.down = function () { // Add bounce effect on click tween(towersButton, { scaleX: 2.0, scaleY: 2.0 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(towersButton, { scaleX: 1.8, scaleY: 1.8 }, { duration: 150, easing: tween.easeIn }); } }); currentInfoMode = 'towers'; currentInfoIndex = 0; updateTutorialStep(); }; enemiesButton.down = function () { // Add bounce effect on click tween(enemiesButton, { scaleX: 2.0, scaleY: 2.0 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(enemiesButton, { scaleX: 1.8, scaleY: 1.8 }, { duration: 150, easing: tween.easeIn }); } }); currentInfoMode = 'enemies'; currentInfoIndex = 0; updateTutorialStep(); }; startGameButton.down = function () { // Add bounce effect on click tween(startGameButton, { scaleX: 1.7, scaleY: 1.7 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(startGameButton, { scaleX: 1.5, scaleY: 1.5 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { gameState = 'playing'; tutorialContainer.visible = false; startNextWave(); } }); } }); }; backButton.down = function () { if (currentInfoMode === 'towers') { if (currentInfoIndex > 0) { currentInfoIndex--; updateTutorialStep(); } else { currentInfoMode = 'main'; currentInfoIndex = 0; updateTutorialStep(); } } else if (currentInfoMode === 'enemies') { if (currentInfoIndex > 0) { currentInfoIndex--; updateTutorialStep(); } else { currentInfoMode = 'main'; currentInfoIndex = 0; updateTutorialStep(); } } else { gameState = 'menu'; tutorialContainer.visible = false; menuContainer.visible = true; } }; // Path definition - reversed to go from bottom to top var pathPoints = [{ x: 1750, y: 2250 }, { x: 1700, y: 2100 }, { x: 1650, y: 1950 }, { x: 1550, y: 1800 }, { x: 1400, y: 1750 }, { x: 1250, y: 1700 }, { x: 1100, y: 1650 }, { x: 950, y: 1600 }, { x: 800, y: 1550 }, { x: 650, y: 1500 }, { x: 500, y: 1450 }, { x: 350, y: 1400 }, { x: 200, y: 1300 }, { x: 100, y: 1150 }, { x: 200, y: 1000 }, { x: 350, y: 950 }, { x: 500, y: 900 }, { x: 650, y: 800 }, { x: 700, y: 650 }, { x: 650, y: 500 }, { x: 500, y: 400 }, { x: 350, y: 300 }, { x: 200, y: 300 }, { x: 50, y: 300 }]; // Create elegant wooden walkway with proper connectivity for (var i = 0; i < pathPoints.length; i++) { var pathNode = game.addChild(LK.getAsset('woodPanel', { anchorX: 0.5, anchorY: 0.5 })); pathNode.x = pathPoints[i].x; pathNode.y = pathPoints[i].y; pathNode.scaleX = 0.4; pathNode.scaleY = 0.4; pathNode.alpha = 0.95; pathNode.tint = 0xD2691E; // Warm wood color // Calculate rotation based on path direction for better connectivity if (i < pathPoints.length - 1) { var dx = pathPoints[i + 1].x - pathPoints[i].x; var dy = pathPoints[i + 1].y - pathPoints[i].y; pathNode.rotation = Math.atan2(dy, dx); } else if (i > 0) { var dx = pathPoints[i].x - pathPoints[i - 1].x; var dy = pathPoints[i].y - pathPoints[i - 1].y; pathNode.rotation = Math.atan2(dy, dx); } // Add gentle animation to make the wooden path feel alive tween(pathNode, { scaleX: 0.42, alpha: 1.0 }, { duration: 2000 + Math.random() * 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(pathNode, { scaleX: 0.4, alpha: 0.95 }, { duration: 2000 + Math.random() * 1000, easing: tween.easeInOut, onFinish: arguments.callee }); } }); // Add connecting wooden planks between path points for better continuity if (i < pathPoints.length - 1) { var distance = Math.sqrt(Math.pow(pathPoints[i + 1].x - pathPoints[i].x, 2) + Math.pow(pathPoints[i + 1].y - pathPoints[i].y, 2)); var numConnectors = Math.floor(distance / 80); // Add connectors every 80 pixels for (var j = 1; j <= numConnectors; j++) { var ratio = j / (numConnectors + 1); var connectorX = pathPoints[i].x + (pathPoints[i + 1].x - pathPoints[i].x) * ratio; var connectorY = pathPoints[i].y + (pathPoints[i + 1].y - pathPoints[i].y) * ratio; var connector = game.addChild(LK.getAsset('woodPanel', { anchorX: 0.5, anchorY: 0.5 })); connector.x = connectorX; connector.y = connectorY; connector.scaleX = 0.3; connector.scaleY = 0.3; connector.alpha = 0.8; connector.tint = 0xCD853F; // Slightly different wood tone for variety var dx = pathPoints[i + 1].x - pathPoints[i].x; var dy = pathPoints[i + 1].y - pathPoints[i].y; connector.rotation = Math.atan2(dy, dx) + (Math.random() - 0.5) * 0.2; } } } // Create goal var goal = game.addChild(LK.getAsset('goal', { anchorX: 0.5, anchorY: 0.5 })); goal.x = pathPoints[pathPoints.length - 1].x; goal.y = pathPoints[pathPoints.length - 1].y; // === BUILD SPOTS CREATION === var purpleNumbers = []; // Keep empty array for build spot reference // Create build spots positioned every 2 wooden path tables on both left and right sides for (var i = 0; i < pathPoints.length - 1; i += 2) { // Get the path point for this segment var pathPoint = pathPoints[i]; // Calculate direction to next path point for perpendicular positioning var nextPathPoint = pathPoints[Math.min(i + 1, pathPoints.length - 1)]; var pathDx = nextPathPoint.x - pathPoint.x; var pathDy = nextPathPoint.y - pathPoint.y; var pathLength = Math.sqrt(pathDx * pathDx + pathDy * pathDy); if (pathLength > 0) { // Normalize the path direction pathDx /= pathLength; pathDy /= pathLength; // Calculate perpendicular vectors (90 degrees rotated) var perpDx = -pathDy; // Left side perpendicular var perpDy = pathDx; // Distance from path center to build spots var offsetDistance = 150; // Create left side build spot var leftBuildSpot = new BuildSpot(buildSpots.length + 1); leftBuildSpot.x = pathPoint.x + perpDx * offsetDistance; leftBuildSpot.y = pathPoint.y + perpDy * offsetDistance; buildSpots.push(leftBuildSpot); game.addChild(leftBuildSpot); // Create right side build spot var rightBuildSpot = new BuildSpot(buildSpots.length + 1); rightBuildSpot.x = pathPoint.x - perpDx * offsetDistance; rightBuildSpot.y = pathPoint.y - perpDy * offsetDistance; buildSpots.push(rightBuildSpot); game.addChild(rightBuildSpot); } } // UI Elements // Gold background panel for better visibility var goldBackground = LK.getAsset('fondo', { anchorX: 0.5, anchorY: 0.5 }); goldBackground.scaleX = 0.5; goldBackground.scaleY = 0.25; goldBackground.alpha = 0.9; goldBackground.tint = 0x8B4513; LK.gui.topRight.addChild(goldBackground); goldBackground.x = -200; goldBackground.y = 100; // Gold icon and text var goldIcon = LK.getAsset('gold', { anchorX: 0.5, anchorY: 0.5 }); goldIcon.tint = 0xFFD700; goldIcon.scaleX = 0.8; goldIcon.scaleY = 0.8; LK.gui.topRight.addChild(goldIcon); goldIcon.x = -280; goldIcon.y = 100; var goldText = new Text2(gold, { size: 80, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" }); goldText.anchor.set(0.5, 0.5); LK.gui.topRight.addChild(goldText); goldText.x = -200; goldText.y = 100; // Lives background panel for better visibility var livesBackground = LK.getAsset('fondo', { anchorX: 0.5, anchorY: 0.5 }); livesBackground.scaleX = 0.5; livesBackground.scaleY = 0.25; livesBackground.alpha = 0.9; livesBackground.tint = 0x8B4513; LK.gui.topRight.addChild(livesBackground); livesBackground.x = -200; livesBackground.y = 300; // Lives icon and text var livesIcon = LK.getAsset('lives', { anchorX: 0.5, anchorY: 0.5 }); livesIcon.tint = 0xFF0000; livesIcon.scaleX = 0.8; livesIcon.scaleY = 0.8; LK.gui.topRight.addChild(livesIcon); livesIcon.x = -280; livesIcon.y = 300; var livesText = new Text2(lives, { size: 85, fill: 0xFF6B6B, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" }); livesText.anchor.set(0.5, 0.5); LK.gui.topRight.addChild(livesText); livesText.x = -200; livesText.y = 300; var waveText = new Text2('♪ WAVE ' + wave + ' ♪', { size: 80, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" }); waveText.anchor.set(0.5, 0.5); LK.gui.bottomLeft.addChild(waveText); waveText.x = 320; waveText.y = -150; // Add wave background for better visibility var waveBackground = LK.getAsset('fondo', { anchorX: 0.5, anchorY: 0.5 }); waveBackground.scaleX = 1.2; waveBackground.scaleY = 0.4; waveBackground.alpha = 0.7; waveBackground.tint = 0x8B4513; LK.gui.bottomLeft.addChild(waveBackground); waveBackground.x = 320; waveBackground.y = -150; // Move wave text to front LK.gui.bottomLeft.removeChild(waveText); LK.gui.bottomLeft.addChild(waveText); // Add music mute button var musicMuted = false; var muteButtonBackground = LK.getAsset('fondo', { anchorX: 0.5, anchorY: 0.5 }); muteButtonBackground.scaleX = 0.4; muteButtonBackground.scaleY = 0.25; muteButtonBackground.alpha = 0.9; muteButtonBackground.tint = 0x8B4513; LK.gui.topRight.addChild(muteButtonBackground); muteButtonBackground.x = -200; muteButtonBackground.y = 450; var muteButton = LK.getAsset('violinTower', { anchorX: 0.5, anchorY: 0.5 }); muteButton.scaleX = 0.8; muteButton.scaleY = 0.8; LK.gui.topRight.addChild(muteButton); muteButton.x = -200; muteButton.y = 450; var muteText = new Text2('♪ MUSIC ♪', { size: 45, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" }); muteText.anchor.set(0.5, 0.5); LK.gui.topRight.addChild(muteText); muteText.x = -200; muteText.y = 500; // Start Wave button var startWaveBackground = LK.getAsset('fondo', { anchorX: 0.5, anchorY: 0.5 }); startWaveBackground.scaleX = 0.8; startWaveBackground.scaleY = 0.4; startWaveBackground.alpha = 0.9; startWaveBackground.tint = 0x8B4513; LK.gui.bottomRight.addChild(startWaveBackground); startWaveBackground.x = -200; startWaveBackground.y = -150; var startWaveButton = LK.getAsset('trumpetTower', { anchorX: 0.5, anchorY: 0.5 }); startWaveButton.scaleX = 1.0; startWaveButton.scaleY = 1.0; LK.gui.bottomRight.addChild(startWaveButton); startWaveButton.x = -200; startWaveButton.y = -150; var startWaveText = new Text2('♪ START WAVE ♪', { size: 50, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" }); startWaveText.anchor.set(0.5, 0.5); LK.gui.bottomRight.addChild(startWaveText); startWaveText.x = -200; startWaveText.y = -100; var autoStartText = new Text2('Auto: 5s', { size: 35, fill: 0xFFFFFF, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" }); autoStartText.anchor.set(0.5, 0.5); LK.gui.bottomRight.addChild(autoStartText); autoStartText.x = -200; autoStartText.y = -70; // Grand Piano Power Button (initially hidden) grandPianoButtonBackground = LK.getAsset('fondo', { anchorX: 0.5, anchorY: 0.5 }); grandPianoButtonBackground.scaleX = 0.8; grandPianoButtonBackground.scaleY = 0.5; grandPianoButtonBackground.alpha = 0.9; grandPianoButtonBackground.tint = 0x8B4513; LK.gui.bottomLeft.addChild(grandPianoButtonBackground); grandPianoButtonBackground.x = 200; grandPianoButtonBackground.y = -400; grandPianoButtonBackground.visible = false; grandPianoPowerButton = LK.getAsset('grandPiano', { anchorX: 0.5, anchorY: 0.5 }); grandPianoPowerButton.scaleX = 1.0; grandPianoPowerButton.scaleY = 1.0; LK.gui.bottomLeft.addChild(grandPianoPowerButton); grandPianoPowerButton.x = 200; grandPianoPowerButton.y = -400; grandPianoPowerButton.visible = false; grandPianoPowerText = new Text2('♪ GRAND PIANO ♪', { size: 45, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" }); grandPianoPowerText.anchor.set(0.5, 0.5); LK.gui.bottomLeft.addChild(grandPianoPowerText); grandPianoPowerText.x = 200; grandPianoPowerText.y = -350; grandPianoPowerText.visible = false; grandPianoCooldownText = new Text2('Ready!', { size: 35, fill: 0x00FFFF, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" }); grandPianoCooldownText.anchor.set(0.5, 0.5); LK.gui.bottomLeft.addChild(grandPianoCooldownText); grandPianoCooldownText.x = 200; grandPianoCooldownText.y = -320; grandPianoCooldownText.visible = false; muteButton.down = function () { if (musicMuted) { // Unmuting - start appropriate music if (gameState === 'playing') { console.log("Unmuting - starting Melodia"); playMelodia(); } else { // In menu, play menu music console.log("Unmuting - starting menu music"); try { LK.playMusic('MusicaInicio', { loop: true }); } catch (error) { console.log("Error starting menu music:", error); } } muteText.setText('♪ MUSIC ♪'); muteButton.tint = 0xFFFFFF; musicMuted = false; } else { // Muting - stop all music and reset system console.log("Muting music"); stopMusicSystem(); muteText.setText('♪ MUTED ♪'); muteButton.tint = 0x888888; musicMuted = true; } }; startWaveButton.down = function () { if (gameState === 'playing' && wavePaused && enemies.length === 0) { startCurrentWave(); } }; grandPianoPowerButton.down = function () { if (grandPianoPowerUnlocked && grandPianoCooldownRemaining === 0 && !grandPianoPowerActive && gameState === 'playing') { enterGrandPianoTargetingMode(); } }; // Build menu elements (initially hidden) var buildMenuContainer = new Container(); game.addChild(buildMenuContainer); buildMenuContainer.visible = false; var drumButton = buildMenuContainer.addChild(LK.getAsset('drumTower', { anchorX: 0.5, anchorY: 0.5 })); var guitarButton = buildMenuContainer.addChild(LK.getAsset('guitarTower', { anchorX: 0.5, anchorY: 0.5 })); var violinButton = buildMenuContainer.addChild(LK.getAsset('violinTower', { anchorX: 0.5, anchorY: 0.5 })); var trumpetButton = buildMenuContainer.addChild(LK.getAsset('trumpetTower', { anchorX: 0.5, anchorY: 0.5 })); var djButton = buildMenuContainer.addChild(LK.getAsset('djTower', { anchorX: 0.5, anchorY: 0.5 })); var drumText = buildMenuContainer.addChild(new Text2('$50', { size: 45, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); drumText.anchor.set(0.5, 0); var trumpetText = buildMenuContainer.addChild(new Text2('$80', { size: 45, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); trumpetText.anchor.set(0.5, 0); var guitarText = buildMenuContainer.addChild(new Text2('$120', { size: 45, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); guitarText.anchor.set(0.5, 0); var violinText = buildMenuContainer.addChild(new Text2('$100', { size: 45, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); violinText.anchor.set(0.5, 0); var djText = buildMenuContainer.addChild(new Text2('$200', { size: 45, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); djText.anchor.set(0.5, 0); // Upgrade menu elements (initially hidden) upgradeMenuContainer = new Container(); game.addChild(upgradeMenuContainer); upgradeMenuContainer.visible = false; var upgradeMenuBackground = upgradeMenuContainer.addChild(LK.getAsset('woodPanel', { anchorX: 0.5, anchorY: 0.5 })); upgradeMenuBackground.scaleX = 1.2; upgradeMenuBackground.scaleY = 0.8; upgradeMenuBackground.alpha = 0.95; upgradeMenuBackground.tint = 0x8B4513; var upgradeButton = upgradeMenuContainer.addChild(LK.getAsset('upgradeIcon', { anchorX: 0.5, anchorY: 0.5 })); upgradeButton.x = -80; upgradeButton.y = 0; upgradeButton.scaleX = 1.0; upgradeButton.scaleY = 1.0; var removeButton = upgradeMenuContainer.addChild(LK.getAsset('removeIcon', { anchorX: 0.5, anchorY: 0.5 })); removeButton.x = 80; removeButton.y = 0; removeButton.scaleX = 1.0; removeButton.scaleY = 1.0; var upgradeCostText = upgradeMenuContainer.addChild(new Text2('UPGRADE\n$50', { size: 40, fill: 0xFFFFFF, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); upgradeCostText.anchor.set(0.5, 0); upgradeCostText.x = -80; upgradeCostText.y = 50; var removeText = upgradeMenuContainer.addChild(new Text2('REMOVE\nRefund 50%', { size: 40, fill: 0xFFFFFF, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); removeText.anchor.set(0.5, 0); removeText.x = 120; removeText.y = 50; var towerInfoText = upgradeMenuContainer.addChild(new Text2('Level 1 Tower', { size: 45, fill: 0xFFFFFF, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); towerInfoText.anchor.set(0.5, 1); towerInfoText.x = 0; towerInfoText.y = -50; function updateUI() { goldText.setText(gold); livesText.setText(lives); waveText.setText('♪ WAVE ' + wave + ' ♪'); updateGrandPianoPowerUI(); } function showBuildMenu(buildSpot) { showingBuildMenu = true; selectedBuildSpot = buildSpot; buildMenuContainer.visible = true; // Ensure build menu appears above all other elements game.removeChild(buildMenuContainer); game.addChild(buildMenuContainer); // Position menu near build spot buildMenuContainer.x = buildSpot.x; buildMenuContainer.y = buildSpot.y - 200; // Create circular menu layout var radius = 120; var centerX = 0; var centerY = 0; // Position buttons in a circle var angleStep = Math.PI * 2 / 5; // 5 buttons in circle drumButton.x = centerX + Math.cos(0) * radius; drumButton.y = centerY + Math.sin(0) * radius; trumpetButton.x = centerX + Math.cos(angleStep) * radius; trumpetButton.y = centerY + Math.sin(angleStep) * radius; guitarButton.x = centerX + Math.cos(angleStep * 2) * radius; guitarButton.y = centerY + Math.sin(angleStep * 2) * radius; violinButton.x = centerX + Math.cos(angleStep * 3) * radius; violinButton.y = centerY + Math.sin(angleStep * 3) * radius; djButton.x = centerX + Math.cos(angleStep * 4) * radius; djButton.y = centerY + Math.sin(angleStep * 4) * radius; // Position text closer to buttons in circle var textRadius = radius + 30; drumText.x = centerX + Math.cos(0) * textRadius; drumText.y = centerY + Math.sin(0) * textRadius; trumpetText.x = centerX + Math.cos(angleStep) * textRadius; trumpetText.y = centerY + Math.sin(angleStep) * textRadius; guitarText.x = centerX + Math.cos(angleStep * 2) * textRadius; guitarText.y = centerY + Math.sin(angleStep * 2) * textRadius; violinText.x = centerX + Math.cos(angleStep * 3) * textRadius; violinText.y = centerY + Math.sin(angleStep * 3) * textRadius; djText.x = centerX + Math.cos(angleStep * 4) * textRadius; djText.y = centerY + Math.sin(angleStep * 4) * textRadius; // Start buttons small and animate them opening drumButton.scaleX = 0.1; drumButton.scaleY = 0.1; trumpetButton.scaleX = 0.1; trumpetButton.scaleY = 0.1; guitarButton.scaleX = 0.1; guitarButton.scaleY = 0.1; violinButton.scaleX = 0.1; violinButton.scaleY = 0.1; djButton.scaleX = 0.1; djButton.scaleY = 0.1; // Animate buttons appearing in sequence with slight delays tween(drumButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeOut }); LK.setTimeout(function () { tween(trumpetButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeOut }); }, 100); LK.setTimeout(function () { tween(guitarButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeOut }); }, 200); LK.setTimeout(function () { tween(violinButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeOut }); }, 300); LK.setTimeout(function () { tween(djButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeOut }); }, 400); } function hideBuildMenu() { showingBuildMenu = false; selectedBuildSpot = null; buildMenuContainer.visible = false; } function showUpgradeMenu(tower) { if (showingBuildMenu) return; // Don't show if build menu is open // Force hide any existing upgrade menu first if (showingUpgradeMenu) { hideUpgradeMenu(); // Wait for hide animation to complete before showing new menu LK.setTimeout(function () { actuallyShowUpgradeMenu(tower); }, 250); return; } actuallyShowUpgradeMenu(tower); } function actuallyShowUpgradeMenu(tower) { showingUpgradeMenu = true; selectedTower = tower; // Ensure container is properly reset upgradeMenuContainer.visible = true; upgradeMenuContainer.scaleX = 0.1; upgradeMenuContainer.scaleY = 0.1; upgradeMenuContainer.alpha = 0; // Position menu near tower but higher up upgradeMenuContainer.x = tower.x; upgradeMenuContainer.y = tower.y - 250; // Hide background for circular menu upgradeMenuBackground.visible = false; // Position buttons in circular layout var radius = 80; upgradeButton.x = -radius; upgradeButton.y = 0; removeButton.x = radius; removeButton.y = 0; // Update menu content based on tower level and type var upgradeCost = 0; if (tower.towerType === 'drum') { upgradeCost = tower.level === 1 ? 90 : tower.level === 2 ? 160 : 0; } else if (tower.towerType === 'trumpet') { upgradeCost = tower.level === 1 ? 130 : tower.level === 2 ? 200 : 0; } else if (tower.towerType === 'guitar') { upgradeCost = tower.level === 1 ? 180 : tower.level === 2 ? 250 : 0; } else if (tower.towerType === 'violin') { upgradeCost = tower.level === 1 ? 160 : tower.level === 2 ? 220 : 0; } else if (tower.towerType === 'dj') { upgradeCost = tower.level === 1 ? 280 : tower.level === 2 ? 350 : 0; } var canUpgrade = tower.level < 3 && gold >= upgradeCost; var refundAmount = Math.floor(tower.totalCost * 0.5); if (tower.level >= 3) { upgradeCostText.setText('UPGRADE\nMAX LEVEL'); upgradeCostText.tint = 0x888888; upgradeButton.tint = 0x666666; } else { upgradeCostText.setText('UPGRADE\n$' + upgradeCost); upgradeCostText.tint = canUpgrade ? 0x00FF00 : 0xFF4444; upgradeButton.tint = canUpgrade ? 0xFFFFFF : 0x888888; } removeText.setText('REMOVE\nRefund $' + refundAmount); towerInfoText.setText('Level ' + tower.level + ' ' + tower.towerType.toUpperCase()); // Position text relative to circular buttons - center upgrade cost below upgrade button upgradeCostText.x = -radius; // Center below upgrade button upgradeCostText.y = 50; removeText.x = radius; removeText.y = 50; towerInfoText.x = 0; towerInfoText.y = -50; // Animate menu opening tween(upgradeMenuContainer, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 300, easing: tween.easeOut }); } function hideUpgradeMenu() { showingUpgradeMenu = false; selectedTower = null; tween(upgradeMenuContainer, { scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { upgradeMenuContainer.visible = false; } }); } function removeTower(tower) { if (!tower) return; // Calculate refund (50% of total investment) var refundAmount = Math.floor(tower.totalCost * 0.5); gold += refundAmount; // Find and free the build spot for (var i = 0; i < buildSpots.length; i++) { var spot = buildSpots[i]; var distance = Math.sqrt((spot.x - tower.x) * (spot.x - tower.x) + (spot.y - tower.y) * (spot.y - tower.y)); if (distance < 50) { // Close enough to be the same spot spot.occupied = false; break; } } // Remove from towers array var towerIndex = towers.indexOf(tower); if (towerIndex > -1) { towers.splice(towerIndex, 1); } // Destruction animation and sound LK.getSound('Destruir').play(); // Tower destruction sound tween(tower, { scaleX: 0.1, scaleY: 0.1, rotation: Math.PI * 2, alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { tower.destroy(); } }); updateUI(); hideUpgradeMenu(); } function buildTower(towerType, cost) { console.log("buildTower called:", towerType, "cost:", cost, "gold:", gold, "selectedBuildSpot:", selectedBuildSpot); if (!selectedBuildSpot) { console.log("No build spot selected"); return; } if (gold < cost) { console.log("Not enough gold:", gold, "needed:", cost); return; } console.log("Building tower..."); // Deduct gold first gold -= cost; // Track gold spent totalGoldSpent += cost; console.log("Gold after purchase:", gold); var tower = new Tower(towerType); tower.x = selectedBuildSpot.x; tower.y = selectedBuildSpot.y; towers.push(tower); game.addChild(tower); selectedBuildSpot.occupied = true; console.log("Tower built successfully at:", tower.x, tower.y); updateUI(); hideBuildMenu(); // Play specific sound for trumpet and guitar towers if (towerType === 'trumpet') { LK.getSound('sonidotrompeta').play(); } else if (towerType === 'guitar') { LK.getSound('SonidoGuitarra').play(); } else if (towerType === 'violin') { LK.getSound('SonidoV').play(); } else if (towerType === 'dj') { LK.getSound('SonidoDJ').play(); } else { LK.getSound('build').play(); } } // Button handlers with proper event handling drumButton.down = function (x, y, obj) { console.log("Drum button clicked, gold:", gold); if (gold >= 50) { buildTower('drum', 50); } else { console.log("Not enough gold for drum tower"); LK.getSound('eror').play(); } }; guitarButton.down = function (x, y, obj) { console.log("Guitar button clicked, gold:", gold); if (gold >= 120) { buildTower('guitar', 120); } else { console.log("Not enough gold for guitar tower"); LK.getSound('eror').play(); } }; violinButton.down = function (x, y, obj) { console.log("Violin button clicked, gold:", gold); if (gold >= 100) { buildTower('violin', 100); } else { console.log("Not enough gold for violin tower"); LK.getSound('eror').play(); } }; trumpetButton.down = function (x, y, obj) { console.log("Trumpet button clicked, gold:", gold); if (gold >= 80) { buildTower('trumpet', 80); } else { console.log("Not enough gold for trumpet tower"); LK.getSound('eror').play(); } }; djButton.down = function (x, y, obj) { console.log("DJ button clicked, gold:", gold); if (gold >= 200) { buildTower('dj', 200); } else { console.log("Not enough gold for DJ tower"); LK.getSound('eror').play(); } }; // Upgrade menu button handlers upgradeButton.down = function (x, y, obj) { if (selectedTower && selectedTower.level < 3) { var upgradeCost = 0; if (selectedTower.towerType === 'drum') { upgradeCost = selectedTower.level === 1 ? 90 : 160; } else if (selectedTower.towerType === 'trumpet') { upgradeCost = selectedTower.level === 1 ? 130 : 200; } else if (selectedTower.towerType === 'guitar') { upgradeCost = selectedTower.level === 1 ? 180 : 250; } else if (selectedTower.towerType === 'violin') { upgradeCost = selectedTower.level === 1 ? 160 : 220; } else if (selectedTower.towerType === 'dj') { upgradeCost = selectedTower.level === 1 ? 280 : 350; } if (gold >= upgradeCost) { if (selectedTower.upgrade()) { LK.getSound('build').play(); // Upgrade sound hideUpgradeMenu(); } } } }; removeButton.down = function (x, y, obj) { if (selectedTower) { removeTower(selectedTower); } }; // Game click handler to hide menus - only if not clicking on buttons or build spots game.move = function (x, y, obj) { if (grandPianoGhostMode && grandPianoGhost && grandPianoTargetArea) { grandPianoGhost.x = x; grandPianoGhost.y = y; grandPianoTargetArea.x = x; grandPianoTargetArea.y = y; } }; game.down = function (x, y, obj) { // Handle Grand Piano targeting mode first if (grandPianoGhostMode) { activateGrandPianoPowerAt(x, y); return; } // Priority: Build menu takes precedence if (showingBuildMenu) { // Check if clicked within circular menu area var relativeX = x - buildMenuContainer.x; var relativeY = y - buildMenuContainer.y; var distanceFromCenter = Math.sqrt(relativeX * relativeX + relativeY * relativeY); var clickedOnButton = distanceFromCenter <= 180; // Circular area radius + buffer if (!clickedOnButton) { hideBuildMenu(); } return; // Prevent other interactions when build menu is open } // Check if we clicked on a tower first - if so, don't hide upgrade menu var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx = x - tower.x; var dy = y - tower.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= 60) { // Tower click radius clickedOnTower = true; break; } } // Handle upgrade menu interactions if (showingUpgradeMenu) { // Don't hide if we clicked on a tower (allowing tower switching) if (clickedOnTower) { return; } // Check if clicked within upgrade menu area var relativeX = x - upgradeMenuContainer.x; var relativeY = y - upgradeMenuContainer.y; var distanceFromCenter = Math.sqrt(relativeX * relativeX + relativeY * relativeY); var clickedOnButton = distanceFromCenter <= 150; // Menu area radius + buffer if (!clickedOnButton) { hideUpgradeMenu(); } return; // Prevent other interactions when upgrade menu is open } }; function calculateEnemiesForWave(waveNumber) { // Formula: enemiesTotal = round(10 × (1.15 ^ (waveActual - 1))) var baseCount = Math.round(10 * Math.pow(1.15, waveNumber - 1)); // Modify for special waves if (currentSpecialWave === 'massive') { baseCount = Math.round(baseCount * 1.8); // 80% more enemies } else if (currentSpecialWave === 'runners') { baseCount = Math.round(baseCount * 1.3); // 30% more fast enemies } return baseCount; } function getSpawnIntervalForWave(waveNumber) { // Formula: max(0.5, 1.5 - (wave × 0.03)) in seconds, converted to ticks var intervalInSeconds = Math.max(0.5, 1.5 - waveNumber * 0.03); var intervalInTicks = Math.round(intervalInSeconds * 60); // Convert to 60fps ticks return intervalInTicks; } function getEnemyTypesForWave(waveNumber) { var availableTypes = []; // Check for special themed waves from wave 8+ if (waveNumber >= 8) { var isSpecialWave = Math.random() < 0.25; // 25% chance if (isSpecialWave) { var specialWaves = ['runners', 'armored', 'massive']; var specialWave = specialWaves[Math.floor(Math.random() * specialWaves.length)]; // Store special wave type for spawn modifications currentSpecialWave = specialWave; if (specialWave === 'runners') { return ['glitchAudio']; // Fast, low HP enemies } else if (specialWave === 'armored') { return ['ruidoBlanco']; // Slow, high HP enemies } else if (specialWave === 'massive') { // More enemies but weaker return ['notaDesafinada']; } } } currentSpecialWave = null; // Normal wave // Wave 1-4: Only Nota Desafinada if (waveNumber <= 4) { availableTypes = ['notaDesafinada']; } // Wave 5-9: Add Glitch de Audio else if (waveNumber >= 5 && waveNumber <= 9) { availableTypes = ['notaDesafinada', 'glitchAudio']; } // Wave 10-14: Add Ruido Blanco else if (waveNumber >= 10 && waveNumber <= 14) { availableTypes = ['notaDesafinada', 'glitchAudio', 'ruidoBlanco']; } // Wave 15-19: Add Eco Oscuro else if (waveNumber >= 15 && waveNumber <= 19) { availableTypes = ['notaDesafinada', 'glitchAudio', 'ruidoBlanco', 'ecoOscuro']; } // Wave 20-24: Add Autotune Malicioso else if (waveNumber >= 20 && waveNumber <= 24) { availableTypes = ['notaDesafinada', 'glitchAudio', 'ruidoBlanco', 'ecoOscuro', 'autotuneMalicioso']; } // Wave 25+: All enemy types with elites else { availableTypes = ['notaDesafinada', 'glitchAudio', 'ruidoBlanco', 'ecoOscuro', 'autotuneMalicioso']; } return availableTypes; } function getEnemyTypeWeights(waveNumber, availableTypes) { var weights = {}; // Calculate probability of strong enemy: min(0.6, 0.05 × wave) var strongEnemyProbability = Math.min(0.6, 0.05 * waveNumber); var weakEnemyProbability = 1 - strongEnemyProbability; // Early waves (1-4): Only Nota Desafinada if (waveNumber <= 4) { weights['notaDesafinada'] = 100; } // Waves 5-9: Nota Desafinada + Glitch Audio else if (waveNumber >= 5 && waveNumber <= 9) { weights['notaDesafinada'] = Math.round(weakEnemyProbability * 100); weights['glitchAudio'] = Math.round(strongEnemyProbability * 100); } // Waves 10-14: Add Ruido Blanco else if (waveNumber >= 10 && waveNumber <= 14) { var basicProportion = Math.round(weakEnemyProbability * 50); var strongProportion = Math.round(strongEnemyProbability * 50); weights['notaDesafinada'] = basicProportion; weights['glitchAudio'] = basicProportion; weights['ruidoBlanco'] = strongProportion * 2; } // Waves 15-19: Add Eco Oscuro else if (waveNumber >= 15 && waveNumber <= 19) { weights['notaDesafinada'] = Math.round(weakEnemyProbability * 35); weights['glitchAudio'] = Math.round(weakEnemyProbability * 15); weights['ruidoBlanco'] = Math.round(strongEnemyProbability * 30); weights['ecoOscuro'] = Math.round(strongEnemyProbability * 20); } // Waves 20-24: Add Autotune Malicioso else if (waveNumber >= 20 && waveNumber <= 24) { weights['notaDesafinada'] = Math.round(weakEnemyProbability * 25); weights['glitchAudio'] = Math.round(weakEnemyProbability * 15); weights['ruidoBlanco'] = Math.round(strongEnemyProbability * 20); weights['ecoOscuro'] = Math.round(strongEnemyProbability * 20); weights['autotuneMalicioso'] = Math.round(strongEnemyProbability * 20); } // Wave 25+: All enemies with elite variants else { weights['notaDesafinada'] = 15; weights['glitchAudio'] = 15; weights['ruidoBlanco'] = 20; weights['ecoOscuro'] = 30; weights['autotuneMalicioso'] = 20; } return weights; } function spawnEnemy() { var availableTypes = getEnemyTypesForWave(wave); var weights = getEnemyTypeWeights(wave, availableTypes); // Calculate total weight var totalWeight = 0; for (var i = 0; i < availableTypes.length; i++) { totalWeight += weights[availableTypes[i]]; } // Select random enemy type based on weights var random = Math.random() * totalWeight; var selectedType = availableTypes[0]; var currentWeight = 0; for (var i = 0; i < availableTypes.length; i++) { currentWeight += weights[availableTypes[i]]; if (random <= currentWeight) { selectedType = availableTypes[i]; break; } } // Remove elite enemy empowerment - no more elite enemies var isElite = false; // Special enemy mechanics from wave 8+ var isSpecialEnemy = false; var specialType = ''; if (wave >= 8 && Math.random() < 0.3) { // 30% chance for special enemy isSpecialEnemy = true; var specialTypes = ['spectral', 'explosive', 'disruptor', 'runner', 'armored']; if (wave >= 8 && wave < 12) specialTypes = ['spectral', 'runner'];else if (wave >= 12 && wave < 16) specialTypes = ['spectral', 'explosive', 'runner'];else if (wave >= 16) specialTypes = ['spectral', 'explosive', 'disruptor', 'runner', 'armored']; specialType = specialTypes[Math.floor(Math.random() * specialTypes.length)]; } var enemy = new Enemy(selectedType, isElite, isSpecialEnemy, specialType); enemy.x = pathPoints[0].x; enemy.y = pathPoints[0].y; // Scale health based on wave - more aggressive scaling var healthMultiplier; if (wave <= 5) { healthMultiplier = Math.pow(1.15, wave - 1); // Increased from 1.12 } else if (wave <= 15) { // More aggressive scaling from wave 6-15: vida = vida_base × (1.12)^wave healthMultiplier = Math.pow(1.12, wave); } else { // Even more aggressive for wave 16+: vida = vida_base × (1.14)^wave healthMultiplier = Math.pow(1.14, wave); } // Apply difficulty modifier for gold abuse punishment healthMultiplier *= difficultyModifier; enemy.health = Math.floor(enemy.health * healthMultiplier * 1.3); // Increased from 1.2 enemy.maxHealth = enemy.health; // Apply speed modifier from wave 6+ (moved earlier) if (wave >= 6) { var speedIncrease = Math.floor((wave - 6) / 2) * 0.06; // 6% every 2 waves starting wave 6 enemy.speed *= (1 + speedIncrease) * enemySpeedModifier; } // Additional speed boost for waves 15+ if (wave >= 15) { enemy.speed *= 1.15; // 15% additional speed boost } enemies.push(enemy); game.addChild(enemy); } function startNextWave() { if (gameState === 'playing') { // Start background music on first wave if (wave === 1) { LK.stopMusic(); // Stop menu music gameplayMusicPlaying = false; playMelodia(); } else if (!musicMuted && !gameplayMusicPlaying) { // Ensure music is playing for subsequent waves playMelodia(); } // Set up the wave but don't start it yet wavePaused = true; autoStartTimer = 0; enemiesSpawned = 0; enemiesPerWave = calculateEnemiesForWave(wave); // Use calculated enemy count waveStarted = false; // Adjust max gold per wave based on progression if (wave >= 6) { maxGoldPerWave = 45; // Reduced from wave 6 } else { maxGoldPerWave = 60; // Normal for early waves } // Further reduce for waves with punishment if (goldAbuseCooldown > 0) { maxGoldPerWave = Math.round(maxGoldPerWave * 0.7); // 30% reduction } // Show wave announcement var waveAnnouncement = game.addChild(new Text2('♪ WAVE ' + wave + ' READY ♪', { size: 120, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); waveAnnouncement.anchor.set(0.5, 0.5); waveAnnouncement.x = 1024; waveAnnouncement.y = 1366; waveAnnouncement.alpha = 0; // Animate the announcement tween(waveAnnouncement, { alpha: 1, scaleX: 1.1, scaleY: 1.1 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { tween(waveAnnouncement, { alpha: 0, scaleX: 0.9, scaleY: 0.9 }, { duration: 1000, easing: tween.easeIn, onFinish: function onFinish() { waveAnnouncement.destroy(); } }); } }); updateWaveUI(); } } function startCurrentWave() { if (wavePaused && gameState === 'playing') { wavePaused = false; waveStarted = true; autoStartTimer = 0; // Play horn sound for wave start LK.getSound('Cuerno').play(); // Show "GO!" message var goMessage = game.addChild(new Text2('♪ GO! ♪', { size: 150, fill: 0x00FF00, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); goMessage.anchor.set(0.5, 0.5); goMessage.x = 1024; goMessage.y = 1366; goMessage.alpha = 0; tween(goMessage, { alpha: 1, scaleX: 1.3, scaleY: 1.3 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { tween(goMessage, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 600, easing: tween.easeIn, onFinish: function onFinish() { goMessage.destroy(); } }); } }); updateWaveUI(); } } function updateWaveUI() { if (wavePaused && autoStartTimer > 0) { var timeLeft = Math.ceil((300 - autoStartTimer) / 60); // 5 seconds = 300 ticks autoStartText.setText('Auto: ' + timeLeft + 's'); startWaveText.setText('♪ START WAVE ♪'); startWaveButton.tint = 0xFFFFFF; } else if (wavePaused) { autoStartText.setText('Auto: 5s'); startWaveText.setText('♪ START WAVE ♪'); startWaveButton.tint = 0xFFFFFF; } else { autoStartText.setText('In Progress'); startWaveText.setText('♪ WAVE ACTIVE ♪'); startWaveButton.tint = 0x666666; } } // Hide game elements initially goal.visible = false; buildMenuContainer.visible = false; // Hide build spots initially for (var i = 0; i < buildSpots.length; i++) { buildSpots[i].visible = false; } // Title intro system var titleIntroContainer; var leftCurtainIntro; var rightCurtainIntro; function showTitleIntro() { menuContainer.visible = false; gameState = 'titleIntro'; // Play title sound LK.getSound('titleSound').play(); // Create title intro container titleIntroContainer = new Container(); game.addChild(titleIntroContainer); // Create closed curtains covering the entire screen leftCurtainIntro = titleIntroContainer.addChild(LK.getAsset('curtain', { anchorX: 1, anchorY: 0 })); leftCurtainIntro.x = 1024; // Center of screen leftCurtainIntro.y = 0; leftCurtainIntro.scaleX = 3.5; leftCurtainIntro.scaleY = 5.0; rightCurtainIntro = titleIntroContainer.addChild(LK.getAsset('curtain', { anchorX: 0, anchorY: 0 })); rightCurtainIntro.x = 1024; // Center of screen rightCurtainIntro.y = 0; rightCurtainIntro.scaleX = 3.5; rightCurtainIntro.scaleY = 5.0; // Create gothic title text var titleText = titleIntroContainer.addChild(new Text2('Symphony Siege', { size: 160, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 1366; titleText.alpha = 0; // Animate title appearing tween(titleText, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { // After 2 seconds, open curtains LK.setTimeout(function () { openCurtainsAndStartGame(); }, 1000); } }); } function openCurtainsAndStartGame() { // Play dramatic sound for curtain opening LK.getSound('portal').play(); // Create elegant dust particles that escape as curtains begin to part for (var i = 0; i < 15; i++) { var dustParticle = titleIntroContainer.addChild(LK.getAsset('goldenParticle', { anchorX: 0.5, anchorY: 0.5 })); dustParticle.x = 1024 + (Math.random() - 0.5) * 100; dustParticle.y = 1366 + (Math.random() - 0.5) * 200; dustParticle.alpha = 0.4; dustParticle.tint = 0xFFD700; dustParticle.scaleX = 0.3 + Math.random() * 0.4; dustParticle.scaleY = 0.3 + Math.random() * 0.4; // Animate dust particles floating and dispersing tween(dustParticle, { x: dustParticle.x + (Math.random() - 0.5) * 300, y: dustParticle.y - 200 - Math.random() * 400, rotation: Math.PI * 2 * Math.random(), alpha: 0, scaleX: dustParticle.scaleX * 1.5, scaleY: dustParticle.scaleY * 1.5 }, { duration: 3000 + Math.random() * 2000, easing: tween.easeOut, onFinish: function onFinish() { dustParticle.destroy(); } }); } // Enhanced curtain opening sequence with natural fabric physics // Phase 1: Pre-tension - curtains gather tension before opening tween(leftCurtainIntro, { x: leftCurtainIntro.x + 8, scaleX: 1.85, // Slight horizontal compression scaleY: 2.95, // Slight vertical compression rotation: 0.015 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { // Phase 2: Release tension with slight overshoot tween(leftCurtainIntro, { x: leftCurtainIntro.x - 25, scaleX: 1.82, scaleY: 3.05, // Slight stretch as tension releases rotation: -0.02 }, { duration: 350, easing: tween.easeOut, onFinish: function onFinish() { // Phase 3: Main opening with natural deceleration and fabric sway tween(leftCurtainIntro, { x: -900, // Further offscreen for complete reveal rotation: -0.08, // More pronounced rotation as heavy fabric moves scaleX: 1.75, // Fabric compresses horizontally as it moves scaleY: 3.2, // Vertical stretch from movement momentum alpha: 0.85 // Slight fade as it moves away }, { duration: 3200, // Longer duration for more majestic feel easing: tween.easeOut, // Natural deceleration at the end onFinish: function onFinish() { // Phase 4: Final settle with gentle sway tween(leftCurtainIntro, { rotation: -0.04, scaleY: 3.0, alpha: 0.7 }, { duration: 800, easing: tween.easeInOut }); } }); } }); } }); // Mirror animation for right curtain with asymmetric timing for natural feel LK.setTimeout(function () { tween(rightCurtainIntro, { x: rightCurtainIntro.x - 8, scaleX: 1.85, scaleY: 2.95, rotation: -0.015 }, { duration: 450, // Slightly different timing easing: tween.easeInOut, onFinish: function onFinish() { tween(rightCurtainIntro, { x: rightCurtainIntro.x + 25, scaleX: 1.82, scaleY: 3.05, rotation: 0.02 }, { duration: 320, // Different timing for asymmetry easing: tween.easeOut, onFinish: function onFinish() { // Main opening for right curtain tween(rightCurtainIntro, { x: 2950, // Further offscreen rotation: 0.08, scaleX: 1.75, scaleY: 3.2, alpha: 0.85 }, { duration: 3100, // Slightly different duration easing: tween.easeOut, onFinish: function onFinish() { // Final settle for right curtain tween(rightCurtainIntro, { rotation: 0.04, scaleY: 3.0, alpha: 0.7 }, { duration: 750, easing: tween.easeInOut, onFinish: function onFinish() { // Clean up and start game after both curtains have settled titleIntroContainer.destroy(); gameState = 'playing'; startNextWave(); } }); } }); } }); } }); }, 100); // Small delay to start right curtain slightly after left // Enhanced atmospheric effects during opening // Magical glow effect that builds as curtains part var magicalGlow = titleIntroContainer.addChild(LK.getAsset('spotlight', { anchorX: 0.5, anchorY: 0.5 })); magicalGlow.x = 1024; magicalGlow.y = 1366; magicalGlow.alpha = 0; magicalGlow.tint = 0xFFD700; magicalGlow.scaleX = 0.5; magicalGlow.scaleY = 0.5; // Glow builds up as curtains open tween(magicalGlow, { alpha: 0.6, scaleX: 4, scaleY: 4 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // Glow disperses as opening completes tween(magicalGlow, { alpha: 0, scaleX: 8, scaleY: 8 }, { duration: 1500, easing: tween.easeIn, onFinish: function onFinish() { magicalGlow.destroy(); } }); } }); // Multiple floating musical notes with varied timing for (var i = 0; i < 8; i++) { LK.setTimeout(function (noteIndex) { return function () { var musicNote = titleIntroContainer.addChild(LK.getAsset('musicNote', { anchorX: 0.5, anchorY: 0.5 })); musicNote.x = 800 + Math.random() * 448; // Spread across center area musicNote.y = 1200 + Math.random() * 300; musicNote.alpha = 0.5 + Math.random() * 0.3; musicNote.scaleX = 0.8 + Math.random() * 0.6; musicNote.scaleY = 0.8 + Math.random() * 0.6; musicNote.tint = 0xFFD700; // Each note follows unique floating path var targetX = musicNote.x + (Math.random() - 0.5) * 600; var targetY = musicNote.y - 400 - Math.random() * 500; tween(musicNote, { x: targetX, y: targetY, rotation: Math.PI * 3 * (Math.random() - 0.5), alpha: 0, scaleX: musicNote.scaleX * 1.8, scaleY: musicNote.scaleY * 1.8 }, { duration: 2500 + Math.random() * 1500, easing: tween.easeOut, onFinish: function onFinish() { musicNote.destroy(); } }); }; }(i), i * 200 + Math.random() * 300); // Staggered timing } // Subtle orchestral crescendo effect with screen tint var crescendoOverlay = titleIntroContainer.addChild(LK.getAsset('woodPanel', { anchorX: 0.5, anchorY: 0.5 })); crescendoOverlay.x = 1024; crescendoOverlay.y = 1366; crescendoOverlay.scaleX = 5; crescendoOverlay.scaleY = 7; crescendoOverlay.alpha = 0; crescendoOverlay.tint = 0x8B4513; // Warm brown crescendo // Build crescendo effect tween(crescendoOverlay, { alpha: 0.15 }, { duration: 1800, easing: tween.easeInOut, onFinish: function onFinish() { // Fade crescendo as climax approaches tween(crescendoOverlay, { alpha: 0 }, { duration: 1200, easing: tween.easeOut, onFinish: function onFinish() { crescendoOverlay.destroy(); } }); } }); // Enhanced screen flash with golden warmth as curtains fully part LK.setTimeout(function () { LK.effects.flashScreen(0xFFE55C, 500); // Warm golden flash instead of stark white }, 2800); // Timing adjusted for new animation length } function showCreditsScreen() { showingCredits = true; gameState = 'credits'; // Create credits container creditsContainer = new Container(); game.addChild(creditsContainer); // Credits background var creditsBackground = creditsContainer.addChild(LK.getAsset('woodPanel', { anchorX: 0.5, anchorY: 0.5 })); creditsBackground.x = 1024; creditsBackground.y = 1366; creditsBackground.scaleX = 4.5; creditsBackground.scaleY = 6; creditsBackground.alpha = 0.95; creditsBackground.tint = 0x2D1B0E; // Credits title var creditsTitle = creditsContainer.addChild(new Text2('♪ CREDITS ♪', { size: 120, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); creditsTitle.anchor.set(0.5, 0.5); creditsTitle.x = 1024; creditsTitle.y = 400; // Attribution text for Udio music var attributionText = creditsContainer.addChild(new Text2('Music generated with Udio\n\nThis game includes AI-generated music\ncreated using Udio.\n\nAll tracks were produced by the developer\nvia Udio\'s platform under its current\nterms of use.', { size: 55, fill: 0xF5F5DC, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); attributionText.anchor.set(0.5, 0.5); attributionText.x = 1024; attributionText.y = 1000; // Continue button var continueButton = creditsContainer.addChild(LK.getAsset('trumpetTower', { anchorX: 0.5, anchorY: 0.5 })); continueButton.x = 1024; continueButton.y = 1600; continueButton.scaleX = 1.2; continueButton.scaleY = 1.2; var continueButtonText = creditsContainer.addChild(new Text2('♪ CONTINUE ♪', { size: 65, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); continueButtonText.anchor.set(0.5, 0.5); continueButtonText.x = 1024; continueButtonText.y = 1700; // Animate credits appearing creditsContainer.alpha = 0; tween(creditsContainer, { alpha: 1 }, { duration: 1000, easing: tween.easeOut }); // Continue button handler continueButton.down = function () { hideCreditsScreen(); }; } function enterGrandPianoTargetingMode() { if (grandPianoGhostMode) return; grandPianoGhostMode = true; hideUpgradeMenu(); hideBuildMenu(); // Create ghost piano that follows mouse grandPianoGhost = game.addChild(LK.getAsset('grandPianoGhost', { anchorX: 0.5, anchorY: 0.5 })); grandPianoGhost.alpha = 0.6; grandPianoGhost.tint = 0x00FFFF; // Create targeting area indicator grandPianoTargetArea = game.addChild(LK.getAsset('grandPianoShadow', { anchorX: 0.5, anchorY: 0.5 })); grandPianoTargetArea.alpha = 0.3; grandPianoTargetArea.tint = 0xFF0000; updateGrandPianoPowerUI(); } function exitGrandPianoTargetingMode() { if (!grandPianoGhostMode) return; grandPianoGhostMode = false; if (grandPianoGhost) { grandPianoGhost.destroy(); grandPianoGhost = null; } if (grandPianoTargetArea) { grandPianoTargetArea.destroy(); grandPianoTargetArea = null; } } function activateGrandPianoPowerAt(targetX, targetY) { exitGrandPianoTargetingMode(); activateGrandPianoPower(targetX, targetY); } function activateGrandPianoPower(targetX, targetY) { if (grandPianoPowerActive) return; grandPianoPowerActive = true; grandPianoCooldownRemaining = 2; // 2 wave cooldown updateGrandPianoPowerUI(); // Use center of screen as default if no target provided var impactX = targetX || 1024; var impactY = targetY || 1366; // Play dramatic piano chord LK.getSound('grandPianoChord').play(); // Create falling piano animation var fallingPiano = game.addChild(LK.getAsset('grandPianoFalling', { anchorX: 0.5, anchorY: 0.5 })); fallingPiano.x = impactX; fallingPiano.y = -200; // Start above screen fallingPiano.rotation = 0.1; fallingPiano.alpha = 0.9; // Create piano keys falling effect around target area for (var i = 0; i < 12; i++) { var pianoKey = game.addChild(LK.getAsset('pianoKey', { anchorX: 0.5, anchorY: 0.5 })); pianoKey.x = impactX - 200 + i * 35; // Centered around impact point pianoKey.y = -100 - Math.random() * 200; pianoKey.rotation = Math.random() * Math.PI; pianoKey.tint = i % 2 === 0 ? 0xFFFFFF : 0x000000; // Animate piano key falling tween(pianoKey, { y: 2800, rotation: pianoKey.rotation + Math.PI * 4, alpha: 0 }, { duration: 2000 + Math.random() * 1000, easing: tween.easeIn, onFinish: function onFinish() { if (pianoKey.parent) { pianoKey.destroy(); } } }); } // Animate piano falling with dramatic effect tween(fallingPiano, { y: impactY, // Target position rotation: 0.3, scaleX: 1.5, scaleY: 1.5 }, { duration: 1500, easing: tween.easeIn, onFinish: function onFinish() { // Piano impact effect LK.getSound('grandPianoImpact').play(); // Screen shake effect tween(game, { x: 20 }, { duration: 100, onFinish: function onFinish() { tween(game, { x: -20 }, { duration: 100, onFinish: function onFinish() { tween(game, { x: 0 }, { duration: 100 }); } }); } }); // Damage enemies within reduced area (120 pixels instead of all screen) var areaRadius = 120; // Reduced from affecting all enemies on screen var enemiesHit = 0; for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; // Check if enemy is within area of effect var dx = enemy.x - impactX; var dy = enemy.y - impactY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= areaRadius) { // Scale damage based on wave: 300 + (wave * 25) for progression var scaledDamage = 300 + wave * 25; var died = enemy.takeDamage(scaledDamage); if (died) { // Track enemy killed by Grand Piano totalEnemiesKilled++; // Calculate gold reward for enemies killed by Grand Piano var goldReward = enemy.goldValue; // Apply wave restrictions for gold if (wave >= 6) { goldReward = Math.min(goldReward, 3); } // Ensure minimum gold reward of 1 for all enemies from wave 10+ if (wave >= 10 && goldReward === 0) { goldReward = 1; } // Check wave gold limit (before adding universal bonus) if (lastWaveGoldEarned + goldReward > maxGoldPerWave) { goldReward = Math.max(1, maxGoldPerWave - lastWaveGoldEarned); } // Add universal +3 bonus for ALL enemies from wave 10+ (after all other restrictions) if (wave >= 10) { goldReward += 3; console.log("Grand Piano Wave " + wave + ": Added +3 universal bonus, total gold reward:", goldReward); } // Apply gold reward if (goldReward > 0) { gold += goldReward; lastWaveGoldEarned += goldReward; } updateUI(); LK.getSound('enemyDeath').play(); } else { // Stun surviving enemies for 3 seconds enemy.grandPianoStunnedUntil = LK.ticks + 180; // 3 seconds at 60fps // Visual stun effect var enemyGraphics = enemy.getChildAt(0); if (enemyGraphics) { var originalTint = enemyGraphics.tint || 0xffffff; tween(enemyGraphics, { tint: 0xFFD700, // Gold stun effect scaleX: 0.7, scaleY: 0.7 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { LK.setTimeout(function () { if (enemyGraphics && enemyGraphics.parent) { tween(enemyGraphics, { tint: originalTint, scaleX: 1.0, scaleY: 1.0 }, { duration: 400 }); } }, 180 * 16); // Stun duration } }); } } enemiesHit++; } } // Show impact effect at target location var impactEffect = game.addChild(LK.getAsset('spotlight', { anchorX: 0.5, anchorY: 0.5 })); impactEffect.x = impactX; impactEffect.y = impactY; impactEffect.tint = 0xFFD700; impactEffect.alpha = 0.8; impactEffect.scaleX = 0.5; impactEffect.scaleY = 0.5; tween(impactEffect, { scaleX: 6, // Smaller impact effect to match reduced area scaleY: 6, alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { impactEffect.destroy(); } }); // Piano disappears after impact tween(fallingPiano, { alpha: 0, scaleY: 0.1, y: fallingPiano.y + 200 }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { fallingPiano.destroy(); grandPianoPowerActive = false; } }); } }); } function updateGrandPianoPowerUI() { if (!grandPianoPowerUnlocked) { grandPianoButtonBackground.visible = false; grandPianoPowerButton.visible = false; grandPianoPowerText.visible = false; grandPianoCooldownText.visible = false; return; } grandPianoButtonBackground.visible = true; grandPianoPowerButton.visible = true; grandPianoPowerText.visible = true; grandPianoCooldownText.visible = true; if (grandPianoGhostMode) { grandPianoCooldownText.setText('Click to target!'); grandPianoCooldownText.tint = 0x00FFFF; grandPianoPowerButton.tint = 0x00FFFF; grandPianoPowerButton.alpha = 1.0; // Add pulsing effect during targeting var pulseScale = 1.0 + Math.sin(LK.ticks * 0.2) * 0.1; grandPianoPowerButton.scaleX = pulseScale; grandPianoPowerButton.scaleY = pulseScale; } else if (grandPianoCooldownRemaining > 0) { grandPianoCooldownText.setText(grandPianoCooldownRemaining + ' waves left'); grandPianoCooldownText.tint = 0x00FFFF; grandPianoPowerButton.tint = 0x666666; grandPianoPowerButton.alpha = 0.6; grandPianoPowerButton.scaleX = 1.0; grandPianoPowerButton.scaleY = 1.0; // Visual cooldown progress - create loading circle effect var cooldownProgress = 1.0 - grandPianoCooldownRemaining / 2.0; var progressAngle = cooldownProgress * Math.PI * 2; grandPianoPowerButton.rotation = progressAngle; } else { grandPianoCooldownText.setText('Ready!'); grandPianoCooldownText.tint = 0x00FFFF; grandPianoPowerButton.tint = 0xFFFFFF; grandPianoPowerButton.alpha = 1.0; grandPianoPowerButton.scaleX = 1.0; grandPianoPowerButton.scaleY = 1.0; grandPianoPowerButton.rotation = 0; } } function showGrandPianoUnlockAnimation() { // Create chest that appears in center of screen var musicChest = game.addChild(LK.getAsset('musicChest', { anchorX: 0.5, anchorY: 0.5 })); musicChest.x = 1024; musicChest.y = 1366; musicChest.scaleX = 2.0; musicChest.scaleY = 2.0; musicChest.alpha = 0; musicChest.tint = 0x8B4513; // Chest appears with light effect var chestLight = game.addChild(LK.getAsset('spotlight', { anchorX: 0.5, anchorY: 0.5 })); chestLight.x = 1024; chestLight.y = 1366; chestLight.tint = 0xFFD700; chestLight.alpha = 0; chestLight.scaleX = 3; chestLight.scaleY = 3; // Animate chest appearing tween(musicChest, { alpha: 1, scaleX: 2.5, scaleY: 2.5 }, { duration: 1000, easing: tween.easeOut }); tween(chestLight, { alpha: 0.6 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { // Chest opens with sound LK.getSound('chestOpen').play(); // Create golden particles exploding from chest for (var i = 0; i < 20; i++) { var particle = game.addChild(LK.getAsset('goldenParticle', { anchorX: 0.5, anchorY: 0.5 })); particle.x = 1024; particle.y = 1366; particle.tint = 0xFFD700; particle.scaleX = 0.5; particle.scaleY = 0.5; var angle = Math.PI * 2 * i / 20; var speed = 200 + Math.random() * 150; var targetX = 1024 + Math.cos(angle) * speed; var targetY = 1366 + Math.sin(angle) * speed; tween(particle, { x: targetX, y: targetY, scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } // Grand Piano emerges from chest LK.setTimeout(function () { var emergingPiano = game.addChild(LK.getAsset('grandPiano', { anchorX: 0.5, anchorY: 1 })); emergingPiano.x = 1024; emergingPiano.y = 1366; emergingPiano.scaleX = 0.1; emergingPiano.scaleY = 0.1; emergingPiano.alpha = 0; emergingPiano.rotation = Math.PI * 2; // Play fanfare music LK.playMusic('fanfare', { loop: false }); // Piano emerges and floats upward with light from below var pianoLight = game.addChild(LK.getAsset('spotlight', { anchorX: 0.5, anchorY: 0.5 })); pianoLight.x = 1024; pianoLight.y = 1500; pianoLight.tint = 0x00FFFF; pianoLight.alpha = 0; pianoLight.scaleX = 2; pianoLight.scaleY = 4; tween(pianoLight, { alpha: 0.8 }, { duration: 2000 }); tween(emergingPiano, { alpha: 1, scaleX: 1.5, scaleY: 1.5, y: 1200, rotation: 0 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // Piano moves to its UI position var targetX = grandPianoPowerButton.x + 200; var targetY = grandPianoPowerButton.y - 400; tween(emergingPiano, { x: targetX, y: targetY, scaleX: 1.0, scaleY: 1.0 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { // Unlock power and show UI grandPianoPowerUnlocked = true; updateGrandPianoPowerUI(); // Restart Melodia after Grand Piano unlock animation if (!musicMuted) { console.log("Restarting Melodia after Grand Piano unlock"); LK.setTimeout(function () { playMelodia(); }, 1000); } // Show unlock message var unlockMsg = game.addChild(new Text2('♪ GRAND PIANO POWER UNLOCKED! ♪\nTarget and unleash devastating musical might!', { size: 70, fill: 0xFFD700, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); unlockMsg.anchor.set(0.5, 0.5); unlockMsg.x = 1024; unlockMsg.y = 1000; unlockMsg.alpha = 0; tween(unlockMsg, { alpha: 1 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { LK.setTimeout(function () { tween(unlockMsg, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { unlockMsg.destroy(); } }); }, 3000); } }); // Clean up animation objects emergingPiano.destroy(); pianoLight.destroy(); chestLight.destroy(); musicChest.destroy(); } }); } }); }, 1000); } }); } function showEnhancedGameOver() { gameState = 'gameOver'; // Kill all existing enemies immediately for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy && enemy.parent) { enemy.destroy(); } enemies.splice(i, 1); } // Destroy all existing towers immediately for (var i = towers.length - 1; i >= 0; i--) { var tower = towers[i]; if (tower && tower.parent) { tower.destroy(); } towers.splice(i, 1); } // Create game over container var gameOverContainer = new Container(); game.addChild(gameOverContainer); // Game over background var gameOverBackground = gameOverContainer.addChild(LK.getAsset('woodPanel', { anchorX: 0.5, anchorY: 0.5 })); gameOverBackground.x = 1024; gameOverBackground.y = 1366; gameOverBackground.scaleX = 4.5; gameOverBackground.scaleY = 6; gameOverBackground.alpha = 0.95; gameOverBackground.tint = 0x2D1B0E; // Game Over title var gameOverTitle = gameOverContainer.addChild(new Text2('♪ GAME OVER ♪', { size: 120, fill: 0xFF4444, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); gameOverTitle.anchor.set(0.5, 0.5); gameOverTitle.x = 1024; gameOverTitle.y = 400; // Statistics text var statsText = gameOverContainer.addChild(new Text2('Total enemies killed: ' + totalEnemiesKilled + '\n\n' + 'Wave number: ' + wave + '\n\n' + 'Gold spent: $' + totalGoldSpent, { size: 70, fill: 0xF5F5DC, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); statsText.anchor.set(0.5, 0.5); statsText.x = 1024; statsText.y = 1000; // Restart button var restartButton = gameOverContainer.addChild(LK.getAsset('restart', { anchorX: 0.5, anchorY: 0.5 })); restartButton.x = 1024; restartButton.y = 1600; restartButton.scaleX = 1.5; restartButton.scaleY = 1.5; // Animate game over screen appearing gameOverContainer.alpha = 0; tween(gameOverContainer, { alpha: 1 }, { duration: 1000, easing: tween.easeOut }); // Restart button handler - simplified for immediate single-click response restartButton.down = function () { console.log("Restart button clicked - performing restart"); // Play immediate click feedback LK.getSound('build').play(); // Immediate visual feedback tween(restartButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, easing: tween.easeOut }); // Stop all music immediately try { LK.stopMusic(); } catch (error) { console.log("Error stopping music:", error); } gameplayMusicPlaying = false; // Hide game over container immediately if (gameOverContainer && gameOverContainer.parent) { gameOverContainer.destroy(); } // Reset game state to menu immediately gameState = 'menu'; // Show menu container (initial start screen) menuContainer.visible = true; // Clear all game arrays enemies = []; towers = []; bullets = []; floatingNotes = []; // Reset all game variables to initial values totalEnemiesKilled = 0; totalGoldSpent = 0; gold = 150; lives = 40; wave = 1; enemiesSpawned = 0; enemiesPerWave = 10; spawnTimer = 0; waveStarted = false; wavePaused = true; autoStartTimer = 0; showingBuildMenu = false; selectedBuildSpot = null; showingUpgradeMenu = false; selectedTower = null; // Reset Grand Piano power system grandPianoPowerUnlocked = false; grandPianoCooldownRemaining = 0; grandPianoUnlockAnimationShown = false; grandPianoPowerActive = false; grandPianoGhostMode = false; // Clean up Grand Piano ghost objects if (grandPianoGhost) { grandPianoGhost.destroy(); grandPianoGhost = null; } if (grandPianoTargetArea) { grandPianoTargetArea.destroy(); grandPianoTargetArea = null; } // Reset difficulty and gold tracking lastWaveGoldEarned = 0; difficultyModifier = 1.0; enemySpeedModifier = 1.0; forceExpenseEvent = false; currentSpecialWave = null; goldAbuseCooldown = 0; highGoldWaveCount = 0; maxGoldPerWave = 60; // Reset build spots for (var i = 0; i < buildSpots.length; i++) { buildSpots[i].occupied = false; buildSpots[i].visible = false; } // Hide all menu containers buildMenuContainer.visible = false; upgradeMenuContainer.visible = false; // Hide game elements (return to menu state) goal.visible = false; // Update UI to reflect reset state updateUI(); updateGrandPianoPowerUI(); // Start menu music if (!musicMuted) { console.log("Starting menu music"); try { LK.playMusic('MusicaInicio', { loop: true }); // Play initial sounds var vozSound = LK.getSound('Voz'); vozSound.play(); LK.setTimeout(function () { LK.getSound('Risa').play(); }, 3000); } catch (error) { console.log("Error starting menu music:", error); } } console.log("Restart completed - returned to start screen"); }; } function hideCreditsScreen() { showingCredits = false; // Animate credits disappearing tween(creditsContainer, { alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { creditsContainer.destroy(); gameState = 'playing'; // Now advance to wave 101 wave++; waveStarted = false; gold += 75; // Bonus gold for completing wave updateUI(); // Continue with normal wave progression LK.setTimeout(function () { startNextWave(); }, 1000); } }); } // Show main menu first gameState = 'menu'; // Play Voz sound immediately when entering initial menu and chain Risa after it finishes var vozSound = LK.getSound('Voz'); vozSound.play(); // Calculate duration of Voz sound and play Risa after that duration // Assuming Voz sound is approximately 3 seconds, adjust timing as needed LK.setTimeout(function () { LK.getSound('Risa').play(); }, 3000); // 3 seconds delay, adjust based on actual Voz sound duration // Play menu music if (!musicMuted) { LK.playMusic('MusicaInicio', { loop: true }); } game.update = function () { // Concert hall ambiance - create floating musical notes periodically if (LK.ticks % 180 === 0) { // Every 3 seconds createFloatingNote(); } // Simple Music System - ensure Melodia is playing during gameplay if (gameState === 'playing' && !musicMuted) { // Check if music needs to be started or restarted (every 5 seconds) if (LK.ticks % 300 === 0) { if (!gameplayMusicPlaying) { console.log("Music not playing, starting Melodia"); playMelodia(); } } } if (gameState === 'playing') { // Handle Grand Piano targeting mode cleanup if game state changes if (grandPianoGhostMode && (gameState !== 'playing' || showingBuildMenu || showingUpgradeMenu)) { exitGrandPianoTargetingMode(); } // Show game elements when playing goal.visible = true; // Show build spots for (var i = 0; i < buildSpots.length; i++) { buildSpots[i].visible = true; } // Spawn enemies if (waveStarted && enemiesSpawned < enemiesPerWave) { spawnTimer++; var spawnInterval = getSpawnIntervalForWave(wave); if (spawnTimer >= spawnInterval) { spawnEnemy(); enemiesSpawned++; spawnTimer = 0; } } // Handle wave auto-start timer if (wavePaused && enemies.length === 0) { autoStartTimer++; if (autoStartTimer >= 300) { // 5 seconds at 60fps startCurrentWave(); } // Update UI every 60 ticks (1 second) if (autoStartTimer % 60 === 0) { updateWaveUI(); } } // Check if wave is complete if (waveStarted && enemiesSpawned >= enemiesPerWave && enemies.length === 0) { // Check if player has lives before advancing if (lives <= 0) { showEnhancedGameOver(); return; } // Show credits screen after wave 100 if (wave === 100) { showCreditsScreen(); return; // Don't advance wave yet, credits will handle it } wave++; waveStarted = false; gold += 75; // Bonus gold for completing wave // Add 10 lives every 10 waves with announcement if (wave % 10 === 0 && wave > 0) { lives += 10; var livesBonus = game.addChild(new Text2('♪ LIFE BONUS! ♪\n+10 Lives Added!\nTotal Lives: ' + lives, { size: 80, fill: 0x00FF00, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); livesBonus.anchor.set(0.5, 0.5); livesBonus.x = 1024; livesBonus.y = 1366; livesBonus.alpha = 0; tween(livesBonus, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { tween(livesBonus, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 2000, easing: tween.easeIn, onFinish: function onFinish() { livesBonus.destroy(); } }); } }); } updateUI(); // Unlock Grand Piano power after wave 10 if (wave === 11 && !grandPianoPowerUnlocked && !grandPianoUnlockAnimationShown) { grandPianoUnlockAnimationShown = true; showGrandPianoUnlockAnimation(); } // Reduce Grand Piano cooldown if (grandPianoCooldownRemaining > 0) { grandPianoCooldownRemaining--; updateGrandPianoPowerUI(); } // Music management code removed to prevent issues after wave 10 // Reset wave gold tracking lastWaveGoldEarned = 0; // Check for gold abuse (maintaining >600 gold) if (gold > 600) { highGoldWaveCount++; if (highGoldWaveCount >= 3) { // Activate punishment modifier difficultyModifier = 1.5; // 50% more health for next 2 waves enemySpeedModifier = 1.3; // 30% faster enemies goldAbuseCooldown = 2; // Lasts for 2 waves highGoldWaveCount = 0; // Show warning message var warningMsg = game.addChild(new Text2('⚠ DIFFICULTY INCREASED ⚠\nToo much gold hoarded!', { size: 80, fill: 0xFF0000, font: "'Blackletter', 'Old English Text MT', 'Germania One', serif" })); warningMsg.anchor.set(0.5, 0.5); warningMsg.x = 1024; warningMsg.y = 1000; tween(warningMsg, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { warningMsg.destroy(); } }); } } else { highGoldWaveCount = 0; } // Decrement punishment cooldown if (goldAbuseCooldown > 0) { goldAbuseCooldown--; if (goldAbuseCooldown === 0) { difficultyModifier = 1.0; enemySpeedModifier = 1.0; } } // Tower maintenance taxes removed per player request // Start next wave after delay LK.setTimeout(function () { startNextWave(); }, 3000); } } // Clean up destroyed bullets for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i].destroyed) { bullets.splice(i, 1); } } // Clean up dead enemies for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i].health <= 0) { enemies[i].destroy(); enemies.splice(i, 1); } } };
===================================================================
--- original.js
+++ change.js
@@ -907,11 +907,11 @@
/****
* Game Code
****/
-// LK Game engine handles mobile display automatically through initialization parameters
-// The scaleMode: 'stretch' and autoResize: true in game initialization ensure proper mobile scaling
// Game variables
+// The scaleMode: 'stretch' and autoResize: true in game initialization ensure proper mobile scaling
+// LK Game engine handles mobile display automatically through initialization parameters
var gameState = 'menu'; // 'menu', 'tutorial', 'playing'
var tutorialStep = 0;
var menuContainer;
var tutorialContainer;
@@ -3340,18 +3340,18 @@
anchorY: 0
}));
leftCurtainIntro.x = 1024; // Center of screen
leftCurtainIntro.y = 0;
- leftCurtainIntro.scaleX = 1.8;
- leftCurtainIntro.scaleY = 3.0;
+ leftCurtainIntro.scaleX = 3.5;
+ leftCurtainIntro.scaleY = 5.0;
rightCurtainIntro = titleIntroContainer.addChild(LK.getAsset('curtain', {
anchorX: 0,
anchorY: 0
}));
rightCurtainIntro.x = 1024; // Center of screen
rightCurtainIntro.y = 0;
- rightCurtainIntro.scaleX = 1.8;
- rightCurtainIntro.scaleY = 3.0;
+ rightCurtainIntro.scaleX = 3.5;
+ rightCurtainIntro.scaleY = 5.0;
// Create gothic title text
var titleText = titleIntroContainer.addChild(new Text2('Symphony Siege', {
size: 160,
fill: 0xFFD700,
Bloque de piedra que ocupe todo el sprite. In-Game asset. 2d. High contrast. No shadows
El pilar de una Sala de Conciertos Viviente. In-Game asset. 2d. High contrast. No shadows
Silla de un Sala de Conciertos Viviente vista desde arriba. In-Game asset. 2d. High contrast. No shadows
Candelabro de una Sala de Conciertos Viviente. In-Game asset. 2d. High contrast. No shadows
Orquesta de una Sala de Conciertos Viviente. In-Game asset. 2d. High contrast. No shadows
escenario de una Sala de Conciertos Viviente. In-Game asset. 2d. High contrast. No shadows
Cortina de una Sala de Conciertos Viviente. In-Game asset. 2d. High contrast. No shadows
Vip box de una Sala de Conciertos Viviente. In-Game asset. 2d. High contrast. No shadows
RoyalCrest de una Sala de Conciertos Viviente. In-Game asset. 2d. High contrast. No shadows
Nota musical de una Sala de Conciertos Viviente. In-Game asset. 2d. High contrast. No shadows
Una nota negrita endiablada. In-Game asset. 2d. High contrast. No shadows
WoodPanel en una sala de conciertos viviente. In-Game asset. 2d. High contrast. No shadows
Tarimas con cuerdas tensadas flotando debajo.. In-Game asset. 2d. High contrast. No shadows
tambor basico. In-Game asset. 2d. High contrast. No shadows
Trompeta de choque. In-Game asset. 2d. High contrast. No shadows
Un bailarin del caos. In-Game asset. 2d. High contrast. No shadows
Guitarra electrica. In-Game asset. 2d. High contrast. No shadows
Violin congelante. In-Game asset. 2d. High contrast. No shadows
DJ ritmico. In-Game asset. 2d. High contrast. No shadows
Oro. In-Game asset. 2d. High contrast. No shadows
Nota musical semifusa. In-Game asset. 2d. High contrast. No shadows
Clave de sol. In-Game asset. 2d. High contrast. No shadows
Portal siniestro. In-Game asset. 2d. High contrast. No shadows
Clave de fa. In-Game asset. 2d. High contrast. No shadows
nota musical. In-Game asset. 2d. High contrast. No shadows
Upgrade. In-Game asset. 2d. High contrast. No shadows
Remove. In-Game asset. 2d. High contrast. No shadows
Ruido blanco personificado como un enemigo. In-Game asset. 2d. High contrast. No shadows
Una nota desafinada personificada como enemigo. In-Game asset. 2d. High contrast. No shadows
Autotune malicioso personificado como enemigo. In-Game asset. 2d. High contrast. No shadows
Glitch audio personificado como un enemigo. In-Game asset. 2d. High contrast. No shadows
Eco oscuro personificado como un enemigo. In-Game asset. 2d. High contrast. No shadows
Un enemigo del infierno de elite, tiene armas y tambien armadura. In-Game asset. 2d. High contrast. No shadows
Crea un botón visual para la pantalla de inicio de un videojuego llamado Symphony Siege. El botón debe decir "Start" y estar inspirado en una sala de conciertos clásica y elementos musicales. El diseño debe tener un estilo elegante, ligeramente barroco o sinfónico, con detalles dorados y formas suaves. El texto "Start" debe estar centrado y ser legible, con una tipografía estilizada tipo partitura o manuscrito musical. El fondo del botón debe parecer una tecla de piano brillante o una placa de madera de violín. El contorno puede tener detalles dorados o bronces ornamentales. El botón debe emitir una sensación de majestuosidad y armonía, no modernidad ni minimalismo. In-Game asset. 2d. High contrast. No shadows
Diseña un botón visual para un videojuego llamado Symphony Siege, destinado a la opción "Tutorial". El estilo debe ser coherente con una interfaz inspirada en una sala de conciertos clásica: elegante, musical y refinada. El botón debe incluir la palabra "Tutorial" centrada, usando una tipografía manuscrita o de partitura antigua, legible y estilizada. El fondo puede simular una partitura enrollada, una tablilla de madera pulida o una tecla de piano estirada horizontalmente. El contorno del botón debe tener detalles ornamentales dorados o cobrizos, evocando la estética barroca o sinfónica. El tono general debe ser acogedor y educativo, sin perder la elegancia musical. In-Game asset. 2d. High contrast. No shadows
Fondo azul con un marco de hierro. In-Game asset. 2d. High contrast. No shadows
Tambor evolucionado. In-Game asset. 2d. High contrast. No shadows
guitarra evolucionada. In-Game asset. 2d. High contrast. No shadows
Trompeta evolucionada. In-Game asset. 2d. High contrast. No shadows
Violin de hielo futurista evolucionado. In-Game asset. 2d. High contrast. No shadows
DJ evolucionado. In-Game asset. 2d. High contrast. No shadows
estrellitas luminosas. In-Game asset. 2d. High contrast. No shadows
Piano enorme. In-Game asset. 2d. High contrast. No shadows
piano de cola cayendo. In-Game asset. 2d. High contrast. No shadows
musicChest. In-Game asset. 2d. High contrast. No shadows
Design a stylish "Restart" button for a fantasy-themed tower defense game called Symphony Siege. The button should look like a polished UI element with a musical theme, fitting the visual style of a haunted concert hall. Shape: rounded rectangle or ornate frame, with golden or bronze edges and a subtle wood or velvet texture background. Icon: a circular restart arrow symbol (⟳ or similar), stylized like a treble clef or musical motif, glowing softly in white, gold, or blue. Optional details: faint floating music notes, light reflections, or sparkles around the icon to suggest magical energy. Text (optional): include the word "Restart" in elegant serif font, or leave it icon-only. Make sure the design is readable at small sizes and fits with the existing UI style (clean, magical, classical). No clutter, no background image — just the button asset.. In-Game asset. 2d. High contrast. No shadows
Design an icon for a button labeled "Towers" in a fantasy tower defense game set in a haunted concert hall. The icon should clearly represent access to a selection of instrument-based defense towers. Use a stylized rack or display of musical instruments arranged like tower miniatures: a drum, violin, trumpet, electric guitar, and DJ deck. They should look magical and glowing slightly, as if floating or placed on a scroll or magical stand. The icon should be square (256×256 px), clean and readable at small sizes. Background should be subtle—wood, velvet, or magical mist—but not distracting. The icon must not include text, only imagery. Style: digital painted or semi-flat fantasy UI, fitting with a classical, magical orchestral theme.. In-Game asset. 2d. High contrast. No shadows
Design an icon for a button labeled "Enemies" in a fantasy tower defense game set in a haunted concert hall. The icon should represent the chaotic and musical nature of the enemies. Show a cluster of stylized enemy silhouettes made of shadow, glitchy waveforms, or cracked musical notes. They should look menacing but stylized, like abstract creatures of sound and dissonance. Use a dark, slightly glowing background (deep purple, blue, or smoky black) with subtle magical accents—floating broken clefs, static, or distortion. Icon must be square (256×256 px), clean, and readable at small sizes. Avoid text—use only imagery. Style: semi-flat or digitally painted fantasy UI, consistent with an elegant but eerie orchestral theme.. In-Game asset. 2d. High contrast. No shadows
Design a button icon for "Back to Menu" in a fantasy tower defense game set in a haunted, musical concert hall. The button should feature a stylized arrow pointing left, wrapped in or formed by musical elements such as a ribbon of notes, a bass clef, or a scroll with a staff line. The background should be elegant and soft: deep velvet red or dark wood with subtle glow. Optional: add a small menu symbol (like sliders or a parchment icon) subtly integrated behind or beneath the arrow. Keep the icon square (256×256 px), readable at small sizes, and without text. Style should match the UI of the game—refined, fantasy-themed, and orchestral in tone.. In-Game asset. 2d. High contrast. No shadows
Design an icon for the Settings menu in a fantasy tower defense game set in a haunted concert hall. The icon should be a stylized gear or cogwheel, but with a musical twist: integrate treble clefs, tuning pegs, or parts of old instruments (like violin scrolls or piano strings) into the gear design. Use metallic textures (bronze, dark gold, or polished silver), with soft magical glow or engraved music notes along the edges. Background should be subtle—deep velvet or dark wood, with ambient lighting to highlight the gear. Icon must be square (256×256 px), readable at small sizes, and include no text. Style: elegant, orchestral fantasy UI—matching the tone of a classical concert hall with magical elements.. In-Game asset. 2d. High contrast. No shadows
Simbolo de mas de color verde. In-Game asset. 2d. High contrast. No shadows
Simbolo de menos de color rojo. In-Game asset. 2d. High contrast. No shadows
Design an icon for an Auto Start Wave toggle in a fantasy tower defense game set in a haunted concert hall. The icon should represent automatic wave progression using a musical or magical theme. Main element: a stylized fast-forward symbol (⏩) or two angled arrows, designed from musical elements like overlapping notes, metronome arms, or flowing sheet music. Optional overlay: a glowing circle, enchanted loop, or small play symbol to suggest automation. Use glowing magical accents (blue, gold, or purple) and keep the shape elegant, readable, and consistent with the orchestral UI. Icon must be square (256×256 px), readable at small sizes, and include no text. Provide two visual states: Enabled: glowing softly with animated sparkles or highlights. Disabled: desaturated or dimmed, with no glow. Style: clean fantasy UI, matching a mystical and musical battlefield interface.. In-Game asset. 2d. High contrast. No shadows
Design an icon for a toggle button labeled "Show Damage Numbers" in a fantasy tower defense game set in a magical concert hall. The icon should represent visible damage output using a musical and magical theme. Main elements: show floating numbers (like “+35”, “-120”) rising or popping out from stylized musical symbols—such as a treble clef or burst of notes. Optionally, display a glowing impact spark or small explosion with numbers around it to represent hit feedback. Use gold, red, or white tones for the numbers and magical trails for emphasis. Background should be neutral or dark, subtly textured (like velvet or wood), to enhance readability. The icon must be square (256×256 px), readable at small sizes, and include no text. Provide two visual states: Enabled: numbers glowing, slightly animated or rising. Disabled: numbers greyed out or crossed subtly with a muted tone. Style: fantasy UI, clean and elegant, consistent with the musical combat theme of the game.. In-Game asset. 2d. High contrast. No shadows
Design an icon for a Language selection button in a fantasy tower defense game set in a magical concert hall. The icon should combine a classic globe symbol with musical or magical elements to reflect the game's unique theme. Main symbol: a stylized globe with subtle music note engravings on the surface or longitude/latitude lines formed from staff lines (like a musical sheet). Optional elements: a floating treble clef, sparkles, or an open scroll representing language or translation. Use elegant gold, bronze, or blue tones, with a soft magical glow. The background should be subtle—velvet, dark wood, or parchment-like texture. Icon must be square (256×256 px), readable at small sizes, and must not include text. Optional: provide a state where a small flag symbol or dropdown arrow appears to suggest language selection. Style: refined, orchestral fantasy UI—fitting the atmosphere of a haunted concert hall with magical elegance.. In-Game asset. 2d. High contrast. No shadows
destello. In-Game asset. 2d. High contrast. No shadows
Create a detailed fantasy icon of a War Horn designed for a musical-themed tower defense game set in a haunted concert hall. The horn should be ornate and elegant, resembling a mix between a classical brass instrument (like a French horn or trumpet) and a battle horn. Crafted from polished brass or gold, with engraved musical symbols (clefs, notes, or swirling staff lines) along its surface. The mouthpiece and flared bell should look slightly exaggerated, magical, or ceremonial. Add glowing accents (blue, violet, or gold) or floating music notes around it to suggest it's enchanted. The horn may rest on a pedestal, float slightly, or face outward ready to be sounded. Background should be minimal or transparent. Icon must be square (256×256 or 512×512), clean and readable at small sizes. Style: elegant fantasy UI, semi-realistic digital painting, matching the orchestral theme of the game.. In-Game asset. 2d. High contrast. No shadows
Torre de guitarra moderna, futurista con amplificacion.. In-Game asset. 2d. High contrast. No shadows
Una estructura imponente, como una batería gigante, con tambores hechos de cristales vibrantes o rocas volcánicas incandescentes. Podría tener vetas de energía luminosa (magma, hielo o electricidad) recorriendo su superficie, y un aura de pulsaciones rítmicas visibles. La base podría estar agrietada por la energía que emana.. In-Game asset. 2d. High contrast. No shadows
Una trompeta de proporciones colosales, con una campana que se abre como un altavoz parabólico gigante. Su superficie podría ser de un metal brillante y pulido, con intrincados grabados o incrustaciones de cristales resonantes que brillan intensamente. Podría tener un sistema de lentes o emisores de energía en su boca.. In-Game asset. 2d. High contrast. No shadows
Un violín que parece estar esculpido en nubes etéreas o cristal de éter, con un brillo suave. El arco es un haz de luz pura que deja un rastro luminoso al moverse. Las cuerdas son finos hilos de energía que brillan con diferentes colores, y el cuerpo del violín podría tener constelaciones o nebulosas incrustadas.. In-Game asset. 2d. High contrast. No shadows
Una torre que es una mesa de DJ futurista y auto-programable, flotando sobre el suelo. Tendría múltiples pantallas holográficas que muestran complejos patrones de ondas sonoras y datos. Los platos serían discos de energía giratorios que emiten luz y chispas, y la torre en sí podría estar rodeada por un campo de fuerza o una rejilla de luz.. In-Game asset. 2d. High contrast. No shadows
Aura de luz. In-Game asset. 2d. High contrast. No shadows
enemyDeath
Sound effect
hit
Sound effect
build
Sound effect
sonidotrompeta
Sound effect
proyectilT
Sound effect
ProyectilTA
Sound effect
SonidoGuitarra
Sound effect
ProyectilG
Sound effect
portal
Sound effect
titleSound
Sound effect
SonidoViolin
Sound effect
ProyectilV
Sound effect
SonidoV
Sound effect
Cuerno
Sound effect
SonidoDJ
Sound effect
ProyectilDJ
Sound effect
MusicaInicio
Music
Risa
Sound effect
Voz
Sound effect
Melodia
Music
Destruir
Sound effect
eror
Sound effect
grandPianoChord
Sound effect
grandPianoImpact
Sound effect
chestOpen
Sound effect
powerUnlock
Sound effect
fanfare
Music
orito
Sound effect