User prompt
Update with: // In Bubble.activate() self.twinPair = null; // Add this line to reset twin pair reference
Code edit (5 edits merged)
Please save this source code
User prompt
update with: if (stage === 3 || stage === 5 || stage === 7) { LK.setTimeout(function () { if (game.tutorialContainer) { game.tutorialContainer.destroy(); game.tutorialContainer = null; } }, 6000); }
Code edit (1 edits merged)
Please save this source code
User prompt
Update as needed with: if (LK.ticks % game.baseSpawnRate == 0) { var x = Math.random() * (game.width - 200) + 100; var titleBubble = spawnBubble(x, game.height + 100, 100, 0, true); // Apply saved color to the bubble if available if (titleBubble && game.titleColorSettings) { // Add this new code to immediately apply rainbow colors var activeColorKey = game.titleColorSettings.activeColor; var colorUnlocks = game.titleColorSettings.colorUnlocks; var isRainbowActive = (activeColorKey === "rainbowBubbles" || (activeColorKey === "auto" && colorUnlocks && colorUnlocks.length > 9 && parseInt(colorUnlocks[9]) > 0)); if (isRainbowActive) { var rainbowColors = [0xFF0000, 0xFFA500, 0xFFFF00, 0x00FF00, 0x0000FF, 0xFF00FF]; var colorChoice = Math.floor(Math.random() * rainbowColors.length); var bubbleSprite = titleBubble.children[0]; if (bubbleSprite) { bubbleSprite.tint = rainbowColors[colorChoice]; titleBubble.hasRainbowColor = true; } } else { applyTitleScreenColor(titleBubble); } } }
User prompt
Update with: else if (isRainbowActive) { // For rainbow, assign random color when first created var bubbleSprite = bubble.children[0]; if (bubbleSprite) { // Only assign a color if it hasn't been assigned yet if (!bubble.hasRainbowColor) { var rainbowColors = [0xFF0000, 0xFFA500, 0xFFFF00, 0x00FF00, 0x0000FF, 0xFF00FF]; var colorChoice = Math.floor(Math.random() * rainbowColors.length); bubbleSprite.tint = rainbowColors[colorChoice]; bubble.hasRainbowColor = true; } // Otherwise keep the color it already has } }
Code edit (2 edits merged)
Please save this source code
User prompt
Update with: game.activeBubbles.forEach(function (bubble) { // Apply dynamic color effects for rainbow/prismatic, or static colors for others if (bubble.visible && game.titleColorSettings) { var activeColorKey = game.titleColorSettings.activeColor; var colorUnlocks = game.titleColorSettings.colorUnlocks; // Check if rainbow or prismatic is unlocked and active var rainbowUnlocked = colorUnlocks.length >= 10 && parseInt(colorUnlocks[9]) > 0; var prismaticUnlocked = colorUnlocks.length >= 11 && parseInt(colorUnlocks[10]) > 0; if ((activeColorKey === "rainbowBubbles" && rainbowUnlocked) || (activeColorKey === "prismaticBubbles" && prismaticUnlocked) || (activeColorKey === "auto" && prismaticUnlocked)) { // Initialize color phase if needed if (!bubble.colorPhase) { bubble.colorPhase = Math.random() * Math.PI * 2; } // Apply prismatic effect if (activeColorKey === "prismaticBubbles" || (activeColorKey === "auto" && prismaticUnlocked)) { bubble.colorPhase += 0.02; var r = Math.sin(bubble.colorPhase) * 127 + 128; var g = Math.sin(bubble.colorPhase + 2) * 127 + 128; var b = Math.sin(bubble.colorPhase + 4) * 127 + 128; var color = (Math.floor(r) << 16) + (Math.floor(g) << 8) + Math.floor(b); // Apply the color to the bubble var bubbleSprite = bubble.children[0]; if (bubbleSprite) { bubbleSprite.tint = color; } } // Apply rainbow effect else if (activeColorKey === "rainbowBubbles" || (activeColorKey === "auto" && rainbowUnlocked && !prismaticUnlocked)) { // Only change colors occasionally for rainbow effect if (LK.ticks % 30 === 0 && Math.random() < 0.3) { var rainbowColors = [0xFF0000, 0xFFA500, 0xFFFF00, 0x00FF00, 0x0000FF, 0xFF00FF]; var colorChoice = Math.floor(Math.random() * rainbowColors.length); var bubbleSprite = bubble.children[0]; if (bubbleSprite) { bubbleSprite.tint = rainbowColors[colorChoice]; } } } } else { // For static colors, apply the regular method applyTitleScreenColor(bubble); } } if (bubble.update) { bubble.update(); } });
User prompt
Donβt fade the title music out, just stop immediately and play start button sound effect. Add screen flash and reduce timeout for start game back to 500
User prompt
The title music should fade out completely before the start button sound effect is played.
User prompt
Add start button sound effect to the touching the start button on the title screen. Add a little timeout to allow sound effect to finish before starting game.
User prompt
Update with: // Load tutorial status if (storage.tutorialComplete) { game.tutorial.stage = 6; // Set to a stage past the tutorial } βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
Update with: storage.tutorialComplete = game.tutorial.stage >= 5; βͺπ‘ Consider importing and using the following plugins: @upit/storage.v1
User prompt
Update with: if (game.tutorial.stage === 4 && menuOpen) { // No need to advance to stage 5 anymore since we removed it // Just ensure the tutorial is on top if (game.tutorialContainer) { game.setChildIndex(game.tutorialContainer, game.children.length - 1); } }
Code edit (1 edits merged)
Please save this source code
User prompt
Update with: // After a successful purchase in any upgrade category if (game.bp >= cost) { // [existing purchase code] game.bp -= cost; // Add this check after the purchase is processed if (game.tutorial.stage === 4) { game.tutorial.boughtClam = true; // We'll keep the same variable name for compatibility showTutorialPopup(5); // Show the final tutorial message } // [rest of existing code] }
User prompt
Update with: switch (stage) { case 1: // Welcome message = "Welcome to Bubble Blower Tycoon! Open your mouth to start growing a bubble and close to release it. Go ahead and try!"; break; case 2: // After blowing bubble message = "Great! Now go ahead and pop it to collect Bubble Points (BP)."; break; case 3: // After popping message = "That's all there is to it! Now get popping!"; break; case 4: // Can afford upgrade message = "It looks like you've saved enough BP. Open the 'Upgrades' menu at the bottom and choose any upgrade you can afford!"; break; case 5: // After buying upgrade (formerly stage 7) message = "Great choice! Continue exploring different upgrades to build your bubble empire. Good luck on becoming the Bubble Blower Tycoon!"; break; }
User prompt
Update with: case 6: // Any upgrade message = "Choose any upgrade you can afford. Clams create bubbles automatically, while other upgrades enhance your bubble blowing!"; break;
User prompt
Add a short timeout in the start button.down handler in the show title screen method to allow the music to fade out before game.startGame is called.
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'LK.fadeMusic('titlemusic', {' Line Number: 2462
Code edit (1 edits merged)
Please save this source code
User prompt
Use the jellyfish sound for when the jellyfish touch event happens instead of bubbles
User prompt
Update with: self.update = function() { // Decrement immunity timer if active if (self.immuneTimer > 0) { self.immuneTimer--; } // Only check for bubble collisions if not immune if (self.immuneTimer === 0) { // Bubble collision check game.activeBubbles.forEach(function(bubble) { if (bubble.visible) { var dx = self.x - bubble.x; var dy = self.y - bubble.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= bubble.size / 2 + 70) { // Add pop effect for jellyfish pops game.createPopEffect(bubble.x, bubble.y, bubble.size, bubble.colorTint); var points = bubble.getBP(); game.addBP(points, bubble.x, bubble.y, false); // Play a random bubble pop sound var bubbleSounds = ['bubble1', 'bubble2', 'bubble3', 'bubble4']; var randomSound = bubbleSounds[Math.floor(Math.random() * bubbleSounds.length)]; LK.getSound(randomSound).play(); bubble.deactivate(); } } }); } if (self.touched) { // When touched, jellyfish floats up rapidly self.y -= 15; // Much faster upward movement // Check if off screen to destroy if (self.y < -100) { self.destroy(); } return; } // Remove when off bottom of screen if (self.y > game.height + 100) { self.destroy(); } };
User prompt
Update jellyfish only as needed with: self.down = function() { if (self.touched) return true; self.touched = true; self.immuneTimer = 30; // Immunity for 30 frames (half a second) // Play sound var bubbleSounds = ['bubble1', 'bubble2', 'bubble3', 'bubble4']; var randomSound = bubbleSounds[Math.floor(Math.random() * bubbleSounds.length)]; LK.getSound(randomSound).play(); // Spawn bubbles in a jet var bubbleCount = 5 + Math.floor(Math.random() * 3); for (var i = 0; i < bubbleCount; i++) { LK.setTimeout(function() { if (!self) return; // Safety check var size = 50 + Math.random() * 50; var bubble = spawnBubble( self.x + (Math.random() * 40 - 20), self.y + (Math.random() * 40 - 20), size, (Math.random() * 2 - 1) * 2, false ); if (bubble) { bubble.verticalVelocity = -(Math.random() * 6 + 4); } }, i * 5); } // Apply a "flee" animation tween(sprite, { scaleX: sprite.scaleX * 1.3, scaleY: sprite.scaleY * 0.7, alpha: 0.8 }, { duration: 300, easing: tween.easeOutBack }); return true; }; βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ // Bubble class to represent each bubble in the game var Bubble = Container.expand(function () { var self = Container.call(this); self.lifetime = 0; self.hasSplit = false; self.splitHeight = null; self.AUTO_POP_SIZE = 40; self.MIN_SPLIT_SIZE = 30; self.lastPopTime = 0; self.visible = false; // Start invisible in pool var sprite = self.attachAsset('bubble', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); self.colorTint = 0xFFFFFF; // Default white tint self.colorMultiplier = 1.0; // Default multiplier self.colorPhase = Math.random() * Math.PI * 2; // Random starting phase self.isTwin = false; self.starSprite = null; self.size = 100; self.updateColor = function () { // Check if color upgrades are active if (UPGRADE_CONFIG.colors.blueBubbles.currentLevel > 0) { var color = 0xFFFFFF; var multiplier = 1.0; if (UPGRADE_CONFIG.colors.prismaticBubbles.currentLevel > 0) { // Prismatic - constantly shifting color with variable multiplier self.colorPhase += 0.02; var colorValue = Math.sin(self.colorPhase) * 0.5 + 0.5; // 0 to 1 multiplier = 0.8 + colorValue * 1.2; // 0.8x to 2.0x // Generate rainbow color var r = Math.sin(self.colorPhase) * 127 + 128; var g = Math.sin(self.colorPhase + 2) * 127 + 128; var b = Math.sin(self.colorPhase + 4) * 127 + 128; color = (Math.floor(r) << 16) + (Math.floor(g) << 8) + Math.floor(b); } else if (UPGRADE_CONFIG.colors.rainbowBubbles.currentLevel > 0) { // Rainbow - randomly changes color on activation var colorChoice = Math.floor(Math.random() * 6); switch (colorChoice) { case 0: color = 0xFF0000; multiplier = 1.5; break; // Red case 1: color = 0xFFAA00; multiplier = 1.4; break; // Orange case 2: color = 0xFFFF00; multiplier = 1.3; break; // Yellow case 3: color = 0x00FF00; multiplier = 1.2; break; // Green case 4: color = 0x0000FF; multiplier = 1.1; break; // Blue case 5: color = 0xFF00FF; multiplier = 1.6; break; // Purple } } else if (UPGRADE_CONFIG.colors.pinkBubbles.currentLevel > 0) { color = 0xFF80C0; // Pink multiplier = 1.3; } else if (UPGRADE_CONFIG.colors.greenBubbles.currentLevel > 0) { color = 0x00FF80; // Green multiplier = 1.2; } else { // Blue bubbles color = 0x80C0FF; // Blue multiplier = 1.1; } self.colorTint = color; self.colorMultiplier = multiplier; // Apply tint to the sprite sprite.tint = color; // Try direct assignment // If that doesn't work, try: // sprite.color = color; } }; self.activate = function (x, y, size, isPlayerBlown) { // Existing reset code self.x = x; self.y = y; self.size = size; self.lifetime = 0; self.hasSplit = false; self.splitHeight = null; self.justSplit = false; self.autoPopDisplayed = false; self.lastPopTime = 0; self.verticalVelocity = 0; self.driftX = (Math.random() * 20 - 10) / 60; self.floatSpeed = 50 * (120 / size * (0.9 + Math.random() * 0.2)) / 60; self.initLifetime(); // Always get fresh lifetime self.visible = true; // Reset twin properties self.isTwin = false; if (self.starSprite) { self.starSprite.visible = false; } // Reset color properties self.colorTint = 0xFFFFFF; // Reset to default white self.colorMultiplier = 1.0; // Reset multiplier self.colorPhase = Math.random() * Math.PI * 2; // Fresh random phase sprite.tint = 0xFFFFFF; // Reset the sprite tint directly // Check if color upgrades are active and apply colors self.applyColorUpgrade(); // Add some debug logs }; self.setTwin = function () { self.isTwin = true; // Create star sprite if it doesn't exist if (!self.starSprite) { self.starSprite = LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, alpha: 0.9 }); self.addChild(self.starSprite); } // Make sure it's visible self.starSprite.visible = true; // Scale star based on bubble size var starScale = self.size / 200; // Adjust divisor to change star size relative to bubble self.starSprite.scaleX = starScale; self.starSprite.scaleY = starScale; // Add a small rotation animation self.starSprite.rotationSpeed = Math.random() * 0.02 + 0.01; }; self.applyColorUpgrade = function () { // Only apply if any color upgrade is active if (UPGRADE_CONFIG.colors.blueBubbles.currentLevel > 0) { var color = 0xFFFFFF; var multiplier = 1.0; // Get active color setting var activeColorKey = getActiveColorKey(); // Apply the selected color if (activeColorKey === "prismaticBubbles") { // Prismatic - constantly shifting color with variable multiplier self.colorPhase += 0.02; var colorValue = Math.sin(self.colorPhase) * 0.5 + 0.5; // 0 to 1 multiplier = 0.8 + colorValue * 1.2; // 0.8x to 2.0x // Generate rainbow color var r = Math.sin(self.colorPhase) * 127 + 128; var g = Math.sin(self.colorPhase + 2) * 127 + 128; var b = Math.sin(self.colorPhase + 4) * 127 + 128; color = (Math.floor(r) << 16) + (Math.floor(g) << 8) + Math.floor(b); } else if (activeColorKey === "rainbowBubbles") { // Rainbow - randomly changes color var rainbowColors = [0xFF0000, // Red 0xFFA500, // Orange 0xFFFF00, // Yellow 0x00FF00, // Green 0x0000FF, // Blue 0xFF00FF // Purple ]; var multipliers = [1.5, 1.4, 1.3, 1.2, 1.1, 1.6]; var index = Math.floor(Math.random() * rainbowColors.length); color = rainbowColors[index]; multiplier = multipliers[index]; } else { // Check all single colors in a more maintainable way var colorKeys = ["silverBubbles", "crimsonBubbles", "goldBubbles", "tealBubbles", "pinkBubbles", "orangeBubbles", "greenBubbles", "purpleBubbles", "blueBubbles"]; // Find the active color or highest unlocked for (var i = 0; i < colorKeys.length; i++) { var key = colorKeys[i]; if (activeColorKey === key || activeColorKey === "auto" && UPGRADE_CONFIG.colors[key].currentLevel > 0) { color = UPGRADE_CONFIG.colors[key].color; multiplier = UPGRADE_CONFIG.colors[key].multiplier; break; } } } // Apply the color and multiplier sprite.tint = color; self.colorTint = color; self.colorMultiplier = multiplier; } }; self.deactivate = function () { self.visible = false; if (self.starSprite) { self.starSprite.visible = false; } var index = game.activeBubbles.indexOf(self); if (index > -1) { game.activeBubbles.splice(index, 1); } // Don't award points here - let the calling function handle it }; self.initLifetime = function () { self.maxLifetime = Math.floor(Math.random() * 960 + 1440); self.maxLifetime *= Math.min(1, self.size / 100); }; self.initLifetime(); // Subtle size-based variance plus small random factor var speedMultiplier = 120 / self.size * (0.9 + Math.random() * 0.2); // Just 10% variance self.floatSpeed = 50 * speedMultiplier / 60; self.driftX = (Math.random() * 20 - 10) / 60; // Normal drift variance self.verticalVelocity = 0; // In the Bubble class, modify the down method: self.down = function (e) { var currentTime = Date.now(); if (currentTime - self.lastPopTime < 100) { return true; } self.lastPopTime = currentTime; // Calculate points before any effects var points = self.getBP(); // Create pop effect with the bubble's color game.createPopEffect(self.x, self.y, self.size, self.colorTint); // Continue with the rest of the existing code game.addBP(points, self.x, self.y, false); if (self.size > 60 && !self.justSplit) { var splitCount = 2 + UPGRADE_CONFIG.machine.bubbleDurability.currentLevel; var newSize = Math.max(self.MIN_SPLIT_SIZE, self.size * 0.6); for (var i = 0; i < splitCount; i++) { var angle = i / splitCount * Math.PI * 2; spawnBubble(self.x, self.y, newSize, Math.cos(angle) * 0.5, false); } } // Handle twin bubbles var twinPair = game.twinBubbles.find(function (pair) { return pair.bubble1 === self && !pair.popped || pair.bubble2 === self && !pair.popped; }); if (twinPair) { // Mark as popped twinPair.popped = true; twinPair.timestamp = LK.ticks; // Find the other bubble in the pair var otherBubble = twinPair.bubble1 === self ? twinPair.bubble2 : twinPair.bubble1; // Add bonus for twin pop var twinLevel = UPGRADE_CONFIG.player.twinBubbles.currentLevel; var bonusMultiplier = 0.3 + 0.15 * twinLevel; // 30/45/60% bonus // Pop the other bubble automatically and add bonus points if (otherBubble.visible) { // Add pop effect for twin bubble pop game.createPopEffect(otherBubble.x, otherBubble.y, otherBubble.size, otherBubble.colorTint); var bonusPoints = Math.floor(otherBubble.getBP() * bonusMultiplier); game.addBP(bonusPoints, otherBubble.x, otherBubble.y, false); otherBubble.deactivate(); } } // Play sound var bubbleSounds = ['bubble1', 'bubble2', 'bubble3', 'bubble4']; var randomSound = bubbleSounds[Math.floor(Math.random() * bubbleSounds.length)]; LK.getSound(randomSound).play(); // Check if this is the currently growing bubble if (game && game.growingBubble === self) { game.growingBubble = null; game.mouthOpenDuration = 0; game.blowCooldown = game.BLOW_COOLDOWN_TIME; } self.deactivate(); if (game && game.tutorial && game.tutorial.stage === 2) { game.tutorial.poppedBubble = true; LK.setTimeout(function () { showTutorialPopup(3); }, 30); } return true; }; self.getBP = function () { var baseValue = Math.max(1, Math.floor(Math.pow(self.size, 1.4) * 0.018)); // Lowered by 10% // Apply Bubble Refinement upgrade var refinementLevel = UPGRADE_CONFIG.player.bubbleRefinement.currentLevel; if (refinementLevel > 0) { baseValue *= 1 + 0.25 * refinementLevel; // +25% per level } if (self.fromClam && UPGRADE_CONFIG.machine.bubbleQuality.currentLevel > 0) { var qualityLevel = UPGRADE_CONFIG.machine.bubbleQuality.currentLevel; baseValue *= 1 + 0.4 * qualityLevel; // +40% per level } // Apply color multiplier baseValue = Math.floor(baseValue * self.colorMultiplier); // Apply treasure zone bonus if applicable baseValue = Math.floor(baseValue * getTreasureBonusMultiplier(self.x, self.y)); return baseValue; }; self.update = function () { if (UPGRADE_CONFIG.colors.prismaticBubbles.currentLevel > 0) { self.colorPhase += 0.02; var r = Math.sin(self.colorPhase) * 127 + 128; var g = Math.sin(self.colorPhase + 2) * 127 + 128; var b = Math.sin(self.colorPhase + 4) * 127 + 128; var color = (Math.floor(r) << 16) + (Math.floor(g) << 8) + Math.floor(b); // Update the color value sprite.tint = color; self.colorTint = color; // Make the multiplier oscillate for prismatic var colorValue = Math.sin(self.colorPhase) * 0.5 + 0.5; self.colorMultiplier = 0.8 + colorValue * 1.2; // 0.8x to 2.0x } // Update star rotation if this is a twin bubble if (self.isTwin && self.starSprite && self.starSprite.visible) { self.starSprite.rotation += self.starSprite.rotationSpeed; // Scale star based on bubble size var starScale = self.size / 200; self.starSprite.scaleX = starScale; self.starSprite.scaleY = starScale; } // Only increment lifetime if not being actively blown if (game.growingBubble !== self) { self.lifetime++; if (self.lifetime % 60 === 0) { self.driftX += (Math.random() - 0.5) * 0.8; } } self.x += self.driftX * 1.2; if (self.lifetime > self.maxLifetime) { if (self.size > 60 && !self.hasSplit) { self.hasSplit = true; var newSize = Math.max(self.MIN_SPLIT_SIZE, self.size * 0.6); for (var i = 0; i < 2; i++) { var split = spawnBubble(self.x, self.y, newSize, i === 0 ? -1 : 1, true); if (split) { split.maxLifetime *= 0.7; } } self.deactivate(); return; } self.autoPop(); return; } if (self.y < -self.size) { // Use the deactivate method for consistent cleanup self.deactivate(); return; } self.justSplit = false; if (self.verticalVelocity < self.floatSpeed) { self.verticalVelocity += 0.08; } self.y -= self.verticalVelocity; if (Math.abs(self.driftX) > (Math.random() * 20 - 10) / 60) { self.driftX *= 0.98; } self.x += self.driftX; if (self.x < self.size) { self.x = self.size; self.driftX = Math.abs(self.driftX); } else if (self.x > game.width - self.size) { self.x = game.width - self.size; self.driftX = -Math.abs(self.driftX); } var scale = self.size / sprite.width; sprite.scaleX = scale; sprite.scaleY = scale; }; // In the Bubble class, find the autoPop method self.autoPop = function () { // Only award points if bubble is on screen if (!self.autoPopDisplayed && self.y > -self.size) { var points = Math.floor(self.getBP() * 0.5); // Add pop effect for auto-pops game.createPopEffect(self.x, self.y, self.size, self.colorTint); game.addBP(points, self.x, self.y, true); self.autoPopDisplayed = true; // Only play sound if cooldown is 0 if (game.autoPopSoundCooldown <= 0) { // Play a softer pop sound for auto-pops var bubbleSounds = ['bubble1', 'bubble2', 'bubble3', 'bubble4']; var randomSound = bubbleSounds[Math.floor(Math.random() * bubbleSounds.length)]; var sound = LK.getSound(randomSound); sound.volume = 0.3; // Lower volume for auto-pops sound.play(); // Set cooldown game.autoPopSoundCooldown = game.AUTO_POP_SOUND_COOLDOWN; } } self.deactivate(); return; }; self.updateColor = function () { // Check if color upgrades are active if (UPGRADE_CONFIG.colors.blueBubbles.currentLevel > 0) { var color = 0xFFFFFF; var multiplier = 1.0; if (UPGRADE_CONFIG.colors.prismaticBubbles.currentLevel > 0) { // Prismatic - constantly shifting color with variable multiplier self.colorPhase += 0.02; var colorValue = Math.sin(self.colorPhase) * 0.5 + 0.5; // 0 to 1 multiplier = 0.8 + colorValue * 1.2; // 0.8x to 2.0x // Generate rainbow color var r = Math.sin(self.colorPhase) * 127 + 128; var g = Math.sin(self.colorPhase + 2) * 127 + 128; var b = Math.sin(self.colorPhase + 4) * 127 + 128; color = (Math.floor(r) << 16) + (Math.floor(g) << 8) + Math.floor(b); } else if (UPGRADE_CONFIG.colors.rainbowBubbles.currentLevel > 0) { // Rainbow - randomly changes color on activation var colorChoice = Math.floor(Math.random() * 6); switch (colorChoice) { case 0: color = 0xFF0000; multiplier = 1.5; break; // Red case 1: color = 0xFFAA00; multiplier = 1.4; break; // Orange case 2: color = 0xFFFF00; multiplier = 1.3; break; // Yellow case 3: color = 0x00FF00; multiplier = 1.2; break; // Green case 4: color = 0x0000FF; multiplier = 1.1; break; // Blue case 5: color = 0xFF00FF; multiplier = 1.6; break; // Purple } } else if (UPGRADE_CONFIG.colors.pinkBubbles.currentLevel > 0) { color = 0xFF80C0; // Pink multiplier = 1.3; } else if (UPGRADE_CONFIG.colors.greenBubbles.currentLevel > 0) { color = 0x00FF80; // Green multiplier = 1.2; } else { // Blue bubbles color = 0x80C0FF; // Blue multiplier = 1.1; } self.colorTint = color; self.colorMultiplier = multiplier; // Apply tint to the sprite sprite.tint = self.colorTint; } }; return self; }); var Fish = Container.expand(function () { var self = Container.call(this); var fishTypes = ['redfish', 'bluefish', 'yellowfish']; var fishType = fishTypes[Math.floor(Math.random() * fishTypes.length)]; // Create fish sprite var sprite = self.attachAsset(fishType, { anchorX: 0.5, anchorY: 0.5 }); // Initialize position and movement self.fromLeft = Math.random() < 0.5; self.x = self.fromLeft ? -100 : game.width + 100; self.y = Math.random() * (game.height * 0.7) + game.height * 0.1; sprite.scaleX = self.fromLeft ? 1 : -1; self.speed = 8; // Changed from 4 self.update = function () { self.x += self.fromLeft ? self.speed : -self.speed; // Add bubble collision check game.activeBubbles.forEach(function (bubble) { if (bubble.visible) { var dx = self.x - bubble.x; var dy = self.y - bubble.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= bubble.size / 2 + 70) { // Add pop effect for fish pops game.createPopEffect(bubble.x, bubble.y, bubble.size, bubble.colorTint); var points = bubble.getBP(); game.addBP(points, bubble.x, bubble.y, false); // false = manual pop points // Play a random bubble pop sound var bubbleSounds = ['bubble1', 'bubble2', 'bubble3', 'bubble4']; var randomSound = bubbleSounds[Math.floor(Math.random() * bubbleSounds.length)]; LK.getSound(randomSound).play(); bubble.deactivate(); } } }); // Remove when off screen if (self.fromLeft && self.x > game.width + 100 || !self.fromLeft && self.x < -100) { self.destroy(); } }; return self; }); var Jellyfish = Container.expand(function () { var self = Container.call(this); // Create jellyfish sprite var sprite = self.attachAsset('jellyfish', { anchorX: 0.5, anchorY: 0.3, // Set anchor higher to make tentacles flow better x: 0, y: 0 }); // Initialize position and movement self.x = Math.random() * (game.width - 200) + 100; self.y = -100; // Start above screen self.speed = 2.5; // Slower base speed self.targetX = self.x; self.touched = false; self.immuneTimer = 0; // Timer for bubble popping immunity // Motion variables self.pulsePhase = Math.random() * Math.PI * 2; // Random starting phase self.pulseFrequency = 0.03 + Math.random() * 0.01; // How fast it pulses self.pulseAmplitude = 0.15; // How much it pulses self.driftPhase = Math.random() * Math.PI * 2; self.driftFrequency = 0.01 + Math.random() * 0.005; self.driftAmount = 30 + Math.random() * 20; // Start pulsing animation immediately self.startPulsingAnimation = function () { // Cancel any existing animation if (self.pulseAnimation) { self.pulseAnimation.stop(); } // Create the pulsing animation var originalScaleX = 1; var originalScaleY = 1; // Function to update the pulse animation function updatePulse() { if (!self || !sprite) { return; } // Safety check var pulseValue = Math.sin(self.pulsePhase); // Scale effect - contract and expand sprite.scaleX = originalScaleX * (1 - pulseValue * self.pulseAmplitude); sprite.scaleY = originalScaleY * (1 + pulseValue * self.pulseAmplitude); // Progress the phase self.pulsePhase += self.pulseFrequency; // Adjust vertical position slightly with pulse self.y += pulseValue > 0 ? self.speed * 1.2 : self.speed * 0.8; // Update horizontal drift var drift = Math.sin(self.driftPhase) * self.driftAmount / 60; self.targetX += drift; // Constrain to screen bounds self.targetX = Math.max(50, Math.min(game.width - 50, self.targetX)); // Smooth movement toward target X self.x += (self.targetX - self.x) * 0.05; // Progress drift phase self.driftPhase += self.driftFrequency; // Continue animation if not destroyed if (self && !self.destroyed) { LK.setTimeout(updatePulse, 1); } } // Start the update loop updatePulse(); }; self.startPulsingAnimation(); self.update = function () { // Decrement immunity timer if active if (self.immuneTimer > 0) { self.immuneTimer--; } // Only check for bubble collisions if not immune if (self.immuneTimer === 0) { game.activeBubbles.forEach(function (bubble) { if (bubble.visible) { var dx = self.x - bubble.x; var dy = self.y - bubble.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= bubble.size / 2 + 70) { // Add pop effect for jellyfish pops game.createPopEffect(bubble.x, bubble.y, bubble.size, bubble.colorTint); var points = bubble.getBP(); game.addBP(points, bubble.x, bubble.y, false); // Play a random bubble pop sound var bubbleSounds = ['bubble1', 'bubble2', 'bubble3', 'bubble4']; var randomSound = bubbleSounds[Math.floor(Math.random() * bubbleSounds.length)]; LK.getSound(randomSound).play(); bubble.deactivate(); } } }); } if (self.touched) { // When touched, jellyfish floats up rapidly self.y -= 15; // Much faster upward movement // Check if off screen to destroy if (self.y < -100) { self.destroy(); } return; } // Remove when off bottom of screen if (self.y > game.height + 100) { self.destroy(); } }; // Handle being touched self.down = function () { if (self.touched) { return true; } self.touched = true; self.immuneTimer = 30; // Immunity for 30 frames (half a second) // Play sound LK.getSound('jellyfish').play(); // Spawn bubbles in a jet var bubbleCount = 5 + Math.floor(Math.random() * 3); for (var i = 0; i < bubbleCount; i++) { LK.setTimeout(function () { if (!self) { return; } // Safety check var size = 50 + Math.random() * 50; var bubble = spawnBubble(self.x + (Math.random() * 40 - 20), self.y + (Math.random() * 40 - 20), size, (Math.random() * 2 - 1) * 2, false); if (bubble) { bubble.verticalVelocity = -(Math.random() * 6 + 4); } }, i * 5); } // Apply a "flee" animation tween(sprite, { scaleX: sprite.scaleX * 1.3, scaleY: sprite.scaleY * 0.7, alpha: 0.8 }, { duration: 300, easing: tween.easeOutBack }); return true; }; return self; }); // Pufferfish mask that follows face var pufferMask = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('pufferfish', { anchorX: 0.5, anchorY: 0.5 }); var targetX = 0; var targetY = 0; var smoothingFactor = 0.12; var prevX = null; var prevY = null; var targetRotation = 0; var rotationSmoothingFactor = 0.1; var targetTilt = 0; var tiltSmoothingFactor = 0.11; // Reduced from 0.08 for smoother movement var tiltScaleFactor = 0.09; // Reduced from 0.15 for less tilt var scaleHistory = new Array(5).fill(0); // Keep last 5 scale values var scaleIndex = 0; var baseScale = 1; var minScale = 0.1; var maxScale = 3; self.update = function () { // Only use face tracking when enabled if (game.faceTrackingEnabled) { // Adjust scale based on face size if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) { var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x); var newScale = eyeDistance / 500; // Update rolling average scaleHistory[scaleIndex] = newScale; scaleIndex = (scaleIndex + 1) % scaleHistory.length; // Calculate average scale var avgScale = scaleHistory.reduce(function (a, b) { return a + b; }, 0) / scaleHistory.length; // More gentle smoothing sprite.scaleX = sprite.scaleX * 0.85 + avgScale * 0.15; sprite.scaleY = sprite.scaleY * 0.85 + avgScale * 0.15; } // Follow nose position for main face tracking if (facekit.noseTip) { targetX = facekit.noseTip.x; targetY = facekit.noseTip.y; // Initialize previous positions if not set if (prevX === null) { prevX = targetX; prevY = targetY; } // Weighted average between previous and target position var newX = prevX * (1 - smoothingFactor) + targetX * smoothingFactor; var newY = prevY * (1 - smoothingFactor) + targetY * smoothingFactor; self.x = newX; self.y = newY; // Update previous positions prevX = newX; prevY = newY; } if (facekit.leftEye && facekit.rightEye) { targetTilt = calculateFaceTilt() * tiltScaleFactor; // Scale down the tilt // Reduce max rotation to Β±15 degrees targetTilt = Math.max(-15, Math.min(15, targetTilt)); self.rotation += (targetTilt - self.rotation) * tiltSmoothingFactor; } } }; function calculateFaceTilt() { if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) { // Calculate midpoint between eyes var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2; var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2; // Calculate angle between eye midpoint and mouth, negated to fix direction var dx = facekit.mouthCenter.x - eyeMidX; var dy = facekit.mouthCenter.y - eyeMidY; var angle = -(Math.atan2(dx, dy) * (180 / Math.PI)); // Reduced max angle to Β±15 degrees and lowered multiplier return Math.max(-15, Math.min(15, angle * 0.15)); } return 0; // Default to straight when face points aren't available } return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB // Light blue background to represent the sky }); /**** * Game Code ****/ // Import storage plugin for data persistence function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var savedBP = storage.bp || 0; var savedLastPlayTime = storage.lastPlayTime || Date.now(); var savedTutorialComplete = storage.tutorialComplete || false; function loadGame() { // Load BP if (storage.bp !== undefined) { game.bp = parseFloat(storage.bp); bpText.setText(formatBP(game.bp) + " BP"); } // Load upgrades if (storage.simpleUpgrades) { var values = storage.simpleUpgrades.split(","); if (values.length >= 13) { // Clams UPGRADE_CONFIG.machines.basicClam.amount = parseInt(values[0]) || 0; UPGRADE_CONFIG.machines.advancedClam.amount = parseInt(values[1]) || 0; UPGRADE_CONFIG.machines.premiumClam.amount = parseInt(values[2]) || 0; // Player upgrades UPGRADE_CONFIG.player.lungCapacity.currentLevel = parseInt(values[3]) || 0; UPGRADE_CONFIG.player.quickBreath.currentLevel = parseInt(values[4]) || 0; UPGRADE_CONFIG.player.autoPop.currentLevel = parseInt(values[5]) || 0; UPGRADE_CONFIG.player.bubbleRefinement.currentLevel = parseInt(values[6]) || 0; UPGRADE_CONFIG.player.twinBubbles.currentLevel = parseInt(values[7]) || 0; // Check if save has jellyfish data (for backward compatibility) if (values.length >= 15) { UPGRADE_CONFIG.player.jellyfish.currentLevel = parseInt(values[14]) || 0; } else { // For older save files, initialize to 0 UPGRADE_CONFIG.player.jellyfish.currentLevel = 0; } UPGRADE_CONFIG.player.sizeVariance.currentLevel = parseInt(values[8]) || 0; // Machine upgrades UPGRADE_CONFIG.machine.bubbleDurability.currentLevel = parseInt(values[9]) || 0; UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel = parseInt(values[10]) || 0; UPGRADE_CONFIG.machine.bubbleQuality.currentLevel = parseInt(values[11]) || 0; // Treasures UPGRADE_CONFIG.decorations.sunkenTreasures.amount = parseInt(values[12]) || 0; // Active color setting (if present) if (values.length >= 14) { UPGRADE_CONFIG.gameSettings.activeColor = values[13] || "auto"; } } } // Load color unlocks if (storage.colorUnlocks) { var colorValues = storage.colorUnlocks.split(","); var colorKeys = ["blueBubbles", "purpleBubbles", "greenBubbles", "orangeBubbles", "pinkBubbles", "tealBubbles", "goldBubbles", "crimsonBubbles", "silverBubbles", "rainbowBubbles", "prismaticBubbles"]; colorKeys.forEach(function (key, index) { if (index < colorValues.length) { UPGRADE_CONFIG.colors[key].currentLevel = parseInt(colorValues[index]) || 0; } }); } // Load tutorial status if (storage.tutorialComplete) { game.tutorial.stage = 8; // Set to a stage past the tutorial } // Update all visuals based on loaded data updateClamVisuals(); updateTreasureDecorations(); updateAllUpgradeTexts(); // Apply player upgrade effects var lungCapacityLevel = UPGRADE_CONFIG.player.lungCapacity.currentLevel; var quickBreathLevel = UPGRADE_CONFIG.player.quickBreath.currentLevel; // Update max bubble size from Lung Capacity var baseSize = UPGRADE_EFFECTS.lungCapacity.baseValue; var increasePercent = UPGRADE_EFFECTS.lungCapacity.incrementPercent; var multiplier = 1 + increasePercent / 100 * lungCapacityLevel; game.maxBubbleSize = baseSize * multiplier; // Update growth rate from Quick Breath game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + UPGRADE_EFFECTS.quickBreath.incrementPercent / 100 * quickBreathLevel); // Calculate offline progress if (storage.lastPlayTime) { var currentTime = Date.now(); var timeDiff = currentTime - storage.lastPlayTime; if (timeDiff > 60000) { // 1 minute calculateOfflineProgress(timeDiff); } } } // Save function - call periodically function saveGame() { // Store basic values storage.bp = game.bp; // Create color unlock state string var colorUnlocks = ""; var colorKeys = ["blueBubbles", "purpleBubbles", "greenBubbles", "orangeBubbles", "pinkBubbles", "tealBubbles", "goldBubbles", "crimsonBubbles", "silverBubbles", "rainbowBubbles", "prismaticBubbles"]; colorKeys.forEach(function (key) { colorUnlocks += UPGRADE_CONFIG.colors[key].currentLevel + ","; }); // Save active color setting var activeColor = UPGRADE_CONFIG.gameSettings.activeColor || "auto"; // Save treasure amounts var treasureAmount = UPGRADE_CONFIG.decorations.sunkenTreasures.amount; // Save as comma-separated values - machine amounts and upgrade levels storage.simpleUpgrades = // Clams UPGRADE_CONFIG.machines.basicClam.amount + "," + UPGRADE_CONFIG.machines.advancedClam.amount + "," + UPGRADE_CONFIG.machines.premiumClam.amount + "," + // Player upgrades UPGRADE_CONFIG.player.lungCapacity.currentLevel + "," + UPGRADE_CONFIG.player.quickBreath.currentLevel + "," + UPGRADE_CONFIG.player.autoPop.currentLevel + "," + UPGRADE_CONFIG.player.bubbleRefinement.currentLevel + "," + UPGRADE_CONFIG.player.twinBubbles.currentLevel + "," + UPGRADE_CONFIG.player.sizeVariance.currentLevel + "," + // Machine upgrades UPGRADE_CONFIG.machine.bubbleDurability.currentLevel + "," + UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel + "," + UPGRADE_CONFIG.machine.bubbleQuality.currentLevel + "," + // Treasures treasureAmount + "," + // Active color setting activeColor + "," + // Jellyfish UPGRADE_CONFIG.player.jellyfish.currentLevel; // Store color unlocks separately storage.colorUnlocks = colorUnlocks; storage.lastPlayTime = Date.now(); storage.tutorialComplete = game.tutorial.stage >= 7; } // Calculate offline progress // Calculate offline progress function calculateOfflineProgress(timeDiff) { // Cap at 12 hours as before var secondsAway = Math.min(Math.floor(timeDiff / 1000), 43200); var totalBP = 0; // Calculate for each clam type ['basicClam', 'advancedClam', 'premiumClam'].forEach(function (clamType) { var config = UPGRADE_CONFIG.machines[clamType]; var clamCount = config.amount; if (clamCount > 0) { // Same calculation for production rate // Correct the production time to match in-game values var baseTime = config.production * 2.5; // Apply a multiplier to match the in-game frame conversion var speedMultiplier = Math.pow(1 - UPGRADE_EFFECTS.autoBubbleSpeed.decrementPercent / 100, UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel); var adjustedTime = Math.max(1, baseTime * speedMultiplier); var bubblesPerSecond = clamCount / Math.max(adjustedTime, 0.5); var bubbleValue = Math.pow(config.bubbleSize, 1.4) * 0.02; // Apply upgrades as before if (UPGRADE_CONFIG.machine.bubbleQuality.currentLevel > 0) { bubbleValue *= 1 + 0.4 * UPGRADE_CONFIG.machine.bubbleQuality.currentLevel; } var refinementLevel = UPGRADE_CONFIG.player.bubbleRefinement.currentLevel; if (refinementLevel > 0) { bubbleValue *= 1 + 0.25 * refinementLevel; } var activeColorKey = getActiveColorKey(); var colorMultiplier = 1.0; if (activeColorKey && UPGRADE_CONFIG.colors[activeColorKey]) { colorMultiplier = UPGRADE_CONFIG.colors[activeColorKey].multiplier || 1.0; } bubbleValue *= colorMultiplier; // New efficiency calculation - starts lower but improves with upgrades var baseEfficiency = 0.06; // Start at just 6% efficiency for new players // Each upgrade type boosts offline efficiency var totalUpgradeLevels = UPGRADE_CONFIG.player.lungCapacity.currentLevel + UPGRADE_CONFIG.player.quickBreath.currentLevel + UPGRADE_CONFIG.player.bubbleRefinement.currentLevel + UPGRADE_CONFIG.machine.bubbleDurability.currentLevel + UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel; // Each upgrade adds 0.75% efficiency (up to about 30% with all upgrades) var upgradeBonus = Math.min(0.24, totalUpgradeLevels * 0.0075); // Premium clams provide better offline efficiency var clamTypeBonus = clamType === 'premiumClam' ? 0.1 : clamType === 'advancedClam' ? 0.05 : 0; // Time decay - efficiency drops by half after 24 hours var timeDecay = Math.max(0.5, 1 - secondsAway / 86400); // Final efficiency combines base rate, upgrades, clam type, and time away var efficiencyFactor = (baseEfficiency + upgradeBonus + clamTypeBonus) * timeDecay; // Calculate BP from this clam type totalBP += bubblesPerSecond * bubbleValue * secondsAway * efficiencyFactor; } }); // Round to integer - no additional penalty needed since it's built into the efficiency formula totalBP = Math.floor(totalBP); // More progressive cap system var minCap = 200; // Minimum cap prevents huge jumps for new players var maxCap = 25000; // Higher maximum for advanced players var percentCap = Math.min(0.75, 0.3 + UPGRADE_CONFIG.player.bubbleRefinement.currentLevel * 0.05); // 30% base, up to 75% with refinement var progressiveCap = Math.min(maxCap, Math.max(minCap, game.bp * percentCap)); totalBP = Math.min(totalBP, progressiveCap); // Add the BP and show a message if (totalBP > 0) { game.bp += totalBP; bpText.setText(formatBP(game.bp) + " BP"); game.showMessage("You earned " + formatBP(totalBP) + " BP while away!"); } } game.faceTrackingEnabled = false; var UPGRADE_CONFIG = { gameSettings: { activeColor: "auto" // Default to automatic progression (highest unlocked) }, player: { lungCapacity: { name: "Lung Capacity", baseCost: 200, costScale: 3.5, maxLevel: 10, currentLevel: 0 }, quickBreath: { name: "Quick Breath", baseCost: 200, costScale: 3.5, maxLevel: 10, currentLevel: 0 }, autoPop: { name: "Fish Friends", baseCost: 400, costScale: 4, maxLevel: 6, currentLevel: 0 }, bubbleRefinement: { name: "Bubble Refinement", baseCost: 2500, costScale: 3.5, maxLevel: 10, currentLevel: 0 }, twinBubbles: { name: "Twin Bubbles", baseCost: 4000, costScale: 3, maxLevel: 8, currentLevel: 0 }, sizeVariance: { name: "Size Variance", baseCost: 5000, costScale: 3, maxLevel: 5, currentLevel: 0 }, jellyfish: { name: "Jellyfish Bloom", baseCost: 5000, costScale: 4, maxLevel: 6, currentLevel: 0 } }, machines: { basicClam: { name: "Basic Clam", baseCost: 300, costScale: 3, amount: 0, maxAmount: 4, // Add max amount production: 3, bubbleSize: 80 }, advancedClam: { name: "Advanced Clam", baseCost: 12000, costScale: 3.0, amount: 0, maxAmount: 4, // Add max amount production: 2, bubbleSize: 100, unlockCost: 12000, requires: "basicClam" // Add requirement }, premiumClam: { name: "Premium Clam", baseCost: 80000, costScale: 3, amount: 0, maxAmount: 4, // Add max amount production: 1, bubbleSize: 150, unlockCost: 80000, requires: "advancedClam" // Add requirement } }, machine: { bubbleDurability: { name: "Bubble Splitting", baseCost: 20000, costScale: 5, maxLevel: 3, currentLevel: 0 }, autoBubbleSpeed: { name: "Clam Speed", baseCost: 3000, costScale: 3.5, maxLevel: 8, currentLevel: 0 }, bubbleQuality: { name: "Bubble Quality", baseCost: 5000, costScale: 4, maxLevel: 8, currentLevel: 0 } }, colors: { blueBubbles: { name: "Blue Bubbles", baseCost: 1000, costScale: 1.0, maxLevel: 1, currentLevel: 0, multiplier: 1.1, color: 0x80C0FF }, purpleBubbles: { name: "Purple Bubbles", baseCost: 2500, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "blueBubbles", multiplier: 1.15, color: 0x8A2BE2 }, greenBubbles: { name: "Green Bubbles", baseCost: 5000, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "purpleBubbles", multiplier: 1.2, color: 0x00FF80 }, orangeBubbles: { name: "Orange Bubbles", baseCost: 8000, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "greenBubbles", multiplier: 1.25, color: 0xFFA500 }, pinkBubbles: { name: "Pink Bubbles", baseCost: 12000, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "orangeBubbles", multiplier: 1.3, color: 0xFF80C0 }, tealBubbles: { name: "Teal Bubbles", baseCost: 18000, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "pinkBubbles", multiplier: 1.35, color: 0x00CED1 }, goldBubbles: { name: "Gold Bubbles", baseCost: 25000, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "tealBubbles", multiplier: 1.4, color: 0xFFD700 }, crimsonBubbles: { name: "Crimson Bubbles", baseCost: 35000, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "goldBubbles", multiplier: 1.45, color: 0xDC143C }, silverBubbles: { name: "Silver Bubbles", baseCost: 45000, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "crimsonBubbles", multiplier: 1.5, color: 0xC0C0C0 }, rainbowBubbles: { name: "Rainbow Bubbles", baseCost: 75000, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "silverBubbles", multiplier: 1.6 }, prismaticBubbles: { name: "Prismatic Bubbles", baseCost: 200000, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "rainbowBubbles", multiplier: 1.8 } }, decorations: { sunkenTreasures: { name: "Sunken Treasures", baseCost: 25000, costScale: 2.5, amount: 0, maxAmount: 3 } } }; // Initialize upgrade registry for UI elements game.upgradeRegistry = {}; // At the top level of your game, add a sound cooldown tracker game.autoPopSoundCooldown = 0; game.AUTO_POP_SOUND_COOLDOWN = 5; // frames to wait between auto-pop sounds var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: game.width / 2, y: game.height / 2 }); game.addChild(background); // Add treasure container game.titleMode = true; // Track if we're in title mode game.showTitleScreen = function () { // Create title container to hold all title elements var titleContainer = new Container(); game.titleContainer = titleContainer; game.addChild(titleContainer); // Try to play music with fade-in effect LK.playMusic('titlemusic', { fade: { start: 0, end: 0.5, // Slightly lower than in-game volume duration: 2000 // Longer fade-in for title screen } }); // Add the logo that falls in from top var logo = LK.getAsset('titlebubble', { anchorX: 0.5, anchorY: 0.5, x: game.width / 2, y: -400, // Start above screen scaleX: 1, scaleY: 1 }); titleContainer.addChild(logo); // Animate logo falling in var savedActiveColor = storage.simpleUpgrades ? storage.simpleUpgrades.split(",")[13] || "auto" : "auto"; var savedColorUnlocks = storage.colorUnlocks ? storage.colorUnlocks.split(",") : []; // Set temporary color settings for title screen game.titleColorSettings = { activeColor: savedActiveColor, colorUnlocks: savedColorUnlocks }; tween(logo, { y: game.height / 2 - 300 // Stop at halfway mark }, { duration: 1500, easing: tween.easeOutElastic, onFinish: function onFinish() { // After logo animation completes, show the start button var startButton = LK.getAsset('startbutton', { anchorX: 0.5, anchorY: 0.5, x: game.width / 2, y: game.height / 2 + 700, scaleX: 0.01, // Start tiny scaleY: 0.01 }); titleContainer.addChild(startButton); // Animate button growing tween(startButton, { scaleX: 1, scaleY: 1 }, { duration: 800, easing: tween.easeOutBack }); // Add tap/click handler to button startButton.down = function () { LK.playMusic('titlemusic', { start: 0.5, // Current title screen volume end: 0, // Fade to silence duration: 1000 }); game.startGame(); return true; }; } }); }; var treasureContainer = new Container(); game.addChild(treasureContainer); game.setChildIndex(treasureContainer, 1); // Add clam container var clamContainer = new Container(); game.addChild(clamContainer); // Add player mask var playerMask = new pufferMask(); game.addChild(playerMask); playerMask.visible = false; game.showTitleScreen(); var UPGRADE_EFFECTS = { lungCapacity: { baseValue: 160, incrementPercent: 25 }, quickBreath: { baseValue: 1.6, incrementPercent: 25 }, autoBubbleSpeed: { decrementPercent: 10 }, bubbleDurability: { extraSplits: 1 }, autoPop: { timeReduction: 0.8 } }; // Define column assignments for each tab var tabColumns = { bubbles: { left: [['player', 'lungCapacity'], ['player', 'quickBreath'], ['player', 'bubbleRefinement'], ['player', 'twinBubbles']], right: [['player', 'autoPop'], ['player', 'jellyfish'], ['player', 'sizeVariance'], ['machine', 'bubbleDurability']] }, clams: { left: [['machines', 'basicClam'], ['machines', 'advancedClam'], ['machines', 'premiumClam']], right: [['machine', 'autoBubbleSpeed'], ['machine', 'bubbleQuality']] }, colors: { left: [['colors', 'blueBubbles'], ['colors', 'purpleBubbles'], ['colors', 'greenBubbles'], ['colors', 'orangeBubbles'], ['colors', 'pinkBubbles']], right: [['colors', 'tealBubbles'], ['colors', 'goldBubbles'], ['colors', 'crimsonBubbles'], ['colors', 'silverBubbles'], ['colors', 'rainbowBubbles'], ['colors', 'prismaticBubbles']] }, decorations: { right: [['decorations', 'sunkenTreasures']] } }; function getUpgradeCost(upgrade) { if (upgrade.amount !== undefined) { // For clams and decorations return Math.floor(upgrade.baseCost * Math.pow(upgrade.costScale, upgrade.amount)); } else { // For regular upgrades return Math.floor(upgrade.baseCost * Math.pow(upgrade.costScale, upgrade.currentLevel)); } } function getActiveColorKey() { if (UPGRADE_CONFIG.gameSettings.activeColor === "auto") { // Find highest unlocked color in order of value var colorKeys = ["prismaticBubbles", "rainbowBubbles", "silverBubbles", "crimsonBubbles", "goldBubbles", "tealBubbles", "pinkBubbles", "orangeBubbles", "greenBubbles", "purpleBubbles", "blueBubbles"]; for (var i = 0; i < colorKeys.length; i++) { if (UPGRADE_CONFIG.colors[colorKeys[i]].currentLevel > 0) { return colorKeys[i]; } } } return UPGRADE_CONFIG.gameSettings.activeColor; } // Add this new function outside of other functions function applyTitleScreenColor(bubble) { if (!game.titleColorSettings) { return; } var activeColorKey = game.titleColorSettings.activeColor; var colorUnlocks = game.titleColorSettings.colorUnlocks; // Check if we have color unlocks if (colorUnlocks && colorUnlocks.length > 0) { // Find highest unlocked color var colorKeys = ["blueBubbles", "purpleBubbles", "greenBubbles", "orangeBubbles", "pinkBubbles", "tealBubbles", "goldBubbles", "crimsonBubbles", "silverBubbles", "rainbowBubbles", "prismaticBubbles"]; var color = 0xFFFFFF; // Default white if (activeColorKey === "auto") { // Auto mode - find highest unlocked color for (var i = colorKeys.length - 1; i >= 0; i--) { if (i < colorUnlocks.length && parseInt(colorUnlocks[i]) > 0) { var key = colorKeys[i]; if (UPGRADE_CONFIG.colors[key]) { color = UPGRADE_CONFIG.colors[key].color || 0xFFFFFF; break; } } } } else if (UPGRADE_CONFIG.colors[activeColorKey]) { // Specific color selected color = UPGRADE_CONFIG.colors[activeColorKey].color || 0xFFFFFF; } // Apply the color var bubbleSprite = bubble.children[0]; // Get the sprite of the bubble if (bubbleSprite) { bubbleSprite.tint = color; } } } function setActiveColor(colorKey) { if (colorKey === "auto" || UPGRADE_CONFIG.colors[colorKey].currentLevel > 0) { UPGRADE_CONFIG.gameSettings.activeColor = colorKey; // Update visual indicator in UI updateColorSelectionUI(); } else { game.showError("Unlock this color first!"); } } function getTreasureBonusMultiplier(x, y) { if (!game.treasureZones || game.treasureZones.length === 0) { return 1.0; // No bonus if no treasures } // Start with no bonus var totalBonus = 0; // Check each treasure zone game.treasureZones.forEach(function (zone) { var dx = x - zone.x; var dy = y - zone.y; var distance = Math.sqrt(dx * dx + dy * dy); // If bubble is in zone, add 30% bonus if (distance <= zone.radius) { totalBonus += 0.3; // +30% per overlapping zone } }); // Return multiplier (1.0 = no bonus, 1.3 = one zone, 1.6 = two zones, etc.) return 1.0 + totalBonus; } function formatBP(value) { var units = ['', 'K', 'M', 'B', 'T']; var unitIndex = 0; while (value >= 1000 && unitIndex < units.length - 1) { value /= 1000; unitIndex++; } return Math.floor(value * 10) / 10 + units[unitIndex]; } game.showError = function (message) { var errorText = new Text2(message, { size: 120, fill: 0xFF0000, stroke: 0x000000, strokeThickness: 5, font: "Impact" }); errorText.anchor = { x: 0.5, y: 0.5 }; errorText.x = game.width / 2; errorText.y = game.height / 2; game.addChild(errorText); tween(errorText, { alpha: 0, y: errorText.y - 50 }, { duration: 3000, onFinish: function onFinish() { errorText.destroy(); } }); }; game.showMessage = function (message) { var messageText = new Text2(message, { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); messageText.anchor = { x: 0.5, y: 0.5 }; messageText.x = game.width / 2; messageText.y = game.height / 2; game.addChild(messageText); tween(messageText, { alpha: 0, y: messageText.y - 30 }, { duration: 3000, onFinish: function onFinish() { messageText.destroy(); } }); }; var currentTab = 'bubbles'; // Default tab var menuTabs = ['bubbles', 'clams', 'colors', 'decorations']; var tabButtons = {}; // Will hold references to tab buttons // Create menu container at the right position var menuContainer = new Container(); menuContainer.x = game.width / 2; menuContainer.y = game.height; // Position at bottom var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, y: -570, alpha: 0.9, scaleX: 2048 / 200, scaleY: game.height * 0.4 / 100.3 }); var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, y: 0, scaleX: 3, scaleY: 0.8, alpha: 0.9 }); // Add panel first (so it's behind tab) menuContainer.addChild(menuPanel); menuContainer.addChild(menuTab); // Menu text var menuText = new Text2("Upgrades", { size: 90, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); menuText.anchor = { x: 0.5, y: 0.5 }; menuText.x = 0; // Relative to container menuText.y = -menuTab.height / 2; // Position relative to container bottom menuContainer.addChild(menuText); // Add to game game.addChild(menuContainer); menuContainer.visible = false; // Create text container var menuTextContainer = new Container(); menuContainer.addChild(menuTextContainer); // Create tab containers var tabContainers = {}; menuTabs.forEach(function (tab) { var contentContainer = new Container(); contentContainer.visible = tab === currentTab; tabContainers[tab] = contentContainer; menuTextContainer.addChild(contentContainer); }); // Create tabs container (only visible when menu is open) var tabsContainer = new Container(); tabsContainer.x = menuPanel.width * menuPanel.scaleX / 2 - 850; // Center horizontally tabsContainer.y = 0; menuContainer.addChild(tabsContainer); // Define tab dimensions var tabWidth = menuPanel.width * 0.8 * menuPanel.scaleX / menuTabs.length; var tabHeight = 120; // Create tabs menuTabs.forEach(function (tab, index) { var tabButton = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, x: -menuPanel.width * menuPanel.scaleX / 2 + (index + 0.5) * tabWidth, y: 0, scaleX: tabWidth / 200, scaleY: tabHeight / 299, alpha: tab === currentTab ? 1.0 : 0.7 }); // Add hit detection tabButton.down = function () { if (tab !== currentTab) { // Check if this is the clams tab during tutorial if (game.tutorial && game.tutorial.stage === 5 && tab === 'clams') { LK.setTimeout(function () { showTutorialPopup(6); if (game.tutorialContainer) { game.setChildIndex(game.tutorialContainer, game.children.length - 1); } }, 1); } // Update tab appearance Object.keys(tabButtons).forEach(function (t) { if (tabButtons[t]) { tabButtons[t].alpha = t === tab ? 1.0 : 0.7; } }); // Remove old indicator if (tabsContainer.currentIndicator) { tabsContainer.removeChild(tabsContainer.currentIndicator); tabsContainer.currentIndicator.destroy(); } // Create new indicator var newIndicator = LK.getAsset('blower', { width: tabWidth, height: 10, color: 0xFFFF00, alpha: 1.0 }); // Position at the bottom of this tab newIndicator.x = tabButton.x - tabWidth / 2; newIndicator.y = tabHeight - 5; // Add to container and track it tabsContainer.addChild(newIndicator); tabsContainer.currentIndicator = newIndicator; // Hide current tab content, show new tab content if (tabContainers[currentTab]) { tabContainers[currentTab].visible = false; } if (tabContainers[tab]) { tabContainers[tab].visible = true; } // Update current tab currentTab = tab; } return true; }; // Store reference to the button tabButtons[tab] = tabButton; // Add text to tab var tabText = new Text2(tab.charAt(0).toUpperCase() + tab.slice(1), { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); tabText.anchor = { x: 0.5, y: 0.5 }; tabText.x = tabButton.x; tabText.y = tabHeight / 2; // Add to container tabsContainer.addChild(tabButton); tabsContainer.addChild(tabText); }); // Create initial indicator for current tab (bubbles) var activeTabIndicator = LK.getAsset('blower', { width: tabWidth, height: 10, color: 0xFFFF00, alpha: 1.0 }); // Position at the bottom of current tab activeTabIndicator.x = tabButtons[currentTab].x - tabWidth / 2; activeTabIndicator.y = tabHeight - 5; // Add to container tabsContainer.addChild(activeTabIndicator); // Store it in a property for tracking tabsContainer.currentIndicator = activeTabIndicator; // Move the entire text container to align with panel menuTextContainer.y = 0; menuTextContainer.x = 0; // Initialize menu state var menuOpen = false; var menuTargetY = game.height; // Create BP display text var bpText = new Text2("0 BP", { size: 120, fill: 0xFFFFFF, stroke: 0x33caf8, strokeThickness: 4, font: "Impact", fontWeight: "bold" }); bpText.anchor.set(1, 0); bpText.x = game.width - 20; bpText.y = 20; game.addChild(bpText); bpText.visible = false; // Initialize BP tracking game.bp = 0; game.combo = 0; game.lastPopTime = 0; game.COMBO_WINDOW = 60; // 1 second in frames game.addBP = function (points, x, y, isAutoPop) { if (game.titleMode) { return; } var currentTime = LK.ticks; // Only update combo if it's not an auto-pop if (!isAutoPop) { game.lastPopTime = currentTime; } // Ensure points is at least 1 points = Math.max(1, Math.floor(points)); game.bp += points; bpText.setText(formatBP(game.bp) + " BP"); // Set size and color based on point value var textSize = 96; var textColor = 0xFFFF00; // Default yellow if (points > 100) { textSize = 120; textColor = 0xFF0000; // Red for >100 } else if (points > 50) { textSize = 108; textColor = 0xFFA500; // Orange for 51-100 } // Always show point text var pointText = new Text2("+" + points, { size: textSize, fill: textColor, font: "Impact", fontWeight: 'bold' }); pointText.anchorX = 0.5; pointText.anchorY = 0.5; pointText.x = x; pointText.y = y; game.addChild(pointText); // If menu is open, ensure popup is below menu if (menuOpen) { game.setChildIndex(pointText, game.getChildIndex(menuContainer) - 1); } tween(pointText, { y: pointText.y - 100, alpha: 0 }, { duration: 1200, onFinish: function onFinish() { pointText.destroy(); } }); // Only show combo text if it's a manual pop and we have a combo if (!isAutoPop && game.combo > 0) { var comboText = new Text2("x" + (game.combo + 1), { size: 96, fill: 0xFFA500, stroke: 0x000000, strokeThickness: 4, fontWeight: 'bold' }); comboText.anchorX = 0.5; comboText.anchorY = 0; comboText.x = game.width / 2; comboText.y = 20; game.addChild(comboText); tween(comboText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { comboText.destroy(); } }); } }; // Function to create upgrade text (MODIFIED with Registry) function createUpgradeText(category, key, index, isLeftColumn, tab) { var upgrade = UPGRADE_CONFIG[category][key]; if (!upgrade) { return; } var xOffset = isLeftColumn ? -750 : 200; var yPos = startY + index * upgradeSpacing + 120; // Create hit container var hitContainer = new Container(); var hitArea = LK.getAsset('blower', { width: 600, height: 200, color: 0xFFFFFF, alpha: 0.0 }); hitContainer.addChild(hitArea); hitContainer.x = xOffset; hitContainer.y = yPos; hitArea.x = 0; hitArea.y = -40; // Create name text var nameText = new Text2(upgrade.name, { size: 96, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); nameText.x = xOffset; nameText.y = yPos; // Create cost text var cost = getUpgradeCost(upgrade); var costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); costText.x = xOffset; costText.y = yPos + 100; // Register the UI elements if (!game.upgradeRegistry[category]) { game.upgradeRegistry[category] = {}; } game.upgradeRegistry[category][key] = { nameText: nameText, costText: costText, hitContainer: hitContainer, tab: tab }; // Add click handler hitContainer.down = function () { var cost = getUpgradeCost(upgrade); // Special handling for colors that are already purchased if (category === 'colors' && upgrade.currentLevel > 0) { // Toggle this color as active if (UPGRADE_CONFIG.gameSettings.activeColor === key) { // If already active, switch to auto UPGRADE_CONFIG.gameSettings.activeColor = "auto"; game.showMessage("Auto color mode"); } else { // Otherwise activate this color UPGRADE_CONFIG.gameSettings.activeColor = key; game.showMessage(upgrade.name + " activated"); } // Refresh the tab to update the text displays refreshUpgradeTab('colors'); return true; } // Regular upgrade purchase logic if (category === 'colors' && upgrade.requires) { var required = UPGRADE_CONFIG.colors[upgrade.requires]; if (required && required.currentLevel === 0) { game.showError("Unlock " + required.name + " first!"); return true; } } if (game.bp >= cost) { if (category === 'machines') { // Check if this clam type is locked if (upgrade.requires) { var requiredType = UPGRADE_CONFIG.machines[upgrade.requires]; if (requiredType && requiredType.amount < requiredType.maxAmount) { game.showError("Max out " + requiredType.name + " first!"); return true; } } // Check if this clam type is already maxed if (upgrade.amount >= upgrade.maxAmount) { updateCostText(category, key, "SOLD OUT", 0x888888); return true; } // Standard purchase logic if (game.bp >= cost) { upgrade.amount++; if (category === 'machines' && key === 'basicClam' && upgrade.amount === 1 && game.tutorial.stage === 6) { game.tutorial.boughtClam = true; showTutorialPopup(7); } game.bp -= cost; LK.getSound('upgrade').play(); bpText.setText(formatBP(game.bp) + " BP"); // Update cost display if (upgrade.amount >= upgrade.maxAmount) { updateCostText(category, key, "SOLD OUT", 0x888888); } else { updateCostText(category, key, getUpgradeCost(upgrade) + " BP", 0xFFFF00); } // Update visuals updateClamVisuals(); updateCostTexts('clams'); saveGame(); return true; } else { game.showError("Not enough BP!"); return true; } } if (upgrade.amount !== undefined) { // For clams and decorations with amount if (upgrade.amount < (upgrade.maxAmount || 999)) { upgrade.amount++; if (upgrade.amount >= upgrade.maxAmount) { updateCostText(category, key, "SOLD OUT", 0x888888); } game.bp -= cost; LK.getSound('upgrade').play(); bpText.setText(formatBP(game.bp) + " BP"); // Check if we're at the max clam limit after this purchase var newTotalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; // Force update all clam cost displays if we reached the limit if (newTotalClams >= 4) { updateCostText('machines', 'basicClam', "SOLD OUT", 0x888888); } updateCostText(category, key, getUpgradeCost(upgrade) + " BP", 0xFFFF00); // Update visuals if (category === 'machines') { updateClamVisuals(); saveGame(); } else if (category === 'decorations') { if (upgrade.amount >= upgrade.maxAmount) { costText.setText("SOLD OUT"); costText.fill = 0x888888; } else { costText.setText(getUpgradeCost(upgrade) + " BP"); costText.fill = 0xFFFF00; } if (key === 'bubbleCoral') { updateCoralDecorations(); } else if (key === 'sunkenTreasures') { updateTreasureDecorations(); saveGame(); } } } } else if (upgrade.currentLevel < upgrade.maxLevel) { // For regular upgrades with levels upgrade.currentLevel++; game.bp -= cost; LK.getSound('upgrade').play(); bpText.setText(formatBP(game.bp) + " BP"); saveGame(); // If this is a color upgrade, update the UI if (category === 'colors') { refreshUpgradeTab('colors'); return true; } // Update cost text if (upgrade.currentLevel >= upgrade.maxLevel) { updateCostText(category, key, "SOLD OUT", 0x888888); } else { updateCostText(category, key, getUpgradeCost(upgrade) + " BP", 0xFFFF00); } // Handle specific upgrade effects if (category === 'player') { if (key === 'lungCapacity') { var baseSize = UPGRADE_EFFECTS.lungCapacity.baseValue; var increasePercent = UPGRADE_EFFECTS.lungCapacity.incrementPercent; var multiplier = 1 + increasePercent / 100 * upgrade.currentLevel; game.maxBubbleSize = baseSize * multiplier; } else if (key === 'quickBreath') { game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + UPGRADE_EFFECTS.quickBreath.incrementPercent / 100 * upgrade.currentLevel); } } } } else { game.showError("Not enough BP!"); } return true; }; // Add elements to the appropriate tab container tabContainers[tab].addChild(hitContainer); tabContainers[tab].addChild(nameText); tabContainers[tab].addChild(costText); } // Helper function to update cost text (NEW) function updateCostText(category, key, text, color) { if (game.upgradeRegistry[category] && game.upgradeRegistry[category][key] && game.upgradeRegistry[category][key].costText) { var costText = game.upgradeRegistry[category][key].costText; costText.setText(text); costText.fill = color; } } // Function to refresh upgrade tab (MODIFIED) function refreshUpgradeTab(tabName) { // Clear the tab container while (tabContainers[tabName].children.length > 0) { tabContainers[tabName].children[0].destroy(); } // Clear registry entries for this tab Object.keys(game.upgradeRegistry).forEach(function (category) { Object.keys(game.upgradeRegistry[category]).forEach(function (key) { if (game.upgradeRegistry[category][key].tab === tabName) { delete game.upgradeRegistry[category][key]; } }); }); // Recreate all upgrades for the tab if (tabColumns[tabName] && tabColumns[tabName].left) { tabColumns[tabName].left.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, true, tabName); }); } if (tabColumns[tabName] && tabColumns[tabName].right) { tabColumns[tabName].right.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, false, tabName); }); } if (tabName === 'clams') { updateClamVisuals(); updateCostTexts('clams'); } // Update all cost texts for this tab updateCostTexts(tabName); } // Function to update cost texts (NEW) function updateCostTexts(tabName) { Object.keys(game.upgradeRegistry).forEach(function (category) { Object.keys(game.upgradeRegistry[category]).forEach(function (key) { var registry = game.upgradeRegistry[category][key]; // Only update elements in the current tab if (registry.tab !== tabName) { return; } var upgrade = UPGRADE_CONFIG[category][key]; var costText = registry.costText; // Special handling for basic clams - always check total count if (category === 'machines' && key === 'basicClam') { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; if (totalClams >= 4) { costText.setText("SOLD OUT"); costText.fill = 0x888888; return; // Skip any other updates for basic clams } } // Handle color upgrades if (category === 'colors') { var activeColorKey = getActiveColorKey(); if (upgrade.currentLevel > 0) { if (key === activeColorKey || UPGRADE_CONFIG.gameSettings.activeColor === "auto" && key === activeColorKey) { costText.setText("ACTIVE"); costText.fill = 0x00FF00; } else if (upgrade.currentLevel >= upgrade.maxLevel) { costText.setText("SOLD OUT"); costText.fill = 0x888888; } else { costText.setText(getUpgradeCost(upgrade) + " BP"); costText.fill = 0xFFFF00; } } else if (upgrade.requires) { var required = UPGRADE_CONFIG.colors[upgrade.requires]; if (required && required.currentLevel === 0) { costText.setText("LOCKED"); costText.fill = 0x888888; } else { costText.setText(getUpgradeCost(upgrade) + " BP"); costText.fill = 0xFFFF00; } } } // Handle machine upgrades else if (category === 'machines') { var upgrade = UPGRADE_CONFIG[category][key]; // Check if this clam type is locked by requirements if (upgrade.requires) { var requiredType = UPGRADE_CONFIG.machines[upgrade.requires]; if (requiredType && requiredType.amount < requiredType.maxAmount) { costText.setText("LOCKED"); costText.fill = 0x888888; return; } } // Check if maxed out if (upgrade.amount >= upgrade.maxAmount) { costText.setText("SOLD OUT"); costText.fill = 0x888888; } else { costText.setText(getUpgradeCost(upgrade) + " BP"); costText.fill = 0xFFFF00; } } // Handle regular upgrades else if (upgrade.currentLevel >= upgrade.maxLevel) { costText.setText("SOLD OUT"); costText.fill = 0x888888; } else { costText.setText(getUpgradeCost(upgrade) + " BP"); costText.fill = 0xFFFF00; } }); }); } // New function to update all upgrade texts function updateAllUpgradeTexts() { menuTabs.forEach(function (tab) { updateCostTexts(tab); }); } // Replace the old updateColorSelectionUI function with this function updateColorSelectionUI() { updateCostTexts('colors'); } // Variables for upgrade texts var upgradeTexts = []; var startY = 150; var upgradeSpacing = 250; var columnWidth = 1024; // Define legacy variables for backward compatibility var leftColumnUpgrades = [['player', 'lungCapacity'], ['player', 'quickBreath'], ['player', 'autoPop']]; var rightColumnUpgrades = [['machines', 'basicClam'], ['machines', 'advancedClam'], ['machines', 'premiumClam'], ['machine', 'bubbleDurability'], ['machine', 'autoBubbleSpeed']]; // Function to determine which tab an upgrade belongs to (for backward compatibility) function getTabForUpgrade(category, key) { if (category === 'player') { return 'bubbles'; } if (category === 'machines' || category === 'machine') { return 'clams'; } if (category === 'colors') { return 'colors'; } if (category === 'decorations') { return 'decorations'; } return 'bubbles'; // Default } // Function to switch between tabs function switchTab(newTab) { // Hide old tab content, show new tab content tabContainers[currentTab].visible = false; tabContainers[newTab].visible = true; // Update tab button appearance tabButtons[currentTab].alpha = 0.7; tabButtons[newTab].alpha = 1.0; currentTab = newTab; if (game.tutorial.stage === 5 && newTab === 'clams') { showTutorialPopup(6); // Ensure tutorial is on top if (game.tutorialContainer) { game.setChildIndex(game.tutorialContainer, game.children.length - 1); } } } // Function to update treasure decorations function updateTreasureDecorations() { // Clear existing treasures while (treasureContainer.children.length) { treasureContainer.children[0].destroy(); } // Clear zone tracking game.treasureZones = []; var treasureCount = UPGRADE_CONFIG.decorations.sunkenTreasures.amount; if (treasureCount <= 0) { return; } // Available treasure types var treasureTypes = ['treasure1', 'treasure2', 'treasure3']; // Position treasures at specific spots in the bottom half of screen var positions = [ // Left side { x: game.width * 0.25, y: game.height * 0.65 }, // Right side { x: game.width * 0.75, y: game.height * 0.65 }, // Middle bottom { x: game.width * 0.5, y: game.height * 0.8 }]; // Place treasures at predetermined spots for (var i = 0; i < treasureCount; i++) { // Don't exceed available positions if (i >= positions.length) { break; } // Choose treasure type based on position var treasureType = treasureTypes[i % treasureTypes.length]; var pos = positions[i]; // Create circular zone indicator var zoneRadius = game.width * 0.25; var zoneIndicator = LK.getAsset('zoneIndicator', { width: zoneRadius * 2, height: zoneRadius * 2, shape: 'circle', color: 0xFFFFFF, alpha: 0.15, anchorX: 0.5, anchorY: 0.5, x: pos.x, y: pos.y }); // Add zone to tracking for bonus calculation game.treasureZones.push({ id: 'treasure_' + i, x: pos.x, y: pos.y, radius: zoneRadius }); // Create treasure sprite on top of zone var treasure = LK.getAsset(treasureType, { anchorX: 0.5, anchorY: 0.5, x: pos.x, y: pos.y, scaleX: 2.2, scaleY: 2.2, alpha: 0.9 }); // Add zone first (so it's behind treasure) treasureContainer.addChild(zoneIndicator); treasureContainer.addChild(treasure); } } function showTutorialPopup(stage) { // Set current stage game.tutorial.stage = stage; // Remove existing tutorial popup if any if (game.tutorialContainer) { game.tutorialContainer.destroy(); } // Create new popup container game.tutorialContainer = new Container(); game.addChild(game.tutorialContainer); game.setChildIndex(game.tutorialContainer, game.children.length - 1); // Create background var bg = LK.getAsset('blower', { width: game.width * 0.8, height: 500, color: 0x000066, shape: 'box', alpha: 0.8, anchorX: 0.5, anchorY: 0.5, tint: 0x000000 // Use tint to make it black }); // Position above the menu tab bg.x = game.width / 2; bg.y = game.height - 600; game.tutorialContainer.addChild(bg); if (menuOpen) { // Position above the menu when it's open bg.y = game.height - 700; // Move higher up the screen } // Create tutorial text var message = ""; switch (stage) { case 1: // Welcome message = "Welcome to Bubble Blower Tycoon! Open your mouth to start growing a bubble and close to release it. Go ahead and try!"; break; case 2: // After blowing bubble message = "Great! Now go ahead and pop it to collect Bubble Points (BP)."; break; case 3: // After popping message = "That's all there is to it! Now get popping!"; break; case 4: // Can afford upgrade message = "It looks like you've saved enough BP. Open the 'Upgrades' menu at the bottom and choose any upgrade you can afford!"; break; case 5: // After buying upgrade message = "Great choice! Continue exploring different upgrades to build your bubble empire. Good luck on becoming the Bubble Blower Tycoon!"; break; } var tutorialText = new Text2(message, { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact", wordWrap: true, wordWrapWidth: game.width * 0.75 }); tutorialText.anchor = { x: 0.5, y: 0.5 }; tutorialText.x = bg.x; tutorialText.y = bg.y; game.tutorialContainer.addChild(tutorialText); // If final message, add a timer to close if (stage === 3 || stage === 7) { LK.setTimeout(function () { if (game.tutorialContainer) { game.tutorialContainer.destroy(); game.tutorialContainer = null; } }, 6000); } } // Function to update coral bubbles // Function to update coral decorations // Function to update clam visuals function updateClamVisuals() { // Clear existing clams while (clamContainer.children.length) { clamContainer.children[0].destroy(); } // Place clams - now much simpler game.clamSpawnPoints = []; // Process each clam type in order ['basicClam', 'advancedClam', 'premiumClam'].forEach(function (clamType) { var clamCount = UPGRADE_CONFIG.machines[clamType].amount; for (var i = 0; i < clamCount; i++) { // Position logic for each clam instance var isRight = i % 2 === 1; var baseX = isRight ? game.width * 0.9 : game.width * 0.1; var direction = isRight ? -1 : 1; var position = Math.floor(i / 2); var spacing = 250; var x = baseX + direction * position * spacing; var y = game.height - 100; // Create clam sprite var sprite = LK.getAsset(clamType, { anchorX: 0.5, anchorY: 1, x: x, y: y, scaleX: isRight ? -0.5 : 0.5, scaleY: 0.5 }); // Store spawn point for bubble generation game.clamSpawnPoints.push({ x: x + (isRight ? -75 : 75), y: y - 50, type: clamType, isRight: isRight }); clamContainer.addChild(sprite); } }); } // Function to update clams (spawn bubbles) function updateClams() { if (!game.clamSpawnPoints) { return; } game.clamSpawnPoints.forEach(function (spawnPoint) { var config = UPGRADE_CONFIG.machines[spawnPoint.type]; // Calculate production time with speed upgrade var baseTime = config.production * 150; // Convert to frames and further increase base time var autoBubbleLevel = UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel; var baseEffect = UPGRADE_EFFECTS.autoBubbleSpeed.decrementPercent / 100; var effectiveLevel; if (autoBubbleLevel <= 3) { // First 3 levels at full effect effectiveLevel = autoBubbleLevel; } else if (autoBubbleLevel <= 6) { // Levels 4-6 at 75% effectiveness effectiveLevel = 3 + (autoBubbleLevel - 3) * 0.75; } else { // Levels 7+ at 50% effectiveness effectiveLevel = 3 + 3 * 0.75 + (autoBubbleLevel - 6) * 0.5; } var speedMultiplier = Math.pow(1 - baseEffect, effectiveLevel); var adjustedTime = Math.max(1, Math.floor(baseTime * speedMultiplier)); var qualityLevel = UPGRADE_CONFIG.machine.bubbleQuality.currentLevel; if (qualityLevel > 0) { // -10% production rate per level adjustedTime = Math.floor(adjustedTime * (1 + 0.1 * qualityLevel)); } if (LK.ticks % adjustedTime === 0) { // Find first available bubble in pool var bubble = game.bubblePool.find(function (b) { return !b.visible; }); if (bubble && game.activeBubbles.length < game.MAX_BUBBLES) { bubble.activate(spawnPoint.x, spawnPoint.y, config.bubbleSize, false); bubble.fromClam = true; if (UPGRADE_CONFIG.player.sizeVariance.currentLevel > 0) { var variance = UPGRADE_CONFIG.player.sizeVariance.currentLevel; var minIncrease = 0.1 * variance; // +10% per level to min size var maxIncrease = Math.min(0.5, 0.15 * variance); // Cap at +50% total // Apply size variance var sizeMultiplier = 1 - minIncrease + Math.random() * (minIncrease + maxIncrease); bubble.size *= sizeMultiplier; } // Set initial velocities for clam bubbles bubble.verticalVelocity = 0; bubble.driftX = (spawnPoint.isRight ? -1 : 1) * (Math.random() * 1.5 + 2); game.activeBubbles.push(bubble); } } }); } // Initialize twin bubbles array game.twinBubbles = []; game.treasureZones = []; // Initialize game variables for bubbles game.growingBubble = null; game.lastMouthState = false; // Track previous mouth state game.mouthOpenDuration = 0; // Track how long mouth has been open game.MOUTH_OPEN_THRESHOLD = 10; // Frames required with mouth open to start bubble game.MIN_SPAWN_SIZE = 25; // Minimum initial bubble size game.blowCooldown = 0; // Cooldown timer between bubble starts game.BLOW_COOLDOWN_TIME = 15; // Frames to wait between new bubbles game.maxBubbleSize = 120; // Increased by 30% game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + UPGRADE_EFFECTS.quickBreath.incrementPercent / 100 * UPGRADE_CONFIG.player.quickBreath.currentLevel); // Create bubble pool game.bubblePool = Array(250).fill(null).map(function () { return new Bubble(); }); game.popEffectPool = []; var POP_EFFECT_COUNT = 20; game.activeBubbles = []; game.MAX_BUBBLES = 200; // Active bubble limit // Add bubbles to game game.bubblePool.forEach(function (bubble) { game.addChild(bubble); }); for (var i = 0; i < POP_EFFECT_COUNT; i++) { var popEffect = LK.getAsset('zoneIndicator', { alpha: 0, visible: false, anchorX: 0.5, anchorY: 0.5 }); game.addChild(popEffect); game.popEffectPool.push(popEffect); } // Set base spawn rate game.baseSpawnRate = 180; // Every 3 seconds // Adjust existing pop particle pool initialization for more visibility for (var i = 0; i < game.MAX_POP_PARTICLES; i++) { var particle = LK.getAsset('zoneIndicator', { width: 15, height: 15, // Start with larger size alpha: 0, visible: false, anchorX: 0.5, anchorY: 0.5 }); game.addChild(particle); game.popParticlePool.push(particle); } // Enhanced pop effect function game.createPopEffect = function (x, y, size, color) { var popEffect = game.popEffectPool.find(function (p) { return !p.visible; }); if (!popEffect) { return; } // Skip if no available effect // Position and configure the effect popEffect.x = x; popEffect.y = y; popEffect.width = size; popEffect.height = size; popEffect.visible = true; popEffect.alpha = 0.8; popEffect.tint = color || 0xFFFFFF; // Store reference for the callback var effectRef = popEffect; // Simple fade out animation tween(popEffect, { alpha: 0, width: size * 1.15, // Slight growth for better effect height: size * 1.15 }, { duration: 250, // Long enough to be noticed easing: tween.easeOut, onFinish: function onFinish() { if (effectRef) { effectRef.visible = false; } } }); }; // Function to spawn a bubble function spawnBubble(x, y, size) { var direction = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; var isAutoPop = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; if (game.activeBubbles.length >= game.MAX_BUBBLES) { return null; } // Find first available bubble in pool var bubble = game.bubblePool.find(function (b) { return !b.visible; }); if (!bubble) { return null; } bubble.activate(x, y, size); if (isAutoPop) { bubble.verticalVelocity = bubble.floatSpeed; bubble.driftX = direction * (Math.random() * 0.8 + 0.5); } else { bubble.verticalVelocity = -(Math.random() * 2 + 4); bubble.driftX = direction * (Math.random() * 1.5 + 2); } game.activeBubbles.push(bubble); return bubble; } game.startGame = function () { // After fade-out completes, restart with new fade-in LK.playMusic('backgroundmusic', { fade: { start: 0, end: 0.5, duration: 2000 } }); // Remove title screen if (game.titleContainer) { game.titleContainer.destroy(); game.titleContainer = null; } // Exit title mode game.titleMode = false; // Load saved game data FIRST loadGame(); // Initialize BP display bpText.visible = true; // Initialize pufferfish for animation (not visible yet) playerMask.visible = true; playerMask.scaleX = 0.2; playerMask.scaleY = 0.2; playerMask.alpha = 0.8; playerMask.x = game.width / 2; playerMask.y = game.height + 100; // Start animation sequence tween(playerMask, { x: game.width / 2, y: game.height / 2, scaleX: 0.7, scaleY: 0.7, alpha: 1 }, { duration: 1500, easing: tween.easeOutBack, onFinish: function onFinish() { // Enable face tracking after animation game.faceTrackingEnabled = true; // Make menu visible menuContainer.visible = true; // Update clam visuals updateClamVisuals(); updateTreasureDecorations(); } }); // Only show tutorial if not completed if (!storage.tutorialComplete) { LK.setTimeout(function () { showTutorialPopup(1); }, 60); } }; game.tutorial = { stage: 0, // 0=none, 1=welcome, 2=blow bubble, 3=pop bubble, 4=first clam, 5=open menu, 6=clams tab, 7=buy clam, 8=final blownBubble: false, poppedBubble: false, boughtClam: false, tutorialBubble: null, minBubbleSize: 60 // Minimum size to consider a proper blown bubble }; // Game update function game.update = function () { // Update sound cooldowns if (game.autoPopSoundCooldown > 0) { game.autoPopSoundCooldown--; } if (!game.titleMode) { // Tutorial progression logic if (game.tutorial.stage === 1 && game.growingBubble === null && game.tutorial.blownBubble) { // Player released a bubble showTutorialPopup(2); } // Track if player has blown a proper bubble if (game.tutorial.stage === 1 && game.growingBubble && game.growingBubble.size >= game.tutorial.minBubbleSize) { game.tutorial.blownBubble = true; } // Check if we can afford first clam and haven't bought one yet if (game.tutorial.stage === 3 && game.bp >= UPGRADE_CONFIG.machines.basicClam.baseCost && UPGRADE_CONFIG.machines.basicClam.amount === 0) { showTutorialPopup(4); } } if (game.titleMode) { // Just handle bubble spawning and updates during title // Random bubble spawning if (game.activeBubbles.length < game.MAX_BUBBLES) { if (LK.ticks % game.baseSpawnRate == 0) { var x = Math.random() * (game.width - 200) + 100; var titleBubble = spawnBubble(x, game.height + 100, 100, 0, true); // Apply saved color to the bubble if available if (titleBubble && game.titleColorSettings) { applyTitleScreenColor(titleBubble); } } } // Update all active bubbles game.activeBubbles.forEach(function (bubble) { // Always apply color to ensure it's maintained, even for new bubbles from splits if (bubble.visible && game.titleColorSettings) { applyTitleScreenColor(bubble); } if (bubble.update) { bubble.update(); } }); return; // Skip rest of update when in title mode } // Update mouth state and duration if (!game.lastMouthState) { game.mouthOpenDuration = 0; } if (facekit.mouthOpen) { game.mouthOpenDuration++; } else { game.mouthOpenDuration = 0; } // Only allow bubble creation if menu is closed and mouth has been open long enough if (!menuOpen && facekit.mouthOpen && game.mouthOpenDuration >= game.MOUTH_OPEN_THRESHOLD) { // Only allow new bubbles after cooldown if (!game.growingBubble && game.blowCooldown <= 0) { // Fixed vertical offset - keep your preferred value var offsetY = playerMask.height * 0.15; // Convert rotation to radians var angle = playerMask.rotation * (Math.PI / 180); // Calculate rotated position - reduce the X rotation effect by applying a dampening factor var rotatedX = offsetY * Math.sin(angle) * -0.3; // Reduce horizontal movement by multiplying by 0.3 var rotatedY = offsetY * Math.cos(angle); var spawnX = playerMask.x + rotatedX; var spawnY = playerMask.y + rotatedY; var sizeVarianceLevel = UPGRADE_CONFIG.player.sizeVariance.currentLevel; var minSizeMultiplier = 1 + 0.1 * sizeVarianceLevel; var adjustedMinSize = game.MIN_SPAWN_SIZE * minSizeMultiplier; game.growingBubble = spawnBubble(spawnX, spawnY, adjustedMinSize, 0, false); if (game.growingBubble) { game.blowCooldown = game.BLOW_COOLDOWN_TIME; } } if (game.growingBubble) { // Fixed vertical offset var offsetY = playerMask.height * 0.15; // Convert rotation to radians var angle = playerMask.rotation * (Math.PI / 180); // Calculate rotated position with dampening factor on X var rotatedX = offsetY * Math.sin(angle) * -0.3; // Dampen horizontal movement var rotatedY = offsetY * Math.cos(angle); // Update bubble position game.growingBubble.x = playerMask.x + rotatedX; game.growingBubble.y = playerMask.y + rotatedY; game.growingBubble.size = Math.min(game.growingBubble.size + game.growthRate, game.maxBubbleSize); game.growingBubble.verticalVelocity = 0; game.growingBubble.driftX = 0; } } else { if (game.growingBubble) { LK.getSound('bubbleshoot').play(); // Play bloop sound effect when bubble is released // Recalculate float speed and lifetime based on final size game.growingBubble.floatSpeed = 50 * (120 / game.growingBubble.size * (0.9 + Math.random() * 0.2)) / 60; game.growingBubble.initLifetime(); // Then apply the release velocity game.growingBubble.verticalVelocity = -10; game.growingBubble.driftX = (Math.random() * 2 - 1) * 2.5; var twinLevel = UPGRADE_CONFIG.player.twinBubbles.currentLevel; if (twinLevel > 0 && Math.random() < twinLevel * 0.05 + 0.10) { // 5/10/15% chance based on level var twinBubble = spawnBubble(game.growingBubble.x + (Math.random() * 40 - 20), game.growingBubble.y + (Math.random() * 40 - 20), game.growingBubble.size * 0.9, game.growingBubble.driftX * 0.8, false); if (twinBubble) { // Set twin flags and add stars to both bubbles game.growingBubble.setTwin(); twinBubble.setTwin(); // Link the bubbles game.twinBubbles.push({ bubble1: game.growingBubble, bubble2: twinBubble, popped: false, timestamp: LK.ticks }); } } game.growingBubble = null; game.mouthOpenDuration = 0; } } // Update cooldown timer if (game.blowCooldown > 0) { game.blowCooldown--; } game.lastMouthState = facekit.mouthOpen; updateClams(); // Fish spawning (auto-pop upgrade) if (UPGRADE_CONFIG.player.autoPop.currentLevel > 0) { if (LK.ticks % Math.max(60, 960 - UPGRADE_CONFIG.player.autoPop.currentLevel * 120) === 0) { var fish = new Fish(); game.addChild(fish); // If menu is open, ensure fish is below menu if (menuOpen) { game.setChildIndex(fish, game.getChildIndex(menuContainer) - 1); } } } // Jellyfish spawning if (UPGRADE_CONFIG.player.jellyfish.currentLevel > 0) { if (LK.ticks % Math.max(60, 960 - UPGRADE_CONFIG.player.jellyfish.currentLevel * 120) === 0) { var jellyfish = new Jellyfish(); game.addChild(jellyfish); // If menu is open, ensure jellyfish is below menu if (menuOpen) { game.setChildIndex(jellyfish, game.getChildIndex(menuContainer) - 1); } } } // Random bubble spawning if (game.activeBubbles.length < game.MAX_BUBBLES) { if (LK.ticks % game.baseSpawnRate == 0) { var x = Math.random() * (game.width - 200) + 100; spawnBubble(x, game.height + 100, 100, 0, true); } } // Clean up old twin bubble pairs game.twinBubbles = game.twinBubbles.filter(function (pair) { // Remove pairs where one or both bubbles are invisible or time expired if (!pair.bubble1.visible || !pair.bubble2.visible) { return false; // Remove pair immediately when either bubble is invisible } // Keep the pair if it hasn't been popped if (!pair.popped) { return true; } // If popped, check if enough time has passed return LK.ticks - pair.timestamp <= 60; }); // Update all active bubbles game.activeBubbles.forEach(function (bubble) { if (bubble.update) { bubble.update(); } }); if (LK.ticks % 1800 === 0) { saveGame(); } }; // Handle touch/mouse events for the game // Replace the game.down function with this improved version game.down = function (x, y, obj) { if (game.titleMode) { return false; // Let containers handle their own clicks } // Check for bubbles first throughout the entire screen var popped = false; for (var i = game.activeBubbles.length - 1; i >= 0; i--) { var bubble = game.activeBubbles[i]; var dx = x - bubble.x; var dy = y - bubble.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= bubble.size / 2 + 50 && bubble.down) { bubble.down(); popped = true; break; } } // If we popped a bubble, don't check for menu interaction if (popped) { return true; } // Now check for menu tab interaction var localX = x - menuContainer.x; var localY = y - menuContainer.y; var tabBounds = { x: -menuTab.width * menuTab.scaleX / 2, y: -menuTab.height * menuTab.scaleY, width: menuTab.width * menuTab.scaleX, height: menuTab.height * menuTab.scaleY }; if (localX >= tabBounds.x && localX <= tabBounds.x + tabBounds.width && localY >= tabBounds.y && localY <= tabBounds.y + tabBounds.height) { LK.getSound('menuopen').play(); menuOpen = !menuOpen; if (game.tutorial.stage === 4 && menuOpen) { showTutorialPopup(5); // Make sure the tutorial popup is on top of everything if (game.tutorialContainer) { game.setChildIndex(game.tutorialContainer, game.children.length - 1); } } var targetY = menuOpen ? menuTab.height : game.height; if (menuOpen) { game.setChildIndex(menuContainer, game.children.length - 1); // If we're in tutorial, make sure tutorial popup is above menu if (game.tutorialContainer) { LK.setTimeout(function () { game.setChildIndex(game.tutorialContainer, game.children.length - 1); }, 1); // Use tiny delay to run after current frame completes } } tween(menuContainer, { y: targetY }, { duration: 300, easing: tween.easeOutBack, onFinish: function onFinish() { tabsContainer.visible = menuOpen; // Show/hide tabs based on menu state if (!menuOpen) { game.setChildIndex(menuContainer, 1); } } }); return true; } if (menuOpen) { return true; // Let containers handle their own clicks } }; // Initialize decoration visuals updateClamVisuals(); updateTreasureDecorations(); // Initialize upgrade texts for all tabs menuTabs.forEach(function (tab) { if (tabColumns[tab] && tabColumns[tab].left) { tabColumns[tab].left.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, true, tab); }); } if (tabColumns[tab] && tabColumns[tab].right) { tabColumns[tab].right.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, false, tab); }); } }); // Update all upgrade texts to their correct initial state updateAllUpgradeTexts();
===================================================================
--- original.js
+++ change.js
@@ -2178,23 +2178,15 @@
// After popping
message = "That's all there is to it! Now get popping!";
break;
case 4:
- // Can afford clam
- message = "It looks like you've saved enough for your first clam. Go ahead and open up the 'Upgrades' menu at the bottom.";
+ // Can afford upgrade
+ message = "It looks like you've saved enough BP. Open the 'Upgrades' menu at the bottom and choose any upgrade you can afford!";
break;
case 5:
- // Menu opened
- message = "Click on the clams tab.";
+ // After buying upgrade
+ message = "Great choice! Continue exploring different upgrades to build your bubble empire. Good luck on becoming the Bubble Blower Tycoon!";
break;
- case 6:
- // Any upgrade
- message = "Choose any upgrade you can afford. Clams create bubbles automatically, while other upgrades enhance your bubble blowing!";
- break;
- case 7:
- // After buying clam
- message = "Great! There's many other upgrades to explore that will help you on your way. Good luck on becoming the Bubble Blower Tycoon!";
- break;
}
var tutorialText = new Text2(message, {
size: 80,
fill: 0xFFFFFF,
A white bubble with a black outline Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A filled in white circle.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A yellow star. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a game logo for a game called 'Bubble Blower Tycoon' about a happy purple pufferfish with yellow fins and spines that builds an underwater empire of bubbles. Cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
an SVG of the word 'Start'. word should be yellow and the font should look like its made out of bubbles. cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A outstretched straight octopus tentacle. Green with purple suckers. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A colorful underwater coral reef background. Cartoon Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A white bubble with a black outline. Pixel art.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
bubblelow
Sound effect
backgroundmusic
Music
bubblehigh
Sound effect
bubble1
Sound effect
bubble2
Sound effect
bubble3
Sound effect
bubble4
Sound effect
blowing
Sound effect
bubbleshoot
Sound effect
fishtank
Sound effect
menuopen
Sound effect
upgrade
Sound effect
jellyfish
Sound effect
titlemusic
Music
startbutton
Sound effect