User prompt
замедление всех при открытом меню настроек должно быть таким же как и при меню апгрейда
User prompt
Верни управление с джойстика но добавь кнопку под меню апгрейда с всплывающим окном где можно будет переключаться с управления на джойсти и на мышку
User prompt
все равно не соответствует. Надеюсь скорость прицела не привязана к частоте экрана
User prompt
скорость не соответствует
User prompt
скорость прицела при управлении мышкой должна быть такойже как и скорость при управлении джойстиком
User prompt
слева вверху добавь кнопку для всплывающего меню настроек где можно выбрать как ьудет управлятся прице :джойстиком или ехать за мышкой. Нужно будет соответственно настроить для мышки управление сохранив скорость прицела ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
а ты можешь сделать так чтобы с телефона оставался джойстик а на ПК его убрать и управление сделать мышкой так чтобы прицел с тойже скоростью ехал за мышкой
User prompt
Я понял в чем проблема. Когда открывается меню то скорость всех обьектов сохраняется как фактическая, и все щамедляется, а потом когда меню закрывается то всем обьектам возвращается их скорость которая была записана в том числе и скорость под заморозкой. Такой метод паузы во время меню апгрейда нам не подходит, как можно этого исбежать?
User prompt
Заморозка работает не корректно: замедление продолжается бесконечно если открыть и закрыть меню апгрейда
User prompt
Проблема сохранилась
User prompt
Если открыть и закрыть меню во время того как враг заморожет, то после того как заморозка проходит он не падат а висит на месте
User prompt
Есть ошибка с заморозкой. Когда обьект замораживается на 100% если открыть мень апгрейда, то после того как ее закрыть обьект продолжает висеть на одном месте бесконечное время. Так быть не должно.
User prompt
Есть ошибка с заморозкой. Когда обьект замораживается нв 100% если открыть мень апгрейда, то после того как ее закрыть обьект продолжает висеть на одном месте
User prompt
Глыба меньше по размеру некоторых метеоритов, а нужно что бы была больше
User prompt
Пусть урона будет в два раза меньше но зато не каждую секунду а каждые 0.5 секунд
User prompt
Исправь и сделай как надо
User prompt
Давай на первом уровне 55% что бы на 10 было 100%. На цепные молнии урон должен быть такойже
User prompt
Почему-то последующие цели получили урон от молнии меньше чем первые, хотя должны были столько же
User prompt
Пусть урон на первом уровне буде 50%, а за каждый уровень по мимо шанса будет прибавка к урону 5% и того к 10 там будет 100%
User prompt
Чтобы заморозить теперь нужно 5 стаков
User prompt
пусть молния наносит 75% урона
User prompt
убери отображение фпс
User prompt
каждый последующий апгрейд дороже на 10
User prompt
глыба появляется со звуком icetresk
User prompt
снежинки не прозрачные и они все время тают и исчезают и снова пояыляются пока на обьекте холод ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var City = Container.expand(function () { var self = Container.call(this); self.takeDamage = function (damageAmount) { if (self.health <= 0) return; // Already destroyed if (self.shielded) return; // Shield active, ignore damage self.health -= damageAmount; LK.effects.flashObject(self.graphic, 0xFF0000, 300); // Flash city red // Flash both city images red when damage is taken if (leftCityImage) { tween(leftCityImage, { tint: 0xFF0000 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(leftCityImage, { tint: 0xFFFFFF }, { duration: 150, easing: tween.easeIn }); } }); } if (rightCityImage) { tween(rightCityImage, { tint: 0xFF0000 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(rightCityImage, { tint: 0xFFFFFF }, { duration: 150, easing: tween.easeIn }); } }); } // Flash main city graphic red when damage is taken tween(self.graphic, { tint: 0xFF0000 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(self.graphic, { tint: 0xFFFFFF }, { duration: 150, easing: tween.easeIn }); } }); if (self.healthDisplay) { self.healthDisplay.setText('City Health: ' + Math.max(0, self.health) + ' / ' + CITY_MAX_HEALTH); } if (self.health <= 0) { self.health = 0; // Cap at 0 LK.showGameOver(); // Trigger game over } }; self.setHealthDisplay = function (textObject) { self.healthDisplay = textObject; self.healthDisplay.setText('City Health: ' + self.health + ' / ' + CITY_MAX_HEALTH); }; // Initialization self.graphic = self.attachAsset('city', { anchorX: 0.5, anchorY: 1.0 }); // Anchor bottom-center self.health = CITY_MAX_HEALTH; self.healthDisplay = null; // To be linked to a Text2 object return self; }); var Crosshair = Container.expand(function () { var self = Container.call(this); self.isOver = function (target) { if (!target || !target.graphic || !self.graphic || !target.graphic.width || !target.graphic.height) return false; // Basic bounding box collision var selfHalfWidth = self.graphic.width / 2; var selfHalfHeight = self.graphic.height / 2; var targetHalfWidth = target.graphic.width * (target.graphic.scaleX || 1) / 2; var targetHalfHeight = target.graphic.height * (target.graphic.scaleY || 1) / 2; var selfLeft = self.x - selfHalfWidth; var selfRight = self.x + selfHalfWidth; var selfTop = self.y - selfHalfHeight; var selfBottom = self.y + selfHalfHeight; var targetLeft = target.x - targetHalfWidth; var targetRight = target.x + targetHalfWidth; var targetTop = target.y - targetHalfHeight; var targetBottom = target.y + targetHalfHeight; return !(selfRight < targetLeft || selfLeft > targetRight || selfBottom < targetTop || selfTop > targetBottom); }; // Initialization code, graphic will be attached in Game Code self.graphic = null; self.speed = 0; // Will be set from game constant return self; }); // Base class for falling objects var FallingObject = Container.expand(function (assetId, initialHealth, fallSpeed, objectType) { var self = Container.call(this); self.takeDamage = function (damageAmount) { if (self.isDestroyed) return; // Check for critical hit var critChance = upgrades.critChance * 5; // 5% per level var isCritical = Math.random() * 100 < critChance; var finalDamage = damageAmount; if (isCritical) { finalDamage = damageAmount * 2; // Double damage LK.getSound('krit').play(); // Play critical hit sound } self.health -= finalDamage; LK.effects.flashObject(self.graphic, 0xFF0000, 100); // Red flash on hit // Limit damage numbers to prevent accumulation if (!game.damageNumbers) game.damageNumbers = []; if (game.damageNumbers.length >= 20) { var oldDamageNumber = game.damageNumbers.shift(); if (oldDamageNumber.parent) { game.removeChild(oldDamageNumber); } } // Create floating damage number var damageNumber = new Text2(finalDamage.toString(), { size: isCritical ? 80 : 60, fill: isCritical ? 0xFF0000 : 0xFFFFFF, align: 'center', font: 'Impact' }); damageNumber.anchor.set(0.5, 0.5); damageNumber.x = self.x + (Math.random() - 0.5) * 40; // Slight random offset damageNumber.y = self.y - 20; game.addChild(damageNumber); game.damageNumbers.push(damageNumber); // Animate damage number floating up without fading out tween(damageNumber, { y: damageNumber.y - 120, scaleX: 1.2, scaleY: 1.2 }, { duration: 1200, easing: tween.easeOut }); // Remove damage number after timeout LK.setTimeout(function () { if (damageNumber.parent) { game.removeChild(damageNumber); var index = game.damageNumbers.indexOf(damageNumber); if (index > -1) game.damageNumbers.splice(index, 1); } }, 1200); // Add pulsing scale animation when hit tween.stop(self.graphic, { scaleX: true, scaleY: true }); // Stop any existing scale tweens tween(self.graphic, { scaleX: 1.3, scaleY: 1.3 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(self.graphic, { scaleX: 1, scaleY: 1 }, { duration: 150, easing: tween.easeIn }); } }); if (self.health <= 0) { self.destroySelf(); } }; self.destroySelf = function (giveMoney) { if (giveMoney === undefined) giveMoney = true; // Default to giving money if (self.isDestroyed) return; self.isDestroyed = true; // Comprehensive tween cleanup - stop all tweens on this object and ALL its properties tween.stop(self); if (self.graphic) { tween.stop(self.graphic); // Stop specific property tweens that might be running tween.stop(self.graphic, { tint: true, scaleX: true, scaleY: true, alpha: true, rotation: true }); } // Clean up health display completely if (self.healthDisplay) { tween.stop(self.healthDisplay); tween.stop(self.healthDisplay, { x: true, y: true, scaleX: true, scaleY: true, alpha: true }); if (self.healthDisplay.parent) { self.healthDisplay.parent.removeChild(self.healthDisplay); } self.healthDisplay = null; // Remove reference } // Enhanced burn flames cleanup if (self.burnFlames) { for (var f = 0; f < self.burnFlames.length; f++) { var flame = self.burnFlames[f]; if (flame) { // Stop all tweens on flame tween.stop(flame); tween.stop(flame, { alpha: true, scaleX: true, scaleY: true, x: true, y: true }); if (flame.parent) { flame.parent.removeChild(flame); } // Clear flame references flame.offsetX = undefined; flame.offsetY = undefined; } } self.burnFlames = []; } // Clear burn-related properties to prevent memory leaks self.burnStacks = 0; self.burnStartTime = 0; self.lastBurnDamageTime = 0; self.burnPausedTime = null; // Clear freeze-related properties and cleanup self.freezeStacks = 0; self.freezeStartTime = 0; self.freezePausedTime = null; self.isFullyFrozen = false; self.freezeBlockEndTime = 0; if (self.iceBlock) { tween.stop(self.iceBlock); if (self.iceBlock.parent) { self.iceBlock.parent.removeChild(self.iceBlock); } self.iceBlock = null; } // Clean up object's snowflake particles if (self.snowflakeParticles) { for (var sf = 0; sf < self.snowflakeParticles.length; sf++) { var snowflake = self.snowflakeParticles[sf]; if (snowflake) { tween.stop(snowflake); if (snowflake.parent) { snowflake.parent.removeChild(snowflake); } } } self.snowflakeParticles = []; } // Limit fragment creation to prevent excessive memory usage var maxFragments = Math.min(3 + Math.floor(Math.random() * 4), 6); // Cap at 6 fragments var fragmentCount = game.particles ? Math.min(maxFragments, Math.max(0, 50 - game.particles.length)) : maxFragments; for (var p = 0; p < fragmentCount; p++) { var randomScale = 0.4 + Math.random() * 0.8; // Random scale between 0.4 and 1.2 var kusokFragment = LK.getAsset('kusok', { anchorX: 0.5, anchorY: 0.5, scaleX: randomScale, scaleY: randomScale }); kusokFragment.x = self.x; kusokFragment.y = self.y; game.addChild(kusokFragment); // Track fragment in particles array for cleanup if (!game.particles) game.particles = []; game.particles.push(kusokFragment); // Random direction and speed for each fragment var angle = Math.random() * Math.PI * 2; var speed = 80 + Math.random() * 120; // Random speed between 80-200 var velocityX = Math.cos(angle) * speed; var velocityY = Math.sin(angle) * speed; // Random rotation speed and direction for each fragment (slower rotation) var rotationSpeed = 0.01 + Math.random() * 0.03; // Random rotation speed between 0.01 and 0.04 radians per frame var rotationDirection = Math.random() < 0.5 ? 1 : -1; // Random direction (clockwise or counterclockwise) var totalRotation = rotationSpeed * rotationDirection * 120; // Total rotation over 2 seconds (120 frames at 60fps) // Animate fragment movement, rotation, and scale down over 2 seconds tween(kusokFragment, { x: kusokFragment.x + velocityX, y: kusokFragment.y + velocityY, scaleX: 0, scaleY: 0, rotation: kusokFragment.rotation + totalRotation }, { duration: 2000, easing: tween.easeOut, onFinish: function (fragmentRef) { return function onFinish() { if (fragmentRef.parent) { fragmentRef.parent.removeChild(fragmentRef); } // Remove from particles tracking array if (game.particles) { var fragIndex = game.particles.indexOf(fragmentRef); if (fragIndex > -1) game.particles.splice(fragIndex, 1); } }; }(kusokFragment) }); } // Add a small particle explosion or shrink effect with proper cleanup if (tween && self.graphic) { // Stop ALL ongoing tweens on this graphic specifically tween.stop(self.graphic); tween.stop(self.graphic, { scaleX: true, scaleY: true, alpha: true, tint: true, rotation: true }); tween(self.graphic, { scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 200, onFinish: function (selfRef) { return function onFinish() { // Final cleanup - remove any remaining references if (selfRef.graphic) { tween.stop(selfRef.graphic); selfRef.graphic = null; } // Actual removal from game will be handled in game.update }; }(self) }); } // Play boom sound when object is destroyed LK.getSound('Boom').play(); // Only give money if specified if (giveMoney) { // Points for destroying object var moneyEarned = self.objectType === 'meteor' ? 10 : 15; LK.setScore(LK.getScore() + moneyEarned); updateScoreDisplay(); // Create floating money number display var moneyNumber = new Text2('$' + moneyEarned.toString(), { size: 110, fill: 0x00FF00, align: 'center', font: 'Impact' }); moneyNumber.anchor.set(0.5, 0.5); moneyNumber.x = self.x + (Math.random() - 0.5) * 50; // Slight random offset moneyNumber.y = self.y + 40; game.addChild(moneyNumber); // Animate money number floating down without fading out tween(moneyNumber, { y: moneyNumber.y + 150, scaleX: 1.4, scaleY: 1.4 }, { duration: 1500, easing: tween.easeOut }); // Remove money number after timeout LK.setTimeout(function () { if (moneyNumber.parent) { game.removeChild(moneyNumber); } }, 1500); } }; // Define update method on prototype so it can be called by subclasses self.updateParent = function () { if (self.isDestroyed) return; // Pause all movement and effects when upgrades popup is open if (upgradesPopup) return; self.y += self.speedY; // Update health display position if (self.healthDisplay) { self.healthDisplay.x = self.x; // Position at center of the object self.healthDisplay.y = self.y + self.graphic.height * (self.graphic.scaleY || 1) / 2 + 30; // Position below the object self.healthDisplay.setText(self.health.toString()); } // Process freeze effect var currentTime = LK.ticks * (1000 / 60); // Handle freeze mechanics if (self.isFullyFrozen) { // If upgrades popup is open, pause the freeze timer by extending the block end time if (upgradesPopup && !self.freezePausedTime) { self.freezePausedTime = currentTime; } else if (!upgradesPopup && self.freezePausedTime) { // Resume freeze timer by adjusting block end time var pauseDuration = currentTime - self.freezePausedTime; self.freezeBlockEndTime += pauseDuration; self.freezePausedTime = null; } // Update ice block position if (self.iceBlock) { self.iceBlock.x = self.x; self.iceBlock.y = self.y; } // Check if freeze block duration expired (only if upgrades popup is not open) if (!upgradesPopup && currentTime >= self.freezeBlockEndTime) { self.isFullyFrozen = false; self.speedY = self.originalSpeedY; // Restore normal speed // Remove ice block if (self.iceBlock && self.iceBlock.parent) { game.removeChild(self.iceBlock); self.iceBlock = null; } // Restore normal tint tween(self.graphic, { tint: 0xFFFFFF }, { duration: 300, easing: tween.easeOut }); } } else if (self.freezeStacks > 0) { // If upgrades popup is open, pause the freeze timer by extending the start time if (upgradesPopup && !self.freezePausedTime) { self.freezePausedTime = currentTime; } else if (!upgradesPopup && self.freezePausedTime) { // Resume freeze timer by adjusting start time var pauseDuration = currentTime - self.freezePausedTime; self.freezeStartTime += pauseDuration; self.freezePausedTime = null; } // Handle partial freeze (50% slowdown and blue tint) - only if upgrades popup is not open if (!upgradesPopup && currentTime - self.freezeStartTime >= 4000) { // Freeze stacks expired self.freezeStacks = 0; self.speedY = self.originalSpeedY; // Restore normal speed // Restore normal tint when freeze expires tween(self.graphic, { tint: 0xFFFFFF }, { duration: 300, easing: tween.easeOut }); } } // Create and manage snowflake particles for frozen targets (both partially frozen and fully frozen) if (self.freezeStacks > 0 || self.isFullyFrozen) { // Initialize snowflake particles array on the object if not exists if (!self.snowflakeParticles) { self.snowflakeParticles = []; } // Create new snowflakes periodically if (LK.ticks % 20 === 0 && self.snowflakeParticles.length < 8) { var snowflake = LK.getAsset('Sneg', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3 + Math.random() * 0.4, // Random scale between 0.3 and 0.7 scaleY: 0.3 + Math.random() * 0.4 }); // Position snowflake around the frozen target and store relative offset var targetWidth = self.graphic.width * (self.graphic.scaleX || 1); var targetHeight = self.graphic.height * (self.graphic.scaleY || 1); // Store relative offset from object center snowflake.offsetX = (Math.random() - 0.5) * targetWidth * 1.2; snowflake.offsetY = (Math.random() - 0.5) * targetHeight * 1.2; // Set initial position snowflake.x = self.x + snowflake.offsetX; snowflake.y = self.y + snowflake.offsetY; snowflake.alpha = 1.0; // Non-transparent // Add gentle floating motion within small radius snowflake.floatAngle = Math.random() * Math.PI * 2; snowflake.floatSpeed = 2 + Math.random() * 3; // Very slow float speed game.addChild(snowflake); self.snowflakeParticles.push(snowflake); // Add melting/fading cycle animation var meltDelay = Math.random() * 1000; LK.setTimeout(function (flakeRef) { return function () { var _meltCycle = function _meltCycle() { if (flakeRef.parent) { // Start melting/fading tween(flakeRef, { alpha: 0, scaleX: flakeRef.scaleX * 0.3, scaleY: flakeRef.scaleY * 0.3 }, { duration: 800 + Math.random() * 600, easing: tween.easeOut, onFinish: function onFinish() { // Immediately reappear at full opacity flakeRef.alpha = 1.0; // Non-transparent flakeRef.scaleX = 0.3 + Math.random() * 0.4; flakeRef.scaleY = flakeRef.scaleX; // Wait before next melt cycle LK.setTimeout(_meltCycle, 200 + Math.random() * 400); } }); } }; _meltCycle(); }; }(snowflake), meltDelay); } // Update positions of existing snowflakes to follow the object with gentle floating for (var sf = 0; sf < self.snowflakeParticles.length; sf++) { var snowflake = self.snowflakeParticles[sf]; if (snowflake.parent && snowflake.offsetX !== undefined && snowflake.offsetY !== undefined) { // Update floating motion snowflake.floatAngle += 0.02 + Math.random() * 0.01; // Slow rotation of float pattern var floatX = Math.cos(snowflake.floatAngle) * snowflake.floatSpeed; var floatY = Math.sin(snowflake.floatAngle) * snowflake.floatSpeed; // Update position to follow object with gentle floating motion snowflake.x = self.x + snowflake.offsetX + floatX; snowflake.y = self.y + snowflake.offsetY + floatY; } } } else { // Clean up snowflakes when freeze ends if (self.snowflakeParticles) { for (var sf = 0; sf < self.snowflakeParticles.length; sf++) { var snowflake = self.snowflakeParticles[sf]; if (snowflake.parent) { // Fade out and remove tween(snowflake, { alpha: 0, scaleX: 0, scaleY: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function (flakeRef) { return function () { if (flakeRef.parent) { flakeRef.parent.removeChild(flakeRef); } }; }(snowflake) }); } } self.snowflakeParticles = []; } } // Process burning effect if (self.burnStacks > 0) { // If upgrades popup is open, pause the burn timer by extending the start time if (upgradesPopup && !self.burnPausedTime) { self.burnPausedTime = currentTime; } else if (!upgradesPopup && self.burnPausedTime) { // Resume burn timer by adjusting start time var pauseDuration = currentTime - self.burnPausedTime; self.burnStartTime += pauseDuration; self.burnPausedTime = null; } // Update burn flame visuals based on stack count var requiredFlames = 0; if (self.burnStacks >= 1 && self.burnStacks <= 3) { requiredFlames = 1; } else if (self.burnStacks >= 4 && self.burnStacks <= 7) { requiredFlames = 2; } else if (self.burnStacks >= 8 && self.burnStacks <= 10) { requiredFlames = 3; } // Initialize burn flames array if not exists if (!self.burnFlames) { self.burnFlames = []; } // Add flames if we need more while (self.burnFlames.length < requiredFlames) { var flame = LK.getAsset('ogonek', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); // Position flame randomly on the target and store relative offset var targetWidth = self.graphic.width * (self.graphic.scaleX || 1); var targetHeight = self.graphic.height * (self.graphic.scaleY || 1); flame.offsetX = (Math.random() - 0.5) * targetWidth * 0.8; flame.offsetY = (Math.random() - 0.5) * targetHeight * 0.8; flame.x = self.x + flame.offsetX; flame.y = self.y + flame.offsetY; game.addChild(flame); self.burnFlames.push(flame); // Add initial scale-up effect to show fire ignition tween(flame, { scaleX: 0.15, scaleY: 0.15 }, { duration: 2000, easing: tween.easeOut }); // Add flickering animation to flame var flickerDelay = Math.random() * 500; LK.setTimeout(function (flameRef) { return function () { var _flickerFlame = function flickerFlame() { if (flameRef.parent) { tween(flameRef, { alpha: 0.6 + Math.random() * 0.4, scaleX: 0.12 + Math.random() * 0.06, scaleY: 0.12 + Math.random() * 0.06 }, { duration: 200 + Math.random() * 300, easing: tween.easeInOut, onFinish: function onFinish() { // Continue flickering LK.setTimeout(_flickerFlame, 100 + Math.random() * 200); } }); } }; _flickerFlame(); }; }(flame), flickerDelay); } // Update positions of existing flames to follow the object for (var f = 0; f < self.burnFlames.length; f++) { var flame = self.burnFlames[f]; if (flame.parent && flame.offsetX !== undefined && flame.offsetY !== undefined) { flame.x = self.x + flame.offsetX; flame.y = self.y + flame.offsetY; } } // Remove excess flames if we have too many while (self.burnFlames.length > requiredFlames) { var excessFlame = self.burnFlames.pop(); if (excessFlame.parent) { game.removeChild(excessFlame); } } // Check if burn duration has expired (10 seconds = 10000ms) - only if upgrades popup is not open if (!upgradesPopup && currentTime - self.burnStartTime >= 10000) { self.burnStacks = 0; // Remove all burn flames when burn expires for (var f = 0; f < self.burnFlames.length; f++) { if (self.burnFlames[f].parent) { game.removeChild(self.burnFlames[f]); } } self.burnFlames = []; } // Apply burn damage every 0.5 seconds (only if there are active burn stacks and upgrades popup is not open) if (currentTime - self.lastBurnDamageTime >= 500 && self.burnStacks > 0 && !upgradesPopup) { var burnDamage = Math.floor(FIRE_DAMAGE * 0.1 * self.burnStacks); // 10% of actual fire damage per stack (half damage, twice frequency) self.health -= burnDamage; self.lastBurnDamageTime = currentTime; // Create floating burn damage number var burnDamageNumber = new Text2(burnDamage.toString(), { size: 50, fill: 0xFF8C00, // Orange color for burn damage align: 'center', font: 'Impact' }); burnDamageNumber.anchor.set(0.5, 0.5); burnDamageNumber.x = self.x - 60 + (Math.random() - 0.5) * 20; // Position to the left of regular damage burnDamageNumber.y = self.y - 20; game.addChild(burnDamageNumber); // Animate burn damage number tween(burnDamageNumber, { y: burnDamageNumber.y - 80, scaleX: 1.1, scaleY: 1.1 }, { duration: 1000, easing: tween.easeOut }); // Remove burn damage number after timeout LK.setTimeout(function () { if (burnDamageNumber.parent) { game.removeChild(burnDamageNumber); } }, 1000); // Check if object is destroyed by burn damage if (self.health <= 0) { self.destroySelf(); return; } } } }; // Set the update method to call updateParent by default self.update = self.updateParent; // Initialization self.graphic = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.health = initialHealth; self.maxHealth = initialHealth; // Store max health for potential future use (e.g. health bars on objects) self.speedY = fallSpeed; self.isDestroyed = false; self.objectType = objectType; // 'meteor' or 'alien' self.burnStacks = 0; // Number of burn stacks (max 5) self.burnStartTime = 0; // Time when burn effect started self.lastBurnDamageTime = 0; // Track when burn damage was last applied self.freezeStacks = 0; // Number of freeze stacks (max 5) self.freezeStartTime = 0; // Time when freeze effect started self.isFullyFrozen = false; // When 10 stacks reached self.freezeBlockEndTime = 0; // When ice block expires self.freezePausedTime = null; // Track freeze pause time for upgrades menu self.originalSpeedY = self.speedY; // Store original speed for freeze slowdown self.iceBlock = null; // Reference to ice block visual // Create health display self.healthDisplay = new Text2(self.health.toString(), { size: 40, fill: 0xFFFFFF, align: 'center', font: 'Impact' }); self.healthDisplay.anchor.set(0.5, 0.5); self.healthDisplay.x = self.x; // Position at center of the object self.healthDisplay.y = self.y + self.graphic.height * (self.graphic.scaleY || 1) / 2 + 30; // Position below the object game.addChild(self.healthDisplay); self.applyBurn = function () { // Add burn stack (max 10) if (self.burnStacks < 10) { self.burnStacks++; } // Reset burn start time to current time for 10 second duration var currentTime = LK.ticks * (1000 / 60); self.burnStartTime = currentTime; self.burnPausedTime = null; // Reset pause time when burn is applied // Visual effect for applying burn tween(self.graphic, { tint: 0xFF4500 // Orange-red tint for burn }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self.graphic, { tint: 0xFFFFFF // Back to white }, { duration: 200, easing: tween.easeIn }); } }); }; self.applyFreeze = function () { // Cannot apply freeze if fully frozen (ice block active) if (self.isFullyFrozen) return; // Add freeze stack (max 5) if (self.freezeStacks < 5) { self.freezeStacks++; } // Reset freeze start time to current time for 4 second duration var currentTime = LK.ticks * (1000 / 60); self.freezeStartTime = currentTime; // Apply 50% speed reduction self.speedY = self.originalSpeedY * 0.5; // Visual effect for applying freeze - blue tint that stays while frozen tween(self.graphic, { tint: 0x87CEEB // Light blue tint for freeze }, { duration: 200, easing: tween.easeOut }); // Check if 5 stacks reached - full freeze if (self.freezeStacks >= 5) { // Play ice block creation sound LK.getSound('icetresk').play(); self.isFullyFrozen = true; self.freezeStacks = 0; // Clear stacks self.speedY = 0; // Stop completely self.freezeBlockEndTime = currentTime + 4000; // 4 seconds // Create ice block visual self.iceBlock = LK.getAsset('Ice', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); self.iceBlock.x = self.x; self.iceBlock.y = self.y; self.iceBlock.alpha = 0.6; game.addChild(self.iceBlock); // Keep object blue tinted while frozen tween(self.graphic, { tint: 0x4169E1 // Dark blue for full freeze }, { duration: 300, easing: tween.easeOut }); } }; return self; }); var MeteorFast = FallingObject.expand(function () { var self = FallingObject.call(this, 'meteorfast1', 120, METEOR_SPEED * 1.35, 'meteor'); // 60% health, 35% faster speed (reduced from 80% faster) // Add rotation animation to meteor self.rotationSpeed = 0.008 + Math.random() * 0.015; // Faster rotation than regular meteor self.rotationDirection = Math.random() < 0.5 ? 1 : -1; // Random direction (clockwise or counterclockwise) self.update = function () { if (self.isDestroyed) return; // Call parent update method to handle health display and burn effects self.updateParent(); // Add rotation self.graphic.rotation += self.rotationSpeed * self.rotationDirection; }; return self; }); var Meteor = FallingObject.expand(function () { var self = FallingObject.call(this, 'meteor', METEOR_HEALTH, METEOR_SPEED / 2, 'meteor'); // Add rotation animation to meteor self.rotationSpeed = 0.005 + Math.random() * 0.01; // Random rotation speed between 0.005 and 0.015 radians per frame self.rotationDirection = Math.random() < 0.5 ? 1 : -1; // Random direction (clockwise or counterclockwise) self.update = function () { if (self.isDestroyed) return; // Call parent update method to handle health display and burn effects self.updateParent(); // Add rotation self.graphic.rotation += self.rotationSpeed * self.rotationDirection; }; return self; }); var Alien = FallingObject.expand(function () { var self = FallingObject.call(this, 'alien', ALIEN_HEALTH, ALIEN_SPEED / 2, 'alien'); // Alien-specific properties or methods (e.g., different movement pattern) return self; }); var VirtualJoystick = Container.expand(function () { var self = Container.call(this); self.initialize = function (baseAssetId, knobAssetId, visualRadius, interactionRadius) { // baseGraphic and knobGraphic are children of 'self' (the joystick container) // Their positions (0,0) will be the center of 'self'. self.baseGraphic = self.addChild(LK.getAsset(baseAssetId, { anchorX: 0.5, anchorY: 0.5 })); self.knobGraphic = self.addChild(LK.getAsset(knobAssetId, { anchorX: 0.5, anchorY: 0.5 })); self.visualRadius = visualRadius; // Max distance knob can move visually self.interactionRadius = interactionRadius; // Touchable area around joystick center self.active = false; self.touchId = null; self.deltaX = 0; // Normalized movement vector x (-1 to 1) self.deltaY = 0; // Normalized movement vector y (-1 to 1) self.initialTouchX = 0; // Track initial touch position self.initialTouchY = 0; // Track initial touch position }; self.processDown = function (gameGlobalX, gameGlobalY, eventObj, treatTouchAsLocalOrigin) { var localClickPos; if (treatTouchAsLocalOrigin) { // If the joystick was just repositioned to gameGlobalX, gameGlobalY (the touch location), // then this touch point is effectively at the joystick's new local origin (0,0). localClickPos = { x: 0, y: 0 }; } else { // Standard case: joystick is already positioned. Convert global touch to local. localClickPos = self.toLocal({ x: gameGlobalX, y: gameGlobalY }); } var distSq = localClickPos.x * localClickPos.x + localClickPos.y * localClickPos.y; if (distSq <= self.interactionRadius * self.interactionRadius) { self.active = true; self.touchId = eventObj.identifier; // Use identifier from the event object // Always start knob at center when joystick is activated self.knobGraphic.x = 0; self.knobGraphic.y = 0; self.deltaX = 0; self.deltaY = 0; // Store the initial touch position to avoid coordinate transformation issues self.initialTouchX = gameGlobalX; self.initialTouchY = gameGlobalY; return true; // Consumed event } return false; }; self.processMove = function (gameGlobalX, gameGlobalY, eventObj) { if (!self.active || eventObj.identifier !== undefined && eventObj.identifier !== self.touchId) { return; } // Calculate movement relative to initial touch position var deltaX = gameGlobalX - self.initialTouchX; var deltaY = gameGlobalY - self.initialTouchY; self.updateKnobPosition(deltaX, deltaY); }; self.processUp = function (eventObj) { if (!self.active || eventObj.identifier !== undefined && eventObj.identifier !== self.touchId) { return; } self.active = false; self.touchId = null; self.knobGraphic.x = 0; // Reset to center of joystick container self.knobGraphic.y = 0; self.deltaX = 0; self.deltaY = 0; }; self.updateKnobPosition = function (localX, localY) { var dx = localX; var dy = localY; var distance = Math.sqrt(dx * dx + dy * dy); var clampedX = dx; var clampedY = dy; if (distance > self.visualRadius) { clampedX = dx / distance * self.visualRadius; clampedY = dy / distance * self.visualRadius; } self.knobGraphic.x = clampedX; self.knobGraphic.y = clampedY; if (self.visualRadius === 0) { // Avoid division by zero if radius is 0 self.deltaX = 0; self.deltaY = 0; } else { self.deltaX = clampedX / self.visualRadius; self.deltaY = clampedY / self.visualRadius; } self.deltaX = Math.max(-1, Math.min(1, self.deltaX)); self.deltaY = Math.max(-1, Math.min(1, self.deltaY)); }; 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 stars = []; var STAR_COUNT = 50; // Device detection variables var isMobileDevice = false; var isMouseControlling = false; var mouseX = WORLD_WIDTH / 2; var mouseY = WORLD_HEIGHT / 2; // --- 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++) { // Randomly choose star size var starTypes = ['star_small', 'star_medium', 'star_large']; var starWeights = [0.6, 0.3, 0.1]; // 60% small, 30% medium, 10% large var randomValue = Math.random(); var starType = 'star_small'; if (randomValue > 0.6 && randomValue <= 0.9) { starType = 'star_medium'; } else if (randomValue > 0.9) { starType = 'star_large'; } var star = LK.getAsset(starType, { anchorX: 0.5, anchorY: 0.5 }); star.x = Math.random() * WORLD_WIDTH; star.y = Math.random() * WORLD_HEIGHT; 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); game.addChild(star); } } 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 change color (either due to regular twinkle time or 15-second limit) var shouldTwinkle = currentTime >= star.nextTwinkleTime; var timeSinceLastChange = currentTime - (star.lastColorChangeTime || 0); var forcedChange = timeSinceLastChange >= 15000; // 15 seconds if ((shouldTwinkle || forcedChange) && !star.isTwinkling) { // Start twinkling 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.1 + Math.random() * 0.9; // Random target brightness // Animate both color and alpha tween(star, { alpha: targetAlpha, tint: targetColor }, { duration: 500 + Math.random() * 1500, easing: tween.easeInOut, onFinish: function (starRef, colorRef) { return function onFinish() { // Set next twinkle time (3-8 seconds from now, but no more than 15 seconds total) var now = LK.ticks * (1000 / 60); starRef.lastColorChangeTime = now; var nextInterval = Math.min(3000 + Math.random() * 5000, 15000); starRef.nextTwinkleTime = now + Math.max(nextInterval, 1000); // Ensure at least 1 second starRef.isTwinkling = false; // Set the color to the final value to avoid rounding issues starRef.tint = colorRef; }; }(star, targetColor) }); } } } // --- 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 Health Display cityHealthText = new Text2('', { size: 60, fill: 0xFFFFFF, align: 'center', font: 'Impact' }); cityHealthText.anchor.set(0.5, 1); // Anchor bottom-center for text LK.gui.bottom.addChild(cityHealthText); // Add to bottom center of GUI cityInstance.setHealthDisplay(cityHealthText); // Link to city instance // 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); // --- 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; // Upgrade definitions var UPGRADE_DEFS = [{ key: 'fireDamage', name: 'Fire Damage +', desc: 'Increase shot damage', cost: function cost(level) { return 10 + level * 10; }, max: 10 }, { key: 'fireRate', name: 'Fire Rate +', desc: 'Reduce delay between shots', cost: function cost(level) { return 10 + level * 10; }, max: 10 }, { key: 'critChance', name: 'Critical Chance +', desc: 'Increase double damage chance', cost: function cost(level) { return 10 + level * 10; }, max: 10 }, { key: 'lightningChance', name: 'Lightning Chance +', desc: 'Increase lightning strike chance', cost: function cost(level) { return 10 + level * 10; }, max: 10 }, { key: 'fire', name: 'Fire Chance +', desc: 'Burn damage over time', cost: function cost(level) { return 10 + level * 10; }, max: 10 }, { key: 'freeze', name: 'Freeze Chance +', desc: 'Slow and freeze enemies', cost: function cost(level) { return 10 + level * 10; }, max: 10 }]; // Global variable for upgrade icon size var iconSize = 360; // 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) showUpgradesPopup(); }); } // 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 // Create controls button below upgrades button controlsButton = LK.getAsset('test', { anchorX: 1, anchorY: 0, scaleX: controlsButtonWidth / 360, scaleY: controlsButtonHeight / 360 }); controlsButton.x = -upgradesButtonPadding; controlsButton.y = upgradesButtonY + upgradesButtonHeight + 20; // Position below upgrades button controlsButton.interactive = true; controlsButton.buttonMode = true; controlsButton.visible = true; controlsButtonActive = false; LK.gui.topRight.addChild(controlsButton); // Add hit area for controls button var controlsHitArea = LK.getAsset('fon', { anchorX: 1, anchorY: 0, scaleX: 220 / 2048, scaleY: 100 / 2732 }); controlsHitArea.x = controlsButton.x; controlsHitArea.y = controlsButton.y; controlsHitArea.alpha = 0.01; controlsHitArea.interactive = true; controlsHitArea.buttonMode = true; LK.gui.topRight.addChild(controlsHitArea); controlsHitArea.on('down', function (x, y, obj) { if (!controlsButtonActive) showControlsPopup(); }); // 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 = true; popupBg.buttonMode = true; popupBg.on('down', function (x, y, obj) { closeControlsPopup(); }); controlsPopup.addChild(popupBg); // Popup window var popupWidth = 1200; var popupHeight = 800; 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; controlsPopup.addChild(popupWin); // Title var titleText = new Text2('CONTROL SETTINGS', { size: 120, fill: 0xFFFFFF, align: 'center', font: 'Impact' }); titleText.anchor.set(0.5, 0.5); titleText.x = WORLD_WIDTH / 2; titleText.y = popupWin.y - popupHeight / 2 + 120; controlsPopup.addChild(titleText); // Joystick control button var joystickButtonWidth = 400; var joystickButtonHeight = 120; var joystickControlButton = LK.getAsset('upgrade', { anchorX: 0.5, anchorY: 0.5, scaleX: joystickButtonWidth / 800, scaleY: joystickButtonHeight / 400 }); joystickControlButton.x = WORLD_WIDTH / 2; joystickControlButton.y = WORLD_HEIGHT / 2 - 80; joystickControlButton.tint = useJoystickControl ? 0x00FF00 : 0x808080; joystickControlButton.interactive = true; joystickControlButton.buttonMode = true; controlsPopup.addChild(joystickControlButton); var joystickText = new Text2('JOYSTICK CONTROL', { size: 60, fill: 0xFFFFFF, align: 'center', font: 'Impact' }); joystickText.anchor.set(0.5, 0.5); joystickText.x = joystickControlButton.x; joystickText.y = joystickControlButton.y; controlsPopup.addChild(joystickText); // Mouse control button var mouseControlButton = LK.getAsset('upgrade', { anchorX: 0.5, anchorY: 0.5, scaleX: joystickButtonWidth / 800, scaleY: joystickButtonHeight / 400 }); mouseControlButton.x = WORLD_WIDTH / 2; mouseControlButton.y = WORLD_HEIGHT / 2 + 80; mouseControlButton.tint = !useJoystickControl ? 0x00FF00 : 0x808080; mouseControlButton.interactive = true; mouseControlButton.buttonMode = true; controlsPopup.addChild(mouseControlButton); var mouseText = new Text2('MOUSE CONTROL', { size: 60, fill: 0xFFFFFF, align: 'center', font: 'Impact' }); mouseText.anchor.set(0.5, 0.5); mouseText.x = mouseControlButton.x; mouseText.y = mouseControlButton.y; controlsPopup.addChild(mouseText); // Button interactions joystickControlButton.on('down', function (x, y, obj) { if (!useJoystickControl) { useJoystickControl = true; isMouseControlling = false; joystickControlButton.tint = 0x00FF00; mouseControlButton.tint = 0x808080; LK.getSound('Pokupka').play(); } }); mouseControlButton.on('down', function (x, y, obj) { if (useJoystickControl) { useJoystickControl = false; isMouseControlling = true; joystickControlButton.tint = 0x808080; mouseControlButton.tint = 0x00FF00; LK.getSound('Pokupka').play(); } }); // Add popup to game game.addChild(controlsPopup); controlsButtonActive = true; controlsButton.visible = false; } function closeControlsPopup() { if (controlsPopup && controlsPopup.parent) { controlsPopup.parent.removeChild(controlsPopup); LK.getSound('menu').play(); } controlsPopup = null; controlsButtonActive = false; controlsButton.visible = 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 = true; // Allow closing popup by tapping background popupBg.buttonMode = true; popupBg.on('down', function (x, y, obj) { closeUpgradesPopup(); }); 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); // 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); // Button interaction iconBg.interactive = true; iconBg.buttonMode = true; iconBg.upgradeKey = upg.key; iconBg.on('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; } function closeUpgradesPopup() { 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; } // Handle upgrade purchase function handleUpgradePurchase(upg, priceText, levelText) { 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) 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) { // 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; } }; game.move = function (x, y, eventObj) { if (upgradesPopup || controlsPopup) { // 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; } // No crosshair update here; handled in game.update }; game.up = function (x, y, eventObj) { if (upgradesPopup || controlsPopup) { // 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 --- // 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(); game.update = function () { // Block all game updates while upgrades or controls popup is open if (upgradesPopup || controlsPopup) { return; } var currentTime = LK.ticks * (1000 / 60); // Approximate current time in ms // 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 the same speed as joystick var moveSpeed = CROSSHAIR_SPEED * 2; 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)); } // 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(); // 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); } };
===================================================================
--- original.js
+++ change.js
Метеорит без огня пастельные цвета 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