User prompt
в шопе под номером 1 будет восстановление зоровья города. Здоровье будет восстанавливаться постепенно в течении 12 секунд по 70. Установи цену на верху ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
сделай хп бар толще в 2 раза
User prompt
хп бар должен быть поверх всего его ничего не должно перекрывать
User prompt
опусти в самый низ ХП бар И растяни его от края до края слева на право
User prompt
убери текст здоровье города и числа ХП
User prompt
желтая не в ту сторону отъехала ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
нужно добавить хп бару красивую анимацию. при получении урона желтеет тот кусок который пропадет а потом плавно движется влево ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
добавь хп бар городу
User prompt
Please fix the bug: 'Crosshair is not defined' in or related to this line: 'crosshairInstance = new Crosshair();' Line Number: 1591
User prompt
Please fix the bug: 'CityHPBar is not defined' in or related to this line: 'var cityHPBar = new CityHPBar();' Line Number: 456 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'City is not defined' in or related to this line: 'cityInstance = new City();' Line Number: 404
User prompt
давай сделаем хп бар с анимациями и изменением цвет со всем чем можно ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
нужно уменьшить еще разжатие чуть. замедлить скорость разжатия и частоту слишком часто ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
звезды слишком сильно разжимаются нужно уменьшить в 3 раза разжатие ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
возможно для звезд стоит использовать метот как с молнией. чтобы они как бы светились сжимались и разжимались ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
для звезд используй star размещай случайно и с разными размерами
User prompt
сделай ее прозрачно 80%
User prompt
вместо кружка и I используй iconI для кнопки подсказки
User prompt
на сколько я знаю при каждом апгрейде у молнии растет урон а не только шанс срабатывания. Укажи это в подсказках
User prompt
пусть music1: 3:00
User prompt
переправь время: 1 - 3 минуты
User prompt
все равно вырастает. Вот моя идея оставь размер как он есть. а я сам подгоню
User prompt
обе картинки 100 на 100. я не знаю какие они становятся при старте но после того как их нажмешь они вырастают
User prompt
иконка изначально маленькая увеличь в два раза
User prompt
лпять как только я нажимаю она становится большой иа и та и такими остаются
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // --- City Class --- // Represents the main city in the center of the screen var City = Container.expand(function () { var self = Container.call(this); // Attach city image asset self.graphic = self.attachAsset('city', { anchorX: 0.5, anchorY: 1 }); // Set initial health self.maxHealth = CITY_MAX_HEALTH; self.health = CITY_MAX_HEALTH; // Reference to HP bar (set externally) self.hpBar = null; // Take damage and update HP bar self.takeDamage = function (amount) { if (self.health <= 0) return; self.health -= amount; if (self.health < 0) self.health = 0; if (self.hpBar) self.hpBar.setHealth(self.health, self.maxHealth); // Optionally, flash city on damage LK.effects.flashObject(self.graphic, 0xff0000, 200); // Game over if city destroyed if (self.health <= 0) { LK.showGameOver(); } }; // For compatibility with destroySelf in cleanup self.isDestroyed = false; self.destroySelf = function () { self.isDestroyed = true; if (self.parent) self.parent.removeChild(self); }; return self; }); // --- CityHPBar Class --- // Animated HP bar for the city with color-changing effects var CityHPBar = Container.expand(function () { var self = Container.call(this); // HP bar background (gray) self.bgBar = self.attachAsset('Puha2', { anchorX: 0.5, anchorY: 0.5, scaleX: 4.0, scaleY: 0.3 }); self.bgBar.tint = 0x444444; // Gray background // HP bar foreground (health indicator) self.healthBar = self.attachAsset('Puha2', { anchorX: 0.5, anchorY: 0.5, scaleX: 4.0, scaleY: 0.3 }); self.healthBar.tint = 0x00FF00; // Start green // Health percentage tracking self.currentHealthPercent = 1.0; self.lastHealthPercent = 1.0; // Set health and update bar self.setHealth = function (currentHealth, maxHealth) { var healthPercent = Math.max(0, currentHealth / maxHealth); // Check if health decreased (damage taken) if (healthPercent < self.currentHealthPercent) { // Flash effect on damage LK.effects.flashObject(self.healthBar, 0xFF0000, 300); } self.currentHealthPercent = healthPercent; // Animate health bar width change tween(self.healthBar, { scaleX: 4.0 * healthPercent }, { duration: 200, easing: tween.easeOut }); // Update color based on health percentage var targetColor; if (healthPercent > 0.6) { targetColor = 0x00FF00; // Green (healthy) } else if (healthPercent > 0.3) { targetColor = 0xFFFF00; // Yellow (warning) } else { targetColor = 0xFF0000; // Red (critical) } // Animate color change tween(self.healthBar, { tint: targetColor }, { duration: 300, easing: tween.easeInOut }); }; return self; }); // --- Crosshair Class --- // Represents the targeting crosshair controlled by player input var Crosshair = Container.expand(function () { var self = Container.call(this); // Crosshair graphic will be attached externally self.graphic = null; // Movement speed self.speed = CROSSHAIR_SPEED; // Check if crosshair is over a target self.isOver = function (target) { if (!target || target.isDestroyed || !target.graphic) return false; // Simple bounds check using crosshair center point var crosshairRadius = 25; // Half of crosshair size for hit detection var targetHalfWidth = target.graphic.width * 0.5 * (target.graphic.scaleX || 1); var targetHalfHeight = target.graphic.height * 0.5 * (target.graphic.scaleY || 1); return self.x >= target.x - targetHalfWidth - crosshairRadius && self.x <= target.x + targetHalfWidth + crosshairRadius && self.y >= target.y - targetHalfHeight - crosshairRadius && self.y <= target.y + targetHalfHeight + crosshairRadius; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0a0a18 // Even darker blue for space }); /**** * Game Code ****/ // A simple red circle for the crosshair // Grey city base // Lime green alien (can be simple box for now) // Brownish meteor // --- Game Constants --- var WORLD_WIDTH = 2048; var WORLD_HEIGHT = 2732; var METEOR_HEALTH = 2000; var METEOR_SPEED = 3; // Pixels per frame var METEOR_DAMAGE_TO_CITY = 75; var ALIEN_HEALTH = 3000; var ALIEN_SPEED = 2.5; var ALIEN_DAMAGE_TO_CITY = 100; var CITY_MAX_HEALTH = 1000; // var CLICK_DAMAGE = 35; // Replaced by FIRE_DAMAGE // var HOLD_DAMAGE_PER_INTERVAL = 20; // Replaced by FIRE_DAMAGE // var HOLD_DAMAGE_INTERVAL_MS = 150; // Replaced by FIRE_RATE_MS var CROSSHAIR_SPEED = 10; var JOYSTICK_VISUAL_RADIUS = 180; // Increased max knob center travel distance for larger joystick var JOYSTICK_INTERACTION_RADIUS = 260; // Increased touchable area radius for larger joystick var FIRE_RATE_MS = 1000; // How often the player can fire (1 shot per second) var FIRE_DAMAGE = 35; // Damage per shot from crosshair var SPAWN_INTERVAL_MS_MIN = 1200; // Minimum time between spawns (halved) var SPAWN_INTERVAL_MS_MAX = 2500; // Maximum time between spawns (halved) // --- Global Game Variables --- var cityInstance; var fallingObjects = []; var cityHealthText; // For GUI var scoreText; // For player score // var playerHoldingTarget = null; // Removed // var isPlayerHolding = false; // Replaced by isFiring // var holdDamageInterval = null; // Removed var crosshairInstance; var joystickInstance; var isFiring = false; var lastFireTime = 0; var joystickTouchId = null; // To track the touch interacting with the joystick var nextSpawnTime = 0; var spawnPausedTime = null; // Track when spawn timer was paused var stars = []; var STAR_COUNT = 50; // Device detection variables var isMobileDevice = false; var isMouseControlling = false; var mouseX = WORLD_WIDTH / 2; var mouseY = WORLD_HEIGHT / 2; var mouseCoordinatesText = null; // Text display for crosshair speed var crosshairLastX = WORLD_WIDTH / 2; var crosshairLastY = WORLD_HEIGHT / 2; var crosshairSpeed = 0; // Music system variables var currentMusicIndex = 0; var musicTracks = ['music1', 'music2', 'music3']; var trackDurations = [180000, 117000, 93000]; // music1: 3:00, music2: 1:57, music3: 1:33 (in milliseconds) var musicPlaying = false; var musicStartTime = 0; // --- Helper Functions --- function detectDevice() { // Check screen size - mobile devices typically have smaller screens var screenWidth = 2048; // LK engine resolution var screenHeight = 2732; // Check for touch capability var isTouchDevice = 'ontouchstart' in window || navigator && navigator.maxTouchPoints > 0; // Simple heuristic: if touch is available and we're in a typical mobile viewport, assume mobile // In LK engine, we can use the fact that mobile games are the primary target isMobileDevice = isTouchDevice; console.log("Device detected as:", isMobileDevice ? "Mobile" : "Desktop"); } function spawnFallingObject() { var randomValue = Math.random(); var newObject; if (randomValue < 0.4) { // 40% regular meteors newObject = new Meteor(); } else if (randomValue < 0.65) { // 25% fast meteors newObject = new MeteorFast(); } else { newObject = new Alien(); // 35% aliens } // Position just above the screen, at a random horizontal position newObject.x = Math.random() * (WORLD_WIDTH - newObject.graphic.width) + newObject.graphic.width / 2; newObject.y = -newObject.graphic.height / 2; // Start just off-screen top fallingObjects.push(newObject); game.addChild(newObject); } function updateScoreDisplay() { if (scoreText) { scoreText.setText('$' + LK.getScore()); } } function createStars() { for (var i = 0; i < STAR_COUNT; i++) { // Use star asset with random scale for different sizes var randomScale = 0.5 + Math.random() * 1.5; // Scale between 0.5x and 2.0x var star = LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, scaleX: randomScale, scaleY: randomScale }); // Generate star position with exclusion zones to prevent overlap with UI elements var starX, starY; var validPosition = false; var attempts = 0; var maxAttempts = 50; while (!validPosition && attempts < maxAttempts) { starX = Math.random() * WORLD_WIDTH; starY = Math.random() * WORLD_HEIGHT; attempts++; // Check if star is not in cannon area (bottom left) var cannonAreaLeft = 0; var cannonAreaRight = 800; var cannonAreaTop = WORLD_HEIGHT - 600; var cannonAreaBottom = WORLD_HEIGHT; var inCannonArea = starX >= cannonAreaLeft && starX <= cannonAreaRight && starY >= cannonAreaTop && starY <= cannonAreaBottom; // Check if star is not in joystick area (will be positioned dynamically, so reserve bottom area) var joystickAreaLeft = 0; var joystickAreaRight = 600; var joystickAreaTop = WORLD_HEIGHT - 800; var joystickAreaBottom = WORLD_HEIGHT; var inJoystickArea = starX >= joystickAreaLeft && starX <= joystickAreaRight && starY >= joystickAreaTop && starY <= joystickAreaBottom; // Check if star is not in city area (bottom center and sides) var cityAreaLeft = 0; var cityAreaRight = WORLD_WIDTH; var cityAreaTop = WORLD_HEIGHT - 448; // City image height var cityAreaBottom = WORLD_HEIGHT; var inCityArea = starX >= cityAreaLeft && starX <= cityAreaRight && starY >= cityAreaTop && starY <= cityAreaBottom; if (!inCannonArea && !inJoystickArea && !inCityArea) { validPosition = true; } } // If no valid position found after max attempts, place randomly (fallback) if (!validPosition) { starX = Math.random() * WORLD_WIDTH; starY = Math.random() * (WORLD_HEIGHT - 600); // At least avoid bottom area } star.x = starX; star.y = starY; star.alpha = 0.3 + Math.random() * 0.7; // Random initial brightness star.twinkleDelay = Math.random() * 3000; // Random delay before first twinkle star.nextTwinkleTime = LK.ticks * (1000 / 60) + star.twinkleDelay; star.isTwinkling = false; // Track if star is currently twinkling star.lastColorChangeTime = LK.ticks * (1000 / 60); // Initialize last color change time stars.push(star); // Add star right after background to ensure it stays behind all other elements game.addChildAt(star, 1); // Position 1 is right after the background (position 0) } } function updateStars() { var currentTime = LK.ticks * (1000 / 60); var colors = [0x4169E1, 0x00FFFF, 0xFFFFFF, 0xFFFF00]; // Blue, Cyan, White, Yellow for (var i = 0; i < stars.length; i++) { var star = stars[i]; // Check if star should start glowing (either due to regular twinkle time or 30-second limit) var shouldGlow = currentTime >= star.nextTwinkleTime; var timeSinceLastChange = currentTime - (star.lastColorChangeTime || 0); var forcedChange = timeSinceLastChange >= 30000; // 30 seconds (increased from 15) if ((shouldGlow || forcedChange) && !star.isTwinkling) { // Start lightning-like glow and pulsing animation star.isTwinkling = true; // Stop any existing tween on this star first tween.stop(star); // Pick a new color different from the current one var currentColor = star.tint; var availableColors = []; for (var c = 0; c < colors.length; c++) { if (colors[c] !== currentColor) { availableColors.push(colors[c]); } } // Fallback in case all colors are the same (shouldn't happen) if (availableColors.length === 0) { availableColors = colors.slice(); } var targetColor = availableColors[Math.floor(Math.random() * availableColors.length)]; var targetAlpha = 0.8 + Math.random() * 0.2; // Brighter for glow effect // Store original scale if not set if (!star.originalScale) { star.originalScale = star.scaleX; } // Lightning-like pulsing: subtle expansion and contraction tween(star, { scaleX: star.originalScale * (1.05 + Math.random() * 0.1), // Expand 1.05-1.15x (much smaller expansion) scaleY: star.originalScale * (1.05 + Math.random() * 0.1), alpha: targetAlpha, tint: targetColor }, { duration: 400 + Math.random() * 200, // Slower expansion (400-600ms) easing: tween.easeOut, onFinish: function (starRef, colorRef) { return function onFinish() { // Contract back with pulsing effect tween(starRef, { scaleX: starRef.originalScale * (0.95 + Math.random() * 0.05), // Contract to 0.95-1.0x (very subtle) scaleY: starRef.originalScale * (0.95 + Math.random() * 0.05), alpha: 0.3 + Math.random() * 0.5 }, { duration: 500 + Math.random() * 300, // Slower contraction (500-800ms) easing: tween.easeInOut, onFinish: function onFinish() { // Final pulse back to normal with minimal variation tween(starRef, { scaleX: starRef.originalScale * (0.98 + Math.random() * 0.04), // Near original size (0.98-1.02x) scaleY: starRef.originalScale * (0.98 + Math.random() * 0.04), alpha: 0.3 + Math.random() * 0.7 }, { duration: 600 + Math.random() * 400, // Even slower final pulse (600-1000ms) easing: tween.easeOut, onFinish: function onFinish() { // Set next glow time (8-20 seconds from now, but no more than 30 seconds total) var now = LK.ticks * (1000 / 60); starRef.lastColorChangeTime = now; var nextInterval = Math.min(8000 + Math.random() * 12000, 30000); // 8-20 seconds starRef.nextTwinkleTime = now + Math.max(nextInterval, 2000); // Ensure at least 2 seconds starRef.isTwinkling = false; // Set the color to the final value to avoid rounding issues starRef.tint = colorRef; } }); } }); }; }(star, targetColor) }); } } } // Music management functions function startMusicRotation() { if (musicTracks.length === 0) return; currentMusicIndex = 0; musicPlaying = true; musicStartTime = LK.ticks * (1000 / 60); LK.playMusic(musicTracks[currentMusicIndex]); } function updateMusicRotation() { if (!musicPlaying || !musicEnabled || musicTracks.length === 0) return; var currentTime = LK.ticks * (1000 / 60); // Check if enough time has passed to switch tracks using custom durations var trackDuration = trackDurations[currentMusicIndex] || 180000; // Use current track's duration or fallback to 3 minutes if (currentTime - musicStartTime >= trackDuration) { switchToNextTrack(); } } function switchToNextTrack() { if (musicTracks.length === 0) return; currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length; musicStartTime = LK.ticks * (1000 / 60); LK.playMusic(musicTracks[currentMusicIndex]); } // --- Game Initialization --- // Initialize tracking arrays for cleanup game.damageNumbers = []; game.particles = []; game.muzzleParticles = []; game.snowflakeParticles = []; // Reset upgrades for (var k in upgrades) upgrades[k] = 0; // Add background var background = LK.getAsset('fon', { anchorX: 0, anchorY: 0 }); background.x = 0; background.y = 0; game.addChild(background); // Initialize Puha character in bottom left corner - split into base and barrel var puhaBaseInstance = LK.getAsset('Puha2', { anchorX: 0.0, anchorY: 0.5, scaleX: 0.15, scaleY: 0.15 }); puhaBaseInstance.x = 450; // Left edge positioned correctly from left edge, moved right by 400 puhaBaseInstance.y = WORLD_HEIGHT - 50 - 2000 * 0.15 / 2 + 200; // Center positioned correctly from bottom edge, moved down by 200 // Rotating barrel part var puhaBarrelInstance = LK.getAsset('Puha', { anchorX: 0.0, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); puhaBarrelInstance.x = 450; // Same position as base puhaBarrelInstance.y = WORLD_HEIGHT - 50 - 2056 * 0.3 / 2 + 200; // Same position as base // Add puha parts before city images to hide them behind cities game.addChild(puhaBaseInstance); game.addChild(puhaBarrelInstance); // Add left city image at bottom left var leftCityImage = LK.getAsset('city', { anchorX: 0, anchorY: 1 }); leftCityImage.x = 0; leftCityImage.y = WORLD_HEIGHT; game.addChild(leftCityImage); // Add right city image at bottom right var rightCityImage = LK.getAsset('city', { anchorX: 1, anchorY: 1 }); rightCityImage.x = WORLD_WIDTH; rightCityImage.y = WORLD_HEIGHT; game.addChild(rightCityImage); // Create and position the city cityInstance = new City(); cityInstance.x = WORLD_WIDTH / 2; cityInstance.y = WORLD_HEIGHT; // Bottom edge of city at bottom of screen game.addChild(cityInstance); // Setup City HP Bar (animated, color-changing) var cityHPBar = new CityHPBar(); cityHPBar.x = WORLD_WIDTH / 2; cityHPBar.y = WORLD_HEIGHT - cityInstance.graphic.height - 60; // 60px above city game.addChild(cityHPBar); cityInstance.hpBar = cityHPBar; // Setup Score Display LK.setScore(10000); // Initialize score scoreText = new Text2('$0', { size: 90, fill: 0x00FF00, align: 'center', font: 'Impact' }); scoreText.anchor.set(0.5, 0); // Anchor top-center // Position score text slightly below the top edge to avoid any engine icons, if necessary // LK.gui.top has y=0. We'll add a small offset. scoreText.y = 60; // Moved higher by 20 pixels (was 80, now 60). LK.gui.top.addChild(scoreText); // Crosshair speed display removed as requested // --- Event Handlers --- // --- Upgrades System --- // Upgrade state variables var upgrades = { fireDamage: 0, // Level fireRate: 0, // Level freeze: 0, // Level lightningChance: 0, // Level fire: 0, // Level critChance: 0 // Level }; var upgradesPopup = null; var upgradesButton = null; var upgradesButtonActive = false; var upgradesButtonWidth = 180; var upgradesButtonHeight = 90; var upgradesButtonPadding = 30; var upgradesButtonY = 20; // Offset from top var upgradesButtonX = WORLD_WIDTH - upgradesButtonWidth - upgradesButtonPadding; // Shop system variables var shopPopup = null; var shopButton = null; var shopButtonActive = false; var shopButtonWidth = 180; var shopButtonHeight = 90; var shopButtonPadding = 30; var shopButtonY = 220; // Position below upgrades button var shopButtonX = WORLD_WIDTH - shopButtonWidth - shopButtonPadding; // Upgrade definitions var UPGRADE_DEFS = [{ key: 'fireDamage', name: 'Урон +', desc: 'Увеличить урон выстрела', tooltip: 'Увеличивает урон от каждого выстрела. Каждый уровень добавляет +20 урона к вашим атакам.', cost: function cost(level) { return 10 + level * 10; }, max: 10 }, { key: 'fireRate', name: 'Скорострельность +', desc: 'Уменьшить задержку между выстрелами', tooltip: 'Уменьшает время между выстрелами, позволяя стрелять чаще.', cost: function cost(level) { return 10 + level * 10; }, max: 10 }, { key: 'critChance', name: 'Критический урон +', desc: 'Увеличить шанс двойного урона', tooltip: 'Увеличивает шанс нанести двойной урон выстрелом. Каждый уровень добавляет +5% шанса критического удара.', cost: function cost(level) { return 10 + level * 10; }, max: 10 }, { key: 'lightningChance', name: 'Шанс молнии +', desc: 'Увеличить шанс удара молнии', tooltip: 'Добавляет шанс поражения целей молнией, наносящей урон и перескакивающей на ближайших врагов. Каждый уровень добавляет +5% шанса.', cost: function cost(level) { return 10 + level * 10; }, max: 10 }, { key: 'fire', name: 'Шанс поджога +', desc: 'Урон огнем со временем', tooltip: 'Добавляет шанс поджечь врагов, наносящий урон в течение 10 секунд. Огонь накладывается до 10 раз, увеличивая урон. Каждый уровень добавляет +5% шанса.', cost: function cost(level) { return 10 + level * 10; }, max: 10 }, { key: 'freeze', name: 'Шанс заморозки +', desc: 'Замедление и заморозка врагов', tooltip: 'Добавляет шанс замедлить врагов на 50%. При 5 стаках враги полностью замерзают на 4 секунды. Каждый уровень добавляет +5% шанса.', cost: function cost(level) { return 10 + level * 10; }, max: 10 }]; // Global variable for upgrade icon size var iconSize = 360; // Tooltip system variables var currentTooltip = null; // Language system variables var currentLanguage = 'ru'; // Default to Russian var languageFlags = []; var languages = ['en', 'ru', 'de', 'fr', 'es']; var flagAssets = ['Eng', 'Rus', 'Germ', 'Fran', 'Esp']; // English, Russian, German, French, Spanish // Language data for tooltips var languageData = { en: { fireDamage: 'Increases damage from each shot. Each level adds +20 damage to your attacks.', fireRate: 'Reduces time between shots, allowing you to shoot more frequently.', critChance: 'Increases chance to deal double damage with shots. Each level adds +5% critical hit chance.', lightningChance: 'Adds chance to strike targets with lightning that damages and jumps to nearby enemies. Each level adds +5% chance and +5% damage.', fire: 'Adds chance to ignite enemies, dealing damage over 10 seconds. Fire stacks up to 10 times, increasing damage. Each level adds +5% chance.', freeze: 'Adds chance to slow enemies by 50%. At 5 stacks enemies freeze completely for 4 seconds. Each level adds +5% chance.' }, ru: { fireDamage: 'Увеличивает урон от каждого выстрела. Каждый уровень добавляет +20 урона к вашим атакам.', fireRate: 'Уменьшает время между выстрелами, позволяя стрелять чаще.', critChance: 'Увеличивает шанс нанести двойной урон выстрелом. Каждый уровень добавляет +5% шанса критического удара.', lightningChance: 'Добавляет шанс поражения целей молнией, наносящей урон и перескакивающей на ближайших врагов. Каждый уровень добавляет +5% шанса и +5% урона.', fire: 'Добавляет шанс поджечь врагов, наносящий урон в течение 10 секунд. Огонь накладывается до 10 раз, увеличивая урон. Каждый уровень добавляет +5% шанса.', freeze: 'Добавляет шанс замедлить врагов на 50%. При 5 стаках враги полностью замерзают на 4 секунды. Каждый уровень добавляет +5% шанса.' }, de: { fireDamage: 'Erhöht den Schaden jedes Schusses. Jede Stufe fügt +20 Schaden zu Ihren Angriffen hinzu.', fireRate: 'Reduziert die Zeit zwischen Schüssen und ermöglicht häufigeres Schießen.', critChance: 'Erhöht die Chance, doppelten Schaden mit Schüssen zu verursachen. Jede Stufe fügt +5% kritische Trefferchance hinzu.', lightningChance: 'Fügt die Chance hinzu, Ziele mit Blitzen zu treffen, die Schaden verursachen und zu nahegelegenen Feinden springen. Jede Stufe fügt +5% Chance und +5% Schaden hinzu.', fire: 'Fügt die Chance hinzu, Feinde zu entzünden und 10 Sekunden lang Schaden zu verursachen. Feuer stapelt sich bis zu 10 Mal und erhöht den Schaden. Jede Stufe fügt +5% Chance hinzu.', freeze: 'Fügt die Chance hinzu, Feinde um 50% zu verlangsamen. Bei 5 Stapeln frieren Feinde 4 Sekunden lang komplett ein. Jede Stufe fügt +5% Chance hinzu.' }, fr: { fireDamage: 'Augmente les dégâts de chaque tir. Chaque niveau ajoute +20 dégâts à vos attaques.', fireRate: 'Réduit le temps entre les tirs, permettant de tirer plus fréquemment.', critChance: 'Augmente les chances d\'infliger des dégâts doubles avec les tirs. Chaque niveau ajoute +5% de chance de coup critique.', lightningChance: 'Ajoute une chance de frapper les cibles avec la foudre qui inflige des dégâts et saute vers les ennemis proches. Chaque niveau ajoute +5% de chance et +5% de dégâts.', fire: 'Ajoute une chance d\'enflammer les ennemis, infligeant des dégâts pendant 10 secondes. Le feu se cumule jusqu\'à 10 fois, augmentant les dégâts. Chaque niveau ajoute +5% de chance.', freeze: 'Ajoute une chance de ralentir les ennemis de 50%. À 5 cumuls, les ennemis gèlent complètement pendant 4 secondes. Chaque niveau ajoute +5% de chance.' }, es: { fireDamage: 'Aumenta el daño de cada disparo. Cada nivel añade +20 daño a tus ataques.', fireRate: 'Reduce el tiempo entre disparos, permitiendo disparar más frecuentemente.', critChance: 'Aumenta la posibilidad de infligir daño doble con los disparos. Cada nivel añade +5% de posibilidad de golpe crítico.', lightningChance: 'Añade posibilidad de golpear objetivos con rayos que infligen daño y saltan a enemigos cercanos. Cada nivel añade +5% de posibilidad y +5% de daño.', fire: 'Añade posibilidad de incendiar enemigos, infligiendo daño durante 10 segundos. El fuego se acumula hasta 10 veces, aumentando el daño. Cada nivel añade +5% de posibilidad.', freeze: 'Añade posibilidad de ralentizar enemigos en 50%. Con 5 acumulaciones los enemigos se congelan completamente durante 4 segundos. Cada nivel añade +5% de posibilidad.' } }; // Tooltip functions function showUpgradeTooltip(upgradeKey, iconX, iconY) { if (currentTooltip) hideUpgradeTooltip(); var upgradeDef = null; for (var i = 0; i < UPGRADE_DEFS.length; i++) { if (UPGRADE_DEFS[i].key === upgradeKey) { upgradeDef = UPGRADE_DEFS[i]; break; } } if (!upgradeDef) return; currentTooltip = new Container(); // Large tooltip background positioned in center of screen var tooltipWidth = 1000; var tooltipHeight = 600; var tooltipBg = LK.getAsset('Fon2', { anchorX: 0.5, anchorY: 0.5, scaleX: tooltipWidth / 1000, scaleY: tooltipHeight / 1000 }); tooltipBg.alpha = 1.0; tooltipBg.x = WORLD_WIDTH / 2; tooltipBg.y = WORLD_HEIGHT / 2; tooltipBg.interactive = false; tooltipBg.buttonMode = false; currentTooltip.addChild(tooltipBg); // Language flags above tooltip var flagSize = 120; var flagSpacing = 140; var totalFlagsWidth = languages.length * flagSpacing - (flagSpacing - flagSize); var flagStartX = WORLD_WIDTH / 2 - totalFlagsWidth / 2; var flagY = WORLD_HEIGHT / 2 - tooltipHeight / 2 - flagSize - 120; for (var f = 0; f < languages.length; f++) { (function (langIndex) { var flag = LK.getAsset(flagAssets[langIndex], { anchorX: 0.5, anchorY: 0.5, scaleX: flagSize / 100, scaleY: flagSize / 100 }); flag.x = flagStartX + langIndex * flagSpacing; flag.y = flagY; flag.alpha = currentLanguage === languages[langIndex] ? 1.0 : 1.0; flag.interactive = true; flag.buttonMode = true; flag.down = function (x, y, obj) { // Switch language currentLanguage = languages[langIndex]; // Update all flag alphas for (var ff = 0; ff < languageFlags.length; ff++) { languageFlags[ff].alpha = currentLanguage === languages[ff] ? 1.0 : 1.0; } // Update tooltip text var newTooltipText = languageData[currentLanguage][upgradeKey] || upgradeDef.tooltip; // Update title text with new language var newTitleText = titleData[currentLanguage][upgradeKey] || upgradeDef.name; titleText.setText(newTitleText); // Update tooltip text with new language var maxWidth = tooltipWidth - 160; var words = newTooltipText.split(' '); var lines = []; var currentLine = ''; var charactersPerLine = Math.floor(maxWidth / 32); for (var w = 0; w < words.length; w++) { var testLine = currentLine + (currentLine ? ' ' : '') + words[w]; if (testLine.length > charactersPerLine && currentLine) { lines.push(currentLine); currentLine = words[w]; } else { currentLine = testLine; } } if (currentLine) lines.push(currentLine); var finalLines = []; for (var l = 0; l < lines.length; l++) { if (lines[l].length > charactersPerLine) { var longLine = lines[l]; while (longLine.length > charactersPerLine) { var breakPoint = charactersPerLine; for (var bp = charactersPerLine - 1; bp > charactersPerLine * 0.7; bp--) { if (longLine[bp] === ' ') { breakPoint = bp; break; } } finalLines.push(longLine.substring(0, breakPoint)); longLine = longLine.substring(breakPoint + (longLine[breakPoint] === ' ' ? 1 : 0)); } if (longLine.length > 0) { finalLines.push(longLine); } } else { finalLines.push(lines[l]); } } tooltipText.setText(finalLines.join('\n')); }; currentTooltip.addChild(flag); languageFlags.push(flag); })(f); } // Close button (X) in bottom center of tooltip using krest image var closeButton = LK.getAsset('krest', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); closeButton.x = WORLD_WIDTH / 2; closeButton.y = WORLD_HEIGHT / 2 + tooltipHeight / 2 - 80 + crosshairInstance.graphic.height + crosshairInstance.graphic.height + crosshairInstance.graphic.height - 8; closeButton.interactive = true; closeButton.buttonMode = true; closeButton.down = function (x, y, obj) { hideUpgradeTooltip(); }; currentTooltip.addChild(closeButton); // Language data for upgrade titles var titleData = { en: { fireDamage: 'Damage +', fireRate: 'Fire Rate +', critChance: 'Critical Damage +', lightningChance: 'Lightning Chance +', fire: 'Fire Chance +', freeze: 'Freeze Chance +' }, ru: { fireDamage: 'Урон +', fireRate: 'Скорострельность +', critChance: 'Критический урон +', lightningChance: 'Шанс молнии +', fire: 'Шанс поджога +', freeze: 'Шанс заморозки +' }, de: { fireDamage: 'Schaden +', fireRate: 'Feuerrate +', critChance: 'Kritischer Schaden +', lightningChance: 'Blitz-Chance +', fire: 'Feuer-Chance +', freeze: 'Frost-Chance +' }, fr: { fireDamage: 'Dégâts +', fireRate: 'Cadence de tir +', critChance: 'Dégâts critiques +', lightningChance: 'Chance de foudre +', fire: 'Chance de feu +', freeze: 'Chance de gel +' }, es: { fireDamage: 'Daño +', fireRate: 'Cadencia +', critChance: 'Daño crítico +', lightningChance: 'Oportunidad de rayo +', fire: 'Oportunidad de fuego +', freeze: 'Oportunidad de congelación +' } }; // Upgrade title var upgradeTitleText = titleData[currentLanguage][upgradeKey] || upgradeDef.name; var titleText = new Text2(upgradeTitleText, { size: 80, fill: 0xFFFF00, align: 'center', font: 'Impact' }); titleText.anchor.set(0.5, 0); titleText.x = WORLD_WIDTH / 2; titleText.y = WORLD_HEIGHT / 2 - tooltipHeight / 2 + 20 - 50; currentTooltip.addChild(titleText); // Tooltip text with better word wrapping var tooltipText = new Text2('', { size: 60, fill: 0xFFFFFF, align: 'left', font: 'Impact' }); tooltipText.anchor.set(0, 0); tooltipText.x = WORLD_WIDTH / 2 - tooltipWidth / 2 + 80; tooltipText.y = WORLD_HEIGHT / 2 - tooltipHeight / 2 + 90; // Get tooltip text in current language var tooltipTextContent = languageData[currentLanguage][upgradeKey] || upgradeDef.tooltip; // Improved word wrap for long text var maxWidth = tooltipWidth - 160; var words = tooltipTextContent.split(' '); var lines = []; var currentLine = ''; var charactersPerLine = Math.floor(maxWidth / 32); // Approximate characters per line based on font size for (var w = 0; w < words.length; w++) { var testLine = currentLine + (currentLine ? ' ' : '') + words[w]; if (testLine.length > charactersPerLine && currentLine) { lines.push(currentLine); currentLine = words[w]; } else { currentLine = testLine; } } if (currentLine) lines.push(currentLine); // Add line breaks if text is still too long var finalLines = []; for (var l = 0; l < lines.length; l++) { if (lines[l].length > charactersPerLine) { // Force break long lines var longLine = lines[l]; while (longLine.length > charactersPerLine) { var breakPoint = charactersPerLine; // Try to break at a space for (var bp = charactersPerLine - 1; bp > charactersPerLine * 0.7; bp--) { if (longLine[bp] === ' ') { breakPoint = bp; break; } } finalLines.push(longLine.substring(0, breakPoint)); longLine = longLine.substring(breakPoint + (longLine[breakPoint] === ' ' ? 1 : 0)); } if (longLine.length > 0) { finalLines.push(longLine); } } else { finalLines.push(lines[l]); } } tooltipText.setText(finalLines.join('\n')); currentTooltip.addChild(tooltipText); // Position tooltip in center currentTooltip.x = 0; currentTooltip.y = 0; upgradesPopup.addChild(currentTooltip); } function hideUpgradeTooltip() { if (currentTooltip && currentTooltip.parent) { currentTooltip.parent.removeChild(currentTooltip); } currentTooltip = null; languageFlags = []; // Clear language flags array } // Create upgrades button (top right, not in topLeft) upgradesButton = LK.getAsset('upgrade', { anchorX: 1, anchorY: 0, scaleX: upgradesButtonWidth / 800, // Scale to fit button size (upgrade asset is 800px wide) scaleY: upgradesButtonHeight / 400 // Scale to fit button size (upgrade asset is 400px tall) }); // Place button at top right, but not in the top left 100x100 area upgradesButton.x = -upgradesButtonPadding; // Will be positioned by LK.gui.topRight upgradesButton.y = upgradesButtonY; upgradesButton.interactive = true; upgradesButton.buttonMode = true; upgradesButton.visible = true; upgradesButtonActive = false; LK.gui.topRight.addChild(upgradesButton); // Also add a large invisible hit area to make it easier to tap if (!upgradesButton.hitArea) { var hitArea = LK.getAsset('fon', { anchorX: 1, anchorY: 0, scaleX: 220 / 2048, scaleY: 100 / 2732 }); hitArea.x = upgradesButton.x; hitArea.y = upgradesButton.y; hitArea.alpha = 0.01; hitArea.interactive = true; hitArea.buttonMode = true; LK.gui.topRight.addChild(hitArea); hitArea.on('down', function (x, y, obj) { if (!upgradesButtonActive && !controlsPopup && !shopPopup) showUpgradesPopup(); }); } // Create shop button (top right, below upgrades button) shopButton = LK.getAsset('shop_button', { anchorX: 1, anchorY: 0, scaleX: 1, scaleY: 1 }); // Place button at top right, below upgrades button shopButton.x = -shopButtonPadding; // Will be positioned by LK.gui.topRight shopButton.y = shopButtonY; shopButton.interactive = true; shopButton.buttonMode = true; shopButton.visible = true; shopButtonActive = false; LK.gui.topRight.addChild(shopButton); // Also add a large invisible hit area to make it easier to tap if (!shopButton.hitArea) { var shopHitArea = LK.getAsset('fon', { anchorX: 1, anchorY: 0, scaleX: 300 / 2048, scaleY: 150 / 2732 }); shopHitArea.x = shopButton.x; shopHitArea.y = shopButton.y; shopHitArea.alpha = 0.01; shopHitArea.interactive = true; shopHitArea.buttonMode = true; LK.gui.topRight.addChild(shopHitArea); shopHitArea.on('down', function (x, y, obj) { if (!shopButtonActive && !upgradesPopup && !controlsPopup) showShopPopup(); }); } // Control settings variables var controlsPopup = null; var controlsButton = null; var controlsButtonActive = false; var controlsButtonWidth = 180; var controlsButtonHeight = 90; var useJoystickControl = true; // Default to joystick control // Controls popup container function showControlsPopup() { if (controlsPopup) return; // Already open LK.getSound('menu').play(); controlsPopup = new Container(); // Dimmed background var popupBg = LK.getAsset('fon', { anchorX: 0, anchorY: 0, scaleX: 1, scaleY: 1 }); popupBg.tint = 0x1a1a2e; popupBg.alpha = 0.85; popupBg.width = WORLD_WIDTH; popupBg.height = WORLD_HEIGHT; popupBg.interactive = false; popupBg.buttonMode = false; controlsPopup.addChild(popupBg); // Popup window var popupWidth = 1200; var popupHeight = 800; var popupWin = LK.getAsset('fonsettings', { anchorX: 0.5, anchorY: 0.5, scaleX: popupWidth / 3000, scaleY: popupHeight / 4000 }); popupWin.x = WORLD_WIDTH / 2; popupWin.y = WORLD_HEIGHT / 2; popupWin.alpha = 0.98; controlsPopup.addChild(popupWin); // Close button (X) in bottom center of controls popup using krest image var controlsCloseButton = LK.getAsset('krest', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); controlsCloseButton.x = WORLD_WIDTH / 2; controlsCloseButton.y = WORLD_HEIGHT / 2 + popupHeight / 2 - 80 + 300 + 10 + 10 + 10 + 10; controlsCloseButton.interactive = true; controlsCloseButton.buttonMode = true; controlsCloseButton.down = function (x, y, obj) { closeControlsPopup(); }; controlsPopup.addChild(controlsCloseButton); // Title removed as requested // Joystick control button var joystickButtonWidth = 300; var joystickButtonHeight = 300; var joystickControlButton = LK.getAsset('joy', { anchorX: 0.5, anchorY: 0.5, scaleX: joystickButtonWidth / 300, scaleY: joystickButtonHeight / 300 }); joystickControlButton.x = WORLD_WIDTH / 2 - 300; joystickControlButton.y = WORLD_HEIGHT / 2 + 50; joystickControlButton.tint = useJoystickControl ? 0x00FF00 : 0x808080; joystickControlButton.interactive = true; joystickControlButton.buttonMode = true; controlsPopup.addChild(joystickControlButton); // Mouse control button var mouseControlButton = LK.getAsset('mouse', { anchorX: 0.5, anchorY: 0.5, scaleX: joystickButtonWidth / 300, scaleY: joystickButtonHeight / 400 }); mouseControlButton.x = WORLD_WIDTH / 2 + 300; mouseControlButton.y = WORLD_HEIGHT / 2 + 50; mouseControlButton.tint = !useJoystickControl ? 0x00FF00 : 0x808080; mouseControlButton.interactive = true; mouseControlButton.buttonMode = true; controlsPopup.addChild(mouseControlButton); // Button interactions joystickControlButton.on('down', function (x, y, obj) { if (!useJoystickControl) { useJoystickControl = true; isMouseControlling = false; joystickControlButton.tint = 0x00FF00; mouseControlButton.tint = 0x808080; LK.getSound('peretik').play(); } }); mouseControlButton.on('down', function (x, y, obj) { if (useJoystickControl) { useJoystickControl = false; isMouseControlling = true; joystickControlButton.tint = 0x808080; mouseControlButton.tint = 0x00FF00; // Hide joystick when switching to mouse control joystickInstance.visible = false; LK.getSound('peretik').play(); } }); // Add popup to game game.addChild(controlsPopup); controlsButtonActive = true; controlsButton.visible = false; controlsButton.interactive = false; controlsButton.buttonMode = false; // Hide upgrades and shop buttons when controls popup is open upgradesButton.visible = false; upgradesButton.interactive = false; upgradesButton.buttonMode = false; shopButton.visible = false; shopButton.interactive = false; shopButton.buttonMode = false; } function closeControlsPopup() { if (controlsPopup && controlsPopup.parent) { controlsPopup.parent.removeChild(controlsPopup); LK.getSound('menu').play(); } controlsPopup = null; controlsButtonActive = false; controlsButton.visible = true; controlsButton.interactive = true; controlsButton.buttonMode = true; // Show upgrades and shop buttons when controls popup is closed upgradesButton.visible = true; upgradesButton.interactive = true; upgradesButton.buttonMode = true; shopButton.visible = true; shopButton.interactive = true; shopButton.buttonMode = true; } // Shop popup container function showShopPopup() { if (shopPopup) return; // Already open LK.getSound('menu').play(); shopPopup = new Container(); // Dimmed background for shop popup var popupBg = LK.getAsset('fon', { anchorX: 0, anchorY: 0, scaleX: 1, scaleY: 1 }); popupBg.tint = 0x2e1a1a; // Dark red-purple color for shop background popupBg.alpha = 0.85; popupBg.width = WORLD_WIDTH; popupBg.height = WORLD_HEIGHT; popupBg.interactive = false; popupBg.buttonMode = false; shopPopup.addChild(popupBg); // Popup window var popupWidth = 1800; var popupHeight = 1650; var popupWin = LK.getAsset('Fonshop', { anchorX: 0.5, anchorY: 0.5, scaleX: popupWidth / 2048, scaleY: popupHeight / 2732 }); popupWin.x = WORLD_WIDTH / 2; popupWin.y = WORLD_HEIGHT / 2; popupWin.alpha = 0.98; shopPopup.addChild(popupWin); // Close button (X) in bottom center of shop popup using krest image var shopCloseButton = LK.getAsset('krest', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); shopCloseButton.x = WORLD_WIDTH / 2; shopCloseButton.y = WORLD_HEIGHT / 2 + popupHeight / 2 - 100 + 200 + 80; shopCloseButton.interactive = true; shopCloseButton.buttonMode = true; shopCloseButton.down = function (x, y, obj) { closeShopPopup(); }; shopPopup.addChild(shopCloseButton); // Shop icons grid (3x2) - same layout as upgrades var iconSize = 360; var availableWidth = popupWidth - 60; var availableHeight = popupHeight - 220; var cols = 3; var rows = 2; var iconSpacingX = availableWidth / (cols - 1); var iconSpacingY = availableHeight / (rows - 1); var gridStartX = WORLD_WIDTH / 2 - availableWidth / 2; var gridStartY = popupWin.y - popupHeight / 2 + 80 + 480; for (var i = 0; i < 6; i++) { (function (i) { var row = Math.floor(i / 3); var col = i % 3; // Move leftmost and rightmost icons closer to center var iconX = gridStartX + col * iconSpacingX; if (col === 0) { iconX += 300; } if (col === 2) { iconX -= 300; } var iconY = gridStartY + row * iconSpacingY; // Move bottom row up if (row === 1) { iconY = iconY - 650; } // Create numbered shop icon var iconBg = LK.getAsset('shop_icon_' + (i + 1), { anchorX: 0.5, anchorY: 0.5, scaleX: iconSize / 360, scaleY: iconSize / 360 }); iconBg.x = iconX; iconBg.y = iconY; shopPopup.addChild(iconBg); // Add number text to icon var numberText = new Text2((i + 1).toString(), { size: 120, fill: 0xFFFFFF, align: 'center', font: 'Impact' }); numberText.anchor.set(0.5, 0.5); numberText.x = iconX; numberText.y = iconY; shopPopup.addChild(numberText); // Button interaction (placeholder for now) iconBg.interactive = true; iconBg.buttonMode = true; iconBg.on('down', function (x, y, obj) { // Placeholder - will implement shop item functionality later console.log('Shop item ' + (i + 1) + ' clicked'); }); })(i); } // Add popup to game game.addChild(shopPopup); shopButtonActive = true; shopButton.visible = false; shopButton.interactive = false; shopButton.buttonMode = false; // Hide upgrades and controls buttons when shop popup is open upgradesButton.visible = false; upgradesButton.interactive = false; upgradesButton.buttonMode = false; controlsButton.visible = false; controlsButton.interactive = false; controlsButton.buttonMode = false; } function closeShopPopup() { if (shopPopup && shopPopup.parent) { shopPopup.parent.removeChild(shopPopup); LK.getSound('menu').play(); } shopPopup = null; shopButtonActive = false; shopButton.visible = true; shopButton.interactive = true; shopButton.buttonMode = true; // Show upgrades and controls buttons when shop popup is closed upgradesButton.visible = true; upgradesButton.interactive = true; upgradesButton.buttonMode = true; controlsButton.visible = true; controlsButton.interactive = true; controlsButton.buttonMode = true; } // Upgrades popup container function showUpgradesPopup() { if (upgradesPopup) return; // Already open // No speed manipulation needed - objects will check upgradesPopup flag in their update methods LK.getSound('menu').play(); upgradesPopup = new Container(); // Dimmed background for upgrades popup with its own color var popupBg = LK.getAsset('fon', { anchorX: 0, anchorY: 0, scaleX: 1, scaleY: 1 }); popupBg.tint = 0x1a1a2e; // Dark blue-purple color for upgrades background popupBg.alpha = 0.85; // Make it semi-transparent popupBg.width = WORLD_WIDTH; popupBg.height = WORLD_HEIGHT; popupBg.interactive = false; popupBg.buttonMode = false; upgradesPopup.addChild(popupBg); // Popup window var popupWidth = 1800; var popupHeight = 1650; var popupWin = LK.getAsset('fonmenu', { anchorX: 0.5, anchorY: 0.5, scaleX: popupWidth / 2048, scaleY: popupHeight / 2732 }); popupWin.x = WORLD_WIDTH / 2; popupWin.y = WORLD_HEIGHT / 2; popupWin.alpha = 0.98; upgradesPopup.addChild(popupWin); // Close button (X) in top-right corner of upgrades popup using krest image var upgradesCloseButton = LK.getAsset('krest', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); upgradesCloseButton.x = WORLD_WIDTH / 2; upgradesCloseButton.y = WORLD_HEIGHT / 2 + popupHeight / 2 - 100 + 280; upgradesCloseButton.interactive = true; upgradesCloseButton.buttonMode = true; upgradesCloseButton.down = function (x, y, obj) { closeUpgradesPopup(); }; upgradesPopup.addChild(upgradesCloseButton); // Title removed as per request // Upgrade icons grid (3x3) - icons closer together var iconSize = 360; var availableWidth = popupWidth - 60; // Much smaller margin for even closer icons var availableHeight = popupHeight - 220; // Smaller margin for closer icons var cols = 3; var rows = 2; var iconSpacingX = availableWidth / (cols - 1); var iconSpacingY = availableHeight / (rows - 1); var gridStartX = WORLD_WIDTH / 2 - availableWidth / 2; var gridStartY = popupWin.y - popupHeight / 2 + 80 + 480; // Move top row down by 480 pixels (was 450, now +30) for (var i = 0; i < UPGRADE_DEFS.length; i++) { (function (i) { var upg = UPGRADE_DEFS[i]; var row = Math.floor(i / 3); var col = i % 3; // Move leftmost and rightmost icons closer to center var iconX = gridStartX + col * iconSpacingX; if (col === 0) { iconX += 300; // Move leftmost icon right by 300px (even closer to center) } if (col === 2) { iconX -= 300; // Move rightmost icon left by 300px (even closer to center) } var iconY = gridStartY + row * iconSpacingY; // Move bottom row up by 650 pixels (500 + 150) if (row === 1) { iconY = iconY - 650; } // Icon background/placeholder (will be replaced with actual upgrade icons later) var iconBg; if (upg.key === 'fireDamage') { iconBg = LK.getAsset('uron', { anchorX: 0.5, anchorY: 0.5, scaleX: iconSize / 360, // Assuming uron asset is 360x360 or similar aspect for this scaling scaleY: iconSize / 360 }); } else if (upg.key === 'fireRate') { iconBg = LK.getAsset('Skorost', { anchorX: 0.5, anchorY: 0.5, scaleX: iconSize / 360, // Skorost asset is 360x360 scaleY: iconSize / 360 }); } else if (upg.key === 'critChance') { iconBg = LK.getAsset('krit', { anchorX: 0.5, anchorY: 0.5, scaleX: iconSize / 360, // krit asset is 360x360 scaleY: iconSize / 360 }); } else if (upg.key === 'lightningChance') { iconBg = LK.getAsset('lightning', { anchorX: 0.5, anchorY: 0.5, scaleX: iconSize / 337, // lightning asset is 337x337 scaleY: iconSize / 337 }); } else if (upg.key === 'fire') { iconBg = LK.getAsset('fire', { anchorX: 0.5, anchorY: 0.5, scaleX: iconSize / 317, scaleY: iconSize / 317 }); } else if (upg.key === 'freeze') { iconBg = LK.getAsset('Iceicon', { anchorX: 0.5, anchorY: 0.5, scaleX: iconSize / 316, scaleY: iconSize / 316 }); } else { iconBg = LK.getAsset('test', { anchorX: 0.5, anchorY: 0.5, scaleX: iconSize / 360, scaleY: iconSize / 360 }); } iconBg.x = iconX; iconBg.y = iconY; upgradesPopup.addChild(iconBg); // Price text above icon var priceText = new Text2("$" + upg.cost(upgrades[upg.key]), { size: 108, // Increased by 1.5x (72 * 1.5) fill: 0x00FF00, align: 'center', font: 'Impact' }); priceText.anchor.set(0.5, 1); priceText.x = iconX; priceText.y = iconY - iconSize / 2 - 30; // Increased offset for larger icon upgradesPopup.addChild(priceText); // Level indicator below icon var levelText = new Text2(upgrades[upg.key] + "/" + upg.max, { size: 96, // Increased by 1.5x (64 * 1.5) fill: 0xFFFFFF, align: 'center', font: 'Impact' }); levelText.anchor.set(0.5, 0); levelText.x = iconX; levelText.y = iconY + iconSize / 2 + 30; // Increased offset for larger icon upgradesPopup.addChild(levelText); // Create info icon in top-right corner of upgrade icon using iconI asset var infoIcon = LK.getAsset('iconI', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); infoIcon.alpha = 0.8; infoIcon.x = iconX + iconSize / 2 - 30; infoIcon.y = iconY - iconSize / 2 + 30; upgradesPopup.addChild(infoIcon); // Info icon interaction infoIcon.interactive = true; infoIcon.buttonMode = true; infoIcon.down = function (x, y, obj) { showUpgradeTooltip(upg.key, iconX, iconY); }; // Button interaction for main icon iconBg.interactive = true; iconBg.buttonMode = true; iconBg.upgradeKey = upg.key; iconBg.down = function (x, y, obj) { handleUpgradePurchase(upg, priceText, levelText); }; })(i); } // Add popup to game game.addChild(upgradesPopup); // Pause game logic (handled by LK automatically if popup is modal) upgradesButtonActive = true; upgradesButton.visible = false; upgradesButton.interactive = false; upgradesButton.buttonMode = false; // Hide controls and shop buttons when upgrades popup is open controlsButton.visible = false; controlsButton.interactive = false; controlsButton.buttonMode = false; shopButton.visible = false; shopButton.interactive = false; shopButton.buttonMode = false; } function closeUpgradesPopup() { // Hide any active tooltip hideUpgradeTooltip(); if (upgradesPopup && upgradesPopup.parent) { upgradesPopup.parent.removeChild(upgradesPopup); LK.getSound('menu').play(); } // No speed restoration needed - objects check upgradesPopup flag automatically upgradesPopup = null; upgradesButtonActive = false; upgradesButton.visible = true; upgradesButton.interactive = true; upgradesButton.buttonMode = true; // Show controls and shop buttons when upgrades popup is closed controlsButton.visible = true; controlsButton.interactive = true; controlsButton.buttonMode = true; shopButton.visible = true; shopButton.interactive = true; shopButton.buttonMode = true; } // Handle upgrade purchase function handleUpgradePurchase(upg, priceText, levelText) { // Block upgrade purchase when tooltip is open if (currentTooltip) return; var level = upgrades[upg.key]; var cost = upg.cost(level); if (level >= upg.max) return; if (LK.getScore() < cost) return; // Apply upgrade LK.setScore(LK.getScore() - cost); upgrades[upg.key]++; // Play purchase sound LK.getSound('Pokupka').play(); // Apply effect applyUpgradeEffect(upg.key); updateScoreDisplay(); // Update price and level text priceText.setText("$" + upg.cost(upgrades[upg.key])); levelText.setText(upgrades[upg.key] + "/" + upg.max); // Optionally, flash or animate the texts LK.effects.flashObject(priceText, 0x00ff00, 200); LK.effects.flashObject(levelText, 0x00ff00, 200); // Find and pulse the upgrade icon that was purchased for (var i = 0; i < upgradesPopup.children.length; i++) { var child = upgradesPopup.children[i]; if (child.upgradeKey === upg.key) { // Store original scale if not already stored if (!child.originalScaleX) { if (upg.key === 'lightningChance') { child.originalScaleX = iconSize / 337; // Lightning asset scale child.originalScaleY = iconSize / 337; } else if (upg.key === 'fire') { child.originalScaleX = iconSize / 317; // Fire asset scale child.originalScaleY = iconSize / 317; } else if (upg.key === 'freeze') { child.originalScaleX = iconSize / 316; // Freeze asset scale child.originalScaleY = iconSize / 316; } else { child.originalScaleX = iconSize / 360; // Other assets scale child.originalScaleY = iconSize / 360; } } // Pulse animation: scale up and then back down using absolute values tween.stop(child, { scaleX: true, scaleY: true }); tween(child, { scaleX: child.originalScaleX * 1.15, scaleY: child.originalScaleY * 1.15 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(child, { scaleX: child.originalScaleX, scaleY: child.originalScaleY }, { duration: 120, easing: tween.easeIn }); } }); break; } } } // Apply upgrade effect function applyUpgradeEffect(key) { if (key === 'fireDamage') { FIRE_DAMAGE = 35 + 20 * upgrades.fireDamage; } else if (key === 'fireRate') { FIRE_RATE_MS = 1000 - 176 * upgrades.fireRate; if (FIRE_RATE_MS < 120) FIRE_RATE_MS = 120; } else if (key === 'freeze') { // Freeze chance upgrade applied - no immediate visual effect needed } else if (key === 'lightningChance') { // Lightning chance upgrade applied - no immediate visual effect needed } else if (key === 'fire') { // Fire upgrade applied - no immediate visual effect needed } else if (key === 'critChance') { // Critical chance upgrade applied - no immediate visual effect needed } } // Button event - using LK event system for image asset upgradesButton.down = function (x, y, obj) { if (!upgradesButtonActive && !controlsPopup && !shopPopup) showUpgradesPopup(); }; // Initialize Crosshair crosshairInstance = new Crosshair(); crosshairInstance.graphic = crosshairInstance.attachAsset('crosshair_asset', { anchorX: 0.5, anchorY: 0.5 }); crosshairInstance.speed = CROSSHAIR_SPEED; crosshairInstance.x = WORLD_WIDTH / 2; crosshairInstance.y = WORLD_HEIGHT / 2; game.addChild(crosshairInstance); // Detect device type detectDevice(); // Initialize Virtual Joystick joystickInstance = new VirtualJoystick(); joystickInstance.initialize('joystick_base_asset', 'joystick_knob_asset', JOYSTICK_VISUAL_RADIUS, JOYSTICK_INTERACTION_RADIUS); game.addChild(joystickInstance); joystickInstance.visible = false; // Start hidden, will appear on first touch // Hide joystick completely on desktop if (!isMobileDevice) { joystickInstance.visible = false; isMouseControlling = true; } // --- Event Handlers --- game.down = function (x, y, eventObj) { if (upgradesPopup || controlsPopup || shopPopup || currentTooltip) { // Block all input and pause game when popup is open return; } if (useJoystickControl) { // Joystick controls // Always show joystick at finger, and only allow one joystick at a time joystickInstance.x = x; joystickInstance.y = y; joystickInstance.visible = true; // Always activate joystick at this position, treat as local origin if (joystickInstance.processDown(x, y, eventObj, true)) { joystickTouchId = eventObj.identifier; isFiring = true; // Only allow firing while finger is held } } else { // Mouse controls mouseX = x; mouseY = y; isFiring = true; // Update mouse coordinates display if (mouseCoordinatesText) { mouseCoordinatesText.setText('Mouse: ' + Math.round(mouseX) + ', ' + Math.round(mouseY)); } } }; game.move = function (x, y, eventObj) { if (upgradesPopup || controlsPopup || shopPopup || currentTooltip) { // Block all input and pause game when popup is open return; } if (useJoystickControl) { // Joystick: handle joystick movement if (joystickInstance.active && eventObj.identifier === joystickTouchId) { joystickInstance.processMove(x, y, eventObj); } } else { // Mouse: update mouse position for crosshair tracking mouseX = x; mouseY = y; // Update mouse coordinates display if (mouseCoordinatesText) { mouseCoordinatesText.setText('Mouse: ' + Math.round(mouseX) + ', ' + Math.round(mouseY)); } } // No crosshair update here; handled in game.update }; game.up = function (x, y, eventObj) { if (upgradesPopup || controlsPopup || shopPopup || currentTooltip) { // Block all input and pause game when popup is open return; } if (useJoystickControl) { // Joystick: handle joystick release if (eventObj.identifier === joystickTouchId) { joystickInstance.processUp(eventObj); joystickInstance.visible = false; joystickTouchId = null; isFiring = false; } } else { // Mouse: stop firing on mouse release isFiring = false; } }; // --- Game Loop --- // Set initial spawn delay to 5 seconds (5000ms) nextSpawnTime = 5000; // 5 second delay before first spawn // Enhanced cleanup function to prevent memory leaks function cleanupGame() { // Comprehensive tween cleanup - stop ALL active tweens if (tween) { // Try to stop all tweens globally if method exists if (tween.stop) { try { tween.stop(); // Stop all active tweens } catch (e) { console.log("Error stopping global tweens:", e); } } // Stop tweens on specific objects that might have them if (crosshairInstance && crosshairInstance.graphic) { tween.stop(crosshairInstance); tween.stop(crosshairInstance.graphic); } if (puhaBarrelInstance) { tween.stop(puhaBarrelInstance); } if (leftCityImage) { tween.stop(leftCityImage); } if (rightCityImage) { tween.stop(rightCityImage); } if (cityInstance && cityInstance.graphic) { tween.stop(cityInstance); tween.stop(cityInstance.graphic); } } // Clear any remaining timeouts/intervals for (var i = 0; i < 1000; i++) { LK.clearTimeout(i); LK.clearInterval(i); } // Enhanced falling objects cleanup if (fallingObjects) { for (var j = 0; j < fallingObjects.length; j++) { var obj = fallingObjects[j]; if (obj) { // Stop all tweens on object before destroying if (tween) { tween.stop(obj); if (obj.graphic) tween.stop(obj.graphic); if (obj.healthDisplay) tween.stop(obj.healthDisplay); } // Destroy object properly if (!obj.isDestroyed) { obj.destroySelf(); } } } fallingObjects = []; } // Enhanced damage numbers cleanup if (game.damageNumbers) { for (var d = 0; d < game.damageNumbers.length; d++) { var damageNum = game.damageNumbers[d]; if (damageNum) { tween.stop(damageNum); // Stop any active tweens if (damageNum.parent) { damageNum.parent.removeChild(damageNum); } } } game.damageNumbers = []; } // Enhanced particles cleanup if (game.particles) { for (var p = 0; p < game.particles.length; p++) { var particle = game.particles[p]; if (particle) { tween.stop(particle); // Stop any active tweens if (particle.parent) { particle.parent.removeChild(particle); } } } game.particles = []; } // Enhanced muzzle particles cleanup if (game.muzzleParticles) { for (var m = 0; m < game.muzzleParticles.length; m++) { var muzzleParticle = game.muzzleParticles[m]; if (muzzleParticle) { tween.stop(muzzleParticle); // Stop any active tweens if (muzzleParticle.parent) { muzzleParticle.parent.removeChild(muzzleParticle); } } } game.muzzleParticles = []; } // Enhanced snowflake particles cleanup if (game.snowflakeParticles) { for (var sf = 0; sf < game.snowflakeParticles.length; sf++) { var snowflakeParticle = game.snowflakeParticles[sf]; if (snowflakeParticle) { tween.stop(snowflakeParticle); // Stop any active tweens if (snowflakeParticle.parent) { snowflakeParticle.parent.removeChild(snowflakeParticle); } } } game.snowflakeParticles = []; } // Clean up stars and their tweens if (stars) { for (var s = 0; s < stars.length; s++) { var star = stars[s]; if (star) { tween.stop(star); if (star.parent) { star.parent.removeChild(star); } } } stars = []; } // Force garbage collection hint (if available) if (typeof gc === 'function') { try { gc(); } catch (e) { // Ignore if gc is not available } } } // Call cleanup when game starts cleanupGame(); // --- Game Loop --- // Create twinkling stars (after cleanup to prevent immediate removal) createStars(); // Start background music rotation startMusicRotation(); // Music toggle button variables var musicButton = null; var musicEnabled = true; // Track music state // Create controls button at absolute position 450, 160 - added at end to ensure highest z-index controlsButton = LK.getAsset('Control', { anchorX: 0.5, anchorY: 0.5, scaleX: controlsButtonWidth / 100, scaleY: controlsButtonWidth / 100 }); controlsButton.x = 450; controlsButton.y = 160; controlsButton.interactive = true; controlsButton.buttonMode = true; controlsButton.visible = true; controlsButtonActive = false; game.addChild(controlsButton); // Create music toggle button positioned to the right of controls button musicButton = LK.getAsset('musonn', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.8, scaleY: 1.8 }); musicButton.x = 630; // Position to the right of controls button (450 + 180) musicButton.y = 160; musicButton.interactive = true; musicButton.buttonMode = true; musicButton.visible = true; game.addChild(musicButton); // Add hit area for controls button - make it larger to cover the full button var controlsHitArea = LK.getAsset('fon', { anchorX: 0.5, anchorY: 0.5, scaleX: 300 / 2048, scaleY: 200 / 2732 }); controlsHitArea.x = 450; controlsHitArea.y = 160; controlsHitArea.alpha = 0.01; controlsHitArea.interactive = true; controlsHitArea.buttonMode = true; game.addChild(controlsHitArea); controlsHitArea.on('down', function (x, y, obj) { if (!controlsButtonActive && !upgradesPopup && !shopPopup) showControlsPopup(); }); // Add hit area for music button - same size as controls button var musicHitArea = LK.getAsset('fon', { anchorX: 0.5, anchorY: 0.5, scaleX: 300 / 2048, scaleY: 200 / 2732 }); musicHitArea.x = 630; // Same position as music button musicHitArea.y = 160; musicHitArea.alpha = 0.01; musicHitArea.interactive = true; musicHitArea.buttonMode = true; game.addChild(musicHitArea); // Music button interaction function toggleMusic() { musicEnabled = !musicEnabled; if (musicEnabled) { // Switch to music on icon and resume music musicButton.removeChild(musicButton.children[0]); var onIcon = musicButton.attachAsset('musonn', { anchorX: 0.5, anchorY: 0.5 }); if (!musicPlaying) { startMusicRotation(); } } else { // Switch to music off icon and stop music musicButton.removeChild(musicButton.children[0]); var offIcon = musicButton.attachAsset('musoff', { anchorX: 0.5, anchorY: 0.5 }); LK.stopMusic(); musicPlaying = false; } } musicHitArea.on('down', function (x, y, obj) { if (!controlsButtonActive && !upgradesPopup && !shopPopup) { toggleMusic(); } }); musicButton.down = function (x, y, obj) { if (!controlsButtonActive && !upgradesPopup && !shopPopup) { toggleMusic(); } }; game.update = function () { // Block all game updates while upgrades, controls, or shop popup is open if (upgradesPopup || controlsPopup || shopPopup || currentTooltip) { // Pause spawn timer when popup is open if (!spawnPausedTime) { spawnPausedTime = LK.ticks * (1000 / 60); } return; } var currentTime = LK.ticks * (1000 / 60); // Approximate current time in ms // Resume spawn timer when popup is closed if (spawnPausedTime) { var pauseDuration = currentTime - spawnPausedTime; nextSpawnTime += pauseDuration; // Extend spawn time by pause duration spawnPausedTime = null; } // 0. Update Crosshair Position if (crosshairInstance) { if (useJoystickControl && joystickInstance) { // Joystick: Update crosshair based on joystick input // Calculate how far the knob is from the center (0..1) var knobDistance = Math.sqrt(joystickInstance.deltaX * joystickInstance.deltaX + joystickInstance.deltaY * joystickInstance.deltaY); // Crosshair speed is proportional to knob distance, up to CROSSHAIR_SPEED var effectiveSpeed = CROSSHAIR_SPEED * knobDistance * 2 / 1.5; crosshairInstance.x += joystickInstance.deltaX * effectiveSpeed; crosshairInstance.y += joystickInstance.deltaY * effectiveSpeed; } else if (!useJoystickControl) { // Mouse: Move crosshair directly to mouse position with same speed as joystick var targetX = mouseX; var targetY = mouseY; var deltaX = targetX - crosshairInstance.x; var deltaY = targetY - crosshairInstance.y; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (distance > 1) { // Move crosshair towards mouse at reduced speed to match joystick (13.3 speed) var moveSpeed = CROSSHAIR_SPEED * 1.33; var moveX = deltaX / distance * moveSpeed; var moveY = deltaY / distance * moveSpeed; // Don't overshoot the target if (Math.abs(moveX) > Math.abs(deltaX)) moveX = deltaX; if (Math.abs(moveY) > Math.abs(deltaY)) moveY = deltaY; crosshairInstance.x += moveX; crosshairInstance.y += moveY; } } // Clamp crosshair to screen bounds (considering anchor is 0.5, 0.5) var chHW = crosshairInstance.graphic.width / 2; var chHH = crosshairInstance.graphic.height / 2; crosshairInstance.x = Math.max(chHW, Math.min(WORLD_WIDTH - chHW, crosshairInstance.x)); crosshairInstance.y = Math.max(chHH, Math.min(WORLD_HEIGHT - chHH, crosshairInstance.y)); // Crosshair speed calculation and display removed as requested } // 1. Check if crosshair is over any target and change color accordingly var crosshairOverTarget = false; for (var k = 0; k < fallingObjects.length; k++) { var target = fallingObjects[k]; if (!target.isDestroyed && crosshairInstance.isOver(target)) { crosshairOverTarget = true; break; } } // Change crosshair color based on targeting if (crosshairOverTarget && crosshairInstance.graphic.tint !== 0xFF0000) { // Turn red when over target tween(crosshairInstance.graphic, { tint: 0xFF0000 }, { duration: 100, easing: tween.easeOut }); } else if (!crosshairOverTarget && crosshairInstance.graphic.tint !== 0xFFFFFF) { // Turn white when not over target tween(crosshairInstance.graphic, { tint: 0xFFFFFF }, { duration: 100, easing: tween.easeOut }); } // 2. Firing Logic // Only fire when crosshair is over target and firing is enabled if (crosshairOverTarget && currentTime >= lastFireTime + FIRE_RATE_MS) { lastFireTime = currentTime; var hitTargets = []; // Track which targets were hit this frame for (var k = 0; k < fallingObjects.length; k++) { var target = fallingObjects[k]; if (!target.isDestroyed && crosshairInstance.isOver(target)) { target.takeDamage(FIRE_DAMAGE); hitTargets.push(target); // Add to hit targets list // Check for fire chance var fireChance = upgrades.fire * 5; // 5% per level if (Math.random() * 100 < fireChance) { target.applyBurn(); } // Check for freeze chance var freezeChance = upgrades.freeze * 5; // 5% per level if (Math.random() * 100 < freezeChance) { target.applyFreeze(); } // Aggressive particle cleanup to prevent accumulation if (!game.particles) game.particles = []; if (game.particles.length >= 30) { // Reduced threshold from 50 to 30 // Clean up oldest 15 particles instead of 10 for (var oldP = 0; oldP < 15; oldP++) { var oldParticle = game.particles.shift(); if (oldParticle) { // Stop any active tweens before removal tween.stop(oldParticle); if (oldParticle.parent) { oldParticle.parent.removeChild(oldParticle); } } } } // Create particle explosion at hit location var particleCount = 3 + Math.floor(Math.random() * 3); // 3 to 5 particles for (var p = 0; p < particleCount; p++) { var randomScale = 0.5 + Math.random() * 1.5; // Random scale between 0.5 and 2.0 var particle = LK.getAsset('particle', { anchorX: 0.5, anchorY: 0.5, scaleX: randomScale, scaleY: randomScale }); particle.x = target.x; particle.y = target.y; game.addChild(particle); game.particles.push(particle); // Random direction and speed for each particle var angle = Math.random() * Math.PI * 2; var speed = 100 + Math.random() * 100; // Random speed between 100-200 var velocityX = Math.cos(angle) * speed; var velocityY = Math.sin(angle) * speed; // Animate particle movement and scale down with proper cleanup tween(particle, { x: particle.x + velocityX, y: particle.y + velocityY, scaleX: 0, scaleY: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function (particleRef) { return function onFinish() { // Stop any remaining tweens on this particle tween.stop(particleRef); if (particleRef.parent) { particleRef.parent.removeChild(particleRef); } // Remove from tracking array if (game.particles) { var pIndex = game.particles.indexOf(particleRef); if (pIndex > -1) game.particles.splice(pIndex, 1); } }; }(particle) }); } } } // Play shoot sound once if any targets were hit if (hitTargets.length > 0) { LK.getSound('Shoot').play(); // Lightning strike effect - based on lightningChance upgrade (5% per level) var lightningChance = upgrades.lightningChance * 5; // 5% per level if (Math.random() * 100 < lightningChance && hitTargets.length > 0) { var primaryTarget = hitTargets[0]; // Use first hit target as lightning destination // Calculate cannon muzzle position var puhaWidth = 900 * 0.3; // Original width * scale var muzzleDistance = puhaWidth * 2.2; // Distance from pivot to muzzle end var cannonX = puhaBarrelInstance.x + Math.cos(puhaBarrelInstance.rotation) * muzzleDistance; var cannonY = puhaBarrelInstance.y + Math.sin(puhaBarrelInstance.rotation) * muzzleDistance; // Play lightning sound LK.getSound('molnia').play(); // Lightning also damages the primary target - check for critical hit // Lightning damage: 55% base + 5% per level (55% at level 1, 100% at level 10) var lightningDamagePercent = 0.55 + (upgrades.lightningChance - 1) * 0.05; // 55% + 5% per level var baseLightningDamage = Math.floor(FIRE_DAMAGE * lightningDamagePercent); var critChance = upgrades.critChance * 5; // 5% per level var isLightningCritical = Math.random() * 100 < critChance; var lightningDamage = isLightningCritical ? baseLightningDamage * 2 : baseLightningDamage; if (isLightningCritical) { LK.getSound('krit').play(); // Play critical hit sound for lightning } primaryTarget.health -= lightningDamage; LK.effects.flashObject(primaryTarget.graphic, 0xFF0000, 100); // Red flash on hit // Check for fire chance on lightning var fireChance = upgrades.fire * 5; // 5% per level if (Math.random() * 100 < fireChance) { primaryTarget.applyBurn(); } // Check for freeze chance on lightning var freezeChance = upgrades.freeze * 5; // 5% per level if (Math.random() * 100 < freezeChance) { primaryTarget.applyFreeze(); } // Create floating lightning damage number var lightningDamageNumber = new Text2(lightningDamage.toString(), { size: isLightningCritical ? 80 : 60, fill: isLightningCritical ? 0xFF0000 : 0x00FFFF, // Red color for critical lightning damage, blue for normal align: 'center', font: 'Impact' }); lightningDamageNumber.anchor.set(0.5, 0.5); lightningDamageNumber.x = primaryTarget.x + 60 + Math.random() * 20; // Position to the right of regular damage lightningDamageNumber.y = primaryTarget.y - 20; game.addChild(lightningDamageNumber); // Animate lightning damage number floating up tween(lightningDamageNumber, { y: lightningDamageNumber.y - 120, scaleX: 1.2, scaleY: 1.2 }, { duration: 1200, easing: tween.easeOut }); // Remove lightning damage number after timeout LK.setTimeout(function () { if (lightningDamageNumber.parent) { game.removeChild(lightningDamageNumber); } }, 1200); // Create initial lightning bolt from cannon to primary target var initialBoltDx = primaryTarget.x - cannonX; var initialBoltDy = primaryTarget.y - cannonY; var initialBoltDistance = Math.sqrt(initialBoltDx * initialBoltDx + initialBoltDy * initialBoltDy); var initialSegmentCount = Math.max(2, Math.floor(initialBoltDistance / 180)); var initialBoltAngle = Math.atan2(initialBoltDy, initialBoltDx); // Create molnia segments for initial bolt from cannon to primary target for (var s = 0; s < initialSegmentCount; s++) { var segmentProgress = (s + 0.05) / initialSegmentCount; var segmentX = cannonX + initialBoltDx * segmentProgress; var segmentY = cannonY + initialBoltDy * segmentProgress; // Add slight random offset for more natural lightning look segmentX += (Math.random() - 0.5) * 20; segmentY += (Math.random() - 0.5) * 20; // Create glow effect for initial bolt var glowSegment = LK.getAsset('molnia', { anchorX: 0.5, anchorY: 0.5 }); glowSegment.x = segmentX; glowSegment.y = segmentY; glowSegment.rotation = initialBoltAngle + (Math.random() - 0.5) * 0.3; glowSegment.scaleX = (0.25 + Math.random() * 0.15) * 3.0; glowSegment.scaleY = (0.25 + Math.random() * 0.15) * 3.0; glowSegment.alpha = 0.4; glowSegment.tint = 0x0088BB; game.addChild(glowSegment); // Main lightning segment for initial bolt var lightningSegment = LK.getAsset('molnia', { anchorX: 0.5, anchorY: 0.5 }); lightningSegment.x = segmentX; lightningSegment.y = segmentY; lightningSegment.rotation = initialBoltAngle + (Math.random() - 0.5) * 0.3; lightningSegment.scaleX = (0.25 + Math.random() * 0.15) * 1.3; lightningSegment.scaleY = (0.25 + Math.random() * 0.15) * 1.3; lightningSegment.alpha = 0.7 + Math.random() * 0.3; lightningSegment.tint = 0x66DDFF; game.addChild(lightningSegment); // Animate initial bolt segments tween(glowSegment, { alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function (segment) { return function onFinish() { if (segment.parent) { game.removeChild(segment); } }; }(glowSegment) }); tween(lightningSegment, { scaleY: 0, alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function (segment) { return function onFinish() { if (segment.parent) { game.removeChild(segment); } }; }(lightningSegment) }); } // Check if primary target is destroyed by lightning if (primaryTarget.health <= 0) { primaryTarget.destroySelf(); } var lightningTargets = []; // Find 2 nearest targets to the primary target (excluding already hit targets) var distances = []; for (var lt = 0; lt < fallingObjects.length; lt++) { var candidate = fallingObjects[lt]; if (!candidate.isDestroyed && hitTargets.indexOf(candidate) === -1) { var distX = candidate.x - primaryTarget.x; var distY = candidate.y - primaryTarget.y; var distance = Math.sqrt(distX * distX + distY * distY); distances.push({ target: candidate, distance: distance }); } } // Sort by distance and take closest 2 distances.sort(function (a, b) { return a.distance - b.distance; }); for (var d = 0; d < Math.min(2, distances.length); d++) { lightningTargets.push(distances[d].target); } // Chain lightning effect: jump from target to target sequentially var _chainLightning = function chainLightning(fromTarget, targetIndex) { if (targetIndex >= lightningTargets.length) return; var toTarget = lightningTargets[targetIndex]; var chainLightningDamage = baseLightningDamage; // Same as base lightning damage toTarget.health -= chainLightningDamage; LK.effects.flashObject(toTarget.graphic, 0xFF0000, 100); // Red flash on hit // Check for fire chance on chain lightning var fireChance = upgrades.fire * 5; // 5% per level if (Math.random() * 100 < fireChance) { toTarget.applyBurn(); } // Check for freeze chance on chain lightning var freezeChance = upgrades.freeze * 5; // 5% per level if (Math.random() * 100 < freezeChance) { toTarget.applyFreeze(); } // Create floating lightning damage number for chain targets var chainLightningDamageNumber = new Text2(chainLightningDamage.toString(), { size: 60, fill: 0x00FFFF, // Blue color for lightning damage align: 'center', font: 'Impact' }); chainLightningDamageNumber.anchor.set(0.5, 0.5); chainLightningDamageNumber.x = toTarget.x + 60 + Math.random() * 20; // Position to the right of regular damage chainLightningDamageNumber.y = toTarget.y - 20; game.addChild(chainLightningDamageNumber); // Animate chain lightning damage number floating up tween(chainLightningDamageNumber, { y: chainLightningDamageNumber.y - 120, scaleX: 1.2, scaleY: 1.2 }, { duration: 1200, easing: tween.easeOut }); // Remove chain lightning damage number after timeout LK.setTimeout(function () { if (chainLightningDamageNumber.parent) { game.removeChild(chainLightningDamageNumber); } }, 1200); // Check if chain target is destroyed by lightning if (toTarget.health <= 0) { toTarget.destroySelf(); } // Create lightning using multiple molnia segments var boltDx = toTarget.x - fromTarget.x; var boltDy = toTarget.y - fromTarget.y; var boltDistance = Math.sqrt(boltDx * boltDx + boltDy * boltDy); var segmentCount = Math.max(2, Math.floor(boltDistance / 180)); // Fewer segments with more distance between them var segmentLength = boltDistance / segmentCount; // Calculate angle of the bolt var boltAngle = Math.atan2(boltDy, boltDx); // Create molnia segments along the path with minimal overlap for (var s = 0; s < segmentCount; s++) { // Reduce overlap by using smaller offset var segmentProgress = (s + 0.05) / segmentCount; // Much smaller overlap var segmentX = fromTarget.x + boltDx * segmentProgress; var segmentY = fromTarget.y + boltDy * segmentProgress; // Add slight random offset for more natural lightning look segmentX += (Math.random() - 0.5) * 20; // Reduced random offset segmentY += (Math.random() - 0.5) * 20; // Create glow effect (larger, darker copy behind lightning) var glowSegment = LK.getAsset('molnia', { anchorX: 0.5, anchorY: 0.5 }); glowSegment.x = segmentX; glowSegment.y = segmentY; glowSegment.rotation = boltAngle + (Math.random() - 0.5) * 0.3; glowSegment.scaleX = (0.25 + Math.random() * 0.15) * 3.0; // Much larger for glow effect glowSegment.scaleY = (0.25 + Math.random() * 0.15) * 3.0; glowSegment.alpha = 0.4; // Semi-transparent glow glowSegment.tint = 0x0088BB; // Darker blue for glow game.addChild(glowSegment); // Main lightning segment (thinner, very light blue) var lightningSegment = LK.getAsset('molnia', { anchorX: 0.5, anchorY: 0.5 }); lightningSegment.x = segmentX; lightningSegment.y = segmentY; lightningSegment.rotation = boltAngle + (Math.random() - 0.5) * 0.3; // Slight rotation variation lightningSegment.scaleX = (0.25 + Math.random() * 0.15) * 1.3; // Thinner lightningSegment.scaleY = (0.25 + Math.random() * 0.15) * 1.3; // Thinner lightningSegment.alpha = 0.7 + Math.random() * 0.3; // Random brightness lightningSegment.tint = 0x66DDFF; // Very light blue color game.addChild(lightningSegment); // Animate glow segment fade out tween(glowSegment, { alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function (segment) { return function onFinish() { if (segment.parent) { game.removeChild(segment); } }; }(glowSegment) }); // Lightning segments shrink from both ends to middle before fade out tween(lightningSegment, { scaleY: 0, // Shrink vertically to create top-bottom to middle effect alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function (segment) { return function onFinish() { if (segment.parent) { game.removeChild(segment); } }; }(lightningSegment) }); } // Create additional impact particles at target for (var lp = 0; lp < 6; lp++) { var impactParticle = LK.getAsset('particle', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.8, scaleY: 1.8 }); impactParticle.tint = 0x00FFFF; // Cyan lightning color impactParticle.x = toTarget.x + (Math.random() - 0.5) * 30; impactParticle.y = toTarget.y + (Math.random() - 0.5) * 30; game.addChild(impactParticle); // Animate impact particles bursting outward var lpAngle = Math.random() * Math.PI * 2; var lpSpeed = 80 + Math.random() * 60; var lpVelX = Math.cos(lpAngle) * lpSpeed; var lpVelY = Math.sin(lpAngle) * lpSpeed; // Delay impact particles to start after traveling particles arrive LK.setTimeout(function (particle, velX, velY) { return function () { tween(particle, { x: particle.x + velX, y: particle.y + velY, scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { if (particle.parent) { game.removeChild(particle); } } }); }; }(impactParticle, lpVelX, lpVelY), 100 + lp * 10); // Start after particles arrive } // Chain to next target after delay LK.setTimeout(function () { _chainLightning(toTarget, targetIndex + 1); }, 150); // 150ms delay between jumps for faster lightning }; // Start the chain from primary target to first lightning target if (lightningTargets.length > 0) { _chainLightning(primaryTarget, 0); } } // Add visual recoil effect to barrel (scale-based, not position-based) if (puhaBarrelInstance) { // Stop any existing recoil animations tween.stop(puhaBarrelInstance, { scaleX: true, scaleY: true }); // Store original scale var originalScaleX = 0.3; var originalScaleY = 0.3; // Quick recoil animation using scale tween(puhaBarrelInstance, { scaleX: originalScaleX * 0.85, // Shrink slightly scaleY: originalScaleY * 1.1 // Stretch slightly vertically }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { // Return to original scale tween(puhaBarrelInstance, { scaleX: originalScaleX, scaleY: originalScaleY }, { duration: 250, easing: tween.easeInOut }); } }); } // Aggressive muzzle particles cleanup to prevent accumulation if (!game.muzzleParticles) game.muzzleParticles = []; if (game.muzzleParticles.length >= 20) { // Reduced threshold from 30 to 20 // Clean up oldest 10 particles instead of 5 for (var oldM = 0; oldM < 10; oldM++) { var oldMuzzleParticle = game.muzzleParticles.shift(); if (oldMuzzleParticle) { // Stop any active tweens before removal tween.stop(oldMuzzleParticle); if (oldMuzzleParticle.parent) { oldMuzzleParticle.parent.removeChild(oldMuzzleParticle); } } } } // Create muzzle flash particles at puha's muzzle end var muzzleParticleCount = 5 + Math.floor(Math.random() * 3); // 5 to 7 particles for (var mp = 0; mp < muzzleParticleCount; mp++) { var muzzleParticle = LK.getAsset('particle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0 + Math.random() * 2.0, // Random scale between 2.0 and 4.0 for larger smoke-like particles scaleY: 2.0 + Math.random() * 2.0 }); // Position particles at the muzzle end accounting for rotation // Puha barrel is anchored at left side (0,0.5) and scaled to 0.3 var puhaWidth = 900 * 0.3; // Original width * scale var puhaHeight = 2056 * 0.3; // Original height * scale // Calculate muzzle position relative to puha barrel's pivot point (left side, center) var muzzleDistance = puhaWidth * 2.2; // Distance from pivot to muzzle end - moved closer to barrel for smoke effect var muzzleX = puhaBarrelInstance.x + Math.cos(puhaBarrelInstance.rotation) * muzzleDistance; var muzzleY = puhaBarrelInstance.y + Math.sin(puhaBarrelInstance.rotation) * muzzleDistance; muzzleParticle.x = muzzleX + (Math.random() - 0.5) * 20; // Small random spread muzzleParticle.y = muzzleY + (Math.random() - 0.5) * 20; game.addChild(muzzleParticle); game.muzzleParticles.push(muzzleParticle); // Animate muzzle particles in the direction puha is facing var muzzleAngle = puhaBarrelInstance.rotation + (Math.random() - 0.5) * 0.5; // Small angle variation var muzzleSpeed = 150 + Math.random() * 100; // Speed between 150-250 var muzzleVelX = Math.cos(muzzleAngle) * muzzleSpeed; var muzzleVelY = Math.sin(muzzleAngle) * muzzleSpeed; tween(muzzleParticle, { x: muzzleParticle.x + muzzleVelX, y: muzzleParticle.y + muzzleVelY, scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function (muzzleRef) { return function onFinish() { // Stop any remaining tweens on this muzzle particle tween.stop(muzzleRef); if (muzzleRef.parent) { muzzleRef.parent.removeChild(muzzleRef); } // Remove from tracking array if (game.muzzleParticles) { var mIndex = game.muzzleParticles.indexOf(muzzleRef); if (mIndex > -1) game.muzzleParticles.splice(mIndex, 1); } }; }(muzzleParticle) }); } } } // 3. Update twinkling stars updateStars(); // 3.1. Update music rotation updateMusicRotation(); // 4. Spawn new falling objects if (currentTime >= nextSpawnTime) { spawnFallingObject(); nextSpawnTime = currentTime + SPAWN_INTERVAL_MS_MIN + Math.random() * (SPAWN_INTERVAL_MS_MAX - SPAWN_INTERVAL_MS_MIN); } // 5. Update and check falling objects for (var i = fallingObjects.length - 1; i >= 0; i--) { var obj = fallingObjects[i]; if (obj.isDestroyed) { // Remove from game immediately when destroyed if (obj.parent) { game.removeChild(obj); } fallingObjects.splice(i, 1); continue; } obj.update(); // Calculate the Y coordinate of the "middle" of the city image var cityMiddleY = cityInstance.y - cityInstance.graphic.height / 2; // Check if the falling object has reached the middle of the city if (obj.y + obj.graphic.height * 0.5 * (obj.graphic.scaleY || 1) >= cityMiddleY) { var damageToCity = obj.objectType === 'meteor' ? METEOR_DAMAGE_TO_CITY : ALIEN_DAMAGE_TO_CITY; cityInstance.takeDamage(damageToCity); LK.getSound('Boom').play(); // Play boom sound when object hits city obj.destroySelf(false); // Don't give money when enemy crashes into city continue; } if (obj.y - obj.graphic.height * 0.5 * (obj.graphic.scaleY || 1) > WORLD_HEIGHT) { obj.destroySelf(); continue; } } // Old safety check for playerHoldingTarget is removed as that system is gone. // Update puha barrel to track crosshair if (puhaBarrelInstance && crosshairInstance) { // Calculate angle from puha barrel to crosshair var dx = crosshairInstance.x - puhaBarrelInstance.x; var dy = crosshairInstance.y - puhaBarrelInstance.y; var angle = Math.atan2(dy, dx); // Rotate only the barrel to face crosshair puhaBarrelInstance.rotation = angle; } // Always ensure crosshair is rendered above all falling objects if (crosshairInstance && crosshairInstance.parent === game) { game.removeChild(crosshairInstance); game.addChild(crosshairInstance); } // Always ensure controls button is rendered above all objects (including meteors) if (controlsButton && controlsButton.parent === game) { game.removeChild(controlsButton); game.addChild(controlsButton); } if (controlsHitArea && controlsHitArea.parent === game) { game.removeChild(controlsHitArea); game.addChild(controlsHitArea); } // Always ensure music button is rendered above all objects (including meteors) if (musicButton && musicButton.parent === game) { game.removeChild(musicButton); game.addChild(musicButton); } if (musicHitArea && musicHitArea.parent === game) { game.removeChild(musicHitArea); game.addChild(musicHitArea); } };
===================================================================
--- original.js
+++ change.js
@@ -98,8 +98,27 @@
});
};
return self;
});
+// --- Crosshair Class ---
+// Represents the targeting crosshair controlled by player input
+var Crosshair = Container.expand(function () {
+ var self = Container.call(this);
+ // Crosshair graphic will be attached externally
+ self.graphic = null;
+ // Movement speed
+ self.speed = CROSSHAIR_SPEED;
+ // Check if crosshair is over a target
+ self.isOver = function (target) {
+ if (!target || target.isDestroyed || !target.graphic) return false;
+ // Simple bounds check using crosshair center point
+ var crosshairRadius = 25; // Half of crosshair size for hit detection
+ var targetHalfWidth = target.graphic.width * 0.5 * (target.graphic.scaleX || 1);
+ var targetHalfHeight = target.graphic.height * 0.5 * (target.graphic.scaleY || 1);
+ return self.x >= target.x - targetHalfWidth - crosshairRadius && self.x <= target.x + targetHalfWidth + crosshairRadius && self.y >= target.y - targetHalfHeight - crosshairRadius && self.y <= target.y + targetHalfHeight + crosshairRadius;
+ };
+ return self;
+});
/****
* Initialize Game
****/
Метеорит без огня пастельные цвета In-Game asset. 2d. High contrast. No shadows
Похожий
Иконка повышение урона, сочные цвета. In-Game asset. 2d. High contrast. No shadows. Comix
иконка на скорость атаки
надпись upgrade как красивая кнопка In-Game asset. 2d. High contrast. No shadows. comix
центральный круг желтый а внешний оранжевый
голубой вместо оранжевого
Красно оранжевый
Restyled
Разрешение 2048 на 400
молния должна быть с двух концов одинаковая и ответвления смотреть строго вверх и вниз а не наискосок
иконка шанса двойного урона (x2)
иконка голубой молнии без текста и цыферблата
иконка огня
Вместо молнии синяя снежинка, все остальное без изменений
сделать светлее
Комикс
сделать рамку толще в два раза и немного не правильной формы как в комиксах
сделать рамку тоньше сохранив стиль и цвета сочнее
надпись shop как красивая кнопка In-Game asset. 2d. High contrast. No shadows. comix
Рамка для всплывающей меню подсказки. In-Game asset. 2d. High contrast. No shadows
Крестик для закрытия окна. In-Game asset. 2d. High contrast. No shadows
Иконка английского языка флаг без текста In-Game asset. 2d. High contrast. No shadows
Заменить на российский без текста, рамку сохранить
Удалить желтый фон
Флаг земенить на немецкий рамки сохранить
Заменить на испанский, сохранить рамку.
сделать точно такуюже рамку но надпись заменить на shop. звезду заменить на ракету, а стрелку на щит
все оставить как есть но удалить черноту за рамками
круглая иконка подсказки I. In-Game asset. 2d. High contrast. No shadows
убери все звезды оставь только чистое небо
иконка восстановление здоровья много зеленых крестов в рамке, сочные цвета красивый фон. In-Game asset. 2d. High contrast. No shadows
синий щит на ярко оранжевом фоне
залп ракетного огня
шаровая молния. In-Game asset. 2d. High contrast. No shadows
башня тесла с молниями фон голубой
Огненный шар
перекрасить больше желтого и оранжевого
перекрасить больше голубого, светло-голубого,
турецкий флаг
Вместо огненного кольца, огненные шары разлетающие вверх в разные стороны
Текст убрать. Вместо молний снежинки
Вместо молнии снежинка, и покрасить в синий
Льдинка как стеклышко. In-Game asset. 2d. High contrast. No shadows
убрать дырку
бесформенная амеба
удали крывлья оставь только жука
оставь только крылья, удали жука
перекрась
Shoot
Sound effect
Boom
Sound effect
Pokupka
Sound effect
menu
Sound effect
molnia
Sound effect
krit
Sound effect
icetresk
Sound effect
peretik
Sound effect
music1
Music
music2
Music
music3
Music
musicFight
Music
udarshield
Sound effect
startraket
Sound effect
raketaudar
Sound effect
Ognemet
Sound effect
Tresklda
Sound effect
stop
Sound effect
goldsound
Sound effect
alien_bum
Sound effect