User prompt
Add knock sound for Tesla Tower
User prompt
Add sound to build towers
User prompt
Please apply the following final gameplay improvements and polish features to elevate the game experience: --- 1. 🔁 ENEMY PHASE-OUT LOGIC Enemies should stop appearing after certain waves to avoid overly easy late-game encounters. Use this spawn and phase-out system: • Enemy 1 → Appears at Wave 1, stops after Wave 5 • Enemy 2 → Appears at Wave 4, stops after Wave 8 • Enemy 3 → Appears at Wave 7, stops after Wave 12 • Enemy 4 → Appears at Wave 10, stops after Wave 16 • Enemy 5 → Appears at Wave 13, stops after Wave 20 • Enemy 6 → Appears at Wave 16, stops after Wave 24 • Enemy 7 → Appears at Wave 19, stops after Wave 28 • Enemy 8 → Appears at Wave 22, stops after Wave 32 • Enemy 9 → Appears at Wave 25, stops after Wave 36 • Enemy 10 → Appears at Wave 28, stops after Wave 40 (Continue the same logic: each enemy lasts for ~8–12 waves) • Enemies 26–30 (final tier) should continue to appear until the end of the game once unlocked. --- 2. 💰 COIN DROP TEXT When an enemy is defeated: - Show a floating gold gain indicator at their position (e.g., “+10”) - Text should fade out and disappear smoothly. --- 3. 🔊 SOUND EFFECTS Add the following sound effects: • Tower placed → “placement” sound • Tower upgraded → “upgrade” sound • Tower fires → shooting effect (unique if possible) • Coin gained → small pop or sparkle Make sure volume levels are balanced and not distracting. --- 4. ♾️ ENDLESS MODE (after wave 100) - After wave 100, unlock **Endless Mode**: • Enemies keep coming with increased HP and speed every 5 waves (+10% HP, +5% speed per 5 waves) • Add a score counter to track how long the player survives past wave 100 • Optional: show a leaderboard placeholder UI or a “NEW RECORD!” banner when the run ends --- 5. 📜 SHORT STORY INTRO (before game starts) - When player clicks “Start Game,” show a short atmospheric story as an overlay (like floating clouds or typing effect). - English text only. Keep it brief and immersive. - Example story text: “The land is on the brink of ruin. Shadows rise from the east, and only your towers stand in their way. You must defend the kingdom wave after wave, until the last breath of hope remains...” - Allow the player to skip it by clicking or pressing any key. After 4–6 seconds, it fades out and the game begins. --- These changes will dramatically increase the quality, clarity, and depth of the experience — especially for competitions or showcases. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please update the Credits section: 1. Remove all references to: - ChatGPT - Grok - Any AI assistant or OpenAI mention 2. The only visible name in the credits should be: - Game Design: ilker.mez (or the name I input) 3. Ensure there are no hidden or background credits to AI services, as this is required for competition compliance. Thank you!
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Reduce damage from all towers by 30%
User prompt
Slow down all towers' firing rate by 35%
User prompt
Slow down turrets' fire rate by 35%
User prompt
Reduce tower damage by 35%
User prompt
Let's increase the frost tower's enemy slowing by 30% and reduce its damage by 20%
User prompt
The frost tower has no slowing effect. Each enemy it hits will have their walking speed reduced by 40%. The enemy will regain their previous speed 2 seconds after leaving the frost tower's range.
User prompt
The slowing effect of the ice tower on the enemy it hits will be increased by 40% and the damage of the ice tower will be reduced by 90%.
User prompt
Reduce the rate of incoming gold from enemies by 30%
User prompt
Starting money is 160 gold
User prompt
Please fix the bug: 'Uncaught ReferenceError: infantryBtn is not defined' in or related to this line: 'var towers = [{' Line Number: 3418
User prompt
Please fix the bug: 'Uncaught ReferenceError: infantryBtn is not defined' in or related to this line: 'var towers = [{' Line Number: 3418
User prompt
Please implement both tower unlocks and tower upgrade level restrictions based on wave progression, using the following structure: --- 1. 🗼 TOWER UNLOCK WAVES: Each tower becomes available at a specific wave: • Tower 1: Archer Tower → Unlocked at Wave 1 • Tower 2: Freeze Tower → Unlocked at Wave 3 • Tower 3: Anti-Air Tower → Unlocked at Wave 8 • Tower 4: Poison Tower → Unlocked at Wave 15 • Tower 5: Tesla Tower → Unlocked at Wave 25 - Before these waves, the towers must appear **greyed out / inactive** in the tower selection menu. - If the player clicks a locked tower, display a message like: **"This tower will unlock at Wave 15."** - Once a tower is unlocked, it stays available for the rest of the game. --- 2. 🔼 TOWER UPGRADE LEVEL RESTRICTIONS: Each upgrade tier is limited by the wave number. This prevents players from maxing towers too early. Use the following system: • Upgrade Level 1 → Available from Wave 1 • Upgrade Level 2 → Wave 3 • Upgrade Level 3 → Wave 6 • Upgrade Level 4 → Wave 10 • Upgrade Level 5 → Wave 14 • Upgrade Level 6 → Wave 20 • Upgrade Level 7 → Wave 30 • Upgrade Level 8 → Wave 40 • Upgrade Level 9 → Wave 50 • Upgrade Level 10 → Wave 60 - If a player tries to upgrade beyond the allowed level, block the upgrade and display a message: **"Upgrade level X is locked until Wave Y."** --- 3. 📌 Summary: - Combine both systems: → Towers are unlocked progressively. → Upgrades are also unlocked progressively. - Both systems should be clearly communicated to the player via UI text or tooltips. This ensures fair pacing, prevents overpowering in early waves, and rewards long-term progression.
User prompt
In the tower upgrade panel, the name of the tower currently appears as the heading (e.g., “Soldier Tower”). Please replace this title with a clear and action-focused label: 1. Change the title text to simply: **“Upgrade”** 2. This helps make it more obvious that the panel is interactive and upgrade-focused. 3. Remove or hide the actual tower name from the upgrade panel. This improves clarity and encourages player interaction.
User prompt
The text size inside the tower upgrade panel has been increased, which is great, but the background panel is now misaligned: - The background box/panel that appears behind the upgrade text is currently positioned **too low**. - As a result, the text appears **above or outside the visible panel**, making it look broken or off-center. Please fix this by: 1. Repositioning the panel background so that it **perfectly aligns with the visible text** and buttons. 2. The text and all UI elements should appear **centered and properly enclosed** within the panel. 3. Ensure this works for all tower types and screen sizes. This will restore UI clarity and make the upgrade interface look clean and polished.
User prompt
Please apply the following gameplay and UI updates to improve tower logic and interface usability: --- 1. **Flying Enemy Rules:** - Flying enemies should **only be targetable by Anti-Air Towers**. - All other towers (Soldier, AoE, Poison, Tesla) must ignore flying enemies completely. 2. **Freeze Tower Rule:** - Freeze (slow) towers should be used **only to slow down enemies**. - Enemies frozen by this tower should be **temporarily immune to attacks from other towers**, unless hit by Freeze tower itself. --- 3. **Upgrade Panel Size:** - When clicking on a tower, the upgrade panel that appears is currently too small. - Increase the **panel size** and **text size** so that all upgrade information is clearly readable on all devices. --- 4. **Sell Tower Option:** - Add a new button inside the upgrade panel: **“Sell Tower”** - When clicked: • The tower is removed from the field. • The player is refunded **50% of the total cost** invested in that tower (including all upgrades). • This refund is granted in gold and should be displayed on the screen briefly. - Make sure the sell button is clearly visible and positioned consistently across all towers. --- These changes will improve clarity of tower interactions, make UI easier to use, and give the player more flexibility during gameplay.
User prompt
There is a visual issue with some Goblin enemies: - Some Goblins appear with the correct sprite/skin, but others appear as blank squares or placeholder boxes. - This suggests that the enemy is using a skin randomization system or a sprite assignment function, but some of the visual assets are missing or not properly referenced. Please fix this by: 1. Ensuring that **every instance of the Goblin enemy** has a valid and correctly loaded visual/sprite. 2. If you're using multiple Goblin variants (skins), make sure **all of them are fully implemented** and **all file references are correct**. 3. If a sprite fails to load, use a **default fallback Goblin skin** instead of showing a box or null object. This should fix the inconsistent appearance issue.
User prompt
Please implement the following full balancing system for towers, upgrades, and enemies. The game should feel challenging but fair, and allow players to progress with proper strategy. --- 🛡️ ENEMY BALANCING - Total enemies: 30 types - Each new enemy appears every 3 waves (Wave 1 → Enemy 1, Wave 4 → Enemy 2, ..., Wave 88 → Enemy 30) - Older enemies continue to appear in mixed waves Define each enemy with: • Health (HP): - Enemy 1: 100 HP - Each next enemy gains +60 HP over the previous (e.g., Enemy 2 = 160, Enemy 3 = 220, ..., Enemy 30 = 1840) • Speed: - Enemies 1–10: 1.2 to 1.8 units/sec - Enemies 11–20: 1.8 to 2.4 units/sec - Enemies 21–30: 2.4 to 3.0 units/sec (Use a curve for smooth transition; boss-like enemies may be slower but tankier) --- 🏰 WAVE SIZE SYSTEM - Waves 1–5: 15–20 enemies - Waves 6–15: 20–25 enemies - Waves 16–30: 25–35 enemies - Waves 31–60: 35–45 enemies - Waves 61–97: 45–55 enemies - Waves 98–100: 60–75 enemies Randomize each wave within the range. --- 🗼 TOWER SYSTEM (5 Total Types) Each tower has 10 upgrades. Each upgrade costs: • Level 1: base price • Level 2: +80% base • Level 3: +80% base ... and so on. (Damage and range scale separately) All towers start with a base range of **150 units**, and upgrade range increases as follows: - Upgrade 1: +10% - Upgrade 2: +5% - Upgrade 3: +3.5% - Upgrades 4–10: +2% per level --- ### 1. SOLDIER TOWER (Rapid physical attack) - Base Cost: 80 gold - Attack Speed: 0.8 shots/sec - Base Damage: 25 - Damage increase per level: +10 flat (upgrade 1 = 35 dmg, upgrade 2 = 45 dmg, ...) --- ### 2. AOE + SLOW TOWER (Area + Debuff) - Base Cost: 120 gold - Attack Speed: 1 shot/2 seconds - Base Damage: 40 (AoE radius: 120 units) - Damage increase per level: +12 - Enemies hit are slowed by: • Level 1: 15% • Level 2–3: 25% • Level 4+: 35% --- ### 3. ANTI-AIR TOWER (Only hits flying enemies) - Base Cost: 100 gold - Attack Speed: 1.5 shots/sec - Base Damage: 30 - Damage increase per level: +8 - Effective only vs flying enemies (introduced mid to late game) --- ### 4. POISON TOWER (Damage over time) - Base Cost: 130 gold - Attack Speed: 1.2 shots/sec - Base Damage (per tick): 10 over 3 seconds - Tick interval: 1s (i.e., 10 + 10 + 10) - Upgrade increases DoT duration and damage: • +1s duration and +5/tick per level • Max at level 10: 10s, 55/tick --- ### 5. TESLA TOWER (Chain lightning) - Base Cost: 150 gold - Attack Speed: 0.7 shots/sec - Base Damage: 35 - Chain to 3 enemies (increasing to 6 at max level) - Damage per chain target reduces by 10% per jump - Damage increase per level: +10 base, +1 chain target every 2 levels --- 🏁 Final Notes: - All towers should feel meaningful. Upgrade cost must be calculated and deducted properly. - Enemies should scale in a way that high-tier towers are required in mid-late game. - The game should be beatable with good strategy but become increasingly intense. Please use the following enemy stats exactly as listed. Do not auto-scale or generate values. Each enemy has a specific wave appearance, HP, and speed that must be preserved. Enemy 1: Goblin Scout - Appears at Wave 1 - HP: 100 - Speed: 1.2 Enemy 2: Mire Crawler - Appears at Wave 4 - HP: 160 - Speed: 1.3 Enemy 3: Fang Imp - Appears at Wave 7 - HP: 220 - Speed: 1.4 Enemy 4: Crimson Leaper - Appears at Wave 10 - HP: 280 - Speed: 1.5 Enemy 5: Rustback Beetle - Appears at Wave 13 - HP: 340 - Speed: 1.6 Enemy 6: Gravel Hound - Appears at Wave 16 - HP: 400 - Speed: 1.7 Enemy 7: Vine Stalker - Appears at Wave 19 - HP: 460 - Speed: 1.8 Enemy 8: Ashborne Wraith - Appears at Wave 22 - HP: 520 - Speed: 1.9 Enemy 9: Stonehide Orc - Appears at Wave 25 - HP: 580 - Speed: 2.0 Enemy 10: Toxic Maw - Appears at Wave 28 - HP: 640 - Speed: 2.0 Enemy 11: Frostback Boar - Appears at Wave 31 - HP: 700 - Speed: 2.1 Enemy 12: Ironmaw Troll - Appears at Wave 34 - HP: 760 - Speed: 2.1 Enemy 13: Volcanic Strider - Appears at Wave 37 - HP: 820 - Speed: 2.2 Enemy 14: Stormblade Knight - Appears at Wave 40 - HP: 880 - Speed: 2.2 Enemy 15: Twilight Serpent - Appears at Wave 43 - HP: 940 - Speed: 2.3 Enemy 16: Obsidian Colossus - Appears at Wave 46 - HP: 1000 - Speed: 2.3 Enemy 17: Spectral Ravager - Appears at Wave 49 - HP: 1060 - Speed: 2.4 Enemy 18: Boneclad Warbeast - Appears at Wave 52 - HP: 1120 - Speed: 2.4 Enemy 19: Plague Titan - Appears at Wave 55 - HP: 1180 - Speed: 2.5 Enemy 20: Abyss Reaper - Appears at Wave 58 - HP: 1240 - Speed: 2.5 Enemy 21: Wraith Bringer - Appears at Wave 61 - HP: 1300 - Speed: 2.6 Enemy 22: Inferno Crawler - Appears at Wave 64 - HP: 1360 - Speed: 2.6 Enemy 23: Thornback Hydra - Appears at Wave 67 - HP: 1420 - Speed: 2.7 Enemy 24: Ghostfire Shade - Appears at Wave 70 - HP: 1480 - Speed: 2.7 Enemy 25: Storm Herald - Appears at Wave 73 - HP: 1540 - Speed: 2.8 Enemy 26: Voidling Runner - Appears at Wave 76 - HP: 1600 - Speed: 2.8 Enemy 27: Doom Shell - Appears at Wave 79 - HP: 1660 - Speed: 2.9 Enemy 28: Nether Juggernaut - Appears at Wave 82 - HP: 1720 - Speed: 2.9 Enemy 29: Abyss Howler - Appears at Wave 85 - HP: 1780 - Speed: 3.0 Enemy 30: Void Devourer - Appears at Wave 88 - HP: 1840 - Speed: 3.0
User prompt
The last waterfall we placed should be positioned 100 pixels more to the right and 400 pixels more upwards.
User prompt
Place a waterfall 400 pixels above the bottom right of the map
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Enemy = Container.expand(function (type, wave, pathIndex, strengthLevel) { var self = Container.call(this); self.type = type; self.wave = wave; self.pathIndex = pathIndex || 1; self.strengthLevel = strengthLevel || 1; self.currentPath = self.pathIndex === 1 ? enemyPath1 : self.pathIndex === 2 ? enemyPath2 : enemyPath3; // Define base stats for each enemy type with exact balancing specifications var baseStats = { // 30 enemies with specific wave appearance, HP, and speed goblin_scout: { speed: 1.2, health: 100, coins: 15, flying: false, waveAppearance: 1 }, mire_crawler: { speed: 1.3, health: 160, coins: 18, flying: false, waveAppearance: 4 }, fang_imp: { speed: 1.4, health: 220, coins: 22, flying: false, waveAppearance: 7 }, crimson_leaper: { speed: 1.5, health: 280, coins: 25, flying: true, waveAppearance: 10 }, rustback_beetle: { speed: 1.6, health: 340, coins: 28, flying: false, waveAppearance: 13 }, gravel_hound: { speed: 1.7, health: 400, coins: 32, flying: false, waveAppearance: 16 }, vine_stalker: { speed: 1.8, health: 460, coins: 35, flying: false, waveAppearance: 19 }, ashborne_wraith: { speed: 1.9, health: 520, coins: 38, flying: true, waveAppearance: 22 }, stonehide_orc: { speed: 2.0, health: 580, coins: 42, flying: false, waveAppearance: 25 }, toxic_maw: { speed: 2.0, health: 640, coins: 45, flying: false, waveAppearance: 28 }, frostback_boar: { speed: 2.1, health: 700, coins: 48, flying: false, waveAppearance: 31 }, ironmaw_troll: { speed: 2.1, health: 760, coins: 52, flying: false, waveAppearance: 34 }, volcanic_strider: { speed: 2.2, health: 820, coins: 55, flying: false, waveAppearance: 37 }, stormblade_knight: { speed: 2.2, health: 880, coins: 58, flying: false, waveAppearance: 40 }, twilight_serpent: { speed: 2.3, health: 940, coins: 62, flying: true, waveAppearance: 43 }, obsidian_colossus: { speed: 2.3, health: 1000, coins: 65, flying: false, waveAppearance: 46 }, spectral_ravager: { speed: 2.4, health: 1060, coins: 68, flying: false, waveAppearance: 49 }, boneclad_warbeast: { speed: 2.4, health: 1120, coins: 72, flying: false, waveAppearance: 52 }, plague_titan: { speed: 2.5, health: 1180, coins: 75, flying: false, waveAppearance: 55 }, abyss_reaper: { speed: 2.5, health: 1240, coins: 78, flying: false, waveAppearance: 58 }, wraith_bringer: { speed: 2.6, health: 1300, coins: 82, flying: true, waveAppearance: 61 }, inferno_crawler: { speed: 2.6, health: 1360, coins: 85, flying: false, waveAppearance: 64 }, thornback_hydra: { speed: 2.7, health: 1420, coins: 88, flying: false, waveAppearance: 67 }, ghostfire_shade: { speed: 2.7, health: 1480, coins: 92, flying: false, waveAppearance: 70 }, storm_herald: { speed: 2.8, health: 1540, coins: 95, flying: true, waveAppearance: 73 }, voidling_runner: { speed: 2.8, health: 1600, coins: 98, flying: false, waveAppearance: 76 }, doom_shell: { speed: 2.9, health: 1660, coins: 102, flying: false, waveAppearance: 79 }, nether_juggernaut: { speed: 2.9, health: 1720, coins: 105, flying: false, waveAppearance: 82 }, abyss_howler: { speed: 3.0, health: 1780, coins: 108, flying: true, waveAppearance: 85 }, void_devourer: { speed: 3.0, health: 1840, coins: 112, flying: false, waveAppearance: 88 }, // Legacy support for existing enemies goblin: { speed: 1.2, health: 100, coins: 15, flying: false, waveAppearance: 1 }, crawler: { speed: 1.3, health: 160, coins: 18, flying: false, waveAppearance: 4 }, bandit: { speed: 1.4, health: 220, coins: 22, flying: false, waveAppearance: 7 }, golem: { speed: 1.5, health: 280, coins: 25, flying: false, waveAppearance: 10 }, imp: { speed: 1.6, health: 340, coins: 28, flying: false, waveAppearance: 13 }, knight: { speed: 1.7, health: 400, coins: 32, flying: false, waveAppearance: 16 }, beast: { speed: 1.8, health: 460, coins: 35, flying: false, waveAppearance: 19 }, mage: { speed: 1.9, health: 520, coins: 38, flying: false, waveAppearance: 22 }, reaver: { speed: 2.0, health: 580, coins: 42, flying: true, waveAppearance: 25 }, doom: { speed: 2.0, health: 640, coins: 45, flying: false, waveAppearance: 28 } }; var stats = baseStats[type] || baseStats.goblin_scout; // Apply endless mode scaling if active var healthMultiplier = 1; var speedMultiplier = 1; if (endlessMode && currentWave > 100) { var endlessWaves = Math.floor((currentWave - 100) / 5); healthMultiplier = 1 + endlessWaves * 0.1; // +10% HP every 5 waves speedMultiplier = 1 + endlessWaves * 0.05; // +5% speed every 5 waves } // Apply scaling to stats self.speed = stats.speed * speedMultiplier; self.maxHealth = Math.floor(stats.health * healthMultiplier); self.health = self.maxHealth; self.pathStep = 0; self.coinValue = stats.coins; self.flying = stats.flying; self.regeneration = type === 'beast' ? 0.5 : type === 'frostback_boar' ? 0.3 : type === 'plague_titan' ? 0.8 : 0; self.poisonResistant = type === 'doom' || type === 'toxic_maw' || type === 'plague_titan' || type === 'void_devourer'; self.slowResistant = type === 'doom' || type === 'volcanic_strider' || type === 'stormblade_knight' || type === 'abyss_reaper'; self.originalSpeed = self.speed; // Store original speed for slowing effect self.slowedByFrost = false; // Track if enemy is slowed by frost tower self.slowTimer = 0; // Timer for slow effect duration self.slowPercentage = 0; // Current slow percentage applied // Map enemy types to valid asset IDs with fallback system var assetMapping = { 'goblin_scout': 'enemy_goblin', 'mire_crawler': 'enemy_mire_crawler', 'fang_imp': 'enemy_fang_imp', 'crimson_leaper': 'enemy_crimson_leaper', 'rustback_beetle': 'enemy_rustback_beetle', 'gravel_hound': 'enemy_gravel_hound', 'vine_stalker': 'enemy_vine_stalker', 'ashborne_wraith': 'enemy_ashborne_wraith', 'stonehide_orc': 'enemy_stonehide_orc', 'toxic_maw': 'enemy_toxic_maw', 'frostback_boar': 'enemy_frostback_boar', 'ironmaw_troll': 'enemy_ironmaw_troll', 'volcanic_strider': 'enemy_volcanic_strider', 'stormblade_knight': 'enemy_stormblade_knight', 'twilight_serpent': 'enemy_twilight_serpent', 'obsidian_colossus': 'enemy_obsidian_colossus', 'spectral_ravager': 'enemy_spectral_ravager', 'boneclad_warbeast': 'enemy_boneclad_warbeast', 'plague_titan': 'enemy_plague_titan', 'abyss_reaper': 'enemy_abyss_reaper', 'wraith_bringer': 'enemy_mage', // fallback to mage 'inferno_crawler': 'enemy_crawler', 'thornback_hydra': 'enemy_beast', // fallback to beast 'ghostfire_shade': 'enemy_mage', // fallback to mage 'storm_herald': 'enemy_knight', // fallback to knight 'voidling_runner': 'enemy_reaver', // fallback to reaver 'doom_shell': 'enemy_doom', 'nether_juggernaut': 'enemy_golem', // fallback to golem 'abyss_howler': 'enemy_beast', // fallback to beast 'void_devourer': 'enemy_void_devourer' }; // Get the correct asset ID or fallback to goblin var assetId = assetMapping[type] || 'enemy_' + type; // Try to get the asset, fallback to goblin if it doesn't exist var graphics; try { graphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); } catch (e) { // Fallback to default goblin sprite if asset doesn't exist graphics = self.attachAsset('enemy_goblin', { anchorX: 0.5, anchorY: 0.5 }); } // Create health bar background using the same asset fallback system try { self.healthBarBg = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.1, y: -25 }); } catch (e) { self.healthBarBg = self.attachAsset('enemy_goblin', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.1, y: -25 }); } self.healthBarBg.tint = 0x333333; // Create health bar fill using the same asset fallback system try { self.healthBarFill = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.1, y: -25 }); } catch (e) { self.healthBarFill = self.attachAsset('enemy_goblin', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.1, y: -25 }); } self.healthBarFill.tint = 0x00FF00; self.x = self.currentPath[0].x; self.y = self.currentPath[0].y; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xFF0000, 200); // Update health bar fill based on remaining health if (self.healthBarFill) { var healthPercent = Math.max(0, self.health / self.maxHealth); self.healthBarFill.scaleX = 0.8 * healthPercent; // Change color from green to red based on health var redComponent = Math.floor(255 * (1 - healthPercent)); var greenComponent = Math.floor(255 * healthPercent); var healthColor = redComponent << 16 | greenComponent << 8 | 0x00; self.healthBarFill.tint = healthColor; } if (self.health <= 0) { if (!audioMuted) { LK.getSound('enemy_hit').play(); LK.getSound('coin_gain').play(); } // Create floating coin gain text var coinText = new Text2('+' + self.coinValue, { size: 40, fill: 0xFFD700 }); coinText.anchor.set(0.5, 0.5); coinText.x = self.x; coinText.y = self.y - 30; game.addChild(coinText); // Animate coin text floating up and fading out tween(coinText, { y: coinText.y - 80, alpha: 0 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { if (coinText && coinText.destroy) { coinText.destroy(); } } }); // Create death effect at enemy position tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, easing: tween.easeOut }); return true; } return false; }; self.update = function () { // Handle slow timer for frost effect if (self.slowTimer > 0) { self.slowTimer--; if (self.slowTimer <= 0) { // Restore original speed when slow effect expires self.speed = self.originalSpeed; self.slowedByFrost = false; self.slowPercentage = 0; } } if (self.pathStep < self.currentPath.length - 1) { var target = self.currentPath[self.pathStep + 1]; var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 5) { self.pathStep++; if (self.pathStep >= self.currentPath.length - 1) { self.reachedCastle = true; } } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } }; self.applyFrostSlow = function (slowPercentage) { // Don't apply slow to slow-resistant enemies if (self.slowResistant) { return; } // Apply the slow effect self.slowedByFrost = true; self.slowPercentage = slowPercentage; self.speed = self.originalSpeed * (1 - slowPercentage / 100); self.slowTimer = 120; // 2 seconds at 60 FPS }; return self; }); var Projectile = Container.expand(function (type, startX, startY, targetX, targetY, damage) { var self = Container.call(this); self.type = type; self.damage = damage; self.targetX = targetX; self.targetY = targetY; self.speed = type === 'arrow' ? 16 : type === 'cannonball' ? 12 : 14; var graphics = self.attachAsset('projectile_' + type, { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.velocityX = dx / distance * self.speed; self.velocityY = dy / distance * self.speed; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; var distToTarget = Math.sqrt((self.x - self.targetX) * (self.x - self.targetX) + (self.y - self.targetY) * (self.y - self.targetY)); if (distToTarget < 10) { self.hasReachedTarget = true; } }; return self; }); var Tower = Container.expand(function (type) { var self = Container.call(this); self.type = type; self.level = 1; self.lastShotTime = 0; self.maxLevel = type === 'antiair' || type === 'poison' || type === 'tesla' ? 5 : 10; // Initialize tower stats based on balanced specifications if (type === 'infantry') { self.damage = 11.375; self.range = 150; self.attackSpeed = Math.floor(60 / 0.8 * 1.54 * 1.35); // 0.8 shots/sec slowed by 70% = 155 ticks between shots self.cost = 80; self.projectileType = 'arrow'; } else if (type === 'frost') { self.damage = 1.456; self.range = 150; self.attackSpeed = Math.floor(60 * 2 * 1.54 * 1.35); // 1 shot per 2 seconds slowed by 70% = 250 ticks self.cost = 120; self.projectileType = 'magic'; self.slowEffect = true; self.slowPercentage = 27; // Level 1 slow - increased by 30% from 21% self.aoeRadius = 120; } else if (type === 'antiair') { self.damage = 13.65; self.range = 150; self.attackSpeed = Math.floor(60 / 1.5 * 1.54 * 1.35); // 1.5 shots/sec slowed by 70% = 84 ticks self.cost = 100; self.projectileType = 'magic'; self.antiAir = true; } else if (type === 'poison') { self.damage = 4.55; self.range = 150; self.attackSpeed = Math.floor(60 / 1.2 * 1.54 * 1.35); // 1.2 shots/sec slowed by 70% = 104 ticks self.cost = 130; self.projectileType = 'magic'; self.poisonEffect = true; self.poisonDuration = 3; // 3 seconds self.poisonTickInterval = 60; // 1 second in ticks } else if (type === 'tesla') { self.damage = 15.925; self.range = 150; self.attackSpeed = Math.floor(60 / 0.7 * 1.54 * 1.35); // 0.7 shots/sec slowed by 70% = 178 ticks self.cost = 150; self.projectileType = 'magic'; self.chainLightning = true; self.chainTargets = 3; self.chainDamageReduction = 0.1; // 10% reduction per jump } var graphics = self.attachAsset('tower_' + type, { anchorX: 0.5, anchorY: 0.5 }); self.getUpgradeNames = function () { if (self.type === 'infantry') { return ['Rifleman', 'Marksman', 'Sharpshooter', 'Machine Gunner', 'Grenadier', 'Mortar Operator', 'Tank Commander', 'Bazooka Specialist', 'Rocket Launcher', 'Missile Battery']; } else if (self.type === 'frost') { return ['Frost Spire', 'Ice Shard Tower', 'Blizzard Bastion', 'Cryo Cannon', 'Glacier Fortress', 'Snowstorm Citadel', 'Avalanche Keep', 'Frozen Maelstrom', 'Arctic Warden', 'Absolute Zero']; } else if (self.type === 'antiair') { return ['Flak Cannon', 'SAM Launcher', 'Radar Missile Battery', 'Laser Air Defense', 'Plasma Barrier']; } else if (self.type === 'poison') { return ['Venom Sprayer', 'Toxic Spitter', 'Acid Dart Tower', 'Corrosive Spire', 'Plague Altar']; } else if (self.type === 'tesla') { return ['Static Coil', 'Dual Coil', 'Arc Emitter', 'Thunderstorm Core', 'Lightning God']; } }; self.canShoot = function () { return LK.ticks - self.lastShotTime > self.attackSpeed / (1000 / 60); }; self.findTarget = function () { var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var distance = Math.sqrt((enemy.x - self.x) * (enemy.x - self.x) + (enemy.y - self.y) * (enemy.y - self.y)); if (distance <= self.range && distance < closestDistance) { // Flying enemy rules: only Anti-Air towers can target flying enemies if (enemy.flying && self.type !== 'antiair') { continue; // Skip flying enemies for non-antiair towers } // Non-flying enemies cannot be targeted by Anti-Air towers if (!enemy.flying && self.type === 'antiair') { continue; // Skip non-flying enemies for antiair towers } closestDistance = distance; closestEnemy = enemy; } } return closestEnemy; }; self.shoot = function (target) { if (!self.canShoot()) { return; } self.lastShotTime = LK.ticks; var projectile = new Projectile(self.projectileType, self.x, self.y, target.x, target.y, self.damage); projectile.towerType = self.type; projectile.slowEffect = self.slowEffect; projectile.poisonEffect = self.poisonEffect; projectile.chainLightning = self.chainLightning; projectiles.push(projectile); isoContainer.addChild(projectile); if (!audioMuted) { if (self.type === 'tesla') { LK.getSound('tesla_knock').play(); } else { LK.getSound('shoot_arrow').play(); } } }; self.getUpgradeCost = function () { // Calculate upgrade cost: Level 1 = base price, then +80% each level var upgradeCost = self.cost; for (var i = 2; i <= self.level; i++) { upgradeCost = Math.floor(upgradeCost * 1.8); } return upgradeCost; }; self.upgrade = function () { // Check if upgrade level is unlocked var nextLevel = self.level + 1; if (nextLevel > upgradeUnlockWaves.length || currentWave < upgradeUnlockWaves[nextLevel - 1]) { showUpgradeRestrictedMessage(nextLevel, upgradeUnlockWaves[nextLevel - 1]); return; } if (self.level < self.maxLevel) { self.level++; // Tower-specific damage upgrades if (self.type === 'infantry') { self.damage += 4.55; // +4.55 flat damage per level (30% reduction from 6.5) } else if (self.type === 'frost') { self.damage += 0.437; // +0.437 damage per level (30% reduction from 0.624) // Update slow percentage - increased by 30% if (self.level === 2 || self.level === 3) { self.slowPercentage = 45; } else if (self.level >= 4) { self.slowPercentage = 64; } } else if (self.type === 'antiair') { self.damage += 3.64; // +3.64 damage per level (30% reduction from 5.2) } else if (self.type === 'poison') { self.damage += 2.275; // +2.275 tick damage per level (30% reduction from 3.25) self.poisonDuration += 1; // +1 second duration } else if (self.type === 'tesla') { self.damage += 4.55; // +4.55 base damage per level (30% reduction from 6.5) // Increase chain targets every 2 levels if (self.level % 2 === 0) { self.chainTargets = Math.min(6, self.chainTargets + 1); } } // Calculate range increase with diminishing returns - all towers start at 150 base range var rangeMultiplier = 1.0; if (self.level === 2) { rangeMultiplier = 1.10; // +10% for level 1 upgrade } else if (self.level === 3) { rangeMultiplier = 1.10 * 1.05; // +10% then +5% } else if (self.level === 4) { rangeMultiplier = 1.10 * 1.05 * 1.035; // +10%, +5%, then +3.5% } else if (self.level >= 5) { // +10%, +5%, +3.5%, then +2% for each additional level rangeMultiplier = 1.10 * 1.05 * 1.035; var additionalLevels = self.level - 4; rangeMultiplier *= Math.pow(1.02, additionalLevels); } // Apply range multiplier to base range of 150 for all towers self.range = Math.floor(150 * rangeMultiplier); self.attackSpeed = Math.max(200, Math.floor(self.attackSpeed * 0.95)); // Visual upgrade indicators var tintColors = [0xFFFFFF, 0xFFD700, 0xFF6347, 0xFF4500, 0x8A2BE2, 0x00FF00, 0x00FFFF, 0xFF69B4, 0xFFFF00, 0xFF0000]; graphics.tint = tintColors[self.level - 1] || 0xFF0000; // Show range indicator for upgraded towers if (self.level > 1 && !self.rangeIndicator) { self.rangeIndicator = LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, alpha: 0.1, scaleX: self.range / 40, scaleY: self.range / 40 }); self.rangeIndicator.x = self.x; self.rangeIndicator.y = self.y; self.rangeIndicator.tint = 0x00FF00; isoContainer.addChild(self.rangeIndicator); } else if (self.rangeIndicator) { self.rangeIndicator.scaleX = self.range / 40; self.rangeIndicator.scaleY = self.range / 40; } } }; self.down = function (x, y, obj) { selectedTower = self; // Clear any existing range indicator if (activeRangeIndicator && activeRangeIndicator.destroy) { activeRangeIndicator.destroy(); activeRangeIndicator = null; } // Clear any existing upgrade panel if (upgradePanel && upgradePanel.destroy) { upgradePanel.destroy(); upgradePanel = null; } // Create new range indicator activeRangeIndicator = LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, alpha: 0.2, scaleX: self.range / 27.5, scaleY: self.range / 27.5 }); activeRangeIndicator.x = self.x; activeRangeIndicator.y = self.y; activeRangeIndicator.tint = 0x00FFFF; // Cyan color for range indicator isoContainer.addChild(activeRangeIndicator); // Create larger upgrade panel upgradePanel = new Container(); isoContainer.addChild(upgradePanel); // Panel background - much larger var panelBg = upgradePanel.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y - 100, scaleX: 12, scaleY: 8 })); panelBg.tint = 0x000000; panelBg.alpha = 0.9; // Calculate total investment for sell value var totalInvestment = self.cost; for (var i = 2; i <= self.level; i++) { var levelCost = self.cost; for (var j = 2; j <= i; j++) { levelCost = Math.floor(levelCost * 1.8); } totalInvestment += levelCost; } var sellValue = Math.floor(totalInvestment * 0.5); if (self.level < self.maxLevel) { var cost = self.getUpgradeCost(); var names = self.getUpgradeNames(); var nextName = names[self.level - 1] || 'Max Level'; var nextLevel = self.level + 1; var isUpgradeUnlocked = nextLevel <= upgradeUnlockWaves.length && currentWave >= upgradeUnlockWaves[nextLevel - 1]; // Upgrade button text - larger size var upgradeText = upgradePanel.addChild(new Text2('Upgrade', { size: 40, fill: isUpgradeUnlocked ? 0xFFD700 : 0x666666 })); upgradeText.anchor.set(0.5, 1); upgradeText.x = self.x; upgradeText.y = self.y - 120; var costText; if (isUpgradeUnlocked) { costText = upgradePanel.addChild(new Text2('Cost: ' + cost + ' gold', { size: 36, fill: coins >= cost ? 0x00FF00 : 0xFF0000 })); } else { costText = upgradePanel.addChild(new Text2('Unlocks at Wave ' + upgradeUnlockWaves[nextLevel - 1], { size: 32, fill: 0xFFAA00 })); } costText.anchor.set(0.5, 1); costText.x = self.x; costText.y = self.y - 80; if (coins >= cost && isUpgradeUnlocked) { upgradeText.down = costText.down = panelBg.down = function () { coins -= cost; coinsText.setText('Gold: ' + coins); self.upgrade(); // Play upgrade sound if (!audioMuted) { LK.getSound('upgrade').play(); } if (upgradePanel && upgradePanel.destroy) { upgradePanel.destroy(); upgradePanel = null; } selectedTower = null; if (activeRangeIndicator && activeRangeIndicator.destroy) { activeRangeIndicator.destroy(); activeRangeIndicator = null; } }; } } else { var maxText = upgradePanel.addChild(new Text2('MAX LEVEL', { size: 44, fill: 0xFFFFFF })); maxText.anchor.set(0.5, 1); maxText.x = self.x; maxText.y = self.y - 100; } // Sell button - always visible var sellText = upgradePanel.addChild(new Text2('Sell Tower', { size: 38, fill: 0xFF4444 })); sellText.anchor.set(0.5, 1); sellText.x = self.x; sellText.y = self.y - 40; var sellValueText = upgradePanel.addChild(new Text2('Refund: ' + sellValue + ' gold', { size: 32, fill: 0xFFD700 })); sellValueText.anchor.set(0.5, 1); sellValueText.x = self.x; sellValueText.y = self.y - 5; sellText.down = sellValueText.down = function () { // Find and free the build zone for (var i = 0; i < buildZones.length; i++) { var zone = buildZones[i]; var distance = Math.sqrt((zone.x - self.x) * (zone.x - self.x) + (zone.y - self.y) * (zone.y - self.y)); if (distance < 30) { zone.zoneData.occupied = false; break; } } // Add refund money coins += sellValue; coinsText.setText('Gold: ' + coins); // Show refund notification var refundNotification = new Text2('+' + sellValue + ' gold', { size: 50, fill: 0x00FF00 }); refundNotification.anchor.set(0.5, 0.5); refundNotification.x = self.x; refundNotification.y = self.y - 100; isoContainer.addChild(refundNotification); tween(refundNotification, { y: refundNotification.y - 80, alpha: 0 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { if (refundNotification && refundNotification.destroy) { refundNotification.destroy(); } } }); // Remove tower from towers array for (var i = 0; i < towers.length; i++) { if (towers[i] === self) { towers.splice(i, 1); break; } } // Clean up UI elements if (upgradePanel && upgradePanel.destroy) { upgradePanel.destroy(); upgradePanel = null; } if (activeRangeIndicator && activeRangeIndicator.destroy) { activeRangeIndicator.destroy(); activeRangeIndicator = null; } selectedTower = null; // Remove tower self.destroy(); updateBuildZoneVisuals(); }; }; self.update = function () { var target = self.findTarget(); if (target) { self.shoot(target); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x228B22 }); /**** * Game Code ****/ // Use game container directly for top-down view var isoContainer = game; var gameState = 'mainMenu'; var mainMenuContainer = null; var howToPlayContainer = null; var creditsContainer = null; var settingsContainer = null; var storyIntroContainer = null; var settingsIcon = null; var audioMuted = false; var muteIndicator = null; var endlessMode = false; var endlessScore = 0; var castleType = 'square'; var difficulty = 'easy'; var currentWave = 0; var maxWaves = 100; var waveEnemies = []; var enemies = []; var towers = []; var projectiles = []; var coins = 160; var castleHealth = 100; var waveInProgress = false; var nextWaveTimer = 0; var selectedTowerType = null; var selectedTower = null; var upgradeText = null; var upgradePanel = null; var activeRangeIndicator = null; // Shop button references for visual updates var infantryBtn, frostBtn, antiairBtn, poisonBtn, teslaBtn; var infantryCost, frostCost, antiairCost, poisonCost, teslaCost; var infantryName, frostName, antiairName, poisonName, teslaName; // Tower unlock system - waves when towers become available var towerUnlockWaves = { infantry: 1, frost: 3, antiair: 8, poison: 15, tesla: 25 }; // Upgrade level restrictions based on wave progression var upgradeUnlockWaves = [1, 3, 6, 10, 14, 20, 30, 40, 50, 60]; // Enemy path 1: Detailed horizontal then vertical path var enemyPath1 = [{ x: 0, y: 200 }, { x: 25.6, y: 200 }, { x: 51.2, y: 200 }, { x: 76.8, y: 200 }, { x: 102.4, y: 200 }, { x: 128, y: 200 }, { x: 153.6, y: 200 }, { x: 179.2, y: 200 }, { x: 204.8, y: 200 }, { x: 230.4, y: 200 }, { x: 256, y: 200 }, { x: 281.6, y: 200 }, { x: 307.2, y: 200 }, { x: 332.8, y: 200 }, { x: 358.4, y: 200 }, { x: 384, y: 200 }, { x: 409.6, y: 200 }, { x: 435.2, y: 200 }, { x: 460.8, y: 200 }, { x: 486.4, y: 200 }, { x: 512, y: 200 }, { x: 537.6, y: 200 }, { x: 563.2, y: 200 }, { x: 588.8, y: 200 }, { x: 614.4, y: 200 }, { x: 640, y: 200 }, { x: 665.6, y: 200 }, { x: 691.2, y: 200 }, { x: 716.8, y: 200 }, { x: 742.4, y: 200 }, { x: 768, y: 200 }, { x: 793.6, y: 200 }, { x: 819.2, y: 200 }, { x: 844.8, y: 200 }, { x: 870.4, y: 200 }, { x: 896, y: 200 }, { x: 921.6, y: 200 }, { x: 947.2, y: 200 }, { x: 972.8, y: 200 }, { x: 998.4, y: 200 }, { x: 1024, y: 200 }, { x: 1024, y: 240 }, { x: 1024, y: 280 }, { x: 1024, y: 320 }, { x: 1024, y: 360 }, { x: 1024, y: 400 }, { x: 1024, y: 440 }, { x: 1024, y: 480 }, { x: 1024, y: 520 }, { x: 1024, y: 560 }, { x: 1024, y: 600 }, { x: 998.4, y: 600 }, { x: 972.8, y: 600 }, { x: 947.2, y: 600 }, { x: 921.6, y: 600 }, { x: 896, y: 600 }, { x: 870.4, y: 600 }, { x: 844.8, y: 600 }, { x: 819.2, y: 600 }, { x: 793.6, y: 600 }, { x: 768, y: 600 }, { x: 768, y: 640 }, { x: 768, y: 680 }, { x: 768, y: 720 }, { x: 768, y: 760 }, { x: 768, y: 800 }, { x: 768, y: 840 }, { x: 768, y: 880 }, { x: 768, y: 920 }, { x: 768, y: 960 }, { x: 768, y: 1000 }, { x: 768, y: 1040 }, { x: 768, y: 1080 }, { x: 768, y: 1120 }, { x: 768, y: 1160 }, { x: 768, y: 1200 }, { x: 768, y: 1240 }, { x: 768, y: 1280 }, { x: 768, y: 1320 }, { x: 768, y: 1360 }, { x: 768, y: 1400 }, { x: 768, y: 1440 }, { x: 768, y: 1480 }, { x: 768, y: 1520 }, { x: 768, y: 1560 }, { x: 768, y: 1600 }, { x: 768, y: 1640 }, { x: 768, y: 1680 }, { x: 768, y: 1720 }, { x: 768, y: 1760 }, { x: 768, y: 1800 }, { x: 793.6, y: 1800 }, { x: 819.2, y: 1800 }, { x: 844.8, y: 1800 }, { x: 870.4, y: 1800 }, { x: 896, y: 1800 }, { x: 921.6, y: 1800 }, { x: 947.2, y: 1800 }, { x: 972.8, y: 1800 }, { x: 998.4, y: 1800 }, { x: 1024, y: 1800 }, { x: 1024, y: 1840 }, { x: 1024, y: 1880 }, { x: 1024, y: 1920 }, { x: 1024, y: 1960 }, { x: 1024, y: 2000 }, { x: 1024, y: 2040 }, { x: 1024, y: 2080 }, { x: 1024, y: 2120 }, { x: 1024, y: 2160 }, { x: 1024, y: 2200 }]; // Enemy path 2: Detailed eastern route path var enemyPath2 = [{ x: 2048, y: 800 }, { x: 2022.4, y: 800 }, { x: 1996.8, y: 800 }, { x: 1971.2, y: 800 }, { x: 1945.6, y: 800 }, { x: 1920, y: 800 }, { x: 1894.4, y: 800 }, { x: 1868.8, y: 800 }, { x: 1843.2, y: 800 }, { x: 1817.6, y: 800 }, { x: 1792, y: 800 }, { x: 1766.4, y: 800 }, { x: 1740.8, y: 800 }, { x: 1715.2, y: 800 }, { x: 1689.6, y: 800 }, { x: 1664, y: 800 }, { x: 1638.4, y: 800 }, { x: 1612.8, y: 800 }, { x: 1587.2, y: 800 }, { x: 1561.6, y: 800 }, { x: 1536, y: 800 }, { x: 1510.4, y: 800 }, { x: 1484.8, y: 800 }, { x: 1459.2, y: 800 }, { x: 1433.6, y: 800 }, { x: 1408, y: 800 }, { x: 1382.4, y: 800 }, { x: 1356.8, y: 800 }, { x: 1331.2, y: 800 }, { x: 1305.6, y: 800 }, { x: 1280, y: 800 }, { x: 1254.4, y: 800 }, { x: 1228.8, y: 800 }, { x: 1203.2, y: 800 }, { x: 1177.6, y: 800 }, { x: 1152, y: 800 }, { x: 1126.4, y: 800 }, { x: 1100.8, y: 800 }, { x: 1075.2, y: 800 }, { x: 1049.6, y: 800 }, { x: 1024, y: 800 }, { x: 1024, y: 780 }, { x: 1024, y: 760 }, { x: 1024, y: 740 }, { x: 1024, y: 720 }, { x: 1024, y: 700 }, { x: 1024, y: 680 }, { x: 1024, y: 660 }, { x: 1024, y: 640 }, { x: 1024, y: 620 }, { x: 1024, y: 600 }, { x: 1049.6, y: 600 }, { x: 1075.2, y: 600 }, { x: 1100.8, y: 600 }, { x: 1126.4, y: 600 }, { x: 1152, y: 600 }, { x: 1177.6, y: 600 }, { x: 1203.2, y: 600 }, { x: 1228.8, y: 600 }, { x: 1254.4, y: 600 }, { x: 1280, y: 600 }, { x: 1280, y: 640 }, { x: 1280, y: 680 }, { x: 1280, y: 720 }, { x: 1280, y: 760 }, { x: 1280, y: 800 }, { x: 1280, y: 840 }, { x: 1280, y: 880 }, { x: 1280, y: 920 }, { x: 1280, y: 960 }, { x: 1280, y: 1000 }, { x: 1280, y: 1040 }, { x: 1280, y: 1080 }, { x: 1280, y: 1120 }, { x: 1280, y: 1160 }, { x: 1280, y: 1200 }, { x: 1280, y: 1240 }, { x: 1280, y: 1280 }, { x: 1280, y: 1320 }, { x: 1280, y: 1360 }, { x: 1280, y: 1400 }, { x: 1280, y: 1440 }, { x: 1280, y: 1480 }, { x: 1280, y: 1520 }, { x: 1280, y: 1560 }, { x: 1280, y: 1600 }, { x: 1280, y: 1640 }, { x: 1280, y: 1680 }, { x: 1280, y: 1720 }, { x: 1280, y: 1760 }, { x: 1280, y: 1800 }, { x: 1254.4, y: 1800 }, { x: 1228.8, y: 1800 }, { x: 1203.2, y: 1800 }, { x: 1177.6, y: 1800 }, { x: 1152, y: 1800 }, { x: 1126.4, y: 1800 }, { x: 1100.8, y: 1800 }, { x: 1075.2, y: 1800 }, { x: 1049.6, y: 1800 }, { x: 1024, y: 1800 }, { x: 1024, y: 1840 }, { x: 1024, y: 1880 }, { x: 1024, y: 1920 }, { x: 1024, y: 1960 }, { x: 1024, y: 2000 }, { x: 1024, y: 2040 }, { x: 1024, y: 2080 }, { x: 1024, y: 2120 }, { x: 1024, y: 2160 }, { x: 1024, y: 2200 }]; // Enemy path 3: Detailed western route path var enemyPath3 = [{ x: 2048, y: 50 }, { x: 2022.4, y: 50 }, { x: 1996.8, y: 50 }, { x: 1971.2, y: 50 }, { x: 1945.6, y: 50 }, { x: 1920, y: 50 }, { x: 1894.4, y: 50 }, { x: 1868.8, y: 50 }, { x: 1843.2, y: 50 }, { x: 1817.6, y: 50 }, { x: 1792, y: 50 }, { x: 1766.4, y: 50 }, { x: 1740.8, y: 50 }, { x: 1715.2, y: 50 }, { x: 1689.6, y: 50 }, { x: 1664, y: 50 }, { x: 1638.4, y: 50 }, { x: 1612.8, y: 50 }, { x: 1587.2, y: 50 }, { x: 1561.6, y: 50 }, { x: 1536, y: 50 }, { x: 1510.4, y: 50 }, { x: 1484.8, y: 50 }, { x: 1459.2, y: 50 }, { x: 1433.6, y: 50 }, { x: 1408, y: 50 }, { x: 1382.4, y: 50 }, { x: 1356.8, y: 50 }, { x: 1331.2, y: 50 }, { x: 1305.6, y: 50 }, { x: 1280, y: 50 }, { x: 1280, y: 90 }, { x: 1280, y: 130 }, { x: 1280, y: 170 }, { x: 1280, y: 210 }, { x: 1280, y: 250 }, { x: 1280, y: 290 }, { x: 1280, y: 330 }, { x: 1280, y: 370 }, { x: 1280, y: 410 }, { x: 1280, y: 450 }, { x: 1280, y: 490 }, { x: 1280, y: 530 }, { x: 1280, y: 570 }, { x: 1280, y: 610 }, { x: 1280, y: 650 }, { x: 1280, y: 690 }, { x: 1280, y: 730 }, { x: 1280, y: 770 }, { x: 1280, y: 810 }, { x: 1280, y: 850 }, { x: 1280, y: 890 }, { x: 1280, y: 930 }, { x: 1280, y: 970 }, { x: 1280, y: 1010 }, { x: 1280, y: 1050 }, { x: 1280, y: 1090 }, { x: 1280, y: 1130 }, { x: 1280, y: 1170 }, { x: 1280, y: 1210 }, { x: 1280, y: 1250 }, { x: 1280, y: 1290 }, { x: 1280, y: 1330 }, { x: 1280, y: 1370 }, { x: 1280, y: 1410 }, { x: 1280, y: 1450 }, { x: 1280, y: 1490 }, { x: 1280, y: 1530 }, { x: 1280, y: 1570 }, { x: 1280, y: 1610 }, { x: 1280, y: 1650 }, { x: 1280, y: 1690 }, { x: 1280, y: 1730 }, { x: 1280, y: 1770 }, { x: 1280, y: 1800 }, { x: 1254.4, y: 1800 }, { x: 1228.8, y: 1800 }, { x: 1203.2, y: 1800 }, { x: 1177.6, y: 1800 }, { x: 1152, y: 1800 }, { x: 1126.4, y: 1800 }, { x: 1100.8, y: 1800 }, { x: 1075.2, y: 1800 }, { x: 1049.6, y: 1800 }, { x: 1024, y: 1800 }, { x: 1024, y: 1840 }, { x: 1024, y: 1880 }, { x: 1024, y: 1920 }, { x: 1024, y: 1960 }, { x: 1024, y: 2000 }, { x: 1024, y: 2040 }, { x: 1024, y: 2080 }, { x: 1024, y: 2120 }, { x: 1024, y: 2160 }, { x: 1024, y: 2200 }]; var buildZones = []; var terrainFeatures = []; function createPath() { // Create all paths visible from the start for strategic planning // Create path 1 for (var i = 0; i < enemyPath1.length; i++) { var pathTile = isoContainer.addChild(LK.getAsset('path_tile', { anchorX: 0.5, anchorY: 0.5, x: enemyPath1[i].x, y: enemyPath1[i].y, alpha: 0.7 })); } // Create path 2 - always visible for (var i = 0; i < enemyPath2.length; i++) { var pathTile = isoContainer.addChild(LK.getAsset('path_tile', { anchorX: 0.5, anchorY: 0.5, x: enemyPath2[i].x, y: enemyPath2[i].y, alpha: 0.7 })); } // Create path 3 - always visible for (var i = 0; i < enemyPath3.length; i++) { var pathTile = isoContainer.addChild(LK.getAsset('path_tile', { anchorX: 0.5, anchorY: 0.5, x: enemyPath3[i].x, y: enemyPath3[i].y, alpha: 0.7 })); } // Create build zones around paths createBuildZones(); } function createTerrain() { // Create lake var lake = isoContainer.addChild(LK.getAsset('lake', { anchorX: 0.5, anchorY: 0.5, x: 400, y: 500, alpha: 0.8 })); // Create waterfall (three parallel lines) var waterfall1 = isoContainer.addChild(LK.getAsset('waterfall_line', { anchorX: 0.5, anchorY: 0.5, x: 1550, y: 300 })); var waterfall2 = isoContainer.addChild(LK.getAsset('waterfall_line', { anchorX: 0.5, anchorY: 0.5, x: 1560, y: 300 })); var waterfall3 = isoContainer.addChild(LK.getAsset('waterfall_line', { anchorX: 0.5, anchorY: 0.5, x: 1570, y: 300 })); // Create bottom right waterfall (400 pixels above bottom right corner) var bottomRightWaterfall = isoContainer.addChild(LK.getAsset('waterfall_line', { anchorX: 0.5, anchorY: 0.5, x: 1748, y: 1932 })); // Create lake in the middle of the map var centralLake = isoContainer.addChild(LK.getAsset('lake', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, alpha: 0.8, scaleX: 1.5, scaleY: 1.5 })); // Create left village (further west of castle) var leftVillagePositions = [{ x: 450, y: 2050 }, { x: 700, y: 2300 }, { x: 300, y: 2350 }, { x: 650, y: 2100 }]; for (var i = 0; i < leftVillagePositions.length; i++) { var pos = leftVillagePositions[i]; // Create house base var house = isoContainer.addChild(LK.getAsset('village_house', { anchorX: 0.5, anchorY: 0.5, x: pos.x, y: pos.y, alpha: 0.9 })); // Roof removed } // Create right village (further east of castle) var rightVillagePositions = [{ x: 1600, y: 2050 }, { x: 1350, y: 2300 }, { x: 1750, y: 2250 }, { x: 1500, y: 2450 }]; for (var i = 0; i < rightVillagePositions.length; i++) { var pos = rightVillagePositions[i]; // Create house base var house = isoContainer.addChild(LK.getAsset('village_house', { anchorX: 0.5, anchorY: 0.5, x: pos.x, y: pos.y, alpha: 0.9 })); // Roof removed } // Create 50 trees scattered across the map avoiding all objects var treePositions = []; // Helper function to check if a position conflicts with existing objects function isPositionSafe(x, y, buffer) { buffer = buffer || 200; // Check boundaries if (x < 50 || x > 1998 || y < 50 || y > 2682) { return false; } // Check castle area if (Math.sqrt((x - 1024) * (x - 1024) + (y - 2200) * (y - 2200)) < 300 + buffer) { return false; } // Check waterfall area if (x >= 1500 - buffer && x <= 1600 + buffer) { return false; } // Check central lake area if (Math.sqrt((x - 1024) * (x - 1024) + (y - 1366) * (y - 1366)) < 200 + buffer) { return false; } // Check original lake area if (Math.sqrt((x - 400) * (x - 400) + (y - 500) * (y - 500)) < 150 + buffer) { return false; } // Check all enemy paths with buffer var allPaths = [enemyPath1, enemyPath2, enemyPath3]; for (var p = 0; p < allPaths.length; p++) { var path = allPaths[p]; for (var i = 0; i < path.length; i++) { if (Math.sqrt((x - path[i].x) * (x - path[i].x) + (y - path[i].y) * (y - path[i].y)) < buffer + 20) { return false; } } } // Check build zones - now requiring 200 pixels distance for (var i = 0; i < buildZones.length; i++) { if (Math.sqrt((x - buildZones[i].x) * (x - buildZones[i].x) + (y - buildZones[i].y) * (y - buildZones[i].y)) < 200) { return false; } } // Check village house areas var allVillagePositions = leftVillagePositions.concat(rightVillagePositions); for (var i = 0; i < allVillagePositions.length; i++) { var house = allVillagePositions[i]; if (Math.sqrt((x - house.x) * (x - house.x) + (y - house.y) * (y - house.y)) < buffer) { return false; } } // Check against other trees for (var i = 0; i < treePositions.length; i++) { var tree = treePositions[i]; if (Math.sqrt((x - tree.x) * (x - tree.x) + (y - tree.y) * (y - tree.y)) < buffer) { return false; } } // Check against rocks for (var i = 0; i < rockPositions.length; i++) { var rock = rockPositions[i]; if (Math.sqrt((x - rock.x) * (x - rock.x) + (y - rock.y) * (y - rock.y)) < buffer) { return false; } } return true; } // Define fixed tree positions far from roads and tower installation points var fixedTreePositions = [{ x: 1700, y: 200 }, { x: 1800, y: 250 }, { x: 1650, y: 350 }, { x: 1750, y: 450 }, { x: 1900, y: 300 }, { x: 1950, y: 200 }, { x: 1600, y: 150 }, { x: 1850, y: 100 }, { x: 1950, y: 150 }, { x: 150, y: 300 }, { x: 250, y: 350 }, { x: 350, y: 400 }, { x: 200, y: 450 }, { x: 100, y: 500 }, { x: 300, y: 300 }, { x: 400, y: 350 }, { x: 500, y: 400 }, { x: 450, y: 300 }, { x: 550, y: 350 }, { x: 100, y: 1000 }, { x: 200, y: 1100 }, { x: 300, y: 1200 }, { x: 150, y: 1300 }, { x: 250, y: 1400 }, { x: 100, y: 1500 }, { x: 350, y: 1000 }, { x: 400, y: 1100 }, { x: 450, y: 1200 }, { x: 500, y: 1300 }, { x: 550, y: 1400 }, { x: 600, y: 1500 }, { x: 1700, y: 1000 }, { x: 1800, y: 1100 }, { x: 1900, y: 1200 }, { x: 1950, y: 1300 }, { x: 1850, y: 1400 }, { x: 1750, y: 1500 }, { x: 1650, y: 1000 }, { x: 1600, y: 1100 }, { x: 1550, y: 1200 }, { x: 1500, y: 1300 }, { x: 1450, y: 1400 }, { x: 1400, y: 1500 }, { x: 100, y: 2100 }, { x: 200, y: 2000 }, { x: 300, y: 1950 }, { x: 400, y: 2000 }, { x: 500, y: 2100 }, { x: 1600, y: 2100 }, { x: 1700, y: 2000 }]; // Define rock positions in empty spaces at least 200 pixels away from any object var rockPositions = [{ x: 200, y: 800 }, { x: 1700, y: 350 }, { x: 250, y: 1700 }, { x: 1800, y: 1500 }, { x: 700, y: 350 }, { x: 1600, y: 100 }, { x: 1900, y: 950 }, { x: 150, y: 1200 }, { x: 1700, y: 1200 }, { x: 650, y: 1750 }]; // Use the first 50 fixed positions (or however many we have) for (var i = 0; i < Math.min(50, fixedTreePositions.length); i++) { treePositions.push(fixedTreePositions[i]); } // Create the trees for (var i = 0; i < treePositions.length; i++) { var pos = treePositions[i]; var tree = isoContainer.addChild(LK.getAsset('tree', { anchorX: 0.5, anchorY: 0.5, x: pos.x, y: pos.y, scaleX: 1.0, scaleY: 1.0, alpha: 0.9 })); } // Create rocks at predefined positions for (var i = 0; i < rockPositions.length; i++) { var pos = rockPositions[i]; var rock = isoContainer.addChild(LK.getAsset('ruin', { anchorX: 0.5, anchorY: 0.5, x: pos.x, y: pos.y, scaleX: 1.2, scaleY: 1.2, alpha: 0.8 })); terrainFeatures.push(rock); } } function createBuildZones() { var zones = [ // enemyPath1: y=200, both sides { x: 900, y: 150 }, { x: 900, y: 250 }, // enemyPath1: Corner (1024, 200, right turn) { x: 974, y: 150 }, { x: 1074, y: 150 }, { x: 974, y: 250 }, { x: 1074, y: 250 }, // enemyPath1: U-shape (x=1024, y=200-600) { x: 974, y: 500 }, { x: 1074, y: 500 }, // enemyPath1: Corner (768, 1800, right turn) { x: 718, y: 1850 }, { x: 818, y: 1850 }, // enemyPath1: Corner (1024, 1800, down, intersection) { x: 974, y: 1750 }, { x: 1074, y: 1750 }, { x: 974, y: 1850 }, { x: 1074, y: 1850 }, // enemyPath2: y=800, both sides { x: 1448, y: 750 }, { x: 1448, y: 850 }, { x: 1148, y: 750 }, { x: 1148, y: 850 }, // enemyPath2: Corner (1024, 800, down) { x: 974, y: 750 }, { x: 1074, y: 750 }, { x: 974, y: 850 }, { x: 1074, y: 850 }, // enemyPath2: Corner (1024, 600, right turn, intersection) { x: 974, y: 550 }, { x: 1074, y: 550 }, { x: 974, y: 650 }, { x: 1074, y: 650 }, // enemyPath2: U-shape (x=1280, y=600-1800) { x: 1230, y: 650 }, { x: 1330, y: 650 }, { x: 1230, y: 750 }, { x: 1330, y: 750 }, { x: 1230, y: 850 }, { x: 1330, y: 850 }, // enemyPath3: Corner (1280, 50, down) { x: 1230, y: 0 }, { x: 1330, y: 0 }, { x: 1230, y: 100 }, { x: 1330, y: 100 }, // enemyPath3: Corner (1280, 1800, left turn) { x: 1230, y: 1850 }, { x: 1330, y: 1850 }]; for (var i = 0; i < zones.length; i++) { var zone = isoContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: zones[i].x, y: zones[i].y, alpha: 0.3 })); zone.zoneData = { x: zones[i].x, y: zones[i].y, occupied: false, originalAlpha: 0.3 }; buildZones.push(zone); } } function updateBuildZoneVisuals() { for (var i = 0; i < buildZones.length; i++) { var zone = buildZones[i]; if (zone.zoneData.occupied) { // Occupied zones are invisible zone.alpha = 0; } else if (selectedTowerType) { // Available zones are highlighted when a tower is selected zone.alpha = 0.6; zone.tint = 0x00FF00; // Green highlight for available zones } else { // Normal state when no tower is selected zone.alpha = zone.zoneData.originalAlpha; zone.tint = 0xFFFFFF; // Reset tint } } } var castleHealthBarBg = null; var castleHealthBarFill = null; function createCastle() { var castle = isoContainer.addChild(LK.getAsset('castle_' + castleType, { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2200 })); // Create health bar background positioned under the castle (moved down) castleHealthBarBg = isoContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2350, scaleX: 4, scaleY: 0.4 })); castleHealthBarBg.tint = 0x333333; // Create health bar fill positioned under the castle (moved down) castleHealthBarFill = isoContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2350, scaleX: 4, scaleY: 0.4 })); castleHealthBarFill.tint = 0x00FF00; // Store references for updating castle.healthBarBg = castleHealthBarBg; castle.healthBarFill = castleHealthBarFill; } function createMainMenu() { mainMenuContainer = new Container(); isoContainer.addChild(mainMenuContainer); // Main menu background var menuBg = mainMenuContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 40, scaleY: 50 })); menuBg.tint = 0x000000; menuBg.alpha = 0.8; // Title var titleText = mainMenuContainer.addChild(new Text2('Tower Defense', { size: 120, fill: 0xFFD700 })); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 800; // New Game Button var newGameBtn = mainMenuContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1200, scaleX: 8, scaleY: 2 })); newGameBtn.tint = 0x00AA00; var newGameText = mainMenuContainer.addChild(new Text2('New Game', { size: 60, fill: 0xFFFFFF })); newGameText.anchor.set(0.5, 0.5); newGameText.x = 1024; newGameText.y = 1200; // How to Play Button var howToPlayBtn = mainMenuContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1400, scaleX: 8, scaleY: 2 })); howToPlayBtn.tint = 0x0066CC; var howToPlayText = mainMenuContainer.addChild(new Text2('How to Play', { size: 60, fill: 0xFFFFFF })); howToPlayText.anchor.set(0.5, 0.5); howToPlayText.x = 1024; howToPlayText.y = 1400; // Credits Button var creditsBtn = mainMenuContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1600, scaleX: 8, scaleY: 2 })); creditsBtn.tint = 0xAA6600; var creditsText = mainMenuContainer.addChild(new Text2('Credits', { size: 60, fill: 0xFFFFFF })); creditsText.anchor.set(0.5, 0.5); creditsText.x = 1024; creditsText.y = 1600; // Button interactions newGameBtn.down = function () { startGame(); }; howToPlayBtn.down = function () { showHowToPlay(); }; creditsBtn.down = function () { showCredits(); }; } function showStoryIntro() { if (mainMenuContainer) { mainMenuContainer.alpha = 0.2; } storyIntroContainer = new Container(); isoContainer.addChild(storyIntroContainer); // Background var bg = storyIntroContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 45, scaleY: 50 })); bg.tint = 0x000000; bg.alpha = 0.95; // Story text var storyLines = ["The land is on the brink of ruin.", "", "Shadows rise from the east, and only your towers", "stand in their way.", "", "You must defend the kingdom wave after wave,", "until the last breath of hope remains..."]; for (var i = 0; i < storyLines.length; i++) { var storyText = storyIntroContainer.addChild(new Text2(storyLines[i], { size: 50, fill: 0xFFFFFF })); storyText.anchor.set(0.5, 0.5); storyText.x = 1024; storyText.y = 1000 + i * 80; } // Skip instruction var skipText = storyIntroContainer.addChild(new Text2('Click anywhere to begin...', { size: 40, fill: 0xFFD700 })); skipText.anchor.set(0.5, 0.5); skipText.x = 1024; skipText.y = 1700; // Auto-skip after 6 seconds or manual skip var skipTimer = LK.setTimeout(function () { startGameAfterIntro(); }, 6000); // Manual skip bg.down = function () { LK.clearTimeout(skipTimer); startGameAfterIntro(); }; } function startGameAfterIntro() { if (storyIntroContainer) { storyIntroContainer.destroy(); storyIntroContainer = null; } if (mainMenuContainer) { mainMenuContainer.destroy(); mainMenuContainer = null; } gameState = 'playing'; createSettingsIcon(); createPath(); createTerrain(); createCastle(); createShop(); createWave(); updateShopVisuals(); } function startGame() { showStoryIntro(); } function showHowToPlay() { if (mainMenuContainer) { mainMenuContainer.alpha = 0.3; } howToPlayContainer = new Container(); isoContainer.addChild(howToPlayContainer); // Background var bg = howToPlayContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 35, scaleY: 45 })); bg.tint = 0x000000; bg.alpha = 0.9; // Title var title = howToPlayContainer.addChild(new Text2('How to Play', { size: 80, fill: 0xFFD700 })); title.anchor.set(0.5, 0.5); title.x = 1024; title.y = 600; // Instructions var instructions = ['Build towers to defend your castle from waves of enemies', 'Click tower icons at bottom to select, then click build zones', 'Upgrade towers by clicking them when you have enough gold', 'Different towers have different abilities:', '• Infantry: Basic damage, good range', '• Freezer: Slows enemies down', '• Air: Targets flying enemies effectively', '• Poison: Damages over time', '• Tesla: Chain lightning damage', 'Survive 100 waves to win!']; for (var i = 0; i < instructions.length; i++) { var instructText = howToPlayContainer.addChild(new Text2(instructions[i], { size: 35, fill: 0xFFFFFF })); instructText.anchor.set(0.5, 0.5); instructText.x = 1024; instructText.y = 800 + i * 60; } // Back button var backBtn = howToPlayContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2000, scaleX: 6, scaleY: 2 })); backBtn.tint = 0xAA0000; var backText = howToPlayContainer.addChild(new Text2('Back', { size: 50, fill: 0xFFFFFF })); backText.anchor.set(0.5, 0.5); backText.x = 1024; backText.y = 2000; backBtn.down = function () { howToPlayContainer.destroy(); howToPlayContainer = null; if (mainMenuContainer) { mainMenuContainer.alpha = 1.0; } }; } function showCredits() { if (mainMenuContainer) { mainMenuContainer.alpha = 0.3; } creditsContainer = new Container(); isoContainer.addChild(creditsContainer); // Background var bg = creditsContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 25, scaleY: 30 })); bg.tint = 0x000000; bg.alpha = 0.9; // Title var title = creditsContainer.addChild(new Text2('Credits', { size: 80, fill: 0xFFD700 })); title.anchor.set(0.5, 0.5); title.x = 1024; title.y = 1000; // Credits info var gameDesignText = creditsContainer.addChild(new Text2('Game Design: ilker.mez', { size: 50, fill: 0xFFFFFF })); gameDesignText.anchor.set(0.5, 0.5); gameDesignText.x = 1024; gameDesignText.y = 1300; // Back button var backBtn = creditsContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1600, scaleX: 6, scaleY: 2 })); backBtn.tint = 0xAA0000; var backText = creditsContainer.addChild(new Text2('Back', { size: 50, fill: 0xFFFFFF })); backText.anchor.set(0.5, 0.5); backText.x = 1024; backText.y = 1600; backBtn.down = function () { creditsContainer.destroy(); creditsContainer = null; if (mainMenuContainer) { mainMenuContainer.alpha = 1.0; } }; } function createSettingsIcon() { settingsIcon = LK.gui.topLeft.addChild(LK.getAsset('build_zone', { anchorX: 0, anchorY: 0, x: 20, y: 400, scaleX: 2, scaleY: 2 })); settingsIcon.tint = 0x666666; var settingsText = LK.gui.topLeft.addChild(new Text2('⚙', { size: 60, fill: 0xFFFFFF })); settingsText.anchor.set(0, 0); settingsText.x = 20; settingsText.y = 400; settingsIcon.down = function () { showSettings(); }; } function showSettings() { if (settingsContainer) { return; } settingsContainer = new Container(); isoContainer.addChild(settingsContainer); // Background var bg = settingsContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 20, scaleY: 25 })); bg.tint = 0x000000; bg.alpha = 0.9; // Title var title = settingsContainer.addChild(new Text2('Settings', { size: 80, fill: 0xFFD700 })); title.anchor.set(0.5, 0.5); title.x = 1024; title.y = 1000; // Return to Main Menu button var mainMenuBtn = settingsContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1200, scaleX: 10, scaleY: 2 })); mainMenuBtn.tint = 0x0066CC; var mainMenuText = settingsContainer.addChild(new Text2('Return to Main Menu', { size: 50, fill: 0xFFFFFF })); mainMenuText.anchor.set(0.5, 0.5); mainMenuText.x = 1024; mainMenuText.y = 1200; // Mute/Unmute button var muteBtn = settingsContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1400, scaleX: 8, scaleY: 2 })); muteBtn.tint = 0x00AA00; var muteText = settingsContainer.addChild(new Text2('Mute Audio', { size: 50, fill: 0xFFFFFF })); muteText.anchor.set(0.5, 0.5); muteText.x = 1024; muteText.y = 1400; // Close button var closeBtn = settingsContainer.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1600, scaleX: 6, scaleY: 2 })); closeBtn.tint = 0xAA0000; var closeText = settingsContainer.addChild(new Text2('Close', { size: 50, fill: 0xFFFFFF })); closeText.anchor.set(0.5, 0.5); closeText.x = 1024; closeText.y = 1600; // Button interactions mainMenuBtn.down = function () { // Reset game state gameState = 'mainMenu'; // Clear all game objects for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].destroy(); } enemies = []; for (var i = towers.length - 1; i >= 0; i--) { towers[i].destroy(); } towers = []; for (var i = projectiles.length - 1; i >= 0; i--) { projectiles[i].destroy(); } projectiles = []; // Reset game variables currentWave = 0; coins = 160; castleHealth = 100; waveInProgress = false; // Clear UI elements if (settingsIcon) { settingsIcon.destroy(); settingsIcon = null; } if (muteIndicator) { muteIndicator.destroy(); muteIndicator = null; } // Close settings and show main menu settingsContainer.destroy(); settingsContainer = null; createMainMenu(); }; muteBtn.down = function () { // Toggle audio mute state audioMuted = !audioMuted; // Update button text and color if (audioMuted) { muteText.setText('Unmute Audio'); muteBtn.tint = 0xAA0000; // Create mute indicator overlay on settings icon if (settingsIcon && !muteIndicator) { muteIndicator = LK.gui.topLeft.addChild(new Text2('✕', { size: 40, fill: 0xFF0000 })); muteIndicator.anchor.set(0, 0); muteIndicator.x = 30; muteIndicator.y = 410; // Animate the mute indicator tween(muteIndicator, { scaleX: 1.3, scaleY: 1.3, alpha: 0.8 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { // Auto-hide the X indicator after 2 seconds LK.setTimeout(function () { if (muteIndicator && audioMuted) { tween(muteIndicator, { alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { if (muteIndicator && muteIndicator.destroy) { muteIndicator.destroy(); muteIndicator = null; } } }); } }, 2000); } }); } } else { muteText.setText('Mute Audio'); muteBtn.tint = 0x00AA00; // Remove mute indicator immediately when unmuting if (muteIndicator && muteIndicator.destroy) { muteIndicator.destroy(); muteIndicator = null; } } }; closeBtn.down = function () { settingsContainer.destroy(); settingsContainer = null; }; } function showUpgradeRestrictedMessage(level, requiredWave) { // Create temporary message var message = new Text2('Upgrade level ' + level + ' is locked until Wave ' + requiredWave + '.', { size: 45, fill: 0xFFAA00 }); message.anchor.set(0.5, 0.5); message.x = 1024; message.y = 1100; isoContainer.addChild(message); // Animate and remove message tween(message, { y: message.y - 50, alpha: 0 }, { duration: 2500, easing: tween.easeOut, onFinish: function onFinish() { if (message && message.destroy) { message.destroy(); } } }); } function showUnlockMessage(towerType, unlockWave) { // Create temporary message var message = new Text2('This tower will unlock at Wave ' + unlockWave + '.', { size: 50, fill: 0xFFFF00 }); message.anchor.set(0.5, 0.5); message.x = 1024; message.y = 1200; isoContainer.addChild(message); // Animate and remove message tween(message, { y: message.y - 50, alpha: 0 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { if (message && message.destroy) { message.destroy(); } } }); } function updateShopVisuals() { // Update tower button visuals based on unlock status var towers = [{ btn: infantryBtn, cost: infantryCost, name: infantryName, type: 'infantry', price: 80 }, { btn: frostBtn, cost: frostCost, name: frostName, type: 'frost', price: 120 }, { btn: antiairBtn, cost: antiairCost, name: antiairName, type: 'antiair', price: 100 }, { btn: poisonBtn, cost: poisonCost, name: poisonName, type: 'poison', price: 130 }, { btn: teslaBtn, cost: teslaCost, name: teslaName, type: 'tesla', price: 150 }]; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var isUnlocked = currentWave >= towerUnlockWaves[tower.type]; var canAfford = coins >= tower.price; if (!isUnlocked) { // Greyed out for locked towers tower.btn.tint = 0x666666; tower.btn.alpha = 0.5; tower.cost.tint = 0x666666; tower.name.tint = 0x666666; } else if (!canAfford) { // Red tint for unaffordable towers tower.btn.tint = 0xFFFFFF; tower.btn.alpha = 1.0; tower.cost.tint = 0xFF0000; tower.name.tint = 0xFFFFFF; } else { // Normal appearance for available towers tower.btn.tint = 0xFFFFFF; tower.btn.alpha = 1.0; tower.cost.tint = 0xFFD700; tower.name.tint = 0xFFFFFF; } } } function createShop() { var shopBg = LK.gui.bottom.addChild(LK.getAsset('shop_bg', { anchorX: 0.5, anchorY: 1, alpha: 1.0 })); shopBg.tint = 0x000000; infantryBtn = LK.gui.bottom.addChild(LK.getAsset('tower_infantry', { anchorX: 0.5, anchorY: 1, x: -400, y: -60 })); frostBtn = LK.gui.bottom.addChild(LK.getAsset('tower_frost', { anchorX: 0.5, anchorY: 1, x: -200, y: -60 })); antiairBtn = LK.gui.bottom.addChild(LK.getAsset('tower_antiair', { anchorX: 0.5, anchorY: 1, x: 0, y: -60 })); poisonBtn = LK.gui.bottom.addChild(LK.getAsset('tower_poison', { anchorX: 0.5, anchorY: 1, x: 200, y: -60 })); teslaBtn = LK.gui.bottom.addChild(LK.getAsset('tower_tesla', { anchorX: 0.5, anchorY: 1, x: 400, y: -60 })); // Add cost labels with larger, more distinct text - updated balanced costs infantryCost = LK.gui.bottom.addChild(new Text2('80g', { size: 32, fill: 0xFFD700 })); infantryCost.anchor.set(0.5, 1); infantryCost.x = -400; infantryCost.y = -130; frostCost = LK.gui.bottom.addChild(new Text2('120g', { size: 32, fill: 0xFFD700 })); frostCost.anchor.set(0.5, 1); frostCost.x = -200; frostCost.y = -130; antiairCost = LK.gui.bottom.addChild(new Text2('100g', { size: 32, fill: 0xFFD700 })); antiairCost.anchor.set(0.5, 1); antiairCost.x = 0; antiairCost.y = -130; poisonCost = LK.gui.bottom.addChild(new Text2('130g', { size: 32, fill: 0xFFD700 })); poisonCost.anchor.set(0.5, 1); poisonCost.x = 200; poisonCost.y = -130; teslaCost = LK.gui.bottom.addChild(new Text2('150g', { size: 32, fill: 0xFFD700 })); teslaCost.anchor.set(0.5, 1); teslaCost.x = 400; teslaCost.y = -130; // Add tower name labels below the cost text infantryName = LK.gui.bottom.addChild(new Text2('Infantry', { size: 28, fill: 0xFFFFFF })); infantryName.anchor.set(0.5, 1); infantryName.x = -400; infantryName.y = -160; frostName = LK.gui.bottom.addChild(new Text2('Freezer', { size: 28, fill: 0xFFFFFF })); frostName.anchor.set(0.5, 1); frostName.x = -200; frostName.y = -160; antiairName = LK.gui.bottom.addChild(new Text2('Air', { size: 28, fill: 0xFFFFFF })); antiairName.anchor.set(0.5, 1); antiairName.x = 0; antiairName.y = -160; poisonName = LK.gui.bottom.addChild(new Text2('Poison', { size: 28, fill: 0xFFFFFF })); poisonName.anchor.set(0.5, 1); poisonName.x = 200; poisonName.y = -160; teslaName = LK.gui.bottom.addChild(new Text2('Tesla', { size: 28, fill: 0xFFFFFF })); teslaName.anchor.set(0.5, 1); teslaName.x = 400; teslaName.y = -160; infantryBtn.down = function () { if (currentWave < towerUnlockWaves.infantry) { showUnlockMessage('infantry', towerUnlockWaves.infantry); return; } if (coins >= 80) { selectedTowerType = 'infantry'; updateBuildZoneVisuals(); } }; frostBtn.down = function () { if (currentWave < towerUnlockWaves.frost) { showUnlockMessage('frost', towerUnlockWaves.frost); return; } if (coins >= 120) { selectedTowerType = 'frost'; updateBuildZoneVisuals(); } }; antiairBtn.down = function () { if (currentWave < towerUnlockWaves.antiair) { showUnlockMessage('antiair', towerUnlockWaves.antiair); return; } if (coins >= 100) { selectedTowerType = 'antiair'; updateBuildZoneVisuals(); } }; poisonBtn.down = function () { if (currentWave < towerUnlockWaves.poison) { showUnlockMessage('poison', towerUnlockWaves.poison); return; } if (coins >= 130) { selectedTowerType = 'poison'; updateBuildZoneVisuals(); } }; teslaBtn.down = function () { if (currentWave < towerUnlockWaves.tesla) { showUnlockMessage('tesla', towerUnlockWaves.tesla); return; } if (coins >= 150) { selectedTowerType = 'tesla'; updateBuildZoneVisuals(); } }; } function createWave() { if (currentWave >= maxWaves && !endlessMode) { // Enable endless mode endlessMode = true; endlessScore = 0; // Show endless mode notification var endlessNotification = new Text2('♾️ ENDLESS MODE UNLOCKED! ♾️', { size: 80, fill: 0xFFD700 }); endlessNotification.anchor.set(0.5, 0.5); endlessNotification.x = 1024; endlessNotification.y = 1000; isoContainer.addChild(endlessNotification); tween(endlessNotification, { scaleX: 1.2, scaleY: 1.2, alpha: 0 }, { duration: 3000, easing: tween.easeOut, onFinish: function onFinish() { if (endlessNotification && endlessNotification.destroy) { endlessNotification.destroy(); } } }); } currentWave++; if (endlessMode) { endlessScore++; } waveEnemies = []; // Scale enemy count based on wave number with new progression var enemyCount; if (currentWave <= 5) { // Waves 1-5: 15 to 20 enemies enemyCount = 15 + Math.floor(Math.random() * 6); } else if (currentWave <= 15) { // Waves 6-15: 20 to 25 enemies enemyCount = 20 + Math.floor(Math.random() * 6); } else if (currentWave <= 30) { // Waves 16-30: 25 to 35 enemies enemyCount = 25 + Math.floor(Math.random() * 11); } else if (currentWave <= 60) { // Waves 31-60: 35 to 45 enemies enemyCount = 35 + Math.floor(Math.random() * 11); } else if (currentWave <= 97) { // Waves 61-97: 45 to 55 enemies enemyCount = 45 + Math.floor(Math.random() * 11); } else { // Waves 98-100: 60 to 75 enemies enemyCount = 60 + Math.floor(Math.random() * 16); } // Calculate available enemy strength levels based on wave var maxStrengthLevel = Math.min(10, Math.floor((currentWave + 1) / 2)); var minStrengthLevel = Math.max(1, maxStrengthLevel - 2); for (var i = 0; i < enemyCount; i++) { // Define which enemies can appear based on wave number - exact wave appearances as specified var availableEnemies = []; // 30 balanced enemies with exact wave appearance and phase-out logic if (currentWave >= 1 && currentWave <= 5) { availableEnemies.push('goblin_scout'); } if (currentWave >= 4 && currentWave <= 8) { availableEnemies.push('mire_crawler'); } if (currentWave >= 7 && currentWave <= 12) { availableEnemies.push('fang_imp'); } if (currentWave >= 10 && currentWave <= 16) { availableEnemies.push('crimson_leaper'); } if (currentWave >= 13 && currentWave <= 20) { availableEnemies.push('rustback_beetle'); } if (currentWave >= 16 && currentWave <= 24) { availableEnemies.push('gravel_hound'); } if (currentWave >= 19 && currentWave <= 28) { availableEnemies.push('vine_stalker'); } if (currentWave >= 22 && currentWave <= 32) { availableEnemies.push('ashborne_wraith'); } if (currentWave >= 25 && currentWave <= 36) { availableEnemies.push('stonehide_orc'); } if (currentWave >= 28 && currentWave <= 40) { availableEnemies.push('toxic_maw'); } if (currentWave >= 31 && currentWave <= 43) { availableEnemies.push('frostback_boar'); } if (currentWave >= 34 && currentWave <= 46) { availableEnemies.push('ironmaw_troll'); } if (currentWave >= 37 && currentWave <= 49) { availableEnemies.push('volcanic_strider'); } if (currentWave >= 40 && currentWave <= 52) { availableEnemies.push('stormblade_knight'); } if (currentWave >= 43 && currentWave <= 55) { availableEnemies.push('twilight_serpent'); } if (currentWave >= 46 && currentWave <= 58) { availableEnemies.push('obsidian_colossus'); } if (currentWave >= 49 && currentWave <= 61) { availableEnemies.push('spectral_ravager'); } if (currentWave >= 52 && currentWave <= 64) { availableEnemies.push('boneclad_warbeast'); } if (currentWave >= 55 && currentWave <= 67) { availableEnemies.push('plague_titan'); } if (currentWave >= 58 && currentWave <= 70) { availableEnemies.push('abyss_reaper'); } if (currentWave >= 61 && currentWave <= 73) { availableEnemies.push('wraith_bringer'); } if (currentWave >= 64 && currentWave <= 76) { availableEnemies.push('inferno_crawler'); } if (currentWave >= 67 && currentWave <= 79) { availableEnemies.push('thornback_hydra'); } if (currentWave >= 70 && currentWave <= 82) { availableEnemies.push('ghostfire_shade'); } if (currentWave >= 73 && currentWave <= 85) { availableEnemies.push('storm_herald'); } if (currentWave >= 76 && currentWave <= 88) { availableEnemies.push('voidling_runner'); } if (currentWave >= 79 && currentWave <= 91) { availableEnemies.push('doom_shell'); } if (currentWave >= 82 && currentWave <= 94) { availableEnemies.push('nether_juggernaut'); } if (currentWave >= 85) { availableEnemies.push('abyss_howler'); } if (currentWave >= 88) { availableEnemies.push('void_devourer'); } // Legacy enemy support for existing assets if (currentWave >= 1) { availableEnemies.push('goblin'); } if (currentWave >= 4) { availableEnemies.push('crawler'); } if (currentWave >= 7) { availableEnemies.push('bandit'); } if (currentWave >= 10) { availableEnemies.push('golem'); } if (currentWave >= 13) { availableEnemies.push('imp'); } if (currentWave >= 16) { availableEnemies.push('knight'); } if (currentWave >= 19) { availableEnemies.push('beast'); } if (currentWave >= 22) { availableEnemies.push('mage'); } if (currentWave >= 25) { availableEnemies.push('reaver'); } if (currentWave >= 28) { availableEnemies.push('doom'); } // Select random enemy from available types var enemyType = availableEnemies[Math.floor(Math.random() * availableEnemies.length)]; // Assign strength level within available range var strengthLevel = minStrengthLevel + Math.floor(Math.random() * (maxStrengthLevel - minStrengthLevel + 1)); var pathChoice = 1; if (currentWave >= 6) { pathChoice = Math.floor(Math.random() * 3) + 1; } else if (currentWave >= 3) { pathChoice = Math.floor(Math.random() * 2) + 1; } waveEnemies.push({ type: enemyType, pathIndex: pathChoice, strengthLevel: strengthLevel, spawnTime: LK.ticks + i * 90 }); } waveInProgress = true; if (!audioMuted) { LK.getSound('wave_start').play(); } // Show round start announcement if (roundStartText && roundStartText.destroy) { roundStartText.destroy(); } roundStartText = new Text2('Wave ' + currentWave + ' / 100 Starting...', { size: 80, fill: 0xFFFFFF }); roundStartText.anchor.set(0.5, 0.5); roundStartText.x = 1024; roundStartText.y = 1000; isoContainer.addChild(roundStartText); // Animate the announcement text tween(roundStartText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { tween(roundStartText, { alpha: 0 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { if (roundStartText && roundStartText.destroy) { roundStartText.destroy(); roundStartText = null; } } }); } }); } // Create background panel for castle lives text var castleLivesTextBg = isoContainer.addChild(LK.getAsset('build_zone', { anchorX: 0, anchorY: 0.5, x: 30, y: 1900, scaleX: 8, scaleY: 1.8 })); castleLivesTextBg.tint = 0x000000; castleLivesTextBg.alpha = 0.8; var castleLivesText = new Text2('Castle Health: 100', { size: 70, fill: 0xFF4444, font: "'Arial Black', Impact, 'Arial Bold'" }); castleLivesText.anchor.set(0, 0.5); castleLivesText.x = 50; castleLivesText.y = 1900; isoContainer.addChild(castleLivesText); // Create background panel for gold text var coinsTextBg = isoContainer.addChild(LK.getAsset('build_zone', { anchorX: 0, anchorY: 0.5, x: 30, y: 2180, scaleX: 6, scaleY: 1.8 })); coinsTextBg.tint = 0x000000; coinsTextBg.alpha = 0.8; var coinsText = new Text2('Gold: 160', { size: 70, fill: 0xFFD700, font: "'Arial Black', Impact, 'Arial Bold'" }); coinsText.anchor.set(0, 0.5); coinsText.x = 50; coinsText.y = 2180; isoContainer.addChild(coinsText); var waveText = new Text2('Wave: 0/100', { size: 40, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); LK.gui.top.addChild(waveText); var roundStartText = null; if (gameState === 'mainMenu') { createMainMenu(); } else { createPath(); createTerrain(); createCastle(); createShop(); } game.down = function (x, y, obj) { // Clear any existing upgrade panel and range indicator when clicking elsewhere if (upgradePanel && upgradePanel.destroy && selectedTower && obj && !selectedTower.intersects(obj)) { upgradePanel.destroy(); upgradePanel = null; selectedTower = null; // Clear range indicator if (activeRangeIndicator && activeRangeIndicator.destroy) { activeRangeIndicator.destroy(); activeRangeIndicator = null; } } // Fallback for old upgradeText system if (upgradeText && upgradeText.destroy && selectedTower && obj && !selectedTower.intersects(obj)) { upgradeText.destroy(); upgradeText = null; selectedTower = null; } // Use screen coordinates directly for top-down view var isoPos = { x: x, y: y }; if (selectedTowerType && gameState === 'playing') { // Check if clicking on a valid, unoccupied build zone var validBuildZone = null; for (var i = 0; i < buildZones.length; i++) { if (!buildZones[i].zoneData.occupied) { var distance = Math.sqrt((buildZones[i].x - isoPos.x) * (buildZones[i].x - isoPos.x) + (buildZones[i].y - isoPos.y) * (buildZones[i].y - isoPos.y)); if (distance < 40) { validBuildZone = buildZones[i]; break; } } } // Only place tower if clicking on a valid build zone and player has enough coins if (validBuildZone) { var tower = new Tower(selectedTowerType); if (coins >= tower.cost) { // Place tower exactly at build zone center tower.x = validBuildZone.x; tower.y = validBuildZone.y; towers.push(tower); isoContainer.addChild(tower); coins -= tower.cost; coinsText.setText('Gold: ' + coins); selectedTowerType = null; // Mark build zone as occupied validBuildZone.zoneData.occupied = true; updateBuildZoneVisuals(); // Play placement sound if (!audioMuted) { LK.getSound('placement').play(); } } } else { // Clear selection if clicking outside build zones selectedTowerType = null; updateBuildZoneVisuals(); } } }; game.update = function () { if (gameState === 'mainMenu' || gameState === 'setup') { return; } if (gameState !== 'playing') { return; } if (waveInProgress) { for (var i = waveEnemies.length - 1; i >= 0; i--) { var enemyData = waveEnemies[i]; if (LK.ticks >= enemyData.spawnTime) { var enemy = new Enemy(enemyData.type, currentWave, enemyData.pathIndex, enemyData.strengthLevel); enemies.push(enemy); isoContainer.addChild(enemy); waveEnemies.splice(i, 1); } } if (waveEnemies.length === 0 && enemies.length === 0) { waveInProgress = false; nextWaveTimer = LK.ticks + 180; } } if (!waveInProgress && LK.ticks >= nextWaveTimer && enemies.length === 0) { createWave(); } for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.reachedCastle) { castleHealth -= 15; // Update castle lives text castleLivesText.setText('Castle Health: ' + castleHealth); // Update health bar with smooth animation var healthPercent = castleHealth / 100; if (castleHealthBarFill) { // Animate health bar scale change smoothly tween(castleHealthBarFill, { scaleX: 4 * healthPercent }, { duration: 300 }); // Update color based on health percentage - green to red gradient var redComponent = Math.floor(255 * (1 - healthPercent)); var greenComponent = Math.floor(255 * healthPercent); var healthColor = redComponent << 16 | greenComponent << 8 | 0x00; castleHealthBarFill.tint = healthColor; // Flash effect when taking damage LK.effects.flashObject(castleHealthBarFill, 0xFF0000, 200); } enemy.destroy(); enemies.splice(i, 1); if (castleHealth <= 0) { LK.showGameOver(); return; } } } for (var i = projectiles.length - 1; i >= 0; i--) { var projectile = projectiles[i]; if (projectile.hasReachedTarget) { if (projectile.type === 'cannonball') { for (var j = 0; j < enemies.length; j++) { var enemy = enemies[j]; var distance = Math.sqrt((enemy.x - projectile.x) * (enemy.x - projectile.x) + (enemy.y - projectile.y) * (enemy.y - projectile.y)); if (distance <= 50) { if (enemy.takeDamage(projectile.damage)) { coins += Math.floor(enemy.coinValue * 0.7); coinsText.setText('Gold: ' + coins); // Create death effect at enemy position tween(enemy, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, easing: tween.easeOut }); enemy.destroy(); enemies.splice(j, 1); j--; } } } } else { for (var j = 0; j < enemies.length; j++) { var enemy = enemies[j]; var distance = Math.sqrt((enemy.x - projectile.x) * (enemy.x - projectile.x) + (enemy.y - projectile.y) * (enemy.y - projectile.y)); if (distance <= 25) { // Apply slowing effect for frost towers if (projectile.towerType === 'frost') { // Find the tower that shot this projectile to get its slow percentage var frostTower = null; for (var k = 0; k < towers.length; k++) { if (towers[k].type === 'frost') { var towerDistance = Math.sqrt((towers[k].x - projectile.x) * (towers[k].x - projectile.x) + (towers[k].y - projectile.y) * (towers[k].y - projectile.y)); if (towerDistance <= towers[k].range + 50) { // Some tolerance for projectile travel frostTower = towers[k]; break; } } } if (frostTower) { enemy.applyFrostSlow(frostTower.slowPercentage); } } if (enemy.takeDamage(projectile.damage)) { coins += Math.floor(enemy.coinValue * 0.7); coinsText.setText('Gold: ' + coins); // Create death effect at enemy position tween(enemy, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, easing: tween.easeOut }); enemy.destroy(); enemies.splice(j, 1); } break; } } } projectile.destroy(); projectiles.splice(i, 1); } else if (projectile.x < -50 || projectile.x > 2098 || projectile.y < -50 || projectile.y > 2782) { projectile.destroy(); projectiles.splice(i, 1); } } if (endlessMode) { waveText.setText('Endless Mode - Survived: ' + endlessScore + ' waves'); } else { waveText.setText('Wave: ' + currentWave + '/' + maxWaves); } updateShopVisuals(); };
===================================================================
--- original.js
+++ change.js
@@ -639,9 +639,13 @@
projectile.chainLightning = self.chainLightning;
projectiles.push(projectile);
isoContainer.addChild(projectile);
if (!audioMuted) {
- LK.getSound('shoot_arrow').play();
+ if (self.type === 'tesla') {
+ LK.getSound('tesla_knock').play();
+ } else {
+ LK.getSound('shoot_arrow').play();
+ }
}
};
self.getUpgradeCost = function () {
// Calculate upgrade cost: Level 1 = base price, then +80% each level
A lake with a 3d view. In-Game asset. 2d. High contrast. No shadows
tree. In-Game asset. 2d. High contrast. No shadows
house. In-Game asset. 2d. High contrast. No shadows
magic ball. In-Game asset. 2d. High contrast. No shadows
arrow bullet. In-Game asset. 2d. High contrast. No shadows
realistic majestic castle. In-Game asset. 2d. High contrast. No shadows
dirt road. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
This is a button, the outer part is red, the middle circle is red, the 2nd circle is dark gray. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
village house. In-Game asset. 2d. High contrast. No shadows
poisonous tower realistic. In-Game asset. 2d. High contrast. No shadows
freezing tower realistic. In-Game asset. 2d. High contrast. No shadows
goblin. In-Game asset. 2d. High contrast. No shadows
goblin emperor. In-Game asset. 2d. High contrast. No shadows
haydut goblin. In-Game asset. 2d. High contrast. No shadows
titan goblin. In-Game asset. 2d. High contrast. No shadows
goblin tazısı. In-Game asset. 2d. High contrast. No shadows
gezgin goblin. In-Game asset. 2d. High contrast. No shadows
kıyamet canavarı goblin. In-Game asset. 2d. High contrast. No shadows
kemikli iskelet goblin. In-Game asset. 2d. High contrast. No shadows
buzul goblin. In-Game asset. 2d. High contrast. No shadows
golem goblin. In-Game asset. 2d. High contrast. No shadows
kadın goblin imparatoru. In-Game asset. 2d. High contrast. No shadows
demir goblin. In-Game asset. 2d. High contrast. No shadows
şövalye goblin. In-Game asset. 2d. High contrast. No shadows
obsidyen dev goblin. In-Game asset. 2d. High contrast. No shadows
üzerinde okçu olan, ahşap, okçu kulesi. In-Game asset. 2d. High contrast. No shadows
ahşap hava savunma kulesi, üstünde arbalet olsun. In-Game asset. 2d. High contrast. No shadows
antik elektrik atıcı kule. In-Game asset. 2d. High contrast. No shadows
goblin canavar. In-Game asset. 2d. High contrast. No shadows
volkanik gezgini goblin. In-Game asset. 2d. High contrast. No shadows
kahverengi gezgin eli bıçaklı goblin. In-Game asset. 2d. High contrast. No shadows
büyücü goblin. In-Game asset. 2d. High contrast. No shadows
paslı böcek goblin. In-Game asset. 2d. High contrast. No shadows
spektral yıkıcı goblin. In-Game asset. 2d. High contrast. No shadows
taş goblin. In-Game asset. 2d. High contrast. No shadows
kimyasalcı goblin, boydan. In-Game asset. 2d. High contrast. No shadows
yuvarlak havan topu mermisi. In-Game asset. 2d. High contrast. No shadows
karanlık büyücü goblin. In-Game asset. 2d. High contrast. No shadows
yeşil yapraklı dövüşçü goblin. In-Game asset. 2d. High contrast. No shadows
kayalık. In-Game asset. 2d. High contrast. No shadows
uçan küllü goblin. In-Game asset. 2d. High contrast. No shadows
uçan goblin yılan. In-Game asset. 2d. High contrast. No shadows
kırmızı kaslı uçan goblin. In-Game asset. 2d. High contrast. No shadows
uçan karanlık goblin. In-Game asset. 2d. High contrast. No shadows
uçan goblin yağmacı. In-Game asset. 2d. High contrast. No shadows
uçan fırtına golem. In-Game asset. 2d. High contrast. No shadows