/**** * 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