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
User prompt
Haz que la nueva capa tenga la misma velocidad de verde
User prompt
Agrega una nueva capa de color blanco
User prompt
Haz que cada capa de enemigo de dinero
User prompt
Please fix the bug: 'ReferenceError: currentWaveIndex is not defined' in or related to this line: 'waveText.setText('Wave: ' + (currentWaveIndex + 1));' Line Number: 1349
User prompt
Elimina toda la lógica de los enemigos
User prompt
La velocidad sigue sin ser la correspondiente a la capa que posee el enemigo cuando pierde 1
User prompt
Empieza en la oleada 15
User prompt
Arregla el error que hace que no se actualice la velocidad del enemigo
User prompt
Arregla el error que hace que azul no se vuelve su capa inferior
User prompt
Mantén el sistema de capas pero mantén la clasificación actual
User prompt
Mantén el sistema de capas pero mantén la clasificación actual
User prompt
Mantén el sistemas de capas ya establecida pero con la nueva clasificación por t xto
/****
* 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;
});
// 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 = [[10, 0, 0, 0, 0, 0],
// Wave 1: 10x1-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;
// 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;
// Create health layers based on healthLayers parameter
for (var i = 0; i < healthLayers; i++) {
var colors = [0xff0000, 0x0000ff, 0x00ff00, 0xffff00, 0xff00ff, 0xFFFFFF]; // Red, Blue, Green, Yellow, Pink, White
self.healthLayers.push({
min: i + 1,
max: i + 1,
color: colors[i % colors.length]
});
}
// 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
}
// 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 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);
// Enemy Layer 6 (White, 10% faster - same as green)
var EnemyCapa6 = createEnemyBase(6, 1.1, 0xFFFFFF);
// 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 (for compatibility)
function createEnemy(healthLayers) {
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;
}
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);
// Spawn enemy
createEnemy(healthLayers);
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
@@ -1836,8 +1836,10 @@
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)