User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'visible')' in or related to this line: 'puntero.visible = visible;' Line Number: 2249
User prompt
Please fix the bug: 'game.addChild(...) is not a function' in or related to this line: 'return self;' Line Number: 1088
User prompt
Please fix the bug: 'game.addChild(...) is not a function' in or related to this line: 'return self;' Line Number: 835
User prompt
Please fix the bug: 'game.addChild(...) is not a function' in or related to this line: 'return self;' Line Number: 837
User prompt
Agrega una variable para cambiar el fondo de gameplay para permitir múltiples mapas
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'visible')' in or related to this line: 'puntero.visible = visible;' Line Number: 2249
User prompt
Agrega un menú inicial
User prompt
Agrega un menú inicial
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'length')' in or related to this line: 'for (var i = 0; i < enemies.length; i++) {' Line Number: 1062
User prompt
Agrega un menú inicial y un botón play con selección de mapas con un display de como se ve y su nombre debajo (agrega a cada mapa nuevo 3 caminos para tener una base modificable)
User prompt
Agrega un menú inicial y un botón play con selección de mapas con un display de como se ve y su nombre debajo (agrega cada mapa con 3 caminos para tener una base modificable)
User prompt
Asigna los caminos actuales como parte de mapa 1 para poder soportar nuevos mapas en el futuro
User prompt
Crea una nueva página por separado de menú y uno de niveles
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
/****
* 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
// Use ice-specific attack sprites if this is an ice tower
var attackAsset1 = params.asset === 'Icetower' ? 'IceTowerAttack1' : 'TorreinicialAttack';
var attackAsset2 = params.asset === 'Icetower' ? 'IceTowerAttack2' : 'TorreinicialAttack2';
var attackAsset3 = params.asset === 'Icetower' ? 'IceTowerAttack3' : 'TorreinicialAttack3';
if (!self.attackSprite1) {
self.attackSprite1 = self.attachAsset(attackAsset1, {
anchorX: 0.5,
anchorY: 0.5
});
self.attackSprite1.visible = false;
}
if (!self.attackSprite2) {
self.attackSprite2 = self.attachAsset(attackAsset2, {
anchorX: 0.5,
anchorY: 0.5
});
self.attackSprite2.visible = false;
}
if (!self.attackSprite3) {
self.attackSprite3 = self.attachAsset(attackAsset3, {
anchorX: 0.5,
anchorY: 0.5
});
self.attackSprite3.visible = false;
}
if (!self.normalSprite) {
self.normalSprite = towerGraphics;
}
// Initialize animation frame counter if not exists
if (!self.animationFrame) {
self.animationFrame = 0;
}
// Cycle through attack sprites
self.animationFrame = (self.animationFrame + 1) % 3;
var currentAttackSprite;
if (self.animationFrame === 0) {
currentAttackSprite = self.attackSprite1;
} else if (self.animationFrame === 1) {
currentAttackSprite = self.attackSprite2;
} else {
currentAttackSprite = self.attackSprite3;
}
// Sync attack sprite properties with current sprite
currentAttackSprite.rotation = towerGraphics.rotation;
currentAttackSprite.scaleY = towerGraphics.scaleY;
currentAttackSprite.x = towerGraphics.x;
currentAttackSprite.y = towerGraphics.y;
// Hide current sprite and show attack sprite
towerGraphics.visible = false;
currentAttackSprite.visible = true;
towerGraphics = currentAttackSprite;
// Animate attack sprite with enhanced effects for ice tower
if (params.asset === 'Icetower') {
// Ice tower specific animation: pulse without tint
tween(currentAttackSprite, {
scaleX: 1.3,
scaleY: currentAttackSprite.scaleY > 0 ? 1.3 : -1.3
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(currentAttackSprite, {
scaleX: 1.0,
scaleY: currentAttackSprite.scaleY > 0 ? 1.0 : -1.0
}, {
duration: 120,
easing: tween.elasticOut
});
}
});
} else {
// Standard animation for other towers
tween(currentAttackSprite, {
scaleX: 1.2,
scaleY: currentAttackSprite.scaleY > 0 ? 1.2 : -1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(currentAttackSprite, {
scaleX: 1.0,
scaleY: currentAttackSprite.scaleY > 0 ? 1.0 : -1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
// Change back to normal sprite after a short delay using LK.setTimeout
LK.setTimeout(function () {
if (self.parent && currentAttackSprite && self.normalSprite) {
// Sync normal sprite properties with current attack sprite
self.normalSprite.rotation = currentAttackSprite.rotation;
self.normalSprite.scaleY = currentAttackSprite.scaleY;
self.normalSprite.x = currentAttackSprite.x;
self.normalSprite.y = currentAttackSprite.y;
// Hide attack sprite and show normal sprite
currentAttackSprite.visible = false;
self.normalSprite.visible = true;
towerGraphics = self.normalSprite;
}
}, 250);
}
};
// Update method for tower
self.update = function () {
// Clean up destroyed bullets
for (var i = self.bullets.length - 1; i >= 0; i--) {
if (!self.bullets[i].parent) {
self.bullets.splice(i, 1);
}
}
// Only shoot if tower is placed (check cached status)
if (self.isPlaced) {
// Clear current target if it's no longer valid
if (self.currentTarget && (!self.currentTarget.parent || self.currentTarget.currentHealth <= 0)) {
self.currentTarget = null;
}
// Find and shoot at enemies
var enemies = self.findEnemiesInRange();
if (enemies.length > 0) {
var target = enemies[0];
// Set current target
self.currentTarget = target;
// Calculate predicted target position for tower rotation
var distance = Math.sqrt((target.x - self.x) * (target.x - self.x) + (target.y - self.y) * (target.y - self.y));
var bulletSpeed = 800; // pixels per second
// Apply fastGame speed boost to bullet speed for prediction calculations
if (fastGame) {
bulletSpeed = bulletSpeed * 1.5;
}
var timeToReach = distance / bulletSpeed;
var enemySpeed = getEnemySpeed(target);
// Adjust prediction factor based on fastGame mode for better accuracy
var predictionFactor = fastGame ? 0.8 : 0.6; // More aggressive prediction in fast mode
var predictionDistance = enemySpeed * timeToReach * predictionFactor;
// Get enemy's current movement direction
var enemyDx = 0;
var enemyDy = 0;
if (target.currentWaypointIndex < caminoPositions.length - 1) {
var nextWaypoint = caminoPositions[target.currentWaypointIndex + 1];
enemyDx = nextWaypoint.x - target.x;
enemyDy = nextWaypoint.y - target.y;
var enemyMoveDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
if (enemyMoveDistance > 0) {
// Normalize enemy direction
enemyDx = enemyDx / enemyMoveDistance;
enemyDy = enemyDy / enemyMoveDistance;
}
}
// Calculate predicted target position
var predictedX = target.x + enemyDx * predictionDistance;
var predictedY = target.y + enemyDy * predictionDistance;
// Calculate angle to predicted target position
var dx = predictedX - self.x;
var dy = predictedY - self.y;
var targetAngle = Math.atan2(dy, dx) + Math.PI / 2; // Add 90° rotation offset
// Smooth rotation using tween animation
var angleDiff = targetAngle - towerGraphics.rotation;
// Normalize angle difference to shortest path
while (angleDiff > Math.PI) {
angleDiff -= 2 * Math.PI;
}
while (angleDiff < -Math.PI) {
angleDiff += 2 * Math.PI;
}
// Only rotate if angle difference is significant (> 0.1 radians)
if (Math.abs(angleDiff) > 0.1) {
// Stop any existing rotation tween
tween.stop(towerGraphics, {
rotation: true
});
// Animate rotation smoothly
tween(towerGraphics, {
rotation: targetAngle
}, {
duration: 200,
easing: tween.easeOut
});
}
// Keep tower orientation consistent - no flipping based on target position
towerGraphics.scaleY = -1; // Always maintain normal orientation
// Shoot at the first enemy found
self.shoot(target);
} else {
// No enemies in range, clear current target
self.currentTarget = null;
}
}
};
// Handle tower selection
self.down = function (x, y, obj) {
// Deselect all other towers first
for (var i = 0; i < placedTowers.length; i++) {
if (placedTowers[i] !== self) {
placedTowers[i].isSelected = false;
placedTowers[i].hideRange();
}
}
// Toggle selection for this tower
self.isSelected = !self.isSelected;
if (self.isSelected) {
self.showRange();
updateTowerUpgrade(self); // Update UI with this tower's upgrade options
} else {
self.hideRange();
updateTowerUpgrade(null); // Clear UI when deselected
}
};
return self;
});
}
var Tower = createTowerBase({
damage: 1,
// Damage dealt per shot
cadence: 1000,
// Time between shots in milliseconds (1 second)
range: 400,
// Range in pixels
asset: 'torreInicial'
});
// Initialize tower-specific properties
Tower.prototype.cross = 1; // Each tower starts with cross value of 1
var IceTower = createTowerBase({
damage: 1,
// Damage dealt per shot
cadence: 1000,
// Time between shots in milliseconds (1 second)
range: 400,
// Range in pixels
asset: 'Icetower'
});
// Initialize ice tower-specific properties
IceTower.prototype.cross = 1; // Each tower starts with cross value of 1
var FireTower = createTowerBase({
damage: 1,
// Damage dealt per shot
cadence: 1000,
// Time between shots in milliseconds (1 second)
range: 400,
// Range in pixels
asset: 'FireTower'
});
// Initialize fire tower-specific properties
FireTower.prototype.cross = 1; // Each tower starts with cross value of 1
var draggedTower = null;
var isDragging = false;
var placedTowers = []; // Track all placed towers
// Cache frequently used values
var gameplayBounds = null;
var uiBounds = null;
var frameTime = 1 / 60; // Cache frame time calculation
// Reusable tower creation function that accepts tower constructor
function createTowerButton(color, x, y, TowerClass) {
var buttonBG = game.addChild(LK.getAsset('BGbuttonTower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
}));
buttonBG.x = x;
buttonBG.y = y;
var button = game.addChild(new TowerClass());
button.x = x;
button.y = y;
// Store reference to button background for easy access
button.buttonBG = buttonBG;
button.down = function (x, y, obj) {
isDragging = true;
draggedTower = game.addChild(new TowerClass());
draggedTower.x = button.x;
draggedTower.y = button.y;
draggedTower.alpha = 0.7;
draggedTower.showRange(); // Show range area when dragging
// Initialize last valid position to starting position
draggedTower.lastValidX = button.x;
draggedTower.lastValidY = button.y;
};
return button;
}
// Game down handler to deselect towers when clicking on gameplay area
game.down = function (x, y, obj) {
// Check if click is in gameplay area (not on UI or towers)
var gameplayBounds = getGameplayBounds();
var clickInGameplay = x >= gameplayBounds.left && x <= gameplayBounds.right && y >= gameplayBounds.top && y <= gameplayBounds.bottom;
if (clickInGameplay) {
// Deselect all towers
for (var i = 0; i < placedTowers.length; i++) {
placedTowers[i].isSelected = false;
placedTowers[i].hideRange();
}
updateTowerUpgrade(null); // Clear tower upgrade when no tower is selected
}
};
// Helper function to check if point is inside rotated rectangle
function isPointInRotatedRect(pointX, pointY, rectX, rectY, rectWidth, rectHeight, rotation) {
// Translate point to rectangle's coordinate system
var dx = pointX - rectX;
var dy = pointY - rectY;
// Rotate point by negative rotation to align with rectangle
var cos = Math.cos(-rotation);
var sin = Math.sin(-rotation);
var rotatedX = dx * cos - dy * sin;
var rotatedY = dx * sin + dy * cos;
// Check if rotated point is within rectangle bounds with much larger bounds for better collision
var halfWidth = rectWidth / 2 + 90; // Add 80 pixels buffer for much better collision detection
var halfHeight = rectHeight / 2 + 60; // Add 80 pixels buffer for much better collision detection
return Math.abs(rotatedX) <= halfWidth && Math.abs(rotatedY) <= halfHeight;
}
// Helper functions for bounds calculations
function getGameplayBounds() {
return {
left: gameplay.x - gameplay.width / 2,
right: gameplay.x + gameplay.width / 2,
top: gameplay.y - gameplay.height / 2,
bottom: gameplay.y + gameplay.height / 2,
centerX: gameplay.x,
centerY: gameplay.y
};
}
function getUIBounds() {
return {
left: ui.x - ui.width / 2,
right: ui.x + ui.width / 2,
top: ui.y - ui.height / 2,
bottom: ui.y + ui.height / 2
};
}
function isPointInUI(x, y) {
var bounds = getUIBounds();
return x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom;
}
function calculateDragOffset(x, y) {
var bounds = getGameplayBounds();
var distanceFromCenterX = Math.abs(x - bounds.centerX);
var distanceFromCenterY = Math.abs(y - bounds.centerY);
var maxDistanceX = gameplay.width / 2;
var maxDistanceY = gameplay.height / 2;
var normalizedDistanceX = distanceFromCenterX / maxDistanceX;
var normalizedDistanceY = distanceFromCenterY / maxDistanceY;
var maxOffset = 200;
var offsetMagnitudeX = maxOffset * normalizedDistanceX;
var offsetMagnitudeY = maxOffset * normalizedDistanceY;
var offsetX = 0;
var offsetY = 0;
if (x >= bounds.centerX && y <= bounds.centerY) {
offsetX = offsetMagnitudeX;
offsetY = -offsetMagnitudeY;
} else if (x <= bounds.centerX && y <= bounds.centerY) {
offsetX = -offsetMagnitudeX;
offsetY = -offsetMagnitudeY;
} else if (x <= bounds.centerX && y >= bounds.centerY) {
offsetX = -offsetMagnitudeX;
offsetY = offsetMagnitudeY;
} else if (x >= bounds.centerX && y >= bounds.centerY) {
offsetX = offsetMagnitudeX;
offsetY = offsetMagnitudeY;
}
return {
offsetX: offsetX,
offsetY: offsetY
};
}
// Layer management - objects are added in z-index order (bottom to top)
// Background layer
var gameplay = game.addChild(new Gameplay());
gameplay.x = 1024;
gameplay.y = 1093;
// Game objects layer (towers will be added here)
// UI layer
var ui = game.addChild(new UI());
ui.x = 1024;
ui.y = 2459;
// Create tower creation buttons
var towerButtons = [];
var startX = 400; // Position button on the left side
towerButtons.push(createTowerButton(0xffffff, startX, 2459, Tower)); // White tower (normal)
towerButtons.push(createTowerButton(0x87CEEB, startX + 200, 2459, IceTower)); // Light blue ice tower
towerButtons.push(createTowerButton(0xFF4500, startX + 400, 2459, FireTower)); // Orange fire tower
// Map system for supporting multiple maps
var mapData = {
map1: {
name: "Forest Path",
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
}]
}
// Future maps can be added here:
// map2: { name: "Desert Canyon", caminoPositions: [...] },
// map3: { name: "Ice Cavern", caminoPositions: [...] }
};
// Current map configuration
var currentMapId = 'map1';
var currentMap = mapData[currentMapId];
// Create path objects with transparency
var caminoObjects = [];
// Get path positions from current map
var caminoPositions = currentMap.caminoPositions;
// Function to load a specific map
function loadMap(mapId) {
// Clear existing path objects
for (var i = 0; i < caminoObjects.length; i++) {
if (caminoObjects[i] && caminoObjects[i].parent) {
caminoObjects[i].destroy();
}
}
caminoObjects = [];
// Update current map references
currentMapId = mapId;
currentMap = mapData[mapId] || mapData['map1']; // Fallback to map1 if invalid ID
caminoPositions = currentMap.caminoPositions;
// Create new path objects for the loaded map
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);
}
// Update end object position for the new map
if (end && end.parent) {
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;
}
}
// Initialize the default map (Map 1)
loadMap('map1');
// Create end object and place it at the final waypoint
var end = game.addChild(LK.getAsset('End', {
anchorX: 0.5,
anchorY: 0.5
}));
// Position end closer to the penultimate waypoint
var finalPosition = caminoPositions[caminoPositions.length - 1];
var penultimatePosition = caminoPositions[caminoPositions.length - 2];
// Calculate direction from penultimate to final waypoint
var dx = finalPosition.x - penultimatePosition.x;
var dy = finalPosition.y - penultimatePosition.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move end object 60% of the way from final to penultimate position
var moveBackRatio = 0.6;
end.x = finalPosition.x - dx * moveBackRatio;
end.y = finalPosition.y - dy * moveBackRatio;
var directionAngle = Math.atan2(dy, dx);
end.rotation = directionAngle;
end.alpha = 1.0; // Remove transparency
// Wave system variables
var enemies = [];
// Dynamic wave generation function
function generateWaveData() {
var waves = [];
var baseEnemyCount = 10;
var baseSpawnDelay = 800;
for (var i = 0; i < 15; i++) {
var distribution = getWaveDistribution(i);
waves.push({
enemyCount: baseEnemyCount + i * 5,
spawnDelay: Math.max(220, baseSpawnDelay - i * 40),
// Dynamic enemy distribution based on wave number
distribution: distribution,
// Track spawned layer 6 variants
layer6SpawnedA: 0,
layer6SpawnedB: 0
});
}
return waves;
}
// Simplified enemy distribution logic with layer 6 variant support
function getWaveDistribution(waveIndex) {
var distributions = [{
layers: [8, 0, 0, 0, 0, 2],
layer6Variants: {
A: 1,
B: 1
}
},
// Wave 1: 8x1-layer, 2x6-layer (1x6A, 1x6B)
{
layers: [15, 5, 0, 0, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 2: 15x1-layer, 5x2-layer
{
layers: [15, 5, 5, 0, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 3: 15x1, 5x2, 5x3-layer
{
layers: [15, 10, 5, 0, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 4: 15x1, 10x2, 5x3-layer
{
layers: [15, 10, 10, 0, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 5: 15x1, 10x2, 10x3-layer
{
layers: [15, 15, 10, 0, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 6: 15x1, 15x2, 10x3-layer
{
layers: [15, 15, 15, 0, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 7: 15x1, 15x2, 15x3-layer
{
layers: [15, 20, 15, 0, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 8: 15x1, 20x2, 15x3-layer
{
layers: [15, 20, 20, 0, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 9: 15x1, 20x2, 20x3-layer
{
layers: [15, 25, 20, 0, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 10: 15x1, 25x2, 20x3-layer
{
layers: [15, 25, 20, 5, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 11: 15x1, 25x2, 20x3, 5x4-layer
{
layers: [15, 25, 20, 10, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 12: 15x1, 25x2, 20x3, 10x4-layer
{
layers: [15, 20, 20, 15, 5, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 13: 15x1, 20x2, 20x3, 15x4, 5x5-layer
{
layers: [15, 20, 20, 15, 10, 0],
layer6Variants: {
A: 0,
B: 0
}
},
// Wave 14: 15x1, 20x2, 20x3, 15x4, 10x5-layer
{
layers: [15, 20, 20, 15, 15, 5],
layer6Variants: {
A: 3,
B: 2
}
} // Wave 15: 15x1, 20x2, 20x3, 15x4, 15x5-layer, 5x6-layer (3x6A, 2x6B)
];
return distributions[waveIndex] || {
layers: [10, 0, 0, 0, 0, 0],
layer6Variants: {
A: 0,
B: 0
}
};
}
// Optimized function to determine enemy health layers
function getEnemyHealthLayers(waveIndex, enemyIndex) {
var distribution = getWaveDistribution(waveIndex);
var layers = distribution.layers;
var cumulative = 0;
for (var layer = 0; layer < layers.length; layer++) {
var previousCumulative = cumulative;
cumulative += layers[layer];
if (enemyIndex < cumulative) {
return {
layer: layer + 1,
indexInLayer: enemyIndex - previousCumulative
};
}
}
return {
layer: 1,
indexInLayer: 0
}; // Default to 1 layer
}
var waveData = generateWaveData();
var currentWaveIndex = 0;
var enemiesSpawned = 0;
var waveInProgress = false;
var nextSpawnTime = 0;
// Enemy color configuration system
var enemyColorConfigurations = {
layer1: {
variants: [{
color: 0xff0000,
properties: {}
} // Red
]
},
layer2: {
variants: [{
color: 0x0000ff,
properties: {}
} // Blue
]
},
layer3: {
variants: [{
color: 0x00ff00,
properties: {}
} // Green
]
},
layer4: {
variants: [{
color: 0xffff00,
properties: {}
} // Yellow
]
},
layer5: {
variants: [{
color: 0xff00ff,
properties: {}
} // Pink
]
},
layer6: {
variants: [{
color: 0xFFFFFF,
properties: {
antiHielo: true,
antiFuego: false
}
},
// White for Anti hielo
{
color: 0x000000,
properties: {
antiHielo: false,
antiFuego: true
}
} // Black for anti fuego
]
}
};
// Function to get enemy variant configuration
function getEnemyVariant(layer, variantIndex) {
var config = enemyColorConfigurations['layer' + layer];
if (!config || !config.variants || config.variants.length === 0) {
return {
color: 0xff0000,
properties: {}
}; // Default red
}
// If no specific variant requested, choose randomly
if (variantIndex === undefined || variantIndex === null) {
variantIndex = Math.floor(Math.random() * config.variants.length);
}
// Clamp variant index to available variants
variantIndex = Math.max(0, Math.min(variantIndex, config.variants.length - 1));
return config.variants[variantIndex];
}
// Function to apply enemy variant properties
function applyEnemyVariant(enemy, variant) {
// Apply color with smooth transition
tween(enemy, {
tint: variant.color
}, {
duration: 200,
easing: tween.easeOut
});
// Apply special properties
if (variant.properties) {
for (var prop in variant.properties) {
enemy[prop] = variant.properties[prop];
}
}
}
// Base enemy class with common functionality
function createEnemyBase(healthLayers, speedMultiplier, color) {
return Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('Enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
// Add hitbox to enemy
var hitboxGraphics = self.attachAsset('hitbox', {
anchorX: 0.5,
anchorY: 0.5
});
hitboxGraphics.alpha = shotHitbox ? 0.2 : 0;
// Set starting position to first camino waypoint
self.x = caminoPositions[0].x;
self.y = caminoPositions[0].y;
// Path following properties
self.currentWaypointIndex = 0;
self.isMoving = false;
// Health system for enemy
self.maxHealth = healthLayers;
self.currentHealth = self.maxHealth;
self.healthLayers = [];
self.canBeDestroyed = false;
self.speedMultiplier = speedMultiplier;
// Initialize special properties
self.antiHielo = false;
self.antiFuego = false;
// Create health layers based on healthLayers parameter
for (var i = 0; i < healthLayers; i++) {
var layerVariant = getEnemyVariant(i + 1, 0); // Use first variant for each layer by default
self.healthLayers.push({
min: i + 1,
max: i + 1,
color: layerVariant.color
});
}
// Method to update enemy color based on health
self.updateHealthColor = function () {
for (var i = 0; i < this.healthLayers.length; i++) {
var layer = this.healthLayers[i];
if (this.currentHealth >= layer.min && this.currentHealth <= layer.max) {
// Animate color transition using tween
tween(this, {
tint: layer.color
}, {
duration: 200,
easing: tween.easeOut
});
break;
}
}
};
// Method to take damage
self.takeDamage = function (damage) {
var oldHealth = this.currentHealth;
this.currentHealth = Math.max(0, this.currentHealth - damage);
// Check if we crossed a layer boundary
var oldLayer = -1;
var newLayer = -1;
for (var i = 0; i < this.healthLayers.length; i++) {
var layer = this.healthLayers[i];
if (oldHealth >= layer.min && oldHealth <= layer.max) {
oldLayer = i;
}
if (this.currentHealth >= layer.min && this.currentHealth <= layer.max) {
newLayer = i;
}
}
// Update color if we changed layers or if this is the first damage
if (oldLayer !== newLayer || oldHealth === this.maxHealth) {
this.updateHealthColor();
}
// Update speed multiplier and movement speed based on new current health
var newSpeed = getEnemySpeed(this);
this.speedMultiplier = newSpeed / 260; // Calculate multiplier from speed
// Immediately update movement speed if enemy is currently moving
if (this.isMoving && this.moveSpeed) {
// Apply fastGame speed boost
if (fastGame) {
newSpeed = newSpeed * 1.5; // 50% faster when fastGame is true
}
this.moveSpeed = newSpeed / 60; // Convert to pixels per frame (60 FPS)
}
// Check if enemy is dead
if (this.currentHealth <= 0) {
// Play random enemy death sound (1 of 3)
var deathSounds = ['EnemyDerrivado', 'EnemigoDerrivado2', 'EnemigoDerrivado3'];
var randomSoundIndex = Math.floor(Math.random() * deathSounds.length);
LK.getSound(deathSounds[randomSoundIndex]).play();
// Stop any running animations if enemy is in end animation
if (this.canBeDestroyed) {
tween.stop(this, {
scaleX: true,
scaleY: true,
alpha: true
});
}
// Remove from enemies array
for (var j = 0; j < enemies.length; j++) {
if (enemies[j] === this) {
enemies.splice(j, 1);
break;
}
}
// Enemy is destroyed, remove it from the game
this.destroy();
}
};
// Method to move to next waypoint
self.moveToNextWaypoint = function () {
if (this.isMoving) {
return;
} // Don't start new movement if already moving
// Check if we've reached near the end of the path (start animation much earlier)
if (this.currentWaypointIndex >= caminoPositions.length - 2) {
// Start shrinking and fading animation much earlier (5 waypoints before the end)
this.canBeDestroyed = true; // Mark enemy as able to be destroyed during animation
this.isEndAnimation = true;
this.endAnimationTimer = 3000; // 3 seconds for end animation
this.endAnimationStartScale = {
x: this.scaleX,
y: this.scaleY
};
this.endAnimationStartAlpha = this.alpha;
return;
}
this.currentWaypointIndex = (this.currentWaypointIndex + 1) % caminoPositions.length;
var nextWaypoint = caminoPositions[this.currentWaypointIndex];
// Calculate movement properties for frame-by-frame movement
var dx = nextWaypoint.x - this.x;
var dy = nextWaypoint.y - this.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var speed = getEnemySpeed(this);
// Apply fastGame speed boost
if (fastGame) {
speed = speed * 1.5; // 50% faster when fastGame is true
}
// 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);
// Centralized enemy speed calculation function
function getEnemySpeed(enemy) {
var baseEnemySpeed = 260; // Base enemy speed in pixels per second
var enemySpeedMultiplier = 1.0;
if (enemy.currentHealth === 2) {
enemySpeedMultiplier = 1.05; // 5% faster for 2 layers
} else if (enemy.currentHealth === 3) {
enemySpeedMultiplier = 1.1; // 10% faster for 3 layers
} else if (enemy.currentHealth === 4) {
enemySpeedMultiplier = 1.75; // 75% faster for 4 layers (yellow)
} else if (enemy.currentHealth === 5) {
enemySpeedMultiplier = 2.5; // 150% faster for 5 layers (pink)
} else if (enemy.currentHealth === 6) {
enemySpeedMultiplier = 1.1; // 10% faster for 6 layers (white) - same as green
}
return baseEnemySpeed * enemySpeedMultiplier;
}
// Function to update all enemy speeds (useful for fastGame toggle)
function updateAllEnemySpeeds() {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy && enemy.parent && enemy.isMoving && enemy.moveSpeed) {
var newSpeed = getEnemySpeed(enemy);
// Apply fastGame speed boost
if (fastGame) {
newSpeed = newSpeed * 1.5; // 50% faster when fastGame is true
}
enemy.moveSpeed = newSpeed / 60; // Convert to pixels per frame (60 FPS)
}
}
}
// Function to create enemy with specified health layers and optional variant
function createEnemy(healthLayers, variantIndex) {
var enemy;
switch (healthLayers) {
case 1:
enemy = game.addChild(new EnemyCapa1());
break;
case 2:
enemy = game.addChild(new EnemyCapa2());
break;
case 3:
enemy = game.addChild(new EnemyCapa3());
break;
case 4:
enemy = game.addChild(new EnemyCapa4());
break;
case 5:
enemy = game.addChild(new EnemyCapa5());
break;
case 6:
enemy = game.addChild(new EnemyCapa6());
break;
default:
enemy = game.addChild(new EnemyCapa1());
break;
}
// Apply variant configuration after enemy creation
var variant = getEnemyVariant(healthLayers, variantIndex);
applyEnemyVariant(enemy, variant);
enemies.push(enemy);
return enemy;
}
// Function to start a wave
function startWave(waveIndex) {
if (waveIndex >= waveData.length) {
// All waves completed
LK.showYouWin();
return;
}
currentWaveIndex = waveIndex;
enemiesSpawned = 0;
waveInProgress = true;
nextSpawnTime = Date.now() + 2000; // Start spawning after 2 seconds
currentWave = waveIndex + 1;
}
// Start first wave only if autoStartWaves is true
if (autoStartWaves) {
startWave(0);
}
// Enemy is now stationary - no movement code needed
// UI mode management
var UI_MODE_TOWER_CREATION = 'creation';
var UI_MODE_TOWER_UPGRADE = 'upgrade';
var currentUIMode = UI_MODE_TOWER_CREATION;
// UI Upgrade panel for selected tower
var towerUpgradeContainer = game.addChild(new Container());
towerUpgradeContainer.x = 1024;
towerUpgradeContainer.y = 2250;
// Helper function to update range area visually
function updateRangeArea(tower) {
if (tower.children[0]) {
var areaScale = tower.range * 2 / 100;
tower.children[0].scaleX = areaScale;
tower.children[0].scaleY = areaScale;
}
}
// Helper function to increment upgrade counter
function incrementUpgradeCounter(tower, type) {
if (type === 'left') {
tower.leftUpgrades = tower.leftUpgrades || 0;
tower.leftUpgrades++;
} else if (type === 'right') {
tower.rightUpgrades = tower.rightUpgrades || 0;
tower.rightUpgrades++;
}
}
// 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
// Map selection variables
var selectedMapId = 'map1'; // Currently selected map for gameplay
var availableMaps = ['map1']; // Maps available to player (can be expanded)
// Camino path dimensions - can be changed while maintaining 200x200 default
var caminoWidth = 200;
var caminoHeight = 200;
// --- Initial Menu Overlay ---
var menuOverlay = game.addChild(new Container());
menuOverlay.zIndex = 9999; // Ensure on top
// Background for menu
var menuBG = menuOverlay.addChild(LK.getAsset('MenuBackground', {
anchorX: 0.5,
anchorY: 0.5
}));
menuBG.x = 1024;
menuBG.y = 1366;
// Title text
var menuTitle = menuOverlay.addChild(new Text2('Tower Defense', {
size: 180,
fill: 0xffffff
}));
menuTitle.anchor.set(0.5, 0.5);
menuTitle.x = 1024;
menuTitle.y = 700;
// Play button
var playButton = menuOverlay.addChild(LK.getAsset('MenuButton', {
anchorX: 0.5,
anchorY: 0.5
}));
playButton.x = 1024;
playButton.y = 1400;
var playText = menuOverlay.addChild(new Text2('JUGAR', {
size: 120,
fill: 0xffffff
}));
playText.anchor.set(0.5, 0.5);
playText.x = 1024;
playText.y = 1400;
// Hide gameplay UI until game starts
function setGameplayUIVisible(visible) {
// Hide tower buttons and backgrounds
for (var i = 0; i < towerButtons.length; i++) {
towerButtons[i].visible = visible;
if (towerButtons[i].buttonBG) {
towerButtons[i].buttonBG.visible = visible;
}
}
// Hide/Show UI containers
ui.visible = visible;
gameplay.visible = visible;
// Hide/Show money/life/wave texts
moneyText.visible = visible;
lifeText.visible = visible;
waveText.visible = visible;
// Hide/Show speed and auto wave buttons
if (speedButton && speedButton.parent) speedButton.visible = visible;
if (autoWaveButton && autoWaveButton.parent) autoWaveButton.visible = visible;
// Hide/Show pointer
puntero.visible = visible;
// Hide/Show upgrade container
towerUpgradeContainer.visible = false;
}
// Start with gameplay UI hidden
setGameplayUIVisible(false);
// Play button handler
playButton.down = function (x, y, obj) {
if (gameStart) return;
gameStart = true;
menuOverlay.visible = false;
setGameplayUIVisible(true);
// Optionally start first wave if autoStartWaves is true
if (autoStartWaves && !waveInProgress) {
startWave(0);
}
};
// 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) {
if (!gameStart) return;
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 () {
if (!gameStart) return;
// Update UI texts with current values
moneyText.setText('Money: ' + playerMoney);
lifeText.setText('Life: ' + playerLife);
waveText.setText('Wave: ' + (currentWaveIndex + 1));
// Update path visibility based on PathShow variable
for (var i = 0; i < caminoObjects.length; i++) {
caminoObjects[i].alpha = PathShow ? 0.2 : 0;
}
// Update enemy hitbox visibility based on shotHitbox variable
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy && enemy.parent) {
// Find the hitbox child in the enemy
for (var j = 0; j < enemy.children.length; j++) {
var child = enemy.children[j];
// Check if this is a hitbox (either by texture URL or by alpha value)
var isHitbox = false;
if (child.texture && child.texture.baseTexture && child.texture.baseTexture.resource && child.texture.baseTexture.resource.url && child.texture.baseTexture.resource.url.includes('hitbox')) {
isHitbox = true;
} else if (child.alpha === 0.2 || child.alpha === 0) {
// This might be a hitbox based on alpha value
isHitbox = true;
}
if (isHitbox) {
child.alpha = shotHitbox ? 0.2 : 0;
break;
}
}
}
}
// Wave spawning logic
if (waveInProgress && currentWaveIndex < waveData.length) {
var currentWaveData = waveData[currentWaveIndex];
var currentTime = Date.now();
if (currentTime >= nextSpawnTime && enemiesSpawned < currentWaveData.enemyCount) {
// Determine health layers for this enemy using optimized function
var enemyData = getEnemyHealthLayers(currentWaveIndex, enemiesSpawned);
var healthLayers = enemyData.layer;
// Determine variant for layer 6 enemies based on wave configuration
var variantIndex = undefined;
if (healthLayers === 6) {
var distribution = currentWaveData.distribution;
// Check if we should spawn 6A or 6B based on configured amounts
if (currentWaveData.layer6SpawnedA < distribution.layer6Variants.A) {
variantIndex = 0; // Spawn 6A (Anti hielo - white)
currentWaveData.layer6SpawnedA++;
} else if (currentWaveData.layer6SpawnedB < distribution.layer6Variants.B) {
variantIndex = 1; // Spawn 6B (Anti fuego - black)
currentWaveData.layer6SpawnedB++;
}
}
// Spawn enemy with variant
createEnemy(healthLayers, variantIndex);
enemiesSpawned++;
var spawnDelay = currentWaveData.spawnDelay;
// Apply fastGame spawn rate boost (50% faster = divide by 1.5)
if (fastGame) {
spawnDelay = spawnDelay / 1.5;
}
nextSpawnTime = currentTime + spawnDelay;
}
// Check if wave is complete
if (enemiesSpawned >= currentWaveData.enemyCount && enemies.length === 0) {
waveInProgress = false;
// Start next wave after a delay only if autoStartWaves is true
if (autoStartWaves) {
var waveDelay = 500; // 0.5 second delay between waves (much faster)
// Apply fastGame wave interval boost (50% faster = divide by 1.5)
if (fastGame) {
waveDelay = waveDelay / 1.5;
}
LK.setTimeout(function () {
startWave(currentWaveIndex + 1);
}, waveDelay);
}
}
}
};
// Game release handler
game.up = function (x, y, obj) {
if (!gameStart) return;
if (isDragging && draggedTower) {
var gameplayBounds = getGameplayBounds();
var uiBounds = getUIBounds();
var pointerInUI = isPointInUI(puntero.x, puntero.y);
var towerInUI = draggedTower.x >= uiBounds.left && draggedTower.x <= uiBounds.right && draggedTower.y >= uiBounds.top && draggedTower.y <= uiBounds.bottom;
// Check if tower is too close to existing towers at current position
var minDistance = 140; // Standardized minimum distance between towers
var minDistanceSquared = minDistance * minDistance;
var tooCloseToTowers = false;
for (var i = 0; i < placedTowers.length; i++) {
var existingTower = placedTowers[i];
var dx = draggedTower.x - existingTower.x;
var dy = draggedTower.y - existingTower.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < minDistanceSquared) {
tooCloseToTowers = true;
break;
}
}
// Check if tower would be placed on camino path at current position
var onCamino = false;
for (var i = 0; i < caminoObjects.length; i++) {
var camino = caminoObjects[i];
// Use dynamic dimensions with scaling
var caminoEffectiveWidth = caminoWidth * camino.scaleX;
var caminoEffectiveHeight = caminoHeight * camino.scaleY;
// Check collision with rotated rectangle
if (isPointInRotatedRect(draggedTower.x, draggedTower.y, camino.x, camino.y, caminoEffectiveWidth, caminoEffectiveHeight, camino.rotation)) {
onCamino = true;
break;
}
}
var canPlaceAtCurrentPosition = !pointerInUI && !towerInUI && !tooCloseToTowers && !onCamino && draggedTower.x >= gameplayBounds.left && draggedTower.x <= gameplayBounds.right && draggedTower.y >= gameplayBounds.top && draggedTower.y <= gameplayBounds.bottom;
if (canPlaceAtCurrentPosition) {
// Can place at current position
draggedTower.alpha = 1.0;
draggedTower.hideRange(); // Hide range area when placed
draggedTower.isPlaced = true; // Set placement status
placedTowers.push(draggedTower); // Add to placed towers array
LK.getSound('TorreColocada').play(); // Play tower placement sound
} else {
// Cannot place at current position - destroy tower if it's in UI or invalid position
draggedTower.destroy();
}
isDragging = false;
draggedTower = null;
}
}; ===================================================================
--- original.js
+++ change.js
@@ -194,577 +194,8 @@
/****
* Game Code
****/
-// Game state management
-var gameState = 'menu'; // 'menu', 'mapSelection', 'gameplay'
-var menuContainer = null;
-var mapSelectionContainer = null;
-// Map data with multiple maps (3 paths each for modificability)
-var mapData = {
- map1: {
- name: "Forest Path",
- preview: 'Mapa2',
- 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
- }]
- },
- map2: {
- name: "Desert Canyon",
- preview: 'Mapa3',
- caminoPositions: [{
- x: 200,
- y: 100,
- rotation: 0,
- width: 200,
- height: 200
- }, {
- x: 400,
- y: 300,
- rotation: 0.5,
- width: 200,
- height: 300
- }, {
- x: 800,
- y: 500,
- rotation: -0.3,
- width: 250,
- height: 200
- }, {
- x: 1200,
- y: 400,
- rotation: 0.8,
- width: 200,
- height: 280
- }, {
- x: 1400,
- y: 700,
- rotation: -0.6,
- width: 220,
- height: 200
- }, {
- x: 1100,
- y: 900,
- rotation: 0.2,
- width: 300,
- height: 200
- }, {
- x: 700,
- y: 1100,
- rotation: -0.4,
- width: 200,
- height: 250
- }, {
- x: 400,
- y: 1300,
- rotation: 0.7,
- width: 240,
- height: 200
- }, {
- x: 600,
- y: 1500,
- rotation: -0.2,
- width: 200,
- height: 200
- }, {
- x: 900,
- y: 1600,
- rotation: 0.4,
- width: 260,
- height: 200
- }, {
- x: 1200,
- y: 1500,
- rotation: -0.5,
- width: 200,
- height: 240
- }, {
- x: 1400,
- y: 1300,
- rotation: 0.6,
- width: 200,
- height: 200
- }, {
- x: 1300,
- y: 1700,
- rotation: -0.3,
- width: 230,
- height: 200
- }, {
- x: 1000,
- y: 1900,
- rotation: 0.3,
- width: 200,
- height: 220
- }, {
- x: 700,
- y: 2000,
- rotation: -0.4,
- width: 200,
- height: 200
- }, {
- x: 500,
- y: 1800,
- rotation: 0.5,
- width: 210,
- height: 200
- }, {
- x: 800,
- y: 2100,
- rotation: -0.2,
- width: 200,
- height: 200
- }, {
- x: 1100,
- y: 2200,
- rotation: 0.1,
- width: 200,
- height: 200
- }]
- },
- map3: {
- name: "Ice Cavern",
- preview: 'Mapa4',
- caminoPositions: [{
- x: 1800,
- y: 200,
- rotation: 0,
- width: 200,
- height: 200
- }, {
- x: 1600,
- y: 400,
- rotation: -0.4,
- width: 220,
- height: 200
- }, {
- x: 1300,
- y: 300,
- rotation: 0.6,
- width: 200,
- height: 240
- }, {
- x: 1000,
- y: 500,
- rotation: -0.3,
- width: 250,
- height: 200
- }, {
- x: 800,
- y: 300,
- rotation: 0.5,
- width: 200,
- height: 220
- }, {
- x: 500,
- y: 400,
- rotation: -0.2,
- width: 230,
- height: 200
- }, {
- x: 300,
- y: 600,
- rotation: 0.4,
- width: 200,
- height: 260
- }, {
- x: 600,
- y: 800,
- rotation: -0.5,
- width: 200,
- height: 200
- }, {
- x: 900,
- y: 700,
- rotation: 0.3,
- width: 270,
- height: 200
- }, {
- x: 1200,
- y: 900,
- rotation: -0.4,
- width: 200,
- height: 200
- }, {
- x: 1500,
- y: 800,
- rotation: 0.6,
- width: 200,
- height: 250
- }, {
- x: 1700,
- y: 1000,
- rotation: -0.3,
- width: 240,
- height: 200
- }, {
- x: 1400,
- y: 1200,
- rotation: 0.2,
- width: 200,
- height: 200
- }, {
- x: 1100,
- y: 1100,
- rotation: -0.5,
- width: 200,
- height: 230
- }, {
- x: 800,
- y: 1300,
- rotation: 0.4,
- width: 220,
- height: 200
- }, {
- x: 500,
- y: 1400,
- rotation: -0.2,
- width: 200,
- height: 200
- }, {
- x: 700,
- y: 1600,
- rotation: 0.3,
- width: 200,
- height: 240
- }, {
- x: 1000,
- y: 1700,
- rotation: -0.1,
- width: 200,
- height: 200
- }]
- }
-};
-// Function to show initial menu
-function showInitialMenu() {
- gameState = 'menu';
- // Hide gameplay elements
- if (gameplay && gameplay.parent) gameplay.visible = false;
- if (ui && ui.parent) ui.visible = false;
- if (moneyText && moneyText.parent) moneyText.visible = false;
- if (lifeText && lifeText.parent) lifeText.visible = false;
- if (waveText && waveText.parent) waveText.visible = false;
- if (end && end.parent) end.visible = false;
- if (puntero && puntero.parent) puntero.visible = false;
- // Hide towers and enemies
- for (var i = 0; i < placedTowers.length; i++) {
- if (placedTowers[i] && placedTowers[i].parent) placedTowers[i].visible = false;
- }
- for (var i = 0; i < enemies.length; i++) {
- if (enemies[i] && enemies[i].parent) enemies[i].visible = false;
- }
- for (var i = 0; i < caminoObjects.length; i++) {
- if (caminoObjects[i] && caminoObjects[i].parent) caminoObjects[i].visible = false;
- }
- // Create menu container
- menuContainer = game.addChild(new Container());
- // Add menu background
- var menuBG = menuContainer.addChild(LK.getAsset('MenuBackground', {
- anchorX: 0.5,
- anchorY: 0.5
- }));
- menuBG.x = 1024;
- menuBG.y = 1366;
- // Add title text
- var titleText = menuContainer.addChild(new Text2('Tower Defense', {
- size: 120,
- fill: 0xFFFFFF
- }));
- titleText.anchor.set(0.5, 0.5);
- titleText.x = 1024;
- titleText.y = 600;
- // Add play button
- var playButton = menuContainer.addChild(LK.getAsset('MenuButton', {
- anchorX: 0.5,
- anchorY: 0.5
- }));
- playButton.x = 1024;
- playButton.y = 1366;
- var playButtonText = menuContainer.addChild(new Text2('PLAY', {
- size: 80,
- fill: 0xFFFFFF
- }));
- playButtonText.anchor.set(0.5, 0.5);
- playButtonText.x = 1024;
- playButtonText.y = 1366;
- // Play button handler
- playButton.down = function (x, y, obj) {
- // Animate button press
- tween(playButton, {
- scaleX: 0.9,
- scaleY: 0.9
- }, {
- duration: 100,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- tween(playButton, {
- scaleX: 1.0,
- scaleY: 1.0
- }, {
- duration: 100,
- easing: tween.bounceOut,
- onFinish: function onFinish() {
- showMapSelection();
- }
- });
- }
- });
- };
-}
-// Function to show map selection screen
-function showMapSelection() {
- gameState = 'mapSelection';
- // Hide menu
- if (menuContainer && menuContainer.parent) {
- menuContainer.destroy();
- menuContainer = null;
- }
- // Create map selection container
- mapSelectionContainer = game.addChild(new Container());
- // Add menu background
- var mapSelectionBG = mapSelectionContainer.addChild(LK.getAsset('MenuBackground', {
- anchorX: 0.5,
- anchorY: 0.5
- }));
- mapSelectionBG.x = 1024;
- mapSelectionBG.y = 1366;
- // Add title text
- var mapTitleText = mapSelectionContainer.addChild(new Text2('Select Map', {
- size: 100,
- fill: 0xFFFFFF
- }));
- mapTitleText.anchor.set(0.5, 0.5);
- mapTitleText.x = 1024;
- mapTitleText.y = 400;
- // Create map selection buttons
- var mapIds = ['map1', 'map2', 'map3'];
- var startX = 512;
- var spacing = 512;
- for (var i = 0; i < mapIds.length; i++) {
- var mapId = mapIds[i];
- var mapInfo = mapData[mapId];
- var mapX = startX + i * spacing;
- // Map preview button
- var mapButton = mapSelectionContainer.addChild(LK.getAsset('LevelButton', {
- anchorX: 0.5,
- anchorY: 0.5
- }));
- mapButton.x = mapX;
- mapButton.y = 1000;
- mapButton.mapId = mapId; // Store map ID for reference
- // Map preview image
- var mapPreview = mapSelectionContainer.addChild(LK.getAsset(mapInfo.preview, {
- anchorX: 0.5,
- anchorY: 0.5
- }));
- mapPreview.x = mapX;
- mapPreview.y = 950;
- mapPreview.scaleX = 2.0;
- mapPreview.scaleY = 2.0;
- // Map name text
- var mapNameText = mapSelectionContainer.addChild(new Text2(mapInfo.name, {
- size: 60,
- fill: 0x000000
- }));
- mapNameText.anchor.set(0.5, 0.5);
- mapNameText.x = mapX;
- mapNameText.y = 1100;
- // Map button handler
- mapButton.down = function (x, y, obj) {
- var selectedMapId = obj.mapId;
- // Animate button press
- var button = obj;
- tween(button, {
- scaleX: 0.9,
- scaleY: 0.9
- }, {
- duration: 100,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- tween(button, {
- scaleX: 1.0,
- scaleY: 1.0
- }, {
- duration: 100,
- easing: tween.bounceOut,
- onFinish: function onFinish() {
- startGameplay(selectedMapId);
- }
- });
- }
- });
- };
- }
- // Add back button
- var backButton = mapSelectionContainer.addChild(LK.getAsset('BackButton', {
- anchorX: 0.5,
- anchorY: 0.5
- }));
- backButton.x = 200;
- backButton.y = 200;
- var backButtonText = mapSelectionContainer.addChild(new Text2('BACK', {
- size: 50,
- fill: 0x000000
- }));
- backButtonText.anchor.set(0.5, 0.5);
- backButtonText.x = 200;
- backButtonText.y = 200;
- // Back button handler
- backButton.down = function (x, y, obj) {
- tween(backButton, {
- scaleX: 0.9,
- scaleY: 0.9
- }, {
- duration: 100,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- tween(backButton, {
- scaleX: 1.0,
- scaleY: 1.0
- }, {
- duration: 100,
- easing: tween.bounceOut,
- onFinish: function onFinish() {
- showInitialMenu();
- }
- });
- }
- });
- };
-}
-// Function to start gameplay with selected map
-function startGameplay(selectedMapId) {
- gameState = 'gameplay';
- // Hide map selection
- if (mapSelectionContainer && mapSelectionContainer.parent) {
- mapSelectionContainer.destroy();
- mapSelectionContainer = null;
- }
- // Load selected map
- loadMap(selectedMapId);
- // Show gameplay elements
- if (gameplay && gameplay.parent) gameplay.visible = true;
- if (ui && ui.parent) ui.visible = true;
- if (moneyText && moneyText.parent) moneyText.visible = true;
- if (lifeText && lifeText.parent) lifeText.visible = true;
- if (waveText && waveText.parent) waveText.visible = true;
- if (end && end.parent) end.visible = true;
- if (puntero && puntero.parent) puntero.visible = true;
- // Show towers and path objects
- for (var i = 0; i < placedTowers.length; i++) {
- if (placedTowers[i] && placedTowers[i].parent) placedTowers[i].visible = true;
- }
- for (var i = 0; i < caminoObjects.length; i++) {
- if (caminoObjects[i] && caminoObjects[i].parent) caminoObjects[i].visible = true;
- }
-}
// Reusable tower creation function
7;
function createTowerBase(params) {
return Container.expand(function () {
@@ -1360,8 +791,126 @@
var startX = 400; // Position button on the left side
towerButtons.push(createTowerButton(0xffffff, startX, 2459, Tower)); // White tower (normal)
towerButtons.push(createTowerButton(0x87CEEB, startX + 200, 2459, IceTower)); // Light blue ice tower
towerButtons.push(createTowerButton(0xFF4500, startX + 400, 2459, FireTower)); // Orange fire tower
+// Map system for supporting multiple maps
+var mapData = {
+ map1: {
+ name: "Forest Path",
+ 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
+ }]
+ }
+ // Future maps can be added here:
+ // map2: { name: "Desert Canyon", caminoPositions: [...] },
+ // map3: { name: "Ice Cavern", caminoPositions: [...] }
+};
// Current map configuration
var currentMapId = 'map1';
var currentMap = mapData[currentMapId];
// Create path objects with transparency
@@ -1413,10 +962,10 @@
var directionAngle = Math.atan2(dy, dx);
end.rotation = directionAngle;
}
}
-// Start with initial menu instead of direct gameplay
-showInitialMenu();
+// Initialize the default map (Map 1)
+loadMap('map1');
// Create end object and place it at the final waypoint
var end = game.addChild(LK.getAsset('End', {
anchorX: 0.5,
anchorY: 0.5
@@ -2594,8 +2143,77 @@
var availableMaps = ['map1']; // Maps available to player (can be expanded)
// Camino path dimensions - can be changed while maintaining 200x200 default
var caminoWidth = 200;
var caminoHeight = 200;
+// --- Initial Menu Overlay ---
+var menuOverlay = game.addChild(new Container());
+menuOverlay.zIndex = 9999; // Ensure on top
+// Background for menu
+var menuBG = menuOverlay.addChild(LK.getAsset('MenuBackground', {
+ anchorX: 0.5,
+ anchorY: 0.5
+}));
+menuBG.x = 1024;
+menuBG.y = 1366;
+// Title text
+var menuTitle = menuOverlay.addChild(new Text2('Tower Defense', {
+ size: 180,
+ fill: 0xffffff
+}));
+menuTitle.anchor.set(0.5, 0.5);
+menuTitle.x = 1024;
+menuTitle.y = 700;
+// Play button
+var playButton = menuOverlay.addChild(LK.getAsset('MenuButton', {
+ anchorX: 0.5,
+ anchorY: 0.5
+}));
+playButton.x = 1024;
+playButton.y = 1400;
+var playText = menuOverlay.addChild(new Text2('JUGAR', {
+ size: 120,
+ fill: 0xffffff
+}));
+playText.anchor.set(0.5, 0.5);
+playText.x = 1024;
+playText.y = 1400;
+// Hide gameplay UI until game starts
+function setGameplayUIVisible(visible) {
+ // Hide tower buttons and backgrounds
+ for (var i = 0; i < towerButtons.length; i++) {
+ towerButtons[i].visible = visible;
+ if (towerButtons[i].buttonBG) {
+ towerButtons[i].buttonBG.visible = visible;
+ }
+ }
+ // Hide/Show UI containers
+ ui.visible = visible;
+ gameplay.visible = visible;
+ // Hide/Show money/life/wave texts
+ moneyText.visible = visible;
+ lifeText.visible = visible;
+ waveText.visible = visible;
+ // Hide/Show speed and auto wave buttons
+ if (speedButton && speedButton.parent) speedButton.visible = visible;
+ if (autoWaveButton && autoWaveButton.parent) autoWaveButton.visible = visible;
+ // Hide/Show pointer
+ puntero.visible = visible;
+ // Hide/Show upgrade container
+ towerUpgradeContainer.visible = false;
+}
+// Start with gameplay UI hidden
+setGameplayUIVisible(false);
+// Play button handler
+playButton.down = function (x, y, obj) {
+ if (gameStart) return;
+ gameStart = true;
+ menuOverlay.visible = false;
+ setGameplayUIVisible(true);
+ // Optionally start first wave if autoStartWaves is true
+ if (autoStartWaves && !waveInProgress) {
+ startWave(0);
+ }
+};
// Top overlay layer (pointer on top of everything)
var puntero = game.addChild(LK.getAsset('Puntero', {
anchorX: 0.5,
anchorY: 0.5
@@ -2604,8 +2222,9 @@
puntero.y = 1366;
puntero.alpha = 0;
// Game move handler for dragging
game.move = function (x, y, obj) {
+ if (!gameStart) return;
puntero.x = x;
puntero.y = y;
if (isDragging && draggedTower) {
var gameplayBounds = getGameplayBounds();
@@ -2764,12 +2383,9 @@
// Variable to control enemy hitbox visibility
var shotHitbox = true; // When true, show enemy hitboxes; when false, hide them
// Game update handler
game.update = function () {
- // Only update gameplay elements when in gameplay state
- if (gameState !== 'gameplay') {
- return;
- }
+ if (!gameStart) return;
// Update UI texts with current values
moneyText.setText('Money: ' + playerMoney);
lifeText.setText('Life: ' + playerLife);
waveText.setText('Wave: ' + (currentWaveIndex + 1));
@@ -2848,8 +2464,9 @@
}
};
// Game release handler
game.up = function (x, y, obj) {
+ if (!gameStart) return;
if (isDragging && draggedTower) {
var gameplayBounds = getGameplayBounds();
var uiBounds = getUIBounds();
var pointerInUI = isPointInUI(puntero.x, puntero.y);