User prompt
quiero que el oro con el que se inicie sea 150 otra vez
User prompt
Un poco mas abajo, y quiero que todo el texto este centrado
User prompt
Ponlo un poco mas abajo y quiero que ocupe 3 lineas
User prompt
Quiero que añadas la frase "Some say that assembling a true orchestra brings harmony... and power. Maybe it’s worth trying?"abajo de el boton de settings en el menu principal y quiero que tenga el tipo de letra que tiene "start wave"
Code edit (1 edits merged)
Please save this source code
User prompt
When the player unlocks the Grand Piano ability, display a special animated message: Text: "Grand Piano Unlocked" Font: Use a serif-style font similar to Georgia or Garamond (or Upit’s default serif if customization is limited). Style: Capitalize only first letters: Grand Piano Unlocked Text color: gold gradient or bright white with a soft dark drop shadow. Outline: subtle glowing border (gold or purple-tinted). Size: large and centered at the top or middle of the screen. Animation: Fade in over 0.5s, then rise slowly upward while fading out after 2.5s. Add a brief glow pulse behind the text (like a spotlight or light burst). Optional: include floating musical notes (white or gold) swirling gently behind the message. Sound: Play a short, resonant piano chord (deep and rich, like a Grand Piano's opening). The sound should match the reveal timing: trigger right as the text appears. This event should only happen once, immediately when the Grand Piano becomes available. The message must be click-only accessible (no keyboard required) and not interfere with gameplay. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Quiero que la "Iluminacion" que hay en las torres de nivel 3 sea un asset nuevo llamado "Iluminacion3" ademas 🎼 Add a special reward system called “Full Orchestra”. When the player upgrades at least one of each tower type to level 3 (all 5 tower types represented), trigger the following event: 🎉 One-time celebration: Display a central message: "Congratulations! You've formed a full orchestra!" Animate glowing music notes swirling around the screen. Play a short victory sequence (e.g., musical crescendo and final chord). Show a subtle stage curtain opening or spotlight sweeping effect. 🔥 Permanent gameplay buff: All existing towers receive a +20% damage bonus and a 10% cooldown reduction. Any tower built after this moment also receives these bonuses. Visually indicate buffed towers with a small orchestral emblem or glow outline. 🧠 Tutorial addition: In the tower upgrade tutorial panel, include a vague but tempting hint: "Some say that assembling a true orchestra brings harmony... and power. Maybe it’s worth trying?" Ensure this hint appears only once and doesn’t spoil the exact mechanic, but encourages experimentation. ⚠️ This system must be click-only compatible and should not trigger multiple times. Buff effects must persist even after restarting waves or building new towers post-event. ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
No veo mis 7000 de oro
User prompt
Podrias hacer que el jugador inicie con 7000 de oro? (provisionalmete para hacer prueba de juego
User prompt
When a tower reaches level 3, trigger an enhanced feedback system to reward the player visually, audibly, and mechanically. 🎇 Visual Effects (one-time upgrade flash): Play a glowing musical explosion around the tower — floating musical notes (white or golden), a circular energy pulse, and subtle sparkles. The tower emits a radiant light burst lasting 1–2 seconds with a glowing trail upward. Overlay the word “Fortissimo!” or “Solo Performance!” above the tower in a bold serif font (e.g., Playfair Display), fading out after 1.5 seconds. 🌟 Permanent visual upgrade on the tower: Add a glowing aura around the tower (color varies per tower type). Bonus effects (one-time gameplay boost): When upgraded to level 3, the tower triggers a sound wave blast that damages all enemies in a small radius (minor AoE damage). Apply a temporary +20% speed buff to all towers within a small radius for 10 seconds. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Use the font Playfair Display, in all caps, with subtle glowing gold outline and slight shadow, for the phrase: 'Grand Piano Unlocked!'"
User prompt
Quiero que a partir de la wave 15 ya no aparezcan enemigos potenciados
User prompt
Los nombres de lo enemigos en el tutorial estan en español soluciona eso, adema quiero que TODOS los enemigos den +3 de oro extra al morir desde la wave 13, adema quiero que a partir de la wave 13 las recompensas de oleadass aumenten en 20 (ejemplo en la wave 13 se aumentaria "20", en la 14 "40", en la 15 "60", en la 16 "80" y asi sucesivamente
User prompt
quiero que los nombres de los enemigos en el tutorial esten siempre en ingles
User prompt
Quiero que a partir de la wave 12+ TODOS los enemigos den +2 de oro extra al morir
User prompt
quiero que cuando el jugador pierda vida se haga una animacion en el icono de vida de hacerse chiquito y luego volver a su tamaño original ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
quiero que se inicie siempre con 40 vidas
User prompt
quiero que cuando un jugador pulse el boton de restart ya no se reproduzca ni "Voz" ni "Risa" ademas quiero que reesteblezacas las vidas en 40 y que cambies la descripcion del tutorial que dice que solo tienes 1 vida cuando en realidad son 40
User prompt
Quiero que para poder hacer restart al juego se tenga que hacer doble click al boton de restart, tambien quiero una instruccion de que se requiere doble click
User prompt
quiero que una vez se este en la pantalla de game over 0,5 segundos despues el contador de vidas se iguale a 0 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
quiero que una vez se este en la pantalla de game over el contador de vida se iguale a 0
User prompt
Ahora quiero que hagas el paso 5
User prompt
Ahora quiero que hagas el paso 5
User prompt
Ahora quiero que hagas el paso 4
User prompt
Ahora quiero que hagas el paso 3
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BuildSpot = Container.expand(function (spotNumber) {
var self = Container.call(this);
var spotGraphics = self.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5
});
self.occupied = false;
self.spotNumber = spotNumber || 0;
// Build spot graphic only, no number display
self.down = function (x, y, obj) {
console.log("Build spot", self.spotNumber, "clicked, occupied:", self.occupied, "showingBuildMenu:", showingBuildMenu);
if (!self.occupied && !showingBuildMenu) {
console.log("Showing build menu for spot", self.spotNumber, "at:", self.x, self.y);
showBuildMenu(self);
}
};
return self;
});
var Bullet = Container.expand(function (startX, startY, target, damage, towerType, specialData) {
var self = Container.call(this);
// Choose random bullet asset from all available types
var bulletTypes = ['bullet', 'drumBullet', 'trumpetBullet', 'guitarBullet', 'violinBullet', 'djBullet'];
var bulletAsset = bulletTypes[Math.floor(Math.random() * bulletTypes.length)];
var bulletGraphics = self.attachAsset(bulletAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 8;
self.towerType = towerType || 'drum';
self.specialData = specialData || {};
self.hitTargets = []; // For chain attacks
self.update = function () {
// Stop all bullet movement if game over is triggered
if (gameOverTriggered) return;
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) {
// Stop collision processing if game over is triggered
if (gameOverTriggered) {
self.destroy();
return;
}
// Hit target
var died = false;
// Handle different tower effects
if (self.towerType === 'drum') {
// Area damage around target
died = self.target.takeDamage(self.damage);
// Damage other enemies in area
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy !== self.target) {
var dx2 = enemy.x - self.target.x;
var dy2 = enemy.y - self.target.y;
var areaDist = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (areaDist <= (self.specialData.areaRadius || 60)) {
enemy.takeDamage(self.damage);
// Level III drum tower slow effect
if (self.specialData.slowEffect && Math.random() < (self.specialData.slowChance || 0.1)) {
enemy.slowedUntil = LK.ticks + (self.specialData.slowDuration || 90);
}
}
}
}
} else if (self.towerType === 'trumpet') {
// Damage and pushback
died = self.target.takeDamage(self.damage);
// Apply pushback effect
if (!died && self.target.pathIndex < pathPoints.length - 2) {
self.target.pathIndex = Math.max(0, self.target.pathIndex - 1);
}
// Level III stun effect
if (!died && self.specialData.stunChance && Math.random() < self.specialData.stunChance) {
self.target.stunnedUntil = LK.ticks + (self.specialData.stunDuration || 60);
// Visual stun effect
var enemyGraphics = self.target.getChildAt(0);
if (enemyGraphics) {
var originalTint = enemyGraphics.tint || 0xffffff;
tween(enemyGraphics, {
tint: 0xFFFF00,
// Yellow stun effect
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
LK.setTimeout(function () {
if (enemyGraphics && enemyGraphics.parent) {
tween(enemyGraphics, {
tint: originalTint,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300
});
}
}, (self.specialData.stunDuration || 60) * 16);
}
});
}
}
} else if (self.towerType === 'guitar') {
// Chain attack with multiple hits
var hitsPerTarget = 3; // Guitar does 3 hits per target
for (var hit = 0; hit < hitsPerTarget; hit++) {
died = self.target.takeDamage(self.damage) || died;
}
self.hitTargets.push(self.target);
var chainsLeft = (self.specialData.chainCount || 3) - self.hitTargets.length;
if (chainsLeft > 0) {
// Find next target for chain
var nextTarget = null;
var closestDist = 150; // Chain range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.hitTargets.indexOf(enemy) === -1 && enemy.health > 0) {
var dx2 = enemy.x - self.target.x;
var dy2 = enemy.y - self.target.y;
var chainDist = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (chainDist < closestDist) {
nextTarget = enemy;
closestDist = chainDist;
}
}
}
if (nextTarget) {
self.target = nextTarget;
return; // Continue to next target
}
// Level III extended chaining
else if (self.specialData.extendedChain && self.hitTargets.length === self.specialData.chainCount) {
// Try to find enemies near the last hit target for extended chain
var lastTarget = self.hitTargets[self.hitTargets.length - 1];
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.hitTargets.indexOf(enemy) === -1 && enemy.health > 0) {
var dx2 = enemy.x - lastTarget.x;
var dy2 = enemy.y - lastTarget.y;
var chainDist = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (chainDist < 100) {
// Shorter range for extended chain
self.target = enemy;
self.specialData.chainCount++; // Extend the chain
return;
}
}
}
}
}
} else if (self.towerType === 'violin') {
// Damage with slow and freeze chance
died = self.target.takeDamage(self.damage);
// Check for freeze effect first (Level III only)
var froze = false;
if (!died && self.specialData.freezeChance && Math.random() < self.specialData.freezeChance) {
self.target.frozenUntil = LK.ticks + (self.specialData.freezeDuration || 90);
froze = true;
// Apply freeze animation
var enemyGraphics = self.target.getChildAt(0);
if (enemyGraphics) {
var originalTint = enemyGraphics.tint || 0xffffff;
tween(enemyGraphics, {
tint: 0x00FFFF,
// Cyan freeze effect
scaleX: 0.9,
scaleY: 0.9,
alpha: 0.7
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
LK.setTimeout(function () {
if (enemyGraphics && enemyGraphics.parent) {
tween(enemyGraphics, {
tint: originalTint,
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 300
});
}
}, (self.specialData.freezeDuration || 90) * 16);
}
});
}
}
// Apply slow effect if not frozen
else if (!died && !froze && Math.random() < (self.specialData.slowChance || 0.25)) {
self.target.slowedUntil = LK.ticks + (self.specialData.slowDuration || 120);
// Apply freeze animation - enemy becomes frozen and shimmers
var enemyGraphics = self.target.getChildAt(0);
if (enemyGraphics) {
// Store original values
var originalTint = enemyGraphics.tint || 0xffffff;
var originalScaleX = enemyGraphics.scaleX || 1.0;
var originalScaleY = enemyGraphics.scaleY || 1.0;
var originalAlpha = enemyGraphics.alpha || 1.0;
// Freeze effect with ice-blue tint and slight scale animation
tween(enemyGraphics, {
tint: 0x0080FF,
scaleX: originalScaleX * 1.1,
scaleY: originalScaleY * 1.1,
alpha: 0.8
}, {
duration: 300,
easing: tween.easeOut
});
// Shimmer effect during freeze duration
var shimmerCount = 0;
var maxShimmers = Math.floor((self.specialData.slowDuration || 120) / 30);
var _doShimmer = function doShimmer() {
if (shimmerCount < maxShimmers && self.target && self.target.slowedUntil && LK.ticks < self.target.slowedUntil) {
tween(enemyGraphics, {
alpha: 0.5,
tint: 0xB3D9FF
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(enemyGraphics, {
alpha: 0.8,
tint: 0x0080FF
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
shimmerCount++;
if (shimmerCount < maxShimmers) {
LK.setTimeout(_doShimmer, 100);
}
}
});
}
});
}
};
LK.setTimeout(_doShimmer, 300);
// Return to normal after freeze duration
LK.setTimeout(function () {
if (enemyGraphics && enemyGraphics.parent) {
tween(enemyGraphics, {
tint: originalTint,
scaleX: originalScaleX,
scaleY: originalScaleY,
alpha: originalAlpha
}, {
duration: 500
});
}
}, (self.specialData.slowDuration || 120) * 16);
}
}
} else if (self.towerType === 'dj') {
// Area damage with blind effect and Level III distortion
died = self.target.takeDamage(self.damage);
// Damage and apply effects to other enemies in large area
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx2 = enemy.x - self.target.x;
var dy2 = enemy.y - self.target.y;
var areaDist = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (areaDist <= (self.specialData.areaRadius || 120)) {
if (enemy !== self.target) {
enemy.takeDamage(self.damage * 0.8); // Reduced area damage
}
// Apply blind effect (reduce speed)
enemy.blindedUntil = LK.ticks + (self.specialData.blindDuration || 60);
// Level III distortion effect
if (self.specialData.distortionEffect) {
enemy.distortedUntil = LK.ticks + (self.specialData.distortionDuration || 180);
enemy.distortionDamage = self.specialData.distortionDamage || 5;
enemy.distortionSlowPercent = self.specialData.distortionSlowPercent || 0.25;
// Visual distortion effect
var enemyGraphics = enemy.getChildAt(0);
if (enemyGraphics) {
tween(enemyGraphics, {
tint: 0xFF00FF,
// Magenta distortion effect
rotation: 0.2
}, {
duration: 300,
easing: tween.easeOut
});
}
}
}
}
} else {
// Default damage
died = self.target.takeDamage(self.damage);
}
if (died) {
// Track enemy killed
totalEnemiesKilled++;
// Calculate gold reward with restrictions
var goldReward = self.target.goldValue;
// Reduce gold per enemy from wave 6
if (wave >= 6) {
goldReward = Math.min(goldReward, 3);
}
// Ensure minimum gold reward of 1 for all enemies from wave 10+
if (wave >= 10 && goldReward === 0) {
goldReward = 1;
}
// Check wave gold limit (before adding universal bonus)
if (lastWaveGoldEarned + goldReward > maxGoldPerWave) {
goldReward = Math.max(1, maxGoldPerWave - lastWaveGoldEarned);
}
// Add universal +3 bonus for ALL enemies from wave 10+ (after all other restrictions)
if (wave >= 10) {
goldReward += 3;
console.log("Wave " + wave + ": Added +3 universal bonus, total gold reward:", goldReward);
}
// Add additional +2 gold bonus for ALL enemies from wave 11+ (after all other restrictions)
if (wave >= 11) {
goldReward += 2;
console.log("Wave " + wave + ": Added +2 wave 11+ bonus, total gold reward:", goldReward);
}
// Add additional +2 gold bonus for ALL enemies from wave 12+ (after all other restrictions)
if (wave >= 12) {
goldReward += 2;
console.log("Wave " + wave + ": Added +2 wave 12+ bonus, total gold reward:", goldReward);
}
// Add additional +3 gold bonus for ALL enemies from wave 13+ (after all other restrictions)
if (wave >= 13) {
goldReward += 3;
console.log("Wave " + wave + ": Added +3 wave 13+ bonus, total gold reward:", goldReward);
}
// Add additional +4 gold bonus for ALL enemies from wave 14+ (after all other restrictions)
if (wave >= 14) {
goldReward += 4;
console.log("Wave " + wave + ": Added +4 wave 14+ bonus, total gold reward:", goldReward);
}
// Apply gold reward
if (goldReward > 0) {
gold += goldReward;
lastWaveGoldEarned += goldReward;
}
updateUI();
LK.getSound('enemyDeath').play();
}
self.destroy();
} else {
self.x += dx / distance * self.speed * gameSpeed;
self.y += dy / distance * self.speed * gameSpeed;
}
};
return self;
});
var Enemy = Container.expand(function (enemyType, isElite, isSpecial, specialType) {
var self = Container.call(this);
// Set enemy properties based on type
self.enemyType = enemyType || 'notaDesafinada';
self.isElite = isElite || false;
self.isSpecial = isSpecial || false;
self.specialType = specialType || '';
var assetName = self.isElite ? 'eliteEnemy' : self.enemyType;
var enemyGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
if (self.enemyType === 'notaDesafinada') {
self.health = 90;
self.maxHealth = 90;
self.speed = 2;
self.goldValue = 5;
} else if (self.enemyType === 'ruidoBlanco') {
self.health = 144;
self.maxHealth = 144;
self.speed = 1.2;
self.goldValue = 8;
} else if (self.enemyType === 'glitchAudio') {
self.health = 72;
self.maxHealth = 72;
self.speed = 2.7; // Further reduced for better balance when empowered
self.goldValue = 6;
} else if (self.enemyType === 'ecoOscuro') {
self.health = 180;
self.maxHealth = 180;
self.speed = 2;
self.goldValue = 12;
self.canSplit = true;
} else if (self.enemyType === 'autotuneMalicioso') {
self.health = 270;
self.maxHealth = 270;
self.speed = 1.8;
self.goldValue = 20;
self.debuffRadius = 100;
}
// Elite enemies have double HP and gold value
if (self.isElite) {
self.health *= 2;
self.maxHealth *= 2;
self.goldValue *= 2;
enemyGraphics.scaleX = 1.3;
enemyGraphics.scaleY = 1.3;
}
// Special enemy modifications
if (self.isSpecial) {
if (self.specialType === 'spectral') {
self.goldValue = 0; // No gold reward
enemyGraphics.tint = 0x8800FF; // Purple tint
enemyGraphics.alpha = 0.7;
} else if (self.specialType === 'runner') {
self.speed *= 2.5; // Very fast
self.health *= 0.5; // Low health
enemyGraphics.tint = 0x00FFFF; // Cyan tint
} else if (self.specialType === 'armored') {
self.health *= 1.8; // High health
self.speed *= 0.7; // Slower
self.damageResistance = 0.3; // 30% damage reduction
enemyGraphics.tint = 0x808080; // Gray tint
} else if (self.specialType === 'explosive') {
self.explosiveDamage = 25;
self.explosiveRadius = 80;
enemyGraphics.tint = 0xFF4400; // Orange-red tint
} else if (self.specialType === 'disruptor') {
self.disruptRadius = 100;
enemyGraphics.tint = 0xFFFF00; // Yellow tint
}
}
self.pathIndex = 0;
self.walkAnimationTimer = 0;
self.originalScale = 1.0;
self.takeDamage = function (damage) {
// Apply damage resistance for armored enemies
if (self.isSpecial && self.specialType === 'armored' && self.damageResistance) {
damage *= 1 - self.damageResistance;
}
self.health -= damage;
// Show damage numbers if enabled
if (showDamageNumbers) {
var damageText = game.addChild(new Text2(Math.round(damage).toString(), {
size: 45,
fill: 0xFF0000,
font: "Arial, sans-serif"
}));
damageText.anchor.set(0.5, 0.5);
damageText.x = self.x + (Math.random() - 0.5) * 30;
damageText.y = self.y - 30;
damageText.alpha = 1.0;
damageText.scaleX = 1.5;
damageText.scaleY = 1.5;
// Animate damage number with pop effect, then float up and fade
tween(damageText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(damageText, {
y: damageText.y - 80,
alpha: 0
}, {
duration: 1200,
easing: tween.easeOut,
onFinish: function onFinish() {
damageText.destroy();
}
});
}
});
}
if (self.health <= 0) {
self.health = 0;
// Special enemy death effects
if (self.isSpecial) {
if (self.specialType === 'explosive') {
// Damage nearby towers
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx = tower.x - self.x;
var dy = tower.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.explosiveRadius) {
tower.disruptedUntil = LK.ticks + 120; // 2 seconds disruption
// Visual explosion effect
var explosion = game.addChild(LK.getAsset('spotlight', {
anchorX: 0.5,
anchorY: 0.5
}));
explosion.x = self.x;
explosion.y = self.y;
explosion.tint = 0xFF4400;
explosion.alpha = 0.8;
tween(explosion, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
}
}
}
// Handle splitting for Eco Oscuro
if (self.enemyType === 'ecoOscuro' && self.canSplit) {
// Create two Nota Desafinada enemies
for (var i = 0; i < 2; i++) {
var splitEnemy = new Enemy('notaDesafinada');
splitEnemy.x = self.x + (i === 0 ? -30 : 30);
splitEnemy.y = self.y;
splitEnemy.pathIndex = self.pathIndex;
enemies.push(splitEnemy);
game.addChild(splitEnemy);
}
}
return true; // Enemy died
}
// Flash red when hit but preserve original tint
var originalTint = enemyGraphics.tint;
tween(enemyGraphics, {
tint: 0xff0000
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemyGraphics, {
tint: originalTint
}, {
duration: 100
});
}
});
return false;
};
self.update = function () {
// Stop all enemy movement if game over is triggered
if (gameOverTriggered) return;
// Enhanced walking animation - more pronounced feet movement and body oscillation
self.walkAnimationTimer += 0.4; // Increased animation speed
var walkBob = Math.sin(self.walkAnimationTimer) * 0.12; // More pronounced bobbing
var walkSway = Math.cos(self.walkAnimationTimer * 0.9) * 0.05; // Increased sway
var footStep = Math.sin(self.walkAnimationTimer * 2) * 0.08; // Foot stepping animation
// Enhanced scale animation for walking feet effect
enemyGraphics.scaleY = self.originalScale + walkBob + footStep;
enemyGraphics.scaleX = self.originalScale + walkBob * 0.4 + Math.abs(footStep) * 0.2;
// Enhanced rotation for body sway during movement
enemyGraphics.rotation = Math.sin(self.walkAnimationTimer * 1.4) * 0.06 + walkSway;
// Add slight vertical movement for walking bounce
enemyGraphics.y = Math.sin(self.walkAnimationTimer * 1.8) * 3;
// Additional movement for floating/spectral enemies
if (self.isSpecial && self.specialType === 'spectral') {
enemyGraphics.y = Math.sin(self.walkAnimationTimer * 0.6) * 20; // Increased floating range
enemyGraphics.rotation += Math.sin(self.walkAnimationTimer * 0.4) * 0.15; // More rotation
enemyGraphics.alpha = 0.7 + Math.sin(self.walkAnimationTimer * 0.8) * 0.1; // Ghostly flickering
}
if (self.pathIndex < pathPoints.length - 1) {
var targetPoint = pathPoints[self.pathIndex + 1];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
self.pathIndex++;
if (self.pathIndex >= pathPoints.length - 1) {
// Reached goal - play portal sound and animate enemy entering portal
LK.getSound('portal').play();
// Animate enemy being sucked into portal
tween(self, {
x: goal.x,
y: goal.y,
scaleX: 0.2,
scaleY: 0.2,
rotation: Math.PI * 4
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
// Final disappearing effect
tween(self, {
alpha: 0,
scaleX: 0.05,
scaleY: 0.05
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
lives--;
updateUI();
if (lives <= 0) {
showEnhancedGameOver();
}
// Animate lives icon when life is lost
tween(livesIcon, {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(livesIcon, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeOut
});
}
});
// Remove enemy from array and destroy
var enemyIndex = enemies.indexOf(self);
if (enemyIndex > -1) {
enemies.splice(enemyIndex, 1);
}
self.destroy();
}
});
}
});
return; // Exit update to prevent further movement
}
} else {
// Calculate current speed with effects
var currentSpeed = self.speed;
// Apply stun effect from trumpet (completely stops movement)
if (self.stunnedUntil && LK.ticks < self.stunnedUntil) {
currentSpeed = 0; // Can't move when stunned
}
// Apply Grand Piano stun effect (completely stops movement)
else if (self.grandPianoStunnedUntil && LK.ticks < self.grandPianoStunnedUntil) {
currentSpeed = 0; // Can't move when stunned by Grand Piano
}
// Apply freeze effect from violin (completely stops movement)
else if (self.frozenUntil && LK.ticks < self.frozenUntil) {
currentSpeed = 0; // Can't move when frozen
}
// Apply slow effect from violin
else if (self.slowedUntil && LK.ticks < self.slowedUntil) {
currentSpeed *= 0.5; // 50% speed when slowed
}
// Apply blind effect from DJ (further speed reduction)
if (self.blindedUntil && LK.ticks < self.blindedUntil) {
currentSpeed *= 0.3; // 30% speed when blinded
}
// Apply distortion effect from DJ Level III
if (self.distortedUntil && LK.ticks < self.distortedUntil) {
currentSpeed *= 1 - (self.distortionSlowPercent || 0.25); // Additional slow from distortion
// Apply DoT damage every 30 ticks (0.5 seconds)
if (LK.ticks % 30 === 0) {
self.takeDamage(self.distortionDamage || 5);
}
}
// Apply autotune debuff to nearby towers
if (self.enemyType === 'autotuneMalicioso') {
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx2 = tower.x - self.x;
var dy2 = tower.y - self.y;
var towerDist = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (towerDist <= self.debuffRadius) {
tower.debuffedUntil = LK.ticks + 1; // Continuous debuff while in range
}
}
}
// Special disruptor enemy abilities
if (self.isSpecial && self.specialType === 'disruptor') {
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx2 = tower.x - self.x;
var dy2 = tower.y - self.y;
var towerDist = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (towerDist <= self.disruptRadius) {
tower.disruptedUntil = LK.ticks + 1; // Continuous disruption
}
}
}
self.x += dx / distance * currentSpeed * gameSpeed;
self.y += dy / distance * currentSpeed * gameSpeed;
}
}
};
return self;
});
var Tower = Container.expand(function (towerType) {
var self = Container.call(this);
self.towerType = towerType || 'drum';
var assetName = self.towerType + 'Tower';
var towerGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Function to update tower graphics based on level
self.updateTowerGraphics = function () {
var newAssetName;
if (self.level === 1) {
newAssetName = self.towerType + 'Tower';
} else if (self.level === 2) {
if (self.towerType === 'drum') newAssetName = 'Tambor2';else if (self.towerType === 'trumpet') newAssetName = 'Trompeta2';else if (self.towerType === 'guitar') newAssetName = 'Guitarra2';else if (self.towerType === 'violin') newAssetName = 'Violin2';else if (self.towerType === 'dj') newAssetName = 'DJ2';
} else if (self.level === 3) {
if (self.towerType === 'drum') newAssetName = 'Tambor3';else if (self.towerType === 'trumpet') newAssetName = 'Trompeta3';else if (self.towerType === 'guitar') newAssetName = 'Guitarra3';else if (self.towerType === 'violin') newAssetName = 'Violin3';else if (self.towerType === 'dj') newAssetName = 'DJ3';
}
// Remove old graphics and add new ones
if (towerGraphics && towerGraphics.parent) {
self.removeChild(towerGraphics);
}
towerGraphics = self.attachAsset(newAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
};
// Tower level system
self.level = 1;
self.totalCost = 0; // Track total investment for removal refund
// Tower properties based on type
if (self.towerType === 'drum') {
self.damage = 15;
self.range = 165; // Short range
self.fireRate = 30; // Fast (0.5s at 60fps)
self.cost = 50;
self.areaRadius = 60; // Small area for drum
} else if (self.towerType === 'trumpet') {
self.damage = 35;
self.range = 180; // Medium range
self.fireRate = 60; // Medium (1s at 60fps)
self.cost = 80;
self.pushback = true;
} else if (self.towerType === 'guitar') {
self.damage = 20;
self.range = 220; // Long range
self.fireRate = 90; // Slow (1.5s at 60fps)
self.cost = 120;
self.chainCount = 3;
} else if (self.towerType === 'violin') {
self.damage = 10;
self.range = 220; // Long range
self.fireRate = 60; // Medium (1s at 60fps)
self.cost = 100;
self.slowChance = 0.25; // 25% chance
self.slowDuration = 120; // 2s at 60fps
} else if (self.towerType === 'dj') {
self.damage = 60; // Increased from 50
self.range = 220; // Increased from 200
self.fireRate = 120; // Improved from 150 (2.0s instead of 2.5s)
self.cost = 200;
self.areaRadius = 140; // Increased from 120
self.blindEffect = true;
}
// Store base stats for upgrade calculations
self.baseDamage = self.damage;
self.baseRange = self.range;
self.baseFireRate = self.fireRate;
self.baseAreaRadius = self.areaRadius || 0;
self.totalCost = self.cost; // Track total investment
self.lastFire = self.fireRate; // Initialize to fireRate to allow immediate first shot after properties are set
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = self.range;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < closestDistance) {
closestEnemy = enemy;
closestDistance = distance;
}
}
return closestEnemy;
};
self.fire = function (target) {
var specialData = {
areaRadius: self.areaRadius,
chainCount: self.chainCount,
slowChance: self.slowChance,
slowDuration: self.slowDuration
};
// Apply autotune debuff if present
var actualDamage = self.damage;
if (self.debuffedUntil && LK.ticks < self.debuffedUntil) {
actualDamage *= 0.8; // 20% damage reduction
}
var bullet = new Bullet(self.x, self.y, target, actualDamage, self.towerType, specialData);
bullets.push(bullet);
game.addChild(bullet);
// Play specific sound for different tower types
if (self.towerType === 'trumpet') {
var sound = LK.getSound('proyectilT');
sound.volume = soundEffectsVolume;
sound.play();
} else if (self.towerType === 'drum') {
try {
var sound = LK.getSound('ProyectilTA');
sound.volume = soundEffectsVolume;
sound.play();
} catch (error) {
console.log("Error playing drum sound, using fallback");
var sound = LK.getSound('hit');
sound.volume = soundEffectsVolume;
sound.play();
}
} else if (self.towerType === 'guitar') {
var sound = LK.getSound('ProyectilG');
sound.volume = soundEffectsVolume;
sound.play();
} else if (self.towerType === 'violin') {
var sound = LK.getSound('ProyectilV');
sound.volume = soundEffectsVolume;
sound.play();
} else if (self.towerType === 'dj') {
var sound = LK.getSound('ProyectilDJ');
sound.volume = soundEffectsVolume;
sound.play();
} else {
var sound = LK.getSound('hit');
sound.volume = soundEffectsVolume;
sound.play();
}
};
// Tower upgrade method with specific stats per tower type and level
self.upgrade = function () {
if (self.level >= 3) return false; // Max level reached
// Calculate upgrade cost based on tower type and level
var upgradeCost = 0;
if (self.towerType === 'drum') {
upgradeCost = self.level === 1 ? 90 : 160;
} else if (self.towerType === 'trumpet') {
upgradeCost = self.level === 1 ? 130 : 200;
} else if (self.towerType === 'guitar') {
upgradeCost = self.level === 1 ? 180 : 250;
} else if (self.towerType === 'violin') {
upgradeCost = self.level === 1 ? 160 : 220;
} else if (self.towerType === 'dj') {
upgradeCost = self.level === 1 ? 280 : 350;
}
if (gold < upgradeCost) return false; // Not enough gold
gold -= upgradeCost;
// Track gold spent on upgrades
totalGoldSpent += upgradeCost;
self.totalCost += upgradeCost;
self.level++;
// Apply specific stats based on tower type and level
if (self.towerType === 'drum') {
if (self.level === 2) {
self.damage = 20;
self.fireRate = 30; // Same as level 1 (0.5s)
self.areaRadius = Math.floor(self.baseAreaRadius * 1.25); // 25% larger area
} else if (self.level === 3) {
self.damage = 25;
self.fireRate = 24; // 0.4s at 60fps
self.areaRadius = Math.floor(self.baseAreaRadius * 1.25);
self.slowEffect = true; // Add 10% slow for 1.5s
self.slowChance = 0.1;
self.slowDuration = 90; // 1.5s at 60fps
}
} else if (self.towerType === 'trumpet') {
if (self.level === 2) {
self.damage = 45;
self.fireRate = 54; // 0.9s at 60fps
self.strongerPushback = true;
} else if (self.level === 3) {
self.damage = 60;
self.fireRate = 48; // 0.8s at 60fps
self.stunChance = 0.1; // 10% stun chance
self.stunDuration = 60; // 1s at 60fps
}
} else if (self.towerType === 'guitar') {
if (self.level === 2) {
self.damage = 25; // Per hit
self.fireRate = 78; // 1.3s at 60fps
self.chainCount = 4;
} else if (self.level === 3) {
self.damage = 30; // Per hit
self.fireRate = 72; // 1.2s at 60fps
self.chainCount = 4;
self.extendedChain = true; // Enhanced chaining
}
} else if (self.towerType === 'violin') {
if (self.level === 2) {
self.damage = 14;
self.fireRate = 60; // Same as level 1 (1.0s)
self.slowChance = 0.35; // 35% slow
self.slowDuration = 180; // 3s at 60fps
} else if (self.level === 3) {
self.damage = 18;
self.fireRate = 54; // 0.9s at 60fps
self.slowChance = 0.40; // 40% slow
self.slowDuration = 180;
self.freezeChance = 0.1; // 10% freeze chance
self.freezeDuration = 90; // 1.5s at 60fps
}
} else if (self.towerType === 'dj') {
if (self.level === 2) {
self.damage = 75; // Increased from 65
self.fireRate = 108; // Improved from 138 (1.8s instead of 2.3s)
self.areaRadius = Math.floor(self.baseAreaRadius * 1.3); // Increased from 1.2
self.blindDuration = 90; // Increased from 60 (1.5s blind)
} else if (self.level === 3) {
self.damage = 95; // Increased from 80
self.fireRate = 96; // Improved from 120 (1.6s instead of 2.0s)
self.distortionEffect = true; // Add distortion (25% slow + DoT)
self.distortionSlowPercent = 0.3; // Increased from 0.25
self.distortionDamage = 7; // Increased from 5
self.distortionDuration = 210; // Increased from 180 (3.5s duration)
}
}
// Update tower graphics to show new level
self.updateTowerGraphics();
// Light animation effect without changing tower color
var lightEffect = self.addChild(LK.getAsset('spotlight', {
anchorX: 0.5,
anchorY: 0.5
}));
lightEffect.x = 0;
lightEffect.y = 0;
lightEffect.scaleX = 2;
lightEffect.scaleY = 2;
lightEffect.alpha = 0.8;
lightEffect.tint = 0xFFD700; // Gold light effect
tween(lightEffect, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
lightEffect.destroy();
}
});
updateUI();
return true;
};
// Tower click handler for upgrade menu
self.down = function (x, y, obj) {
if (!showingBuildMenu && gameState === 'playing') {
// If this tower is already selected and upgrade menu is showing, close it
if (showingUpgradeMenu && selectedTower === self) {
hideUpgradeMenu();
} else {
// Otherwise show upgrade menu for this tower
showUpgradeMenu(self);
}
}
};
self.update = function () {
// Stop all tower firing if game over is triggered
if (gameOverTriggered) return;
self.lastFire += gameSpeed; // Increase fire timer by current game speed
// Check if tower is disrupted
if (self.disruptedUntil && LK.ticks < self.disruptedUntil) {
return; // Skip firing if disrupted
}
if (self.lastFire >= self.fireRate) {
var target = self.findTarget();
if (target && target.health > 0) {
self.fire(target);
self.lastFire = 0;
// Add visual firing effect
tween(towerGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(towerGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100
});
}
});
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x8B4513,
width: 2048,
height: 2732,
autoResize: true,
scaleMode: 'fitScreen',
preserveAspectRatio: false,
fullScreen: true,
antialias: true
});
/****
* Game Code
****/
// Mobile viewport configuration
if (typeof window !== 'undefined' && window.innerWidth && window.innerHeight) {
// Set viewport meta tag for mobile devices
var viewport = document.querySelector('meta[name=viewport]');
if (!viewport) {
viewport = document.createElement('meta');
viewport.name = 'viewport';
document.head.appendChild(viewport);
}
viewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover';
// Prevent zoom on mobile
document.addEventListener('touchmove', function (e) {
if (e.scale && e.scale !== 1) {
e.preventDefault();
}
}, {
passive: false
});
// Handle orientation changes
window.addEventListener('orientationchange', function () {
setTimeout(function () {
if (game && game.renderer) {
game.renderer.resize(window.innerWidth, window.innerHeight);
}
}, 100);
});
// Force full screen on mobile browsers
if (game && game.view && game.view.style) {
game.view.style.position = 'fixed';
game.view.style.top = '0';
game.view.style.left = '0';
game.view.style.width = '100%';
game.view.style.height = '100%';
game.view.style.zIndex = '1000';
}
}
// Game variables
// Enhanced mobile scaling configuration with fitScreen mode and full-screen support
// LK Game engine handles mobile display with improved viewport and scaling settings
var gameState = 'menu'; // 'menu', 'tutorial', 'playing', 'settings'
var gameOverTriggered = false; // Flag to prevent race conditions during game over
var tutorialStep = 0;
var menuContainer;
var tutorialContainer;
// Mobile CSS injection for full-screen support
if (typeof document !== 'undefined') {
var style = document.createElement('style');
style.textContent = "\n\t\thtml, body {\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\toverflow: hidden;\n\t\t\tposition: fixed;\n\t\t\t-webkit-user-select: none;\n\t\t\t-moz-user-select: none;\n\t\t\t-ms-user-select: none;\n\t\t\tuser-select: none;\n\t\t\t-webkit-touch-callout: none;\n\t\t\t-webkit-tap-highlight-color: transparent;\n\t\t}\n\t\t\n\t\tcanvas {\n\t\t\tdisplay: block;\n\t\t\tposition: fixed !important;\n\t\t\ttop: 0 !important;\n\t\t\tleft: 0 !important;\n\t\t\twidth: 100% !important;\n\t\t\theight: 100% !important;\n\t\t\tz-index: 1000;\n\t\t\ttouch-action: manipulation;\n\t\t}\n\t\t\n\t\t/* Ensure no scrollbars or overflows */\n\t\t* {\n\t\t\tbox-sizing: border-box;\n\t\t\t-webkit-tap-highlight-color: transparent;\n\t\t}\n\t\t\n\t\t/* Handle safe areas on newer mobile devices */\n\t\t@supports (padding: max(0px)) {\n\t\t\tcanvas {\n\t\t\t\tpadding-top: max(0px, env(safe-area-inset-top));\n\t\t\t\tpadding-bottom: max(0px, env(safe-area-inset-bottom));\n\t\t\t\tpadding-left: max(0px, env(safe-area-inset-left));\n\t\t\t\tpadding-right: max(0px, env(safe-area-inset-right));\n\t\t\t}\n\t\t}\n\t";
document.head.appendChild(style);
}
// Settings system variables
var showingSettings = false;
var settingsContainer;
var gameVolume = storage.gameVolume || 1.0;
var musicVolume = storage.musicVolume || 0.3;
var soundEffectsVolume = storage.soundEffectsVolume || 0.5;
var autoStartWaves = false; // Always start as OFF
var showDamageNumbers = false; // Always start as OFF
var gameLanguage = 'english'; // Always default to English
// Language dropdown system
var showingLanguageDropdown = false;
var languageDropdownContainer;
var availableLanguages = [{
code: 'english',
name: 'English',
nativeName: 'English'
}, {
code: 'spanish',
name: 'Spanish',
nativeName: 'Español'
}, {
code: 'french',
name: 'French',
nativeName: 'Français'
}, {
code: 'german',
name: 'German',
nativeName: 'Deutsch'
}, {
code: 'italian',
name: 'Italian',
nativeName: 'Italiano'
}, {
code: 'portuguese',
name: 'Portuguese',
nativeName: 'Português'
}, {
code: 'russian',
name: 'Russian',
nativeName: 'Русский'
}];
// Function to get localized settings text
function getSettingsText() {
var settingsData = {
english: {
title: '♪ SETTINGS ♪',
musicVolume: 'Music Volume',
soundEffectsVolume: 'Sound Effects Volume',
autoStartWaves: 'Auto-Start Waves',
showDamageNumbers: 'Show Damage Numbers',
language: 'Language',
resetDefaults: 'Reset to Defaults',
backToMenu: 'Back to Menu',
on: 'ON',
off: 'OFF'
},
spanish: {
title: '♪ CONFIGURACIÓN ♪',
musicVolume: 'Volumen de Música',
soundEffectsVolume: 'Volumen de Efectos',
autoStartWaves: 'Auto-Iniciar Oleadas',
showDamageNumbers: 'Mostrar Números de Daño',
language: 'Idioma',
resetDefaults: 'Restablecer Predeterminados',
backToMenu: 'Volver al Menú',
on: 'SÍ',
off: 'NO'
},
french: {
title: '♪ PARAMÈTRES ♪',
musicVolume: 'Volume de Musique',
soundEffectsVolume: 'Volume des Effets',
autoStartWaves: 'Démarrage Auto des Vagues',
showDamageNumbers: 'Afficher Nombres de Dégâts',
language: 'Langue',
resetDefaults: 'Réinitialiser par Défaut',
backToMenu: 'Retour au Menu',
on: 'OUI',
off: 'NON'
},
german: {
title: '♪ EINSTELLUNGEN ♪',
musicVolume: 'Musiklautstärke',
soundEffectsVolume: 'Effektlautstärke',
autoStartWaves: 'Wellen Auto-Start',
showDamageNumbers: 'Schadenszahlen Anzeigen',
language: 'Sprache',
resetDefaults: 'Zurücksetzen',
backToMenu: 'Zurück zum Menü',
on: 'AN',
off: 'AUS'
},
italian: {
title: '♪ IMPOSTAZIONI ♪',
musicVolume: 'Volume Musica',
soundEffectsVolume: 'Volume Effetti',
autoStartWaves: 'Avvio Auto Ondate',
showDamageNumbers: 'Mostra Numeri Danno',
language: 'Lingua',
resetDefaults: 'Ripristina Predefiniti',
backToMenu: 'Torna al Menu',
on: 'SÌ',
off: 'NO'
},
portuguese: {
title: '♪ CONFIGURAÇÕES ♪',
musicVolume: 'Volume da Música',
soundEffectsVolume: 'Volume dos Efeitos',
autoStartWaves: 'Auto-Iniciar Ondas',
showDamageNumbers: 'Mostrar Números de Dano',
language: 'Idioma',
resetDefaults: 'Restaurar Padrões',
backToMenu: 'Voltar ao Menu',
on: 'SIM',
off: 'NÃO'
},
russian: {
title: '♪ НАСТРОЙКИ ♪',
musicVolume: 'Громкость Музыки',
soundEffectsVolume: 'Громкость Эффектов',
autoStartWaves: 'Авто-Запуск Волн',
showDamageNumbers: 'Показать Урон',
language: 'Язык',
resetDefaults: 'Сбросить Настройки',
backToMenu: 'Назад в Меню',
on: 'ВКЛ',
off: 'ВЫКЛ'
}
};
return settingsData[gameLanguage] || settingsData.english;
}
// Game statistics tracking
var totalEnemiesKilled = 0;
var totalGoldSpent = 0;
// Difficulty system variables
var goldPerEnemy = 5; // Base gold per enemy
var maxGoldPerWave = 60; // Base maximum gold per wave
var goldAbuseCooldown = 0; // Timer for gold abuse punishment
var highGoldWaveCount = 0; // Count of waves with excess gold
var lastWaveGoldEarned = 0; // Track gold earned in current wave
var difficultyModifier = 1.0; // Applied to enemy stats for punishment
var enemySpeedModifier = 1.0; // Speed increase from wave 10+
var forceExpenseEvent = false; // Force expense event flag
var nextForceExpenseWave = 5; // Next wave to force expense
var currentSpecialWave = null; // Track current special wave type
// Simple music system - just track if Melodia is playing
var gameplayMusicPlaying = false; // Flag to track if music is currently playing
// Game Elements
var enemies = [];
var towers = [];
var bullets = [];
var buildSpots = [];
var gold = 150;
var lives = 40;
var wave = 1;
var enemiesSpawned = 0;
var enemiesPerWave = 10;
var spawnTimer = 0;
var waveStarted = false;
var wavePaused = true; // New: waves start paused between waves
var autoStartTimer = 0; // New: timer for auto-starting waves
var showingBuildMenu = false;
var selectedBuildSpot = null;
var backgroundElements = [];
var floatingNotes = [];
// Tower upgrade system variables
var showingUpgradeMenu = false;
var selectedTower = null;
var upgradeMenuContainer;
// Credits screen variables
var creditsContainer;
var showingCredits = false;
// Settings screen setup
settingsContainer = new Container();
settingsContainer.visible = false;
game.addChild(settingsContainer);
var settingsBackground = settingsContainer.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
settingsBackground.x = 1024;
settingsBackground.y = 1366;
settingsBackground.scaleX = 4.5;
settingsBackground.scaleY = 5.5;
settingsBackground.alpha = 0.95;
settingsBackground.tint = 0x654321;
var settingsTitle = settingsContainer.addChild(new Text2('♪ SETTINGS ♪', {
size: 100,
fill: 0xFFD700,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
settingsTitle.anchor.set(0.5, 0.5);
settingsTitle.x = 1024;
settingsTitle.y = 300;
// Music Volume Control
var musicVolumeLabel = settingsContainer.addChild(new Text2('Music Volume', {
size: 60,
fill: 0xF5F5DC,
font: "Georgia, serif"
}));
musicVolumeLabel.anchor.set(0.5, 0.5);
musicVolumeLabel.x = 1024;
musicVolumeLabel.y = 450;
var musicVolumeValue = settingsContainer.addChild(new Text2(Math.round(musicVolume * 100) + '%', {
size: 50,
fill: 0x00FFFF,
font: "Arial, sans-serif"
}));
musicVolumeValue.anchor.set(0.5, 0.5);
musicVolumeValue.x = 1024;
musicVolumeValue.y = 500;
var musicVolumeDownButton = settingsContainer.addChild(LK.getAsset('menos', {
anchorX: 0.5,
anchorY: 0.5
}));
musicVolumeDownButton.x = 675;
musicVolumeDownButton.y = 450;
musicVolumeDownButton.scaleX = 1.0;
musicVolumeDownButton.scaleY = 1.0;
var musicVolumeUpButton = settingsContainer.addChild(LK.getAsset('mas', {
anchorX: 0.5,
anchorY: 0.5
}));
musicVolumeUpButton.x = 1373;
musicVolumeUpButton.y = 450;
musicVolumeUpButton.scaleX = 1.0;
musicVolumeUpButton.scaleY = 1.0;
// Sound Effects Volume Control
var soundVolumeLabel = settingsContainer.addChild(new Text2('Sound Effects Volume', {
size: 60,
fill: 0xF5F5DC,
font: "Georgia, serif"
}));
soundVolumeLabel.anchor.set(0.5, 0.5);
soundVolumeLabel.x = 1024;
soundVolumeLabel.y = 600;
var soundVolumeValue = settingsContainer.addChild(new Text2(Math.round(soundEffectsVolume * 100) + '%', {
size: 50,
fill: 0x00FFFF,
font: "Arial, sans-serif"
}));
soundVolumeValue.anchor.set(0.5, 0.5);
soundVolumeValue.x = 1024;
soundVolumeValue.y = 650;
var soundVolumeDownButton = settingsContainer.addChild(LK.getAsset('menos', {
anchorX: 0.5,
anchorY: 0.5
}));
soundVolumeDownButton.x = 620;
soundVolumeDownButton.y = 600;
soundVolumeDownButton.scaleX = 1.0;
soundVolumeDownButton.scaleY = 1.0;
var soundVolumeUpButton = settingsContainer.addChild(LK.getAsset('mas', {
anchorX: 0.5,
anchorY: 0.5
}));
soundVolumeUpButton.x = 1428;
soundVolumeUpButton.y = 600;
soundVolumeUpButton.scaleX = 1.0;
soundVolumeUpButton.scaleY = 1.0;
// Auto-Start Waves Toggle
var autoStartLabel = settingsContainer.addChild(new Text2('Auto-Start Waves', {
size: 60,
fill: 0xF5F5DC,
font: "Georgia, serif"
}));
autoStartLabel.anchor.set(0.5, 0.5);
autoStartLabel.x = 1024;
autoStartLabel.y = 750;
var autoStartToggle = settingsContainer.addChild(LK.getAsset('BotonW', {
anchorX: 0.5,
anchorY: 0.5
}));
autoStartToggle.x = 1024;
autoStartToggle.y = 800;
autoStartToggle.scaleX = 1.0;
autoStartToggle.scaleY = 1.0;
var autoStartValue = settingsContainer.addChild(new Text2(autoStartWaves ? 'ON' : 'OFF', {
size: 50,
fill: autoStartWaves ? 0x00FFFF : 0xFF0000,
// Cyan for ON, Red for OFF
font: "Arial, sans-serif"
}));
autoStartValue.anchor.set(0.5, 0.5);
autoStartValue.x = 1024;
autoStartValue.y = 850;
// Show Damage Numbers Toggle
var damageNumbersLabel = settingsContainer.addChild(new Text2('Show Damage Numbers', {
size: 60,
fill: 0xF5F5DC,
font: "Georgia, serif"
}));
damageNumbersLabel.anchor.set(0.5, 0.5);
damageNumbersLabel.x = 1024;
damageNumbersLabel.y = 950;
var damageNumbersToggle = settingsContainer.addChild(LK.getAsset('SDN', {
anchorX: 0.5,
anchorY: 0.5
}));
damageNumbersToggle.x = 1024;
damageNumbersToggle.y = 1000;
damageNumbersToggle.scaleX = 1.0;
damageNumbersToggle.scaleY = 1.0;
var damageNumbersValue = settingsContainer.addChild(new Text2(showDamageNumbers ? 'ON' : 'OFF', {
size: 50,
fill: showDamageNumbers ? 0x00FFFF : 0xFF0000,
// Cyan for ON, Red for OFF
font: "Arial, sans-serif"
}));
damageNumbersValue.anchor.set(0.5, 0.5);
damageNumbersValue.x = 1024;
damageNumbersValue.y = 1050;
// Reset to Defaults Button
var resetDefaultsButton = settingsContainer.addChild(LK.getAsset('Setting', {
anchorX: 0.5,
anchorY: 0.5
}));
resetDefaultsButton.x = 700;
resetDefaultsButton.y = 1400;
resetDefaultsButton.scaleX = 1.0;
resetDefaultsButton.scaleY = 1.0;
var resetDefaultsText = settingsContainer.addChild(new Text2('Reset to Defaults', {
size: 45,
fill: 0xFF8800,
font: "Verdana, sans-serif"
}));
resetDefaultsText.anchor.set(0.5, 0.5);
resetDefaultsText.x = 700;
resetDefaultsText.y = 1450;
// Back to Menu Button
var settingsBackButton = settingsContainer.addChild(LK.getAsset('BackMenu', {
anchorX: 0.5,
anchorY: 0.5
}));
settingsBackButton.x = 1348;
settingsBackButton.y = 1400;
settingsBackButton.scaleX = 1.0;
settingsBackButton.scaleY = 1.0;
var settingsBackText = settingsContainer.addChild(new Text2('Back to Menu', {
size: 45,
fill: 0xFFD700,
font: "Verdana, sans-serif"
}));
settingsBackText.anchor.set(0.5, 0.5);
settingsBackText.x = 1348;
settingsBackText.y = 1450;
// Language Selection Control
var languageLabel = settingsContainer.addChild(new Text2('Language', {
size: 60,
fill: 0xF5F5DC,
font: "Georgia, serif"
}));
languageLabel.anchor.set(0.5, 0.5);
languageLabel.x = 1024;
languageLabel.y = 1150;
var languageToggle = settingsContainer.addChild(LK.getAsset('Idioma', {
anchorX: 0.5,
anchorY: 0.5
}));
languageToggle.x = 1024;
languageToggle.y = 1200;
languageToggle.scaleX = 1.0;
languageToggle.scaleY = 1.0;
var languageValue = settingsContainer.addChild(new Text2('English', {
size: 50,
fill: 0x00FFFF,
font: "Arial, sans-serif"
}));
languageValue.anchor.set(0.5, 0.5);
languageValue.x = 1024;
languageValue.y = 1250;
// Grand Piano Power System
var grandPianoPowerUnlocked = false;
var grandPianoCooldownRemaining = 0;
var grandPianoPowerButton;
var grandPianoPowerText;
var grandPianoCooldownText;
var grandPianoButtonBackground;
var grandPianoPowerActive = false;
var grandPianoGhostMode = false;
var grandPianoGhost = null;
var grandPianoTargetArea = null;
var grandPianoUnlockAnimationShown = false;
// === GIANT WOODEN TABLE BACKGROUND ===
// Create a large wooden table that covers the entire game area as the base
var giantTable = game.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
giantTable.x = 1024;
giantTable.y = 1366;
giantTable.scaleX = 5.0; // Scale to cover full width
giantTable.scaleY = 6.0; // Scale to cover full height
giantTable.tint = 0x8B4513; // Rich wooden brown color
giantTable.alpha = 0.3; // Semi-transparent so other elements are visible
backgroundElements.push(giantTable);
// === STAGE AREA (Top section of concert hall) ===
// Main stage backdrop
var stage = game.addChild(LK.getAsset('stage', {
anchorX: 0.5,
anchorY: 1
}));
stage.x = 1024;
stage.y = 350;
stage.scaleX = 0.9;
stage.scaleY = 0.8;
backgroundElements.push(stage);
// Stage curtain backdrop
var stageBackdrop = game.addChild(LK.getAsset('curtain', {
anchorX: 0.5,
anchorY: 0
}));
stageBackdrop.x = 1024;
stageBackdrop.y = 30;
stageBackdrop.scaleX = 1.2;
stageBackdrop.scaleY = 0.7;
backgroundElements.push(stageBackdrop);
// Orchestra pit and performance area
var orchestraPit = game.addChild(LK.getAsset('orchestraPit', {
anchorX: 0.5,
anchorY: 1
}));
orchestraPit.x = 1024;
orchestraPit.y = 550;
orchestraPit.scaleX = 0.8;
orchestraPit.scaleY = 0.6;
backgroundElements.push(orchestraPit);
// Grand piano in orchestra pit
var grandPiano = game.addChild(LK.getAsset('grandPiano', {
anchorX: 0.5,
anchorY: 0.5
}));
grandPiano.x = 900;
grandPiano.y = 400;
grandPiano.scaleX = 0.8;
grandPiano.scaleY = 0.8;
backgroundElements.push(grandPiano);
// Conductor podium
var conductor = game.addChild(LK.getAsset('conductor', {
anchorX: 0.5,
anchorY: 1
}));
conductor.x = 1024;
conductor.y = 420;
conductor.scaleX = 0.8;
conductor.scaleY = 0.8;
backgroundElements.push(conductor);
// Music stands arranged in orchestra pit
for (var i = 0; i < 6; i++) {
var musicStand = game.addChild(LK.getAsset('musicStand', {
anchorX: 0.5,
anchorY: 1
}));
musicStand.x = 750 + i * 90;
musicStand.y = 440;
musicStand.scaleX = 0.6;
musicStand.scaleY = 0.8;
backgroundElements.push(musicStand);
}
// === UPPER BALCONIES AND VIP AREAS ===
// Main balconies positioned logically
for (var i = 0; i < 2; i++) {
var balcony = game.addChild(LK.getAsset('balcony', {
anchorX: 0.5,
anchorY: 0.5
}));
balcony.x = 400 + i * 1200;
balcony.y = 600;
balcony.scaleX = 0.7;
balcony.scaleY = 0.6;
backgroundElements.push(balcony);
// Royal crests for main balconies
var crest = game.addChild(LK.getAsset('royalCrest', {
anchorX: 0.5,
anchorY: 0.5
}));
crest.x = balcony.x;
crest.y = balcony.y - 60;
crest.scaleX = 0.5;
crest.scaleY = 0.5;
backgroundElements.push(crest);
}
// VIP boxes on middle level
for (var i = 0; i < 3; i++) {
var vipBox = game.addChild(LK.getAsset('vipBox', {
anchorX: 0.5,
anchorY: 0.5
}));
vipBox.x = 300 + i * 700;
vipBox.y = 800;
vipBox.scaleX = 0.6;
vipBox.scaleY = 0.5;
backgroundElements.push(vipBox);
// VIP box seating removed
}
// === LIGHTING AND ATMOSPHERE ===
// Chandeliers positioned strategically
for (var i = 0; i < 4; i++) {
var chandelier = game.addChild(LK.getAsset('chandelier', {
anchorX: 0.5,
anchorY: 0.5
}));
chandelier.x = 400 + i * 400;
chandelier.y = 120;
chandelier.scaleX = 0.8;
chandelier.scaleY = 0.8;
backgroundElements.push(chandelier);
// Chandelier swaying animation
tween(chandelier, {
rotation: 0.08
}, {
duration: 2500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(chandelier, {
rotation: -0.08
}, {
duration: 2500,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
}
// === STRUCTURAL ELEMENTS ===
// Supporting pillars
var leftPillar = game.addChild(LK.getAsset('pillar', {
anchorX: 0.5,
anchorY: 1
}));
leftPillar.x = 200;
leftPillar.y = 1200;
leftPillar.scaleX = 0.6;
leftPillar.scaleY = 1.0;
backgroundElements.push(leftPillar);
var rightPillar = game.addChild(LK.getAsset('pillar', {
anchorX: 0.5,
anchorY: 1
}));
rightPillar.x = 1848;
rightPillar.y = 1200;
rightPillar.scaleX = 0.6;
rightPillar.scaleY = 1.0;
backgroundElements.push(rightPillar);
// Decorative archways section removed
// === AUDIENCE SEATING AREAS ===
// Classical concert hall seating arrangement
// Orchestra Level - Main Floor Seating
// Left orchestra section with center aisle
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 6; col++) {
var seat = game.addChild(LK.getAsset('seat', {
anchorX: 0.5,
anchorY: 0.5
}));
seat.x = 300 + col * 65;
seat.y = 1800 + row * 70;
seat.scaleX = 0.8;
seat.scaleY = 0.8;
seat.rotation = 0.05; // Slight angle toward stage
backgroundElements.push(seat);
}
}
// Right orchestra section with center aisle
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 6; col++) {
var seat = game.addChild(LK.getAsset('seat', {
anchorX: 0.5,
anchorY: 0.5
}));
seat.x = 1400 + col * 65;
seat.y = 1800 + row * 70;
seat.scaleX = 0.8;
seat.scaleY = 0.8;
seat.rotation = -0.05; // Slight angle toward stage
backgroundElements.push(seat);
}
}
// Center orchestra section - split for main aisle
for (var row = 0; row < 6; row++) {
// Left side of center section
for (var col = 0; col < 4; col++) {
var seat = game.addChild(LK.getAsset('seat', {
anchorX: 0.5,
anchorY: 0.5
}));
seat.x = 750 + col * 65;
seat.y = 1850 + row * 70;
seat.scaleX = 0.9;
seat.scaleY = 0.9;
backgroundElements.push(seat);
}
// Right side of center section
for (var col = 0; col < 4; col++) {
var seat = game.addChild(LK.getAsset('seat', {
anchorX: 0.5,
anchorY: 0.5
}));
seat.x = 1100 + col * 65;
seat.y = 1850 + row * 70;
seat.scaleX = 0.9;
seat.scaleY = 0.9;
backgroundElements.push(seat);
}
}
// Premium front rows - closer to orchestra pit
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 12; col++) {
var seat = game.addChild(LK.getAsset('seat', {
anchorX: 0.5,
anchorY: 0.5
}));
seat.x = 650 + col * 75;
seat.y = 1650 + row * 70;
seat.scaleX = 1.0;
seat.scaleY = 1.0;
seat.tint = 0xB8860B; // Gold tint for premium seats
backgroundElements.push(seat);
}
}
// Mezzanine Level - First Balcony
// Left and right mezzanine sections removed
// Center mezzanine - elevated premium seating
for (var row = 0; row < 4; row++) {
for (var col = 0; col < 10; col++) {
var seat = game.addChild(LK.getAsset('seat', {
anchorX: 0.5,
anchorY: 0.5
}));
seat.x = 750 + col * 70;
seat.y = 1100 + row * 65;
seat.scaleX = 0.8;
seat.scaleY = 0.8;
seat.tint = 0x8B0000; // Deep red for premium balcony seats
backgroundElements.push(seat);
}
}
// Upper Gallery - Third Level
// Compact seating in upper gallery
for (var row = 0; row < 6; row++) {
for (var col = 0; col < 16; col++) {
var seat = game.addChild(LK.getAsset('seat', {
anchorX: 0.5,
anchorY: 0.5
}));
seat.x = 500 + col * 65;
seat.y = 800 + row * 60;
seat.scaleX = 0.6;
seat.scaleY = 0.6;
// Create perspective effect - chairs get smaller toward back
var perspectiveScale = 1.0 - row * 0.05;
seat.scaleX *= perspectiveScale;
seat.scaleY *= perspectiveScale;
backgroundElements.push(seat);
}
}
// Side Box Seats - Elevated positions along walls
// Rear Orchestra - Additional seating toward entrance
for (var row = 0; row < 5; row++) {
for (var col = 0; col < 14; col++) {
var seat = game.addChild(LK.getAsset('seat', {
anchorX: 0.5,
anchorY: 0.5
}));
seat.x = 600 + col * 70;
seat.y = 2100 + row * 70;
seat.scaleX = 0.8;
seat.scaleY = 0.8;
// Create slight curve effect for better sightlines
var curveOffset = Math.sin(col / 13 * Math.PI) * 20;
seat.y += curveOffset;
backgroundElements.push(seat);
}
}
// Standing room areas - simulated with smaller seats for atmosphere
// Upper standing gallery
for (var i = 0; i < 20; i++) {
var standingSpot = game.addChild(LK.getAsset('seat', {
anchorX: 0.5,
anchorY: 0.5
}));
standingSpot.x = 400 + i * 60;
standingSpot.y = 650;
standingSpot.scaleX = 0.3;
standingSpot.scaleY = 0.3;
standingSpot.alpha = 0.7;
standingSpot.tint = 0x696969; // Gray for standing areas
backgroundElements.push(standingSpot);
}
// === DECORATIVE LIGHTING ===
// Elegant candelabras positioned throughout
for (var i = 0; i < 6; i++) {
var candelabra = game.addChild(LK.getAsset('candelabra', {
anchorX: 0.5,
anchorY: 1
}));
candelabra.x = 400 + i * 200;
candelabra.y = 1600;
candelabra.scaleX = 0.9;
candelabra.scaleY = 1.1;
backgroundElements.push(candelabra);
// Flickering flame effect
tween(candelabra, {
scaleY: 1.15,
alpha: 0.85
}, {
duration: 1000 + Math.random() * 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(candelabra, {
scaleY: 1.1,
alpha: 1.0
}, {
duration: 1000 + Math.random() * 500,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
}
// Wall-mounted lighting
for (var i = 0; i < 4; i++) {
var wallCandelabra = game.addChild(LK.getAsset('candelabra', {
anchorX: 0.5,
anchorY: 0.5
}));
wallCandelabra.x = 150;
wallCandelabra.y = 1000 + i * 300;
wallCandelabra.scaleX = 0.7;
wallCandelabra.scaleY = 0.8;
backgroundElements.push(wallCandelabra);
var rightWallCandelabra = game.addChild(LK.getAsset('candelabra', {
anchorX: 0.5,
anchorY: 0.5
}));
rightWallCandelabra.x = 1898;
rightWallCandelabra.y = 1000 + i * 300;
rightWallCandelabra.scaleX = 0.7;
rightWallCandelabra.scaleY = 0.8;
backgroundElements.push(rightWallCandelabra);
// Elevated box seats along left wall
var leftBoxSeat = game.addChild(LK.getAsset('vipBox', {
anchorX: 1,
anchorY: 0.5
}));
leftBoxSeat.x = 300;
leftBoxSeat.y = 1000 + i * 300;
leftBoxSeat.scaleX = 0.4;
leftBoxSeat.scaleY = 0.3;
leftBoxSeat.rotation = 0.1;
backgroundElements.push(leftBoxSeat);
// Premium seats inside left box removed
// Elevated box seats along right wall
var rightBoxSeat = game.addChild(LK.getAsset('vipBox', {
anchorX: 0,
anchorY: 0.5
}));
rightBoxSeat.x = 1748;
rightBoxSeat.y = 1000 + i * 300;
rightBoxSeat.scaleX = 0.4;
rightBoxSeat.scaleY = 0.3;
rightBoxSeat.rotation = -0.1;
backgroundElements.push(rightBoxSeat);
// Premium seats inside right box removed
// Synchronized flickering for wall candelabras
tween(wallCandelabra, {
rotation: 0.03,
alpha: 0.9
}, {
duration: 1500 + Math.random() * 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wallCandelabra, {
rotation: -0.03,
alpha: 1.0
}, {
duration: 1500 + Math.random() * 700,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
tween(rightWallCandelabra, {
rotation: -0.03,
alpha: 0.9
}, {
duration: 1500 + Math.random() * 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(rightWallCandelabra, {
rotation: 0.03,
alpha: 1.0
}, {
duration: 1500 + Math.random() * 700,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
}
// === ENTRANCE AREA ===
// Grand entrance architecture
var entranceArch = game.addChild(LK.getAsset('archDetail', {
anchorX: 0.5,
anchorY: 1
}));
entranceArch.x = 1750;
entranceArch.y = 2732;
entranceArch.scaleX = 2.5;
entranceArch.scaleY = 1.8;
backgroundElements.push(entranceArch);
// Entrance pillars
var leftEntrancePillar = game.addChild(LK.getAsset('pillar', {
anchorX: 0.5,
anchorY: 1
}));
leftEntrancePillar.x = 1600;
leftEntrancePillar.y = 2732;
leftEntrancePillar.scaleX = 0.7;
leftEntrancePillar.scaleY = 1.0;
backgroundElements.push(leftEntrancePillar);
var rightEntrancePillar = game.addChild(LK.getAsset('pillar', {
anchorX: 0.5,
anchorY: 1
}));
rightEntrancePillar.x = 1900;
rightEntrancePillar.y = 2732;
rightEntrancePillar.scaleX = 0.7;
rightEntrancePillar.scaleY = 1.0;
backgroundElements.push(rightEntrancePillar);
// Entrance lighting
for (var i = 0; i < 3; i++) {
var entranceCandelabra = game.addChild(LK.getAsset('candelabra', {
anchorX: 0.5,
anchorY: 1
}));
entranceCandelabra.x = 1600 + i * 150;
entranceCandelabra.y = 2550;
entranceCandelabra.scaleX = 1.0;
entranceCandelabra.scaleY = 1.3;
backgroundElements.push(entranceCandelabra);
// Flickering effect for entrance lighting
tween(entranceCandelabra, {
scaleY: 1.35,
alpha: 0.95
}, {
duration: 1200 + Math.random() * 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(entranceCandelabra, {
scaleY: 1.3,
alpha: 1.0
}, {
duration: 1200 + Math.random() * 600,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
}
// === CURTAINS AND DRAPING ===
// Side curtains for proper hall framing
var leftCurtain = game.addChild(LK.getAsset('curtain', {
anchorX: 0,
anchorY: 0
}));
leftCurtain.x = 0;
leftCurtain.y = 0;
leftCurtain.scaleX = 0.5;
leftCurtain.scaleY = 1.2;
backgroundElements.push(leftCurtain);
var rightCurtain = game.addChild(LK.getAsset('curtain', {
anchorX: 1,
anchorY: 0
}));
rightCurtain.x = 2048;
rightCurtain.y = 0;
rightCurtain.scaleX = 0.5;
rightCurtain.scaleY = 1.2;
backgroundElements.push(rightCurtain);
// === CONDUCTOR ANIMATION ===
// Conductor movement for liveliness
tween(conductor, {
rotation: 0.06
}, {
duration: 1800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(conductor, {
rotation: -0.06
}, {
duration: 1800,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
// Function to create floating musical notes
function playMelodia() {
// Only start music if not muted
if (!musicMuted) {
try {
LK.stopMusic();
console.log("Successfully stopped current music");
} catch (error) {
console.log("Error stopping music:", error);
}
gameplayMusicPlaying = false;
try {
LK.playMusic('Melodia', {
loop: true,
volume: musicVolume
});
gameplayMusicPlaying = true;
console.log("Successfully started Melodia");
} catch (error) {
console.log("Error starting Melodia:", error);
gameplayMusicPlaying = false;
}
} else {
console.log("Music not started - muted:", musicMuted);
}
}
function startMelodia() {
// Only start music if not muted
if (!musicMuted) {
try {
LK.playMusic('Melodia', {
loop: true,
volume: musicVolume
});
gameplayMusicPlaying = true;
console.log("Successfully started Melodia");
} catch (error) {
console.log("Error starting Melodia:", error);
gameplayMusicPlaying = false;
}
} else {
console.log("Music not started - muted:", musicMuted);
}
}
// Manual music control function - stops current music
function stopMusicSystem() {
console.log("Stopping music system manually");
try {
LK.stopMusic();
} catch (error) {
console.log("Error stopping music:", error);
}
gameplayMusicPlaying = false;
console.log("Music system stopped");
}
function createFloatingNote() {
var note = game.addChild(LK.getAsset('musicNote', {
anchorX: 0.5,
anchorY: 0.5
}));
note.x = Math.random() * 2048;
note.y = 2732 + 100;
note.alpha = 0.6;
floatingNotes.push(note);
// Animate note floating upward
tween(note, {
y: -100,
rotation: Math.PI * 2,
alpha: 0
}, {
duration: 8000 + Math.random() * 4000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (note.parent) {
note.destroy();
}
var index = floatingNotes.indexOf(note);
if (index > -1) {
floatingNotes.splice(index, 1);
}
}
});
}
// Create initial floating notes
for (var i = 0; i < 5; i++) {
LK.setTimeout(function () {
createFloatingNote();
}, Math.random() * 3000);
}
// === MAIN MENU ===
menuContainer = new Container();
// Add menu container after all background elements to ensure it appears on top
game.addChild(menuContainer);
var menuBackground = menuContainer.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
menuBackground.x = 1024;
menuBackground.y = 1366;
menuBackground.scaleX = 4.5;
menuBackground.scaleY = 6;
menuBackground.alpha = 0.95;
menuBackground.tint = 0x3D2914;
var startButton = menuContainer.addChild(LK.getAsset('BotonStart', {
anchorX: 0.5,
anchorY: 0.5
}));
startButton.x = 1024;
startButton.y = 1000;
startButton.scaleX = 1.5;
startButton.scaleY = 1.5;
var tutorialButton = menuContainer.addChild(LK.getAsset('TutorialB', {
anchorX: 0.5,
anchorY: 0.5
}));
tutorialButton.x = 1024;
tutorialButton.y = 1300;
tutorialButton.scaleX = 1.5;
tutorialButton.scaleY = 1.5;
var settingsButton = menuContainer.addChild(LK.getAsset('Setting', {
anchorX: 0.5,
anchorY: 0.5
}));
settingsButton.x = 1024;
settingsButton.y = 1900;
settingsButton.scaleX = 1.5;
settingsButton.scaleY = 1.5;
// Add hover effect for Start Game button
startButton.move = function (x, y, obj) {
// Check if obj and obj.parent exist before using them
if (!obj || !obj.parent) {
return;
}
// Get local coordinates relative to the button
var localPoint = startButton.toLocal(obj.parent.toGlobal({
x: x,
y: y
}));
// Check if mouse is actually over the button icon
if (Math.abs(localPoint.x) <= startButton.width / 2 && Math.abs(localPoint.y) <= startButton.height / 2) {
if (!startButton.isHovering) {
startButton.isHovering = true;
tween(startButton, {
scaleX: 1.65,
scaleY: 1.65
}, {
duration: 200,
easing: tween.easeOut
});
// Add subtle glow effect
startButton.tint = 0xFFD700;
}
} else {
// Mouse is not over button, reset hover state if needed
if (startButton.isHovering) {
startButton.isHovering = false;
tween(startButton, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
startButton.tint = 0xFFFFFF;
}
}
};
startButton.up = function () {
if (startButton.isHovering) {
startButton.isHovering = false;
tween(startButton, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
startButton.tint = 0xFFFFFF;
}
};
startButton.down = function () {
// Click animation
tween(startButton, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(startButton, {
scaleX: 1.65,
scaleY: 1.65
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Show title intro with curtains
showTitleIntro();
}
});
}
});
};
// Add hover effect for Tutorial button
tutorialButton.move = function (x, y, obj) {
// Check if obj and obj.parent exist before using them
if (!obj || !obj.parent) {
return;
}
// Get local coordinates relative to the button
var localPoint = tutorialButton.toLocal(obj.parent.toGlobal({
x: x,
y: y
}));
// Check if mouse is actually over the button icon
if (Math.abs(localPoint.x) <= tutorialButton.width / 2 && Math.abs(localPoint.y) <= tutorialButton.height / 2) {
if (!tutorialButton.isHovering) {
tutorialButton.isHovering = true;
tween(tutorialButton, {
scaleX: 1.65,
scaleY: 1.65
}, {
duration: 200,
easing: tween.easeOut
});
tutorialButton.tint = 0x00FFFF;
}
} else {
// Mouse is not over button, reset hover state if needed
if (tutorialButton.isHovering) {
tutorialButton.isHovering = false;
tween(tutorialButton, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
tutorialButton.tint = 0xFFFFFF;
}
}
};
tutorialButton.up = function () {
if (tutorialButton.isHovering) {
tutorialButton.isHovering = false;
tween(tutorialButton, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
tutorialButton.tint = 0xFFFFFF;
}
};
tutorialButton.down = function () {
// Click animation
tween(tutorialButton, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(tutorialButton, {
scaleX: 1.65,
scaleY: 1.65
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
gameState = 'tutorial';
tutorialStep = 0;
menuContainer.visible = false;
showTutorial();
}
});
}
});
};
// Add hover effect for Settings button
settingsButton.move = function (x, y, obj) {
// Check if obj and obj.parent exist before using them
if (!obj || !obj.parent) {
return;
}
// Get local coordinates relative to the button
var localPoint = settingsButton.toLocal(obj.parent.toGlobal({
x: x,
y: y
}));
// Check if mouse is actually over the button icon
if (Math.abs(localPoint.x) <= settingsButton.width / 2 && Math.abs(localPoint.y) <= settingsButton.height / 2) {
if (!settingsButton.isHovering) {
settingsButton.isHovering = true;
tween(settingsButton, {
scaleX: 1.65,
scaleY: 1.65
}, {
duration: 200,
easing: tween.easeOut
});
settingsButton.tint = 0xAA55FF;
}
} else {
// Mouse is not over button, reset hover state if needed
if (settingsButton.isHovering) {
settingsButton.isHovering = false;
tween(settingsButton, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
settingsButton.tint = 0xFFFFFF;
}
}
};
settingsButton.up = function () {
if (settingsButton.isHovering) {
settingsButton.isHovering = false;
tween(settingsButton, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
settingsButton.tint = 0xFFFFFF;
}
};
settingsButton.down = function () {
// Click animation
tween(settingsButton, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(settingsButton, {
scaleX: 1.65,
scaleY: 1.65
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Change ON text color to cyan, OFF text color to cyan as well
autoStartValue.fill = 0x00FFFF;
damageNumbersValue.fill = 0x00FFFF;
gameState = 'settings';
menuContainer.visible = false;
showSettings();
}
});
}
});
};
// === TUTORIAL SYSTEM ===
tutorialContainer = new Container();
tutorialContainer.visible = false;
// Add tutorial container after all background elements to ensure it appears on top
game.addChild(tutorialContainer);
// Add click handler to tutorial container for cycling through info
tutorialContainer.down = function (x, y, obj) {
var localX = x - tutorialContainer.x;
var localY = y - tutorialContainer.y;
// Expanded clickable area - covers entire description area including tower/enemy image and text
if (localX > 100 && localX < 1900 && localY > 300 && localY < 1400) {
if (currentInfoMode === 'towers' && currentInfoIndex < towerInfos.length - 1) {
currentInfoIndex++;
updateTutorialStep();
} else if (currentInfoMode === 'enemies' && currentInfoIndex < enemyInfos.length - 1) {
currentInfoIndex++;
updateTutorialStep();
}
}
};
var tutorialBackground = tutorialContainer.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
tutorialBackground.x = 1024;
tutorialBackground.y = 1366;
tutorialBackground.scaleX = 4.5;
tutorialBackground.scaleY = 5.5;
tutorialBackground.alpha = 0.95;
tutorialBackground.tint = 0x8B4513;
// Add subtle background accent for button area
var buttonAreaBackground = tutorialContainer.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
buttonAreaBackground.x = 1024;
buttonAreaBackground.y = 1700; // Moved down to match new button positions
buttonAreaBackground.scaleX = 4.0; // Slightly wider for increased spacing
buttonAreaBackground.scaleY = 2.5; // Taller to accommodate more separation
buttonAreaBackground.alpha = 0.3;
buttonAreaBackground.tint = 0x654321;
var tutorialTitleText = tutorialContainer.addChild(new Text2('♪ TUTORIAL ♪', {
size: 100,
fill: 0xFFD700,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
tutorialTitleText.anchor.set(0.5, 0.5);
tutorialTitleText.x = 1024;
tutorialTitleText.y = 300;
var tutorialStepText = tutorialContainer.addChild(new Text2('', {
size: 60,
fill: 0xF5F5DC,
font: "Georgia, serif"
}));
tutorialStepText.anchor.set(0.5, 0.5);
tutorialStepText.x = 1024;
tutorialStepText.y = 1200;
var towersButton = tutorialContainer.addChild(LK.getAsset('Towers', {
anchorX: 0.5,
anchorY: 0.5
}));
towersButton.x = 800;
towersButton.y = 1400;
var enemiesButton = tutorialContainer.addChild(LK.getAsset('Enemiesss', {
anchorX: 0.5,
anchorY: 0.5
}));
enemiesButton.x = 1248;
enemiesButton.y = 1400;
var startGameButton = tutorialContainer.addChild(LK.getAsset('StartGames', {
anchorX: 0.5,
anchorY: 0.5
}));
startGameButton.x = 1024;
startGameButton.y = 1600;
startGameButton.scaleX = 1.2;
startGameButton.scaleY = 1.2;
var backButton = tutorialContainer.addChild(LK.getAsset('BackMenu', {
anchorX: 0.5,
anchorY: 0.5
}));
backButton.x = 1024;
backButton.y = 2100;
// Add elegant text for all tutorial buttons
// Define positioning variables for tutorial buttons
var centerX = 1024;
var centerY = 1500;
var buttonRadius = 280;
var towersButtonText = tutorialContainer.addChild(new Text2('♪ TOWERS ♪', {
size: 50,
fill: 0xFFD700,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
towersButtonText.anchor.set(0.5, 0.5);
towersButtonText.x = centerX - buttonRadius;
towersButtonText.y = centerY + 90; // Directly below towers icon
var enemiesButtonText = tutorialContainer.addChild(new Text2('♪ ENEMIES ♪', {
size: 50,
fill: 0xFFD700,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
enemiesButtonText.anchor.set(0.5, 0.5);
enemiesButtonText.x = centerX + buttonRadius;
enemiesButtonText.y = centerY + 90; // Directly below enemies icon
var startGameButtonText = tutorialContainer.addChild(new Text2('♪ START GAME ♪', {
size: 55,
fill: 0xFFD700,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
startGameButtonText.anchor.set(0.5, 0.5);
startGameButtonText.x = centerX;
startGameButtonText.y = centerY + 370; // Directly below start game icon
var backButtonText = tutorialContainer.addChild(new Text2('♪ BACK TO MENU ♪', {
size: 50,
fill: 0xFFD700,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = centerX;
backButtonText.y = centerY + 610; // Directly below back button icon
var tutorialSteps = [getTutorialDescriptions()];
function showTutorial() {
tutorialContainer.visible = true;
// Move tutorial container to front to ensure visibility
game.removeChild(tutorialContainer);
game.addChild(tutorialContainer);
updateTutorialStep();
}
// Add tower reference image container
var tutorialTowerImage = null;
// Function to get localized tower information
function getTowerInfos() {
var towerData = {
english: [{
name: 'DRUM TOWER',
asset: 'drumTower',
info: 'Cost: $50 | Damage: 15 | Range: 165 | Speed: Fast\n\nArea damage tower that hits multiple enemies.\nSpecial: Explosive area damage around target.\n\nLevel 2: Increased damage and area\nLevel 3: Adds slow effect to area damage'
}, {
name: 'TRUMPET TOWER',
asset: 'trumpetTower',
info: 'Cost: $80 | Damage: 35 | Range: 180 | Speed: Medium\n\nPushback tower that sends enemies backward.\nSpecial: Forces enemies back down the path.\n\nLevel 2: Stronger pushback effect\nLevel 3: Adds stun chance to attacks'
}, {
name: 'GUITAR TOWER',
asset: 'guitarTower',
info: 'Cost: $120 | Damage: 20 | Range: 220 | Speed: Slow\n\nChain lightning tower hitting multiple targets.\nSpecial: Lightning chains between 3 enemies.\n\nLevel 2: Chains to 4 enemies\nLevel 3: Extended chaining capabilities'
}, {
name: 'VIOLIN TOWER',
asset: 'violinTower',
info: 'Cost: $100 | Damage: 10 | Range: 220 | Speed: Medium\n\nMagical control tower with slowing effects.\nSpecial: 25% chance to slow enemies significantly.\n\nLevel 2: Higher slow chance and duration\nLevel 3: Adds freeze chance to attacks'
}, {
name: 'DJ TOWER',
asset: 'djTower',
info: 'Cost: $200 | Damage: 60 | Range: 220 | Speed: Very Slow\n\nMassive area damage with blinding effects.\nSpecial: Large area damage + blind debuff.\n\nLevel 2: Increased area and blind duration\nLevel 3: Adds distortion DoT effect'
}],
spanish: [{
name: 'TORRE DE TAMBOR',
asset: 'drumTower',
info: 'Costo: $50 | Daño: 15 | Alcance: 165 | Velocidad: Rápida\n\nTorre de daño en área que golpea múltiples enemigos.\nEspecial: Daño explosivo en área alrededor del objetivo.\n\nNivel 2: Mayor daño y área\nNivel 3: Añade efecto de ralentización al daño en área'
}, {
name: 'TORRE DE TROMPETA',
asset: 'trumpetTower',
info: 'Costo: $80 | Daño: 35 | Alcance: 180 | Velocidad: Media\n\nTorre de empuje que envía enemigos hacia atrás.\nEspecial: Fuerza a los enemigos a retroceder por el camino.\n\nNivel 2: Efecto de empuje más fuerte\nNivel 3: Añade probabilidad de aturdimiento a los ataques'
}, {
name: 'TORRE DE GUITARRA',
asset: 'guitarTower',
info: 'Costo: $120 | Daño: 20 | Alcance: 220 | Velocidad: Lenta\n\nTorre de rayos en cadena que golpea múltiples objetivos.\nEspecial: Los rayos se encadenan entre 3 enemigos.\n\nNivel 2: Se encadena a 4 enemigos\nNivel 3: Capacidades de encadenamiento extendidas'
}, {
name: 'TORRE DE VIOLÍN',
asset: 'violinTower',
info: 'Costo: $100 | Daño: 10 | Alcance: 220 | Velocidad: Media\n\nTorre de control mágico con efectos de ralentización.\nEspecial: 25% de probabilidad de ralentizar enemigos significativamente.\n\nNivel 2: Mayor probabilidad y duración de ralentización\nNivel 3: Añade probabilidad de congelamiento a los ataques'
}, {
name: 'TORRE DE DJ',
asset: 'djTower',
info: 'Costo: $200 | Daño: 60 | Alcance: 220 | Velocidad: Muy Lenta\n\nDaño masivo en área con efectos de ceguera.\nEspecial: Gran daño en área + debuff de ceguera.\n\nNivel 2: Mayor área y duración de ceguera\nNivel 3: Añade efecto de distorsión DoT'
}]
};
return towerData[gameLanguage] || towerData.english;
}
// Function to get localized enemy information
function getEnemyInfos() {
var enemyData = {
english: [{
name: 'NOTA DESAFINADA',
asset: 'notaDesafinada',
info: 'Basic enemy with moderate health and speed.\nAppears from wave 1.\n\nHealth: 90 | Speed: Medium | Gold: 5\n\nThe most common musical threat.'
}, {
name: 'GLITCH AUDIO',
asset: 'glitchAudio',
info: 'Fast but fragile enemy.\nAppears from wave 5.\n\nHealth: 72 | Speed: Fast | Gold: 6\n\nQuick and unpredictable digital distortion.'
}, {
name: 'RUIDO BLANCO',
asset: 'ruidoBlanco',
info: 'Tanky enemy with high health.\nAppears from wave 10.\n\nHealth: 144 | Speed: Slow | Gold: 8\n\nStaticky interference that absorbs damage.'
}, {
name: 'ECO OSCURO',
asset: 'ecoOscuro',
info: 'Splits into two smaller enemies when killed.\nAppears from wave 15.\n\nHealth: 180 | Speed: Medium | Gold: 12\n\nDark echo that multiplies upon death.'
}, {
name: 'AUTOTUNE MALICIOSO',
asset: 'autotuneMalicioso',
info: 'Debuffs nearby towers, reducing their damage.\nAppears from wave 20.\n\nHealth: 270 | Speed: Medium-Slow | Gold: 20\n\nCorrupts the musical harmony of your defense.'
}],
spanish: [{
name: 'NOTA DESAFINADA',
asset: 'notaDesafinada',
info: 'Enemigo básico con salud y velocidad moderadas.\nAparece desde la oleada 1.\n\nSalud: 90 | Velocidad: Media | Oro: 5\n\nLa amenaza musical más común.'
}, {
name: 'GLITCH AUDIO',
asset: 'glitchAudio',
info: 'Enemigo rápido pero frágil.\nAparece desde la oleada 5.\n\nSalud: 72 | Velocidad: Rápida | Oro: 6\n\nDistorsión digital rápida e impredecible.'
}, {
name: 'RUIDO BLANCO',
asset: 'ruidoBlanco',
info: 'Enemigo tanque con mucha salud.\nAparece desde la oleada 10.\n\nSalud: 144 | Velocidad: Lenta | Oro: 8\n\nInterferencia estática que absorbe daño.'
}, {
name: 'ECO OSCURO',
asset: 'ecoOscuro',
info: 'Se divide en dos enemigos más pequeños al morir.\nAparece desde la oleada 15.\n\nSalud: 180 | Velocidad: Media | Oro: 12\n\nEco oscuro que se multiplica al morir.'
}, {
name: 'AUTOTUNE MALICIOSO',
asset: 'autotuneMalicioso',
info: 'Debilita las torres cercanas, reduciendo su daño.\nAparece desde la oleada 20.\n\nSalud: 270 | Velocidad: Media-Lenta | Oro: 20\n\nCorrompe la armonía musical de tu defensa.'
}]
};
// Always use English data for enemy names and force English names
var englishData = enemyData.english;
var localizedData = enemyData[gameLanguage] || enemyData.english;
// Create result with English names but localized descriptions
var result = [];
for (var i = 0; i < englishData.length; i++) {
result.push({
name: englishData[i].name,
// Always use English name
asset: englishData[i].asset,
info: localizedData[i] ? localizedData[i].info : englishData[i].info // Use localized description if available, otherwise English
});
}
return result;
}
// Use dynamic tower and enemy info arrays
var towerInfos = getTowerInfos();
var enemyInfos = getEnemyInfos();
// Current info display mode
var currentInfoMode = 'main'; // 'main', 'towers', 'enemies'
var currentInfoIndex = 0;
function updateTutorialStep() {
// Remove previous image if exists
if (tutorialTowerImage && tutorialTowerImage.parent) {
tutorialContainer.removeChild(tutorialTowerImage);
tutorialTowerImage = null;
}
// Hide/show navigation buttons based on mode
towersButton.visible = currentInfoMode === 'main';
enemiesButton.visible = currentInfoMode === 'main';
startGameButton.visible = currentInfoMode === 'main';
towersButtonText.visible = currentInfoMode === 'main';
enemiesButtonText.visible = currentInfoMode === 'main';
startGameButtonText.visible = currentInfoMode === 'main';
backButtonText.visible = true;
// Aesthetic positioning for main tutorial mode
if (currentInfoMode === 'main') {
// Create circular arrangement around center with better spacing, moved much lower
var centerX = 1024;
var centerY = 1500; // Moved down from 1150 to 1500
var buttonRadius = 280; // Increased separation from 200 to 280
// Position TOWERS button (left)
towersButton.x = centerX - buttonRadius;
towersButton.y = centerY;
towersButton.scaleX = 1.8;
towersButton.scaleY = 1.8;
// Position ENEMIES button (right)
enemiesButton.x = centerX + buttonRadius;
enemiesButton.y = centerY;
enemiesButton.scaleX = 1.8;
enemiesButton.scaleY = 1.8;
// Position START GAME button (center, larger, more separated)
startGameButton.x = centerX;
startGameButton.y = centerY + 280; // Increased separation to create more space
startGameButton.scaleX = 1.5;
startGameButton.scaleY = 1.5;
// Position BACK button (bottom center, more separated)
backButton.x = centerX;
backButton.y = centerY + 520; // Increased separation for more space
backButton.scaleX = 1.2;
backButton.scaleY = 1.2;
// Add entrance animation cascade effect
towersButton.alpha = 0;
enemiesButton.alpha = 0;
startGameButton.alpha = 0;
backButton.alpha = 0;
towersButtonText.alpha = 0;
enemiesButtonText.alpha = 0;
startGameButtonText.alpha = 0;
backButtonText.alpha = 0;
// Animate buttons appearing with cascade effect
tween(towersButton, {
alpha: 1,
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 400,
easing: tween.easeOut
});
tween(towersButtonText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
LK.setTimeout(function () {
tween(enemiesButton, {
alpha: 1,
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 400,
easing: tween.easeOut
});
tween(enemiesButtonText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
}, 150);
LK.setTimeout(function () {
tween(startGameButton, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 400,
easing: tween.easeOut
});
tween(startGameButtonText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
}, 300);
LK.setTimeout(function () {
tween(backButton, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 400,
easing: tween.easeOut
});
tween(backButtonText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
}, 450);
}
if (currentInfoMode === 'main') {
// Refresh tutorial description based on current language
tutorialSteps[0] = getTutorialDescriptions();
// Show main tutorial text
tutorialStepText.setText(tutorialSteps[0]);
// Move tutorial text up for main tutorial to avoid collision
tutorialStepText.y = 800;
} else if (currentInfoMode === 'towers') {
// Refresh tower info data to get current language
towerInfos = getTowerInfos();
// Reset position for other modes
tutorialStepText.y = 1200;
// Show tower information
if (currentInfoIndex < towerInfos.length) {
var towerInfo = towerInfos[currentInfoIndex];
var progressText = '(' + (currentInfoIndex + 1) + '/' + towerInfos.length + ')';
var navigationHint = currentInfoIndex < towerInfos.length - 1 ? '\n\n→ CLICK ANYWHERE TO SEE NEXT TOWER ←' : '\n\n→ ALL TOWERS SHOWN ←';
var mainText = towerInfo.name + ' ' + progressText + '\n\n' + towerInfo.info;
tutorialStepText.setText(mainText + navigationHint);
tutorialStepText.tint = 0xF5F5DC; // Set main text to original color
// Apply cyan color to navigation instruction text
if (currentInfoIndex < towerInfos.length - 1) {
if (tutorialStepText.text) {
var textParts = tutorialStepText.text.split('→ CLICK ANYWHERE TO SEE NEXT TOWER ←');
if (textParts.length === 2) {
tutorialStepText.setText(textParts[0]);
var navText = tutorialStepText.parent.addChild(new Text2('→ CLICK ANYWHERE TO SEE NEXT TOWER ←', {
size: 60,
fill: 0x00FFFF,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
navText.anchor.set(0.5, 0.5);
navText.x = 1024;
navText.y = 1150;
}
}
}
// Show tower image
tutorialTowerImage = tutorialContainer.addChild(LK.getAsset(towerInfo.asset, {
anchorX: 0.5,
anchorY: 0.5
}));
tutorialTowerImage.x = 1024;
tutorialTowerImage.y = 650;
tutorialTowerImage.scaleX = 2.0;
tutorialTowerImage.scaleY = 2.0;
// Show navigation
if (currentInfoIndex > 0) {
// Previous tower navigation
} else {
// Back to menu navigation
}
} else {
currentInfoMode = 'main';
currentInfoIndex = 0;
updateTutorialStep();
return;
}
} else if (currentInfoMode === 'enemies') {
// Refresh enemy info data to get current language
enemyInfos = getEnemyInfos();
// Reset position for enemy mode to match main tutorial positioning
tutorialStepText.y = 1200;
// Show enemy information
if (currentInfoIndex < enemyInfos.length) {
var enemyInfo = enemyInfos[currentInfoIndex];
var progressText = '(' + (currentInfoIndex + 1) + '/' + enemyInfos.length + ')';
var navigationHint = currentInfoIndex < enemyInfos.length - 1 ? '\n\n→ CLICK ANYWHERE TO SEE NEXT ENEMY ←' : '\n\n→ ALL ENEMIES SHOWN ←';
var mainText = enemyInfo.name + ' ' + progressText + '\n\n' + enemyInfo.info;
tutorialStepText.setText(mainText + navigationHint);
tutorialStepText.tint = 0xF5F5DC; // Set main text to original color
// Apply cyan color to navigation instruction text
if (currentInfoIndex < enemyInfos.length - 1) {
if (tutorialStepText.text) {
var textParts = tutorialStepText.text.split('→ CLICK ANYWHERE TO SEE NEXT ENEMY ←');
if (textParts.length === 2) {
tutorialStepText.setText(textParts[0]);
var navText = tutorialStepText.parent.addChild(new Text2('→ CLICK ANYWHERE TO SEE NEXT ENEMY ←', {
size: 60,
fill: 0x00FFFF,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
navText.anchor.set(0.5, 0.5);
navText.x = 1024;
navText.y = 1150;
}
}
}
// Show enemy image
tutorialTowerImage = tutorialContainer.addChild(LK.getAsset(enemyInfo.asset, {
anchorX: 0.5,
anchorY: 0.5
}));
tutorialTowerImage.x = 1024;
tutorialTowerImage.y = 650;
tutorialTowerImage.scaleX = 2.0;
tutorialTowerImage.scaleY = 2.0;
// Show navigation
if (currentInfoIndex > 0) {
// Previous enemy navigation
} else {
// Back to menu navigation
}
} else {
currentInfoMode = 'main';
currentInfoIndex = 0;
updateTutorialStep();
return;
}
}
}
towersButton.down = function () {
// Add bounce effect on click
tween(towersButton, {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(towersButton, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 150,
easing: tween.easeIn
});
}
});
currentInfoMode = 'towers';
currentInfoIndex = 0;
updateTutorialStep();
};
enemiesButton.down = function () {
// Add bounce effect on click
tween(enemiesButton, {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemiesButton, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 150,
easing: tween.easeIn
});
}
});
currentInfoMode = 'enemies';
currentInfoIndex = 0;
updateTutorialStep();
};
startGameButton.down = function () {
// Add bounce effect on click
tween(startGameButton, {
scaleX: 1.7,
scaleY: 1.7
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(startGameButton, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
gameState = 'playing';
tutorialContainer.visible = false;
startNextWave();
}
});
}
});
};
backButton.down = function () {
if (currentInfoMode === 'towers') {
if (currentInfoIndex > 0) {
currentInfoIndex--;
updateTutorialStep();
} else {
currentInfoMode = 'main';
currentInfoIndex = 0;
updateTutorialStep();
}
} else if (currentInfoMode === 'enemies') {
if (currentInfoIndex > 0) {
currentInfoIndex--;
updateTutorialStep();
} else {
currentInfoMode = 'main';
currentInfoIndex = 0;
updateTutorialStep();
}
} else {
gameState = 'menu';
tutorialContainer.visible = false;
menuContainer.visible = true;
}
};
// Path definition - reversed to go from bottom to top
var pathPoints = [{
x: 1750,
y: 2250
}, {
x: 1700,
y: 2100
}, {
x: 1650,
y: 1950
}, {
x: 1550,
y: 1800
}, {
x: 1400,
y: 1750
}, {
x: 1250,
y: 1700
}, {
x: 1100,
y: 1650
}, {
x: 950,
y: 1600
}, {
x: 800,
y: 1550
}, {
x: 650,
y: 1500
}, {
x: 500,
y: 1450
}, {
x: 350,
y: 1400
}, {
x: 200,
y: 1300
}, {
x: 100,
y: 1150
}, {
x: 200,
y: 1000
}, {
x: 350,
y: 950
}, {
x: 500,
y: 900
}, {
x: 650,
y: 800
}, {
x: 700,
y: 650
}, {
x: 650,
y: 500
}, {
x: 500,
y: 400
}, {
x: 350,
y: 300
}, {
x: 200,
y: 300
}, {
x: 50,
y: 300
}];
// Create elegant wooden walkway with proper connectivity
for (var i = 0; i < pathPoints.length; i++) {
var pathNode = game.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
pathNode.x = pathPoints[i].x;
pathNode.y = pathPoints[i].y;
pathNode.scaleX = 0.4;
pathNode.scaleY = 0.4;
pathNode.alpha = 0.95;
pathNode.tint = 0xD2691E; // Warm wood color
// Calculate rotation based on path direction for better connectivity
if (i < pathPoints.length - 1) {
var dx = pathPoints[i + 1].x - pathPoints[i].x;
var dy = pathPoints[i + 1].y - pathPoints[i].y;
pathNode.rotation = Math.atan2(dy, dx);
} else if (i > 0) {
var dx = pathPoints[i].x - pathPoints[i - 1].x;
var dy = pathPoints[i].y - pathPoints[i - 1].y;
pathNode.rotation = Math.atan2(dy, dx);
}
// Add gentle animation to make the wooden path feel alive
tween(pathNode, {
scaleX: 0.42,
alpha: 1.0
}, {
duration: 2000 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(pathNode, {
scaleX: 0.4,
alpha: 0.95
}, {
duration: 2000 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
// Add connecting wooden planks between path points for better continuity
if (i < pathPoints.length - 1) {
var distance = Math.sqrt(Math.pow(pathPoints[i + 1].x - pathPoints[i].x, 2) + Math.pow(pathPoints[i + 1].y - pathPoints[i].y, 2));
var numConnectors = Math.floor(distance / 80); // Add connectors every 80 pixels
for (var j = 1; j <= numConnectors; j++) {
var ratio = j / (numConnectors + 1);
var connectorX = pathPoints[i].x + (pathPoints[i + 1].x - pathPoints[i].x) * ratio;
var connectorY = pathPoints[i].y + (pathPoints[i + 1].y - pathPoints[i].y) * ratio;
var connector = game.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
connector.x = connectorX;
connector.y = connectorY;
connector.scaleX = 0.3;
connector.scaleY = 0.3;
connector.alpha = 0.8;
connector.tint = 0xCD853F; // Slightly different wood tone for variety
var dx = pathPoints[i + 1].x - pathPoints[i].x;
var dy = pathPoints[i + 1].y - pathPoints[i].y;
connector.rotation = Math.atan2(dy, dx) + (Math.random() - 0.5) * 0.2;
}
}
}
// Create goal
var goal = game.addChild(LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5
}));
goal.x = pathPoints[pathPoints.length - 1].x;
goal.y = pathPoints[pathPoints.length - 1].y;
// === BUILD SPOTS CREATION ===
var purpleNumbers = []; // Keep empty array for build spot reference
// Create build spots positioned every 2 wooden path tables on both left and right sides
for (var i = 0; i < pathPoints.length - 1; i += 2) {
// Get the path point for this segment
var pathPoint = pathPoints[i];
// Calculate direction to next path point for perpendicular positioning
var nextPathPoint = pathPoints[Math.min(i + 1, pathPoints.length - 1)];
var pathDx = nextPathPoint.x - pathPoint.x;
var pathDy = nextPathPoint.y - pathPoint.y;
var pathLength = Math.sqrt(pathDx * pathDx + pathDy * pathDy);
if (pathLength > 0) {
// Normalize the path direction
pathDx /= pathLength;
pathDy /= pathLength;
// Calculate perpendicular vectors (90 degrees rotated)
var perpDx = -pathDy; // Left side perpendicular
var perpDy = pathDx;
// Distance from path center to build spots
var offsetDistance = 150;
// Create left side build spot
var leftBuildSpot = new BuildSpot(buildSpots.length + 1);
leftBuildSpot.x = pathPoint.x + perpDx * offsetDistance;
leftBuildSpot.y = pathPoint.y + perpDy * offsetDistance;
buildSpots.push(leftBuildSpot);
game.addChild(leftBuildSpot);
// Create right side build spot
var rightBuildSpot = new BuildSpot(buildSpots.length + 1);
rightBuildSpot.x = pathPoint.x - perpDx * offsetDistance;
rightBuildSpot.y = pathPoint.y - perpDy * offsetDistance;
buildSpots.push(rightBuildSpot);
game.addChild(rightBuildSpot);
}
}
// UI Elements
// Gold background panel for better visibility
var goldBackground = LK.getAsset('fondo', {
anchorX: 0.5,
anchorY: 0.5
});
goldBackground.scaleX = 0.5;
goldBackground.scaleY = 0.25;
goldBackground.alpha = 0.9;
goldBackground.tint = 0x8B4513;
LK.gui.topRight.addChild(goldBackground);
goldBackground.x = -200;
goldBackground.y = 100;
// Gold icon and text
var goldIcon = LK.getAsset('gold', {
anchorX: 0.5,
anchorY: 0.5
});
goldIcon.tint = 0xFFD700;
goldIcon.scaleX = 0.8;
goldIcon.scaleY = 0.8;
LK.gui.topRight.addChild(goldIcon);
goldIcon.x = -280;
goldIcon.y = 100;
var goldText = new Text2(gold, {
size: 80,
fill: 0xFFD700,
font: "Arial, sans-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: "Arial, sans-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: "Impact, sans-serif"
});
waveText.anchor.set(0.5, 0.5);
LK.gui.bottomLeft.addChild(waveText);
waveText.x = 320;
waveText.y = -50;
// 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 = -50;
// 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: "Impact, sans-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('cuernito', {
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: "Impact, sans-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: "Arial, sans-serif"
});
autoStartText.anchor.set(0.5, 0.5);
LK.gui.bottomRight.addChild(autoStartText);
autoStartText.x = -200;
autoStartText.y = -70;
// Speed Acceleration Button
var gameSpeed = 1; // 1x normal speed, 2x fast speed
var videoTapeRibbons = []; // Array to track video tape ribbon effects
var fastForwardOverlay = null; // Track fast-forward overlay
var scanLines = []; // Track scan line effects
// Video tape fast-forward effect
function createFastForwardEffect() {
if (fastForwardOverlay) return; // Prevent multiple overlays
// Create semi-transparent overlay for overall effect
fastForwardOverlay = game.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
fastForwardOverlay.x = 1024;
fastForwardOverlay.y = 1366;
fastForwardOverlay.scaleX = 5.0;
fastForwardOverlay.scaleY = 7.0;
fastForwardOverlay.alpha = 0;
fastForwardOverlay.tint = 0x00FFFF; // Cyan tint for video effect
// Create horizontal scan lines
for (var i = 0; i < 12; i++) {
var scanLine = game.addChild(LK.getAsset('woodPanel', {
anchorX: 0,
anchorY: 0.5
}));
scanLine.x = 0;
scanLine.y = i * 200 + Math.random() * 100;
scanLine.scaleX = 6.0; // Full width
scanLine.scaleY = 0.05; // Very thin
scanLine.alpha = 0.3 + Math.random() * 0.2;
scanLine.tint = 0xFFFFFF; // White scan lines
scanLines.push(scanLine);
// Animate scan lines moving down
tween(scanLine, {
y: scanLine.y + 2732 + 400,
alpha: 0
}, {
duration: 800 + Math.random() * 400,
easing: tween.linear,
onFinish: function onFinish() {
if (scanLine.parent) {
scanLine.destroy();
}
}
});
}
// Main overlay fade in and out effect
tween(fastForwardOverlay, {
alpha: 0.15
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(fastForwardOverlay, {
alpha: 0.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(fastForwardOverlay, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (fastForwardOverlay && fastForwardOverlay.parent) {
fastForwardOverlay.destroy();
fastForwardOverlay = null;
}
}
});
}
});
}
});
}
function applyChomaticShiftToEnemies() {
// Apply subtle chromatic shift to moving elements
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyGraphics = enemy.getChildAt(0);
if (enemyGraphics && !enemy.chromaticShifted) {
enemy.chromaticShifted = true;
var originalTint = enemyGraphics.tint || 0xffffff;
// Quick chromatic shift sequence
tween(enemyGraphics, {
tint: 0xFF00FF
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemyGraphics, {
tint: 0x00FFFF
}, {
duration: 150,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(enemyGraphics, {
tint: originalTint
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
enemy.chromaticShifted = false;
}
});
}
});
}
});
}
}
}
function applyMotionBlurToTowers() {
// Apply subtle motion blur effect to towers
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (!tower.motionBlurred) {
tower.motionBlurred = true;
var towerGraphics = tower.getChildAt(0);
if (towerGraphics) {
// Quick scale pulse to simulate motion blur
tween(towerGraphics, {
scaleX: 1.05,
scaleY: 1.05,
alpha: 0.9
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(towerGraphics, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 250,
easing: tween.easeIn,
onFinish: function onFinish() {
tower.motionBlurred = false;
}
});
}
});
}
}
}
}
var speedButtonBackground = LK.getAsset('fondo', {
anchorX: 0.5,
anchorY: 0.5
});
speedButtonBackground.scaleX = 0.8;
speedButtonBackground.scaleY = 0.4;
speedButtonBackground.alpha = 0.9;
speedButtonBackground.tint = 0x8B4513;
LK.gui.bottomLeft.addChild(speedButtonBackground);
speedButtonBackground.x = 200;
speedButtonBackground.y = -550;
var speedButton = LK.getAsset('adelantar', {
anchorX: 0.5,
anchorY: 0.5
});
speedButton.scaleX = 1.0;
speedButton.scaleY = 1.0;
LK.gui.bottomLeft.addChild(speedButton);
speedButton.x = 200;
speedButton.y = -550;
var speedText = new Text2('♪ SPEED: 1X ♪', {
size: 45,
fill: 0xFFFFFF,
font: "Impact, sans-serif"
});
speedText.anchor.set(0.5, 0.5);
LK.gui.bottomLeft.addChild(speedText);
speedText.x = 200;
speedText.y = -500;
var speedStatusText = new Text2('Normal', {
size: 35,
fill: 0xFFFFFF,
font: "Arial, sans-serif"
});
speedStatusText.anchor.set(0.5, 0.5);
LK.gui.bottomLeft.addChild(speedStatusText);
speedStatusText.x = 200;
speedStatusText.y = -470;
// Grand Piano Power Button (initially hidden)
grandPianoButtonBackground = LK.getAsset('fondo', {
anchorX: 0.5,
anchorY: 0.5
});
grandPianoButtonBackground.scaleX = 0.8;
grandPianoButtonBackground.scaleY = 0.5;
grandPianoButtonBackground.alpha = 0.9;
grandPianoButtonBackground.tint = 0x8B4513;
LK.gui.bottomLeft.addChild(grandPianoButtonBackground);
grandPianoButtonBackground.x = 200;
grandPianoButtonBackground.y = -300;
grandPianoButtonBackground.visible = false;
grandPianoPowerButton = LK.getAsset('grandPiano', {
anchorX: 0.5,
anchorY: 0.5
});
grandPianoPowerButton.scaleX = 1.0;
grandPianoPowerButton.scaleY = 1.0;
LK.gui.bottomLeft.addChild(grandPianoPowerButton);
grandPianoPowerButton.x = 200;
grandPianoPowerButton.y = -300;
grandPianoPowerButton.visible = false;
grandPianoPowerText = new Text2('♪ GRAND PIANO ♪', {
size: 45,
fill: 0xFFD700,
font: "Impact, sans-serif"
});
grandPianoPowerText.anchor.set(0.5, 0.5);
LK.gui.bottomLeft.addChild(grandPianoPowerText);
grandPianoPowerText.x = 200;
grandPianoPowerText.y = -250;
grandPianoPowerText.visible = false;
grandPianoCooldownText = new Text2('Ready!', {
size: 35,
fill: 0x00FFFF,
font: "Arial, sans-serif"
});
grandPianoCooldownText.anchor.set(0.5, 0.5);
LK.gui.bottomLeft.addChild(grandPianoCooldownText);
grandPianoCooldownText.x = 200;
grandPianoCooldownText.y = -220;
grandPianoCooldownText.visible = false;
muteButton.down = function () {
if (musicMuted) {
// Unmuting - start appropriate music
if (gameState === 'playing') {
console.log("Unmuting - starting Melodia");
playMelodia();
} else {
// In menu, play menu music
console.log("Unmuting - starting menu music");
try {
LK.playMusic('MusicaInicio', {
loop: true,
volume: musicVolume
});
} catch (error) {
console.log("Error starting menu music:", error);
}
}
muteText.setText('♪ MUSIC ♪');
muteButton.tint = 0xFFFFFF;
musicMuted = false;
} else {
// Muting - stop all music and reset system
console.log("Muting music");
stopMusicSystem();
muteText.setText('♪ MUTED ♪');
muteButton.tint = 0x888888;
musicMuted = true;
}
};
startWaveButton.down = function () {
if (gameState === 'playing' && wavePaused && enemies.length === 0) {
startCurrentWave();
}
};
speedButton.down = function () {
if (gameState === 'playing') {
// Toggle between 1x, 2x and 3x speed
if (gameSpeed === 1) {
gameSpeed = 2;
speedText.setText('♪ SPEED: 2X ♪');
speedText.tint = 0x00FF00; // Green color to match 2x icon
speedStatusText.setText('Fast');
speedButton.tint = 0x00FF00; // Green tint for fast mode
// Trigger video tape fast-forward effect
createFastForwardEffect();
// Apply chromatic shift to enemies
LK.setTimeout(function () {
applyChomaticShiftToEnemies();
}, 200);
// Apply motion blur to towers
LK.setTimeout(function () {
applyMotionBlurToTowers();
}, 350);
} else if (gameSpeed === 2) {
gameSpeed = 3;
speedText.setText('♪ SPEED: 3X ♪');
speedText.tint = 0x9932CC; // Purple color for 3x text to match icon
speedStatusText.setText('Ultra Fast');
speedButton.tint = 0x9932CC; // Purple tint for ultra fast mode
// Trigger video tape fast-forward effect
createFastForwardEffect();
// Apply chromatic shift to enemies
LK.setTimeout(function () {
applyChomaticShiftToEnemies();
}, 200);
// Apply motion blur to towers
LK.setTimeout(function () {
applyMotionBlurToTowers();
}, 350);
} else {
gameSpeed = 1;
speedText.setText('♪ SPEED: 1X ♪');
speedText.tint = 0xFFFFFF; // White color to match 1x icon
speedStatusText.setText('Normal');
speedButton.tint = 0xFFFFFF; // Normal tint
}
// Add click animation
tween(speedButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(speedButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
});
// Play sound feedback
LK.getSound('build').play();
}
};
grandPianoPowerButton.down = function () {
if (grandPianoPowerUnlocked && grandPianoCooldownRemaining === 0 && !grandPianoPowerActive && gameState === 'playing' && !gameOverTriggered) {
enterGrandPianoTargetingMode();
}
};
// Build menu elements (initially hidden)
var buildMenuContainer = new Container();
game.addChild(buildMenuContainer);
buildMenuContainer.visible = false;
var drumButton = buildMenuContainer.addChild(LK.getAsset('drumTower', {
anchorX: 0.5,
anchorY: 0.5
}));
var guitarButton = buildMenuContainer.addChild(LK.getAsset('guitarTower', {
anchorX: 0.5,
anchorY: 0.5
}));
var violinButton = buildMenuContainer.addChild(LK.getAsset('violinTower', {
anchorX: 0.5,
anchorY: 0.5
}));
var trumpetButton = buildMenuContainer.addChild(LK.getAsset('trumpetTower', {
anchorX: 0.5,
anchorY: 0.5
}));
var djButton = buildMenuContainer.addChild(LK.getAsset('djTower', {
anchorX: 0.5,
anchorY: 0.5
}));
var drumText = buildMenuContainer.addChild(new Text2('$50', {
size: 45,
fill: 0xFFD700,
font: "Verdana, sans-serif"
}));
drumText.anchor.set(0.5, 0);
var trumpetText = buildMenuContainer.addChild(new Text2('$80', {
size: 45,
fill: 0xFFD700,
font: "Verdana, sans-serif"
}));
trumpetText.anchor.set(0.5, 0);
var guitarText = buildMenuContainer.addChild(new Text2('$120', {
size: 45,
fill: 0xFFD700,
font: "Verdana, sans-serif"
}));
guitarText.anchor.set(0.5, 0);
var violinText = buildMenuContainer.addChild(new Text2('$100', {
size: 45,
fill: 0xFFD700,
font: "Verdana, sans-serif"
}));
violinText.anchor.set(0.5, 0);
var djText = buildMenuContainer.addChild(new Text2('$200', {
size: 45,
fill: 0xFFD700,
font: "Verdana, sans-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: "Verdana, sans-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: "Verdana, sans-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: "Verdana, sans-serif"
}));
towerInfoText.anchor.set(0.5, 1);
towerInfoText.x = 0;
towerInfoText.y = -50;
function updateUI() {
goldText.setText(gold);
livesText.setText(lives);
waveText.setText('♪ WAVE ' + wave + ' ♪');
updateGrandPianoPowerUI();
}
function showBuildMenu(buildSpot) {
showingBuildMenu = true;
selectedBuildSpot = buildSpot;
buildMenuContainer.visible = true;
// Ensure build menu appears above all other elements
game.removeChild(buildMenuContainer);
game.addChild(buildMenuContainer);
// Position menu near build spot
buildMenuContainer.x = buildSpot.x;
buildMenuContainer.y = buildSpot.y - 200;
// Create circular menu layout
var radius = 120;
var centerX = 0;
var centerY = 0;
// Position buttons in a circle
var angleStep = Math.PI * 2 / 5; // 5 buttons in circle
drumButton.x = centerX + Math.cos(0) * radius;
drumButton.y = centerY + Math.sin(0) * radius;
trumpetButton.x = centerX + Math.cos(angleStep) * radius;
trumpetButton.y = centerY + Math.sin(angleStep) * radius;
guitarButton.x = centerX + Math.cos(angleStep * 2) * radius;
guitarButton.y = centerY + Math.sin(angleStep * 2) * radius;
violinButton.x = centerX + Math.cos(angleStep * 3) * radius;
violinButton.y = centerY + Math.sin(angleStep * 3) * radius;
djButton.x = centerX + Math.cos(angleStep * 4) * radius;
djButton.y = centerY + Math.sin(angleStep * 4) * radius;
// Position text closer to buttons in circle
var textRadius = radius + 30;
drumText.x = centerX + Math.cos(0) * textRadius;
drumText.y = centerY + Math.sin(0) * textRadius;
trumpetText.x = centerX + Math.cos(angleStep) * textRadius;
trumpetText.y = centerY + Math.sin(angleStep) * textRadius;
guitarText.x = centerX + Math.cos(angleStep * 2) * textRadius;
guitarText.y = centerY + Math.sin(angleStep * 2) * textRadius;
violinText.x = centerX + Math.cos(angleStep * 3) * textRadius;
violinText.y = centerY + Math.sin(angleStep * 3) * textRadius;
djText.x = centerX + Math.cos(angleStep * 4) * textRadius;
djText.y = centerY + Math.sin(angleStep * 4) * textRadius;
// Start buttons small and animate them opening
drumButton.scaleX = 0.1;
drumButton.scaleY = 0.1;
trumpetButton.scaleX = 0.1;
trumpetButton.scaleY = 0.1;
guitarButton.scaleX = 0.1;
guitarButton.scaleY = 0.1;
violinButton.scaleX = 0.1;
violinButton.scaleY = 0.1;
djButton.scaleX = 0.1;
djButton.scaleY = 0.1;
// Animate buttons appearing in sequence with slight delays
tween(drumButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut
});
LK.setTimeout(function () {
tween(trumpetButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut
});
}, 100);
LK.setTimeout(function () {
tween(guitarButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut
});
}, 200);
LK.setTimeout(function () {
tween(violinButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut
});
}, 300);
LK.setTimeout(function () {
tween(djButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut
});
}, 400);
}
function hideBuildMenu() {
showingBuildMenu = false;
selectedBuildSpot = null;
buildMenuContainer.visible = false;
}
function showUpgradeMenu(tower) {
if (showingBuildMenu) return; // Don't show if build menu is open
// Force hide any existing upgrade menu first
if (showingUpgradeMenu) {
hideUpgradeMenu();
// Wait for hide animation to complete before showing new menu
LK.setTimeout(function () {
actuallyShowUpgradeMenu(tower);
}, 250);
return;
}
actuallyShowUpgradeMenu(tower);
}
function actuallyShowUpgradeMenu(tower) {
showingUpgradeMenu = true;
selectedTower = tower;
// Ensure container is properly reset
upgradeMenuContainer.visible = true;
upgradeMenuContainer.scaleX = 0.1;
upgradeMenuContainer.scaleY = 0.1;
upgradeMenuContainer.alpha = 0;
// Position menu near tower but higher up
upgradeMenuContainer.x = tower.x;
upgradeMenuContainer.y = tower.y - 250;
// Hide background for circular menu
upgradeMenuBackground.visible = false;
// Position buttons in circular layout
var radius = 80;
upgradeButton.x = -radius;
upgradeButton.y = 0;
removeButton.x = radius;
removeButton.y = 0;
// Update menu content based on tower level and type
var upgradeCost = 0;
if (tower.towerType === 'drum') {
upgradeCost = tower.level === 1 ? 90 : tower.level === 2 ? 160 : 0;
} else if (tower.towerType === 'trumpet') {
upgradeCost = tower.level === 1 ? 130 : tower.level === 2 ? 200 : 0;
} else if (tower.towerType === 'guitar') {
upgradeCost = tower.level === 1 ? 180 : tower.level === 2 ? 250 : 0;
} else if (tower.towerType === 'violin') {
upgradeCost = tower.level === 1 ? 160 : tower.level === 2 ? 220 : 0;
} else if (tower.towerType === 'dj') {
upgradeCost = tower.level === 1 ? 280 : tower.level === 2 ? 350 : 0;
}
var canUpgrade = tower.level < 3 && gold >= upgradeCost;
var refundAmount = Math.floor(tower.totalCost * 0.5);
if (tower.level >= 3) {
upgradeCostText.setText('UPGRADE\nMAX LEVEL');
upgradeCostText.tint = 0x888888;
upgradeButton.tint = 0x666666;
} else {
upgradeCostText.setText('UPGRADE\n$' + upgradeCost);
upgradeCostText.tint = canUpgrade ? 0x00FF00 : 0xFF4444;
upgradeButton.tint = canUpgrade ? 0xFFFFFF : 0x888888;
}
removeText.setText('REMOVE\nRefund $' + refundAmount);
towerInfoText.setText('Level ' + tower.level + ' ' + tower.towerType.toUpperCase());
// Position text relative to circular buttons - center upgrade cost below upgrade button
upgradeCostText.x = -radius; // Center below upgrade button
upgradeCostText.y = 50;
removeText.x = radius;
removeText.y = 50;
towerInfoText.x = 0;
towerInfoText.y = -50;
// Animate menu opening
tween(upgradeMenuContainer, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.easeOut
});
}
function hideUpgradeMenu() {
showingUpgradeMenu = false;
selectedTower = null;
tween(upgradeMenuContainer, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
upgradeMenuContainer.visible = false;
}
});
}
function removeTower(tower) {
if (!tower) return;
// Calculate refund (50% of total investment)
var refundAmount = Math.floor(tower.totalCost * 0.5);
gold += refundAmount;
// Find and free the build spot
for (var i = 0; i < buildSpots.length; i++) {
var spot = buildSpots[i];
var distance = Math.sqrt((spot.x - tower.x) * (spot.x - tower.x) + (spot.y - tower.y) * (spot.y - tower.y));
if (distance < 50) {
// Close enough to be the same spot
spot.occupied = false;
break;
}
}
// Remove from towers array
var towerIndex = towers.indexOf(tower);
if (towerIndex > -1) {
towers.splice(towerIndex, 1);
}
// Destruction animation and sound
LK.getSound('Destruir').play(); // Tower destruction sound
tween(tower, {
scaleX: 0.1,
scaleY: 0.1,
rotation: Math.PI * 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
tower.destroy();
}
});
updateUI();
hideUpgradeMenu();
}
function buildTower(towerType, cost) {
console.log("buildTower called:", towerType, "cost:", cost, "gold:", gold, "selectedBuildSpot:", selectedBuildSpot);
if (!selectedBuildSpot) {
console.log("No build spot selected");
return;
}
if (gold < cost) {
console.log("Not enough gold:", gold, "needed:", cost);
return;
}
console.log("Building tower...");
// Deduct gold first
gold -= cost;
// Track gold spent
totalGoldSpent += cost;
console.log("Gold after purchase:", gold);
var tower = new Tower(towerType);
tower.x = selectedBuildSpot.x;
tower.y = selectedBuildSpot.y;
towers.push(tower);
game.addChild(tower);
selectedBuildSpot.occupied = true;
console.log("Tower built successfully at:", tower.x, tower.y);
updateUI();
hideBuildMenu();
// Play specific sound for trumpet and guitar towers
if (towerType === 'trumpet') {
LK.getSound('sonidotrompeta').play();
} else if (towerType === 'guitar') {
LK.getSound('SonidoGuitarra').play();
} else if (towerType === 'violin') {
LK.getSound('SonidoV').play();
} else if (towerType === 'dj') {
LK.getSound('SonidoDJ').play();
} else {
LK.getSound('build').play();
}
}
// Button handlers with proper event handling
drumButton.down = function (x, y, obj) {
console.log("Drum button clicked, gold:", gold);
if (gold >= 50) {
buildTower('drum', 50);
} else {
console.log("Not enough gold for drum tower");
LK.getSound('eror').play();
}
};
guitarButton.down = function (x, y, obj) {
console.log("Guitar button clicked, gold:", gold);
if (gold >= 120) {
buildTower('guitar', 120);
} else {
console.log("Not enough gold for guitar tower");
LK.getSound('eror').play();
}
};
violinButton.down = function (x, y, obj) {
console.log("Violin button clicked, gold:", gold);
if (gold >= 100) {
buildTower('violin', 100);
} else {
console.log("Not enough gold for violin tower");
LK.getSound('eror').play();
}
};
trumpetButton.down = function (x, y, obj) {
console.log("Trumpet button clicked, gold:", gold);
if (gold >= 80) {
buildTower('trumpet', 80);
} else {
console.log("Not enough gold for trumpet tower");
LK.getSound('eror').play();
}
};
djButton.down = function (x, y, obj) {
console.log("DJ button clicked, gold:", gold);
if (gold >= 200) {
buildTower('dj', 200);
} else {
console.log("Not enough gold for DJ tower");
LK.getSound('eror').play();
}
};
// Upgrade menu button handlers
upgradeButton.down = function (x, y, obj) {
if (selectedTower && selectedTower.level < 3) {
var upgradeCost = 0;
if (selectedTower.towerType === 'drum') {
upgradeCost = selectedTower.level === 1 ? 90 : 160;
} else if (selectedTower.towerType === 'trumpet') {
upgradeCost = selectedTower.level === 1 ? 130 : 200;
} else if (selectedTower.towerType === 'guitar') {
upgradeCost = selectedTower.level === 1 ? 180 : 250;
} else if (selectedTower.towerType === 'violin') {
upgradeCost = selectedTower.level === 1 ? 160 : 220;
} else if (selectedTower.towerType === 'dj') {
upgradeCost = selectedTower.level === 1 ? 280 : 350;
}
if (gold >= upgradeCost) {
if (selectedTower.upgrade()) {
LK.getSound('build').play(); // Upgrade sound
hideUpgradeMenu();
}
}
}
};
removeButton.down = function (x, y, obj) {
if (selectedTower) {
removeTower(selectedTower);
}
};
// Game click handler to hide menus - only if not clicking on buttons or build spots
game.move = function (x, y, obj) {
if (grandPianoGhostMode && grandPianoGhost && grandPianoTargetArea) {
grandPianoGhost.x = x;
grandPianoGhost.y = y;
grandPianoTargetArea.x = x;
grandPianoTargetArea.y = y;
}
};
game.down = function (x, y, obj) {
// Hide language dropdown if clicking outside settings
if (showingLanguageDropdown && gameState !== 'settings') {
hideLanguageDropdown();
}
// Handle Grand Piano targeting mode first
if (grandPianoGhostMode) {
activateGrandPianoPowerAt(x, y);
return;
}
// Priority: Build menu takes precedence
if (showingBuildMenu) {
// Check if clicked within circular menu area
var relativeX = x - buildMenuContainer.x;
var relativeY = y - buildMenuContainer.y;
var distanceFromCenter = Math.sqrt(relativeX * relativeX + relativeY * relativeY);
var clickedOnButton = distanceFromCenter <= 180; // Circular area radius + buffer
if (!clickedOnButton) {
hideBuildMenu();
}
return; // Prevent other interactions when build menu is open
}
// Check if we clicked on a tower first - if so, don't hide upgrade menu
var clickedOnTower = false;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx = x - tower.x;
var dy = y - tower.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 60) {
// Tower click radius
clickedOnTower = true;
break;
}
}
// Handle upgrade menu interactions
if (showingUpgradeMenu) {
// Don't hide if we clicked on a tower (allowing tower switching)
if (clickedOnTower) {
return;
}
// Check if clicked within upgrade menu area
var relativeX = x - upgradeMenuContainer.x;
var relativeY = y - upgradeMenuContainer.y;
var distanceFromCenter = Math.sqrt(relativeX * relativeX + relativeY * relativeY);
var clickedOnButton = distanceFromCenter <= 150; // Menu area radius + buffer
if (!clickedOnButton) {
hideUpgradeMenu();
}
return; // Prevent other interactions when upgrade menu is open
}
};
function calculateEnemiesForWave(waveNumber) {
// Formula: enemiesTotal = round(10 × (1.15 ^ (waveActual - 1)))
var baseCount = Math.round(10 * Math.pow(1.15, waveNumber - 1));
// Modify for special waves
if (currentSpecialWave === 'massive') {
baseCount = Math.round(baseCount * 1.8); // 80% more enemies
} else if (currentSpecialWave === 'runners') {
baseCount = Math.round(baseCount * 1.3); // 30% more fast enemies
}
return baseCount;
}
function getSpawnIntervalForWave(waveNumber) {
// Formula: max(0.5, 1.5 - (wave × 0.03)) in seconds, converted to ticks
var intervalInSeconds = Math.max(0.5, 1.5 - waveNumber * 0.03);
var intervalInTicks = Math.round(intervalInSeconds * 60); // Convert to 60fps ticks
return intervalInTicks;
}
function getEnemyTypesForWave(waveNumber) {
var availableTypes = [];
// Remove special empowered waves from wave 15+ (no more empowered enemies)
if (waveNumber >= 8 && waveNumber < 15) {
var isSpecialWave = Math.random() < 0.25; // 25% chance only for waves 8-14
if (isSpecialWave) {
var specialWaves = ['runners', 'armored', 'massive'];
var specialWave = specialWaves[Math.floor(Math.random() * specialWaves.length)];
// Store special wave type for spawn modifications
currentSpecialWave = specialWave;
if (specialWave === 'runners') {
return ['glitchAudio']; // Fast, low HP enemies
} else if (specialWave === 'armored') {
return ['ruidoBlanco']; // Slow, high HP enemies
} else if (specialWave === 'massive') {
// More enemies but weaker
return ['notaDesafinada'];
}
}
}
currentSpecialWave = null; // Normal wave
// Wave 1-4: Only Nota Desafinada
if (waveNumber <= 4) {
availableTypes = ['notaDesafinada'];
}
// Wave 5-9: Add Glitch de Audio
else if (waveNumber >= 5 && waveNumber <= 9) {
availableTypes = ['notaDesafinada', 'glitchAudio'];
}
// Wave 10-14: Add Ruido Blanco
else if (waveNumber >= 10 && waveNumber <= 14) {
availableTypes = ['notaDesafinada', 'glitchAudio', 'ruidoBlanco'];
}
// Wave 15-19: Add Eco Oscuro
else if (waveNumber >= 15 && waveNumber <= 19) {
availableTypes = ['notaDesafinada', 'glitchAudio', 'ruidoBlanco', 'ecoOscuro'];
}
// Wave 20-24: Add Autotune Malicioso
else if (waveNumber >= 20 && waveNumber <= 24) {
availableTypes = ['notaDesafinada', 'glitchAudio', 'ruidoBlanco', 'ecoOscuro', 'autotuneMalicioso'];
}
// Wave 25+: All enemy types (no empowered enemies)
else {
availableTypes = ['notaDesafinada', 'glitchAudio', 'ruidoBlanco', 'ecoOscuro', 'autotuneMalicioso'];
}
return availableTypes;
}
function getEnemyTypeWeights(waveNumber, availableTypes) {
var weights = {};
// Calculate probability of strong enemy: min(0.6, 0.05 × wave)
var strongEnemyProbability = Math.min(0.6, 0.05 * waveNumber);
var weakEnemyProbability = 1 - strongEnemyProbability;
// Early waves (1-4): Only Nota Desafinada
if (waveNumber <= 4) {
weights['notaDesafinada'] = 100;
}
// Waves 5-9: Nota Desafinada + Glitch Audio
else if (waveNumber >= 5 && waveNumber <= 9) {
weights['notaDesafinada'] = Math.round(weakEnemyProbability * 100);
weights['glitchAudio'] = Math.round(strongEnemyProbability * 100);
}
// Waves 10-14: Add Ruido Blanco
else if (waveNumber >= 10 && waveNumber <= 14) {
var basicProportion = Math.round(weakEnemyProbability * 50);
var strongProportion = Math.round(strongEnemyProbability * 50);
weights['notaDesafinada'] = basicProportion;
weights['glitchAudio'] = basicProportion;
weights['ruidoBlanco'] = strongProportion * 2;
}
// Waves 15-19: Add Eco Oscuro
else if (waveNumber >= 15 && waveNumber <= 19) {
weights['notaDesafinada'] = Math.round(weakEnemyProbability * 35);
weights['glitchAudio'] = Math.round(weakEnemyProbability * 15);
weights['ruidoBlanco'] = Math.round(strongEnemyProbability * 30);
weights['ecoOscuro'] = Math.round(strongEnemyProbability * 20);
}
// Waves 20-24: Add Autotune Malicioso
else if (waveNumber >= 20 && waveNumber <= 24) {
weights['notaDesafinada'] = Math.round(weakEnemyProbability * 25);
weights['glitchAudio'] = Math.round(weakEnemyProbability * 15);
weights['ruidoBlanco'] = Math.round(strongEnemyProbability * 20);
weights['ecoOscuro'] = Math.round(strongEnemyProbability * 20);
weights['autotuneMalicioso'] = Math.round(strongEnemyProbability * 20);
}
// Wave 25+: All enemies with elite variants
else {
weights['notaDesafinada'] = 15;
weights['glitchAudio'] = 15;
weights['ruidoBlanco'] = 20;
weights['ecoOscuro'] = 30;
weights['autotuneMalicioso'] = 20;
}
return weights;
}
function spawnEnemy() {
var availableTypes = getEnemyTypesForWave(wave);
var weights = getEnemyTypeWeights(wave, availableTypes);
// Calculate total weight
var totalWeight = 0;
for (var i = 0; i < availableTypes.length; i++) {
totalWeight += weights[availableTypes[i]];
}
// Select random enemy type based on weights
var random = Math.random() * totalWeight;
var selectedType = availableTypes[0];
var currentWeight = 0;
for (var i = 0; i < availableTypes.length; i++) {
currentWeight += weights[availableTypes[i]];
if (random <= currentWeight) {
selectedType = availableTypes[i];
break;
}
}
// Remove elite enemy empowerment - no more elite enemies
var isElite = false;
// Special enemy mechanics from wave 8+
var isSpecialEnemy = false;
var specialType = '';
if (wave >= 8 && Math.random() < 0.3) {
// 30% chance for special enemy
isSpecialEnemy = true;
var specialTypes = ['spectral', 'explosive', 'disruptor', 'runner', 'armored'];
if (wave >= 8 && wave < 12) specialTypes = ['spectral', 'runner'];else if (wave >= 12 && wave < 16) specialTypes = ['spectral', 'explosive', 'runner'];else if (wave >= 16) specialTypes = ['spectral', 'explosive', 'disruptor', 'runner', 'armored'];
specialType = specialTypes[Math.floor(Math.random() * specialTypes.length)];
}
var enemy = new Enemy(selectedType, isElite, isSpecialEnemy, specialType);
enemy.x = pathPoints[0].x;
enemy.y = pathPoints[0].y;
// Scale health based on wave - more aggressive scaling
var healthMultiplier;
if (wave <= 5) {
healthMultiplier = Math.pow(1.15, wave - 1); // Increased from 1.12
} else if (wave <= 15) {
// More aggressive scaling from wave 6-15: vida = vida_base × (1.12)^wave
healthMultiplier = Math.pow(1.12, wave);
} else {
// Even more aggressive for wave 16+: vida = vida_base × (1.14)^wave
healthMultiplier = Math.pow(1.14, wave);
}
// Apply difficulty modifier for gold abuse punishment
healthMultiplier *= difficultyModifier;
enemy.health = Math.floor(enemy.health * healthMultiplier * 1.3); // Increased from 1.2
enemy.maxHealth = enemy.health;
// Apply speed modifier from wave 6+ (moved earlier)
if (wave >= 6) {
var speedIncrease = Math.floor((wave - 6) / 2) * 0.06; // 6% every 2 waves starting wave 6
enemy.speed *= (1 + speedIncrease) * enemySpeedModifier;
}
// Additional speed boost for waves 15+
if (wave >= 15) {
enemy.speed *= 1.15; // 15% additional speed boost
}
enemies.push(enemy);
game.addChild(enemy);
}
function startNextWave() {
if (gameState === 'playing') {
// Start background music on first wave
if (wave === 1) {
LK.stopMusic(); // Stop menu music
gameplayMusicPlaying = false;
playMelodia();
} else if (!musicMuted && !gameplayMusicPlaying) {
// Ensure music is playing for subsequent waves
playMelodia();
}
// Set up the wave but don't start it yet
wavePaused = true;
autoStartTimer = 0;
enemiesSpawned = 0;
enemiesPerWave = calculateEnemiesForWave(wave); // Use calculated enemy count
waveStarted = false;
// Adjust max gold per wave based on progression
if (wave >= 6) {
maxGoldPerWave = 45; // Reduced from wave 6
} else {
maxGoldPerWave = 60; // Normal for early waves
}
// Further reduce for waves with punishment
if (goldAbuseCooldown > 0) {
maxGoldPerWave = Math.round(maxGoldPerWave * 0.7); // 30% reduction
}
// Show wave announcement
var waveAnnouncement = game.addChild(new Text2('♪ WAVE ' + wave + ' READY ♪', {
size: 120,
fill: 0xFFD700,
font: "Times New Roman, 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: "Impact, sans-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) {
if (autoStartWaves) {
autoStartText.setText('Auto: Instant');
} else {
var secondsLeft = Math.ceil((300 - autoStartTimer) / 60);
if (autoStartTimer > 0) {
autoStartText.setText('Auto: ' + secondsLeft + 's');
} else {
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';
gameOverTriggered = false; // Ensure clean state for new game
// Play title sound
LK.getSound('titleSound').play();
// Create title intro container
titleIntroContainer = new Container();
game.addChild(titleIntroContainer);
// Create closed curtains covering the entire screen
leftCurtainIntro = titleIntroContainer.addChild(LK.getAsset('curtain', {
anchorX: 1,
anchorY: 0
}));
leftCurtainIntro.x = 1024; // Center of screen
leftCurtainIntro.y = 0;
leftCurtainIntro.scaleX = 3.5;
leftCurtainIntro.scaleY = 5.0;
rightCurtainIntro = titleIntroContainer.addChild(LK.getAsset('curtain', {
anchorX: 0,
anchorY: 0
}));
rightCurtainIntro.x = 1024; // Center of screen
rightCurtainIntro.y = 0;
rightCurtainIntro.scaleX = 3.5;
rightCurtainIntro.scaleY = 5.0;
// Create gothic title text
var titleText = titleIntroContainer.addChild(new Text2('Symphony Siege', {
size: 160,
fill: 0xFFD700,
font: "Impact, sans-serif"
}));
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 1366;
titleText.alpha = 0;
// Animate title appearing
tween(titleText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
// After 2 seconds, open curtains
LK.setTimeout(function () {
openCurtainsAndStartGame();
}, 1000);
}
});
}
function openCurtainsAndStartGame() {
// Play dramatic sound for curtain opening
LK.getSound('portal').play();
// Create elegant dust particles that escape as curtains begin to part
for (var i = 0; i < 15; i++) {
var dustParticle = titleIntroContainer.addChild(LK.getAsset('goldenParticle', {
anchorX: 0.5,
anchorY: 0.5
}));
dustParticle.x = 1024 + (Math.random() - 0.5) * 100;
dustParticle.y = 1366 + (Math.random() - 0.5) * 200;
dustParticle.alpha = 0.4;
dustParticle.tint = 0xFFD700;
dustParticle.scaleX = 0.3 + Math.random() * 0.4;
dustParticle.scaleY = 0.3 + Math.random() * 0.4;
// Animate dust particles floating and dispersing
tween(dustParticle, {
x: dustParticle.x + (Math.random() - 0.5) * 300,
y: dustParticle.y - 200 - Math.random() * 400,
rotation: Math.PI * 2 * Math.random(),
alpha: 0,
scaleX: dustParticle.scaleX * 1.5,
scaleY: dustParticle.scaleY * 1.5
}, {
duration: 3000 + Math.random() * 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
dustParticle.destroy();
}
});
}
// Enhanced curtain opening sequence with natural fabric physics
// Phase 1: Pre-tension - curtains gather tension before opening
tween(leftCurtainIntro, {
x: leftCurtainIntro.x + 8,
scaleX: 1.85,
// Slight horizontal compression
scaleY: 2.95,
// Slight vertical compression
rotation: 0.015
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Phase 2: Release tension with slight overshoot
tween(leftCurtainIntro, {
x: leftCurtainIntro.x - 25,
scaleX: 1.82,
scaleY: 3.05,
// Slight stretch as tension releases
rotation: -0.02
}, {
duration: 350,
easing: tween.easeOut,
onFinish: function onFinish() {
// Phase 3: Main opening with natural deceleration and fabric sway
tween(leftCurtainIntro, {
x: -900,
// Further offscreen for complete reveal
rotation: -0.08,
// More pronounced rotation as heavy fabric moves
scaleX: 1.75,
// Fabric compresses horizontally as it moves
scaleY: 3.2,
// Vertical stretch from movement momentum
alpha: 0.85 // Slight fade as it moves away
}, {
duration: 3200,
// Longer duration for more majestic feel
easing: tween.easeOut,
// Natural deceleration at the end
onFinish: function onFinish() {
// Phase 4: Final settle with gentle sway
tween(leftCurtainIntro, {
rotation: -0.04,
scaleY: 3.0,
alpha: 0.7
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
});
}
});
// Mirror animation for right curtain with asymmetric timing for natural feel
LK.setTimeout(function () {
tween(rightCurtainIntro, {
x: rightCurtainIntro.x - 8,
scaleX: 1.85,
scaleY: 2.95,
rotation: -0.015
}, {
duration: 450,
// Slightly different timing
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(rightCurtainIntro, {
x: rightCurtainIntro.x + 25,
scaleX: 1.82,
scaleY: 3.05,
rotation: 0.02
}, {
duration: 320,
// Different timing for asymmetry
easing: tween.easeOut,
onFinish: function onFinish() {
// Main opening for right curtain
tween(rightCurtainIntro, {
x: 2950,
// Further offscreen
rotation: 0.08,
scaleX: 1.75,
scaleY: 3.2,
alpha: 0.85
}, {
duration: 3100,
// Slightly different duration
easing: tween.easeOut,
onFinish: function onFinish() {
// Final settle for right curtain
tween(rightCurtainIntro, {
rotation: 0.04,
scaleY: 3.0,
alpha: 0.7
}, {
duration: 750,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Clean up and start game after both curtains have settled
titleIntroContainer.destroy();
gameState = 'playing';
gameOverTriggered = false; // Ensure clean state for actual gameplay
startNextWave();
}
});
}
});
}
});
}
});
}, 100); // Small delay to start right curtain slightly after left
// Enhanced atmospheric effects during opening
// Magical glow effect that builds as curtains part
var magicalGlow = titleIntroContainer.addChild(LK.getAsset('spotlight', {
anchorX: 0.5,
anchorY: 0.5
}));
magicalGlow.x = 1024;
magicalGlow.y = 1366;
magicalGlow.alpha = 0;
magicalGlow.tint = 0xFFD700;
magicalGlow.scaleX = 0.5;
magicalGlow.scaleY = 0.5;
// Glow builds up as curtains open
tween(magicalGlow, {
alpha: 0.6,
scaleX: 4,
scaleY: 4
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Glow disperses as opening completes
tween(magicalGlow, {
alpha: 0,
scaleX: 8,
scaleY: 8
}, {
duration: 1500,
easing: tween.easeIn,
onFinish: function onFinish() {
magicalGlow.destroy();
}
});
}
});
// Multiple floating musical notes with varied timing
for (var i = 0; i < 8; i++) {
LK.setTimeout(function (noteIndex) {
return function () {
var musicNote = titleIntroContainer.addChild(LK.getAsset('musicNote', {
anchorX: 0.5,
anchorY: 0.5
}));
musicNote.x = 800 + Math.random() * 448; // Spread across center area
musicNote.y = 1200 + Math.random() * 300;
musicNote.alpha = 0.5 + Math.random() * 0.3;
musicNote.scaleX = 0.8 + Math.random() * 0.6;
musicNote.scaleY = 0.8 + Math.random() * 0.6;
musicNote.tint = 0xFFD700;
// Each note follows unique floating path
var targetX = musicNote.x + (Math.random() - 0.5) * 600;
var targetY = musicNote.y - 400 - Math.random() * 500;
tween(musicNote, {
x: targetX,
y: targetY,
rotation: Math.PI * 3 * (Math.random() - 0.5),
alpha: 0,
scaleX: musicNote.scaleX * 1.8,
scaleY: musicNote.scaleY * 1.8
}, {
duration: 2500 + Math.random() * 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
musicNote.destroy();
}
});
};
}(i), i * 200 + Math.random() * 300); // Staggered timing
}
// Subtle orchestral crescendo effect with screen tint
var crescendoOverlay = titleIntroContainer.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
crescendoOverlay.x = 1024;
crescendoOverlay.y = 1366;
crescendoOverlay.scaleX = 5;
crescendoOverlay.scaleY = 7;
crescendoOverlay.alpha = 0;
crescendoOverlay.tint = 0x8B4513; // Warm brown crescendo
// Build crescendo effect
tween(crescendoOverlay, {
alpha: 0.15
}, {
duration: 1800,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Fade crescendo as climax approaches
tween(crescendoOverlay, {
alpha: 0
}, {
duration: 1200,
easing: tween.easeOut,
onFinish: function onFinish() {
crescendoOverlay.destroy();
}
});
}
});
// Enhanced screen flash with golden warmth as curtains fully part
LK.setTimeout(function () {
LK.effects.flashScreen(0xFFE55C, 500); // Warm golden flash instead of stark white
}, 2800); // Timing adjusted for new animation length
}
function showCreditsScreen() {
showingCredits = true;
gameState = 'credits';
// Create credits container
creditsContainer = new Container();
game.addChild(creditsContainer);
// Credits background
var creditsBackground = creditsContainer.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
creditsBackground.x = 1024;
creditsBackground.y = 1366;
creditsBackground.scaleX = 4.5;
creditsBackground.scaleY = 6;
creditsBackground.alpha = 0.95;
creditsBackground.tint = 0x2D1B0E;
// Credits title
var creditsTitle = creditsContainer.addChild(new Text2('♪ CREDITS ♪', {
size: 120,
fill: 0xFFD700,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
creditsTitle.anchor.set(0.5, 0.5);
creditsTitle.x = 1024;
creditsTitle.y = 400;
// Attribution text for Udio music
var attributionText = creditsContainer.addChild(new Text2('Music generated with Udio\n\nThis game includes AI-generated music\ncreated using Udio.\n\nAll tracks were produced by the developer\nvia Udio\'s platform under its current\nterms of use.', {
size: 55,
fill: 0xF5F5DC,
font: "Georgia, serif"
}));
attributionText.anchor.set(0.5, 0.5);
attributionText.x = 1024;
attributionText.y = 1000;
// Continue button
var continueButton = creditsContainer.addChild(LK.getAsset('trumpetTower', {
anchorX: 0.5,
anchorY: 0.5
}));
continueButton.x = 1024;
continueButton.y = 1600;
continueButton.scaleX = 1.2;
continueButton.scaleY = 1.2;
var continueButtonText = creditsContainer.addChild(new Text2('♪ CONTINUE ♪', {
size: 65,
fill: 0xFFD700,
font: "Impact, sans-serif"
}));
continueButtonText.anchor.set(0.5, 0.5);
continueButtonText.x = 1024;
continueButtonText.y = 1700;
// Animate credits appearing
creditsContainer.alpha = 0;
tween(creditsContainer, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeOut
});
// Continue button handler
continueButton.down = function () {
hideCreditsScreen();
};
}
function enterGrandPianoTargetingMode() {
if (grandPianoGhostMode) return;
grandPianoGhostMode = true;
hideUpgradeMenu();
hideBuildMenu();
// Create ghost piano that follows mouse
grandPianoGhost = game.addChild(LK.getAsset('grandPianoGhost', {
anchorX: 0.5,
anchorY: 0.5
}));
grandPianoGhost.alpha = 0.6;
grandPianoGhost.tint = 0x00FFFF;
// Create targeting area indicator
grandPianoTargetArea = game.addChild(LK.getAsset('grandPianoShadow', {
anchorX: 0.5,
anchorY: 0.5
}));
grandPianoTargetArea.alpha = 0.3;
grandPianoTargetArea.tint = 0xFF0000;
updateGrandPianoPowerUI();
}
function exitGrandPianoTargetingMode() {
if (!grandPianoGhostMode) return;
grandPianoGhostMode = false;
if (grandPianoGhost) {
grandPianoGhost.destroy();
grandPianoGhost = null;
}
if (grandPianoTargetArea) {
grandPianoTargetArea.destroy();
grandPianoTargetArea = null;
}
}
function activateGrandPianoPowerAt(targetX, targetY) {
exitGrandPianoTargetingMode();
activateGrandPianoPower(targetX, targetY);
}
function activateGrandPianoPower(targetX, targetY) {
if (grandPianoPowerActive) return;
grandPianoPowerActive = true;
grandPianoCooldownRemaining = 2; // 2 wave cooldown
updateGrandPianoPowerUI();
// Use center of screen as default if no target provided
var impactX = targetX || 1024;
var impactY = targetY || 1366;
// Play dramatic piano chord
LK.getSound('grandPianoChord').play();
// Create falling piano animation
var fallingPiano = game.addChild(LK.getAsset('grandPianoFalling', {
anchorX: 0.5,
anchorY: 0.5
}));
fallingPiano.x = impactX;
fallingPiano.y = -200; // Start above screen
fallingPiano.rotation = 0.1;
fallingPiano.alpha = 0.9;
// Create piano keys falling effect around target area
for (var i = 0; i < 12; i++) {
var pianoKey = game.addChild(LK.getAsset('pianoKey', {
anchorX: 0.5,
anchorY: 0.5
}));
pianoKey.x = impactX - 200 + i * 35; // Centered around impact point
pianoKey.y = -100 - Math.random() * 200;
pianoKey.rotation = Math.random() * Math.PI;
pianoKey.tint = i % 2 === 0 ? 0xFFFFFF : 0x000000;
// Animate piano key falling
tween(pianoKey, {
y: 2800,
rotation: pianoKey.rotation + Math.PI * 4,
alpha: 0
}, {
duration: 2000 + Math.random() * 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (pianoKey.parent) {
pianoKey.destroy();
}
}
});
}
// Animate piano falling with dramatic effect
tween(fallingPiano, {
y: impactY,
// Target position
rotation: 0.3,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1500,
easing: tween.easeIn,
onFinish: function onFinish() {
// Piano impact effect
LK.getSound('grandPianoImpact').play();
// Screen shake effect
tween(game, {
x: 20
}, {
duration: 100,
onFinish: function onFinish() {
tween(game, {
x: -20
}, {
duration: 100,
onFinish: function onFinish() {
tween(game, {
x: 0
}, {
duration: 100
});
}
});
}
});
// Damage enemies within reduced area (120 pixels instead of all screen)
var areaRadius = 120; // Reduced from affecting all enemies on screen
var enemiesHit = 0;
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
// Check if enemy is within area of effect
var dx = enemy.x - impactX;
var dy = enemy.y - impactY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= areaRadius) {
// Scale damage based on wave: 300 + (wave * 25) for progression
var scaledDamage = 300 + wave * 25;
var died = enemy.takeDamage(scaledDamage);
if (died) {
// Track enemy killed by Grand Piano
totalEnemiesKilled++;
// Calculate gold reward for enemies killed by Grand Piano
var goldReward = enemy.goldValue;
// Apply wave restrictions for gold
if (wave >= 6) {
goldReward = Math.min(goldReward, 3);
}
// Ensure minimum gold reward of 1 for all enemies from wave 10+
if (wave >= 10 && goldReward === 0) {
goldReward = 1;
}
// Check wave gold limit (before adding universal bonus)
if (lastWaveGoldEarned + goldReward > maxGoldPerWave) {
goldReward = Math.max(1, maxGoldPerWave - lastWaveGoldEarned);
}
// Add universal +3 bonus for ALL enemies from wave 10+ (after all other restrictions)
if (wave >= 10) {
goldReward += 3;
console.log("Grand Piano Wave " + wave + ": Added +3 universal bonus, total gold reward:", goldReward);
}
// Add additional +2 gold bonus for ALL enemies from wave 11+ (after all other restrictions)
if (wave >= 11) {
goldReward += 2;
console.log("Grand Piano Wave " + wave + ": Added +2 wave 11+ bonus, total gold reward:", goldReward);
}
// Add additional +2 gold bonus for ALL enemies from wave 12+ (after all other restrictions)
if (wave >= 12) {
goldReward += 2;
console.log("Grand Piano Wave " + wave + ": Added +2 additional bonus, total gold reward:", goldReward);
}
// Add additional +3 gold bonus for ALL enemies from wave 13+ (after all other restrictions)
if (wave >= 13) {
goldReward += 3;
console.log("Grand Piano Wave " + wave + ": Added +3 wave 13+ bonus, total gold reward:", goldReward);
}
// Apply gold reward
if (goldReward > 0) {
gold += goldReward;
lastWaveGoldEarned += goldReward;
}
updateUI();
LK.getSound('enemyDeath').play();
} else {
// Stun surviving enemies for 3 seconds
enemy.grandPianoStunnedUntil = LK.ticks + 180; // 3 seconds at 60fps
// Visual stun effect
var enemyGraphics = enemy.getChildAt(0);
if (enemyGraphics) {
var originalTint = enemyGraphics.tint || 0xffffff;
tween(enemyGraphics, {
tint: 0xFFD700,
// Gold stun effect
scaleX: 0.7,
scaleY: 0.7
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
LK.setTimeout(function () {
if (enemyGraphics && enemyGraphics.parent) {
tween(enemyGraphics, {
tint: originalTint,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400
});
}
}, 180 * 16); // Stun duration
}
});
}
}
enemiesHit++;
}
}
// Show impact effect at target location
var impactEffect = game.addChild(LK.getAsset('spotlight', {
anchorX: 0.5,
anchorY: 0.5
}));
impactEffect.x = impactX;
impactEffect.y = impactY;
impactEffect.tint = 0xFFD700;
impactEffect.alpha = 0.8;
impactEffect.scaleX = 0.5;
impactEffect.scaleY = 0.5;
tween(impactEffect, {
scaleX: 6,
// Smaller impact effect to match reduced area
scaleY: 6,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
impactEffect.destroy();
}
});
// Piano disappears after impact
tween(fallingPiano, {
alpha: 0,
scaleY: 0.1,
y: fallingPiano.y + 200
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
fallingPiano.destroy();
grandPianoPowerActive = false;
}
});
}
});
}
function updateGrandPianoPowerUI() {
if (!grandPianoPowerUnlocked) {
grandPianoButtonBackground.visible = false;
grandPianoPowerButton.visible = false;
grandPianoPowerText.visible = false;
grandPianoCooldownText.visible = false;
return;
}
grandPianoButtonBackground.visible = true;
grandPianoPowerButton.visible = true;
grandPianoPowerText.visible = true;
grandPianoCooldownText.visible = true;
if (grandPianoGhostMode) {
grandPianoCooldownText.setText('Click to target!');
grandPianoCooldownText.tint = 0x00FFFF;
grandPianoPowerButton.tint = 0x00FFFF;
grandPianoPowerButton.alpha = 1.0;
// Add pulsing effect during targeting
var pulseScale = 1.0 + Math.sin(LK.ticks * 0.2) * 0.1;
grandPianoPowerButton.scaleX = pulseScale;
grandPianoPowerButton.scaleY = pulseScale;
} else if (grandPianoCooldownRemaining > 0) {
grandPianoCooldownText.setText(grandPianoCooldownRemaining + ' waves left');
grandPianoCooldownText.tint = 0x00FFFF;
grandPianoPowerButton.tint = 0x666666;
grandPianoPowerButton.alpha = 0.6;
grandPianoPowerButton.scaleX = 1.0;
grandPianoPowerButton.scaleY = 1.0;
// Visual cooldown progress - create loading circle effect
var cooldownProgress = 1.0 - grandPianoCooldownRemaining / 2.0;
var progressAngle = cooldownProgress * Math.PI * 2;
grandPianoPowerButton.rotation = progressAngle;
} else {
grandPianoCooldownText.setText('Ready!');
grandPianoCooldownText.tint = 0x00FFFF;
grandPianoPowerButton.tint = 0xFFFFFF;
grandPianoPowerButton.alpha = 1.0;
grandPianoPowerButton.scaleX = 1.0;
grandPianoPowerButton.scaleY = 1.0;
grandPianoPowerButton.rotation = 0;
}
}
function showGrandPianoUnlockAnimation() {
// Create chest that appears in center of screen
var musicChest = game.addChild(LK.getAsset('musicChest', {
anchorX: 0.5,
anchorY: 0.5
}));
musicChest.x = 1024;
musicChest.y = 1366;
musicChest.scaleX = 2.0;
musicChest.scaleY = 2.0;
musicChest.alpha = 0;
musicChest.tint = 0x8B4513;
// Chest appears with light effect
var chestLight = game.addChild(LK.getAsset('spotlight', {
anchorX: 0.5,
anchorY: 0.5
}));
chestLight.x = 1024;
chestLight.y = 1366;
chestLight.tint = 0xFFD700;
chestLight.alpha = 0;
chestLight.scaleX = 3;
chestLight.scaleY = 3;
// Animate chest appearing
tween(musicChest, {
alpha: 1,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 1000,
easing: tween.easeOut
});
tween(chestLight, {
alpha: 0.6
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Chest opens with sound
LK.getSound('chestOpen').play();
// Create golden particles exploding from chest
for (var i = 0; i < 20; i++) {
var particle = game.addChild(LK.getAsset('goldenParticle', {
anchorX: 0.5,
anchorY: 0.5
}));
particle.x = 1024;
particle.y = 1366;
particle.tint = 0xFFD700;
particle.scaleX = 0.5;
particle.scaleY = 0.5;
var angle = Math.PI * 2 * i / 20;
var speed = 200 + Math.random() * 150;
var targetX = 1024 + Math.cos(angle) * speed;
var targetY = 1366 + Math.sin(angle) * speed;
tween(particle, {
x: targetX,
y: targetY,
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Grand Piano emerges from chest
LK.setTimeout(function () {
var emergingPiano = game.addChild(LK.getAsset('grandPiano', {
anchorX: 0.5,
anchorY: 1
}));
emergingPiano.x = 1024;
emergingPiano.y = 1366;
emergingPiano.scaleX = 0.1;
emergingPiano.scaleY = 0.1;
emergingPiano.alpha = 0;
emergingPiano.rotation = Math.PI * 2;
// Play fanfare music
LK.playMusic('fanfare', {
loop: false
});
// Piano emerges and floats upward with light from below
var pianoLight = game.addChild(LK.getAsset('spotlight', {
anchorX: 0.5,
anchorY: 0.5
}));
pianoLight.x = 1024;
pianoLight.y = 1500;
pianoLight.tint = 0x00FFFF;
pianoLight.alpha = 0;
pianoLight.scaleX = 2;
pianoLight.scaleY = 4;
tween(pianoLight, {
alpha: 0.8
}, {
duration: 2000
});
tween(emergingPiano, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5,
y: 1200,
rotation: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Piano moves to its UI position
var targetX = grandPianoPowerButton.x + 200;
var targetY = grandPianoPowerButton.y - 400;
tween(emergingPiano, {
x: targetX,
y: targetY,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Unlock power and show UI
grandPianoPowerUnlocked = true;
updateGrandPianoPowerUI();
// Restart Melodia after Grand Piano unlock animation
if (!musicMuted) {
console.log("Restarting Melodia after Grand Piano unlock");
LK.setTimeout(function () {
playMelodia();
}, 1000);
}
// Show unlock message
var unlockMsg = game.addChild(new Text2('♪ GRAND PIANO POWER UNLOCKED! ♪\nTarget and unleash devastating musical might!', {
size: 70,
fill: 0xFFD700,
font: "Times New Roman, serif"
}));
unlockMsg.anchor.set(0.5, 0.5);
unlockMsg.x = 1024;
unlockMsg.y = 1000;
unlockMsg.alpha = 0;
tween(unlockMsg, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(unlockMsg, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
unlockMsg.destroy();
}
});
}, 3000);
}
});
// Clean up animation objects
emergingPiano.destroy();
pianoLight.destroy();
chestLight.destroy();
musicChest.destroy();
}
});
}
});
}, 1000);
}
});
}
function showEnhancedGameOver() {
gameState = 'gameOver';
// Set lives counter to 0 after 0.5 seconds when game over screen is displayed
tween({}, {}, {
duration: 500,
onFinish: function onFinish() {
lives = 0;
updateUI();
}
});
// Reset game speed to 1x when game over screen is presented
gameSpeed = 1;
speedText.setText('♪ SPEED: 1X ♪');
speedText.tint = 0xFFFFFF; // White color to match 1x icon
speedStatusText.setText('Normal');
speedButton.tint = 0xFFFFFF; // Normal tint
// Clear all active effects and animations immediately
// Stop all tweens on bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet && bullet.parent) {
bullet.destroy();
}
bullets.splice(i, 1);
}
// Clear all floating notes
for (var i = floatingNotes.length - 1; i >= 0; i--) {
var note = floatingNotes[i];
if (note && note.parent) {
note.destroy();
}
floatingNotes.splice(i, 1);
}
// Clear video tape effects
if (fastForwardOverlay && fastForwardOverlay.parent) {
fastForwardOverlay.destroy();
fastForwardOverlay = null;
}
// Clear scan lines
for (var i = scanLines.length - 1; i >= 0; i--) {
if (scanLines[i] && scanLines[i].parent) {
scanLines[i].destroy();
}
scanLines.splice(i, 1);
}
// Kill all existing enemies immediately
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy && enemy.parent) {
enemy.destroy();
}
enemies.splice(i, 1);
}
// Kill all existing enemies immediately
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy && enemy.parent) {
enemy.destroy();
}
enemies.splice(i, 1);
}
// Destroy all existing towers immediately
for (var i = towers.length - 1; i >= 0; i--) {
var tower = towers[i];
if (tower && tower.parent) {
tower.destroy();
}
towers.splice(i, 1);
}
// Create game over container
var gameOverContainer = new Container();
game.addChild(gameOverContainer);
// Game over background
var gameOverBackground = gameOverContainer.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0.5
}));
gameOverBackground.x = 1024;
gameOverBackground.y = 1366;
gameOverBackground.scaleX = 4.5;
gameOverBackground.scaleY = 6;
gameOverBackground.alpha = 0.95;
gameOverBackground.tint = 0x2D1B0E;
// Game Over title
var gameOverTitle = gameOverContainer.addChild(new Text2('♪ GAME OVER ♪', {
size: 120,
fill: 0xFF4444,
font: "'Blackletter', 'Old English Text MT', 'Germania One', serif"
}));
gameOverTitle.anchor.set(0.5, 0.5);
gameOverTitle.x = 1024;
gameOverTitle.y = 400;
// Statistics text
var statsText = gameOverContainer.addChild(new Text2('Total enemies killed: ' + totalEnemiesKilled + '\n\n' + 'Wave number: ' + wave + '\n\n' + 'Gold spent: $' + totalGoldSpent, {
size: 70,
fill: 0xF5F5DC,
font: "Times New Roman, serif"
}));
statsText.anchor.set(0.5, 0.5);
statsText.x = 1024;
statsText.y = 1000;
// Restart button
var restartButton = gameOverContainer.addChild(LK.getAsset('restart', {
anchorX: 0.5,
anchorY: 0.5
}));
restartButton.x = 1024;
restartButton.y = 1600;
restartButton.scaleX = 1.5;
restartButton.scaleY = 1.5;
// Add instruction text for double-click requirement
var doubleClickInstruction = gameOverContainer.addChild(new Text2('♪ DOUBLE-CLICK TO RESTART ♪', {
size: 55,
fill: 0x00FFFF,
font: "Impact, sans-serif"
}));
doubleClickInstruction.anchor.set(0.5, 0.5);
doubleClickInstruction.x = 1024;
doubleClickInstruction.y = 1750;
// Animate game over screen appearing
gameOverContainer.alpha = 0;
tween(gameOverContainer, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeOut
});
// Double-click tracking variables for restart button
var restartClickCount = 0;
var restartClickTimer = 0;
var restartClickTimeout = 90; // 1.5 seconds at 60fps for double-click window
// Restart button handler with double-click requirement
restartButton.down = function () {
console.log("Restart button clicked - click count:", restartClickCount + 1);
// Increment click count
restartClickCount++;
restartClickTimer = 0; // Reset timer
// Play immediate click feedback
LK.getSound('build').play();
// Check if this is the second click within the timeout window
if (restartClickCount >= 2) {
console.log("Double-click detected - performing immediate full game restart");
// Reset click tracking
restartClickCount = 0;
restartClickTimer = 0;
// Disable the restart button to prevent multiple clicks during transition
restartButton.tint = 0x666666;
restartButton.alpha = 0.5;
// Perform complete game reset immediately without time-based transitions
console.log("Performing complete game reset");
// 1. Stop all in-game music and restart background track from beginning
try {
LK.stopMusic();
} catch (error) {
console.log("Error stopping music:", error);
}
gameplayMusicPlaying = false;
// 2. Reset the wave counter to 1
wave = 1;
// 3. Set player's gold back to starting amount
gold = 150;
// 4. Remove all towers placed on map and reset upgrades
for (var i = towers.length - 1; i >= 0; i--) {
var tower = towers[i];
if (tower && tower.parent) {
tower.destroy();
}
}
towers = [];
// 5. Clear all active enemies, projectiles, and effects from field
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy && enemy.parent) {
enemy.destroy();
}
}
enemies = [];
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet && bullet.parent) {
bullet.destroy();
}
}
bullets = [];
// Clear floating visual effects
for (var i = floatingNotes.length - 1; i >= 0; i--) {
var note = floatingNotes[i];
if (note && note.parent) {
note.destroy();
}
}
floatingNotes = [];
// 6. Reset all cooldowns and special powers (Grand Piano)
grandPianoPowerUnlocked = false;
grandPianoCooldownRemaining = 0;
grandPianoUnlockAnimationShown = false;
grandPianoPowerActive = false;
grandPianoGhostMode = false;
// Clean up Grand Piano ghost objects
if (grandPianoGhost) {
grandPianoGhost.destroy();
grandPianoGhost = null;
}
if (grandPianoTargetArea) {
grandPianoTargetArea.destroy();
grandPianoTargetArea = null;
}
// 7. Reset all game variables to initial state
totalEnemiesKilled = 0;
totalGoldSpent = 0;
lives = 40;
enemiesSpawned = 0;
enemiesPerWave = 10;
spawnTimer = 0;
waveStarted = false;
wavePaused = true;
autoStartTimer = 0;
// 8. Reset game speed and UI indicators immediately
gameSpeed = 1;
speedText.setText('♪ SPEED: 1X ♪');
speedText.tint = 0xFFFFFF;
speedStatusText.setText('Normal');
speedButton.tint = 0xFFFFFF;
// 9. Re-initialize enemy path and tower placement areas
for (var i = 0; i < buildSpots.length; i++) {
buildSpots[i].occupied = false;
buildSpots[i].visible = false;
}
// 10. Clean up video tape effects
if (fastForwardOverlay && fastForwardOverlay.parent) {
fastForwardOverlay.destroy();
fastForwardOverlay = null;
}
// Clean up scan lines
for (var i = scanLines.length - 1; i >= 0; i--) {
if (scanLines[i] && scanLines[i].parent) {
scanLines[i].destroy();
}
scanLines.splice(i, 1);
}
// 11. Reset UI menus and interactions
showingBuildMenu = false;
selectedBuildSpot = null;
showingUpgradeMenu = false;
selectedTower = null;
// Hide all menu containers
buildMenuContainer.visible = false;
upgradeMenuContainer.visible = false;
// 12. Reset difficulty and progression systems
lastWaveGoldEarned = 0;
difficultyModifier = 1.0;
enemySpeedModifier = 1.0;
forceExpenseEvent = false;
currentSpecialWave = null;
goldAbuseCooldown = 0;
highGoldWaveCount = 0;
maxGoldPerWave = 60;
// 13. Hide game elements and return to clean battlefield
goal.visible = false;
// Hide game over container if it exists
if (gameOverContainer && gameOverContainer.parent) {
gameOverContainer.destroy();
}
// 14. Update UI to reflect reset state
updateUI();
updateGrandPianoPowerUI();
// 13. Reset game state for new session
gameState = 'menu';
gameOverTriggered = false; // Reset game over flag for new game
// Show main menu
menuContainer.visible = true;
// 16. Restart background music from beginning
if (!musicMuted) {
console.log("Restarting background music from beginning");
try {
LK.playMusic('MusicaInicio', {
loop: true,
volume: musicVolume
});
// Play initial menu sounds in sequence
var vozSound = LK.getSound('Voz');
vozSound.play();
LK.setTimeout(function () {
LK.getSound('Risa').play();
}, 3000);
} catch (error) {
console.log("Error starting background music:", error);
}
}
// Re-enable restart button immediately
restartButton.tint = 0xFFFFFF;
restartButton.alpha = 1.0;
console.log("Game restart completed immediately - clean battlefield ready");
} else {
console.log("First click detected - click again quickly to restart");
// Provide visual feedback for first click
tween(restartButton, {
scaleX: 1.7,
scaleY: 1.7
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(restartButton, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 100,
easing: tween.easeOut
});
}
});
}
};
function performRestartImmediately() {
// Play immediate click feedback
LK.getSound('build').play();
// Stop all music immediately
try {
LK.stopMusic();
} catch (error) {
console.log("Error stopping music:", error);
}
gameplayMusicPlaying = false;
// Hide game over container immediately
if (gameOverContainer && gameOverContainer.parent) {
gameOverContainer.destroy();
}
// Reset game state to menu immediately
gameState = 'menu';
gameOverTriggered = false; // Reset game over flag for new game
// Show menu container (initial start screen)
menuContainer.visible = true;
// Clear all game arrays
enemies = [];
towers = [];
bullets = [];
floatingNotes = [];
// Reset all game variables to initial values
totalEnemiesKilled = 0;
totalGoldSpent = 0;
gold = 150;
lives = 40;
wave = 1;
enemiesSpawned = 0;
enemiesPerWave = 10;
spawnTimer = 0;
waveStarted = false;
wavePaused = true;
autoStartTimer = 0;
// Reset game speed to normal
gameSpeed = 1;
speedText.setText('♪ SPEED: 1X ♪');
speedText.tint = 0xFFFFFF; // Reset to white color to match 1x icon
speedStatusText.setText('Normal');
speedButton.tint = 0xFFFFFF;
// Clean up video tape effects
if (fastForwardOverlay && fastForwardOverlay.parent) {
fastForwardOverlay.destroy();
fastForwardOverlay = null;
}
// Clean up scan lines
for (var i = scanLines.length - 1; i >= 0; i--) {
if (scanLines[i] && scanLines[i].parent) {
scanLines[i].destroy();
}
scanLines.splice(i, 1);
}
showingBuildMenu = false;
selectedBuildSpot = null;
showingUpgradeMenu = false;
selectedTower = null;
// Reset Grand Piano power system
grandPianoPowerUnlocked = false;
grandPianoCooldownRemaining = 0;
grandPianoUnlockAnimationShown = false;
grandPianoPowerActive = false;
grandPianoGhostMode = false;
// Clean up Grand Piano ghost objects
if (grandPianoGhost) {
grandPianoGhost.destroy();
grandPianoGhost = null;
}
if (grandPianoTargetArea) {
grandPianoTargetArea.destroy();
grandPianoTargetArea = null;
}
// Reset difficulty and gold tracking
lastWaveGoldEarned = 0;
difficultyModifier = 1.0;
enemySpeedModifier = 1.0;
forceExpenseEvent = false;
currentSpecialWave = null;
goldAbuseCooldown = 0;
highGoldWaveCount = 0;
maxGoldPerWave = 60;
// Reset build spots
for (var i = 0; i < buildSpots.length; i++) {
buildSpots[i].occupied = false;
buildSpots[i].visible = false;
}
// Hide all menu containers
buildMenuContainer.visible = false;
upgradeMenuContainer.visible = false;
// Hide game elements (return to menu state)
goal.visible = false;
// Update UI to reflect reset state
updateUI();
updateGrandPianoPowerUI();
// Start menu music
if (!musicMuted) {
console.log("Starting menu music");
try {
LK.playMusic('MusicaInicio', {
loop: true
});
// Don't play Voz and Risa sounds on restart
} catch (error) {
console.log("Error starting menu music:", error);
}
}
console.log("Restart completed - returned to start screen");
}
;
}
function hideCreditsScreen() {
showingCredits = false;
// Animate credits disappearing
tween(creditsContainer, {
alpha: 0
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
creditsContainer.destroy();
gameState = 'playing';
// Now advance to wave 101
wave++;
waveStarted = false;
gold += 75; // Bonus gold for completing wave
updateUI();
// Continue with normal wave progression
LK.setTimeout(function () {
startNextWave();
}, 1000);
}
});
}
function showSettings() {
showingSettings = true;
settingsContainer.visible = true;
game.removeChild(settingsContainer);
game.addChild(settingsContainer);
updateSettingsDisplay();
}
function hideSettings() {
if (showingLanguageDropdown) {
hideLanguageDropdown();
}
showingSettings = false;
settingsContainer.visible = false;
gameState = 'menu';
menuContainer.visible = true;
}
function updateSettingsDisplay() {
var settingsText = getSettingsText();
// Update all settings labels with localized text
settingsTitle.setText(settingsText.title);
musicVolumeLabel.setText(settingsText.musicVolume);
soundVolumeLabel.setText(settingsText.soundEffectsVolume);
autoStartLabel.setText(settingsText.autoStartWaves);
damageNumbersLabel.setText(settingsText.showDamageNumbers);
languageLabel.setText(settingsText.language);
resetDefaultsText.setText(settingsText.resetDefaults);
settingsBackText.setText(settingsText.backToMenu);
// Update values with localized ON/OFF text
musicVolumeValue.setText(Math.round(musicVolume * 100) + '%');
soundVolumeValue.setText(Math.round(soundEffectsVolume * 100) + '%');
autoStartValue.setText(autoStartWaves ? settingsText.on : settingsText.off);
autoStartValue.fill = 0x00FFFF; // Cyan for both ON and OFF
damageNumbersValue.setText(showDamageNumbers ? settingsText.on : settingsText.off);
damageNumbersValue.fill = 0x00FFFF; // Cyan for both ON and OFF
// Find current language in available languages list
var currentLang = availableLanguages.find(function (lang) {
return lang.code === gameLanguage;
});
languageValue.setText(currentLang ? currentLang.nativeName : 'English');
}
function saveSettings() {
storage.musicVolume = musicVolume;
storage.soundEffectsVolume = soundEffectsVolume;
storage.autoStartWaves = autoStartWaves;
storage.showDamageNumbers = showDamageNumbers;
storage.gameLanguage = gameLanguage;
}
function showLanguageDropdown() {
if (showingLanguageDropdown) return;
showingLanguageDropdown = true;
// Create dropdown container
languageDropdownContainer = new Container();
settingsContainer.addChild(languageDropdownContainer);
// Position dropdown below language button
languageDropdownContainer.x = 1024;
languageDropdownContainer.y = 1320;
// Create elegant background panel for dropdown
var dropdownBackground = languageDropdownContainer.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0
}));
dropdownBackground.scaleX = 2.0;
dropdownBackground.scaleY = 2.5;
dropdownBackground.alpha = 0.95;
dropdownBackground.tint = 0x654321;
// Create language option buttons
var optionHeight = 60;
var startY = 20;
for (var i = 0; i < availableLanguages.length; i++) {
var lang = availableLanguages[i];
var optionY = startY + i * optionHeight;
// Language option background (for click detection)
var optionBg = languageDropdownContainer.addChild(LK.getAsset('woodPanel', {
anchorX: 0.5,
anchorY: 0
}));
optionBg.x = 0;
optionBg.y = optionY;
optionBg.scaleX = 1.8;
optionBg.scaleY = 0.4;
optionBg.alpha = gameLanguage === lang.code ? 0.3 : 0.1;
optionBg.tint = gameLanguage === lang.code ? 0xFFD700 : 0x8B4513;
// Language text
var langText = languageDropdownContainer.addChild(new Text2(lang.nativeName, {
size: 45,
fill: gameLanguage === lang.code ? 0xFFD700 : 0xF5F5DC,
font: "Georgia, serif"
}));
langText.anchor.set(0.5, 0.5);
langText.x = 0;
langText.y = optionY + 20;
// Store language code for click handler
optionBg.languageCode = lang.code;
// Click handler for language selection
optionBg.down = function (x, y, obj) {
selectLanguage(this.languageCode);
};
}
// Animate dropdown appearing
languageDropdownContainer.alpha = 0;
languageDropdownContainer.scaleY = 0.1;
tween(languageDropdownContainer, {
alpha: 1,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeOut
});
// Play soft confirmation sound
LK.getSound('build').play();
}
function hideLanguageDropdown() {
if (!showingLanguageDropdown || !languageDropdownContainer) return;
showingLanguageDropdown = false;
tween(languageDropdownContainer, {
alpha: 0,
scaleY: 0.1
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (languageDropdownContainer && languageDropdownContainer.parent) {
languageDropdownContainer.destroy();
languageDropdownContainer = null;
}
}
});
}
function selectLanguage(languageCode) {
// Play soft confirmation sound
LK.getSound('build').play();
// Update language
gameLanguage = languageCode;
updateSettingsDisplay();
saveSettings();
// Hide dropdown
hideLanguageDropdown();
// Update all UI text immediately based on selected language
updateAllUIText();
}
function updateAllUIText() {
// Update tutorial descriptions for towers and enemies based on new language
towerInfos = getTowerInfos();
enemyInfos = getEnemyInfos();
// If currently viewing tutorial mode, refresh the display
if (gameState === 'tutorial' && (currentInfoMode === 'towers' || currentInfoMode === 'enemies')) {
updateTutorialStep();
}
// Show confirmation message
var confirmationMsg = game.addChild(new Text2('♪ Language Updated! ♪', {
size: 70,
fill: 0x00FFFF,
font: "Impact, sans-serif"
}));
confirmationMsg.anchor.set(0.5, 0.5);
confirmationMsg.x = 1024;
confirmationMsg.y = 1366;
confirmationMsg.alpha = 0;
tween(confirmationMsg, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(confirmationMsg, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
confirmationMsg.destroy();
}
});
}
});
}
// Settings button handlers
musicVolumeDownButton.down = function () {
musicVolume = Math.max(0, musicVolume - 0.1);
updateSettingsDisplay();
saveSettings();
// Apply new volume immediately if music is playing
if (!musicMuted && gameplayMusicPlaying) {
LK.stopMusic();
playMelodia();
} else if (!musicMuted && gameState === 'menu') {
LK.stopMusic();
LK.playMusic('MusicaInicio', {
loop: true,
volume: musicVolume
});
}
};
musicVolumeUpButton.down = function () {
musicVolume = Math.min(1, musicVolume + 0.1);
updateSettingsDisplay();
saveSettings();
// Apply new volume immediately if music is playing
if (!musicMuted && gameplayMusicPlaying) {
LK.stopMusic();
playMelodia();
} else if (!musicMuted && gameState === 'menu') {
LK.stopMusic();
LK.playMusic('MusicaInicio', {
loop: true,
volume: musicVolume
});
}
};
soundVolumeDownButton.down = function () {
soundEffectsVolume = Math.max(0, soundEffectsVolume - 0.1);
updateSettingsDisplay();
saveSettings();
};
soundVolumeUpButton.down = function () {
soundEffectsVolume = Math.min(1, soundEffectsVolume + 0.1);
updateSettingsDisplay();
saveSettings();
};
autoStartToggle.down = function () {
autoStartWaves = !autoStartWaves;
autoStartValue.fill = 0x00FFFF; // Set to cyan when toggling
updateSettingsDisplay();
saveSettings();
};
damageNumbersToggle.down = function () {
showDamageNumbers = !showDamageNumbers;
damageNumbersValue.fill = 0x00FFFF; // Set to cyan when toggling
updateSettingsDisplay();
saveSettings();
};
languageToggle.down = function () {
if (showingLanguageDropdown) {
hideLanguageDropdown();
} else {
showLanguageDropdown();
}
};
resetDefaultsButton.down = function () {
musicVolume = 0.3;
soundEffectsVolume = 0.5;
autoStartWaves = false;
showDamageNumbers = false;
gameLanguage = 'english';
updateSettingsDisplay();
saveSettings();
LK.getSound('build').play();
};
settingsBackButton.down = function () {
hideSettings();
};
// 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
});
}
// Mobile resize handler
game.onResize = function (width, height) {
// Ensure game maintains proper scaling on mobile devices
if (typeof window !== 'undefined') {
var canvas = game.view;
if (canvas) {
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.position = 'fixed';
canvas.style.top = '0';
canvas.style.left = '0';
}
}
};
game.update = function () {
// Stop all processing if game over is triggered
if (gameOverTriggered) {
// Handle double-click timer for restart button when game over
if (restartClickCount > 0) {
restartClickTimer++;
if (restartClickTimer >= restartClickTimeout) {
// Reset click count if timeout exceeded
restartClickCount = 0;
restartClickTimer = 0;
console.log("Double-click timeout - reset click count");
}
}
return;
}
// Concert hall ambiance - create floating musical notes periodically
if (LK.ticks % 180 === 0) {
// Every 3 seconds
createFloatingNote();
}
// Simple Music System - ensure Melodia is playing during gameplay
if (gameState === 'playing' && !musicMuted) {
// Check if music needs to be started or restarted (every 5 seconds)
if (LK.ticks % 300 === 0) {
if (!gameplayMusicPlaying) {
console.log("Music not playing, starting Melodia");
playMelodia();
}
}
}
if (gameState === 'playing') {
// Stop all game processing if game over is triggered
if (gameOverTriggered) return;
// Handle Grand Piano targeting mode cleanup if game state changes
if (grandPianoGhostMode && (gameState !== 'playing' || showingBuildMenu || showingUpgradeMenu)) {
exitGrandPianoTargetingMode();
}
// Show game elements when playing
goal.visible = true;
// Show build spots
for (var i = 0; i < buildSpots.length; i++) {
buildSpots[i].visible = true;
}
// Spawn enemies
if (waveStarted && enemiesSpawned < enemiesPerWave) {
spawnTimer += gameSpeed; // Increase spawn timer by current game speed
var spawnInterval = getSpawnIntervalForWave(wave);
if (spawnTimer >= spawnInterval) {
spawnEnemy();
enemiesSpawned++;
spawnTimer = 0;
}
}
// Handle wave auto-start timer
if (wavePaused && enemies.length === 0) {
if (autoStartWaves) {
// Instantly start next wave with no delay when auto start is enabled
startCurrentWave();
} else {
// Manual mode - increment timer and check for auto start after 5 seconds
autoStartTimer += gameSpeed;
if (autoStartTimer >= 300) {
// 5 seconds at 60fps
startCurrentWave();
autoStartTimer = 0;
}
updateWaveUI();
}
}
// Immediate game over check - trigger as soon as lives reach 0
if (lives <= 0 && !gameOverTriggered) {
gameOverTriggered = true;
// Reset game speed to 1x immediately when game over is triggered
gameSpeed = 1;
speedText.setText('♪ SPEED: 1X ♪');
speedText.tint = 0xFFFFFF; // White color to match 1x icon
speedStatusText.setText('Normal');
speedButton.tint = 0xFFFFFF; // Normal tint
// Immediately remove all enemies when lives reach 0
for (var ei = enemies.length - 1; ei >= 0; ei--) {
var enemyToRemove = enemies[ei];
if (enemyToRemove && enemyToRemove.parent) {
enemyToRemove.destroy();
}
enemies.splice(ei, 1);
}
showEnhancedGameOver();
return;
}
// Check if wave is complete
if (waveStarted && enemiesSpawned >= enemiesPerWave && enemies.length === 0) {
// Show credits screen after wave 100
if (wave === 100) {
showCreditsScreen();
return; // Don't advance wave yet, credits will handle it
}
wave++;
waveStarted = false;
// Calculate wave completion bonus - base 75 gold plus progressive bonus from wave 13+
var waveBonus = 75;
if (wave >= 13) {
var progressiveBonus = (wave - 12) * 20; // Wave 13: +20, Wave 14: +40, Wave 15: +60, etc.
waveBonus += progressiveBonus;
console.log("Wave " + wave + ": Added progressive bonus of +" + progressiveBonus + ", total wave bonus:", waveBonus);
}
gold += waveBonus; // Bonus gold for completing wave
// Add 10 lives every 10 waves with announcement
if (wave % 10 === 0 && wave > 0) {
lives += 10;
var livesBonus = game.addChild(new Text2('♪ LIFE BONUS! ♪\n+10 Lives Added!\nTotal Lives: ' + lives, {
size: 80,
fill: 0x00FF00,
font: "Times New Roman, serif"
}));
livesBonus.anchor.set(0.5, 0.5);
livesBonus.x = 1024;
livesBonus.y = 1366;
livesBonus.alpha = 0;
tween(livesBonus, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(livesBonus, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 2000,
easing: tween.easeIn,
onFinish: function onFinish() {
livesBonus.destroy();
}
});
}
});
}
updateUI();
// Play orito sound when wave ends and animate gold
LK.getSound('orito').play();
// Animate gold icon and value when orito sound plays
tween(goldIcon, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(goldIcon, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(goldIcon, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
});
// Animate gold text
tween(goldText, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(goldText, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(goldText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
});
// Unlock Grand Piano power after wave 10
if (wave === 11 && !grandPianoPowerUnlocked && !grandPianoUnlockAnimationShown) {
grandPianoUnlockAnimationShown = true;
showGrandPianoUnlockAnimation();
}
// Reduce Grand Piano cooldown
if (grandPianoCooldownRemaining > 0) {
grandPianoCooldownRemaining--;
updateGrandPianoPowerUI();
}
// Music management code removed to prevent issues after wave 10
// Reset wave gold tracking
lastWaveGoldEarned = 0;
// Check for gold abuse (maintaining >600 gold)
if (gold > 600) {
highGoldWaveCount++;
if (highGoldWaveCount >= 3) {
// Activate punishment modifier
difficultyModifier = 1.5; // 50% more health for next 2 waves
enemySpeedModifier = 1.3; // 30% faster enemies
goldAbuseCooldown = 2; // Lasts for 2 waves
highGoldWaveCount = 0;
// Show warning message
var warningMsg = game.addChild(new Text2('⚠ DIFFICULTY INCREASED ⚠\nToo much gold hoarded!', {
size: 80,
fill: 0xFF0000,
font: "Impact, sans-serif"
}));
warningMsg.anchor.set(0.5, 0.5);
warningMsg.x = 1024;
warningMsg.y = 1000;
tween(warningMsg, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
warningMsg.destroy();
}
});
}
} else {
highGoldWaveCount = 0;
}
// Decrement punishment cooldown
if (goldAbuseCooldown > 0) {
goldAbuseCooldown--;
if (goldAbuseCooldown === 0) {
difficultyModifier = 1.0;
enemySpeedModifier = 1.0;
}
}
// Tower maintenance taxes removed per player request
// Start next wave after delay
LK.setTimeout(function () {
startNextWave();
}, 3000);
}
}
// Clean up destroyed bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i].destroyed) {
bullets.splice(i, 1);
}
}
// Clean up dead enemies
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i].health <= 0) {
enemies[i].destroy();
enemies.splice(i, 1);
}
}
};
// Function to get localized tutorial descriptions
function getTutorialDescriptions() {
var tutorialData = {
english: 'Welcome to Concert Hall Defense!\n\nWaves are infinite - survive as long as possible!\nYou start with 40 lives.\nEvery 10 waves, you gain 10 additional lives.\n\nChoose your path of learning:',
spanish: '¡Bienvenido a la Defensa del Concert Hall!\n\n¡Las oleadas son infinitas - sobrevive tanto como puedas!\nComienzas con 40 vidas.\nCada 10 oleadas, ganas 10 vidas adicionales.\n\nElige tu camino de aprendizaje:',
french: 'Bienvenue dans Concert Hall Defense!\n\nLes vagues sont infinies - survivez le plus longtemps possible!\nVous commencez avec 40 vies.\nToutes les 10 vagues, vous gagnez 10 vies supplémentaires.\n\nChoisissez votre voie d\'apprentissage:',
german: 'Willkommen bei Concert Hall Defense!\n\nWellen sind unendlich - überlebe so lange wie möglich!\nDu beginnst mit 40 Leben.\nAlle 10 Wellen erhältst du 10 zusätzliche Leben.\n\nWähle deinen Lernpfad:',
italian: 'Benvenuti in Concert Hall Defense!\n\nLe ondate sono infinite - sopravvivi il più a lungo possibile!\nInizi con 40 vite.\nOgni 10 ondate, ottieni 10 vite aggiuntive.\n\nScegli il tuo percorso di apprendimento:',
portuguese: 'Bem-vindos ao Concert Hall Defense!\n\nAs ondas são infinitas - sobreviva o máximo possível!\nVocê começa com 40 vidas.\nA cada 10 ondas, ganha 10 vidas adicionais.\n\nEscolha seu caminho de aprendizado:',
russian: 'Добро пожаловать в Concert Hall Defense!\n\nВолны бесконечны - выживайте как можно дольше!\nВы начинаете с 40 жизней.\nКаждые 10 волн вы получаете 10 дополнительных жизней.\n\nВыберите свой путь обучения:'
};
return tutorialData[gameLanguage] || tutorialData.english;
} ===================================================================
--- original.js
+++ change.js
@@ -4073,11 +4073,11 @@
return intervalInTicks;
}
function getEnemyTypesForWave(waveNumber) {
var availableTypes = [];
- // Check for special themed waves from wave 8+
- if (waveNumber >= 8) {
- var isSpecialWave = Math.random() < 0.25; // 25% chance
+ // Remove special empowered waves from wave 15+ (no more empowered enemies)
+ if (waveNumber >= 8 && waveNumber < 15) {
+ var isSpecialWave = Math.random() < 0.25; // 25% chance only for waves 8-14
if (isSpecialWave) {
var specialWaves = ['runners', 'armored', 'massive'];
var specialWave = specialWaves[Math.floor(Math.random() * specialWaves.length)];
// Store special wave type for spawn modifications
@@ -4112,9 +4112,9 @@
// 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
+ // Wave 25+: All enemy types (no empowered enemies)
else {
availableTypes = ['notaDesafinada', 'glitchAudio', 'ruidoBlanco', 'ecoOscuro', 'autotuneMalicioso'];
}
return availableTypes;
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