Code edit (1 edits merged)
Please save this source code
User prompt
Warden of the Woods: Grove Defense
Initial prompt
🎮 Game Title: "Warden of the Woods: Grove Defense" Genre: Tower Defense Light Action-Adventure 🧙♂️ Core Concept: You are a forest spirit defending the Heart Tree at the center of the magical grove. Waves of corrupted creatures and machines attack from the forest edges, and you must build, upgrade, and strategically place magical defenses to protect it! 🏗️ Gameplay Mechanics: Tower Building: Plant different magical plants and trees as towers: 🌳 Thornroot Tree — Fires spikes at enemies. 🍄 Shroom Puff — Explodes spores that slow enemies. 🌸 Healing Bloom — Creates a healing aura for nearby defenses. 🔥 Flame Fern — Short-range, burns groups of enemies. Spirit Powers (You): While towers defend, you can run around casting quick spells: Magic burst (tiny damage). Speed boost for towers. Root trap (briefly freezes enemies). Resources: Enemies drop Nature Essence. Use it to plant, upgrade, or heal your towers. Wave System: After each wave, you get a brief pause to rebuild. Some waves have boss monsters (giant corrupted stags, metallic boars, etc). Upgrades Between Waves: Buff your powers. Evolve plants into stronger versions (e.g., Thornroot ➔ Bramble Hydra). 🌳 Setting: Enchanted glades, misty swamps, crystal-clear rivers. Each "world" has slightly different enemy types and environmental hazards (ex: in the swamp, enemies move slower, but so do you).
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0, unlockedTowers: ["thorn", "shroom", "flame", "healing"] }); /**** * Classes ****/ var BuildingSpot = Container.expand(function () { var self = Container.call(this); var spotGraphics; if (gameDifficulty === 'race') { spotGraphics = self.attachAsset('tire', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); } else if (gameDifficulty === 'infection') { spotGraphics = self.attachAsset('mutanttree', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 }); } else { spotGraphics = self.attachAsset('buildingSpot', { anchorX: 0.5, anchorY: 0.5, alpha: 0.4 }); } self.gridX = 0; self.gridY = 0; self.occupied = false; self.tower = null; self.down = function (x, y, obj) { if (!self.occupied && currentMode === 'building' && selectedTower !== null && essence >= towerCosts[selectedTower]) { self.buildTower(selectedTower); } }; self.buildTower = function (towerType) { var tower; if (towerType === 'thorn') { tower = new ThornTower(); } else if (towerType === 'shroom') { tower = new ShroomTower(); } else if (towerType === 'flame') { tower = new FlameTower(); } else if (towerType === 'healing') { tower = new FarmTower(); } if (tower) { tower.x = self.x; tower.y = self.y; tower.gridX = self.gridX; tower.gridY = self.gridY; game.addChild(tower); towers.push(tower); essence -= towerCosts[towerType]; updateEssenceText(); self.occupied = true; self.tower = tower; LK.getSound('towerPlaced').play(); } }; return self; }); var Car = Container.expand(function () { var self = Container.call(this); var carGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0xFF3300, // Reddish tint for car scaleX: 1.3, scaleY: 0.6 // Low and wide like a car }); return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.maxHealth = 50; self.health = self.maxHealth; self.value = 10; // Essence value when defeated self.pathIndex = 0; self.alive = true; self.update = function () { if (!self.alive) { return; } // Follow path if (self.pathIndex < path.length) { var targetPoint = path[self.pathIndex]; var dx = targetPoint.x - self.x; var dy = targetPoint.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If near target point, move to next point if (distance < 20) { self.pathIndex++; // If reached the end of path (HeartTree) if (self.pathIndex >= path.length) { heartTree.takeDamage(10); self.alive = false; return; } } else { // Move towards current target point var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } } }; // Add a static method to Enemy to use for inheritance Enemy.prototype.update = function () { if (!this.alive) { return; } // Follow path if (this.pathIndex < path.length) { var targetPoint = path[this.pathIndex]; var dx = targetPoint.x - this.x; var dy = targetPoint.y - this.y; var distance = Math.sqrt(dx * dx + dy * dy); // If near target point, move to next point if (distance < 20) { this.pathIndex++; // If reached the end of path (HeartTree) if (this.pathIndex >= path.length) { heartTree.takeDamage(10); this.alive = false; return; } } else { // Move towards current target point var angle = Math.atan2(dy, dx); this.x += Math.cos(angle) * this.speed; this.y += Math.sin(angle) * this.speed; } } }; self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { self.die(); } else { // Flash enemy when hit LK.effects.flashObject(self, 0xFF0000, 200); } }; self.die = function () { self.alive = false; // Drop essence var essenceObj = new Essence(); essenceObj.value = self.value; essenceObj.x = self.x; essenceObj.y = self.y; game.addChild(essenceObj); essenceItems.push(essenceObj); // Increase score LK.setScore(LK.getScore() + self.value); scoreTxt.setText(LK.getScore()); LK.getSound('enemyDeath').play(); }; return self; }); var ToxicCloudEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var cloudGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0x55DD55, // Light green tint for toxic cloud scaleX: 1.3, scaleY: 1.0 }); self.speed = 1.5; self.maxHealth = 95; self.health = self.maxHealth; self.value = 25; self.cloudsReleased = 0; self.maxClouds = 5; self.cloudInterval = 2; // Seconds between releasing clouds self.lastCloudTime = 0; // Override update to add cloud-releasing behavior self.update = function () { if (!self.alive) { return; } // Call parent update for movement Enemy.prototype.update.call(self); // Release toxic clouds periodically var currentTime = LK.ticks / 60; if (currentTime - self.lastCloudTime >= self.cloudInterval && self.cloudsReleased < self.maxClouds) { self.lastCloudTime = currentTime; self.releaseCloud(); self.cloudsReleased++; } // Gaseous pulsing effect var pulse = 1 + Math.sin(LK.ticks / 10) * 0.1; self.scale.set(pulse, pulse); }; self.releaseCloud = function () { // Create a toxic cloud that stays in place and damages over time var cloud = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, tint: 0x00FF00, scaleX: 2.5, scaleY: 1.5, alpha: 0.6 }); game.addChild(cloud); // Animate cloud expansion and fade tween(cloud, { alpha: 0, scaleX: 4, scaleY: 2.5 }, { duration: 8000, // Long-lasting cloud onFinish: function onFinish() { cloud.destroy(); } }); // Cloud damages anything nearby every second var cloudDamageInterval = LK.setInterval(function (cloudPos) { return function () { // Check if guardian is in range if (guardian) { var dx = guardian.x - cloudPos.x; var dy = guardian.y - cloudPos.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 120) { // Damage the heart tree heartTree.takeDamage(1); // Visual effect LK.effects.flashObject(guardian, 0x00FF00, 100); } } }; }({ "x": cloud.x, "y": cloud.y }), 1000); // Clear interval when cloud is gone LK.setTimeout(function (interval) { return function () { LK.clearInterval(interval); }; }(cloudDamageInterval), 8000); }; return self; }); var ToxicBlobEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var blobGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0x80FF80, // Light green tint scaleX: 1.4, scaleY: 1.4 }); self.speed = 1.3; self.maxHealth = 140; self.health = self.maxHealth; self.value = 30; // Override die method to spawn smaller toxic enemies self.die = function () { self.alive = false; // Spawn smaller toxic enemies for (var i = 0; i < 3; i++) { var smallToxic = new PoisonEnemy(); // Make it smaller smallToxic.scale.set(0.7, 0.7); // Reduce stats for smaller version smallToxic.maxHealth = 30; smallToxic.health = smallToxic.maxHealth; smallToxic.speed = 2.5; smallToxic.value = 8; smallToxic.pathIndex = self.pathIndex; // Position with slight offset smallToxic.x = self.x + (Math.random() * 60 - 30); smallToxic.y = self.y + (Math.random() * 60 - 30); game.addChild(smallToxic); enemies.push(smallToxic); enemiesRemaining++; } // Create explosion effect var explosion = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, tint: 0x00FF00, scaleX: 2, scaleY: 2 }); game.addChild(explosion); // Animate explosion tween(explosion, { alpha: 0, scaleX: 3, scaleY: 3 }, { duration: 400, onFinish: function onFinish() { explosion.destroy(); } }); // Drop essence var essenceObj = new Essence(); essenceObj.value = self.value; essenceObj.x = self.x; essenceObj.y = self.y; game.addChild(essenceObj); essenceItems.push(essenceObj); // Increase score LK.setScore(LK.getScore() + self.value); scoreTxt.setText(LK.getScore()); LK.getSound('enemyDeath').play(); }; return self; }); var TankEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var tankGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0x666666, // Grey tint for tank enemies scaleX: 1.4, scaleY: 1.4 // Larger }); self.speed = 1; // Slower self.maxHealth = 120; // Higher health self.health = self.maxHealth; self.value = 25; // Higher value return self; }); var SplitEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var splitGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0xFF6347, // Tomato red for split enemies scaleX: 1.3, scaleY: 1.3 }); self.speed = 1.5; self.maxHealth = 70; self.health = self.maxHealth; self.value = 12; self.splitCount = 2; // Number of smaller enemies to spawn when killed self.size = 'large'; // Size tracking for spawned children // Override die method to create smaller enemies when killed self.die = function () { self.alive = false; // Only split if this is the original large enemy if (self.size === 'large') { // Spawn smaller enemies for (var i = 0; i < self.splitCount; i++) { var smallEnemy = new SplitEnemy(); // Make it smaller smallEnemy.scale.set(0.6, 0.6); // Reduce stats for smaller version smallEnemy.size = 'small'; smallEnemy.maxHealth = 25; smallEnemy.health = smallEnemy.maxHealth; smallEnemy.speed = 2.2; smallEnemy.value = 8; smallEnemy.pathIndex = self.pathIndex; // Position with slight offset smallEnemy.x = self.x + (Math.random() * 60 - 30); smallEnemy.y = self.y + (Math.random() * 60 - 30); game.addChild(smallEnemy); enemies.push(smallEnemy); enemiesRemaining++; } } // Create explosion effect var explosion = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, tint: 0xFF4500, scaleX: 2, scaleY: 2 }); game.addChild(explosion); // Animate explosion tween(explosion, { alpha: 0, scaleX: 3, scaleY: 3 }, { duration: 300, onFinish: function onFinish() { explosion.destroy(); } }); // Drop essence var essenceObj = new Essence(); essenceObj.value = self.value; essenceObj.x = self.x; essenceObj.y = self.y; game.addChild(essenceObj); essenceItems.push(essenceObj); // Increase score LK.setScore(LK.getScore() + self.value); scoreTxt.setText(LK.getScore()); LK.getSound('enemyDeath').play(); }; return self; }); var SpeedyEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var speedyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0xFFFF00, // Yellow tint for speedy enemies scaleX: 0.8, scaleY: 1.2 // Tall and thin }); self.speed = 4; // Much faster self.maxHealth = 30; // Lower health self.health = self.maxHealth; self.value = 15; // Slightly higher value return self; }); var RaceEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var raceGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0xFF5500, // Orange-red tint for race enemies scaleX: 1.2, scaleY: 0.5 // Low and wide like a race car }); self.speed = 6; // Very fast self.maxHealth = 25; // Lower health self.health = self.maxHealth; self.value = 20; self.exhaustCounter = 0; // Override update to add exhaust particle effect self.update = function () { if (!self.alive) { return; } // Call parent update for movement Enemy.prototype.update.call(self); // Create exhaust particle effect every few ticks self.exhaustCounter++; if (self.exhaustCounter >= 5) { self.exhaustCounter = 0; var exhaust = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: self.x - Math.cos(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * 30, y: self.y - Math.sin(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * 30, tint: 0x888888, scaleX: 0.5, scaleY: 0.5, alpha: 0.7 }); game.addChild(exhaust); // Animate exhaust tween(exhaust, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 300, onFinish: function onFinish() { exhaust.destroy(); } }); } }; return self; }); var SpeedDemonEnemy = RaceEnemy.expand(function () { var self = RaceEnemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var speedDemonGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0xFF0000, // Red tint for speed demon scaleX: 1.0, scaleY: 0.4 // Very low and streamlined }); self.speed = 8; // Extremely fast self.maxHealth = 15; // Lower health self.health = self.maxHealth; self.value = 30; // Higher value // Override update to add more intense exhaust effect self.update = function () { if (!self.alive) { return; } // Call parent update for movement Enemy.prototype.update.call(self); // Create more frequent exhaust particle effect self.exhaustCounter++; if (self.exhaustCounter >= 3) { // More frequent than regular race cars self.exhaustCounter = 0; // Create multiple exhaust particles for (var i = 0; i < 2; i++) { var exhaust = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: self.x - Math.cos(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * (30 + i * 10), y: self.y - Math.sin(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * (30 + i * 10), tint: 0xFF4500, // Orange-red for more intense exhaust scaleX: 0.5, scaleY: 0.5, alpha: 0.8 }); game.addChild(exhaust); // Animate exhaust tween(exhaust, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 200, // Faster animation onFinish: function onFinish() { exhaust.destroy(); } }); } } }; return self; }); var MotorcycleEnemy = RaceEnemy.expand(function () { var self = RaceEnemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var motorGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0xFFFF00, // Yellow tint for motorcycles scaleX: 0.6, scaleY: 0.7 // Smaller than cars }); self.speed = 7; // Very fast self.maxHealth = 20; // Lower health self.health = self.maxHealth; self.value = 25; self.zigzagOffset = 0; self.zigzagDirection = 1; // Override update to add zigzag movement pattern self.update = function () { if (!self.alive) { return; } // Call parent update for basic movement Enemy.prototype.update.call(self); // Add zigzag pattern to movement if (self.pathIndex < path.length) { self.zigzagOffset += 0.2 * self.zigzagDirection; // Reverse direction at max amplitude if (Math.abs(self.zigzagOffset) > 30) { self.zigzagDirection *= -1; } // Calculate movement direction var targetPoint = path[self.pathIndex]; var dx = targetPoint.x - self.x; var dy = targetPoint.y - self.y; var angle = Math.atan2(dy, dx); // Apply perpendicular offset (zigzag) self.x += Math.cos(angle + Math.PI / 2) * self.zigzagOffset * 0.05; self.y += Math.sin(angle + Math.PI / 2) * self.zigzagOffset * 0.05; } // Create exhaust particle effect self.exhaustCounter++; if (self.exhaustCounter >= 4) { self.exhaustCounter = 0; var exhaust = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: self.x - Math.cos(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * 20, y: self.y - Math.sin(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * 20, tint: 0x999999, // Light gray exhaust scaleX: 0.4, scaleY: 0.4, alpha: 0.6 }); game.addChild(exhaust); // Animate exhaust tween(exhaust, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 250, onFinish: function onFinish() { exhaust.destroy(); } }); } }; return self; }); var PoisonEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var poisonGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0x00FF00, // Green tint for poison enemies scaleX: 1.1, scaleY: 1.1 }); self.speed = 1.8; self.maxHealth = 80; self.health = self.maxHealth; self.value = 20; self.lastShotTime = 0; self.shotInterval = 3; // Seconds between shots // Override update to add shooting behavior self.update = function () { if (!self.alive) { return; } // Call parent update method for movement Enemy.prototype.update.call(self); // Add shooting behavior var currentTime = LK.ticks / 60; if (currentTime - self.lastShotTime >= self.shotInterval) { self.lastShotTime = currentTime; self.shootProjectile(); } }; self.shootProjectile = function () { // Create a poison projectile aimed at heart tree var projectile = new Projectile(); projectile.x = self.x; projectile.y = self.y; projectile.damage = 5; projectile.speed = 8; projectile.target = heartTree; // Make it green projectile.getChildAt(0).tint = 0x00FF00; game.addChild(projectile); // Visual effect LK.effects.flashObject(self, 0x00FF00, 200); }; return self; }); var MineLayerEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var mineLayerGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0xFF9900, // Orange tint for mine layer scaleX: 1.3, scaleY: 1.0 }); self.speed = 2.2; self.maxHealth = 70; self.health = self.maxHealth; self.value = 30; self.lastMineTime = 0; self.mineInterval = 3; // Seconds between mines // Override update to implement mine-dropping behavior self.update = function () { if (!self.alive) { return; } // Call parent update for movement Enemy.prototype.update.call(self); // Drop mines periodically var currentTime = LK.ticks / 60; if (currentTime - self.lastMineTime >= self.mineInterval) { self.lastMineTime = currentTime; self.dropMine(); } }; self.dropMine = function () { // Create a mine that damages when stepped on var mine = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, tint: 0xFF3300, scaleX: 0.8, scaleY: 0.8, alpha: 0.9 }); game.addChild(mine); // Add pulsing animation to make mine more visible var mineInterval = LK.setInterval(function () { var pulse = 1 + Math.sin(LK.ticks / 10) * 0.2; mine.scale.set(0.8 * pulse, 0.8 * pulse); }, 100); // Check for guardian proximity to mine var mineCheckInterval = LK.setInterval(function () { if (guardian) { var dx = guardian.x - mine.x; var dy = guardian.y - mine.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 60) { // Mine explosion var explosion = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: mine.x, y: mine.y, tint: 0xFF3300, scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }); game.addChild(explosion); // Animate explosion tween(explosion, { alpha: 0, scaleX: 3.0, scaleY: 3.0 }, { duration: 500, onFinish: function onFinish() { explosion.destroy(); } }); // Damage heart tree when mine explodes heartTree.takeDamage(8); // Remove mine LK.clearInterval(mineInterval); LK.clearInterval(mineCheckInterval); mine.destroy(); } } }, 100); // Mines eventually disappear after 20 seconds LK.setTimeout(function () { LK.clearInterval(mineInterval); LK.clearInterval(mineCheckInterval); // Fade out effect tween(mine, { alpha: 0, scaleX: 0.4, scaleY: 0.4 }, { duration: 500, onFinish: function onFinish() { mine.destroy(); } }); }, 20000); }; return self; }); var FlyingEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var flyingGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0x9370DB, // Purple tint for flying enemies scaleX: 0.9, scaleY: 0.6 // Flatter shape }); self.speed = 3; self.maxHealth = 40; self.health = self.maxHealth; self.value = 18; self.flyingMode = true; // Override the update method to go directly to heart tree self.update = function () { if (!self.alive) { return; } // Flying enemies ignore the path and go straight for the heart tree var dx = heartTree.x - self.x; var dy = heartTree.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If near heart tree, attack it if (distance < 20) { heartTree.takeDamage(15); self.alive = false; return; } // Move towards heart tree var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; // Add a slight wave pattern to flight self.y += Math.sin(LK.ticks / 10) * 0.5; }; return self; }); var BossEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with boss graphics self.removeChildAt(0); // Remove default enemy graphics var bossGraphics = self.attachAsset('bossEnemy', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.8, // Make boss bigger scaleY: 1.8 // Make boss bigger }); self.speed = 1.2; self.maxHealth = 300 + currentWave * 30; self.health = self.maxHealth; self.value = 100 + currentWave * 15; self.lastAttackTime = 0; self.attackCooldown = 3; // seconds between special attacks // Override update method to add special attacks self.update = function () { if (!self.alive) { return; } // Call parent update for movement Enemy.prototype.update.call(self); // Special attacks var currentTime = LK.ticks / 60; if (currentTime - self.lastAttackTime >= self.attackCooldown) { self.lastAttackTime = currentTime; self.specialAttack(); } // Pulsate effect var pulse = 1 + Math.sin(LK.ticks / 15) * 0.1; self.scale.set(1.8 * pulse, 1.8 * pulse); }; // Special attack based on boss wave level self.specialAttack = function () { // Boss becomes more dangerous in later waves var attackType = Math.floor(Math.random() * 3); // Flash effect for attack LK.effects.flashObject(self, 0xFF0000, 300); if (attackType === 0) { // Projectile burst attack var projectileCount = 8; for (var i = 0; i < projectileCount; i++) { var angle = i / projectileCount * Math.PI * 2; var projectile = new Projectile(); projectile.x = self.x; projectile.y = self.y; projectile.damage = 8; // Set a fixed position as target based on angle var targetDummy = { x: self.x + Math.cos(angle) * 500, y: self.y + Math.sin(angle) * 500, alive: true }; projectile.target = targetDummy; // Give projectile red color projectile.getChildAt(0).tint = 0xFF0000; game.addChild(projectile); } } else if (attackType === 1) { // Spawn minions var minionCount = 2 + Math.floor(currentWave / 5); for (var i = 0; i < minionCount; i++) { var minion = new SpeedyEnemy(); minion.x = self.x; minion.y = self.y; minion.pathIndex = self.pathIndex; minion.maxHealth = 20; minion.health = minion.maxHealth; minion.speed = 5; minion.value = 5; // Make minions smaller minion.scale.set(0.7, 0.7); game.addChild(minion); enemies.push(minion); enemiesRemaining++; } } else { // Heal self var healAmount = Math.min(self.maxHealth - self.health, self.maxHealth * 0.1); if (healAmount > 0) { self.health += healAmount; // Create healing effect var healEffect = new Text2("+" + Math.floor(healAmount), { size: 40, fill: 0x00FF00 }); healEffect.anchor.set(0.5, 0.5); healEffect.x = self.x; healEffect.y = self.y - 50; game.addChild(healEffect); // Animate healing text tween(healEffect, { alpha: 0, y: healEffect.y - 50 }, { duration: 1000, onFinish: function onFinish() { healEffect.destroy(); } }); } } }; // Override takeDamage to get enraged at low health self.takeDamage = function (amount) { self.health -= amount; // Enrage when below 30% health if (self.health <= self.maxHealth * 0.3 && self.speed < 1.8) { self.speed *= 1.5; // Move faster self.attackCooldown *= 0.6; // Attack more often // Visual effect for enrage LK.effects.flashObject(self, 0xFF0000, 1000); // Create enrage message var enrageText = new Text2("BOSS ENRAGED!", { size: 70, fill: 0xFF0000 }); enrageText.anchor.set(0.5, 0.5); enrageText.x = 2048 / 2; enrageText.y = 2732 / 2; game.addChild(enrageText); // Animate message tween(enrageText, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 1500, onFinish: function onFinish() { enrageText.destroy(); } }); } if (self.health <= 0) { self.die(); } else { // Flash when hit LK.effects.flashObject(self, 0xFF0000, 200); } }; return self; }); var InfectionBossEnemy = BossEnemy.expand(function () { var self = BossEnemy.call(this); // Replace boss graphics with infected version self.removeChildAt(0); // Remove default boss graphics var bossGraphics = self.attachAsset('bossEnemy', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.8, scaleY: 1.8, tint: 0x00FF00 // Green tint for infection }); // Override special attack to be infection-themed self.specialAttack = function () { // Flash effect for attack LK.effects.flashObject(self, 0x00FF00, 300); var attackType = Math.floor(Math.random() * 3); if (attackType === 0) { // Toxic cloud attack var cloudCount = 3 + Math.floor(currentWave / 7); for (var i = 0; i < cloudCount; i++) { var angle = i / cloudCount * Math.PI * 2; var distance = 200 + Math.random() * 300; var cloudX = self.x + Math.cos(angle) * distance; var cloudY = self.y + Math.sin(angle) * distance; var toxicCloud = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: cloudX, y: cloudY, tint: 0x00FF00, scaleX: 3, scaleY: 3, alpha: 0.7 }); game.addChild(toxicCloud); // Animate toxic cloud tween(toxicCloud, { alpha: 0, scaleX: 5, scaleY: 5 }, { duration: 4000, onFinish: function onFinish() { toxicCloud.destroy(); } }); // Damage anything near the cloud var checkCloud = LK.setInterval(function (cloudPos) { return function () { // Check for guardian in range if (guardian) { var dx = guardian.x - cloudPos.x; var dy = guardian.y - cloudPos.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 150) { // Damage the heart tree heartTree.takeDamage(2); // Visual effect LK.effects.flashObject(guardian, 0x00FF00, 100); } } }; }({ "x": cloudX, "y": cloudY }), 500); // Clear interval when cloud is gone LK.setTimeout(function (interval) { return function () { LK.clearInterval(interval); }; }(checkCloud), 4000); } } else if (attackType === 1) { // Spawn infection minions var minionCount = 2 + Math.floor(currentWave / 6); for (var i = 0; i < minionCount; i++) { var minion; if (Math.random() < 0.5) { minion = new AcidEnemy(); } else { minion = new PoisonEnemy(); } minion.x = self.x; minion.y = self.y; minion.pathIndex = self.pathIndex; minion.maxHealth = 40; minion.health = minion.maxHealth; // Make minions smaller minion.scale.set(0.8, 0.8); game.addChild(minion); enemies.push(minion); enemiesRemaining++; } } else { // Viral infection - heals self and damages heart tree var healAmount = Math.min(self.maxHealth - self.health, self.maxHealth * 0.15); if (healAmount > 0) { self.health += healAmount; // Create healing effect var healEffect = new Text2("+" + Math.floor(healAmount), { size: 40, fill: 0x00FF00 }); healEffect.anchor.set(0.5, 0.5); healEffect.x = self.x; healEffect.y = self.y - 50; game.addChild(healEffect); // Animate healing text tween(healEffect, { alpha: 0, y: healEffect.y - 50 }, { duration: 1000, onFinish: function onFinish() { healEffect.destroy(); } }); } // Damage heart tree directly heartTree.takeDamage(5); // Visual effect showing infection spread to heart tree var infectionLine = new Container(); for (var i = 0; i < 10; i++) { var segment = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, tint: 0x00FF00, scaleX: 0.5, scaleY: 0.5, alpha: 0.7 }); segment.x = self.x + (heartTree.x - self.x) * (i / 10); segment.y = self.y + (heartTree.y - self.y) * (i / 10); infectionLine.addChild(segment); } game.addChild(infectionLine); // Animate and remove infection line LK.setTimeout(function () { infectionLine.destroy(); }, 1000); } }; return self; }); var ArmoredEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var armorGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0x888888, // Gray tint for armor scaleX: 1.2, scaleY: 1.2 }); // Add shield graphic var shieldGraphics = self.attachAsset('projectile', { anchorX: 0.5, anchorY: 0.5, tint: 0x3399FF, // Blue shield scaleX: 2.0, scaleY: 2.0, alpha: 0.6 }); self.speed = 1.8; self.maxHealth = 100; self.health = self.maxHealth; self.value = 40; self.shieldHealth = 50; self.shieldActive = true; // Override takeDamage to implement shield mechanics self.takeDamage = function (amount) { if (self.shieldActive) { // Shield takes reduced damage self.shieldHealth -= amount * 0.5; // Shield break effect if (self.shieldHealth <= 0) { self.shieldActive = false; shieldGraphics.visible = false; // Shield break visual effect var breakEffect = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, tint: 0x3399FF, scaleX: 2.5, scaleY: 2.5, alpha: 0.8 }); game.addChild(breakEffect); // Animate shield break tween(breakEffect, { alpha: 0, scaleX: 3.5, scaleY: 3.5 }, { duration: 500, onFinish: function onFinish() { breakEffect.destroy(); } }); } else { // Shield hit effect LK.effects.flashObject(shieldGraphics, 0xFFFFFF, 200); } } else { // Normal damage when shield is down self.health -= amount; if (self.health <= 0) { self.die(); } else { // Flash enemy when hit LK.effects.flashObject(self, 0xFF0000, 200); } } }; // Override update to add shield visual effects self.update = function () { if (!self.alive) { return; } // Call parent update for movement Enemy.prototype.update.call(self); // Pulsating shield effect if (self.shieldActive) { var pulse = 1 + Math.sin(LK.ticks / 15) * 0.1; shieldGraphics.scale.set(2.0 * pulse, 2.0 * pulse); } }; return self; }); var AcidEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var acidGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0x00AA00, // Darker green tint for acid enemies scaleX: 1.2, scaleY: 1.2 }); self.speed = 1.6; self.maxHealth = 90; self.health = self.maxHealth; self.value = 22; self.lastAcidTime = 0; self.acidInterval = 2; // Seconds between acid puddles // Override update to add acid trail self.update = function () { if (!self.alive) { return; } // Call parent update method for movement Enemy.prototype.update.call(self); // Add acid trail behavior var currentTime = LK.ticks / 60; if (currentTime - self.lastAcidTime >= self.acidInterval) { self.lastAcidTime = currentTime; self.dropAcid(); } }; self.dropAcid = function () { // Create an acid puddle that damages the guardian var acid = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, tint: 0x00FF00, scaleX: 1.8, scaleY: 0.4, alpha: 0.7 }); game.addChild(acid); // Animate acid puddle tween(acid, { alpha: 0, scaleX: 2.5, scaleY: 0.2 }, { duration: 5000, onFinish: function onFinish() { acid.destroy(); } }); // Check if guardian is in acid puddle var checkAcid = LK.setInterval(function () { if (guardian) { var dx = guardian.x - acid.x; var dy = guardian.y - acid.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 60) { // Damage the heart tree if guardian is in acid heartTree.takeDamage(1); // Visual effect on guardian LK.effects.flashObject(guardian, 0x00FF00, 200); } } }, 500); // Clear interval when acid is gone LK.setTimeout(function () { LK.clearInterval(checkAcid); }, 5000); }; return self; }); var Essence = Container.expand(function () { var self = Container.call(this); var essenceGraphics = self.attachAsset('essence', { anchorX: 0.5, anchorY: 0.5 }); self.value = 10; self.lifeTime = 10; // seconds self.creationTime = LK.ticks / 60; self.collected = false; self.update = function () { if (self.collected) { return; } // Check if guardian is close enough to collect var dx = guardian.x - self.x; var dy = guardian.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 80) { self.collect(); return; } // Check if timed out var currentTime = LK.ticks / 60; if (currentTime - self.creationTime > self.lifeTime) { self.collected = true; } // Pulse animation var pulseFactor = 1 + Math.sin(LK.ticks / 20) * 0.1; self.scale.set(pulseFactor, pulseFactor); }; self.collect = function () { essence += self.value; updateEssenceText(); self.collected = true; LK.getSound('essenceCollected').play(); // Animate collection tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); var Guardian = Container.expand(function () { var self = Container.call(this); var guardianGraphics = self.attachAsset('guardian', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 7; self.targetX = null; self.targetY = null; self.moving = false; self.update = function () { if (self.moving && self.targetX !== null && self.targetY !== null) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If near target, stop moving if (distance < 10) { self.moving = false; self.targetX = null; self.targetY = null; } else { // Move towards target var angle = Math.atan2(dy, dx); var nextX = self.x + Math.cos(angle) * self.speed; var nextY = self.y + Math.sin(angle) * self.speed; // Check if the next position is valid if (isPositionValid(nextX, nextY)) { self.x = nextX; self.y = nextY; } else { self.moving = false; } } } }; self.moveTo = function (x, y) { if (isPositionValid(x, y)) { self.targetX = x; self.targetY = y; self.moving = true; } }; return self; }); var HeartTree = Container.expand(function () { var self = Container.call(this); var treeGraphics; if (gameDifficulty === 'race') { treeGraphics = self.attachAsset('finishline', { anchorX: 0.5, anchorY: 0.5 }); } else if (gameDifficulty === 'infection') { treeGraphics = self.attachAsset('oldbuilding', { anchorX: 0.5, anchorY: 0.5 }); } else { treeGraphics = self.attachAsset('heartTree', { anchorX: 0.5, anchorY: 0.5 }); } self.maxHealth = 100; self.health = self.maxHealth; self.takeDamage = function (amount) { self.health -= amount; updateHealthBar(); LK.getSound('heartTreeDamage').play(); LK.effects.flashObject(self, 0xFF0000, 300); if (self.health <= 0) { // Game over LK.showGameOver(); // Update high score if needed if (LK.getScore() > storage.highScore) { storage.highScore = LK.getScore(); } } }; return self; }); var PathTile = Container.expand(function () { var self = Container.call(this); var pathGraphics; if (gameDifficulty === 'race') { pathGraphics = self.attachAsset('racetrack', { anchorX: 0.5, anchorY: 0.5, alpha: 0.9 }); } else if (gameDifficulty === 'infection') { pathGraphics = self.attachAsset('acidspill', { anchorX: 0.5, anchorY: 0.5, alpha: 0.9 }); } else { pathGraphics = self.attachAsset('path', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); } self.gridX = 0; self.gridY = 0; return self; }); var Projectile = Container.expand(function () { var self = Container.call(this); var projectileGraphics = self.attachAsset('projectile', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 20; self.damage = 10; self.target = null; self.alive = true; self.update = function () { if (!self.target || !self.target.alive) { self.alive = false; return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 20) { // Hit target if (typeof self.target.takeDamage === 'function') { self.target.takeDamage(self.damage); } self.alive = false; return; } // Move towards target var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; }; return self; }); var SpawnPoint = Container.expand(function () { var self = Container.call(this); var spawnGraphics; if (gameDifficulty === 'race') { spawnGraphics = self.attachAsset('car', { anchorX: 0.5, anchorY: 0.5 }); } else if (gameDifficulty === 'infection') { spawnGraphics = self.attachAsset('acidbarrel', { anchorX: 0.5, anchorY: 0.5 }); } else { spawnGraphics = self.attachAsset('spawnPoint', { anchorX: 0.5, anchorY: 0.5 }); } self.active = true; return self; }); var TitleScreen = Container.expand(function () { var self = Container.call(this); // Background overlay for title screen var overlay = self.attachAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732, tint: 0x122112, alpha: 0.8 }); // Game title self.title = new Text2('Forest Defender', { size: 180, fill: 0x83de44 }); self.title.anchor.set(0.5, 0.5); self.title.y = -500; self.addChild(self.title); // Subtitle self.subtitle = new Text2('Protect the Heart Tree!', { size: 80, fill: 0xFFFFFF }); self.subtitle.anchor.set(0.5, 0.5); self.subtitle.y = -350; self.addChild(self.subtitle); // Instructions self.instructions = new Text2('Build towers, collect essence\nand survive enemy waves', { size: 60, fill: 0xCCCCCC }); self.instructions.anchor.set(0.5, 0.5); self.instructions.y = -150; self.addChild(self.instructions); // Create difficulty buttons self.easyBtn = new Container(); var easyBtnBg = LK.getAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 100, tint: 0x00AA00 // Green for easy }); self.easyBtn.addChild(easyBtnBg); var easyBtnText = new Text2('EASY', { size: 60, fill: 0xFFFFFF }); easyBtnText.anchor.set(0.5, 0.5); easyBtnText.y = -15; self.easyBtn.addChild(easyBtnText); // Add description text beneath button var easyDescText = new Text2('Made for beginners', { size: 35, fill: 0xCCFFCC }); easyDescText.anchor.set(0.5, 0.5); easyDescText.y = 20; self.easyBtn.addChild(easyDescText); self.easyBtn.y = 50; self.addChild(self.easyBtn); self.normalBtn = new Container(); var normalBtnBg = LK.getAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 100, tint: 0x0088AA // Blue for normal }); self.normalBtn.addChild(normalBtnBg); var normalBtnText = new Text2('NORMAL', { size: 60, fill: 0xFFFFFF }); normalBtnText.anchor.set(0.5, 0.5); normalBtnText.y = -15; self.normalBtn.addChild(normalBtnText); // Add description text beneath button var normalDescText = new Text2('Balanced challenge', { size: 35, fill: 0xCCEEFF }); normalDescText.anchor.set(0.5, 0.5); normalDescText.y = 20; self.normalBtn.addChild(normalDescText); self.normalBtn.y = 170; self.addChild(self.normalBtn); self.hardBtn = new Container(); var hardBtnBg = LK.getAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 100, tint: 0xAA0000 // Red for hard }); self.hardBtn.addChild(hardBtnBg); var hardBtnText = new Text2('HARD', { size: 60, fill: 0xFFFFFF }); hardBtnText.anchor.set(0.5, 0.5); hardBtnText.y = -15; self.hardBtn.addChild(hardBtnText); // Add description text beneath button var hardDescText = new Text2('For experienced players', { size: 35, fill: 0xFFCCCC }); hardDescText.anchor.set(0.5, 0.5); hardDescText.y = 20; self.hardBtn.addChild(hardDescText); self.hardBtn.y = 290; self.addChild(self.hardBtn); // Race mode button self.raceBtn = new Container(); var raceBtnBg = LK.getAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 100, tint: 0xFF5500 // Orange for race }); self.raceBtn.addChild(raceBtnBg); var raceBtnText = new Text2('RACE', { size: 60, fill: 0xFFFFFF }); raceBtnText.anchor.set(0.5, 0.5); raceBtnText.y = -15; self.raceBtn.addChild(raceBtnText); // Add description text beneath button var raceDescText = new Text2('Fast-paced enemies', { size: 35, fill: 0xFFDDCC }); raceDescText.anchor.set(0.5, 0.5); raceDescText.y = 20; self.raceBtn.addChild(raceDescText); self.raceBtn.y = 410; self.addChild(self.raceBtn); // Infection mode button self.infectionBtn = new Container(); var infectionBtnBg = LK.getAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 100, tint: 0x00AA00 // Green for infection }); self.infectionBtn.addChild(infectionBtnBg); var infectionBtnText = new Text2('INFECTION', { size: 50, fill: 0xFFFFFF }); infectionBtnText.anchor.set(0.5, 0.5); infectionBtnText.y = -15; self.infectionBtn.addChild(infectionBtnText); // Add description text beneath button var infectionDescText = new Text2('Toxic & poison enemies', { size: 35, fill: 0xCCFFCC }); infectionDescText.anchor.set(0.5, 0.5); infectionDescText.y = 20; self.infectionBtn.addChild(infectionDescText); self.infectionBtn.y = 530; self.addChild(self.infectionBtn); // High score display self.highScoreText = new Text2('High Score: ' + storage.highScore, { size: 50, fill: 0xFFD700 }); self.highScoreText.anchor.set(0.5, 0.5); self.highScoreText.y = 650; // Moved lower to make room for all difficulty buttons self.addChild(self.highScoreText); // Add pulsing animation to difficulty buttons self.update = function () { var pulseFactor = 1 + Math.sin(LK.ticks / 20) * 0.05; self.easyBtn.scale.set(pulseFactor, pulseFactor); self.normalBtn.scale.set(pulseFactor, pulseFactor); self.hardBtn.scale.set(pulseFactor, pulseFactor); self.raceBtn.scale.set(pulseFactor, pulseFactor); self.infectionBtn.scale.set(pulseFactor, pulseFactor); }; // Difficulty button event handlers self.easyBtn.down = function () { // Set game difficulty to easy gameDifficulty = 'easy'; // Create fade out effect tween(self, { alpha: 0 }, { duration: 800, onFinish: function onFinish() { self.destroy(); initGame(); } }); }; self.normalBtn.down = function () { // Set game difficulty to normal gameDifficulty = 'normal'; // Create fade out effect tween(self, { alpha: 0 }, { duration: 800, onFinish: function onFinish() { self.destroy(); initGame(); } }); }; self.hardBtn.down = function () { // Set game difficulty to hard gameDifficulty = 'hard'; // Create fade out effect tween(self, { alpha: 0 }, { duration: 800, onFinish: function onFinish() { self.destroy(); initGame(); } }); }; self.raceBtn.down = function () { // Set game difficulty to race gameDifficulty = 'race'; // Create fade out effect tween(self, { alpha: 0 }, { duration: 800, onFinish: function onFinish() { self.destroy(); initGame(); } }); }; self.infectionBtn.down = function () { // Set game difficulty to infection gameDifficulty = 'infection'; // Create fade out effect tween(self, { alpha: 0 }, { duration: 800, onFinish: function onFinish() { self.destroy(); initGame(); } }); }; return self; }); var Tower = Container.expand(function () { var self = Container.call(this); var baseGraphics = self.attachAsset('towerBase', { anchorX: 0.5, anchorY: 0.5 }); self.gridX = 0; self.gridY = 0; self.range = 300; self.damage = 10; self.attackSpeed = 1; // attacks per second self.lastAttackTime = 0; self.level = 1; self.target = null; self.projectiles = []; self.findTarget = function () { var closestEnemy = null; var closestDistance = self.range; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } return closestEnemy; }; self.attack = function () { if (!self.target || !self.target.alive) { self.target = self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.range) { self.target = null; return; } var currentTime = LK.ticks / 60; if (currentTime - self.lastAttackTime >= 1 / self.attackSpeed) { self.lastAttackTime = currentTime; self.fireProjectile(); } } }; self.fireProjectile = function () { var projectile = new Projectile(); projectile.damage = self.damage; projectile.target = self.target; projectile.x = self.x; projectile.y = self.y; game.addChild(projectile); self.projectiles.push(projectile); LK.getSound('projectileShot').play(); }; self.down = function (x, y, obj) { // If in delete mode, sell the tower if (currentMode === 'delete') { // Find the tower's type to determine sell value var towerType = ''; if (self instanceof ThornTower) { towerType = 'thorn'; } else if (self instanceof ShroomTower) { towerType = 'shroom'; } else if (self instanceof FlameTower) { towerType = 'flame'; } else if (self instanceof FarmTower) { towerType = 'healing'; } // Add essence based on tower type essence += towerSellValues[towerType]; updateEssenceText(); // Find and mark the building spot as unoccupied for (var i = 0; i < buildingSpots.length; i++) { if (buildingSpots[i].gridX === self.gridX && buildingSpots[i].gridY === self.gridY) { buildingSpots[i].occupied = false; buildingSpots[i].tower = null; break; } } // Remove tower from towers array for (var i = 0; i < towers.length; i++) { if (towers[i] === self) { towers.splice(i, 1); break; } } // Show sell effect var sellEffect = new Text2("+" + towerSellValues[towerType], { size: 40, fill: 0xFFD700 }); sellEffect.anchor.set(0.5, 0.5); sellEffect.x = self.x; sellEffect.y = self.y; game.addChild(sellEffect); // Animate the effect tween(sellEffect, { alpha: 0, y: sellEffect.y - 50 }, { duration: 1000, onFinish: function onFinish() { sellEffect.destroy(); } }); // Destroy the tower self.destroy(); } }; self.update = function () { self.attack(); // Update projectiles for (var i = self.projectiles.length - 1; i >= 0; i--) { var projectile = self.projectiles[i]; if (!projectile.alive) { projectile.destroy(); self.projectiles.splice(i, 1); } } }; return self; }); var ThornTower = Tower.expand(function () { var self = Tower.call(this); var towerGraphics = self.attachAsset('thornTower', { anchorX: 0.5, anchorY: 0.5, y: -20 // Offset to position on base }); self.range = 350; self.damage = 15; self.attackSpeed = 1.2; return self; }); var ShroomTower = Tower.expand(function () { var self = Tower.call(this); var towerGraphics = self.attachAsset('shroomTower', { anchorX: 0.5, anchorY: 0.5, y: -20 // Offset to position on base }); self.range = 250; self.damage = 25; self.attackSpeed = 0.8; return self; }); var FlameTower = Tower.expand(function () { var self = Tower.call(this); // Remove default base and add custom base with red tint self.removeChildAt(0); var baseGraphics = self.attachAsset('towerBase', { anchorX: 0.5, anchorY: 0.5, tint: 0xFF8800 }); var towerGraphics = self.attachAsset('flameTower', { anchorX: 0.5, anchorY: 0.5, y: -20, // Offset to position on base tint: 0xFF3300 }); self.range = 200; // Short range self.damage = 8; // Lower damage per hit self.attackSpeed = 2.5; // Fast attack speed self.areaOfEffect = true; // This tower hits multiple enemies // Override the fireProjectile method for area attack self.fireProjectile = function () { // Find all enemies in range var hitEnemies = []; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range) { hitEnemies.push(enemy); // Visual effect - flame burst var flameBurst = LK.getAsset('projectile', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y, tint: 0xFF4500, scaleX: 1.5, scaleY: 1.5 }); game.addChild(flameBurst); // Animate and remove flame effect tween(flameBurst, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 300, onFinish: function onFinish() { flameBurst.destroy(); } }); // Damage the enemy enemy.takeDamage(self.damage); } } if (hitEnemies.length > 0) { LK.getSound('projectileShot').play(); } }; return self; }); var FarmTower = Tower.expand(function () { var self = Tower.call(this); // Remove default base and add custom base with gold tint self.removeChildAt(0); var baseGraphics = self.attachAsset('towerBase', { anchorX: 0.5, anchorY: 0.5, tint: 0xFFD700 }); var towerGraphics = self.attachAsset('farmTower', { anchorX: 0.5, anchorY: 0.5, y: -20 // Offset to position on base }); self.range = 280; self.incomeAmount = 25; // Base income per wave self.level = 1; // Override the attack method to do nothing - this tower doesn't attack self.attack = function () { // Instead, just pulsate to show it's active if (LK.ticks % 60 == 0) { // Visual effect on self - gold coins visual var coinEffect = LK.getAsset('essence', { anchorX: 0.5, anchorY: 0.5, x: self.x + (Math.random() * 40 - 20), y: self.y + (Math.random() * 40 - 20), tint: 0xFFD700, alpha: 0.7 }); game.addChild(coinEffect); // Animate and remove coin effect tween(coinEffect, { alpha: 0, y: coinEffect.y - 50, scaleX: 0.5, scaleY: 0.5 }, { duration: 800, onFinish: function onFinish() { coinEffect.destroy(); } }); } }; // Get income amount based on level self.getIncomePerWave = function () { return Math.floor(self.incomeAmount * (1 + (self.level - 1) * 0.5)); }; // Override the fireProjectile to do nothing self.fireProjectile = function () {}; return self; }); var ParasiteEnemy = Enemy.expand(function () { var self = Enemy.call(this); // Replace with custom graphics self.removeChildAt(0); // Remove default enemy graphics var parasiteGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, tint: 0xAA00AA, // Purple tint for parasite scaleX: 0.7, scaleY: 0.7 }); self.speed = 3.5; // Fast to catch guardian self.maxHealth = 45; self.health = self.maxHealth; self.value = 35; self.attached = false; self.attachedTime = 0; self.damageInterval = 0.5; // Damage every half second when attached self.lastDamageTime = 0; // Override update to implement parasite behavior self.update = function () { if (!self.alive) { return; } var currentTime = LK.ticks / 60; if (self.attached) { // When attached, follow guardian and deal damage over time if (guardian) { self.x = guardian.x + 10; self.y = guardian.y - 10; // Deal damage periodically if (currentTime - self.lastDamageTime >= self.damageInterval) { self.lastDamageTime = currentTime; heartTree.takeDamage(2); // Visual effect LK.effects.flashObject(guardian, 0xAA00AA, 150); } // Detach after 10 seconds if (currentTime - self.attachedTime >= 10) { self.attached = false; // Resume normal pathing // Find closest path point var closestDist = Infinity; var closestIdx = 0; for (var i = 0; i < path.length; i++) { var dx = path[i].x - self.x; var dy = path[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < closestDist) { closestDist = dist; closestIdx = i; } } self.pathIndex = closestIdx; } } else { // If guardian is gone, resume normal behavior self.attached = false; } } else { // When not attached, target guardian instead of following path if (guardian) { var dx = guardian.x - self.x; var dy = guardian.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If close to guardian, attach if (distance < 20) { self.attached = true; self.attachedTime = currentTime; // Visual effect on attaching LK.effects.flashObject(guardian, 0xAA00AA, 500); return; } // Move towards guardian var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; // If too far from path for too long, return to following path var nearPath = false; for (var i = 0; i < path.length; i++) { var pathDx = path[i].x - self.x; var pathDy = path[i].y - self.y; var pathDist = Math.sqrt(pathDx * pathDx + pathDy * pathDy); if (pathDist < 300) { nearPath = true; break; } } if (!nearPath) { // Follow normal path Enemy.prototype.update.call(self); } } else { // If no guardian, follow normal path Enemy.prototype.update.call(self); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2E5C2E // Forest green }); /**** * Game Code ****/ // Game variables var gameDifficulty = 'normal'; // Default difficulty: 'normal', 'easy', 'hard', or 'race' var deleteBtn = new Container(); // Add delete button container function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) { return; } f = !1; } else { for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) { ; } } } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) { return; } } finally { if (o) { throw n; } } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) { return r; } } var gridSize = 120; var currentMode = 'guardian'; // 'guardian', 'building', or 'delete' var selectedTower = 'thorn'; var essence = 100; var currentWave = 0; var waveInProgress = false; var waveTimer = null; var enemiesRemaining = 0; var towers = []; var enemies = []; var essenceItems = []; var buildingSpots = []; var path = []; var spawnPoints = []; var guardian; // Reference to the guardian instance var heartTree = null; // Reference to the heart tree instance // Tower costs var towerCosts = { 'thorn': 50, 'shroom': 100, 'flame': 150, 'healing': 200 }; // Tower sell values (will be calculated based on cost and level) var towerSellValues = { 'thorn': Math.floor(towerCosts['thorn'] * 0.7), 'shroom': Math.floor(towerCosts['shroom'] * 0.7), 'flame': Math.floor(towerCosts['flame'] * 0.7), 'healing': Math.floor(towerCosts['healing'] * 0.7) }; // Create UI elements var scoreTxt = new Text2('0', { size: 70, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var essenceTxt = new Text2('💎 100', { size: 70, fill: 0x00FFFF }); essenceTxt.anchor.set(0, 0); essenceTxt.x = -250; // Move it away from the edge LK.gui.topRight.addChild(essenceTxt); var waveTxt = new Text2('Wave: 0', { size: 50, fill: 0xFFFFFF }); waveTxt.anchor.set(1, 0); LK.gui.topLeft.addChild(waveTxt); // Position away from the top left corner where menu icon is waveTxt.x = 150; var healthBarBg = new Container(); var healthBarFill = new Container(); var healthBarTxt = new Text2('100/100', { size: 40, fill: 0xFFFFFF }); healthBarTxt.anchor.set(0.5, 0.5); // Setup health bar function setupHealthBar() { // Background bar var healthBg = LK.getAsset('healthBarBg', { anchorX: 0, anchorY: 0, width: 300, height: 40 }); healthBarBg.addChild(healthBg); // Fill bar var healthFill = LK.getAsset('healthBar', { anchorX: 0, anchorY: 0, width: 300, height: 40 }); healthBarFill.addChild(healthFill); // Add to GUI LK.gui.bottom.addChild(healthBarBg); LK.gui.bottom.addChild(healthBarFill); LK.gui.bottom.addChild(healthBarTxt); // Position health bar healthBarBg.x = -150; // Center the healthbar healthBarFill.x = -150; // Center the healthbar fill healthBarTxt.x = 0; // Center the text healthBarBg.y = -80; // Position below start wave button (which is at -150) healthBarFill.y = -80; // Match background position healthBarTxt.y = -60; // Position text in middle of healthbar } // Update health bar function updateHealthBar() { var healthPercent = heartTree.health / heartTree.maxHealth; healthBarFill.scale.x = healthPercent; healthBarTxt.setText(heartTree.health + '/' + heartTree.maxHealth); // Update color based on health percentage var fill = healthBarFill.getChildAt(0); if (healthPercent > 0.6) { fill.tint = 0x00FF00; // Green } else if (healthPercent > 0.3) { fill.tint = 0xFFFF00; // Yellow } else { fill.tint = 0xFF0000; // Red } // Flash effect when health is low if (healthPercent < 0.2 && LK.ticks % 60 < 30) { healthBarFill.alpha = 0.7; } else { healthBarFill.alpha = 1.0; } } // Create game mode buttons var guardianBtn = new Container(); var buildBtn = new Container(); function setupModeButtons() { // Guardian mode button var guardianBtnBg = LK.getAsset('moveButton', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80 }); guardianBtn.addChild(guardianBtnBg); var guardianBtnTxt = new Text2('Move', { size: 30, fill: 0xFFFFFF }); guardianBtnTxt.anchor.set(0.5, 0.5); guardianBtnTxt.y = 50; guardianBtn.addChild(guardianBtnTxt); // Building mode button var buildBtnBg = LK.getAsset('buildButton', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80 }); buildBtn.addChild(buildBtnBg); var buildBtnTxt = new Text2('Build', { size: 30, fill: 0xFFFFFF }); buildBtnTxt.anchor.set(0.5, 0.5); buildBtnTxt.y = 50; buildBtn.addChild(buildBtnTxt); // Delete button var deleteBtn = new Container(); var deleteBtnBg = LK.getAsset('deleteButton', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80 }); deleteBtn.addChild(deleteBtnBg); var deleteBtnTxt = new Text2('Delete', { size: 30, fill: 0xFFFFFF }); deleteBtnTxt.anchor.set(0.5, 0.5); deleteBtnTxt.y = 50; deleteBtn.addChild(deleteBtnTxt); // Add to GUI LK.gui.bottomLeft.addChild(guardianBtn); LK.gui.bottomLeft.addChild(buildBtn); LK.gui.bottomLeft.addChild(deleteBtn); // Position buttons guardianBtn.x = 100; guardianBtn.y = -100; buildBtn.x = 220; buildBtn.y = -100; deleteBtn.x = 340; deleteBtn.y = -100; // Add event listeners guardianBtn.interactive = true; guardianBtn.down = function () { setGameMode('guardian'); }; buildBtn.interactive = true; buildBtn.down = function () { setGameMode('building'); }; deleteBtn.interactive = true; deleteBtn.down = function () { setGameMode('delete'); }; // Highlight current mode updateModeButtons(); } // Create tower selection buttons var thornBtn = new Container(); var shroomBtn = new Container(); var flameBtn = new Container(); var healingBtn = new Container(); function setupTowerButtons() { // Thorn tower button var thornBtnBg = LK.getAsset('thornTower', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80 }); thornBtn.addChild(thornBtnBg); var thornBtnTxt = new Text2('50', { size: 30, fill: 0xFFFFFF }); thornBtnTxt.anchor.set(0.5, 0.5); thornBtnTxt.y = 50; thornBtn.addChild(thornBtnTxt); // Shroom tower button var shroomBtnBg = LK.getAsset('shroomTower', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80 }); shroomBtn.addChild(shroomBtnBg); var shroomBtnTxt = new Text2('100', { size: 30, fill: 0xFFFFFF }); shroomBtnTxt.anchor.set(0.5, 0.5); shroomBtnTxt.y = 50; shroomBtn.addChild(shroomBtnTxt); // Flame tower button var flameBtnBg = LK.getAsset('flameTower', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80, tint: 0xFF3300 }); flameBtn.addChild(flameBtnBg); var flameBtnTxt = new Text2('150', { size: 30, fill: 0xFFFFFF }); flameBtnTxt.anchor.set(0.5, 0.5); flameBtnTxt.y = 50; flameBtn.addChild(flameBtnTxt); // Farm tower button var healingBtnBg = LK.getAsset('farmTower', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80 }); healingBtn.addChild(healingBtnBg); var healingBtnTxt = new Text2('200', { size: 30, fill: 0xFFFFFF }); healingBtnTxt.anchor.set(0.5, 0.5); healingBtnTxt.y = 50; healingBtn.addChild(healingBtnTxt); // Add to GUI LK.gui.bottomRight.addChild(thornBtn); LK.gui.bottomRight.addChild(shroomBtn); LK.gui.bottomRight.addChild(flameBtn); LK.gui.bottomRight.addChild(healingBtn); // Position buttons thornBtn.x = -100; thornBtn.y = -100; shroomBtn.x = -220; shroomBtn.y = -100; flameBtn.x = -100; flameBtn.y = -200; healingBtn.x = -220; healingBtn.y = -200; // Add event listeners thornBtn.interactive = true; thornBtn.down = function () { if (currentMode === 'building') { selectTower('thorn'); } }; shroomBtn.interactive = true; shroomBtn.down = function () { if (currentMode === 'building' && storage.unlockedTowers.includes('shroom')) { selectTower('shroom'); } }; flameBtn.interactive = true; flameBtn.down = function () { if (currentMode === 'building' && storage.unlockedTowers.includes('flame')) { selectTower('flame'); } }; healingBtn.interactive = true; healingBtn.down = function () { if (currentMode === 'building' && storage.unlockedTowers.includes('healing')) { selectTower('healing'); } }; // If towers are not unlocked, gray them out if (!storage.unlockedTowers.includes('shroom')) { shroomBtn.alpha = 0.5; } if (!storage.unlockedTowers.includes('flame')) { flameBtn.alpha = 0.5; } if (!storage.unlockedTowers.includes('healing')) { healingBtn.alpha = 0.5; } // Highlight selected tower updateTowerButtons(); } // Start wave button var startWaveBtn = new Container(); function setupStartWaveButton() { var startWaveButtonBg = LK.getAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 200, height: 80 }); startWaveBtn.addChild(startWaveButtonBg); var startWaveBtnTxt = new Text2('Start Wave', { size: 40, fill: 0xFFFFFF }); startWaveBtnTxt.anchor.set(0.5, 0.5); startWaveBtn.addChild(startWaveBtnTxt); // Add to GUI LK.gui.bottom.addChild(startWaveBtn); // Position button startWaveBtn.y = -150; // Pulsing animation for wave button when not in wave var pulseInterval = LK.setInterval(function () { if (!waveInProgress) { var pulseFactor = 1 + Math.sin(LK.ticks / 20) * 0.1; startWaveBtn.scale.set(pulseFactor, pulseFactor); } else { startWaveBtn.scale.set(1, 1); } }, 100); // Add event listener startWaveBtn.interactive = true; startWaveBtn.down = function () { if (!waveInProgress) { startWave(); } }; } function updateModeButtons() { guardianBtn.alpha = currentMode === 'guardian' ? 1.0 : 0.6; buildBtn.alpha = currentMode === 'building' ? 1.0 : 0.6; deleteBtn.alpha = currentMode === 'delete' ? 1.0 : 0.6; } function updateTowerButtons() { thornBtn.alpha = currentMode === 'building' && selectedTower === 'thorn' ? 1.0 : 0.6; shroomBtn.alpha = currentMode === 'building' && selectedTower === 'shroom' && storage.unlockedTowers.includes('shroom') ? 1.0 : 0.3; flameBtn.alpha = currentMode === 'building' && selectedTower === 'flame' && storage.unlockedTowers.includes('flame') ? 1.0 : 0.3; healingBtn.alpha = currentMode === 'building' && selectedTower === 'healing' && storage.unlockedTowers.includes('healing') ? 1.0 : 0.3; } function setGameMode(mode) { currentMode = mode; updateModeButtons(); updateTowerButtons(); } function selectTower(tower) { selectedTower = tower; updateTowerButtons(); } function updateEssenceText() { essenceTxt.setText('💎 ' + essence); // Flash effect when essence is gained/lost LK.effects.flashObject(essenceTxt, 0x00FFFF, 300); } function createLevel() { // Clear any existing level elements for (var i = 0; i < buildingSpots.length; i++) { buildingSpots[i].destroy(); } buildingSpots = []; path = []; spawnPoints = []; // Level dimensions (in grid cells) var levelWidth = 15; var levelHeight = 19; // Create a grid for the level var grid = []; for (var y = 0; y < levelHeight; y++) { grid[y] = []; for (var x = 0; x < levelWidth; x++) { grid[y][x] = 0; // 0 = empty } } // Define path through the level (1 = path) var pathCoords = [[0, 9], [1, 9], [2, 9], [3, 9], [4, 9], [5, 9], [6, 9], [6, 8], [6, 7], [6, 6], [6, 5], [7, 5], [8, 5], [9, 5], [9, 6], [9, 7], [9, 8], [9, 9], [9, 10], [9, 11], [8, 11], [7, 11], [6, 11], [5, 11], [4, 11], [4, 12], [4, 13], [4, 14], [4, 15], [5, 15], [6, 15], [7, 15], [8, 15], [9, 15], [10, 15], [11, 15], [11, 14], [11, 13], [11, 12], [11, 11], [11, 10], [11, 9], [11, 8], [11, 7], [12, 7], [13, 7], [14, 7]]; for (var i = 0; i < pathCoords.length; i++) { var _pathCoords$i = _slicedToArray(pathCoords[i], 2), x = _pathCoords$i[0], y = _pathCoords$i[1]; grid[y][x] = 1; } // Set spawn point (2 = spawn) grid[9][0] = 2; // Set heart tree location (3 = heart tree) grid[7][14] = 3; // Create level elements based on grid for (var y = 0; y < levelHeight; y++) { for (var x = 0; x < levelWidth; x++) { var gridValue = grid[y][x]; var worldX = x * gridSize + gridSize / 2 + 174; // Center in grid cell with some offset var worldY = y * gridSize + gridSize / 2 + 150; if (gridValue === 1) { // Path tile var pathTile = new PathTile(); pathTile.x = worldX; pathTile.y = worldY; pathTile.gridX = x; pathTile.gridY = y; game.addChild(pathTile); // Add to path array for enemy movement path.push(pathTile); } else if (gridValue === 2) { // Spawn point var spawnPoint = new SpawnPoint(); spawnPoint.x = worldX; spawnPoint.y = worldY; game.addChild(spawnPoint); spawnPoints.push(spawnPoint); // Also add as first path point path.unshift(spawnPoint); } else if (gridValue === 3) { // Heart Tree heartTree = new HeartTree(); heartTree.x = worldX; heartTree.y = worldY; game.addChild(heartTree); } else if (gridValue === 0) { // Check if adjacent to path (can build here) var adjacentToPath = false; // Check all adjacent cells var directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]; for (var _i = 0, _directions = directions; _i < _directions.length; _i++) { var dir = _directions[_i]; var adjX = x + dir[0]; var adjY = y + dir[1]; if (adjX >= 0 && adjX < levelWidth && adjY >= 0 && adjY < levelHeight && grid[adjY][adjX] === 1) { adjacentToPath = true; break; } } if (adjacentToPath) { // Create building spot var spot = new BuildingSpot(); spot.x = worldX; spot.y = worldY; spot.gridX = x; spot.gridY = y; game.addChild(spot); buildingSpots.push(spot); } } } } // Sort path from start to end path.sort(function (a, b) { return pathCoords.findIndex(function (coords) { return coords[0] === a.gridX && coords[1] === a.gridY; }) - pathCoords.findIndex(function (coords) { return coords[0] === b.gridX && coords[1] === b.gridY; }); }); // Create guardian guardian = new Guardian(); guardian.x = path[Math.floor(path.length / 2)].x; guardian.y = path[Math.floor(path.length / 2)].y; game.addChild(guardian); } function startWave() { if (waveInProgress) { return; } currentWave++; waveTxt.setText('Wave: ' + currentWave); waveInProgress = true; startWaveBtn.alpha = 0.5; // Display wave start message var waveStartTxt = new Text2('Wave ' + currentWave + ' Starting!', { size: 80, fill: 0xFF8C00 }); waveStartTxt.anchor.set(0.5, 0.5); waveStartTxt.x = 2048 / 2; waveStartTxt.y = 2732 / 2; game.addChild(waveStartTxt); // Animate and remove wave start message tween(waveStartTxt, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 1500, onFinish: function onFinish() { waveStartTxt.destroy(); } }); // Calculate enemies for this wave var numEnemies = 5 + Math.floor(currentWave * 1.5); var hasBoss = currentWave % 5 === 0; enemiesRemaining = numEnemies + (hasBoss ? 1 : 0); LK.getSound('waveStart').play(); // Spawn enemies periodically var enemiesSpawned = 0; waveTimer = LK.setInterval(function () { if (enemiesSpawned < numEnemies) { spawnEnemy(false); enemiesSpawned++; } else if (hasBoss && enemiesSpawned === numEnemies) { spawnEnemy(true); // Spawn boss enemiesSpawned++; } else { LK.clearInterval(waveTimer); } }, 1500); } function spawnEnemy(isBoss) { var enemy; // Apply difficulty multipliers var healthMultiplier = 1.0; var speedMultiplier = 1.0; var valueMultiplier = 1.0; // Handle race mode separately if (gameDifficulty === 'race') { if (isBoss) { enemy = new BossEnemy(); // Make boss race-themed enemy.speed *= 1.5; enemy.maxHealth *= 0.8; enemy.health = enemy.maxHealth; } else { // In race mode, spawn more varied race enemies var raceRandom = Math.random(); if (raceRandom < 0.4 || currentWave < 2) { // 40% chance of spawning race enemy (down from 70%) enemy = new RaceEnemy(); // Scale difficulty with wave number enemy.maxHealth = 25 + currentWave * 3; enemy.health = enemy.maxHealth; enemy.speed = 6 + currentWave * 0.25; // Even faster with each wave enemy.value = 20 + currentWave * 2; } else if (raceRandom < 0.6) { // 20% chance of spawning speedy enemy in race mode enemy = new SpeedyEnemy(); enemy.maxHealth = 30 + currentWave * 4; enemy.health = enemy.maxHealth; enemy.speed = 4.5 + currentWave * 0.2; // Faster than normal mode enemy.value = 15 + currentWave; // Special visual effects for race mode speedies enemy.getChildAt(0).tint = 0xFFAA00; // More orange than yellow } else if (raceRandom < 0.75 && currentWave >= 2) { // 15% chance of speed demon enemy after wave 2 enemy = new SpeedDemonEnemy(); enemy.maxHealth = 15 + currentWave * 2; enemy.health = enemy.maxHealth; enemy.speed = 8 + currentWave * 0.3; // Extremely fast enemy.value = 30 + currentWave * 2; } else if (raceRandom < 0.9 && currentWave >= 3) { // 15% chance of motorcycle enemy after wave 3 enemy = new MotorcycleEnemy(); enemy.maxHealth = 20 + currentWave * 3; enemy.health = enemy.maxHealth; enemy.speed = 7 + currentWave * 0.2; enemy.value = 25 + currentWave * 1.5; } else { // 10% chance of flying enemy after wave 3 if (currentWave >= 3) { enemy = new FlyingEnemy(); enemy.maxHealth = 40 + currentWave * 4; enemy.health = enemy.maxHealth; enemy.speed = 4 + currentWave * 0.15; // Faster than normal mode enemy.value = 25 + currentWave; // Change color for race mode enemy.getChildAt(0).tint = 0xFF3300; // More reddish } else { // Fallback to race enemy if flying is not available yet enemy = new RaceEnemy(); enemy.maxHealth = 25 + currentWave * 3; enemy.health = enemy.maxHealth; enemy.speed = 6 + currentWave * 0.25; enemy.value = 20 + currentWave * 2; } } } } else if (gameDifficulty === 'infection') { if (isBoss) { enemy = new InfectionBossEnemy(); // Adjust boss for infection theme enemy.maxHealth = 350 + currentWave * 30; enemy.health = enemy.maxHealth; } else { // In infection mode, spawn more varied poison-based enemies var infectionRandom = Math.random(); if (infectionRandom < 0.2 || currentWave < 2) { // Basic poison enemy (reduced chance from 0.3) enemy = new PoisonEnemy(); // Scale with wave enemy.maxHealth = 80 + currentWave * 7; enemy.health = enemy.maxHealth; enemy.speed = 1.8 + currentWave * 0.1; enemy.value = 20 + currentWave; // Faster shot interval enemy.shotInterval = Math.max(1.5, 3 - currentWave * 0.25); } else if (infectionRandom < 0.4) { // Acid enemy (reduced chance from 0.6) enemy = new AcidEnemy(); // Scale with wave enemy.maxHealth = 90 + currentWave * 8; enemy.health = enemy.maxHealth; enemy.speed = 1.6 + currentWave * 0.08; enemy.value = 22 + currentWave; // More frequent acid drops at higher waves enemy.acidInterval = Math.max(1.0, 2 - currentWave * 0.15); } else if (infectionRandom < 0.55 && currentWave >= 3) { // Toxic blob enemy spawns after wave 3 (reduced chance from 0.8) enemy = new ToxicBlobEnemy(); // Scale with wave enemy.maxHealth = 140 + currentWave * 10; enemy.health = enemy.maxHealth; enemy.speed = 1.3 + currentWave * 0.06; enemy.value = 30 + currentWave * 1.5; } else if (infectionRandom < 0.7 && currentWave >= 2) { // New: Toxic cloud enemy after wave 2 enemy = new ToxicCloudEnemy(); enemy.maxHealth = 95 + currentWave * 8; enemy.health = enemy.maxHealth; enemy.speed = 1.5 + currentWave * 0.07; enemy.value = 25 + currentWave; // More frequent cloud releases at higher waves enemy.cloudInterval = Math.max(1.2, 2 - currentWave * 0.1); } else if (infectionRandom < 0.85 && currentWave >= 4) { // New: Parasite enemy after wave 4 enemy = new ParasiteEnemy(); enemy.maxHealth = 45 + currentWave * 5; enemy.health = enemy.maxHealth; enemy.speed = 3.5 + currentWave * 0.12; enemy.value = 35 + currentWave * 1.5; } else if (infectionRandom < 0.95 && currentWave >= 4) { // Flying enemy with poison theme enemy = new FlyingEnemy(); enemy.maxHealth = 40 + currentWave * 5; enemy.health = enemy.maxHealth; enemy.speed = 3 + currentWave * 0.12; enemy.value = 18 + currentWave; // Change color for infection theme enemy.getChildAt(0).tint = 0x00FF00; // Green tint } else { // Default to normal enemy with poison tint as fallback enemy = new Enemy(); enemy.maxHealth = 50 + currentWave * 6; enemy.health = enemy.maxHealth; enemy.speed = 2 + currentWave * 0.1; enemy.value = 10 + currentWave; // Add green tint enemy.getChildAt(0).tint = 0x88FF88; } } } else { // Normal, Easy, Hard difficulties if (gameDifficulty === 'easy') { healthMultiplier = 0.8; // Enemies have less health speedMultiplier = 0.9; // Enemies move slower valueMultiplier = 1.2; // Enemies give more essence } else if (gameDifficulty === 'hard') { healthMultiplier = 1.3; // Enemies have more health speedMultiplier = 1.2; // Enemies move faster valueMultiplier = 0.8; // Enemies give less essence } if (isBoss) { enemy = new BossEnemy(); } else { // Randomly choose enemy type based on wave progression var randomType = Math.random(); if (currentWave >= 8 && randomType < 0.1) { // 10% chance of armored enemy after wave 8 enemy = new ArmoredEnemy(); enemy.maxHealth = Math.floor((100 + currentWave * 12) * healthMultiplier); enemy.health = enemy.maxHealth; enemy.speed = (1.8 + currentWave * 0.05) * speedMultiplier; enemy.value = Math.floor((40 + currentWave * 2) * valueMultiplier); enemy.shieldHealth = Math.floor((50 + currentWave * 5) * healthMultiplier); } else if (currentWave >= 7 && randomType < 0.2) { // 10% chance of mine layer enemy after wave 7 enemy = new MineLayerEnemy(); enemy.maxHealth = Math.floor((70 + currentWave * 8) * healthMultiplier); enemy.health = enemy.maxHealth; enemy.speed = (2.2 + currentWave * 0.07) * speedMultiplier; enemy.value = Math.floor((30 + currentWave * 1.5) * valueMultiplier); // Less frequent mines at lower difficulties enemy.mineInterval = Math.max(2, 3 - currentWave * 0.1) / speedMultiplier; } else if (currentWave >= 6 && randomType < 0.3) { // 10% chance of spawning flying enemy after wave 6 enemy = new FlyingEnemy(); // Scale difficulty with wave number enemy.maxHealth = Math.floor((40 + currentWave * 5) * healthMultiplier); enemy.health = enemy.maxHealth; enemy.speed = (3 + currentWave * 0.12) * speedMultiplier; enemy.value = Math.floor((18 + currentWave) * valueMultiplier); } else if (currentWave >= 7 && randomType < 0.4) { // 10% chance of spawning poison enemy after wave 7 enemy = new PoisonEnemy(); // Scale difficulty with wave number enemy.maxHealth = Math.floor((80 + currentWave * 8) * healthMultiplier); enemy.health = enemy.maxHealth; enemy.speed = (1.8 + currentWave * 0.08) * speedMultiplier; enemy.value = Math.floor((20 + currentWave) * valueMultiplier); enemy.shotInterval = Math.max(1.5, 3 - currentWave * 0.2) / speedMultiplier; // Shoot faster at higher waves } else if (currentWave >= 5 && randomType < 0.55) { // 15% chance of spawning split enemy after wave 5 enemy = new SplitEnemy(); // Scale difficulty with wave number enemy.maxHealth = Math.floor((70 + currentWave * 12) * healthMultiplier); enemy.health = enemy.maxHealth; enemy.speed = (1.5 + currentWave * 0.06) * speedMultiplier; enemy.value = Math.floor((12 + currentWave) * valueMultiplier); enemy.splitCount = 2 + Math.floor(currentWave / 10); // More splits at higher waves, max 4 } else if (currentWave >= 2 && randomType < 0.75) { // 20% chance of spawning speedy enemy after wave 2 enemy = new SpeedyEnemy(); // Scale difficulty with wave number enemy.maxHealth = Math.floor((30 + currentWave * 6) * healthMultiplier); enemy.health = enemy.maxHealth; enemy.speed = (4 + currentWave * 0.15) * speedMultiplier; enemy.value = Math.floor((15 + currentWave) * valueMultiplier); } else if (currentWave >= 4 && randomType < 0.9) { // 15% chance of spawning tank enemy after wave 4 enemy = new TankEnemy(); // Scale difficulty with wave number enemy.maxHealth = Math.floor((120 + currentWave * 15) * healthMultiplier); enemy.health = enemy.maxHealth; enemy.speed = (1 + currentWave * 0.05) * speedMultiplier; enemy.value = Math.floor((25 + currentWave) * valueMultiplier); } else { // Regular enemy enemy = new Enemy(); // Scale difficulty with wave number enemy.maxHealth = Math.floor((50 + currentWave * 10) * healthMultiplier); enemy.health = enemy.maxHealth; enemy.speed = (2 + currentWave * 0.1) * speedMultiplier; enemy.value = Math.floor((10 + currentWave) * valueMultiplier); } } } // Set initial position at spawn point var spawnPoint = spawnPoints[0]; enemy.x = spawnPoint.x; enemy.y = spawnPoint.y; game.addChild(enemy); enemies.push(enemy); // Apply animation for spawning enemy.alpha = 0; enemy.scale.set(0.5, 0.5); tween(enemy, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500 }); } function checkWaveComplete() { if (waveInProgress && enemies.length === 0 && enemiesRemaining === 0) { waveInProgress = false; startWaveBtn.alpha = 1.0; // Reward between waves var waveBonus = 50 + currentWave * 10; // Add farm tower income var farmIncome = 0; for (var i = 0; i < towers.length; i++) { if (towers[i] instanceof FarmTower) { var income = towers[i].getIncomePerWave(); farmIncome += income; // Show visual effect for farm income var incomeTxt = new Text2("+" + income, { size: 40, fill: 0xFFD700 }); incomeTxt.anchor.set(0.5, 0.5); incomeTxt.x = towers[i].x; incomeTxt.y = towers[i].y; game.addChild(incomeTxt); // Animate text tween(incomeTxt, { alpha: 0, y: incomeTxt.y - 50 }, { duration: 1000, onFinish: function onFinish() { incomeTxt.destroy(); } }); } } essence += waveBonus + farmIncome; updateEssenceText(); // Show wave complete message var waveBonusTxt = new Text2('Wave Complete! +' + waveBonus + ' Essence', { size: 80, fill: 0xFFFFFF }); waveBonusTxt.anchor.set(0.5, 0.5); waveBonusTxt.x = 2048 / 2; waveBonusTxt.y = 2732 / 2; game.addChild(waveBonusTxt); // Animate and remove message tween(waveBonusTxt, { alpha: 0, y: waveBonusTxt.y - 100 }, { duration: 2000, onFinish: function onFinish() { waveBonusTxt.destroy(); } }); // Unlock new tower types based on wave progress if (currentWave === 3 && !storage.unlockedTowers.includes('shroom')) { storage.unlockedTowers.push('shroom'); shroomBtn.alpha = 0.6; // Show unlock message var unlockTxt = new Text2('New Tower Unlocked: Shroom Puff!', { size: 60, fill: 0xFFFF00 }); unlockTxt.anchor.set(0.5, 0.5); unlockTxt.x = 2048 / 2; unlockTxt.y = 2732 / 2 + 100; game.addChild(unlockTxt); // Animate and remove message tween(unlockTxt, { alpha: 0, y: unlockTxt.y - 100 }, { duration: 3000, onFinish: function onFinish() { unlockTxt.destroy(); } }); } else if (currentWave === 5 && !storage.unlockedTowers.includes('flame')) { storage.unlockedTowers.push('flame'); flameBtn.alpha = 0.6; // Show unlock message var unlockTxt = new Text2('New Tower Unlocked: Flame Fern!', { size: 60, fill: 0xFF5500 }); unlockTxt.anchor.set(0.5, 0.5); unlockTxt.x = 2048 / 2; unlockTxt.y = 2732 / 2 + 100; game.addChild(unlockTxt); // Animate and remove message tween(unlockTxt, { alpha: 0, y: unlockTxt.y - 100 }, { duration: 3000, onFinish: function onFinish() { unlockTxt.destroy(); } }); } else if (currentWave === 8 && !storage.unlockedTowers.includes('healing')) { storage.unlockedTowers.push('healing'); healingBtn.alpha = 0.6; // Show unlock message var unlockTxt = new Text2('New Tower Unlocked: Farm Shroom!', { size: 60, fill: 0xFFD700 }); unlockTxt.anchor.set(0.5, 0.5); unlockTxt.x = 2048 / 2; unlockTxt.y = 2732 / 2 + 100; game.addChild(unlockTxt); // Animate and remove message tween(unlockTxt, { alpha: 0, y: unlockTxt.y - 100 }, { duration: 3000, onFinish: function onFinish() { unlockTxt.destroy(); } }); } } } function isPositionValid(x, y) { // Check if position is within game bounds if (x < 0 || x > 2048 || y < 0 || y > 2732) { return false; } return true; } // Initialize game function initGame() { // Setup UI elements setupHealthBar(); setupModeButtons(); setupTowerButtons(); setupStartWaveButton(); // Create game level createLevel(); // Set initial game state LK.setScore(0); scoreTxt.setText('0'); // Apply difficulty settings if (gameDifficulty === 'easy') { essence = 200; // More starting essence heartTree.maxHealth = 150; // More health heartTree.health = heartTree.maxHealth; } else if (gameDifficulty === 'normal') { essence = 100; // Default starting essence heartTree.maxHealth = 100; // Default health heartTree.health = heartTree.maxHealth; } else if (gameDifficulty === 'hard') { essence = 50; // Less starting essence heartTree.maxHealth = 75; // Less health heartTree.health = heartTree.maxHealth; } else if (gameDifficulty === 'race') { essence = 150; // Moderate starting essence heartTree.maxHealth = 100; // Default health heartTree.health = heartTree.maxHealth; // Set game background to a gray color for race mode game.setBackgroundColor(0x808080); // Gray color // Show race mode announcement var raceModeTxt = new Text2('RACE MODE ACTIVATED!', { size: 80, fill: 0xFF5500 }); raceModeTxt.anchor.set(0.5, 0.5); raceModeTxt.x = 2048 / 2; raceModeTxt.y = 2732 / 2; game.addChild(raceModeTxt); // Animate and remove announcement tween(raceModeTxt, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 2000, onFinish: function onFinish() { raceModeTxt.destroy(); } }); } else if (gameDifficulty === 'infection') { essence = 120; // Moderate starting essence heartTree.maxHealth = 90; // Lower health due to infection damage heartTree.health = heartTree.maxHealth; // Set game background to a purple color for infection mode game.setBackgroundColor(0x800080); // Purple color // Show infection mode announcement var infectionModeTxt = new Text2('INFECTION MODE ACTIVATED!', { size: 80, fill: 0x00FF00 }); infectionModeTxt.anchor.set(0.5, 0.5); infectionModeTxt.x = 2048 / 2; infectionModeTxt.y = 2732 / 2; game.addChild(infectionModeTxt); // Animate and remove announcement tween(infectionModeTxt, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 2000, onFinish: function onFinish() { infectionModeTxt.destroy(); } }); } updateEssenceText(); updateHealthBar(); // Start background music LK.playMusic('forestAmbience', { loop: true }); } // Game event handlers game.down = function (x, y, obj) { if (currentMode === 'guardian' && guardian) { guardian.moveTo(x, y); } }; // Game update loop game.update = function () { // Update all game objects for (var i = 0; i < towers.length; i++) { towers[i].update(); } // Update enemies (backwards to safely remove) for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].update(); if (!enemies[i].alive) { enemies[i].destroy(); enemies.splice(i, 1); enemiesRemaining--; } } // Update essence items (backwards to safely remove) for (var i = essenceItems.length - 1; i >= 0; i--) { essenceItems[i].update(); if (essenceItems[i].collected) { essenceItems[i].destroy(); essenceItems.splice(i, 1); } } // Update guardian if it exists if (guardian) { guardian.update(); } // Check if wave is complete checkWaveComplete(); }; // Display title screen instead of directly initializing game var titleScreen = new TitleScreen(); titleScreen.x = 2048 / 2; titleScreen.y = 2732 / 2; game.addChild(titleScreen); // Play background music on title screen LK.playMusic('forestAmbience', { loop: true });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
unlockedTowers: ["thorn", "shroom", "flame", "healing"]
});
/****
* Classes
****/
var BuildingSpot = Container.expand(function () {
var self = Container.call(this);
var spotGraphics;
if (gameDifficulty === 'race') {
spotGraphics = self.attachAsset('tire', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
} else if (gameDifficulty === 'infection') {
spotGraphics = self.attachAsset('mutanttree', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
});
} else {
spotGraphics = self.attachAsset('buildingSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.4
});
}
self.gridX = 0;
self.gridY = 0;
self.occupied = false;
self.tower = null;
self.down = function (x, y, obj) {
if (!self.occupied && currentMode === 'building' && selectedTower !== null && essence >= towerCosts[selectedTower]) {
self.buildTower(selectedTower);
}
};
self.buildTower = function (towerType) {
var tower;
if (towerType === 'thorn') {
tower = new ThornTower();
} else if (towerType === 'shroom') {
tower = new ShroomTower();
} else if (towerType === 'flame') {
tower = new FlameTower();
} else if (towerType === 'healing') {
tower = new FarmTower();
}
if (tower) {
tower.x = self.x;
tower.y = self.y;
tower.gridX = self.gridX;
tower.gridY = self.gridY;
game.addChild(tower);
towers.push(tower);
essence -= towerCosts[towerType];
updateEssenceText();
self.occupied = true;
self.tower = tower;
LK.getSound('towerPlaced').play();
}
};
return self;
});
var Car = Container.expand(function () {
var self = Container.call(this);
var carGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF3300,
// Reddish tint for car
scaleX: 1.3,
scaleY: 0.6 // Low and wide like a car
});
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.maxHealth = 50;
self.health = self.maxHealth;
self.value = 10; // Essence value when defeated
self.pathIndex = 0;
self.alive = true;
self.update = function () {
if (!self.alive) {
return;
}
// Follow path
if (self.pathIndex < path.length) {
var targetPoint = path[self.pathIndex];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If near target point, move to next point
if (distance < 20) {
self.pathIndex++;
// If reached the end of path (HeartTree)
if (self.pathIndex >= path.length) {
heartTree.takeDamage(10);
self.alive = false;
return;
}
} else {
// Move towards current target point
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
}
};
// Add a static method to Enemy to use for inheritance
Enemy.prototype.update = function () {
if (!this.alive) {
return;
}
// Follow path
if (this.pathIndex < path.length) {
var targetPoint = path[this.pathIndex];
var dx = targetPoint.x - this.x;
var dy = targetPoint.y - this.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If near target point, move to next point
if (distance < 20) {
this.pathIndex++;
// If reached the end of path (HeartTree)
if (this.pathIndex >= path.length) {
heartTree.takeDamage(10);
this.alive = false;
return;
}
} else {
// Move towards current target point
var angle = Math.atan2(dy, dx);
this.x += Math.cos(angle) * this.speed;
this.y += Math.sin(angle) * this.speed;
}
}
};
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
self.die();
} else {
// Flash enemy when hit
LK.effects.flashObject(self, 0xFF0000, 200);
}
};
self.die = function () {
self.alive = false;
// Drop essence
var essenceObj = new Essence();
essenceObj.value = self.value;
essenceObj.x = self.x;
essenceObj.y = self.y;
game.addChild(essenceObj);
essenceItems.push(essenceObj);
// Increase score
LK.setScore(LK.getScore() + self.value);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDeath').play();
};
return self;
});
var ToxicCloudEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var cloudGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x55DD55,
// Light green tint for toxic cloud
scaleX: 1.3,
scaleY: 1.0
});
self.speed = 1.5;
self.maxHealth = 95;
self.health = self.maxHealth;
self.value = 25;
self.cloudsReleased = 0;
self.maxClouds = 5;
self.cloudInterval = 2; // Seconds between releasing clouds
self.lastCloudTime = 0;
// Override update to add cloud-releasing behavior
self.update = function () {
if (!self.alive) {
return;
}
// Call parent update for movement
Enemy.prototype.update.call(self);
// Release toxic clouds periodically
var currentTime = LK.ticks / 60;
if (currentTime - self.lastCloudTime >= self.cloudInterval && self.cloudsReleased < self.maxClouds) {
self.lastCloudTime = currentTime;
self.releaseCloud();
self.cloudsReleased++;
}
// Gaseous pulsing effect
var pulse = 1 + Math.sin(LK.ticks / 10) * 0.1;
self.scale.set(pulse, pulse);
};
self.releaseCloud = function () {
// Create a toxic cloud that stays in place and damages over time
var cloud = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
tint: 0x00FF00,
scaleX: 2.5,
scaleY: 1.5,
alpha: 0.6
});
game.addChild(cloud);
// Animate cloud expansion and fade
tween(cloud, {
alpha: 0,
scaleX: 4,
scaleY: 2.5
}, {
duration: 8000,
// Long-lasting cloud
onFinish: function onFinish() {
cloud.destroy();
}
});
// Cloud damages anything nearby every second
var cloudDamageInterval = LK.setInterval(function (cloudPos) {
return function () {
// Check if guardian is in range
if (guardian) {
var dx = guardian.x - cloudPos.x;
var dy = guardian.y - cloudPos.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
// Damage the heart tree
heartTree.takeDamage(1);
// Visual effect
LK.effects.flashObject(guardian, 0x00FF00, 100);
}
}
};
}({
"x": cloud.x,
"y": cloud.y
}), 1000);
// Clear interval when cloud is gone
LK.setTimeout(function (interval) {
return function () {
LK.clearInterval(interval);
};
}(cloudDamageInterval), 8000);
};
return self;
});
var ToxicBlobEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var blobGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x80FF80,
// Light green tint
scaleX: 1.4,
scaleY: 1.4
});
self.speed = 1.3;
self.maxHealth = 140;
self.health = self.maxHealth;
self.value = 30;
// Override die method to spawn smaller toxic enemies
self.die = function () {
self.alive = false;
// Spawn smaller toxic enemies
for (var i = 0; i < 3; i++) {
var smallToxic = new PoisonEnemy();
// Make it smaller
smallToxic.scale.set(0.7, 0.7);
// Reduce stats for smaller version
smallToxic.maxHealth = 30;
smallToxic.health = smallToxic.maxHealth;
smallToxic.speed = 2.5;
smallToxic.value = 8;
smallToxic.pathIndex = self.pathIndex;
// Position with slight offset
smallToxic.x = self.x + (Math.random() * 60 - 30);
smallToxic.y = self.y + (Math.random() * 60 - 30);
game.addChild(smallToxic);
enemies.push(smallToxic);
enemiesRemaining++;
}
// Create explosion effect
var explosion = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
tint: 0x00FF00,
scaleX: 2,
scaleY: 2
});
game.addChild(explosion);
// Animate explosion
tween(explosion, {
alpha: 0,
scaleX: 3,
scaleY: 3
}, {
duration: 400,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Drop essence
var essenceObj = new Essence();
essenceObj.value = self.value;
essenceObj.x = self.x;
essenceObj.y = self.y;
game.addChild(essenceObj);
essenceItems.push(essenceObj);
// Increase score
LK.setScore(LK.getScore() + self.value);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDeath').play();
};
return self;
});
var TankEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var tankGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x666666,
// Grey tint for tank enemies
scaleX: 1.4,
scaleY: 1.4 // Larger
});
self.speed = 1; // Slower
self.maxHealth = 120; // Higher health
self.health = self.maxHealth;
self.value = 25; // Higher value
return self;
});
var SplitEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var splitGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF6347,
// Tomato red for split enemies
scaleX: 1.3,
scaleY: 1.3
});
self.speed = 1.5;
self.maxHealth = 70;
self.health = self.maxHealth;
self.value = 12;
self.splitCount = 2; // Number of smaller enemies to spawn when killed
self.size = 'large'; // Size tracking for spawned children
// Override die method to create smaller enemies when killed
self.die = function () {
self.alive = false;
// Only split if this is the original large enemy
if (self.size === 'large') {
// Spawn smaller enemies
for (var i = 0; i < self.splitCount; i++) {
var smallEnemy = new SplitEnemy();
// Make it smaller
smallEnemy.scale.set(0.6, 0.6);
// Reduce stats for smaller version
smallEnemy.size = 'small';
smallEnemy.maxHealth = 25;
smallEnemy.health = smallEnemy.maxHealth;
smallEnemy.speed = 2.2;
smallEnemy.value = 8;
smallEnemy.pathIndex = self.pathIndex;
// Position with slight offset
smallEnemy.x = self.x + (Math.random() * 60 - 30);
smallEnemy.y = self.y + (Math.random() * 60 - 30);
game.addChild(smallEnemy);
enemies.push(smallEnemy);
enemiesRemaining++;
}
}
// Create explosion effect
var explosion = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
tint: 0xFF4500,
scaleX: 2,
scaleY: 2
});
game.addChild(explosion);
// Animate explosion
tween(explosion, {
alpha: 0,
scaleX: 3,
scaleY: 3
}, {
duration: 300,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Drop essence
var essenceObj = new Essence();
essenceObj.value = self.value;
essenceObj.x = self.x;
essenceObj.y = self.y;
game.addChild(essenceObj);
essenceItems.push(essenceObj);
// Increase score
LK.setScore(LK.getScore() + self.value);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyDeath').play();
};
return self;
});
var SpeedyEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var speedyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFF00,
// Yellow tint for speedy enemies
scaleX: 0.8,
scaleY: 1.2 // Tall and thin
});
self.speed = 4; // Much faster
self.maxHealth = 30; // Lower health
self.health = self.maxHealth;
self.value = 15; // Slightly higher value
return self;
});
var RaceEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var raceGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF5500,
// Orange-red tint for race enemies
scaleX: 1.2,
scaleY: 0.5 // Low and wide like a race car
});
self.speed = 6; // Very fast
self.maxHealth = 25; // Lower health
self.health = self.maxHealth;
self.value = 20;
self.exhaustCounter = 0;
// Override update to add exhaust particle effect
self.update = function () {
if (!self.alive) {
return;
}
// Call parent update for movement
Enemy.prototype.update.call(self);
// Create exhaust particle effect every few ticks
self.exhaustCounter++;
if (self.exhaustCounter >= 5) {
self.exhaustCounter = 0;
var exhaust = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x - Math.cos(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * 30,
y: self.y - Math.sin(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * 30,
tint: 0x888888,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.7
});
game.addChild(exhaust);
// Animate exhaust
tween(exhaust, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
onFinish: function onFinish() {
exhaust.destroy();
}
});
}
};
return self;
});
var SpeedDemonEnemy = RaceEnemy.expand(function () {
var self = RaceEnemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var speedDemonGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF0000,
// Red tint for speed demon
scaleX: 1.0,
scaleY: 0.4 // Very low and streamlined
});
self.speed = 8; // Extremely fast
self.maxHealth = 15; // Lower health
self.health = self.maxHealth;
self.value = 30; // Higher value
// Override update to add more intense exhaust effect
self.update = function () {
if (!self.alive) {
return;
}
// Call parent update for movement
Enemy.prototype.update.call(self);
// Create more frequent exhaust particle effect
self.exhaustCounter++;
if (self.exhaustCounter >= 3) {
// More frequent than regular race cars
self.exhaustCounter = 0;
// Create multiple exhaust particles
for (var i = 0; i < 2; i++) {
var exhaust = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x - Math.cos(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * (30 + i * 10),
y: self.y - Math.sin(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * (30 + i * 10),
tint: 0xFF4500,
// Orange-red for more intense exhaust
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.8
});
game.addChild(exhaust);
// Animate exhaust
tween(exhaust, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 200,
// Faster animation
onFinish: function onFinish() {
exhaust.destroy();
}
});
}
}
};
return self;
});
var MotorcycleEnemy = RaceEnemy.expand(function () {
var self = RaceEnemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var motorGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFF00,
// Yellow tint for motorcycles
scaleX: 0.6,
scaleY: 0.7 // Smaller than cars
});
self.speed = 7; // Very fast
self.maxHealth = 20; // Lower health
self.health = self.maxHealth;
self.value = 25;
self.zigzagOffset = 0;
self.zigzagDirection = 1;
// Override update to add zigzag movement pattern
self.update = function () {
if (!self.alive) {
return;
}
// Call parent update for basic movement
Enemy.prototype.update.call(self);
// Add zigzag pattern to movement
if (self.pathIndex < path.length) {
self.zigzagOffset += 0.2 * self.zigzagDirection;
// Reverse direction at max amplitude
if (Math.abs(self.zigzagOffset) > 30) {
self.zigzagDirection *= -1;
}
// Calculate movement direction
var targetPoint = path[self.pathIndex];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var angle = Math.atan2(dy, dx);
// Apply perpendicular offset (zigzag)
self.x += Math.cos(angle + Math.PI / 2) * self.zigzagOffset * 0.05;
self.y += Math.sin(angle + Math.PI / 2) * self.zigzagOffset * 0.05;
}
// Create exhaust particle effect
self.exhaustCounter++;
if (self.exhaustCounter >= 4) {
self.exhaustCounter = 0;
var exhaust = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x - Math.cos(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * 20,
y: self.y - Math.sin(Math.atan2(path[self.pathIndex].y - self.y, path[self.pathIndex].x - self.x)) * 20,
tint: 0x999999,
// Light gray exhaust
scaleX: 0.4,
scaleY: 0.4,
alpha: 0.6
});
game.addChild(exhaust);
// Animate exhaust
tween(exhaust, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 250,
onFinish: function onFinish() {
exhaust.destroy();
}
});
}
};
return self;
});
var PoisonEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var poisonGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00FF00,
// Green tint for poison enemies
scaleX: 1.1,
scaleY: 1.1
});
self.speed = 1.8;
self.maxHealth = 80;
self.health = self.maxHealth;
self.value = 20;
self.lastShotTime = 0;
self.shotInterval = 3; // Seconds between shots
// Override update to add shooting behavior
self.update = function () {
if (!self.alive) {
return;
}
// Call parent update method for movement
Enemy.prototype.update.call(self);
// Add shooting behavior
var currentTime = LK.ticks / 60;
if (currentTime - self.lastShotTime >= self.shotInterval) {
self.lastShotTime = currentTime;
self.shootProjectile();
}
};
self.shootProjectile = function () {
// Create a poison projectile aimed at heart tree
var projectile = new Projectile();
projectile.x = self.x;
projectile.y = self.y;
projectile.damage = 5;
projectile.speed = 8;
projectile.target = heartTree;
// Make it green
projectile.getChildAt(0).tint = 0x00FF00;
game.addChild(projectile);
// Visual effect
LK.effects.flashObject(self, 0x00FF00, 200);
};
return self;
});
var MineLayerEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var mineLayerGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF9900,
// Orange tint for mine layer
scaleX: 1.3,
scaleY: 1.0
});
self.speed = 2.2;
self.maxHealth = 70;
self.health = self.maxHealth;
self.value = 30;
self.lastMineTime = 0;
self.mineInterval = 3; // Seconds between mines
// Override update to implement mine-dropping behavior
self.update = function () {
if (!self.alive) {
return;
}
// Call parent update for movement
Enemy.prototype.update.call(self);
// Drop mines periodically
var currentTime = LK.ticks / 60;
if (currentTime - self.lastMineTime >= self.mineInterval) {
self.lastMineTime = currentTime;
self.dropMine();
}
};
self.dropMine = function () {
// Create a mine that damages when stepped on
var mine = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
tint: 0xFF3300,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.9
});
game.addChild(mine);
// Add pulsing animation to make mine more visible
var mineInterval = LK.setInterval(function () {
var pulse = 1 + Math.sin(LK.ticks / 10) * 0.2;
mine.scale.set(0.8 * pulse, 0.8 * pulse);
}, 100);
// Check for guardian proximity to mine
var mineCheckInterval = LK.setInterval(function () {
if (guardian) {
var dx = guardian.x - mine.x;
var dy = guardian.y - mine.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60) {
// Mine explosion
var explosion = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: mine.x,
y: mine.y,
tint: 0xFF3300,
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
});
game.addChild(explosion);
// Animate explosion
tween(explosion, {
alpha: 0,
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 500,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Damage heart tree when mine explodes
heartTree.takeDamage(8);
// Remove mine
LK.clearInterval(mineInterval);
LK.clearInterval(mineCheckInterval);
mine.destroy();
}
}
}, 100);
// Mines eventually disappear after 20 seconds
LK.setTimeout(function () {
LK.clearInterval(mineInterval);
LK.clearInterval(mineCheckInterval);
// Fade out effect
tween(mine, {
alpha: 0,
scaleX: 0.4,
scaleY: 0.4
}, {
duration: 500,
onFinish: function onFinish() {
mine.destroy();
}
});
}, 20000);
};
return self;
});
var FlyingEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var flyingGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x9370DB,
// Purple tint for flying enemies
scaleX: 0.9,
scaleY: 0.6 // Flatter shape
});
self.speed = 3;
self.maxHealth = 40;
self.health = self.maxHealth;
self.value = 18;
self.flyingMode = true;
// Override the update method to go directly to heart tree
self.update = function () {
if (!self.alive) {
return;
}
// Flying enemies ignore the path and go straight for the heart tree
var dx = heartTree.x - self.x;
var dy = heartTree.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If near heart tree, attack it
if (distance < 20) {
heartTree.takeDamage(15);
self.alive = false;
return;
}
// Move towards heart tree
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
// Add a slight wave pattern to flight
self.y += Math.sin(LK.ticks / 10) * 0.5;
};
return self;
});
var BossEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with boss graphics
self.removeChildAt(0); // Remove default enemy graphics
var bossGraphics = self.attachAsset('bossEnemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.8,
// Make boss bigger
scaleY: 1.8 // Make boss bigger
});
self.speed = 1.2;
self.maxHealth = 300 + currentWave * 30;
self.health = self.maxHealth;
self.value = 100 + currentWave * 15;
self.lastAttackTime = 0;
self.attackCooldown = 3; // seconds between special attacks
// Override update method to add special attacks
self.update = function () {
if (!self.alive) {
return;
}
// Call parent update for movement
Enemy.prototype.update.call(self);
// Special attacks
var currentTime = LK.ticks / 60;
if (currentTime - self.lastAttackTime >= self.attackCooldown) {
self.lastAttackTime = currentTime;
self.specialAttack();
}
// Pulsate effect
var pulse = 1 + Math.sin(LK.ticks / 15) * 0.1;
self.scale.set(1.8 * pulse, 1.8 * pulse);
};
// Special attack based on boss wave level
self.specialAttack = function () {
// Boss becomes more dangerous in later waves
var attackType = Math.floor(Math.random() * 3);
// Flash effect for attack
LK.effects.flashObject(self, 0xFF0000, 300);
if (attackType === 0) {
// Projectile burst attack
var projectileCount = 8;
for (var i = 0; i < projectileCount; i++) {
var angle = i / projectileCount * Math.PI * 2;
var projectile = new Projectile();
projectile.x = self.x;
projectile.y = self.y;
projectile.damage = 8;
// Set a fixed position as target based on angle
var targetDummy = {
x: self.x + Math.cos(angle) * 500,
y: self.y + Math.sin(angle) * 500,
alive: true
};
projectile.target = targetDummy;
// Give projectile red color
projectile.getChildAt(0).tint = 0xFF0000;
game.addChild(projectile);
}
} else if (attackType === 1) {
// Spawn minions
var minionCount = 2 + Math.floor(currentWave / 5);
for (var i = 0; i < minionCount; i++) {
var minion = new SpeedyEnemy();
minion.x = self.x;
minion.y = self.y;
minion.pathIndex = self.pathIndex;
minion.maxHealth = 20;
minion.health = minion.maxHealth;
minion.speed = 5;
minion.value = 5;
// Make minions smaller
minion.scale.set(0.7, 0.7);
game.addChild(minion);
enemies.push(minion);
enemiesRemaining++;
}
} else {
// Heal self
var healAmount = Math.min(self.maxHealth - self.health, self.maxHealth * 0.1);
if (healAmount > 0) {
self.health += healAmount;
// Create healing effect
var healEffect = new Text2("+" + Math.floor(healAmount), {
size: 40,
fill: 0x00FF00
});
healEffect.anchor.set(0.5, 0.5);
healEffect.x = self.x;
healEffect.y = self.y - 50;
game.addChild(healEffect);
// Animate healing text
tween(healEffect, {
alpha: 0,
y: healEffect.y - 50
}, {
duration: 1000,
onFinish: function onFinish() {
healEffect.destroy();
}
});
}
}
};
// Override takeDamage to get enraged at low health
self.takeDamage = function (amount) {
self.health -= amount;
// Enrage when below 30% health
if (self.health <= self.maxHealth * 0.3 && self.speed < 1.8) {
self.speed *= 1.5; // Move faster
self.attackCooldown *= 0.6; // Attack more often
// Visual effect for enrage
LK.effects.flashObject(self, 0xFF0000, 1000);
// Create enrage message
var enrageText = new Text2("BOSS ENRAGED!", {
size: 70,
fill: 0xFF0000
});
enrageText.anchor.set(0.5, 0.5);
enrageText.x = 2048 / 2;
enrageText.y = 2732 / 2;
game.addChild(enrageText);
// Animate message
tween(enrageText, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 1500,
onFinish: function onFinish() {
enrageText.destroy();
}
});
}
if (self.health <= 0) {
self.die();
} else {
// Flash when hit
LK.effects.flashObject(self, 0xFF0000, 200);
}
};
return self;
});
var InfectionBossEnemy = BossEnemy.expand(function () {
var self = BossEnemy.call(this);
// Replace boss graphics with infected version
self.removeChildAt(0); // Remove default boss graphics
var bossGraphics = self.attachAsset('bossEnemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.8,
scaleY: 1.8,
tint: 0x00FF00 // Green tint for infection
});
// Override special attack to be infection-themed
self.specialAttack = function () {
// Flash effect for attack
LK.effects.flashObject(self, 0x00FF00, 300);
var attackType = Math.floor(Math.random() * 3);
if (attackType === 0) {
// Toxic cloud attack
var cloudCount = 3 + Math.floor(currentWave / 7);
for (var i = 0; i < cloudCount; i++) {
var angle = i / cloudCount * Math.PI * 2;
var distance = 200 + Math.random() * 300;
var cloudX = self.x + Math.cos(angle) * distance;
var cloudY = self.y + Math.sin(angle) * distance;
var toxicCloud = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: cloudX,
y: cloudY,
tint: 0x00FF00,
scaleX: 3,
scaleY: 3,
alpha: 0.7
});
game.addChild(toxicCloud);
// Animate toxic cloud
tween(toxicCloud, {
alpha: 0,
scaleX: 5,
scaleY: 5
}, {
duration: 4000,
onFinish: function onFinish() {
toxicCloud.destroy();
}
});
// Damage anything near the cloud
var checkCloud = LK.setInterval(function (cloudPos) {
return function () {
// Check for guardian in range
if (guardian) {
var dx = guardian.x - cloudPos.x;
var dy = guardian.y - cloudPos.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
// Damage the heart tree
heartTree.takeDamage(2);
// Visual effect
LK.effects.flashObject(guardian, 0x00FF00, 100);
}
}
};
}({
"x": cloudX,
"y": cloudY
}), 500);
// Clear interval when cloud is gone
LK.setTimeout(function (interval) {
return function () {
LK.clearInterval(interval);
};
}(checkCloud), 4000);
}
} else if (attackType === 1) {
// Spawn infection minions
var minionCount = 2 + Math.floor(currentWave / 6);
for (var i = 0; i < minionCount; i++) {
var minion;
if (Math.random() < 0.5) {
minion = new AcidEnemy();
} else {
minion = new PoisonEnemy();
}
minion.x = self.x;
minion.y = self.y;
minion.pathIndex = self.pathIndex;
minion.maxHealth = 40;
minion.health = minion.maxHealth;
// Make minions smaller
minion.scale.set(0.8, 0.8);
game.addChild(minion);
enemies.push(minion);
enemiesRemaining++;
}
} else {
// Viral infection - heals self and damages heart tree
var healAmount = Math.min(self.maxHealth - self.health, self.maxHealth * 0.15);
if (healAmount > 0) {
self.health += healAmount;
// Create healing effect
var healEffect = new Text2("+" + Math.floor(healAmount), {
size: 40,
fill: 0x00FF00
});
healEffect.anchor.set(0.5, 0.5);
healEffect.x = self.x;
healEffect.y = self.y - 50;
game.addChild(healEffect);
// Animate healing text
tween(healEffect, {
alpha: 0,
y: healEffect.y - 50
}, {
duration: 1000,
onFinish: function onFinish() {
healEffect.destroy();
}
});
}
// Damage heart tree directly
heartTree.takeDamage(5);
// Visual effect showing infection spread to heart tree
var infectionLine = new Container();
for (var i = 0; i < 10; i++) {
var segment = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00FF00,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.7
});
segment.x = self.x + (heartTree.x - self.x) * (i / 10);
segment.y = self.y + (heartTree.y - self.y) * (i / 10);
infectionLine.addChild(segment);
}
game.addChild(infectionLine);
// Animate and remove infection line
LK.setTimeout(function () {
infectionLine.destroy();
}, 1000);
}
};
return self;
});
var ArmoredEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var armorGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x888888,
// Gray tint for armor
scaleX: 1.2,
scaleY: 1.2
});
// Add shield graphic
var shieldGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x3399FF,
// Blue shield
scaleX: 2.0,
scaleY: 2.0,
alpha: 0.6
});
self.speed = 1.8;
self.maxHealth = 100;
self.health = self.maxHealth;
self.value = 40;
self.shieldHealth = 50;
self.shieldActive = true;
// Override takeDamage to implement shield mechanics
self.takeDamage = function (amount) {
if (self.shieldActive) {
// Shield takes reduced damage
self.shieldHealth -= amount * 0.5;
// Shield break effect
if (self.shieldHealth <= 0) {
self.shieldActive = false;
shieldGraphics.visible = false;
// Shield break visual effect
var breakEffect = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
tint: 0x3399FF,
scaleX: 2.5,
scaleY: 2.5,
alpha: 0.8
});
game.addChild(breakEffect);
// Animate shield break
tween(breakEffect, {
alpha: 0,
scaleX: 3.5,
scaleY: 3.5
}, {
duration: 500,
onFinish: function onFinish() {
breakEffect.destroy();
}
});
} else {
// Shield hit effect
LK.effects.flashObject(shieldGraphics, 0xFFFFFF, 200);
}
} else {
// Normal damage when shield is down
self.health -= amount;
if (self.health <= 0) {
self.die();
} else {
// Flash enemy when hit
LK.effects.flashObject(self, 0xFF0000, 200);
}
}
};
// Override update to add shield visual effects
self.update = function () {
if (!self.alive) {
return;
}
// Call parent update for movement
Enemy.prototype.update.call(self);
// Pulsating shield effect
if (self.shieldActive) {
var pulse = 1 + Math.sin(LK.ticks / 15) * 0.1;
shieldGraphics.scale.set(2.0 * pulse, 2.0 * pulse);
}
};
return self;
});
var AcidEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var acidGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00AA00,
// Darker green tint for acid enemies
scaleX: 1.2,
scaleY: 1.2
});
self.speed = 1.6;
self.maxHealth = 90;
self.health = self.maxHealth;
self.value = 22;
self.lastAcidTime = 0;
self.acidInterval = 2; // Seconds between acid puddles
// Override update to add acid trail
self.update = function () {
if (!self.alive) {
return;
}
// Call parent update method for movement
Enemy.prototype.update.call(self);
// Add acid trail behavior
var currentTime = LK.ticks / 60;
if (currentTime - self.lastAcidTime >= self.acidInterval) {
self.lastAcidTime = currentTime;
self.dropAcid();
}
};
self.dropAcid = function () {
// Create an acid puddle that damages the guardian
var acid = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
tint: 0x00FF00,
scaleX: 1.8,
scaleY: 0.4,
alpha: 0.7
});
game.addChild(acid);
// Animate acid puddle
tween(acid, {
alpha: 0,
scaleX: 2.5,
scaleY: 0.2
}, {
duration: 5000,
onFinish: function onFinish() {
acid.destroy();
}
});
// Check if guardian is in acid puddle
var checkAcid = LK.setInterval(function () {
if (guardian) {
var dx = guardian.x - acid.x;
var dy = guardian.y - acid.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60) {
// Damage the heart tree if guardian is in acid
heartTree.takeDamage(1);
// Visual effect on guardian
LK.effects.flashObject(guardian, 0x00FF00, 200);
}
}
}, 500);
// Clear interval when acid is gone
LK.setTimeout(function () {
LK.clearInterval(checkAcid);
}, 5000);
};
return self;
});
var Essence = Container.expand(function () {
var self = Container.call(this);
var essenceGraphics = self.attachAsset('essence', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 10;
self.lifeTime = 10; // seconds
self.creationTime = LK.ticks / 60;
self.collected = false;
self.update = function () {
if (self.collected) {
return;
}
// Check if guardian is close enough to collect
var dx = guardian.x - self.x;
var dy = guardian.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
self.collect();
return;
}
// Check if timed out
var currentTime = LK.ticks / 60;
if (currentTime - self.creationTime > self.lifeTime) {
self.collected = true;
}
// Pulse animation
var pulseFactor = 1 + Math.sin(LK.ticks / 20) * 0.1;
self.scale.set(pulseFactor, pulseFactor);
};
self.collect = function () {
essence += self.value;
updateEssenceText();
self.collected = true;
LK.getSound('essenceCollected').play();
// Animate collection
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
var Guardian = Container.expand(function () {
var self = Container.call(this);
var guardianGraphics = self.attachAsset('guardian', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 7;
self.targetX = null;
self.targetY = null;
self.moving = false;
self.update = function () {
if (self.moving && self.targetX !== null && self.targetY !== null) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If near target, stop moving
if (distance < 10) {
self.moving = false;
self.targetX = null;
self.targetY = null;
} else {
// Move towards target
var angle = Math.atan2(dy, dx);
var nextX = self.x + Math.cos(angle) * self.speed;
var nextY = self.y + Math.sin(angle) * self.speed;
// Check if the next position is valid
if (isPositionValid(nextX, nextY)) {
self.x = nextX;
self.y = nextY;
} else {
self.moving = false;
}
}
}
};
self.moveTo = function (x, y) {
if (isPositionValid(x, y)) {
self.targetX = x;
self.targetY = y;
self.moving = true;
}
};
return self;
});
var HeartTree = Container.expand(function () {
var self = Container.call(this);
var treeGraphics;
if (gameDifficulty === 'race') {
treeGraphics = self.attachAsset('finishline', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (gameDifficulty === 'infection') {
treeGraphics = self.attachAsset('oldbuilding', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
treeGraphics = self.attachAsset('heartTree', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.maxHealth = 100;
self.health = self.maxHealth;
self.takeDamage = function (amount) {
self.health -= amount;
updateHealthBar();
LK.getSound('heartTreeDamage').play();
LK.effects.flashObject(self, 0xFF0000, 300);
if (self.health <= 0) {
// Game over
LK.showGameOver();
// Update high score if needed
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
}
}
};
return self;
});
var PathTile = Container.expand(function () {
var self = Container.call(this);
var pathGraphics;
if (gameDifficulty === 'race') {
pathGraphics = self.attachAsset('racetrack', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9
});
} else if (gameDifficulty === 'infection') {
pathGraphics = self.attachAsset('acidspill', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9
});
} else {
pathGraphics = self.attachAsset('path', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
}
self.gridX = 0;
self.gridY = 0;
return self;
});
var Projectile = Container.expand(function () {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 20;
self.damage = 10;
self.target = null;
self.alive = true;
self.update = function () {
if (!self.target || !self.target.alive) {
self.alive = false;
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
// Hit target
if (typeof self.target.takeDamage === 'function') {
self.target.takeDamage(self.damage);
}
self.alive = false;
return;
}
// Move towards target
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
};
return self;
});
var SpawnPoint = Container.expand(function () {
var self = Container.call(this);
var spawnGraphics;
if (gameDifficulty === 'race') {
spawnGraphics = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (gameDifficulty === 'infection') {
spawnGraphics = self.attachAsset('acidbarrel', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
spawnGraphics = self.attachAsset('spawnPoint', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.active = true;
return self;
});
var TitleScreen = Container.expand(function () {
var self = Container.call(this);
// Background overlay for title screen
var overlay = self.attachAsset('waveButton', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
tint: 0x122112,
alpha: 0.8
});
// Game title
self.title = new Text2('Forest Defender', {
size: 180,
fill: 0x83de44
});
self.title.anchor.set(0.5, 0.5);
self.title.y = -500;
self.addChild(self.title);
// Subtitle
self.subtitle = new Text2('Protect the Heart Tree!', {
size: 80,
fill: 0xFFFFFF
});
self.subtitle.anchor.set(0.5, 0.5);
self.subtitle.y = -350;
self.addChild(self.subtitle);
// Instructions
self.instructions = new Text2('Build towers, collect essence\nand survive enemy waves', {
size: 60,
fill: 0xCCCCCC
});
self.instructions.anchor.set(0.5, 0.5);
self.instructions.y = -150;
self.addChild(self.instructions);
// Create difficulty buttons
self.easyBtn = new Container();
var easyBtnBg = LK.getAsset('waveButton', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100,
tint: 0x00AA00 // Green for easy
});
self.easyBtn.addChild(easyBtnBg);
var easyBtnText = new Text2('EASY', {
size: 60,
fill: 0xFFFFFF
});
easyBtnText.anchor.set(0.5, 0.5);
easyBtnText.y = -15;
self.easyBtn.addChild(easyBtnText);
// Add description text beneath button
var easyDescText = new Text2('Made for beginners', {
size: 35,
fill: 0xCCFFCC
});
easyDescText.anchor.set(0.5, 0.5);
easyDescText.y = 20;
self.easyBtn.addChild(easyDescText);
self.easyBtn.y = 50;
self.addChild(self.easyBtn);
self.normalBtn = new Container();
var normalBtnBg = LK.getAsset('waveButton', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100,
tint: 0x0088AA // Blue for normal
});
self.normalBtn.addChild(normalBtnBg);
var normalBtnText = new Text2('NORMAL', {
size: 60,
fill: 0xFFFFFF
});
normalBtnText.anchor.set(0.5, 0.5);
normalBtnText.y = -15;
self.normalBtn.addChild(normalBtnText);
// Add description text beneath button
var normalDescText = new Text2('Balanced challenge', {
size: 35,
fill: 0xCCEEFF
});
normalDescText.anchor.set(0.5, 0.5);
normalDescText.y = 20;
self.normalBtn.addChild(normalDescText);
self.normalBtn.y = 170;
self.addChild(self.normalBtn);
self.hardBtn = new Container();
var hardBtnBg = LK.getAsset('waveButton', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100,
tint: 0xAA0000 // Red for hard
});
self.hardBtn.addChild(hardBtnBg);
var hardBtnText = new Text2('HARD', {
size: 60,
fill: 0xFFFFFF
});
hardBtnText.anchor.set(0.5, 0.5);
hardBtnText.y = -15;
self.hardBtn.addChild(hardBtnText);
// Add description text beneath button
var hardDescText = new Text2('For experienced players', {
size: 35,
fill: 0xFFCCCC
});
hardDescText.anchor.set(0.5, 0.5);
hardDescText.y = 20;
self.hardBtn.addChild(hardDescText);
self.hardBtn.y = 290;
self.addChild(self.hardBtn);
// Race mode button
self.raceBtn = new Container();
var raceBtnBg = LK.getAsset('waveButton', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100,
tint: 0xFF5500 // Orange for race
});
self.raceBtn.addChild(raceBtnBg);
var raceBtnText = new Text2('RACE', {
size: 60,
fill: 0xFFFFFF
});
raceBtnText.anchor.set(0.5, 0.5);
raceBtnText.y = -15;
self.raceBtn.addChild(raceBtnText);
// Add description text beneath button
var raceDescText = new Text2('Fast-paced enemies', {
size: 35,
fill: 0xFFDDCC
});
raceDescText.anchor.set(0.5, 0.5);
raceDescText.y = 20;
self.raceBtn.addChild(raceDescText);
self.raceBtn.y = 410;
self.addChild(self.raceBtn);
// Infection mode button
self.infectionBtn = new Container();
var infectionBtnBg = LK.getAsset('waveButton', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100,
tint: 0x00AA00 // Green for infection
});
self.infectionBtn.addChild(infectionBtnBg);
var infectionBtnText = new Text2('INFECTION', {
size: 50,
fill: 0xFFFFFF
});
infectionBtnText.anchor.set(0.5, 0.5);
infectionBtnText.y = -15;
self.infectionBtn.addChild(infectionBtnText);
// Add description text beneath button
var infectionDescText = new Text2('Toxic & poison enemies', {
size: 35,
fill: 0xCCFFCC
});
infectionDescText.anchor.set(0.5, 0.5);
infectionDescText.y = 20;
self.infectionBtn.addChild(infectionDescText);
self.infectionBtn.y = 530;
self.addChild(self.infectionBtn);
// High score display
self.highScoreText = new Text2('High Score: ' + storage.highScore, {
size: 50,
fill: 0xFFD700
});
self.highScoreText.anchor.set(0.5, 0.5);
self.highScoreText.y = 650; // Moved lower to make room for all difficulty buttons
self.addChild(self.highScoreText);
// Add pulsing animation to difficulty buttons
self.update = function () {
var pulseFactor = 1 + Math.sin(LK.ticks / 20) * 0.05;
self.easyBtn.scale.set(pulseFactor, pulseFactor);
self.normalBtn.scale.set(pulseFactor, pulseFactor);
self.hardBtn.scale.set(pulseFactor, pulseFactor);
self.raceBtn.scale.set(pulseFactor, pulseFactor);
self.infectionBtn.scale.set(pulseFactor, pulseFactor);
};
// Difficulty button event handlers
self.easyBtn.down = function () {
// Set game difficulty to easy
gameDifficulty = 'easy';
// Create fade out effect
tween(self, {
alpha: 0
}, {
duration: 800,
onFinish: function onFinish() {
self.destroy();
initGame();
}
});
};
self.normalBtn.down = function () {
// Set game difficulty to normal
gameDifficulty = 'normal';
// Create fade out effect
tween(self, {
alpha: 0
}, {
duration: 800,
onFinish: function onFinish() {
self.destroy();
initGame();
}
});
};
self.hardBtn.down = function () {
// Set game difficulty to hard
gameDifficulty = 'hard';
// Create fade out effect
tween(self, {
alpha: 0
}, {
duration: 800,
onFinish: function onFinish() {
self.destroy();
initGame();
}
});
};
self.raceBtn.down = function () {
// Set game difficulty to race
gameDifficulty = 'race';
// Create fade out effect
tween(self, {
alpha: 0
}, {
duration: 800,
onFinish: function onFinish() {
self.destroy();
initGame();
}
});
};
self.infectionBtn.down = function () {
// Set game difficulty to infection
gameDifficulty = 'infection';
// Create fade out effect
tween(self, {
alpha: 0
}, {
duration: 800,
onFinish: function onFinish() {
self.destroy();
initGame();
}
});
};
return self;
});
var Tower = Container.expand(function () {
var self = Container.call(this);
var baseGraphics = self.attachAsset('towerBase', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.range = 300;
self.damage = 10;
self.attackSpeed = 1; // attacks per second
self.lastAttackTime = 0;
self.level = 1;
self.target = null;
self.projectiles = [];
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = self.range;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
return closestEnemy;
};
self.attack = function () {
if (!self.target || !self.target.alive) {
self.target = self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.range) {
self.target = null;
return;
}
var currentTime = LK.ticks / 60;
if (currentTime - self.lastAttackTime >= 1 / self.attackSpeed) {
self.lastAttackTime = currentTime;
self.fireProjectile();
}
}
};
self.fireProjectile = function () {
var projectile = new Projectile();
projectile.damage = self.damage;
projectile.target = self.target;
projectile.x = self.x;
projectile.y = self.y;
game.addChild(projectile);
self.projectiles.push(projectile);
LK.getSound('projectileShot').play();
};
self.down = function (x, y, obj) {
// If in delete mode, sell the tower
if (currentMode === 'delete') {
// Find the tower's type to determine sell value
var towerType = '';
if (self instanceof ThornTower) {
towerType = 'thorn';
} else if (self instanceof ShroomTower) {
towerType = 'shroom';
} else if (self instanceof FlameTower) {
towerType = 'flame';
} else if (self instanceof FarmTower) {
towerType = 'healing';
}
// Add essence based on tower type
essence += towerSellValues[towerType];
updateEssenceText();
// Find and mark the building spot as unoccupied
for (var i = 0; i < buildingSpots.length; i++) {
if (buildingSpots[i].gridX === self.gridX && buildingSpots[i].gridY === self.gridY) {
buildingSpots[i].occupied = false;
buildingSpots[i].tower = null;
break;
}
}
// Remove tower from towers array
for (var i = 0; i < towers.length; i++) {
if (towers[i] === self) {
towers.splice(i, 1);
break;
}
}
// Show sell effect
var sellEffect = new Text2("+" + towerSellValues[towerType], {
size: 40,
fill: 0xFFD700
});
sellEffect.anchor.set(0.5, 0.5);
sellEffect.x = self.x;
sellEffect.y = self.y;
game.addChild(sellEffect);
// Animate the effect
tween(sellEffect, {
alpha: 0,
y: sellEffect.y - 50
}, {
duration: 1000,
onFinish: function onFinish() {
sellEffect.destroy();
}
});
// Destroy the tower
self.destroy();
}
};
self.update = function () {
self.attack();
// Update projectiles
for (var i = self.projectiles.length - 1; i >= 0; i--) {
var projectile = self.projectiles[i];
if (!projectile.alive) {
projectile.destroy();
self.projectiles.splice(i, 1);
}
}
};
return self;
});
var ThornTower = Tower.expand(function () {
var self = Tower.call(this);
var towerGraphics = self.attachAsset('thornTower', {
anchorX: 0.5,
anchorY: 0.5,
y: -20 // Offset to position on base
});
self.range = 350;
self.damage = 15;
self.attackSpeed = 1.2;
return self;
});
var ShroomTower = Tower.expand(function () {
var self = Tower.call(this);
var towerGraphics = self.attachAsset('shroomTower', {
anchorX: 0.5,
anchorY: 0.5,
y: -20 // Offset to position on base
});
self.range = 250;
self.damage = 25;
self.attackSpeed = 0.8;
return self;
});
var FlameTower = Tower.expand(function () {
var self = Tower.call(this);
// Remove default base and add custom base with red tint
self.removeChildAt(0);
var baseGraphics = self.attachAsset('towerBase', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF8800
});
var towerGraphics = self.attachAsset('flameTower', {
anchorX: 0.5,
anchorY: 0.5,
y: -20,
// Offset to position on base
tint: 0xFF3300
});
self.range = 200; // Short range
self.damage = 8; // Lower damage per hit
self.attackSpeed = 2.5; // Fast attack speed
self.areaOfEffect = true; // This tower hits multiple enemies
// Override the fireProjectile method for area attack
self.fireProjectile = function () {
// Find all enemies in range
var hitEnemies = [];
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
hitEnemies.push(enemy);
// Visual effect - flame burst
var flameBurst = LK.getAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
tint: 0xFF4500,
scaleX: 1.5,
scaleY: 1.5
});
game.addChild(flameBurst);
// Animate and remove flame effect
tween(flameBurst, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
onFinish: function onFinish() {
flameBurst.destroy();
}
});
// Damage the enemy
enemy.takeDamage(self.damage);
}
}
if (hitEnemies.length > 0) {
LK.getSound('projectileShot').play();
}
};
return self;
});
var FarmTower = Tower.expand(function () {
var self = Tower.call(this);
// Remove default base and add custom base with gold tint
self.removeChildAt(0);
var baseGraphics = self.attachAsset('towerBase', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFD700
});
var towerGraphics = self.attachAsset('farmTower', {
anchorX: 0.5,
anchorY: 0.5,
y: -20
// Offset to position on base
});
self.range = 280;
self.incomeAmount = 25; // Base income per wave
self.level = 1;
// Override the attack method to do nothing - this tower doesn't attack
self.attack = function () {
// Instead, just pulsate to show it's active
if (LK.ticks % 60 == 0) {
// Visual effect on self - gold coins visual
var coinEffect = LK.getAsset('essence', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x + (Math.random() * 40 - 20),
y: self.y + (Math.random() * 40 - 20),
tint: 0xFFD700,
alpha: 0.7
});
game.addChild(coinEffect);
// Animate and remove coin effect
tween(coinEffect, {
alpha: 0,
y: coinEffect.y - 50,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 800,
onFinish: function onFinish() {
coinEffect.destroy();
}
});
}
};
// Get income amount based on level
self.getIncomePerWave = function () {
return Math.floor(self.incomeAmount * (1 + (self.level - 1) * 0.5));
};
// Override the fireProjectile to do nothing
self.fireProjectile = function () {};
return self;
});
var ParasiteEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with custom graphics
self.removeChildAt(0); // Remove default enemy graphics
var parasiteGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xAA00AA,
// Purple tint for parasite
scaleX: 0.7,
scaleY: 0.7
});
self.speed = 3.5; // Fast to catch guardian
self.maxHealth = 45;
self.health = self.maxHealth;
self.value = 35;
self.attached = false;
self.attachedTime = 0;
self.damageInterval = 0.5; // Damage every half second when attached
self.lastDamageTime = 0;
// Override update to implement parasite behavior
self.update = function () {
if (!self.alive) {
return;
}
var currentTime = LK.ticks / 60;
if (self.attached) {
// When attached, follow guardian and deal damage over time
if (guardian) {
self.x = guardian.x + 10;
self.y = guardian.y - 10;
// Deal damage periodically
if (currentTime - self.lastDamageTime >= self.damageInterval) {
self.lastDamageTime = currentTime;
heartTree.takeDamage(2);
// Visual effect
LK.effects.flashObject(guardian, 0xAA00AA, 150);
}
// Detach after 10 seconds
if (currentTime - self.attachedTime >= 10) {
self.attached = false;
// Resume normal pathing
// Find closest path point
var closestDist = Infinity;
var closestIdx = 0;
for (var i = 0; i < path.length; i++) {
var dx = path[i].x - self.x;
var dy = path[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < closestDist) {
closestDist = dist;
closestIdx = i;
}
}
self.pathIndex = closestIdx;
}
} else {
// If guardian is gone, resume normal behavior
self.attached = false;
}
} else {
// When not attached, target guardian instead of following path
if (guardian) {
var dx = guardian.x - self.x;
var dy = guardian.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If close to guardian, attach
if (distance < 20) {
self.attached = true;
self.attachedTime = currentTime;
// Visual effect on attaching
LK.effects.flashObject(guardian, 0xAA00AA, 500);
return;
}
// Move towards guardian
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
// If too far from path for too long, return to following path
var nearPath = false;
for (var i = 0; i < path.length; i++) {
var pathDx = path[i].x - self.x;
var pathDy = path[i].y - self.y;
var pathDist = Math.sqrt(pathDx * pathDx + pathDy * pathDy);
if (pathDist < 300) {
nearPath = true;
break;
}
}
if (!nearPath) {
// Follow normal path
Enemy.prototype.update.call(self);
}
} else {
// If no guardian, follow normal path
Enemy.prototype.update.call(self);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2E5C2E // Forest green
});
/****
* Game Code
****/
// Game variables
var gameDifficulty = 'normal'; // Default difficulty: 'normal', 'easy', 'hard', or 'race'
var deleteBtn = new Container(); // Add delete button container
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) {
return _arrayLikeToArray(r, a);
}
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) {
n[e] = r[e];
}
return n;
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) {
return;
}
f = !1;
} else {
for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) {
;
}
}
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) {
return;
}
} finally {
if (o) {
throw n;
}
}
}
return a;
}
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) {
return r;
}
}
var gridSize = 120;
var currentMode = 'guardian'; // 'guardian', 'building', or 'delete'
var selectedTower = 'thorn';
var essence = 100;
var currentWave = 0;
var waveInProgress = false;
var waveTimer = null;
var enemiesRemaining = 0;
var towers = [];
var enemies = [];
var essenceItems = [];
var buildingSpots = [];
var path = [];
var spawnPoints = [];
var guardian; // Reference to the guardian instance
var heartTree = null; // Reference to the heart tree instance
// Tower costs
var towerCosts = {
'thorn': 50,
'shroom': 100,
'flame': 150,
'healing': 200
};
// Tower sell values (will be calculated based on cost and level)
var towerSellValues = {
'thorn': Math.floor(towerCosts['thorn'] * 0.7),
'shroom': Math.floor(towerCosts['shroom'] * 0.7),
'flame': Math.floor(towerCosts['flame'] * 0.7),
'healing': Math.floor(towerCosts['healing'] * 0.7)
};
// Create UI elements
var scoreTxt = new Text2('0', {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var essenceTxt = new Text2('💎 100', {
size: 70,
fill: 0x00FFFF
});
essenceTxt.anchor.set(0, 0);
essenceTxt.x = -250; // Move it away from the edge
LK.gui.topRight.addChild(essenceTxt);
var waveTxt = new Text2('Wave: 0', {
size: 50,
fill: 0xFFFFFF
});
waveTxt.anchor.set(1, 0);
LK.gui.topLeft.addChild(waveTxt);
// Position away from the top left corner where menu icon is
waveTxt.x = 150;
var healthBarBg = new Container();
var healthBarFill = new Container();
var healthBarTxt = new Text2('100/100', {
size: 40,
fill: 0xFFFFFF
});
healthBarTxt.anchor.set(0.5, 0.5);
// Setup health bar
function setupHealthBar() {
// Background bar
var healthBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0,
width: 300,
height: 40
});
healthBarBg.addChild(healthBg);
// Fill bar
var healthFill = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0,
width: 300,
height: 40
});
healthBarFill.addChild(healthFill);
// Add to GUI
LK.gui.bottom.addChild(healthBarBg);
LK.gui.bottom.addChild(healthBarFill);
LK.gui.bottom.addChild(healthBarTxt);
// Position health bar
healthBarBg.x = -150; // Center the healthbar
healthBarFill.x = -150; // Center the healthbar fill
healthBarTxt.x = 0; // Center the text
healthBarBg.y = -80; // Position below start wave button (which is at -150)
healthBarFill.y = -80; // Match background position
healthBarTxt.y = -60; // Position text in middle of healthbar
}
// Update health bar
function updateHealthBar() {
var healthPercent = heartTree.health / heartTree.maxHealth;
healthBarFill.scale.x = healthPercent;
healthBarTxt.setText(heartTree.health + '/' + heartTree.maxHealth);
// Update color based on health percentage
var fill = healthBarFill.getChildAt(0);
if (healthPercent > 0.6) {
fill.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
fill.tint = 0xFFFF00; // Yellow
} else {
fill.tint = 0xFF0000; // Red
}
// Flash effect when health is low
if (healthPercent < 0.2 && LK.ticks % 60 < 30) {
healthBarFill.alpha = 0.7;
} else {
healthBarFill.alpha = 1.0;
}
}
// Create game mode buttons
var guardianBtn = new Container();
var buildBtn = new Container();
function setupModeButtons() {
// Guardian mode button
var guardianBtnBg = LK.getAsset('moveButton', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
guardianBtn.addChild(guardianBtnBg);
var guardianBtnTxt = new Text2('Move', {
size: 30,
fill: 0xFFFFFF
});
guardianBtnTxt.anchor.set(0.5, 0.5);
guardianBtnTxt.y = 50;
guardianBtn.addChild(guardianBtnTxt);
// Building mode button
var buildBtnBg = LK.getAsset('buildButton', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
buildBtn.addChild(buildBtnBg);
var buildBtnTxt = new Text2('Build', {
size: 30,
fill: 0xFFFFFF
});
buildBtnTxt.anchor.set(0.5, 0.5);
buildBtnTxt.y = 50;
buildBtn.addChild(buildBtnTxt);
// Delete button
var deleteBtn = new Container();
var deleteBtnBg = LK.getAsset('deleteButton', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
deleteBtn.addChild(deleteBtnBg);
var deleteBtnTxt = new Text2('Delete', {
size: 30,
fill: 0xFFFFFF
});
deleteBtnTxt.anchor.set(0.5, 0.5);
deleteBtnTxt.y = 50;
deleteBtn.addChild(deleteBtnTxt);
// Add to GUI
LK.gui.bottomLeft.addChild(guardianBtn);
LK.gui.bottomLeft.addChild(buildBtn);
LK.gui.bottomLeft.addChild(deleteBtn);
// Position buttons
guardianBtn.x = 100;
guardianBtn.y = -100;
buildBtn.x = 220;
buildBtn.y = -100;
deleteBtn.x = 340;
deleteBtn.y = -100;
// Add event listeners
guardianBtn.interactive = true;
guardianBtn.down = function () {
setGameMode('guardian');
};
buildBtn.interactive = true;
buildBtn.down = function () {
setGameMode('building');
};
deleteBtn.interactive = true;
deleteBtn.down = function () {
setGameMode('delete');
};
// Highlight current mode
updateModeButtons();
}
// Create tower selection buttons
var thornBtn = new Container();
var shroomBtn = new Container();
var flameBtn = new Container();
var healingBtn = new Container();
function setupTowerButtons() {
// Thorn tower button
var thornBtnBg = LK.getAsset('thornTower', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
thornBtn.addChild(thornBtnBg);
var thornBtnTxt = new Text2('50', {
size: 30,
fill: 0xFFFFFF
});
thornBtnTxt.anchor.set(0.5, 0.5);
thornBtnTxt.y = 50;
thornBtn.addChild(thornBtnTxt);
// Shroom tower button
var shroomBtnBg = LK.getAsset('shroomTower', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
shroomBtn.addChild(shroomBtnBg);
var shroomBtnTxt = new Text2('100', {
size: 30,
fill: 0xFFFFFF
});
shroomBtnTxt.anchor.set(0.5, 0.5);
shroomBtnTxt.y = 50;
shroomBtn.addChild(shroomBtnTxt);
// Flame tower button
var flameBtnBg = LK.getAsset('flameTower', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80,
tint: 0xFF3300
});
flameBtn.addChild(flameBtnBg);
var flameBtnTxt = new Text2('150', {
size: 30,
fill: 0xFFFFFF
});
flameBtnTxt.anchor.set(0.5, 0.5);
flameBtnTxt.y = 50;
flameBtn.addChild(flameBtnTxt);
// Farm tower button
var healingBtnBg = LK.getAsset('farmTower', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
healingBtn.addChild(healingBtnBg);
var healingBtnTxt = new Text2('200', {
size: 30,
fill: 0xFFFFFF
});
healingBtnTxt.anchor.set(0.5, 0.5);
healingBtnTxt.y = 50;
healingBtn.addChild(healingBtnTxt);
// Add to GUI
LK.gui.bottomRight.addChild(thornBtn);
LK.gui.bottomRight.addChild(shroomBtn);
LK.gui.bottomRight.addChild(flameBtn);
LK.gui.bottomRight.addChild(healingBtn);
// Position buttons
thornBtn.x = -100;
thornBtn.y = -100;
shroomBtn.x = -220;
shroomBtn.y = -100;
flameBtn.x = -100;
flameBtn.y = -200;
healingBtn.x = -220;
healingBtn.y = -200;
// Add event listeners
thornBtn.interactive = true;
thornBtn.down = function () {
if (currentMode === 'building') {
selectTower('thorn');
}
};
shroomBtn.interactive = true;
shroomBtn.down = function () {
if (currentMode === 'building' && storage.unlockedTowers.includes('shroom')) {
selectTower('shroom');
}
};
flameBtn.interactive = true;
flameBtn.down = function () {
if (currentMode === 'building' && storage.unlockedTowers.includes('flame')) {
selectTower('flame');
}
};
healingBtn.interactive = true;
healingBtn.down = function () {
if (currentMode === 'building' && storage.unlockedTowers.includes('healing')) {
selectTower('healing');
}
};
// If towers are not unlocked, gray them out
if (!storage.unlockedTowers.includes('shroom')) {
shroomBtn.alpha = 0.5;
}
if (!storage.unlockedTowers.includes('flame')) {
flameBtn.alpha = 0.5;
}
if (!storage.unlockedTowers.includes('healing')) {
healingBtn.alpha = 0.5;
}
// Highlight selected tower
updateTowerButtons();
}
// Start wave button
var startWaveBtn = new Container();
function setupStartWaveButton() {
var startWaveButtonBg = LK.getAsset('waveButton', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 80
});
startWaveBtn.addChild(startWaveButtonBg);
var startWaveBtnTxt = new Text2('Start Wave', {
size: 40,
fill: 0xFFFFFF
});
startWaveBtnTxt.anchor.set(0.5, 0.5);
startWaveBtn.addChild(startWaveBtnTxt);
// Add to GUI
LK.gui.bottom.addChild(startWaveBtn);
// Position button
startWaveBtn.y = -150;
// Pulsing animation for wave button when not in wave
var pulseInterval = LK.setInterval(function () {
if (!waveInProgress) {
var pulseFactor = 1 + Math.sin(LK.ticks / 20) * 0.1;
startWaveBtn.scale.set(pulseFactor, pulseFactor);
} else {
startWaveBtn.scale.set(1, 1);
}
}, 100);
// Add event listener
startWaveBtn.interactive = true;
startWaveBtn.down = function () {
if (!waveInProgress) {
startWave();
}
};
}
function updateModeButtons() {
guardianBtn.alpha = currentMode === 'guardian' ? 1.0 : 0.6;
buildBtn.alpha = currentMode === 'building' ? 1.0 : 0.6;
deleteBtn.alpha = currentMode === 'delete' ? 1.0 : 0.6;
}
function updateTowerButtons() {
thornBtn.alpha = currentMode === 'building' && selectedTower === 'thorn' ? 1.0 : 0.6;
shroomBtn.alpha = currentMode === 'building' && selectedTower === 'shroom' && storage.unlockedTowers.includes('shroom') ? 1.0 : 0.3;
flameBtn.alpha = currentMode === 'building' && selectedTower === 'flame' && storage.unlockedTowers.includes('flame') ? 1.0 : 0.3;
healingBtn.alpha = currentMode === 'building' && selectedTower === 'healing' && storage.unlockedTowers.includes('healing') ? 1.0 : 0.3;
}
function setGameMode(mode) {
currentMode = mode;
updateModeButtons();
updateTowerButtons();
}
function selectTower(tower) {
selectedTower = tower;
updateTowerButtons();
}
function updateEssenceText() {
essenceTxt.setText('💎 ' + essence);
// Flash effect when essence is gained/lost
LK.effects.flashObject(essenceTxt, 0x00FFFF, 300);
}
function createLevel() {
// Clear any existing level elements
for (var i = 0; i < buildingSpots.length; i++) {
buildingSpots[i].destroy();
}
buildingSpots = [];
path = [];
spawnPoints = [];
// Level dimensions (in grid cells)
var levelWidth = 15;
var levelHeight = 19;
// Create a grid for the level
var grid = [];
for (var y = 0; y < levelHeight; y++) {
grid[y] = [];
for (var x = 0; x < levelWidth; x++) {
grid[y][x] = 0; // 0 = empty
}
}
// Define path through the level (1 = path)
var pathCoords = [[0, 9], [1, 9], [2, 9], [3, 9], [4, 9], [5, 9], [6, 9], [6, 8], [6, 7], [6, 6], [6, 5], [7, 5], [8, 5], [9, 5], [9, 6], [9, 7], [9, 8], [9, 9], [9, 10], [9, 11], [8, 11], [7, 11], [6, 11], [5, 11], [4, 11], [4, 12], [4, 13], [4, 14], [4, 15], [5, 15], [6, 15], [7, 15], [8, 15], [9, 15], [10, 15], [11, 15], [11, 14], [11, 13], [11, 12], [11, 11], [11, 10], [11, 9], [11, 8], [11, 7], [12, 7], [13, 7], [14, 7]];
for (var i = 0; i < pathCoords.length; i++) {
var _pathCoords$i = _slicedToArray(pathCoords[i], 2),
x = _pathCoords$i[0],
y = _pathCoords$i[1];
grid[y][x] = 1;
}
// Set spawn point (2 = spawn)
grid[9][0] = 2;
// Set heart tree location (3 = heart tree)
grid[7][14] = 3;
// Create level elements based on grid
for (var y = 0; y < levelHeight; y++) {
for (var x = 0; x < levelWidth; x++) {
var gridValue = grid[y][x];
var worldX = x * gridSize + gridSize / 2 + 174; // Center in grid cell with some offset
var worldY = y * gridSize + gridSize / 2 + 150;
if (gridValue === 1) {
// Path tile
var pathTile = new PathTile();
pathTile.x = worldX;
pathTile.y = worldY;
pathTile.gridX = x;
pathTile.gridY = y;
game.addChild(pathTile);
// Add to path array for enemy movement
path.push(pathTile);
} else if (gridValue === 2) {
// Spawn point
var spawnPoint = new SpawnPoint();
spawnPoint.x = worldX;
spawnPoint.y = worldY;
game.addChild(spawnPoint);
spawnPoints.push(spawnPoint);
// Also add as first path point
path.unshift(spawnPoint);
} else if (gridValue === 3) {
// Heart Tree
heartTree = new HeartTree();
heartTree.x = worldX;
heartTree.y = worldY;
game.addChild(heartTree);
} else if (gridValue === 0) {
// Check if adjacent to path (can build here)
var adjacentToPath = false;
// Check all adjacent cells
var directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];
for (var _i = 0, _directions = directions; _i < _directions.length; _i++) {
var dir = _directions[_i];
var adjX = x + dir[0];
var adjY = y + dir[1];
if (adjX >= 0 && adjX < levelWidth && adjY >= 0 && adjY < levelHeight && grid[adjY][adjX] === 1) {
adjacentToPath = true;
break;
}
}
if (adjacentToPath) {
// Create building spot
var spot = new BuildingSpot();
spot.x = worldX;
spot.y = worldY;
spot.gridX = x;
spot.gridY = y;
game.addChild(spot);
buildingSpots.push(spot);
}
}
}
}
// Sort path from start to end
path.sort(function (a, b) {
return pathCoords.findIndex(function (coords) {
return coords[0] === a.gridX && coords[1] === a.gridY;
}) - pathCoords.findIndex(function (coords) {
return coords[0] === b.gridX && coords[1] === b.gridY;
});
});
// Create guardian
guardian = new Guardian();
guardian.x = path[Math.floor(path.length / 2)].x;
guardian.y = path[Math.floor(path.length / 2)].y;
game.addChild(guardian);
}
function startWave() {
if (waveInProgress) {
return;
}
currentWave++;
waveTxt.setText('Wave: ' + currentWave);
waveInProgress = true;
startWaveBtn.alpha = 0.5;
// Display wave start message
var waveStartTxt = new Text2('Wave ' + currentWave + ' Starting!', {
size: 80,
fill: 0xFF8C00
});
waveStartTxt.anchor.set(0.5, 0.5);
waveStartTxt.x = 2048 / 2;
waveStartTxt.y = 2732 / 2;
game.addChild(waveStartTxt);
// Animate and remove wave start message
tween(waveStartTxt, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1500,
onFinish: function onFinish() {
waveStartTxt.destroy();
}
});
// Calculate enemies for this wave
var numEnemies = 5 + Math.floor(currentWave * 1.5);
var hasBoss = currentWave % 5 === 0;
enemiesRemaining = numEnemies + (hasBoss ? 1 : 0);
LK.getSound('waveStart').play();
// Spawn enemies periodically
var enemiesSpawned = 0;
waveTimer = LK.setInterval(function () {
if (enemiesSpawned < numEnemies) {
spawnEnemy(false);
enemiesSpawned++;
} else if (hasBoss && enemiesSpawned === numEnemies) {
spawnEnemy(true); // Spawn boss
enemiesSpawned++;
} else {
LK.clearInterval(waveTimer);
}
}, 1500);
}
function spawnEnemy(isBoss) {
var enemy;
// Apply difficulty multipliers
var healthMultiplier = 1.0;
var speedMultiplier = 1.0;
var valueMultiplier = 1.0;
// Handle race mode separately
if (gameDifficulty === 'race') {
if (isBoss) {
enemy = new BossEnemy();
// Make boss race-themed
enemy.speed *= 1.5;
enemy.maxHealth *= 0.8;
enemy.health = enemy.maxHealth;
} else {
// In race mode, spawn more varied race enemies
var raceRandom = Math.random();
if (raceRandom < 0.4 || currentWave < 2) {
// 40% chance of spawning race enemy (down from 70%)
enemy = new RaceEnemy();
// Scale difficulty with wave number
enemy.maxHealth = 25 + currentWave * 3;
enemy.health = enemy.maxHealth;
enemy.speed = 6 + currentWave * 0.25; // Even faster with each wave
enemy.value = 20 + currentWave * 2;
} else if (raceRandom < 0.6) {
// 20% chance of spawning speedy enemy in race mode
enemy = new SpeedyEnemy();
enemy.maxHealth = 30 + currentWave * 4;
enemy.health = enemy.maxHealth;
enemy.speed = 4.5 + currentWave * 0.2; // Faster than normal mode
enemy.value = 15 + currentWave;
// Special visual effects for race mode speedies
enemy.getChildAt(0).tint = 0xFFAA00; // More orange than yellow
} else if (raceRandom < 0.75 && currentWave >= 2) {
// 15% chance of speed demon enemy after wave 2
enemy = new SpeedDemonEnemy();
enemy.maxHealth = 15 + currentWave * 2;
enemy.health = enemy.maxHealth;
enemy.speed = 8 + currentWave * 0.3; // Extremely fast
enemy.value = 30 + currentWave * 2;
} else if (raceRandom < 0.9 && currentWave >= 3) {
// 15% chance of motorcycle enemy after wave 3
enemy = new MotorcycleEnemy();
enemy.maxHealth = 20 + currentWave * 3;
enemy.health = enemy.maxHealth;
enemy.speed = 7 + currentWave * 0.2;
enemy.value = 25 + currentWave * 1.5;
} else {
// 10% chance of flying enemy after wave 3
if (currentWave >= 3) {
enemy = new FlyingEnemy();
enemy.maxHealth = 40 + currentWave * 4;
enemy.health = enemy.maxHealth;
enemy.speed = 4 + currentWave * 0.15; // Faster than normal mode
enemy.value = 25 + currentWave;
// Change color for race mode
enemy.getChildAt(0).tint = 0xFF3300; // More reddish
} else {
// Fallback to race enemy if flying is not available yet
enemy = new RaceEnemy();
enemy.maxHealth = 25 + currentWave * 3;
enemy.health = enemy.maxHealth;
enemy.speed = 6 + currentWave * 0.25;
enemy.value = 20 + currentWave * 2;
}
}
}
} else if (gameDifficulty === 'infection') {
if (isBoss) {
enemy = new InfectionBossEnemy();
// Adjust boss for infection theme
enemy.maxHealth = 350 + currentWave * 30;
enemy.health = enemy.maxHealth;
} else {
// In infection mode, spawn more varied poison-based enemies
var infectionRandom = Math.random();
if (infectionRandom < 0.2 || currentWave < 2) {
// Basic poison enemy (reduced chance from 0.3)
enemy = new PoisonEnemy();
// Scale with wave
enemy.maxHealth = 80 + currentWave * 7;
enemy.health = enemy.maxHealth;
enemy.speed = 1.8 + currentWave * 0.1;
enemy.value = 20 + currentWave;
// Faster shot interval
enemy.shotInterval = Math.max(1.5, 3 - currentWave * 0.25);
} else if (infectionRandom < 0.4) {
// Acid enemy (reduced chance from 0.6)
enemy = new AcidEnemy();
// Scale with wave
enemy.maxHealth = 90 + currentWave * 8;
enemy.health = enemy.maxHealth;
enemy.speed = 1.6 + currentWave * 0.08;
enemy.value = 22 + currentWave;
// More frequent acid drops at higher waves
enemy.acidInterval = Math.max(1.0, 2 - currentWave * 0.15);
} else if (infectionRandom < 0.55 && currentWave >= 3) {
// Toxic blob enemy spawns after wave 3 (reduced chance from 0.8)
enemy = new ToxicBlobEnemy();
// Scale with wave
enemy.maxHealth = 140 + currentWave * 10;
enemy.health = enemy.maxHealth;
enemy.speed = 1.3 + currentWave * 0.06;
enemy.value = 30 + currentWave * 1.5;
} else if (infectionRandom < 0.7 && currentWave >= 2) {
// New: Toxic cloud enemy after wave 2
enemy = new ToxicCloudEnemy();
enemy.maxHealth = 95 + currentWave * 8;
enemy.health = enemy.maxHealth;
enemy.speed = 1.5 + currentWave * 0.07;
enemy.value = 25 + currentWave;
// More frequent cloud releases at higher waves
enemy.cloudInterval = Math.max(1.2, 2 - currentWave * 0.1);
} else if (infectionRandom < 0.85 && currentWave >= 4) {
// New: Parasite enemy after wave 4
enemy = new ParasiteEnemy();
enemy.maxHealth = 45 + currentWave * 5;
enemy.health = enemy.maxHealth;
enemy.speed = 3.5 + currentWave * 0.12;
enemy.value = 35 + currentWave * 1.5;
} else if (infectionRandom < 0.95 && currentWave >= 4) {
// Flying enemy with poison theme
enemy = new FlyingEnemy();
enemy.maxHealth = 40 + currentWave * 5;
enemy.health = enemy.maxHealth;
enemy.speed = 3 + currentWave * 0.12;
enemy.value = 18 + currentWave;
// Change color for infection theme
enemy.getChildAt(0).tint = 0x00FF00; // Green tint
} else {
// Default to normal enemy with poison tint as fallback
enemy = new Enemy();
enemy.maxHealth = 50 + currentWave * 6;
enemy.health = enemy.maxHealth;
enemy.speed = 2 + currentWave * 0.1;
enemy.value = 10 + currentWave;
// Add green tint
enemy.getChildAt(0).tint = 0x88FF88;
}
}
} else {
// Normal, Easy, Hard difficulties
if (gameDifficulty === 'easy') {
healthMultiplier = 0.8; // Enemies have less health
speedMultiplier = 0.9; // Enemies move slower
valueMultiplier = 1.2; // Enemies give more essence
} else if (gameDifficulty === 'hard') {
healthMultiplier = 1.3; // Enemies have more health
speedMultiplier = 1.2; // Enemies move faster
valueMultiplier = 0.8; // Enemies give less essence
}
if (isBoss) {
enemy = new BossEnemy();
} else {
// Randomly choose enemy type based on wave progression
var randomType = Math.random();
if (currentWave >= 8 && randomType < 0.1) {
// 10% chance of armored enemy after wave 8
enemy = new ArmoredEnemy();
enemy.maxHealth = Math.floor((100 + currentWave * 12) * healthMultiplier);
enemy.health = enemy.maxHealth;
enemy.speed = (1.8 + currentWave * 0.05) * speedMultiplier;
enemy.value = Math.floor((40 + currentWave * 2) * valueMultiplier);
enemy.shieldHealth = Math.floor((50 + currentWave * 5) * healthMultiplier);
} else if (currentWave >= 7 && randomType < 0.2) {
// 10% chance of mine layer enemy after wave 7
enemy = new MineLayerEnemy();
enemy.maxHealth = Math.floor((70 + currentWave * 8) * healthMultiplier);
enemy.health = enemy.maxHealth;
enemy.speed = (2.2 + currentWave * 0.07) * speedMultiplier;
enemy.value = Math.floor((30 + currentWave * 1.5) * valueMultiplier);
// Less frequent mines at lower difficulties
enemy.mineInterval = Math.max(2, 3 - currentWave * 0.1) / speedMultiplier;
} else if (currentWave >= 6 && randomType < 0.3) {
// 10% chance of spawning flying enemy after wave 6
enemy = new FlyingEnemy();
// Scale difficulty with wave number
enemy.maxHealth = Math.floor((40 + currentWave * 5) * healthMultiplier);
enemy.health = enemy.maxHealth;
enemy.speed = (3 + currentWave * 0.12) * speedMultiplier;
enemy.value = Math.floor((18 + currentWave) * valueMultiplier);
} else if (currentWave >= 7 && randomType < 0.4) {
// 10% chance of spawning poison enemy after wave 7
enemy = new PoisonEnemy();
// Scale difficulty with wave number
enemy.maxHealth = Math.floor((80 + currentWave * 8) * healthMultiplier);
enemy.health = enemy.maxHealth;
enemy.speed = (1.8 + currentWave * 0.08) * speedMultiplier;
enemy.value = Math.floor((20 + currentWave) * valueMultiplier);
enemy.shotInterval = Math.max(1.5, 3 - currentWave * 0.2) / speedMultiplier; // Shoot faster at higher waves
} else if (currentWave >= 5 && randomType < 0.55) {
// 15% chance of spawning split enemy after wave 5
enemy = new SplitEnemy();
// Scale difficulty with wave number
enemy.maxHealth = Math.floor((70 + currentWave * 12) * healthMultiplier);
enemy.health = enemy.maxHealth;
enemy.speed = (1.5 + currentWave * 0.06) * speedMultiplier;
enemy.value = Math.floor((12 + currentWave) * valueMultiplier);
enemy.splitCount = 2 + Math.floor(currentWave / 10); // More splits at higher waves, max 4
} else if (currentWave >= 2 && randomType < 0.75) {
// 20% chance of spawning speedy enemy after wave 2
enemy = new SpeedyEnemy();
// Scale difficulty with wave number
enemy.maxHealth = Math.floor((30 + currentWave * 6) * healthMultiplier);
enemy.health = enemy.maxHealth;
enemy.speed = (4 + currentWave * 0.15) * speedMultiplier;
enemy.value = Math.floor((15 + currentWave) * valueMultiplier);
} else if (currentWave >= 4 && randomType < 0.9) {
// 15% chance of spawning tank enemy after wave 4
enemy = new TankEnemy();
// Scale difficulty with wave number
enemy.maxHealth = Math.floor((120 + currentWave * 15) * healthMultiplier);
enemy.health = enemy.maxHealth;
enemy.speed = (1 + currentWave * 0.05) * speedMultiplier;
enemy.value = Math.floor((25 + currentWave) * valueMultiplier);
} else {
// Regular enemy
enemy = new Enemy();
// Scale difficulty with wave number
enemy.maxHealth = Math.floor((50 + currentWave * 10) * healthMultiplier);
enemy.health = enemy.maxHealth;
enemy.speed = (2 + currentWave * 0.1) * speedMultiplier;
enemy.value = Math.floor((10 + currentWave) * valueMultiplier);
}
}
}
// Set initial position at spawn point
var spawnPoint = spawnPoints[0];
enemy.x = spawnPoint.x;
enemy.y = spawnPoint.y;
game.addChild(enemy);
enemies.push(enemy);
// Apply animation for spawning
enemy.alpha = 0;
enemy.scale.set(0.5, 0.5);
tween(enemy, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500
});
}
function checkWaveComplete() {
if (waveInProgress && enemies.length === 0 && enemiesRemaining === 0) {
waveInProgress = false;
startWaveBtn.alpha = 1.0;
// Reward between waves
var waveBonus = 50 + currentWave * 10;
// Add farm tower income
var farmIncome = 0;
for (var i = 0; i < towers.length; i++) {
if (towers[i] instanceof FarmTower) {
var income = towers[i].getIncomePerWave();
farmIncome += income;
// Show visual effect for farm income
var incomeTxt = new Text2("+" + income, {
size: 40,
fill: 0xFFD700
});
incomeTxt.anchor.set(0.5, 0.5);
incomeTxt.x = towers[i].x;
incomeTxt.y = towers[i].y;
game.addChild(incomeTxt);
// Animate text
tween(incomeTxt, {
alpha: 0,
y: incomeTxt.y - 50
}, {
duration: 1000,
onFinish: function onFinish() {
incomeTxt.destroy();
}
});
}
}
essence += waveBonus + farmIncome;
updateEssenceText();
// Show wave complete message
var waveBonusTxt = new Text2('Wave Complete! +' + waveBonus + ' Essence', {
size: 80,
fill: 0xFFFFFF
});
waveBonusTxt.anchor.set(0.5, 0.5);
waveBonusTxt.x = 2048 / 2;
waveBonusTxt.y = 2732 / 2;
game.addChild(waveBonusTxt);
// Animate and remove message
tween(waveBonusTxt, {
alpha: 0,
y: waveBonusTxt.y - 100
}, {
duration: 2000,
onFinish: function onFinish() {
waveBonusTxt.destroy();
}
});
// Unlock new tower types based on wave progress
if (currentWave === 3 && !storage.unlockedTowers.includes('shroom')) {
storage.unlockedTowers.push('shroom');
shroomBtn.alpha = 0.6;
// Show unlock message
var unlockTxt = new Text2('New Tower Unlocked: Shroom Puff!', {
size: 60,
fill: 0xFFFF00
});
unlockTxt.anchor.set(0.5, 0.5);
unlockTxt.x = 2048 / 2;
unlockTxt.y = 2732 / 2 + 100;
game.addChild(unlockTxt);
// Animate and remove message
tween(unlockTxt, {
alpha: 0,
y: unlockTxt.y - 100
}, {
duration: 3000,
onFinish: function onFinish() {
unlockTxt.destroy();
}
});
} else if (currentWave === 5 && !storage.unlockedTowers.includes('flame')) {
storage.unlockedTowers.push('flame');
flameBtn.alpha = 0.6;
// Show unlock message
var unlockTxt = new Text2('New Tower Unlocked: Flame Fern!', {
size: 60,
fill: 0xFF5500
});
unlockTxt.anchor.set(0.5, 0.5);
unlockTxt.x = 2048 / 2;
unlockTxt.y = 2732 / 2 + 100;
game.addChild(unlockTxt);
// Animate and remove message
tween(unlockTxt, {
alpha: 0,
y: unlockTxt.y - 100
}, {
duration: 3000,
onFinish: function onFinish() {
unlockTxt.destroy();
}
});
} else if (currentWave === 8 && !storage.unlockedTowers.includes('healing')) {
storage.unlockedTowers.push('healing');
healingBtn.alpha = 0.6;
// Show unlock message
var unlockTxt = new Text2('New Tower Unlocked: Farm Shroom!', {
size: 60,
fill: 0xFFD700
});
unlockTxt.anchor.set(0.5, 0.5);
unlockTxt.x = 2048 / 2;
unlockTxt.y = 2732 / 2 + 100;
game.addChild(unlockTxt);
// Animate and remove message
tween(unlockTxt, {
alpha: 0,
y: unlockTxt.y - 100
}, {
duration: 3000,
onFinish: function onFinish() {
unlockTxt.destroy();
}
});
}
}
}
function isPositionValid(x, y) {
// Check if position is within game bounds
if (x < 0 || x > 2048 || y < 0 || y > 2732) {
return false;
}
return true;
}
// Initialize game
function initGame() {
// Setup UI elements
setupHealthBar();
setupModeButtons();
setupTowerButtons();
setupStartWaveButton();
// Create game level
createLevel();
// Set initial game state
LK.setScore(0);
scoreTxt.setText('0');
// Apply difficulty settings
if (gameDifficulty === 'easy') {
essence = 200; // More starting essence
heartTree.maxHealth = 150; // More health
heartTree.health = heartTree.maxHealth;
} else if (gameDifficulty === 'normal') {
essence = 100; // Default starting essence
heartTree.maxHealth = 100; // Default health
heartTree.health = heartTree.maxHealth;
} else if (gameDifficulty === 'hard') {
essence = 50; // Less starting essence
heartTree.maxHealth = 75; // Less health
heartTree.health = heartTree.maxHealth;
} else if (gameDifficulty === 'race') {
essence = 150; // Moderate starting essence
heartTree.maxHealth = 100; // Default health
heartTree.health = heartTree.maxHealth;
// Set game background to a gray color for race mode
game.setBackgroundColor(0x808080); // Gray color
// Show race mode announcement
var raceModeTxt = new Text2('RACE MODE ACTIVATED!', {
size: 80,
fill: 0xFF5500
});
raceModeTxt.anchor.set(0.5, 0.5);
raceModeTxt.x = 2048 / 2;
raceModeTxt.y = 2732 / 2;
game.addChild(raceModeTxt);
// Animate and remove announcement
tween(raceModeTxt, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 2000,
onFinish: function onFinish() {
raceModeTxt.destroy();
}
});
} else if (gameDifficulty === 'infection') {
essence = 120; // Moderate starting essence
heartTree.maxHealth = 90; // Lower health due to infection damage
heartTree.health = heartTree.maxHealth;
// Set game background to a purple color for infection mode
game.setBackgroundColor(0x800080); // Purple color
// Show infection mode announcement
var infectionModeTxt = new Text2('INFECTION MODE ACTIVATED!', {
size: 80,
fill: 0x00FF00
});
infectionModeTxt.anchor.set(0.5, 0.5);
infectionModeTxt.x = 2048 / 2;
infectionModeTxt.y = 2732 / 2;
game.addChild(infectionModeTxt);
// Animate and remove announcement
tween(infectionModeTxt, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 2000,
onFinish: function onFinish() {
infectionModeTxt.destroy();
}
});
}
updateEssenceText();
updateHealthBar();
// Start background music
LK.playMusic('forestAmbience', {
loop: true
});
}
// Game event handlers
game.down = function (x, y, obj) {
if (currentMode === 'guardian' && guardian) {
guardian.moveTo(x, y);
}
};
// Game update loop
game.update = function () {
// Update all game objects
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update enemies (backwards to safely remove)
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].update();
if (!enemies[i].alive) {
enemies[i].destroy();
enemies.splice(i, 1);
enemiesRemaining--;
}
}
// Update essence items (backwards to safely remove)
for (var i = essenceItems.length - 1; i >= 0; i--) {
essenceItems[i].update();
if (essenceItems[i].collected) {
essenceItems[i].destroy();
essenceItems.splice(i, 1);
}
}
// Update guardian if it exists
if (guardian) {
guardian.update();
}
// Check if wave is complete
checkWaveComplete();
};
// Display title screen instead of directly initializing game
var titleScreen = new TitleScreen();
titleScreen.x = 2048 / 2;
titleScreen.y = 2732 / 2;
game.addChild(titleScreen);
// Play background music on title screen
LK.playMusic('forestAmbience', {
loop: true
});
top-down angle of a tree. In-Game asset. 2d. High contrast. No shadows
thorn flower. In-Game asset. 2d. High contrast. No shadows
purple mushroom. In-Game asset. 2d. High contrast. No shadows
guardian of the forest. In-Game asset. 2d. High contrast. No shadows
mini cave invader that is covered in crystals and is made of stone. In-Game asset. 2d. High contrast. No shadows
GIANT cave boss invader that is covered in crystal diamonds and is made of stone.. In-Game asset. 2d. High contrast. No shadows
mini farm tile. In-Game asset. 2d. High contrast. No shadows
flaming flower. In-Game asset. 2d. High contrast. No shadows
cave enterance. In-Game asset. 2d. High contrast. No shadows
car parked at an angle. In-Game asset. 2d. High contrast. No shadows
top-down angle of a tire. In-Game asset. 2d. High contrast. No shadows
finish line. In-Game asset. 2d. High contrast. No shadows
spilt acid barrel fallen over. In-Game asset. 2d. High contrast. No shadows
mutant tree. In-Game asset. 2d. High contrast. No shadows
soul. In-Game asset. 2d. High contrast. No shadows
planty spike ball with no stem. In-Game asset. 2d. High contrast. No shadows
Abandoned building that’s boarded up. In-Game asset. 2d. High contrast. No shadows
Rectangle button. In-Game asset. 2d. High contrast. No shadows
https://api.upit.com/img/IwopnnYt58_k_iLtUBxBq3JSiO4=/fit-in/256x256/filters:format(png):quality(85)/https://cdn.frvr.ai/680fe4cf833660e79135a83b.png%3F3 But empty. In-Game asset. 2d. High contrast. No shadows
Empty healthbar with no heart. In-Game asset. 2d. High contrast. No shadows
Hammer icon. In-Game asset. 2d. High contrast. No shadows
Garbage can icon. In-Game asset. 2d. High contrast. No shadows
Running feet icon. In-Game asset. 2d. High contrast. No shadows