User prompt
Сделай так чтоб анимация прокрутки была зацикленой но крутилось не бесконечно ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Можешь исправить все возможные с ними ошибки ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
Исправь ошибку компиляции L59: не удалось загрузить плагин 59-60 ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
Исправь ошибку с плагинами пожалуйста с war tween и хранилищем вар ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
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/facekit.v1
User prompt
Оптимизируй игру
User prompt
Исправь ошибку из за которой загрузка бесконечная ↪💡 Consider importing and using the following plugins: @upit/facekit.v1, @upit/tween.v1
User prompt
Сделай так чтоб после покупки перед лицом возникала картинка кейса ↪💡 Consider importing and using the following plugins: @upit/facekit.v1, @upit/tween.v1
User prompt
Убери надпись кейс и опусти цену в 100$ по ниже
User prompt
Добавь кейс в магазин за 100$ на картинке которой будет ресур кейс
User prompt
Увеличь хп короблям врага и урон игрока в три раза но не меняй урон дронов
User prompt
Сделай так чтоб дроны убивали обычные корабли с трех ударов ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Уменьши урон дроном в два раза
User prompt
Используй ресурс пол название drons для дронов
User prompt
Сделай так чтоб дрона было два и они летали за врагами обстреливая их на 0,25 урона ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Добавь в магазин дроны за 75$ ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
Исправь ошибку из за которой кнопка дэш зеленая не работает ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
Сделай так чтоб при покупки dash он становится красным в магазине и над кнопкой атак появляется зеленая уменьшаная кнопку дэш при нажатии на зеленую кнопку толкает корабль в перед перезарядка 3 сек ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
Удали кнопку улучшения
User prompt
Убери кнопку улучшения и сделай так чтоб кнопку дэш оставалась после покупки
User prompt
Добавь рядом с кнопкой настройки кнопку магазина
User prompt
Добавь возможность в настройках включить старый вид следа ↪💡 Consider importing and using the following plugins: @upit/storage.v1
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // New armored enemy ship: slower, more health, different sprite var ArmoredEnemyShip = Container.expand(function () { var self = Container.call(this); self.fireCooldown = 120; // Fires more often than normal enemy self.fireTimer = Math.random() * self.fireCooldown; self.enemyProjectileSpawnPoint = { x: 0, y: 0 }; self.hit = function () { if (self.isDestroyed) return; self.health -= 1; if (self.health <= 0) { self.isDestroyed = true; } else { // Flash magenta on hit if (self.shipSprite) LK.effects.flashObject(self.shipSprite, 0xe011a9, 80); } }; self.update = function () { if (self.isOffScreen || self.isDestroyed) return; if (typeof freezeEnemies !== 'undefined' && freezeEnemies) return; // Target the player ship if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) { var dx = playerShip.x - self.x; var dy = playerShip.y - self.y; var targetAngle = Math.atan2(dy, dx); self.angle = targetAngle; if (self.shipSprite) self.shipSprite.rotation = self.angle + Math.PI / 2; } self.lastX = self.x; self.lastY = self.y; // Armored enemy moves slower var moveX = Math.cos(self.angle) * self.speed; var moveY = Math.sin(self.angle) * self.speed; if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) { var dxToPlayer = self.x + moveX - playerShip.x; var dyToPlayer = self.y + moveY - playerShip.y; var distToPlayer = Math.sqrt(dxToPlayer * dxToPlayer + dyToPlayer * dyToPlayer); if (distToPlayer < 120) { moveX = 0; moveY = 0; } } self.x += moveX; self.y += moveY; var shipAsset = self.shipSprite; if (shipAsset) { var noseDistance = shipAsset.height / 2; self.enemyProjectileSpawnPoint.x = self.x + Math.cos(self.angle) * noseDistance; self.enemyProjectileSpawnPoint.y = self.y + Math.sin(self.angle) * noseDistance; } self.fireTimer--; if (self.fireTimer <= 0) { if (typeof EnemyProjectile !== 'undefined' && typeof enemyProjectiles !== 'undefined' && game && typeof game.addChild === 'function') { var newProjectile = new EnemyProjectile(self.angle); newProjectile.x = self.enemyProjectileSpawnPoint.x; newProjectile.y = self.enemyProjectileSpawnPoint.y; enemyProjectiles.push(newProjectile); game.addChild(newProjectile); self.fireTimer = self.fireCooldown; } } var gameWidth = 2048; var gameHeight = 2732; var marginWidth = self.shipSprite && self.shipSprite.width ? self.shipSprite.width / 2 + 50 : 100; var marginHeight = self.shipSprite && self.shipSprite.height ? self.shipSprite.height / 2 + 50 : 100; if (self.x < -marginWidth || self.x > gameWidth + marginWidth || self.y < -marginHeight || self.y > gameHeight + marginHeight) { self.isOffScreen = true; } }; // Use a magenta box for armored enemy self.shipSprite = self.attachAsset('Armoredenyme', { anchorX: 0.5, anchorY: 0.5 }); self.angle = Math.PI / 2; if (self.shipSprite) self.shipSprite.rotation = self.angle + Math.PI / 2; self.speed = 2.5; // Slower than normal enemy self.isOffScreen = false; self.lastX = self.x; self.lastY = self.y; self.health = 9; // Armored enemy starts with 9 HP self.isDestroyed = false; return self; }); var BossCannon = Container.expand(function (parentBoss) { var self = Container.call(this); self.parentBoss = parentBoss; self.health = 15; // Cannons are a bit tough self.isDestroyed = false; self.fireCooldown = 90; // Fires every 1.5 seconds self.fireTimer = Math.random() * self.fireCooldown; self.cannonSprite = self.attachAsset('Bossgun', { anchorX: 0.5, anchorY: 0.5 }); self.hit = function (damage) { if (self.isDestroyed) return; self.health -= damage; LK.effects.flashObject(self.cannonSprite, 0xff8888, 100); if (self.health <= 0) { self.isDestroyed = true; self.visible = false; // Play a smaller explosion for cannon destruction var explosionX = self.parentBoss.x + self.x; var explosionY = self.parentBoss.y + self.y; var boomImg = LK.getAsset('Boom3', { anchorX: 0.5, anchorY: 0.5, x: explosionX, y: explosionY }); game.addChild(boomImg); LK.setTimeout(function () { if (boomImg && typeof boomImg.destroy === 'function') boomImg.destroy(); }, 500); LK.getSound('Boom').play(); } }; self.update = function () { if (self.isDestroyed || typeof freezeEnemies !== 'undefined' && freezeEnemies) return; // Aim at player if (playerShip && !playerShip.isDestroyed) { var globalCannonX = self.parentBoss.x + self.x; var globalCannonY = self.parentBoss.y + self.y; var dx = playerShip.x - globalCannonX; var dy = playerShip.y - globalCannonY; var angleToPlayer = Math.atan2(dy, dx); self.cannonSprite.rotation = angleToPlayer + Math.PI / 2; // Assuming sprite points "up" self.fireTimer--; if (self.fireTimer <= 0) { var noseOffset = self.cannonSprite.height / 2; var projSpawnX = globalCannonX + Math.cos(angleToPlayer) * noseOffset; var projSpawnY = globalCannonY + Math.sin(angleToPlayer) * noseOffset; var newProjectile = new EnemyProjectile(angleToPlayer); newProjectile.x = projSpawnX; newProjectile.y = projSpawnY; enemyProjectiles.push(newProjectile); game.addChild(newProjectile); self.fireTimer = self.fireCooldown; } } }; return self; }); var BossEnemy = Container.expand(function () { var self = Container.call(this); self.isDestroyed = false; self.mainBody = new BossMainBody(self); self.addChild(self.mainBody); self.mainBody.x = 0; self.mainBody.y = 0; self.leftCannon = new BossCannon(self); self.addChild(self.leftCannon); self.rightCannon = new BossCannon(self); self.addChild(self.rightCannon); // Position cannons relative to the main body // Ensure sprites are attached to get dimensions var mainBodyWidth = self.mainBody.bodySprite.width; var cannonWidth = self.leftCannon.cannonSprite.width; // Both cannons use same asset self.leftCannon.x = -(mainBodyWidth / 2) - cannonWidth / 2 - 20; // 20px spacing self.leftCannon.y = 0; self.rightCannon.x = mainBodyWidth / 2 + cannonWidth / 2 + 20; // 20px spacing self.rightCannon.y = 0; self.update = function () { if (self.isDestroyed || typeof freezeEnemies !== 'undefined' && freezeEnemies) return; if (self.mainBody && !self.mainBody.isDestroyed) { self.mainBody.update(); } if (self.leftCannon && !self.leftCannon.isDestroyed) { self.leftCannon.update(); } if (self.rightCannon && !self.rightCannon.isDestroyed) { self.rightCannon.update(); } }; return self; }); var BossMainBody = Container.expand(function (parentBoss) { var self = Container.call(this); self.parentBoss = parentBoss; self.health = 50; // Main body is very tough self.isDestroyed = false; self.bodySprite = self.attachAsset('BossBodyAsset', { anchorX: 0.5, anchorY: 0.5 }); self.hit = function (damage) { if (self.isDestroyed) return; self.health -= damage; LK.effects.flashObject(self.bodySprite, 0xff6666, 150); if (self.health <= 0) { self.isDestroyed = true; self.visible = false; self.parentBoss.isDestroyed = true; // Main body destroyed means boss is defeated // Trigger larger explosion sequence for boss defeat in BossEnemy or game.update } }; self.update = function () { if (self.isDestroyed || typeof freezeEnemies !== 'undefined' && freezeEnemies) return; // Potential movement or special attacks later }; return self; }); var Drone = Container.expand(function () { var self = Container.call(this); self.droneSprite = self.attachAsset('Drons', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); self.speed = 8; self.fireCooldown = 120; // Fire every 2 seconds self.fireTimer = 0; self.orbitRadius = 150; // Distance from player self.orbitAngle = 0; // Current angle around player self.orbitSpeed = 0.02; // How fast it orbits self.isDestroyed = false; self.update = function () { if (self.isDestroyed || !playerShip || playerShip.isDestroyed) return; // Orbit around player self.orbitAngle += self.orbitSpeed; if (self.orbitAngle > Math.PI * 2) self.orbitAngle -= Math.PI * 2; var targetX = playerShip.x + Math.cos(self.orbitAngle) * self.orbitRadius; var targetY = playerShip.y + Math.sin(self.orbitAngle) * self.orbitRadius; // Smooth movement toward orbit position var dx = targetX - self.x; var dy = targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 5) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.x = targetX; self.y = targetY; } // Rotate sprite to face movement direction if (dist > 1) { self.droneSprite.rotation = Math.atan2(dy, dx) + Math.PI / 2; } // Fire at nearest enemy self.fireTimer--; if (self.fireTimer <= 0) { var nearestEnemy = null; var nearestDistance = Infinity; // Check regular enemies for (var i = 0; i < enemyShips.length; i++) { var enemy = enemyShips[i]; if (!enemy.isDestroyed && !enemy.isOffScreen) { var enemyDist = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2)); if (enemyDist < nearestDistance) { nearestDistance = enemyDist; nearestEnemy = enemy; } } } // Check boss if active if (bossEnemyInstance && !bossEnemyInstance.isDestroyed) { var bossDist = Math.sqrt(Math.pow(bossEnemyInstance.x - self.x, 2) + Math.pow(bossEnemyInstance.y - self.y, 2)); if (bossDist < nearestDistance) { nearestDistance = bossDist; nearestEnemy = bossEnemyInstance; } } // Fire at nearest enemy if within range if (nearestEnemy && nearestDistance < 400) { var fireAngle = Math.atan2(nearestEnemy.y - self.y, nearestEnemy.x - self.x); var droneProjectile = new PlayerProjectile(fireAngle); droneProjectile.x = self.x; droneProjectile.y = self.y; droneProjectile.droneDamage = 0.334; // Mark this projectile as drone projectile with 0.334 damage (3 hits to kill regular enemy) playerProjectiles.push(droneProjectile); game.addChild(droneProjectile); self.fireTimer = self.fireCooldown; // Play shot sound if (typeof LK !== 'undefined' && typeof LK.getSound === 'function') { var shotSound = LK.getSound('Shot'); if (shotSound && typeof shotSound.play === 'function') { shotSound.play(); } } } } }; return self; }); var EnemyProjectile = Container.expand(function (fireAngle) { var self = Container.call(this); self.bulletSprite = self.attachAsset('enemyBulletSprite', { anchorX: 0.5, anchorY: 0.5 }); self.angle = fireAngle; // Angle of movement self.speed = 10; // Enemy projectiles are a bit slower if (self.bulletSprite) { // Assuming the bullet shape is wider than tall, rotating by angle aligns its length with movement. self.bulletSprite.rotation = self.angle; } self.isOffScreen = false; self.update = function () { if (self.isOffScreen) return; if (typeof freezeEnemies !== 'undefined' && freezeEnemies) return; // Freeze enemy projectiles during card choice self.x += Math.cos(self.angle) * self.speed; self.y += Math.sin(self.angle) * self.speed; var gameWidth = 2048; var gameHeight = 2732; // Margin based on projectile size to ensure it's fully off-screen var margin = 50; // Default margin if (self.bulletSprite && (self.bulletSprite.width || self.bulletSprite.height)) { margin = Math.max(self.bulletSprite.width || 0, self.bulletSprite.height || 0) / 2 + 50; } if (self.x < -margin || self.x > gameWidth + margin || self.y < -margin || self.y > gameHeight + margin) { self.isOffScreen = true; } }; return self; }); // Orange rectangular bullet var EnemyShip = Container.expand(function () { var self = Container.call(this); self.fireCooldown = 180; // Fire every 3 seconds (180 ticks at 60FPS) self.fireTimer = Math.random() * self.fireCooldown; // Stagger initial firing self.enemyProjectileSpawnPoint = { x: 0, y: 0 }; self.hit = function () { if (self.isDestroyed) return; // Already destroyed self.health--; if (self.health <= 0) { self.isDestroyed = true; // Optionally, trigger a small visual effect here like a flash // LK.effects.flashObject(self, 0xffffff, 100); } else { // Optionally, visual effect for taking damage but not destroyed // LK.effects.flashObject(self, 0xffaaaa, 50); } }; self.update = function () { if (self.isOffScreen || self.isDestroyed) return; // Don't update if off-screen or destroyed if (typeof freezeEnemies !== 'undefined' && freezeEnemies) return; // Freeze all enemy movement and firing during card choice // Target the player ship if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) { var dx = playerShip.x - self.x; var dy = playerShip.y - self.y; // Calculate angle towards player var targetAngle = Math.atan2(dy, dx); // Update the enemy's angle to face the player self.angle = targetAngle; // Update sprite rotation to match the new angle // Assuming the ship sprite is designed "pointing up" (nose along its local -Y axis or top) // visual rotation = world angle + PI/2. if (self.shipSprite) { self.shipSprite.rotation = self.angle + Math.PI / 2; } } // If playerShip is not available or is destroyed, the enemy will continue in its current self.angle. self.lastX = self.x; self.lastY = self.y; // Prevent enemy from moving too close to the player var safeDistance = 120; // Minimum allowed distance between enemy and player var moveX = Math.cos(self.angle) * self.speed; var moveY = Math.sin(self.angle) * self.speed; if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) { var dxToPlayer = self.x + moveX - playerShip.x; var dyToPlayer = self.y + moveY - playerShip.y; var distToPlayer = Math.sqrt(dxToPlayer * dxToPlayer + dyToPlayer * dyToPlayer); if (distToPlayer < safeDistance) { // If moving would bring us too close, do not move this frame moveX = 0; moveY = 0; } } self.x += moveX; self.y += moveY; // Update enemy projectile spawn point var shipAsset = self.shipSprite; if (shipAsset) { var noseDistance = shipAsset.height / 2; // Assuming front is along height axis from center self.enemyProjectileSpawnPoint.x = self.x + Math.cos(self.angle) * noseDistance; self.enemyProjectileSpawnPoint.y = self.y + Math.sin(self.angle) * noseDistance; } // Firing logic self.fireTimer--; if (self.fireTimer <= 0) { if (typeof EnemyProjectile !== 'undefined' && typeof enemyProjectiles !== 'undefined' && game && typeof game.addChild === 'function') { var newProjectile = new EnemyProjectile(self.angle); // Fire in the direction the ship is moving newProjectile.x = self.enemyProjectileSpawnPoint.x; newProjectile.y = self.enemyProjectileSpawnPoint.y; enemyProjectiles.push(newProjectile); game.addChild(newProjectile); self.fireTimer = self.fireCooldown; // Reset cooldown } } // Generalized off-screen check var gameWidth = 2048; var gameHeight = 2732; var marginWidth = self.shipSprite && self.shipSprite.width ? self.shipSprite.width / 2 + 50 : 100; var marginHeight = self.shipSprite && self.shipSprite.height ? self.shipSprite.height / 2 + 50 : 100; if (self.x < -marginWidth || self.x > gameWidth + marginWidth || self.y < -marginHeight || self.y > gameHeight + marginHeight) { self.isOffScreen = true; } }; self.shipSprite = self.attachAsset('enemyShipSprite', { anchorX: 0.5, anchorY: 0.5 }); self.angle = Math.PI / 2; // Default angle: moving downwards (positive Y direction) if (self.shipSprite) { self.shipSprite.rotation = self.angle + Math.PI / 2; } self.speed = 4; self.isOffScreen = false; self.lastX = self.x; self.lastY = self.y; self.health = 3; self.isDestroyed = false; return self; }); var FireButton = Container.expand(function () { var self = Container.call(this); self.buttonSprite = self.attachAsset('fireButtonSprite', { anchorX: 0.5, anchorY: 0.5 }); self.lastFireTick = -1000; // Track last tick when fired self.down = function (x, y, obj) { // Prevent holding down for auto-fire: only allow firing if enough time has passed since last fire if (typeof LK !== 'undefined' && typeof LK.ticks !== 'undefined') { if (typeof self.lastFireTick === 'undefined') self.lastFireTick = -1000; // Only allow firing if at least fireButtonCooldown ticks have passed since last fire if (typeof fireButtonCooldown === 'undefined') fireButtonCooldown = 30; if (LK.ticks - self.lastFireTick < fireButtonCooldown) return; } // Only fire if player has ammo and not holding down for auto-fire if (typeof playerAmmo !== 'undefined' && playerAmmo > 0) { if (playerShip && typeof playerShip.currentAngle !== 'undefined' && typeof projectileSpawnPoint !== 'undefined' && playerProjectiles && PlayerProjectile) { if (typeof playerSplitShotActive !== 'undefined' && playerSplitShotActive) { var spreadAngle = Math.PI / 10; // 18 degrees spread for each projectile from center // Projectile 1 (left/upward component of spread) var proj1 = new PlayerProjectile(playerShip.currentAngle - spreadAngle); proj1.x = projectileSpawnPoint.x; proj1.y = projectileSpawnPoint.y; playerProjectiles.push(proj1); game.addChild(proj1); // Projectile 2 (right/downward component of spread) var proj2 = new PlayerProjectile(playerShip.currentAngle + spreadAngle); proj2.x = projectileSpawnPoint.x; proj2.y = projectileSpawnPoint.y; playerProjectiles.push(proj2); game.addChild(proj2); } else { // Single projectile (default behavior) var newProjectile = new PlayerProjectile(playerShip.currentAngle); newProjectile.x = projectileSpawnPoint.x; newProjectile.y = projectileSpawnPoint.y; playerProjectiles.push(newProjectile); game.addChild(newProjectile); } playerAmmo = Math.max(0, playerAmmo - 1); // Decrease ammo by 1, never below 0 // Removed ammo counter update as per requirements if (typeof LK !== 'undefined' && typeof LK.ticks !== 'undefined') { self.lastFireTick = LK.ticks; } // Play shot sound when firing if (typeof LK !== 'undefined' && typeof LK.getSound === 'function') { var shotSound = LK.getSound('Shot'); if (shotSound && typeof shotSound.play === 'function') { shotSound.play(); } } } } }; return self; }); var HealthRestore = Container.expand(function () { var self = Container.call(this); self.restoreSprite = self.attachAsset('healthRestoreSprite', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { // Check for collision with player ship if (playerShip && !playerShip.isDestroyed && self.intersects(playerShip)) { if (playerShip.health < 3) { playerShip.health = Math.min(playerShip.health + 1, 3); // Restore health, max 3 if (playerShip.shipSprite) { LK.effects.flashObject(playerShip.shipSprite, 0x00ff00, 150); // Flash green on health restore } // Update hearts display for (var i = 0; i < hearts.length; i++) { hearts[i].visible = i < playerShip.health; } } self.destroy(); } }; return self; }); var Heart = Container.expand(function () { var self = Container.call(this); self.heartSprite = self.attachAsset('heartSprite', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); return self; }); // PlayerShip class to handle player ship logic and projectile spawn point calculation var HomingMissile = Container.expand(function (initialAngle) { var self = Container.call(this); self.missileSprite = self.attachAsset('HomingMissileSprite', { anchorX: 0.5, anchorY: 0.5 }); self.angle = initialAngle; self.speed = 7; self.turnRate = 0.05; // Radians per tick self.isOffScreen = false; self.lastX = self.x; self.lastY = self.y; if (self.missileSprite) { // Ensure sprite exists before setting rotation self.missileSprite.rotation = self.angle; // Assuming sprite points right (0 rad) initially } self.update = function () { if (self.isOffScreen || typeof freezeEnemies !== 'undefined' && freezeEnemies) return; if (playerShip && !playerShip.isDestroyed) { var targetAngle = Math.atan2(playerShip.y - self.y, playerShip.x - self.x); var angleDiff = targetAngle - self.angle; while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI; while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI; var turnAmount = Math.sign(angleDiff) * Math.min(self.turnRate, Math.abs(angleDiff)); self.angle += turnAmount; } if (self.missileSprite) self.missileSprite.rotation = self.angle; self.lastX = self.x; self.lastY = self.y; self.x += Math.cos(self.angle) * self.speed; self.y += Math.sin(self.angle) * self.speed; var gameWidth = 2048; var gameHeight = 2732; var margin = (self.missileSprite && self.missileSprite.width ? self.missileSprite.width / 2 : 25) + 50; if (self.x < -margin || self.x > gameWidth + margin || self.y < -margin || self.y > gameHeight + margin) { self.isOffScreen = true; } }; return self; }); // Red circular fire button var Joystick = Container.expand(function () { var self = Container.call(this); self.baseSprite = self.attachAsset('joystickBaseSprite', { anchorX: 0.5, anchorY: 0.5 }); self.knobSprite = self.attachAsset('joystickKnobSprite', { anchorX: 0.5, anchorY: 0.5 }); // Maximum distance the knob's center can move from the base's center self.radius = self.baseSprite.width / 2 - self.knobSprite.width / 2; if (self.radius <= 0) { // Ensure a sensible minimum radius self.radius = self.baseSprite.width > 0 ? self.baseSprite.width / 4 : 50; // Use 1/4 of base width or 50 if base has no width } self.isDragging = false; self.inputVector = { x: 0, y: 0 }; // Normalized output (-1 to 1) self.handleDown = function (localX, localY) { // localX, localY are relative to the joystick's center (its origin) // Check if the touch is within the larger base area to start dragging var distSqFromCenter = localX * localX + localY * localY; if (distSqFromCenter <= self.baseSprite.width / 2 * (self.baseSprite.width / 2)) { self.isDragging = true; self.handleMove(localX, localY); // Snap knob to initial touch position return true; // Indicates joystick took control } return false; // Joystick not activated }; self.handleMove = function (localX, localY) { if (!self.isDragging) return; var dist = Math.sqrt(localX * localX + localY * localY); if (dist > self.radius) { // Normalize and scale to radius if touch is outside draggable area self.knobSprite.x = localX / dist * self.radius; self.knobSprite.y = localY / dist * self.radius; } else { self.knobSprite.x = localX; self.knobSprite.y = localY; } // Calculate normalized input vector if (self.radius > 0) { self.inputVector.x = self.knobSprite.x / self.radius; self.inputVector.y = self.knobSprite.y / self.radius; } else { // Avoid division by zero if radius is zero self.inputVector.x = 0; self.inputVector.y = 0; } }; self.handleUp = function () { if (self.isDragging) { self.isDragging = false; // Reset knob to center and clear input vector self.knobSprite.x = 0; self.knobSprite.y = 0; self.inputVector.x = 0; self.inputVector.y = 0; } }; self.getInput = function () { return self.inputVector; }; self.isActive = function () { return self.isDragging; }; return self; }); // Projectile class for player's bullets var PlayerProjectile = Container.expand(function (fireAngle) { var self = Container.call(this); // Attach bullet sprite self.bulletSprite = self.attachAsset('playerBulletSprite', { anchorX: 0.5, anchorY: 0.5 }); self.angle = fireAngle; // Store the firing angle self.speed = 20; // Projectile speed magnitude // The playerBulletSprite has orientation:1, meaning it's rotated 90deg clockwise. // If original was thin vertical, it's now thin horizontal, "pointing" along its X-axis. // So, self.angle directly applies. self.bulletSprite.rotation = self.angle; self.isOffScreen = false; // Flag to indicate if projectile is off-screen // Update method to move projectile self.update = function () { if (self.isOffScreen) return; // Don't update if already marked off-screen self.x += Math.cos(self.angle) * self.speed; self.y += Math.sin(self.angle) * self.speed; // Check if off-screen var gameWidth = 2048; var gameHeight = 2732; // Margin based on projectile size to ensure it's fully off-screen var margin = Math.max(self.bulletSprite.width, self.bulletSprite.height) / 2 + 50; if (self.x < -margin || self.x > gameWidth + margin || self.y < -margin || self.y > gameHeight + margin) { self.isOffScreen = true; } }; //{M} // Note: Original L was self.destroy related, removed. return self; }); var PlayerShip = Container.expand(function () { var self = Container.call(this); self.health = 3; // Player can take a few hits self._fractionalHealth = self.health; // For fractional damage (e.g., 0.5 from armored enemy) self.isDestroyed = false; self.hit = function () { if (self.isDestroyed) return; if (typeof self._fractionalHealth === 'undefined') self._fractionalHealth = self.health; self._fractionalHealth -= 1; self.health = Math.floor(self._fractionalHealth); LK.effects.flashObject(self.shipSprite || self, 0xff0000, 150); // Flash red on hit (flash sprite if available) // Update hearts display for (var i = 0; i < hearts.length; i++) { hearts[i].visible = i < self.health; } if (self._fractionalHealth <= 0) { self.isDestroyed = true; // Game over will be triggered in game.update } }; // Attach player ship sprite and set reference for spawn point calculation self.shipSprite = self.attachAsset('playerShipSprite', { anchorX: 0.5, anchorY: 0.5 }); self.moveSpeed = 10; // Pixels per tick for movement speed // currentAngle is the direction the ship moves and fires projectiles. // 0 radians = right, -PI/2 = up (screen coordinates). self.currentAngle = -Math.PI / 2; // Initial angle: pointing up. // Initialize dash properties self.dashAvailable = false; self.isDashing = false; self.dashTimer = 0; self.dashCooldown = 0; self.dashSpeed = 25; self.dashDuration = 10; self.dashDirection = 0; self.lastInputTime = 0; self.lastInputDirection = undefined; // playerShipSprite is assumed to be designed pointing "up" (its nose along its local -Y axis). // To make the sprite's nose align with currentAngle, its visual rotation needs adjustment. // Visual rotation = currentAngle + Math.PI / 2. // E.g., currentAngle = -PI/2 (up) => visual rotation = 0. // E.g., currentAngle = 0 (right) => visual rotation = PI/2. self.shipSprite.rotation = self.currentAngle + Math.PI / 2; // Initialize projectile spawn point (remains important) // This will be updated relative to the ship's current position in self.update() // Initial dummy values, will be set correctly on first update. projectileSpawnPoint.x = 0; projectileSpawnPoint.y = 0; self.applyJoystickInput = function (inputX, inputY) { // Handle dash input detection if (self.dashAvailable && inputX !== 0 || inputY !== 0) { var currentTime = LK.ticks; var inputDirection = Math.atan2(inputY, inputX); // Check for double-tap dash (same direction within 20 ticks) if (self.lastInputTime && currentTime - self.lastInputTime < 20 && self.lastInputDirection !== undefined && Math.abs(inputDirection - self.lastInputDirection) < 0.3 && self.dashCooldown <= 0) { // Start dash self.isDashing = true; self.dashTimer = self.dashDuration; self.dashDirection = inputDirection; self.dashCooldown = 180; // 3 second cooldown // Visual effect for dash if (self.shipSprite) { LK.effects.flashObject(self.shipSprite, 0x00FFFF, 200); } } self.lastInputTime = currentTime; self.lastInputDirection = inputDirection; } // Handle dash movement if (self.isDashing && self.dashTimer > 0) { // Move at dash speed during dash var dashMoveX = Math.cos(self.dashDirection) * self.dashSpeed; var dashMoveY = Math.sin(self.dashDirection) * self.dashSpeed; self.x += dashMoveX; self.y += dashMoveY; self.dashTimer--; if (self.dashTimer <= 0) { self.isDashing = false; } } else { // Normal movement when not dashing // Update angle only if joystick provides directional input if (inputX !== 0 || inputY !== 0) { self.currentAngle = Math.atan2(inputY, inputX); self.shipSprite.rotation = self.currentAngle + Math.PI / 2; } self.x += inputX * self.moveSpeed; self.y += inputY * self.moveSpeed; } // Update dash cooldown if (self.dashCooldown > 0) { self.dashCooldown--; } // Boundary checks to keep ship on screen var halfWidth = self.shipSprite.width / 2; var halfHeight = self.shipSprite.height / 2; var gameWidth = 2048; var gameHeight = 2732; var topSafeMargin = 100; // Top 100px area is reserved if (self.x - halfWidth < 0) self.x = halfWidth; if (self.x + halfWidth > gameWidth) self.x = gameWidth - halfWidth; if (self.y - halfHeight < topSafeMargin) self.y = topSafeMargin + halfHeight; if (self.y + halfHeight > gameHeight) self.y = gameHeight - halfHeight; }; // Update projectile spawn point every frame based on player ship position and rotation self.update = function () { var shipAsset = self.shipSprite; if (!shipAsset) return; // Calculate spawn point at the tip of the ship, considering its currentAngle. // The "nose" is shipAsset.height / 2 distance from the center. var noseDistance = shipAsset.height / 2; projectileSpawnPoint.x = self.x + Math.cos(self.currentAngle) * noseDistance; projectileSpawnPoint.y = self.y + Math.sin(self.currentAngle) * noseDistance; }; return self; }); var PlayerTrail = Container.expand(function () { var self = Container.call(this); // Use old trail style (centerCircle) or new trail style (effective) based on setting var trailAsset = typeof useOldTrail !== 'undefined' && useOldTrail ? 'centerCircle' : 'effective'; self.trailSprite = self.attachAsset(trailAsset, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, tint: 0xff3333 }); self.lifeTime = 60; // Trail segment lasts 1 second at 60fps self.currentLife = self.lifeTime; self.update = function () { self.currentLife--; if (self.currentLife <= 0) { self.isExpired = true; } // Smoothly rotate the trail sprite to match the player ship's rotation (only for new trail style) if (typeof playerShip !== 'undefined' && playerShip && playerShip.shipSprite && self.trailSprite && (typeof useOldTrail === 'undefined' || !useOldTrail)) { var targetRotation = playerShip.shipSprite.rotation; var currentRotation = self.trailSprite.rotation; // Calculate the shortest rotation difference var rotationDiff = targetRotation - currentRotation; while (rotationDiff > Math.PI) rotationDiff -= 2 * Math.PI; while (rotationDiff < -Math.PI) rotationDiff += 2 * Math.PI; // Only tween if the difference is significant enough to avoid micro-adjustments if (Math.abs(rotationDiff) > 0.1) { // Stop any existing rotation tween first tween.stop(self.trailSprite, { rotation: true }); // Smooth rotation interpolation over 200ms tween(self.trailSprite, { rotation: targetRotation }, { duration: 200, easing: tween.easeOut }); } else { // For very small differences, just set directly to avoid accumulation of small errors self.trailSprite.rotation = targetRotation; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Will be updated in PlayerShip class update // Projectile spawn point (relative to player ship center) // Placeholder ID // Placeholder ID // Placeholder ID // Placeholder ID // Placeholder ID //Only include the plugins you need to create the game. //Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property. var projectileSpawnPoint = { x: 0, y: 0 }; var joystick; // Declare joystick instance // Array to keep track of all player projectiles var playerProjectiles = []; // Array to keep track of player trail segments var playerTrailSegments = []; var trailSpawnTimer = 0; // Array to keep track of enemy trail segments var enemyTrailSegments = []; var enemyTrailSpawnTimer = 0; // Player projectile damage (can be doubled by Cardsttsck) var playerProjectileDamage = 3; // Player ammo count (start with 10). Player cannot fire forever: must wait for ammo to restore and cannot hold fire for auto-fire. var playerAmmo = 10; // Create and add the game background // You can change the background by updating the ID in the Assets section for 'game_background_image' var gameBackground = LK.getAsset('game_background_image', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(gameBackground); // Add colorful stars to the background var starColors = [0xFFFFFF, 0xFF3333, 0x33FF33, 0x3333FF, 0xFFFF33, 0xFF33FF, 0x33FFFF, 0xFFAA33, 0xAA33FF, 0x33AAFF]; for (var i = 0; i < 50; i++) { var star = LK.getAsset('Star', { anchorX: 0.5, anchorY: 0.5, x: Math.random() * 2048, y: Math.random() * 2732, tint: starColors[Math.floor(Math.random() * starColors.length)] }); // 30% chance for a star to have disappearing/reappearing effect instead of twinkling if (Math.random() < 0.3) { // Create disappearing and reappearing effect for some stars var disappearDelay = 2000 + Math.random() * 8000; // Random delay 2-10 seconds before first disappear LK.setTimeout(function () { function startDisappearCycle() { // Disappear quickly tween(star, { alpha: 0 }, { duration: 200 + Math.random() * 300, easing: tween.easeInOut, onFinish: function onFinish() { // Stay invisible for a random time var invisibleTime = 500 + Math.random() * 2000; // 0.5-2.5 seconds invisible LK.setTimeout(function () { // Reappear tween(star, { alpha: 1.0 }, { duration: 300 + Math.random() * 500, easing: tween.easeInOut, onFinish: function onFinish() { // Wait before next disappear cycle var nextCycleDelay = 3000 + Math.random() * 7000; // 3-10 seconds before next cycle LK.setTimeout(function () { startDisappearCycle(); // Start the cycle again }, nextCycleDelay); } }); }, invisibleTime); } }); } startDisappearCycle(); }, disappearDelay); } else { // Add normal twinkling animation to other stars tween(star, { alpha: 0.3 }, { duration: 1000 + Math.random() * 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(star, { alpha: 1.0 }, { duration: 1000 + Math.random() * 1000, easing: tween.easeInOut, onFinish: function onFinish() { // Create infinite loop by calling the twinkling again tween(star, { alpha: 0.3 }, { duration: 1000 + Math.random() * 1000, easing: tween.easeInOut }); } }); } }); } game.addChild(star); } // Adding it here as one of the first children to 'game' ensures it's in the background. var maxPlayerAmmo = 10; // Maximum player ammo capacity // Fire button cooldown in ticks (default 30, can be halved by Card1) var fireButtonCooldown = 30; // Array to keep track of all enemy ships var enemyShips = []; // Array to keep track of all enemy projectiles var enemyProjectiles = []; // Global flag: freeze all enemy movement and firing during card choice var freezeEnemies = false; var playerSplitShotActive = false; // Flag for Asplitmind card: player fires two projectiles // Create player ship and add to game var playerShip = new PlayerShip(); // Apply saved skin selection if (selectedSkin && selectedSkin !== 'playerShipSprite') { var oldRotation = playerShip.shipSprite.rotation; playerShip.shipSprite.destroy(); playerShip.shipSprite = playerShip.attachAsset(selectedSkin, { anchorX: 0.5, anchorY: 0.5 }); playerShip.shipSprite.rotation = oldRotation; } game.addChild(playerShip); // Display hearts for player health var hearts = []; for (var i = 0; i < playerShip.health; i++) { var heart = new Heart(); heart.x = i * 120 + 120; // Position hearts with increased spacing heart.y = 60; // Top-left corner LK.gui.topLeft.addChild(heart); hearts.push(heart); } // Load saved money from storage var money = storage.money || 0; // Load dash purchase status var dashPurchased = storage.dashPurchased || false; // Set dash availability if purchased if (dashPurchased && playerShip) { playerShip.dashAvailable = true; } // Load drone purchase status var dronesActive = storage.dronesActive || false; var playerDrones = []; // Array to track active drones var droneSpawnCooldown = 0; // Cooldown for spawning new drones var maxDrones = 2; // Maximum number of active drones var moneyTxt = new Text2(money + " $", { size: 120, fill: 0xFFE066, font: "monospace, 'Press Start 2P', 'VT323', 'Courier New', Courier, monospace", // pixel/retro style align: "right" }); moneyTxt.anchor.set(1, 0); // Right-top anchor moneyTxt.x = -60; // Padding from right edge moneyTxt.y = 200; // Move money text down to make room for settings LK.gui.topRight.addChild(moneyTxt); // Settings icon in top-right corner var settingsIcon = new Text2("⚙", { size: 120, fill: 0xFFE066, align: "right" }); settingsIcon.anchor.set(1, 0); // Right-top anchor settingsIcon.x = -60; // Padding from right edge settingsIcon.y = 300; // Position below black hole and money counter settingsIcon.interactive = true; // Add pulsing glow effect to make it more noticeable tween(settingsIcon, { alpha: 0.7 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { tween(settingsIcon, { alpha: 1.0 }, { duration: 1500, easing: tween.easeInOut, loop: true }); } }); settingsIcon.down = function (x, y, obj) { // Open ship skin selection menu if (game._skinMenuActive) return; // Prevent multiple menus openSkinSelectionMenu(); }; // Ship skin selection menu functionality var selectedSkin = storage.selectedSkin || 'shipSkin1'; // Initialize owned skins if not exists (default and heavy are free) if (!storage.ownedSkins) { storage.ownedSkins = ['shipSkin1', 'shipSkin3']; } // Initialize trail setting if not exists (enabled by default) var trailEnabled = storage.trailEnabled !== undefined ? storage.trailEnabled : true; // Initialize trail color if not exists (default to red) var trailColor = storage.trailColor || 0xff3333; // Initialize old trail style setting if not exists (new style by default) var useOldTrail = storage.useOldTrail !== undefined ? storage.useOldTrail : false; // Ensure selected skin is valid (not Fighter skin) if (selectedSkin === 'shipSkin2') { selectedSkin = 'shipSkin1'; storage.selectedSkin = selectedSkin; } function openSkinSelectionMenu() { game._skinMenuActive = true; // Freeze all enemy movement and firing like card selection freezeEnemies = true; // Create overlay for skin selection var skinOverlay = new Container(); skinOverlay.name = "skinSelectionOverlay"; // Semi-transparent background var skinBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 20, scaleY: 20, x: 2048 / 2, y: 2732 / 2, tint: 0x000000, alpha: 0.8 }); skinOverlay.addChild(skinBg); // Title text var titleText = new Text2("SELECT SHIP SKIN", { size: 100, fill: 0xFFFFFF, align: "center" }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 2732 / 2 - 400; skinOverlay.addChild(titleText); // Available ship skins var availableSkins = [{ id: 'shipSkin1', name: 'Default', cost: 0 }, { id: 'shipSkin3', name: 'Heavy', cost: 0 }, { id: 'skinfire', name: 'Ghost', cost: 5 }, { id: 'skinnlood', name: 'Blood', cost: 8 }, { id: 'Skinrocet', name: 'Rocket', cost: 12 }]; // Position skins horizontally var skinPositions = [{ x: 2048 / 2 - 540, y: 2732 / 2 }, { x: 2048 / 2 - 270, y: 2732 / 2 }, { x: 2048 / 2, y: 2732 / 2 }, { x: 2048 / 2 + 270, y: 2732 / 2 }, { x: 2048 / 2 + 540, y: 2732 / 2 }]; for (var skinIdx = 0; skinIdx < availableSkins.length; skinIdx++) { var skinData = availableSkins[skinIdx]; var skinContainer = new Container(); // Ship preview var shipPreview = LK.getAsset(skinData.id, { anchorX: 0.5, anchorY: 0.5, x: skinPositions[skinIdx].x, y: skinPositions[skinIdx].y, scaleX: 1.5, scaleY: 1.5 }); skinContainer.addChild(shipPreview); // Selection border for currently selected skin if (skinData.id === selectedSkin) { var selectionBorder = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: skinPositions[skinIdx].x, y: skinPositions[skinIdx].y, scaleX: 3, scaleY: 3, tint: 0x00FF00, alpha: 0.3 }); skinContainer.addChild(selectionBorder); } // Skin name text var skinNameText = new Text2(skinData.name, { size: 60, fill: 0xFFFFFF, align: "center" }); skinNameText.anchor.set(0.5, 0.5); skinNameText.x = skinPositions[skinIdx].x; skinNameText.y = skinPositions[skinIdx].y + 200; skinContainer.addChild(skinNameText); // Cost text - only show if skin is not owned var ownedSkins = storage.ownedSkins || ['shipSkin1', 'shipSkin3']; var isOwned = ownedSkins.indexOf(skinData.id) !== -1 || skinData.cost === 0; if (!isOwned) { var costText = new Text2(skinData.cost > 0 ? skinData.cost + " $" : "FREE", { size: 40, fill: skinData.cost > 0 ? 0xFFE066 : 0x00FF00, align: "center" }); costText.anchor.set(0.5, 0.5); costText.x = skinPositions[skinIdx].x; costText.y = skinPositions[skinIdx].y + 270; skinContainer.addChild(costText); } // If player doesn't have enough money, make skin appear unavailable if (skinData.cost > money) { shipPreview.tint = 0x666666; skinNameText.tint = 0x666666; costText.fill = 0xFF4444; } skinContainer.interactive = true; // Use closure to capture skin data (function (currentSkinData) { skinContainer.down = function (x_down, y_down, obj_down) { if (!game._skinMenuActive) return; // Check if skin costs money and player has enough if (currentSkinData.cost > 0 && money < currentSkinData.cost) { // Flash red to indicate insufficient funds LK.effects.flashScreen(0xFF0000, 300); return; } // Check if skin is already owned (either free or already purchased) var ownedSkins = storage.ownedSkins || ['shipSkin1', 'shipSkin3']; // Default and Heavy are free if (currentSkinData.cost > 0 && ownedSkins.indexOf(currentSkinData.id) === -1) { // Purchase the skin money -= currentSkinData.cost; storage.money = money; // Save money to storage moneyTxt.setText(money + " $"); ownedSkins.push(currentSkinData.id); storage.ownedSkins = ownedSkins; } // Update selected skin selectedSkin = currentSkinData.id; storage.selectedSkin = selectedSkin; // Update player ship sprite if (playerShip && playerShip.shipSprite) { var oldRotation = playerShip.shipSprite.rotation; playerShip.shipSprite.destroy(); playerShip.shipSprite = playerShip.attachAsset(selectedSkin, { anchorX: 0.5, anchorY: 0.5 }); playerShip.shipSprite.rotation = oldRotation; } closeSkinSelectionMenu(skinOverlay); }; })(skinData); skinOverlay.addChild(skinContainer); } // Trail toggle button var trailToggleText = trailEnabled ? "TRAIL: ON" : "TRAIL: OFF"; var trailToggleButton = new Text2(trailToggleText, { size: 80, fill: trailEnabled ? 0x00FF00 : 0xFF4444, align: "center" }); trailToggleButton.anchor.set(0.5, 0.5); trailToggleButton.x = 2048 / 2 - 300; trailToggleButton.y = 2732 / 2 + 350; trailToggleButton.interactive = true; trailToggleButton.down = function () { if (!game._skinMenuActive) return; // Toggle trail setting trailEnabled = !trailEnabled; storage.trailEnabled = trailEnabled; // Update button text and color trailToggleButton.setText(trailEnabled ? "TRAIL: ON" : "TRAIL: OFF"); trailToggleButton.fill = trailEnabled ? 0x00FF00 : 0xFF4444; }; skinOverlay.addChild(trailToggleButton); // Old trail style toggle button var oldTrailToggleText = useOldTrail ? "OLD TRAIL: ON" : "OLD TRAIL: OFF"; var oldTrailToggleButton = new Text2(oldTrailToggleText, { size: 80, fill: useOldTrail ? 0x00FF00 : 0xFF4444, align: "center" }); oldTrailToggleButton.anchor.set(0.5, 0.5); oldTrailToggleButton.x = 2048 / 2 - 300; oldTrailToggleButton.y = 2732 / 2 + 450; oldTrailToggleButton.interactive = true; oldTrailToggleButton.down = function () { if (!game._skinMenuActive) return; // Toggle old trail setting useOldTrail = !useOldTrail; storage.useOldTrail = useOldTrail; // Update button text and color oldTrailToggleButton.setText(useOldTrail ? "OLD TRAIL: ON" : "OLD TRAIL: OFF"); oldTrailToggleButton.fill = useOldTrail ? 0x00FF00 : 0xFF4444; }; skinOverlay.addChild(oldTrailToggleButton); // Trail color selection section var trailColorTitle = new Text2("TRAIL COLOR", { size: 60, fill: 0xFFFFFF, align: "center" }); trailColorTitle.anchor.set(0.5, 0.5); trailColorTitle.x = 2048 / 2 + 300; trailColorTitle.y = 2732 / 2 + 350; skinOverlay.addChild(trailColorTitle); // Available trail colors var trailColors = [{ name: "RED", color: 0xff3333 }, { name: "BLUE", color: 0x3333ff }, { name: "GREEN", color: 0x33ff33 }, { name: "YELLOW", color: 0xffff33 }, { name: "PURPLE", color: 0xff33ff }, { name: "ORANGE", color: 0xff6600 }]; // Get current trail color from storage or default to red var currentTrailColor = storage.trailColor || 0xff3333; // Display trail color options in a 3x2 grid for (var colorIdx = 0; colorIdx < trailColors.length; colorIdx++) { var colorData = trailColors[colorIdx]; var colorContainer = new Container(); // Calculate position in 3x2 grid var col = colorIdx % 3; var row = Math.floor(colorIdx / 3); var colorX = 2048 / 2 + 150 + col * 100; var colorY = 2732 / 2 + 420 + row * 80; // Color preview circle var colorPreview = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: colorX, y: colorY, scaleX: 0.8, scaleY: 0.8, tint: colorData.color }); colorContainer.addChild(colorPreview); // Selection border for currently selected color if (colorData.color === currentTrailColor) { var colorBorder = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: colorX, y: colorY, scaleX: 1.0, scaleY: 1.0, tint: 0xFFFFFF, alpha: 0.5 }); colorContainer.addChild(colorBorder); } colorContainer.interactive = true; // Use closure to capture color data (function (currentColorData) { colorContainer.down = function () { if (!game._skinMenuActive) return; // Update trail color currentTrailColor = currentColorData.color; storage.trailColor = currentTrailColor; // Refresh the menu to show new selection if (game._skinMenuActive) { closeSkinSelectionMenu(skinOverlay); openSkinSelectionMenu(); } }; })(colorData); skinOverlay.addChild(colorContainer); } // Close button var closeButton = new Text2("✕", { size: 120, fill: 0xFF4444, align: "center" }); closeButton.anchor.set(0.5, 0.5); closeButton.x = 2048 / 2 + 600; closeButton.y = 2732 / 2 - 400; closeButton.interactive = true; closeButton.down = function () { if (!game._skinMenuActive) return; closeSkinSelectionMenu(skinOverlay); }; skinOverlay.addChild(closeButton); game.addChild(skinOverlay); } function closeSkinSelectionMenu(overlay) { game._skinMenuActive = false; // Resume game like card selection freezeEnemies = false; if (overlay && typeof overlay.destroy === 'function') { overlay.destroy(); } } // Upgrade menu functionality function openUpgradeMenu() { game._upgradeMenuActive = true; // Freeze all enemy movement and firing like card selection freezeEnemies = true; // Create overlay for upgrade menu var upgradeOverlay = new Container(); upgradeOverlay.name = "upgradeMenuOverlay"; // Semi-transparent background var upgradeBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 20, scaleY: 20, x: 2048 / 2, y: 2732 / 2, tint: 0x000000, alpha: 0.8 }); upgradeOverlay.addChild(upgradeBg); // Title text var upgradeTitleText = new Text2("UPGRADES", { size: 100, fill: 0xFFFFFF, align: "center" }); upgradeTitleText.anchor.set(0.5, 0.5); upgradeTitleText.x = 2048 / 2; upgradeTitleText.y = 2732 / 2 - 400; upgradeOverlay.addChild(upgradeTitleText); // Available upgrades - basic upgrades removed var availableUpgrades = [{ name: "DASH", cost: 50, effect: function effect() { // Add dash ability - player can dash by double-tapping movement direction if (typeof playerShip !== 'undefined' && playerShip) { playerShip.dashAvailable = true; playerShip.dashCooldown = 0; playerShip.dashSpeed = 25; // Speed during dash playerShip.dashDuration = 10; // Duration in ticks playerShip.dashDistance = 250; // Maximum dash distance } // Mark dash as purchased dashPurchased = true; storage.dashPurchased = dashPurchased; // Show green dash button above attack button if (typeof greenDashButton !== 'undefined' && greenDashButton) { greenDashButton.visible = true; } } }, { name: "DRONES", cost: 75, effect: function effect() { // Add drone support ability dronesActive = true; storage.dronesActive = dronesActive; // Initialize drone spawn timer droneSpawnCooldown = 0; } }, { name: "CASE", cost: 100, effect: function effect() { // Close upgrade menu first closeUpgradeMenu(upgradeOverlay); // Start case opening animation startCaseAnimation(); } }]; // Display message when no upgrades are available if (availableUpgrades.length === 0) { var noUpgradesText = new Text2("NO UPGRADES AVAILABLE", { size: 80, fill: 0xFFFFFF, align: "center" }); noUpgradesText.anchor.set(0.5, 0.5); noUpgradesText.x = 2048 / 2; noUpgradesText.y = 2732 / 2; upgradeOverlay.addChild(noUpgradesText); } else { // Position upgrades in a 2x2 grid with third item centered var upgradePositions = [{ x: 2048 / 2 - 300, y: 2732 / 2 - 100 }, { x: 2048 / 2 + 300, y: 2732 / 2 - 100 }, { x: 2048 / 2, y: 2732 / 2 + 150 }, { x: 2048 / 2 + 300, y: 2732 / 2 + 150 }]; for (var upgradeIdx = 0; upgradeIdx < availableUpgrades.length; upgradeIdx++) { var upgradeData = availableUpgrades[upgradeIdx]; var upgradeContainer = new Container(); // Upgrade background - red if purchased var bgTint = 0x333333; if (upgradeData.name === "DASH" && dashPurchased) { bgTint = 0xFF0000; // Red for purchased dash } else if (upgradeData.name === "DRONES" && dronesActive) { bgTint = 0xFF0000; // Red for purchased drones } var upgradeBoxBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: upgradePositions[upgradeIdx].x, y: upgradePositions[upgradeIdx].y, scaleX: 4, scaleY: 2, tint: bgTint, alpha: 0.8 }); upgradeContainer.addChild(upgradeBoxBg); // Add case image for case upgrade if (upgradeData.name === "CASE") { var caseImage = LK.getAsset('Case', { anchorX: 0.5, anchorY: 0.5, x: upgradePositions[upgradeIdx].x, y: upgradePositions[upgradeIdx].y - 10, scaleX: 1.5, scaleY: 1.5 }); upgradeContainer.addChild(caseImage); } // Upgrade name text - don't show text for case item if (upgradeData.name !== "CASE") { var upgradeNameText = new Text2(upgradeData.name, { size: 50, fill: 0xFFFFFF, align: "center" }); upgradeNameText.anchor.set(0.5, 0.5); upgradeNameText.x = upgradePositions[upgradeIdx].x; upgradeNameText.y = upgradePositions[upgradeIdx].y - 30; upgradeContainer.addChild(upgradeNameText); } // Cost text - move down for case item var costTextY = upgradePositions[upgradeIdx].y + 30; if (upgradeData.name === "CASE") { costTextY = upgradePositions[upgradeIdx].y + 100; // Move price further down for case } var upgradeCostText = new Text2(upgradeData.cost + " $", { size: 40, fill: money >= upgradeData.cost ? 0x00FF00 : 0xFF4444, align: "center" }); upgradeCostText.anchor.set(0.5, 0.5); upgradeCostText.x = upgradePositions[upgradeIdx].x; upgradeCostText.y = costTextY; upgradeContainer.addChild(upgradeCostText); // If player doesn't have enough money, make upgrade appear unavailable if (upgradeData.cost > money) { upgradeBoxBg.tint = 0x666666; upgradeNameText.tint = 0x666666; } upgradeContainer.interactive = true; // Use closure to capture upgrade data (function (currentUpgradeData) { upgradeContainer.down = function (x_down, y_down, obj_down) { if (!game._upgradeMenuActive) return; // Check if player has enough money if (money < currentUpgradeData.cost) { // Flash red to indicate insufficient funds LK.effects.flashScreen(0xFF0000, 300); return; } // Purchase the upgrade money -= currentUpgradeData.cost; storage.money = money; // Save money to storage moneyTxt.setText(money + " $"); // Apply upgrade effect currentUpgradeData.effect(); // Flash green to show successful purchase LK.effects.flashScreen(0x00FF00, 300); closeUpgradeMenu(upgradeOverlay); }; })(upgradeData); upgradeOverlay.addChild(upgradeContainer); } } // Close button var upgradeCloseButton = new Text2("✕", { size: 120, fill: 0xFF4444, align: "center" }); upgradeCloseButton.anchor.set(0.5, 0.5); upgradeCloseButton.x = 2048 / 2 + 600; upgradeCloseButton.y = 2732 / 2 - 400; upgradeCloseButton.interactive = true; upgradeCloseButton.down = function () { if (!game._upgradeMenuActive) return; closeUpgradeMenu(upgradeOverlay); }; upgradeOverlay.addChild(upgradeCloseButton); game.addChild(upgradeOverlay); } function closeUpgradeMenu(overlay) { game._upgradeMenuActive = false; // Resume game like card selection freezeEnemies = false; if (overlay && typeof overlay.destroy === 'function') { overlay.destroy(); } } // Case opening animation system function startCaseAnimation() { game._caseAnimationActive = true; freezeEnemies = true; // Create case animation overlay var caseOverlay = new Container(); caseOverlay.name = "caseAnimationOverlay"; // Semi-transparent background var caseBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 20, scaleY: 20, x: 2048 / 2, y: 2732 / 2, tint: 0x000000, alpha: 0.9 }); caseOverlay.addChild(caseBg); // Title text var caseTitle = new Text2("OPENING CASE...", { size: 80, fill: 0xFFD700, align: "center" }); caseTitle.anchor.set(0.5, 0.5); caseTitle.x = 2048 / 2; caseTitle.y = 2732 / 2 - 300; caseOverlay.addChild(caseTitle); // Define possible rewards with rarities var caseRewards = [{ name: "COINS", value: 25, rarity: "common", color: 0xCCCCCC }, { name: "COINS", value: 50, rarity: "uncommon", color: 0x00FF00 }, { name: "COINS", value: 75, rarity: "rare", color: 0x0066FF }, { name: "COINS", value: 100, rarity: "epic", color: 0x9933FF }, { name: "COINS", value: 200, rarity: "legendary", color: 0xFFD700 }]; // Create reward slots for animation (7 items visible) var rewardSlots = []; var slotWidth = 200; var slotSpacing = 220; var centerX = 2048 / 2; var centerY = 2732 / 2; // Generate 20+ items for smooth scrolling effect var animationItems = []; for (var i = 0; i < 25; i++) { var reward; var rand = Math.random(); if (rand < 0.4) reward = caseRewards[0]; // 40% common else if (rand < 0.7) reward = caseRewards[1]; // 30% uncommon else if (rand < 0.85) reward = caseRewards[2]; // 15% rare else if (rand < 0.97) reward = caseRewards[3]; // 12% epic else reward = caseRewards[4]; // 3% legendary animationItems.push({ name: reward.name, value: reward.value, rarity: reward.rarity, color: reward.color }); } // The final reward (what player actually gets) var finalReward = animationItems[12]; // Middle item will be the winner // Create visual slots for (var s = 0; s < 7; s++) { var slot = new Container(); var slotBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.2, scaleY: 1.8, tint: s === 3 ? 0xFFD700 : 0x333333, // Highlight center slot alpha: s === 3 ? 0.8 : 0.4 }); slot.addChild(slotBg); slot.x = centerX + (s - 3) * slotSpacing; slot.y = centerY; caseOverlay.addChild(slot); rewardSlots.push(slot); } // Create reward display items var rewardDisplays = []; for (var d = 0; d < animationItems.length; d++) { var item = animationItems[d]; var itemContainer = new Container(); // Item background var itemBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.8, scaleY: 1.4, tint: item.color, alpha: 0.7 }); itemContainer.addChild(itemBg); // Item text var itemText = new Text2(item.name + "\n" + item.value, { size: 40, fill: 0xFFFFFF, align: "center" }); itemText.anchor.set(0.5, 0.5); itemContainer.addChild(itemText); // Position items in a line itemContainer.x = centerX + (d - 12) * slotSpacing; // Center around item 12 itemContainer.y = centerY; caseOverlay.addChild(itemContainer); rewardDisplays.push(itemContainer); } // Selection indicator var indicator = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 2.0, tint: 0xFFD700, alpha: 0.6, x: centerX, y: centerY - 150 }); caseOverlay.addChild(indicator); game.addChild(caseOverlay); // Start spinning animation var spinDistance = slotSpacing * 15; // Spin through 15 slots var currentOffset = 0; var targetOffset = spinDistance; var animationSpeed = slotSpacing * 0.3; // Initial speed var deceleration = 0.98; // Slow down factor function animateSpinning() { if (!game._caseAnimationActive) return; // Move all reward displays for (var r = 0; r < rewardDisplays.length; r++) { rewardDisplays[r].x -= animationSpeed; } currentOffset += animationSpeed; animationSpeed *= deceleration; // Gradually slow down // Stop condition if (animationSpeed < 2 && currentOffset >= targetOffset - slotSpacing) { // Snap to final position var finalOffset = Math.round(currentOffset / slotSpacing) * slotSpacing; var adjustment = finalOffset - currentOffset; for (var rf = 0; rf < rewardDisplays.length; rf++) { rewardDisplays[rf].x -= adjustment; } // Show final result after short delay LK.setTimeout(function () { finishCaseAnimation(caseOverlay, finalReward); }, 1000); return; } // Continue animation LK.setTimeout(animateSpinning, 16); // ~60fps } // Start the animation animateSpinning(); } function finishCaseAnimation(overlay, reward) { // Award the reward money += reward.value; storage.money = money; if (typeof moneyTxt !== 'undefined' && moneyTxt.setText) { moneyTxt.setText(money + " $"); } // Flash screen with rarity color LK.effects.flashScreen(reward.color, 800); // Play sound effect if (reward.rarity === "legendary") { LK.getSound('Remote').play(); } else { LK.getSound('Shot').play(); } // Close animation after delay LK.setTimeout(function () { game._caseAnimationActive = false; freezeEnemies = false; if (overlay && typeof overlay.destroy === 'function') { overlay.destroy(); } }, 2000); } LK.gui.topRight.addChild(settingsIcon); // Shop button next to settings icon var shopIcon = new Text2("🛒", { size: 120, fill: 0xFFE066, align: "right" }); shopIcon.anchor.set(1, 0); // Right-top anchor shopIcon.x = -340; // Position to the left of upgrade icon shopIcon.y = 300; // Same height as settings and upgrade icons shopIcon.interactive = true; // Add pulsing glow effect to make it more noticeable tween(shopIcon, { alpha: 0.7 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { tween(shopIcon, { alpha: 1.0 }, { duration: 1500, easing: tween.easeInOut, loop: true }); } }); shopIcon.down = function (x, y, obj) { // Open shop menu (currently just opens upgrade menu as placeholder) if (game._upgradeMenuActive) return; // Prevent multiple menus openUpgradeMenu(); // Placeholder - opens upgrade menu for now }; LK.gui.topRight.addChild(shopIcon); // Create black hole in top right corner var blackHole = LK.getAsset('blackHole', { anchorX: 0.5, anchorY: 0.5 }); blackHole.x = -120; // Position from right edge blackHole.y = 120; // Position from top edge LK.gui.topRight.addChild(blackHole); // Add swirling animation to black hole tween(blackHole, { rotation: Math.PI * 2 }, { duration: 2000, easing: tween.easeLinear, loop: true }); var enemiesKilled = 0; // Counter for killed enemies, to trigger boss var bossEnemyInstance = null; // Holds the boss instance var bossSpawned = false; // Flag to ensure boss spawns only once per game var homingMissiles = []; // Array for new boss's homing missiles var giantBossEnemyInstance = null; // Instance for the new giant boss var giantBossSpawned = false; // Flag for the new giant boss // Ammo counter removed as per requirements // Center player ship horizontally, place near bottom playerShip.x = 2048 / 2; playerShip.y = 2732 - 350; // Create Joystick joystick = new Joystick(); game.addChild(joystick); // Set initial position (e.g., 0,0), though it will jump to touch. joystick.x = 0; joystick.y = 0; // Enemy spawn multiplier (default 1, can be set to 3 for 5 seconds after Card2) game._enemySpawnMultiplier = 1; game._enemySpawnMultiplierTimeout = null; // Create and add Enemy Ships var enemyShip1 = new EnemyShip(); var enemyShipAssetHeight1 = enemyShip1.shipSprite && enemyShip1.shipSprite.height ? enemyShip1.shipSprite.height : 146; // Default to asset height enemyShip1.x = 2048 / 3; enemyShip1.y = -(enemyShipAssetHeight1 / 2) - 50; // Start off-screen at the top enemyShip1.lastX = enemyShip1.x; // Initialize lastX to the actual starting x enemyShip1.lastY = enemyShip1.y; // Initialize lastY to the actual starting y game.addChild(enemyShip1); enemyShips.push(enemyShip1); var enemyShip2 = new EnemyShip(); var enemyShipAssetHeight2 = enemyShip2.shipSprite && enemyShip2.shipSprite.height ? enemyShip2.shipSprite.height : 146; // Default to asset height enemyShip2.x = 2048 * 2 / 3; enemyShip2.y = -(enemyShipAssetHeight2 / 2) - 150; // Start off-screen, staggered further up enemyShip2.lastX = enemyShip2.x; // Initialize lastX to the actual starting x enemyShip2.lastY = enemyShip2.y; // Initialize lastY to the actual starting y game.addChild(enemyShip2); enemyShips.push(enemyShip2); // Game event handlers // Create and position Fire Button var fireButton = new FireButton(); LK.gui.bottomLeft.addChild(fireButton); // Position the fire button visually in the bottom-left corner with some margin // The buttonSprite is 200x200 and anchored at its center (0.5, 0.5) // fireButton (Container) is added to LK.gui.bottomLeft, positioning its top-left (0,0) at screen bottom-left. // Adjust x and y to make the button appear correctly. var buttonMargin = 50; // 50px margin from screen edges fireButton.x = fireButton.buttonSprite.width / 2 + buttonMargin; fireButton.y = -fireButton.buttonSprite.height / 2 - buttonMargin; // Negative Y moves up from the bottom edge // Create green dash button above fire button (initially hidden) var greenDashButton = new Container(); var greenDashButtonSprite = greenDashButton.attachAsset('fireButtonSprite', { anchorX: 0.5, anchorY: 0.5, tint: 0x00FF00, // Green color scaleX: 0.6, // Smaller than fire button scaleY: 0.6 }); greenDashButton.visible = dashPurchased; // Show if dash was already purchased greenDashButton.lastDashTime = 0; // Track last dash time for cooldown LK.gui.bottomLeft.addChild(greenDashButton); // Position green dash button above fire button greenDashButton.x = fireButton.x; greenDashButton.y = fireButton.y - fireButton.buttonSprite.height - 20; // 20px spacing above fire button greenDashButton.down = function (x, y, obj) { if (!playerShip || !dashPurchased || !playerShip.dashAvailable) return; // Check 3-second cooldown (180 ticks at 60fps) var currentTime = LK.ticks; if (currentTime - greenDashButton.lastDashTime < 180) return; // Push ship forward in current facing direction var pushDistance = 250; // Distance to push forward var pushDirection = playerShip.currentAngle; // Ship's current angle var pushX = Math.cos(pushDirection) * pushDistance; var pushY = Math.sin(pushDirection) * pushDistance; // Apply push over time for smooth movement tween(playerShip, { x: playerShip.x + pushX, y: playerShip.y + pushY }, { duration: 300, // 300ms dash duration easing: tween.easeOut }); // Update cooldown greenDashButton.lastDashTime = currentTime; // Visual effect for dash activation if (playerShip.shipSprite) { LK.effects.flashObject(playerShip.shipSprite, 0x00FF00, 200); } // Add button press effect tween(greenDashButtonSprite, { scaleX: 0.5, scaleY: 0.5 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(greenDashButtonSprite, { scaleX: 0.6, scaleY: 0.6 }, { duration: 100, easing: tween.easeOut }); } }); }; game.down = function (x, y, obj) { if (game._cardChoiceActive || game._skinMenuActive) { // Block joystick and ship movement while card choice or skin menu is active return; } // x, y are global game coordinates // Move the joystick to the touch position joystick.x = x; joystick.y = y; joystick.visible = true; // Activate the joystick. Since the joystick's origin is now at the touch point (x,y), // the local coordinates for the touch within the joystick are (0,0). // This centers the knob under the finger and initiates dragging. joystick.handleDown(0, 0); // Pass (0,0) as local coordinates // Note: With this "floating joystick" implementation, the joystick activates on any screen press. // The previous mechanic, where tapping away from a fixed joystick fired projectiles, // is superseded. Further design would be needed for a separate firing mechanism. }; game.move = function (x, y, obj) { if (game._cardChoiceActive || game._skinMenuActive) { // Block joystick movement while card choice or skin menu is active return; } if (joystick.isActive()) { var joystickLocalPos = joystick.toLocal({ x: x, y: y }); joystick.handleMove(joystickLocalPos.x, joystickLocalPos.y); } }; game.up = function (x, y, obj) { if (game._cardChoiceActive || game._skinMenuActive) { // Block joystick release while card choice or skin menu is active return; } if (joystick.isActive()) { joystick.handleUp(); // Resets knob, inputVector, and isDragging flag joystick.visible = false; // Hide the joystick when the touch is released } }; // Update game state var ammoRestoreCooldown = 0; // Ticks until next ammo restore // Start with space music LK.playMusic('Space'); game.update = function () { // Prevent all game logic and object updates while card choice, skin menu, or case animation overlay is active if (game._cardChoiceActive || game._skinMenuActive || game._caseAnimationActive) { // Only allow overlay input, skip all updates and movement return; } // Restore ammo if not full, with a delay (e.g., 30 ticks = 0.5s) if (typeof playerAmmo !== 'undefined') { if (typeof maxPlayerAmmo === 'undefined') maxPlayerAmmo = 10; // Defensive: ensure maxPlayerAmmo is defined if (playerAmmo < maxPlayerAmmo) { // Use maxPlayerAmmo instead of hardcoded 10 if (typeof ammoRestoreCooldown === 'undefined') ammoRestoreCooldown = 0; if (ammoRestoreCooldown > 0) { ammoRestoreCooldown--; } else { playerAmmo += 1; // Removed ammo counter update as per requirements ammoRestoreCooldown = 30; // 0.5s at 60FPS } } else { // Player is at or above max ammo. // Ensure current ammo doesn't exceed new max if it somehow did, though unlikely with current regen. playerAmmo = Math.min(playerAmmo, maxPlayerAmmo); ammoRestoreCooldown = 0; } } // Apply joystick input to player ship movement if (joystick && playerShip && playerShip.applyJoystickInput) { var input = joystick.getInput(); playerShip.applyJoystickInput(input.x, input.y); } // Manage drones if active if (dronesActive && playerShip && !playerShip.isDestroyed) { // Spawn new drones if under limit droneSpawnCooldown--; if (droneSpawnCooldown <= 0 && playerDrones.length < maxDrones) { var newDrone = new Drone(); newDrone.x = playerShip.x; newDrone.y = playerShip.y; newDrone.orbitAngle = playerDrones.length * (Math.PI * 2) / maxDrones; // Spread evenly game.addChild(newDrone); playerDrones.push(newDrone); droneSpawnCooldown = 300; // 5 second cooldown between spawns } // Update existing drones for (var d = playerDrones.length - 1; d >= 0; d--) { var drone = playerDrones[d]; if (drone.update) { drone.update(); } if (drone.isDestroyed) { drone.destroy(); playerDrones.splice(d, 1); } } } // Update player ship (e.g., to recalculate projectileSpawnPoint after moving) if (playerShip && playerShip.update) { playerShip.update(); } // Create trail segments behind player trailSpawnTimer++; if (trailSpawnTimer >= 3 && playerShip && !playerShip.isDestroyed && trailEnabled) { var trailSegment = new PlayerTrail(); trailSegment.x = playerShip.x; trailSegment.y = playerShip.y; // Get current trail color from storage var currentTrailColor = storage.trailColor || 0xff3333; // Add bright pulsing effect to make trail more vibrant with selected color if (trailSegment.trailSprite) { // Calculate a lighter version of the selected color for pulsing effect var lighterColor = currentTrailColor; // Add brightness by increasing RGB values proportionally var r = Math.min(255, (currentTrailColor >> 16 & 0xFF) + 50); var g = Math.min(255, (currentTrailColor >> 8 & 0xFF) + 50); var b = Math.min(255, (currentTrailColor & 0xFF) + 50); lighterColor = r << 16 | g << 8 | b; trailSegment.trailSprite.tint = currentTrailColor; tween(trailSegment.trailSprite, { tint: lighterColor }, { duration: 200, easing: tween.easeInOut, onFinish: function onFinish() { tween(trailSegment.trailSprite, { tint: currentTrailColor }, { duration: 200, easing: tween.easeInOut }); } }); } game.addChild(trailSegment); playerTrailSegments.push(trailSegment); trailSpawnTimer = 0; } // Create trail segments behind enemies enemyTrailSpawnTimer++; if (enemyTrailSpawnTimer >= 4) { // Create trails for all active enemies for (var e = 0; e < enemyShips.length; e++) { var enemy = enemyShips[e]; if (!enemy.isDestroyed && !enemy.isOffScreen) { var enemyTrailSegment = new PlayerTrail(); enemyTrailSegment.x = enemy.x; enemyTrailSegment.y = enemy.y; // Make enemy trails orange/yellow color if (enemyTrailSegment.trailSprite) { enemyTrailSegment.trailSprite.tint = 0xff6600; tween(enemyTrailSegment.trailSprite, { tint: 0xffaa33 }, { duration: 200, easing: tween.easeInOut, onFinish: function onFinish() { tween(enemyTrailSegment.trailSprite, { tint: 0xff6600 }, { duration: 200, easing: tween.easeInOut }); } }); } game.addChild(enemyTrailSegment); enemyTrailSegments.push(enemyTrailSegment); } } enemyTrailSpawnTimer = 0; } // Update and clean up trail segments for (var t = playerTrailSegments.length - 1; t >= 0; t--) { var trailSeg = playerTrailSegments[t]; if (trailSeg.update) { trailSeg.update(); } // Calculate alpha based on remaining life (1.0 to 0.0) var alpha = trailSeg.currentLife / trailSeg.lifeTime; if (trailSeg.trailSprite) { trailSeg.trailSprite.alpha = Math.max(0.3, alpha); // Keep minimum visibility of 0.3 } if (trailSeg.isExpired) { trailSeg.destroy(); playerTrailSegments.splice(t, 1); } } // Update and clean up enemy trail segments for (var et = enemyTrailSegments.length - 1; et >= 0; et--) { var enemyTrailSeg = enemyTrailSegments[et]; if (enemyTrailSeg.update) { enemyTrailSeg.update(); } // Calculate alpha based on remaining life (1.0 to 0.0) var enemyAlpha = enemyTrailSeg.currentLife / enemyTrailSeg.lifeTime; if (enemyTrailSeg.trailSprite) { enemyTrailSeg.trailSprite.alpha = Math.max(0.2, enemyAlpha); // Slightly more transparent than player trails } if (enemyTrailSeg.isExpired) { enemyTrailSeg.destroy(); enemyTrailSegments.splice(et, 1); } } // Update and clean up projectiles, and check for collisions with enemies for (var i = playerProjectiles.length - 1; i >= 0; i--) { var proj = playerProjectiles[i]; if (proj.update) { proj.update(); // Call projectile's own update logic (movement) } // Check if the projectile flagged itself as off-screen AFTER moving if (proj.isOffScreen) { proj.destroy(); // Destroy the projectile playerProjectiles.splice(i, 1); // Remove from the tracking array continue; // Move to the next projectile } // Collision detection with enemy ships for (var k = enemyShips.length - 1; k >= 0; k--) { var enemy = enemyShips[k]; // Skip already destroyed or off-screen enemies for collision checks if (enemy.isDestroyed || enemy.isOffScreen) { continue; } if (proj.intersects(enemy)) { // Use drone damage if projectile is from drone, otherwise use playerProjectileDamage var dmg; if (typeof proj.droneDamage !== 'undefined') { dmg = proj.droneDamage; // Use 0.25 damage for drone projectiles } else { dmg = typeof playerProjectileDamage !== 'undefined' ? playerProjectileDamage : 1; } if (typeof enemy.health === 'number') { enemy.health -= dmg; if (enemy.health <= 0) { enemy.isDestroyed = true; } else { if (typeof enemy.hit === 'function') enemy.hit(); } } else { if (typeof enemy.hit === 'function') enemy.hit(); } proj.destroy(); // Projectile is consumed on hit playerProjectiles.splice(i, 1); // Remove projectile // If enemy.isDestroyed became true, it will be handled in the enemyShips loop below. // Score will be awarded there too. break; // Projectile is gone, no need to check against other enemies } } // If projectile still exists, check collision with boss parts if (!proj.isOffScreen && bossEnemyInstance && !bossEnemyInstance.isDestroyed) { var bossPartsToCheck = []; if (bossEnemyInstance.mainBody && !bossEnemyInstance.mainBody.isDestroyed) { bossPartsToCheck.push(bossEnemyInstance.mainBody); } if (bossEnemyInstance.leftCannon && !bossEnemyInstance.leftCannon.isDestroyed) { bossPartsToCheck.push(bossEnemyInstance.leftCannon); } if (bossEnemyInstance.rightCannon && !bossEnemyInstance.rightCannon.isDestroyed) { bossPartsToCheck.push(bossEnemyInstance.rightCannon); } for (var bp_idx = 0; bp_idx < bossPartsToCheck.length; bp_idx++) { var part = bossPartsToCheck[bp_idx]; if (proj.intersects(part)) { // Use drone damage if projectile is from drone, otherwise use playerProjectileDamage var dmg; if (typeof proj.droneDamage !== 'undefined') { dmg = proj.droneDamage; // Use 0.25 damage for drone projectiles } else { dmg = typeof playerProjectileDamage !== 'undefined' ? playerProjectileDamage : 1; } part.hit(dmg); // Part handles its own health and destruction visuals if (part.isDestroyed) { // Award points/money for destroying a part if (part instanceof BossCannon) { LK.setScore(LK.getScore() + 50); // More points for a cannon money += 25; storage.money = money; // Save money to storage if (typeof moneyTxt !== 'undefined' && moneyTxt.setText) { moneyTxt.setText(money + " $"); } } // Main body destruction score is handled when boss is fully defeated } proj.destroy(); // Projectile is consumed playerProjectiles.splice(i, 1); break; // Projectile is gone, stop checking this projectile against other boss parts } } } // If proj was spliced (either by hitting enemy or boss part), loop continues correctly. } // Update and clean up enemy projectiles, and check for collisions with player for (var l = enemyProjectiles.length - 1; l >= 0; l--) { var eProj = enemyProjectiles[l]; if (eProj.update) { eProj.update(); // Call projectile's own update logic (movement) } // Check if the projectile flagged itself as off-screen AFTER moving if (eProj.isOffScreen) { if (typeof eProj.destroy === 'function') eProj.destroy(); enemyProjectiles.splice(l, 1); // Remove from the tracking array continue; // Move to the next projectile } // Collision detection with player ship // Ensure playerShip exists and is not already destroyed before checking intersection // Skip collision during case animation to make player invulnerable if (playerShip && !playerShip.isDestroyed && !game._caseAnimationActive && typeof eProj.intersects === 'function' && eProj.intersects(playerShip)) { // Check if the projectile's parent is an ArmoredEnemyShip (for 0.5 damage) var isArmored = false; if (typeof enemyShips !== 'undefined') { for (var ai = 0; ai < enemyShips.length; ai++) { var es = enemyShips[ai]; if (es instanceof ArmoredEnemyShip && es.enemyProjectileSpawnPoint && Math.abs(eProj.x - es.enemyProjectileSpawnPoint.x) < 2 && Math.abs(eProj.y - es.enemyProjectileSpawnPoint.y) < 2) { isArmored = true; break; } } } if (isArmored) { // Deal 0.5 damage: use a fractional health system if (typeof playerShip.health === 'number') { if (typeof playerShip._fractionalHealth === 'undefined') playerShip._fractionalHealth = playerShip.health; playerShip._fractionalHealth -= 0.5; if (playerShip._fractionalHealth <= 0) { playerShip.health = 0; playerShip.isDestroyed = true; if (playerShip.shipSprite) playerShip.shipSprite.visible = false;else if (typeof playerShip.visible !== 'undefined') playerShip.visible = false; LK.showGameOver(); } else { playerShip.health = Math.floor(playerShip._fractionalHealth); LK.effects.flashObject(playerShip.shipSprite || playerShip, 0xff0000, 150); // Update hearts display for (var i = 0; i < hearts.length; i++) { hearts[i].visible = i < playerShip.health; } } } } else { if (typeof playerShip.hit === 'function') playerShip.hit(); // Player takes damage if (playerShip.isDestroyed) { if (playerShip.shipSprite) playerShip.shipSprite.visible = false;else if (typeof playerShip.visible !== 'undefined') playerShip.visible = false; LK.showGameOver(); } } if (typeof eProj.destroy === 'function') eProj.destroy(); // Projectile is consumed on hit enemyProjectiles.splice(l, 1); // Remove projectile // If projectile hit player, it's gone. No need to check further for this projectile. } } // Update and clean up enemy ships // This loop should still run even if player is destroyed, to clean up existing enemies, // though new game state might prevent further updates if LK.showGameOver() is effective immediately. var _loop = function _loop() { enemy = enemyShips[j]; // First, check if the enemy ship is marked as destroyed (e.g., by a projectile hit) if (enemy.isDestroyed) { // Capture enemy's current position for the explosion sequence var explosionX = enemy.x; var explosionY = enemy.y; // Show Boom image at the captured position var localBoomImg1 = LK.getAsset('Boom', { // Use a new local variable for the first boom image anchorX: 0.5, anchorY: 0.5, x: explosionX, // Use captured X coordinate y: explosionY // Use captured Y coordinate }); game.addChild(localBoomImg1); // Remove Boom image after a short delay (e.g., 125ms), then show Boom2 at the same position LK.setTimeout(function () { if (localBoomImg1 && typeof localBoomImg1.destroy === 'function') localBoomImg1.destroy(); // Destroy the correct local image // Show Boom2 image at the same captured position var boom2Img = LK.getAsset('Boom2', { // This was already var, so local, good. anchorX: 0.5, anchorY: 0.5, x: explosionX, // Use captured X y: explosionY // Use captured Y }); game.addChild(boom2Img); // Remove Boom2 image after a short delay (e.g., 125ms), then show Boom3 at the same position LK.setTimeout(function () { if (boom2Img && typeof boom2Img.destroy === 'function') boom2Img.destroy(); // Show Boom3 image at the same captured position var boom3Img = LK.getAsset('Boom3', { anchorX: 0.5, anchorY: 0.5, x: explosionX, // Use captured X y: explosionY // Use captured Y }); game.addChild(boom3Img); // Remove Boom3 image after a short delay (e.g., 125ms), then show Boom4 at the same position LK.setTimeout(function () { if (boom3Img && typeof boom3Img.destroy === 'function') boom3Img.destroy(); // Show Boom4 image at the same captured position var boom4Img = LK.getAsset('Boom4', { anchorX: 0.5, anchorY: 0.5, x: explosionX, // Use captured X y: explosionY // Use captured Y }); game.addChild(boom4Img); // Remove Boom4 image after a short delay (e.g., 125ms), then show Boom5 at the same position LK.setTimeout(function () { if (boom4Img && typeof boom4Img.destroy === 'function') boom4Img.destroy(); // Show Boom5 image at the same captured position var boom5Img = LK.getAsset('Boom5', { anchorX: 0.5, anchorY: 0.5, x: explosionX, // Use captured X y: explosionY // Use captured Y }); game.addChild(boom5Img); // Remove Boom5 image after a short delay (e.g., 125ms), then show Boom6 at the same position LK.setTimeout(function () { if (boom5Img && typeof boom5Img.destroy === 'function') boom5Img.destroy(); // Show Boom6 image at the same captured position var boom6Img = LK.getAsset('Boom6', { anchorX: 0.5, anchorY: 0.5, x: explosionX, // Use captured X y: explosionY // Use captured Y }); game.addChild(boom6Img); // Remove Boom6 image after a short delay (e.g., 125ms) LK.setTimeout(function () { if (boom6Img && typeof boom6Img.destroy === 'function') boom6Img.destroy(); }, 125); }, 125); }, 125); }, 125); }, 125); }, 125); enemy.destroy(); // Destroy the enemy ship enemyShips.splice(j, 1); // Remove from the tracking array LK.setScore(LK.getScore() + 10); // Award score for destroying an enemy money += 5; // Add 5 money per enemy destroyed storage.money = money; // Save money to storage if (typeof moneyTxt !== 'undefined' && moneyTxt.setText) { moneyTxt.setText(money + " $"); } LK.getSound('Boom').play(); // Play 'Boom' sound effect // 25% chance to drop a health restoration if (Math.random() < 0.25) { healthRestore = new HealthRestore(); healthRestore.x = enemy.x; healthRestore.y = enemy.y; game.addChild(healthRestore); } // --- Card choice after 3 kills --- enemiesKilled++; // --- Boss Spawn Logic --- // Spawn original boss after 10 kills if (!bossSpawned && bossEnemyInstance === null && enemiesKilled >= 10) { bossEnemyInstance = new BossEnemy(); bossEnemyInstance.x = 2048 / 2; bossEnemyInstance.y = 450; game.addChild(bossEnemyInstance); bossSpawned = true; LK.playMusic('Boss'); } // --- End Boss Spawn Logic --- if (enemiesKilled > 0 && enemiesKilled % 3 === 0 && !game._cardChoiceActive) { // Helper to finish card choice and resume game var finishCardChoice = function finishCardChoice() { if (!game._cardChoiceActive) return; game._cardChoiceActive = false; freezeEnemies = false; if (cardOverlay && typeof cardOverlay.destroy === 'function') cardOverlay.destroy(); // Restore input game.down = oldGameDown; game.move = oldGameMove; game.up = oldGameUp; // Resume game logic // No need to call LK.resumeGame(); LK engine will resume game after overlay is destroyed }; game._cardChoiceActive = true; // Freeze all enemy movement and firing freezeEnemies = true; // --- Block card selection for 2 seconds --- game._cardChoiceBlock = true; LK.setTimeout(function () { game._cardChoiceBlock = false; }, 2000); // Pause game logic is handled by LK.showGameOver() and LK.resumeGame(), no need to call LK.pause() here // Create overlay for card choice cardOverlay = new Container(); cardOverlay.name = "cardChoiceOverlay"; // Semi-transparent background bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 20, scaleY: 20, x: 2048 / 2, y: 2732 / 2, tint: 0x000000, alpha: 0.7 }); cardOverlay.addChild(bg); // --- Define all available cards and their effects --- var allAvailableCards = [{ key: "card1_ammo_firerate", // Card1: More Ammo, Faster Fire Rate asset: "Card1", applyEffect: function applyEffect() { if (typeof maxPlayerAmmo === 'undefined') maxPlayerAmmo = 10; // Defensive initialization maxPlayerAmmo += 2; // Increase maximum ammo capacity playerAmmo = Math.min(playerAmmo + 2, maxPlayerAmmo); // Add 2 to current ammo, capped by new maxPlayerAmmo fireButtonCooldown = Math.max(1, Math.floor(fireButtonCooldown / 2)); // Halve cooldown, min 1 } }, { key: "card2_speed_spawn_heal", // Card2: Speed Boost, Enemy Spawn Boost, Heal 1 HP asset: "Csrd2", applyEffect: function applyEffect() { if (playerShip) { if (typeof playerShip._originalMoveSpeed === 'undefined') { playerShip._originalMoveSpeed = playerShip.moveSpeed; } playerShip.moveSpeed = playerShip._originalMoveSpeed * 2; // Speed boost is now permanent, timeout removed. // Heal 1 HP if not at max health var currentMaxHealth = playerShip.maxHealth || 3; if (playerShip.health < currentMaxHealth) { playerShip.health = Math.min(playerShip.health + 1, currentMaxHealth); if (typeof playerShip._fractionalHealth !== 'undefined') { // Ensure fractional health is updated playerShip._fractionalHealth = playerShip.health; } for (var i = 0; i < hearts.length; i++) { hearts[i].visible = i < playerShip.health; } } } // Removed enemy spawn multiplier effect } }, { key: "cardhp_maxhp_heal", // Cardhp: Increase Max HP, Heal 1 HP asset: "Cardhp", applyEffect: function applyEffect() { if (playerShip) { if (typeof playerShip.maxHealth === 'undefined') { playerShip.maxHealth = 3; } playerShip.maxHealth += 1; playerShip.health = Math.min(playerShip.health + 1, playerShip.maxHealth); // Heal 1 HP up to new max if (typeof playerShip._fractionalHealth !== 'undefined') { // Ensure fractional health is updated playerShip._fractionalHealth = playerShip.health; } // Add a new heart icon if needed (maxHealth might exceed initial hearts array length) // Ensure heart icons match current maxHealth and health while (hearts.length < playerShip.maxHealth) { var newHeart = new Heart(); newHeart.x = hearts.length * 120 + 120; newHeart.y = 60; LK.gui.topLeft.addChild(newHeart); hearts.push(newHeart); } for (var i = 0; i < hearts.length; i++) { hearts[i].visible = i < playerShip.health; } } } }, { key: "cardsttsck_doubledamage", // Cardsttsck: Double Projectile Damage asset: "Cardsttsck", applyEffect: function applyEffect() { if (typeof playerProjectileDamage === 'undefined') { playerProjectileDamage = 1; } playerProjectileDamage *= 2; } }, { // Adding new card Asplitmind key: "asplitmind_split_shot", asset: "Asplitmind", applyEffect: function applyEffect() { // playerSplitShotActive is globally defined and initialized to false. // This effect makes the split shot permanently true. playerSplitShotActive = true; } }]; // --- Shuffle and select 3 cards to display --- // Fisher-Yates shuffle for (var k = allAvailableCards.length - 1; k > 0; k--) { var j_shuffle = Math.floor(Math.random() * (k + 1)); var tempCard = allAvailableCards[k]; allAvailableCards[k] = allAvailableCards[j_shuffle]; allAvailableCards[j_shuffle] = tempCard; } var cardsToDisplay = allAvailableCards.slice(0, 3); // Get the first 3 cards // --- Position and display selected cards --- var cardPositions = [{ x: 2048 / 2 - 450, y: 2732 / 2 }, // Left card { x: 2048 / 2, y: 2732 / 2 }, // Middle card { x: 2048 / 2 + 450, y: 2732 / 2 } // Right card ]; for (var cardIdx = 0; cardIdx < cardsToDisplay.length; cardIdx++) { var cardData = cardsToDisplay[cardIdx]; var cardContainer = new Container(); // Create a new container for each card // The image itself is positioned globally based on cardPositions var cardImg = LK.getAsset(cardData.asset, { anchorX: 0.5, anchorY: 0.5, x: cardPositions[cardIdx].x, y: cardPositions[cardIdx].y }); cardContainer.addChild(cardImg); // The container itself can be at 0,0 as its child (the image) is globally positioned. // However, for consistency with how interactives are often handled, // we can also set the container's position if we wanted the image local to it. // For this setup, global positioning of the image is fine. cardContainer.x = 0; cardContainer.y = 0; cardContainer.interactive = true; // Use a closure to correctly capture cardData for each event handler (function (currentCardData) { cardContainer.down = function (x_down, y_down, obj_down) { if (!game._cardChoiceActive || game._cardChoiceBlock) return; currentCardData.applyEffect(); finishCardChoice(); }; })(cardData); cardOverlay.addChild(cardContainer); } // Add overlay to game game.addChild(cardOverlay); // Block all input except card choice oldGameDown = game.down; oldGameMove = game.move; oldGameUp = game.up; game.down = function (x, y, obj) { if (game._cardChoiceBlock) return; // Defensive: Only forward to cards that exist in overlay if (cardOverlay && cardOverlay.children) { for (var i = 0; i < cardOverlay.children.length; i++) { var child = cardOverlay.children[i]; if (child && child.interactive && typeof child.down === "function" && child.children && child.children.length > 0) { var img = child.children[0]; if (img && typeof child.toLocal === "function") { var local = child.toLocal({ x: x, y: y }); if (img.width && img.height && Math.abs(local.x) < img.width / 2 && Math.abs(local.y) < img.height / 2) { child.down(x, y, obj); return; } } } } } }; game.move = function () {}; game.up = function () {}; } // } // if (typeof scoreTxt !== 'undefined' && scoreTxt && scoreTxt.setText) { // scoreTxt.setText(LK.getScore()); // } return 1; // continue // Move to the next enemy ship in the list } if (enemy.update) { enemy.update(); // Call enemy ship's own update logic (movement, etc.) } // Then, check if the enemy ship flagged itself as off-screen (if not already destroyed) if (enemy.isOffScreen) { enemy.destroy(); // Destroy the enemy ship enemyShips.splice(j, 1); // Remove from the tracking array // No score for merely going off-screen, unless desired. } }, enemy, boomImg, healthRestore, cardOverlay, bg, card1, card1Bg, card1Text, card2, card2Bg, card2Text, oldGameDown, oldGameMove, oldGameUp; for (var j = enemyShips.length - 1; j >= 0; j--) { if (_loop()) continue; } // Increase the number of enemies over time // Spawn regular enemies only if NEITHER boss is currently active var originalBossActive = bossEnemyInstance && !bossEnemyInstance.isDestroyed; var giantBossActive = giantBossEnemyInstance && !giantBossEnemyInstance.isDestroyed; if (!originalBossActive && !giantBossActive && LK.ticks % Math.max(30, 150 - Math.floor(LK.ticks / 300)) === 0) { // Determine multiplier (default 1, or 3x if card2 effect active) var multiplier = typeof game._enemySpawnMultiplier !== 'undefined' && game._enemySpawnMultiplier > 1 ? game._enemySpawnMultiplier : 1; for (var spawnIdx = 0; spawnIdx < multiplier; spawnIdx++) { // 25% chance to spawn armored enemy, else normal var newEnemyShip; if (Math.random() < 0.25 && typeof ArmoredEnemyShip !== 'undefined') { newEnemyShip = new ArmoredEnemyShip(); } else { newEnemyShip = new EnemyShip(); } var enemyShipAssetHeight = newEnemyShip.shipSprite && newEnemyShip.shipSprite.height ? newEnemyShip.shipSprite.height : 146; newEnemyShip.x = Math.random() * 2048; newEnemyShip.y = -(enemyShipAssetHeight / 2) - 50; newEnemyShip.lastX = newEnemyShip.x; newEnemyShip.lastY = newEnemyShip.y; game.addChild(newEnemyShip); enemyShips.push(newEnemyShip); } } // --- Boss Update and Defeat Logic --- if (bossEnemyInstance) { if (bossEnemyInstance.isDestroyed) { // Duration each explosion frame is visible (ms) // Helper function to manage the chained explosion sequence var _showNextExplosion = function showNextExplosion(index, previousExplosionAsset) { // Clean up the previous explosion asset from the screen if (previousExplosionAsset && typeof previousExplosionAsset.destroy === 'function') { previousExplosionAsset.destroy(); } // Check if there are more explosion frames to show if (index < explosionAssets.length) { var assetId = explosionAssets[index]; var currentExplosion = LK.getAsset(assetId, { anchorX: 0.5, anchorY: 0.5, x: explosionX, y: explosionY, scaleX: 1.5, scaleY: 1.5 // Make boss explosions bigger }); game.addChild(currentExplosion); LK.getSound('Boom').play(); // Play sound for each frame LK.setTimeout(function () { _showNextExplosion(index + 1, currentExplosion); }, explosionAssetDuration); } else { // All explosion frames for the first boss are done bossEnemyInstance = null; // Officially nullify the global reference after explosion // Check if giant boss is NOT active (or not spawned / already destroyed) if (!giantBossEnemyInstance || giantBossEnemyInstance.isDestroyed) { LK.stopMusic(); // Stop current music first LK.playMusic('Space'); // Resume normal gameplay music } // If giant boss IS active, its spawn logic would have (or will) set 'Boss' music. } }; // End of _showNextExplosion definition // Boss has been defeated (main body destroyed) LK.setScore(LK.getScore() + 250); // Big score for defeating the boss money += 100; storage.money = money; // Save money to storage if (typeof moneyTxt !== 'undefined' && moneyTxt.setText) { moneyTxt.setText(money + " $"); } // Grand explosion for boss defeat var explosionX = bossEnemyInstance.x + (bossEnemyInstance.mainBody ? bossEnemyInstance.mainBody.x : 0); var explosionY = bossEnemyInstance.y + (bossEnemyInstance.mainBody ? bossEnemyInstance.mainBody.y : 0); // Grand explosion for boss defeat is handled by a chained sequence // Define explosion parameters (position is already defined above this block) var explosionAssets = ['Boom', 'Boom2', 'Boom3', 'Boom4', 'Boom5', 'Boom6']; var explosionAssetDuration = 150; if (explosionAssets.length > 0) { // Initial call to start the sequence with the first asset (index 0) // There's no previousExplosionAsset for the first frame. _showNextExplosion(0, null); } bossEnemyInstance.destroy(); // Remove boss from game (destroys the actual game object) // Note: bossEnemyInstance = null; and LK.playMusic('Space'); have been moved into _showNextExplosion // Optionally, trigger win condition or next phase // LK.showYouWin(); } else { bossEnemyInstance.update(); // Update active boss } } }; // Music will be managed by game state - boss music during boss fights, space music otherwise ; ;
===================================================================
--- original.js
+++ change.js
Звездолет вид сверху два д для 2d игры пиксельный. In-Game asset
Красный лазерный луч пиксельный вид сверху. In-Game asset. 2d. High contrast. No shadows
Пиксельная шестерёнка с гаечным ключом. In-Game asset. 2d. High contrast. No shadows
Карточка с изоброжение скорости атака пиксельная карточка улучшения пиксельная космическая. In-Game asset. 2d. High contrast. No shadows
Карта усиления пиксельная усиливает скорость игрока космическая 2д пиксели. In-Game asset. 2d. High contrast. No shadows
Бронированный летающий корабль звездолет пиксельный вид сверху 2д. In-Game asset. 2d. High contrast. No shadows
Start в космической пмксельном стиле. In-Game asset. 2d. High contrast. No shadows
pixel inscription battle of starships in the style of space pixel art. 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
Пиксельная круглая кнопка атаки. 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