/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Armor Pest - heavily armored, slow but very tough var ArmorPest = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('armorPest', { anchorX: 0.5, anchorY: 0.5, width: 130, height: 130 }); self.lane = null; self.hp = 20; // Very high health self.speed = 1.0; // Slow movement self.destroyed = false; self.armor = 2; // Reduces incoming damage self.update = function () { if (self.destroyed) return; if (self.lastX === undefined) self.lastX = self.x; if (self.eatingPlant && !self.eatingPlant._destroyed) { if (self.eatCooldown === undefined) self.eatCooldown = 0; if (self._chewTweening !== true) { self._chewTweening = true; tween(self, { scaleX: 1.1 }, { duration: 100, yoyo: true, repeat: 2, onFinish: function onFinish() { self.scaleX = 1.0; self._chewTweening = false; } }); } if (self.eatCooldown > 0) { self.eatCooldown--; } else { if (typeof self.eatingPlant.hp === "number") { self.eatingPlant.hp -= 2; // Heavy damage to plants if (self.eatingPlant.hp <= 0) { self.eatingPlant._destroyed = true; if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy(); self.eatingPlant = null; self.scaleX = 1.0; self._chewTweening = false; } } self.eatCooldown = 45; // Slower eating } } else { self.x -= self.speed; var foundPlant = null; for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) { foundPlant = plant; break; } } if (foundPlant) { self.eatingPlant = foundPlant; self.eatCooldown = 0; } } self.lastX = self.x; }; self.hit = function (dmg) { if (self.destroyed) return; // Armor reduces damage var actualDamage = Math.max(1, dmg - self.armor); self.hp -= actualDamage; if (self.hp <= 0) { self.destroyed = true; } // Visual feedback for armor LK.effects.flashObject(self, 0x696969, 100); }; return self; }); // Crystal Plant - fires piercing laser beams that go through multiple enemies var CrystalPlant = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('crystalPlant', { anchorX: 0.5, anchorY: 0.5, width: 150, height: 150 }); self.fireCooldown = 45; self.cooldown = 0; self.lane = null; self.column = null; self.hp = 8; self._destroyed = false; self._chargeTimer = 0; self.update = function () { if (self.cooldown > 0) self.cooldown--; if (self.hp <= 0 && !self._destroyed) { self._destroyed = true; if (typeof self.destroy === "function") self.destroy(); } // Charge animation every 30 frames self._chargeTimer++; if (self._chargeTimer >= 30) { self._chargeTimer = 0; tween(sprite, { scaleX: 1.1, scaleY: 1.1, tint: 0x00FFFF }, { duration: 200, yoyo: true, repeat: 1, onFinish: function onFinish() { sprite.tint = 0xFFFFFF; } }); } }; self.tryFire = function () { if (self.cooldown <= 0) { self.cooldown = self.fireCooldown; return true; } return false; }; return self; }); // Fast, tough pest that spawns on wave 3 var FastPest = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('fastPest', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120 }); self.lane = null; self.hp = 5; // More health than basic pest self.speed = 4.5; // Much faster than basic pest self.destroyed = false; self.update = function () { if (self.destroyed) return; // Initialize lastX for transition checks if (self.lastX === undefined) self.lastX = self.x; // If currently eating a plant, handle eating logic if (self.eatingPlant && !self.eatingPlant._destroyed) { // Only bite every 30 frames if (self.eatCooldown === undefined) self.eatCooldown = 0; if (self._chewTweening !== true) { self._chewTweening = true; tween(self, { scaleX: 1.2 }, { duration: 80, yoyo: true, repeat: 2, onFinish: function onFinish() { self.scaleX = 1.0; self._chewTweening = false; } }); } if (self.eatCooldown > 0) { self.eatCooldown--; } else { // Damage the plant if (typeof self.eatingPlant.hp === "number") { self.eatingPlant.hp -= 1; // Optional: flash or animate plant here if (self.eatingPlant.hp <= 0) { self.eatingPlant._destroyed = true; if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy(); // Remove from plants array in main update self.eatingPlant = null; self.scaleX = 1.0; self._chewTweening = false; } } self.eatCooldown = 30; } // Don't move while eating } else { // Not eating, move left self.x -= self.speed; // Check for collision with plant in same lane var foundPlant = null; for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) { foundPlant = plant; break; } } if (foundPlant) { self.eatingPlant = foundPlant; self.eatCooldown = 0; } } // Update lastX self.lastX = self.x; }; self.hit = function (dmg) { if (self.destroyed) return; self.hp -= dmg; if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Ghost Pest - phases through plants occasionally and has moderate health var GhostPest = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('ghostPest', { anchorX: 0.5, anchorY: 0.5, width: 110, height: 110 }); self.lane = null; self.hp = 8; self.speed = 2.5; self.destroyed = false; self.phaseTimer = 0; self.phaseCooldown = 180; // 3 seconds self.isPhasing = false; self.update = function () { if (self.destroyed) return; if (self.lastX === undefined) self.lastX = self.x; // Handle phasing ability self.phaseTimer++; if (self.phaseTimer >= self.phaseCooldown) { self.phaseTimer = 0; self.isPhasing = true; sprite.alpha = 0.3; tween(sprite, { tint: 0x8A2BE2 }, { duration: 1000, onFinish: function onFinish() { self.isPhasing = false; sprite.alpha = 1.0; sprite.tint = 0xFFFFFF; } }); } // Move left self.x -= self.speed; // Only check plant collisions if not phasing if (!self.isPhasing) { if (self.eatingPlant && !self.eatingPlant._destroyed) { if (self.eatCooldown === undefined) self.eatCooldown = 0; if (self._chewTweening !== true) { self._chewTweening = true; tween(self, { scaleX: 1.2 }, { duration: 80, yoyo: true, repeat: 2, onFinish: function onFinish() { self.scaleX = 1.0; self._chewTweening = false; } }); } if (self.eatCooldown > 0) { self.eatCooldown--; } else { if (typeof self.eatingPlant.hp === "number") { self.eatingPlant.hp -= 1; if (self.eatingPlant.hp <= 0) { self.eatingPlant._destroyed = true; if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy(); self.eatingPlant = null; self.scaleX = 1.0; self._chewTweening = false; } } self.eatCooldown = 30; } } else { var foundPlant = null; for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) { foundPlant = plant; break; } } if (foundPlant) { self.eatingPlant = foundPlant; self.eatCooldown = 0; } } } self.lastX = self.x; }; self.hit = function (dmg) { if (self.destroyed || self.isPhasing) return; // Immune to damage while phasing self.hp -= dmg; if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Mega plant - secret powerful plant created by placing trap on existing plant var MegaPlant = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('megaPlant', { anchorX: 0.5, anchorY: 0.5, width: 180, height: 180 }); self.fireCooldown = 20; // Very fast firing self.cooldown = 0; self.lane = null; self.column = null; self.hp = 20; // Very high health self._destroyed = false; self._pulseTimer = 0; self.update = function () { if (self.cooldown > 0) self.cooldown--; if (self.hp <= 0 && !self._destroyed) { self._destroyed = true; if (typeof self.destroy === "function") self.destroy(); } // Pulse animation every 60 frames self._pulseTimer++; if (self._pulseTimer >= 60) { self._pulseTimer = 0; tween(sprite, { scaleX: 1.2, scaleY: 1.2, tint: 0xFF69B4 }, { duration: 300, yoyo: true, repeat: 1, onFinish: function onFinish() { sprite.tint = 0xFFFFFF; } }); } }; self.tryFire = function () { if (self.cooldown <= 0) { self.cooldown = self.fireCooldown; return true; } return false; }; return self; }); // Mega Rat - massive pest with highest health that instantly kills plants and spans two lanes var MegaRat = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('megaRat', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 300 }); self.lane = null; self.hp = 50; // Highest health in the game self.speed = 1.0; // Slow but devastating self.destroyed = false; self.occupiedLanes = []; // Tracks which lanes this mega rat occupies self.spawnCooldown = 90; // 1.5 seconds at 60fps - rapid spawning self.spawnTimer = 0; self.update = function () { if (self.destroyed) return; // Initialize lastX for transition checks if (self.lastX === undefined) self.lastX = self.x; // Handle rapid PartyRat spawning timer self.spawnTimer++; if (self.spawnTimer >= self.spawnCooldown) { self.spawnTimer = 0; self.spawnPartyRats(); } // Update occupied lanes (current lane and one below) self.occupiedLanes = [self.lane]; if (self.lane + 1 < LANE_COUNT) { self.occupiedLanes.push(self.lane + 1); } // Move left self.x -= self.speed; // Check for collision with plants in occupied lanes - instant kill for (var i = plants.length - 1; i >= 0; i--) { var plant = plants[i]; if (!plant._destroyed && self.occupiedLanes.indexOf(plant.lane) !== -1 && Math.abs(self.x - plant.x) < 150) { // Instantly kill the plant plant.hp = 0; plant._destroyed = true; if (typeof plant.destroy === "function") plant.destroy(); plants.splice(i, 1); // Flash effect when killing plant LK.effects.flashObject(self, 0xFF0000, 200); } } // Update lastX self.lastX = self.x; }; self.spawnPartyRats = function () { // Spawn regular Pests in all lanes for (var i = 0; i < LANE_COUNT; i++) { var newPest = new Pest(); newPest.lane = i; newPest.x = self.x + 50; // Spawn slightly ahead of MegaRat newPest.y = LANE_Y[i]; pests.push(newPest); game.addChild(newPest); // Visual effect for spawning LK.effects.flashObject(newPest, 0xFF69B4, 300); } // Visual effect on the mega rat when it spawns LK.effects.flashObject(self, 0xFF0000, 500); }; self.hit = function (dmg) { if (self.destroyed) return; self.hp -= dmg; if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Omega Plant - ultimate plant that shoots in every lane, created by placing seed producer on mega plant var OmegaPlant = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('omegaPlant', { anchorX: 0.5, anchorY: 0.5, width: 200, height: 200 }); self.fireCooldown = 30; // Very fast firing self.cooldown = 0; self.lane = null; self.column = null; self.hp = 30; // Extremely high health self._destroyed = false; self._pulseTimer = 0; self.update = function () { if (self.cooldown > 0) self.cooldown--; if (self.hp <= 0 && !self._destroyed) { self._destroyed = true; if (typeof self.destroy === "function") self.destroy(); } // Pulse animation every 45 frames self._pulseTimer++; if (self._pulseTimer >= 45) { self._pulseTimer = 0; tween(sprite, { scaleX: 1.3, scaleY: 1.3, tint: 0x00FFFF }, { duration: 250, yoyo: true, repeat: 1, onFinish: function onFinish() { sprite.tint = 0xFFFFFF; } }); } }; self.tryFire = function () { if (self.cooldown <= 0) { self.cooldown = self.fireCooldown; return true; } return false; }; return self; }); // --- Pest Classes --- // Party Rat - high health pest that spawns other pests in 4 directions every 6 seconds var PartyRat = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('partyRat', { anchorX: 0.5, anchorY: 0.5, width: 140, height: 140 }); self.lane = null; self.hp = 15; // Very high health self.speed = 1.5; // Slower than other pests self.destroyed = false; self.spawnCooldown = 270; // 4.5 seconds at 60fps self.spawnTimer = 0; self.update = function () { if (self.destroyed) return; // Initialize lastX for transition checks if (self.lastX === undefined) self.lastX = self.x; // Handle spawning timer self.spawnTimer++; if (self.spawnTimer >= self.spawnCooldown) { self.spawnTimer = 0; self.spawnPests(); } // If currently eating a plant, handle eating logic if (self.eatingPlant && !self.eatingPlant._destroyed) { // Only bite every 30 frames if (self.eatCooldown === undefined) self.eatCooldown = 0; if (self._chewTweening !== true) { self._chewTweening = true; tween(self, { scaleX: 1.3 }, { duration: 100, yoyo: true, repeat: 2, onFinish: function onFinish() { self.scaleX = 1.0; self._chewTweening = false; } }); } if (self.eatCooldown > 0) { self.eatCooldown--; } else { // Damage the plant if (typeof self.eatingPlant.hp === "number") { self.eatingPlant.hp -= 2; // Party rat does more damage if (self.eatingPlant.hp <= 0) { self.eatingPlant._destroyed = true; if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy(); self.eatingPlant = null; self.scaleX = 1.0; self._chewTweening = false; } } self.eatCooldown = 30; } } else { // Not eating, move left self.x -= self.speed; // Check for collision with plant in same lane var foundPlant = null; for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) { foundPlant = plant; break; } } if (foundPlant) { self.eatingPlant = foundPlant; self.eatCooldown = 0; } } // Update lastX self.lastX = self.x; }; self.spawnPests = function () { // Spawn pests in 4 directions: front, back, up lane, down lane var spawnPositions = [{ x: self.x + 100, lane: self.lane }, // Front (right) { x: self.x - 100, lane: self.lane }, // Behind (left) { x: self.x, lane: Math.max(0, self.lane - 1) }, // Up lane { x: self.x, lane: Math.min(LANE_COUNT - 1, self.lane + 1) } // Down lane ]; for (var i = 0; i < spawnPositions.length; i++) { var pos = spawnPositions[i]; // Don't spawn if position is off-screen or invalid if (pos.x > 50 && pos.x < GAME_WIDTH - 50) { var newPest = new Pest(); newPest.lane = pos.lane; newPest.x = pos.x; newPest.y = LANE_Y[pos.lane]; pests.push(newPest); game.addChild(newPest); // Visual effect for spawning LK.effects.flashObject(newPest, 0xFF69B4, 300); } } // Visual effect on the party rat when it spawns LK.effects.flashObject(self, 0xFFFF00, 500); }; self.hit = function (dmg) { if (self.destroyed) return; self.hp -= dmg; if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Basic pest that moves left and can be hit by seeds var Pest = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('pest', { anchorX: 0.5, anchorY: 0.5, width: 100, height: 100 }); self.lane = null; self.hp = 2; self.speed = 2.0; self.destroyed = false; self.update = function () { if (self.destroyed) return; // Initialize lastX for transition checks if (self.lastX === undefined) self.lastX = self.x; // If currently eating a plant, handle eating logic if (self.eatingPlant && !self.eatingPlant._destroyed) { // Only bite every 30 frames if (self.eatCooldown === undefined) self.eatCooldown = 0; if (self._chewTweening !== true) { self._chewTweening = true; tween(self, { scaleX: 1.2 }, { duration: 80, yoyo: true, repeat: 2, onFinish: function onFinish() { self.scaleX = 1.0; self._chewTweening = false; } }); } if (self.eatCooldown > 0) { self.eatCooldown--; } else { // Damage the plant if (typeof self.eatingPlant.hp === "number") { self.eatingPlant.hp -= 1; // Optional: flash or animate plant here if (self.eatingPlant.hp <= 0) { self.eatingPlant._destroyed = true; if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy(); // Remove from plants array in main update self.eatingPlant = null; self.scaleX = 1.0; self._chewTweening = false; } } self.eatCooldown = 30; } // Don't move while eating } else { // Not eating, move left self.x -= self.speed; // Check for collision with plant in same lane var foundPlant = null; for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) { foundPlant = plant; break; } } if (foundPlant) { self.eatingPlant = foundPlant; self.eatCooldown = 0; } } // Update lastX self.lastX = self.x; }; self.hit = function (dmg) { if (self.destroyed) return; self.hp -= dmg; if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // --- Plant Classes --- // Basic shooter plant var Plant = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('plant', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120 }); self.fireCooldown = 60; // frames between shots self.cooldown = 0; self.lane = null; self.column = null; self.hp = 5; // Plant health self._destroyed = false; self.update = function () { if (self.cooldown > 0) self.cooldown--; if (self.hp <= 0 && !self._destroyed) { self._destroyed = true; if (typeof self.destroy === "function") self.destroy(); } }; self.tryFire = function () { if (self.cooldown <= 0) { self.cooldown = self.fireCooldown; return true; } return false; }; return self; }); // Poison Plant - creates toxic clouds that slow and damage enemies over time var PoisonPlant = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('poisonPlant', { anchorX: 0.5, anchorY: 0.5, width: 140, height: 140 }); self.fireCooldown = 90; self.cooldown = 0; self.lane = null; self.column = null; self.hp = 6; self._destroyed = false; self._toxicTimer = 0; self.update = function () { if (self.cooldown > 0) self.cooldown--; if (self.hp <= 0 && !self._destroyed) { self._destroyed = true; if (typeof self.destroy === "function") self.destroy(); } // Toxic bubble animation every 60 frames self._toxicTimer++; if (self._toxicTimer >= 60) { self._toxicTimer = 0; tween(sprite, { scaleX: 1.15, scaleY: 1.15, tint: 0x9ACD32 }, { duration: 250, yoyo: true, repeat: 1, onFinish: function onFinish() { sprite.tint = 0xFFFFFF; } }); } }; self.tryFire = function () { if (self.cooldown <= 0) { self.cooldown = self.fireCooldown; return true; } return false; }; return self; }); // --- Seed Class --- // Seed projectile fired by shooter plants var Seed = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('seed', { anchorX: 0.5, anchorY: 0.5, width: 40, height: 40 }); self.lane = null; self.speed = 10; self.update = function () { self.x += self.speed; }; return self; }); // Plant that generates resources var SeedProducerPlant = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('seedProducerPlant', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120 }); self.resourceCooldown = 90; // frames between resource generation (was 180, now 90 for faster seed gain) self.cooldown = 0; self.lane = null; self.column = null; self.hp = 5; // Plant health self._destroyed = false; self._seedsProduced = 0; self.update = function () { if (self.cooldown > 0) { self.cooldown--; } else { // Give resource and reset cooldown resources += 1; resourceTxt.setText(resources + ''); self.cooldown = self.resourceCooldown; self._seedsProduced = (self._seedsProduced || 0) + 1; if (self._seedsProduced % 2 === 0) { // Glow pink using tween (tint to pink, then back to normal) tween.stop(sprite, { tint: true }); var originalTint = sprite.tint; tween(sprite, { tint: 0xFF69B4 }, { duration: 120, yoyo: true, repeat: 1, onFinish: function onFinish() { sprite.tint = originalTint; } }); } } if (self.hp <= 0 && !self._destroyed) { self._destroyed = true; if (typeof self.destroy === "function") self.destroy(); } }; return self; }); // Shield Plant - provides protection to nearby plants var ShieldPlant = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('shieldPlant', { anchorX: 0.5, anchorY: 0.5, width: 130, height: 130 }); self.lane = null; self.column = null; self.hp = 12; self._destroyed = false; self._shieldTimer = 0; self.shieldRadius = 200; self.update = function () { if (self.hp <= 0 && !self._destroyed) { self._destroyed = true; if (typeof self.destroy === "function") self.destroy(); } // Shield pulse animation every 90 frames self._shieldTimer++; if (self._shieldTimer >= 90) { self._shieldTimer = 0; tween(sprite, { scaleX: 1.2, scaleY: 1.2, tint: 0x4169E1 }, { duration: 300, yoyo: true, repeat: 1, onFinish: function onFinish() { sprite.tint = 0xFFFFFF; } }); } }; return self; }); // Swarm Pest - small, fast, spawns in groups var SwarmPest = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('swarmPest', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80 }); self.lane = null; self.hp = 1; // Very low health self.speed = 4.0; // Very fast self.destroyed = false; self.update = function () { if (self.destroyed) return; if (self.lastX === undefined) self.lastX = self.x; if (self.eatingPlant && !self.eatingPlant._destroyed) { if (self.eatCooldown === undefined) self.eatCooldown = 0; if (self._chewTweening !== true) { self._chewTweening = true; tween(self, { scaleX: 1.3 }, { duration: 60, yoyo: true, repeat: 3, onFinish: function onFinish() { self.scaleX = 1.0; self._chewTweening = false; } }); } if (self.eatCooldown > 0) { self.eatCooldown--; } else { if (typeof self.eatingPlant.hp === "number") { self.eatingPlant.hp -= 1; if (self.eatingPlant.hp <= 0) { self.eatingPlant._destroyed = true; if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy(); self.eatingPlant = null; self.scaleX = 1.0; self._chewTweening = false; } } self.eatCooldown = 20; // Fast eating } } else { self.x -= self.speed; var foundPlant = null; for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) { foundPlant = plant; break; } } if (foundPlant) { self.eatingPlant = foundPlant; self.eatCooldown = 0; } } self.lastX = self.x; }; self.hit = function (dmg) { if (self.destroyed) return; self.hp -= dmg; if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Trap plant that eats any pest that touches it, then disappears var TrapPlant = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('trapPlant', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120 }); self.lane = null; self.column = null; self.hp = 1; // Dies after eating one pest self._destroyed = false; self.hasEaten = false; self.cooldownTime = 6000; // 6 second cooldown in milliseconds self.cooldownRemaining = 0; self.onCooldown = false; self.update = function () { if (self._destroyed || self.hasEaten) return; // Handle cooldown if (self.onCooldown) { self.cooldownRemaining -= 16; // Approximately 16ms per frame at 60fps if (self.cooldownRemaining <= 0) { self.onCooldown = false; // Reset visual appearance when cooldown ends sprite.tint = 0xffffff; sprite.alpha = 1.0; } return; // Don't check for collisions while on cooldown } // Check for collision with any pest in same lane - improved collision detection for (var i = pests.length - 1; i >= 0; i--) { var pest = pests[i]; // Check if pest is in same lane and within touching distance if (pest.lane === self.lane && !pest.destroyed && Math.abs(self.x - pest.x) < 80 && Math.abs(self.y - pest.y) < 60) { // Instantly kill the pest - set HP to 0 and destroy immediately pest.hp = 0; pest.destroyed = true; pest.destroy(); // Remove from pests array immediately pests.splice(i, 1); // Start cooldown instead of destroying plant self.onCooldown = true; self.cooldownRemaining = self.cooldownTime; // Visual feedback for cooldown - make plant greyish and semi-transparent sprite.tint = 0x666666; sprite.alpha = 0.6; // Flash effect on successful kill LK.effects.flashObject(self, 0x00ff00, 200); break; } } if (self.hp <= 0 && !self._destroyed) { self._destroyed = true; if (typeof self.destroy === "function") self.destroy(); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x7ec850 // Light green garden }); /**** * Game Code ****/ // Add background image var background = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChild(background); // --- Constants --- // Unique sprite for fast, tough pest var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var LANE_COUNT = 7; var LANE_HEIGHT = Math.floor(GAME_HEIGHT / LANE_COUNT); var LANE_Y = []; for (var i = 0; i < LANE_COUNT; i++) { LANE_Y[i] = Math.floor((i + 0.5) * LANE_HEIGHT); } var PLANT_COST = 10; var START_RESOURCES = 20; var MAX_BREACHES = 5; var TOTAL_WAVES = 25; // Extended to include 5 extra levels var COLUMN_COUNT = 5; // Number of plant columns var COLUMN_WIDTH = 160; // Distance between columns var COLUMN_START_X = 200; // X position of first column // --- Asset Initialization --- // --- Game State --- var plants = []; // All plant objects var pests = []; // All pest objects var seeds = []; // All seed objects var rockets = []; // All rocket objects (initialized in global game state) var resources = START_RESOURCES; var breaches = 0; var wave = 1; var waveInProgress = false; var waveTimer = 0; var nextPestTick = 0; var selectedLane = null; // Lane index for planting var selectedColumn = null; // Column index for planting var plantPreview = null; // Preview asset for planting var omegaPlantCreated = false; // Track if omega plant has been created (only one allowed) // --- Lane Markers (for touch feedback) --- var laneMarkers = []; for (var i = 0; i < LANE_COUNT; i++) { for (var col = 0; col < COLUMN_COUNT; col++) { // Use a unique grid sprite for the grid cell background var marker = LK.getAsset('grid', { anchorX: 0.5, anchorY: 0.5, x: COLUMN_START_X + col * COLUMN_WIDTH, y: LANE_Y[i], alpha: 0.8, width: 180, height: LANE_HEIGHT - 10, tint: 0xD2B48C }); game.addChild(marker); laneMarkers.push(marker); } } // --- UI Elements --- // Plant selection UI var plantTypes = [{ id: 'plant', name: 'Shooter', asset: 'plant', desc: 'Shoots seeds', classRef: Plant }, { id: 'seedProducer', name: 'Producer', asset: 'seedProducerPlant', desc: 'Generates seeds', classRef: SeedProducerPlant }, { id: 'trapPlant', name: 'Trap', asset: 'trapPlant', desc: 'Eats one pest', classRef: TrapPlant }, { id: 'megaPlant', name: 'Mega', asset: 'megaPlant', desc: 'Secret plant', classRef: MegaPlant }, { id: 'omegaPlant', name: 'Omega', asset: 'omegaPlant', desc: 'Ultimate plant', classRef: OmegaPlant }, { id: 'crystalPlant', name: 'Crystal', asset: 'crystalPlant', desc: 'Piercing laser', classRef: CrystalPlant }, { id: 'shieldPlant', name: 'Shield', asset: 'shieldPlant', desc: 'Protects plants', classRef: ShieldPlant }, { id: 'poisonPlant', name: 'Poison', asset: 'poisonPlant', desc: 'Toxic clouds', classRef: PoisonPlant }]; var selectedPlantType = plantTypes[0]; // Default to shooter var plantSelectButtons = []; var plantSelectY = 120; var plantSelectSpacing = 220; for (var i = 0; i < plantTypes.length; i++) { var btnX = 200 + i * plantSelectSpacing; var btn = LK.getAsset(plantTypes[i].asset, { anchorX: 0.5, anchorY: 0.5, x: btnX, y: plantSelectY, width: 120, height: 120, alpha: 0.9 }); btn._plantTypeIndex = i; btn.interactive = true; btn.buttonMode = true; // Add a border to show selection btn._border = LK.getAsset(plantTypes[i].asset, { anchorX: 0.5, anchorY: 0.5, x: btnX, y: plantSelectY, width: 140, height: 140, alpha: 0.0 // Will set to visible for selected }); LK.gui.top.addChild(btn._border); LK.gui.top.addChild(btn); plantSelectButtons.push(btn); // Add label var label = new Text2(plantTypes[i].name, { size: 40, fill: "#222" }); label.anchor.set(0.5, 0); label.x = btnX; label.y = plantSelectY + 70; LK.gui.top.addChild(label); // Add price label var priceLabel = new Text2(PLANT_COST + ' seeds', { size: 30, fill: "#444" }); priceLabel.anchor.set(0.5, 0); priceLabel.x = btnX; priceLabel.y = plantSelectY + 110; LK.gui.top.addChild(priceLabel); } // Helper to update plant selection UI function updatePlantSelectUI() { for (var i = 0; i < plantSelectButtons.length; i++) { if (plantTypes[i] === selectedPlantType) { plantSelectButtons[i]._border.alpha = 0.7; plantSelectButtons[i]._border.tint = 0xFFD700; } else { plantSelectButtons[i]._border.alpha = 0.0; } } } updatePlantSelectUI(); // Add Seed Shop title var seedShopTitle = new Text2('Seed Shop', { size: 60, fill: "#333", font: "Impact" }); seedShopTitle.anchor.set(0.5, 0); seedShopTitle.x = GAME_WIDTH / 2; seedShopTitle.y = 40; LK.gui.top.addChild(seedShopTitle); // Add touch handler for plant selection for (var i = 0; i < plantSelectButtons.length; i++) { (function (idx) { plantSelectButtons[idx].down = function (x, y, obj) { selectedPlantType = plantTypes[idx]; updatePlantSelectUI(); // Hide preview if showing if (plantPreview) { plantPreview.destroy(); plantPreview = null; } showPlantPreview(null, null); selectedLane = null; selectedColumn = null; }; })(i); } var resourceTxt = new Text2(resources + '', { size: 90, fill: 0xFFF700 }); resourceTxt.anchor.set(0.5, 0); LK.gui.top.addChild(resourceTxt); var waveTxt = new Text2('Wave 1/' + TOTAL_WAVES, { size: 70, fill: 0xFFFFFF }); waveTxt.anchor.set(0.5, 0); LK.gui.top.addChild(waveTxt); var breachTxt = new Text2('Breaches: 0/' + MAX_BREACHES, { size: 70, fill: 0xFF4444 }); breachTxt.anchor.set(0.5, 0); LK.gui.top.addChild(breachTxt); // Position UI resourceTxt.x = 120; // Position to the left of seed shop resourceTxt.y = plantSelectY; // Same height as plant selection buttons waveTxt.x = GAME_WIDTH / 2; waveTxt.y = 20; breachTxt.x = GAME_WIDTH - 400; breachTxt.y = 20; // --- Plant Preview (shows where plant will be placed) --- function showPlantPreview(lane, column) { if (plantPreview) { plantPreview.visible = false; } if (lane === null || column === null) return; if (plantPreview) { plantPreview.destroy(); plantPreview = null; } if (!plantPreview) { // Use correct asset for preview var assetId = selectedPlantType ? selectedPlantType.asset : 'plant'; plantPreview = LK.getAsset(assetId, { anchorX: 0.5, anchorY: 0.5, alpha: 0.5, width: 120, height: 120 }); game.addChild(plantPreview); } plantPreview.x = COLUMN_START_X + column * COLUMN_WIDTH; plantPreview.y = LANE_Y[lane]; plantPreview.visible = true; } // --- Plant Placement Handler --- function canPlacePlant(lane, column) { // Check if there's enough resources first if (resources < PLANT_COST) return false; // Check for existing plants at this position for (var i = 0; i < plants.length; i++) { if (plants[i].lane === lane && plants[i].column === column && !plants[i]._destroyed) { // Allow placement if existing plant is a trap AND selected plant is a shooter (for mega plant transformation) if (plants[i].constructor === TrapPlant && selectedPlantType.classRef === Plant) { return true; } // Allow placement if existing plant is a mega plant AND selected plant is a seed producer (for omega plant transformation) if (plants[i].constructor === MegaPlant && selectedPlantType.classRef === SeedProducerPlant && !omegaPlantCreated) { return true; } // Block placement for all other plant types return false; } } return true; } function placePlant(lane, column) { // Check if placing any plant on existing plant for special transformations for (var i = 0; i < plants.length; i++) { if (plants[i].lane === lane && plants[i].column === column && !plants[i]._destroyed) { // Check if placing seed producer on mega plant to create omega plant if (plants[i].constructor === MegaPlant && selectedPlantType.classRef === SeedProducerPlant) { if (!omegaPlantCreated) { // Transform mega plant into omega plant var existingMega = plants[i]; existingMega.destroy(); plants.splice(i, 1); var omegaPlant = new OmegaPlant(); omegaPlant.lane = lane; omegaPlant.column = column; omegaPlant.x = COLUMN_START_X + column * COLUMN_WIDTH; omegaPlant.y = LANE_Y[lane]; plants.push(omegaPlant); game.addChild(omegaPlant); omegaPlantCreated = true; // Special transformation effect LK.effects.flashObject(omegaPlant, 0x00FFFF, 1000); tween(omegaPlant, { scaleX: 0.3, scaleY: 0.3 }, { duration: 300, yoyo: true, repeat: 2 }); resources -= PLANT_COST; resourceTxt.setText(resources + ''); return true; } } // Check if existing plant is a trap if (plants[i].constructor === TrapPlant) { // Transform trap into mega plant var existingTrap = plants[i]; existingTrap.destroy(); plants.splice(i, 1); var megaPlant = new MegaPlant(); megaPlant.lane = lane; megaPlant.column = column; megaPlant.x = COLUMN_START_X + column * COLUMN_WIDTH; megaPlant.y = LANE_Y[lane]; plants.push(megaPlant); game.addChild(megaPlant); // Special transformation effect LK.effects.flashObject(megaPlant, 0xFFD700, 800); tween(megaPlant, { scaleX: 0.5, scaleY: 0.5 }, { duration: 200, yoyo: true, repeat: 1 }); resources -= PLANT_COST; resourceTxt.setText(resources + ''); return true; } } } // Prevent direct placement of omega plant - it can only be created through transformation if (selectedPlantType.classRef === OmegaPlant) { return false; } if (!canPlacePlant(lane, column)) return false; var plant; if (selectedPlantType && selectedPlantType.classRef) { plant = new selectedPlantType.classRef(); } else { // fallback plant = new Plant(); } plant.lane = lane; plant.column = column; plant.x = COLUMN_START_X + column * COLUMN_WIDTH; plant.y = LANE_Y[lane]; plants.push(plant); game.addChild(plant); resources -= PLANT_COST; resourceTxt.setText(resources + ''); return true; } // --- Touch Handler for Planting --- game.down = function (x, y, obj) { // Ignore top 100px (menu area) if (y < 100) return; // Find lane var lane = Math.floor(y / LANE_HEIGHT); if (lane < 0 || lane >= LANE_COUNT) return; // Find column var column = Math.floor((x - COLUMN_START_X + COLUMN_WIDTH / 2) / COLUMN_WIDTH); if (column < 0 || column >= COLUMN_COUNT) return; selectedLane = lane; selectedColumn = column; showPlantPreview(lane, column); }; game.move = function (x, y, obj) { if (y < 100) { showPlantPreview(null); selectedLane = null; selectedColumn = null; return; } var lane = Math.floor(y / LANE_HEIGHT); var column = Math.floor((x - COLUMN_START_X + COLUMN_WIDTH / 2) / COLUMN_WIDTH); if (lane < 0 || lane >= LANE_COUNT || column < 0 || column >= COLUMN_COUNT) { showPlantPreview(null); selectedLane = null; selectedColumn = null; return; } selectedLane = lane; selectedColumn = column; showPlantPreview(lane, column); }; game.up = function (x, y, obj) { if (selectedLane !== null && selectedColumn !== null) { if (canPlacePlant(selectedLane, selectedColumn)) { placePlant(selectedLane, selectedColumn); } } showPlantPreview(null); selectedLane = null; selectedColumn = null; }; // --- Resource Generation --- var resourceTimer = LK.setInterval(function () { resources += 1; resourceTxt.setText(resources + ''); }, 500); // Increased resource gain rate (was 1000ms, now 500ms) // --- Wave System --- function startWave() { waveInProgress = true; waveTimer = 0; nextPestTick = 0; waveTxt.setText('Wave ' + wave + '/' + TOTAL_WAVES); // Show wave banner if (!game._waveBanner) { var banner = new Text2('Wave ' + wave, { size: 200, fill: 0xFFF700, font: "Impact", stroke: "#222", strokeThickness: 10 }); banner.anchor.set(0.5, 0.5); banner.x = GAME_WIDTH / 2; banner.y = GAME_HEIGHT / 2; banner.alpha = 0.0; game._waveBanner = banner; game.addChild(banner); } else { game._waveBanner.setText('Wave ' + wave); } game._waveBanner.alpha = 1.0; game._waveBanner.visible = true; // Fade out after 1.2s tween(game._waveBanner, { alpha: 0 }, { duration: 900, delay: 1200, onFinish: function onFinish() { game._waveBanner.visible = false; } }); } function endWave() { waveInProgress = false; wave++; if (wave > TOTAL_WAVES) { // Show special victory message for completing extra levels LK.showYouWin(); } else { // Show special message when entering extra levels if (wave === 21) { var extraBanner = new Text2('EXTRA LEVELS!', { size: 180, fill: 0xFF0000, font: "Impact", stroke: "#000", strokeThickness: 8 }); extraBanner.anchor.set(0.5, 0.5); extraBanner.x = GAME_WIDTH / 2; extraBanner.y = GAME_HEIGHT / 2 - 200; extraBanner.alpha = 0.0; game.addChild(extraBanner); tween(extraBanner, { alpha: 1 }, { duration: 600, onFinish: function onFinish() { tween(extraBanner, { alpha: 0 }, { duration: 600, delay: 2000, onFinish: function onFinish() { extraBanner.destroy(); } }); } }); } // Short pause before next wave LK.setTimeout(function () { startWave(); }, 1800); } } // --- Pest Spawning --- function spawnPest() { var lane = Math.floor(Math.random() * LANE_COUNT); var pest; // Extra levels (21-25) spawn special enemies if (wave >= 21) { var extraLevelRand = Math.random(); if (extraLevelRand < 0.3) { // 30% chance for Armor Pest pest = new ArmorPest(); } else if (extraLevelRand < 0.6) { // 30% chance for Ghost Pest pest = new GhostPest(); } else if (extraLevelRand < 0.8) { // 20% chance for multiple Swarm Pests for (var i = 0; i < 3; i++) { var swarmPest = new SwarmPest(); swarmPest.lane = lane; swarmPest.x = GAME_WIDTH - 100 + i * 50; swarmPest.y = LANE_Y[lane]; pests.push(swarmPest); game.addChild(swarmPest); } return; // Early return since we spawned multiple } else { // 20% chance for MegaRat (still appears in extra levels) pest = new MegaRat(); if (lane >= LANE_COUNT - 1) { lane = LANE_COUNT - 2; } } } else if (wave >= 20 && Math.random() < 0.08) { // 8% chance to spawn MegaRat from wave 20+ pest = new MegaRat(); // Ensure MegaRat doesn't spawn in bottom lane (needs space for double height) if (lane >= LANE_COUNT - 1) { lane = LANE_COUNT - 2; } } else if (wave >= 5 && wave <= 15 && Math.random() < 0.15) { // 15% chance to spawn PartyRat from wave 5-15 pest = new PartyRat(); } else if (wave >= 3 && wave <= 15) { pest = new FastPest(); } else { pest = new Pest(); } pest.lane = lane; pest.x = GAME_WIDTH - 100; pest.y = LANE_Y[lane]; pests.push(pest); game.addChild(pest); } // --- Main Game Update --- game.update = function () { // --- Wave logic --- if (!waveInProgress) { startWave(); return; } waveTimer++; // Pest spawn logic: spawn a pest every 60-90 ticks, up to 6+wave pests per wave if (nextPestTick <= 0) { if (waveTimer < (6 + wave) * 80) { spawnPest(); nextPestTick = 60 + Math.floor(Math.random() * 30); } } else { nextPestTick--; } // End wave if all pests spawned and none left if (waveTimer > (6 + wave) * 80 && pests.length === 0) { endWave(); return; } // --- Plants fire seeds --- for (var i = plants.length - 1; i >= 0; i--) { var plant = plants[i]; plant.update(); // Remove destroyed plants if (plant._destroyed) { plant.destroy(); plants.splice(i, 1); continue; } // Only shooter plants fire seeds if (typeof plant.tryFire === "function") { // Fire if pest in lane and cooldown ready var pestInLane = false; // Special logic for Omega plant - fires if any pest exists if (plant.constructor === OmegaPlant) { for (var j = 0; j < pests.length; j++) { if (pests[j].x > plant.x) { pestInLane = true; break; } } } else { // Regular plants only fire at pests in their lane for (var j = 0; j < pests.length; j++) { if (pests[j].lane === plant.lane && pests[j].x > plant.x) { pestInLane = true; break; } } } if (pestInLane && plant.tryFire()) { // Omega plant fires seeds in every lane if (plant.constructor === OmegaPlant) { for (var k = 0; k < LANE_COUNT; k++) { var seed = new Seed(); seed.x = plant.x + 60; seed.y = LANE_Y[k]; seed.lane = k; seed.speed = 15; // Very fast seeds seeds.push(seed); game.addChild(seed); } // Fire effect on omega plant tween(plant, { scaleX: 1.4, scaleY: 1.4 }, { duration: 80, yoyo: true, repeat: 1 }); LK.effects.flashObject(plant, 0x00FFFF, 200); // Crystal plant fires piercing laser } else if (plant.constructor === CrystalPlant) { var seed = new Seed(); seed.x = plant.x + 60; seed.y = plant.y; seed.lane = plant.lane; seed.speed = 20; // Very fast piercing shot seed.piercing = true; // Special property for piercing seeds.push(seed); game.addChild(seed); // Crystal firing effect tween(plant, { scaleX: 1.2, scaleY: 1.2, tint: 0x00FFFF }, { duration: 150, yoyo: true, repeat: 1, onFinish: function onFinish() { plant.tint = 0xFFFFFF; } }); // Poison plant creates toxic cloud } else if (plant.constructor === PoisonPlant) { // Create multiple poison seeds in a spread for (var k = -1; k <= 1; k++) { var targetLane = plant.lane + k; if (targetLane >= 0 && targetLane < LANE_COUNT) { var seed = new Seed(); seed.x = plant.x + 60; seed.y = LANE_Y[targetLane]; seed.lane = targetLane; seed.speed = 8; // Slower poison projectiles seed.poison = true; // Special property for poison seeds.push(seed); game.addChild(seed); } } // Poison effect tween(plant, { scaleX: 1.3, scaleY: 1.3, tint: 0x9ACD32 }, { duration: 200, yoyo: true, repeat: 1, onFinish: function onFinish() { plant.tint = 0xFFFFFF; } }); // Mega plant fires 3 seeds in spread pattern } else if (plant.constructor === MegaPlant) { for (var k = -1; k <= 1; k++) { var targetLane = plant.lane + k; if (targetLane >= 0 && targetLane < LANE_COUNT) { var seed = new Seed(); seed.x = plant.x + 60; seed.y = plant.y; seed.lane = targetLane; seed.speed = 12; // Faster seeds seeds.push(seed); game.addChild(seed); } } // Fire effect on mega plant tween(plant, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, yoyo: true, repeat: 1 }); } else { var seed = new Seed(); seed.x = plant.x + 60; seed.y = plant.y; seed.lane = plant.lane; seeds.push(seed); game.addChild(seed); } } } } // --- Seeds move and hit pests --- for (var s = seeds.length - 1; s >= 0; s--) { var seed = seeds[s]; seed.update(); var hit = false; for (var p = 0; p < pests.length; p++) { var pest = pests[p]; var canHit = false; // Check if seed can hit this pest if (pest.constructor === MegaRat) { // MegaRat can be hit by seeds in either of its occupied lanes canHit = pest.occupiedLanes && pest.occupiedLanes.indexOf(seed.lane) !== -1; } else { // Regular pests use normal lane matching canHit = pest.lane === seed.lane; } if (canHit && !pest.destroyed && Math.abs(seed.x - pest.x) < 60) { pest.hit(1); hit = true; break; } } if (hit || seed.x > GAME_WIDTH + 50) { seed.destroy(); seeds.splice(s, 1); } } // --- Pests move and check for breach or death --- for (var p = pests.length - 1; p >= 0; p--) { var pest = pests[p]; if (pest.destroyed) { pest.destroy(); pests.splice(p, 1); continue; } pest.update(); if (pest.x < 80) { // Breach! breaches++; breachTxt.setText('Breaches: ' + breaches + '/' + MAX_BREACHES); LK.effects.flashScreen(0xff0000, 400); pest.destroy(); pests.splice(p, 1); if (breaches >= MAX_BREACHES) { LK.showGameOver(); return; } } } }; // --- Clean up on game over/win --- game.on('destroy', function () { LK.clearInterval(resourceTimer); plants = []; pests = []; seeds = []; breaches = 0; resources = START_RESOURCES; wave = 1; waveInProgress = false; omegaPlantCreated = false; if (plantPreview) { plantPreview.destroy(); plantPreview = null; } if (game._waveBanner) { game._waveBanner.visible = false; game._waveBanner.alpha = 0; } }); // --- Initial UI update --- resourceTxt.setText(resources + ''); waveTxt.setText('Wave 1/' + TOTAL_WAVES); breachTxt.setText('Breaches: 0/' + MAX_BREACHES); // --- Start background music --- LK.playMusic('Hi1234');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Armor Pest - heavily armored, slow but very tough
var ArmorPest = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('armorPest', {
anchorX: 0.5,
anchorY: 0.5,
width: 130,
height: 130
});
self.lane = null;
self.hp = 20; // Very high health
self.speed = 1.0; // Slow movement
self.destroyed = false;
self.armor = 2; // Reduces incoming damage
self.update = function () {
if (self.destroyed) return;
if (self.lastX === undefined) self.lastX = self.x;
if (self.eatingPlant && !self.eatingPlant._destroyed) {
if (self.eatCooldown === undefined) self.eatCooldown = 0;
if (self._chewTweening !== true) {
self._chewTweening = true;
tween(self, {
scaleX: 1.1
}, {
duration: 100,
yoyo: true,
repeat: 2,
onFinish: function onFinish() {
self.scaleX = 1.0;
self._chewTweening = false;
}
});
}
if (self.eatCooldown > 0) {
self.eatCooldown--;
} else {
if (typeof self.eatingPlant.hp === "number") {
self.eatingPlant.hp -= 2; // Heavy damage to plants
if (self.eatingPlant.hp <= 0) {
self.eatingPlant._destroyed = true;
if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy();
self.eatingPlant = null;
self.scaleX = 1.0;
self._chewTweening = false;
}
}
self.eatCooldown = 45; // Slower eating
}
} else {
self.x -= self.speed;
var foundPlant = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) {
foundPlant = plant;
break;
}
}
if (foundPlant) {
self.eatingPlant = foundPlant;
self.eatCooldown = 0;
}
}
self.lastX = self.x;
};
self.hit = function (dmg) {
if (self.destroyed) return;
// Armor reduces damage
var actualDamage = Math.max(1, dmg - self.armor);
self.hp -= actualDamage;
if (self.hp <= 0) {
self.destroyed = true;
}
// Visual feedback for armor
LK.effects.flashObject(self, 0x696969, 100);
};
return self;
});
// Crystal Plant - fires piercing laser beams that go through multiple enemies
var CrystalPlant = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('crystalPlant', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 150
});
self.fireCooldown = 45;
self.cooldown = 0;
self.lane = null;
self.column = null;
self.hp = 8;
self._destroyed = false;
self._chargeTimer = 0;
self.update = function () {
if (self.cooldown > 0) self.cooldown--;
if (self.hp <= 0 && !self._destroyed) {
self._destroyed = true;
if (typeof self.destroy === "function") self.destroy();
}
// Charge animation every 30 frames
self._chargeTimer++;
if (self._chargeTimer >= 30) {
self._chargeTimer = 0;
tween(sprite, {
scaleX: 1.1,
scaleY: 1.1,
tint: 0x00FFFF
}, {
duration: 200,
yoyo: true,
repeat: 1,
onFinish: function onFinish() {
sprite.tint = 0xFFFFFF;
}
});
}
};
self.tryFire = function () {
if (self.cooldown <= 0) {
self.cooldown = self.fireCooldown;
return true;
}
return false;
};
return self;
});
// Fast, tough pest that spawns on wave 3
var FastPest = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('fastPest', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120
});
self.lane = null;
self.hp = 5; // More health than basic pest
self.speed = 4.5; // Much faster than basic pest
self.destroyed = false;
self.update = function () {
if (self.destroyed) return;
// Initialize lastX for transition checks
if (self.lastX === undefined) self.lastX = self.x;
// If currently eating a plant, handle eating logic
if (self.eatingPlant && !self.eatingPlant._destroyed) {
// Only bite every 30 frames
if (self.eatCooldown === undefined) self.eatCooldown = 0;
if (self._chewTweening !== true) {
self._chewTweening = true;
tween(self, {
scaleX: 1.2
}, {
duration: 80,
yoyo: true,
repeat: 2,
onFinish: function onFinish() {
self.scaleX = 1.0;
self._chewTweening = false;
}
});
}
if (self.eatCooldown > 0) {
self.eatCooldown--;
} else {
// Damage the plant
if (typeof self.eatingPlant.hp === "number") {
self.eatingPlant.hp -= 1;
// Optional: flash or animate plant here
if (self.eatingPlant.hp <= 0) {
self.eatingPlant._destroyed = true;
if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy();
// Remove from plants array in main update
self.eatingPlant = null;
self.scaleX = 1.0;
self._chewTweening = false;
}
}
self.eatCooldown = 30;
}
// Don't move while eating
} else {
// Not eating, move left
self.x -= self.speed;
// Check for collision with plant in same lane
var foundPlant = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) {
foundPlant = plant;
break;
}
}
if (foundPlant) {
self.eatingPlant = foundPlant;
self.eatCooldown = 0;
}
}
// Update lastX
self.lastX = self.x;
};
self.hit = function (dmg) {
if (self.destroyed) return;
self.hp -= dmg;
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Ghost Pest - phases through plants occasionally and has moderate health
var GhostPest = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('ghostPest', {
anchorX: 0.5,
anchorY: 0.5,
width: 110,
height: 110
});
self.lane = null;
self.hp = 8;
self.speed = 2.5;
self.destroyed = false;
self.phaseTimer = 0;
self.phaseCooldown = 180; // 3 seconds
self.isPhasing = false;
self.update = function () {
if (self.destroyed) return;
if (self.lastX === undefined) self.lastX = self.x;
// Handle phasing ability
self.phaseTimer++;
if (self.phaseTimer >= self.phaseCooldown) {
self.phaseTimer = 0;
self.isPhasing = true;
sprite.alpha = 0.3;
tween(sprite, {
tint: 0x8A2BE2
}, {
duration: 1000,
onFinish: function onFinish() {
self.isPhasing = false;
sprite.alpha = 1.0;
sprite.tint = 0xFFFFFF;
}
});
}
// Move left
self.x -= self.speed;
// Only check plant collisions if not phasing
if (!self.isPhasing) {
if (self.eatingPlant && !self.eatingPlant._destroyed) {
if (self.eatCooldown === undefined) self.eatCooldown = 0;
if (self._chewTweening !== true) {
self._chewTweening = true;
tween(self, {
scaleX: 1.2
}, {
duration: 80,
yoyo: true,
repeat: 2,
onFinish: function onFinish() {
self.scaleX = 1.0;
self._chewTweening = false;
}
});
}
if (self.eatCooldown > 0) {
self.eatCooldown--;
} else {
if (typeof self.eatingPlant.hp === "number") {
self.eatingPlant.hp -= 1;
if (self.eatingPlant.hp <= 0) {
self.eatingPlant._destroyed = true;
if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy();
self.eatingPlant = null;
self.scaleX = 1.0;
self._chewTweening = false;
}
}
self.eatCooldown = 30;
}
} else {
var foundPlant = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) {
foundPlant = plant;
break;
}
}
if (foundPlant) {
self.eatingPlant = foundPlant;
self.eatCooldown = 0;
}
}
}
self.lastX = self.x;
};
self.hit = function (dmg) {
if (self.destroyed || self.isPhasing) return; // Immune to damage while phasing
self.hp -= dmg;
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Mega plant - secret powerful plant created by placing trap on existing plant
var MegaPlant = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('megaPlant', {
anchorX: 0.5,
anchorY: 0.5,
width: 180,
height: 180
});
self.fireCooldown = 20; // Very fast firing
self.cooldown = 0;
self.lane = null;
self.column = null;
self.hp = 20; // Very high health
self._destroyed = false;
self._pulseTimer = 0;
self.update = function () {
if (self.cooldown > 0) self.cooldown--;
if (self.hp <= 0 && !self._destroyed) {
self._destroyed = true;
if (typeof self.destroy === "function") self.destroy();
}
// Pulse animation every 60 frames
self._pulseTimer++;
if (self._pulseTimer >= 60) {
self._pulseTimer = 0;
tween(sprite, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFF69B4
}, {
duration: 300,
yoyo: true,
repeat: 1,
onFinish: function onFinish() {
sprite.tint = 0xFFFFFF;
}
});
}
};
self.tryFire = function () {
if (self.cooldown <= 0) {
self.cooldown = self.fireCooldown;
return true;
}
return false;
};
return self;
});
// Mega Rat - massive pest with highest health that instantly kills plants and spans two lanes
var MegaRat = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('megaRat', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 300
});
self.lane = null;
self.hp = 50; // Highest health in the game
self.speed = 1.0; // Slow but devastating
self.destroyed = false;
self.occupiedLanes = []; // Tracks which lanes this mega rat occupies
self.spawnCooldown = 90; // 1.5 seconds at 60fps - rapid spawning
self.spawnTimer = 0;
self.update = function () {
if (self.destroyed) return;
// Initialize lastX for transition checks
if (self.lastX === undefined) self.lastX = self.x;
// Handle rapid PartyRat spawning timer
self.spawnTimer++;
if (self.spawnTimer >= self.spawnCooldown) {
self.spawnTimer = 0;
self.spawnPartyRats();
}
// Update occupied lanes (current lane and one below)
self.occupiedLanes = [self.lane];
if (self.lane + 1 < LANE_COUNT) {
self.occupiedLanes.push(self.lane + 1);
}
// Move left
self.x -= self.speed;
// Check for collision with plants in occupied lanes - instant kill
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (!plant._destroyed && self.occupiedLanes.indexOf(plant.lane) !== -1 && Math.abs(self.x - plant.x) < 150) {
// Instantly kill the plant
plant.hp = 0;
plant._destroyed = true;
if (typeof plant.destroy === "function") plant.destroy();
plants.splice(i, 1);
// Flash effect when killing plant
LK.effects.flashObject(self, 0xFF0000, 200);
}
}
// Update lastX
self.lastX = self.x;
};
self.spawnPartyRats = function () {
// Spawn regular Pests in all lanes
for (var i = 0; i < LANE_COUNT; i++) {
var newPest = new Pest();
newPest.lane = i;
newPest.x = self.x + 50; // Spawn slightly ahead of MegaRat
newPest.y = LANE_Y[i];
pests.push(newPest);
game.addChild(newPest);
// Visual effect for spawning
LK.effects.flashObject(newPest, 0xFF69B4, 300);
}
// Visual effect on the mega rat when it spawns
LK.effects.flashObject(self, 0xFF0000, 500);
};
self.hit = function (dmg) {
if (self.destroyed) return;
self.hp -= dmg;
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Omega Plant - ultimate plant that shoots in every lane, created by placing seed producer on mega plant
var OmegaPlant = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('omegaPlant', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 200
});
self.fireCooldown = 30; // Very fast firing
self.cooldown = 0;
self.lane = null;
self.column = null;
self.hp = 30; // Extremely high health
self._destroyed = false;
self._pulseTimer = 0;
self.update = function () {
if (self.cooldown > 0) self.cooldown--;
if (self.hp <= 0 && !self._destroyed) {
self._destroyed = true;
if (typeof self.destroy === "function") self.destroy();
}
// Pulse animation every 45 frames
self._pulseTimer++;
if (self._pulseTimer >= 45) {
self._pulseTimer = 0;
tween(sprite, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0x00FFFF
}, {
duration: 250,
yoyo: true,
repeat: 1,
onFinish: function onFinish() {
sprite.tint = 0xFFFFFF;
}
});
}
};
self.tryFire = function () {
if (self.cooldown <= 0) {
self.cooldown = self.fireCooldown;
return true;
}
return false;
};
return self;
});
// --- Pest Classes ---
// Party Rat - high health pest that spawns other pests in 4 directions every 6 seconds
var PartyRat = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('partyRat', {
anchorX: 0.5,
anchorY: 0.5,
width: 140,
height: 140
});
self.lane = null;
self.hp = 15; // Very high health
self.speed = 1.5; // Slower than other pests
self.destroyed = false;
self.spawnCooldown = 270; // 4.5 seconds at 60fps
self.spawnTimer = 0;
self.update = function () {
if (self.destroyed) return;
// Initialize lastX for transition checks
if (self.lastX === undefined) self.lastX = self.x;
// Handle spawning timer
self.spawnTimer++;
if (self.spawnTimer >= self.spawnCooldown) {
self.spawnTimer = 0;
self.spawnPests();
}
// If currently eating a plant, handle eating logic
if (self.eatingPlant && !self.eatingPlant._destroyed) {
// Only bite every 30 frames
if (self.eatCooldown === undefined) self.eatCooldown = 0;
if (self._chewTweening !== true) {
self._chewTweening = true;
tween(self, {
scaleX: 1.3
}, {
duration: 100,
yoyo: true,
repeat: 2,
onFinish: function onFinish() {
self.scaleX = 1.0;
self._chewTweening = false;
}
});
}
if (self.eatCooldown > 0) {
self.eatCooldown--;
} else {
// Damage the plant
if (typeof self.eatingPlant.hp === "number") {
self.eatingPlant.hp -= 2; // Party rat does more damage
if (self.eatingPlant.hp <= 0) {
self.eatingPlant._destroyed = true;
if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy();
self.eatingPlant = null;
self.scaleX = 1.0;
self._chewTweening = false;
}
}
self.eatCooldown = 30;
}
} else {
// Not eating, move left
self.x -= self.speed;
// Check for collision with plant in same lane
var foundPlant = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) {
foundPlant = plant;
break;
}
}
if (foundPlant) {
self.eatingPlant = foundPlant;
self.eatCooldown = 0;
}
}
// Update lastX
self.lastX = self.x;
};
self.spawnPests = function () {
// Spawn pests in 4 directions: front, back, up lane, down lane
var spawnPositions = [{
x: self.x + 100,
lane: self.lane
},
// Front (right)
{
x: self.x - 100,
lane: self.lane
},
// Behind (left)
{
x: self.x,
lane: Math.max(0, self.lane - 1)
},
// Up lane
{
x: self.x,
lane: Math.min(LANE_COUNT - 1, self.lane + 1)
} // Down lane
];
for (var i = 0; i < spawnPositions.length; i++) {
var pos = spawnPositions[i];
// Don't spawn if position is off-screen or invalid
if (pos.x > 50 && pos.x < GAME_WIDTH - 50) {
var newPest = new Pest();
newPest.lane = pos.lane;
newPest.x = pos.x;
newPest.y = LANE_Y[pos.lane];
pests.push(newPest);
game.addChild(newPest);
// Visual effect for spawning
LK.effects.flashObject(newPest, 0xFF69B4, 300);
}
}
// Visual effect on the party rat when it spawns
LK.effects.flashObject(self, 0xFFFF00, 500);
};
self.hit = function (dmg) {
if (self.destroyed) return;
self.hp -= dmg;
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Basic pest that moves left and can be hit by seeds
var Pest = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('pest', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 100
});
self.lane = null;
self.hp = 2;
self.speed = 2.0;
self.destroyed = false;
self.update = function () {
if (self.destroyed) return;
// Initialize lastX for transition checks
if (self.lastX === undefined) self.lastX = self.x;
// If currently eating a plant, handle eating logic
if (self.eatingPlant && !self.eatingPlant._destroyed) {
// Only bite every 30 frames
if (self.eatCooldown === undefined) self.eatCooldown = 0;
if (self._chewTweening !== true) {
self._chewTweening = true;
tween(self, {
scaleX: 1.2
}, {
duration: 80,
yoyo: true,
repeat: 2,
onFinish: function onFinish() {
self.scaleX = 1.0;
self._chewTweening = false;
}
});
}
if (self.eatCooldown > 0) {
self.eatCooldown--;
} else {
// Damage the plant
if (typeof self.eatingPlant.hp === "number") {
self.eatingPlant.hp -= 1;
// Optional: flash or animate plant here
if (self.eatingPlant.hp <= 0) {
self.eatingPlant._destroyed = true;
if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy();
// Remove from plants array in main update
self.eatingPlant = null;
self.scaleX = 1.0;
self._chewTweening = false;
}
}
self.eatCooldown = 30;
}
// Don't move while eating
} else {
// Not eating, move left
self.x -= self.speed;
// Check for collision with plant in same lane
var foundPlant = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) {
foundPlant = plant;
break;
}
}
if (foundPlant) {
self.eatingPlant = foundPlant;
self.eatCooldown = 0;
}
}
// Update lastX
self.lastX = self.x;
};
self.hit = function (dmg) {
if (self.destroyed) return;
self.hp -= dmg;
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// --- Plant Classes ---
// Basic shooter plant
var Plant = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('plant', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120
});
self.fireCooldown = 60; // frames between shots
self.cooldown = 0;
self.lane = null;
self.column = null;
self.hp = 5; // Plant health
self._destroyed = false;
self.update = function () {
if (self.cooldown > 0) self.cooldown--;
if (self.hp <= 0 && !self._destroyed) {
self._destroyed = true;
if (typeof self.destroy === "function") self.destroy();
}
};
self.tryFire = function () {
if (self.cooldown <= 0) {
self.cooldown = self.fireCooldown;
return true;
}
return false;
};
return self;
});
// Poison Plant - creates toxic clouds that slow and damage enemies over time
var PoisonPlant = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('poisonPlant', {
anchorX: 0.5,
anchorY: 0.5,
width: 140,
height: 140
});
self.fireCooldown = 90;
self.cooldown = 0;
self.lane = null;
self.column = null;
self.hp = 6;
self._destroyed = false;
self._toxicTimer = 0;
self.update = function () {
if (self.cooldown > 0) self.cooldown--;
if (self.hp <= 0 && !self._destroyed) {
self._destroyed = true;
if (typeof self.destroy === "function") self.destroy();
}
// Toxic bubble animation every 60 frames
self._toxicTimer++;
if (self._toxicTimer >= 60) {
self._toxicTimer = 0;
tween(sprite, {
scaleX: 1.15,
scaleY: 1.15,
tint: 0x9ACD32
}, {
duration: 250,
yoyo: true,
repeat: 1,
onFinish: function onFinish() {
sprite.tint = 0xFFFFFF;
}
});
}
};
self.tryFire = function () {
if (self.cooldown <= 0) {
self.cooldown = self.fireCooldown;
return true;
}
return false;
};
return self;
});
// --- Seed Class ---
// Seed projectile fired by shooter plants
var Seed = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('seed', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40
});
self.lane = null;
self.speed = 10;
self.update = function () {
self.x += self.speed;
};
return self;
});
// Plant that generates resources
var SeedProducerPlant = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('seedProducerPlant', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120
});
self.resourceCooldown = 90; // frames between resource generation (was 180, now 90 for faster seed gain)
self.cooldown = 0;
self.lane = null;
self.column = null;
self.hp = 5; // Plant health
self._destroyed = false;
self._seedsProduced = 0;
self.update = function () {
if (self.cooldown > 0) {
self.cooldown--;
} else {
// Give resource and reset cooldown
resources += 1;
resourceTxt.setText(resources + '');
self.cooldown = self.resourceCooldown;
self._seedsProduced = (self._seedsProduced || 0) + 1;
if (self._seedsProduced % 2 === 0) {
// Glow pink using tween (tint to pink, then back to normal)
tween.stop(sprite, {
tint: true
});
var originalTint = sprite.tint;
tween(sprite, {
tint: 0xFF69B4
}, {
duration: 120,
yoyo: true,
repeat: 1,
onFinish: function onFinish() {
sprite.tint = originalTint;
}
});
}
}
if (self.hp <= 0 && !self._destroyed) {
self._destroyed = true;
if (typeof self.destroy === "function") self.destroy();
}
};
return self;
});
// Shield Plant - provides protection to nearby plants
var ShieldPlant = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('shieldPlant', {
anchorX: 0.5,
anchorY: 0.5,
width: 130,
height: 130
});
self.lane = null;
self.column = null;
self.hp = 12;
self._destroyed = false;
self._shieldTimer = 0;
self.shieldRadius = 200;
self.update = function () {
if (self.hp <= 0 && !self._destroyed) {
self._destroyed = true;
if (typeof self.destroy === "function") self.destroy();
}
// Shield pulse animation every 90 frames
self._shieldTimer++;
if (self._shieldTimer >= 90) {
self._shieldTimer = 0;
tween(sprite, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0x4169E1
}, {
duration: 300,
yoyo: true,
repeat: 1,
onFinish: function onFinish() {
sprite.tint = 0xFFFFFF;
}
});
}
};
return self;
});
// Swarm Pest - small, fast, spawns in groups
var SwarmPest = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('swarmPest', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
self.lane = null;
self.hp = 1; // Very low health
self.speed = 4.0; // Very fast
self.destroyed = false;
self.update = function () {
if (self.destroyed) return;
if (self.lastX === undefined) self.lastX = self.x;
if (self.eatingPlant && !self.eatingPlant._destroyed) {
if (self.eatCooldown === undefined) self.eatCooldown = 0;
if (self._chewTweening !== true) {
self._chewTweening = true;
tween(self, {
scaleX: 1.3
}, {
duration: 60,
yoyo: true,
repeat: 3,
onFinish: function onFinish() {
self.scaleX = 1.0;
self._chewTweening = false;
}
});
}
if (self.eatCooldown > 0) {
self.eatCooldown--;
} else {
if (typeof self.eatingPlant.hp === "number") {
self.eatingPlant.hp -= 1;
if (self.eatingPlant.hp <= 0) {
self.eatingPlant._destroyed = true;
if (typeof self.eatingPlant.destroy === "function") self.eatingPlant.destroy();
self.eatingPlant = null;
self.scaleX = 1.0;
self._chewTweening = false;
}
}
self.eatCooldown = 20; // Fast eating
}
} else {
self.x -= self.speed;
var foundPlant = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.lane === self.lane && !plant._destroyed && Math.abs(self.x - plant.x) < 60) {
foundPlant = plant;
break;
}
}
if (foundPlant) {
self.eatingPlant = foundPlant;
self.eatCooldown = 0;
}
}
self.lastX = self.x;
};
self.hit = function (dmg) {
if (self.destroyed) return;
self.hp -= dmg;
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Trap plant that eats any pest that touches it, then disappears
var TrapPlant = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('trapPlant', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120
});
self.lane = null;
self.column = null;
self.hp = 1; // Dies after eating one pest
self._destroyed = false;
self.hasEaten = false;
self.cooldownTime = 6000; // 6 second cooldown in milliseconds
self.cooldownRemaining = 0;
self.onCooldown = false;
self.update = function () {
if (self._destroyed || self.hasEaten) return;
// Handle cooldown
if (self.onCooldown) {
self.cooldownRemaining -= 16; // Approximately 16ms per frame at 60fps
if (self.cooldownRemaining <= 0) {
self.onCooldown = false;
// Reset visual appearance when cooldown ends
sprite.tint = 0xffffff;
sprite.alpha = 1.0;
}
return; // Don't check for collisions while on cooldown
}
// Check for collision with any pest in same lane - improved collision detection
for (var i = pests.length - 1; i >= 0; i--) {
var pest = pests[i];
// Check if pest is in same lane and within touching distance
if (pest.lane === self.lane && !pest.destroyed && Math.abs(self.x - pest.x) < 80 && Math.abs(self.y - pest.y) < 60) {
// Instantly kill the pest - set HP to 0 and destroy immediately
pest.hp = 0;
pest.destroyed = true;
pest.destroy();
// Remove from pests array immediately
pests.splice(i, 1);
// Start cooldown instead of destroying plant
self.onCooldown = true;
self.cooldownRemaining = self.cooldownTime;
// Visual feedback for cooldown - make plant greyish and semi-transparent
sprite.tint = 0x666666;
sprite.alpha = 0.6;
// Flash effect on successful kill
LK.effects.flashObject(self, 0x00ff00, 200);
break;
}
}
if (self.hp <= 0 && !self._destroyed) {
self._destroyed = true;
if (typeof self.destroy === "function") self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x7ec850 // Light green garden
});
/****
* Game Code
****/
// Add background image
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(background);
// --- Constants ---
// Unique sprite for fast, tough pest
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var LANE_COUNT = 7;
var LANE_HEIGHT = Math.floor(GAME_HEIGHT / LANE_COUNT);
var LANE_Y = [];
for (var i = 0; i < LANE_COUNT; i++) {
LANE_Y[i] = Math.floor((i + 0.5) * LANE_HEIGHT);
}
var PLANT_COST = 10;
var START_RESOURCES = 20;
var MAX_BREACHES = 5;
var TOTAL_WAVES = 25; // Extended to include 5 extra levels
var COLUMN_COUNT = 5; // Number of plant columns
var COLUMN_WIDTH = 160; // Distance between columns
var COLUMN_START_X = 200; // X position of first column
// --- Asset Initialization ---
// --- Game State ---
var plants = []; // All plant objects
var pests = []; // All pest objects
var seeds = []; // All seed objects
var rockets = []; // All rocket objects (initialized in global game state)
var resources = START_RESOURCES;
var breaches = 0;
var wave = 1;
var waveInProgress = false;
var waveTimer = 0;
var nextPestTick = 0;
var selectedLane = null; // Lane index for planting
var selectedColumn = null; // Column index for planting
var plantPreview = null; // Preview asset for planting
var omegaPlantCreated = false; // Track if omega plant has been created (only one allowed)
// --- Lane Markers (for touch feedback) ---
var laneMarkers = [];
for (var i = 0; i < LANE_COUNT; i++) {
for (var col = 0; col < COLUMN_COUNT; col++) {
// Use a unique grid sprite for the grid cell background
var marker = LK.getAsset('grid', {
anchorX: 0.5,
anchorY: 0.5,
x: COLUMN_START_X + col * COLUMN_WIDTH,
y: LANE_Y[i],
alpha: 0.8,
width: 180,
height: LANE_HEIGHT - 10,
tint: 0xD2B48C
});
game.addChild(marker);
laneMarkers.push(marker);
}
}
// --- UI Elements ---
// Plant selection UI
var plantTypes = [{
id: 'plant',
name: 'Shooter',
asset: 'plant',
desc: 'Shoots seeds',
classRef: Plant
}, {
id: 'seedProducer',
name: 'Producer',
asset: 'seedProducerPlant',
desc: 'Generates seeds',
classRef: SeedProducerPlant
}, {
id: 'trapPlant',
name: 'Trap',
asset: 'trapPlant',
desc: 'Eats one pest',
classRef: TrapPlant
}, {
id: 'megaPlant',
name: 'Mega',
asset: 'megaPlant',
desc: 'Secret plant',
classRef: MegaPlant
}, {
id: 'omegaPlant',
name: 'Omega',
asset: 'omegaPlant',
desc: 'Ultimate plant',
classRef: OmegaPlant
}, {
id: 'crystalPlant',
name: 'Crystal',
asset: 'crystalPlant',
desc: 'Piercing laser',
classRef: CrystalPlant
}, {
id: 'shieldPlant',
name: 'Shield',
asset: 'shieldPlant',
desc: 'Protects plants',
classRef: ShieldPlant
}, {
id: 'poisonPlant',
name: 'Poison',
asset: 'poisonPlant',
desc: 'Toxic clouds',
classRef: PoisonPlant
}];
var selectedPlantType = plantTypes[0]; // Default to shooter
var plantSelectButtons = [];
var plantSelectY = 120;
var plantSelectSpacing = 220;
for (var i = 0; i < plantTypes.length; i++) {
var btnX = 200 + i * plantSelectSpacing;
var btn = LK.getAsset(plantTypes[i].asset, {
anchorX: 0.5,
anchorY: 0.5,
x: btnX,
y: plantSelectY,
width: 120,
height: 120,
alpha: 0.9
});
btn._plantTypeIndex = i;
btn.interactive = true;
btn.buttonMode = true;
// Add a border to show selection
btn._border = LK.getAsset(plantTypes[i].asset, {
anchorX: 0.5,
anchorY: 0.5,
x: btnX,
y: plantSelectY,
width: 140,
height: 140,
alpha: 0.0 // Will set to visible for selected
});
LK.gui.top.addChild(btn._border);
LK.gui.top.addChild(btn);
plantSelectButtons.push(btn);
// Add label
var label = new Text2(plantTypes[i].name, {
size: 40,
fill: "#222"
});
label.anchor.set(0.5, 0);
label.x = btnX;
label.y = plantSelectY + 70;
LK.gui.top.addChild(label);
// Add price label
var priceLabel = new Text2(PLANT_COST + ' seeds', {
size: 30,
fill: "#444"
});
priceLabel.anchor.set(0.5, 0);
priceLabel.x = btnX;
priceLabel.y = plantSelectY + 110;
LK.gui.top.addChild(priceLabel);
}
// Helper to update plant selection UI
function updatePlantSelectUI() {
for (var i = 0; i < plantSelectButtons.length; i++) {
if (plantTypes[i] === selectedPlantType) {
plantSelectButtons[i]._border.alpha = 0.7;
plantSelectButtons[i]._border.tint = 0xFFD700;
} else {
plantSelectButtons[i]._border.alpha = 0.0;
}
}
}
updatePlantSelectUI();
// Add Seed Shop title
var seedShopTitle = new Text2('Seed Shop', {
size: 60,
fill: "#333",
font: "Impact"
});
seedShopTitle.anchor.set(0.5, 0);
seedShopTitle.x = GAME_WIDTH / 2;
seedShopTitle.y = 40;
LK.gui.top.addChild(seedShopTitle);
// Add touch handler for plant selection
for (var i = 0; i < plantSelectButtons.length; i++) {
(function (idx) {
plantSelectButtons[idx].down = function (x, y, obj) {
selectedPlantType = plantTypes[idx];
updatePlantSelectUI();
// Hide preview if showing
if (plantPreview) {
plantPreview.destroy();
plantPreview = null;
}
showPlantPreview(null, null);
selectedLane = null;
selectedColumn = null;
};
})(i);
}
var resourceTxt = new Text2(resources + '', {
size: 90,
fill: 0xFFF700
});
resourceTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(resourceTxt);
var waveTxt = new Text2('Wave 1/' + TOTAL_WAVES, {
size: 70,
fill: 0xFFFFFF
});
waveTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(waveTxt);
var breachTxt = new Text2('Breaches: 0/' + MAX_BREACHES, {
size: 70,
fill: 0xFF4444
});
breachTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(breachTxt);
// Position UI
resourceTxt.x = 120; // Position to the left of seed shop
resourceTxt.y = plantSelectY; // Same height as plant selection buttons
waveTxt.x = GAME_WIDTH / 2;
waveTxt.y = 20;
breachTxt.x = GAME_WIDTH - 400;
breachTxt.y = 20;
// --- Plant Preview (shows where plant will be placed) ---
function showPlantPreview(lane, column) {
if (plantPreview) {
plantPreview.visible = false;
}
if (lane === null || column === null) return;
if (plantPreview) {
plantPreview.destroy();
plantPreview = null;
}
if (!plantPreview) {
// Use correct asset for preview
var assetId = selectedPlantType ? selectedPlantType.asset : 'plant';
plantPreview = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5,
width: 120,
height: 120
});
game.addChild(plantPreview);
}
plantPreview.x = COLUMN_START_X + column * COLUMN_WIDTH;
plantPreview.y = LANE_Y[lane];
plantPreview.visible = true;
}
// --- Plant Placement Handler ---
function canPlacePlant(lane, column) {
// Check if there's enough resources first
if (resources < PLANT_COST) return false;
// Check for existing plants at this position
for (var i = 0; i < plants.length; i++) {
if (plants[i].lane === lane && plants[i].column === column && !plants[i]._destroyed) {
// Allow placement if existing plant is a trap AND selected plant is a shooter (for mega plant transformation)
if (plants[i].constructor === TrapPlant && selectedPlantType.classRef === Plant) {
return true;
}
// Allow placement if existing plant is a mega plant AND selected plant is a seed producer (for omega plant transformation)
if (plants[i].constructor === MegaPlant && selectedPlantType.classRef === SeedProducerPlant && !omegaPlantCreated) {
return true;
}
// Block placement for all other plant types
return false;
}
}
return true;
}
function placePlant(lane, column) {
// Check if placing any plant on existing plant for special transformations
for (var i = 0; i < plants.length; i++) {
if (plants[i].lane === lane && plants[i].column === column && !plants[i]._destroyed) {
// Check if placing seed producer on mega plant to create omega plant
if (plants[i].constructor === MegaPlant && selectedPlantType.classRef === SeedProducerPlant) {
if (!omegaPlantCreated) {
// Transform mega plant into omega plant
var existingMega = plants[i];
existingMega.destroy();
plants.splice(i, 1);
var omegaPlant = new OmegaPlant();
omegaPlant.lane = lane;
omegaPlant.column = column;
omegaPlant.x = COLUMN_START_X + column * COLUMN_WIDTH;
omegaPlant.y = LANE_Y[lane];
plants.push(omegaPlant);
game.addChild(omegaPlant);
omegaPlantCreated = true;
// Special transformation effect
LK.effects.flashObject(omegaPlant, 0x00FFFF, 1000);
tween(omegaPlant, {
scaleX: 0.3,
scaleY: 0.3
}, {
duration: 300,
yoyo: true,
repeat: 2
});
resources -= PLANT_COST;
resourceTxt.setText(resources + '');
return true;
}
}
// Check if existing plant is a trap
if (plants[i].constructor === TrapPlant) {
// Transform trap into mega plant
var existingTrap = plants[i];
existingTrap.destroy();
plants.splice(i, 1);
var megaPlant = new MegaPlant();
megaPlant.lane = lane;
megaPlant.column = column;
megaPlant.x = COLUMN_START_X + column * COLUMN_WIDTH;
megaPlant.y = LANE_Y[lane];
plants.push(megaPlant);
game.addChild(megaPlant);
// Special transformation effect
LK.effects.flashObject(megaPlant, 0xFFD700, 800);
tween(megaPlant, {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 200,
yoyo: true,
repeat: 1
});
resources -= PLANT_COST;
resourceTxt.setText(resources + '');
return true;
}
}
}
// Prevent direct placement of omega plant - it can only be created through transformation
if (selectedPlantType.classRef === OmegaPlant) {
return false;
}
if (!canPlacePlant(lane, column)) return false;
var plant;
if (selectedPlantType && selectedPlantType.classRef) {
plant = new selectedPlantType.classRef();
} else {
// fallback
plant = new Plant();
}
plant.lane = lane;
plant.column = column;
plant.x = COLUMN_START_X + column * COLUMN_WIDTH;
plant.y = LANE_Y[lane];
plants.push(plant);
game.addChild(plant);
resources -= PLANT_COST;
resourceTxt.setText(resources + '');
return true;
}
// --- Touch Handler for Planting ---
game.down = function (x, y, obj) {
// Ignore top 100px (menu area)
if (y < 100) return;
// Find lane
var lane = Math.floor(y / LANE_HEIGHT);
if (lane < 0 || lane >= LANE_COUNT) return;
// Find column
var column = Math.floor((x - COLUMN_START_X + COLUMN_WIDTH / 2) / COLUMN_WIDTH);
if (column < 0 || column >= COLUMN_COUNT) return;
selectedLane = lane;
selectedColumn = column;
showPlantPreview(lane, column);
};
game.move = function (x, y, obj) {
if (y < 100) {
showPlantPreview(null);
selectedLane = null;
selectedColumn = null;
return;
}
var lane = Math.floor(y / LANE_HEIGHT);
var column = Math.floor((x - COLUMN_START_X + COLUMN_WIDTH / 2) / COLUMN_WIDTH);
if (lane < 0 || lane >= LANE_COUNT || column < 0 || column >= COLUMN_COUNT) {
showPlantPreview(null);
selectedLane = null;
selectedColumn = null;
return;
}
selectedLane = lane;
selectedColumn = column;
showPlantPreview(lane, column);
};
game.up = function (x, y, obj) {
if (selectedLane !== null && selectedColumn !== null) {
if (canPlacePlant(selectedLane, selectedColumn)) {
placePlant(selectedLane, selectedColumn);
}
}
showPlantPreview(null);
selectedLane = null;
selectedColumn = null;
};
// --- Resource Generation ---
var resourceTimer = LK.setInterval(function () {
resources += 1;
resourceTxt.setText(resources + '');
}, 500); // Increased resource gain rate (was 1000ms, now 500ms)
// --- Wave System ---
function startWave() {
waveInProgress = true;
waveTimer = 0;
nextPestTick = 0;
waveTxt.setText('Wave ' + wave + '/' + TOTAL_WAVES);
// Show wave banner
if (!game._waveBanner) {
var banner = new Text2('Wave ' + wave, {
size: 200,
fill: 0xFFF700,
font: "Impact",
stroke: "#222",
strokeThickness: 10
});
banner.anchor.set(0.5, 0.5);
banner.x = GAME_WIDTH / 2;
banner.y = GAME_HEIGHT / 2;
banner.alpha = 0.0;
game._waveBanner = banner;
game.addChild(banner);
} else {
game._waveBanner.setText('Wave ' + wave);
}
game._waveBanner.alpha = 1.0;
game._waveBanner.visible = true;
// Fade out after 1.2s
tween(game._waveBanner, {
alpha: 0
}, {
duration: 900,
delay: 1200,
onFinish: function onFinish() {
game._waveBanner.visible = false;
}
});
}
function endWave() {
waveInProgress = false;
wave++;
if (wave > TOTAL_WAVES) {
// Show special victory message for completing extra levels
LK.showYouWin();
} else {
// Show special message when entering extra levels
if (wave === 21) {
var extraBanner = new Text2('EXTRA LEVELS!', {
size: 180,
fill: 0xFF0000,
font: "Impact",
stroke: "#000",
strokeThickness: 8
});
extraBanner.anchor.set(0.5, 0.5);
extraBanner.x = GAME_WIDTH / 2;
extraBanner.y = GAME_HEIGHT / 2 - 200;
extraBanner.alpha = 0.0;
game.addChild(extraBanner);
tween(extraBanner, {
alpha: 1
}, {
duration: 600,
onFinish: function onFinish() {
tween(extraBanner, {
alpha: 0
}, {
duration: 600,
delay: 2000,
onFinish: function onFinish() {
extraBanner.destroy();
}
});
}
});
}
// Short pause before next wave
LK.setTimeout(function () {
startWave();
}, 1800);
}
}
// --- Pest Spawning ---
function spawnPest() {
var lane = Math.floor(Math.random() * LANE_COUNT);
var pest;
// Extra levels (21-25) spawn special enemies
if (wave >= 21) {
var extraLevelRand = Math.random();
if (extraLevelRand < 0.3) {
// 30% chance for Armor Pest
pest = new ArmorPest();
} else if (extraLevelRand < 0.6) {
// 30% chance for Ghost Pest
pest = new GhostPest();
} else if (extraLevelRand < 0.8) {
// 20% chance for multiple Swarm Pests
for (var i = 0; i < 3; i++) {
var swarmPest = new SwarmPest();
swarmPest.lane = lane;
swarmPest.x = GAME_WIDTH - 100 + i * 50;
swarmPest.y = LANE_Y[lane];
pests.push(swarmPest);
game.addChild(swarmPest);
}
return; // Early return since we spawned multiple
} else {
// 20% chance for MegaRat (still appears in extra levels)
pest = new MegaRat();
if (lane >= LANE_COUNT - 1) {
lane = LANE_COUNT - 2;
}
}
} else if (wave >= 20 && Math.random() < 0.08) {
// 8% chance to spawn MegaRat from wave 20+
pest = new MegaRat();
// Ensure MegaRat doesn't spawn in bottom lane (needs space for double height)
if (lane >= LANE_COUNT - 1) {
lane = LANE_COUNT - 2;
}
} else if (wave >= 5 && wave <= 15 && Math.random() < 0.15) {
// 15% chance to spawn PartyRat from wave 5-15
pest = new PartyRat();
} else if (wave >= 3 && wave <= 15) {
pest = new FastPest();
} else {
pest = new Pest();
}
pest.lane = lane;
pest.x = GAME_WIDTH - 100;
pest.y = LANE_Y[lane];
pests.push(pest);
game.addChild(pest);
}
// --- Main Game Update ---
game.update = function () {
// --- Wave logic ---
if (!waveInProgress) {
startWave();
return;
}
waveTimer++;
// Pest spawn logic: spawn a pest every 60-90 ticks, up to 6+wave pests per wave
if (nextPestTick <= 0) {
if (waveTimer < (6 + wave) * 80) {
spawnPest();
nextPestTick = 60 + Math.floor(Math.random() * 30);
}
} else {
nextPestTick--;
}
// End wave if all pests spawned and none left
if (waveTimer > (6 + wave) * 80 && pests.length === 0) {
endWave();
return;
}
// --- Plants fire seeds ---
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
plant.update();
// Remove destroyed plants
if (plant._destroyed) {
plant.destroy();
plants.splice(i, 1);
continue;
}
// Only shooter plants fire seeds
if (typeof plant.tryFire === "function") {
// Fire if pest in lane and cooldown ready
var pestInLane = false;
// Special logic for Omega plant - fires if any pest exists
if (plant.constructor === OmegaPlant) {
for (var j = 0; j < pests.length; j++) {
if (pests[j].x > plant.x) {
pestInLane = true;
break;
}
}
} else {
// Regular plants only fire at pests in their lane
for (var j = 0; j < pests.length; j++) {
if (pests[j].lane === plant.lane && pests[j].x > plant.x) {
pestInLane = true;
break;
}
}
}
if (pestInLane && plant.tryFire()) {
// Omega plant fires seeds in every lane
if (plant.constructor === OmegaPlant) {
for (var k = 0; k < LANE_COUNT; k++) {
var seed = new Seed();
seed.x = plant.x + 60;
seed.y = LANE_Y[k];
seed.lane = k;
seed.speed = 15; // Very fast seeds
seeds.push(seed);
game.addChild(seed);
}
// Fire effect on omega plant
tween(plant, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 80,
yoyo: true,
repeat: 1
});
LK.effects.flashObject(plant, 0x00FFFF, 200);
// Crystal plant fires piercing laser
} else if (plant.constructor === CrystalPlant) {
var seed = new Seed();
seed.x = plant.x + 60;
seed.y = plant.y;
seed.lane = plant.lane;
seed.speed = 20; // Very fast piercing shot
seed.piercing = true; // Special property for piercing
seeds.push(seed);
game.addChild(seed);
// Crystal firing effect
tween(plant, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0x00FFFF
}, {
duration: 150,
yoyo: true,
repeat: 1,
onFinish: function onFinish() {
plant.tint = 0xFFFFFF;
}
});
// Poison plant creates toxic cloud
} else if (plant.constructor === PoisonPlant) {
// Create multiple poison seeds in a spread
for (var k = -1; k <= 1; k++) {
var targetLane = plant.lane + k;
if (targetLane >= 0 && targetLane < LANE_COUNT) {
var seed = new Seed();
seed.x = plant.x + 60;
seed.y = LANE_Y[targetLane];
seed.lane = targetLane;
seed.speed = 8; // Slower poison projectiles
seed.poison = true; // Special property for poison
seeds.push(seed);
game.addChild(seed);
}
}
// Poison effect
tween(plant, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0x9ACD32
}, {
duration: 200,
yoyo: true,
repeat: 1,
onFinish: function onFinish() {
plant.tint = 0xFFFFFF;
}
});
// Mega plant fires 3 seeds in spread pattern
} else if (plant.constructor === MegaPlant) {
for (var k = -1; k <= 1; k++) {
var targetLane = plant.lane + k;
if (targetLane >= 0 && targetLane < LANE_COUNT) {
var seed = new Seed();
seed.x = plant.x + 60;
seed.y = plant.y;
seed.lane = targetLane;
seed.speed = 12; // Faster seeds
seeds.push(seed);
game.addChild(seed);
}
}
// Fire effect on mega plant
tween(plant, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
yoyo: true,
repeat: 1
});
} else {
var seed = new Seed();
seed.x = plant.x + 60;
seed.y = plant.y;
seed.lane = plant.lane;
seeds.push(seed);
game.addChild(seed);
}
}
}
}
// --- Seeds move and hit pests ---
for (var s = seeds.length - 1; s >= 0; s--) {
var seed = seeds[s];
seed.update();
var hit = false;
for (var p = 0; p < pests.length; p++) {
var pest = pests[p];
var canHit = false;
// Check if seed can hit this pest
if (pest.constructor === MegaRat) {
// MegaRat can be hit by seeds in either of its occupied lanes
canHit = pest.occupiedLanes && pest.occupiedLanes.indexOf(seed.lane) !== -1;
} else {
// Regular pests use normal lane matching
canHit = pest.lane === seed.lane;
}
if (canHit && !pest.destroyed && Math.abs(seed.x - pest.x) < 60) {
pest.hit(1);
hit = true;
break;
}
}
if (hit || seed.x > GAME_WIDTH + 50) {
seed.destroy();
seeds.splice(s, 1);
}
}
// --- Pests move and check for breach or death ---
for (var p = pests.length - 1; p >= 0; p--) {
var pest = pests[p];
if (pest.destroyed) {
pest.destroy();
pests.splice(p, 1);
continue;
}
pest.update();
if (pest.x < 80) {
// Breach!
breaches++;
breachTxt.setText('Breaches: ' + breaches + '/' + MAX_BREACHES);
LK.effects.flashScreen(0xff0000, 400);
pest.destroy();
pests.splice(p, 1);
if (breaches >= MAX_BREACHES) {
LK.showGameOver();
return;
}
}
}
};
// --- Clean up on game over/win ---
game.on('destroy', function () {
LK.clearInterval(resourceTimer);
plants = [];
pests = [];
seeds = [];
breaches = 0;
resources = START_RESOURCES;
wave = 1;
waveInProgress = false;
omegaPlantCreated = false;
if (plantPreview) {
plantPreview.destroy();
plantPreview = null;
}
if (game._waveBanner) {
game._waveBanner.visible = false;
game._waveBanner.alpha = 0;
}
});
// --- Initial UI update ---
resourceTxt.setText(resources + '');
waveTxt.setText('Wave 1/' + TOTAL_WAVES);
breachTxt.setText('Breaches: 0/' + MAX_BREACHES);
// --- Start background music ---
LK.playMusic('Hi1234');
Pixelated sun . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Flip it around
Make the background white
Make it angry
Make a dirty patch of dirt. In-Game asset. 2d. High contrast. No shadows
Make the background red
Make the background green
Make it shiny
Looks like a gargantuar from plants vs zombies but a rat. In-Game asset. 2d. High contrast. No shadows
Make the background blue