User prompt
make the text under the difficulties bigger and more readable
User prompt
add descriptions of the gamemodes under the gaamemode button ex: Easy Made for begginers
User prompt
make more fast varients for race mode, more poison varients for infection mode, and more enemy varients in general
User prompt
make tower bullets move faster
User prompt
Make the background color change to gray in race mode and purple in infection mode
User prompt
Make the move icon its own asset
User prompt
Make the build and delete buttons their own assets
User prompt
make infection difficulty replace the hearttree asset with an old building asset (create a old building asset in asset section)
User prompt
make infection difficulty replace the buildingspot asset with a mutant tree asset (create mutant tree asset in asset section)
User prompt
make infection difficulty replace the spawn area asset with an acid barrel asset (create an acid barrel asset in asset section) (dont add acid related effects to speedy enemies, just replace the asset)
User prompt
add an infection difficulty where the dirt track becomes a acid spill asset and more poison varients spawn (make more poison varients for infection mode)
User prompt
add a race difficulty where the dirt track becomes a acid spill asset and more poison varients spawn (make more poison varients for infection mode)
User prompt
make race difficulty replace the hearttree asset with a finishline asset (create a finishline asset in asset section)
User prompt
make race difficulty replace the buildingspot asset with a tire asset (create a tire asset in asset section)
User prompt
make race difficulty replace the spawn area asset with a car asset (create a car asset in asset section) (dont add car related effects to speedy enemies, just replace the asset)
User prompt
add a delete button right next to the move and build button that if you click on a tower when it's activated it will delete the tower and give you your money back
User prompt
remove the upgrade and sell button and the tower menu
User prompt
when i click the sell button, nothing happens
User prompt
the sell button isn't working
User prompt
add a race difficulty where the dirt track becomes a racetrack asset and more fast varients spawn *make more fast variemts for fast mode) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Hurry up
User prompt
make difficulty buttons (easy, normal, hard) instead of one play button
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'moveTo')' in or related to this line: 'guardian.moveTo(x, y);' Line Number: 2095
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'update')' in or related to this line: 'guardian.update();' Line Number: 2122
User prompt
Please fix the bug: 'TypeError: null is not an object (evaluating 'guardian.update')' in or related to this line: 'guardian.update();' Line Number: 2122
/**** * 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 = 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(); // Hide tower menu if it's visible if (towerMenu && towerMenu.visible) { towerMenu.hide(); } } }; 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 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 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 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 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 = 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 = 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 = 12; 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 = 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); // Play button self.playBtn = new Container(); var playBtnBg = LK.getAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 400, height: 120, tint: 0x83de44 }); self.playBtn.addChild(playBtnBg); var playBtnText = new Text2('START GAME', { size: 70, fill: 0xFFFFFF }); playBtnText.anchor.set(0.5, 0.5); self.playBtn.addChild(playBtnText); self.playBtn.y = 200; self.addChild(self.playBtn); // 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 = 350; self.addChild(self.highScoreText); // Add pulsing animation to play button self.update = function () { var pulseFactor = 1 + Math.sin(LK.ticks / 20) * 0.05; self.playBtn.scale.set(pulseFactor, pulseFactor); }; // Play button event handler self.playBtn.down = function () { // 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) { // Show tower menu when tower is tapped if (towerMenu && currentMode === 'building') { towerMenu.showForTower(self); } }; 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 TowerMenu = Container.expand(function () { var self = Container.call(this); self.tower = null; // Background for menu var menuBg = self.attachAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 600, height: 450, tint: 0x333333 }); // Title text self.titleText = new Text2('Tower Menu', { size: 70, fill: 0xFFFFFF }); self.titleText.anchor.set(0.5, 0); self.titleText.y = -180; self.addChild(self.titleText); // Level text self.levelText = new Text2('Level: 1', { size: 60, fill: 0xFFFFFF }); self.levelText.anchor.set(0.5, 0); self.levelText.y = -80; self.addChild(self.levelText); // Stats text self.statsText = new Text2('Damage: 10\nRange: 300', { size: 55, fill: 0xFFFFFF }); self.statsText.anchor.set(0.5, 0); self.statsText.y = 20; self.addChild(self.statsText); // Upgrade button self.upgradeBtn = new Container(); var upgradeBtnBg = LK.getAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 100, tint: 0x00AA00 }); self.upgradeBtn.addChild(upgradeBtnBg); self.upgradeCostText = new Text2('Upgrade\n50', { size: 40, fill: 0xFFFFFF }); self.upgradeCostText.anchor.set(0.5, 0.5); self.upgradeBtn.addChild(self.upgradeCostText); self.upgradeBtn.x = -140; self.upgradeBtn.y = 170; self.addChild(self.upgradeBtn); // Sell button self.sellBtn = new Container(); var sellBtnBg = LK.getAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 100, tint: 0xAA0000 }); self.sellBtn.addChild(sellBtnBg); self.sellValueText = new Text2('Sell\n25', { size: 40, fill: 0xFFFFFF }); self.sellValueText.anchor.set(0.5, 0.5); self.sellBtn.addChild(self.sellValueText); self.sellBtn.x = 140; self.sellBtn.y = 170; self.addChild(self.sellBtn); // Close button self.closeBtn = new Container(); var closeBtnBg = LK.getAsset('waveButton', { anchorX: 0.5, anchorY: 0.5, width: 50, height: 50, tint: 0x666666 }); self.closeBtn.addChild(closeBtnBg); var closeBtnText = new Text2('X', { size: 35, fill: 0xFFFFFF }); closeBtnText.anchor.set(0.5, 0.5); self.closeBtn.addChild(closeBtnText); self.closeBtn.x = 270; self.closeBtn.y = -200; self.addChild(self.closeBtn); // Initialize event handlers self.upgradeBtn.down = function () { if (self.tower && essence >= self.getUpgradeCost()) { self.upgradeTower(); } }; self.sellBtn.down = function () { if (self.tower) { self.sellTower(); } }; self.closeBtn.down = function () { self.hide(); }; // Upgrade tower self.upgradeTower = function () { if (self.tower && essence >= self.getUpgradeCost()) { var upgradeCost = self.getUpgradeCost(); essence -= upgradeCost; self.tower.level++; // Different upgrade effects based on tower type if (self.tower instanceof FarmTower) { self.tower.incomeAmount += self.tower.level * 10; self.tower.range += self.tower.level * 15; } else { self.tower.damage += self.tower.level * 5; self.tower.range += self.tower.level * 15; self.tower.attackSpeed += self.tower.level * 0.1; } updateEssenceText(); self.updateDisplay(); // Flash effect LK.effects.flashObject(self.tower, 0x00FF00, 500); } }; // Sell tower self.sellTower = function () { if (self.tower) { var sellValue = self.getSellValue(); essence += sellValue; updateEssenceText(); // Show visual effect for essence gain var sellTxt = new Text2("+" + sellValue, { size: 40, fill: 0x00FFFF }); sellTxt.anchor.set(0.5, 0.5); sellTxt.x = self.tower.x; sellTxt.y = self.tower.y; game.addChild(sellTxt); // Animate text tween(sellTxt, { alpha: 0, y: sellTxt.y - 50 }, { duration: 1000, onFinish: function onFinish() { sellTxt.destroy(); } }); // Find building spot and free it for (var i = 0; i < buildingSpots.length; i++) { if (buildingSpots[i].gridX === self.tower.gridX && buildingSpots[i].gridY === self.tower.gridY) { buildingSpots[i].occupied = false; buildingSpots[i].tower = null; break; } } // Find and remove tower from array for (var i = 0; i < towers.length; i++) { if (towers[i] === self.tower) { towers.splice(i, 1); break; } } // Remove tower with fade effect tween(self.tower, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 300, onFinish: function onFinish() { self.tower.destroy(); } }); self.hide(); } }; // Calculate upgrade cost self.getUpgradeCost = function () { if (!self.tower) { return 0; } var baseCost = 0; if (self.tower instanceof ThornTower) { baseCost = 50; } else if (self.tower instanceof ShroomTower) { baseCost = 100; } else if (self.tower instanceof FlameTower) { baseCost = 150; } else if (self.tower instanceof FarmTower) { baseCost = 200; } return Math.floor(baseCost * (1 + 0.5 * self.tower.level)); }; // Calculate sell value self.getSellValue = function () { if (!self.tower) { return 0; } var towerType = ''; if (self.tower instanceof ThornTower) { towerType = 'thorn'; } else if (self.tower instanceof ShroomTower) { towerType = 'shroom'; } else if (self.tower instanceof FlameTower) { towerType = 'flame'; } else if (self.tower instanceof FarmTower) { towerType = 'healing'; } // Calculate sell value based on tower cost var baseCost = towerCosts[towerType]; // Tower sells for 70% of initial cost plus 50% of upgrade costs var initialValue = Math.floor(baseCost * 0.7); var upgradeValue = Math.floor((self.tower.level - 1) * baseCost * 0.5); return initialValue + upgradeValue; }; // Show menu for a specific tower self.showForTower = function (tower) { self.tower = tower; self.updateDisplay(); // Position menu near tower but ensure it's on screen self.x = Math.min(Math.max(tower.x, 300), 2048 - 300); self.y = Math.min(Math.max(tower.y, 300), 2732 - 300); self.visible = true; // Bring to front by removing and re-adding to parent if (self.parent) { var parent = self.parent; parent.removeChild(self); parent.addChild(self); } // Highlight selected tower tower.alpha = 0.7; }; // Update display with current tower info self.updateDisplay = function () { if (!self.tower) { return; } var towerType = ''; if (self.tower instanceof ThornTower) { towerType = 'Thorn Tower'; } else if (self.tower instanceof ShroomTower) { towerType = 'Shroom Tower'; } else if (self.tower instanceof FlameTower) { towerType = 'Flame Tower'; } else if (self.tower instanceof FarmTower) { towerType = 'Farm Tower'; } self.titleText.setText(towerType); self.levelText.setText('Level: ' + self.tower.level); if (self.tower instanceof FarmTower) { self.statsText.setText('Income: ' + self.tower.getIncomePerWave() + '\nRange: ' + self.tower.range + '\nPer Wave'); } else { self.statsText.setText('Damage: ' + self.tower.damage + '\nRange: ' + self.tower.range + '\nSpeed: ' + self.tower.attackSpeed.toFixed(1)); } var upgradeCost = self.getUpgradeCost(); self.upgradeCostText.setText('Upgrade\n' + upgradeCost); if (essence >= upgradeCost) { self.upgradeBtn.alpha = 1.0; } else { self.upgradeBtn.alpha = 0.5; } var sellValue = self.getSellValue(); self.sellValueText.setText('Sell\n' + sellValue); // Make the sell button more/less prominent based on value var sellBtn = self.sellBtn.getChildAt(0); if (sellValue > 100) { sellBtn.tint = 0xFF0000; // Bright red for high value } else { sellBtn.tint = 0xAA0000; // Default red } }; // Hide menu self.hide = function () { self.visible = false; if (self.tower) { self.tower.alpha = 1.0; self.tower = null; } }; self.visible = false; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2E5C2E // Forest green }); /**** * Game Code ****/ // Game variables 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' or 'building' 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 towerMenu = null; // Tower editing menu var guardian = null; // 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('guardian', { 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('towerBase', { 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); // Add to GUI LK.gui.bottomLeft.addChild(guardianBtn); LK.gui.bottomLeft.addChild(buildBtn); // Position buttons guardianBtn.x = 100; guardianBtn.y = -100; buildBtn.x = 220; buildBtn.y = -100; // Add event listeners guardianBtn.interactive = true; guardianBtn.down = function () { setGameMode('guardian'); }; buildBtn.interactive = true; buildBtn.down = function () { setGameMode('building'); }; // 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; } 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; if (isBoss) { enemy = new BossEnemy(); } else { // Randomly choose enemy type based on wave progression var randomType = Math.random(); if (currentWave >= 6 && randomType < 0.15) { // 15% chance of spawning flying enemy after wave 6 enemy = new FlyingEnemy(); // Scale difficulty with wave number enemy.maxHealth = 40 + currentWave * 5; enemy.health = enemy.maxHealth; enemy.speed = 3 + currentWave * 0.12; enemy.value = 18 + currentWave; } else if (currentWave >= 7 && randomType < 0.3) { // 15% chance of spawning poison enemy after wave 7 enemy = new PoisonEnemy(); // Scale difficulty with wave number enemy.maxHealth = 80 + currentWave * 8; enemy.health = enemy.maxHealth; enemy.speed = 1.8 + currentWave * 0.08; enemy.value = 20 + currentWave; enemy.shotInterval = Math.max(1.5, 3 - currentWave * 0.2); // Shoot faster at higher waves } else if (currentWave >= 5 && randomType < 0.45) { // 15% chance of spawning split enemy after wave 5 enemy = new SplitEnemy(); // Scale difficulty with wave number enemy.maxHealth = 70 + currentWave * 12; enemy.health = enemy.maxHealth; enemy.speed = 1.5 + currentWave * 0.06; enemy.value = 12 + currentWave; enemy.splitCount = 2 + Math.floor(currentWave / 10); // More splits at higher waves, max 4 } else if (currentWave >= 2 && randomType < 0.65) { // 20% chance of spawning speedy enemy after wave 2 enemy = new SpeedyEnemy(); // Scale difficulty with wave number enemy.maxHealth = 30 + currentWave * 6; enemy.health = enemy.maxHealth; enemy.speed = 4 + currentWave * 0.15; enemy.value = 15 + currentWave; } else if (currentWave >= 4 && randomType < 0.8) { // 15% chance of spawning tank enemy after wave 4 enemy = new TankEnemy(); // Scale difficulty with wave number enemy.maxHealth = 120 + currentWave * 15; enemy.health = enemy.maxHealth; enemy.speed = 1 + currentWave * 0.05; enemy.value = 25 + currentWave; } else { // Regular enemy enemy = new Enemy(); // Scale difficulty with wave number enemy.maxHealth = 50 + currentWave * 10; enemy.health = enemy.maxHealth; enemy.speed = 2 + currentWave * 0.1; enemy.value = 10 + currentWave; } } // 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'); updateEssenceText(); updateHealthBar(); // Create tower menu towerMenu = new TowerMenu(); game.addChild(towerMenu); // Make tower menu larger and more noticeable towerMenu.scale.set(1.2, 1.2); // Start background music LK.playMusic('forestAmbience', { loop: true }); } // Game event handlers game.down = function (x, y, obj) { // Hide tower menu when clicking on game area if (towerMenu && towerMenu.visible && obj.target === game) { towerMenu.hide(); } if (currentMode === '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 });
===================================================================
--- original.js
+++ change.js
@@ -1266,9 +1266,8 @@
/****
* Game Code
****/
// Game variables
-var guardian; // Declare guardian in global scope
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _nonIterableRest() {
@@ -2090,10 +2089,12 @@
essenceItems[i].destroy();
essenceItems.splice(i, 1);
}
}
- // Update guardian
- guardian.update();
+ // Update guardian if it exists
+ if (guardian) {
+ guardian.update();
+ }
// Check if wave is complete
checkWaveComplete();
};
// Display title screen instead of directly initializing game
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