User prompt
Elimina los comentarios en la parte de la variable wavedistribution
User prompt
Optimiza la creación de oleadas para hacer un codigo más compacto y eficaz
User prompt
Arregla el error que hace que los enemigos de capas veloces al morir las capaz inferiores poseen su velocidad y no la correspondiente para su capa
User prompt
Expande las oleadas a 15 y actualiza las actuales para agregar los nuevos enemigos
User prompt
Agrega dos enemigos más: capa 4 amarillo velocidad de movimiento +75%, capa 5 rosa velocidad de movimiento +150 (ambos en relación a la capa 1)
User prompt
Haz que el enemigo de dos capas vaya 5% más rápido que el enemigo de una, y el enemigo de tres capas vaya 5% más rápido que el enemigo de dos.
User prompt
Haz que el enemigo de 2 capas vaya 5% más rápido que el enemigo de 1 Y el enemigo de 3 capas vaya 5% más rápido que el enemigo de 2
User prompt
Es que el enemigo de 5...
User prompt
Haz que el enemigo de dos capas vaya 5% más rápido que el de 1, el de 3 5% más rápido que el de 2
User prompt
Haz que el enemigo de dos capas vaya 5% más rápido que el de 1 y el de 3, 5% más rápido que el de2
User prompt
Agrega una cuarta mejora al de la derecha que sea +60% velocidad de bala, +6 Cross, +2 daño
User prompt
Agrega una cuarta mejora al dela izquierda después de la de agregar dos balas laterales, que sea +1 ataque y +200% velocidad de ataque
User prompt
Agrega 1 mejora al de la izquierda que sea +1 daño y +100% carencia de ataque
User prompt
Modifica la mejora de la derecha de torre inicia, haz que la segunda sea el rango y la tercera el Cross, agrégale además a la última mejora +30% velocidad de bala y 50% más duración
User prompt
Reduce la separación de las balas extras
User prompt
Reduce un poco el predic de disparo
User prompt
Agrega una predicción a la posición de los enemigos para las torres que disparan,mientras más lejos están más adelante atacan
User prompt
Elimina todos los comentarios //
User prompt
Elimina comentarios del codigo
User prompt
Mejores el sistema de cambio de asset de ataque -> idle para evitar momentos de transparencia o Sprite desaparecidos entre transiciones
Code edit (1 edits merged)
Please save this source code
User prompt
Haz que el cambio de asset de torre a torreattack sea sin plugins
User prompt
Haz que el cambio de asset de torre no sea por medio del plugin tweet
User prompt
Haz que las balas con Cross sean concisas (siempre atraviesan el N enemigos sin importar su daño) y haz que el daño que le hace a cada enemigo sea igual
User prompt
Arregla lo siguiente: el daño de la bala no se reduce cuando atraviesa un enemigo, siempre quita el mismo daño a cada enemigo
/**** * 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 // Set lifespan of 1.3 seconds using tween tween(self, {}, { duration: 1300, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); self.update = function () { if (!self.isAlive) { return; } // Move in straight line using direction self.x += self.directionX * self.speed * frameTime; self.y += self.directionY * self.speed * 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) { // 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 hitbox size for collision detection (hitbox is 120x120) var hitboxRadius = 60; // Half of hitbox size if (distanceSquared < hitboxRadius * hitboxRadius) { // Apply damage to enemy using its takeDamage method if (enemy.takeDamage) { enemy.takeDamage(self.damage); // Reduce bullet damage after hitting an enemy (50% reduction per hit) self.damage = Math.max(1, Math.floor(self.damage * 0.5)); // Minimum damage of 1 } // 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 < 400) { // 20 * 20 = 400 // Apply damage to enemy using its takeDamage method if (enemy.takeDamage) { enemy.takeDamage(self.damage); // Reduce bullet damage after hitting an enemy (50% reduction per hit) self.damage = Math.max(1, Math.floor(self.damage * 0.5)); // Minimum damage of 1 } // Add damage to tower's total damage counter if (self.towerRef && self.towerRef.parent) { self.towerRef.totalDamage += self.damage; } // Increment hit count and check if bullet should be destroyed self.hitCount++; var towerCross = self.towerRef && self.towerRef.cross ? self.towerRef.cross : 1; if (self.hitCount >= towerCross) { // Bullet has hit the required number of enemies, destroy it self.isAlive = false; if (self.parent) { self.destroy(); } return; } // Continue to check other enemies break; } } } } }; return self; }); // Reusable tower creation function var Gameplay = Container.expand(function () { var self = Container.call(this); var gameplayGraphics = self.attachAsset('gameplayBackground', { anchorX: 0.5, anchorY: 0.5 }); return self; }); var UI = Container.expand(function () { var self = Container.call(this); var uiGraphics = self.attachAsset('uiBackground', { anchorX: 0.5, anchorY: 0.5 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Reusable tower creation function 7; function createTowerBase(params) { return Container.expand(function () { var self = Container.call(this); // Tower parameters self.damage = params.damage; self.cadence = params.cadence; self.range = params.range; self.rangeSquared = self.range * self.range; // Cache squared range for optimization self.totalDamage = 0; // Track total damage dealt by this tower self.cross = 1; // Initialize cross value for this tower 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 = []; // 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) { enemiesInRange.push(enemy); } } } return enemiesInRange; }; // Method to shoot at target self.shoot = function (target) { var currentTime = Date.now(); if (currentTime - self.lastShotTime >= self.cadence) { // 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); // Calculate direction to current target position with angle offset var baseAngle = Math.atan2(dy, dx); 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.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 / 6); // 30 degrees to the left createBullet(Math.PI / 6); // 30 degrees to the right } self.lastShotTime = currentTime; // Play bullet shooting sound LK.getSound('Balababa').play(); // Change to attack sprite var attackSprite = self.attachAsset('TorreinicialAttack', { anchorX: 0.5, anchorY: 0.5 }); attackSprite.rotation = towerGraphics.rotation; attackSprite.scaleY = towerGraphics.scaleY; self.removeChild(towerGraphics); towerGraphics = attackSprite; // Add squash animation to tower tween.stop(towerGraphics, { scaleX: true }); // Stop any existing scale tweens tween(towerGraphics, { scaleX: 0.7 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(towerGraphics, { scaleX: 1.0 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { // Change back to normal sprite after attack animation var normalSprite = self.attachAsset(params.asset, { anchorX: 0.5, anchorY: 0.5 }); normalSprite.rotation = towerGraphics.rotation; normalSprite.scaleY = towerGraphics.scaleY; self.removeChild(towerGraphics); towerGraphics = normalSprite; } }); } }); } }; // 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) { // Find and shoot at enemies var enemies = self.findEnemiesInRange(); if (enemies.length > 0) { var target = enemies[0]; // Calculate angle to target var dx = target.x - self.x; var dy = target.y - self.y; var targetAngle = Math.atan2(dy, dx); // 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 }); } // Flip tower horizontally if target is on the left side if (dx < 0) { towerGraphics.scaleY = -1; // Flip on Y axis when target is to the left } else { towerGraphics.scaleY = 1; // Normal orientation when target is to the right } // Shoot at the first enemy found self.shoot(target); } } }; // Handle tower selection self.down = function (x, y, obj) { // Deselect all other towers first for (var i = 0; i < placedTowers.length; i++) { if (placedTowers[i] !== self) { placedTowers[i].isSelected = false; placedTowers[i].hideRange(); } } // Toggle selection for this tower self.isSelected = !self.isSelected; if (self.isSelected) { self.showRange(); updateTowerUpgrade(self); // Update UI with this tower's upgrade options } else { self.hideRange(); updateTowerUpgrade(null); // Clear UI when deselected } }; return self; }); } var Tower = createTowerBase({ damage: 1, // Damage dealt per shot cadence: 1000, // Time between shots in milliseconds (1 second) range: 400, // Range in pixels asset: 'torreInicial' }); // Initialize tower-specific properties Tower.prototype.cross = 1; // Each tower starts with cross value of 1 var draggedTower = null; var isDragging = false; var placedTowers = []; // Track all placed towers // Cache frequently used values var gameplayBounds = null; var uiBounds = null; var frameTime = 1 / 60; // Cache frame time calculation // Reusable tower creation function that accepts tower constructor function createTowerButton(color, x, y, TowerClass) { var buttonBG = game.addChild(LK.getAsset('BGbuttonTower', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 })); buttonBG.x = x; buttonBG.y = y; var button = game.addChild(new TowerClass()); button.x = x; button.y = y; button.tint = color; // Store reference to button background for easy access button.buttonBG = buttonBG; button.down = function (x, y, obj) { isDragging = true; draggedTower = game.addChild(new TowerClass()); draggedTower.x = button.x; draggedTower.y = button.y; draggedTower.alpha = 0.7; draggedTower.tint = color; draggedTower.showRange(); // Show range area when dragging }; 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 functions for bounds calculations function getGameplayBounds() { return { left: gameplay.x - gameplay.width / 2, right: gameplay.x + gameplay.width / 2, top: gameplay.y - gameplay.height / 2, bottom: gameplay.y + gameplay.height / 2, centerX: gameplay.x, centerY: gameplay.y }; } function getUIBounds() { return { left: ui.x - ui.width / 2, right: ui.x + ui.width / 2, top: ui.y - ui.height / 2, bottom: ui.y + ui.height / 2 }; } function isPointInUI(x, y) { var bounds = getUIBounds(); return x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom; } function calculateDragOffset(x, y) { var bounds = getGameplayBounds(); var distanceFromCenterX = Math.abs(x - bounds.centerX); var distanceFromCenterY = Math.abs(y - bounds.centerY); var maxDistanceX = gameplay.width / 2; var maxDistanceY = gameplay.height / 2; var normalizedDistanceX = distanceFromCenterX / maxDistanceX; var normalizedDistanceY = distanceFromCenterY / maxDistanceY; var maxOffset = 200; var offsetMagnitudeX = maxOffset * normalizedDistanceX; var offsetMagnitudeY = maxOffset * normalizedDistanceY; var offsetX = 0; var offsetY = 0; if (x >= bounds.centerX && y <= bounds.centerY) { offsetX = offsetMagnitudeX; offsetY = -offsetMagnitudeY; } else if (x <= bounds.centerX && y <= bounds.centerY) { offsetX = -offsetMagnitudeX; offsetY = -offsetMagnitudeY; } else if (x <= bounds.centerX && y >= bounds.centerY) { offsetX = -offsetMagnitudeX; offsetY = offsetMagnitudeY; } else if (x >= bounds.centerX && y >= bounds.centerY) { offsetX = offsetMagnitudeX; offsetY = offsetMagnitudeY; } return { offsetX: offsetX, offsetY: offsetY }; } // Layer management - objects are added in z-index order (bottom to top) // Background layer var gameplay = game.addChild(new Gameplay()); gameplay.x = 1024; gameplay.y = 1093; // Game objects layer (towers will be added here) // UI layer var ui = game.addChild(new UI()); ui.x = 1024; ui.y = 2459; // Create single tower creation button var towerButtons = []; var startX = 400; // Position button on the left side towerButtons.push(createTowerButton(0xffffff, startX, 2459, Tower)); // White tower (normal) // Create path objects with transparency var caminoObjects = []; // Add several camino objects to create a path var caminoPositions = [{ x: 990, y: -100, rotation: 0 }, { x: 890, y: 40, rotation: 12 }, { x: 650, y: 150, rotation: 70 }, { x: 500, y: 340, rotation: 5 }, { x: 500, y: 470, rotation: Math.PI }, { x: 550, y: 540, rotation: -Math.PI / 4 }, { x: 700, y: 620, rotation: 0 }, { x: 900, y: 620, rotation: 0 }, { x: 1100, y: 620, rotation: 0 }, { x: 1250, y: 650, rotation: -20 }, { x: 1420, y: 740, rotation: Math.PI / 4 }, { x: 1530, y: 880, rotation: Math.PI }, { x: 1510, y: 1080, rotation: -12 }, { x: 1380, y: 1210, rotation: 20 }, { x: 1180, y: 1300, rotation: 9 }, { x: 950, y: 1370, rotation: 20 }, { x: 750, y: 1480, rotation: 70 }, { x: 655, y: 1610, rotation: 0 }, { x: 610, y: 1750, rotation: 0 }, { x: 610, y: 1900, rotation: Math.PI / 2 }, { x: 610, y: 2100, rotation: 0 }]; 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 camino.alpha = 0.2; // Set transparency to 0.2 caminoObjects.push(camino); } // Create end object and place it at the final waypoint var end = game.addChild(LK.getAsset('End', { anchorX: 0.5, anchorY: 0.5 })); // Position end closer to the penultimate waypoint var finalPosition = caminoPositions[caminoPositions.length - 1]; var penultimatePosition = caminoPositions[caminoPositions.length - 2]; // Calculate direction from penultimate to final waypoint var dx = finalPosition.x - penultimatePosition.x; var dy = finalPosition.y - penultimatePosition.y; var distance = Math.sqrt(dx * dx + dy * dy); // Move end object 60% of the way from final to penultimate position var moveBackRatio = 0.6; end.x = finalPosition.x - dx * moveBackRatio; end.y = finalPosition.y - dy * moveBackRatio; var directionAngle = Math.atan2(dy, dx); end.rotation = directionAngle; end.alpha = 1.0; // Remove transparency // Wave system variables var enemies = []; var waveData = [{ // Wave 1: 10 enemies with 1 layer enemyCount: 10, healthLayers: 1, spawnDelay: 800 // 20% faster than 1000ms }, { // Wave 2: 15 enemies with 1 layer + 5 enemies with 2 layers enemyCount: 20, healthLayers: 1, // We'll handle mixed health in spawn function spawnDelay: 640 // 20% faster than 800ms }, { // Wave 3: 25 enemies with mixed layers enemyCount: 25, healthLayers: 1, spawnDelay: 600 }, { // Wave 4: 30 enemies with mixed layers enemyCount: 30, healthLayers: 1, spawnDelay: 560 }, { // Wave 5: 35 enemies with mixed layers enemyCount: 35, healthLayers: 1, spawnDelay: 520 }, { // Wave 6: 40 enemies with mixed layers enemyCount: 40, healthLayers: 1, spawnDelay: 480 }, { // Wave 7: 45 enemies with mixed layers enemyCount: 45, healthLayers: 1, spawnDelay: 440 }, { // Wave 8: 50 enemies with mixed layers enemyCount: 50, healthLayers: 1, spawnDelay: 400 }, { // Wave 9: 55 enemies with mixed layers enemyCount: 55, healthLayers: 1, spawnDelay: 360 }, { // Wave 10: 60 enemies with mixed layers enemyCount: 60, healthLayers: 1, spawnDelay: 320 }]; var currentWaveIndex = 0; var enemiesSpawned = 0; var waveInProgress = false; var nextSpawnTime = 0; // Function to create enemy with specified health layers function createEnemy(healthLayers) { var enemy = game.addChild(LK.getAsset('Enemy', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1 })); // Add hitbox to enemy var hitboxGraphics = enemy.attachAsset('hitbox', { anchorX: 0.5, anchorY: 0.5 }); hitboxGraphics.alpha = shotHitbox ? 0.2 : 0; // Set visibility based on shotHitbox variable // Set starting position to first camino waypoint enemy.x = caminoPositions[0].x; enemy.y = caminoPositions[0].y; // Path following properties enemy.currentWaypointIndex = 0; enemy.isMoving = false; // Health system for enemy enemy.maxHealth = healthLayers; enemy.currentHealth = enemy.maxHealth; enemy.healthLayers = []; enemy.canBeDestroyed = false; // Enemy cannot be destroyed initially // Create health layers based on healthLayers parameter for (var i = 0; i < healthLayers; i++) { var colors = [0xff0000, 0x0000ff, 0x00ff00]; // Red, Blue, Green enemy.healthLayers.push({ min: i + 1, max: i + 1, color: colors[i % colors.length] }); } // Method to update enemy color based on health enemy.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 enemy.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(); } // 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 enemy.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) var self = this; this.canBeDestroyed = true; // Mark enemy as able to be destroyed during animation tween(this, { scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 3000, easing: tween.easeOut, onFinish: function onFinish() { // Check if enemy was destroyed during animation if (!self.parent) { return; // Enemy was already destroyed } // Enemy reached the end - reduce player life based on remaining health layers playerLife -= self.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] === self) { enemies.splice(j, 1); break; } } // Destroy the enemy self.destroy(); } }); return; } this.currentWaypointIndex = (this.currentWaypointIndex + 1) % caminoPositions.length; var nextWaypoint = caminoPositions[this.currentWaypointIndex]; // Calculate distance for movement duration var dx = nextWaypoint.x - this.x; var dy = nextWaypoint.y - this.y; var distance = Math.sqrt(dx * dx + dy * dy); var speed = 260; // pixels per second (30% increase from 200) var duration = distance / speed * 1000; // Convert to milliseconds this.isMoving = true; // Calculate rotation to face the next waypoint var angle = Math.atan2(dy, dx); // Smooth rotation using tween animation 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 (> 0.1 radians) if (Math.abs(angleDiff) > 0.1) { // Stop any existing rotation tween tween.stop(this, { rotation: true }); // Animate rotation smoothly tween(this, { rotation: angle }, { duration: 800, easing: tween.easeOut }); } else { this.rotation = angle; } // Use tween for smooth movement tween(this, { x: nextWaypoint.x, y: nextWaypoint.y }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { enemy.isMoving = false; // Move to next waypoint immediately without delay enemy.moveToNextWaypoint(); } }); }; // Initialize enemy color enemy.updateHealthColor(); // Start movement after a short delay LK.setTimeout(function () { enemy.moveToNextWaypoint(); }, 500); 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 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; // Reusable upgrade system var upgradeDefinitions = { speed: { title: 'Speed +20%', cost: 75, apply: function apply(tower) { tower.cadence = Math.max(100, tower.cadence * 0.8); // Reduce cadence by 20% (faster shooting) tower.leftUpgrades = tower.leftUpgrades || 0; tower.leftUpgrades++; } }, range: { title: 'Range +25%', cost: 100, apply: function apply(tower) { tower.range = Math.floor(tower.range * 1.25); tower.rangeSquared = tower.range * tower.range; // Update visual range area if (tower.children[0]) { var areaScale = tower.range * 2 / 100; tower.children[0].scaleX = areaScale; tower.children[0].scaleY = areaScale; } tower.leftUpgrades = tower.leftUpgrades || 0; tower.leftUpgrades++; } }, multishot: { title: '2 Side Bullets', cost: 150, apply: function apply(tower) { tower.hasMultishot = true; tower.leftUpgrades = tower.leftUpgrades || 0; tower.leftUpgrades++; } }, damage: { title: 'Damage +1', cost: 50, apply: function apply(tower) { tower.damage += 1; } }, right1_damage: { title: 'Damage +1', cost: 50, apply: function apply(tower) { tower.damage += 1; tower.rightUpgrades = tower.rightUpgrades || 0; tower.rightUpgrades++; } }, right1_cross: { title: 'Cross +2', cost: 75, apply: function apply(tower) { tower.cross = (tower.cross || 1) + 2; // Increase this tower's cross value by 2 tower.rightUpgrades = tower.rightUpgrades || 0; tower.rightUpgrades++; } }, right1_range: { title: 'Range +30%', cost: 100, apply: function apply(tower) { tower.range = Math.floor(tower.range * 1.3); tower.rangeSquared = tower.range * tower.range; // Update visual range area if (tower.children[0]) { var areaScale = tower.range * 2 / 100; tower.children[0].scaleX = areaScale; tower.children[0].scaleY = areaScale; } tower.rightUpgrades = tower.rightUpgrades || 0; tower.rightUpgrades++; } }, maxed: { title: 'Buy max', cost: 999999, // Very high cost so it can't be afforded apply: function apply(tower) { // Do nothing - this upgrade can't be purchased } } }; // Function to get current left upgrade options for a tower function getLeftUpgradeOptions(tower) { if (!tower.leftUpgrades) tower.leftUpgrades = 0; if (tower.leftUpgrades === 0) { return 'speed'; } else if (tower.leftUpgrades === 1) { return 'range'; } else if (tower.leftUpgrades === 2) { return 'multishot'; } return 'maxed'; // Return 'maxed' instead of null } // Function to get current right upgrade options for a tower function getRightUpgradeOptions(tower) { if (!tower.rightUpgrades) tower.rightUpgrades = 0; if (tower.rightUpgrades === 0) { return { first: 'right1_damage', second: null }; } else if (tower.rightUpgrades === 1) { return { first: 'right1_cross', second: null }; } else if (tower.rightUpgrades === 2) { return { first: 'right1_range', second: null }; } return { first: 'maxed', second: null }; } // Left upgrade frame - Damage +1 var leftUpgradeFrame = towerUpgradeContainer.addChild(LK.getAsset('UpgradeBG', { anchorX: 0.5, anchorY: 0.5 })); leftUpgradeFrame.x = -700; leftUpgradeFrame.y = 180; var leftUpgradeTitle = towerUpgradeContainer.addChild(new Text2('', { size: 60, fill: 0xFFFFFF })); leftUpgradeTitle.anchor.set(0.5, 0.5); leftUpgradeTitle.x = -700; leftUpgradeTitle.y = 0; // Right upgrade frames and titles (only 2 slots) var rightUpgradeFrame1 = towerUpgradeContainer.addChild(LK.getAsset('UpgradeBG', { anchorX: 0.5, anchorY: 0.5 })); rightUpgradeFrame1.x = 100; rightUpgradeFrame1.y = 180; var rightUpgradeTitle1 = towerUpgradeContainer.addChild(new Text2('', { size: 60, fill: 0xFFFFFF })); rightUpgradeTitle1.anchor.set(0.5, 0.5); rightUpgradeTitle1.x = 100; rightUpgradeTitle1.y = 0; var rightUpgradeFrame2 = towerUpgradeContainer.addChild(LK.getAsset('UpgradeBG', { anchorX: 0.5, anchorY: 0.5 })); rightUpgradeFrame2.x = 400; rightUpgradeFrame2.y = 180; var rightUpgradeTitle2 = towerUpgradeContainer.addChild(new Text2('', { size: 60, fill: 0xFFFFFF })); rightUpgradeTitle2.anchor.set(0.5, 0.5); rightUpgradeTitle2.x = 400; rightUpgradeTitle2.y = 0; // 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; } 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; } } // 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'); } } }; } // 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); // Clean up existing upgrade buttons if (leftUpgrade && leftUpgrade.button && leftUpgrade.button.parent) { leftUpgrade.button.destroy(); leftUpgrade.text.destroy(); } if (right1Upgrade && right1Upgrade.button && right1Upgrade.button.parent) { right1Upgrade.button.destroy(); right1Upgrade.text.destroy(); } if (right2Upgrade && right2Upgrade.button && right2Upgrade.button.parent) { right2Upgrade.button.destroy(); right2Upgrade.text.destroy(); } // Create new upgrade buttons based on current tower state leftUpgrade = leftOption ? createUpgradeButton(leftOption, -700, 380, towerUpgradeContainer) : null; right1Upgrade = rightOptions.first ? createUpgradeButton(rightOptions.first, 100, 380, towerUpgradeContainer) : null; right2Upgrade = rightOptions.second ? createUpgradeButton(rightOptions.second, 400, 380, towerUpgradeContainer) : null; // Update upgrade titles - always show upgrades, even when maxed if (leftOption) { leftUpgradeTitle.setText(upgradeDefinitions[leftOption].title); leftUpgradeFrame.visible = true; leftUpgradeTitle.visible = true; } else { leftUpgradeTitle.setText(''); leftUpgradeFrame.visible = false; leftUpgradeTitle.visible = false; } if (rightOptions.first) { rightUpgradeTitle1.setText(upgradeDefinitions[rightOptions.first].title); rightUpgradeFrame1.visible = true; rightUpgradeTitle1.visible = true; } else { rightUpgradeTitle1.setText(''); rightUpgradeFrame1.visible = false; rightUpgradeTitle1.visible = false; } if (rightOptions.second) { rightUpgradeTitle2.setText(upgradeDefinitions[rightOptions.second].title); rightUpgradeFrame2.visible = true; rightUpgradeTitle2.visible = true; } else { rightUpgradeTitle2.setText(''); rightUpgradeFrame2.visible = false; rightUpgradeTitle2.visible = false; } // Update all upgrade button displays if (leftUpgrade) leftUpgrade.updateDisplay(); if (right1Upgrade) right1Upgrade.updateDisplay(); if (right2Upgrade) right2Upgrade.updateDisplay(); } else { // Clear reference to selected tower selectedTowerForUpgrade = null; // 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 // 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); // Initialize game variables 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 // Top overlay layer (pointer on top of everything) var puntero = game.addChild(LK.getAsset('Puntero', { anchorX: 0.5, anchorY: 0.5 })); puntero.x = 1024; puntero.y = 1366; puntero.alpha = 0; // Game move handler for dragging game.move = function (x, y, obj) { puntero.x = x; puntero.y = y; if (isDragging && draggedTower) { var gameplayBounds = getGameplayBounds(); var uiBounds = getUIBounds(); var offset = calculateDragOffset(x, y); var targetX = x + offset.offsetX; var targetY = y + offset.offsetY; // Apply boundary constraints if (targetY > uiBounds.top) { targetY = uiBounds.top - 70; } if (targetY < gameplayBounds.top) { targetY = gameplayBounds.top + 70; } if (targetX < uiBounds.left) { targetX = uiBounds.left + 70; } if (targetX > uiBounds.right) { targetX = uiBounds.right - 70; } // Check if tower is too close to existing towers var minDistance = 120; // 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]; var dx = targetX - camino.x; var dy = targetY - camino.y; var distanceSquared = dx * dx + dy * dy; // Check if tower center would overlap with camino (considering both sizes) var overlapDistance = 150; // Reasonable overlap distance if (distanceSquared < overlapDistance * overlapDistance) { 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; if (cannotPlace) { // Apply red tint to area when cannot place tower if (draggedTower.children[0]) { draggedTower.children[0].tint = 0xff0000; } } else { // Remove tint from area when tower can be placed if (draggedTower.children[0]) { draggedTower.children[0].tint = 0xFFFFFF; } } // Smooth movement var smoothness = 0.15; draggedTower.x += (targetX - draggedTower.x) * smoothness; draggedTower.y += (targetY - draggedTower.y) * smoothness; } }; // 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 = false; // When true, show enemy hitboxes; when false, hide them // Game update handler game.update = function () { // Update UI texts with current values moneyText.setText('Money: ' + playerMoney); lifeText.setText('Life: ' + playerLife); waveText.setText('Wave: ' + currentWave); // Update path visibility based on PathShow variable for (var i = 0; i < caminoObjects.length; i++) { if (PathShow) { caminoObjects[i].alpha = 0.1; } else { caminoObjects[i].alpha = 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 var healthLayers = 1; // Default to 1 layer if (currentWaveIndex === 1) { // Wave 2: First 15 enemies have 1 layer, last 5 have 2 layers if (enemiesSpawned >= 15) { healthLayers = 2; } } else if (currentWaveIndex === 2) { // Wave 3: First 15 have 1 layer, next 5 have 2 layers, last 5 have 3 layers if (enemiesSpawned >= 20) { healthLayers = 3; } else if (enemiesSpawned >= 15) { healthLayers = 2; } } else if (currentWaveIndex === 3) { // Wave 4: First 15 have 1 layer, next 10 have 2 layers, last 5 have 3 layers if (enemiesSpawned >= 25) { healthLayers = 3; } else if (enemiesSpawned >= 15) { healthLayers = 2; } } else if (currentWaveIndex === 4) { // Wave 5: First 15 have 1 layer, next 10 have 2 layers, last 10 have 3 layers if (enemiesSpawned >= 25) { healthLayers = 3; } else if (enemiesSpawned >= 15) { healthLayers = 2; } } else if (currentWaveIndex === 5) { // Wave 6: First 15 have 1 layer, next 15 have 2 layers, last 10 have 3 layers if (enemiesSpawned >= 30) { healthLayers = 3; } else if (enemiesSpawned >= 15) { healthLayers = 2; } } else if (currentWaveIndex === 6) { // Wave 7: First 15 have 1 layer, next 15 have 2 layers, last 15 have 3 layers if (enemiesSpawned >= 30) { healthLayers = 3; } else if (enemiesSpawned >= 15) { healthLayers = 2; } } else if (currentWaveIndex === 7) { // Wave 8: First 15 have 1 layer, next 20 have 2 layers, last 15 have 3 layers if (enemiesSpawned >= 35) { healthLayers = 3; } else if (enemiesSpawned >= 15) { healthLayers = 2; } } else if (currentWaveIndex === 8) { // Wave 9: First 15 have 1 layer, next 20 have 2 layers, last 20 have 3 layers if (enemiesSpawned >= 35) { healthLayers = 3; } else if (enemiesSpawned >= 15) { healthLayers = 2; } } else if (currentWaveIndex === 9) { // Wave 10: First 15 have 1 layer, next 25 have 2 layers, last 20 have 3 layers if (enemiesSpawned >= 40) { healthLayers = 3; } else if (enemiesSpawned >= 15) { healthLayers = 2; } } // Spawn enemy createEnemy(healthLayers); enemiesSpawned++; nextSpawnTime = currentTime + currentWaveData.spawnDelay; } // Check if wave is complete if (enemiesSpawned >= currentWaveData.enemyCount && enemies.length === 0) { waveInProgress = false; // Start next wave after a delay LK.setTimeout(function () { startWave(currentWaveIndex + 1); }, 1500); // 1.5 second delay between waves (reduced from 3 seconds) } } }; // Game release handler game.up = function (x, y, obj) { if (isDragging && draggedTower) { var gameplayBounds = getGameplayBounds(); var uiBounds = getUIBounds(); var pointerInUI = isPointInUI(puntero.x, puntero.y); var towerInUI = draggedTower.x >= uiBounds.left && draggedTower.x <= uiBounds.right && draggedTower.y >= uiBounds.top && draggedTower.y <= uiBounds.bottom; if (pointerInUI || towerInUI) { draggedTower.destroy(); } else if (draggedTower.x >= gameplayBounds.left && draggedTower.x <= gameplayBounds.right && draggedTower.y >= gameplayBounds.top && draggedTower.y <= gameplayBounds.bottom) { // Check if tower is too close to existing towers var minDistance = 170; // Minimum distance between towers var minDistanceSquared = minDistance * minDistance; var canPlace = true; 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) { canPlace = false; break; } } // Check if tower would be placed on camino path if (canPlace) { for (var i = 0; i < caminoObjects.length; i++) { var camino = caminoObjects[i]; var dx = draggedTower.x - camino.x; var dy = draggedTower.y - camino.y; var distanceSquared = dx * dx + dy * dy; var overlapDistance = 170; if (distanceSquared < overlapDistance * overlapDistance) { canPlace = false; break; } } } if (canPlace) { draggedTower.alpha = 1.0; draggedTower.tint = 0xffffff; draggedTower.hideRange(); // Hide range area when placed draggedTower.isPlaced = true; // Set placement status placedTowers.push(draggedTower); // Add to placed towers array LK.getSound('TorreColocada').play(); // Play tower placement sound } else { // Tower is too close to existing towers, destroy it draggedTower.destroy(); } } else { draggedTower.destroy(); } isDragging = false; draggedTower = null; } };
/****
* 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
// Set lifespan of 1.3 seconds using tween
tween(self, {}, {
duration: 1300,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
}
}
});
self.update = function () {
if (!self.isAlive) {
return;
}
// Move in straight line using direction
self.x += self.directionX * self.speed * frameTime;
self.y += self.directionY * self.speed * 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) {
// 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 hitbox size for collision detection (hitbox is 120x120)
var hitboxRadius = 60; // Half of hitbox size
if (distanceSquared < hitboxRadius * hitboxRadius) {
// Apply damage to enemy using its takeDamage method
if (enemy.takeDamage) {
enemy.takeDamage(self.damage);
// Reduce bullet damage after hitting an enemy (50% reduction per hit)
self.damage = Math.max(1, Math.floor(self.damage * 0.5)); // Minimum damage of 1
}
// 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 < 400) {
// 20 * 20 = 400
// Apply damage to enemy using its takeDamage method
if (enemy.takeDamage) {
enemy.takeDamage(self.damage);
// Reduce bullet damage after hitting an enemy (50% reduction per hit)
self.damage = Math.max(1, Math.floor(self.damage * 0.5)); // Minimum damage of 1
}
// Add damage to tower's total damage counter
if (self.towerRef && self.towerRef.parent) {
self.towerRef.totalDamage += self.damage;
}
// Increment hit count and check if bullet should be destroyed
self.hitCount++;
var towerCross = self.towerRef && self.towerRef.cross ? self.towerRef.cross : 1;
if (self.hitCount >= towerCross) {
// Bullet has hit the required number of enemies, destroy it
self.isAlive = false;
if (self.parent) {
self.destroy();
}
return;
}
// Continue to check other enemies
break;
}
}
}
}
};
return self;
});
// Reusable tower creation function
var Gameplay = Container.expand(function () {
var self = Container.call(this);
var gameplayGraphics = self.attachAsset('gameplayBackground', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var UI = Container.expand(function () {
var self = Container.call(this);
var uiGraphics = self.attachAsset('uiBackground', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Reusable tower creation function
7;
function createTowerBase(params) {
return Container.expand(function () {
var self = Container.call(this);
// Tower parameters
self.damage = params.damage;
self.cadence = params.cadence;
self.range = params.range;
self.rangeSquared = self.range * self.range; // Cache squared range for optimization
self.totalDamage = 0; // Track total damage dealt by this tower
self.cross = 1; // Initialize cross value for this tower
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 = [];
// 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) {
enemiesInRange.push(enemy);
}
}
}
return enemiesInRange;
};
// Method to shoot at target
self.shoot = function (target) {
var currentTime = Date.now();
if (currentTime - self.lastShotTime >= self.cadence) {
// 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);
// Calculate direction to current target position with angle offset
var baseAngle = Math.atan2(dy, dx);
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.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 / 6); // 30 degrees to the left
createBullet(Math.PI / 6); // 30 degrees to the right
}
self.lastShotTime = currentTime;
// Play bullet shooting sound
LK.getSound('Balababa').play();
// Change to attack sprite
var attackSprite = self.attachAsset('TorreinicialAttack', {
anchorX: 0.5,
anchorY: 0.5
});
attackSprite.rotation = towerGraphics.rotation;
attackSprite.scaleY = towerGraphics.scaleY;
self.removeChild(towerGraphics);
towerGraphics = attackSprite;
// Add squash animation to tower
tween.stop(towerGraphics, {
scaleX: true
}); // Stop any existing scale tweens
tween(towerGraphics, {
scaleX: 0.7
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(towerGraphics, {
scaleX: 1.0
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Change back to normal sprite after attack animation
var normalSprite = self.attachAsset(params.asset, {
anchorX: 0.5,
anchorY: 0.5
});
normalSprite.rotation = towerGraphics.rotation;
normalSprite.scaleY = towerGraphics.scaleY;
self.removeChild(towerGraphics);
towerGraphics = normalSprite;
}
});
}
});
}
};
// 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) {
// Find and shoot at enemies
var enemies = self.findEnemiesInRange();
if (enemies.length > 0) {
var target = enemies[0];
// Calculate angle to target
var dx = target.x - self.x;
var dy = target.y - self.y;
var targetAngle = Math.atan2(dy, dx);
// 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
});
}
// Flip tower horizontally if target is on the left side
if (dx < 0) {
towerGraphics.scaleY = -1; // Flip on Y axis when target is to the left
} else {
towerGraphics.scaleY = 1; // Normal orientation when target is to the right
}
// Shoot at the first enemy found
self.shoot(target);
}
}
};
// Handle tower selection
self.down = function (x, y, obj) {
// Deselect all other towers first
for (var i = 0; i < placedTowers.length; i++) {
if (placedTowers[i] !== self) {
placedTowers[i].isSelected = false;
placedTowers[i].hideRange();
}
}
// Toggle selection for this tower
self.isSelected = !self.isSelected;
if (self.isSelected) {
self.showRange();
updateTowerUpgrade(self); // Update UI with this tower's upgrade options
} else {
self.hideRange();
updateTowerUpgrade(null); // Clear UI when deselected
}
};
return self;
});
}
var Tower = createTowerBase({
damage: 1,
// Damage dealt per shot
cadence: 1000,
// Time between shots in milliseconds (1 second)
range: 400,
// Range in pixels
asset: 'torreInicial'
});
// Initialize tower-specific properties
Tower.prototype.cross = 1; // Each tower starts with cross value of 1
var draggedTower = null;
var isDragging = false;
var placedTowers = []; // Track all placed towers
// Cache frequently used values
var gameplayBounds = null;
var uiBounds = null;
var frameTime = 1 / 60; // Cache frame time calculation
// Reusable tower creation function that accepts tower constructor
function createTowerButton(color, x, y, TowerClass) {
var buttonBG = game.addChild(LK.getAsset('BGbuttonTower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
}));
buttonBG.x = x;
buttonBG.y = y;
var button = game.addChild(new TowerClass());
button.x = x;
button.y = y;
button.tint = color;
// Store reference to button background for easy access
button.buttonBG = buttonBG;
button.down = function (x, y, obj) {
isDragging = true;
draggedTower = game.addChild(new TowerClass());
draggedTower.x = button.x;
draggedTower.y = button.y;
draggedTower.alpha = 0.7;
draggedTower.tint = color;
draggedTower.showRange(); // Show range area when dragging
};
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 functions for bounds calculations
function getGameplayBounds() {
return {
left: gameplay.x - gameplay.width / 2,
right: gameplay.x + gameplay.width / 2,
top: gameplay.y - gameplay.height / 2,
bottom: gameplay.y + gameplay.height / 2,
centerX: gameplay.x,
centerY: gameplay.y
};
}
function getUIBounds() {
return {
left: ui.x - ui.width / 2,
right: ui.x + ui.width / 2,
top: ui.y - ui.height / 2,
bottom: ui.y + ui.height / 2
};
}
function isPointInUI(x, y) {
var bounds = getUIBounds();
return x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom;
}
function calculateDragOffset(x, y) {
var bounds = getGameplayBounds();
var distanceFromCenterX = Math.abs(x - bounds.centerX);
var distanceFromCenterY = Math.abs(y - bounds.centerY);
var maxDistanceX = gameplay.width / 2;
var maxDistanceY = gameplay.height / 2;
var normalizedDistanceX = distanceFromCenterX / maxDistanceX;
var normalizedDistanceY = distanceFromCenterY / maxDistanceY;
var maxOffset = 200;
var offsetMagnitudeX = maxOffset * normalizedDistanceX;
var offsetMagnitudeY = maxOffset * normalizedDistanceY;
var offsetX = 0;
var offsetY = 0;
if (x >= bounds.centerX && y <= bounds.centerY) {
offsetX = offsetMagnitudeX;
offsetY = -offsetMagnitudeY;
} else if (x <= bounds.centerX && y <= bounds.centerY) {
offsetX = -offsetMagnitudeX;
offsetY = -offsetMagnitudeY;
} else if (x <= bounds.centerX && y >= bounds.centerY) {
offsetX = -offsetMagnitudeX;
offsetY = offsetMagnitudeY;
} else if (x >= bounds.centerX && y >= bounds.centerY) {
offsetX = offsetMagnitudeX;
offsetY = offsetMagnitudeY;
}
return {
offsetX: offsetX,
offsetY: offsetY
};
}
// Layer management - objects are added in z-index order (bottom to top)
// Background layer
var gameplay = game.addChild(new Gameplay());
gameplay.x = 1024;
gameplay.y = 1093;
// Game objects layer (towers will be added here)
// UI layer
var ui = game.addChild(new UI());
ui.x = 1024;
ui.y = 2459;
// Create single tower creation button
var towerButtons = [];
var startX = 400; // Position button on the left side
towerButtons.push(createTowerButton(0xffffff, startX, 2459, Tower)); // White tower (normal)
// Create path objects with transparency
var caminoObjects = [];
// Add several camino objects to create a path
var caminoPositions = [{
x: 990,
y: -100,
rotation: 0
}, {
x: 890,
y: 40,
rotation: 12
}, {
x: 650,
y: 150,
rotation: 70
}, {
x: 500,
y: 340,
rotation: 5
}, {
x: 500,
y: 470,
rotation: Math.PI
}, {
x: 550,
y: 540,
rotation: -Math.PI / 4
}, {
x: 700,
y: 620,
rotation: 0
}, {
x: 900,
y: 620,
rotation: 0
}, {
x: 1100,
y: 620,
rotation: 0
}, {
x: 1250,
y: 650,
rotation: -20
}, {
x: 1420,
y: 740,
rotation: Math.PI / 4
}, {
x: 1530,
y: 880,
rotation: Math.PI
}, {
x: 1510,
y: 1080,
rotation: -12
}, {
x: 1380,
y: 1210,
rotation: 20
}, {
x: 1180,
y: 1300,
rotation: 9
}, {
x: 950,
y: 1370,
rotation: 20
}, {
x: 750,
y: 1480,
rotation: 70
}, {
x: 655,
y: 1610,
rotation: 0
}, {
x: 610,
y: 1750,
rotation: 0
}, {
x: 610,
y: 1900,
rotation: Math.PI / 2
}, {
x: 610,
y: 2100,
rotation: 0
}];
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
camino.alpha = 0.2; // Set transparency to 0.2
caminoObjects.push(camino);
}
// Create end object and place it at the final waypoint
var end = game.addChild(LK.getAsset('End', {
anchorX: 0.5,
anchorY: 0.5
}));
// Position end closer to the penultimate waypoint
var finalPosition = caminoPositions[caminoPositions.length - 1];
var penultimatePosition = caminoPositions[caminoPositions.length - 2];
// Calculate direction from penultimate to final waypoint
var dx = finalPosition.x - penultimatePosition.x;
var dy = finalPosition.y - penultimatePosition.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move end object 60% of the way from final to penultimate position
var moveBackRatio = 0.6;
end.x = finalPosition.x - dx * moveBackRatio;
end.y = finalPosition.y - dy * moveBackRatio;
var directionAngle = Math.atan2(dy, dx);
end.rotation = directionAngle;
end.alpha = 1.0; // Remove transparency
// Wave system variables
var enemies = [];
var waveData = [{
// Wave 1: 10 enemies with 1 layer
enemyCount: 10,
healthLayers: 1,
spawnDelay: 800 // 20% faster than 1000ms
}, {
// Wave 2: 15 enemies with 1 layer + 5 enemies with 2 layers
enemyCount: 20,
healthLayers: 1,
// We'll handle mixed health in spawn function
spawnDelay: 640 // 20% faster than 800ms
}, {
// Wave 3: 25 enemies with mixed layers
enemyCount: 25,
healthLayers: 1,
spawnDelay: 600
}, {
// Wave 4: 30 enemies with mixed layers
enemyCount: 30,
healthLayers: 1,
spawnDelay: 560
}, {
// Wave 5: 35 enemies with mixed layers
enemyCount: 35,
healthLayers: 1,
spawnDelay: 520
}, {
// Wave 6: 40 enemies with mixed layers
enemyCount: 40,
healthLayers: 1,
spawnDelay: 480
}, {
// Wave 7: 45 enemies with mixed layers
enemyCount: 45,
healthLayers: 1,
spawnDelay: 440
}, {
// Wave 8: 50 enemies with mixed layers
enemyCount: 50,
healthLayers: 1,
spawnDelay: 400
}, {
// Wave 9: 55 enemies with mixed layers
enemyCount: 55,
healthLayers: 1,
spawnDelay: 360
}, {
// Wave 10: 60 enemies with mixed layers
enemyCount: 60,
healthLayers: 1,
spawnDelay: 320
}];
var currentWaveIndex = 0;
var enemiesSpawned = 0;
var waveInProgress = false;
var nextSpawnTime = 0;
// Function to create enemy with specified health layers
function createEnemy(healthLayers) {
var enemy = game.addChild(LK.getAsset('Enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
}));
// Add hitbox to enemy
var hitboxGraphics = enemy.attachAsset('hitbox', {
anchorX: 0.5,
anchorY: 0.5
});
hitboxGraphics.alpha = shotHitbox ? 0.2 : 0; // Set visibility based on shotHitbox variable
// Set starting position to first camino waypoint
enemy.x = caminoPositions[0].x;
enemy.y = caminoPositions[0].y;
// Path following properties
enemy.currentWaypointIndex = 0;
enemy.isMoving = false;
// Health system for enemy
enemy.maxHealth = healthLayers;
enemy.currentHealth = enemy.maxHealth;
enemy.healthLayers = [];
enemy.canBeDestroyed = false; // Enemy cannot be destroyed initially
// Create health layers based on healthLayers parameter
for (var i = 0; i < healthLayers; i++) {
var colors = [0xff0000, 0x0000ff, 0x00ff00]; // Red, Blue, Green
enemy.healthLayers.push({
min: i + 1,
max: i + 1,
color: colors[i % colors.length]
});
}
// Method to update enemy color based on health
enemy.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
enemy.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();
}
// 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
enemy.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)
var self = this;
this.canBeDestroyed = true; // Mark enemy as able to be destroyed during animation
tween(this, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 3000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Check if enemy was destroyed during animation
if (!self.parent) {
return; // Enemy was already destroyed
}
// Enemy reached the end - reduce player life based on remaining health layers
playerLife -= self.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] === self) {
enemies.splice(j, 1);
break;
}
}
// Destroy the enemy
self.destroy();
}
});
return;
}
this.currentWaypointIndex = (this.currentWaypointIndex + 1) % caminoPositions.length;
var nextWaypoint = caminoPositions[this.currentWaypointIndex];
// Calculate distance for movement duration
var dx = nextWaypoint.x - this.x;
var dy = nextWaypoint.y - this.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var speed = 260; // pixels per second (30% increase from 200)
var duration = distance / speed * 1000; // Convert to milliseconds
this.isMoving = true;
// Calculate rotation to face the next waypoint
var angle = Math.atan2(dy, dx);
// Smooth rotation using tween animation
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 (> 0.1 radians)
if (Math.abs(angleDiff) > 0.1) {
// Stop any existing rotation tween
tween.stop(this, {
rotation: true
});
// Animate rotation smoothly
tween(this, {
rotation: angle
}, {
duration: 800,
easing: tween.easeOut
});
} else {
this.rotation = angle;
}
// Use tween for smooth movement
tween(this, {
x: nextWaypoint.x,
y: nextWaypoint.y
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
enemy.isMoving = false;
// Move to next waypoint immediately without delay
enemy.moveToNextWaypoint();
}
});
};
// Initialize enemy color
enemy.updateHealthColor();
// Start movement after a short delay
LK.setTimeout(function () {
enemy.moveToNextWaypoint();
}, 500);
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
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;
// Reusable upgrade system
var upgradeDefinitions = {
speed: {
title: 'Speed +20%',
cost: 75,
apply: function apply(tower) {
tower.cadence = Math.max(100, tower.cadence * 0.8); // Reduce cadence by 20% (faster shooting)
tower.leftUpgrades = tower.leftUpgrades || 0;
tower.leftUpgrades++;
}
},
range: {
title: 'Range +25%',
cost: 100,
apply: function apply(tower) {
tower.range = Math.floor(tower.range * 1.25);
tower.rangeSquared = tower.range * tower.range;
// Update visual range area
if (tower.children[0]) {
var areaScale = tower.range * 2 / 100;
tower.children[0].scaleX = areaScale;
tower.children[0].scaleY = areaScale;
}
tower.leftUpgrades = tower.leftUpgrades || 0;
tower.leftUpgrades++;
}
},
multishot: {
title: '2 Side Bullets',
cost: 150,
apply: function apply(tower) {
tower.hasMultishot = true;
tower.leftUpgrades = tower.leftUpgrades || 0;
tower.leftUpgrades++;
}
},
damage: {
title: 'Damage +1',
cost: 50,
apply: function apply(tower) {
tower.damage += 1;
}
},
right1_damage: {
title: 'Damage +1',
cost: 50,
apply: function apply(tower) {
tower.damage += 1;
tower.rightUpgrades = tower.rightUpgrades || 0;
tower.rightUpgrades++;
}
},
right1_cross: {
title: 'Cross +2',
cost: 75,
apply: function apply(tower) {
tower.cross = (tower.cross || 1) + 2; // Increase this tower's cross value by 2
tower.rightUpgrades = tower.rightUpgrades || 0;
tower.rightUpgrades++;
}
},
right1_range: {
title: 'Range +30%',
cost: 100,
apply: function apply(tower) {
tower.range = Math.floor(tower.range * 1.3);
tower.rangeSquared = tower.range * tower.range;
// Update visual range area
if (tower.children[0]) {
var areaScale = tower.range * 2 / 100;
tower.children[0].scaleX = areaScale;
tower.children[0].scaleY = areaScale;
}
tower.rightUpgrades = tower.rightUpgrades || 0;
tower.rightUpgrades++;
}
},
maxed: {
title: 'Buy max',
cost: 999999,
// Very high cost so it can't be afforded
apply: function apply(tower) {
// Do nothing - this upgrade can't be purchased
}
}
};
// Function to get current left upgrade options for a tower
function getLeftUpgradeOptions(tower) {
if (!tower.leftUpgrades) tower.leftUpgrades = 0;
if (tower.leftUpgrades === 0) {
return 'speed';
} else if (tower.leftUpgrades === 1) {
return 'range';
} else if (tower.leftUpgrades === 2) {
return 'multishot';
}
return 'maxed'; // Return 'maxed' instead of null
}
// Function to get current right upgrade options for a tower
function getRightUpgradeOptions(tower) {
if (!tower.rightUpgrades) tower.rightUpgrades = 0;
if (tower.rightUpgrades === 0) {
return {
first: 'right1_damage',
second: null
};
} else if (tower.rightUpgrades === 1) {
return {
first: 'right1_cross',
second: null
};
} else if (tower.rightUpgrades === 2) {
return {
first: 'right1_range',
second: null
};
}
return {
first: 'maxed',
second: null
};
}
// Left upgrade frame - Damage +1
var leftUpgradeFrame = towerUpgradeContainer.addChild(LK.getAsset('UpgradeBG', {
anchorX: 0.5,
anchorY: 0.5
}));
leftUpgradeFrame.x = -700;
leftUpgradeFrame.y = 180;
var leftUpgradeTitle = towerUpgradeContainer.addChild(new Text2('', {
size: 60,
fill: 0xFFFFFF
}));
leftUpgradeTitle.anchor.set(0.5, 0.5);
leftUpgradeTitle.x = -700;
leftUpgradeTitle.y = 0;
// Right upgrade frames and titles (only 2 slots)
var rightUpgradeFrame1 = towerUpgradeContainer.addChild(LK.getAsset('UpgradeBG', {
anchorX: 0.5,
anchorY: 0.5
}));
rightUpgradeFrame1.x = 100;
rightUpgradeFrame1.y = 180;
var rightUpgradeTitle1 = towerUpgradeContainer.addChild(new Text2('', {
size: 60,
fill: 0xFFFFFF
}));
rightUpgradeTitle1.anchor.set(0.5, 0.5);
rightUpgradeTitle1.x = 100;
rightUpgradeTitle1.y = 0;
var rightUpgradeFrame2 = towerUpgradeContainer.addChild(LK.getAsset('UpgradeBG', {
anchorX: 0.5,
anchorY: 0.5
}));
rightUpgradeFrame2.x = 400;
rightUpgradeFrame2.y = 180;
var rightUpgradeTitle2 = towerUpgradeContainer.addChild(new Text2('', {
size: 60,
fill: 0xFFFFFF
}));
rightUpgradeTitle2.anchor.set(0.5, 0.5);
rightUpgradeTitle2.x = 400;
rightUpgradeTitle2.y = 0;
// 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;
} 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;
}
}
// 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');
}
}
};
}
// 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);
// Clean up existing upgrade buttons
if (leftUpgrade && leftUpgrade.button && leftUpgrade.button.parent) {
leftUpgrade.button.destroy();
leftUpgrade.text.destroy();
}
if (right1Upgrade && right1Upgrade.button && right1Upgrade.button.parent) {
right1Upgrade.button.destroy();
right1Upgrade.text.destroy();
}
if (right2Upgrade && right2Upgrade.button && right2Upgrade.button.parent) {
right2Upgrade.button.destroy();
right2Upgrade.text.destroy();
}
// Create new upgrade buttons based on current tower state
leftUpgrade = leftOption ? createUpgradeButton(leftOption, -700, 380, towerUpgradeContainer) : null;
right1Upgrade = rightOptions.first ? createUpgradeButton(rightOptions.first, 100, 380, towerUpgradeContainer) : null;
right2Upgrade = rightOptions.second ? createUpgradeButton(rightOptions.second, 400, 380, towerUpgradeContainer) : null;
// Update upgrade titles - always show upgrades, even when maxed
if (leftOption) {
leftUpgradeTitle.setText(upgradeDefinitions[leftOption].title);
leftUpgradeFrame.visible = true;
leftUpgradeTitle.visible = true;
} else {
leftUpgradeTitle.setText('');
leftUpgradeFrame.visible = false;
leftUpgradeTitle.visible = false;
}
if (rightOptions.first) {
rightUpgradeTitle1.setText(upgradeDefinitions[rightOptions.first].title);
rightUpgradeFrame1.visible = true;
rightUpgradeTitle1.visible = true;
} else {
rightUpgradeTitle1.setText('');
rightUpgradeFrame1.visible = false;
rightUpgradeTitle1.visible = false;
}
if (rightOptions.second) {
rightUpgradeTitle2.setText(upgradeDefinitions[rightOptions.second].title);
rightUpgradeFrame2.visible = true;
rightUpgradeTitle2.visible = true;
} else {
rightUpgradeTitle2.setText('');
rightUpgradeFrame2.visible = false;
rightUpgradeTitle2.visible = false;
}
// Update all upgrade button displays
if (leftUpgrade) leftUpgrade.updateDisplay();
if (right1Upgrade) right1Upgrade.updateDisplay();
if (right2Upgrade) right2Upgrade.updateDisplay();
} else {
// Clear reference to selected tower
selectedTowerForUpgrade = null;
// 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
// 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);
// Initialize game variables
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
// Top overlay layer (pointer on top of everything)
var puntero = game.addChild(LK.getAsset('Puntero', {
anchorX: 0.5,
anchorY: 0.5
}));
puntero.x = 1024;
puntero.y = 1366;
puntero.alpha = 0;
// Game move handler for dragging
game.move = function (x, y, obj) {
puntero.x = x;
puntero.y = y;
if (isDragging && draggedTower) {
var gameplayBounds = getGameplayBounds();
var uiBounds = getUIBounds();
var offset = calculateDragOffset(x, y);
var targetX = x + offset.offsetX;
var targetY = y + offset.offsetY;
// Apply boundary constraints
if (targetY > uiBounds.top) {
targetY = uiBounds.top - 70;
}
if (targetY < gameplayBounds.top) {
targetY = gameplayBounds.top + 70;
}
if (targetX < uiBounds.left) {
targetX = uiBounds.left + 70;
}
if (targetX > uiBounds.right) {
targetX = uiBounds.right - 70;
}
// Check if tower is too close to existing towers
var minDistance = 120; // 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];
var dx = targetX - camino.x;
var dy = targetY - camino.y;
var distanceSquared = dx * dx + dy * dy;
// Check if tower center would overlap with camino (considering both sizes)
var overlapDistance = 150; // Reasonable overlap distance
if (distanceSquared < overlapDistance * overlapDistance) {
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;
if (cannotPlace) {
// Apply red tint to area when cannot place tower
if (draggedTower.children[0]) {
draggedTower.children[0].tint = 0xff0000;
}
} else {
// Remove tint from area when tower can be placed
if (draggedTower.children[0]) {
draggedTower.children[0].tint = 0xFFFFFF;
}
}
// Smooth movement
var smoothness = 0.15;
draggedTower.x += (targetX - draggedTower.x) * smoothness;
draggedTower.y += (targetY - draggedTower.y) * smoothness;
}
};
// 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 = false; // When true, show enemy hitboxes; when false, hide them
// Game update handler
game.update = function () {
// Update UI texts with current values
moneyText.setText('Money: ' + playerMoney);
lifeText.setText('Life: ' + playerLife);
waveText.setText('Wave: ' + currentWave);
// Update path visibility based on PathShow variable
for (var i = 0; i < caminoObjects.length; i++) {
if (PathShow) {
caminoObjects[i].alpha = 0.1;
} else {
caminoObjects[i].alpha = 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
var healthLayers = 1; // Default to 1 layer
if (currentWaveIndex === 1) {
// Wave 2: First 15 enemies have 1 layer, last 5 have 2 layers
if (enemiesSpawned >= 15) {
healthLayers = 2;
}
} else if (currentWaveIndex === 2) {
// Wave 3: First 15 have 1 layer, next 5 have 2 layers, last 5 have 3 layers
if (enemiesSpawned >= 20) {
healthLayers = 3;
} else if (enemiesSpawned >= 15) {
healthLayers = 2;
}
} else if (currentWaveIndex === 3) {
// Wave 4: First 15 have 1 layer, next 10 have 2 layers, last 5 have 3 layers
if (enemiesSpawned >= 25) {
healthLayers = 3;
} else if (enemiesSpawned >= 15) {
healthLayers = 2;
}
} else if (currentWaveIndex === 4) {
// Wave 5: First 15 have 1 layer, next 10 have 2 layers, last 10 have 3 layers
if (enemiesSpawned >= 25) {
healthLayers = 3;
} else if (enemiesSpawned >= 15) {
healthLayers = 2;
}
} else if (currentWaveIndex === 5) {
// Wave 6: First 15 have 1 layer, next 15 have 2 layers, last 10 have 3 layers
if (enemiesSpawned >= 30) {
healthLayers = 3;
} else if (enemiesSpawned >= 15) {
healthLayers = 2;
}
} else if (currentWaveIndex === 6) {
// Wave 7: First 15 have 1 layer, next 15 have 2 layers, last 15 have 3 layers
if (enemiesSpawned >= 30) {
healthLayers = 3;
} else if (enemiesSpawned >= 15) {
healthLayers = 2;
}
} else if (currentWaveIndex === 7) {
// Wave 8: First 15 have 1 layer, next 20 have 2 layers, last 15 have 3 layers
if (enemiesSpawned >= 35) {
healthLayers = 3;
} else if (enemiesSpawned >= 15) {
healthLayers = 2;
}
} else if (currentWaveIndex === 8) {
// Wave 9: First 15 have 1 layer, next 20 have 2 layers, last 20 have 3 layers
if (enemiesSpawned >= 35) {
healthLayers = 3;
} else if (enemiesSpawned >= 15) {
healthLayers = 2;
}
} else if (currentWaveIndex === 9) {
// Wave 10: First 15 have 1 layer, next 25 have 2 layers, last 20 have 3 layers
if (enemiesSpawned >= 40) {
healthLayers = 3;
} else if (enemiesSpawned >= 15) {
healthLayers = 2;
}
}
// Spawn enemy
createEnemy(healthLayers);
enemiesSpawned++;
nextSpawnTime = currentTime + currentWaveData.spawnDelay;
}
// Check if wave is complete
if (enemiesSpawned >= currentWaveData.enemyCount && enemies.length === 0) {
waveInProgress = false;
// Start next wave after a delay
LK.setTimeout(function () {
startWave(currentWaveIndex + 1);
}, 1500); // 1.5 second delay between waves (reduced from 3 seconds)
}
}
};
// Game release handler
game.up = function (x, y, obj) {
if (isDragging && draggedTower) {
var gameplayBounds = getGameplayBounds();
var uiBounds = getUIBounds();
var pointerInUI = isPointInUI(puntero.x, puntero.y);
var towerInUI = draggedTower.x >= uiBounds.left && draggedTower.x <= uiBounds.right && draggedTower.y >= uiBounds.top && draggedTower.y <= uiBounds.bottom;
if (pointerInUI || towerInUI) {
draggedTower.destroy();
} else if (draggedTower.x >= gameplayBounds.left && draggedTower.x <= gameplayBounds.right && draggedTower.y >= gameplayBounds.top && draggedTower.y <= gameplayBounds.bottom) {
// Check if tower is too close to existing towers
var minDistance = 170; // Minimum distance between towers
var minDistanceSquared = minDistance * minDistance;
var canPlace = true;
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) {
canPlace = false;
break;
}
}
// Check if tower would be placed on camino path
if (canPlace) {
for (var i = 0; i < caminoObjects.length; i++) {
var camino = caminoObjects[i];
var dx = draggedTower.x - camino.x;
var dy = draggedTower.y - camino.y;
var distanceSquared = dx * dx + dy * dy;
var overlapDistance = 170;
if (distanceSquared < overlapDistance * overlapDistance) {
canPlace = false;
break;
}
}
}
if (canPlace) {
draggedTower.alpha = 1.0;
draggedTower.tint = 0xffffff;
draggedTower.hideRange(); // Hide range area when placed
draggedTower.isPlaced = true; // Set placement status
placedTowers.push(draggedTower); // Add to placed towers array
LK.getSound('TorreColocada').play(); // Play tower placement sound
} else {
// Tower is too close to existing towers, destroy it
draggedTower.destroy();
}
} else {
draggedTower.destroy();
}
isDragging = false;
draggedTower = null;
}
};