User prompt
Please update the main menu title: 1. Remove the current title text “Tower Defence”. 2. Replace it with the new title: **“Goblin Invasion”** 3. Apply the same font style and size unless specified otherwise. 4. Ensure it is centered and visually aligned as before. Optional: - Add a slight glow, shadow, or green-themed color to match the goblin theme.
User prompt
do this
User prompt
Please update the UI visibility behavior as follows: 1. The following HUD elements should be hidden on the main menu screen: - Gold amount display - Castle Health bar and label - Wave counter at the top 2. These UI elements should become **visible only after the player clicks “New Game” or the game actually starts**. 3. Ensure: - These elements do not overlap or flash when hidden - They remain hidden on the credits screen or any intro cutscene 4. Bonus: Add a short fade-in animation (optional) when these elements appear as the game starts. This makes the menu cleaner and keeps gameplay UI contextually appropriate. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please reduce the gold rewards gained from killing enemies by 35%. Instructions: 1. Apply a global multiplier of **0.65x** to all gold rewards dropped by defeated enemies. - Example: if an enemy used to give 20 gold, it now gives 13. - This rule must apply universally unless individual exceptions are defined. 2. Ensure that the floating text (e.g., “+20”) shows the **updated amount** (e.g., “+13”). 3. This change should only affect enemy gold rewards. Do not modify: - Tower prices - Upgrade costs - Passive gold income (if any) This change will increase game difficulty and make resource management more strategic.
User prompt
Please reduce the gold reward gained from killing enemies by 30%. Instructions: 1. Apply a global multiplier of **0.7x** to all coin/gold values dropped by defeated enemies. - Example: if an enemy used to give 10 gold, it now gives 7. - This should affect all enemies equally, unless a specific override is defined. 2. Ensure the floating gold text (e.g., “+10”) reflects the **new reduced value**. 3. Verify that this does not affect upgrade or tower prices — only the reward amount per enemy. This adjustment should increase the challenge by making tower placement and upgrade choices more strategic.
User prompt
Attacks from towers must hit the enemy they are shooting at 100% of the time.
User prompt
Please ensure the following behavior for projectile logic: 1. **Guaranteed Hit Logic:** - All projectiles fired by towers should automatically adjust their trajectory to **always hit the assigned enemy** unless the enemy dies or leaves range. - This prevents projectile misses caused by desync or timing issues in higher speed modes. 2. **Projectile Speed Sync:** - Projectile movement speed must scale with the current game speed: • At X1 speed → normal projectile speed • At X2 speed → projectile speed ×2 • At X3 speed → projectile speed ×3 - This keeps timing consistent and ensures impact occurs at the correct moment. 3. Ensure: - Damage is registered on contact regardless of speed. - There is no delay or misfire due to enemy movement speed increasing. This will make projectile combat feel accurate, balanced, and responsive across all speed settings.
User prompt
There is a gameplay issue when using the X2 and X3 speed modes: Problem: - At higher speeds, towers are firing projectiles, but many of them seem to miss enemies even when properly aimed. - In X1 (normal speed), towers can kill all enemies in early waves. But in X3 mode with identical towers, many enemies survive. - This suggests that **projectile timing, collision detection, or enemy movement interpolation** is not properly synchronized with the increased game speed. Please fix this by: 1. Ensuring that all of the following systems correctly scale with game speed: • Enemy movement and animation • Projectile travel speed and position updates • Collision detection intervals and logic • Tower fire rate and cooldowns • Damage registration timing 2. Confirm that at higher speeds (X2/X3), the game logic behaves exactly the same as in X1 — just faster. 3. Test this especially in early waves with minimal towers to ensure consistent balance across all speed settings. This issue affects combat fairness and must be addressed to preserve proper gameplay balance.
User prompt
Please add the following coordinates as **new valid tower placement spots** (not enemy path points): Tower Placement Coordinates: { x: 1230, y: 1344 }, { x: 1330, y: 1344 }, { x: 1230, y: 1244 }, { x: 1330, y: 1244 }, { x: 1230, y: 1444 }, { x: 1330, y: 1444 } Instructions: 1. These positions should be added to the current list of valid tower build locations. 2. Ensure that when the player clicks on these positions, they can: • Select and place any currently unlocked tower • See the placement zone highlight (if applicable) 3. These new tower spots should follow the same interaction rules as existing build zones (1 tower per spot, proper collision, etc.) 4. Make sure these spots are not overlapping enemy paths and do not block enemy movement. This update gives the player more tower options, especially near the upper part of the map.
User prompt
Please add the following coordinates to Enemy Path 2 (second enemy route): Coordinates to append: { x: 1230, y: 1344 }, { x: 1330, y: 1344 }, { x: 1230, y: 1244 }, { x: 1330, y: 1244 }, { x: 1230, y: 1444 }, { x: 1330, y: 1444 } Instructions: 1. These coordinates should be added to the existing sequence of Path 2. 2. Maintain the order listed above — they define specific waypoints the enemies must follow. 3. Ensure the enemy movement remains smooth and properly transitions through these points. 4. Test the new path to confirm that enemies follow it correctly without cutting corners or skipping waypoints. This addition expands Path 2 and allows for more strategic tower placement around the new route.
User prompt
hatalı yerleri düzelt
Code edit (1 edits merged)
Please save this source code
User prompt
There is a visual/UI issue with the upgrade panel when placing towers near the top edge of the map. Problem: - When a tower is placed in a build spot near the top of the map and the player clicks it, the upgrade panel opens upward and ends up **outside of the visible screen area**. - As a result, the player cannot access the upgrade or sell buttons. Please fix this by: 1. **Auto-Positioning the Upgrade Panel:** - Detect the tower's Y position on screen. - If the tower is placed too close to the top edge, make the upgrade panel open **downward** instead of upward. - Ensure the panel is always fully visible and never clipped off-screen. 2. Alternatively: - Use a smart positioning system that dynamically repositions the panel **based on available screen space.** 3. Also check this behavior for towers placed near the bottom or left/right edges of the screen. This fix is critical for consistent gameplay and player control.
User prompt
Please add a functional "Pause" button next to the X1 speed button, aligned as part of the speed control group. 1. Button Layout: - Place a “Pause” button to the left of the X1 button: [Pause] [X1] [X2] [X3] - Ensure all four buttons are aligned horizontally and maintain the current positioning (already offset 300px left and 100px up). 2. Pause Functionality: - When the player clicks "Pause": • All enemy movement and attacks are frozen. • All animations stop (projectiles, enemy pathing, etc.) • Player is still able to: - Place towers - Upgrade towers - View tooltips and interface - Clicking "Pause" again should resume the last selected speed (X1, X2, or X3). 3. Visual Feedback: - Highlight the “Pause” button when active (e.g., change color or glow). - Optional: Display a subtle “Game Paused” label somewhere on screen. This feature allows players to stop the action and focus on strategy without being rushed.
User prompt
Please adjust the position of the X1, X2, X3 speed control buttons: - Move the entire group **50 pixels upward** from their current position. - Keep the 300-pixel left offset as previously defined. - Buttons should remain fixed (anchored) to the top of the screen, not relative to the map. - Ensure they remain fully visible and do not overlap with other UI elements. This final adjustment should position the buttons exactly as intended.
User prompt
Please adjust the position of the X1, X2, X3 speed control buttons: - Move the entire group **50 pixels upward** from their current position. - Keep the 300-pixel left offset as previously defined. - Buttons should remain fixed (anchored) to the top of the screen, not relative to the map. - Ensure they remain fully visible and do not overlap with other UI elements. This final adjustment should position the buttons exactly as intended.
User prompt
Hız kontrol düğmesi metnini geçerli konumundan 100 piksel sağ tarafa taşıyın. ve 30 piksel yukarı taşı
User prompt
Hız kontrol düğmelerini geçerli konumlarından 100 piksel sağa taşıyın ve 60 piksel yukarı taşıyın
User prompt
The X1, X2, X3 speed control buttons have been added, but they are not positioned as desired. Please adjust their placement as follows: 1. Anchor Position: - The buttons must be **pinned to the top edge of the screen** (absolute position, not relative to the map). - They should remain visible and fixed regardless of map scrolling. 2. Horizontal Offset: - Move the entire speed control button group **300 pixels to the left** from their current position. 3. Alignment: - The buttons should be arranged horizontally in a row: • [X1] [X2] [X3] - Make sure they do not overlap with other UI elements like gold or health. 4. Final Location Target: - Top of the screen (top-left corner), aligned just beneath or near the HUD bar. - Keep consistent spacing between the buttons. This ensures players can easily see and use speed controls without visual clutter.
User prompt
Please add a speed control system to the game interface with the following details: --- 1. SPEED TOGGLE BUTTONS: - Display 3 speed buttons at the **top-left corner** of the screen: • “X1” → Normal speed (default) • “X2” → 2x speed • “X3” → 3x speed - These should be: • Clearly labeled • Always visible during gameplay • Only one can be active at a time (highlight the selected one) --- 2. FUNCTIONALITY: - When the player clicks “X2” or “X3”: • All game processes (enemy movement, animations, projectiles, tower fire rate, etc.) should run **2x or 3x faster** • But gold rewards, upgrade costs, and other values must remain unchanged - Clicking “X1” resets game speed to normal --- 3. DESIGN: - The buttons should be small but readable, placed in the top-left corner (above or near the HUD) - Optional: Use glow or highlight to indicate which speed is currently active --- This lets players control pacing and prevents boredom during slower waves. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
When you press the mute button in the settings area, the music should also be muted.
User prompt
use the music I added to the music section throughout the game
/**** * 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 with reduced gold reward (35% reduction) var actualCoinValue = Math.floor(self.coinValue * 0.65); var coinText = new Text2('+' + actualCoinValue, { 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 -= gameSpeed; // Apply speed multiplier to timer 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 * gameSpeed; // Apply speed multiplier self.y += dy / distance * self.speed * gameSpeed; // Apply speed multiplier } } }; 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, targetEnemy, damage) { var self = Container.call(this); self.type = type; self.damage = damage; self.targetEnemy = targetEnemy; // Store reference to target enemy 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; self.update = function () { // Check if target enemy still exists if (!self.targetEnemy || !self.targetEnemy.x) { self.hasReachedTarget = true; return; } // Calculate direction to current enemy position (tracking) var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Move toward enemy at projectile speed if (distance > 0) { self.velocityX = dx / distance * self.speed; self.velocityY = dy / distance * self.speed; self.x += self.velocityX * gameSpeed; // Apply speed multiplier self.y += self.velocityY * gameSpeed; // Apply speed multiplier } // Check if projectile has reached the target (smaller threshold for guaranteed hit) if (distance < 15) { 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 / gameSpeed / (1000 / 60); // Apply speed multiplier to attack speed }; 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, 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); // Smart positioning: determine if panel should open upward or downward var panelHeight = 220; // Approximate panel height (8 * 27.5) var panelY = self.y - 100; // Default upward position var shouldOpenDownward = false; // Check if tower is too close to top edge (within 300 pixels) if (self.y < 300) { shouldOpenDownward = true; panelY = self.y + 100; // Position below tower } // Check if tower is too close to bottom edge else if (self.y > 2732 - 300) { // Keep upward positioning for bottom towers panelY = self.y - 100; } // Check if tower is too close to left edge var panelX = self.x; if (self.x < 350) { panelX = self.x + 50; // Shift right if too close to left edge } // Check if tower is too close to right edge else if (self.x > 2048 - 350) { panelX = self.x - 50; // Shift left if too close to right edge } // Panel background - much larger var panelBg = upgradePanel.addChild(LK.getAsset('build_zone', { anchorX: 0.5, anchorY: 0.5, x: panelX, y: panelY, 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 = panelX; upgradeText.y = shouldOpenDownward ? panelY - 20 : panelY - 20; 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 = panelX; costText.y = shouldOpenDownward ? panelY + 20 : panelY + 20; 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 = panelX; maxText.y = shouldOpenDownward ? panelY : panelY; } // Sell button - always visible var sellText = upgradePanel.addChild(new Text2('Sell Tower', { size: 38, fill: 0xFF4444 })); sellText.anchor.set(0.5, 1); sellText.x = panelX; sellText.y = shouldOpenDownward ? panelY + 60 : panelY + 60; var sellValueText = upgradePanel.addChild(new Text2('Refund: ' + sellValue + ' gold', { size: 32, fill: 0xFFD700 })); sellValueText.anchor.set(0.5, 1); sellValueText.x = panelX; sellValueText.y = shouldOpenDownward ? panelY + 95 : panelY + 95; 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 gameSpeed = 1; // Current game speed multiplier (1x, 2x, or 3x) var isPaused = false; // Game pause state var lastGameSpeed = 1; // Store last speed before pausing var pauseButton = null; // Pause button reference var pauseButtonText = null; // Pause button text reference var speedButtons = []; // Array to hold speed button references var speedButtonTexts = []; // Array to hold speed button text references 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 }, { x: 718, y: 1344 }, { x: 818, y: 1344 }, { x: 718, y: 1244 }, { x: 818, y: 1244 }, { x: 718, y: 1444 }, { x: 818, y: 1444 }, // 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 }, // Additional tower placement spots near paths { x: 1230, y: 1344 }, { x: 1330, y: 1344 }, { x: 1230, y: 1244 }, { x: 1330, y: 1244 }, { x: 1230, y: 1444 }, { x: 1330, y: 1444 }]; 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 - war scene with towers shooting arrows at goblins var menuBg = mainMenuContainer.addChild(LK.getAsset('war_scene_background', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 })); // Title var titleText = mainMenuContainer.addChild(new Text2('Goblin Invasion', { 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'; // Play background music when game starts if (!audioMuted) { LK.playMusic('main'); } createSettingsIcon(); createSpeedControls(); // Create speed control buttons createPath(); createTerrain(); createCastle(); createShop(); createWave(); updateShopVisuals(); // Show UI elements with fade-in animation if (castleLivesTextBg) { castleLivesTextBg.visible = true; castleLivesTextBg.alpha = 0; tween(castleLivesTextBg, { alpha: 0.8 }, { duration: 800, easing: tween.easeOut }); } if (castleLivesText) { castleLivesText.visible = true; castleLivesText.alpha = 0; tween(castleLivesText, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); } if (coinsTextBg) { coinsTextBg.visible = true; coinsTextBg.alpha = 0; tween(coinsTextBg, { alpha: 0.8 }, { duration: 800, easing: tween.easeOut }); } if (coinsText) { coinsText.visible = true; coinsText.alpha = 0; tween(coinsText, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); } if (waveText) { waveText.visible = true; waveText.alpha = 0; tween(waveText, { alpha: 1 }, { duration: 800, easing: tween.easeOut }); } } 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 createSpeedControls() { // Clear existing speed buttons for (var i = 0; i < speedButtons.length; i++) { if (speedButtons[i] && speedButtons[i].destroy) { speedButtons[i].destroy(); } } for (var i = 0; i < speedButtonTexts.length; i++) { if (speedButtonTexts[i] && speedButtonTexts[i].destroy) { speedButtonTexts[i].destroy(); } } // Clear pause button if (pauseButton && pauseButton.destroy) { pauseButton.destroy(); } if (pauseButtonText && pauseButtonText.destroy) { pauseButtonText.destroy(); } speedButtons = []; speedButtonTexts = []; // Create pause button pauseButton = LK.gui.topLeft.addChild(LK.getAsset('build_zone', { anchorX: 0, anchorY: 0, x: 220, y: 0, scaleX: 2, scaleY: 1.5 })); pauseButton.tint = isPaused ? 0xFF4444 : 0x666666; pauseButton.alpha = isPaused ? 1.0 : 0.7; pauseButtonText = LK.gui.topLeft.addChild(new Text2('⏸', { size: 40, fill: isPaused ? 0xFF4444 : 0xFFFFFF })); pauseButtonText.anchor.set(0, 0); pauseButtonText.x = 230; pauseButtonText.y = 10; pauseButton.down = function () { togglePause(); }; // Create speed control buttons (X1, X2, X3) var speeds = [1, 2, 3]; var speedLabels = ['X1', 'X2', 'X3']; for (var i = 0; i < speeds.length; i++) { var speedBtn = LK.gui.topLeft.addChild(LK.getAsset('build_zone', { anchorX: 0, anchorY: 0, x: 320 + i * 80, y: 0, scaleX: 2, scaleY: 1.5 })); speedBtn.tint = gameSpeed === speeds[i] && !isPaused ? 0x00FF00 : 0x666666; speedBtn.alpha = gameSpeed === speeds[i] && !isPaused ? 1.0 : 0.7; speedButtons.push(speedBtn); var speedText = LK.gui.topLeft.addChild(new Text2(speedLabels[i], { size: 40, fill: gameSpeed === speeds[i] && !isPaused ? 0x00FF00 : 0xFFFFFF })); speedText.anchor.set(0, 0); speedText.x = 330 + i * 80; speedText.y = 10; speedButtonTexts.push(speedText); // Create closure to capture the speed value (function (speed) { speedBtn.down = function () { setGameSpeed(speed); }; })(speeds[i]); } } function setGameSpeed(newSpeed) { if (isPaused) { // If paused, store the selected speed but don't apply it yet lastGameSpeed = newSpeed; } else { gameSpeed = newSpeed; } // Update button visuals for (var i = 0; i < speedButtons.length; i++) { var isActive = (isPaused ? lastGameSpeed : gameSpeed) === i + 1; speedButtons[i].tint = isActive && !isPaused ? 0x00FF00 : 0x666666; speedButtons[i].alpha = isActive && !isPaused ? 1.0 : 0.7; speedButtonTexts[i].fill = isActive && !isPaused ? 0x00FF00 : 0xFFFFFF; speedButtonTexts[i].tint = isActive && !isPaused ? 0x00FF00 : 0xFFFFFF; } } function togglePause() { isPaused = !isPaused; if (isPaused) { // Store current speed and pause the game lastGameSpeed = gameSpeed; gameSpeed = 0; } else { // Resume with the last selected speed gameSpeed = lastGameSpeed; } // Update pause button visuals if (pauseButton) { pauseButton.tint = isPaused ? 0xFF4444 : 0x666666; pauseButton.alpha = isPaused ? 1.0 : 0.7; } if (pauseButtonText) { pauseButtonText.fill = isPaused ? 0xFF4444 : 0xFFFFFF; pauseButtonText.tint = isPaused ? 0xFF4444 : 0xFFFFFF; } // Update speed button visuals to reflect pause state for (var i = 0; i < speedButtons.length; i++) { var isActive = lastGameSpeed === i + 1; speedButtons[i].tint = isActive && !isPaused ? 0x00FF00 : 0x666666; speedButtons[i].alpha = isActive && !isPaused ? 1.0 : 0.7; speedButtonTexts[i].fill = isActive && !isPaused ? 0x00FF00 : 0xFFFFFF; speedButtonTexts[i].tint = isActive && !isPaused ? 0x00FF00 : 0xFFFFFF; } } 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 () { // Stop background music when returning to main menu LK.stopMusic(); // 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; // Hide UI elements when returning to main menu if (castleLivesTextBg) { castleLivesTextBg.visible = false; } if (castleLivesText) { castleLivesText.visible = false; } if (coinsTextBg) { coinsTextBg.visible = false; } if (coinsText) { coinsText.visible = false; } if (waveText) { waveText.visible = false; } // Clear UI elements if (settingsIcon) { settingsIcon.destroy(); settingsIcon = null; } if (muteIndicator) { muteIndicator.destroy(); muteIndicator = null; } // Clean up speed controls for (var i = 0; i < speedButtons.length; i++) { if (speedButtons[i] && speedButtons[i].destroy) { speedButtons[i].destroy(); } } for (var i = 0; i < speedButtonTexts.length; i++) { if (speedButtonTexts[i] && speedButtonTexts[i].destroy) { speedButtonTexts[i].destroy(); } } // Clean up pause controls if (pauseButton && pauseButton.destroy) { pauseButton.destroy(); pauseButton = null; } if (pauseButtonText && pauseButtonText.destroy) { pauseButtonText.destroy(); pauseButtonText = null; } speedButtons = []; speedButtonTexts = []; gameSpeed = 1; // Reset game speed isPaused = false; // Reset pause state lastGameSpeed = 1; // Reset last speed // 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; // Stop music when muting LK.stopMusic(); // 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; // Resume music when unmuting (only if in game state) if (gameState === 'playing') { LK.playMusic('main'); } // 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 + Math.floor(i * 90 / gameSpeed) // Apply speed multiplier to spawn timing }); } 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; castleLivesTextBg.visible = false; // Hidden on main menu 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; castleLivesText.visible = false; // Hidden on main menu 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; coinsTextBg.visible = false; // Hidden on main menu 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; coinsText.visible = false; // Hidden on main menu isoContainer.addChild(coinsText); var waveText = new Text2('Wave: 0/100', { size: 40, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); waveText.visible = false; // Hidden on main menu 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 + Math.floor(180 / gameSpeed); // Apply speed multiplier to wave delay } } 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) { // Target tracking ensures 100% hit rate - damage the intended target if (projectile.targetEnemy && projectile.targetEnemy.x) { var targetEnemy = projectile.targetEnemy; // Apply tower-specific effects 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 + 100) { // Some tolerance for projectile travel frostTower = towers[k]; break; } } } if (frostTower) { targetEnemy.applyFrostSlow(frostTower.slowPercentage); } } // Handle area damage for cannonball projectiles if (projectile.type === 'cannonball') { // Deal damage to target and nearby enemies 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.65 * 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 { // Single target damage - guaranteed hit on intended target if (targetEnemy.takeDamage(projectile.damage)) { coins += Math.floor(targetEnemy.coinValue * 0.65 * 0.7); coinsText.setText('Gold: ' + coins); // Create death effect at enemy position tween(targetEnemy, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, easing: tween.easeOut }); // Remove enemy from array for (var j = 0; j < enemies.length; j++) { if (enemies[j] === targetEnemy) { enemies.splice(j, 1); break; } } targetEnemy.destroy(); } } } 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
@@ -2630,9 +2630,9 @@
x: 1024,
y: 1366
}));
// Title
- var titleText = mainMenuContainer.addChild(new Text2('Tower Defense', {
+ var titleText = mainMenuContainer.addChild(new Text2('Goblin Invasion', {
size: 120,
fill: 0xFFD700
}));
titleText.anchor.set(0.5, 0.5);
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