User prompt
subele el daño al minero a 200. pero haz que contra las torres haga 35 de daño.
User prompt
por alguna razon el minero cuando golpea a una carta enemiga su barra de vida visual pasa a estar vacia sin ninguna razón, creo que es por culpa de lo del redondeo, arreglalo.
User prompt
hay un bug, despues de terminar el segundo de aparicion el minero entra en bucle y empieza a volver a aparecer y de todo, haz que despues del segundo de aparicion no hagas que aparezca el minero de nuevo, sino que funcione con normalidad, empieze a moverse y a atacar y de todo. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
es que hay un bug, cuando colocas al minero aparece durante un segundo donde ha sido colocado y las cartas y torres enemigas empiezan a atacarle, y aunque desaparezca le siguen atacando. por lo visto ocurre debido que al ser colocado tiene 1 segundo de aparicion y todo eso, pero el segundo de aparicion eliminaselo, y haz que ese segundo sea cuando el rastro ha finalziado, no cuando la carta es colocada. aumentale la velocidad a 8. haz que durante la fase del rastro todas las tropas y cartas no le puedan atacar y le ignoren. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ya lo estamos logrando pero... haz que el asset esté en vertical en vez de horizontal. y haz que cuando es colocado no aparezca aun el minero, el minero aparece por primera vez cuando el rastro llega al lugar donde ha sido colocado, primero es el rastro visual, y una vez acabe el rastro visual, se genera el minero donde ha terminado el rastro, y una vez aparece tarda 1 segundo de aparicion. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
nah no lo entiendes, quiero que el minero sin aun aparecer visualmente, aparezca desde la torre del rey, y va a una velocidad de 6 hacia donde ha sido colocada mientras deja un rastro visual (aun no está el minero, todo es visual) hasta llegar a donde ha sido colocado, una vez aparece ya se genera el minero en ese mismo lugar, y el rastro se borrará tras 3 segundos, el rastro pueden ser simplemente assets de un rectangulo que se genran cada vez que avanza 50 unidades por ejemplo, ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que el minero no pueda hacer daño que contenga comas, es decir debe de hacer un numero exacto, haz que se redondee, si hace 64,52 de daño, haz que haga 65, por ejemplo. ademas elimina el sistema del rastro y hazlo de nuevo, que no me gusta el sistema que has hecho. aumentale el radio de ataque de 50 a 75.
User prompt
haz que en vez de 65% menos de daño contra torres, que ha un 74%. ademas haz que tarde 2 segundos en llegar a donde ha sido colocado y deja un rastro que va creandose hasta llegar a donde ha sido colocado y desaparece en 3 segundos. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
vamos a crear una nueva carta, se llamará "minero", tendrá 600hp, una velocidad de 1.25. daño de 130, sin embargo, contra las torres hace un 65% menos de daño. aunque sea una tropa terrestre que solo ataca a tropas terrestres tiene una caracteristica, y es que puede ser colocado en cualquier lugar del mapa. radio de deteccion de 450 unidades, radio de ataque 40 unidades. tiene su propio asset, y su icono está un 100% debajo de la maquina voladora. el cpu le puede tocar como una carta normal, pero haz que siempre lo coloque en un radio de 50 unidades alrededor de la torre del jugador, o de una estructura.
User prompt
vamos a crear una nueva carta, se llama "rompemuros". es una wincondition, asi que ataca a las torres y tambien ataca a las estructuras, asi que debe de ir hacia ellas y atacarlas si están en su rango de deteccion, que es de 400. tienen una velocidad de 2.75. y una vida de 200hp, y daño 275. tiene un radio de ataque de 200. tiene su propio asset, y el asset de la explosion es el mismo que el del golem. añade su icono un 100% debajo del megaesbirro. esta carta tiene una peculiaridad, y es que cuando ataca muere, es decir, lleva una bomba y cuando ataca esta bomba explota la carta muere. se generan 2, uno a la izquierda y otro a la derecha. cuesta 2 de elexir. añadela que le pueda tocar al cpu como wincondition.
User prompt
en antiaereo tambien está aparte de las arqueras la bruja y el mago. y en hechizo está el hielo, pero haz que el cpu solo use el hielo de forma defensiva.
User prompt
haz que si la lapida es destruida, sea como sea generara los 4 esqueletos. subele al montapuercos la vida y el daño en un 10%
User prompt
bajale la vida al golem otro 10%, y tambien a los mini-golems. haz que la lapida genera los 4 esqueletos de ambas maneras, muera por tiempo o por algun enemigo genera los 4 esqueletos, en ambos casos
User prompt
bajale la vida al golem un 10%. haz que la lapida de esqueletos si es destruida por la salud que pierde por segundo tambien genere los 4 esqueletos, no solo cuando la mata un enemigo
User prompt
haz que el icono del golem en vez de estar un 100% debajo del hielo, que esté un 100% debajo de la bruja. bajale la vida al golem de 6000 a 5500. bajale la vida a los mini golems de 3000 a 2250. bajale el alcance a los mini-golems y al golem de 150 a 30. bajale el radio de la explosion al golem un 15%. bajale la velocidad al golem y a los mini-golems a 0.65
User prompt
hay un bug, cuando el golem muere no ocurre nada, debe de haber una explosion, y debe de tener la explosion su propio asset. el asset de los mini-golems es el del golem pero un 50% mas pequeño. y haz que ambas cosas aparezcan, es que no aparecen ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
bien hecho, pero se te ha olvidado hacer la explosion de 200 de radio y 100 de daño cuando muere. tambien junto a la explosion debe de generar 2 mini-golems, que son exactamente igual que él, pero tienen 50% menos de vida y daño, y estos cuando mueren tambien hacen la explosion pero con un 50% menos de daño y de radio. la formacion de los mini golems es uno a la izquierda y otro a la derecha. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
creemos una nueva carta, se llamará "golem", tendrá 6000 hp, es una wincondition, tiene una velocidad de 0.75. tiene un tiempo de aparicion de 4 segundos. ataca solo a estructuras y torres. hace 200 de daño cada 115 ticks. cuando muere causa una explosion de 200 de radio que todas las cartas o torre o estructuras que hayan tocando el circulo sufren 100 de daño, ademas de que al morir genera dos mini-golems, que es exactamente el golem tal cual pero son 2 y tienen un 50% menos de vida y daño, ademas de que estos al morir tambien generan la explosion, pero la explosion tambien hace un 50% menos de daño. cuesta 8 de elixir. radio de deteccion de 500 unidades y radio de ataque 150 unidades (cuerpo a cuerpo)
User prompt
cuando la lapida es destruida debe generar 4 esqueletos. el hielo debe de congelar a las torres tambien, es decir, si el circulo del hielo toca a la torre enemiga, la torre enemiga no debe poder atacar en dicho periodo de tiempo. la descarga debe de hacer un 50% menos de daño contra las torres. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
la lapida es una estructura, asi que las wincondition deben detectarlo como un enemigo y si esta en el radio de deteccion y estan mas cerca de la lapida que de la torre rival deben ir a por ella. aumenta el numero de esqueletos en el ejercito de esqueletos de 12 a 15
User prompt
la carta "lapida" debe costar 3 de elixir, y no debe de mostrar cuanto tiempo queda para volver a generar esqueletos. bajale el tiempo de generacion de esqueletos, de 6 segundos a 5 segundos.
User prompt
crea una estructura llamada "lapida" su icono estará debajo de la ballesta con su cardslot. la lapida tiene 700hp, y pierde un 3% por segundo, es una estructura, no ataca ni se mueve lo que hace es generar esqueletos, tiene un tiempo de generacion de 1 segundo, tarda 6 segundos en generar esqueletos y los genera de dos en dos, cuando es colocada y termina su tiempo de recarga aparecen 2 esqueletos sin esperar los 6 segundos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que el jugador pueda tener en el mazo hasta 12 cartas. haz que el cpu rival no puede usar la misma carta 2 veces, cuando usa una carta, para volver a poder usarla debe usar por lo menos otras 4 cartas diferentes.
User prompt
subele el daño a la ballesta a 18, y haz que la descarga pueda ser colocada en cualquier lugar del mapa. la torre del rey se activa cuando una torre de princesa es destruida, sin embargo, si la torre del rey recibe daño tambien se activará
User prompt
el hielo debe de costar 4 de elixir en vez de 3. y la explosion debe tener el asset de dischargeExplosion
/****
* 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 = 18;
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];
}
}
}
// Find closest enemy towers if no units in range, but only if touching middle line (y around 1366)
var canAttackTowers = Math.abs(self.y - 1366) <= 50; // Must be within 50 units of middle line
if (canAttackTowers) {
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 <= self.range && distance < closestDistance) {
closestDistance = distance;
closestTarget = targetTowers[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 : cardType === 'montapuercos' ? 4 : cardType === 'hielo' ? 4 : cardType === 'lapida' ? 3 : cardType === 'golem' ? 8 : 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) {
// 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 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 Golem = Container.expand(function (isPlayer, isMini) {
var self = Container.call(this);
self.isPlayer = isPlayer;
self.unitType = 'golem';
self.isMini = isMini || false;
self.isAerial = false; // Ground unit
self.maxHealth = self.isMini ? 1822 : 4455; // 10% less health for golem (4455), mini-golem accordingly (1822)
self.currentHealth = self.maxHealth;
self.damage = self.isMini ? 100 : 200; // 50% less damage for mini-golems
self.attackSpeed = 115; // Every 115 ticks
self.range = 500; // Detection range
self.attackRange = 30; // Melee attack range (reduced from 150 to 30)
self.speed = 0.65;
self.target = null;
self.lastAttackTime = 0;
self.isDead = false;
self.isAttacking = false;
self.hasExploded = false; // Prevent multiple explosions
// Spawn delay properties
self.spawnDelay = 240; // 4 seconds (60 * 4 = 240 ticks)
self.spawnTimer = self.spawnDelay;
self.isSpawning = true;
var golemGraphics = self.attachAsset('golem', {
anchorX: 0.5,
anchorY: 0.5
});
// Scale mini-golems smaller (50% as requested)
if (self.isMini) {
golemGraphics.scaleX = 0.5;
golemGraphics.scaleY = 0.5;
}
// Health bar background
var healthBarScale = self.isMini ? 0.3 : 0.4;
var healthBarBg = LK.getAsset('cardSlot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: healthBarScale,
scaleY: 0.1,
y: -60
});
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: -60
});
healthBar.tint = self.isPlayer ? 0x2196F3 : 0xF44336;
self.addChild(healthBar);
self.healthBar = healthBar;
self.healthBarMaxScale = healthBarScale;
// Spawn timer text
self.spawnTimerText = new Text2('', {
size: 35,
fill: 0xFFFFFF
});
self.spawnTimerText.anchor.set(0.5, 0.5);
self.spawnTimerText.y = 40;
self.addChild(self.spawnTimerText);
self.takeDamage = function (damage) {
self.currentHealth -= damage;
if (self.currentHealth <= 0) {
self.currentHealth = 0;
self.isDead = true;
// Explode immediately when golem dies
if (!self.hasExploded) {
self.explodeOnDeath();
}
}
// 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;
// Golem only attacks structures and towers (wincondition behavior)
// First check for structures (cannons, ballesta, lapida) within detection range
var targetStructures = self.isPlayer ? enemyUnits : playerUnits;
for (var i = 0; i < targetStructures.length; i++) {
if (!targetStructures[i].isDead && (targetStructures[i].unitType === 'cannon' || targetStructures[i].unitType === 'ballesta' || targetStructures[i].unitType === 'lapida')) {
var distance = Math.sqrt(Math.pow(self.x - targetStructures[i].x, 2) + Math.pow(self.y - targetStructures[i].y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestTarget = targetStructures[i];
}
}
}
// If no structures found, 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];
}
}
}
}
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;
// Melee attack - direct damage
self.target.takeDamage(self.damage);
// Visual effect for melee strike
LK.effects.flashObject(self, 0xFFFFFF, 200);
// Flash target when hit
tween(self.target, {
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.target, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
});
self.lastAttackTime = LK.ticks;
LK.getSound('attack').play();
}
};
self.explodeOnDeath = function () {
if (self.hasExploded) {
return; // Prevent multiple explosions
}
self.hasExploded = true;
// Calculate explosion parameters based on mini-golem status
var explosionRadius = self.isMini ? 85 : 170; // 50% less radius for mini-golems, reduced by 15% (170 vs 85)
var explosionDamage = self.isMini ? 50 : 100; // 50% less damage for mini-golems (50 vs 100)
var explosionScale = self.isMini ? 1.0 : 2.0; // Scale visual effect accordingly
// Visual explosion effect using golemExplosion asset
var explosionCircle = LK.getAsset('golemExplosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: explosionScale,
scaleY: explosionScale
});
explosionCircle.x = self.x;
explosionCircle.y = self.y;
explosionCircle.tint = 0x8B4513; // Brown color for golem explosion
explosionCircle.alpha = 0.8;
game.addChild(explosionCircle);
// Animate explosion
tween(explosionCircle, {
alpha: 0,
scaleX: explosionScale * 1.5,
scaleY: explosionScale * 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.isPlayer) {
allTargets = allTargets.concat(enemyUnits, enemyTowers);
} else {
allTargets = allTargets.concat(playerUnits, playerTowers);
}
// Apply damage 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.x - targetUnit.x, 2) + Math.pow(self.y - targetUnit.y, 2));
if (distance <= explosionRadius) {
targetUnit.takeDamage(explosionDamage);
// Flash effect on hit units
tween(targetUnit, {
tint: 0x8B4513
}, {
duration: 300,
onFinish: function onFinish() {
tween(targetUnit, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
}
}
}
// Generate 2 mini-golems if this is not already a mini-golem
if (!self.isMini) {
for (var m = 0; m < 2; m++) {
var miniGolem = new Golem(self.isPlayer, true); // Create mini-golem
miniGolem.x = self.x + (m === 0 ? -80 : 80); // Position left and right
miniGolem.y = self.y;
// Remove spawn delay for generated mini-golems
miniGolem.spawnTimer = 0;
miniGolem.isSpawning = false;
miniGolem.spawnTimerText.setText('');
// Add mini-golem to appropriate unit array
if (self.isPlayer) {
playerUnits.push(miniGolem);
} else {
enemyUnits.push(miniGolem);
}
game.addChild(miniGolem);
}
// Play deploy sound for mini-golem generation
LK.getSound('deploy').play();
}
};
self.update = function () {
if (self.isDead) {
// Explode on death if hasn't exploded yet
if (!self.hasExploded) {
self.explodeOnDeath();
}
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 Hielo = Container.expand(function (targetX, targetY, isFromPlayer) {
var self = Container.call(this);
self.targetX = targetX;
self.targetY = targetY;
self.isFromPlayer = isFromPlayer;
self.damage = 50;
self.freezeDuration = 300; // 5 seconds freeze (60 ticks * 5 = 300)
self.freezeRadius = 400; // 400 units radius as requested
// Hielo 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 400 units radius
var explosionCircle = LK.getAsset('dischargeExplosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.2,
// Scale to make it 400 units radius (250 * 3.2 / 2 = 400)
scaleY: 3.2
});
explosionCircle.x = self.targetX;
explosionCircle.y = self.targetY;
explosionCircle.tint = 0x0080FF; // Blue color as requested
explosionCircle.alpha = 0.7;
game.addChild(explosionCircle);
// Keep circle static for duration, then destroy after freeze effect ends
LK.setTimeout(function () {
explosionCircle.destroy();
}, 5000); // Destroy after 5 seconds (freeze duration)
// Play explosion sound
LK.getSound('explosion').play();
// Get all possible targets for damage and freeze
var allTargets = [];
if (self.isFromPlayer) {
allTargets = allTargets.concat(enemyUnits, enemyTowers);
} else {
allTargets = allTargets.concat(playerUnits, playerTowers);
}
// Apply damage and freeze to all units within explosion radius of 400 units
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.freezeRadius) {
// Apply 50 damage
targetUnit.takeDamage(self.damage);
// Apply freeze effect (immobilize for 5 seconds) - works on towers too
targetUnit.stunTimer = self.freezeDuration;
targetUnit.isStunned = true;
// Visual effect for frozen units and towers (blue tint)
tween(targetUnit, {
tint: 0x0080FF
}, {
duration: 5000,
onFinish: function onFinish() {
tween(targetUnit, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
}
}
}
// Destroy hielo immediately after explosion
self.destroy();
};
// Create instant explosion effect - NOW we can call explode since it's defined
self.explode();
return self;
});
var Lapida = Container.expand(function (isPlayer) {
var self = Container.call(this);
self.isPlayer = isPlayer;
self.unitType = 'lapida';
self.maxHealth = 700;
self.currentHealth = 700;
self.isDead = false;
self.isStructure = true; // Mark as structure
// Skeleton generation properties
self.skeletonGenerationTime = 60; // 1 second generation time
self.skeletonProductionTime = 300; // 5 seconds to produce skeletons
self.skeletonTimer = 0; // Start immediately for first generation
self.isGenerating = false;
// Health decay properties - loses 3% per second
self.healthDecayRate = 20; // Every 20 ticks = 3 times per second = 1% each time = 3% per second
// Spawn delay properties
self.spawnDelay = 60; // 1 second
self.spawnTimer = self.spawnDelay;
self.isSpawning = true;
var lapidaGraphics = self.attachAsset('lapida', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarScale = 0.3;
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);
// Generation progress text removed - no longer displayed
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.generateSkeletons = function () {
// Generate 2 skeletons at positions around the lapida
var skeletonPositions = [{
x: self.x - 50,
y: self.y
},
// Left
{
x: self.x + 50,
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 Lapida-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 generation
LK.getSound('deploy').play();
};
// Method called when lapida is destroyed - generates 4 skeletons
self.onDestroy = function () {
// Generate 4 skeletons at positions around the lapida when destroyed
var destructionSkeletonPositions = [{
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 < destructionSkeletonPositions.length; i++) {
var pos = destructionSkeletonPositions[i];
var skeleton = new Skeleton(self.isPlayer);
skeleton.x = pos.x;
skeleton.y = pos.y;
// Remove spawn delay for destruction-generated skeletons
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 generation from destruction
LK.getSound('deploy').play();
};
self.update = function () {
if (self.isDead) {
// Call onDestroy when lapida dies - regardless of how it died
if (!self.hasBeenDestroyed) {
self.hasBeenDestroyed = true;
self.onDestroy();
}
return;
}
// Handle spawn delay
if (self.isSpawning) {
self.spawnTimer--;
if (self.spawnTimer <= 0) {
self.isSpawning = false;
self.spawnTimerText.setText('');
// Generate first 2 skeletons immediately after spawn delay ends
self.generateSkeletons();
self.skeletonTimer = self.skeletonProductionTime; // Start 6 second timer for next generation
} else {
// Show remaining time in seconds
var secondsLeft = Math.ceil(self.spawnTimer / 60);
self.spawnTimerText.setText(secondsLeft.toString());
}
return; // Don't do other actions while spawning
}
// Handle stun effect (structures can be stunned too)
if (self.stunTimer > 0) {
self.stunTimer--;
if (self.stunTimer <= 0) {
self.isStunned = false;
}
// Don't generate skeletons while stunned, but still decay health
}
// Handle health decay - loses 3% of total health per second
if (LK.ticks % self.healthDecayRate === 0) {
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;
}
// Handle skeleton generation (only if not stunned)
if (!self.isStunned) {
if (self.skeletonTimer > 0) {
self.skeletonTimer--;
if (self.skeletonTimer <= 0) {
// Generate 2 skeletons
self.generateSkeletons();
// Reset timer for next generation (5 seconds)
self.skeletonTimer = self.skeletonProductionTime;
}
}
}
};
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 Minero = Container.expand(function (isPlayer) {
var self = Container.call(this);
self.isPlayer = isPlayer;
self.unitType = 'minero';
self.isAerial = false; // Ground unit
self.maxHealth = 600;
self.currentHealth = 600;
self.damage = 200;
self.attackSpeed = 90; // 90 ticks
self.range = 450; // Detection range
self.attackRange = 75; // Attack range
self.speed = 1.25;
self.target = null;
self.lastAttackTime = 0;
self.isDead = false;
self.isAttacking = false;
// Tunnel travel properties
self.isTunneling = false;
self.tunnelStartX = 0;
self.tunnelStartY = 0;
self.tunnelTargetX = 0;
self.tunnelTargetY = 0;
self.tunnelCurrentX = 0;
self.tunnelCurrentY = 0;
self.tunnelTimer = 0;
self.tunnelDuration = 120; // 2 seconds (120 ticks at 60fps)
self.trailSegments = [];
// Spawn delay properties
self.spawnDelay = 60; // 1 second
self.spawnTimer = self.spawnDelay;
self.isSpawning = true;
var mineroGraphics = self.attachAsset('minero', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarScale = 0.25;
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 (only ground units)
var targetUnits = self.isPlayer ? enemyUnits : playerUnits;
for (var i = 0; i < targetUnits.length; i++) {
if (!targetUnits[i].isDead) {
// Skip minero if it's tunneling
if (targetUnits[i].unitType === 'minero' && targetUnits[i].isTunneling) {
continue;
}
// Minero can only attack 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];
}
}
}
// 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;
// Calculate damage - fixed 35 damage against towers, full 200 against units
var damageToApply = self.damage;
var isTower = false;
if (self.isPlayer) {
isTower = enemyTowers.indexOf(self.target) !== -1;
} else {
isTower = playerTowers.indexOf(self.target) !== -1;
}
if (isTower) {
damageToApply = 35; // Fixed 35 damage to towers
} else {
damageToApply = self.damage; // Full 200 damage to units
}
// Melee attack - direct damage
self.target.takeDamage(damageToApply);
// Visual effect for melee strike
LK.effects.flashObject(self, 0xFFFFFF, 200);
// Flash target when hit
tween(self.target, {
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.target, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
});
self.lastAttackTime = LK.ticks;
LK.getSound('attack').play();
}
};
self.startTunneling = function (targetX, targetY) {
self.isTunneling = true;
// Set tunnel origin to king tower position
if (self.isPlayer) {
self.tunnelStartX = 1024; // Player king tower x
self.tunnelStartY = 2600; // Player king tower y
} else {
self.tunnelStartX = 1024; // Enemy king tower x
self.tunnelStartY = 200; // Enemy king tower y
}
self.tunnelTargetX = targetX;
self.tunnelTargetY = targetY;
self.tunnelTimer = self.tunnelDuration;
self.trailSegments = [];
self.tunnelCurrentX = self.tunnelStartX;
self.tunnelCurrentY = self.tunnelStartY;
// Hide minero during tunneling
self.alpha = 0;
};
self.update = function () {
if (self.isDead) {
return;
}
// Handle tunneling
if (self.isTunneling) {
// Calculate tunnel progress
var dx = self.tunnelTargetX - self.tunnelStartX;
var dy = self.tunnelTargetY - self.tunnelStartY;
var totalDistance = Math.sqrt(dx * dx + dy * dy);
var tunnelSpeed = 8; // Speed of tunnel creation increased to 8
var moveX = dx / totalDistance * tunnelSpeed;
var moveY = dy / totalDistance * tunnelSpeed;
// Update tunnel current position
self.tunnelCurrentX += moveX;
self.tunnelCurrentY += moveY;
// Create trail segment every 50 units traveled
var currentDistance = Math.sqrt(Math.pow(self.tunnelCurrentX - self.tunnelStartX, 2) + Math.pow(self.tunnelCurrentY - self.tunnelStartY, 2));
if (Math.floor(currentDistance / 50) > self.trailSegments.length) {
// Create new vertical trail segment
var trailSegment = LK.getAsset('tunnelTrail', {
anchorX: 0.5,
anchorY: 0.5
});
trailSegment.x = self.tunnelCurrentX;
trailSegment.y = self.tunnelCurrentY;
trailSegment.tint = 0x8B4513; // Brown color for dirt trail
trailSegment.alpha = 0.7;
game.addChild(trailSegment);
self.trailSegments.push(trailSegment);
// Schedule trail segment destruction after 3 seconds
LK.setTimeout(function () {
if (trailSegment && trailSegment.destroy) {
trailSegment.destroy();
}
}, 3000);
}
// Check if tunnel reached target
var distanceToTarget = Math.sqrt(Math.pow(self.tunnelTargetX - self.tunnelCurrentX, 2) + Math.pow(self.tunnelTargetY - self.tunnelCurrentY, 2));
if (distanceToTarget < 30) {
// Tunneling complete - now minero appears at target location with 1 second spawn delay
self.x = self.tunnelTargetX;
self.y = self.tunnelTargetY;
self.isTunneling = false;
// Start 1 second spawn delay now that tunnel is complete
self.isSpawning = true;
self.spawnTimer = 60; // 1 second delay
self.alpha = 1; // Make visible but still spawning
}
return; // Don't do anything else while tunneling
}
// Handle spawn delay
if (self.isSpawning) {
self.spawnTimer--;
if (self.spawnTimer <= 0) {
self.isSpawning = false;
self.spawnTimerText.setText('');
// Spawn delay complete - minero can now function normally
} 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 Montapuercos = Container.expand(function (isPlayer) {
var self = Container.call(this);
self.isPlayer = isPlayer;
self.unitType = 'montapuercos';
self.isAerial = false; // Ground unit
self.maxHealth = 935; // 10% increase from 850
self.currentHealth = 935;
self.damage = 165; // 10% increase from 150
self.attackSpeed = 120; // 120 ticks
self.range = 60; // Melee range
self.speed = 2.25;
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 montapuercosGraphics = self.attachAsset('montapuercos', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarScale = 0.25;
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;
// Montapuercos only attacks structures and towers (wincondition behavior)
// First check for structures (cannons, ballesta, lapida) within detection range
var targetStructures = self.isPlayer ? enemyUnits : playerUnits;
for (var i = 0; i < targetStructures.length; i++) {
if (!targetStructures[i].isDead && (targetStructures[i].unitType === 'cannon' || targetStructures[i].unitType === 'ballesta' || targetStructures[i].unitType === 'lapida')) {
var distance = Math.sqrt(Math.pow(self.x - targetStructures[i].x, 2) + Math.pow(self.y - targetStructures[i].y, 2));
if (distance < closestDistance) {
closestDistance = distance;
closestTarget = targetStructures[i];
}
}
}
// If no structures found, 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];
}
}
}
}
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.range) {
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.range && LK.ticks - self.lastAttackTime > self.attackSpeed) {
self.isAttacking = true;
// Melee attack - direct damage
self.target.takeDamage(self.damage);
// Visual effect for melee strike
LK.effects.flashObject(self, 0xFFFFFF, 200);
// Flash target red when hit
tween(self.target, {
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.target, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
});
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 Rompemuros = Container.expand(function (isPlayer) {
var self = Container.call(this);
self.isPlayer = isPlayer;
self.unitType = 'rompemuros';
self.isAerial = false; // Ground unit
self.maxHealth = 200;
self.currentHealth = 200;
self.damage = 275;
self.attackSpeed = 60; // Attack speed doesn't matter much since it dies after attacking
self.range = 400; // Detection range
self.attackRange = 200; // Attack range
self.speed = 2.75;
self.target = null;
self.lastAttackTime = 0;
self.isDead = false;
self.isAttacking = false;
self.hasExploded = false; // Prevent multiple explosions
// Spawn delay properties
self.spawnDelay = 60; // 1 second
self.spawnTimer = self.spawnDelay;
self.isSpawning = true;
var rompemuroGraphics = self.attachAsset('rompemuros', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarScale = 0.25;
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;
// Rompemuros targets structures and towers (wincondition behavior)
// First check for structures (cannons, ballesta, lapida) within detection range
var targetStructures = self.isPlayer ? enemyUnits : playerUnits;
for (var i = 0; i < targetStructures.length; i++) {
if (!targetStructures[i].isDead && (targetStructures[i].unitType === 'cannon' || targetStructures[i].unitType === 'ballesta' || targetStructures[i].unitType === 'lapida')) {
var distance = Math.sqrt(Math.pow(self.x - targetStructures[i].x, 2) + Math.pow(self.y - targetStructures[i].y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestTarget = targetStructures[i];
}
}
}
// If no structures found, 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];
}
}
}
}
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;
// Apply damage to target
self.target.takeDamage(self.damage);
// Kamikaze explosion - rompemuros dies after attacking
if (!self.hasExploded) {
self.explodeOnAttack();
}
self.lastAttackTime = LK.ticks;
LK.getSound('attack').play();
}
};
self.explodeOnAttack = function () {
if (self.hasExploded) {
return; // Prevent multiple explosions
}
self.hasExploded = true;
// Visual explosion effect using golemExplosion asset
var explosionCircle = LK.getAsset('golemExplosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
explosionCircle.x = self.x;
explosionCircle.y = self.y;
explosionCircle.tint = 0xFF6600; // Orange color for rompemuros explosion
explosionCircle.alpha = 0.8;
game.addChild(explosionCircle);
// Animate explosion
tween(explosionCircle, {
alpha: 0,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 800,
onFinish: function onFinish() {
explosionCircle.destroy();
}
});
// Play explosion sound
LK.getSound('explosion').play();
// Rompemuros dies after exploding
self.isDead = true;
};
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;
// Initialize stun properties for freeze compatibility
self.stunTimer = 0;
self.isStunned = false;
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;
}
// Handle stun effect - towers can't attack while stunned/frozen
if (self.stunTimer > 0) {
self.stunTimer--;
if (self.stunTimer <= 0) {
self.isStunned = false;
}
return; // Don't attack while stunned/frozen
}
// King towers cannot attack until at least one Princess tower is destroyed OR king tower has taken damage
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++;
}
}
// King tower can attack if either: 1) Princess tower destroyed OR 2) King tower has taken damage
var kingTowerHasTakenDamage = self.currentHealth < self.maxHealth;
if (princessTowersAlive === 2 && !kingTowerHasTakenDamage) {
return; // King tower cannot attack while both Princess towers are alive AND king tower is undamaged
}
}
var targetUnits = self.isPlayer ? enemyUnits : playerUnits;
var closestTarget = null;
var closestDistance = Infinity;
for (var i = 0; i < targetUnits.length; i++) {
if (!targetUnits[i].isDead) {
// Skip minero if it's tunneling
if (targetUnits[i].unitType === 'minero' && targetUnits[i].isTunneling) {
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];
}
}
}
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, ballesta, lapida) 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' || targetStructures[i].unitType === 'ballesta' || targetStructures[i].unitType === 'lapida')) {
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', 'montapuercos', 'hielo', 'lapida', 'golem', 'rompemuros', 'minero'];
// 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 12)', {
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 if (cardType === 'montapuercos') {
var dischargeIndex = availableCards.indexOf('discharge');
var cannonIndex = availableCards.indexOf('cannon');
cardContainer.x = cardStartX + cannonIndex * cardSpacing;
cardContainer.y = 1100; // 100px below discharge (200px total below cannon)
} else if (cardType === 'hielo') {
var montapuercosIndex = availableCards.indexOf('montapuercos');
var cannonIndex = availableCards.indexOf('cannon');
cardContainer.x = cardStartX + cannonIndex * cardSpacing;
cardContainer.y = 1200; // 100px below montapuercos (300px total below cannon)
} else if (cardType === 'lapida') {
var ballestaIndex = availableCards.indexOf('ballesta');
var wizardIndex = availableCards.indexOf('wizard');
cardContainer.x = cardStartX + wizardIndex * cardSpacing;
cardContainer.y = 1200; // 100px below ballesta (300px total below wizard)
} else if (cardType === 'golem') {
var brujaIndex = availableCards.indexOf('bruja');
var giantIndex = availableCards.indexOf('giant');
cardContainer.x = cardStartX + giantIndex * cardSpacing;
cardContainer.y = 1200; // 100px below bruja (300px total below giant)
} else if (cardType === 'rompemuros') {
var megaesbirroIndex = availableCards.indexOf('megaesbirro');
var knightIndex = availableCards.indexOf('knight');
cardContainer.x = cardStartX + knightIndex * cardSpacing;
cardContainer.y = 1200; // 100px below megaesbirro (300px total below knight)
} else if (cardType === 'minero') {
var maquinaVoladoraIndex = availableCards.indexOf('maquinaVoladora');
var archerIndex = availableCards.indexOf('archer');
cardContainer.x = cardStartX + archerIndex * cardSpacing;
cardContainer.y = 1200; // 100px below maquina voladora (300px total below archer)
} 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 : cardType === 'montapuercos' ? 4 : cardType === 'hielo' ? 4 : cardType === 'lapida' ? 3 : cardType === 'golem' ? 8 : cardType === 'rompemuros' ? 2 : cardType === 'minero' ? 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) {
// 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 + '/12)', {
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 = 224;
var selectedSpacing = 150;
for (var i = 0; i < selectedDeck.length && i < 12; i++) {
var cardType = selectedDeck[i];
var selectedCardContainer = new Container();
// Display cards in two rows if more than 8
if (i < 8) {
selectedCardContainer.x = selectedStartX + i * selectedSpacing;
selectedCardContainer.y = 1400;
} else {
selectedCardContainer.x = selectedStartX + (i - 8) * selectedSpacing;
selectedCardContainer.y = 1500;
}
// Small card background
var selectedCardBg = selectedCardContainer.attachAsset('cardSlot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
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.35,
scaleY: 0.35
});
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 < 12) {
// Add card to deck if not full (max 12 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 : cardType === 'montapuercos' ? 4 : cardType === 'hielo' ? 4 : cardType === 'lapida' ? 3 : cardType === 'golem' ? 8 : cardType === 'rompemuros' ? 2 : cardType === 'minero' ? 3 : 3;
if (isPlayer && elixir < cost) {
return false;
}
if (!isPlayer && aiElixir < cost) {
return false;
} // AI also needs elixir
// Check AI card usage restriction
if (!isPlayer && !canAIUseCard(cardType)) {
return false; // AI can't use this card yet
}
// Check if position is in correct half (except for spells and minero)
if (cardType !== 'fireball' && cardType !== 'hielo' && cardType !== 'discharge' && cardType !== 'minero') {
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
updateAICardUsage(cardType); // Track AI card usage
}
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
updateAICardUsage(cardType); // Track AI card usage
}
game.addChild(skeleton1);
game.addChild(skeleton2);
game.addChild(skeleton3);
LK.getSound('deploy').play();
return true;
} else if (cardType === 'skeletonArmy') {
// Deploy 15 skeletons in a 3x5 formation
var skeletons = [];
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 5; col++) {
var skeleton = new Skeleton(isPlayer);
skeleton.x = x + (col - 2) * 40; // Spread horizontally (adjusted for 5 columns)
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
updateAICardUsage(cardType); // Track AI card usage
}
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
updateAICardUsage(cardType); // Track AI card usage
}
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
updateAICardUsage(cardType); // Track AI card usage
}
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
updateAICardUsage(cardType); // Track AI card usage
}
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 === 'montapuercos') {
unit = new Montapuercos(isPlayer);
} else if (cardType === 'lapida') {
unit = new Lapida(isPlayer);
} else if (cardType === 'golem') {
unit = new Golem(isPlayer, false); // false for regular golem (not mini)
} else if (cardType === 'rompemuros') {
// Deploy 2 rompemuros units - one left, one right
var rompemuros1 = new Rompemuros(isPlayer);
var rompemuros2 = new Rompemuros(isPlayer);
rompemuros1.x = x - 40; // Left rompemuros
rompemuros1.y = y;
rompemuros2.x = x + 40; // Right rompemuros
rompemuros2.y = y;
if (isPlayer) {
playerUnits.push(rompemuros1);
playerUnits.push(rompemuros2);
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(rompemuros1);
enemyUnits.push(rompemuros2);
aiElixir -= cost; // Deduct AI elixir
updateAICardUsage(cardType); // Track AI card usage
}
game.addChild(rompemuros1);
game.addChild(rompemuros2);
LK.getSound('deploy').play();
return true;
} else if (cardType === 'hielo') {
// Hielo is a spell - can be deployed anywhere on the map, appears instantly
var hielo = new Hielo(x, y, isPlayer);
// Don't add to bullets array since it destroys itself immediately
game.addChild(hielo);
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
updateAICardUsage(cardType); // Track AI card usage
}
LK.getSound('deploy').play();
return true;
} else if (cardType === 'minero') {
unit = new Minero(isPlayer);
// Special placement logic for minero - can be placed anywhere but needs tunneling
unit.tunnelTargetX = x;
unit.tunnelTargetY = y;
// Don't set initial position here - let minero handle its own tunneling logic
// Remove spawn delay for minero since tunneling acts as the delay
unit.spawnTimer = 0;
unit.isSpawning = false;
unit.spawnTimerText.setText('');
// Start tunneling immediately
unit.startTunneling(x, y);
} else if (cardType === 'cannon') {
unit = new Cannon(isPlayer);
}
if (unit) {
if (cardType !== 'minero') {
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
updateAICardUsage(cardType); // Track AI card usage
}
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
var aiUsedCards = []; // Track recently used cards
var aiCardUsageRestriction = {}; // Track cards that can't be used yet
// Generate AI deck with required constraints
function generateAIDeck() {
aiDeck = [];
// Required cards (must have)
var requiredCards = {
aerial: Math.random() < 0.33 ? 'archer' : Math.random() < 0.5 ? 'bruja' : 'wizard',
// Card that can attack aerial units (randomly choose between archer, bruja, and wizard)
wincondition: Math.random() < 0.25 ? 'giant' : Math.random() < 0.33 ? 'montapuercos' : Math.random() < 0.5 ? 'golem' : 'rompemuros',
// Wincondition card (randomly choose between giant, montapuercos, and golem)
spell: Math.random() < 0.33 ? 'fireball' : Math.random() < 0.5 ? 'discharge' : 'hielo',
// Spell card (randomly choose between fireball, discharge, and hielo)
structure: Math.random() < 0.33 ? 'cannon' : Math.random() < 0.5 ? 'ballesta' : 'lapida' // Structure card (randomly choose between cannon, ballesta, and lapida)
};
// 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', 'montapuercos', 'hielo', 'lapida', 'golem', 'minero'];
// Fill remaining 8 slots with random cards from remaining pool
while (aiDeck.length < 12) {
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 : deckCard === 'montapuercos' ? 4 : deckCard === 'hielo' ? 4 : deckCard === 'lapida' ? 3 : deckCard === 'golem' ? 8 : deckCard === 'rompemuros' ? 2 : deckCard === 'minero' ? 3 : 3;
if (aiElixir >= cardCost && canAIUseCard(deckCard)) {
// Add defensive units (not spells for defense)
if (deckCard !== 'fireball' && deckCard !== 'discharge') {
defensiveCards.push(deckCard);
}
}
}
}
// Check for spell usage opportunities (discharge, fireball, and hielo for defense)
var spellCards = [];
for (var s = 0; s < aiDeck.length; s++) {
var spellCard = aiDeck[s];
if (spellCard === 'discharge' || spellCard === 'fireball' || spellCard === 'hielo') {
var spellCost = spellCard === 'discharge' ? 2 : spellCard === 'hielo' ? 4 : 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)];
var deployX, deployY;
// Special placement logic for minero - place near player towers/structures
if (randomCard === 'minero') {
// Find closest player tower or structure to place minero near it
var closestTarget = null;
var closestDistance = Infinity;
var allPlayerTargets = playerTowers.concat(playerUnits.filter(function (unit) {
return unit.unitType === 'cannon' || unit.unitType === 'ballesta' || unit.unitType === 'lapida';
}));
for (var t = 0; t < allPlayerTargets.length; t++) {
var target = allPlayerTargets[t];
if (!target.isDead) {
var distance = Math.sqrt(Math.pow(1024 - target.x, 2) + Math.pow(1366 - target.y, 2));
if (distance < closestDistance) {
closestDistance = distance;
closestTarget = target;
}
}
}
if (closestTarget) {
// Place minero within 50 units of the closest target
var angle = Math.random() * Math.PI * 2;
deployX = closestTarget.x + Math.cos(angle) * (30 + Math.random() * 20);
deployY = closestTarget.y + Math.sin(angle) * (30 + Math.random() * 20);
} else {
// Fallback to random position if no target found
deployX = 300 + Math.random() * 1448;
deployY = 800 + Math.random() * 400;
}
} else {
// Deploy near threatened area for other cards
deployX = 300 + Math.random() * 1448;
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 (hielo excluded - defensive only)
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' || aiDeck[w] === 'montapuercos' || aiDeck[w] === 'golem' || aiDeck[w] === 'rompemuros') {
winconditionCard = aiDeck[w];
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 : winconditionCard === 'montapuercos' ? 4 : winconditionCard === 'golem' ? 8 : 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 : supportCard === 'montapuercos' ? 4 : supportCard === 'hielo' ? 4 : supportCard === 'ballesta' ? 6 : supportCard === 'lapida' ? 3 : supportCard === 'golem' ? 8 : supportCard === 'rompemuros' ? 2 : supportCard === 'minero' ? 3 : 3;
if (aiElixir >= supportCost && supportCard !== 'fireball' && supportCard !== 'discharge' && supportCard !== 'hielo' && supportCard !== winconditionCard) {
supportCards.push(supportCard);
}
}
// Check for Ballesta offensive deployment near middle line
if (aiElixir >= 6 && Math.random() < 0.3) {
// 30% chance when AI has enough elixir
var hasBallesta = false;
for (var b = 0; b < aiDeck.length; b++) {
if (aiDeck[b] === 'ballesta') {
hasBallesta = true;
break;
}
}
if (hasBallesta) {
// Deploy Ballesta near middle line for cross-field tower attacks
var ballestaX = 300 + Math.random() * 1448;
var ballestaY = 1316 + Math.random() * 100; // Near middle line (1366 ± 50)
if (deployUnit('ballesta', ballestaX, ballestaY, false)) {
aiLastDeploy = LK.ticks;
aiDeployInterval = 300 + Math.random() * 120;
return; // Exit after deploying Ballesta
}
}
}
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' || aiDeck[w] === 'montapuercos' || aiDeck[w] === 'golem' || aiDeck[w] === 'rompemuros') {
winconditionCard = aiDeck[w];
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 : deckCard === 'hielo' ? 4 : deckCard === 'lapida' ? 3 : deckCard === 'golem' ? 8 : deckCard === 'rompemuros' ? 2 : deckCard === 'minero' ? 3 : 3;
if (aiElixir >= cardCost && deckCard !== 'fireball' && deckCard !== 'discharge' && deckCard !== 'hielo') {
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 : deckCard === 'hielo' ? 4 : deckCard === 'lapida' ? 3 : deckCard === 'golem' ? 8 : deckCard === 'rompemuros' ? 2 : deckCard === 'minero' ? 3 : 3;
if (aiElixir >= cardCost && deckCard !== 'fireball' && deckCard !== 'discharge' && deckCard !== 'hielo' && 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 : cardType === 'montapuercos' ? 4 : cardType === 'hielo' ? 4 : cardType === 'lapida' ? 3 : cardType === 'golem' ? 8 : cardType === 'rompemuros' ? 2 : cardType === 'minero' ? 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());
}
// 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();
};
// Check if AI can use a specific card based on usage restriction
function canAIUseCard(cardType) {
// If card hasn't been used yet, it can be used
if (aiCardUsageRestriction[cardType] === undefined) {
return true;
}
// If card is restricted, check if 4 different cards have been used since
return aiCardUsageRestriction[cardType] <= 0;
}
// Update AI card usage tracking when a card is used
function updateAICardUsage(usedCardType) {
// Add the used card to recently used list
if (aiUsedCards.indexOf(usedCardType) === -1) {
aiUsedCards.push(usedCardType);
}
// Set restriction for the used card (needs 4 different cards before reuse)
aiCardUsageRestriction[usedCardType] = 4;
// Reduce restriction counter for all other restricted cards
for (var cardType in aiCardUsageRestriction) {
if (cardType !== usedCardType && aiCardUsageRestriction[cardType] > 0) {
aiCardUsageRestriction[cardType]--;
}
}
} ===================================================================
--- original.js
+++ change.js
@@ -1969,9 +1969,9 @@
self.unitType = 'minero';
self.isAerial = false; // Ground unit
self.maxHealth = 600;
self.currentHealth = 600;
- self.damage = 130;
+ self.damage = 200;
self.attackSpeed = 90; // 90 ticks
self.range = 450; // Detection range
self.attackRange = 75; // Attack range
self.speed = 1.25;
@@ -2125,20 +2125,20 @@
}
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;
- // Calculate damage - 74% less against towers
+ // Calculate damage - fixed 35 damage against towers, full 200 against units
var damageToApply = self.damage;
var isTower = false;
if (self.isPlayer) {
isTower = enemyTowers.indexOf(self.target) !== -1;
} else {
isTower = playerTowers.indexOf(self.target) !== -1;
}
if (isTower) {
- damageToApply = Math.ceil(self.damage * 0.26); // 74% less damage to towers, rounded up to nearest integer
+ damageToApply = 35; // Fixed 35 damage to towers
} else {
- damageToApply = Math.ceil(damageToApply); // Round up regular damage too
+ damageToApply = self.damage; // Full 200 damage to units
}
// Melee attack - direct damage
self.target.takeDamage(damageToApply);
// Visual effect for melee strike