User prompt
Haz que las mejoras de la izquierda sean 1: +10% de rango.2: +2 segundos de congelación. 3: quita 1 capa al congelar. 4: El 30% de los enemigos en el rango tienen una relentizacion del 100% (se mantienen en su posición) por 1 segundo) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Haz que las mejoras de la izquierda de la torre de hielo sean 1: +10% de rango.2: +2 segundos de congelación. 3: quita 1 capa al congelar. 4: El 30% de los enemigos en el rango tienen una relentizacion del 100% (se mantienen en su posición) por 1 segundo) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Haz el sistema de mejoras flexibles para permitir distintas habilidades individuales para cada a torre
User prompt
Haz que las mejoras de la izquierda sean 1: +10% de rango.2: +2 segundos de congelación. 3: quita 1 capa al congelar. 4: El 30% de los enemigos en el rango tienen una relentizacion del 100% (se mantienen en su posición) por 1 segundo) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Haz que el efecto de congelación dure 1 segundos después de ataque ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Haz que la torre de hielo en vez de quitar capas aplique un efecto de relentizar del 20% a los enemigos
User prompt
Elimina las habilidades actuales de la torre de hielo
User prompt
Haz que torre de hielo no dispare y ataque a todos los enemigos de su rango con una onda espansiva (haz una animación donde se va expandiendo y cobre todo el rango) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Elimina las mejoras de la torre de hielo
User prompt
Elimina el efecto de tinte a la torre de hielo y el tinte en los botones de crear torre
User prompt
Agrega una animación diferente a la torre de hielo con sus propios asset y que no que los asset del inicil ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Agrega dos torres nuevas sin habilidades y ataque básico: icetower y FireTower
User prompt
Elimina la aleatoriedad de aparición de capa 6 y haz que se pueda especificar la cantidad de cada variedad como capa 6A y 6B
User prompt
Haz que capa 6 se divida en 4 capa 5 en vez de 1
User prompt
Haz que capa 6 se divida en más enemigos
User prompt
Haz que capa 6 al perder su capa se divida en 4 capa 5 en vez de 1
User prompt
Haz que cada 6 se divida en 4 capa 5 al morir
User prompt
Haz un sistema flexible para mecánicas extra de cada capa
User prompt
El enemigo no se divide en 4
User prompt
Haz que capa 6 al morir se divida en 4 de su capa inferior
User prompt
Elimina el sistema actual para asignar y cambiar tinte de los enemigos y crea otro que permita más variedad y configuración debido al error que no permite que una misma capa tenga variantes de color ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Arregla el error que hace que capa 6 no cambie de color según su variable
User prompt
Agrega 2 enemigo de ssexta capa en la oleada 1
User prompt
Agrega un sistema para hacer que la sexta capa venga aparezca con la variable Anti hielo o anti fuego activa como variable local. Si viene con Anti hielo su color será blanco, si es anti fuego será negro
User prompt
Crea dos variables: Anti hielo y anti fuego boleanas
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('Bala', { anchorX: 0.5, anchorY: 0.5 }); self.target = null; self.speed = 800; // pixels per second self.damage = 0; self.directionX = 0; // Direction components for straight line movement self.directionY = 0; self.isAlive = true; self.hitCount = 0; // Track number of enemies hit by this bullet self.hitEnemies = []; // Track which specific enemies have been hit to prevent double-hitting // Calculate lifespan based on range and bullet speed with 20% buffer var bulletSpeed = 800; // Base bullet speed in pixels per second // Apply fastGame speed boost to bullet speed for lifespan calculation if (fastGame) { bulletSpeed = bulletSpeed * 1.5; // 50% faster when fastGame is true } var towerRange = self.towerRef ? self.towerRef.range : 400; // Use tower's range or default to 400 var rangeBasedTravelTime = towerRange / bulletSpeed * 1000; // Convert to milliseconds var baseDuration = rangeBasedTravelTime * 1.2; // 20% more than range travel time var duration = baseDuration; // Apply duration multiplier if tower reference exists and has the multiplier if (self.towerRef && self.towerRef.bulletDurationMultiplier) { duration = baseDuration * self.towerRef.bulletDurationMultiplier; } tween(self, {}, { duration: duration, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); self.update = function () { if (!self.isAlive) { return; } // Move in straight line using direction var effectiveSpeed = self.speed; // Apply fastGame speed boost to bullet movement (50% faster) if (fastGame) { effectiveSpeed = self.speed * 1.5; } self.x += self.directionX * effectiveSpeed * frameTime; self.y += self.directionY * effectiveSpeed * frameTime; // Check collision with all enemies, not just the original target for (var e = 0; e < enemies.length; e++) { var enemy = enemies[e]; if (enemy && enemy.parent) { // Skip enemies that have already been hit by this bullet if (self.hitEnemies && self.hitEnemies.indexOf(enemy) !== -1) { continue; } // Check collision with enemy's hitbox instead of enemy center var hitbox = null; // Find the hitbox child in the enemy for (var i = 0; i < enemy.children.length; i++) { var child = enemy.children[i]; // Check by texture URL first if (child.texture && child.texture.baseTexture && child.texture.baseTexture.resource && child.texture.baseTexture.resource.url && child.texture.baseTexture.resource.url.includes('hitbox')) { hitbox = child; break; } // Check by alpha value (hitbox has alpha 0.2 when visible, 0 when invisible) if (child.alpha === 0.2 || child.alpha === 0) { // Additional check: hitbox should have width and height of 120 if (child.width === 120 && child.height === 120) { hitbox = child; break; } } } if (hitbox) { // Calculate distance to hitbox position (relative to enemy) var hitboxWorldX = enemy.x + hitbox.x; var hitboxWorldY = enemy.y + hitbox.y; var dx = hitboxWorldX - self.x; var dy = hitboxWorldY - self.y; var distanceSquared = dx * dx + dy * dy; // Use appropriate hitbox size for collision detection (hitbox is 120x120) var hitboxRadius = 60; // Standard collision radius matching visual hitbox if (distanceSquared < hitboxRadius * hitboxRadius) { // Initialize hit enemies array if it doesn't exist if (!self.hitEnemies) { self.hitEnemies = []; } // Add this enemy to the hit list self.hitEnemies.push(enemy); // Apply damage to enemy using its takeDamage method if (enemy.takeDamage) { enemy.takeDamage(self.damage); } // Add damage to tower's total damage counter if (self.towerRef && self.towerRef.parent) { self.towerRef.totalDamage += self.damage; } // Increment hit count and check if bullet should be destroyed self.hitCount++; var towerCross = self.towerRef && self.towerRef.cross ? self.towerRef.cross : 1; if (self.hitCount >= towerCross) { // Bullet has hit the required number of enemies, destroy it self.isAlive = false; if (self.parent) { self.destroy(); } return; } // Continue to check other enemies break; } } else { // Fallback to enemy center if hitbox not found var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distanceSquared = dx * dx + dy * dy; // If close enough to target, hit it (using squared distance to avoid sqrt) if (distanceSquared < 1600) { // 40 * 40 = 1600 - reasonable collision radius for visual accuracy // Initialize hit enemies array if it doesn't exist if (!self.hitEnemies) { self.hitEnemies = []; } // Add this enemy to the hit list self.hitEnemies.push(enemy); // Apply damage to enemy using its takeDamage method if (enemy.takeDamage) { enemy.takeDamage(self.damage); } // Add damage to tower's total damage counter if (self.towerRef && self.towerRef.parent) { self.towerRef.totalDamage += self.damage; } // Increment hit count and check if bullet should be destroyed self.hitCount++; var towerCross = self.towerRef && self.towerRef.cross ? self.towerRef.cross : 1; if (self.hitCount >= towerCross) { // Bullet has hit the required number of enemies, destroy it self.isAlive = false; if (self.parent) { self.destroy(); } return; } // Continue to check other enemies break; } } } } }; return self; }); // Enemy Layer 6 (White/Black, 10% faster - same as green) var EnemyCapa6 = Container.expand(function () { var self = createEnemyBase(6, 1.1, 0xFFFFFF).call(this); return self; }); // Centralized enemy speed calculation function // Reusable tower creation function var Gameplay = Container.expand(function () { var self = Container.call(this); var gameplayGraphics = self.attachAsset('gameplayBackground', { anchorX: 0.5, anchorY: 0.5 }); return self; }); var UI = Container.expand(function () { var self = Container.call(this); var uiGraphics = self.attachAsset('uiBackground', { anchorX: 0.5, anchorY: 0.5 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Reusable tower creation function 7; function createTowerBase(params) { return Container.expand(function () { var self = Container.call(this); // Tower parameters self.damage = params.damage; self.cadence = params.cadence; self.range = params.range; self.rangeSquared = self.range * self.range; // Cache squared range for optimization self.totalDamage = 0; // Track total damage dealt by this tower self.cross = 1; // Initialize cross value for this tower self.targetingPriority = 'first'; // Default targeting priority: 'first', 'last', 'strongest' var areaGraphics = self.attachAsset('Area', { anchorX: 0.5, anchorY: 0.5 }); // Scale the area to match the tower's actual range var areaScale = self.range * 2 / 100; // Area asset is 100x100, so scale to range diameter areaGraphics.scaleX = areaScale; areaGraphics.scaleY = areaScale; areaGraphics.alpha = 0.3; areaGraphics.visible = false; // Hide range area by default var towerGraphics = self.attachAsset(params.asset, { anchorX: 0.5, anchorY: 0.5 }); // Method to show range area self.showRange = function () { areaGraphics.visible = true; }; // Method to hide range area self.hideRange = function () { areaGraphics.visible = false; }; // Tower selection state self.isSelected = false; // Tower placement state self.isPlaced = false; // Shooting properties self.lastShotTime = 0; self.bullets = []; // Current target tracking self.currentTarget = null; // Method to find enemies in range self.findEnemiesInRange = function () { var enemiesInRange = []; // Check all enemies in the enemies array for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy && enemy.parent) { var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared <= self.rangeSquared) { // Analyze total incoming damage from all towers targeting this enemy var totalIncomingDamage = 0; var isBeingTargeted = false; // Check all towers and their bullets targeting this enemy for (var j = 0; j < placedTowers.length; j++) { var otherTower = placedTowers[j]; if (otherTower !== self) { // Check if this tower is targeting the enemy if (otherTower.currentTarget === enemy) { isBeingTargeted = true; } // Count all bullets from this tower that are targeting this enemy for (var k = 0; k < otherTower.bullets.length; k++) { var bullet = otherTower.bullets[k]; if (bullet && bullet.target === enemy && bullet.isAlive) { // Check if bullet hasn't already hit this enemy if (!bullet.hitEnemies || bullet.hitEnemies.indexOf(enemy) === -1) { totalIncomingDamage += bullet.damage; } } } } } // Add enemy to list if: // 1. Not being targeted at all, OR // 2. Being targeted but total incoming damage won't kill it var canTarget = !isBeingTargeted || totalIncomingDamage < enemy.currentHealth; if (canTarget) { enemiesInRange.push(enemy); } } } } // Sort enemies based on targeting priority enemiesInRange.sort(function (a, b) { if (self.targetingPriority === 'first') { // Calculate progress for enemy a var progressA = a.currentWaypointIndex; if (a.currentWaypointIndex < caminoPositions.length - 1) { var currentWaypointA = caminoPositions[a.currentWaypointIndex]; var nextWaypointA = caminoPositions[a.currentWaypointIndex + 1]; var totalDistanceA = Math.sqrt((nextWaypointA.x - currentWaypointA.x) * (nextWaypointA.x - currentWaypointA.x) + (nextWaypointA.y - currentWaypointA.y) * (nextWaypointA.y - currentWaypointA.y)); var coveredDistanceA = Math.sqrt((a.x - currentWaypointA.x) * (a.x - currentWaypointA.x) + (a.y - currentWaypointA.y) * (a.y - currentWaypointA.y)); progressA += totalDistanceA > 0 ? coveredDistanceA / totalDistanceA : 0; } // Calculate progress for enemy b var progressB = b.currentWaypointIndex; if (b.currentWaypointIndex < caminoPositions.length - 1) { var currentWaypointB = caminoPositions[b.currentWaypointIndex]; var nextWaypointB = caminoPositions[b.currentWaypointIndex + 1]; var totalDistanceB = Math.sqrt((nextWaypointB.x - currentWaypointB.x) * (nextWaypointB.x - currentWaypointB.x) + (nextWaypointB.y - currentWaypointB.y) * (nextWaypointB.y - currentWaypointB.y)); var coveredDistanceB = Math.sqrt((b.x - currentWaypointB.x) * (b.x - currentWaypointB.x) + (b.y - currentWaypointB.y) * (b.y - currentWaypointB.y)); progressB += totalDistanceB > 0 ? coveredDistanceB / totalDistanceB : 0; } // Sort in descending order (highest progress first = closest to end) return progressB - progressA; } else if (self.targetingPriority === 'last') { // Calculate progress for enemy a var progressA = a.currentWaypointIndex; if (a.currentWaypointIndex < caminoPositions.length - 1) { var currentWaypointA = caminoPositions[a.currentWaypointIndex]; var nextWaypointA = caminoPositions[a.currentWaypointIndex + 1]; var totalDistanceA = Math.sqrt((nextWaypointA.x - currentWaypointA.x) * (nextWaypointA.x - currentWaypointA.x) + (nextWaypointA.y - currentWaypointA.y) * (nextWaypointA.y - currentWaypointA.y)); var coveredDistanceA = Math.sqrt((a.x - currentWaypointA.x) * (a.x - currentWaypointA.x) + (a.y - currentWaypointA.y) * (a.y - currentWaypointA.y)); progressA += totalDistanceA > 0 ? coveredDistanceA / totalDistanceA : 0; } // Calculate progress for enemy b var progressB = b.currentWaypointIndex; if (b.currentWaypointIndex < caminoPositions.length - 1) { var currentWaypointB = caminoPositions[b.currentWaypointIndex]; var nextWaypointB = caminoPositions[b.currentWaypointIndex + 1]; var totalDistanceB = Math.sqrt((nextWaypointB.x - currentWaypointB.x) * (nextWaypointB.x - currentWaypointB.x) + (nextWaypointB.y - currentWaypointB.y) * (nextWaypointB.y - currentWaypointB.y)); var coveredDistanceB = Math.sqrt((b.x - currentWaypointB.x) * (b.x - currentWaypointB.x) + (b.y - currentWaypointB.y) * (b.y - currentWaypointB.y)); progressB += totalDistanceB > 0 ? coveredDistanceB / totalDistanceB : 0; } // Sort in ascending order (lowest progress first = farthest from end) return progressA - progressB; } else if (self.targetingPriority === 'strongest') { // Sort by health (highest health first = strongest) return b.currentHealth - a.currentHealth; } return 0; }); return enemiesInRange; }; // Method to perform expanding wave attack for ice tower self.performWaveAttack = function () { // Find all enemies in range var enemiesInRange = self.findEnemiesInRange(); // Create expanding wave visual effect var waveEffect = self.attachAsset('AttackIce', { anchorX: 0.5, anchorY: 0.5 }); waveEffect.alpha = 0.7; waveEffect.scaleX = 0.1; waveEffect.scaleY = 0.1; waveEffect.x = 0; waveEffect.y = 0; // Calculate final wave scale based on tower range var finalScale = self.range * 2 / 100; // AttackIce asset is 100x100 // Animate wave expansion tween(waveEffect, { scaleX: finalScale, scaleY: finalScale, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (waveEffect.parent) { waveEffect.destroy(); } } }); // Apply slow effect to all enemies in range for (var i = 0; i < enemiesInRange.length; i++) { var enemy = enemiesInRange[i]; if (enemy && enemy.parent) { // Apply slow effect (default 20% slow, upgradeable) var slowEffect = self.slowEffect || 0.8; // Default 20% slow var slowDuration = self.slowDuration || 1000; // Default 1 second enemy.slowEffect = slowEffect; enemy.slowDuration = slowDuration; // Apply damage on freeze if upgrade is purchased if (self.damageOnFreeze && enemy.takeDamage) { enemy.takeDamage(1); // Remove 1 layer } // Check for stun effect (100% slow for 1 second) if (self.stunChance && Math.random() < self.stunChance) { // Create separate stun effect list if not exists if (!enemy.stunEffects) { enemy.stunEffects = []; } // Add stun effect enemy.stunEffects.push({ effect: 0.0, // 100% slow (enemy stops) duration: 1000 // 1 second }); } // Update enemy movement speed if currently moving if (enemy.isMoving && enemy.moveSpeed) { enemy.moveSpeed = enemy.moveSpeed * enemy.slowEffect; } } } }; // Method to shoot at target - ice tower uses expanding wave attack self.shoot = function (target) { var currentTime = Date.now(); var effectiveCadence = self.cadence; // Apply fastGame speed boost to firing rate (50% faster = reduce cadence by 33%) if (fastGame) { effectiveCadence = self.cadence / 1.5; } if (currentTime - self.lastShotTime >= effectiveCadence) { // Ice tower uses expanding wave attack instead of bullets if (params.asset === 'Icetower') { // Create expanding wave attack for ice tower self.performWaveAttack(); } else { // Regular towers use bullet shooting // Function to create and fire a bullet var createBullet = function createBullet(angleOffset) { var bullet = new Bullet(); // Calculate current direction to target var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Predict enemy position based on distance var bulletSpeed = 800 * (self.bulletSpeedMultiplier || 1); // Apply speed multiplier if available // Apply fastGame speed boost to bullet speed (50% faster) if (fastGame) { bulletSpeed = bulletSpeed * 1.5; } var timeToReach = distance / bulletSpeed; // time in seconds for bullet to reach current target position // Calculate enemy movement prediction - farther enemies get more prediction (reduced) var baseEnemySpeed = 260; // Base enemy speed in pixels per second var enemySpeed = getEnemySpeed(target); // Adjust prediction factor based on fastGame mode for better accuracy var predictionFactor = fastGame ? 0.8 : 0.6; // More aggressive prediction in fast mode var predictionDistance = enemySpeed * timeToReach * predictionFactor; // Get enemy's current movement direction var enemyDx = 0; var enemyDy = 0; if (target.currentWaypointIndex < caminoPositions.length - 1) { var nextWaypoint = caminoPositions[target.currentWaypointIndex + 1]; enemyDx = nextWaypoint.x - target.x; enemyDy = nextWaypoint.y - target.y; var enemyMoveDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy); if (enemyMoveDistance > 0) { // Normalize enemy direction enemyDx = enemyDx / enemyMoveDistance; enemyDy = enemyDy / enemyMoveDistance; } } // Calculate predicted target position var predictedX = target.x + enemyDx * predictionDistance; var predictedY = target.y + enemyDy * predictionDistance; // Calculate direction to predicted position with angle offset var predictedDx = predictedX - self.x; var predictedDy = predictedY - self.y; var baseAngle = Math.atan2(predictedDy, predictedDx); var adjustedAngle = baseAngle + angleOffset; var offsetDistance = 50; // Distance to spawn bullet ahead of tower // Spawn bullet slightly ahead in the adjusted direction bullet.x = self.x + Math.cos(adjustedAngle) * offsetDistance; bullet.y = self.y + Math.sin(adjustedAngle) * offsetDistance; // Set bullet direction for straight line movement in adjusted direction bullet.directionX = Math.cos(adjustedAngle); bullet.directionY = Math.sin(adjustedAngle); bullet.target = target; bullet.damage = self.damage; bullet.speed = bulletSpeed; // Use calculated speed with multiplier bullet.towerRef = self; // Store reference to the tower that fired this bullet // Rotate bullet to face adjusted direction bullet.rotation = adjustedAngle; self.bullets.push(bullet); game.addChild(bullet); }; createBullet(0); // Fire side bullets if multishot upgrade is active if (self.hasMultishot) { createBullet(-Math.PI / 12); // 15 degrees to the left createBullet(Math.PI / 12); // 15 degrees to the right } // Play bullet shooting sound LK.getSound('Balababa').play(); } self.lastShotTime = currentTime; // Initialize attack sprites if not already created // Use ice-specific attack sprites if this is an ice tower var attackAsset1 = params.asset === 'Icetower' ? 'IceTowerAttack1' : 'TorreinicialAttack'; var attackAsset2 = params.asset === 'Icetower' ? 'IceTowerAttack2' : 'TorreinicialAttack2'; var attackAsset3 = params.asset === 'Icetower' ? 'IceTowerAttack3' : 'TorreinicialAttack3'; if (!self.attackSprite1) { self.attackSprite1 = self.attachAsset(attackAsset1, { anchorX: 0.5, anchorY: 0.5 }); self.attackSprite1.visible = false; } if (!self.attackSprite2) { self.attackSprite2 = self.attachAsset(attackAsset2, { anchorX: 0.5, anchorY: 0.5 }); self.attackSprite2.visible = false; } if (!self.attackSprite3) { self.attackSprite3 = self.attachAsset(attackAsset3, { anchorX: 0.5, anchorY: 0.5 }); self.attackSprite3.visible = false; } if (!self.normalSprite) { self.normalSprite = towerGraphics; } // Initialize animation frame counter if not exists if (!self.animationFrame) { self.animationFrame = 0; } // Cycle through attack sprites self.animationFrame = (self.animationFrame + 1) % 3; var currentAttackSprite; if (self.animationFrame === 0) { currentAttackSprite = self.attackSprite1; } else if (self.animationFrame === 1) { currentAttackSprite = self.attackSprite2; } else { currentAttackSprite = self.attackSprite3; } // Sync attack sprite properties with current sprite currentAttackSprite.rotation = towerGraphics.rotation; currentAttackSprite.scaleY = towerGraphics.scaleY; currentAttackSprite.x = towerGraphics.x; currentAttackSprite.y = towerGraphics.y; // Hide current sprite and show attack sprite towerGraphics.visible = false; currentAttackSprite.visible = true; towerGraphics = currentAttackSprite; // Animate attack sprite with enhanced effects for ice tower if (params.asset === 'Icetower') { // Ice tower specific animation: pulse without tint tween(currentAttackSprite, { scaleX: 1.3, scaleY: currentAttackSprite.scaleY > 0 ? 1.3 : -1.3 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(currentAttackSprite, { scaleX: 1.0, scaleY: currentAttackSprite.scaleY > 0 ? 1.0 : -1.0 }, { duration: 120, easing: tween.elasticOut }); } }); } else { // Standard animation for other towers tween(currentAttackSprite, { scaleX: 1.2, scaleY: currentAttackSprite.scaleY > 0 ? 1.2 : -1.2 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(currentAttackSprite, { scaleX: 1.0, scaleY: currentAttackSprite.scaleY > 0 ? 1.0 : -1.0 }, { duration: 150, easing: tween.easeIn }); } }); } // Change back to normal sprite after a short delay using LK.setTimeout LK.setTimeout(function () { if (self.parent && currentAttackSprite && self.normalSprite) { // Sync normal sprite properties with current attack sprite self.normalSprite.rotation = currentAttackSprite.rotation; self.normalSprite.scaleY = currentAttackSprite.scaleY; self.normalSprite.x = currentAttackSprite.x; self.normalSprite.y = currentAttackSprite.y; // Hide attack sprite and show normal sprite currentAttackSprite.visible = false; self.normalSprite.visible = true; towerGraphics = self.normalSprite; } }, 250); } }; // Update method for tower self.update = function () { // Clean up destroyed bullets for (var i = self.bullets.length - 1; i >= 0; i--) { if (!self.bullets[i].parent) { self.bullets.splice(i, 1); } } // Only shoot if tower is placed (check cached status) if (self.isPlaced) { // Clear current target if it's no longer valid if (self.currentTarget && (!self.currentTarget.parent || self.currentTarget.currentHealth <= 0)) { self.currentTarget = null; } // Find and shoot at enemies var enemies = self.findEnemiesInRange(); if (enemies.length > 0) { // Ice tower uses wave attack - no targeting needed if (params.asset === 'Icetower') { // Ice tower attacks all enemies in range with wave self.shoot(null); // Pass null since ice tower doesn't need specific target } else { // Regular towers need targeting var target = enemies[0]; // Set current target self.currentTarget = target; // Calculate predicted target position for tower rotation var distance = Math.sqrt((target.x - self.x) * (target.x - self.x) + (target.y - self.y) * (target.y - self.y)); var bulletSpeed = 800; // pixels per second // Apply fastGame speed boost to bullet speed for prediction calculations if (fastGame) { bulletSpeed = bulletSpeed * 1.5; } var timeToReach = distance / bulletSpeed; var enemySpeed = getEnemySpeed(target); // Adjust prediction factor based on fastGame mode for better accuracy var predictionFactor = fastGame ? 0.8 : 0.6; // More aggressive prediction in fast mode var predictionDistance = enemySpeed * timeToReach * predictionFactor; // Get enemy's current movement direction var enemyDx = 0; var enemyDy = 0; if (target.currentWaypointIndex < caminoPositions.length - 1) { var nextWaypoint = caminoPositions[target.currentWaypointIndex + 1]; enemyDx = nextWaypoint.x - target.x; enemyDy = nextWaypoint.y - target.y; var enemyMoveDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy); if (enemyMoveDistance > 0) { // Normalize enemy direction enemyDx = enemyDx / enemyMoveDistance; enemyDy = enemyDy / enemyMoveDistance; } } // Calculate predicted target position var predictedX = target.x + enemyDx * predictionDistance; var predictedY = target.y + enemyDy * predictionDistance; // Calculate angle to predicted target position var dx = predictedX - self.x; var dy = predictedY - self.y; var targetAngle = Math.atan2(dy, dx) + Math.PI / 2; // Add 90° rotation offset // Smooth rotation using tween animation var angleDiff = targetAngle - towerGraphics.rotation; // Normalize angle difference to shortest path while (angleDiff > Math.PI) { angleDiff -= 2 * Math.PI; } while (angleDiff < -Math.PI) { angleDiff += 2 * Math.PI; } // Only rotate if angle difference is significant (> 0.1 radians) if (Math.abs(angleDiff) > 0.1) { // Stop any existing rotation tween tween.stop(towerGraphics, { rotation: true }); // Animate rotation smoothly tween(towerGraphics, { rotation: targetAngle }, { duration: 200, easing: tween.easeOut }); } // Keep tower orientation consistent - no flipping based on target position towerGraphics.scaleY = -1; // Always maintain normal orientation // Shoot at the first enemy found self.shoot(target); } } else { // No enemies in range, clear current target self.currentTarget = null; } } }; // Handle tower selection self.down = function (x, y, obj) { // Deselect all other towers first for (var i = 0; i < placedTowers.length; i++) { if (placedTowers[i] !== self) { placedTowers[i].isSelected = false; placedTowers[i].hideRange(); } } // Toggle selection for this tower self.isSelected = !self.isSelected; if (self.isSelected) { self.showRange(); updateTowerUpgrade(self); // Update UI with this tower's upgrade options } else { self.hideRange(); updateTowerUpgrade(null); // Clear UI when deselected } }; return self; }); } var Tower = createTowerBase({ damage: 1, // Damage dealt per shot cadence: 1000, // Time between shots in milliseconds (1 second) range: 400, // Range in pixels asset: 'torreInicial' }); // Initialize tower-specific properties Tower.prototype.cross = 1; // Each tower starts with cross value of 1 var IceTower = createTowerBase({ damage: 1, // Damage dealt per shot cadence: 1000, // Time between shots in milliseconds (1 second) range: 400, // Range in pixels asset: 'Icetower' }); // Initialize ice tower-specific properties IceTower.prototype.cross = 1; // Each tower starts with cross value of 1 var FireTower = createTowerBase({ damage: 1, // Damage dealt per shot cadence: 1000, // Time between shots in milliseconds (1 second) range: 400, // Range in pixels asset: 'FireTower' }); // Initialize fire tower-specific properties FireTower.prototype.cross = 1; // Each tower starts with cross value of 1 var draggedTower = null; var isDragging = false; var placedTowers = []; // Track all placed towers // Cache frequently used values var gameplayBounds = null; var uiBounds = null; var frameTime = 1 / 60; // Cache frame time calculation // Reusable tower creation function that accepts tower constructor function createTowerButton(color, x, y, TowerClass) { var buttonBG = game.addChild(LK.getAsset('BGbuttonTower', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 })); buttonBG.x = x; buttonBG.y = y; var button = game.addChild(new TowerClass()); button.x = x; button.y = y; // Store reference to button background for easy access button.buttonBG = buttonBG; button.down = function (x, y, obj) { isDragging = true; draggedTower = game.addChild(new TowerClass()); draggedTower.x = button.x; draggedTower.y = button.y; draggedTower.alpha = 0.7; draggedTower.showRange(); // Show range area when dragging // Initialize last valid position to starting position draggedTower.lastValidX = button.x; draggedTower.lastValidY = button.y; }; return button; } // Game down handler to deselect towers when clicking on gameplay area game.down = function (x, y, obj) { // Check if click is in gameplay area (not on UI or towers) var gameplayBounds = getGameplayBounds(); var clickInGameplay = x >= gameplayBounds.left && x <= gameplayBounds.right && y >= gameplayBounds.top && y <= gameplayBounds.bottom; if (clickInGameplay) { // Deselect all towers for (var i = 0; i < placedTowers.length; i++) { placedTowers[i].isSelected = false; placedTowers[i].hideRange(); } updateTowerUpgrade(null); // Clear tower upgrade when no tower is selected } }; // Helper function to check if point is inside rotated rectangle function isPointInRotatedRect(pointX, pointY, rectX, rectY, rectWidth, rectHeight, rotation) { // Translate point to rectangle's coordinate system var dx = pointX - rectX; var dy = pointY - rectY; // Rotate point by negative rotation to align with rectangle var cos = Math.cos(-rotation); var sin = Math.sin(-rotation); var rotatedX = dx * cos - dy * sin; var rotatedY = dx * sin + dy * cos; // Check if rotated point is within rectangle bounds with much larger bounds for better collision var halfWidth = rectWidth / 2 + 90; // Add 80 pixels buffer for much better collision detection var halfHeight = rectHeight / 2 + 60; // Add 80 pixels buffer for much better collision detection return Math.abs(rotatedX) <= halfWidth && Math.abs(rotatedY) <= halfHeight; } // Helper functions for bounds calculations function getGameplayBounds() { return { left: gameplay.x - gameplay.width / 2, right: gameplay.x + gameplay.width / 2, top: gameplay.y - gameplay.height / 2, bottom: gameplay.y + gameplay.height / 2, centerX: gameplay.x, centerY: gameplay.y }; } function getUIBounds() { return { left: ui.x - ui.width / 2, right: ui.x + ui.width / 2, top: ui.y - ui.height / 2, bottom: ui.y + ui.height / 2 }; } function isPointInUI(x, y) { var bounds = getUIBounds(); return x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom; } function calculateDragOffset(x, y) { var bounds = getGameplayBounds(); var distanceFromCenterX = Math.abs(x - bounds.centerX); var distanceFromCenterY = Math.abs(y - bounds.centerY); var maxDistanceX = gameplay.width / 2; var maxDistanceY = gameplay.height / 2; var normalizedDistanceX = distanceFromCenterX / maxDistanceX; var normalizedDistanceY = distanceFromCenterY / maxDistanceY; var maxOffset = 200; var offsetMagnitudeX = maxOffset * normalizedDistanceX; var offsetMagnitudeY = maxOffset * normalizedDistanceY; var offsetX = 0; var offsetY = 0; if (x >= bounds.centerX && y <= bounds.centerY) { offsetX = offsetMagnitudeX; offsetY = -offsetMagnitudeY; } else if (x <= bounds.centerX && y <= bounds.centerY) { offsetX = -offsetMagnitudeX; offsetY = -offsetMagnitudeY; } else if (x <= bounds.centerX && y >= bounds.centerY) { offsetX = -offsetMagnitudeX; offsetY = offsetMagnitudeY; } else if (x >= bounds.centerX && y >= bounds.centerY) { offsetX = offsetMagnitudeX; offsetY = offsetMagnitudeY; } return { offsetX: offsetX, offsetY: offsetY }; } // Layer management - objects are added in z-index order (bottom to top) // Background layer var gameplay = game.addChild(new Gameplay()); gameplay.x = 1024; gameplay.y = 1093; // Game objects layer (towers will be added here) // UI layer var ui = game.addChild(new UI()); ui.x = 1024; ui.y = 2459; // Create tower creation buttons var towerButtons = []; var startX = 400; // Position button on the left side towerButtons.push(createTowerButton(0xffffff, startX, 2459, Tower)); // White tower (normal) towerButtons.push(createTowerButton(0x87CEEB, startX + 200, 2459, IceTower)); // Light blue ice tower towerButtons.push(createTowerButton(0xFF4500, startX + 400, 2459, FireTower)); // Orange fire tower // Create path objects with transparency var caminoObjects = []; // Add several camino objects to create a path var caminoPositions = [{ x: 959, y: -99, rotation: 0, width: 200, height: 200 }, { x: 831, y: 50, rotation: 0.9948, width: 200, height: 510.2 }, { x: 574, y: 224, rotation: 0.8029, width: 190.3, height: 201.6 }, { x: 508, y: 313, rotation: 0.4538, width: 203.6, height: 200 }, { x: 488, y: 412, rotation: 0, width: 220.8, height: 191.1 }, { x: 516, y: 506, rotation: -0.6981, width: 214.2, height: 220 }, { x: 632, y: 587, rotation: 0.3491, width: 197, height: 220.9 }, { x: 883, y: 609, rotation: -0.0313, width: 390.1, height: 210.3 }, { x: 1144, y: 619, rotation: 0.1745, width: 200, height: 225.4 }, { x: 1318, y: 675, rotation: 0.4363, width: 224.6, height: 240 }, { x: 1457, y: 772, rotation: -0.733, width: 267.3, height: 199.6 }, { x: 1508, y: 922, rotation: 0, width: 228.8, height: 351.5 }, { x: 1459, y: 1134, rotation: -0.9250, width: 232.2, height: 231.9 }, { x: 1121, y: 1309, rotation: -0.3665, width: 652.6, height: 224.8 }, { x: 754, y: 1478, rotation: -0.7156, width: 227.5, height: 225.5 }, { x: 639, y: 1643, rotation: 0.4013, width: 224.5, height: 257.7 }, { x: 601, y: 1863, rotation: 0.0698, width: 239.2, height: 274.2 }, { x: 612, y: 2086, rotation: -0.1396, width: 236.3, height: 235 }]; for (var i = 0; i < caminoPositions.length; i++) { var camino = game.addChild(LK.getAsset('camino', { anchorX: 0.5, anchorY: 0.5 })); camino.x = caminoPositions[i].x; camino.y = caminoPositions[i].y; camino.rotation = caminoPositions[i].rotation; // Apply rotation from positions array // Scale camino to match desired dimensions using width and height from caminoPositions var caminoWidth = caminoPositions[i].width || 200; // Use width from array or default to 200 var caminoHeight = caminoPositions[i].height || 200; // Use height from array or default to 200 camino.scaleX = caminoWidth / 200; // Asset is 200x200 by default camino.scaleY = caminoHeight / 200; camino.alpha = 0.2; // Set transparency to 0.2 caminoObjects.push(camino); } // Create end object and place it at the final waypoint var end = game.addChild(LK.getAsset('End', { anchorX: 0.5, anchorY: 0.5 })); // Position end closer to the penultimate waypoint var finalPosition = caminoPositions[caminoPositions.length - 1]; var penultimatePosition = caminoPositions[caminoPositions.length - 2]; // Calculate direction from penultimate to final waypoint var dx = finalPosition.x - penultimatePosition.x; var dy = finalPosition.y - penultimatePosition.y; var distance = Math.sqrt(dx * dx + dy * dy); // Move end object 60% of the way from final to penultimate position var moveBackRatio = 0.6; end.x = finalPosition.x - dx * moveBackRatio; end.y = finalPosition.y - dy * moveBackRatio; var directionAngle = Math.atan2(dy, dx); end.rotation = directionAngle; end.alpha = 1.0; // Remove transparency // Wave system variables var enemies = []; // Dynamic wave generation function function generateWaveData() { var waves = []; var baseEnemyCount = 10; var baseSpawnDelay = 800; for (var i = 0; i < 15; i++) { var distribution = getWaveDistribution(i); waves.push({ enemyCount: baseEnemyCount + i * 5, spawnDelay: Math.max(220, baseSpawnDelay - i * 40), // Dynamic enemy distribution based on wave number distribution: distribution, // Track spawned layer 6 variants layer6SpawnedA: 0, layer6SpawnedB: 0 }); } return waves; } // Simplified enemy distribution logic with layer 6 variant support function getWaveDistribution(waveIndex) { var distributions = [{ layers: [8, 0, 0, 0, 0, 2], layer6Variants: { A: 1, B: 1 } }, // Wave 1: 8x1-layer, 2x6-layer (1x6A, 1x6B) { layers: [15, 5, 0, 0, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 2: 15x1-layer, 5x2-layer { layers: [15, 5, 5, 0, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 3: 15x1, 5x2, 5x3-layer { layers: [15, 10, 5, 0, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 4: 15x1, 10x2, 5x3-layer { layers: [15, 10, 10, 0, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 5: 15x1, 10x2, 10x3-layer { layers: [15, 15, 10, 0, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 6: 15x1, 15x2, 10x3-layer { layers: [15, 15, 15, 0, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 7: 15x1, 15x2, 15x3-layer { layers: [15, 20, 15, 0, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 8: 15x1, 20x2, 15x3-layer { layers: [15, 20, 20, 0, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 9: 15x1, 20x2, 20x3-layer { layers: [15, 25, 20, 0, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 10: 15x1, 25x2, 20x3-layer { layers: [15, 25, 20, 5, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 11: 15x1, 25x2, 20x3, 5x4-layer { layers: [15, 25, 20, 10, 0, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 12: 15x1, 25x2, 20x3, 10x4-layer { layers: [15, 20, 20, 15, 5, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 13: 15x1, 20x2, 20x3, 15x4, 5x5-layer { layers: [15, 20, 20, 15, 10, 0], layer6Variants: { A: 0, B: 0 } }, // Wave 14: 15x1, 20x2, 20x3, 15x4, 10x5-layer { layers: [15, 20, 20, 15, 15, 5], layer6Variants: { A: 3, B: 2 } } // Wave 15: 15x1, 20x2, 20x3, 15x4, 15x5-layer, 5x6-layer (3x6A, 2x6B) ]; return distributions[waveIndex] || { layers: [10, 0, 0, 0, 0, 0], layer6Variants: { A: 0, B: 0 } }; } // Optimized function to determine enemy health layers function getEnemyHealthLayers(waveIndex, enemyIndex) { var distribution = getWaveDistribution(waveIndex); var layers = distribution.layers; var cumulative = 0; for (var layer = 0; layer < layers.length; layer++) { var previousCumulative = cumulative; cumulative += layers[layer]; if (enemyIndex < cumulative) { return { layer: layer + 1, indexInLayer: enemyIndex - previousCumulative }; } } return { layer: 1, indexInLayer: 0 }; // Default to 1 layer } var waveData = generateWaveData(); var currentWaveIndex = 0; var enemiesSpawned = 0; var waveInProgress = false; var nextSpawnTime = 0; // Enemy color configuration system var enemyColorConfigurations = { layer1: { variants: [{ color: 0xff0000, properties: {} } // Red ] }, layer2: { variants: [{ color: 0x0000ff, properties: {} } // Blue ] }, layer3: { variants: [{ color: 0x00ff00, properties: {} } // Green ] }, layer4: { variants: [{ color: 0xffff00, properties: {} } // Yellow ] }, layer5: { variants: [{ color: 0xff00ff, properties: {} } // Pink ] }, layer6: { variants: [{ color: 0xFFFFFF, properties: { antiHielo: true, antiFuego: false } }, // White for Anti hielo { color: 0x000000, properties: { antiHielo: false, antiFuego: true } } // Black for anti fuego ] } }; // Function to get enemy variant configuration function getEnemyVariant(layer, variantIndex) { var config = enemyColorConfigurations['layer' + layer]; if (!config || !config.variants || config.variants.length === 0) { return { color: 0xff0000, properties: {} }; // Default red } // If no specific variant requested, choose randomly if (variantIndex === undefined || variantIndex === null) { variantIndex = Math.floor(Math.random() * config.variants.length); } // Clamp variant index to available variants variantIndex = Math.max(0, Math.min(variantIndex, config.variants.length - 1)); return config.variants[variantIndex]; } // Function to apply enemy variant properties function applyEnemyVariant(enemy, variant) { // Apply color with smooth transition tween(enemy, { tint: variant.color }, { duration: 200, easing: tween.easeOut }); // Apply special properties if (variant.properties) { for (var prop in variant.properties) { enemy[prop] = variant.properties[prop]; } } } // Base enemy class with common functionality function createEnemyBase(healthLayers, speedMultiplier, color) { return Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('Enemy', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1 }); // Add hitbox to enemy var hitboxGraphics = self.attachAsset('hitbox', { anchorX: 0.5, anchorY: 0.5 }); hitboxGraphics.alpha = shotHitbox ? 0.2 : 0; // Set starting position to first camino waypoint self.x = caminoPositions[0].x; self.y = caminoPositions[0].y; // Path following properties self.currentWaypointIndex = 0; self.isMoving = false; // Health system for enemy self.maxHealth = healthLayers; self.currentHealth = self.maxHealth; self.healthLayers = []; self.canBeDestroyed = false; self.speedMultiplier = speedMultiplier; // Initialize special properties self.antiHielo = false; self.antiFuego = false; // Create health layers based on healthLayers parameter for (var i = 0; i < healthLayers; i++) { var layerVariant = getEnemyVariant(i + 1, 0); // Use first variant for each layer by default self.healthLayers.push({ min: i + 1, max: i + 1, color: layerVariant.color }); } // Method to update enemy color based on health self.updateHealthColor = function () { for (var i = 0; i < this.healthLayers.length; i++) { var layer = this.healthLayers[i]; if (this.currentHealth >= layer.min && this.currentHealth <= layer.max) { // Animate color transition using tween tween(this, { tint: layer.color }, { duration: 200, easing: tween.easeOut }); break; } } }; // Method to take damage self.takeDamage = function (damage) { var oldHealth = this.currentHealth; this.currentHealth = Math.max(0, this.currentHealth - damage); // Check if we crossed a layer boundary var oldLayer = -1; var newLayer = -1; for (var i = 0; i < this.healthLayers.length; i++) { var layer = this.healthLayers[i]; if (oldHealth >= layer.min && oldHealth <= layer.max) { oldLayer = i; } if (this.currentHealth >= layer.min && this.currentHealth <= layer.max) { newLayer = i; } } // Update color if we changed layers or if this is the first damage if (oldLayer !== newLayer || oldHealth === this.maxHealth) { this.updateHealthColor(); } // Update speed multiplier and movement speed based on new current health var newSpeed = getEnemySpeed(this); this.speedMultiplier = newSpeed / 260; // Calculate multiplier from speed // Immediately update movement speed if enemy is currently moving if (this.isMoving && this.moveSpeed) { // Apply fastGame speed boost if (fastGame) { newSpeed = newSpeed * 1.5; // 50% faster when fastGame is true } this.moveSpeed = newSpeed / 60; // Convert to pixels per frame (60 FPS) } // Check if enemy is dead if (this.currentHealth <= 0) { // Play random enemy death sound (1 of 3) var deathSounds = ['EnemyDerrivado', 'EnemigoDerrivado2', 'EnemigoDerrivado3']; var randomSoundIndex = Math.floor(Math.random() * deathSounds.length); LK.getSound(deathSounds[randomSoundIndex]).play(); // Stop any running animations if enemy is in end animation if (this.canBeDestroyed) { tween.stop(this, { scaleX: true, scaleY: true, alpha: true }); } // Remove from enemies array for (var j = 0; j < enemies.length; j++) { if (enemies[j] === this) { enemies.splice(j, 1); break; } } // Enemy is destroyed, remove it from the game this.destroy(); } }; // Method to move to next waypoint self.moveToNextWaypoint = function () { if (this.isMoving) { return; } // Don't start new movement if already moving // Check if we've reached near the end of the path (start animation much earlier) if (this.currentWaypointIndex >= caminoPositions.length - 2) { // Start shrinking and fading animation much earlier (5 waypoints before the end) this.canBeDestroyed = true; // Mark enemy as able to be destroyed during animation this.isEndAnimation = true; this.endAnimationTimer = 3000; // 3 seconds for end animation this.endAnimationStartScale = { x: this.scaleX, y: this.scaleY }; this.endAnimationStartAlpha = this.alpha; return; } this.currentWaypointIndex = (this.currentWaypointIndex + 1) % caminoPositions.length; var nextWaypoint = caminoPositions[this.currentWaypointIndex]; // Calculate movement properties for frame-by-frame movement var dx = nextWaypoint.x - this.x; var dy = nextWaypoint.y - this.y; var distance = Math.sqrt(dx * dx + dy * dy); var speed = getEnemySpeed(this); // Apply fastGame speed boost if (fastGame) { speed = speed * 1.5; // 50% faster when fastGame is true } // Apply slow effect if active if (this.slowEffect) { speed = speed * this.slowEffect; } // Store movement properties this.targetX = nextWaypoint.x; this.targetY = nextWaypoint.y; this.moveDirectionX = dx / distance; // Normalized direction this.moveDirectionY = dy / distance; this.moveSpeed = speed / 60; // Convert to pixels per frame (60 FPS) this.isMoving = true; // Calculate rotation to face the next waypoint var angle = Math.atan2(dy, dx); this.targetRotation = angle; // Calculate rotation difference for smooth rotation var angleDiff = angle - this.rotation; // Normalize angle difference to shortest path while (angleDiff > Math.PI) { angleDiff -= 2 * Math.PI; } while (angleDiff < -Math.PI) { angleDiff += 2 * Math.PI; } // Only rotate if angle difference is significant enough if (Math.abs(angleDiff) > 0.05) { // Stop any existing rotation tween tween.stop(this, { rotation: true }); // Calculate rotation duration based on enemy speed for more responsive turning var baseDuration = 400; // Base duration in milliseconds var speedFactor = this.speedMultiplier || 1; var rotationDuration = Math.max(150, baseDuration / speedFactor); // Faster enemies turn quicker // Adjust duration based on how much we need to turn var angleMagnitude = Math.abs(angleDiff); var angleFactor = angleMagnitude / Math.PI; // 0 to 1 based on how much turn is needed rotationDuration = rotationDuration * (0.3 + 0.7 * angleFactor); // Scale duration with turn amount // Use tween for smooth, velocity-adjusted rotation tween(this, { rotation: angle }, { duration: rotationDuration, easing: tween.easeOut }); } }; // Frame-by-frame update method for movement self.update = function () { // Handle slow effect countdown if (this.slowEffect && this.slowDuration > 0) { this.slowDuration -= frameTime * 1000; // Convert frame time to milliseconds if (this.slowDuration <= 0) { // Slow effect expired, restore normal speed this.slowEffect = null; this.slowDuration = 0; // Recalculate movement speed if currently moving if (this.isMoving && this.moveSpeed) { var normalSpeed = getEnemySpeed(this); if (fastGame) { normalSpeed = normalSpeed * 1.5; } this.moveSpeed = normalSpeed / 60; } } } // Handle stun effects countdown if (this.stunEffects && this.stunEffects.length > 0) { var activeStunEffect = null; for (var s = this.stunEffects.length - 1; s >= 0; s--) { var stunEffect = this.stunEffects[s]; stunEffect.duration -= frameTime * 1000; if (stunEffect.duration <= 0) { // Stun effect expired, remove it this.stunEffects.splice(s, 1); } else { // Keep the strongest stun effect (lowest value = more severe) if (!activeStunEffect || stunEffect.effect < activeStunEffect.effect) { activeStunEffect = stunEffect; } } } // Apply the strongest active stun effect if (activeStunEffect && this.isMoving && this.moveSpeed) { var baseSpeed = getEnemySpeed(this); if (fastGame) { baseSpeed = baseSpeed * 1.5; } // Apply both slow and stun effects (multiplicative) var totalEffect = (this.slowEffect || 1.0) * activeStunEffect.effect; this.moveSpeed = baseSpeed * totalEffect / 60; } } // Handle end animation if (this.isEndAnimation) { var animationSpeedMultiplier = fastGame ? 1.5 : 1.0; // 50% faster animation when fastGame is true this.endAnimationTimer -= frameTime * 1000 * animationSpeedMultiplier; // Convert frame time to milliseconds and apply speed multiplier var progress = 1 - this.endAnimationTimer / 3000; // Progress from 0 to 1 // Animate scale and alpha this.scaleX = this.endAnimationStartScale.x * (1 - progress * 0.9); // Scale down to 10% this.scaleY = this.endAnimationStartScale.y * (1 - progress * 0.9); this.alpha = this.endAnimationStartAlpha * (1 - progress); // Fade to 0 if (this.endAnimationTimer <= 0) { // Animation complete if (!this.parent) { return; // Enemy was already destroyed } // Enemy reached the end - reduce player life based on remaining health layers playerLife -= this.currentHealth; // Update life display lifeText.setText('Life: ' + playerLife); // Check if player is defeated if (playerLife <= 0) { LK.showGameOver(); return; } // Remove from enemies array for (var j = 0; j < enemies.length; j++) { if (enemies[j] === this) { enemies.splice(j, 1); break; } } // Destroy the enemy this.destroy(); } return; } // Rotation is now handled by tween system in moveToNextWaypoint method // Handle movement if (this.isMoving) { // Move towards target this.x += this.moveDirectionX * this.moveSpeed; this.y += this.moveDirectionY * this.moveSpeed; // Check if we've reached the target waypoint var dx = this.targetX - this.x; var dy = this.targetY - this.y; var distanceToTarget = Math.sqrt(dx * dx + dy * dy); if (distanceToTarget <= this.moveSpeed) { // Snap to target position this.x = this.targetX; this.y = this.targetY; this.isMoving = false; // Move to next waypoint immediately this.moveToNextWaypoint(); } } }; // Initialize enemy color self.updateHealthColor(); // Start movement after a short delay LK.setTimeout(function () { self.moveToNextWaypoint(); }, 500); return self; }); } // Enemy Layer 1 (Red, base speed) var EnemyCapa1 = createEnemyBase(1, 1.0, 0xff0000); // Enemy Layer 2 (Blue, 5% faster) var EnemyCapa2 = createEnemyBase(2, 1.05, 0x0000ff); // Enemy Layer 3 (Green, 10% faster) var EnemyCapa3 = createEnemyBase(3, 1.1, 0x00ff00); // Enemy Layer 4 (Yellow, 75% faster) var EnemyCapa4 = createEnemyBase(4, 1.75, 0xffff00); // Enemy Layer 5 (Pink, 150% faster) var EnemyCapa5 = createEnemyBase(5, 2.5, 0xff00ff); // Centralized enemy speed calculation function function getEnemySpeed(enemy) { var baseEnemySpeed = 260; // Base enemy speed in pixels per second var enemySpeedMultiplier = 1.0; if (enemy.currentHealth === 2) { enemySpeedMultiplier = 1.05; // 5% faster for 2 layers } else if (enemy.currentHealth === 3) { enemySpeedMultiplier = 1.1; // 10% faster for 3 layers } else if (enemy.currentHealth === 4) { enemySpeedMultiplier = 1.75; // 75% faster for 4 layers (yellow) } else if (enemy.currentHealth === 5) { enemySpeedMultiplier = 2.5; // 150% faster for 5 layers (pink) } else if (enemy.currentHealth === 6) { enemySpeedMultiplier = 1.1; // 10% faster for 6 layers (white) - same as green } return baseEnemySpeed * enemySpeedMultiplier; } // Function to update all enemy speeds (useful for fastGame toggle) function updateAllEnemySpeeds() { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy && enemy.parent && enemy.isMoving && enemy.moveSpeed) { var newSpeed = getEnemySpeed(enemy); // Apply fastGame speed boost if (fastGame) { newSpeed = newSpeed * 1.5; // 50% faster when fastGame is true } enemy.moveSpeed = newSpeed / 60; // Convert to pixels per frame (60 FPS) } } } // Function to create enemy with specified health layers and optional variant function createEnemy(healthLayers, variantIndex) { var enemy; switch (healthLayers) { case 1: enemy = game.addChild(new EnemyCapa1()); break; case 2: enemy = game.addChild(new EnemyCapa2()); break; case 3: enemy = game.addChild(new EnemyCapa3()); break; case 4: enemy = game.addChild(new EnemyCapa4()); break; case 5: enemy = game.addChild(new EnemyCapa5()); break; case 6: enemy = game.addChild(new EnemyCapa6()); break; default: enemy = game.addChild(new EnemyCapa1()); break; } // Apply variant configuration after enemy creation var variant = getEnemyVariant(healthLayers, variantIndex); applyEnemyVariant(enemy, variant); enemies.push(enemy); return enemy; } // Function to start a wave function startWave(waveIndex) { if (waveIndex >= waveData.length) { // All waves completed LK.showYouWin(); return; } currentWaveIndex = waveIndex; enemiesSpawned = 0; waveInProgress = true; nextSpawnTime = Date.now() + 2000; // Start spawning after 2 seconds currentWave = waveIndex + 1; } // Start first wave only if autoStartWaves is true if (autoStartWaves) { startWave(0); } // Enemy is now stationary - no movement code needed // UI mode management var UI_MODE_TOWER_CREATION = 'creation'; var UI_MODE_TOWER_UPGRADE = 'upgrade'; var currentUIMode = UI_MODE_TOWER_CREATION; // UI Upgrade panel for selected tower var towerUpgradeContainer = game.addChild(new Container()); towerUpgradeContainer.x = 1024; towerUpgradeContainer.y = 2250; // Helper function to update range area visually function updateRangeArea(tower) { if (tower.children[0]) { var areaScale = tower.range * 2 / 100; tower.children[0].scaleX = areaScale; tower.children[0].scaleY = areaScale; } } // Helper function to increment upgrade counter function incrementUpgradeCounter(tower, type) { if (type === 'left') { tower.leftUpgrades = tower.leftUpgrades || 0; tower.leftUpgrades++; } else if (type === 'right') { tower.rightUpgrades = tower.rightUpgrades || 0; tower.rightUpgrades++; } } // Tower-specific upgrade definitions for flexible individual abilities var upgradeDefinitions = { // Normal Tower (torreInicial) upgrades normal: { speed: { title: 'Rapid Fire', cost: 75, apply: function apply(tower) { tower.cadence = Math.max(100, tower.cadence * 0.8); incrementUpgradeCounter(tower, 'left'); } }, range: { title: 'Eagle Eye', cost: 100, apply: function apply(tower) { tower.range = Math.floor(tower.range * 1.25); tower.rangeSquared = tower.range * tower.range; updateRangeArea(tower); incrementUpgradeCounter(tower, 'left'); } }, multishot: { title: 'Triple Threat', cost: 150, apply: function apply(tower) { tower.hasMultishot = true; incrementUpgradeCounter(tower, 'left'); } }, left_final: { title: 'Berserker Mode', cost: 200, apply: function apply(tower) { tower.damage += 1; tower.cadence = Math.max(50, tower.cadence * 0.25); incrementUpgradeCounter(tower, 'left'); } }, right1_damage: { title: 'Brutal Force', cost: 50, apply: function apply(tower) { tower.damage += 1; incrementUpgradeCounter(tower, 'right'); } }, right1_cross: { title: 'Chain Lightning', cost: 75, apply: function apply(tower) { tower.cross = (tower.cross || 1) + 2; incrementUpgradeCounter(tower, 'right'); } }, right1_range: { title: 'Sniper Scope', cost: 100, apply: function apply(tower) { tower.range = Math.floor(tower.range * 1.3); tower.rangeSquared = tower.range * tower.range; updateRangeArea(tower); incrementUpgradeCounter(tower, 'right'); } }, right1_cross_final: { title: 'Storm Walker', cost: 125, apply: function apply(tower) { tower.cross = (tower.cross || 1) + 2; tower.bulletSpeedMultiplier = 1.3; tower.bulletDurationMultiplier = 1.5; incrementUpgradeCounter(tower, 'right'); } }, right1_ultimate: { title: 'Apocalypse', cost: 300, apply: function apply(tower) { tower.bulletSpeedMultiplier = (tower.bulletSpeedMultiplier || 1) * 1.6; tower.cross = (tower.cross || 1) + 6; tower.damage += 2; incrementUpgradeCounter(tower, 'right'); } } }, // Ice Tower (Icetower) upgrades ice: { range: { title: '+10% Range', cost: 100, apply: function apply(tower) { tower.range = Math.floor(tower.range * 1.1); tower.rangeSquared = tower.range * tower.range; updateRangeArea(tower); incrementUpgradeCounter(tower, 'left'); } }, slow_duration: { title: '+2 Sec Freeze', cost: 75, apply: function apply(tower) { tower.slowDuration = (tower.slowDuration || 1000) + 2000; // Add 2 seconds incrementUpgradeCounter(tower, 'left'); } }, damage_freeze: { title: 'Damage Freeze', cost: 125, apply: function apply(tower) { tower.damageOnFreeze = true; // Remove 1 layer when freezing incrementUpgradeCounter(tower, 'left'); } }, left_final: { title: 'Stun Field', cost: 250, apply: function apply(tower) { tower.stunChance = 0.3; // 30% chance to stun (100% slow) for 1 second incrementUpgradeCounter(tower, 'left'); } }, right1_slow_duration: { title: 'Lingering Frost', cost: 60, apply: function apply(tower) { tower.slowDuration = 1500; // Increase from 1 to 1.5 seconds incrementUpgradeCounter(tower, 'right'); } }, right1_freeze_chance: { title: 'Ice Shards', cost: 100, apply: function apply(tower) { tower.freezeChance = 0.3; // 30% chance to freeze for 1 second incrementUpgradeCounter(tower, 'right'); } }, right1_ice_storm: { title: 'Ice Storm', cost: 150, apply: function apply(tower) { tower.hasIceStorm = true; // Creates additional ice waves incrementUpgradeCounter(tower, 'right'); } }, right1_ultimate: { title: 'Glacier Prison', cost: 300, apply: function apply(tower) { tower.slowEffect = 0.2; // 80% slow tower.slowDuration = 3000; // 3 seconds tower.freezeChance = 0.5; // 50% freeze chance tower.hasIceStorm = true; incrementUpgradeCounter(tower, 'right'); } } }, // Fire Tower (FireTower) upgrades fire: { burn_damage: { title: 'Ignite', cost: 80, apply: function apply(tower) { tower.hasBurn = true; tower.burnDamage = 0.5; // Damage over time tower.burnDuration = 2000; // 2 seconds incrementUpgradeCounter(tower, 'left'); } }, explosive_shots: { title: 'Explosive Rounds', cost: 120, apply: function apply(tower) { tower.hasExplosion = true; tower.explosionRadius = 100; incrementUpgradeCounter(tower, 'left'); } }, inferno: { title: 'Inferno Blast', cost: 150, apply: function apply(tower) { tower.explosionRadius = Math.floor((tower.explosionRadius || 100) * 1.5); tower.burnDamage = (tower.burnDamage || 0.5) * 1.5; incrementUpgradeCounter(tower, 'left'); } }, left_final: { title: 'Phoenix Wrath', cost: 280, apply: function apply(tower) { tower.damage += 2; tower.hasExplosion = true; tower.explosionRadius = 200; tower.burnDamage = 1.0; tower.burnDuration = 3000; incrementUpgradeCounter(tower, 'left'); } }, right1_damage: { title: 'Blazing Power', cost: 50, apply: function apply(tower) { tower.damage += 1; incrementUpgradeCounter(tower, 'right'); } }, right1_speed: { title: 'Rapid Burn', cost: 75, apply: function apply(tower) { tower.cadence = Math.max(100, tower.cadence * 0.8); incrementUpgradeCounter(tower, 'right'); } }, right1_flame_spread: { title: 'Flame Spread', cost: 125, apply: function apply(tower) { tower.hasFlameSpread = true; // Burns spread to nearby enemies incrementUpgradeCounter(tower, 'right'); } }, right1_ultimate: { title: 'Solar Flare', cost: 350, apply: function apply(tower) { tower.damage += 3; tower.cadence = Math.max(50, tower.cadence * 0.5); tower.hasFlameSpread = true; tower.explosionRadius = 250; tower.burnDamage = 1.5; incrementUpgradeCounter(tower, 'right'); } } }, // Universal upgrades (available to all towers) universal: { targeting_priority: { title: 'Hunter Instinct', cost: 0, apply: function apply(tower) { var priorities = ['first', 'last', 'strongest']; var currentIndex = priorities.indexOf(tower.targetingPriority); tower.targetingPriority = priorities[(currentIndex + 1) % priorities.length]; } }, maxed: { title: 'Legendary', cost: 999999, apply: function apply(tower) { // Do nothing - this upgrade can't be purchased } } } }; // Tower-specific upgrade path configurations var upgradePathConfigurations = { // Normal Tower paths normal: { left: ['speed', 'range', 'multishot', 'left_final'], right: ['right1_damage', 'right1_range', 'right1_cross_final', 'right1_ultimate'] }, // Ice Tower paths ice: { left: ['range', 'slow_duration', 'damage_freeze', 'left_final'], right: ['right1_slow_duration', 'right1_freeze_chance', 'right1_ice_storm', 'right1_ultimate'] }, // Fire Tower paths fire: { left: ['burn_damage', 'explosive_shots', 'inferno', 'left_final'], right: ['right1_damage', 'right1_speed', 'right1_flame_spread', 'right1_ultimate'] } }; // Function to detect tower type from asset function getTowerType(tower) { // Check tower's asset type by looking at its children for (var i = 0; i < tower.children.length; i++) { var child = tower.children[i]; if (child.texture && child.texture.baseTexture && child.texture.baseTexture.resource && child.texture.baseTexture.resource.url) { var url = child.texture.baseTexture.resource.url; if (url.includes('torreInicial')) { return 'normal'; } else if (url.includes('Icetower')) { return 'ice'; } else if (url.includes('FireTower')) { return 'fire'; } } } return 'normal'; // Default to normal if can't detect } // Function to get upgrade definition for a tower function getUpgradeDefinition(tower, upgradeKey) { var towerType = getTowerType(tower); var towerUpgrades = upgradeDefinitions[towerType]; if (towerUpgrades && towerUpgrades[upgradeKey]) { return towerUpgrades[upgradeKey]; } // Fall back to universal upgrades return upgradeDefinitions.universal[upgradeKey] || null; } // Function to get current left upgrade options for a tower function getLeftUpgradeOptions(tower) { tower.leftUpgrades = tower.leftUpgrades || 0; var towerType = getTowerType(tower); var pathConfig = upgradePathConfigurations[towerType]; if (pathConfig && pathConfig.left) { return pathConfig.left[tower.leftUpgrades] || 'maxed'; } return 'maxed'; } // Function to get current right upgrade options for a tower function getRightUpgradeOptions(tower) { tower.rightUpgrades = tower.rightUpgrades || 0; var towerType = getTowerType(tower); var pathConfig = upgradePathConfigurations[towerType]; if (pathConfig && pathConfig.right) { return { first: pathConfig.right[tower.rightUpgrades] || 'maxed', second: null }; } return { first: 'maxed', second: null }; } // Left upgrade frame - Damage +1 var leftUpgradeFrame = towerUpgradeContainer.addChild(LK.getAsset('UpgradeBG', { anchorX: 0.5, anchorY: 0.5 })); leftUpgradeFrame.x = -700; leftUpgradeFrame.y = 180; var leftUpgradeTitle = towerUpgradeContainer.addChild(new Text2('', { size: 60, fill: 0xFFFFFF })); leftUpgradeTitle.anchor.set(0.5, 0.5); leftUpgradeTitle.x = -700; leftUpgradeTitle.y = 0; // Right upgrade frames and titles (only 2 slots) var rightUpgradeFrame1 = towerUpgradeContainer.addChild(LK.getAsset('UpgradeBG', { anchorX: 0.5, anchorY: 0.5 })); rightUpgradeFrame1.x = 100; rightUpgradeFrame1.y = 180; var rightUpgradeTitle1 = towerUpgradeContainer.addChild(new Text2('', { size: 60, fill: 0xFFFFFF })); rightUpgradeTitle1.anchor.set(0.5, 0.5); rightUpgradeTitle1.x = 100; rightUpgradeTitle1.y = 0; var rightUpgradeFrame2 = towerUpgradeContainer.addChild(LK.getAsset('UpgradeBG', { anchorX: 0.5, anchorY: 0.5 })); rightUpgradeFrame2.x = 400; rightUpgradeFrame2.y = 180; var rightUpgradeTitle2 = towerUpgradeContainer.addChild(new Text2('', { size: 60, fill: 0xFFFFFF })); rightUpgradeTitle2.anchor.set(0.5, 0.5); rightUpgradeTitle2.x = 400; rightUpgradeTitle2.y = 0; // Priority button frame and title var priorityUpgradeFrame = towerUpgradeContainer.addChild(LK.getAsset('UpgradeBG', { anchorX: 0.5, anchorY: 0.5 })); priorityUpgradeFrame.x = 700; priorityUpgradeFrame.y = 180; var priorityUpgradeTitle = towerUpgradeContainer.addChild(new Text2('', { size: 60, fill: 0xFFFFFF })); priorityUpgradeTitle.anchor.set(0.5, 0.5); priorityUpgradeTitle.x = 700; priorityUpgradeTitle.y = 0; // Store reference to currently selected tower var selectedTowerForUpgrade = null; // Function to switch UI modes function setUIMode(mode) { currentUIMode = mode; if (mode === UI_MODE_TOWER_CREATION) { // Show tower creation buttons and their backgrounds for (var i = 0; i < towerButtons.length; i++) { towerButtons[i].visible = true; if (towerButtons[i].buttonBG) { towerButtons[i].buttonBG.visible = true; } } // Hide tower upgrade panel towerUpgradeContainer.visible = false; // Show fast mode button when in tower creation mode if (speedButton && speedButton.parent) { speedButton.visible = true; } // Show auto wave button when in tower creation mode if (autoWaveButton && autoWaveButton.parent) { autoWaveButton.visible = true; } } else if (mode === UI_MODE_TOWER_UPGRADE) { // Hide tower creation buttons and their backgrounds for (var i = 0; i < towerButtons.length; i++) { towerButtons[i].visible = false; if (towerButtons[i].buttonBG) { towerButtons[i].buttonBG.visible = false; } } // Show tower upgrade panel towerUpgradeContainer.visible = true; // Hide fast mode button when in tower upgrade mode if (speedButton && speedButton.parent) { speedButton.visible = false; } // Hide auto wave button when in tower upgrade mode if (autoWaveButton && autoWaveButton.parent) { autoWaveButton.visible = false; } } } // Function to create upgrade button with animation and functionality function createUpgradeButton(upgradeType, x, y, container) { var upgradeDef = getUpgradeDefinition(selectedTowerForUpgrade, upgradeType); if (!upgradeDef) { return null; } var upgradeButton = container.addChild(LK.getAsset('uiBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.15, scaleY: 0.15 })); upgradeButton.x = x; upgradeButton.y = y; var upgradeText = container.addChild(new Text2('BUY', { size: 50, fill: 0x000000 })); upgradeText.anchor.set(0.5, 0.5); upgradeText.x = x; upgradeText.y = y; // Add click handler with animation upgradeButton.down = function (x, y, obj) { var currentUpgradeDef = getUpgradeDefinition(selectedTowerForUpgrade, upgradeType); if (selectedTowerForUpgrade && currentUpgradeDef && playerMoney >= currentUpgradeDef.cost && upgradeType !== 'maxed') { // Apply upgrade currentUpgradeDef.apply(selectedTowerForUpgrade); // Deduct cost playerMoney -= currentUpgradeDef.cost; // Animate button press effect tween(upgradeButton, { scaleX: 0.12, scaleY: 0.12 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(upgradeButton, { scaleX: 0.15, scaleY: 0.15 }, { duration: 150, easing: tween.bounceOut }); } }); // Flash button to show upgrade LK.effects.flashObject(upgradeButton, 0xFFFFFF, 300); // Refresh the upgrade display immediately after purchase updateTowerUpgrade(selectedTowerForUpgrade); } }; return { button: upgradeButton, text: upgradeText, updateDisplay: function updateDisplay() { if (upgradeType === 'maxed') { upgradeButton.alpha = 0.5; upgradeText.alpha = 0.5; upgradeText.setText('BUY MAX'); } else { var currentUpgradeDef = getUpgradeDefinition(selectedTowerForUpgrade, upgradeType); if (currentUpgradeDef) { var canAfford = selectedTowerForUpgrade && playerMoney >= currentUpgradeDef.cost; upgradeButton.alpha = canAfford ? 1.0 : 0.5; upgradeText.alpha = canAfford ? 1.0 : 0.5; upgradeText.setText('BUY'); } else { upgradeButton.alpha = 0.5; upgradeText.alpha = 0.5; upgradeText.setText('N/A'); } } } }; } // Upgrade button configurations var upgradeButtonConfigs = [{ type: 'left', x: -700, y: 380, title: leftUpgradeTitle, frame: leftUpgradeFrame }, { type: 'right1', x: 100, y: 380, title: rightUpgradeTitle1, frame: rightUpgradeFrame1 }, { type: 'right2', x: 400, y: 380, title: rightUpgradeTitle2, frame: rightUpgradeFrame2 }, { type: 'priority', x: 700, y: 380, title: priorityUpgradeTitle, frame: priorityUpgradeFrame }]; // Function to update tower upgrade display function updateTowerUpgrade(tower) { if (tower) { // Store reference to selected tower for upgrades selectedTowerForUpgrade = tower; // Switch to tower upgrade mode when a tower is selected setUIMode(UI_MODE_TOWER_UPGRADE); // Get current upgrade options for this tower var leftOption = getLeftUpgradeOptions(tower); var rightOptions = getRightUpgradeOptions(tower); var upgradeOptions = [leftOption, rightOptions.first, rightOptions.second, 'targeting_priority']; // Clean up existing upgrade buttons and create new ones var upgradeButtons = [leftUpgrade, right1Upgrade, right2Upgrade, priorityUpgrade]; for (var i = 0; i < upgradeButtons.length; i++) { // Clean up existing button if (upgradeButtons[i] && upgradeButtons[i].button && upgradeButtons[i].button.parent) { upgradeButtons[i].button.destroy(); upgradeButtons[i].text.destroy(); } // Create new button var config = upgradeButtonConfigs[i]; var option = upgradeOptions[i]; upgradeButtons[i] = option ? createUpgradeButton(option, config.x, config.y, towerUpgradeContainer) : null; // Update frame visibility if (option) { var upgradeDef = getUpgradeDefinition(tower, option); if (upgradeDef) { var title = option === 'targeting_priority' ? 'Priority: ' + (tower.targetingPriority === 'first' ? 'First' : tower.targetingPriority === 'last' ? 'Last' : 'Strongest') : upgradeDef.title; config.title.setText(title); config.frame.visible = true; config.title.visible = true; } else { config.title.setText(''); config.frame.visible = false; config.title.visible = false; } } else { config.title.setText(''); config.frame.visible = false; config.title.visible = false; } // Update display if (upgradeButtons[i]) { upgradeButtons[i].updateDisplay(); } } // Update references leftUpgrade = upgradeButtons[0]; right1Upgrade = upgradeButtons[1]; right2Upgrade = upgradeButtons[2]; priorityUpgrade = upgradeButtons[3]; } else { // Clear reference to selected tower selectedTowerForUpgrade = null; // Hide priority upgrade frame when no tower selected priorityUpgradeFrame.visible = false; priorityUpgradeTitle.visible = false; // Switch back to tower creation mode when no tower is selected setUIMode(UI_MODE_TOWER_CREATION); } } // Create upgrade buttons using the reusable system var leftUpgrade = null; // Will be created dynamically var right1Upgrade = null; // Will be created dynamically var right2Upgrade = null; // Will be created dynamically var priorityUpgrade = null; // Will be created dynamically // Initialize with no tower selected and tower creation mode updateTowerUpgrade(null); setUIMode(UI_MODE_TOWER_CREATION); // Add money, life and wave UI texts in the top-right corner var moneyText = new Text2('Money: 100', { size: 80, fill: 0xFFFF00 }); moneyText.anchor.set(1, 0); moneyText.x = 1900; moneyText.y = 150; game.addChild(moneyText); var lifeText = new Text2('Life: 20', { size: 80, fill: 0xFF0000 }); lifeText.anchor.set(1, 0); lifeText.x = 1900; lifeText.y = 250; game.addChild(lifeText); var waveText = new Text2('Wave: 1', { size: 80, fill: 0x00FF00 }); waveText.anchor.set(1, 0); waveText.x = 1900; waveText.y = 350; game.addChild(waveText); // Add fast mode button to UI towers section var speedButton = ui.addChild(LK.getAsset('x1Speed', { anchorX: 0.5, anchorY: 0.5 })); speedButton.x = 800; speedButton.y = -130; // Add auto wave button below speed button var autoWaveButton = ui.addChild(LK.getAsset('AutoWaveStop', { anchorX: 0.5, anchorY: 0.5 })); autoWaveButton.x = 800; autoWaveButton.y = 70; // Add touch handler to speed button speedButton.down = function (x, y, obj) { // Toggle fastGame variable fastGame = !fastGame; // Update button asset to reflect current speed if (fastGame) { // Remove current button and create new one with FastMode asset speedButton.destroy(); speedButton = ui.addChild(LK.getAsset('FastMode', { anchorX: 0.5, anchorY: 0.5 })); speedButton.x = 800; speedButton.y = -130; // Re-attach the down handler to the new button speedButton.down = arguments.callee; } else { // Remove current button and create new one with x1Speed asset speedButton.destroy(); speedButton = ui.addChild(LK.getAsset('x1Speed', { anchorX: 0.5, anchorY: 0.5 })); speedButton.x = 800; speedButton.y = -130; // Re-attach the down handler to the new button speedButton.down = arguments.callee; } // Immediately update all existing enemies' speeds updateAllEnemySpeeds(); // Immediately update next spawn time to prevent delayed spawning if (waveInProgress && nextSpawnTime > Date.now()) { var currentWaveData = waveData[currentWaveIndex]; var baseSpawnDelay = currentWaveData.spawnDelay; var adjustedSpawnDelay = fastGame ? baseSpawnDelay / 1.5 : baseSpawnDelay; var timeRemaining = nextSpawnTime - Date.now(); var newTimeRemaining = fastGame ? timeRemaining / 1.5 : timeRemaining * 1.5; nextSpawnTime = Date.now() + newTimeRemaining; } }; // Add touch handler to auto wave button autoWaveButton.down = function (x, y, obj) { // Toggle autoStartWaves variable autoStartWaves = !autoStartWaves; // Update button asset to reflect current auto wave state if (autoStartWaves) { // Add rotation animation when auto wave is true var _startAutoWaveAnimation = function startAutoWaveAnimation() { tween(autoWaveButton, { rotation: autoWaveButton.rotation + Math.PI * 4 // Rotate 2 full turns (4 * PI) }, { duration: 5000, // Animation duration in milliseconds easing: tween.easeInOut, // Start slow, speed up, slow down onFinish: function onFinish() { // Animation complete, check if autoStartWaves is still true and repeat the animation if (autoStartWaves && autoWaveButton && autoWaveButton.parent) { _startAutoWaveAnimation(); // Start the animation again } } }); }; // Remove current button and create new one with Wavestart asset autoWaveButton.destroy(); autoWaveButton = ui.addChild(LK.getAsset('Wavestart', { anchorX: 0.5, anchorY: 0.5 })); autoWaveButton.x = 800; autoWaveButton.y = 70; // Re-attach the down handler to the new button autoWaveButton.down = arguments.callee; // Start the first wave if no wave is in progress if (!waveInProgress && currentWaveIndex < waveData.length) { startWave(currentWaveIndex); } _startAutoWaveAnimation(); // Initial call to start the loop } else { // Remove current button and create new one with AutoWaveStop asset autoWaveButton.destroy(); autoWaveButton = ui.addChild(LK.getAsset('AutoWaveStop', { anchorX: 0.5, anchorY: 0.5 })); autoWaveButton.x = 800; autoWaveButton.y = 70; // Re-attach the down handler to the new button autoWaveButton.down = arguments.callee; // Stop the rotation animation when auto wave is false tween.stop(autoWaveButton, { rotation: true }); } }; // Initialize game variables var gameStart = false; // Boolean to control game start state var playerMoney = 99999; var playerLife = 20; var currentWave = 1; var PathShow = false; // Boolean to control path visibility var vida = 20; // Player's life/health var dinero = 100; // Player's money/currency var fastGame = false; // Boolean to control 2x speed mode var autoStartWaves = false; // Boolean to control automatic wave starting var antiHielo = false; // Boolean to control anti ice effect var antiFuego = false; // Boolean to control anti fire effect // Camino path dimensions - can be changed while maintaining 200x200 default var caminoWidth = 200; var caminoHeight = 200; // Top overlay layer (pointer on top of everything) var puntero = game.addChild(LK.getAsset('Puntero', { anchorX: 0.5, anchorY: 0.5 })); puntero.x = 1024; puntero.y = 1366; puntero.alpha = 0; // Game move handler for dragging game.move = function (x, y, obj) { puntero.x = x; puntero.y = y; if (isDragging && draggedTower) { var gameplayBounds = getGameplayBounds(); var uiBounds = getUIBounds(); var offset = calculateDragOffset(x, y); var targetX = x + offset.offsetX; var targetY = y + offset.offsetY; // Apply boundary constraints if (targetY > uiBounds.top) { targetY = uiBounds.top - 70; } if (targetY < gameplayBounds.top) { targetY = gameplayBounds.top + 70; } if (targetX < uiBounds.left) { targetX = uiBounds.left + 70; } if (targetX < gameplayBounds.left + 70) { targetX = gameplayBounds.left + 70; } if (targetX > gameplayBounds.right - 70) { targetX = gameplayBounds.right - 70; } // Check if tower is too close to existing towers var minDistance = 140; // Standardized minimum distance between towers var minDistanceSquared = minDistance * minDistance; var tooCloseToOtherTowers = false; for (var i = 0; i < placedTowers.length; i++) { var existingTower = placedTowers[i]; var dx = targetX - existingTower.x; var dy = targetY - existingTower.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared < minDistanceSquared) { tooCloseToOtherTowers = true; break; } } // Check if tower would be placed on camino path var onCamino = false; for (var i = 0; i < caminoObjects.length; i++) { var camino = caminoObjects[i]; // Use dynamic dimensions with scaling var caminoEffectiveWidth = caminoWidth * camino.scaleX; var caminoEffectiveHeight = caminoHeight * camino.scaleY; // Check collision with rotated rectangle if (isPointInRotatedRect(targetX, targetY, camino.x, camino.y, caminoEffectiveWidth, caminoEffectiveHeight, camino.rotation)) { onCamino = true; break; } } // Update area tint based on pointer position and tower proximity var pointerInUI = isPointInUI(puntero.x, puntero.y); var cannotPlace = pointerInUI || tooCloseToOtherTowers || onCamino; var finalX, finalY; if (cannotPlace) { // Apply red tint to area when cannot place tower if (draggedTower.children[0]) { draggedTower.children[0].tint = 0xff0000; } // If position is invalid, try to find the closest valid position // First check if we have a last valid position if (draggedTower.lastValidX !== undefined && draggedTower.lastValidY !== undefined) { // Calculate distance to see if we should try to interpolate or just use last valid var distanceToLastValid = Math.sqrt((targetX - draggedTower.lastValidX) * (targetX - draggedTower.lastValidX) + (targetY - draggedTower.lastValidY) * (targetY - draggedTower.lastValidY)); // If we're reasonably close to last valid position, try to find a valid position between current and last valid if (distanceToLastValid < 250) { var validPositionFound = false; var steps = 20; // More steps for better precision for (var step = 1; step <= steps; step++) { var ratio = step / steps; var testX = draggedTower.lastValidX + (targetX - draggedTower.lastValidX) * ratio; var testY = draggedTower.lastValidY + (targetY - draggedTower.lastValidY) * ratio; // Test this interpolated position var testTooClose = false; var testMinDistanceSquared = 140 * 140; // Use same standardized distance for (var i = 0; i < placedTowers.length; i++) { var existingTower = placedTowers[i]; var dx = testX - existingTower.x; var dy = testY - existingTower.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared < testMinDistanceSquared) { testTooClose = true; break; } } var testOnCamino = false; if (!testTooClose) { for (var i = 0; i < caminoObjects.length; i++) { var camino = caminoObjects[i]; // Use dynamic dimensions with scaling var caminoEffectiveWidth = caminoWidth * camino.scaleX; var caminoEffectiveHeight = caminoHeight * camino.scaleY; // Check collision with rotated rectangle if (isPointInRotatedRect(testX, testY, camino.x, camino.y, caminoEffectiveWidth, caminoEffectiveHeight, camino.rotation)) { testOnCamino = true; break; } } } var testPointerInUI = isPointInUI(testX, testY); if (!testTooClose && !testOnCamino && !testPointerInUI) { // Found a valid position finalX = testX; finalY = testY; validPositionFound = true; // Update last valid position to this new position draggedTower.lastValidX = testX; draggedTower.lastValidY = testY; // Remove red tint since we found a valid position if (draggedTower.children[0]) { draggedTower.children[0].tint = 0xFFFFFF; } break; } } if (!validPositionFound) { // Use last valid position finalX = draggedTower.lastValidX; finalY = draggedTower.lastValidY; } } else { // Too far from last valid position, just use it finalX = draggedTower.lastValidX; finalY = draggedTower.lastValidY; } } else { // No last valid position, use target position (shouldn't happen with proper initialization) finalX = targetX; finalY = targetY; } } else { // Remove tint from area when tower can be placed if (draggedTower.children[0]) { draggedTower.children[0].tint = 0xFFFFFF; } // Store this as the last valid position draggedTower.lastValidX = targetX; draggedTower.lastValidY = targetY; finalX = targetX; finalY = targetY; } // Apply different movement behavior based on validity if (cannotPlace) { // In invalid zones, use immediate positioning for quick stops draggedTower.x = finalX; draggedTower.y = finalY; } else { // In valid zones, use instant positioning for immediate tower appearance draggedTower.x = finalX; draggedTower.y = finalY; } } }; // Cross variable to control bullet destruction after hitting enemies - now tower-specific // var Cross = 1; // Removed global Cross, now each tower has its own cross value // Variable to control enemy hitbox visibility var shotHitbox = true; // When true, show enemy hitboxes; when false, hide them // Game update handler game.update = function () { // Update UI texts with current values moneyText.setText('Money: ' + playerMoney); lifeText.setText('Life: ' + playerLife); waveText.setText('Wave: ' + (currentWaveIndex + 1)); // Update path visibility based on PathShow variable for (var i = 0; i < caminoObjects.length; i++) { caminoObjects[i].alpha = PathShow ? 0.2 : 0; } // Update enemy hitbox visibility based on shotHitbox variable for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy && enemy.parent) { // Find the hitbox child in the enemy for (var j = 0; j < enemy.children.length; j++) { var child = enemy.children[j]; // Check if this is a hitbox (either by texture URL or by alpha value) var isHitbox = false; if (child.texture && child.texture.baseTexture && child.texture.baseTexture.resource && child.texture.baseTexture.resource.url && child.texture.baseTexture.resource.url.includes('hitbox')) { isHitbox = true; } else if (child.alpha === 0.2 || child.alpha === 0) { // This might be a hitbox based on alpha value isHitbox = true; } if (isHitbox) { child.alpha = shotHitbox ? 0.2 : 0; break; } } } } // Wave spawning logic if (waveInProgress && currentWaveIndex < waveData.length) { var currentWaveData = waveData[currentWaveIndex]; var currentTime = Date.now(); if (currentTime >= nextSpawnTime && enemiesSpawned < currentWaveData.enemyCount) { // Determine health layers for this enemy using optimized function var enemyData = getEnemyHealthLayers(currentWaveIndex, enemiesSpawned); var healthLayers = enemyData.layer; // Determine variant for layer 6 enemies based on wave configuration var variantIndex = undefined; if (healthLayers === 6) { var distribution = currentWaveData.distribution; // Check if we should spawn 6A or 6B based on configured amounts if (currentWaveData.layer6SpawnedA < distribution.layer6Variants.A) { variantIndex = 0; // Spawn 6A (Anti hielo - white) currentWaveData.layer6SpawnedA++; } else if (currentWaveData.layer6SpawnedB < distribution.layer6Variants.B) { variantIndex = 1; // Spawn 6B (Anti fuego - black) currentWaveData.layer6SpawnedB++; } } // Spawn enemy with variant createEnemy(healthLayers, variantIndex); enemiesSpawned++; var spawnDelay = currentWaveData.spawnDelay; // Apply fastGame spawn rate boost (50% faster = divide by 1.5) if (fastGame) { spawnDelay = spawnDelay / 1.5; } nextSpawnTime = currentTime + spawnDelay; } // Check if wave is complete if (enemiesSpawned >= currentWaveData.enemyCount && enemies.length === 0) { waveInProgress = false; // Start next wave after a delay only if autoStartWaves is true if (autoStartWaves) { var waveDelay = 500; // 0.5 second delay between waves (much faster) // Apply fastGame wave interval boost (50% faster = divide by 1.5) if (fastGame) { waveDelay = waveDelay / 1.5; } LK.setTimeout(function () { startWave(currentWaveIndex + 1); }, waveDelay); } } } }; // Game release handler game.up = function (x, y, obj) { if (isDragging && draggedTower) { var gameplayBounds = getGameplayBounds(); var uiBounds = getUIBounds(); var pointerInUI = isPointInUI(puntero.x, puntero.y); var towerInUI = draggedTower.x >= uiBounds.left && draggedTower.x <= uiBounds.right && draggedTower.y >= uiBounds.top && draggedTower.y <= uiBounds.bottom; // Check if tower is too close to existing towers at current position var minDistance = 140; // Standardized minimum distance between towers var minDistanceSquared = minDistance * minDistance; var tooCloseToTowers = false; for (var i = 0; i < placedTowers.length; i++) { var existingTower = placedTowers[i]; var dx = draggedTower.x - existingTower.x; var dy = draggedTower.y - existingTower.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared < minDistanceSquared) { tooCloseToTowers = true; break; } } // Check if tower would be placed on camino path at current position var onCamino = false; for (var i = 0; i < caminoObjects.length; i++) { var camino = caminoObjects[i]; // Use dynamic dimensions with scaling var caminoEffectiveWidth = caminoWidth * camino.scaleX; var caminoEffectiveHeight = caminoHeight * camino.scaleY; // Check collision with rotated rectangle if (isPointInRotatedRect(draggedTower.x, draggedTower.y, camino.x, camino.y, caminoEffectiveWidth, caminoEffectiveHeight, camino.rotation)) { onCamino = true; break; } } var canPlaceAtCurrentPosition = !pointerInUI && !towerInUI && !tooCloseToTowers && !onCamino && draggedTower.x >= gameplayBounds.left && draggedTower.x <= gameplayBounds.right && draggedTower.y >= gameplayBounds.top && draggedTower.y <= gameplayBounds.bottom; if (canPlaceAtCurrentPosition) { // Can place at current position draggedTower.alpha = 1.0; draggedTower.hideRange(); // Hide range area when placed draggedTower.isPlaced = true; // Set placement status placedTowers.push(draggedTower); // Add to placed towers array LK.getSound('TorreColocada').play(); // Play tower placement sound } else { // Cannot place at current position - destroy tower if it's in UI or invalid position draggedTower.destroy(); } isDragging = false; draggedTower = null; } };
===================================================================
--- original.js
+++ change.js
@@ -364,35 +364,37 @@
waveEffect.destroy();
}
}
});
- // Apply effects to all enemies in range
+ // Apply slow effect to all enemies in range
for (var i = 0; i < enemiesInRange.length; i++) {
var enemy = enemiesInRange[i];
if (enemy && enemy.parent) {
- // Check for absolute zero upgrade (30% chance for 100% slow)
- var shouldFreeze = false;
- if (self.absoluteZero && Math.random() < 0.3) {
- // 30% chance for absolute zero effect
- enemy.slowEffect = 0.0; // 100% slow (freeze)
- enemy.slowDuration = 1000; // 1 second freeze
- shouldFreeze = true;
- } else {
- // Normal slow effect
- enemy.slowEffect = self.slowEffect || 0.8; // Use upgraded slow effect or default 20% slow
- enemy.slowDuration = self.slowDuration || 1000; // Use upgraded duration or default 1 second
+ // Apply slow effect (default 20% slow, upgradeable)
+ var slowEffect = self.slowEffect || 0.8; // Default 20% slow
+ var slowDuration = self.slowDuration || 1000; // Default 1 second
+ enemy.slowEffect = slowEffect;
+ enemy.slowDuration = slowDuration;
+ // Apply damage on freeze if upgrade is purchased
+ if (self.damageOnFreeze && enemy.takeDamage) {
+ enemy.takeDamage(1); // Remove 1 layer
}
- // Apply freeze damage if upgrade is active
- if (self.freezeDamage && enemy.takeDamage) {
- enemy.takeDamage(1); // Remove 1 layer when freezing
+ // Check for stun effect (100% slow for 1 second)
+ if (self.stunChance && Math.random() < self.stunChance) {
+ // Create separate stun effect list if not exists
+ if (!enemy.stunEffects) {
+ enemy.stunEffects = [];
+ }
+ // Add stun effect
+ enemy.stunEffects.push({
+ effect: 0.0,
+ // 100% slow (enemy stops)
+ duration: 1000 // 1 second
+ });
}
// Update enemy movement speed if currently moving
if (enemy.isMoving && enemy.moveSpeed) {
- var newSpeed = getEnemySpeed(enemy);
- if (fastGame) {
- newSpeed = newSpeed * 1.5;
- }
- enemy.moveSpeed = newSpeed * enemy.slowEffect / 60;
+ enemy.moveSpeed = enemy.moveSpeed * enemy.slowEffect;
}
}
}
};
@@ -1484,8 +1486,35 @@
this.moveSpeed = normalSpeed / 60;
}
}
}
+ // Handle stun effects countdown
+ if (this.stunEffects && this.stunEffects.length > 0) {
+ var activeStunEffect = null;
+ for (var s = this.stunEffects.length - 1; s >= 0; s--) {
+ var stunEffect = this.stunEffects[s];
+ stunEffect.duration -= frameTime * 1000;
+ if (stunEffect.duration <= 0) {
+ // Stun effect expired, remove it
+ this.stunEffects.splice(s, 1);
+ } else {
+ // Keep the strongest stun effect (lowest value = more severe)
+ if (!activeStunEffect || stunEffect.effect < activeStunEffect.effect) {
+ activeStunEffect = stunEffect;
+ }
+ }
+ }
+ // Apply the strongest active stun effect
+ if (activeStunEffect && this.isMoving && this.moveSpeed) {
+ var baseSpeed = getEnemySpeed(this);
+ if (fastGame) {
+ baseSpeed = baseSpeed * 1.5;
+ }
+ // Apply both slow and stun effects (multiplicative)
+ var totalEffect = (this.slowEffect || 1.0) * activeStunEffect.effect;
+ this.moveSpeed = baseSpeed * totalEffect / 60;
+ }
+ }
// Handle end animation
if (this.isEndAnimation) {
var animationSpeedMultiplier = fastGame ? 1.5 : 1.0; // 50% faster animation when fastGame is true
this.endAnimationTimer -= frameTime * 1000 * animationSpeedMultiplier; // Convert frame time to milliseconds and apply speed multiplier
@@ -1754,38 +1783,38 @@
},
// Ice Tower (Icetower) upgrades
ice: {
range: {
- title: 'Arctic Reach',
+ title: '+10% Range',
cost: 100,
apply: function apply(tower) {
- tower.range = Math.floor(tower.range * 1.1); // +10% range
+ tower.range = Math.floor(tower.range * 1.1);
tower.rangeSquared = tower.range * tower.range;
updateRangeArea(tower);
incrementUpgradeCounter(tower, 'left');
}
},
- freeze_duration: {
- title: 'Extended Frost',
- cost: 120,
+ slow_duration: {
+ title: '+2 Sec Freeze',
+ cost: 75,
apply: function apply(tower) {
- tower.slowDuration = (tower.slowDuration || 1000) + 2000; // +2 seconds freeze duration
+ tower.slowDuration = (tower.slowDuration || 1000) + 2000; // Add 2 seconds
incrementUpgradeCounter(tower, 'left');
}
},
- freeze_damage: {
- title: 'Frost Bite',
- cost: 150,
+ damage_freeze: {
+ title: 'Damage Freeze',
+ cost: 125,
apply: function apply(tower) {
- tower.freezeDamage = true; // Remove 1 layer when freezing
+ tower.damageOnFreeze = true; // Remove 1 layer when freezing
incrementUpgradeCounter(tower, 'left');
}
},
left_final: {
- title: 'Absolute Zero',
+ title: 'Stun Field',
cost: 250,
apply: function apply(tower) {
- tower.absoluteZero = true; // 30% of enemies get 100% slow (freeze) for 1 second
+ tower.stunChance = 0.3; // 30% chance to stun (100% slow) for 1 second
incrementUpgradeCounter(tower, 'left');
}
},
right1_slow_duration: {
@@ -1931,9 +1960,9 @@
right: ['right1_damage', 'right1_range', 'right1_cross_final', 'right1_ultimate']
},
// Ice Tower paths
ice: {
- left: ['range', 'freeze_duration', 'freeze_damage', 'left_final'],
+ left: ['range', 'slow_duration', 'damage_freeze', 'left_final'],
right: ['right1_slow_duration', 'right1_freeze_chance', 'right1_ice_storm', 'right1_ultimate']
},
// Fire Tower paths
fire: {