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 shoot at target 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) { // 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 } self.lastShotTime = currentTime; // Play bullet shooting sound LK.getSound('Balababa').play(); // Initialize attack sprites if not already created if (!self.attackSprite1) { self.attackSprite1 = self.attachAsset('TorreinicialAttack', { anchorX: 0.5, anchorY: 0.5 }); self.attackSprite1.visible = false; } if (!self.attackSprite2) { self.attackSprite2 = self.attachAsset('TorreinicialAttack2', { anchorX: 0.5, anchorY: 0.5 }); self.attackSprite2.visible = false; } if (!self.attackSprite3) { self.attackSprite3 = self.attachAsset('TorreinicialAttack3', { 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 scaling effect 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) { 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 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; button.tint = color; // 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.tint = color; 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 single tower creation button var towerButtons = []; var startX = 400; // Position button on the left side towerButtons.push(createTowerButton(0xffffff, startX, 2459, Tower)); // White tower (normal) // 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++) { waves.push({ enemyCount: baseEnemyCount + i * 5, spawnDelay: Math.max(220, baseSpawnDelay - i * 40), // Dynamic enemy distribution based on wave number distribution: getWaveDistribution(i) }); } return waves; } // Simplified enemy distribution logic function getWaveDistribution(waveIndex) { var distributions = [[8, 0, 0, 0, 0, 2], // Wave 1: 8x1-layer, 2x6-layer [15, 5, 0, 0, 0, 0], // Wave 2: 15x1-layer, 5x2-layer [15, 5, 5, 0, 0, 0], // Wave 3: 15x1, 5x2, 5x3-layer [15, 10, 5, 0, 0, 0], // Wave 4: 15x1, 10x2, 5x3-layer [15, 10, 10, 0, 0, 0], // Wave 5: 15x1, 10x2, 10x3-layer [15, 15, 10, 0, 0, 0], // Wave 6: 15x1, 15x2, 10x3-layer [15, 15, 15, 0, 0, 0], // Wave 7: 15x1, 15x2, 15x3-layer [15, 20, 15, 0, 0, 0], // Wave 8: 15x1, 20x2, 15x3-layer [15, 20, 20, 0, 0, 0], // Wave 9: 15x1, 20x2, 20x3-layer [15, 25, 20, 0, 0, 0], // Wave 10: 15x1, 25x2, 20x3-layer [15, 25, 20, 5, 0, 0], // Wave 11: 15x1, 25x2, 20x3, 5x4-layer [15, 25, 20, 10, 0, 0], // Wave 12: 15x1, 25x2, 20x3, 10x4-layer [15, 20, 20, 15, 5, 0], // Wave 13: 15x1, 20x2, 20x3, 15x4, 5x5-layer [15, 20, 20, 15, 10, 0], // Wave 14: 15x1, 20x2, 20x3, 15x4, 10x5-layer [15, 20, 20, 15, 15, 5] // Wave 15: 15x1, 20x2, 20x3, 15x4, 15x5-layer, 5x6-layer ]; return distributions[waveIndex] || [10, 0, 0, 0, 0, 0]; } // Optimized function to determine enemy health layers function getEnemyHealthLayers(waveIndex, enemyIndex) { var distribution = getWaveDistribution(waveIndex); var cumulative = 0; for (var layer = 0; layer < distribution.length; layer++) { cumulative += distribution[layer]; if (enemyIndex < cumulative) { return layer + 1; } } return 1; // Default to 1 layer } var waveData = generateWaveData(); var currentWaveIndex = 0; var enemiesSpawned = 0; var waveInProgress = false; var nextSpawnTime = 0; // Flexible enemy mechanics system var enemyMechanicsConfig = { layer1: { // Layer 1 mechanics - Basic enemies mechanics: { regeneration: false, shielded: false, flying: false, splitting: false, camouflage: false, explosive: false }, // Passive effects that apply automatically passives: { speedBoost: 1.0, healthMultiplier: 1.0, damageReduction: 0.0, immunities: [] }, // Active abilities that trigger under conditions abilities: { onDeath: null, onDamage: null, onSpawn: null, onReachWaypoint: null } }, layer2: { mechanics: { regeneration: false, shielded: false, flying: false, splitting: false, camouflage: false, explosive: false }, passives: { speedBoost: 1.05, healthMultiplier: 1.0, damageReduction: 0.0, immunities: [] }, abilities: { onDeath: null, onDamage: null, onSpawn: null, onReachWaypoint: null } }, layer3: { mechanics: { regeneration: false, shielded: false, flying: false, splitting: false, camouflage: false, explosive: false }, passives: { speedBoost: 1.1, healthMultiplier: 1.0, damageReduction: 0.0, immunities: [] }, abilities: { onDeath: null, onDamage: null, onSpawn: null, onReachWaypoint: null } }, layer4: { mechanics: { regeneration: false, shielded: false, flying: false, splitting: false, camouflage: false, explosive: false }, passives: { speedBoost: 1.75, healthMultiplier: 1.0, damageReduction: 0.0, immunities: [] }, abilities: { onDeath: null, onDamage: null, onSpawn: null, onReachWaypoint: null } }, layer5: { mechanics: { regeneration: false, shielded: false, flying: false, splitting: false, camouflage: false, explosive: false }, passives: { speedBoost: 2.5, healthMultiplier: 1.0, damageReduction: 0.0, immunities: [] }, abilities: { onDeath: null, onDamage: null, onSpawn: null, onReachWaypoint: null } }, layer6: { mechanics: { regeneration: false, shielded: false, flying: false, splitting: false, camouflage: false, explosive: false }, passives: { speedBoost: 1.1, healthMultiplier: 1.0, damageReduction: 0.0, immunities: [] }, abilities: { onDeath: null, onDamage: null, onSpawn: null, onReachWaypoint: null } } }; // 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 mechanics based on layer function applyEnemyMechanics(enemy, layer) { var mechanics = enemyMechanicsConfig['layer' + layer]; if (!mechanics) return; // Apply passive effects if (mechanics.passives) { // Apply health multiplier if (mechanics.passives.healthMultiplier !== 1.0) { enemy.maxHealth = Math.floor(enemy.maxHealth * mechanics.passives.healthMultiplier); enemy.currentHealth = enemy.maxHealth; } // Apply damage reduction enemy.damageReduction = mechanics.passives.damageReduction || 0.0; // Apply immunities enemy.immunities = mechanics.passives.immunities || []; } // Apply mechanics flags if (mechanics.mechanics) { for (var mechanic in mechanics.mechanics) { enemy[mechanic] = mechanics.mechanics[mechanic]; } } // Apply abilities if (mechanics.abilities) { for (var ability in mechanics.abilities) { if (mechanics.abilities[ability]) { enemy[ability] = mechanics.abilities[ability]; } } } // Initialize mechanic-specific properties if (enemy.regeneration) { enemy.regenTimer = 0; enemy.regenRate = 0.1; // Heal 0.1 health per second } if (enemy.shielded) { enemy.shieldHealth = 1; enemy.shieldActive = true; } if (enemy.camouflage) { enemy.camouflageTimer = 0; enemy.camouflageActive = false; } if (enemy.explosive) { enemy.explosionRadius = 100; enemy.explosionDamage = 1; } } // 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; // Initialize mechanic-specific properties self.damageReduction = 0.0; self.immunities = []; self.regenTimer = 0; self.shieldHealth = 0; self.shieldActive = false; self.camouflageTimer = 0; self.camouflageActive = false; self.explosionRadius = 0; self.explosionDamage = 0; // Apply layer-specific mechanics applyEnemyMechanics(self, healthLayers); // 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; // Apply damage reduction var effectiveDamage = damage * (1 - (this.damageReduction || 0)); // Check for shield if (this.shielded && this.shieldActive && this.shieldHealth > 0) { this.shieldHealth -= effectiveDamage; if (this.shieldHealth <= 0) { this.shieldActive = false; // Visual effect for shield break tween(this, { tint: 0xff0000 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { this.updateHealthColor(); } }); } return; // Shield absorbed the damage } this.currentHealth = Math.max(0, this.currentHealth - effectiveDamage); // Trigger onDamage ability if it exists if (this.onDamage) { this.onDamage(effectiveDamage); } // 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) { // Trigger onDeath ability if it exists if (this.onDeath) { this.onDeath(); } // Handle layer 6 splitting into 4 layer 5 enemies if (this.maxHealth === 6) { // Create 4 layer 5 enemies for (var i = 0; i < 4; i++) { var splitEnemy = createEnemy(5); // Position enemies in a cross pattern around the original position var angle = i * Math.PI / 2; // 90 degrees apart var offsetDistance = 60; // Distance from original position splitEnemy.x = this.x + Math.cos(angle) * offsetDistance; splitEnemy.y = this.y + Math.sin(angle) * offsetDistance; splitEnemy.currentWaypointIndex = this.currentWaypointIndex; } } // Handle explosive death if (this.explosive) { // Find nearby enemies and damage them for (var i = 0; i < enemies.length; i++) { var nearbyEnemy = enemies[i]; if (nearbyEnemy !== this && nearbyEnemy.parent) { var dx = nearbyEnemy.x - this.x; var dy = nearbyEnemy.y - this.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= this.explosionRadius) { nearbyEnemy.takeDamage(this.explosionDamage); } } } // Visual explosion effect LK.effects.flashObject(this, 0xffaa00, 300); } // Handle splitting if (this.splitting && this.currentHealth <= 0) { // Create two smaller enemies for (var i = 0; i < 2; i++) { var splitEnemy = createEnemy(Math.max(1, this.maxHealth - 1)); splitEnemy.x = this.x + (i === 0 ? -50 : 50); splitEnemy.y = this.y; splitEnemy.currentWaypointIndex = this.currentWaypointIndex; } } // 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 } // 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 () { // Process mechanics this.processMechanics(); // 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(); } } }; // Method to process active mechanics self.processMechanics = function () { // Handle regeneration if (this.regeneration && this.currentHealth > 0 && this.currentHealth < this.maxHealth) { this.regenTimer += frameTime; if (this.regenTimer >= 1.0) { // Regenerate every second this.currentHealth = Math.min(this.maxHealth, this.currentHealth + this.regenRate); this.regenTimer = 0; this.updateHealthColor(); } } // Handle camouflage if (this.camouflage) { this.camouflageTimer += frameTime; if (this.camouflageTimer >= 3.0) { // Toggle camouflage every 3 seconds this.camouflageActive = !this.camouflageActive; this.alpha = this.camouflageActive ? 0.3 : 1.0; this.camouflageTimer = 0; } } // Handle shield regeneration if (this.shielded && !this.shieldActive) { // Regenerate shield after 5 seconds if (!this.shieldRegenTimer) this.shieldRegenTimer = 0; this.shieldRegenTimer += frameTime; if (this.shieldRegenTimer >= 5.0) { this.shieldHealth = 1; this.shieldActive = true; this.shieldRegenTimer = 0; } } }; // 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) } } } // Utility functions for mechanics configuration function enableMechanic(layer, mechanic, enabled) { if (enemyMechanicsConfig['layer' + layer] && enemyMechanicsConfig['layer' + layer].mechanics) { enemyMechanicsConfig['layer' + layer].mechanics[mechanic] = enabled; } } function setPassiveEffect(layer, effect, value) { if (enemyMechanicsConfig['layer' + layer] && enemyMechanicsConfig['layer' + layer].passives) { enemyMechanicsConfig['layer' + layer].passives[effect] = value; } } function setAbility(layer, ability, func) { if (enemyMechanicsConfig['layer' + layer] && enemyMechanicsConfig['layer' + layer].abilities) { enemyMechanicsConfig['layer' + layer].abilities[ability] = func; } } // 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++; } } // Reusable upgrade system var upgradeDefinitions = { 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'); } }, damage: { title: 'Power Surge', cost: 50, apply: function apply(tower) { tower.damage += 1; } }, 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'); } }, 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 } } }; // Upgrade path configurations var leftUpgradePath = ['speed', 'range', 'multishot', 'left_final']; var rightUpgradePath = ['right1_damage', 'right1_range', 'right1_cross_final', 'right1_ultimate']; // Function to get current left upgrade options for a tower function getLeftUpgradeOptions(tower) { tower.leftUpgrades = tower.leftUpgrades || 0; return leftUpgradePath[tower.leftUpgrades] || 'maxed'; } // Function to get current right upgrade options for a tower function getRightUpgradeOptions(tower) { tower.rightUpgrades = tower.rightUpgrades || 0; return { first: rightUpgradePath[tower.rightUpgrades] || '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 = upgradeDefinitions[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) { if (selectedTowerForUpgrade && playerMoney >= upgradeDef.cost && upgradeType !== 'maxed') { // Apply upgrade upgradeDef.apply(selectedTowerForUpgrade); // Deduct cost playerMoney -= upgradeDef.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 canAfford = selectedTowerForUpgrade && playerMoney >= upgradeDef.cost; upgradeButton.alpha = canAfford ? 1.0 : 0.5; upgradeText.alpha = canAfford ? 1.0 : 0.5; upgradeText.setText('BUY'); } } }; } // 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 && upgradeDefinitions[option]) { var title = option === 'targeting_priority' ? 'Priority: ' + (tower.targetingPriority === 'first' ? 'First' : tower.targetingPriority === 'last' ? 'Last' : 'Strongest') : upgradeDefinitions[option].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; } // 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 healthLayers = getEnemyHealthLayers(currentWaveIndex, enemiesSpawned); // Determine variant for layer 6 enemies (or any layer that has variants) var variantIndex = undefined; if (healthLayers === 6) { // For layer 6, randomly choose between variants (Anti hielo or anti fuego) variantIndex = Math.floor(Math.random() * 2); // 0 or 1 } // 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.tint = 0xffffff; 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
@@ -1079,9 +1079,9 @@
mechanics: {
regeneration: false,
shielded: false,
flying: false,
- splitting: true,
+ splitting: false,
camouflage: false,
explosive: false
},
passives: {
@@ -1090,26 +1090,9 @@
damageReduction: 0.0,
immunities: []
},
abilities: {
- onDeath: function onDeath() {
- // Create 4 layer 5 enemies when layer 6 dies
- for (var i = 0; i < 4; i++) {
- var splitEnemy = createEnemy(5);
- // Position them around the original enemy's position
- var angle = i * Math.PI * 2 / 4; // Distribute evenly in a circle
- var distance = 80; // Distance from original position
- splitEnemy.x = this.x + Math.cos(angle) * distance;
- splitEnemy.y = this.y + Math.sin(angle) * distance;
- splitEnemy.currentWaypointIndex = this.currentWaypointIndex;
- // Start their movement
- LK.setTimeout(function () {
- if (splitEnemy.parent) {
- splitEnemy.moveToNextWaypoint();
- }
- }.bind(splitEnemy), 200);
- }
- },
+ onDeath: null,
onDamage: null,
onSpawn: null,
onReachWaypoint: null
}
@@ -1380,8 +1363,21 @@
// Trigger onDeath ability if it exists
if (this.onDeath) {
this.onDeath();
}
+ // Handle layer 6 splitting into 4 layer 5 enemies
+ if (this.maxHealth === 6) {
+ // Create 4 layer 5 enemies
+ for (var i = 0; i < 4; i++) {
+ var splitEnemy = createEnemy(5);
+ // Position enemies in a cross pattern around the original position
+ var angle = i * Math.PI / 2; // 90 degrees apart
+ var offsetDistance = 60; // Distance from original position
+ splitEnemy.x = this.x + Math.cos(angle) * offsetDistance;
+ splitEnemy.y = this.y + Math.sin(angle) * offsetDistance;
+ splitEnemy.currentWaypointIndex = this.currentWaypointIndex;
+ }
+ }
// Handle explosive death
if (this.explosive) {
// Find nearby enemies and damage them
for (var i = 0; i < enemies.length; i++) {
@@ -1397,11 +1393,11 @@
}
// Visual explosion effect
LK.effects.flashObject(this, 0xffaa00, 300);
}
- // Handle splitting - Note: Layer 6 splitting is now handled in onDeath ability
- if (this.splitting && this.currentHealth <= 0 && this.maxHealth !== 6) {
- // Create two smaller enemies for non-layer-6 enemies
+ // Handle splitting
+ if (this.splitting && this.currentHealth <= 0) {
+ // Create two smaller enemies
for (var i = 0; i < 2; i++) {
var splitEnemy = createEnemy(Math.max(1, this.maxHealth - 1));
splitEnemy.x = this.x + (i === 0 ? -50 : 50);
splitEnemy.y = this.y;