User prompt
all demons appear in 60 seconds
User prompt
los demonios aparezcan dentro de 12 segundos
User prompt
los demonios se tarden 12 segundos en aparecer
User prompt
haz que los demonios aparezcan en 12 segundo y aumenta en 1 casilla en rango de todos los golems y que el heavy golem cueste 250 Stones y que las Stones aparezcan mas rapido
User prompt
haz que los demonios ignoren al spikeGolem y que los golems solo puedan atacar en su propia fila y que los demonios aparezcan en 12 segundos y que el basicGolem cueste 100 Stones y que las Stones desaparescan en 5 segundos despues de aparecer ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
mejor haz que los demonios aguanten 12 disparos
User prompt
haz que los demonios puedan comerse a los golems mordiendolos 15 veces y que los demonios tarden 12 segundos en aparecer y que salgan Stones del suelo que los puedas recoger y te den 5 Stones ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz el rango del basicGolem sea de 3 casillas y los demonios aguanten 10 disparos
User prompt
haz que el spikeGolem no mate al instante a los demonios
User prompt
haz que el spikeGolem haga poco daño y que los zombies lo ignoren
User prompt
añade a un golem que sea como la pincho hierba de pvz y que los demonios sean mas resistentes
User prompt
haz que el crystalGolem haga menos daño, pero dispare mas rapido y tenga mas rango
User prompt
haz las casillas de pasto mas grandes y aumenta un poco el rango de los golems
User prompt
haz a los iconos de colocar golems mucho mas grandes y mas separados
User prompt
haz el icono de los golems mas grandes y añade una pala para quitar a los golems
User prompt
reduce la velocidad de los demonios y aumenta el rango de los golems
Code edit (1 edits merged)
Please save this source code
User prompt
Stone Guardians vs Demons
Initial prompt
puedes hacer un juego estilo plantas versus zombies, pero que sea golems de piedra versus demonio
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Demon = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'basic';
// Base stats that get modified by wave difficulty
var baseHealth = 450;
var baseSpeed = 0.3;
var baseReward = 10;
// Wave difficulty multiplier - increases health and speed
var waveMultiplier = 1 + (wave - 1) * 0.15; // 15% increase per wave
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 2.0); // Cap speed at 2x
self.reward = Math.floor(baseReward * waveMultiplier);
self.biteDamage = 1 + Math.floor((wave - 1) / 3); // Increase bite damage every 3 waves
self.biteDelay = Math.max(15, 30 - wave); // Faster biting as waves progress
self.lastBite = 0;
var assetName = 'basicDemon';
if (self.type === 'fast') {
assetName = 'fastDemon';
baseHealth = 360;
baseSpeed = 1;
baseReward = 15;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 2.5); // Fast demons can go faster
self.reward = Math.floor(baseReward * waveMultiplier);
} else if (self.type === 'tank') {
assetName = 'tankDemon';
baseHealth = 1800;
baseSpeed = 0.25;
baseReward = 25;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 1.5); // Tank demons have speed cap
self.reward = Math.floor(baseReward * waveMultiplier);
} else if (self.type === 'zombieAllStar') {
assetName = 'tankDemon'; // Using tank demon sprite
baseHealth = 1200;
baseSpeed = 0.15;
baseReward = 50;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 1.3);
self.reward = Math.floor(baseReward * waveMultiplier);
self.biteDamage = 3 + Math.floor((wave - 1) / 2); // Extra damage scaling
self.biteDelay = Math.max(10, 20 - wave); // Faster biting
} else if (self.type === 'speedy') {
assetName = 'speedyDemon';
baseHealth = 600; // Reduced resistance
baseSpeed = 1.0; // Reduced speed
baseReward = 40;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 2.5); // Reduced max speed multiplier
self.reward = Math.floor(baseReward * waveMultiplier);
self.biteDamage = 2 + Math.floor((wave - 1) / 3);
self.biteDelay = Math.max(10, 20 - wave); // Fast attacks
} else if (self.type === 'dancing') {
assetName = 'dancingDemon';
baseHealth = 600;
baseSpeed = 0.4;
baseReward = 30;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 2.0);
self.reward = Math.floor(baseReward * waveMultiplier);
self.biteDamage = 2 + Math.floor((wave - 1) / 4);
self.biteDelay = Math.max(15, 25 - wave);
self.isDancing = false;
} else if (self.type === 'nutrient') {
assetName = 'basicDemon';
baseHealth = 400;
baseSpeed = 0.35;
baseReward = 20;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 2.0);
self.reward = Math.floor(baseReward * waveMultiplier);
self.biteDamage = 1 + Math.floor((wave - 1) / 3);
self.biteDelay = Math.max(15, 30 - wave);
self.isNutrientDemon = true;
}
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.3
});
self.update = function () {
// Check for golem collision and bite them
var blocked = false;
var targetGolem = null;
for (var i = 0; i < golems.length; i++) {
var golem = golems[i];
if (golem.type !== 'spike' && self.intersects(golem)) {
blocked = true;
targetGolem = golem;
break;
}
}
if (targetGolem && LK.ticks - self.lastBite > self.biteDelay) {
// Bite the golem
if (!targetGolem.health) {
if (targetGolem.type === 'barrier') {
targetGolem.health = 160; // Barrier golems have 160 health
targetGolem.maxHealth = 160;
} else {
targetGolem.health = 15; // All other golems have 15 health
targetGolem.maxHealth = 15;
}
}
targetGolem.health -= self.biteDamage;
self.lastBite = LK.ticks;
// Visual feedback - flash golem red
tween(targetGolem, {
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(targetGolem, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
if (targetGolem.health <= 0) {
// Remove golem from grid
var golemGridCol = Math.floor((targetGolem.x - gridStartX + cellSize / 2) / cellSize);
var golemGridRow = Math.floor((targetGolem.y - gridStartY + cellSize / 2) / cellSize);
if (golemGridCol >= 0 && golemGridCol < gridCols && golemGridRow >= 0 && golemGridRow < gridRows) {
gridOccupied[golemGridRow][golemGridCol] = false;
}
// Remove from golems array
for (var j = golems.length - 1; j >= 0; j--) {
if (golems[j] === targetGolem) {
targetGolem.destroy();
golems.splice(j, 1);
break;
}
}
blocked = false; // Can continue moving after eating golem
}
}
if (!blocked) {
self.x -= self.speed;
}
// Special dancing demon effects
if (self.type === 'dancing' && !self.isDancing && Math.random() < 0.01) {
// Start dancing animation
self.isDancing = true;
var originalSpeed = self.speed;
self.speed = 0; // Stop moving while dancing
// Dancing rotation animation
var danceRotations = 0;
var maxRotations = 3;
var _danceRotate2 = function _danceRotate() {
tween(graphics, {
rotation: Math.PI * 2
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
graphics.rotation = 0; // Reset rotation
danceRotations++;
if (danceRotations < maxRotations) {
_danceRotate2();
} else {
// Dance complete, resume movement with speed boost
self.speed = originalSpeed * 1.5; // 50% speed boost after dance
self.isDancing = false;
// Speed boost lasts for 3 seconds
LK.setTimeout(function () {
if (self.health > 0) {
self.speed = originalSpeed; // Return to normal speed
}
}, 3000);
}
}
});
};
_danceRotate2();
// Color flash during dance
tween(graphics, {
tint: 0xFF00FF
}, {
duration: 200,
onFinish: function onFinish() {
tween(graphics, {
tint: 0x00FFFF
}, {
duration: 200,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xFFFF00
}, {
duration: 200,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
}
});
}
});
}
// Special zombie all star effects
if (self.type === 'zombieAllStar') {
// Scale pulsing effect when at low health
var healthPercent = self.health / self.maxHealth;
if (healthPercent < 0.5 && !self.isPulsing) {
self.isPulsing = true;
var _pulseScale = function pulseScale() {
tween(graphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.health > 0 && healthPercent < 0.5) {
_pulseScale();
} else {
self.isPulsing = false;
}
}
});
}
});
};
_pulseScale();
}
}
// Update health bar color
var healthPercent = self.health / self.maxHealth;
if (healthPercent < 0.3) {
graphics.tint = 0xFF0000;
} else if (healthPercent < 0.6) {
graphics.tint = 0xFF6600;
}
};
// Add green glowing effect for nutrient demons
if (self.type === 'nutrient') {
// Pulsing green glow effect
if (!self.isGlowing) {
self.isGlowing = true;
var _glowPulse = function glowPulse() {
tween(graphics, {
tint: 0x00FF00,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
tint: 0x88FF88,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.health > 0) {
_glowPulse();
}
}
});
}
});
};
_glowPulse();
}
}
self.takeDamage = function (damage) {
self.health -= damage;
LK.getSound('hit').play();
if (self.health <= 0) {
self.shouldRemove = true;
score += self.reward;
demonsKilledInWave++;
// Drop purple nutrient stone if this is a nutrient demon
if (self.type === 'nutrient') {
var nutrient = new NutrientStone();
nutrient.x = self.x;
nutrient.y = self.y;
nutrients.push(nutrient);
game.addChild(nutrient);
}
updateUI();
}
};
return self;
});
var Golem = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'basic';
self.lastShot = 0;
self.range = 2048; // Full screen width for entire line attack
self.damage = 25;
self.shootDelay = 60; // frames
var assetName = 'basicGolem';
if (self.type === 'heavy') {
assetName = 'heavyGolem';
self.damage = 75;
self.range = 2048; // Full screen width for entire line attack
self.shootDelay = 120;
} else if (self.type === 'crystal') {
assetName = 'crystalGolem';
self.damage = 20;
self.range = 2048; // Full screen width for entire line attack
self.shootDelay = 30;
} else if (self.type === 'barrier') {
assetName = 'barrierGolem';
self.damage = 0;
self.range = 0;
self.shootDelay = 0;
} else if (self.type === 'spike') {
assetName = 'spikeGolem';
self.damage = 10;
self.range = 0;
self.shootDelay = 0;
} else if (self.type === 'stone') {
assetName = 'stoneGolem';
self.damage = 0;
self.range = 0;
self.shootDelay = 450; // Generate stone every 7.5 seconds
} else if (self.type === 'melee') {
assetName = 'meleeGolem';
self.damage = 40;
self.range = cellSize * 2; // 2 cell radius
self.shootDelay = 0; // No projectiles, direct combat
self.meleeDelay = 30; // frames between melee attacks
self.lastMelee = 0;
} else if (self.type === 'explosive') {
assetName = 'explosiveGolem';
self.damage = 80;
self.range = cellSize * 1.5; // 1.5 cell radius for detection
self.shootDelay = 0; // No projectiles, explodes on contact
self.hasExploded = false;
}
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: self.type === 'spike' ? 0.0 : 0.3
});
self.update = function () {
if (self.type === 'barrier') {
return;
}
if (self.type === 'spike') {
// Check for contact damage with cooldown
if (!self.damageCooldown) {
self.damageCooldown = {};
}
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
if (self.intersects(demon)) {
var demonId = demon.id || i; // Use demon id or index as identifier
var currentTime = LK.ticks;
if (!self.damageCooldown[demonId] || currentTime - self.damageCooldown[demonId] > 60) {
demon.takeDamage(self.damage);
self.damageCooldown[demonId] = currentTime;
}
}
}
return;
}
if (self.type === 'stone') {
// Generate stones periodically
if (LK.ticks - self.lastShot > self.shootDelay) {
var stone = new Stone();
stone.x = self.x + (Math.random() - 0.5) * 100; // Spawn near golem
stone.y = self.y + (Math.random() - 0.5) * 100;
stone.value = 25; // Stone golem generates stones worth 25
stones_collectible.push(stone);
game.addChild(stone);
self.lastShot = LK.ticks;
}
return;
}
if (self.type === 'melee') {
// Melee combat - attack demons in 2 cell radius front and back
if (LK.ticks - self.lastMelee > self.meleeDelay) {
var golemRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
var golemCol = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
var attackedDemons = [];
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
var demonRow = Math.floor((demon.y - gridStartY + cellSize / 2) / cellSize);
var demonCol = Math.floor((demon.x - gridStartX + cellSize / 2) / cellSize);
// Check if demon is in same row and within 2 cells front or back
if (demonRow === golemRow) {
var colDistance = Math.abs(demonCol - golemCol);
if (colDistance <= 2 && colDistance > 0) {
var dx = demon.x - self.x;
var dy = demon.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
attackedDemons.push(demon);
}
}
}
}
if (attackedDemons.length > 0) {
// Attack all demons in range
for (var k = 0; k < attackedDemons.length; k++) {
var targetDemon = attackedDemons[k];
targetDemon.takeDamage(self.damage);
// Visual melee attack effect
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFF4444
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
}
self.lastMelee = LK.ticks;
LK.getSound('hit').play();
}
}
return;
}
if (self.type === 'explosive') {
// Explosive golem - explode when demon gets close
if (!self.hasExploded) {
var golemRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
var golemCol = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
var demonsInRange = [];
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
var dx = demon.x - self.x;
var dy = demon.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
demonsInRange.push(demon);
}
}
if (demonsInRange.length > 0) {
// Explode in 3x3 area
self.hasExploded = true;
// Visual explosion effect
tween(self, {
scaleX: 2.0,
scaleY: 2.0,
tint: 0xFF4400,
alpha: 0.8
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
// Remove golem from grid after explosion
var golemGridCol = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
var golemGridRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
if (golemGridCol >= 0 && golemGridCol < gridCols && golemGridRow >= 0 && golemGridRow < gridRows) {
gridOccupied[golemGridRow][golemGridCol] = false;
}
// Remove from golems array
for (var j = golems.length - 1; j >= 0; j--) {
if (golems[j] === self) {
self.destroy();
golems.splice(j, 1);
break;
}
}
}
});
}
});
// Instantly kill all demons in 3x3 area
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
var demonRow = Math.floor((demon.y - gridStartY + cellSize / 2) / cellSize);
var demonCol = Math.floor((demon.x - gridStartX + cellSize / 2) / cellSize);
// Check if demon is within 3x3 area centered on golem
var rowDistance = Math.abs(demonRow - golemRow);
var colDistance = Math.abs(demonCol - golemCol);
if (rowDistance <= 1 && colDistance <= 1) {
// Instantly kill the demon by setting health to 0
demon.health = 0;
demon.shouldRemove = true;
stones += demon.reward;
score += demon.reward;
}
}
LK.getSound('hit').play();
}
}
return;
}
// Check if special ability has expired
if (self.hasSpecialAbility && LK.ticks > self.specialAbilityEndTime) {
self.hasSpecialAbility = false;
// Return to normal color
tween(self, {
tint: 0xFFFFFF
}, {
duration: 300
});
}
var currentShootDelay = self.shootDelay;
var currentDamage = self.damage;
// Apply special ability effects
if (self.hasSpecialAbility) {
if (self.type === 'basic' || self.type === 'heavy' || self.type === 'crystal') {
// Shooting golems: double fire rate and +50% damage
currentShootDelay = Math.floor(self.shootDelay * 0.5);
currentDamage = Math.floor(self.damage * 1.5);
} else if (self.type === 'spike') {
// Spike golems: triple damage
currentDamage = self.damage * 3;
} else if (self.type === 'stone') {
// Stone golems: generate stones 3x faster
currentShootDelay = Math.floor(self.shootDelay * 0.33);
} else if (self.type === 'melee') {
// Melee golems: double attack speed and damage
self.meleeDelay = Math.floor(30 * 0.5);
currentDamage = self.damage * 2;
} else if (self.type === 'explosive') {
// Explosive golems: larger explosion range
self.range = cellSize * 2.5;
}
}
if (LK.ticks - self.lastShot > currentShootDelay) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.lastShot = LK.ticks;
}
}
};
self.findTarget = function () {
var closestDemon = null;
var closestDistance = self.range;
var golemRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
var demonRow = Math.floor((demon.y - gridStartY + cellSize / 2) / cellSize);
// Only target demons in the same row
if (demonRow !== golemRow) {
continue;
}
var dx = demon.x - self.x;
var dy = demon.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestDemon = demon;
}
}
return closestDemon;
};
self.shoot = function (target) {
var projectile = new Projectile(self.type);
projectile.x = self.x;
projectile.y = self.y;
projectile.targetX = target.x;
projectile.targetY = target.y;
// Use enhanced damage if special ability is active
if (self.hasSpecialAbility) {
projectile.damage = Math.floor(self.damage * 1.5);
} else {
projectile.damage = self.damage;
}
projectiles.push(projectile);
game.addChild(projectile);
LK.getSound('shoot').play();
};
return self;
});
var Lawnmower = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('lawnmower', {
anchorX: 0.5,
anchorY: 0.3
});
self.isActive = false;
self.speed = 8;
self.row = 0;
self.update = function () {
if (self.isActive) {
self.x += self.speed;
// Remove lawnmower when it goes off screen
if (self.x > 2200) {
self.shouldRemove = true;
}
// Check collision with demons
for (var i = demons.length - 1; i >= 0; i--) {
var demon = demons[i];
if (self.intersects(demon)) {
// Instantly kill demon
demon.health = 0;
demon.shouldRemove = true;
score += demon.reward;
demonsKilledInWave++;
}
}
}
};
self.activate = function () {
if (!self.isActive) {
self.isActive = true;
LK.getSound('shoot').play();
}
};
return self;
});
var NutrientStone = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('stone', {
anchorX: 0.5,
anchorY: 0.3
});
// Make it purple and larger
graphics.tint = 0x8B008B;
graphics.scaleX = 1.3;
graphics.scaleY = 1.3;
self.value = 1;
self.bobOffset = Math.random() * Math.PI * 2;
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60 FPS
self.isCollected = false;
self.update = function () {
// Bobbing animation with purple sparkle effect
self.y += Math.sin(LK.ticks * 0.05 + self.bobOffset) * 0.5;
// Purple sparkle effect
if (LK.ticks % 30 === 0) {
tween(graphics, {
tint: 0xFF00FF
}, {
duration: 200,
onFinish: function onFinish() {
tween(graphics, {
tint: 0x8B008B
}, {
duration: 200
});
}
});
}
// Check if stone should disappear
if (LK.ticks - self.spawnTime > self.lifespan) {
self.shouldRemove = true;
}
};
return self;
});
var Projectile = Container.expand(function (golemType) {
var self = Container.call(this);
self.golemType = golemType || 'basic';
var assetName = 'projectile';
if (self.golemType === 'heavy') {
assetName = 'heavyProjectile';
} else if (self.golemType === 'crystal') {
assetName = 'crystalProjectile';
}
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.3
});
self.damage = 25;
self.speed = 8;
self.targetX = 0;
self.targetY = 0;
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 < 10) {
self.shouldRemove = true;
return;
}
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
};
return self;
});
var Stone = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('stone', {
anchorX: 0.5,
anchorY: 0.3
});
self.value = 10;
self.bobOffset = Math.random() * Math.PI * 2;
self.spawnTime = LK.ticks;
self.lifespan = 300; // 5 seconds at 60 FPS
self.isMarkedForCollection = false;
self.collectionMarkTime = 0;
self.collectionDelay = 60; // 1 second at 60 FPS
self.update = function () {
// Bobbing animation
self.y += Math.sin(LK.ticks * 0.05 + self.bobOffset) * 0.3;
// Check if stone should disappear
if (LK.ticks - self.spawnTime > self.lifespan) {
self.shouldRemove = true;
}
};
return self;
});
var miniExplodeGolem = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('potatoMine', {
anchorX: 0.5,
anchorY: 0.0
});
self.isUnderground = true;
self.armingTime = 900; // 15 seconds to arm
self.spawnTime = LK.ticks;
self.isArmed = false;
self.hasExploded = false;
self.explosionDamage = 150; // High damage but less than explosiveGolem
// Start underground (invisible and smaller)
graphics.alpha = 0.3;
graphics.scaleX = 0.5;
graphics.scaleY = 0.5;
graphics.tint = 0x8B4513; // Brown color for underground
self.update = function () {
// Arm the mine after arming time
if (!self.isArmed && LK.ticks - self.spawnTime > self.armingTime) {
self.isArmed = true;
self.isUnderground = false;
// Surface animation - become visible and full size
tween(graphics, {
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
}
// Check for demon collision if armed and not exploded
if (self.isArmed && !self.hasExploded) {
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
if (self.intersects(demon)) {
self.explode();
break;
}
}
}
};
self.explode = function () {
if (self.hasExploded) {
return;
}
self.hasExploded = true;
// Visual explosion effect
tween(graphics, {
scaleX: 3.0,
scaleY: 3.0,
tint: 0xFF4400,
alpha: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 4.0,
scaleY: 4.0,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
// Remove from grid and golems array
var mineGridCol = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
var mineGridRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
if (mineGridCol >= 0 && mineGridCol < gridCols && mineGridRow >= 0 && mineGridRow < gridRows) {
gridOccupied[mineGridRow][mineGridCol] = false;
}
// Remove from golems array
for (var j = golems.length - 1; j >= 0; j--) {
if (golems[j] === self) {
self.destroy();
golems.splice(j, 1);
break;
}
}
}
});
}
});
// Kill all demons in a 1x1 area (single cell) around the mine
var mineRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
var mineCol = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
var demonRow = Math.floor((demon.y - gridStartY + cellSize / 2) / cellSize);
var demonCol = Math.floor((demon.x - gridStartX + cellSize / 2) / cellSize);
// Check if demon is within same cell as mine
if (demonRow === mineRow && demonCol === mineCol) {
// Damage the demon with explosion damage
demon.health -= self.explosionDamage;
if (demon.health <= 0) {
demon.shouldRemove = true;
score += demon.reward;
demonsKilledInWave++;
}
}
}
LK.getSound('hit').play();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F5233
});
/****
* Game Code
****/
// Game variables
var stones = 100;
var score = 0;
var lives = 10;
var wave = 1;
var nextWaveTimer = 0;
var waveDelay = 300; // frames between waves
// Flag wave system variables
var totalDemonsInWave = 0;
var demonsKilledInWave = 0;
var flagWaves = [5, 10, 15, 20]; // Waves with massive demon spawns
var isFlagWave = false;
var flagWaveSpawned = false;
// Game arrays
var golems = [];
var demons = [];
var projectiles = [];
var stones_collectible = [];
var lawnmowers = [];
var nutrients = [];
// Special ability system
var specialAbilityActive = false;
var specialAbilityEndTime = 0;
var specialAbilityDuration = 300; // 5 seconds at 60 FPS
// Nutrient stone inventory system
var nutrientInventory = []; // Max 3 stones
var maxNutrientSlots = 3;
var selectedNutrientSlot = -1; // -1 means no slot selected
// Stone spawning - gets slower as waves progress
var lastStoneSpawn = 0;
var baseStoneSpawnDelay = 150;
var stoneSpawnDelay = baseStoneSpawnDelay + (wave - 1) * 20; // Slower stone spawning as waves progress
// Demon spawn cooldown
var demonSpawnCooldown = 300; // 5 seconds between demon spawns
var lastDemonSpawn = 0;
// Initial game delay - demons don't spawn for first 12 seconds
var gameStartDelay = 720; // 12 seconds at 60 FPS
var gameStartTime = 0;
var demonsCanSpawn = false;
// Grid system
var gridCols = 12;
var gridRows = 5;
var gridStartX = 224;
var gridStartY = 800;
var cellSize = 200;
// Golem costs
var golemCosts = {
basic: 100,
heavy: 250,
crystal: 75,
barrier: 80,
spike: 60,
stone: 50,
melee: 120,
explosive: 150,
miniExplodeGolem: 25,
shovel: 0
};
// Selected golem type
var selectedGolemType = 'basic';
// Double-tap detection variables
var lastClickedButton = null;
var lastClickTime = 0;
var doubleTapThreshold = 500; // 500ms window for double tap
// Golem placement cooldown system
var golemPlacementCooldown = {};
var golemCooldownDuration = 180; // 3 seconds at 60 FPS - base duration
var lastGolemPlacement = 0;
// Function to get cooldown duration based on golem cost
function getGolemCooldownDuration(golemType) {
var cost = golemCosts[golemType];
if (cost === 0) {
return 0;
} // No cooldown for free items like shovel
// More expensive golems have longer cooldowns
// Base formula: cost * 1.5 frames (at 60 FPS)
return Math.floor(cost * 1.5);
}
function updateNutrientUI() {
// Update nutrient title to show current count
nutrientTitle.setText('Nutrientes: ' + nutrientInventory.length + '/3');
// Clear existing nutrient visuals
for (var s = 0; s < maxNutrientSlots; s++) {
// Start with gray/opaque for empty slots, purple for selected
if (s < nutrientInventory.length) {
nutrientSlotBg[s].tint = selectedNutrientSlot === s ? 0xFF00FF : 0x8B008B;
nutrientSlotBg[s].alpha = 1.0;
} else {
nutrientSlotBg[s].tint = 0x666666; // Gray for empty slots
nutrientSlotBg[s].alpha = 0.6; // More opaque
}
// Remove old nutrient stones from slots
for (var c = nutrientSlotBg[s].children.length - 1; c >= 0; c--) {
nutrientSlotBg[s].children[c].destroy();
}
}
// Add nutrient stones to occupied slots
for (var i = 0; i < nutrientInventory.length; i++) {
var nutrientStone = LK.getAsset('stone', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0x8B008B
});
nutrientStone.x = 0;
nutrientStone.y = 0;
nutrientSlotBg[i].addChild(nutrientStone);
}
}
function applyNutrientToGolem(golem) {
if (golem.type === 'barrier') {
// Barrier golems get permanent durability boost
if (!golem.health) {
golem.health = 160;
golem.maxHealth = 160;
}
golem.health += 80; // Permanent +80 health boost
golem.maxHealth += 80;
// Visual enhancement for barrier
tween(golem, {
tint: 0x4444FF,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300
});
} else {
// Other golems get temporary special abilities
golem.hasSpecialAbility = true;
golem.specialAbilityEndTime = LK.ticks + specialAbilityDuration;
// Visual enhancement
tween(golem, {
tint: 0xFF8800
}, {
duration: 300
});
}
// Show effect text
var effectText = new Text2('¡POTENCIADO!', {
size: 30,
fill: 0x8B008B
});
effectText.anchor.set(0.5, 0.5);
effectText.x = golem.x;
effectText.y = golem.y - 50;
game.addChild(effectText);
// Animate effect text
tween(effectText, {
y: golem.y - 100,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
effectText.destroy();
}
});
}
function activateSpecialAbilities() {
specialAbilityActive = true;
specialAbilityEndTime = LK.ticks + specialAbilityDuration;
// Show special ability activation message
var abilityText = new Text2('¡PODER ESPECIAL ACTIVADO!', {
size: 50,
fill: 0x8B008B
});
abilityText.anchor.set(0.5, 0.5);
abilityText.x = 2048 / 2;
abilityText.y = 300;
game.addChild(abilityText);
// Animate ability text
tween(abilityText, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFF00FF
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(abilityText, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 1500,
onFinish: function onFinish() {
abilityText.destroy();
}
});
}
});
// Apply special effects to all golems
for (var i = 0; i < golems.length; i++) {
var golem = golems[i];
if (golem.type === 'barrier') {
// Barrier golems get permanent durability boost
if (!golem.health) {
golem.health = 160;
golem.maxHealth = 160;
}
golem.health += 80; // Permanent +80 health boost
golem.maxHealth += 80;
// Visual enhancement for barrier
tween(golem, {
tint: 0x4444FF,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300
});
} else {
// Other golems get temporary special abilities
golem.hasSpecialAbility = true;
golem.specialAbilityEndTime = specialAbilityEndTime;
// Visual enhancement
tween(golem, {
tint: 0xFF8800
}, {
duration: 300,
onFinish: function onFinish() {
// Keep enhanced color during ability duration
}
});
}
}
}
// UI elements
var stonesText = new Text2('Stones: 100', {
size: 40,
fill: 0xFFFFFF
});
stonesText.anchor.set(0, 0);
LK.gui.topLeft.addChild(stonesText);
stonesText.x = 120;
stonesText.y = 20;
var livesText = new Text2('Lives: 10', {
size: 40,
fill: 0xFFFFFF
});
livesText.anchor.set(0, 0);
LK.gui.topLeft.addChild(livesText);
livesText.x = 120;
livesText.y = 70;
var waveText = new Text2('Wave: 1', {
size: 40,
fill: 0xFFFFFF
});
waveText.anchor.set(0, 0);
LK.gui.topLeft.addChild(waveText);
waveText.x = 120;
waveText.y = 120;
var scoreText = new Text2('Score: 0', {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -20;
scoreText.y = 20;
// Wave progress bar
var progressBarBg = LK.getAsset('gridCell', {
scaleX: 12,
scaleY: 0.3,
alpha: 0.3,
tint: 0x444444
});
progressBarBg.x = 2048 / 2;
progressBarBg.y = 50;
game.addChild(progressBarBg);
var progressBarFill = LK.getAsset('gridCell', {
scaleX: 0,
scaleY: 0.3,
tint: 0x00FF00
});
progressBarFill.x = 2048 / 2 - 1200;
progressBarFill.y = 50;
game.addChild(progressBarFill);
var progressText = new Text2('Wave 1 Progress', {
size: 30,
fill: 0xFFFFFF
});
progressText.anchor.set(0.5, 0.5);
progressText.x = 2048 / 2;
progressText.y = 80;
game.addChild(progressText);
// Create nutrient inventory slots
var nutrientSlots = [];
var nutrientSlotBg = [];
for (var s = 0; s < maxNutrientSlots; s++) {
// Background for slot
var slotBg = LK.getAsset('gridCell', {
scaleX: 0.4,
scaleY: 0.4,
alpha: 0.5,
tint: 0x333333
});
slotBg.x = 2048 - 200 - s * 100;
slotBg.y = 200;
LK.gui.topRight.addChild(slotBg);
nutrientSlotBg.push(slotBg);
// Slot for nutrient stone (initially empty)
nutrientSlots.push(null);
}
// Nutrient inventory title
var nutrientTitle = new Text2('Nutrientes: 0/3', {
size: 50,
fill: 0x8B008B
});
nutrientTitle.anchor.set(1, 0);
nutrientTitle.x = -20;
nutrientTitle.y = 140;
LK.gui.topRight.addChild(nutrientTitle);
// Cooldown status text
var cooldownText = new Text2('', {
size: 30,
fill: 0xFF6600
});
cooldownText.anchor.set(0.5, 0.5);
cooldownText.x = 2048 / 2;
cooldownText.y = 200;
game.addChild(cooldownText);
// Golem selection buttons
var buttonY = 200;
var buttonSpacing = 150;
var basicButton = LK.getAsset('basicGolem', {
scaleX: 1.2,
scaleY: 1.2
});
basicButton.x = 50;
basicButton.y = buttonY;
LK.gui.topLeft.addChild(basicButton);
var heavyButton = LK.getAsset('heavyGolem', {
scaleX: 1.0,
scaleY: 1.0
});
heavyButton.x = 50;
heavyButton.y = buttonY + buttonSpacing;
LK.gui.topLeft.addChild(heavyButton);
var crystalButton = LK.getAsset('crystalGolem', {
scaleX: 1.1,
scaleY: 1.1
});
crystalButton.x = 50;
crystalButton.y = buttonY + buttonSpacing * 2;
LK.gui.topLeft.addChild(crystalButton);
var barrierButton = LK.getAsset('barrierGolem', {
scaleX: 0.9,
scaleY: 1.0
});
barrierButton.x = 50;
barrierButton.y = buttonY + buttonSpacing * 3;
LK.gui.topLeft.addChild(barrierButton);
var spikeButton = LK.getAsset('spikeGolem', {
scaleX: 1.2,
scaleY: 1.2
});
spikeButton.x = 50;
spikeButton.y = buttonY + buttonSpacing * 4;
LK.gui.topLeft.addChild(spikeButton);
var stoneButton = LK.getAsset('stoneGolem', {
scaleX: 1.0,
scaleY: 1.0
});
stoneButton.x = 50;
stoneButton.y = buttonY + buttonSpacing * 5;
LK.gui.topLeft.addChild(stoneButton);
var meleeButton = LK.getAsset('meleeGolem', {
scaleX: 1.0,
scaleY: 1.0
});
meleeButton.x = 50;
meleeButton.y = buttonY + buttonSpacing * 6;
LK.gui.topLeft.addChild(meleeButton);
var explosiveButton = LK.getAsset('explosiveGolem', {
scaleX: 1.0,
scaleY: 1.0
});
explosiveButton.x = 50;
explosiveButton.y = buttonY + buttonSpacing * 7;
LK.gui.topLeft.addChild(explosiveButton);
var miniExplodeGolemButton = LK.getAsset('potatoMine', {
scaleX: 1.0,
scaleY: 1.0
});
miniExplodeGolemButton.x = 50;
miniExplodeGolemButton.y = buttonY + buttonSpacing * 8;
LK.gui.topLeft.addChild(miniExplodeGolemButton);
var shovelButton = LK.getAsset('shovel', {
scaleX: 1.0,
scaleY: 1.0
});
shovelButton.x = 50;
shovelButton.y = buttonY + buttonSpacing * 9;
LK.gui.topLeft.addChild(shovelButton);
// Cost labels
var basicCostText = new Text2('100', {
size: 24,
fill: 0xFFFF00
});
basicCostText.x = 150;
basicCostText.y = buttonY + 20;
LK.gui.topLeft.addChild(basicCostText);
var heavyCostText = new Text2('250', {
size: 24,
fill: 0xFFFF00
});
heavyCostText.x = 150;
heavyCostText.y = buttonY + buttonSpacing + 20;
LK.gui.topLeft.addChild(heavyCostText);
var crystalCostText = new Text2('75', {
size: 24,
fill: 0xFFFF00
});
crystalCostText.x = 150;
crystalCostText.y = buttonY + buttonSpacing * 2 + 20;
LK.gui.topLeft.addChild(crystalCostText);
var barrierCostText = new Text2('80', {
size: 24,
fill: 0xFFFF00
});
barrierCostText.x = 150;
barrierCostText.y = buttonY + buttonSpacing * 3 + 20;
LK.gui.topLeft.addChild(barrierCostText);
var spikeCostText = new Text2('60', {
size: 24,
fill: 0xFFFF00
});
spikeCostText.x = 150;
spikeCostText.y = buttonY + buttonSpacing * 4 + 20;
LK.gui.topLeft.addChild(spikeCostText);
var stoneCostText = new Text2('50', {
size: 24,
fill: 0xFFFF00
});
stoneCostText.x = 150;
stoneCostText.y = buttonY + buttonSpacing * 5 + 20;
LK.gui.topLeft.addChild(stoneCostText);
var meleeCostText = new Text2('120', {
size: 24,
fill: 0xFFFF00
});
meleeCostText.x = 150;
meleeCostText.y = buttonY + buttonSpacing * 6 + 20;
LK.gui.topLeft.addChild(meleeCostText);
var explosiveCostText = new Text2('150', {
size: 24,
fill: 0xFFFF00
});
explosiveCostText.x = 150;
explosiveCostText.y = buttonY + buttonSpacing * 7 + 20;
LK.gui.topLeft.addChild(explosiveCostText);
var miniExplodeGolemCostText = new Text2('25', {
size: 24,
fill: 0xFFFF00
});
miniExplodeGolemCostText.x = 150;
miniExplodeGolemCostText.y = buttonY + buttonSpacing * 8 + 20;
LK.gui.topLeft.addChild(miniExplodeGolemCostText);
var shovelCostText = new Text2('Remove', {
size: 20,
fill: 0xFFFF00
});
shovelCostText.x = 150;
shovelCostText.y = buttonY + buttonSpacing * 9 + 20;
LK.gui.topLeft.addChild(shovelCostText);
// Cooldown text displays for buttons
var basicCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
basicCooldownText.anchor.set(0.5, 0.5);
basicCooldownText.x = 50;
basicCooldownText.y = buttonY - 20;
basicCooldownText.alpha = 0;
LK.gui.topLeft.addChild(basicCooldownText);
var heavyCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
heavyCooldownText.anchor.set(0.5, 0.5);
heavyCooldownText.x = 50;
heavyCooldownText.y = buttonY + buttonSpacing - 20;
heavyCooldownText.alpha = 0;
LK.gui.topLeft.addChild(heavyCooldownText);
var crystalCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
crystalCooldownText.anchor.set(0.5, 0.5);
crystalCooldownText.x = 50;
crystalCooldownText.y = buttonY + buttonSpacing * 2 - 20;
crystalCooldownText.alpha = 0;
LK.gui.topLeft.addChild(crystalCooldownText);
var barrierCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
barrierCooldownText.anchor.set(0.5, 0.5);
barrierCooldownText.x = 50;
barrierCooldownText.y = buttonY + buttonSpacing * 3 - 20;
barrierCooldownText.alpha = 0;
LK.gui.topLeft.addChild(barrierCooldownText);
var spikeCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
spikeCooldownText.anchor.set(0.5, 0.5);
spikeCooldownText.x = 50;
spikeCooldownText.y = buttonY + buttonSpacing * 4 - 20;
spikeCooldownText.alpha = 0;
LK.gui.topLeft.addChild(spikeCooldownText);
var stoneCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
stoneCooldownText.anchor.set(0.5, 0.5);
stoneCooldownText.x = 50;
stoneCooldownText.y = buttonY + buttonSpacing * 5 - 20;
stoneCooldownText.alpha = 0;
LK.gui.topLeft.addChild(stoneCooldownText);
var meleeCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
meleeCooldownText.anchor.set(0.5, 0.5);
meleeCooldownText.x = 50;
meleeCooldownText.y = buttonY + buttonSpacing * 6 - 20;
meleeCooldownText.alpha = 0;
LK.gui.topLeft.addChild(meleeCooldownText);
var explosiveCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
explosiveCooldownText.anchor.set(0.5, 0.5);
explosiveCooldownText.x = 50;
explosiveCooldownText.y = buttonY + buttonSpacing * 7 - 20;
explosiveCooldownText.alpha = 0;
LK.gui.topLeft.addChild(explosiveCooldownText);
var miniExplodeGolemCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
miniExplodeGolemCooldownText.anchor.set(0.5, 0.5);
miniExplodeGolemCooldownText.x = 50;
miniExplodeGolemCooldownText.y = buttonY + buttonSpacing * 8 - 20;
miniExplodeGolemCooldownText.alpha = 0;
LK.gui.topLeft.addChild(miniExplodeGolemCooldownText);
// Create grid visualization
var gridCells = [];
for (var row = 0; row < gridRows; row++) {
gridCells[row] = [];
for (var col = 0; col < gridCols; col++) {
var cell = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.3,
alpha: 0.3,
tint: row === 0 ? 0x87CEEB : 0xFFFFFF // Sky blue for first row
});
cell.x = gridStartX + col * cellSize;
cell.y = gridStartY + row * cellSize;
cell.gridX = col;
cell.gridY = row;
gridCells[row][col] = cell;
game.addChild(cell);
}
}
// Grid occupied tracking
var gridOccupied = [];
for (var row = 0; row < gridRows; row++) {
gridOccupied[row] = [];
for (var col = 0; col < gridCols; col++) {
gridOccupied[row][col] = false;
}
}
function updateUI() {
stonesText.setText('Stones: ' + stones);
livesText.setText('Lives: ' + lives);
waveText.setText('Wave: ' + wave);
scoreText.setText('Score: ' + score);
var currentTime = LK.ticks;
// Update button availability based on cost and cooldown
function getButtonAlpha(golemType, cost) {
var hasEnoughStones = stones >= cost;
var cooldownDuration = getGolemCooldownDuration(golemType);
var isOnCooldown = golemPlacementCooldown[golemType] && currentTime - golemPlacementCooldown[golemType] < cooldownDuration;
return hasEnoughStones && !isOnCooldown ? 1.0 : 0.5;
}
basicButton.alpha = getButtonAlpha('basic', golemCosts.basic);
heavyButton.alpha = getButtonAlpha('heavy', golemCosts.heavy);
crystalButton.alpha = getButtonAlpha('crystal', golemCosts.crystal);
barrierButton.alpha = getButtonAlpha('barrier', golemCosts.barrier);
spikeButton.alpha = getButtonAlpha('spike', golemCosts.spike);
stoneButton.alpha = getButtonAlpha('stone', golemCosts.stone);
meleeButton.alpha = getButtonAlpha('melee', golemCosts.melee);
explosiveButton.alpha = getButtonAlpha('explosive', golemCosts.explosive);
miniExplodeGolemButton.alpha = getButtonAlpha('miniExplodeGolem', golemCosts.miniExplodeGolem);
shovelButton.alpha = 1.0; // Shovel is always available
// Update cooldown displays
function updateCooldownDisplay(golemType, cooldownText) {
var cooldownDuration = getGolemCooldownDuration(golemType);
if (golemPlacementCooldown[golemType] && currentTime - golemPlacementCooldown[golemType] < cooldownDuration) {
var remainingTime = Math.ceil((cooldownDuration - (currentTime - golemPlacementCooldown[golemType])) / 60);
cooldownText.setText(remainingTime + 's');
cooldownText.alpha = 1;
} else {
cooldownText.alpha = 0;
}
}
updateCooldownDisplay('basic', basicCooldownText);
updateCooldownDisplay('heavy', heavyCooldownText);
updateCooldownDisplay('crystal', crystalCooldownText);
updateCooldownDisplay('barrier', barrierCooldownText);
updateCooldownDisplay('spike', spikeCooldownText);
updateCooldownDisplay('stone', stoneCooldownText);
updateCooldownDisplay('melee', meleeCooldownText);
updateCooldownDisplay('explosive', explosiveCooldownText);
updateCooldownDisplay('miniExplodeGolem', miniExplodeGolemCooldownText);
}
function spawnWave() {
// Don't spawn demons if initial delay hasn't passed
if (!demonsCanSpawn) {
return;
}
// Check if this is a flag wave
isFlagWave = flagWaves.indexOf(wave) !== -1;
flagWaveSpawned = false;
var demonsToSpawn;
if (isFlagWave) {
// Flag waves spawn 3x more demons
demonsToSpawn = Math.floor((2 + Math.floor(wave * 1.5)) * 3);
} else {
// Dramatically increase demons per wave - starts at 3, increases significantly
demonsToSpawn = Math.max(3, 2 + Math.floor(wave * 1.5)); // More aggressive scaling
}
totalDemonsInWave = demonsToSpawn;
demonsKilledInWave = 0;
var spawnDelay = 300; // 5 seconds delay (300 frames at 60 FPS)
// Decrease spawn delay between demons as waves progress
var delayBetweenSpawns = Math.max(150, 450 - wave * 15); // Faster spawning in later waves
// Flag waves spawn demons much faster
if (isFlagWave) {
delayBetweenSpawns = Math.max(60, delayBetweenSpawns / 2);
}
for (var i = 0; i < demonsToSpawn; i++) {
LK.setTimeout(function () {
// Check if enough time has passed since last demon spawn
if (LK.ticks - lastDemonSpawn >= demonSpawnCooldown) {
var demonType = 'basic';
// More aggressive probability scaling based on wave
var waveScaling = Math.min(wave / 10, 1.0); // Scale from 0 to 1 over 10 waves
// Flag waves have higher chance of special demons
var flagMultiplier = isFlagWave ? 1.5 : 1.0;
if (wave > 2 && Math.random() < (0.2 + waveScaling * 0.4) * flagMultiplier) {
demonType = 'fast';
} // Up to 60% chance
if (wave > 3 && Math.random() < (0.15 + waveScaling * 0.3) * flagMultiplier) {
demonType = 'dancing';
} // Up to 45% chance
if (wave > 4 && Math.random() < (0.1 + waveScaling * 0.4) * flagMultiplier) {
demonType = 'tank';
} // Up to 50% chance
if (wave > 5 && Math.random() < (0.05 + waveScaling * 0.3) * flagMultiplier) {
demonType = 'speedy';
} // Up to 35% chance
// Add elite demon spawning for high waves
if (wave > 7 && Math.random() < (0.05 + waveScaling * 0.15) * flagMultiplier) {
demonType = 'zombieAllStar';
} // Up to 20% chance
// Add nutrient demons starting from wave 3
if (wave > 2 && Math.random() < 0.15) {
demonType = 'nutrient';
} // 15% chance for nutrient demons
var demon = new Demon(demonType);
demon.x = 2048;
demon.y = gridStartY + Math.floor(Math.random() * gridRows) * cellSize;
demons.push(demon);
game.addChild(demon);
lastDemonSpawn = LK.ticks;
}
}, spawnDelay);
spawnDelay += delayBetweenSpawns; // Use variable delay that decreases with wave
}
}
function getGridPosition(x, y) {
var col = Math.floor((x - gridStartX + cellSize / 2) / cellSize);
var row = Math.floor((y - gridStartY + cellSize / 2) / cellSize);
if (col >= 0 && col < gridCols && row >= 0 && row < gridRows) {
return {
col: col,
row: row
};
}
return null;
}
// Button click handlers
basicButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'basic' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.basic) {
selectedGolemType = 'basic';
lastClickedButton = 'basic';
lastClickTime = currentTime;
updateButtonSelection();
}
};
heavyButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'heavy' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.heavy) {
selectedGolemType = 'heavy';
lastClickedButton = 'heavy';
lastClickTime = currentTime;
updateButtonSelection();
}
};
crystalButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'crystal' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.crystal) {
selectedGolemType = 'crystal';
lastClickedButton = 'crystal';
lastClickTime = currentTime;
updateButtonSelection();
}
};
barrierButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'barrier' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.barrier) {
selectedGolemType = 'barrier';
lastClickedButton = 'barrier';
lastClickTime = currentTime;
updateButtonSelection();
}
};
spikeButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'spike' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.spike) {
selectedGolemType = 'spike';
lastClickedButton = 'spike';
lastClickTime = currentTime;
updateButtonSelection();
}
};
stoneButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'stone' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.stone) {
selectedGolemType = 'stone';
lastClickedButton = 'stone';
lastClickTime = currentTime;
updateButtonSelection();
}
};
meleeButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'melee' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.melee) {
selectedGolemType = 'melee';
lastClickedButton = 'melee';
lastClickTime = currentTime;
updateButtonSelection();
}
};
explosiveButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'explosive' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.explosive) {
selectedGolemType = 'explosive';
lastClickedButton = 'explosive';
lastClickTime = currentTime;
updateButtonSelection();
}
};
miniExplodeGolemButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'miniExplodeGolem' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.miniExplodeGolem) {
selectedGolemType = 'miniExplodeGolem';
lastClickedButton = 'miniExplodeGolem';
lastClickTime = currentTime;
updateButtonSelection();
}
};
shovelButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'shovel' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else {
selectedGolemType = 'shovel';
lastClickedButton = 'shovel';
lastClickTime = currentTime;
updateButtonSelection();
}
};
function updateButtonSelection() {
// If no golem is selected, all buttons return to normal color
basicButton.tint = selectedGolemType === 'basic' ? 0x00FF00 : 0xFFFFFF;
heavyButton.tint = selectedGolemType === 'heavy' ? 0x00FF00 : 0xFFFFFF;
crystalButton.tint = selectedGolemType === 'crystal' ? 0x00FF00 : 0xFFFFFF;
barrierButton.tint = selectedGolemType === 'barrier' ? 0x00FF00 : 0xFFFFFF;
spikeButton.tint = selectedGolemType === 'spike' ? 0x00FF00 : 0xFFFFFF;
stoneButton.tint = selectedGolemType === 'stone' ? 0x00FF00 : 0xFFFFFF;
meleeButton.tint = selectedGolemType === 'melee' ? 0x00FF00 : 0xFFFFFF;
explosiveButton.tint = selectedGolemType === 'explosive' ? 0x00FF00 : 0xFFFFFF;
miniExplodeGolemButton.tint = selectedGolemType === 'miniExplodeGolem' ? 0x00FF00 : 0xFFFFFF;
shovelButton.tint = selectedGolemType === 'shovel' ? 0x00FF00 : 0xFFFFFF;
}
// Initialize button selection
updateButtonSelection();
// Initialize lawnmowers
for (var row = 0; row < gridRows; row++) {
var lawnmower = new Lawnmower();
lawnmower.x = 300;
lawnmower.y = gridStartY + row * cellSize;
lawnmower.row = row;
lawnmowers.push(lawnmower);
game.addChild(lawnmower);
}
// Game click handler
game.down = function (x, y, obj) {
// Check if clicking on a nutrient stone first
for (var n = 0; n < nutrients.length; n++) {
var nutrient = nutrients[n];
var dx = x - nutrient.x;
var dy = y - nutrient.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 50 && nutrientInventory.length < maxNutrientSlots) {
// Collect the nutrient stone
tween(nutrient, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
// Add to inventory
nutrientInventory.push(1);
updateNutrientUI();
nutrient.destroy();
}
});
nutrients.splice(n, 1);
return; // Exit early, don't process grid placement
}
}
// Check if clicking on nutrient inventory slots
var slotClicked = -1;
for (var s = 0; s < maxNutrientSlots; s++) {
var slotX = 2048 - 200 - s * 100;
var slotY = 200;
var globalSlotPos = LK.gui.topRight.toGlobal({
x: slotX,
y: slotY
});
var dx = x - globalSlotPos.x;
var dy = y - globalSlotPos.y;
if (Math.abs(dx) < 40 && Math.abs(dy) < 40 && s < nutrientInventory.length) {
slotClicked = s;
break;
}
}
if (slotClicked >= 0) {
selectedNutrientSlot = selectedNutrientSlot === slotClicked ? -1 : slotClicked;
updateNutrientUI();
return;
}
var gridPos = getGridPosition(x, y);
if (gridPos) {
if (selectedGolemType === 'shovel') {
// Remove golem if one exists at this position
if (gridOccupied[gridPos.row][gridPos.col]) {
for (var i = golems.length - 1; i >= 0; i--) {
var golem = golems[i];
var golemGridCol = Math.floor((golem.x - gridStartX + cellSize / 2) / cellSize);
var golemGridRow = Math.floor((golem.y - gridStartY + cellSize / 2) / cellSize);
if (golemGridCol === gridPos.col && golemGridRow === gridPos.row) {
golem.destroy();
golems.splice(i, 1);
gridOccupied[gridPos.row][gridPos.col] = false;
LK.getSound('remove').play();
break;
}
}
}
} else if (!gridOccupied[gridPos.row][gridPos.col]) {
// Check if we have a nutrient selected and there's a golem here
if (selectedNutrientSlot >= 0 && gridOccupied[gridPos.row][gridPos.col]) {
// Find the golem at this position
for (var g = 0; g < golems.length; g++) {
var golem = golems[g];
var golemGridCol = Math.floor((golem.x - gridStartX + cellSize / 2) / cellSize);
var golemGridRow = Math.floor((golem.y - gridStartY + cellSize / 2) / cellSize);
if (golemGridCol === gridPos.col && golemGridRow === gridPos.row) {
// Apply nutrient stone to this golem
applyNutrientToGolem(golem);
// Remove nutrient from inventory
nutrientInventory.splice(selectedNutrientSlot, 1);
selectedNutrientSlot = -1;
updateNutrientUI();
return;
}
}
}
var cost = golemCosts[selectedGolemType];
var currentTime = LK.ticks;
var cooldownKey = selectedGolemType;
var cooldownDuration = getGolemCooldownDuration(selectedGolemType);
// Check if golem type is on cooldown
if (golemPlacementCooldown[cooldownKey] && currentTime - golemPlacementCooldown[cooldownKey] < cooldownDuration) {
var remainingTime = Math.ceil((cooldownDuration - (currentTime - golemPlacementCooldown[cooldownKey])) / 60);
cooldownText.setText('Espera ' + remainingTime + 's para colocar ' + selectedGolemType);
cooldownText.alpha = 1;
tween(cooldownText, {
alpha: 0
}, {
duration: 2000
});
return;
}
if (stones >= cost) {
var golem;
if (selectedGolemType === 'miniExplodeGolem') {
golem = new miniExplodeGolem();
} else {
golem = new Golem(selectedGolemType);
}
golem.x = gridStartX + gridPos.col * cellSize;
golem.y = gridStartY + gridPos.row * cellSize;
golems.push(golem);
game.addChild(golem);
gridOccupied[gridPos.row][gridPos.col] = true;
stones -= cost;
golemPlacementCooldown[cooldownKey] = currentTime;
lastGolemPlacement = currentTime;
updateUI();
LK.getSound('place').play();
}
}
}
};
// Countdown sequence before first wave
var countdownText = new Text2('', {
size: 80,
fill: 0xFFFFFF
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 2048 / 2;
countdownText.y = 2732 / 2;
game.addChild(countdownText);
// Show "preparado..." at 4 seconds
LK.setTimeout(function () {
countdownText.setText('preparado...');
countdownText.alpha = 1;
// Fade in effect
tween(countdownText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(countdownText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}, 4000);
// Show "listo..." at 8 seconds
LK.setTimeout(function () {
countdownText.setText('listo...');
// Scale and color effect
tween(countdownText, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFFFF00
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(countdownText, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
}, 8000);
// Show "¡PELEA!" at 12 seconds and start wave
LK.setTimeout(function () {
countdownText.setText('¡PELEA!');
// Dramatic entrance effect
tween(countdownText, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFF0000
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Enable demon spawning after PELEA text is shown
demonsCanSpawn = true;
// Start first wave immediately after PELEA
spawnWave();
// Fade out after showing PELEA
tween(countdownText, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
countdownText.destroy();
}
});
}
});
}, 12000);
game.update = function () {
// Update golems
for (var i = golems.length - 1; i >= 0; i--) {
var golem = golems[i];
if (golem.update) {
golem.update();
}
}
// Update demons
for (var i = demons.length - 1; i >= 0; i--) {
var demon = demons[i];
demon.update();
// Check if demon reached left side
if (demon.x < 350) {
// Check if there's a lawnmower in this row to activate
var demonRow = Math.floor((demon.y - gridStartY + cellSize / 2) / cellSize);
var lawnmowerActivated = false;
for (var l = 0; l < lawnmowers.length; l++) {
var lawnmower = lawnmowers[l];
if (lawnmower.row === demonRow && !lawnmower.isActive) {
lawnmower.activate();
lawnmowerActivated = true;
break;
}
}
if (!lawnmowerActivated) {
lives--;
demon.destroy();
demons.splice(i, 1);
updateUI();
if (lives <= 0) {
LK.setScore(score);
LK.showGameOver();
return;
}
}
} else if (demon.shouldRemove) {
demon.destroy();
demons.splice(i, 1);
}
}
// Update lawnmowers
for (var i = lawnmowers.length - 1; i >= 0; i--) {
var lawnmower = lawnmowers[i];
lawnmower.update();
if (lawnmower.shouldRemove) {
lawnmower.destroy();
lawnmowers.splice(i, 1);
}
}
// Update progress bar
if (totalDemonsInWave > 0) {
var progress = demonsKilledInWave / totalDemonsInWave;
var targetScale = progress * 12;
tween.stop(progressBarFill, {
scaleX: true
});
tween(progressBarFill, {
scaleX: targetScale
}, {
duration: 200
});
// Update progress text with flag wave indicator
var progressPercentage = Math.floor(progress * 100);
var flagText = isFlagWave ? " (BANDERA!)" : "";
progressText.setText('Wave ' + wave + ': ' + progressPercentage + '%' + flagText);
// Change progress bar color for flag waves
if (isFlagWave) {
progressBarFill.tint = 0xFF4444; // Red for flag waves
progressText.tint = 0xFF4444;
} else {
progressBarFill.tint = 0x00FF00; // Green for normal waves
progressText.tint = 0xFFFFFF;
}
}
// Update projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
projectile.update();
if (projectile.shouldRemove) {
projectile.destroy();
projectiles.splice(i, 1);
continue;
}
// Check projectile vs demon collision
for (var j = demons.length - 1; j >= 0; j--) {
var demon = demons[j];
if (projectile.intersects(demon)) {
demon.takeDamage(projectile.damage);
// Heavy golem projectiles have 25% chance to push demons backwards
if (projectile.golemType === 'heavy' && Math.random() < 0.25) {
// Push demon backwards (to the right) by 100 pixels
tween(demon, {
x: demon.x + 100
}, {
duration: 300,
easing: tween.easeOut
});
}
projectile.destroy();
projectiles.splice(i, 1);
break;
}
}
}
// Update nutrients - no auto collection, manual only
for (var i = nutrients.length - 1; i >= 0; i--) {
var nutrient = nutrients[i];
nutrient.update();
if (nutrient.shouldRemove) {
nutrient.destroy();
nutrients.splice(i, 1);
continue;
}
}
// Update stones with automatic collection only
for (var i = stones_collectible.length - 1; i >= 0; i--) {
var stone = stones_collectible[i];
stone.update();
if (stone.shouldRemove) {
stone.destroy();
stones_collectible.splice(i, 1);
continue;
}
// Auto-collect stones after 2 seconds (no manual collection)
if (!stone.isMarkedForCollection && LK.ticks - stone.spawnTime > 120) {
stone.isMarkedForCollection = true;
stone.collectionMarkTime = LK.ticks;
// Visual indicator - slight glow effect
tween(stone, {
tint: 0xFFFF88
}, {
duration: 200
});
}
// Check if marked stone should be collected after delay
if (stone.isMarkedForCollection && LK.ticks - stone.collectionMarkTime >= stone.collectionDelay) {
// Animate stone collection
tween(stone, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
stones += stone.value;
stone.destroy();
updateUI();
}
});
stones_collectible.splice(i, 1);
}
}
// Update stone spawn delay based on current wave
stoneSpawnDelay = baseStoneSpawnDelay + (wave - 1) * 20;
// Spawn stones periodically
if (LK.ticks - lastStoneSpawn > stoneSpawnDelay) {
var stone = new Stone();
stone.x = 400 + Math.random() * (2048 - 800); // Spawn in playable area
stone.y = 600 + Math.random() * 400;
stones_collectible.push(stone);
game.addChild(stone);
lastStoneSpawn = LK.ticks;
}
// Wave management
if (demons.length === 0 && demonsCanSpawn) {
nextWaveTimer++;
if (nextWaveTimer >= waveDelay) {
wave++;
nextWaveTimer = 0;
// Show flag wave announcement
if (flagWaves.indexOf(wave) !== -1) {
var flagText = new Text2('¡OLEADA BANDERA!', {
size: 60,
fill: 0xFF0000
});
flagText.anchor.set(0.5, 0.5);
flagText.x = 2048 / 2;
flagText.y = 400;
game.addChild(flagText);
// Animate flag announcement
tween(flagText, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFFFF00
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(flagText, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 1000,
onFinish: function onFinish() {
flagText.destroy();
}
});
}
});
}
spawnWave();
updateUI();
// Victory condition - now goes to wave 25
if (wave > 25) {
LK.setScore(score);
LK.showYouWin();
return;
}
}
}
};
// Initialize game start time
gameStartTime = LK.ticks;
// Add decorative elements outside garden
// Rocks scattered around the edges with rock texture
for (var i = 0; i < 15; i++) {
var rock = LK.getAsset('rockTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8 + Math.random() * 1.0,
scaleY: 0.8 + Math.random() * 1.0,
tint: 0x8B7355 // Natural rock brown color
});
// Position rocks outside garden area
if (Math.random() < 0.5) {
// Left side decorations
rock.x = 50 + Math.random() * 150;
rock.y = 700 + Math.random() * 800;
} else {
// Right side decorations
rock.x = 2600 + Math.random() * 300;
rock.y = 700 + Math.random() * 800;
}
game.addChild(rock);
}
// Grass patches for decoration with grass texture
for (var i = 0; i < 20; i++) {
var grass = LK.getAsset('grassTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8 + Math.random() * 0.6,
scaleY: 0.6 + Math.random() * 0.4,
alpha: 0.7,
tint: 0x32CD32 // Lime green for natural grass
});
// Scatter grass around the garden
if (Math.random() < 0.3) {
// Above garden
grass.x = 400 + Math.random() * 2000;
grass.y = 600 + Math.random() * 150;
} else if (Math.random() < 0.5) {
// Below garden
grass.x = 400 + Math.random() * 2000;
grass.y = 1800 + Math.random() * 200;
} else {
// Left and right sides
grass.x = Math.random() < 0.5 ? 100 + Math.random() * 100 : 2700 + Math.random() * 200;
grass.y = 700 + Math.random() * 1000;
}
game.addChild(grass);
}
// Small decorative stones near garden edges with rock texture
for (var i = 0; i < 8; i++) {
var smallStone = LK.getAsset('rockTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0x696969 // Darker gray for small stones
});
// Position near garden borders
smallStone.x = gridStartX - 50 + Math.random() * (gridCols * cellSize + 100);
smallStone.y = Math.random() < 0.5 ? gridStartY - 100 : gridStartY + gridRows * cellSize + 50;
game.addChild(smallStone);
}
// Initialize UI
updateUI();
updateNutrientUI(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Demon = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'basic';
// Base stats that get modified by wave difficulty
var baseHealth = 450;
var baseSpeed = 0.3;
var baseReward = 10;
// Wave difficulty multiplier - increases health and speed
var waveMultiplier = 1 + (wave - 1) * 0.15; // 15% increase per wave
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 2.0); // Cap speed at 2x
self.reward = Math.floor(baseReward * waveMultiplier);
self.biteDamage = 1 + Math.floor((wave - 1) / 3); // Increase bite damage every 3 waves
self.biteDelay = Math.max(15, 30 - wave); // Faster biting as waves progress
self.lastBite = 0;
var assetName = 'basicDemon';
if (self.type === 'fast') {
assetName = 'fastDemon';
baseHealth = 360;
baseSpeed = 1;
baseReward = 15;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 2.5); // Fast demons can go faster
self.reward = Math.floor(baseReward * waveMultiplier);
} else if (self.type === 'tank') {
assetName = 'tankDemon';
baseHealth = 1800;
baseSpeed = 0.25;
baseReward = 25;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 1.5); // Tank demons have speed cap
self.reward = Math.floor(baseReward * waveMultiplier);
} else if (self.type === 'zombieAllStar') {
assetName = 'tankDemon'; // Using tank demon sprite
baseHealth = 1200;
baseSpeed = 0.15;
baseReward = 50;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 1.3);
self.reward = Math.floor(baseReward * waveMultiplier);
self.biteDamage = 3 + Math.floor((wave - 1) / 2); // Extra damage scaling
self.biteDelay = Math.max(10, 20 - wave); // Faster biting
} else if (self.type === 'speedy') {
assetName = 'speedyDemon';
baseHealth = 600; // Reduced resistance
baseSpeed = 1.0; // Reduced speed
baseReward = 40;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 2.5); // Reduced max speed multiplier
self.reward = Math.floor(baseReward * waveMultiplier);
self.biteDamage = 2 + Math.floor((wave - 1) / 3);
self.biteDelay = Math.max(10, 20 - wave); // Fast attacks
} else if (self.type === 'dancing') {
assetName = 'dancingDemon';
baseHealth = 600;
baseSpeed = 0.4;
baseReward = 30;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 2.0);
self.reward = Math.floor(baseReward * waveMultiplier);
self.biteDamage = 2 + Math.floor((wave - 1) / 4);
self.biteDelay = Math.max(15, 25 - wave);
self.isDancing = false;
} else if (self.type === 'nutrient') {
assetName = 'basicDemon';
baseHealth = 400;
baseSpeed = 0.35;
baseReward = 20;
self.health = Math.floor(baseHealth * waveMultiplier);
self.maxHealth = self.health;
self.speed = baseSpeed * Math.min(waveMultiplier, 2.0);
self.reward = Math.floor(baseReward * waveMultiplier);
self.biteDamage = 1 + Math.floor((wave - 1) / 3);
self.biteDelay = Math.max(15, 30 - wave);
self.isNutrientDemon = true;
}
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.3
});
self.update = function () {
// Check for golem collision and bite them
var blocked = false;
var targetGolem = null;
for (var i = 0; i < golems.length; i++) {
var golem = golems[i];
if (golem.type !== 'spike' && self.intersects(golem)) {
blocked = true;
targetGolem = golem;
break;
}
}
if (targetGolem && LK.ticks - self.lastBite > self.biteDelay) {
// Bite the golem
if (!targetGolem.health) {
if (targetGolem.type === 'barrier') {
targetGolem.health = 160; // Barrier golems have 160 health
targetGolem.maxHealth = 160;
} else {
targetGolem.health = 15; // All other golems have 15 health
targetGolem.maxHealth = 15;
}
}
targetGolem.health -= self.biteDamage;
self.lastBite = LK.ticks;
// Visual feedback - flash golem red
tween(targetGolem, {
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(targetGolem, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
if (targetGolem.health <= 0) {
// Remove golem from grid
var golemGridCol = Math.floor((targetGolem.x - gridStartX + cellSize / 2) / cellSize);
var golemGridRow = Math.floor((targetGolem.y - gridStartY + cellSize / 2) / cellSize);
if (golemGridCol >= 0 && golemGridCol < gridCols && golemGridRow >= 0 && golemGridRow < gridRows) {
gridOccupied[golemGridRow][golemGridCol] = false;
}
// Remove from golems array
for (var j = golems.length - 1; j >= 0; j--) {
if (golems[j] === targetGolem) {
targetGolem.destroy();
golems.splice(j, 1);
break;
}
}
blocked = false; // Can continue moving after eating golem
}
}
if (!blocked) {
self.x -= self.speed;
}
// Special dancing demon effects
if (self.type === 'dancing' && !self.isDancing && Math.random() < 0.01) {
// Start dancing animation
self.isDancing = true;
var originalSpeed = self.speed;
self.speed = 0; // Stop moving while dancing
// Dancing rotation animation
var danceRotations = 0;
var maxRotations = 3;
var _danceRotate2 = function _danceRotate() {
tween(graphics, {
rotation: Math.PI * 2
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
graphics.rotation = 0; // Reset rotation
danceRotations++;
if (danceRotations < maxRotations) {
_danceRotate2();
} else {
// Dance complete, resume movement with speed boost
self.speed = originalSpeed * 1.5; // 50% speed boost after dance
self.isDancing = false;
// Speed boost lasts for 3 seconds
LK.setTimeout(function () {
if (self.health > 0) {
self.speed = originalSpeed; // Return to normal speed
}
}, 3000);
}
}
});
};
_danceRotate2();
// Color flash during dance
tween(graphics, {
tint: 0xFF00FF
}, {
duration: 200,
onFinish: function onFinish() {
tween(graphics, {
tint: 0x00FFFF
}, {
duration: 200,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xFFFF00
}, {
duration: 200,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
}
});
}
});
}
// Special zombie all star effects
if (self.type === 'zombieAllStar') {
// Scale pulsing effect when at low health
var healthPercent = self.health / self.maxHealth;
if (healthPercent < 0.5 && !self.isPulsing) {
self.isPulsing = true;
var _pulseScale = function pulseScale() {
tween(graphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.health > 0 && healthPercent < 0.5) {
_pulseScale();
} else {
self.isPulsing = false;
}
}
});
}
});
};
_pulseScale();
}
}
// Update health bar color
var healthPercent = self.health / self.maxHealth;
if (healthPercent < 0.3) {
graphics.tint = 0xFF0000;
} else if (healthPercent < 0.6) {
graphics.tint = 0xFF6600;
}
};
// Add green glowing effect for nutrient demons
if (self.type === 'nutrient') {
// Pulsing green glow effect
if (!self.isGlowing) {
self.isGlowing = true;
var _glowPulse = function glowPulse() {
tween(graphics, {
tint: 0x00FF00,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
tint: 0x88FF88,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.health > 0) {
_glowPulse();
}
}
});
}
});
};
_glowPulse();
}
}
self.takeDamage = function (damage) {
self.health -= damage;
LK.getSound('hit').play();
if (self.health <= 0) {
self.shouldRemove = true;
score += self.reward;
demonsKilledInWave++;
// Drop purple nutrient stone if this is a nutrient demon
if (self.type === 'nutrient') {
var nutrient = new NutrientStone();
nutrient.x = self.x;
nutrient.y = self.y;
nutrients.push(nutrient);
game.addChild(nutrient);
}
updateUI();
}
};
return self;
});
var Golem = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'basic';
self.lastShot = 0;
self.range = 2048; // Full screen width for entire line attack
self.damage = 25;
self.shootDelay = 60; // frames
var assetName = 'basicGolem';
if (self.type === 'heavy') {
assetName = 'heavyGolem';
self.damage = 75;
self.range = 2048; // Full screen width for entire line attack
self.shootDelay = 120;
} else if (self.type === 'crystal') {
assetName = 'crystalGolem';
self.damage = 20;
self.range = 2048; // Full screen width for entire line attack
self.shootDelay = 30;
} else if (self.type === 'barrier') {
assetName = 'barrierGolem';
self.damage = 0;
self.range = 0;
self.shootDelay = 0;
} else if (self.type === 'spike') {
assetName = 'spikeGolem';
self.damage = 10;
self.range = 0;
self.shootDelay = 0;
} else if (self.type === 'stone') {
assetName = 'stoneGolem';
self.damage = 0;
self.range = 0;
self.shootDelay = 450; // Generate stone every 7.5 seconds
} else if (self.type === 'melee') {
assetName = 'meleeGolem';
self.damage = 40;
self.range = cellSize * 2; // 2 cell radius
self.shootDelay = 0; // No projectiles, direct combat
self.meleeDelay = 30; // frames between melee attacks
self.lastMelee = 0;
} else if (self.type === 'explosive') {
assetName = 'explosiveGolem';
self.damage = 80;
self.range = cellSize * 1.5; // 1.5 cell radius for detection
self.shootDelay = 0; // No projectiles, explodes on contact
self.hasExploded = false;
}
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: self.type === 'spike' ? 0.0 : 0.3
});
self.update = function () {
if (self.type === 'barrier') {
return;
}
if (self.type === 'spike') {
// Check for contact damage with cooldown
if (!self.damageCooldown) {
self.damageCooldown = {};
}
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
if (self.intersects(demon)) {
var demonId = demon.id || i; // Use demon id or index as identifier
var currentTime = LK.ticks;
if (!self.damageCooldown[demonId] || currentTime - self.damageCooldown[demonId] > 60) {
demon.takeDamage(self.damage);
self.damageCooldown[demonId] = currentTime;
}
}
}
return;
}
if (self.type === 'stone') {
// Generate stones periodically
if (LK.ticks - self.lastShot > self.shootDelay) {
var stone = new Stone();
stone.x = self.x + (Math.random() - 0.5) * 100; // Spawn near golem
stone.y = self.y + (Math.random() - 0.5) * 100;
stone.value = 25; // Stone golem generates stones worth 25
stones_collectible.push(stone);
game.addChild(stone);
self.lastShot = LK.ticks;
}
return;
}
if (self.type === 'melee') {
// Melee combat - attack demons in 2 cell radius front and back
if (LK.ticks - self.lastMelee > self.meleeDelay) {
var golemRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
var golemCol = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
var attackedDemons = [];
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
var demonRow = Math.floor((demon.y - gridStartY + cellSize / 2) / cellSize);
var demonCol = Math.floor((demon.x - gridStartX + cellSize / 2) / cellSize);
// Check if demon is in same row and within 2 cells front or back
if (demonRow === golemRow) {
var colDistance = Math.abs(demonCol - golemCol);
if (colDistance <= 2 && colDistance > 0) {
var dx = demon.x - self.x;
var dy = demon.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
attackedDemons.push(demon);
}
}
}
}
if (attackedDemons.length > 0) {
// Attack all demons in range
for (var k = 0; k < attackedDemons.length; k++) {
var targetDemon = attackedDemons[k];
targetDemon.takeDamage(self.damage);
// Visual melee attack effect
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFF4444
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
}
self.lastMelee = LK.ticks;
LK.getSound('hit').play();
}
}
return;
}
if (self.type === 'explosive') {
// Explosive golem - explode when demon gets close
if (!self.hasExploded) {
var golemRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
var golemCol = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
var demonsInRange = [];
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
var dx = demon.x - self.x;
var dy = demon.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
demonsInRange.push(demon);
}
}
if (demonsInRange.length > 0) {
// Explode in 3x3 area
self.hasExploded = true;
// Visual explosion effect
tween(self, {
scaleX: 2.0,
scaleY: 2.0,
tint: 0xFF4400,
alpha: 0.8
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
// Remove golem from grid after explosion
var golemGridCol = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
var golemGridRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
if (golemGridCol >= 0 && golemGridCol < gridCols && golemGridRow >= 0 && golemGridRow < gridRows) {
gridOccupied[golemGridRow][golemGridCol] = false;
}
// Remove from golems array
for (var j = golems.length - 1; j >= 0; j--) {
if (golems[j] === self) {
self.destroy();
golems.splice(j, 1);
break;
}
}
}
});
}
});
// Instantly kill all demons in 3x3 area
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
var demonRow = Math.floor((demon.y - gridStartY + cellSize / 2) / cellSize);
var demonCol = Math.floor((demon.x - gridStartX + cellSize / 2) / cellSize);
// Check if demon is within 3x3 area centered on golem
var rowDistance = Math.abs(demonRow - golemRow);
var colDistance = Math.abs(demonCol - golemCol);
if (rowDistance <= 1 && colDistance <= 1) {
// Instantly kill the demon by setting health to 0
demon.health = 0;
demon.shouldRemove = true;
stones += demon.reward;
score += demon.reward;
}
}
LK.getSound('hit').play();
}
}
return;
}
// Check if special ability has expired
if (self.hasSpecialAbility && LK.ticks > self.specialAbilityEndTime) {
self.hasSpecialAbility = false;
// Return to normal color
tween(self, {
tint: 0xFFFFFF
}, {
duration: 300
});
}
var currentShootDelay = self.shootDelay;
var currentDamage = self.damage;
// Apply special ability effects
if (self.hasSpecialAbility) {
if (self.type === 'basic' || self.type === 'heavy' || self.type === 'crystal') {
// Shooting golems: double fire rate and +50% damage
currentShootDelay = Math.floor(self.shootDelay * 0.5);
currentDamage = Math.floor(self.damage * 1.5);
} else if (self.type === 'spike') {
// Spike golems: triple damage
currentDamage = self.damage * 3;
} else if (self.type === 'stone') {
// Stone golems: generate stones 3x faster
currentShootDelay = Math.floor(self.shootDelay * 0.33);
} else if (self.type === 'melee') {
// Melee golems: double attack speed and damage
self.meleeDelay = Math.floor(30 * 0.5);
currentDamage = self.damage * 2;
} else if (self.type === 'explosive') {
// Explosive golems: larger explosion range
self.range = cellSize * 2.5;
}
}
if (LK.ticks - self.lastShot > currentShootDelay) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.lastShot = LK.ticks;
}
}
};
self.findTarget = function () {
var closestDemon = null;
var closestDistance = self.range;
var golemRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
var demonRow = Math.floor((demon.y - gridStartY + cellSize / 2) / cellSize);
// Only target demons in the same row
if (demonRow !== golemRow) {
continue;
}
var dx = demon.x - self.x;
var dy = demon.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestDemon = demon;
}
}
return closestDemon;
};
self.shoot = function (target) {
var projectile = new Projectile(self.type);
projectile.x = self.x;
projectile.y = self.y;
projectile.targetX = target.x;
projectile.targetY = target.y;
// Use enhanced damage if special ability is active
if (self.hasSpecialAbility) {
projectile.damage = Math.floor(self.damage * 1.5);
} else {
projectile.damage = self.damage;
}
projectiles.push(projectile);
game.addChild(projectile);
LK.getSound('shoot').play();
};
return self;
});
var Lawnmower = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('lawnmower', {
anchorX: 0.5,
anchorY: 0.3
});
self.isActive = false;
self.speed = 8;
self.row = 0;
self.update = function () {
if (self.isActive) {
self.x += self.speed;
// Remove lawnmower when it goes off screen
if (self.x > 2200) {
self.shouldRemove = true;
}
// Check collision with demons
for (var i = demons.length - 1; i >= 0; i--) {
var demon = demons[i];
if (self.intersects(demon)) {
// Instantly kill demon
demon.health = 0;
demon.shouldRemove = true;
score += demon.reward;
demonsKilledInWave++;
}
}
}
};
self.activate = function () {
if (!self.isActive) {
self.isActive = true;
LK.getSound('shoot').play();
}
};
return self;
});
var NutrientStone = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('stone', {
anchorX: 0.5,
anchorY: 0.3
});
// Make it purple and larger
graphics.tint = 0x8B008B;
graphics.scaleX = 1.3;
graphics.scaleY = 1.3;
self.value = 1;
self.bobOffset = Math.random() * Math.PI * 2;
self.spawnTime = LK.ticks;
self.lifespan = 600; // 10 seconds at 60 FPS
self.isCollected = false;
self.update = function () {
// Bobbing animation with purple sparkle effect
self.y += Math.sin(LK.ticks * 0.05 + self.bobOffset) * 0.5;
// Purple sparkle effect
if (LK.ticks % 30 === 0) {
tween(graphics, {
tint: 0xFF00FF
}, {
duration: 200,
onFinish: function onFinish() {
tween(graphics, {
tint: 0x8B008B
}, {
duration: 200
});
}
});
}
// Check if stone should disappear
if (LK.ticks - self.spawnTime > self.lifespan) {
self.shouldRemove = true;
}
};
return self;
});
var Projectile = Container.expand(function (golemType) {
var self = Container.call(this);
self.golemType = golemType || 'basic';
var assetName = 'projectile';
if (self.golemType === 'heavy') {
assetName = 'heavyProjectile';
} else if (self.golemType === 'crystal') {
assetName = 'crystalProjectile';
}
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.3
});
self.damage = 25;
self.speed = 8;
self.targetX = 0;
self.targetY = 0;
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 < 10) {
self.shouldRemove = true;
return;
}
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
};
return self;
});
var Stone = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('stone', {
anchorX: 0.5,
anchorY: 0.3
});
self.value = 10;
self.bobOffset = Math.random() * Math.PI * 2;
self.spawnTime = LK.ticks;
self.lifespan = 300; // 5 seconds at 60 FPS
self.isMarkedForCollection = false;
self.collectionMarkTime = 0;
self.collectionDelay = 60; // 1 second at 60 FPS
self.update = function () {
// Bobbing animation
self.y += Math.sin(LK.ticks * 0.05 + self.bobOffset) * 0.3;
// Check if stone should disappear
if (LK.ticks - self.spawnTime > self.lifespan) {
self.shouldRemove = true;
}
};
return self;
});
var miniExplodeGolem = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('potatoMine', {
anchorX: 0.5,
anchorY: 0.0
});
self.isUnderground = true;
self.armingTime = 900; // 15 seconds to arm
self.spawnTime = LK.ticks;
self.isArmed = false;
self.hasExploded = false;
self.explosionDamage = 150; // High damage but less than explosiveGolem
// Start underground (invisible and smaller)
graphics.alpha = 0.3;
graphics.scaleX = 0.5;
graphics.scaleY = 0.5;
graphics.tint = 0x8B4513; // Brown color for underground
self.update = function () {
// Arm the mine after arming time
if (!self.isArmed && LK.ticks - self.spawnTime > self.armingTime) {
self.isArmed = true;
self.isUnderground = false;
// Surface animation - become visible and full size
tween(graphics, {
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
}
// Check for demon collision if armed and not exploded
if (self.isArmed && !self.hasExploded) {
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
if (self.intersects(demon)) {
self.explode();
break;
}
}
}
};
self.explode = function () {
if (self.hasExploded) {
return;
}
self.hasExploded = true;
// Visual explosion effect
tween(graphics, {
scaleX: 3.0,
scaleY: 3.0,
tint: 0xFF4400,
alpha: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 4.0,
scaleY: 4.0,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
// Remove from grid and golems array
var mineGridCol = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
var mineGridRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
if (mineGridCol >= 0 && mineGridCol < gridCols && mineGridRow >= 0 && mineGridRow < gridRows) {
gridOccupied[mineGridRow][mineGridCol] = false;
}
// Remove from golems array
for (var j = golems.length - 1; j >= 0; j--) {
if (golems[j] === self) {
self.destroy();
golems.splice(j, 1);
break;
}
}
}
});
}
});
// Kill all demons in a 1x1 area (single cell) around the mine
var mineRow = Math.floor((self.y - gridStartY + cellSize / 2) / cellSize);
var mineCol = Math.floor((self.x - gridStartX + cellSize / 2) / cellSize);
for (var i = 0; i < demons.length; i++) {
var demon = demons[i];
var demonRow = Math.floor((demon.y - gridStartY + cellSize / 2) / cellSize);
var demonCol = Math.floor((demon.x - gridStartX + cellSize / 2) / cellSize);
// Check if demon is within same cell as mine
if (demonRow === mineRow && demonCol === mineCol) {
// Damage the demon with explosion damage
demon.health -= self.explosionDamage;
if (demon.health <= 0) {
demon.shouldRemove = true;
score += demon.reward;
demonsKilledInWave++;
}
}
}
LK.getSound('hit').play();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F5233
});
/****
* Game Code
****/
// Game variables
var stones = 100;
var score = 0;
var lives = 10;
var wave = 1;
var nextWaveTimer = 0;
var waveDelay = 300; // frames between waves
// Flag wave system variables
var totalDemonsInWave = 0;
var demonsKilledInWave = 0;
var flagWaves = [5, 10, 15, 20]; // Waves with massive demon spawns
var isFlagWave = false;
var flagWaveSpawned = false;
// Game arrays
var golems = [];
var demons = [];
var projectiles = [];
var stones_collectible = [];
var lawnmowers = [];
var nutrients = [];
// Special ability system
var specialAbilityActive = false;
var specialAbilityEndTime = 0;
var specialAbilityDuration = 300; // 5 seconds at 60 FPS
// Nutrient stone inventory system
var nutrientInventory = []; // Max 3 stones
var maxNutrientSlots = 3;
var selectedNutrientSlot = -1; // -1 means no slot selected
// Stone spawning - gets slower as waves progress
var lastStoneSpawn = 0;
var baseStoneSpawnDelay = 150;
var stoneSpawnDelay = baseStoneSpawnDelay + (wave - 1) * 20; // Slower stone spawning as waves progress
// Demon spawn cooldown
var demonSpawnCooldown = 300; // 5 seconds between demon spawns
var lastDemonSpawn = 0;
// Initial game delay - demons don't spawn for first 12 seconds
var gameStartDelay = 720; // 12 seconds at 60 FPS
var gameStartTime = 0;
var demonsCanSpawn = false;
// Grid system
var gridCols = 12;
var gridRows = 5;
var gridStartX = 224;
var gridStartY = 800;
var cellSize = 200;
// Golem costs
var golemCosts = {
basic: 100,
heavy: 250,
crystal: 75,
barrier: 80,
spike: 60,
stone: 50,
melee: 120,
explosive: 150,
miniExplodeGolem: 25,
shovel: 0
};
// Selected golem type
var selectedGolemType = 'basic';
// Double-tap detection variables
var lastClickedButton = null;
var lastClickTime = 0;
var doubleTapThreshold = 500; // 500ms window for double tap
// Golem placement cooldown system
var golemPlacementCooldown = {};
var golemCooldownDuration = 180; // 3 seconds at 60 FPS - base duration
var lastGolemPlacement = 0;
// Function to get cooldown duration based on golem cost
function getGolemCooldownDuration(golemType) {
var cost = golemCosts[golemType];
if (cost === 0) {
return 0;
} // No cooldown for free items like shovel
// More expensive golems have longer cooldowns
// Base formula: cost * 1.5 frames (at 60 FPS)
return Math.floor(cost * 1.5);
}
function updateNutrientUI() {
// Update nutrient title to show current count
nutrientTitle.setText('Nutrientes: ' + nutrientInventory.length + '/3');
// Clear existing nutrient visuals
for (var s = 0; s < maxNutrientSlots; s++) {
// Start with gray/opaque for empty slots, purple for selected
if (s < nutrientInventory.length) {
nutrientSlotBg[s].tint = selectedNutrientSlot === s ? 0xFF00FF : 0x8B008B;
nutrientSlotBg[s].alpha = 1.0;
} else {
nutrientSlotBg[s].tint = 0x666666; // Gray for empty slots
nutrientSlotBg[s].alpha = 0.6; // More opaque
}
// Remove old nutrient stones from slots
for (var c = nutrientSlotBg[s].children.length - 1; c >= 0; c--) {
nutrientSlotBg[s].children[c].destroy();
}
}
// Add nutrient stones to occupied slots
for (var i = 0; i < nutrientInventory.length; i++) {
var nutrientStone = LK.getAsset('stone', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0x8B008B
});
nutrientStone.x = 0;
nutrientStone.y = 0;
nutrientSlotBg[i].addChild(nutrientStone);
}
}
function applyNutrientToGolem(golem) {
if (golem.type === 'barrier') {
// Barrier golems get permanent durability boost
if (!golem.health) {
golem.health = 160;
golem.maxHealth = 160;
}
golem.health += 80; // Permanent +80 health boost
golem.maxHealth += 80;
// Visual enhancement for barrier
tween(golem, {
tint: 0x4444FF,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300
});
} else {
// Other golems get temporary special abilities
golem.hasSpecialAbility = true;
golem.specialAbilityEndTime = LK.ticks + specialAbilityDuration;
// Visual enhancement
tween(golem, {
tint: 0xFF8800
}, {
duration: 300
});
}
// Show effect text
var effectText = new Text2('¡POTENCIADO!', {
size: 30,
fill: 0x8B008B
});
effectText.anchor.set(0.5, 0.5);
effectText.x = golem.x;
effectText.y = golem.y - 50;
game.addChild(effectText);
// Animate effect text
tween(effectText, {
y: golem.y - 100,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
effectText.destroy();
}
});
}
function activateSpecialAbilities() {
specialAbilityActive = true;
specialAbilityEndTime = LK.ticks + specialAbilityDuration;
// Show special ability activation message
var abilityText = new Text2('¡PODER ESPECIAL ACTIVADO!', {
size: 50,
fill: 0x8B008B
});
abilityText.anchor.set(0.5, 0.5);
abilityText.x = 2048 / 2;
abilityText.y = 300;
game.addChild(abilityText);
// Animate ability text
tween(abilityText, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFF00FF
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(abilityText, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 1500,
onFinish: function onFinish() {
abilityText.destroy();
}
});
}
});
// Apply special effects to all golems
for (var i = 0; i < golems.length; i++) {
var golem = golems[i];
if (golem.type === 'barrier') {
// Barrier golems get permanent durability boost
if (!golem.health) {
golem.health = 160;
golem.maxHealth = 160;
}
golem.health += 80; // Permanent +80 health boost
golem.maxHealth += 80;
// Visual enhancement for barrier
tween(golem, {
tint: 0x4444FF,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300
});
} else {
// Other golems get temporary special abilities
golem.hasSpecialAbility = true;
golem.specialAbilityEndTime = specialAbilityEndTime;
// Visual enhancement
tween(golem, {
tint: 0xFF8800
}, {
duration: 300,
onFinish: function onFinish() {
// Keep enhanced color during ability duration
}
});
}
}
}
// UI elements
var stonesText = new Text2('Stones: 100', {
size: 40,
fill: 0xFFFFFF
});
stonesText.anchor.set(0, 0);
LK.gui.topLeft.addChild(stonesText);
stonesText.x = 120;
stonesText.y = 20;
var livesText = new Text2('Lives: 10', {
size: 40,
fill: 0xFFFFFF
});
livesText.anchor.set(0, 0);
LK.gui.topLeft.addChild(livesText);
livesText.x = 120;
livesText.y = 70;
var waveText = new Text2('Wave: 1', {
size: 40,
fill: 0xFFFFFF
});
waveText.anchor.set(0, 0);
LK.gui.topLeft.addChild(waveText);
waveText.x = 120;
waveText.y = 120;
var scoreText = new Text2('Score: 0', {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -20;
scoreText.y = 20;
// Wave progress bar
var progressBarBg = LK.getAsset('gridCell', {
scaleX: 12,
scaleY: 0.3,
alpha: 0.3,
tint: 0x444444
});
progressBarBg.x = 2048 / 2;
progressBarBg.y = 50;
game.addChild(progressBarBg);
var progressBarFill = LK.getAsset('gridCell', {
scaleX: 0,
scaleY: 0.3,
tint: 0x00FF00
});
progressBarFill.x = 2048 / 2 - 1200;
progressBarFill.y = 50;
game.addChild(progressBarFill);
var progressText = new Text2('Wave 1 Progress', {
size: 30,
fill: 0xFFFFFF
});
progressText.anchor.set(0.5, 0.5);
progressText.x = 2048 / 2;
progressText.y = 80;
game.addChild(progressText);
// Create nutrient inventory slots
var nutrientSlots = [];
var nutrientSlotBg = [];
for (var s = 0; s < maxNutrientSlots; s++) {
// Background for slot
var slotBg = LK.getAsset('gridCell', {
scaleX: 0.4,
scaleY: 0.4,
alpha: 0.5,
tint: 0x333333
});
slotBg.x = 2048 - 200 - s * 100;
slotBg.y = 200;
LK.gui.topRight.addChild(slotBg);
nutrientSlotBg.push(slotBg);
// Slot for nutrient stone (initially empty)
nutrientSlots.push(null);
}
// Nutrient inventory title
var nutrientTitle = new Text2('Nutrientes: 0/3', {
size: 50,
fill: 0x8B008B
});
nutrientTitle.anchor.set(1, 0);
nutrientTitle.x = -20;
nutrientTitle.y = 140;
LK.gui.topRight.addChild(nutrientTitle);
// Cooldown status text
var cooldownText = new Text2('', {
size: 30,
fill: 0xFF6600
});
cooldownText.anchor.set(0.5, 0.5);
cooldownText.x = 2048 / 2;
cooldownText.y = 200;
game.addChild(cooldownText);
// Golem selection buttons
var buttonY = 200;
var buttonSpacing = 150;
var basicButton = LK.getAsset('basicGolem', {
scaleX: 1.2,
scaleY: 1.2
});
basicButton.x = 50;
basicButton.y = buttonY;
LK.gui.topLeft.addChild(basicButton);
var heavyButton = LK.getAsset('heavyGolem', {
scaleX: 1.0,
scaleY: 1.0
});
heavyButton.x = 50;
heavyButton.y = buttonY + buttonSpacing;
LK.gui.topLeft.addChild(heavyButton);
var crystalButton = LK.getAsset('crystalGolem', {
scaleX: 1.1,
scaleY: 1.1
});
crystalButton.x = 50;
crystalButton.y = buttonY + buttonSpacing * 2;
LK.gui.topLeft.addChild(crystalButton);
var barrierButton = LK.getAsset('barrierGolem', {
scaleX: 0.9,
scaleY: 1.0
});
barrierButton.x = 50;
barrierButton.y = buttonY + buttonSpacing * 3;
LK.gui.topLeft.addChild(barrierButton);
var spikeButton = LK.getAsset('spikeGolem', {
scaleX: 1.2,
scaleY: 1.2
});
spikeButton.x = 50;
spikeButton.y = buttonY + buttonSpacing * 4;
LK.gui.topLeft.addChild(spikeButton);
var stoneButton = LK.getAsset('stoneGolem', {
scaleX: 1.0,
scaleY: 1.0
});
stoneButton.x = 50;
stoneButton.y = buttonY + buttonSpacing * 5;
LK.gui.topLeft.addChild(stoneButton);
var meleeButton = LK.getAsset('meleeGolem', {
scaleX: 1.0,
scaleY: 1.0
});
meleeButton.x = 50;
meleeButton.y = buttonY + buttonSpacing * 6;
LK.gui.topLeft.addChild(meleeButton);
var explosiveButton = LK.getAsset('explosiveGolem', {
scaleX: 1.0,
scaleY: 1.0
});
explosiveButton.x = 50;
explosiveButton.y = buttonY + buttonSpacing * 7;
LK.gui.topLeft.addChild(explosiveButton);
var miniExplodeGolemButton = LK.getAsset('potatoMine', {
scaleX: 1.0,
scaleY: 1.0
});
miniExplodeGolemButton.x = 50;
miniExplodeGolemButton.y = buttonY + buttonSpacing * 8;
LK.gui.topLeft.addChild(miniExplodeGolemButton);
var shovelButton = LK.getAsset('shovel', {
scaleX: 1.0,
scaleY: 1.0
});
shovelButton.x = 50;
shovelButton.y = buttonY + buttonSpacing * 9;
LK.gui.topLeft.addChild(shovelButton);
// Cost labels
var basicCostText = new Text2('100', {
size: 24,
fill: 0xFFFF00
});
basicCostText.x = 150;
basicCostText.y = buttonY + 20;
LK.gui.topLeft.addChild(basicCostText);
var heavyCostText = new Text2('250', {
size: 24,
fill: 0xFFFF00
});
heavyCostText.x = 150;
heavyCostText.y = buttonY + buttonSpacing + 20;
LK.gui.topLeft.addChild(heavyCostText);
var crystalCostText = new Text2('75', {
size: 24,
fill: 0xFFFF00
});
crystalCostText.x = 150;
crystalCostText.y = buttonY + buttonSpacing * 2 + 20;
LK.gui.topLeft.addChild(crystalCostText);
var barrierCostText = new Text2('80', {
size: 24,
fill: 0xFFFF00
});
barrierCostText.x = 150;
barrierCostText.y = buttonY + buttonSpacing * 3 + 20;
LK.gui.topLeft.addChild(barrierCostText);
var spikeCostText = new Text2('60', {
size: 24,
fill: 0xFFFF00
});
spikeCostText.x = 150;
spikeCostText.y = buttonY + buttonSpacing * 4 + 20;
LK.gui.topLeft.addChild(spikeCostText);
var stoneCostText = new Text2('50', {
size: 24,
fill: 0xFFFF00
});
stoneCostText.x = 150;
stoneCostText.y = buttonY + buttonSpacing * 5 + 20;
LK.gui.topLeft.addChild(stoneCostText);
var meleeCostText = new Text2('120', {
size: 24,
fill: 0xFFFF00
});
meleeCostText.x = 150;
meleeCostText.y = buttonY + buttonSpacing * 6 + 20;
LK.gui.topLeft.addChild(meleeCostText);
var explosiveCostText = new Text2('150', {
size: 24,
fill: 0xFFFF00
});
explosiveCostText.x = 150;
explosiveCostText.y = buttonY + buttonSpacing * 7 + 20;
LK.gui.topLeft.addChild(explosiveCostText);
var miniExplodeGolemCostText = new Text2('25', {
size: 24,
fill: 0xFFFF00
});
miniExplodeGolemCostText.x = 150;
miniExplodeGolemCostText.y = buttonY + buttonSpacing * 8 + 20;
LK.gui.topLeft.addChild(miniExplodeGolemCostText);
var shovelCostText = new Text2('Remove', {
size: 20,
fill: 0xFFFF00
});
shovelCostText.x = 150;
shovelCostText.y = buttonY + buttonSpacing * 9 + 20;
LK.gui.topLeft.addChild(shovelCostText);
// Cooldown text displays for buttons
var basicCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
basicCooldownText.anchor.set(0.5, 0.5);
basicCooldownText.x = 50;
basicCooldownText.y = buttonY - 20;
basicCooldownText.alpha = 0;
LK.gui.topLeft.addChild(basicCooldownText);
var heavyCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
heavyCooldownText.anchor.set(0.5, 0.5);
heavyCooldownText.x = 50;
heavyCooldownText.y = buttonY + buttonSpacing - 20;
heavyCooldownText.alpha = 0;
LK.gui.topLeft.addChild(heavyCooldownText);
var crystalCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
crystalCooldownText.anchor.set(0.5, 0.5);
crystalCooldownText.x = 50;
crystalCooldownText.y = buttonY + buttonSpacing * 2 - 20;
crystalCooldownText.alpha = 0;
LK.gui.topLeft.addChild(crystalCooldownText);
var barrierCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
barrierCooldownText.anchor.set(0.5, 0.5);
barrierCooldownText.x = 50;
barrierCooldownText.y = buttonY + buttonSpacing * 3 - 20;
barrierCooldownText.alpha = 0;
LK.gui.topLeft.addChild(barrierCooldownText);
var spikeCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
spikeCooldownText.anchor.set(0.5, 0.5);
spikeCooldownText.x = 50;
spikeCooldownText.y = buttonY + buttonSpacing * 4 - 20;
spikeCooldownText.alpha = 0;
LK.gui.topLeft.addChild(spikeCooldownText);
var stoneCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
stoneCooldownText.anchor.set(0.5, 0.5);
stoneCooldownText.x = 50;
stoneCooldownText.y = buttonY + buttonSpacing * 5 - 20;
stoneCooldownText.alpha = 0;
LK.gui.topLeft.addChild(stoneCooldownText);
var meleeCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
meleeCooldownText.anchor.set(0.5, 0.5);
meleeCooldownText.x = 50;
meleeCooldownText.y = buttonY + buttonSpacing * 6 - 20;
meleeCooldownText.alpha = 0;
LK.gui.topLeft.addChild(meleeCooldownText);
var explosiveCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
explosiveCooldownText.anchor.set(0.5, 0.5);
explosiveCooldownText.x = 50;
explosiveCooldownText.y = buttonY + buttonSpacing * 7 - 20;
explosiveCooldownText.alpha = 0;
LK.gui.topLeft.addChild(explosiveCooldownText);
var miniExplodeGolemCooldownText = new Text2('', {
size: 30,
fill: 0xFF0000
});
miniExplodeGolemCooldownText.anchor.set(0.5, 0.5);
miniExplodeGolemCooldownText.x = 50;
miniExplodeGolemCooldownText.y = buttonY + buttonSpacing * 8 - 20;
miniExplodeGolemCooldownText.alpha = 0;
LK.gui.topLeft.addChild(miniExplodeGolemCooldownText);
// Create grid visualization
var gridCells = [];
for (var row = 0; row < gridRows; row++) {
gridCells[row] = [];
for (var col = 0; col < gridCols; col++) {
var cell = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.3,
alpha: 0.3,
tint: row === 0 ? 0x87CEEB : 0xFFFFFF // Sky blue for first row
});
cell.x = gridStartX + col * cellSize;
cell.y = gridStartY + row * cellSize;
cell.gridX = col;
cell.gridY = row;
gridCells[row][col] = cell;
game.addChild(cell);
}
}
// Grid occupied tracking
var gridOccupied = [];
for (var row = 0; row < gridRows; row++) {
gridOccupied[row] = [];
for (var col = 0; col < gridCols; col++) {
gridOccupied[row][col] = false;
}
}
function updateUI() {
stonesText.setText('Stones: ' + stones);
livesText.setText('Lives: ' + lives);
waveText.setText('Wave: ' + wave);
scoreText.setText('Score: ' + score);
var currentTime = LK.ticks;
// Update button availability based on cost and cooldown
function getButtonAlpha(golemType, cost) {
var hasEnoughStones = stones >= cost;
var cooldownDuration = getGolemCooldownDuration(golemType);
var isOnCooldown = golemPlacementCooldown[golemType] && currentTime - golemPlacementCooldown[golemType] < cooldownDuration;
return hasEnoughStones && !isOnCooldown ? 1.0 : 0.5;
}
basicButton.alpha = getButtonAlpha('basic', golemCosts.basic);
heavyButton.alpha = getButtonAlpha('heavy', golemCosts.heavy);
crystalButton.alpha = getButtonAlpha('crystal', golemCosts.crystal);
barrierButton.alpha = getButtonAlpha('barrier', golemCosts.barrier);
spikeButton.alpha = getButtonAlpha('spike', golemCosts.spike);
stoneButton.alpha = getButtonAlpha('stone', golemCosts.stone);
meleeButton.alpha = getButtonAlpha('melee', golemCosts.melee);
explosiveButton.alpha = getButtonAlpha('explosive', golemCosts.explosive);
miniExplodeGolemButton.alpha = getButtonAlpha('miniExplodeGolem', golemCosts.miniExplodeGolem);
shovelButton.alpha = 1.0; // Shovel is always available
// Update cooldown displays
function updateCooldownDisplay(golemType, cooldownText) {
var cooldownDuration = getGolemCooldownDuration(golemType);
if (golemPlacementCooldown[golemType] && currentTime - golemPlacementCooldown[golemType] < cooldownDuration) {
var remainingTime = Math.ceil((cooldownDuration - (currentTime - golemPlacementCooldown[golemType])) / 60);
cooldownText.setText(remainingTime + 's');
cooldownText.alpha = 1;
} else {
cooldownText.alpha = 0;
}
}
updateCooldownDisplay('basic', basicCooldownText);
updateCooldownDisplay('heavy', heavyCooldownText);
updateCooldownDisplay('crystal', crystalCooldownText);
updateCooldownDisplay('barrier', barrierCooldownText);
updateCooldownDisplay('spike', spikeCooldownText);
updateCooldownDisplay('stone', stoneCooldownText);
updateCooldownDisplay('melee', meleeCooldownText);
updateCooldownDisplay('explosive', explosiveCooldownText);
updateCooldownDisplay('miniExplodeGolem', miniExplodeGolemCooldownText);
}
function spawnWave() {
// Don't spawn demons if initial delay hasn't passed
if (!demonsCanSpawn) {
return;
}
// Check if this is a flag wave
isFlagWave = flagWaves.indexOf(wave) !== -1;
flagWaveSpawned = false;
var demonsToSpawn;
if (isFlagWave) {
// Flag waves spawn 3x more demons
demonsToSpawn = Math.floor((2 + Math.floor(wave * 1.5)) * 3);
} else {
// Dramatically increase demons per wave - starts at 3, increases significantly
demonsToSpawn = Math.max(3, 2 + Math.floor(wave * 1.5)); // More aggressive scaling
}
totalDemonsInWave = demonsToSpawn;
demonsKilledInWave = 0;
var spawnDelay = 300; // 5 seconds delay (300 frames at 60 FPS)
// Decrease spawn delay between demons as waves progress
var delayBetweenSpawns = Math.max(150, 450 - wave * 15); // Faster spawning in later waves
// Flag waves spawn demons much faster
if (isFlagWave) {
delayBetweenSpawns = Math.max(60, delayBetweenSpawns / 2);
}
for (var i = 0; i < demonsToSpawn; i++) {
LK.setTimeout(function () {
// Check if enough time has passed since last demon spawn
if (LK.ticks - lastDemonSpawn >= demonSpawnCooldown) {
var demonType = 'basic';
// More aggressive probability scaling based on wave
var waveScaling = Math.min(wave / 10, 1.0); // Scale from 0 to 1 over 10 waves
// Flag waves have higher chance of special demons
var flagMultiplier = isFlagWave ? 1.5 : 1.0;
if (wave > 2 && Math.random() < (0.2 + waveScaling * 0.4) * flagMultiplier) {
demonType = 'fast';
} // Up to 60% chance
if (wave > 3 && Math.random() < (0.15 + waveScaling * 0.3) * flagMultiplier) {
demonType = 'dancing';
} // Up to 45% chance
if (wave > 4 && Math.random() < (0.1 + waveScaling * 0.4) * flagMultiplier) {
demonType = 'tank';
} // Up to 50% chance
if (wave > 5 && Math.random() < (0.05 + waveScaling * 0.3) * flagMultiplier) {
demonType = 'speedy';
} // Up to 35% chance
// Add elite demon spawning for high waves
if (wave > 7 && Math.random() < (0.05 + waveScaling * 0.15) * flagMultiplier) {
demonType = 'zombieAllStar';
} // Up to 20% chance
// Add nutrient demons starting from wave 3
if (wave > 2 && Math.random() < 0.15) {
demonType = 'nutrient';
} // 15% chance for nutrient demons
var demon = new Demon(demonType);
demon.x = 2048;
demon.y = gridStartY + Math.floor(Math.random() * gridRows) * cellSize;
demons.push(demon);
game.addChild(demon);
lastDemonSpawn = LK.ticks;
}
}, spawnDelay);
spawnDelay += delayBetweenSpawns; // Use variable delay that decreases with wave
}
}
function getGridPosition(x, y) {
var col = Math.floor((x - gridStartX + cellSize / 2) / cellSize);
var row = Math.floor((y - gridStartY + cellSize / 2) / cellSize);
if (col >= 0 && col < gridCols && row >= 0 && row < gridRows) {
return {
col: col,
row: row
};
}
return null;
}
// Button click handlers
basicButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'basic' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.basic) {
selectedGolemType = 'basic';
lastClickedButton = 'basic';
lastClickTime = currentTime;
updateButtonSelection();
}
};
heavyButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'heavy' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.heavy) {
selectedGolemType = 'heavy';
lastClickedButton = 'heavy';
lastClickTime = currentTime;
updateButtonSelection();
}
};
crystalButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'crystal' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.crystal) {
selectedGolemType = 'crystal';
lastClickedButton = 'crystal';
lastClickTime = currentTime;
updateButtonSelection();
}
};
barrierButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'barrier' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.barrier) {
selectedGolemType = 'barrier';
lastClickedButton = 'barrier';
lastClickTime = currentTime;
updateButtonSelection();
}
};
spikeButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'spike' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.spike) {
selectedGolemType = 'spike';
lastClickedButton = 'spike';
lastClickTime = currentTime;
updateButtonSelection();
}
};
stoneButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'stone' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.stone) {
selectedGolemType = 'stone';
lastClickedButton = 'stone';
lastClickTime = currentTime;
updateButtonSelection();
}
};
meleeButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'melee' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.melee) {
selectedGolemType = 'melee';
lastClickedButton = 'melee';
lastClickTime = currentTime;
updateButtonSelection();
}
};
explosiveButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'explosive' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.explosive) {
selectedGolemType = 'explosive';
lastClickedButton = 'explosive';
lastClickTime = currentTime;
updateButtonSelection();
}
};
miniExplodeGolemButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'miniExplodeGolem' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else if (stones >= golemCosts.miniExplodeGolem) {
selectedGolemType = 'miniExplodeGolem';
lastClickedButton = 'miniExplodeGolem';
lastClickTime = currentTime;
updateButtonSelection();
}
};
shovelButton.down = function () {
var currentTime = LK.ticks;
if (lastClickedButton === 'shovel' && currentTime - lastClickTime < doubleTapThreshold) {
// Double tap detected - deselect all
selectedGolemType = null;
lastClickedButton = null;
updateButtonSelection();
} else {
selectedGolemType = 'shovel';
lastClickedButton = 'shovel';
lastClickTime = currentTime;
updateButtonSelection();
}
};
function updateButtonSelection() {
// If no golem is selected, all buttons return to normal color
basicButton.tint = selectedGolemType === 'basic' ? 0x00FF00 : 0xFFFFFF;
heavyButton.tint = selectedGolemType === 'heavy' ? 0x00FF00 : 0xFFFFFF;
crystalButton.tint = selectedGolemType === 'crystal' ? 0x00FF00 : 0xFFFFFF;
barrierButton.tint = selectedGolemType === 'barrier' ? 0x00FF00 : 0xFFFFFF;
spikeButton.tint = selectedGolemType === 'spike' ? 0x00FF00 : 0xFFFFFF;
stoneButton.tint = selectedGolemType === 'stone' ? 0x00FF00 : 0xFFFFFF;
meleeButton.tint = selectedGolemType === 'melee' ? 0x00FF00 : 0xFFFFFF;
explosiveButton.tint = selectedGolemType === 'explosive' ? 0x00FF00 : 0xFFFFFF;
miniExplodeGolemButton.tint = selectedGolemType === 'miniExplodeGolem' ? 0x00FF00 : 0xFFFFFF;
shovelButton.tint = selectedGolemType === 'shovel' ? 0x00FF00 : 0xFFFFFF;
}
// Initialize button selection
updateButtonSelection();
// Initialize lawnmowers
for (var row = 0; row < gridRows; row++) {
var lawnmower = new Lawnmower();
lawnmower.x = 300;
lawnmower.y = gridStartY + row * cellSize;
lawnmower.row = row;
lawnmowers.push(lawnmower);
game.addChild(lawnmower);
}
// Game click handler
game.down = function (x, y, obj) {
// Check if clicking on a nutrient stone first
for (var n = 0; n < nutrients.length; n++) {
var nutrient = nutrients[n];
var dx = x - nutrient.x;
var dy = y - nutrient.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 50 && nutrientInventory.length < maxNutrientSlots) {
// Collect the nutrient stone
tween(nutrient, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
// Add to inventory
nutrientInventory.push(1);
updateNutrientUI();
nutrient.destroy();
}
});
nutrients.splice(n, 1);
return; // Exit early, don't process grid placement
}
}
// Check if clicking on nutrient inventory slots
var slotClicked = -1;
for (var s = 0; s < maxNutrientSlots; s++) {
var slotX = 2048 - 200 - s * 100;
var slotY = 200;
var globalSlotPos = LK.gui.topRight.toGlobal({
x: slotX,
y: slotY
});
var dx = x - globalSlotPos.x;
var dy = y - globalSlotPos.y;
if (Math.abs(dx) < 40 && Math.abs(dy) < 40 && s < nutrientInventory.length) {
slotClicked = s;
break;
}
}
if (slotClicked >= 0) {
selectedNutrientSlot = selectedNutrientSlot === slotClicked ? -1 : slotClicked;
updateNutrientUI();
return;
}
var gridPos = getGridPosition(x, y);
if (gridPos) {
if (selectedGolemType === 'shovel') {
// Remove golem if one exists at this position
if (gridOccupied[gridPos.row][gridPos.col]) {
for (var i = golems.length - 1; i >= 0; i--) {
var golem = golems[i];
var golemGridCol = Math.floor((golem.x - gridStartX + cellSize / 2) / cellSize);
var golemGridRow = Math.floor((golem.y - gridStartY + cellSize / 2) / cellSize);
if (golemGridCol === gridPos.col && golemGridRow === gridPos.row) {
golem.destroy();
golems.splice(i, 1);
gridOccupied[gridPos.row][gridPos.col] = false;
LK.getSound('remove').play();
break;
}
}
}
} else if (!gridOccupied[gridPos.row][gridPos.col]) {
// Check if we have a nutrient selected and there's a golem here
if (selectedNutrientSlot >= 0 && gridOccupied[gridPos.row][gridPos.col]) {
// Find the golem at this position
for (var g = 0; g < golems.length; g++) {
var golem = golems[g];
var golemGridCol = Math.floor((golem.x - gridStartX + cellSize / 2) / cellSize);
var golemGridRow = Math.floor((golem.y - gridStartY + cellSize / 2) / cellSize);
if (golemGridCol === gridPos.col && golemGridRow === gridPos.row) {
// Apply nutrient stone to this golem
applyNutrientToGolem(golem);
// Remove nutrient from inventory
nutrientInventory.splice(selectedNutrientSlot, 1);
selectedNutrientSlot = -1;
updateNutrientUI();
return;
}
}
}
var cost = golemCosts[selectedGolemType];
var currentTime = LK.ticks;
var cooldownKey = selectedGolemType;
var cooldownDuration = getGolemCooldownDuration(selectedGolemType);
// Check if golem type is on cooldown
if (golemPlacementCooldown[cooldownKey] && currentTime - golemPlacementCooldown[cooldownKey] < cooldownDuration) {
var remainingTime = Math.ceil((cooldownDuration - (currentTime - golemPlacementCooldown[cooldownKey])) / 60);
cooldownText.setText('Espera ' + remainingTime + 's para colocar ' + selectedGolemType);
cooldownText.alpha = 1;
tween(cooldownText, {
alpha: 0
}, {
duration: 2000
});
return;
}
if (stones >= cost) {
var golem;
if (selectedGolemType === 'miniExplodeGolem') {
golem = new miniExplodeGolem();
} else {
golem = new Golem(selectedGolemType);
}
golem.x = gridStartX + gridPos.col * cellSize;
golem.y = gridStartY + gridPos.row * cellSize;
golems.push(golem);
game.addChild(golem);
gridOccupied[gridPos.row][gridPos.col] = true;
stones -= cost;
golemPlacementCooldown[cooldownKey] = currentTime;
lastGolemPlacement = currentTime;
updateUI();
LK.getSound('place').play();
}
}
}
};
// Countdown sequence before first wave
var countdownText = new Text2('', {
size: 80,
fill: 0xFFFFFF
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 2048 / 2;
countdownText.y = 2732 / 2;
game.addChild(countdownText);
// Show "preparado..." at 4 seconds
LK.setTimeout(function () {
countdownText.setText('preparado...');
countdownText.alpha = 1;
// Fade in effect
tween(countdownText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(countdownText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}, 4000);
// Show "listo..." at 8 seconds
LK.setTimeout(function () {
countdownText.setText('listo...');
// Scale and color effect
tween(countdownText, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFFFF00
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(countdownText, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
}, 8000);
// Show "¡PELEA!" at 12 seconds and start wave
LK.setTimeout(function () {
countdownText.setText('¡PELEA!');
// Dramatic entrance effect
tween(countdownText, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFF0000
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Enable demon spawning after PELEA text is shown
demonsCanSpawn = true;
// Start first wave immediately after PELEA
spawnWave();
// Fade out after showing PELEA
tween(countdownText, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
countdownText.destroy();
}
});
}
});
}, 12000);
game.update = function () {
// Update golems
for (var i = golems.length - 1; i >= 0; i--) {
var golem = golems[i];
if (golem.update) {
golem.update();
}
}
// Update demons
for (var i = demons.length - 1; i >= 0; i--) {
var demon = demons[i];
demon.update();
// Check if demon reached left side
if (demon.x < 350) {
// Check if there's a lawnmower in this row to activate
var demonRow = Math.floor((demon.y - gridStartY + cellSize / 2) / cellSize);
var lawnmowerActivated = false;
for (var l = 0; l < lawnmowers.length; l++) {
var lawnmower = lawnmowers[l];
if (lawnmower.row === demonRow && !lawnmower.isActive) {
lawnmower.activate();
lawnmowerActivated = true;
break;
}
}
if (!lawnmowerActivated) {
lives--;
demon.destroy();
demons.splice(i, 1);
updateUI();
if (lives <= 0) {
LK.setScore(score);
LK.showGameOver();
return;
}
}
} else if (demon.shouldRemove) {
demon.destroy();
demons.splice(i, 1);
}
}
// Update lawnmowers
for (var i = lawnmowers.length - 1; i >= 0; i--) {
var lawnmower = lawnmowers[i];
lawnmower.update();
if (lawnmower.shouldRemove) {
lawnmower.destroy();
lawnmowers.splice(i, 1);
}
}
// Update progress bar
if (totalDemonsInWave > 0) {
var progress = demonsKilledInWave / totalDemonsInWave;
var targetScale = progress * 12;
tween.stop(progressBarFill, {
scaleX: true
});
tween(progressBarFill, {
scaleX: targetScale
}, {
duration: 200
});
// Update progress text with flag wave indicator
var progressPercentage = Math.floor(progress * 100);
var flagText = isFlagWave ? " (BANDERA!)" : "";
progressText.setText('Wave ' + wave + ': ' + progressPercentage + '%' + flagText);
// Change progress bar color for flag waves
if (isFlagWave) {
progressBarFill.tint = 0xFF4444; // Red for flag waves
progressText.tint = 0xFF4444;
} else {
progressBarFill.tint = 0x00FF00; // Green for normal waves
progressText.tint = 0xFFFFFF;
}
}
// Update projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
projectile.update();
if (projectile.shouldRemove) {
projectile.destroy();
projectiles.splice(i, 1);
continue;
}
// Check projectile vs demon collision
for (var j = demons.length - 1; j >= 0; j--) {
var demon = demons[j];
if (projectile.intersects(demon)) {
demon.takeDamage(projectile.damage);
// Heavy golem projectiles have 25% chance to push demons backwards
if (projectile.golemType === 'heavy' && Math.random() < 0.25) {
// Push demon backwards (to the right) by 100 pixels
tween(demon, {
x: demon.x + 100
}, {
duration: 300,
easing: tween.easeOut
});
}
projectile.destroy();
projectiles.splice(i, 1);
break;
}
}
}
// Update nutrients - no auto collection, manual only
for (var i = nutrients.length - 1; i >= 0; i--) {
var nutrient = nutrients[i];
nutrient.update();
if (nutrient.shouldRemove) {
nutrient.destroy();
nutrients.splice(i, 1);
continue;
}
}
// Update stones with automatic collection only
for (var i = stones_collectible.length - 1; i >= 0; i--) {
var stone = stones_collectible[i];
stone.update();
if (stone.shouldRemove) {
stone.destroy();
stones_collectible.splice(i, 1);
continue;
}
// Auto-collect stones after 2 seconds (no manual collection)
if (!stone.isMarkedForCollection && LK.ticks - stone.spawnTime > 120) {
stone.isMarkedForCollection = true;
stone.collectionMarkTime = LK.ticks;
// Visual indicator - slight glow effect
tween(stone, {
tint: 0xFFFF88
}, {
duration: 200
});
}
// Check if marked stone should be collected after delay
if (stone.isMarkedForCollection && LK.ticks - stone.collectionMarkTime >= stone.collectionDelay) {
// Animate stone collection
tween(stone, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
stones += stone.value;
stone.destroy();
updateUI();
}
});
stones_collectible.splice(i, 1);
}
}
// Update stone spawn delay based on current wave
stoneSpawnDelay = baseStoneSpawnDelay + (wave - 1) * 20;
// Spawn stones periodically
if (LK.ticks - lastStoneSpawn > stoneSpawnDelay) {
var stone = new Stone();
stone.x = 400 + Math.random() * (2048 - 800); // Spawn in playable area
stone.y = 600 + Math.random() * 400;
stones_collectible.push(stone);
game.addChild(stone);
lastStoneSpawn = LK.ticks;
}
// Wave management
if (demons.length === 0 && demonsCanSpawn) {
nextWaveTimer++;
if (nextWaveTimer >= waveDelay) {
wave++;
nextWaveTimer = 0;
// Show flag wave announcement
if (flagWaves.indexOf(wave) !== -1) {
var flagText = new Text2('¡OLEADA BANDERA!', {
size: 60,
fill: 0xFF0000
});
flagText.anchor.set(0.5, 0.5);
flagText.x = 2048 / 2;
flagText.y = 400;
game.addChild(flagText);
// Animate flag announcement
tween(flagText, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFFFF00
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(flagText, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 1000,
onFinish: function onFinish() {
flagText.destroy();
}
});
}
});
}
spawnWave();
updateUI();
// Victory condition - now goes to wave 25
if (wave > 25) {
LK.setScore(score);
LK.showYouWin();
return;
}
}
}
};
// Initialize game start time
gameStartTime = LK.ticks;
// Add decorative elements outside garden
// Rocks scattered around the edges with rock texture
for (var i = 0; i < 15; i++) {
var rock = LK.getAsset('rockTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8 + Math.random() * 1.0,
scaleY: 0.8 + Math.random() * 1.0,
tint: 0x8B7355 // Natural rock brown color
});
// Position rocks outside garden area
if (Math.random() < 0.5) {
// Left side decorations
rock.x = 50 + Math.random() * 150;
rock.y = 700 + Math.random() * 800;
} else {
// Right side decorations
rock.x = 2600 + Math.random() * 300;
rock.y = 700 + Math.random() * 800;
}
game.addChild(rock);
}
// Grass patches for decoration with grass texture
for (var i = 0; i < 20; i++) {
var grass = LK.getAsset('grassTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8 + Math.random() * 0.6,
scaleY: 0.6 + Math.random() * 0.4,
alpha: 0.7,
tint: 0x32CD32 // Lime green for natural grass
});
// Scatter grass around the garden
if (Math.random() < 0.3) {
// Above garden
grass.x = 400 + Math.random() * 2000;
grass.y = 600 + Math.random() * 150;
} else if (Math.random() < 0.5) {
// Below garden
grass.x = 400 + Math.random() * 2000;
grass.y = 1800 + Math.random() * 200;
} else {
// Left and right sides
grass.x = Math.random() < 0.5 ? 100 + Math.random() * 100 : 2700 + Math.random() * 200;
grass.y = 700 + Math.random() * 1000;
}
game.addChild(grass);
}
// Small decorative stones near garden edges with rock texture
for (var i = 0; i < 8; i++) {
var smallStone = LK.getAsset('rockTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0x696969 // Darker gray for small stones
});
// Position near garden borders
smallStone.x = gridStartX - 50 + Math.random() * (gridCols * cellSize + 100);
smallStone.y = Math.random() < 0.5 ? gridStartY - 100 : gridStartY + gridRows * cellSize + 50;
game.addChild(smallStone);
}
// Initialize UI
updateUI();
updateNutrientUI();
un demonio bola grande 2d y semi realista con muchos cuernos y que sus ojos sean negros con pupilas blancas. In-Game asset. 2d. High contrast. No shadows
haz a este personaje mas realista
haz a este personaje mucho mas realista
recrea este personaje mucho mas realista
un golem de cristal muy realista con un cañon en la mano. In-Game asset. 2d. High contrast. No shadows
una roca circular muy realista. In-Game asset. 2d. High contrast. No shadows
haz a este personaje mas realista
una pala de madera realista. In-Game asset. 2d. High contrast. No shadows
un golem de piedra con puas plano en horizontal en 3d muy realista. In-Game asset. 2d. High contrast. No shadows
un golem de piedra que parezca un minero y que se vea ultra realista. In-Game asset. 2d. High contrast. No shadows
un demonio bola ultra realista con una peluca y traje de bailarin con lentes de sol y una sonrisa con colmillos afilados. In-Game asset. 2d. High contrast. No shadows
un golem de piedra listo para pelear con puños con puas ultra realista. In-Game asset. 2d. High contrast. No shadows
un golem de piedra ultra realista con una bomba en la espalda. In-Game asset. 2d. High contrast. No shadows
un demonio bola con un casco de jugador de futbol americano ultra realista. In-Game asset. 2d. High contrast. No shadows
haz esta imagen muy, muy, muy, muy, muy realista, es una piedra con ruedas. In-Game asset. 2d. High contrast. No shadows
una piedra con ojos muy realista con una granada encima. In-Game asset. 2d. High contrast. No shadows
un arbusto ultra realista. In-Game asset. 2d. High contrast. No shadows
una roca ultra realista. In-Game asset. 2d. High contrast. No shadows