Code edit (1 edits merged)
Please save this source code
User prompt
por alguna razon, aunque cuesta 6 de elixir, en la partida su icono pone que cuesta 3, pon que cueste 6. ademas aumentale el alcance hasta 2000 de radio
User prompt
haz que cueste 6 de elixir. y que tenga 1500 de radio de ataque.
User prompt
la ballesta debe de tener su propio asset, creaselo. ademas sus balas deben de ir un 100% mas rapido de lo que van actualmente. y cuesta 6 de elixir.
User prompt
añadamos una nueva carta, será la ballesta, cuesta 6 de elixir y es una estructura, solo ataca a tropas terrestres, hace 10 de daño por disparo y dispara cada 0,3 segundos, tiene 900 hp y pierde un 4% por segundo, tarda 4 segundos su tiempo de aparicion, no se mueve, no puede ser desplazada por nada,y tiene un alcance de ataque de 850. es terrestre. su icono debe de estar un 100% debajo del de los esbirros, junto a un cardslot y su icono sobre él. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que se generen a 75 unidades de la bruja en vez de 50. ademas, los esqueletos generados por la bruja no tiene el segundo de aparicion, aparecen y directamente se pueden mover y atacar
User prompt
haz que la bruja sea capaz de generar esqueletos de la siguiente manera, cuando es colocada cuando pasen 3 segundos genera esqueletos, despues de eso tarda 10 segundos en generar esqueletos en forma de bucle de 10 segundos siempre y cuando esté viva. la formacion de los esqueletos es uno arriba, otro debajo, otro izquierda, y otro derecha, cada uno a 50 unidades de separacion, y en total son 4 esqueletos. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que la bruja tambien haga daño en area, que cuando se golpea a un enemigo se genere un circulo de color azul con un 75 unidades de radio sobre el enemigo golpeado y si hay alguna tropa en ese radio tambien recibe el daño ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
pero sigue persistiendo el bug de que si eliges la bruja tambien eliges la bola de fuego, creo que es porque el cardslot de la bola de fuego es tan grande que está debajo del de la bruja, de todas formas busca posibles razones y arreglalo
User prompt
la has puesto demasiado abajo, debe estar un 100% debajo del de la bola de fuego, y la has puesto como un 200% o por ahi. ademas has hecho que si seleccionas la bruja tambien selecciones la bola de fuego, eso no debe de ser asi
User prompt
crea un cardSlot un 100% debajo del cardslot de la bola de fuego para que cuando le das a "mazo" haya un cardslot debajo del de la bola de fuego con el icono de la bruja en él
Remix started
Copy Tower Defense Royale
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var AreaDamageBullet = Container.expand(function (startX, startY, target, damage, isFromPlayer, areaRadius) { var self = Container.call(this); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 6; self.isFromPlayer = isFromPlayer; self.areaRadius = areaRadius || 100; // Default area damage radius var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); bulletGraphics.tint = isFromPlayer ? 0xFF9800 : 0xFF5722; // Orange tint for area damage bullets bulletGraphics.scaleX = 1.3; bulletGraphics.scaleY = 1.3; self.update = function () { if (!self.target || self.target.isDead) { self.destroy(); return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 30) { // Apply direct damage to the target first self.target.takeDamage(self.damage); // Then area damage explosion self.explode(); self.destroy(); return; } var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; }; self.explode = function () { // Visual explosion effect (removed screen flash to prevent bugs) LK.getSound('explosion').play(); // Create visual circle around the hit target var explosionCircle = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 13, scaleY: 13 }); explosionCircle.x = self.target.x; explosionCircle.y = self.target.y; explosionCircle.tint = 0xFF9800; explosionCircle.alpha = 0.6; game.addChild(explosionCircle); // Animate the circle to fade out tween(explosionCircle, { alpha: 0, scaleX: 15, scaleY: 15 }, { duration: 500, onFinish: function onFinish() { explosionCircle.destroy(); } }); // Get all possible targets for area damage var allTargets = []; if (self.isFromPlayer) { allTargets = allTargets.concat(enemyUnits, enemyTowers); } else { allTargets = allTargets.concat(playerUnits, playerTowers); } // Apply damage to all units within area radius (100 units as requested) for (var i = 0; i < allTargets.length; i++) { var targetUnit = allTargets[i]; if (!targetUnit.isDead && targetUnit !== self.target) { // Exclude the initial target from area damage var distance = Math.sqrt(Math.pow(self.target.x - targetUnit.x, 2) + Math.pow(self.target.y - targetUnit.y, 2)); if (distance <= 100) { targetUnit.takeDamage(140); // Flash each affected unit tween(targetUnit, { tint: 0xFF9800 }, { duration: 300, onFinish: function onFinish() { tween(targetUnit, { tint: 0xFFFFFF }, { duration: 200 }); } }); } } } }; return self; }); var Ballesta = Container.expand(function (isPlayer) { var self = Container.call(this); self.isPlayer = isPlayer; self.unitType = 'ballesta'; self.maxHealth = 900; self.currentHealth = 900; self.isDead = false; self.damage = 10; self.range = 2500; self.attackSpeed = 18; // Every 0.3 seconds (60fps * 0.3 = 18 ticks) self.lastAttackTime = 0; self.target = null; self.isAttacking = false; // Spawn delay properties self.spawnDelay = 240; // 4 seconds (60 * 4 = 240 ticks) self.spawnTimer = self.spawnDelay; self.isSpawning = true; var ballestaGraphics = self.attachAsset('ballesta', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarBg = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.25, scaleY: 0.1, y: -50 }); healthBarBg.tint = 0x000000; self.addChild(healthBarBg); // Health bar var healthBar = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.25, scaleY: 0.1, y: -50 }); healthBar.tint = self.isPlayer ? 0x2196F3 : 0xF44336; self.addChild(healthBar); self.healthBar = healthBar; self.healthBarMaxScale = 0.25; // Spawn timer text self.spawnTimerText = new Text2('', { size: 30, fill: 0xFFFFFF }); self.spawnTimerText.anchor.set(0.5, 0.5); self.spawnTimerText.y = 30; self.addChild(self.spawnTimerText); self.takeDamage = function (damage) { self.currentHealth -= damage; if (self.currentHealth <= 0) { self.currentHealth = 0; self.isDead = true; } // Update health bar var healthPercent = self.currentHealth / self.maxHealth; self.healthBar.scaleX = self.healthBarMaxScale * healthPercent; // Flash red when taking damage LK.effects.flashObject(self, 0xFF0000, 300); }; self.findTarget = function () { // Reset target if current target is dead or invalid if (self.target && self.target.isDead) { self.target = null; self.isAttacking = false; } // If already attacking a target, don't change targets if (self.isAttacking && self.target && !self.target.isDead) { return; } var closestDistance = Infinity; var closestTarget = null; // Find closest enemy units first var targetUnits = self.isPlayer ? enemyUnits : playerUnits; for (var i = 0; i < targetUnits.length; i++) { if (!targetUnits[i].isDead) { // Ballesta cannot attack aerial units - only ground units if (targetUnits[i].isAerial) { continue; } var distance = Math.sqrt(Math.pow(self.x - targetUnits[i].x, 2) + Math.pow(self.y - targetUnits[i].y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestTarget = targetUnits[i]; } } } self.target = closestTarget; }; self.attack = function () { if (!self.target || self.target.isDead) { return; } var distance = Math.sqrt(Math.pow(self.x - self.target.x, 2) + Math.pow(self.y - self.target.y, 2)); if (distance <= self.range && LK.ticks - self.lastAttackTime > self.attackSpeed) { self.isAttacking = true; var bullet = new Bullet(self.x, self.y, self.target, self.damage, self.isPlayer); bullet.speed = 16; // 100% faster than default speed of 8 bullets.push(bullet); game.addChild(bullet); self.lastAttackTime = LK.ticks; LK.getSound('attack').play(); } }; self.update = function () { if (self.isDead) { return; } // Handle spawn delay if (self.isSpawning) { self.spawnTimer--; if (self.spawnTimer <= 0) { self.isSpawning = false; self.spawnTimerText.setText(''); } else { // Show remaining time in seconds var secondsLeft = Math.ceil(self.spawnTimer / 60); self.spawnTimerText.setText(secondsLeft.toString()); } return; // Don't attack while spawning } // Handle stun effect if (self.stunTimer > 0) { self.stunTimer--; if (self.stunTimer <= 0) { self.isStunned = false; } return; // Don't attack while stunned } // Lose 4% of total health per second (60 ticks = 1 second) if (LK.ticks % 15 === 0) { // Every 15 ticks = 4 times per second = 1% each time = 4% per second self.currentHealth -= self.maxHealth * 0.01; if (self.currentHealth <= 0) { self.currentHealth = 0; self.isDead = true; } // Update health bar without flash effect for natural decay var healthPercent = self.currentHealth / self.maxHealth; self.healthBar.scaleX = self.healthBarMaxScale * healthPercent; } self.findTarget(); self.attack(); }; return self; }); var Bruja = Container.expand(function (isPlayer) { var self = Container.call(this); self.isPlayer = isPlayer; self.unitType = 'bruja'; self.isAerial = false; // Ground unit self.maxHealth = 550; self.currentHealth = 550; self.damage = 130; self.attackSpeed = 90; // 90 ticks self.range = 500; // Detection range self.attackRange = 425; // Attack range self.speed = 1.25; self.target = null; self.lastAttackTime = 0; self.isDead = false; self.isAttacking = false; // Skeleton summoning properties self.skeletonSummonTimer = 180; // 3 seconds initial delay (60 ticks = 1 second) self.skeletonSummonInterval = 600; // 10 seconds interval (60 * 10 = 600 ticks) self.hasSummonedFirst = false; // Track if first summon has happened // Spawn delay properties self.spawnDelay = 60; // 1 second self.spawnTimer = self.spawnDelay; self.isSpawning = true; var brujaGraphics = self.attachAsset('bruja', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarScale = 0.2; var healthBarBg = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: healthBarScale, scaleY: 0.1, y: -50 }); healthBarBg.tint = 0x000000; self.addChild(healthBarBg); // Health bar var healthBar = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: healthBarScale, scaleY: 0.1, y: -50 }); healthBar.tint = self.isPlayer ? 0x2196F3 : 0xF44336; self.addChild(healthBar); self.healthBar = healthBar; self.healthBarMaxScale = healthBarScale; // Spawn timer text self.spawnTimerText = new Text2('', { size: 30, fill: 0xFFFFFF }); self.spawnTimerText.anchor.set(0.5, 0.5); self.spawnTimerText.y = 30; self.addChild(self.spawnTimerText); self.takeDamage = function (damage) { self.currentHealth -= damage; if (self.currentHealth <= 0) { self.currentHealth = 0; self.isDead = true; } // Update health bar var healthPercent = self.currentHealth / self.maxHealth; self.healthBar.scaleX = self.healthBarMaxScale * healthPercent; // Flash red when taking damage LK.effects.flashObject(self, 0xFF0000, 300); }; self.findTarget = function () { // Reset target if current target is dead or invalid if (self.target && self.target.isDead) { self.target = null; self.isAttacking = false; } // If already attacking a target, don't change targets if (self.isAttacking && self.target && !self.target.isDead) { return; } var closestDistance = Infinity; var closestTarget = null; // Find closest enemy units first var targetUnits = self.isPlayer ? enemyUnits : playerUnits; for (var i = 0; i < targetUnits.length; i++) { if (!targetUnits[i].isDead) { // Bruja can attack both air and ground units var distance = Math.sqrt(Math.pow(self.x - targetUnits[i].x, 2) + Math.pow(self.y - targetUnits[i].y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestTarget = targetUnits[i]; } } } // Find closest enemy towers if no units in range if (!closestTarget) { var targetTowers = self.isPlayer ? enemyTowers : playerTowers; for (var i = 0; i < targetTowers.length; i++) { if (!targetTowers[i].isDead) { var distance = Math.sqrt(Math.pow(self.x - targetTowers[i].x, 2) + Math.pow(self.y - targetTowers[i].y, 2)); if (distance < closestDistance) { closestDistance = distance; closestTarget = targetTowers[i]; } } } } self.target = closestTarget; }; self.moveToTarget = function () { if (!self.target || self.target.isDead) { return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.attackRange) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } }; self.attack = function () { if (!self.target || self.target.isDead) { return; } // Verify target is from opposing team before attacking var isValidTarget = false; if (self.isPlayer && enemyUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (self.isPlayer && enemyTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!isValidTarget) { self.target = null; return; } var distance = Math.sqrt(Math.pow(self.x - self.target.x, 2) + Math.pow(self.y - self.target.y, 2)); if (distance <= self.attackRange && LK.ticks - self.lastAttackTime > self.attackSpeed) { self.isAttacking = true; // Create purple area damage bullet with 75 units radius var brujaAreaBullet = new BrujaAreaBullet(self.x, self.y, self.target, self.damage, self.isPlayer, 75); bullets.push(brujaAreaBullet); game.addChild(brujaAreaBullet); self.lastAttackTime = LK.ticks; LK.getSound('attack').play(); } }; self.summonSkeletons = function () { // Generate 4 skeletons in formation: up, down, left, right at 75 units distance var skeletonPositions = [{ x: self.x, y: self.y - 75 }, // Up { x: self.x, y: self.y + 75 }, // Down { x: self.x - 75, y: self.y }, // Left { x: self.x + 75, y: self.y } // Right ]; for (var i = 0; i < skeletonPositions.length; i++) { var pos = skeletonPositions[i]; var skeleton = new Skeleton(self.isPlayer); skeleton.x = pos.x; skeleton.y = pos.y; // Remove spawn delay for Bruja-generated skeletons so they can move and attack immediately skeleton.spawnTimer = 0; skeleton.isSpawning = false; skeleton.spawnTimerText.setText(''); // Add skeleton to appropriate unit array if (self.isPlayer) { playerUnits.push(skeleton); } else { enemyUnits.push(skeleton); } game.addChild(skeleton); } // Play deploy sound for skeleton summoning LK.getSound('deploy').play(); }; self.update = function () { if (self.isDead) { return; } // Handle spawn delay if (self.isSpawning) { self.spawnTimer--; if (self.spawnTimer <= 0) { self.isSpawning = false; self.spawnTimerText.setText(''); } else { // Show remaining time in seconds var secondsLeft = Math.ceil(self.spawnTimer / 60); self.spawnTimerText.setText(secondsLeft.toString()); } return; // Don't move or attack while spawning } // Handle stun effect if (self.stunTimer > 0) { self.stunTimer--; if (self.stunTimer <= 0) { self.isStunned = false; } return; // Don't move or attack while stunned } // Handle skeleton summoning self.skeletonSummonTimer--; if (!self.hasSummonedFirst && self.skeletonSummonTimer <= 0) { // First summon after 3 seconds self.summonSkeletons(); self.hasSummonedFirst = true; self.skeletonSummonTimer = self.skeletonSummonInterval; // Set to 10 second interval } else if (self.hasSummonedFirst && self.skeletonSummonTimer <= 0) { // Subsequent summons every 10 seconds self.summonSkeletons(); self.skeletonSummonTimer = self.skeletonSummonInterval; // Reset to 10 second interval } self.findTarget(); self.moveToTarget(); self.attack(); }; return self; }); var BrujaAreaBullet = Container.expand(function (startX, startY, target, damage, isFromPlayer, areaRadius) { var self = Container.call(this); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 6; self.isFromPlayer = isFromPlayer; self.areaRadius = areaRadius || 75; // 75 units radius as requested var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); bulletGraphics.tint = 0x9C27B0; // Purple color for bruja bullets bulletGraphics.scaleX = 1.2; bulletGraphics.scaleY = 1.2; self.update = function () { if (!self.target || self.target.isDead) { self.destroy(); return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 30) { // Apply direct damage to the target first self.target.takeDamage(self.damage); // Then area damage explosion self.explode(); self.destroy(); return; } var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; }; self.explode = function () { // Play explosion sound LK.getSound('explosion').play(); // Create blue visual circle around the hit target with 75 units radius var explosionCircle = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 10, // Scale to make it 75 units radius (15 * 10 / 2 = 75) scaleY: 10 }); explosionCircle.x = self.target.x; explosionCircle.y = self.target.y; explosionCircle.tint = 0x0080FF; // Blue color as requested explosionCircle.alpha = 0.6; game.addChild(explosionCircle); // Animate the circle to fade out tween(explosionCircle, { alpha: 0, scaleX: 12, scaleY: 12 }, { duration: 500, onFinish: function onFinish() { explosionCircle.destroy(); } }); // Get all possible targets for area damage var allTargets = []; if (self.isFromPlayer) { allTargets = allTargets.concat(enemyUnits, enemyTowers); } else { allTargets = allTargets.concat(playerUnits, playerTowers); } // Apply damage to all units within area radius (75 units as requested) for (var i = 0; i < allTargets.length; i++) { var targetUnit = allTargets[i]; if (!targetUnit.isDead && targetUnit !== self.target) { // Exclude the initial target from area damage var distance = Math.sqrt(Math.pow(self.target.x - targetUnit.x, 2) + Math.pow(self.target.y - targetUnit.y, 2)); if (distance <= self.areaRadius) { targetUnit.takeDamage(self.damage); // Flash each affected unit with blue color tween(targetUnit, { tint: 0x0080FF }, { duration: 300, onFinish: function onFinish() { tween(targetUnit, { tint: 0xFFFFFF }, { duration: 200 }); } }); } } } }; return self; }); var Bullet = Container.expand(function (startX, startY, target, damage, isFromPlayer) { var self = Container.call(this); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 8; self.isFromPlayer = isFromPlayer; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); bulletGraphics.tint = isFromPlayer ? 0x4CAF50 : 0xF44336; self.update = function () { if (!self.target || self.target.isDead) { self.destroy(); return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 20) { self.target.takeDamage(self.damage); self.destroy(); return; } var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; }; return self; }); var Cannon = Container.expand(function (isPlayer) { var self = Container.call(this); self.isPlayer = isPlayer; self.unitType = 'cannon'; self.maxHealth = 600; self.currentHealth = 600; self.isDead = false; self.damage = 90; self.range = 500; self.attackSpeed = 50; self.lastAttackTime = 0; self.target = null; self.isAttacking = false; // Spawn delay properties self.spawnDelay = 60; // 1 second self.spawnTimer = self.spawnDelay; self.isSpawning = true; var cannonGraphics = self.attachAsset('cannon', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarBg = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.25, scaleY: 0.1, y: -50 }); healthBarBg.tint = 0x000000; self.addChild(healthBarBg); // Health bar var healthBar = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.25, scaleY: 0.1, y: -50 }); healthBar.tint = self.isPlayer ? 0x2196F3 : 0xF44336; self.addChild(healthBar); self.healthBar = healthBar; self.healthBarMaxScale = 0.25; // Spawn timer text self.spawnTimerText = new Text2('', { size: 30, fill: 0xFFFFFF }); self.spawnTimerText.anchor.set(0.5, 0.5); self.spawnTimerText.y = 30; self.addChild(self.spawnTimerText); self.takeDamage = function (damage) { self.currentHealth -= damage; if (self.currentHealth <= 0) { self.currentHealth = 0; self.isDead = true; } // Update health bar var healthPercent = self.currentHealth / self.maxHealth; self.healthBar.scaleX = self.healthBarMaxScale * healthPercent; // Flash red when taking damage LK.effects.flashObject(self, 0xFF0000, 300); }; self.findTarget = function () { // Reset target if current target is dead or invalid if (self.target && self.target.isDead) { self.target = null; self.isAttacking = false; } // If already attacking a target, don't change targets if (self.isAttacking && self.target && !self.target.isDead) { return; } var closestDistance = Infinity; var closestTarget = null; // Find closest enemy units first var targetUnits = self.isPlayer ? enemyUnits : playerUnits; for (var i = 0; i < targetUnits.length; i++) { if (!targetUnits[i].isDead) { // Cannon cannot attack aerial units if (targetUnits[i].isAerial) { continue; } var distance = Math.sqrt(Math.pow(self.x - targetUnits[i].x, 2) + Math.pow(self.y - targetUnits[i].y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestTarget = targetUnits[i]; } } } self.target = closestTarget; }; self.attack = function () { if (!self.target || self.target.isDead) { return; } var distance = Math.sqrt(Math.pow(self.x - self.target.x, 2) + Math.pow(self.y - self.target.y, 2)); if (distance <= self.range && LK.ticks - self.lastAttackTime > self.attackSpeed) { self.isAttacking = true; var bullet = new Bullet(self.x, self.y, self.target, self.damage, self.isPlayer); bullets.push(bullet); game.addChild(bullet); self.lastAttackTime = LK.ticks; LK.getSound('attack').play(); } }; self.update = function () { if (self.isDead) { return; } // Handle spawn delay if (self.isSpawning) { self.spawnTimer--; if (self.spawnTimer <= 0) { self.isSpawning = false; self.spawnTimerText.setText(''); } else { // Show remaining time in seconds var secondsLeft = Math.ceil(self.spawnTimer / 60); self.spawnTimerText.setText(secondsLeft.toString()); } return; // Don't attack while spawning } // Handle stun effect if (self.stunTimer > 0) { self.stunTimer--; if (self.stunTimer <= 0) { self.isStunned = false; } return; // Don't attack while stunned } // Lose 3% of total health per second (60 ticks = 1 second) if (LK.ticks % 20 === 0) { // Every 20 ticks = 3 times per second = 1% each time = 3% per second self.currentHealth -= self.maxHealth * 0.01; if (self.currentHealth <= 0) { self.currentHealth = 0; self.isDead = true; } // Update health bar without flash effect for natural decay var healthPercent = self.currentHealth / self.maxHealth; self.healthBar.scaleX = self.healthBarMaxScale * healthPercent; } self.findTarget(); self.attack(); }; return self; }); var Card = Container.expand(function (cardType) { var self = Container.call(this); self.cardType = cardType; self.cost = cardType === 'giant' ? 5 : cardType === 'wizard' ? 5 : cardType === 'bruja' ? 5 : cardType === 'skeleton' ? 1 : cardType === 'skeletonArmy' ? 3 : cardType === 'fireball' ? 4 : cardType === 'discharge' ? 2 : cardType === 'minion' ? 3 : cardType === 'maquinaVoladora' ? 4 : cardType === 'megaesbirro' ? 3 : 3; var cardBg = self.attachAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5 }); var cardIcon = LK.getAsset(cardType === 'discharge' ? 'descarga_descardina' : cardType === 'maquinaVoladora' ? 'flying_machine' : cardType === 'bruja' ? 'bruja' : cardType, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); self.addChild(cardIcon); var costText = new Text2(self.cost.toString(), { size: 30, fill: 0xFFFFFF }); costText.anchor.set(0.5, 0.5); costText.x = 120; costText.y = -40; self.addChild(costText); return self; }); var Discharge = Container.expand(function (targetX, targetY, isFromPlayer) { var self = Container.call(this); self.targetX = targetX; self.targetY = targetY; self.isFromPlayer = isFromPlayer; self.damage = 200; self.stunDuration = 60; // 1 second stun (60 ticks) // Discharge appears instantly at target location self.x = targetX; self.y = targetY; // Define explode method BEFORE calling it self.explode = function () { // Visual explosion effect - blue circle of 150 units radius var explosionCircle = LK.getAsset('dischargeExplosion', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, // Scale to make it 150 units radius (250 * 1.2 / 2 = 150) scaleY: 1.2 }); explosionCircle.x = self.targetX; explosionCircle.y = self.targetY; explosionCircle.tint = 0x0080FF; // Blue color explosionCircle.alpha = 0.9; game.addChild(explosionCircle); // Animate explosion tween(explosionCircle, { alpha: 0, scaleX: 1.8, scaleY: 1.8 }, { duration: 800, onFinish: function onFinish() { explosionCircle.destroy(); } }); // Play explosion sound LK.getSound('explosion').play(); // Get all possible targets for damage var allTargets = []; if (self.isFromPlayer) { allTargets = allTargets.concat(enemyUnits, enemyTowers); } else { allTargets = allTargets.concat(playerUnits, playerTowers); } // Apply damage and stun to all units within explosion radius of 150 units var explosionRadius = 150; // 150 units as requested for (var i = 0; i < allTargets.length; i++) { var targetUnit = allTargets[i]; if (!targetUnit.isDead) { var distance = Math.sqrt(Math.pow(self.targetX - targetUnit.x, 2) + Math.pow(self.targetY - targetUnit.y, 2)); if (distance <= explosionRadius) { // Apply 200 damage targetUnit.takeDamage(self.damage); // Apply stun effect (immobilize for 1 second) targetUnit.stunTimer = self.stunDuration; targetUnit.isStunned = true; // Visual effect for stunned units (blue tint) tween(targetUnit, { tint: 0x0080FF }, { duration: 1000, onFinish: function onFinish() { tween(targetUnit, { tint: 0xFFFFFF }, { duration: 200 }); } }); } } } // Destroy discharge immediately after explosion self.destroy(); }; // Create instant explosion effect - NOW we can call explode since it's defined self.explode(); return self; }); var Fireball = Container.expand(function (targetX, targetY, isFromPlayer) { var self = Container.call(this); self.targetX = targetX; self.targetY = targetY; self.isFromPlayer = isFromPlayer; self.speed = 13; self.damage = 415; self.explosionRadius = 150; // Start from king tower position if (isFromPlayer) { self.x = 1024; // Player king tower x self.y = 2600; // Player king tower y } else { self.x = 1024; // Enemy king tower x self.y = 200; // Enemy king tower y } var fireballGraphics = self.attachAsset('fireball', { anchorX: 0.5, anchorY: 0.5 }); fireballGraphics.tint = 0xFF6600; self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 30) { // Explode at target location self.explode(); self.destroy(); return; } var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; }; self.explode = function () { // Visual explosion effect var explosionCircle = LK.getAsset('fireballExplosion', { anchorX: 0.5, anchorY: 0.5 }); explosionCircle.x = self.targetX; explosionCircle.y = self.targetY; explosionCircle.tint = 0xFF3300; explosionCircle.alpha = 0.8; game.addChild(explosionCircle); // Animate explosion tween(explosionCircle, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 1000, onFinish: function onFinish() { explosionCircle.destroy(); } }); // Play explosion sound LK.getSound('explosion').play(); // Get all possible targets for damage var allTargets = []; if (self.isFromPlayer) { allTargets = allTargets.concat(enemyUnits, enemyTowers); } else { allTargets = allTargets.concat(playerUnits, playerTowers); } // Apply damage and knockback to all units within explosion radius for (var i = 0; i < allTargets.length; i++) { var targetUnit = allTargets[i]; if (!targetUnit.isDead) { var distance = Math.sqrt(Math.pow(self.targetX - targetUnit.x, 2) + Math.pow(self.targetY - targetUnit.y, 2)); if (distance <= self.explosionRadius) { // Check if target is a tower var isTower = false; if (self.isFromPlayer) { isTower = enemyTowers.indexOf(targetUnit) !== -1; } else { isTower = playerTowers.indexOf(targetUnit) !== -1; } // Apply damage - 50% less to towers var damageToApply = isTower ? self.damage * 0.5 : self.damage; targetUnit.takeDamage(damageToApply); // Apply knockback only to non-tower units if (!isTower) { var knockbackDistance = 100; var knockbackDx = targetUnit.x - self.targetX; var knockbackDy = targetUnit.y - self.targetY; var knockbackLength = Math.sqrt(knockbackDx * knockbackDx + knockbackDy * knockbackDy); if (knockbackLength > 0) { var normalizedDx = knockbackDx / knockbackLength; var normalizedDy = knockbackDy / knockbackLength; var newX = targetUnit.x + normalizedDx * knockbackDistance; var newY = targetUnit.y + normalizedDy * knockbackDistance; // Animate knockback tween(targetUnit, { x: newX, y: newY }, { duration: 300, easing: tween.easeOut }); } } // Flash effect on hit units tween(targetUnit, { tint: 0xFF6600 }, { duration: 300, onFinish: function onFinish() { tween(targetUnit, { tint: 0xFFFFFF }, { duration: 200 }); } }); } } } }; return self; }); var MaquinaVoladora = Container.expand(function (isPlayer) { var self = Container.call(this); self.isPlayer = isPlayer; self.unitType = 'maquinaVoladora'; self.isAerial = true; // Mark as aerial unit self.maxHealth = 500; self.currentHealth = 500; self.damage = 85; self.attackSpeed = 90; // 90 ticks self.range = 500; // Detection range self.attackRange = 475; // Attack range self.speed = 1.5; self.target = null; self.lastAttackTime = 0; self.isDead = false; self.isAttacking = false; // Spawn delay properties self.spawnDelay = 60; // 1 second self.spawnTimer = self.spawnDelay; self.isSpawning = true; var maquinaGraphics = self.attachAsset('flying_machine', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarScale = 0.2; var healthBarBg = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: healthBarScale, scaleY: 0.1, y: -50 }); healthBarBg.tint = 0x000000; self.addChild(healthBarBg); // Health bar var healthBar = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: healthBarScale, scaleY: 0.1, y: -50 }); healthBar.tint = self.isPlayer ? 0x2196F3 : 0xF44336; self.addChild(healthBar); self.healthBar = healthBar; self.healthBarMaxScale = healthBarScale; // Spawn timer text self.spawnTimerText = new Text2('', { size: 30, fill: 0xFFFFFF }); self.spawnTimerText.anchor.set(0.5, 0.5); self.spawnTimerText.y = 30; self.addChild(self.spawnTimerText); self.takeDamage = function (damage) { self.currentHealth -= damage; if (self.currentHealth <= 0) { self.currentHealth = 0; self.isDead = true; } // Update health bar var healthPercent = self.currentHealth / self.maxHealth; self.healthBar.scaleX = self.healthBarMaxScale * healthPercent; // Flash red when taking damage LK.effects.flashObject(self, 0xFF0000, 300); }; self.findTarget = function () { // Reset target if current target is dead or invalid if (self.target && self.target.isDead) { self.target = null; self.isAttacking = false; } // If already attacking a target, don't change targets if (self.isAttacking && self.target && !self.target.isDead) { return; } var closestDistance = Infinity; var closestTarget = null; // Find closest enemy units first var targetUnits = self.isPlayer ? enemyUnits : playerUnits; for (var i = 0; i < targetUnits.length; i++) { if (!targetUnits[i].isDead) { var distance = Math.sqrt(Math.pow(self.x - targetUnits[i].x, 2) + Math.pow(self.y - targetUnits[i].y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestTarget = targetUnits[i]; } } } // Find closest enemy towers if no units in range if (!closestTarget) { var targetTowers = self.isPlayer ? enemyTowers : playerTowers; for (var i = 0; i < targetTowers.length; i++) { if (!targetTowers[i].isDead) { var distance = Math.sqrt(Math.pow(self.x - targetTowers[i].x, 2) + Math.pow(self.y - targetTowers[i].y, 2)); if (distance < closestDistance) { closestDistance = distance; closestTarget = targetTowers[i]; } } } } self.target = closestTarget; }; self.moveToTarget = function () { if (!self.target || self.target.isDead) { return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.attackRange) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } }; self.attack = function () { if (!self.target || self.target.isDead) { return; } // Verify target is from opposing team before attacking var isValidTarget = false; if (self.isPlayer && enemyUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (self.isPlayer && enemyTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!isValidTarget) { self.target = null; return; } var distance = Math.sqrt(Math.pow(self.x - self.target.x, 2) + Math.pow(self.y - self.target.y, 2)); if (distance <= self.attackRange && LK.ticks - self.lastAttackTime > self.attackSpeed) { self.isAttacking = true; // Create purple bullet var bullet = new Bullet(self.x, self.y, self.target, self.damage, self.isPlayer); bullet.children[0].tint = 0x9C27B0; // Purple color bullets.push(bullet); game.addChild(bullet); self.lastAttackTime = LK.ticks; LK.getSound('attack').play(); } }; self.update = function () { if (self.isDead) { return; } // Handle spawn delay if (self.isSpawning) { self.spawnTimer--; if (self.spawnTimer <= 0) { self.isSpawning = false; self.spawnTimerText.setText(''); } else { // Show remaining time in seconds var secondsLeft = Math.ceil(self.spawnTimer / 60); self.spawnTimerText.setText(secondsLeft.toString()); } return; // Don't move or attack while spawning } // Handle stun effect if (self.stunTimer > 0) { self.stunTimer--; if (self.stunTimer <= 0) { self.isStunned = false; } return; // Don't move or attack while stunned } self.findTarget(); self.moveToTarget(); self.attack(); }; return self; }); var Megaesbirro = Container.expand(function (isPlayer) { var self = Container.call(this); self.isPlayer = isPlayer; self.unitType = 'megaesbirro'; self.isAerial = true; // Mark as aerial unit self.maxHealth = 650; // 500 + 150 self.currentHealth = 650; self.damage = 135; // 85 + 50 self.attackSpeed = 90; // 90 ticks self.range = 500; // Detection range self.attackRange = 150; // Attack range self.speed = 1.75; self.target = null; self.lastAttackTime = 0; self.isDead = false; self.isAttacking = false; // Spawn delay properties self.spawnDelay = 60; // 1 second self.spawnTimer = self.spawnDelay; self.isSpawning = true; var megaesbirroGraphics = self.attachAsset('megaesbirro', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarScale = 0.2; var healthBarBg = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: healthBarScale, scaleY: 0.1, y: -50 }); healthBarBg.tint = 0x000000; self.addChild(healthBarBg); // Health bar var healthBar = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: healthBarScale, scaleY: 0.1, y: -50 }); healthBar.tint = self.isPlayer ? 0x2196F3 : 0xF44336; self.addChild(healthBar); self.healthBar = healthBar; self.healthBarMaxScale = healthBarScale; // Spawn timer text self.spawnTimerText = new Text2('', { size: 30, fill: 0xFFFFFF }); self.spawnTimerText.anchor.set(0.5, 0.5); self.spawnTimerText.y = 30; self.addChild(self.spawnTimerText); self.takeDamage = function (damage) { self.currentHealth -= damage; if (self.currentHealth <= 0) { self.currentHealth = 0; self.isDead = true; } // Update health bar var healthPercent = self.currentHealth / self.maxHealth; self.healthBar.scaleX = self.healthBarMaxScale * healthPercent; // Flash red when taking damage LK.effects.flashObject(self, 0xFF0000, 300); }; self.findTarget = function () { // Reset target if current target is dead or invalid if (self.target && self.target.isDead) { self.target = null; self.isAttacking = false; } // If already attacking a target, don't change targets if (self.isAttacking && self.target && !self.target.isDead) { return; } var closestDistance = Infinity; var closestTarget = null; // Find closest enemy units first var targetUnits = self.isPlayer ? enemyUnits : playerUnits; for (var i = 0; i < targetUnits.length; i++) { if (!targetUnits[i].isDead) { var distance = Math.sqrt(Math.pow(self.x - targetUnits[i].x, 2) + Math.pow(self.y - targetUnits[i].y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestTarget = targetUnits[i]; } } } // Find closest enemy towers if no units in range if (!closestTarget) { var targetTowers = self.isPlayer ? enemyTowers : playerTowers; for (var i = 0; i < targetTowers.length; i++) { if (!targetTowers[i].isDead) { var distance = Math.sqrt(Math.pow(self.x - targetTowers[i].x, 2) + Math.pow(self.y - targetTowers[i].y, 2)); if (distance < closestDistance) { closestDistance = distance; closestTarget = targetTowers[i]; } } } } self.target = closestTarget; }; self.moveToTarget = function () { if (!self.target || self.target.isDead) { return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.attackRange) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } }; self.attack = function () { if (!self.target || self.target.isDead) { return; } // Verify target is from opposing team before attacking var isValidTarget = false; if (self.isPlayer && enemyUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (self.isPlayer && enemyTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!isValidTarget) { self.target = null; return; } var distance = Math.sqrt(Math.pow(self.x - self.target.x, 2) + Math.pow(self.y - self.target.y, 2)); if (distance <= self.attackRange && LK.ticks - self.lastAttackTime > self.attackSpeed) { self.isAttacking = true; // Create purple bullet var bullet = new Bullet(self.x, self.y, self.target, self.damage, self.isPlayer); bullet.children[0].tint = 0x9C27B0; // Purple color bullets.push(bullet); game.addChild(bullet); self.lastAttackTime = LK.ticks; LK.getSound('attack').play(); } }; self.update = function () { if (self.isDead) { return; } // Handle spawn delay if (self.isSpawning) { self.spawnTimer--; if (self.spawnTimer <= 0) { self.isSpawning = false; self.spawnTimerText.setText(''); } else { // Show remaining time in seconds var secondsLeft = Math.ceil(self.spawnTimer / 60); self.spawnTimerText.setText(secondsLeft.toString()); } return; // Don't move or attack while spawning } // Handle stun effect if (self.stunTimer > 0) { self.stunTimer--; if (self.stunTimer <= 0) { self.isStunned = false; } return; // Don't move or attack while stunned } self.findTarget(); self.moveToTarget(); self.attack(); }; return self; }); var Minion = Container.expand(function (isPlayer) { var self = Container.call(this); self.isPlayer = isPlayer; self.unitType = 'minion'; self.isAerial = true; // Mark as aerial unit self.maxHealth = 400; self.currentHealth = 400; self.damage = 85; self.attackSpeed = 90; // 90 ticks self.range = 500; // Detection range self.attackRange = 150; // Attack range self.speed = 1.75; self.target = null; self.lastAttackTime = 0; self.isDead = false; self.isAttacking = false; // Spawn delay properties self.spawnDelay = 60; // 1 second self.spawnTimer = self.spawnDelay; self.isSpawning = true; var minionGraphics = self.attachAsset('minion', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarScale = 0.2; var healthBarBg = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: healthBarScale, scaleY: 0.1, y: -50 }); healthBarBg.tint = 0x000000; self.addChild(healthBarBg); // Health bar var healthBar = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: healthBarScale, scaleY: 0.1, y: -50 }); healthBar.tint = self.isPlayer ? 0x2196F3 : 0xF44336; self.addChild(healthBar); self.healthBar = healthBar; self.healthBarMaxScale = healthBarScale; // Spawn timer text self.spawnTimerText = new Text2('', { size: 30, fill: 0xFFFFFF }); self.spawnTimerText.anchor.set(0.5, 0.5); self.spawnTimerText.y = 30; self.addChild(self.spawnTimerText); self.takeDamage = function (damage) { self.currentHealth -= damage; if (self.currentHealth <= 0) { self.currentHealth = 0; self.isDead = true; } // Update health bar var healthPercent = self.currentHealth / self.maxHealth; self.healthBar.scaleX = self.healthBarMaxScale * healthPercent; // Flash red when taking damage LK.effects.flashObject(self, 0xFF0000, 300); }; self.findTarget = function () { // Reset target if current target is dead or invalid if (self.target && self.target.isDead) { self.target = null; self.isAttacking = false; } // If already attacking a target, don't change targets if (self.isAttacking && self.target && !self.target.isDead) { return; } var closestDistance = Infinity; var closestTarget = null; // Find closest enemy units first var targetUnits = self.isPlayer ? enemyUnits : playerUnits; for (var i = 0; i < targetUnits.length; i++) { if (!targetUnits[i].isDead) { var distance = Math.sqrt(Math.pow(self.x - targetUnits[i].x, 2) + Math.pow(self.y - targetUnits[i].y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestTarget = targetUnits[i]; } } } // Find closest enemy towers if no units in range if (!closestTarget) { var targetTowers = self.isPlayer ? enemyTowers : playerTowers; for (var i = 0; i < targetTowers.length; i++) { if (!targetTowers[i].isDead) { var distance = Math.sqrt(Math.pow(self.x - targetTowers[i].x, 2) + Math.pow(self.y - targetTowers[i].y, 2)); if (distance < closestDistance) { closestDistance = distance; closestTarget = targetTowers[i]; } } } } self.target = closestTarget; }; self.moveToTarget = function () { if (!self.target || self.target.isDead) { return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.attackRange) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } }; self.attack = function () { if (!self.target || self.target.isDead) { return; } // Verify target is from opposing team before attacking var isValidTarget = false; if (self.isPlayer && enemyUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (self.isPlayer && enemyTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!isValidTarget) { self.target = null; return; } var distance = Math.sqrt(Math.pow(self.x - self.target.x, 2) + Math.pow(self.y - self.target.y, 2)); if (distance <= self.attackRange && LK.ticks - self.lastAttackTime > self.attackSpeed) { self.isAttacking = true; // Create purple bullet var bullet = new Bullet(self.x, self.y, self.target, self.damage, self.isPlayer); bullet.children[0].tint = 0x9C27B0; // Purple color bullets.push(bullet); game.addChild(bullet); self.lastAttackTime = LK.ticks; LK.getSound('attack').play(); } }; self.update = function () { if (self.isDead) { return; } // Handle spawn delay if (self.isSpawning) { self.spawnTimer--; if (self.spawnTimer <= 0) { self.isSpawning = false; self.spawnTimerText.setText(''); } else { // Show remaining time in seconds var secondsLeft = Math.ceil(self.spawnTimer / 60); self.spawnTimerText.setText(secondsLeft.toString()); } return; // Don't move or attack while spawning } // Handle stun effect if (self.stunTimer > 0) { self.stunTimer--; if (self.stunTimer <= 0) { self.isStunned = false; } return; // Don't move or attack while stunned } self.findTarget(); self.moveToTarget(); self.attack(); }; return self; }); var Tower = Container.expand(function (isPlayer, isKing) { var self = Container.call(this); self.isPlayer = isPlayer; self.isKing = isKing; self.maxHealth = isKing ? 3700 : 2400; self.currentHealth = self.maxHealth; self.isDead = false; self.range = isKing ? 800 : 900; self.damage = 75; self.attackSpeed = 67; // 50% slower than 45 ticks (45 * 1.5 = 67.5, rounded to 67) self.lastAttackTime = 0; var towerGraphics = self.attachAsset(isKing ? 'kingTower' : 'tower', { anchorX: 0.5, anchorY: 1 }); towerGraphics.tint = self.isPlayer ? 0x4CAF50 : 0xF44336; // Health display var healthText = new Text2(self.currentHealth.toString(), { size: 40, fill: 0xFFFFFF }); healthText.anchor.set(0.5, 0.5); healthText.y = -75; self.addChild(healthText); self.healthText = healthText; self.takeDamage = function (damage) { self.currentHealth -= damage; if (self.currentHealth <= 0) { self.currentHealth = 0; self.isDead = true; towersDestroyed++; LK.effects.flashObject(self, 0xFF0000, 1000); LK.getSound('towerDestroy').play(); if (self.isPlayer) { enemyTowersDestroyed++; } else { playerTowersDestroyed++; } } self.healthText.setText(self.currentHealth.toString()); LK.effects.flashObject(self, 0xFF0000, 300); }; self.attack = function () { if (self.isDead) { return; } // King towers cannot attack until at least one Princess tower is destroyed if (self.isKing) { var teamTowers = self.isPlayer ? playerTowers : enemyTowers; var princessTowersAlive = 0; for (var i = 0; i < teamTowers.length; i++) { if (!teamTowers[i].isKing && !teamTowers[i].isDead) { princessTowersAlive++; } } if (princessTowersAlive === 2) { return; // King tower cannot attack while both Princess towers are alive } } var targetUnits = self.isPlayer ? enemyUnits : playerUnits; var closestTarget = null; var closestDistance = Infinity; for (var i = 0; i < targetUnits.length; i++) { if (!targetUnits[i].isDead) { var distance = Math.sqrt(Math.pow(self.x - targetUnits[i].x, 2) + Math.pow(self.y - targetUnits[i].y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestTarget = targetUnits[i]; } } } if (closestTarget && LK.ticks - self.lastAttackTime > self.attackSpeed) { var bullet = new Bullet(self.x, self.y - 50, closestTarget, self.damage, self.isPlayer); bullets.push(bullet); game.addChild(bullet); self.lastAttackTime = LK.ticks; LK.getSound('attack').play(); } }; self.update = function () { self.attack(); }; return self; }); var Unit = Container.expand(function (isPlayer, unitType, health, damage, attackSpeed, range, speed) { var self = Container.call(this); self.isPlayer = isPlayer; self.unitType = unitType; self.maxHealth = health; self.currentHealth = health; self.damage = damage; self.attackSpeed = attackSpeed; self.range = range; self.speed = speed; self.target = null; self.lastAttackTime = 0; self.isDead = false; self.isAttacking = false; // Track if unit has started attacking current target // Spawn delay properties self.spawnDelay = unitType === 'giant' ? 180 : 60; // 3 seconds for giant, 1 second for others (60 ticks = 1 second) self.spawnTimer = self.spawnDelay; self.isSpawning = true; var unitGraphics = self.attachAsset(unitType, { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var healthBarScale = unitType === 'giant' ? 0.4 : 0.2; var healthBarBg = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: healthBarScale, scaleY: 0.1, y: -50 }); healthBarBg.tint = 0x000000; self.addChild(healthBarBg); // Health bar var healthBar = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: healthBarScale, scaleY: 0.1, y: -50 }); healthBar.tint = self.isPlayer ? 0x2196F3 : 0xF44336; self.addChild(healthBar); self.healthBar = healthBar; self.healthBarMaxScale = healthBarScale; // Spawn timer text self.spawnTimerText = new Text2('', { size: 30, fill: 0xFFFFFF }); self.spawnTimerText.anchor.set(0.5, 0.5); self.spawnTimerText.y = 30; self.addChild(self.spawnTimerText); self.takeDamage = function (damage) { self.currentHealth -= damage; if (self.currentHealth <= 0) { self.currentHealth = 0; self.isDead = true; } // Update health bar var healthPercent = self.currentHealth / self.maxHealth; self.healthBar.scaleX = self.healthBarMaxScale * healthPercent; // Flash red when taking damage LK.effects.flashObject(self, 0xFF0000, 300); }; self.findTarget = function () { // Reset target if current target is dead or invalid if (self.target && self.target.isDead) { self.target = null; self.isAttacking = false; // Reset attacking state when target dies } // If already attacking a target, don't change targets if (self.isAttacking && self.target && !self.target.isDead) { return; // Keep current target while attacking } var closestDistance = Infinity; var closestTarget = null; // Giant (wincondition unit) targets structures within detection range, then towers if (self.unitType === 'giant') { // First check for structures (cannons) within detection range (538) var targetStructures = self.isPlayer ? enemyUnits : playerUnits; for (var i = 0; i < targetStructures.length; i++) { if (!targetStructures[i].isDead && targetStructures[i].unitType === 'cannon') { var distance = Math.sqrt(Math.pow(self.x - targetStructures[i].x, 2) + Math.pow(self.y - targetStructures[i].y, 2)); if (distance <= 538 && distance < closestDistance) { closestDistance = distance; closestTarget = targetStructures[i]; } } } // If no structures in detection range, target closest tower if (!closestTarget) { var targetTowers = self.isPlayer ? enemyTowers : playerTowers; for (var i = 0; i < targetTowers.length; i++) { if (!targetTowers[i].isDead) { var distance = Math.sqrt(Math.pow(self.x - targetTowers[i].x, 2) + Math.pow(self.y - targetTowers[i].y, 2)); if (distance < closestDistance) { closestDistance = distance; closestTarget = targetTowers[i]; } } } } } else { // Normal units: First priority - Find closest enemy units and structures (they are more immediate threats) var targetUnits = self.isPlayer ? enemyUnits : playerUnits; for (var i = 0; i < targetUnits.length; i++) { if (!targetUnits[i].isDead) { // Skip aerial units if this unit cannot attack air (archers, wizards, and towers can attack air) if (targetUnits[i].isAerial && self.unitType !== 'archer' && self.unitType !== 'wizard') { continue; } var distance = Math.sqrt(Math.pow(self.x - targetUnits[i].x, 2) + Math.pow(self.y - targetUnits[i].y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestTarget = targetUnits[i]; } } } // Second priority: Find closest enemy towers if no units in range if (!closestTarget) { var targetTowers = self.isPlayer ? enemyTowers : playerTowers; for (var i = 0; i < targetTowers.length; i++) { if (!targetTowers[i].isDead) { var distance = Math.sqrt(Math.pow(self.x - targetTowers[i].x, 2) + Math.pow(self.y - targetTowers[i].y, 2)); if (distance < closestDistance) { closestDistance = distance; closestTarget = targetTowers[i]; } } } } } self.target = closestTarget; }; self.moveToTarget = function () { if (!self.target || self.target.isDead) { return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Use proper attack range for movement positioning var attackRange = self.attackRange || (self.unitType === 'knight' ? 168 : self.unitType === 'giant' ? 60 : self.range); if (distance > attackRange) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } }; self.attack = function () { if (!self.target || self.target.isDead) { return; } // Verify target is from opposing team before attacking var isValidTarget = false; if (self.isPlayer && enemyUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (self.isPlayer && enemyTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!isValidTarget) { self.target = null; return; } var distance = Math.sqrt(Math.pow(self.x - self.target.x, 2) + Math.pow(self.y - self.target.y, 2)); // Use different ranges for detection vs attack var attackRange = self.attackRange || (self.unitType === 'knight' ? 168 : self.unitType === 'giant' ? 60 : self.range); // Use attackRange property if defined, otherwise use unit-specific ranges if (distance <= attackRange && LK.ticks - self.lastAttackTime > self.attackSpeed) { // Set attacking flag when first attack begins self.isAttacking = true; // Knight, Giant, and Skeleton use melee attack - direct damage without bullet if (self.unitType === 'knight' || self.unitType === 'giant' || self.unitType === 'skeleton') { self.target.takeDamage(self.damage); // Visual effect for melee strike LK.effects.flashObject(self, 0xFFFFFF, 200); // Flash target red when hit by Giant if (self.unitType === 'giant') { tween(self.target, { tint: 0xFF0000 }, { duration: 200, onFinish: function onFinish() { tween(self.target, { tint: 0xFFFFFF }, { duration: 100 }); } }); } // Knight and Skeleton no longer apply knockback to units } else { // Other units (like archers, wizard) still use bullets var bullet = new Bullet(self.x, self.y, self.target, self.damage, self.isPlayer); bullets.push(bullet); game.addChild(bullet); } self.lastAttackTime = LK.ticks; LK.getSound('attack').play(); } }; self.update = function () { if (self.isDead) { return; } // Handle spawn delay if (self.isSpawning) { self.spawnTimer--; if (self.spawnTimer <= 0) { self.isSpawning = false; self.spawnTimerText.setText(''); } else { // Show remaining time in seconds var secondsLeft = Math.ceil(self.spawnTimer / 60); self.spawnTimerText.setText(secondsLeft.toString()); } return; // Don't move or attack while spawning } // Handle stun effect if (self.stunTimer > 0) { self.stunTimer--; if (self.stunTimer <= 0) { self.isStunned = false; } return; // Don't move or attack while stunned } // Always find targets every frame to ensure units never ignore enemies self.findTarget(); self.moveToTarget(); self.attack(); }; return self; }); var Wizard = Unit.expand(function (isPlayer) { var self = Unit.call(this, isPlayer, 'wizard', 550, 140, 100, 530, 1.25); // Override the attack function to use area damage self.attack = function () { if (!self.target || self.target.isDead) { return; } // Verify target is from opposing team before attacking var isValidTarget = false; if (self.isPlayer && enemyUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (self.isPlayer && enemyTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerUnits.indexOf(self.target) !== -1) { isValidTarget = true; } if (!self.isPlayer && playerTowers.indexOf(self.target) !== -1) { isValidTarget = true; } if (!isValidTarget) { self.target = null; return; } var distance = Math.sqrt(Math.pow(self.x - self.target.x, 2) + Math.pow(self.y - self.target.y, 2)); // Wizard uses ranged attack with area damage projectile (use detection range for attacking) if (distance <= self.range && LK.ticks - self.lastAttackTime > self.attackSpeed) { // Set attacking flag when first attack begins self.isAttacking = true; // Create area damage bullet with 100 unit area radius var areaBullet = new AreaDamageBullet(self.x, self.y, self.target, self.damage, self.isPlayer, 100); bullets.push(areaBullet); game.addChild(areaBullet); self.lastAttackTime = LK.ticks; LK.getSound('attack').play(); } }; return self; }); var Skeleton = Unit.expand(function (isPlayer) { var self = Unit.call(this, isPlayer, 'skeleton', 25, 45, 60, 500, 2); // Skeleton: 25 HP, 45 damage, 60 tick attack speed, 500 detection range, speed 2 // Attacks ground units and structures only (same targeting as other units) // Override attack range to be 150 instead of detection range self.attackRange = 150; return self; }); var Knight = Unit.expand(function (isPlayer) { var self = Unit.call(this, isPlayer, 'knight', 825, 160, 60, 538, 1.75); // Detection range set to 538 (same as archer for consistent detection) // Attack range remains at melee distance in attack function return self; }); var Giant = Unit.expand(function (isPlayer) { var self = Unit.call(this, isPlayer, 'giant', 4000, 175, 90, 538, 0.85); // Giant is a wincondition unit that only attacks structures and towers // Detection range set to 538 (same as archer), attack range remains at 60 in attack function return self; }); var Archer = Unit.expand(function (isPlayer) { var self = Unit.call(this, isPlayer, 'archer', 400, 65, 90, 525, 1.5); // Range reduced and stats adjusted return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x228B22 }); /**** * Game Code ****/ // Game state variables /* CARD AND TOWER STATS: TOWERS: - Princess Towers: 2400 HP, 75 damage, attacks every 1.1s (67 ticks), range 800 units - King Tower: 3700 HP, 75 damage, attacks every 1.1s (67 ticks), range 800 units * King tower cannot attack until at least one Princess tower is destroyed CARDS: - Archers: 3 elixir cost, 425 HP each, 75 damage, attacks every 1.5s (90 ticks), range 567 units, speed 1.5 * Deploys 2 archers, can attack air and ground targets - Knight: 3 elixir cost, 825 HP, 160 damage, attacks every 1s (60 ticks), range 168 units, speed 2 * Single melee unit, ground targets only - Giant: 5 elixir cost, 4000 HP, 175 damage, attacks every 1.5s (90 ticks), range 300 units, speed 1 * Wincondition unit that only attacks structures and towers, ground unit UNIT SPEEDS: - Knight: speed 2 (medium) - Archer: speed 1.5 (medium-slow) - Giant: speed 1 (slow) ELIXIR SYSTEM: - Maximum: 10 elixir - Regeneration: 1 elixir every 2.0 seconds (120 ticks at 60fps) - Starting amount: 5 elixir GAME RULES: - Match duration: 2 minutes (120 seconds) - Victory: Most towers destroyed wins - Tiebreaker: Remaining tower health - Draw: Equal towers destroyed and equal remaining health */ var gameStarted = false; var gameTime = 250; // 250 seconds total duration var elixir = 5; // Starting elixir var maxElixir = 10; var elixirRegenRate = 114; // Ticks for 1.9 seconds at 60fps (1.9 * 60 = 114) var doubleElixirRegenRate = 57; // Half the time for double speed (1.9 / 2 = 0.95 seconds = 57 ticks) var lastElixirRegen = 0; var aiElixir = 5; // AI starting elixir var aiMaxElixir = 10; var aiElixirRegenRate = 120; // Ticks for 2 seconds at 60fps var doubleAiElixirRegenRate = 60; // Half the time for double speed (2 / 2 = 1 second = 60 ticks) var lastAiElixirRegen = 0; var doubleElixirActive = false; var doubleElixirMessageShown = false; var doubleElixirMessageText = null; var doubleElixirMessageTimer = 0; var playerUnits = []; var enemyUnits = []; var bullets = []; var playerTowers = []; var enemyTowers = []; var playerTowersDestroyed = 0; var enemyTowersDestroyed = 0; var towersDestroyed = 0; var gameEnded = false; var aiLastDeploy = 0; var aiDeployInterval = 240; // 4 seconds // Card and UI variables var draggedCard = null; var menuElements = []; var deckMenuElements = []; var deckCards = []; var archerCard, knightCard, giantCard, wizardCard, cannonCard; var timerText, elixirText; var battlefieldLine; var showingDeckSelection = false; var selectedDeck = ['archer', 'knight', 'giant', 'wizard', 'cannon']; // Default deck (minimum 5 cards) var availableCards = ['archer', 'knight', 'giant', 'wizard', 'cannon', 'skeleton', 'skeletonArmy', 'fireball', 'minion', 'maquinaVoladora', 'megaesbirro', 'discharge', 'bruja', 'ballesta']; // Card system variables var activeCards = []; // Currently displayed 4 cards var cardQueue = []; // Queue of remaining cards var maxActiveCards = 4; // Create main menu function createMenu() { // Title var titleText = new Text2('CLASH ROYALE', { size: 120, fill: 0xFFD700 }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 800; game.addChild(titleText); menuElements.push(titleText); // Play button background var playButton = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.8, x: 1024, y: 1400 }); playButton.tint = 0x4CAF50; game.addChild(playButton); menuElements.push(playButton); // Play button text var playText = new Text2('JUGAR', { size: 80, fill: 0xFFFFFF }); playText.anchor.set(0.5, 0.5); playText.x = 1024; playText.y = 1400; game.addChild(playText); menuElements.push(playText); // Mazo button background var mazoButton = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.8, x: 1024, y: 1600 }); mazoButton.tint = 0x9C27B0; game.addChild(mazoButton); menuElements.push(mazoButton); // Mazo button text var mazoText = new Text2('MAZO', { size: 80, fill: 0xFFFFFF }); mazoText.anchor.set(0.5, 0.5); mazoText.x = 1024; mazoText.y = 1600; game.addChild(mazoText); menuElements.push(mazoText); // Instructions var instructionsText = new Text2('Arrastra las cartas al campo de batalla', { size: 40, fill: 0xFFFFFF }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 1024; instructionsText.y = 1800; game.addChild(instructionsText); menuElements.push(instructionsText); // Make play button clickable playButton.down = function (x, y, obj) { startGame(); }; playText.down = function (x, y, obj) { startGame(); }; // Make mazo button clickable mazoButton.down = function (x, y, obj) { showDeckSelection(); }; mazoText.down = function (x, y, obj) { showDeckSelection(); }; } function showDeckSelection() { // Hide main menu for (var i = 0; i < menuElements.length; i++) { menuElements[i].alpha = 0.3; } showingDeckSelection = true; createDeckMenu(); } function createDeckMenu() { // Title var deckTitle = new Text2('SELECCIONA TU MAZO', { size: 80, fill: 0xFFD700 }); deckTitle.anchor.set(0.5, 0.5); deckTitle.x = 1024; deckTitle.y = 600; game.addChild(deckTitle); deckMenuElements.push(deckTitle); // Instructions var instructions = new Text2('Toca las cartas para añadir/quitar del mazo (min 5, max 8)', { size: 35, fill: 0xFFFFFF }); instructions.anchor.set(0.5, 0.5); instructions.x = 1024; instructions.y = 700; game.addChild(instructions); deckMenuElements.push(instructions); // Available cards display var cardStartX = 300; var cardSpacing = 350; for (var i = 0; i < availableCards.length; i++) { var cardType = availableCards[i]; var cardContainer = new Container(); cardContainer.x = cardStartX + i * cardSpacing; // Position skeleton card 100px below archer, skeleton army 100px below knight, fireball 100px below giant, minion 100px below wizard, maquina voladora 100px below skeleton, discharge 100px below cannon if (cardType === 'skeleton') { var archerIndex = availableCards.indexOf('archer'); cardContainer.x = cardStartX + archerIndex * cardSpacing; cardContainer.y = 1000; // 100px below archer } else if (cardType === 'skeletonArmy') { var knightIndex = availableCards.indexOf('knight'); cardContainer.x = cardStartX + knightIndex * cardSpacing; cardContainer.y = 1000; // 100px below knight } else if (cardType === 'fireball') { var giantIndex = availableCards.indexOf('giant'); cardContainer.x = cardStartX + giantIndex * cardSpacing; cardContainer.y = 1000; // 100px below giant } else if (cardType === 'minion') { var wizardIndex = availableCards.indexOf('wizard'); cardContainer.x = cardStartX + wizardIndex * cardSpacing; cardContainer.y = 1000; // 100px below wizard } else if (cardType === 'maquinaVoladora') { var skeletonIndex = availableCards.indexOf('skeleton'); var archerIndex = availableCards.indexOf('archer'); cardContainer.x = cardStartX + archerIndex * cardSpacing; cardContainer.y = 1100; // 100px below skeleton (200px total below archer) } else if (cardType === 'megaesbirro') { var skeletonArmyIndex = availableCards.indexOf('skeletonArmy'); var knightIndex = availableCards.indexOf('knight'); cardContainer.x = cardStartX + knightIndex * cardSpacing; cardContainer.y = 1100; // 100px below skeleton army (200px total below knight) } else if (cardType === 'discharge') { var cannonIndex = availableCards.indexOf('cannon'); cardContainer.x = cardStartX + cannonIndex * cardSpacing; cardContainer.y = 1000; // 100px below cannon } else if (cardType === 'bruja') { var fireballIndex = availableCards.indexOf('fireball'); cardContainer.x = cardStartX + fireballIndex * cardSpacing; cardContainer.y = 1100; // 100px below fireball } else if (cardType === 'ballesta') { var minionIndex = availableCards.indexOf('minion'); var wizardIndex = availableCards.indexOf('wizard'); cardContainer.x = cardStartX + wizardIndex * cardSpacing; cardContainer.y = 1100; // 100px below minion (200px total below wizard) } else { cardContainer.y = 900; } // Card background var cardBg = cardContainer.attachAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5 }); // Check if card is in selected deck var isSelected = selectedDeck.indexOf(cardType) !== -1; cardBg.tint = isSelected ? 0x4CAF50 : 0x607d8b; // Create special cardslot container for bruja below fireball if (cardType === 'fireball') { // Create bruja cardslot container var brujaCardSlot = new Container(); brujaCardSlot.x = 0; brujaCardSlot.y = 100; // 100px below fireball // Bruja cardslot background var brujaCardBg = brujaCardSlot.attachAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5 }); brujaCardBg.tint = selectedDeck.indexOf('bruja') !== -1 ? 0x4CAF50 : 0x607d8b; // Bruja icon var brujaIcon = LK.getAsset('bruja', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); brujaCardSlot.addChild(brujaIcon); // Bruja cost var brujaCost = new Text2('5', { size: 25, fill: 0xFFFFFF }); brujaCost.anchor.set(0.5, 0.5); brujaCost.x = 120; brujaCost.y = -40; brujaCardSlot.addChild(brujaCost); // Selection indicator for bruja if (selectedDeck.indexOf('bruja') !== -1) { var brujaCheckmark = new Text2('✓', { size: 40, fill: 0xFFFFFF }); brujaCheckmark.anchor.set(0.5, 0.5); brujaCheckmark.x = 0; brujaCheckmark.y = 80; brujaCardSlot.addChild(brujaCheckmark); } // Make bruja cardslot clickable brujaCardSlot.cardType = 'bruja'; brujaCardSlot.down = function (x, y, obj) { toggleCardInDeck('bruja'); // Return true to stop event propagation to parent containers return true; }; cardContainer.addChild(brujaCardSlot); deckMenuElements.push(brujaCardSlot); } // Card icon var cardIcon = LK.getAsset(cardType === 'discharge' ? 'descarga_descardina' : cardType === 'maquinaVoladora' ? 'flying_machine' : cardType === 'bruja' ? 'bruja' : cardType, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); cardContainer.addChild(cardIcon); // Card cost var cost = cardType === 'giant' ? 5 : cardType === 'wizard' ? 5 : cardType === 'bruja' ? 5 : cardType === 'skeleton' ? 1 : cardType === 'skeletonArmy' ? 3 : cardType === 'fireball' ? 4 : cardType === 'discharge' ? 2 : cardType === 'minion' ? 3 : cardType === 'maquinaVoladora' ? 4 : cardType === 'megaesbirro' ? 3 : cardType === 'ballesta' ? 6 : 3; var costText = new Text2(cost.toString(), { size: 25, fill: 0xFFFFFF }); costText.anchor.set(0.5, 0.5); costText.x = 120; costText.y = -40; cardContainer.addChild(costText); // Selection indicator if (isSelected) { var checkmark = new Text2('✓', { size: 40, fill: 0xFFFFFF }); checkmark.anchor.set(0.5, 0.5); checkmark.x = 0; checkmark.y = 80; cardContainer.addChild(checkmark); } // Make card clickable cardContainer.cardType = cardType; cardContainer.down = function (x, y, obj) { // For fireball, check if click is in the bruja area to prevent double selection if (this.cardType === 'fireball' && y > 50) { // Click is in the lower area where bruja cardslot is, ignore it return true; } toggleCardInDeck(this.cardType); }; game.addChild(cardContainer); deckMenuElements.push(cardContainer); } // Selected deck display var deckTitle2 = new Text2('MAZO ACTUAL (' + selectedDeck.length + '/8)', { size: 60, fill: 0x4CAF50 }); deckTitle2.anchor.set(0.5, 0.5); deckTitle2.x = 1024; deckTitle2.y = 1200; game.addChild(deckTitle2); deckMenuElements.push(deckTitle2); // Display selected cards var selectedStartX = 424; var selectedSpacing = 240; for (var i = 0; i < selectedDeck.length && i < 8; i++) { var cardType = selectedDeck[i]; var selectedCardContainer = new Container(); selectedCardContainer.x = selectedStartX + i * selectedSpacing; selectedCardContainer.y = 1400; // Small card background var selectedCardBg = selectedCardContainer.attachAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7 }); selectedCardBg.tint = 0x2196F3; // Small card icon var selectedCardIcon = LK.getAsset(cardType === 'discharge' ? 'descarga_descardina' : cardType === 'maquinaVoladora' ? 'flying_machine' : cardType === 'bruja' ? 'bruja' : cardType, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4 }); selectedCardContainer.addChild(selectedCardIcon); game.addChild(selectedCardContainer); deckMenuElements.push(selectedCardContainer); } // Back button var backButton = LK.getAsset('cardSlot', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 0.6, x: 1024, y: 1650 }); backButton.tint = 0xFF9800; game.addChild(backButton); deckMenuElements.push(backButton); var backText = new Text2('VOLVER', { size: 50, fill: 0xFFFFFF }); backText.anchor.set(0.5, 0.5); backText.x = 1024; backText.y = 1650; game.addChild(backText); deckMenuElements.push(backText); // Make back button clickable backButton.down = function (x, y, obj) { hideDeckSelection(); }; backText.down = function (x, y, obj) { hideDeckSelection(); }; } function toggleCardInDeck(cardType) { var cardIndex = selectedDeck.indexOf(cardType); if (cardIndex !== -1) { // Only allow removal if deck has more than 5 cards if (selectedDeck.length > 5) { selectedDeck.splice(cardIndex, 1); } } else if (selectedDeck.length < 8) { // Add card to deck if not full (max 8 cards) selectedDeck.push(cardType); } // Refresh deck menu refreshDeckMenu(); } function refreshDeckMenu() { // Remove current deck menu elements for (var i = 0; i < deckMenuElements.length; i++) { deckMenuElements[i].destroy(); } deckMenuElements = []; // Recreate deck menu createDeckMenu(); } function hideDeckSelection() { // Remove deck menu elements for (var i = 0; i < deckMenuElements.length; i++) { deckMenuElements[i].destroy(); } deckMenuElements = []; // Show main menu again for (var i = 0; i < menuElements.length; i++) { menuElements[i].alpha = 1; } showingDeckSelection = false; } function startGame() { // Remove menu elements for (var i = 0; i < menuElements.length; i++) { menuElements[i].destroy(); } menuElements = []; // Start the actual game gameStarted = true; initializeGame(); } function initializeGame() { // Generate AI's randomized deck for this match generateAIDeck(); // Create battlefield divider battlefieldLine = game.addChild(LK.getAsset('battlefield', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 })); battlefieldLine.tint = 0x000000; battlefieldLine.alpha = 0.3; // Create towers // Player towers (bottom) var playerLeftTower = game.addChild(new Tower(true, false)); playerLeftTower.x = 512; playerLeftTower.y = 2400; playerTowers.push(playerLeftTower); var playerRightTower = game.addChild(new Tower(true, false)); playerRightTower.x = 1536; playerRightTower.y = 2400; playerTowers.push(playerRightTower); var playerKingTower = game.addChild(new Tower(true, true)); playerKingTower.x = 1024; playerKingTower.y = 2600; playerTowers.push(playerKingTower); // Enemy towers (top) var enemyLeftTower = game.addChild(new Tower(false, false)); enemyLeftTower.x = 512; enemyLeftTower.y = 400; enemyTowers.push(enemyLeftTower); var enemyRightTower = game.addChild(new Tower(false, false)); enemyRightTower.x = 1536; enemyRightTower.y = 400; enemyTowers.push(enemyRightTower); var enemyKingTower = game.addChild(new Tower(false, true)); enemyKingTower.x = 1024; enemyKingTower.y = 200; enemyTowers.push(enemyKingTower); // UI Elements timerText = new Text2(gameTime.toString(), { size: 60, fill: 0xFFFFFF }); timerText.anchor.set(0.5, 0); LK.gui.top.addChild(timerText); elixirText = new Text2(elixir + "/" + maxElixir, { size: 40, fill: 0xE91E63 }); elixirText.anchor.set(0, 1); elixirText.x = 50; LK.gui.bottomLeft.addChild(elixirText); // Initialize card system activeCards = []; cardQueue = []; deckCards = []; // Setup active cards (first 4) and queue (remaining) for (var i = 0; i < selectedDeck.length; i++) { if (i < maxActiveCards) { activeCards.push(selectedDeck[i]); } else { cardQueue.push(selectedDeck[i]); } } // Create visual cards for active cards only var cardSpacing = 300; var startX = -(maxActiveCards - 1) * cardSpacing / 2; for (var i = 0; i < activeCards.length; i++) { var cardType = activeCards[i]; var newCard = LK.gui.bottom.addChild(new Card(cardType)); newCard.x = startX + i * cardSpacing; newCard.y = -100; newCard.cardType = cardType; // Store card type for event handling newCard.cardIndex = i; // Store index for replacement deckCards.push(newCard); } // Store references for backward compatibility if (activeCards.indexOf('archer') !== -1) { archerCard = deckCards[activeCards.indexOf('archer')]; } if (activeCards.indexOf('knight') !== -1) { knightCard = deckCards[activeCards.indexOf('knight')]; } if (activeCards.indexOf('giant') !== -1) { giantCard = deckCards[activeCards.indexOf('giant')]; } if (activeCards.indexOf('wizard') !== -1) { wizardCard = deckCards[activeCards.indexOf('wizard')]; } if (activeCards.indexOf('cannon') !== -1) { cannonCard = deckCards[activeCards.indexOf('cannon')]; } // Setup event handlers setupEventHandlers(); } function rotateCard(usedCardIndex) { if (cardQueue.length === 0) { return; } // No cards in queue to rotate // Get the next card from queue var nextCard = cardQueue.shift(); // Remove first card from queue var usedCard = activeCards[usedCardIndex]; // Get the used card // Add used card to end of queue cardQueue.push(usedCard); // Replace active card with next card activeCards[usedCardIndex] = nextCard; // Update visual card var cardToUpdate = deckCards[usedCardIndex]; if (cardToUpdate) { // Store position and index var cardX = cardToUpdate.x; var cardY = cardToUpdate.y; var cardIndex = cardToUpdate.cardIndex; // Remove old card cardToUpdate.destroy(); // Create new card with next card type var newCard = LK.gui.bottom.addChild(new Card(nextCard)); newCard.x = cardX; newCard.y = cardY; newCard.cardType = nextCard; newCard.cardIndex = cardIndex; // Update references deckCards[usedCardIndex] = newCard; // Update event handler for new card var cardType = newCard.cardType; var cost = cardType === 'giant' ? 5 : cardType === 'wizard' ? 5 : cardType === 'bruja' ? 5 : cardType === 'skeleton' ? 1 : cardType === 'skeletonArmy' ? 3 : cardType === 'fireball' ? 4 : cardType === 'discharge' ? 2 : cardType === 'minion' ? 3 : cardType === 'maquinaVoladora' ? 4 : cardType === 'megaesbirro' ? 3 : 3; newCard.down = function (x, y, obj) { if (elixir >= cost) { draggedCard = cardType; } }; } } function deployUnit(cardType, x, y, isPlayer) { var cost = cardType === 'giant' ? 5 : cardType === 'wizard' ? 5 : cardType === 'bruja' ? 5 : cardType === 'skeleton' ? 1 : cardType === 'skeletonArmy' ? 3 : cardType === 'fireball' ? 4 : cardType === 'discharge' ? 2 : cardType === 'minion' ? 3 : cardType === 'maquinaVoladora' ? 4 : cardType === 'megaesbirro' ? 3 : cardType === 'ballesta' ? 6 : 3; if (isPlayer && elixir < cost) { return false; } if (!isPlayer && aiElixir < cost) { return false; } // AI also needs elixir // Check if position is in correct half (except for spells) if (cardType !== 'fireball') { if (isPlayer && y < 1366) { return false; } // Player can only deploy in bottom half if (!isPlayer && y > 1366) { return false; } // AI can only deploy in top half } var unit = null; if (cardType === 'archer') { // Deploy 2 archers var archer1 = new Archer(isPlayer); var archer2 = new Archer(isPlayer); archer1.x = x - 40; // Left archer archer1.y = y; archer2.x = x + 40; // Right archer archer2.y = y; if (isPlayer) { playerUnits.push(archer1); playerUnits.push(archer2); elixir -= cost; elixirText.setText(elixir + "/" + maxElixir); // Rotate card if player used it var cardIndex = activeCards.indexOf(cardType); if (cardIndex !== -1) { rotateCard(cardIndex); } } else { enemyUnits.push(archer1); enemyUnits.push(archer2); aiElixir -= cost; // Deduct AI elixir } game.addChild(archer1); game.addChild(archer2); LK.getSound('deploy').play(); return true; } else if (cardType === 'knight') { unit = new Knight(isPlayer); } else if (cardType === 'giant') { unit = new Giant(isPlayer); } else if (cardType === 'wizard') { unit = new Wizard(isPlayer); } else if (cardType === 'skeleton') { // Deploy 3 skeletons in formation: front, back-right, back-left var skeleton1 = new Skeleton(isPlayer); // Front skeleton var skeleton2 = new Skeleton(isPlayer); // Back-right skeleton var skeleton3 = new Skeleton(isPlayer); // Back-left skeleton skeleton1.x = x; // Front skeleton at deployment position skeleton1.y = y; skeleton2.x = x + 60; // Back-right skeleton skeleton2.y = isPlayer ? y + 60 : y - 60; // Adjust based on player side skeleton3.x = x - 60; // Back-left skeleton skeleton3.y = isPlayer ? y + 60 : y - 60; // Adjust based on player side if (isPlayer) { playerUnits.push(skeleton1); playerUnits.push(skeleton2); playerUnits.push(skeleton3); elixir -= cost; elixirText.setText(elixir + "/" + maxElixir); // Rotate card if player used it var cardIndex = activeCards.indexOf(cardType); if (cardIndex !== -1) { rotateCard(cardIndex); } } else { enemyUnits.push(skeleton1); enemyUnits.push(skeleton2); enemyUnits.push(skeleton3); aiElixir -= cost; // Deduct AI elixir } game.addChild(skeleton1); game.addChild(skeleton2); game.addChild(skeleton3); LK.getSound('deploy').play(); return true; } else if (cardType === 'skeletonArmy') { // Deploy 12 skeletons in a 3x4 formation var skeletons = []; for (var row = 0; row < 3; row++) { for (var col = 0; col < 4; col++) { var skeleton = new Skeleton(isPlayer); skeleton.x = x + (col - 1.5) * 40; // Spread horizontally skeleton.y = y + (row - 1) * 40; // Spread vertically skeletons.push(skeleton); game.addChild(skeleton); } } if (isPlayer) { for (var s = 0; s < skeletons.length; s++) { playerUnits.push(skeletons[s]); } elixir -= cost; elixirText.setText(elixir + "/" + maxElixir); // Rotate card if player used it var cardIndex = activeCards.indexOf(cardType); if (cardIndex !== -1) { rotateCard(cardIndex); } } else { for (var s = 0; s < skeletons.length; s++) { enemyUnits.push(skeletons[s]); } aiElixir -= cost; // Deduct AI elixir } LK.getSound('deploy').play(); return true; } else if (cardType === 'fireball') { // Fireball is a spell - can be deployed anywhere on the map var fireball = new Fireball(x, y, isPlayer); bullets.push(fireball); // Add to bullets array for cleanup game.addChild(fireball); if (isPlayer) { elixir -= cost; elixirText.setText(elixir + "/" + maxElixir); // Rotate card if player used it var cardIndex = activeCards.indexOf(cardType); if (cardIndex !== -1) { rotateCard(cardIndex); } } else { aiElixir -= cost; // Deduct AI elixir } LK.getSound('deploy').play(); return true; } else if (cardType === 'discharge') { // Discharge is a spell - can be deployed anywhere on the map, appears instantly var discharge = new Discharge(x, y, isPlayer); // Don't add to bullets array since it destroys itself immediately game.addChild(discharge); if (isPlayer) { elixir -= cost; elixirText.setText(elixir + "/" + maxElixir); // Rotate card if player used it var cardIndex = activeCards.indexOf(cardType); if (cardIndex !== -1) { rotateCard(cardIndex); } } else { aiElixir -= cost; // Deduct AI elixir } LK.getSound('deploy').play(); return true; } else if (cardType === 'minion') { // Deploy 3 minions in formation like skeletons: front, back-right, back-left var minion1 = new Minion(isPlayer); // Front minion var minion2 = new Minion(isPlayer); // Back-right minion var minion3 = new Minion(isPlayer); // Back-left minion minion1.x = x; // Front minion at deployment position minion1.y = y; minion2.x = x + 60; // Back-right minion minion2.y = isPlayer ? y + 60 : y - 60; // Adjust based on player side minion3.x = x - 60; // Back-left minion minion3.y = isPlayer ? y + 60 : y - 60; // Adjust based on player side if (isPlayer) { playerUnits.push(minion1); playerUnits.push(minion2); playerUnits.push(minion3); elixir -= cost; elixirText.setText(elixir + "/" + maxElixir); // Rotate card if player used it var cardIndex = activeCards.indexOf(cardType); if (cardIndex !== -1) { rotateCard(cardIndex); } } else { enemyUnits.push(minion1); enemyUnits.push(minion2); enemyUnits.push(minion3); aiElixir -= cost; // Deduct AI elixir } game.addChild(minion1); game.addChild(minion2); game.addChild(minion3); LK.getSound('deploy').play(); return true; } else if (cardType === 'maquinaVoladora') { // Deploy single maquina voladora unit = new MaquinaVoladora(isPlayer); } else if (cardType === 'megaesbirro') { // Deploy single megaesbirro unit = new Megaesbirro(isPlayer); } else if (cardType === 'bruja') { // Deploy single bruja unit = new Bruja(isPlayer); } else if (cardType === 'ballesta') { unit = new Ballesta(isPlayer); } else if (cardType === 'cannon') { unit = new Cannon(isPlayer); } if (unit) { unit.x = x; unit.y = y; if (isPlayer) { playerUnits.push(unit); elixir -= cost; elixirText.setText(elixir + "/" + maxElixir); // Rotate card if player used it var cardIndex = activeCards.indexOf(cardType); if (cardIndex !== -1) { rotateCard(cardIndex); } } else { enemyUnits.push(unit); aiElixir -= cost; // Deduct AI elixir } game.addChild(unit); LK.getSound('deploy').play(); return true; } return false; } // AI deployment variables var aiLastUsedCard = null; var aiCardCooldown = {}; var aiDeck = []; // AI's randomized deck for the match // Generate AI deck with required constraints function generateAIDeck() { aiDeck = []; // Required cards (must have) var requiredCards = { aerial: 'archer', // Card that can attack aerial units wincondition: 'giant', // Wincondition card spell: Math.random() < 0.5 ? 'fireball' : 'discharge', // Spell card (randomly choose between fireball and discharge) structure: Math.random() < 0.5 ? 'cannon' : 'ballesta' // Structure card (randomly choose between cannon and ballesta) }; // Add required cards to AI deck aiDeck.push(requiredCards.aerial); aiDeck.push(requiredCards.wincondition); aiDeck.push(requiredCards.spell); aiDeck.push(requiredCards.structure); // Remaining card pool (excluding already added required cards) var remainingCards = ['knight', 'wizard', 'bruja', 'skeleton', 'skeletonArmy', 'minion', 'maquinaVoladora', 'megaesbirro', 'ballesta']; // Fill remaining 4 slots with random cards from remaining pool while (aiDeck.length < 8) { var randomIndex = Math.floor(Math.random() * remainingCards.length); var randomCard = remainingCards[randomIndex]; aiDeck.push(randomCard); // Don't remove from remainingCards to allow duplicates } // Shuffle the deck to randomize order for (var i = aiDeck.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = aiDeck[i]; aiDeck[i] = aiDeck[j]; aiDeck[j] = temp; } } // AI deployment function function aiDeploy() { // Check if AI is being attacked (player units in AI territory) var isUnderAttack = false; var playerUnitsInAITerritory = 0; for (var i = 0; i < playerUnits.length; i++) { if (playerUnits[i].y < 1366) { // Player units in AI half isUnderAttack = true; playerUnitsInAITerritory++; } } // Check if AI towers are taking damage var aiTowersUnderAttack = false; for (var i = 0; i < enemyTowers.length; i++) { if (enemyTowers[i].currentHealth < enemyTowers[i].maxHealth) { aiTowersUnderAttack = true; break; } } if (isUnderAttack || aiTowersUnderAttack) { // DEFENSE MODE: Deploy defensive units quickly if being attacked if (LK.ticks - aiLastDeploy > 60 && aiElixir >= 3) { // Deploy faster when defending var defensiveCards = []; // Check if player has used wincondition (giant) or if towers are damaged var playerHasWincondition = false; for (var i = 0; i < playerUnits.length; i++) { if (playerUnits[i].unitType === 'giant') { playerHasWincondition = true; break; } } if (aiElixir >= 3) { // Use cards from AI's deck for defense for (var d = 0; d < aiDeck.length; d++) { var deckCard = aiDeck[d]; var cardCost = deckCard === 'giant' ? 5 : deckCard === 'wizard' ? 5 : deckCard === 'bruja' ? 5 : deckCard === 'skeleton' ? 1 : deckCard === 'skeletonArmy' ? 3 : deckCard === 'fireball' ? 4 : deckCard === 'discharge' ? 2 : deckCard === 'minion' ? 3 : deckCard === 'maquinaVoladora' ? 4 : deckCard === 'megaesbirro' ? 3 : deckCard === 'ballesta' ? 6 : 3; if (aiElixir >= cardCost) { // Add defensive units (not spells for defense) if (deckCard !== 'fireball' && deckCard !== 'discharge') { defensiveCards.push(deckCard); } } } } // Check for spell usage opportunities (discharge and fireball) var spellCards = []; for (var s = 0; s < aiDeck.length; s++) { var spellCard = aiDeck[s]; if (spellCard === 'discharge' || spellCard === 'fireball') { var spellCost = spellCard === 'discharge' ? 2 : 4; if (aiElixir >= spellCost) { spellCards.push(spellCard); } } } if (spellCards.length > 0 && playerUnits.length > 0) { // Find player unit with most enemies nearby for spell targeting var bestTarget = null; var maxNearbyUnits = 0; for (var p = 0; p < playerUnits.length; p++) { var playerUnit = playerUnits[p]; if (!playerUnit.isDead) { var nearbyCount = 0; // Count nearby player units for (var n = 0; n < playerUnits.length; n++) { var otherUnit = playerUnits[n]; if (!otherUnit.isDead && otherUnit !== playerUnit) { var distance = Math.sqrt(Math.pow(playerUnit.x - otherUnit.x, 2) + Math.pow(playerUnit.y - otherUnit.y, 2)); if (distance <= 200) { // Within spell effect range nearbyCount++; } } } if (nearbyCount > maxNearbyUnits) { maxNearbyUnits = nearbyCount; bestTarget = playerUnit; } } } // Use spell if we found a good target (at least 1 nearby unit or any unit if no clusters) if (bestTarget && (maxNearbyUnits > 0 || playerUnits.length > 0)) { var chosenSpell = spellCards[Math.floor(Math.random() * spellCards.length)]; if (deployUnit(chosenSpell, bestTarget.x, bestTarget.y, false)) { aiLastDeploy = LK.ticks; aiDeployInterval = 120 + Math.random() * 60; return; // Exit early after using spell } } } if (defensiveCards.length > 0) { var randomCard = defensiveCards[Math.floor(Math.random() * defensiveCards.length)]; // Deploy near threatened area var deployX = 300 + Math.random() * 1448; var deployY = 800 + Math.random() * 400; // Deploy in middle-upper area if (deployUnit(randomCard, deployX, deployY, false)) { aiLastDeploy = LK.ticks; aiDeployInterval = 120 + Math.random() * 60; // Quick deploy interval when defending } } } } else { // ATTACK MODE: Use wincondition strategy when not under attack // Check for spell usage opportunities in attack mode if (aiElixir >= 2 && playerUnits.length > 0 && Math.random() < 0.3) { // 30% chance to use spells in attack var attackSpellCards = []; for (var s = 0; s < aiDeck.length; s++) { var spellCard = aiDeck[s]; if (spellCard === 'discharge' || spellCard === 'fireball') { var spellCost = spellCard === 'discharge' ? 2 : 4; if (aiElixir >= spellCost) { attackSpellCards.push(spellCard); } } } if (attackSpellCards.length > 0) { // Target strongest or most clustered player units var bestAttackTarget = null; var maxValue = 0; for (var p = 0; p < playerUnits.length; p++) { var playerUnit = playerUnits[p]; if (!playerUnit.isDead) { // Calculate value based on unit health + nearby units var unitValue = playerUnit.currentHealth; // Count nearby player units for cluster bonus for (var n = 0; n < playerUnits.length; n++) { var otherUnit = playerUnits[n]; if (!otherUnit.isDead && otherUnit !== playerUnit) { var distance = Math.sqrt(Math.pow(playerUnit.x - otherUnit.x, 2) + Math.pow(playerUnit.y - otherUnit.y, 2)); if (distance <= 200) { unitValue += otherUnit.currentHealth * 0.5; // Bonus for clustered units } } } if (unitValue > maxValue) { maxValue = unitValue; bestAttackTarget = playerUnit; } } } if (bestAttackTarget) { var chosenAttackSpell = attackSpellCards[Math.floor(Math.random() * attackSpellCards.length)]; if (deployUnit(chosenAttackSpell, bestAttackTarget.x, bestAttackTarget.y, false)) { aiLastDeploy = LK.ticks; aiDeployInterval = 180 + Math.random() * 120; return; // Exit after using spell } } } } // Check if AI already has giants on the field var giantsOnField = 0; for (var i = 0; i < enemyUnits.length; i++) { if (enemyUnits[i].unitType === 'giant' && !enemyUnits[i].isDead) { giantsOnField++; } } if (aiElixir >= 8 && LK.ticks - aiLastDeploy > aiDeployInterval && giantsOnField < 2) { // Big push with wincondition + support (max 2 giants on field) if (aiElixir >= 8 && Math.random() < 0.9) { // 90% chance for big attack when having 8+ elixir // Find wincondition card in AI deck var winconditionCard = null; for (var w = 0; w < aiDeck.length; w++) { if (aiDeck[w] === 'giant') { winconditionCard = 'giant'; break; } } if (winconditionCard) { // Deploy wincondition first var deployX = 300 + Math.random() * 1448; var deployY = 200 + Math.random() * 600; if (deployUnit(winconditionCard, deployX, deployY, false)) { var winconditionCost = winconditionCard === 'giant' ? 5 : 3; aiElixir -= winconditionCost; // Wait a bit then deploy support unit behind wincondition LK.setTimeout(function () { if (aiElixir >= 3) { var supportCards = []; // Get support cards from AI deck for (var s = 0; s < aiDeck.length; s++) { var supportCard = aiDeck[s]; var supportCost = supportCard === 'giant' ? 5 : supportCard === 'wizard' ? 5 : supportCard === 'skeleton' ? 1 : supportCard === 'skeletonArmy' ? 3 : supportCard === 'fireball' ? 4 : supportCard === 'discharge' ? 2 : supportCard === 'minion' ? 3 : 3; if (aiElixir >= supportCost && supportCard !== 'fireball' && supportCard !== 'discharge' && supportCard !== winconditionCard) { supportCards.push(supportCard); } } if (supportCards.length > 0) { var chosenSupport = supportCards[Math.floor(Math.random() * supportCards.length)]; var supportX = deployX + (Math.random() - 0.5) * 200; // Near wincondition var supportY = deployY + 100 + Math.random() * 200; // Behind wincondition deployUnit(chosenSupport, supportX, supportY, false); } } }, 1000); // 1 second delay aiLastDeploy = LK.ticks; aiDeployInterval = 300 + Math.random() * 180; // Longer interval after big push } } } } else if (aiElixir >= 5 && aiElixir < 8 && LK.ticks - aiLastDeploy > aiDeployInterval && giantsOnField < 2) { // Single wincondition deploy when having 5-7 elixir (more likely to save) if (Math.random() < 0.4) { // 40% chance to use wincondition (reduced from 60% to encourage saving) var winconditionCard = null; for (var w = 0; w < aiDeck.length; w++) { if (aiDeck[w] === 'giant') { winconditionCard = 'giant'; break; } } if (winconditionCard) { var deployX = 300 + Math.random() * 1448; var deployY = 200 + Math.random() * 600; if (deployUnit(winconditionCard, deployX, deployY, false)) { aiLastDeploy = LK.ticks; aiDeployInterval = 240 + Math.random() * 120; } } } else { // Regular unit deployment (less likely when saving) if (aiElixir >= 3 && Math.random() < 0.3) { var availableCards = []; for (var a = 0; a < aiDeck.length; a++) { var deckCard = aiDeck[a]; var cardCost = deckCard === 'giant' ? 5 : deckCard === 'wizard' ? 5 : deckCard === 'bruja' ? 5 : deckCard === 'skeleton' ? 1 : deckCard === 'skeletonArmy' ? 3 : deckCard === 'fireball' ? 4 : deckCard === 'discharge' ? 2 : deckCard === 'minion' ? 3 : deckCard === 'maquinaVoladora' ? 4 : deckCard === 'megaesbirro' ? 3 : deckCard === 'ballesta' ? 6 : 3; if (aiElixir >= cardCost && deckCard !== 'fireball' && deckCard !== 'discharge') { availableCards.push(deckCard); } } if (availableCards.length > 0) { var randomCard = availableCards[Math.floor(Math.random() * availableCards.length)]; var deployX = 300 + Math.random() * 1448; var deployY = 200 + Math.random() * 1000; if (deployUnit(randomCard, deployX, deployY, false)) { aiLastDeploy = LK.ticks; aiDeployInterval = 180 + Math.random() * 120; } } } } } else if (aiElixir >= 3 && aiElixir < 5 && LK.ticks - aiLastDeploy > aiDeployInterval * 3) { // Wait even longer when having low elixir (3-4), prioritize saving for wincondition // Only deploy if waiting for too long or in emergency if (Math.random() < 0.15) { // Only 15% chance to deploy regular units when saving (reduced from 30%) var lowCostCards = []; for (var l = 0; l < aiDeck.length; l++) { var deckCard = aiDeck[l]; var cardCost = deckCard === 'giant' ? 5 : deckCard === 'wizard' ? 5 : deckCard === 'bruja' ? 5 : deckCard === 'skeleton' ? 1 : deckCard === 'skeletonArmy' ? 3 : deckCard === 'fireball' ? 4 : deckCard === 'discharge' ? 2 : deckCard === 'minion' ? 3 : deckCard === 'maquinaVoladora' ? 4 : deckCard === 'megaesbirro' ? 3 : deckCard === 'ballesta' ? 6 : 3; if (aiElixir >= cardCost && deckCard !== 'fireball' && deckCard !== 'discharge' && cardCost <= 4) { lowCostCards.push(deckCard); } } if (lowCostCards.length > 0) { var randomCard = lowCostCards[Math.floor(Math.random() * lowCostCards.length)]; var deployX = 300 + Math.random() * 1448; var deployY = 200 + Math.random() * 1000; if (deployUnit(randomCard, deployX, deployY, false)) { aiLastDeploy = LK.ticks; aiDeployInterval = 240 + Math.random() * 120; } } } } } } function checkGameEnd() { if (gameTime <= 0 && !gameEnded) { gameEnded = true; if (playerTowersDestroyed > enemyTowersDestroyed) { LK.showGameOver(); } else if (enemyTowersDestroyed > playerTowersDestroyed) { LK.showYouWin(); } else { // Tie-breaker: check tower health var playerTotalHealth = 0; var enemyTotalHealth = 0; for (var i = 0; i < playerTowers.length; i++) { playerTotalHealth += playerTowers[i].currentHealth; } for (var i = 0; i < enemyTowers.length; i++) { enemyTotalHealth += enemyTowers[i].currentHealth; } if (playerTotalHealth > enemyTotalHealth) { LK.showYouWin(); } else if (enemyTotalHealth > playerTotalHealth) { LK.showGameOver(); } else { LK.showGameOver(); // Draw counts as loss } } } } function setupEventHandlers() { // Event handlers for active deck cards only for (var i = 0; i < deckCards.length; i++) { var card = deckCards[i]; var cardType = card.cardType; var cost = cardType === 'giant' ? 5 : cardType === 'wizard' ? 5 : cardType === 'bruja' ? 5 : cardType === 'skeleton' ? 1 : cardType === 'skeletonArmy' ? 3 : cardType === 'fireball' ? 4 : cardType === 'discharge' ? 2 : cardType === 'minion' ? 3 : cardType === 'maquinaVoladora' ? 4 : cardType === 'megaesbirro' ? 3 : cardType === 'ballesta' ? 6 : 3; // Create closure to capture cardType and cost (function (type, cardCost, index) { card.down = function (x, y, obj) { if (elixir >= cardCost) { draggedCard = type; } }; })(cardType, cost, i); } game.down = function (x, y, obj) { // Only handle game events if game has started if (!gameStarted) { return; } // This will handle deployment when clicking on the battlefield }; game.up = function (x, y, obj) { // Only handle game events if game has started if (!gameStarted) { return; } if (draggedCard) { // Convert coordinates to game space var gamePos = { x: x, y: y }; if (obj && obj.parent) { var globalPos = obj.parent.toGlobal ? obj.parent.toGlobal(obj.position) : { x: x, y: y }; gamePos = game.toLocal ? game.toLocal(globalPos) : { x: x, y: y }; } if (deployUnit(draggedCard, gamePos.x, gamePos.y, true)) { // Unit deployed successfully } draggedCard = null; } }; } // Initialize menu createMenu(); game.update = function () { // Only run game logic if game has started if (!gameStarted) { return; } if (gameEnded) { return; } // Update timer if (LK.ticks % 60 === 0 && gameTime > 0) { gameTime--; timerText.setText(gameTime.toString()); } // Check for double elixir activation at 120 seconds remaining (130 seconds elapsed) if (gameTime === 130 && !doubleElixirActive) { doubleElixirActive = true; doubleElixirMessageShown = false; // Create double elixir message doubleElixirMessageText = new Text2('¡ELIXIR DOBLE ACTIVADO!', { size: 80, fill: 0xFFD700 }); doubleElixirMessageText.anchor.set(0.5, 0.5); doubleElixirMessageText.x = 1024; doubleElixirMessageText.y = 300; game.addChild(doubleElixirMessageText); // Set message timer for 3 seconds doubleElixirMessageTimer = 180; // 3 seconds at 60fps // Flash effect for the message tween(doubleElixirMessageText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 500, onFinish: function onFinish() { tween(doubleElixirMessageText, { scaleX: 1.0, scaleY: 1.0 }, { duration: 500 }); } }); } // Handle double elixir message timer if (doubleElixirMessageTimer > 0) { doubleElixirMessageTimer--; if (doubleElixirMessageTimer <= 0 && doubleElixirMessageText) { doubleElixirMessageText.destroy(); doubleElixirMessageText = null; } } // Regenerate elixir (double speed after 120 seconds passed) var currentElixirRegenRate = doubleElixirActive ? doubleElixirRegenRate : elixirRegenRate; if (LK.ticks - lastElixirRegen >= currentElixirRegenRate && elixir < maxElixir) { elixir++; elixirText.setText(elixir + "/" + maxElixir); lastElixirRegen = LK.ticks; } // Regenerate AI elixir (double speed after 120 seconds passed) var currentAiElixirRegenRate = doubleElixirActive ? doubleAiElixirRegenRate : aiElixirRegenRate; if (LK.ticks - lastAiElixirRegen >= currentAiElixirRegenRate && aiElixir < aiMaxElixir) { aiElixir++; lastAiElixirRegen = LK.ticks; } // AI deployment aiDeploy(); // Clean up dead units for (var i = playerUnits.length - 1; i >= 0; i--) { if (playerUnits[i].isDead) { playerUnits[i].destroy(); playerUnits.splice(i, 1); } } for (var i = enemyUnits.length - 1; i >= 0; i--) { if (enemyUnits[i].isDead) { enemyUnits[i].destroy(); enemyUnits.splice(i, 1); } } // Clean up bullets for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i].destroyed || bullets[i].y < -100 || bullets[i].y > 2800) { bullets[i].destroy(); bullets.splice(i, 1); } } // Check for immediate game end conditions var allPlayerTowersDead = true; var allEnemyTowersDead = true; for (var i = 0; i < playerTowers.length; i++) { if (!playerTowers[i].isDead) { allPlayerTowersDead = false; break; } } for (var i = 0; i < enemyTowers.length; i++) { if (!enemyTowers[i].isDead) { allEnemyTowersDead = false; break; } } if (allPlayerTowersDead && !gameEnded) { gameEnded = true; LK.showGameOver(); } else if (allEnemyTowersDead && !gameEnded) { gameEnded = true; LK.showYouWin(); } checkGameEnd(); };
===================================================================
--- original.js
+++ change.js
@@ -110,9 +110,9 @@
self.maxHealth = 900;
self.currentHealth = 900;
self.isDead = false;
self.damage = 10;
- self.range = 2000;
+ self.range = 2500;
self.attackSpeed = 18; // Every 0.3 seconds (60fps * 0.3 = 18 ticks)
self.lastAttackTime = 0;
self.target = null;
self.isAttacking = false;