User prompt
haz que debajo del icono de la bola de fuego haya un cardslot con el icono de la bruja en él para poder seleccionar a la bruja
User prompt
sin querer he tocado el cardslot en la seccion de assets y he bugueado todo el juego... puedes hacer que vuelva a estar como antes? por favor
User prompt
joder, sigues sin crearlo! acaso es invisible? vamos a ver joder arregla todo lo del cardslot, que se ha bugueado y ponlo de nuevo como estaba, y añade un cardslot un 100% debajo del de la bola de fuego y tambiel el asset de la bruja
User prompt
vamos a ver, no lo has hecho. haz que cuando le das a "mazo" y aparezcan todas las cartas, que la carta de la bruja este un 100% debajo de la de la bola de fuego
User prompt
te ha faltado crear su icono en la seccion de mazo para que esté un 100% debajo de la bola de fuego
User prompt
crea una nueva carta, llamada "bruja", cuesta 5 de elixir, tiene 550 hp, y 130 de daño, velocidad 1.25, puede atacar aereo y terrestre. es una tropa terrestre, tiene 500 unidades de deteccion de enmigos y 425 unidades de alcance, y 90 ticks tarda en atacar, creale su propio asset, y su icono está un 100% debajo de la bola de fuego. el cpu tambien tiene esta carta.
User prompt
haz que la partida en vez de 120 segundos dure 250, pero una vez pasan 120 segundos el elixir se recarga el doble de rapido tanto el cpu como el jugador, y sale un mensaje durante 3 segundos arriba de la pantalla que avisa de ello ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que el jugador recargue elexir cada 1.9 segundos, pero el bot siga en 2 segundos. haz que el mago tambien pueda atacar a las tropas aereas. aumentale el alcance a la torre de princesa a 900. bajale la vida a las arqueras a 400hp y el daño a 65, y bajale el alcance a 525. aumentale la vida al megaesbirro a 650, y el daño a 135.
User prompt
haz que en el apartado de mazo y durante la partida el icono de la maquina voladora sea "flying_machine" no solo en el campo de batalla sino tambien fuera
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 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 === '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, { 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 ****/ /* 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 */ // Game state variables var gameStarted = false; var gameTime = 120; // 2 minutes in seconds var elixir = 5; // Starting elixir var maxElixir = 10; var elixirRegenRate = 114; // Ticks for 1.9 seconds at 60fps (1.9 * 60 = 114) var lastElixirRegen = 0; var aiElixir = 5; // AI starting elixir var aiMaxElixir = 10; var aiElixirRegenRate = 120; // Ticks for 2 seconds at 60fps var lastAiElixirRegen = 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']; // 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 { 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; // Card icon var cardIcon = LK.getAsset(cardType === 'discharge' ? 'descarga_descardina' : cardType === 'maquinaVoladora' ? 'flying_machine' : 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 === 'skeleton' ? 1 : cardType === 'skeletonArmy' ? 3 : cardType === 'fireball' ? 4 : cardType === 'discharge' ? 2 : cardType === 'minion' ? 3 : cardType === 'maquinaVoladora' ? 4 : cardType === 'megaesbirro' ? 3 : 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) { 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 === 'maquinaVoladora' ? 'flying_machine' : 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 === '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 === 'skeleton' ? 1 : cardType === 'skeletonArmy' ? 3 : cardType === 'fireball' ? 4 : cardType === 'discharge' ? 2 : cardType === 'minion' ? 3 : cardType === 'maquinaVoladora' ? 4 : cardType === 'megaesbirro' ? 3 : 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 === '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: 'cannon' // Structure card }; // 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', 'skeleton', 'skeletonArmy', 'minion', 'maquinaVoladora', 'megaesbirro']; // 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 === 'skeleton' ? 1 : deckCard === 'skeletonArmy' ? 3 : deckCard === 'fireball' ? 4 : deckCard === 'discharge' ? 2 : deckCard === 'minion' ? 3 : deckCard === 'maquinaVoladora' ? 4 : deckCard === 'megaesbirro' ? 3 : 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 === 'skeleton' ? 1 : deckCard === 'skeletonArmy' ? 3 : deckCard === 'fireball' ? 4 : deckCard === 'discharge' ? 2 : deckCard === 'minion' ? 3 : deckCard === 'maquinaVoladora' ? 4 : deckCard === 'megaesbirro' ? 3 : 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 === 'skeleton' ? 1 : deckCard === 'skeletonArmy' ? 3 : deckCard === 'fireball' ? 4 : deckCard === 'discharge' ? 2 : deckCard === 'minion' ? 3 : deckCard === 'maquinaVoladora' ? 4 : deckCard === 'megaesbirro' ? 3 : 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 === 'skeleton' ? 1 : cardType === 'skeletonArmy' ? 3 : cardType === 'fireball' ? 4 : cardType === 'discharge' ? 2 : cardType === 'minion' ? 3 : cardType === 'maquinaVoladora' ? 4 : cardType === 'megaesbirro' ? 3 : 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()); } // Regenerate elixir if (LK.ticks - lastElixirRegen >= elixirRegenRate && elixir < maxElixir) { elixir++; elixirText.setText(elixir + "/" + maxElixir); lastElixirRegen = LK.ticks; } // Regenerate AI elixir if (LK.ticks - lastAiElixirRegen >= aiElixirRegenRate && 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
@@ -700,11 +700,11 @@
var self = Container.call(this);
self.isPlayer = isPlayer;
self.unitType = 'megaesbirro';
self.isAerial = true; // Mark as aerial unit
- self.maxHealth = 625; // 500 + 125
- self.currentHealth = 625;
- self.damage = 120; // 85 + 35
+ 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;
@@ -1070,9 +1070,9 @@
self.isKing = isKing;
self.maxHealth = isKing ? 3700 : 2400;
self.currentHealth = self.maxHealth;
self.isDead = false;
- self.range = 800;
+ 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', {
@@ -1255,10 +1255,10 @@
// 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 (only archers and towers can attack air)
- if (targetUnits[i].isAerial && self.unitType !== 'archer') {
+ // 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) {
@@ -1450,10 +1450,10 @@
// 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', 425, 75, 90, 538, 1.5);
- // Range reduced by 30% from 756 to 538 (25% + 5%)
+ var self = Unit.call(this, isPlayer, 'archer', 400, 65, 90, 525, 1.5);
+ // Range reduced and stats adjusted
return self;
});
/****
@@ -1497,13 +1497,13 @@
var gameStarted = false;
var gameTime = 120; // 2 minutes in seconds
var elixir = 5; // Starting elixir
var maxElixir = 10;
-var elixirRegenRate = 120; // Ticks for 2.0 seconds at 60fps
+var elixirRegenRate = 114; // Ticks for 1.9 seconds at 60fps (1.9 * 60 = 114)
var lastElixirRegen = 0;
var aiElixir = 5; // AI starting elixir
var aiMaxElixir = 10;
-var aiElixirRegenRate = 120; // Ticks for 2 seconds at 60fps (faster than player)
+var aiElixirRegenRate = 120; // Ticks for 2 seconds at 60fps
var lastAiElixirRegen = 0;
var playerUnits = [];
var enemyUnits = [];
var bullets = [];