User prompt
Quiero que reduzcas un poco la velocidad de el enemigo glitch audio cuando viene potenciado
User prompt
quiero que a la hora de seleccionar una torre y no tener el dinero suficiente se reproduzca un asset nuevo llamado "eror" ademas quiero que ninguna torre se sobreponga al menu de construir torre
User prompt
si
User prompt
Ahora quiero que añadas una pantalla de "creditos" pasada la wave 2 que diga Plantilla de atribución para música generada con Udio Usa esta leyenda en los créditos de tu juego (en pantalla de créditos, menú o documentación): Music generated with Udio This game includes AI-generated music created using Udio. All tracks were produced by the developer via Udio’s platform under its current terms of use.
User prompt
Quiero que a la hora de remover una torre se active un asset nuevo llamado "Destruir"
User prompt
tengo un problema los tambores ya no hacen ruido al atacar
User prompt
Olvidemos la musica aleatoria quiero que el asset "Melodia" se reproduzca en el juego
User prompt
Okay quiero que olvides totalmente el sistema de musica aleatoria, ahora quiero que se reproduzca unicamente el asset llamado "Melodia" como musica
User prompt
Crea un sistema de reproducción de música aleatoria para un videojuego. El sistema debe seleccionar una canción aleatoria de una lista definida por el usuario, pero debe asegurarse de que solo se reproduzca una canción a la vez. Las condiciones clave son: Si una canción ya está sonando, no debe iniciar una nueva. Si por cualquier razón hay una transición (ej. nueva wave, cambio de escena), la canción anterior debe detenerse por completo antes de iniciar otra. El sistema no debe crear múltiples instancias del reproductor de audio. Evita la repetición inmediata de la misma canción (opcional). Incluye control manual: una función para detener la música si se necesita. Puede implementarse en Unity (C#), Godot (GDScript) o JavaScript con HTML5 Audio. Usa comentarios para explicar cada sección y prevé errores como múltiples llamadas seguidas al mismo evento. Opcional: añade un pequeño retraso (1-2 segundos) entre el final de una canción y el inicio de la siguiente si hay autoplay.
User prompt
Reestructura el sistema de dificultad del juego “Symphony Siege” para que el jugador no pueda llegar cómodamente a la wave 15 ahorrando grandes cantidades de oro. Implementa los siguientes cambios: 1. Escalado de enemigos A partir de la wave 6, la salud de los enemigos debe escalar de forma exponencial suave, usando una fórmula como: vida = vida_base × (1.1)^(número_de_wave) A partir de la wave 10, aumenta la velocidad de los enemigos un 5% cada 2 waves. 2. Sistema de economía más estricto Reduce la ganancia por enemigo a 3 de oro a partir de la wave 6. Limita el oro total que puede obtenerse por wave a 45, a menos que el jugador tenga torres o mejoras que aumenten la ganancia. Introduce enemigos especiales que no den oro al morir (ej: espectros o sombras musicales). 3. Waves especiales y aleatorias A partir de la wave 8, introduce ocasionalmente oleadas temáticas como: Oleada de corredores (enemigos muy rápidos con poca vida) Oleada de blindados (enemigos muy resistentes a daño físico) Oleada masiva (más enemigos pero menos vida) 4. Enemigos con mecánicas disruptivas Introduce enemigos que: Son inmunes a ralentización o empuje Explotan al morir y dañan torres cercanas Desactivan torres por 2 segundos al tocarlas 5. Sistema de gasto forzado Cada 5 waves, genera un evento donde el jugador debe gastar oro para: Reparar torres Activar un escudo defensivo Desbloquear mejoras o evitar penalizaciones 6. Anti-abuso de ahorro Si el jugador mantiene más de 600 de oro durante 3 waves seguidas, activa un modificador: Incrementa la dificultad de las próximas 2 waves (más enemigos o más rápidos) ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Crea un sistema de reproducción aleatoria de música para un videojuego. El juego tiene una lista de canciones predeterminadas y se debe seleccionar una canción aleatoriamente al iniciar el juego o al comenzar una nueva wave. Solo debe reproducirse una canción a la vez. Si una canción ya está sonando, no debe iniciar otra hasta que termine (o hasta que se la detenga manualmente). Asegúrate de que el sistema: Elija una canción aleatoria del listado (los que en assets en el apartado de musica llevan los nombres): "1" "2" "3" "4" "5" y "6" Detenga la canción anterior antes de reproducir una nueva (si aplica) No permita que múltiples canciones se reproduzcan a la vez Evite la reproducción repetida de la misma canción consecutivamente Opcional: agregue una pequeña pausa entre canciones (ej. 1-2 segundos) Esté optimizado para no generar acumulación de llamadas de audio si se reinicia el evento varias veces
User prompt
Quiero agregar una nueva regla a la musica aleatoria, una nueva cancion se reproducira cada 23 segundos, esta condicion solo se rompera cuando el jugador haga click en el boton de start en ese caso una canncion al azar sera reproducida
User prompt
Hay musica que se reproduce una sobre otra
User prompt
Me gustaria que centres el precio del upgrade ademas de poner el menu upgrade un poco mas arriba
User prompt
Si
User prompt
Podrias hacer que al actualizar cualquiera de las torres haga el mismo sonido que hizo al ser construida? ademas me gustaria que multipliques x1,2 la vida de los enemigos
User prompt
Si
User prompt
Quiero que elimines "archdetail" ademas de las sillas sombreadas de los lados derecho e izquierdo
User prompt
ahora Place classical concert chairs in neat, evenly spaced rows inside a concert hall setting. Align them facing the stage, with realistic perspective and natural spacing. Include central and side aisles, follow terrain elevation if present, and avoid obstructing key gameplay zones. Emphasize symmetry, readability, and functional layout.”
User prompt
Quiero que quites todas las sillas, ojo no quiero que borres el asset, solo las sillas
User prompt
Quiero que al clickar el boton start te den 5000 de oro
User prompt
A la hora de actualizar las torres ya no quiero que las cambies de color
User prompt
Quiero que el costo de upgrade este justo debajo de la r de la palabra "upgrade" ademas quiero que crees un asset nuevo para el wave active llamado "wave" ademas de que quiero que crees assets nuevos para todas las actualizaciones 2 y 3 de TODAS las torres denominandolos de la siguiente forma ejemplo para la torre tambor "Tambor2" y "Tambor3" mientras que para la torre trompeta "Trompeta2" y "Trompeta3" y asi sucesivamente
User prompt
Si
User prompt
Okay te voy a hacer un reajuste en el sistema de upgrade con los siguientes puntos: 🥁 1. Tambor Básico Tipo: Torre rápida de corto alcance con daño en área Nivel Daño Velocidad (disparos) Efecto especial Costo I 15 0.5s Golpea en área pequeña 50 II 20 0.5s Aumenta el área de impacto un 25% 90 III 25 0.4s Aplica ralentización del 10% por 1.5 segundos 160 🎺 2. Trompeta de Choque Tipo: Torre de daño medio que empuja a los enemigos Nivel Daño Velocidad (disparos) Efecto especial Costo I 35 1.0s Empuja levemente a los enemigos 80 II 45 0.9s Empuje más fuerte, puede interrumpir animaciones 130 III 60 0.8s 10% de probabilidad de aturdir por 1 segundo 200 🎸 3. Guitarra Eléctrica Tipo: Torre de rebote en cadena, ataque lento con daño dividido Nivel Daño total Velocidad Efecto especial Costo I 20 1.5s Rebota entre 3 enemigos 120 II 25 1.3s Rebota entre 4 enemigos 180 III 30 1.2s Rebote puede encadenar a enemigos cercanos del último impacto 250 🎻 4. Violín Congelante Tipo: Torre de control con ralentización Nivel Daño Velocidad Efecto especial Costo I 10 1.0s Ralentiza 25% por 2 segundos 100 II 14 1.0s Ralentiza 35% por 3 segundos 160 III 18 0.9s Ralentiza 40% y 10% de probabilidad de congelar por 1.5s 220 🎧 5. DJ Rítmico Tipo: Torre poderosa de gran alcance con efectos en área Nivel Daño Velocidad Efecto especial Costo I 50 2.5s Daño en área + ceguera temporal (reduce precisión o velocidad) 200 II 65 2.3s Aumenta el área + la ceguera dura 1 segundo 280 III 80 2.0s Aplica distorsión: -25% de velocidad + daño en el tiempo (DoT leve)
/****
* Plugins
****/
var tween = LK.import("@upit/tween.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) {
gold += self.target.goldValue;
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) {
var self = Container.call(this);
// Set enemy properties based on type
self.enemyType = enemyType || 'notaDesafinada';
self.isElite = isElite || false;
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 = 3.5;
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;
}
self.pathIndex = 0;
self.walkAnimationTimer = 0;
self.originalScale = 1.0;
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
// 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 () {
// Walking animation - bobbing effect
self.walkAnimationTimer += 0.2;
var walkBob = Math.sin(self.walkAnimationTimer) * 0.1;
enemyGraphics.scaleY = self.originalScale + walkBob;
enemyGraphics.scaleX = self.originalScale + walkBob * 0.5;
enemyGraphics.rotation = Math.sin(self.walkAnimationTimer * 1.5) * 0.05;
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) {
LK.showGameOver();
}
// 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 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
}
}
}
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 = 50;
self.range = 200; // Large area
self.fireRate = 150; // Very slow (2.5s at 60fps)
self.cost = 200;
self.areaRadius = 120; // Large area
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') {
LK.getSound('ProyectilTA').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;
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 = 65;
self.fireRate = 138; // 2.3s at 60fps
self.areaRadius = Math.floor(self.baseAreaRadius * 1.2); // Larger area
self.blindDuration = 60; // 1s blind
} else if (self.level === 3) {
self.damage = 80;
self.fireRate = 120; // 2.0s at 60fps
self.distortionEffect = true; // Add distortion (25% slow + DoT)
self.distortionSlowPercent = 0.25;
self.distortionDamage = 5; // DoT damage per tick
self.distortionDuration = 180; // 3s 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++;
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
});
/****
* Game Code
****/
// Game variables
var gameState = 'menu'; // 'menu', 'tutorial', 'playing'
var tutorialStep = 0;
var menuContainer;
var tutorialContainer;
// Music system variables
var currentMusicTrack = null;
var musicTracks = ["1", "2", "3"]; // Initial tracks for waves 1-15
var advancedMusicTracks = ["4", "5", "6"]; // Advanced tracks for wave 16+
var musicCheckInterval = 1380; // Check every 23 seconds (1380 ticks = 23 seconds at 60fps)
var musicCheckTimer = 0;
var gameplayMusicPlaying = false;
// Game Elements
var enemies = [];
var towers = [];
var bullets = [];
var buildSpots = [];
var gold = 150;
var lives = 20;
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;
// === 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 playRandomMusic() {
var availableTracks = wave >= 16 ? advancedMusicTracks : musicTracks;
var selectedTrack;
// Ensure we don't play the same track twice in a row
do {
var randomIndex = Math.floor(Math.random() * availableTracks.length);
selectedTrack = availableTracks[randomIndex];
} while (selectedTrack === currentMusicTrack && availableTracks.length > 1);
// Stop current music before playing new track to prevent overlap
LK.stopMusic();
// Always update and play the new track
currentMusicTrack = selectedTrack;
if (!musicMuted) {
LK.playMusic(currentMusicTrack, {
loop: true
});
gameplayMusicPlaying = true;
}
}
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);
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;
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 = 1000;
var nextButton = tutorialContainer.addChild(LK.getAsset('guitarTower', {
anchorX: 0.5,
anchorY: 0.5
}));
nextButton.x = 1200;
nextButton.y = 1600;
var nextButtonText = tutorialContainer.addChild(new Text2('♫ NEXT ♫', {
size: 60,
fill: 0xFFD700,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
nextButtonText.anchor.set(0.5, 0.5);
nextButtonText.x = 1200;
nextButtonText.y = 1680;
var backButton = tutorialContainer.addChild(LK.getAsset('drumTower', {
anchorX: 0.5,
anchorY: 0.5
}));
backButton.x = 848;
backButton.y = 1600;
var backButtonText = tutorialContainer.addChild(new Text2('♪ BACK TO MENU ♪', {
size: 60,
fill: 0xFFD700,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = 848;
backButtonText.y = 1680;
var tutorialSteps = ['Welcome to Concert Hall Defense!\nProtect the stage from invading enemies.', 'Enemies follow the wooden path from bottom to top.\nThey want to disrupt the concert!', 'Click on build spots (circular areas) to place towers.\nEach tower has different abilities and costs.', 'DRUM TOWERS ($50): Fast firing with area damage.\nGreat for groups of enemies.', 'TRUMPET TOWERS ($80): Push enemies back down the path.\nUse to buy more time.', 'GUITAR TOWERS ($120): Chain lightning between enemies.\nEffective against clustered foes.', 'VIOLIN TOWERS ($100): Slow enemies with magical music.\nPerfect for crowd control.', 'DJ TOWERS ($200): Massive area damage with blinding effects.\nExpensive but very powerful.', 'Place towers strategically around the path.\nElevated positions like balconies give better coverage.', 'You earn gold by defeating enemies.\nUse it wisely to build your defense!', 'Good luck defending the concert hall!\nThe show must go on!'];
function showTutorial() {
tutorialContainer.visible = true;
// Move tutorial container to front to ensure visibility
game.removeChild(tutorialContainer);
game.addChild(tutorialContainer);
updateTutorialStep();
}
function updateTutorialStep() {
if (tutorialStep < tutorialSteps.length) {
tutorialStepText.setText(tutorialSteps[tutorialStep]);
nextButtonText.setText(tutorialStep === tutorialSteps.length - 1 ? 'START GAME' : 'NEXT');
} else {
gameState = 'playing';
tutorialContainer.visible = false;
startNextWave();
}
}
nextButton.down = function () {
tutorialStep++;
if (tutorialStep >= tutorialSteps.length) {
gameState = 'playing';
tutorialContainer.visible = false;
startNextWave();
} else {
updateTutorialStep();
}
};
backButton.down = function () {
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: 85,
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;
muteButton.down = function () {
if (musicMuted) {
if (gameState === 'playing') {
playRandomMusic();
} else {
LK.playMusic('MusicaInicio', {
loop: true
});
}
muteText.setText('♪ MUSIC ♪');
muteButton.tint = 0xFFFFFF;
musicMuted = false;
} else {
LK.stopMusic();
muteText.setText('♪ MUTED ♪');
muteButton.tint = 0x888888;
musicMuted = true;
gameplayMusicPlaying = false;
}
};
startWaveButton.down = function () {
if (gameState === 'playing' && wavePaused && enemies.length === 0) {
// Play random music immediately when start button is clicked
if (!musicMuted) {
playRandomMusic();
}
startCurrentWave();
}
};
// 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 + ' ♪');
}
function showBuildMenu(buildSpot) {
showingBuildMenu = true;
selectedBuildSpot = buildSpot;
buildMenuContainer.visible = true;
// 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('enemyDeath').play(); // 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;
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");
}
};
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");
}
};
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");
}
};
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");
}
};
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");
}
};
// 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.down = function (x, y, obj) {
// 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)))
return Math.round(10 * Math.pow(1.15, waveNumber - 1));
}
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 = [];
// 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;
}
}
// Determine if this should be an elite enemy (25+ waves only)
var isElite = wave >= 25 && Math.random() < 0.25; // 25% chance for elite
var enemy = new Enemy(selectedType, isElite);
enemy.x = pathPoints[0].x;
enemy.y = pathPoints[0].y;
// Scale health based on wave using formula: enemy_HP = base_HP × (1.12 ^ (wave - 1))
var healthMultiplier = Math.pow(1.12, wave - 1);
enemy.health = Math.floor(enemy.health * healthMultiplier * 1.2);
enemy.maxHealth = enemy.health;
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
playRandomMusic();
} else if (!musicMuted && !gameplayMusicPlaying) {
// Ensure music is playing for subsequent waves
playRandomMusic();
}
// 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;
// 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 = 1.8;
leftCurtainIntro.scaleY = 3.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;
// 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() {
// Animate curtains opening
tween(leftCurtainIntro, {
x: -600
}, {
duration: 2000,
easing: tween.easeInOut
});
tween(rightCurtainIntro, {
x: 2648
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Clean up title intro and start game
titleIntroContainer.destroy();
gameState = 'playing';
startNextWave();
}
});
}
// 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();
}
// Music system - check if we need to switch tracks and ensure music is always playing
if (!musicMuted && gameState === 'playing') {
// Ensure music is playing during gameplay
if (!gameplayMusicPlaying || LK.ticks % 300 === 0) {
// Check every 5 seconds
if (!currentMusicTrack) {
playRandomMusic();
} else {
// Restart current track if not playing
LK.playMusic(currentMusicTrack, {
loop: true
});
gameplayMusicPlaying = true;
}
}
musicCheckTimer++;
if (musicCheckTimer >= musicCheckInterval) {
musicCheckTimer = 0;
// Play a new random track every 23 seconds
playRandomMusic();
}
}
if (gameState === 'playing') {
// 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) {
LK.showGameOver();
return;
}
wave++;
waveStarted = false;
gold += 75; // Bonus gold for completing wave
updateUI();
// Check if we've reached wave 16 to switch to advanced music
if (wave === 16 && !musicMuted) {
playRandomMusic(); // Switch to advanced tracks
}
// 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
@@ -783,9 +783,9 @@
// Music system variables
var currentMusicTrack = null;
var musicTracks = ["1", "2", "3"]; // Initial tracks for waves 1-15
var advancedMusicTracks = ["4", "5", "6"]; // Advanced tracks for wave 16+
-var musicCheckInterval = 1800; // Check every 30 seconds (1800 ticks = 30 seconds at 60fps)
+var musicCheckInterval = 1380; // Check every 23 seconds (1380 ticks = 23 seconds at 60fps)
var musicCheckTimer = 0;
var gameplayMusicPlaying = false;
// Game Elements
var enemies = [];
@@ -1331,20 +1331,18 @@
do {
var randomIndex = Math.floor(Math.random() * availableTracks.length);
selectedTrack = availableTracks[randomIndex];
} while (selectedTrack === currentMusicTrack && availableTracks.length > 1);
- // Always stop current music first to prevent overlap
+ // Stop current music before playing new track to prevent overlap
LK.stopMusic();
- // Small delay to ensure previous track stops completely
- LK.setTimeout(function () {
- currentMusicTrack = selectedTrack;
- if (!musicMuted) {
- LK.playMusic(currentMusicTrack, {
- loop: true
- });
- gameplayMusicPlaying = true;
- }
- }, 100); // 100ms delay to ensure clean transition
+ // Always update and play the new track
+ currentMusicTrack = selectedTrack;
+ if (!musicMuted) {
+ LK.playMusic(currentMusicTrack, {
+ loop: true
+ });
+ gameplayMusicPlaying = true;
+ }
}
function createFloatingNote() {
var note = game.addChild(LK.getAsset('musicNote', {
anchorX: 0.5,
@@ -1850,19 +1848,15 @@
autoStartText.x = -200;
autoStartText.y = -70;
muteButton.down = function () {
if (musicMuted) {
- // Always stop first to prevent overlap
- LK.stopMusic();
- LK.setTimeout(function () {
- if (gameState === 'playing') {
- playRandomMusic();
- } else {
- LK.playMusic('MusicaInicio', {
- loop: true
- });
- }
- }, 100);
+ if (gameState === 'playing') {
+ playRandomMusic();
+ } else {
+ LK.playMusic('MusicaInicio', {
+ loop: true
+ });
+ }
muteText.setText('♪ MUSIC ♪');
muteButton.tint = 0xFFFFFF;
musicMuted = false;
} else {
@@ -1874,8 +1868,12 @@
}
};
startWaveButton.down = function () {
if (gameState === 'playing' && wavePaused && enemies.length === 0) {
+ // Play random music immediately when start button is clicked
+ if (!musicMuted) {
+ playRandomMusic();
+ }
startCurrentWave();
}
};
// Build menu elements (initially hidden)
@@ -2697,22 +2695,19 @@
// Check every 5 seconds
if (!currentMusicTrack) {
playRandomMusic();
} else {
- // Stop first then restart current track to prevent overlap
- LK.stopMusic();
- LK.setTimeout(function () {
- LK.playMusic(currentMusicTrack, {
- loop: true
- });
- gameplayMusicPlaying = true;
- }, 50);
+ // Restart current track if not playing
+ LK.playMusic(currentMusicTrack, {
+ loop: true
+ });
+ gameplayMusicPlaying = true;
}
}
musicCheckTimer++;
if (musicCheckTimer >= musicCheckInterval) {
musicCheckTimer = 0;
- // Play a new random track every 30 seconds
+ // Play a new random track every 23 seconds
playRandomMusic();
}
}
if (gameState === 'playing') {
@@ -2756,12 +2751,9 @@
gold += 75; // Bonus gold for completing wave
updateUI();
// Check if we've reached wave 16 to switch to advanced music
if (wave === 16 && !musicMuted) {
- LK.stopMusic(); // Stop current music first
- LK.setTimeout(function () {
- playRandomMusic(); // Switch to advanced tracks after delay
- }, 200);
+ playRandomMusic(); // Switch to advanced tracks
}
// Start next wave after delay
LK.setTimeout(function () {
startNextWave();
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