User prompt
Ensure that there is always only one **glm** asset in the game at any given time; prevent spawning or existing of two or more simultaneously.
User prompt
Update it as: The axes thrown by the orc travel straight in the throwing direction and disappear after reaching three-quarters (3/4) of the maximum throwing range. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The orc moves forward like other enemies. The axes it throws travel straight in the throwing direction and disappear after reaching one-quarter of the maximum throwing range. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Reduce the orcs' throwing speed to one-third of the original (3x slower). Make them advance toward the main hero to attack. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: self.target.takeDamage is not a function' in or related to this line: 'self.target.takeDamage(self.damage);' Line Number: 1328
User prompt
The orc enemy inherits all attributes and behaviors of the main hero character but uses the axe (balt) asset instead of the heroProjectile for its attacks. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The **orc** asset moves forward until it reaches a position where it can attack either the main hero or the nearest archer tower. It continues advancing if neither is within its throwing range. Once the hero or a tower enters its range, the orc stops and begins throwing axes every 2 seconds. The orc prioritizes the closest target within range. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The **orc** asset throws the **balt** asset toward the nearest archer tower or the main hero. The **balt** deals damage equal to 2% of the target's current HP upon impact. The orc has a throwing range equal to 70% of an archer tower's attack range. If the target is within this range, the orc stops moving and throws one **balt** every 2 seconds. The **balt** has a visible projectile animation and travels toward the target in a straight line. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Starting from wave 7, add the **orc** asset to the pool of attacking enemies. The orc should have the same health, speed, and damage values as the **troll** asset. However, instead of melee attacks, orcs should perform ranged attacks by throwing axes at their targets. The thrown axe should have a distinct projectile animation to visually differentiate it from other enemy attacks.
User prompt
After wave 5, increase the gold dropped by all enemies by 50%. This bonus should be a one-time increase and not cumulative with future waves. Next to the archer button, add two additional upgrade buttons with modern, stylized UI designs suitable for contemporary games: HP Upgrade Button: Costs150 gold. Increases the HP of all existing archer towers by 10%. Range Upgrade Button: Costs 250 gold. Increases the attack range of all existing archer towers by 10%. Each button should feature clean, visually distinct icons and animations, with soft shadows, rounded corners, and consistent color schemes for clear user feedback. The layout should be intuitive and visually appealing on both desktop and mobile platforms. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make all buttons have softened, beveled edges to create a modern, polished appearance. Each button should feature a subtle 3D bevel effect with smooth corner rounding, giving them a tactile, pressable look. The style should match a cohesive fantasy or adventure UI theme, using consistent lighting and shading for depth and clarity.
User prompt
After wave 5, increase the gold dropped by all enemies by 50%. This bonus should be a one-time increase and not cumulative with future waves. Next to the archer button, add two additional upgrade buttons with modern, stylized UI designs suitable for contemporary games: HP Upgrade Button: Costs 200 gold. Increases the HP of all existing archer towers by 10%. Range Upgrade Button: Costs 300 gold. Increases the attack range of all existing archer towers by 10%. Each button should feature clean, visually distinct icons and animations, with soft shadows, rounded corners, and consistent color schemes for clear user feedback. The layout should be intuitive and visually appealing on both desktop and mobile platforms. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
When the hero contacts the glm asset for the first time, award the player 300 gold once. This reward should only be given once per game session upon the initial contact between the hero and glm.
User prompt
Increase the enemies’ HP and speed by 20% (multiply by 1.2) at the start of each new wave. This scaling should apply progressively to make enemies stronger and faster as waves advance.
User prompt
Cancel all existing HP scaling and recalculations. Set the main building’s HP to a fixed value of 500 at the start of the first wave only. The HP should remain constant throughout all subsequent waves without any automatic changes or multiplications.
User prompt
When we have enough gold to buy a new archer tower, the archer button should turn light yellow; when our gold is insufficient, it should be a dark yellow color. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Double the HP of all enemies. Set the main building’s starting HP to 500 and increase it by 50 at the start of each wave.
User prompt
Double the HP of all enemies. Set the main building’s starting HP to 500 and increase it by 50 at the start of each wave.
User prompt
Design a modern UI for the "Game Over" screen. Just below the main "Game Over" message, display two clean and visually distinct info boxes: Enemies Defeated This Game – shows the total number of enemies defeated in the current game. All-Time Record – displays the highest number of enemies defeated in any game. Both counters should use bold, stylized fonts and subtle icons (such as skulls or swords). The layout should be clean and minimalist, featuring soft shadows, rounded corners, and a sleek fantasy-themed aesthetic. The UI should be readable and polished on both desktop and mobile platforms. ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
Design a modern UI "Game Over" screen. Below the main "Game Over" message, display two clean and visually distinct info boxes: Enemies Defeated This Game – shows the total number of enemies defeated in the current run. All-Time Record – displays the highest number of enemies ever defeated in a single game. Both counters should use bold, stylized fonts and subtle icons (e.g., skulls or swords). Layout should be clean and minimalistic, with soft shadows, rounded corners, and a sleek fantasy-themed aesthetic. The interface should feel polished and readable on both desktop and mobile platforms. ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
There should be only one glm asset on the map. It should not attack the player. The glm moves randomly around the map. If it comes into contact with the magicAura asset, it receives a slow effect and moves slower for 3 seconds. The player can attack the glm, which has 500 HP. If its HP drops below 1, the player is rewarded with 300 gold. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
If the glm asset comes into contact with the magicAura asset, it should receive a slow effect and move slower for 3 seconds. The player should also be able to attack the glm asset. It has 500 HP. If its HP drops below 1, the player is rewarded with 300 gold. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Set the gold dropped upon death for each enemy type as follows: Goblin – 10 gold, Skeleton – 5 gold, Troll – 20 gold, Boss – 30 gold. Additionally, implement a dynamic pricing system for archer towers: each newly built archer tower should cost 15% more than the price of the previous one.
User prompt
Set the gold dropped by each enemy type as follows: Goblin – 15 gold, Skeleton – 10 gold, Troll – 30 gold, Boss – 50 gold. Additionally, implement a dynamic pricing system for archer towers: the cost of each new archer tower increases by 15% of the base price (100 gold) for every tower already built. For example, the second archer tower costs 115 gold, the third costs 132.25 gold, and so on. In systems that use decimal numbers (e.g., pricing), always reference the lower whole number by ignoring the decimal part. For example: 132.25 → 132 199.99 → 199 100.0 → 100
User prompt
Make the archer towers use the **heroProjectile** asset for their attack projectiles.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var ArrowTrail = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('heroProjectile', { anchorX: 0.5, anchorY: 0.5 }); graphics.alpha = 0.8; graphics.scaleX = 0.8; graphics.scaleY = 2.0; // Elongated for arrow trail effect graphics.tint = 0x8b4513; // Brown color for wooden arrow self.scaleX = 1.0; self.scaleY = 1.0; // Arrow trail effect with slight movement tween(self, { scaleX: 1.3, scaleY: 0.8, alpha: 0, rotation: Math.PI * 0.1 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); return self; }); var Boss = Container.expand(function () { var self = Container.call(this); self.health = Math.floor(8000 * difficultyScaling.healthMultiplier); self.maxHealth = Math.floor(8000 * difficultyScaling.healthMultiplier); self.speed = 0.6; self.goldValue = 30; self.currentTarget = null; self.lastX = 0; self.lastY = 0; self.dustTimer = 0; self.motionEffects = []; var graphics = self.attachAsset('boos', { anchorX: 0.5, anchorY: 1 }); // Scale boss to be larger graphics.scaleX = 1.5; graphics.scaleY = 1.5; graphics.tint = 0x800080; // Purple tint for boss // Add glowing red eyes self.redEyes = self.attachAsset('redEyes', { anchorX: 0.5, anchorY: 0.5 }); self.redEyes.y = -graphics.height * 0.8; self.redEyes.scaleX = 1.5; self.redEyes.scaleY = 1.5; // Make eyes glow with pulsing effect self.eyeGlowDirection = 1; self.eyeGlowAlpha = 0.8; self.targetX = 1024; self.targetY = 1366; self.findNearestTarget = function () { var nearestTarget = null; var shortestDistance = Infinity; // First priority: Check for glm asset when nearby (within detection range) var glmDetectionRange = 400; // Bosses have longer detection range for glm for (var glmIndex = 0; glmIndex < game.children.length; glmIndex++) { var child = game.children[glmIndex]; // Check if this child has glm asset attached if (child && child.attachedAssets && child.attachedAssets.length > 0) { for (var assetIndex = 0; assetIndex < child.attachedAssets.length; assetIndex++) { var asset = child.attachedAssets[assetIndex]; if (asset && asset.asset && asset.asset.id === 'glm') { var dx = child.x - self.x; var dy = child.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < glmDetectionRange) { // GLM is nearby - prioritize it as target with aggressive behavior // Add visual effect to show boss is targeting glm LK.effects.flashObject(self, 0xff4500, 300); return child; } } } } } // Check crystal tower var dx = crystalTower.x - self.x; var dy = crystalTower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); nearestTarget = crystalTower; shortestDistance = distance; // Check hero dx = hero.x - self.x; dy = hero.y - self.y; distance = Math.sqrt(dx * dx + dy * dy); if (distance < shortestDistance) { nearestTarget = hero; shortestDistance = distance; } // Check towers for (var i = 0; i < towers.length; i++) { var tower = towers[i]; dx = tower.x - self.x; dy = tower.y - self.y; distance = Math.sqrt(dx * dx + dy * dy); if (distance < shortestDistance) { nearestTarget = tower; shortestDistance = distance; } } return nearestTarget; }; self.createDustCloud = function () { var dust = LK.getAsset('dustCloud', { anchorX: 0.5, anchorY: 0.5 }); dust.x = self.x + (Math.random() - 0.5) * 30; dust.y = self.y + (Math.random() - 0.5) * 20; dust.alpha = 0.6; game.addChild(dust); // Animate dust cloud tween(dust, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: dust.y - 20 }, { duration: Math.floor(800 / gameSpeed), easing: tween.easeOut, onFinish: function onFinish() { dust.destroy(); } }); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.die(); } }; self.die = function () { // Clean up motion effects for (var i = 0; i < self.motionEffects.length; i++) { self.motionEffects[i].destroy(); } var coin = new GoldCoin(); coin.x = self.x; coin.y = self.y; coin.value = self.goldValue; // Apply 50% gold drop bonus after wave 5 (one-time increase) if (wave > 5) { coin.value = Math.floor(coin.value * 1.5); } game.addChild(coin); goldCoins.push(coin); LK.getSound('enemyDeath').play(); enemyKills++; // Increment kill counter when boss dies killsText.setText('Enemies Killed: ' + enemyKills); // Continue wave progression after boss death and re-enable hero shooting bossActive = false; heroCanShoot = true; // Special wave advancement for wave 5 to 6 transition if (wave === 5) { wave++; // Increase enemies' HP and speed by 20% (multiply by 1.2) at the start of each new wave difficultyScaling.healthMultiplier *= 1.2; difficultyScaling.skeletonSpeedMultiplier *= 1.2; // Scale existing enemies on the field for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; // Scale HP by 1.2x enemy.health = Math.floor(enemy.health * 1.2); enemy.maxHealth = Math.floor(enemy.maxHealth * 1.2); // Scale speed by 1.2x enemy.speed *= 1.2; } enemiesInWave += 2; enemiesSpawned = 0; enemyKills = 0; // Reset kill counter for next wave killsText.setText('Enemies Killed: ' + enemyKills); // Keep crystal tower HP fixed - no recalculation needed waveText.setText('Wave: ' + wave); } // Remove boss from enemies array for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); }; self.update = function () { // Store last position for motion effects self.lastX = self.x; self.lastY = self.y; // Update glowing eyes effect self.eyeGlowAlpha += self.eyeGlowDirection * 0.05 * gameSpeed; if (self.eyeGlowAlpha >= 1) { self.eyeGlowDirection = -1; } else if (self.eyeGlowAlpha <= 0.5) { self.eyeGlowDirection = 1; } self.redEyes.alpha = self.eyeGlowAlpha; // Find nearest target and charge toward it self.currentTarget = self.findNearestTarget(); var targetX = self.currentTarget.x; var targetY = self.currentTarget.y; var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { // Move toward target with charging behavior // Check if targeting glm for increased speed and aggressive behavior var currentSpeed = self.speed; var isTargetingGlm = false; if (self.currentTarget && self.currentTarget.attachedAssets) { for (var assetIndex = 0; assetIndex < self.currentTarget.attachedAssets.length; assetIndex++) { var asset = self.currentTarget.attachedAssets[assetIndex]; if (asset && asset.asset && asset.asset.id === 'glm') { isTargetingGlm = true; currentSpeed = self.speed * 2.0; // 100% speed increase when boss targets glm // Add intense red glow to show extreme aggression if (LK.ticks % 20 === 0) { tween(self, { tint: 0xff0000 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { tint: 0x800080 }, { duration: 150, easing: tween.easeOut }); } }); } break; } } } var moveX = dx / distance * currentSpeed * gameSpeed; var moveY = dy / distance * currentSpeed * gameSpeed; self.x += moveX; self.y += moveY; // Create dust clouds periodically while moving self.dustTimer += gameSpeed; if (self.dustTimer >= 15) { self.createDustCloud(); self.dustTimer = 0; } } else { // Attack the target (boss deals more damage) if (self.currentTarget === crystalTower) { crystalTower.takeDamage(40); enemyAttacks++; } else if (self.currentTarget === hero) { // Flash hero red when attacked LK.effects.flashObject(hero, 0xff0000, 500); // Damage hero heroHealth -= 50; enemyAttacks++; if (heroHealth <= 0 && !heroCollapsed) { heroCollapsed = true; // Hero collapse effect tween(hero, { alpha: 0.3, scaleX: 0.8, scaleY: 0.4, rotation: Math.PI / 2 }, { duration: Math.floor(1000 / gameSpeed), easing: tween.easeOut }); // Add smoke effect var smoke = new SmokeEffect(); smoke.x = hero.x; smoke.y = hero.y; game.addChild(smoke); } } else { // Check if target is a tower for (var i = 0; i < towers.length; i++) { if (towers[i] === self.currentTarget) { self.currentTarget.takeDamage(); enemyAttacks++; break; } } } } }; return self; }); var CrystalTower = Container.expand(function () { var self = Container.call(this); self.health = 1500; self.maxHealth = 1500; self.range = 200; self.damage = 40; self.fireRate = 30; self.lastShot = 0; self.hits = 0; self.maxHits = 10; // Create magic aura for attack radius visualization self.magicAura = new MagicAura(); self.addChild(self.magicAura); // Scale aura to match tower range (range 200 = diameter 400) self.magicAura.scaleX = self.range * 2 / 400; self.magicAura.scaleY = self.range * 2 / 400; var graphics = self.attachAsset('crystalTower', { anchorX: 0.5, anchorY: 1 }); self.takeDamage = function (damage) { self.health -= damage; self.hits++; // Tower blinks red when taking damage tween(graphics, { tint: 0xff0000 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(graphics, { tint: 0xffffff }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(graphics, { tint: 0xff0000 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(graphics, { tint: 0xffffff }, { duration: 150, easing: tween.easeOut }); } }); } }); } }); LK.effects.flashObject(self, 0xff0000, 500); // Add damage effects based on health percentage var healthPercent = self.health / self.maxHealth; if (healthPercent < 0.7) { // Add cracks var crack = LK.getAsset('crackEffect', { anchorX: 0.5, anchorY: 0.5 }); crack.x = self.x + (Math.random() - 0.5) * 100; crack.y = self.y - Math.random() * 150; crack.rotation = Math.random() * Math.PI; crack.alpha = 0.8; game.addChild(crack); } if (healthPercent < 0.5) { // Add smoke effects var smoke = new SmokeEffect(); smoke.x = self.x + (Math.random() - 0.5) * 80; smoke.y = self.y - 100 - Math.random() * 50; game.addChild(smoke); } if (healthPercent < 0.3) { // Add red alert glow LK.effects.flashScreen(0xff0000, 300); } if (self.health < 1) { // Create massive explosion effect for (var i = 0; i < 5; i++) { var explosion = new ExplosionEffect(); explosion.x = self.x + (Math.random() - 0.5) * 150; explosion.y = self.y - Math.random() * 200; game.addChild(explosion); } // Play destruction sound LK.getSound('towerDestroy').play(); // Flash screen red for dramatic effect LK.effects.flashScreen(0xff0000, 1500); // Game ends when main building's HP drops below 1 LK.showGameOver(); } }; self.findTarget = function () { var closest = 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) { closest = enemy; closestDistance = distance; } } return closest; }; self.shoot = function (target) { if (LK.ticks - self.lastShot >= Math.floor(self.fireRate / gameSpeed)) { var projectile = new TowerProjectile(); projectile.x = self.x; projectile.y = self.y - 60; projectile.target = target; projectile.damage = self.damage; game.addChild(projectile); towerProjectiles.push(projectile); self.lastShot = LK.ticks; // Trigger magical attack aura effect self.magicAura.showAttackPulse(); // Add magical flash effect to tower LK.effects.flashObject(graphics, 0x4169e1, 500); // Add muzzle flash var flash = new MuzzleFlash(); flash.x = self.x; flash.y = self.y - 80; game.addChild(flash); LK.getSound('towerShoot').play(); } }; self.update = function () { var target = self.findTarget(); if (target) { self.shoot(target); } }; return self; }); var Enemy = Container.expand(function (type) { var self = Container.call(this); self.enemyType = type || 'goblin'; self.health = 100; self.maxHealth = 100; self.speed = 1; self.goldValue = 10; self.currentTarget = null; self.lastX = 0; self.lastY = 0; self.dustTimer = 0; self.motionEffects = []; if (self.enemyType === 'goblin') { self.health = Math.floor(160 * difficultyScaling.healthMultiplier); self.maxHealth = Math.floor(160 * difficultyScaling.healthMultiplier); self.speed = 1.5; self.goldValue = 10; var graphics = self.attachAsset('goblin', { anchorX: 0.5, anchorY: 1 }); } else if (self.enemyType === 'troll') { self.health = Math.floor(400 * difficultyScaling.healthMultiplier); self.maxHealth = Math.floor(400 * difficultyScaling.healthMultiplier); self.speed = 0.8; self.goldValue = 20; var graphics = self.attachAsset('troll', { anchorX: 0.5, anchorY: 1 }); } else if (self.enemyType === 'skeleton') { self.health = Math.floor(240 * difficultyScaling.healthMultiplier); self.maxHealth = Math.floor(240 * difficultyScaling.healthMultiplier); self.speed = 1.2 * difficultyScaling.skeletonSpeedMultiplier; self.goldValue = 5; var graphics = self.attachAsset('skeleton', { anchorX: 0.5, anchorY: 1 }); } else if (self.enemyType === 'orc') { self.health = Math.floor(400 * difficultyScaling.healthMultiplier); self.maxHealth = Math.floor(400 * difficultyScaling.healthMultiplier); self.speed = 0.8; self.goldValue = 20; self.lastRangedAttack = 0; self.rangedAttackRate = 120; // Attack every 2 seconds var graphics = self.attachAsset('orc', { anchorX: 0.5, anchorY: 1 }); } // Add glowing red eyes self.redEyes = self.attachAsset('redEyes', { anchorX: 0.5, anchorY: 0.5 }); self.redEyes.y = -graphics.height * 0.8; // Make eyes glow with pulsing effect self.eyeGlowDirection = 1; self.eyeGlowAlpha = 0.8; self.targetX = 1024; self.targetY = 1366; self.findNearestTarget = function () { var nearestTarget = null; var shortestDistance = Infinity; // First priority: Check for glm asset when nearby (within detection range) var glmDetectionRange = 300; // Range within which enemies detect and prioritize glm for (var glmIndex = 0; glmIndex < game.children.length; glmIndex++) { var child = game.children[glmIndex]; // Check if this child has glm asset attached if (child && child.attachedAssets && child.attachedAssets.length > 0) { for (var assetIndex = 0; assetIndex < child.attachedAssets.length; assetIndex++) { var asset = child.attachedAssets[assetIndex]; if (asset && asset.asset && asset.asset.id === 'glm') { var dx = child.x - self.x; var dy = child.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < glmDetectionRange) { // GLM is nearby - prioritize it as target return child; } } } } } // Check crystal tower var dx = crystalTower.x - self.x; var dy = crystalTower.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); nearestTarget = crystalTower; shortestDistance = distance; // Check hero dx = hero.x - self.x; dy = hero.y - self.y; distance = Math.sqrt(dx * dx + dy * dy); if (distance < shortestDistance) { nearestTarget = hero; shortestDistance = distance; } // Check towers for (var i = 0; i < towers.length; i++) { var tower = towers[i]; dx = tower.x - self.x; dy = tower.y - self.y; distance = Math.sqrt(dx * dx + dy * dy); if (distance < shortestDistance) { nearestTarget = tower; shortestDistance = distance; } } return nearestTarget; }; self.createDustCloud = function () { var dust = LK.getAsset('dustCloud', { anchorX: 0.5, anchorY: 0.5 }); dust.x = self.x + (Math.random() - 0.5) * 30; dust.y = self.y + (Math.random() - 0.5) * 20; dust.alpha = 0.6; game.addChild(dust); // Animate dust cloud tween(dust, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: dust.y - 20 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { dust.destroy(); } }); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.die(); } }; // Add hero-like methods for orc enemies self.findTarget = function () { if (self.enemyType !== 'orc') return null; var closest = null; var closestDistance = 450; // Orc range similar to hero // Check hero first (priority target) var dx = hero.x - self.x; var dy = hero.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closest = hero; closestDistance = distance; } // Check crystal tower dx = crystalTower.x - self.x; dy = crystalTower.y - self.y; distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closest = crystalTower; closestDistance = distance; } // Check towers for (var i = 0; i < towers.length; i++) { var tower = towers[i]; dx = tower.x - self.x; dy = tower.y - self.y; distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closest = tower; closestDistance = distance; } } return closest; }; self.shoot = function (target) { if (self.enemyType !== 'orc') return; if (LK.ticks - self.lastRangedAttack >= Math.floor(60 / gameSpeed)) { // 3x slower fire rate than hero (60 vs 20) var projectile = new OrcAxeProjectile(); projectile.x = self.x; projectile.y = self.y - 25; projectile.target = target; projectile.damage = 35; // Same damage as hero // Set initial position and direction for straight-line movement projectile.startX = projectile.x; projectile.startY = projectile.y; var dx = target.x - projectile.x; var dy = target.y - projectile.y; var distance = Math.sqrt(dx * dx + dy * dy); projectile.directionX = dx / distance; projectile.directionY = dy / distance; game.addChild(projectile); orcAxeProjectiles.push(projectile); self.lastRangedAttack = LK.ticks; } }; self.die = function () { // Clean up motion effects for (var i = 0; i < self.motionEffects.length; i++) { self.motionEffects[i].destroy(); } var coin = new GoldCoin(); coin.x = self.x; coin.y = self.y; coin.value = self.goldValue; // Apply 50% gold drop bonus after wave 5 (one-time increase) if (wave > 5) { coin.value = Math.floor(coin.value * 1.5); } game.addChild(coin); goldCoins.push(coin); LK.getSound('enemyDeath').play(); enemyKills++; // Increment kill counter when enemy dies killsText.setText('Enemies Killed: ' + enemyKills); for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); }; self.update = function () { // Store last position for motion effects self.lastX = self.x; self.lastY = self.y; // Update glowing eyes effect self.eyeGlowAlpha += self.eyeGlowDirection * 0.05 * gameSpeed; if (self.eyeGlowAlpha >= 1) { self.eyeGlowDirection = -1; } else if (self.eyeGlowAlpha <= 0.5) { self.eyeGlowDirection = 1; } self.redEyes.alpha = self.eyeGlowAlpha; // Find nearest target and charge toward it self.currentTarget = self.findNearestTarget(); var targetX = self.currentTarget.x; var targetY = self.currentTarget.y; var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Orc shooting behavior - shoot while moving if (self.enemyType === 'orc') { var target = self.findTarget(); if (target) { self.shoot(target); } } if (distance > 5) { // Move toward target with charging behavior // Check if targeting glm for increased speed and aggressive behavior var currentSpeed = self.speed; var isTargetingGlm = false; if (self.currentTarget && self.currentTarget.attachedAssets) { for (var assetIndex = 0; assetIndex < self.currentTarget.attachedAssets.length; assetIndex++) { var asset = self.currentTarget.attachedAssets[assetIndex]; if (asset && asset.asset && asset.asset.id === 'glm') { isTargetingGlm = true; currentSpeed = self.speed * 1.5; // 50% speed increase when targeting glm // Add red tint to show aggressive state if (LK.ticks % 30 === 0) { tween(self, { tint: 0xff4500 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { tint: 0xffffff }, { duration: 200, easing: tween.easeOut }); } }); } break; } } } var moveX = dx / distance * currentSpeed * gameSpeed; var moveY = dy / distance * currentSpeed * gameSpeed; self.x += moveX; self.y += moveY; // Create dust clouds periodically while moving self.dustTimer += gameSpeed; if (self.dustTimer >= 15) { self.createDustCloud(); self.dustTimer = 0; } } else { // Attack behavior for non-orc enemies (orcs use ranged attacks) if (self.enemyType !== 'orc') { if (self.currentTarget === crystalTower) { crystalTower.takeDamage(20); enemyAttacks++; } else if (self.currentTarget === hero) { // Flash hero red when attacked LK.effects.flashObject(hero, 0xff0000, 500); // Damage hero heroHealth -= 25; enemyAttacks++; if (heroHealth <= 0 && !heroCollapsed) { heroCollapsed = true; // Hero collapse effect tween(hero, { alpha: 0.3, scaleX: 0.8, scaleY: 0.4, rotation: Math.PI / 2 }, { duration: Math.floor(1000 / gameSpeed), easing: tween.easeOut }); // Add smoke effect var smoke = new SmokeEffect(); smoke.x = hero.x; smoke.y = hero.y; game.addChild(smoke); } } else { // Check if target is a tower for (var i = 0; i < towers.length; i++) { if (towers[i] === self.currentTarget) { self.currentTarget.takeDamage(); enemyAttacks++; break; } } } } } }; return self; }); var ExplosionEffect = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); graphics.alpha = 0.8; self.scaleX = 0.3; self.scaleY = 0.3; // Animate explosion tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); return self; }); var GLMWaveAnnouncement = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('glm', { anchorX: 0.5, anchorY: 0.5 }); self.animationDuration = 2000; // Total animation time self.onScreenDuration = 800; // Time spent visible on screen self.startAnimation = function () { // Random starting position from screen edges var side = Math.floor(Math.random() * 4); var startX, startY, endX, endY, finalX, finalY; // Calculate positions for smooth movement path if (side === 0) { // Top startX = Math.random() * 2048; startY = -100; endX = 500 + Math.random() * 1048; endY = 200 + Math.random() * 400; } else if (side === 1) { // Right startX = 2148; startY = Math.random() * 2732; endX = 1400 + Math.random() * 400; endY = 500 + Math.random() * 800; } else if (side === 2) { // Bottom startX = Math.random() * 2048; startY = 2832; endX = 500 + Math.random() * 1048; endY = 1800 + Math.random() * 400; } else { // Left startX = -100; startY = Math.random() * 2732; endX = 200 + Math.random() * 400; endY = 500 + Math.random() * 800; } // Exit position (opposite side with slight variation) var exitSide = (side + 2) % 4; if (exitSide === 0) { // Top finalX = endX + (Math.random() - 0.5) * 300; finalY = -100; } else if (exitSide === 1) { // Right finalX = 2148; finalY = endY + (Math.random() - 0.5) * 300; } else if (exitSide === 2) { // Bottom finalX = endX + (Math.random() - 0.5) * 300; finalY = 2832; } else { // Left finalX = -100; finalY = endY + (Math.random() - 0.5) * 300; } // Set starting position self.x = startX; self.y = startY; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; // Phase 1: Move onto screen with fade in tween(self, { x: endX, y: endY, alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { // Phase 2: Brief pause on screen LK.setTimeout(function () { // Phase 3: Move off screen with fade out tween(self, { x: finalX, y: finalY, alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { // Clear global reference when this GLM instance is destroyed if (existingGlmInstance === self) { existingGlmInstance = null; } self.destroy(); } }); }, self.onScreenDuration); } }); // Add slight rotation for dynamic effect tween(self, { rotation: (Math.random() - 0.5) * 0.4 }, { duration: self.animationDuration, easing: tween.easeInOut }); }; return self; }); var GoldCoin = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('goldCoin', { anchorX: 0.5, anchorY: 0.5 }); self.value = 10; self.collected = false; self.collect = function () { if (!self.collected) { self.collected = true; gold += self.value; goldText.setText('Gold: ' + gold); LK.getSound('coinCollect').play(); for (var i = goldCoins.length - 1; i >= 0; i--) { if (goldCoins[i] === self) { goldCoins.splice(i, 1); break; } } self.destroy(); } }; self.down = function (x, y, obj) { self.collect(); }; return self; }); var GrassPatch = Container.expand(function (grassType) { var self = Container.call(this); self.grassType = grassType || 'grassPatch'; self.swayAmount = 0.1 + Math.random() * 0.15; // Random sway intensity self.swaySpeed = 2000 + Math.random() * 1000; // Random sway duration var graphics = self.attachAsset(self.grassType, { anchorX: 0.5, anchorY: 1 }); // Start initial sway animation self.startSway = function () { var targetRotation = self.swayAmount * (Math.random() > 0.5 ? 1 : -1); tween(self, { rotation: targetRotation }, { duration: self.swaySpeed, easing: tween.easeInOut, onFinish: function onFinish() { // Reverse sway direction var reverseRotation = -targetRotation * (0.8 + Math.random() * 0.4); tween(self, { rotation: reverseRotation }, { duration: self.swaySpeed * (0.8 + Math.random() * 0.4), easing: tween.easeInOut, onFinish: function onFinish() { self.startSway(); // Continue swaying indefinitely } }); } }); }; // Start swaying with random delay to create natural effect LK.setTimeout(function () { self.startSway(); }, Math.random() * 2000); return self; }); var HealthBarButton = Container.expand(function () { var self = Container.call(this); // Create border with subtle shadow effect var border = self.attachAsset('healthBarBorder', { anchorX: 0.5, anchorY: 0.5 }); border.alpha = 0.8; // Create base/background var base = self.attachAsset('healthBarBase', { anchorX: 0.5, anchorY: 0.5 }); // Create health fill with gradient effect self.healthFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5 }); self.healthFill.x = -99; // Center the fill self.healthFill.y = 0; // Create heart icon self.heartIcon = self.attachAsset('heartIcon', { anchorX: 0.5, anchorY: 0.5 }); self.heartIcon.x = -85; // Position on left side self.heartIcon.y = 0; self.heartIcon.scaleX = 0.8; self.heartIcon.scaleY = 0.8; // Add glossy highlight effect var highlight = self.attachAsset('healthBarBase', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0xffffff; highlight.alpha = 0.3; highlight.scaleY = 0.4; highlight.y = -6; // Health bar properties self.currentHealth = 100; self.maxHealth = 100; self.lastHealth = 100; // Animation properties self.pulseDirection = 1; self.glowIntensity = 0.8; self.setHealth = function (current, max) { if (max !== undefined) { self.maxHealth = max; } self.currentHealth = Math.max(0, Math.min(current, self.maxHealth)); self.updateHealthDisplay(); }; self.updateHealthDisplay = function () { var healthPercent = self.currentHealth / self.maxHealth; // Update fill width self.healthFill.scaleX = healthPercent; // Update color based on health level if (healthPercent > 0.6) { // Green to yellow gradient var greenComponent = 1.0; var redComponent = (1.0 - healthPercent) * 2.5; self.healthFill.tint = Math.floor(redComponent * 255) << 16 | Math.floor(greenComponent * 255) << 8 | 0; } else if (healthPercent > 0.3) { // Yellow to orange self.healthFill.tint = 0xffa500; } else { // Red with pulsing effect for critical health self.healthFill.tint = 0xff0000; } // Animate health changes if (self.currentHealth !== self.lastHealth) { self.animateHealthChange(); self.lastHealth = self.currentHealth; } }; self.animateHealthChange = function () { // Scale animation for health change tween(self, { scaleX: 1.05, scaleY: 1.05 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 150, easing: tween.easeOut }); } }); // Heart beat animation tween(self.heartIcon, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self.heartIcon, { scaleX: 0.8, scaleY: 0.8 }, { duration: 200, easing: tween.easeOut }); } }); }; self.update = function () { // Subtle pulsing glow effect self.glowIntensity += self.pulseDirection * 0.02; if (self.glowIntensity >= 1.0) { self.pulseDirection = -1; } else if (self.glowIntensity <= 0.6) { self.pulseDirection = 1; } // Apply glow to border border.alpha = 0.6 + (self.glowIntensity - 0.6) * 0.4; // Critical health warning pulse if (self.currentHealth / self.maxHealth <= 0.2) { var criticalPulse = Math.sin(LK.ticks * 0.3) * 0.3 + 0.7; self.healthFill.alpha = criticalPulse; self.heartIcon.alpha = criticalPulse; } else { self.healthFill.alpha = 1.0; self.heartIcon.alpha = 1.0; } }; // Initialize display self.updateHealthDisplay(); return self; }); var Hero = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('hero', { anchorX: 0.5, anchorY: 1 }); self.speed = 3; self.range = 450; self.damage = 35; self.fireRate = 20; self.lastShot = 0; self.findTarget = function () { var closest = 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) { closest = enemy; closestDistance = distance; } } return closest; }; self.shoot = function (target) { if (LK.ticks - self.lastShot >= Math.floor(self.fireRate / gameSpeed)) { var projectile = new HeroProjectile(); projectile.x = self.x; projectile.y = self.y - 25; projectile.target = target; projectile.damage = self.damage; game.addChild(projectile); heroProjectiles.push(projectile); self.lastShot = LK.ticks; } }; self.update = function () { // Only shoot if hero is not collapsed (dead) and is allowed to shoot if (!heroCollapsed && heroCanShoot) { var target = self.findTarget(); if (target) { self.shoot(target); } } }; return self; }); var HeroProjectile = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('heroProjectile', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 10; self.target = null; self.damage = 35; self.update = function () { if (!self.target || self.target.destroyed) { self.destroy(); for (var i = heroProjectiles.length - 1; i >= 0; i--) { if (heroProjectiles[i] === self) { heroProjectiles.splice(i, 1); break; } } 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) { self.target.takeDamage(self.damage); LK.getSound('arrowImpact').play(); self.destroy(); for (var i = heroProjectiles.length - 1; i >= 0; i--) { if (heroProjectiles[i] === self) { heroProjectiles.splice(i, 1); break; } } } else { self.x += dx / distance * self.speed * gameSpeed; self.y += dy / distance * self.speed * gameSpeed; } }; return self; }); var MagicAura = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('magicAura', { anchorX: 0.5, anchorY: 0.5 }); graphics.alpha = 0.2; self.pulseDirection = 1; self.basePulse = 0.15; self.currentPulse = self.basePulse; self.update = function () { // Create pulsing effect self.currentPulse += self.pulseDirection * 0.003; if (self.currentPulse >= 0.4) { self.pulseDirection = -1; } else if (self.currentPulse <= self.basePulse) { self.pulseDirection = 1; } graphics.alpha = self.currentPulse; // Slight rotation for magical effect graphics.rotation += 0.01; }; self.showAttackPulse = function () { // Flash brighter when tower attacks tween(graphics, { alpha: 0.7, scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(graphics, { alpha: self.currentPulse, scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } }); }; return self; }); var MuzzleFlash = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('muzzleFlash', { anchorX: 0.5, anchorY: 0.5 }); graphics.alpha = 0.9; self.scaleX = 0.5; self.scaleY = 0.5; // Quick flash effect tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); return self; }); var OrcAxeProjectile = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('balt', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 10; // Same speed as heroProjectile self.target = null; self.damage = 35; // Same damage as hero self.startX = 0; self.startY = 0; self.directionX = 0; self.directionY = 0; self.maxRange = 450 * 3 / 4; // Three-quarters of maximum throwing range (450) self.distanceTraveled = 0; self.update = function () { // Check if axe has traveled beyond one-quarter range if (self.distanceTraveled >= self.maxRange) { self.destroy(); for (var i = orcAxeProjectiles.length - 1; i >= 0; i--) { if (orcAxeProjectiles[i] === self) { orcAxeProjectiles.splice(i, 1); break; } } return; } // Move in straight line using initial direction var moveDistance = self.speed * gameSpeed; self.x += self.directionX * moveDistance; self.y += self.directionY * moveDistance; self.distanceTraveled += moveDistance; // Check for target collision if (self.target && !self.target.destroyed) { 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) { // Apply damage based on target type if (self.target === crystalTower) { crystalTower.takeDamage(self.damage); } else if (self.target === hero) { // Flash hero red when attacked LK.effects.flashObject(hero, 0xff0000, 500); // Damage hero directly heroHealth -= self.damage; if (heroHealth <= 0 && !heroCollapsed) { heroCollapsed = true; // Hero collapse effect tween(hero, { alpha: 0.3, scaleX: 0.8, scaleY: 0.4, rotation: Math.PI / 2 }, { duration: Math.floor(1000 / gameSpeed), easing: tween.easeOut }); // Add smoke effect var smoke = new SmokeEffect(); smoke.x = hero.x; smoke.y = hero.y; game.addChild(smoke); } } else if (self.target.takeDamage) { // For towers and other objects with takeDamage method self.target.takeDamage(self.damage); } LK.getSound('arrowImpact').play(); self.destroy(); for (var i = orcAxeProjectiles.length - 1; i >= 0; i--) { if (orcAxeProjectiles[i] === self) { orcAxeProjectiles.splice(i, 1); break; } } } } }; return self; }); var SmokeEffect = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('smokeCloud', { anchorX: 0.5, anchorY: 0.5 }); graphics.alpha = 0.4; self.scaleX = 0.8; self.scaleY = 0.8; // Rising smoke animation tween(self, { y: self.y - 100, scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); return self; }); var StylizedButton = Container.expand(function (config) { var self = Container.call(this); // Default configuration var buttonConfig = config || {}; self.buttonWidth = buttonConfig.width || 200; self.buttonHeight = buttonConfig.height || 80; self.buttonColor = buttonConfig.color || 0x4169e1; self.textContent = buttonConfig.text || "Button"; self.textSize = buttonConfig.textSize || 28; // Create softened beveled edge layers for 3D effect // Deep shadow for depth self.deepShadow = self.attachAsset('uiPanel', { anchorX: 0.5, anchorY: 0.5 }); self.deepShadow.scaleX = (self.buttonWidth + 8) / 200; self.deepShadow.scaleY = (self.buttonHeight + 8) / 100; self.deepShadow.tint = 0x000000; self.deepShadow.alpha = 0.4; self.deepShadow.x = 6; self.deepShadow.y = 6; // Soft shadow for rounded effect self.shadow = self.attachAsset('uiPanel', { anchorX: 0.5, anchorY: 0.5 }); self.shadow.scaleX = (self.buttonWidth + 4) / 200; self.shadow.scaleY = (self.buttonHeight + 4) / 100; self.shadow.tint = 0x000000; self.shadow.alpha = 0.25; self.shadow.x = 3; self.shadow.y = 3; // Dark bevel edge (bottom-right) self.darkBevel = self.attachAsset('uiPanel', { anchorX: 0.5, anchorY: 0.5 }); self.darkBevel.scaleX = (self.buttonWidth + 2) / 200; self.darkBevel.scaleY = (self.buttonHeight + 2) / 100; self.darkBevel.tint = 0x2c2c2c; self.darkBevel.alpha = 0.8; self.darkBevel.x = 1; self.darkBevel.y = 1; // Main button background with slight elevation self.background = self.attachAsset('uiPanel', { anchorX: 0.5, anchorY: 0.5 }); self.background.scaleX = self.buttonWidth / 200; self.background.scaleY = self.buttonHeight / 100; self.background.tint = self.buttonColor; self.background.alpha = 0.95; // Light bevel edge (top-left highlight) self.lightBevel = self.attachAsset('uiPanel', { anchorX: 0.5, anchorY: 0.5 }); self.lightBevel.scaleX = (self.buttonWidth - 4) / 200; self.lightBevel.scaleY = (self.buttonHeight - 4) / 100; self.lightBevel.tint = 0xffffff; self.lightBevel.alpha = 0.4; self.lightBevel.x = -1; self.lightBevel.y = -1; // Gradient highlight overlay for glossy effect self.highlight = self.attachAsset('uiPanel', { anchorX: 0.5, anchorY: 0.5 }); self.highlight.scaleX = (self.buttonWidth - 8) / 200; self.highlight.scaleY = self.buttonHeight * 0.3 / 100; self.highlight.tint = 0xffffff; self.highlight.alpha = 0.35; self.highlight.y = -self.buttonHeight * 0.2; // Subtle inner glow for modern feel self.innerGlow = self.attachAsset('uiPanel', { anchorX: 0.5, anchorY: 0.5 }); self.innerGlow.scaleX = (self.buttonWidth - 12) / 200; self.innerGlow.scaleY = (self.buttonHeight - 12) / 100; self.innerGlow.tint = 0xffffff; self.innerGlow.alpha = 0.15; // Outer border for crisp edges self.border = self.attachAsset('uiPanel', { anchorX: 0.5, anchorY: 0.5 }); self.border.scaleX = (self.buttonWidth + 6) / 200; self.border.scaleY = (self.buttonHeight + 6) / 100; self.border.tint = 0xc0c0c0; self.border.alpha = 0.7; // Layer ordering for proper 3D effect self.removeChild(self.border); self.addChildAt(self.border, 0); self.removeChild(self.deepShadow); self.addChildAt(self.deepShadow, 1); self.removeChild(self.shadow); self.addChildAt(self.shadow, 2); self.removeChild(self.darkBevel); self.addChildAt(self.darkBevel, 3); self.removeChild(self.background); self.addChildAt(self.background, 4); self.removeChild(self.lightBevel); self.addChildAt(self.lightBevel, 5); self.removeChild(self.innerGlow); self.addChildAt(self.innerGlow, 6); self.removeChild(self.highlight); self.addChildAt(self.highlight, 7); // Button text self.buttonText = new Text2(self.textContent, { size: self.textSize, fill: 0xFFFFFF }); self.buttonText.anchor.set(0.5, 0.5); self.addChild(self.buttonText); // Animation properties self.isPressed = false; self.glowIntensity = 0.8; self.glowDirection = 1; // Enhanced hover/glow animation with beveled edges self.update = function () { // Subtle glow pulsing self.glowIntensity += self.glowDirection * 0.012; if (self.glowIntensity >= 1.0) { self.glowDirection = -1; } else if (self.glowIntensity <= 0.6) { self.glowDirection = 1; } // Apply coordinated glow to all bevel layers for cohesive 3D effect var glowFactor = (self.glowIntensity - 0.6) * 0.5; // Border glow self.border.alpha = 0.5 + glowFactor * 0.4; // Inner glow pulsing self.innerGlow.alpha = 0.1 + glowFactor * 0.15; // Light bevel intensity variation self.lightBevel.alpha = 0.35 + glowFactor * 0.15; // Highlight shimmer effect self.highlight.alpha = 0.3 + glowFactor * 0.2; // Subtle shadow depth variation for breathing effect self.shadow.alpha = 0.2 + glowFactor * 0.1; }; // Press animation with enhanced 3D bevel effects self.playPressAnimation = function () { if (self.isPressed) return; self.isPressed = true; // Pressed state: button appears to sink in with inverted bevel tween(self, { scaleX: 0.96, scaleY: 0.96 }, { duration: 60, easing: tween.easeOut }); // Invert shadow positions for pressed effect tween(self.shadow, { x: 1, y: 1, alpha: 0.4 }, { duration: 60, easing: tween.easeOut }); tween(self.deepShadow, { x: 2, y: 2, alpha: 0.6 }, { duration: 60, easing: tween.easeOut }); // Darken the button surface when pressed tween(self.background, { alpha: 0.85 }, { duration: 60, easing: tween.easeOut, onFinish: function onFinish() { // Return to normal state tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { self.isPressed = false; } }); // Restore shadow positions tween(self.shadow, { x: 3, y: 3, alpha: 0.25 }, { duration: 120, easing: tween.easeOut }); tween(self.deepShadow, { x: 6, y: 6, alpha: 0.4 }, { duration: 120, easing: tween.easeOut }); // Restore button surface tween(self.background, { alpha: 0.95 }, { duration: 120, easing: tween.easeOut }); } }); // Enhanced highlight flash with bevel interaction tween(self.highlight, { alpha: 0.7, scaleY: 0.4 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(self.highlight, { alpha: 0.35, scaleY: 0.3 }, { duration: 140, easing: tween.easeOut }); } }); // Light bevel flash for tactile feedback tween(self.lightBevel, { alpha: 0.6 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(self.lightBevel, { alpha: 0.4 }, { duration: 140, easing: tween.easeOut }); } }); }; // Update text content self.setText = function (newText) { self.buttonText.setText(newText); }; // Update button color self.setColor = function (newColor) { self.buttonColor = newColor; self.background.tint = newColor; }; return self; }); var Tower = Container.expand(function (type) { var self = Container.call(this); self.towerType = type || 'archer'; self.range = 150; self.damage = 25; self.fireRate = 60; self.lastShot = 0; self.hits = 0; self.maxHits = 4; if (self.towerType === 'archer') { self.range = 380; self.damage = 30; self.fireRate = 45; self.maxHits = 500; var graphics = self.attachAsset('archerTower', { anchorX: 0.5, anchorY: 1 }); } else if (self.towerType === 'cannon') { self.range = 120; self.damage = 60; self.fireRate = 90; self.maxHits = 40; var graphics = self.attachAsset('cannonTower', { anchorX: 0.5, anchorY: 1 }); } self.takeDamage = function () { self.hits++; // Tower blinks red when taking damage tween(graphics, { tint: 0xff0000 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(graphics, { tint: 0xffffff }, { duration: 200, easing: tween.easeOut }); } }); if (self.hits === self.maxHits) { // Create explosion effect var explosion = new ExplosionEffect(); explosion.x = self.x; explosion.y = self.y; game.addChild(explosion); // Play destruction sound LK.getSound('towerDestroy').play(); // Remove from towers array for (var i = towers.length - 1; i >= 0; i--) { if (towers[i] === self) { towers.splice(i, 1); break; } } self.destroy(); } }; self.findTarget = function () { var closest = 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) { closest = enemy; closestDistance = distance; } } return closest; }; self.shoot = function (target) { if (LK.ticks - self.lastShot >= Math.floor(self.fireRate / gameSpeed)) { // Use different projectile types based on tower type var projectile; if (self.towerType === 'archer') { projectile = new HeroProjectile(); // Use heroProjectile asset for archer towers } else { projectile = new TowerProjectile(); // Use regular towerProjectile for other towers } projectile.x = self.x; projectile.y = self.y - 40; projectile.target = target; projectile.damage = self.damage; game.addChild(projectile); // Add to appropriate projectile array based on tower type if (self.towerType === 'archer') { heroProjectiles.push(projectile); } else { towerProjectiles.push(projectile); } self.lastShot = LK.ticks; LK.getSound('towerShoot').play(); // Add different visual effects based on tower type if (self.towerType === 'archer') { // Arrow trail effect for archer towers var arrowTrail = new ArrowTrail(); arrowTrail.x = self.x; arrowTrail.y = self.y - 30; game.addChild(arrowTrail); } else { // Muzzle flash effect for other tower types var flash = new MuzzleFlash(); flash.x = self.x; flash.y = self.y - 30; game.addChild(flash); } } }; self.update = function () { var target = self.findTarget(); if (target) { self.shoot(target); } }; return self; }); var TowerProjectile = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('towerProjectile', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.target = null; self.damage = 25; self.update = function () { if (!self.target || self.target.destroyed) { self.destroy(); for (var i = towerProjectiles.length - 1; i >= 0; i--) { if (towerProjectiles[i] === self) { towerProjectiles.splice(i, 1); break; } } 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) { self.target.takeDamage(self.damage); self.destroy(); for (var i = towerProjectiles.length - 1; i >= 0; i--) { if (towerProjectiles[i] === self) { towerProjectiles.splice(i, 1); break; } } } else { self.x += dx / distance * self.speed * gameSpeed; self.y += dy / distance * self.speed * gameSpeed; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a4d1a }); /**** * Game Code ****/ var enemies = []; var towers = []; var archerTowerCount = 0; var archerTowerBaseCost = 100; var archerTowerLastCost = 100; var towerProjectiles = []; var heroProjectiles = []; var orcAxeProjectiles = []; var goldCoins = []; var crystalTower; var hero; var gold = 100; var wave = 1; var enemiesInWave = 5; var enemySpawnTimer = 0; var minEnemiesOnField = 3; // Always maintain minimum enemies var enemiesSpawned = 0; var waveComplete = false; var draggedHero = false; var heroCollapsed = false; var heroHealth = 5000; var heroMaxHealth = 5000; var enemyAttacks = 0; var enemyKills = 0; var bossActive = false; var difficultyScaling = { healthMultiplier: 1.0, skeletonSpeedMultiplier: 1.0 }; var scalingApplied = 0; // Track which wave milestone we last applied scaling for // Control hero shooting behavior var heroCanShoot = true; // Track enemies in contact with crystal tower for continuous damage var enemiesInContact = []; var contactDamageTimer = 0; // Hero contact damage system var heroContactDamageRate = 1; // Base damage per second var heroContactDamageTimer = 0; var enemiesInContactWithHero = []; var heroGlmContactRewardGiven = false; // Track if hero has received glm contact reward var goldDropBonusApplied = false; // Track if 50% gold drop bonus has been applied after wave 5 var existingGlmInstance = null; // Track existing GLM instance to ensure only one exists // Game speed control var gameSpeed = 1.0; var gameSpeedOptions = [1.0, 1.5, 2.0, 3.0, 5.0]; var currentSpeedIndex = 0; // Sound control var soundMuted = false; // Grass background removed - clean battlefield environment var grassPatches = []; // Function to calculate total enemy HP for current wave function calculateWaveEnemyHP() { var totalHP = 0; var regularEnemyCount = 0; var bossCount = 0; // Determine enemy composition for current wave if (wave === 5) { bossCount = 1; regularEnemyCount = 0; } else if (wave === 10) { bossCount = 2; regularEnemyCount = 28; } else if (wave === 15) { bossCount = 1; regularEnemyCount = 5 + (wave - 1) * 2; } else if (wave === 100) { bossCount = 1; // Special boss with 10000 HP regularEnemyCount = 0; totalHP += 10000; // Special boss HP return Math.floor(totalHP / 2); } else { bossCount = 0; regularEnemyCount = 5 + (wave - 1) * 2; } // Calculate boss HP if (bossCount > 0 && wave !== 100) { var bossHP = Math.floor(8000 * difficultyScaling.healthMultiplier); totalHP += bossHP * bossCount; } // Calculate regular enemy HP (mix of goblins, skeletons, trolls) if (regularEnemyCount > 0) { var avgEnemyHP = 0; if (wave < 3) { // Only goblins and skeletons avgEnemyHP = (160 + 240) / 2; // Average of goblin (160) and skeleton (240) } else { // All enemy types including trolls avgEnemyHP = (160 + 240 + 400) / 3; // Average including troll (400) } avgEnemyHP = Math.floor(avgEnemyHP * difficultyScaling.healthMultiplier); totalHP += avgEnemyHP * regularEnemyCount; } return Math.floor(totalHP / 2); } // Add dungeon background at center var dungeonBackground = LK.getAsset('dungeonBackground', { anchorX: 0.5, anchorY: 0.5 }); dungeonBackground.x = 1024; dungeonBackground.y = 1366; game.addChild(dungeonBackground); // Create crystal tower at center crystalTower = new CrystalTower(); crystalTower.x = 1024; crystalTower.y = 1366; // Set fixed HP to 500 crystalTower.health = 500; crystalTower.maxHealth = 500; game.addChild(crystalTower); // Create hero hero = new Hero(); hero.x = 900; hero.y = 1200; game.addChild(hero); // Create hero HP bar that follows hero var heroHealthBar = LK.getAsset('uiPanel', { anchorX: 0.5, anchorY: 0.5 }); heroHealthBar.scaleX = 1.0; heroHealthBar.scaleY = 0.6; heroHealthBar.tint = 0x404040; game.addChild(heroHealthBar); var heroHealthFill = LK.getAsset('uiPanel', { anchorX: 0, anchorY: 0.5 }); heroHealthFill.scaleX = 1.0; heroHealthFill.scaleY = 0.6; heroHealthFill.tint = 0x00ff00; heroHealthBar.addChild(heroHealthFill); heroHealthFill.x = -100; heroHealthFill.y = 0; var heroHealthText = new Text2(heroHealth + '/' + heroMaxHealth, { size: 28, fill: 0xFFFFFF }); heroHealthText.anchor.set(0.5, 0.5); heroHealthBar.addChild(heroHealthText); // UI Elements var goldText = new Text2('Gold: ' + gold, { size: 40, fill: 0xFFD700 }); goldText.anchor.set(0, 0); LK.gui.topRight.addChild(goldText); goldText.x = -350; goldText.y = 20; // Add click handler to gold text to set gold to 10,000 (simplified approach) goldText.down = function (x, y, obj) { // Set gold to 10,000 on any click gold = 10000; goldText.setText('Gold: ' + gold); // Flash screen gold to show effect LK.effects.flashScreen(0xFFD700, 500); }; var killsText = new Text2('Enemies Killed: ' + enemyKills, { size: 40, fill: 0xFFFFFF }); killsText.anchor.set(0, 0); LK.gui.topRight.addChild(killsText); killsText.x = -350; killsText.y = 70; var waveText = new Text2('Wave: ' + wave, { size: 60, fill: 0xFFFFFF }); waveText.anchor.set(0, 0); LK.gui.top.addChild(waveText); waveText.x = -100; waveText.y = 50; var healthText = new Text2('Tower HP: ' + crystalTower.health, { size: 50, fill: 0x00FFFF }); healthText.anchor.set(0, 0); LK.gui.bottom.addChild(healthText); healthText.x = -150; healthText.y = -80; // Boss health bar (initially hidden) var bossHealthBar = LK.getAsset('uiPanel', { anchorX: 0.5, anchorY: 0.5 }); bossHealthBar.scaleX = 4; bossHealthBar.scaleY = 0.5; bossHealthBar.tint = 0x800080; bossHealthBar.visible = false; LK.gui.top.addChild(bossHealthBar); bossHealthBar.x = 0; bossHealthBar.y = 100; var bossHealthFill = LK.getAsset('uiPanel', { anchorX: 0, anchorY: 0.5 }); bossHealthFill.scaleX = 4; bossHealthFill.scaleY = 0.4; bossHealthFill.tint = 0xff0000; bossHealthFill.visible = false; bossHealthBar.addChild(bossHealthFill); bossHealthFill.x = -400; bossHealthFill.y = 0; var bossHealthText = new Text2('BOSS', { size: 40, fill: 0xFFFFFF }); bossHealthText.anchor.set(0.5, 0.5); bossHealthText.visible = false; LK.gui.top.addChild(bossHealthText); bossHealthText.x = 0; bossHealthText.y = 60; // Tower placement UI var archerButton = new StylizedButton({ width: 160, height: 100, color: 0x8b4513, text: 'Archer\n100g', textSize: 30 }); archerButton.x = 50; archerButton.y = -150; LK.gui.bottom.addChild(archerButton); // HP Upgrade Button: Costs 150 gold. Increases the HP of all existing archer towers by 10% var hpUpgradeButton = new StylizedButton({ width: 160, height: 100, color: 0x228b22, text: 'HP+\n150g', textSize: 30 }); hpUpgradeButton.x = 250; hpUpgradeButton.y = -150; LK.gui.bottom.addChild(hpUpgradeButton); // Range Upgrade Button: Costs 250 gold. Increases the attack range of all existing archer towers by 10% var rangeUpgradeButtonTowers = new StylizedButton({ width: 160, height: 100, color: 0x4169e1, text: 'Range+\n250g', textSize: 30 }); rangeUpgradeButtonTowers.x = 450; rangeUpgradeButtonTowers.y = -150; LK.gui.bottom.addChild(rangeUpgradeButtonTowers); // Game speed button var speedButton = new StylizedButton({ width: 160, height: 60, color: 0x4169e1, text: 'Speed: 1.0x', textSize: 28 }); speedButton.x = 150; speedButton.y = -50; LK.gui.bottomLeft.addChild(speedButton); // Restart button var restartButton = new StylizedButton({ width: 180, height: 70, color: 0xff4500, text: 'Restart', textSize: 28 }); restartButton.x = 150; restartButton.y = -200; LK.gui.bottomLeft.addChild(restartButton); // Map panning state var isPanning = false; var panStartX = 0; var panStartY = 0; var panLastX = 0; var panLastY = 0; var mapOffsetX = 0; var mapOffsetY = 0; // Map expansion variables var mapBounds = { left: 0, right: 2048, top: 0, bottom: 2732 }; var mapExpansionDistance = 500; // How much to expand when character reaches edge // Range upgrade button // Range upgrade pricing system var rangeUpgradeCost = 5000; var rangeUpgradeLevel = 0; var rangeUpgradeButton = new StylizedButton({ width: 180, height: 70, color: 0x9932cc, text: 'Range+\n5000g', textSize: 28 }); rangeUpgradeButton.x = 150; rangeUpgradeButton.y = -400; LK.gui.bottomLeft.addChild(rangeUpgradeButton); // HP regeneration button - stylized health bar design var regenButton = new HealthBarButton(); regenButton.x = 150; regenButton.y = -300; regenButton.scaleX = 1.2; regenButton.scaleY = 1.5; LK.gui.bottomLeft.addChild(regenButton); // Set initial health display for the button regenButton.setHealth(75, 100); // Show 75% health as example var regenText = new Text2('Heal 10000g', { size: 24, fill: 0xFFFFFF }); regenText.anchor.set(0.5, 0.5); regenText.y = 25; // Position below the health bar regenButton.addChild(regenText); // Range upgrade button handler rangeUpgradeButton.down = function (x, y, obj) { // Check if player has enough gold if (gold >= rangeUpgradeCost) { // Play stylized button press animation rangeUpgradeButton.playPressAnimation(); // Deduct gold gold -= rangeUpgradeCost; goldText.setText('Gold: ' + gold); // Double the crystal tower's range and magic aura size crystalTower.range *= 2; crystalTower.magicAura.scaleX = crystalTower.range * 2 / 400; crystalTower.magicAura.scaleY = crystalTower.range * 2 / 400; // Flash crystal tower blue to show upgrade LK.effects.flashObject(crystalTower, 0x4169e1, 1000); // Flash screen blue for upgrade effect LK.effects.flashScreen(0x4169e1, 500); // Update upgrade level and cost for next purchase rangeUpgradeLevel++; if (rangeUpgradeLevel === 1) { rangeUpgradeCost = 25000; rangeUpgradeButton.setText('Range+\n25000g'); } // Animate button color change to show upgrade progression tween(rangeUpgradeButton, { tint: 0xffd700 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(rangeUpgradeButton, { tint: 0xffffff }, { duration: 200, easing: tween.easeOut }); } }); } }; // HP regeneration button handler regenButton.down = function (x, y, obj) { // Check if player has enough gold if (gold >= 10000) { // Animate health bar filling up tween(regenButton, { scaleX: 1.4, scaleY: 1.7 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { // Animate health bar fill to 100% var fillTween = function fillTween(startHealth, targetHealth, duration) { var healthDiff = targetHealth - startHealth; var steps = 30; var stepSize = healthDiff / steps; var currentStep = 0; var _animateStep = function animateStep() { if (currentStep < steps) { regenButton.setHealth(startHealth + stepSize * currentStep, 100); currentStep++; LK.setTimeout(_animateStep, duration / steps); } else { regenButton.setHealth(targetHealth, 100); } }; _animateStep(); }; // Animate from current to full health fillTween(regenButton.currentHealth, 100, 800); tween(regenButton, { scaleX: 1.2, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut }); } }); // Deduct gold and restore hero health gold -= 10000; heroHealth = heroMaxHealth; goldText.setText('Gold: ' + gold); // Flash hero green to show healing LK.effects.flashObject(hero, 0x00ff00, 1000); // Flash screen green for healing effect LK.effects.flashScreen(0x00ff00, 500); } }; // Wave selection buttons var waveButtons = []; var waveOptions = [1, 5, 10, 15, 20, 30, 40, 50]; for (var w = 0; w < waveOptions.length; w++) { var waveNumber = waveOptions[w]; var waveButton = new StylizedButton({ width: 80, height: 50, color: 0x4b0082, text: 'W' + waveNumber, textSize: 24 }); waveButton.waveTarget = waveNumber; // Position buttons in a row at the top waveButton.x = -650 + w * 90; waveButton.y = 220; LK.gui.top.addChild(waveButton); // Add click handler for wave selection waveButton.down = function (x, y, obj) { var targetWave = this.waveTarget; // Play stylized button press animation this.playPressAnimation(); // Jump to selected wave wave = targetWave; enemiesInWave = 5 + (wave - 1) * 2; enemiesSpawned = 0; enemyKills = 0; bossActive = false; heroCanShoot = true; // Update difficulty scaling based on wave using progressive 20% scaling difficultyScaling.healthMultiplier = Math.pow(1.2, wave - 1); difficultyScaling.skeletonSpeedMultiplier = Math.pow(1.2, wave - 1); // Update hero contact damage rate based on wave heroContactDamageRate = Math.pow(1.2, wave - 1); // Keep crystal tower HP fixed at 500 - no recalculation needed // Clear existing enemies for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].destroy(); enemies.splice(i, 1); } // Clear projectiles for (var i = towerProjectiles.length - 1; i >= 0; i--) { towerProjectiles[i].destroy(); towerProjectiles.splice(i, 1); } for (var i = heroProjectiles.length - 1; i >= 0; i--) { heroProjectiles[i].destroy(); heroProjectiles.splice(i, 1); } for (var i = orcAxeProjectiles.length - 1; i >= 0; i--) { orcAxeProjectiles[i].destroy(); orcAxeProjectiles.splice(i, 1); } // Update UI waveText.setText('Wave: ' + wave); killsText.setText('Enemies Killed: ' + enemyKills); // Create GLM wave announcement for wave selection only if none exists if (!existingGlmInstance || existingGlmInstance.destroyed) { var glmAnnouncement = new GLMWaveAnnouncement(); existingGlmInstance = glmAnnouncement; // Track this instance game.addChild(glmAnnouncement); glmAnnouncement.startAnimation(); } // Flash screen to indicate wave change LK.effects.flashScreen(0x4b0082, 500); }; waveButtons.push(waveButton); } // Speed button handler speedButton.down = function (x, y, obj) { // Play stylized button press animation speedButton.playPressAnimation(); currentSpeedIndex = (currentSpeedIndex + 1) % gameSpeedOptions.length; gameSpeed = gameSpeedOptions[currentSpeedIndex]; speedButton.setText('Speed: ' + gameSpeed + 'x'); // Note: Game speed functionality not available in LK engine }; // Restart button handler restartButton.down = function (x, y, obj) { // Play stylized button press animation restartButton.playPressAnimation(); // Trigger game over state first, which will automatically reset the game LK.showGameOver(); }; // HP upgrade button handler hpUpgradeButton.down = function (x, y, obj) { // Check if player has enough gold and has archer towers if (gold >= 150 && towers.length > 0) { // Play stylized button press animation hpUpgradeButton.playPressAnimation(); // Count archer towers and apply 10% HP increase var archerTowersUpgraded = 0; for (var i = 0; i < towers.length; i++) { if (towers[i].towerType === 'archer') { towers[i].maxHits = Math.floor(towers[i].maxHits * 1.1); archerTowersUpgraded++; // Flash tower green to show upgrade LK.effects.flashObject(towers[i], 0x00ff00, 800); } } if (archerTowersUpgraded > 0) { // Deduct gold gold -= 150; goldText.setText('Gold: ' + gold); // Flash screen green for upgrade effect LK.effects.flashScreen(0x00ff00, 500); } } }; // Range upgrade button handler for towers rangeUpgradeButtonTowers.down = function (x, y, obj) { // Check if player has enough gold and has archer towers if (gold >= 250 && towers.length > 0) { // Play stylized button press animation rangeUpgradeButtonTowers.playPressAnimation(); // Count archer towers and apply 10% range increase var archerTowersUpgraded = 0; for (var i = 0; i < towers.length; i++) { if (towers[i].towerType === 'archer') { towers[i].range = Math.floor(towers[i].range * 1.1); archerTowersUpgraded++; // Flash tower blue to show upgrade LK.effects.flashObject(towers[i], 0x4169e1, 800); } } if (archerTowersUpgraded > 0) { // Deduct gold gold -= 250; goldText.setText('Gold: ' + gold); // Flash screen blue for upgrade effect LK.effects.flashScreen(0x4169e1, 500); } } }; // Tower placement handlers archerButton.down = function (x, y, obj) { // Play stylized button press animation archerButton.playPressAnimation(); // Calculate dynamic cost: 15% more than the last tower cost var dynamicCost = Math.floor(archerTowerLastCost * 1.15); if (gold >= dynamicCost) { placingTowerType = 'archer'; placingTowerCost = dynamicCost; } }; var placingTowerType = null; var placingTowerCost = 0; function spawnEnemy() { var enemyTypes = ['goblin', 'goblin', 'skeleton']; if (wave >= 3) enemyTypes.push('troll'); if (wave >= 5) enemyTypes.push('troll', 'troll'); if (wave >= 7) enemyTypes.push('orc', 'orc'); var randomType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; var enemy = new Enemy(randomType); // Spawn from random edge using dynamic map boundaries var side = Math.floor(Math.random() * 4); if (side === 0) { // Top enemy.x = mapBounds.left + Math.random() * (mapBounds.right - mapBounds.left); enemy.y = mapBounds.top; } else if (side === 1) { // Right enemy.x = mapBounds.right; enemy.y = mapBounds.top + Math.random() * (mapBounds.bottom - mapBounds.top); } else if (side === 2) { // Bottom enemy.x = mapBounds.left + Math.random() * (mapBounds.right - mapBounds.left); enemy.y = mapBounds.bottom; } else { // Left enemy.x = mapBounds.left; enemy.y = mapBounds.top + Math.random() * (mapBounds.bottom - mapBounds.top); } game.addChild(enemy); enemies.push(enemy); enemiesSpawned++; } function expandMapIfNeeded() { var expanded = false; var edgeBuffer = 200; // Distance from edge to trigger expansion // Check if hero is near left edge if (hero.x - mapBounds.left < edgeBuffer) { mapBounds.left -= mapExpansionDistance; expanded = true; } // Check if hero is near right edge if (mapBounds.right - hero.x < edgeBuffer) { mapBounds.right += mapExpansionDistance; expanded = true; } // Check if hero is near top edge if (hero.y - mapBounds.top < edgeBuffer) { mapBounds.top -= mapExpansionDistance; expanded = true; } // Check if hero is near bottom edge if (mapBounds.bottom - hero.y < edgeBuffer) { mapBounds.bottom += mapExpansionDistance; expanded = true; } if (expanded) { // Visual feedback for map expansion LK.effects.flashScreen(0x4169e1, 300); } } function checkWaveComplete() { // Special handling for wave 5 to 6 transition - only advance when boss dies if (wave === 5) { // Wave 5 to 6 transition is handled in boss.die() method only return; } // Special handling for wave 10 - need 30 kills to advance var killsNeeded = wave === 10 ? 30 : 30; if (enemyKills >= killsNeeded) { // Start next wave after required kills wave++; // Create GLM wave announcement for regular wave transitions only if none exists if (!existingGlmInstance || existingGlmInstance.destroyed) { var glmAnnouncement = new GLMWaveAnnouncement(); existingGlmInstance = glmAnnouncement; // Track this instance game.addChild(glmAnnouncement); glmAnnouncement.startAnimation(); } // Increase enemies' HP and speed by 20% (multiply by 1.2) at the start of each new wave difficultyScaling.healthMultiplier *= 1.2; difficultyScaling.skeletonSpeedMultiplier *= 1.2; // Scale existing enemies on the field for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; // Scale HP by 1.2x enemy.health = Math.floor(enemy.health * 1.2); enemy.maxHealth = Math.floor(enemy.maxHealth * 1.2); // Scale speed by 1.2x enemy.speed *= 1.2; } // Increase hero contact damage rate by 1.2x each wave heroContactDamageRate *= 1.2; enemiesInWave += 2; enemiesSpawned = 0; enemyKills = 0; // Reset kill counter for next wave killsText.setText('Enemies Killed: ' + enemyKills); bossActive = false; // Reset boss active flag for next wave // Keep crystal tower HP fixed at 500 - no recalculation needed // Re-enable hero shooting for wave 6 and beyond (after first boss death) if (wave >= 6) { heroCanShoot = true; } waveText.setText('Wave: ' + wave); if (wave > 100) { LK.showYouWin(); } } } game.down = function (x, y, obj) { // Check for middle mouse button to start panning if (obj.event && obj.event.button === 1) { isPanning = true; panStartX = x; panStartY = y; panLastX = x; panLastY = y; return; } if (placingTowerType) { // Check if position is valid (not too close to crystal tower or other towers) var tooClose = false; var dx = x - crystalTower.x; var dy = y - crystalTower.y; if (Math.sqrt(dx * dx + dy * dy) < 200) { tooClose = true; } for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var dx = x - tower.x; var dy = y - tower.y; if (Math.sqrt(dx * dx + dy * dy) < 120) { tooClose = true; break; } } if (!tooClose) { var newTower = new Tower(placingTowerType); newTower.x = x; newTower.y = y; game.addChild(newTower); towers.push(newTower); gold -= placingTowerCost; goldText.setText('Gold: ' + gold); LK.getSound('towerPlace').play(); // Update archer tower count and button display if (placingTowerType === 'archer') { archerTowerCount++; archerTowerLastCost = placingTowerCost; var nextCost = Math.floor(archerTowerLastCost * 1.15); archerButton.setText('Archer\n' + nextCost + 'g'); } } placingTowerType = null; placingTowerCost = 0; } else { // Check if clicking on hero to start drag - expanded hitbox 4x larger than original visual area var dx = x - hero.x; var dy = y - hero.y; // Hero asset is 125x156, expand hitbox to 4x the size (500x624 total area) var heroWidth = 125 * 2; // 4x area means 2x width/height var heroHeight = 156 * 2; if (Math.abs(dx) < heroWidth / 2 && Math.abs(dy) < heroHeight / 2) { draggedHero = true; } } }; game.move = function (x, y, obj) { if (isPanning) { // Calculate pan delta var deltaX = x - panLastX; var deltaY = y - panLastY; // Update map offset mapOffsetX += deltaX; mapOffsetY += deltaY; // Apply pan offset to game container game.x = mapOffsetX; game.y = mapOffsetY; // Update last position panLastX = x; panLastY = y; } else if (draggedHero) { hero.x = x - mapOffsetX; hero.y = y - mapOffsetY; } }; game.up = function (x, y, obj) { if (isPanning) { isPanning = false; } draggedHero = false; }; game.update = function () { // Determine boss spawn requirements based on wave var shouldSpawnBoss = false; var bossCount = 0; var shouldSpawnRegularEnemies = true; if (wave === 5 && !bossActive && enemiesSpawned === 0) { shouldSpawnBoss = true; bossCount = 1; shouldSpawnRegularEnemies = false; // Only boss on wave 5 } else if (wave === 10 && !bossActive && enemiesSpawned === 0) { shouldSpawnBoss = true; bossCount = 2; shouldSpawnRegularEnemies = true; // Bosses plus regular enemies on wave 10 } else if (wave === 15 && !bossActive && enemiesSpawned === 0) { shouldSpawnBoss = true; bossCount = 1; // One boss plus regular enemies shouldSpawnRegularEnemies = true; // Both bosses and regular enemies } else if (wave === 100 && !bossActive && enemiesSpawned === 0) { // Special wave 100: spawn main character as boss shouldSpawnBoss = true; bossCount = 1; shouldSpawnRegularEnemies = false; } if (shouldSpawnBoss) { // Create GLM wave announcement for boss waves only if none exists if (!existingGlmInstance || existingGlmInstance.destroyed) { var glmAnnouncement = new GLMWaveAnnouncement(); existingGlmInstance = glmAnnouncement; // Track this instance game.addChild(glmAnnouncement); glmAnnouncement.startAnimation(); } for (var bossIndex = 0; bossIndex < bossCount; bossIndex++) { var boss; if (wave === 100) { // Create main character as enemy with 10,000 HP boss = new Boss(); boss.health = 10000; boss.maxHealth = 10000; boss.goldValue = 1000; // Make it look like the hero boss.removeChild(boss.attachedAssets[0]); // Remove boss graphics var heroGraphics = boss.attachAsset('hero', { anchorX: 0.5, anchorY: 1 }); heroGraphics.tint = 0x800000; // Dark red tint to show it's evil heroGraphics.scaleX = 2; // Make it larger heroGraphics.scaleY = 2; } else { boss = new Boss(); } // Spawn from random edge using dynamic map boundaries var side = Math.floor(Math.random() * 4); if (side === 0) { // Top boss.x = mapBounds.left + Math.random() * (mapBounds.right - mapBounds.left); boss.y = mapBounds.top; } else if (side === 1) { // Right boss.x = mapBounds.right; boss.y = mapBounds.top + Math.random() * (mapBounds.bottom - mapBounds.top); } else if (side === 2) { // Bottom boss.x = mapBounds.left + Math.random() * (mapBounds.right - mapBounds.left); boss.y = mapBounds.bottom; } else { // Left boss.x = mapBounds.left; boss.y = mapBounds.top + Math.random() * (mapBounds.bottom - mapBounds.top); } game.addChild(boss); enemies.push(boss); enemiesSpawned++; // Count boss as spawned enemy } bossActive = true; // Flash screen to indicate boss spawn LK.effects.flashScreen(0x800080, 1000); // Increase difficulty scaling after each boss round difficultyScaling.healthMultiplier += 0.15; // 15% health increase per round difficultyScaling.skeletonSpeedMultiplier += 0.08; // 8% speed increase for skeletons per round } // Spawn regular enemies continuously - always keep enemies on the field // Only spawn regular enemies if it's not a boss-only wave (5) or if it's a mixed wave (10, 15) var shouldSpawnRegularNow = true; if (wave === 5 && !bossActive) { shouldSpawnRegularNow = false; // Don't spawn regular enemies on boss-only wave 5 before boss spawns } else if (wave === 5 && bossActive) { shouldSpawnRegularNow = false; // Don't spawn regular enemies on boss-only wave 5 after boss spawns } // Set proper enemy count for wave 10 var currentWaveEnemyLimit = enemiesInWave; if (wave === 10) { currentWaveEnemyLimit = 30; // Total of 30 enemies including 2 bosses } if (shouldSpawnRegularNow && (enemiesSpawned < currentWaveEnemyLimit || enemies.length < 3)) { enemySpawnTimer += gameSpeed; if (enemySpawnTimer >= 60) { // Spawn every 1 second for continuous pressure spawnEnemy(); enemySpawnTimer = 0; } } // Update health display healthText.setText('Tower HP: ' + crystalTower.health); // Update boss health bar var currentBoss = null; for (var i = 0; i < enemies.length; i++) { if (enemies[i].health === undefined || enemies[i].maxHealth === undefined) continue; if (enemies[i].maxHealth >= 800) { // Boss has 800+ max health currentBoss = enemies[i]; break; } } if (currentBoss && bossActive) { bossHealthBar.visible = true; bossHealthFill.visible = true; bossHealthText.visible = true; var healthPercent = currentBoss.health / currentBoss.maxHealth; bossHealthFill.scaleX = 4 * healthPercent; bossHealthText.setText('BOSS - ' + currentBoss.health + '/' + currentBoss.maxHealth); } else { bossHealthBar.visible = false; bossHealthFill.visible = false; bossHealthText.visible = false; } // Check for wave 10 boss death logic - if one boss dies, kill the other if (wave === 10 && bossActive) { var aliveBosses = []; for (var i = 0; i < enemies.length; i++) { if (enemies[i].maxHealth >= 800) { aliveBosses.push(enemies[i]); } } // If we had 2 bosses but now only have 1, kill the remaining boss if (aliveBosses.length === 1) { aliveBosses[0].die(); } } // Check for enemies in contact with crystal tower for continuous damage var currentEnemiesInContact = []; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - crystalTower.x; var dy = enemy.y - crystalTower.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 80) { // Contact threshold currentEnemiesInContact.push(enemy); } } // Apply contact damage every second (60 ticks) contactDamageTimer += gameSpeed; if (contactDamageTimer >= 60 && currentEnemiesInContact.length > 0) { crystalTower.takeDamage(2 * currentEnemiesInContact.length); contactDamageTimer = 0; } // Update enemies in contact array enemiesInContact = currentEnemiesInContact; // Check for enemies in contact with hero for continuous damage var currentEnemiesInContactWithHero = []; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - hero.x; var dy = enemy.y - hero.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 60) { // Contact threshold for hero currentEnemiesInContactWithHero.push(enemy); } } // Apply hero contact damage every second (60 ticks) heroContactDamageTimer += gameSpeed; if (heroContactDamageTimer >= 60 && currentEnemiesInContactWithHero.length > 0 && !heroCollapsed) { var damageAmount = Math.floor(heroContactDamageRate * currentEnemiesInContactWithHero.length); heroHealth -= damageAmount; // Flash hero red when taking contact damage LK.effects.flashObject(hero, 0xff0000, 300); heroContactDamageTimer = 0; } // Update enemies in contact with hero array enemiesInContactWithHero = currentEnemiesInContactWithHero; // Check for hero contact with glm asset for one-time reward if (!heroGlmContactRewardGiven) { for (var glmIndex = 0; glmIndex < game.children.length; glmIndex++) { var child = game.children[glmIndex]; // Check if this child has glm asset attached if (child && child.attachedAssets && child.attachedAssets.length > 0) { for (var assetIndex = 0; assetIndex < child.attachedAssets.length; assetIndex++) { var asset = child.attachedAssets[assetIndex]; if (asset && asset.asset && asset.asset.id === 'glm') { var dx = child.x - hero.x; var dy = child.y - hero.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 60) { // Contact threshold // Award 300 gold for first-time contact heroGlmContactRewardGiven = true; gold += 300; goldText.setText('Gold: ' + gold); // Visual feedback for reward LK.effects.flashObject(hero, 0xFFD700, 1000); LK.effects.flashScreen(0xFFD700, 500); LK.getSound('coinCollect').play(); break; } } } } if (heroGlmContactRewardGiven) break; // Exit outer loop if reward given } } // Check for hero collecting coins for (var i = goldCoins.length - 1; i >= 0; i--) { var coin = goldCoins[i]; var dx = coin.x - hero.x; var dy = coin.y - hero.y; if (Math.sqrt(dx * dx + dy * dy) < 40) { coin.collect(); } } // Add random battlefield explosions during intense moments if (enemies.length > 8 && LK.ticks % Math.max(1, Math.floor(180 / gameSpeed)) === 0) { var explosion = new ExplosionEffect(); explosion.x = mapBounds.left + 200 + Math.random() * (mapBounds.right - mapBounds.left - 400); explosion.y = mapBounds.top + 200 + Math.random() * (mapBounds.bottom - mapBounds.top - 400); game.addChild(explosion); } // Add red alert glow when tower health is critical if (crystalTower.health < crystalTower.maxHealth * 0.2 && LK.ticks % Math.max(1, Math.floor(60 / gameSpeed)) === 0) { LK.effects.flashScreen(0x8b0000, 200); } // Check for map expansion based on hero position expandMapIfNeeded(); // Start background music on loop LK.playMusic('med'); // Check if hero HP drops below 1 for game over if (heroHealth < 1) { LK.showGameOver(); } // Update hero HP bar position to follow hero heroHealthBar.x = hero.x; heroHealthBar.y = hero.y + 40; // Position below hero // Update hero HP bar display var heroHealthPercent = Math.max(0, heroHealth) / heroMaxHealth; heroHealthFill.scaleX = 1.0 * heroHealthPercent; heroHealthText.setText(Math.max(0, heroHealth) + '/' + heroMaxHealth); // Change HP bar color based on health if (heroHealthPercent > 0.6) { heroHealthFill.tint = 0x00ff00; // Green } else if (heroHealthPercent > 0.3) { heroHealthFill.tint = 0xffff00; // Yellow } else { heroHealthFill.tint = 0xff0000; // Red } // Power scaling every 5 waves - crystal tower HP remains fixed at 500 if (wave > 0 && wave % 5 === 0) { // Check if we haven't already applied scaling for this wave milestone if (!scalingApplied || scalingApplied < wave) { // Increase hero attack power hero.damage *= 1.2; // 20% increase each 5 waves // Crystal tower HP stays fixed at 500, but scale damage only crystalTower.damage = Math.floor(crystalTower.damage * 5); // Multiply all existing towers' damage by 5 for (var i = 0; i < towers.length; i++) { towers[i].damage = Math.floor(towers[i].damage * 5); } scalingApplied = wave; } } // Update archer button color based on gold availability var dynamicCost = Math.floor(archerTowerLastCost * 1.15); if (gold >= dynamicCost) { // Light yellow when affordable tween.stop(archerButton, { tint: true }); // Stop any existing color tween archerButton.setColor(0xffff99); // Light yellow } else { // Dark yellow when not affordable tween.stop(archerButton, { tint: true }); // Stop any existing color tween archerButton.setColor(0xb8860b); // Dark yellow } // Update HP upgrade button color based on gold availability if (gold >= 150) { // Light green when affordable tween.stop(hpUpgradeButton, { tint: true }); hpUpgradeButton.setColor(0x90ee90); // Light green } else { // Dark green when not affordable tween.stop(hpUpgradeButton, { tint: true }); hpUpgradeButton.setColor(0x228b22); // Dark green } // Update range upgrade button color based on gold availability if (gold >= 250) { // Light blue when affordable tween.stop(rangeUpgradeButtonTowers, { tint: true }); rangeUpgradeButtonTowers.setColor(0x87ceeb); // Light blue } else { // Dark blue when not affordable tween.stop(rangeUpgradeButtonTowers, { tint: true }); rangeUpgradeButtonTowers.setColor(0x4169e1); // Dark blue } checkWaveComplete(); }; // Override LK.getSound to respect mute state var originalGetSound = LK.getSound; LK.getSound = function (soundId) { var sound = originalGetSound(soundId); var originalPlay = sound.play; sound.play = function () { if (!soundMuted) { originalPlay.call(sound); } }; return sound; };
===================================================================
--- original.js
+++ change.js
@@ -888,8 +888,12 @@
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
+ // Clear global reference when this GLM instance is destroyed
+ if (existingGlmInstance === self) {
+ existingGlmInstance = null;
+ }
self.destroy();
}
});
}, self.onScreenDuration);
@@ -1840,8 +1844,9 @@
var heroContactDamageTimer = 0;
var enemiesInContactWithHero = [];
var heroGlmContactRewardGiven = false; // Track if hero has received glm contact reward
var goldDropBonusApplied = false; // Track if 50% gold drop bonus has been applied after wave 5
+var existingGlmInstance = null; // Track existing GLM instance to ensure only one exists
// Game speed control
var gameSpeed = 1.0;
var gameSpeedOptions = [1.0, 1.5, 2.0, 3.0, 5.0];
var currentSpeedIndex = 0;
@@ -2258,12 +2263,15 @@
}
// Update UI
waveText.setText('Wave: ' + wave);
killsText.setText('Enemies Killed: ' + enemyKills);
- // Create GLM wave announcement for wave selection
- var glmAnnouncement = new GLMWaveAnnouncement();
- game.addChild(glmAnnouncement);
- glmAnnouncement.startAnimation();
+ // Create GLM wave announcement for wave selection only if none exists
+ if (!existingGlmInstance || existingGlmInstance.destroyed) {
+ var glmAnnouncement = new GLMWaveAnnouncement();
+ existingGlmInstance = glmAnnouncement; // Track this instance
+ game.addChild(glmAnnouncement);
+ glmAnnouncement.startAnimation();
+ }
// Flash screen to indicate wave change
LK.effects.flashScreen(0x4b0082, 500);
};
waveButtons.push(waveButton);
@@ -2415,12 +2423,15 @@
var killsNeeded = wave === 10 ? 30 : 30;
if (enemyKills >= killsNeeded) {
// Start next wave after required kills
wave++;
- // Create GLM wave announcement for regular wave transitions
- var glmAnnouncement = new GLMWaveAnnouncement();
- game.addChild(glmAnnouncement);
- glmAnnouncement.startAnimation();
+ // Create GLM wave announcement for regular wave transitions only if none exists
+ if (!existingGlmInstance || existingGlmInstance.destroyed) {
+ var glmAnnouncement = new GLMWaveAnnouncement();
+ existingGlmInstance = glmAnnouncement; // Track this instance
+ game.addChild(glmAnnouncement);
+ glmAnnouncement.startAnimation();
+ }
// Increase enemies' HP and speed by 20% (multiply by 1.2) at the start of each new wave
difficultyScaling.healthMultiplier *= 1.2;
difficultyScaling.skeletonSpeedMultiplier *= 1.2;
// Scale existing enemies on the field
@@ -2556,12 +2567,15 @@
bossCount = 1;
shouldSpawnRegularEnemies = false;
}
if (shouldSpawnBoss) {
- // Create GLM wave announcement for boss waves
- var glmAnnouncement = new GLMWaveAnnouncement();
- game.addChild(glmAnnouncement);
- glmAnnouncement.startAnimation();
+ // Create GLM wave announcement for boss waves only if none exists
+ if (!existingGlmInstance || existingGlmInstance.destroyed) {
+ var glmAnnouncement = new GLMWaveAnnouncement();
+ existingGlmInstance = glmAnnouncement; // Track this instance
+ game.addChild(glmAnnouncement);
+ glmAnnouncement.startAnimation();
+ }
for (var bossIndex = 0; bossIndex < bossCount; bossIndex++) {
var boss;
if (wave === 100) {
// Create main character as enemy with 10,000 HP
izometric cannon tower. In-Game asset. 2d. High contrast. No shadows. izometric
archerTower. In-Game asset. 2d. High contrast. No shadows
goblin. In-Game asset. 2d. High contrast. No shadows
goldCoin. In-Game asset. 2d. High contrast. No shadows
Archer hero. In-Game asset. 2d. High contrast. No shadows
skeleton. In-Game asset. 2d. High contrast. No shadows
troll. In-Game asset. 2d. High contrast. No shadows
single arrow image. In-Game asset. 2d. High contrast. No shadows
Create a flying dragon enemy with the following features:. In-Game asset. 2d. High contrast. No shadows
Here’s a prompt for a very distant, wide-angle view of a tree-free forest floor: **Prompt:** A very distant, wide-angle aerial view of a tree-free forest floor, showing expansive grassy plains with patches of dirt, scattered rocks, and low vegetation. The terrain stretches far into the horizon with subtle color variations and soft natural lighting, creating a vast, open, and serene natural environment without any trees.. In-Game asset. 2d. High contrast. No shadows
A stylized full-body illustration of a small hobbit holding a glowing ring in one hand, viewed from a 45-degree angle. The hobbit has curly hair, bare feet, and wears rustic, earth-toned clothing with detailed textures. The scene has warm, soft lighting emphasizing the character’s expressive face and the shining ring. The art style is cartoonish with rich colors, smooth shading, and a fantasy atmosphere.. In-Game asset. 2d. High contrast. No shadows
A magical yellow light glowing softly, with radiant beams and sparkling particles floating around. The light has a warm, enchanting aura that illuminates its surroundings with a golden hue. The atmosphere feels mystical and inviting, perfect for fantasy scenes or magical effects.. In-Game asset. 2d. High contrast. No shadows
A full-body stylized illustration of Smeagol (Gollum), showing his thin, hunched frame and large expressive eyes. He is barefoot and shirtless, wearing ragged shorts, with exaggerated cartoonish features that highlight his creepy yet pitiful nature. He clutches a glowing precious ring tightly in one hand. The art style is dark fantasy with vibrant colors, detailed skin textures, and a shadowy, mysterious background to enhance the eerie atmosphere. Perfect for full-character concept art or game design.. In-Game asset. 2d. High contrast. No shadows
A full-body stylized illustration of an orc warrior, standing in a dynamic pose. The orc has green or grayish skin, muscular build, tusks, and tribal armor made of bone, leather, and metal. The style is fantasy-themed with bold lines, exaggerated proportions, and detailed textures. The lighting is dramatic, emphasizing the orc’s strength and menace. Background is minimal or softly blurred to keep focus on the character. Suitable for fantasy RPG game concept art.. In-Game asset. 2d. High contrast. No shadows
A stylized fantasy axe with a broad, curved blade and intricate engravings. The handle is wrapped in worn leather, and the metal has a slightly weathered look, giving it a battle-worn feel. The design is bold and exaggerated, suitable for an orc warrior, with a glowing rune etched into the blade. The style is high-fantasy with clean lines, vibrant highlights, and a dramatic shadow for depth. Perfect for 2D game assets or concept art.. In-Game asset. 2d. High contrast. No shadows