User prompt
Update as needed with: // Inside hitContainer.down function where it handles color upgrades if (category === 'colors') { // Purchase the upgrade upgrade.currentLevel++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); // Refresh the entire colors tab to update all text elements refreshUpgradeTab('colors'); return true; // Skip the rest of the function } // Now modify the createUpgradeText function to correctly handle SOLD OUT status for color upgrades // Find the part where it creates the cost text (around line 1100-1150) // Add this condition inside the createUpgradeText function where it creates costText // After checking for requires, add this: if (category === 'colors' && upgrade.currentLevel >= upgrade.maxLevel) { // Create "SOLD OUT" text for maxed out color upgrades costText = new Text2("SOLD OUT", { size: 96, fill: 0x888888, // Gray color stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } else if (category === 'colors' && upgrade.requires) { // The existing code for requires check var required = UPGRADE_CONFIG.colors[upgrade.requires]; if (required && required.currentLevel === 0) { // Create "LOCKED" text for locked upgrades costText = new Text2("LOCKED", { size: 96, fill: 0x888888, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } else { // Normal cost text costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } } else { // Normal cost text for non-color upgrades costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); }
User prompt
Update as needed with: // Add this function near the other utility functions: function refreshUpgradeTab(tabName) { // Clear all children from the tab container while (tabContainers[tabName].children.length > 0) { tabContainers[tabName].children[0].destroy(); } // 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); }); } } // Now modify the hitContainer.down function to call this // Inside hitContainer.down after successful purchase: if (category === 'colors') { upgrade.currentLevel++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); // Refresh the entire colors tab to update all text elements refreshUpgradeTab('colors'); return true; // Skip the rest of the function }
User prompt
Update as needed with: // Inside hitContainer.down function after upgrade.currentLevel++ if (category === 'colors') { // Mark this upgrade as SOLD OUT costText.setText("SOLD OUT"); costText.fill = 0x888888; // Gray color // Now loop through all color upgrades to update their locked status tabContainers['colors'].children.forEach(function(child) { // Only look at Text2 objects if (child.text) { // For each upgrade name that we find var matchingColorKey = null; Object.keys(UPGRADE_CONFIG.colors).forEach(function(colorKey) { if (UPGRADE_CONFIG.colors[colorKey].name === child.text) { matchingColorKey = colorKey; } }); // If we found a matching upgrade name if (matchingColorKey) { var colorUpgrade = UPGRADE_CONFIG.colors[matchingColorKey]; // If this upgrade depends on the one we just bought if (colorUpgrade.requires === key) { // Find its cost text (which is the next text element at the same X position) var costX = child.x; var nameY = child.y; tabContainers['colors'].children.forEach(function(costElement) { if (costElement.text === "LOCKED" && costElement.x === costX && costElement.y > nameY && costElement.y < nameY + 150) { // Check reasonable Y range // Update from LOCKED to actual cost costElement.setText(getUpgradeCost(colorUpgrade) + " BP"); costElement.fill = 0xFFFF00; // Yellow cost color } }); } } } }); }
User prompt
Update with: // Replace the existing color upgrade dependency update code with this: if (category === 'colors') { // Set this upgrade to SOLD OUT costText.setText("SOLD OUT"); costText.fill = 0x888888; // Gray color for sold out // Find any upgrades that depend on this one and update them directly Object.keys(UPGRADE_CONFIG.colors).forEach(function(colorKey) { var colorUpgrade = UPGRADE_CONFIG.colors[colorKey]; if (colorUpgrade.requires === key) { // Find the corresponding name and cost text elements for this dependent upgrade var nameText = null; var lockedText = null; tabContainers['colors'].children.forEach(function(child) { // Find the name text first if (child.text === colorUpgrade.name) { nameText = child; } // Then find the LOCKED text that follows it if (nameText && child.text === "LOCKED" && child.y > nameText.y && !lockedText) { lockedText = child; } }); // Update the locked text if found if (lockedText) { lockedText.setText(getUpgradeCost(colorUpgrade) + " BP"); lockedText.fill = 0xFFFF00; // Yellow color for cost } } }); }
User prompt
Update with: // After the line: upgrade.currentLevel++; // Replace the existing color upgrade dependency update code with this: if (category === 'colors') { // Find any upgrades that depend on this one Object.keys(UPGRADE_CONFIG.colors).forEach(function(colorKey) { var colorUpgrade = UPGRADE_CONFIG.colors[colorKey]; if (colorUpgrade.requires === key) { // This upgrade depends on the one we just purchased // Find all cost texts that need updating in the colors tab tabContainers['colors'].children.forEach(function(child) { if (child.text === "LOCKED") { // Find the matching upgrade name text that comes before this locked text var nameTextFound = false; for (var i = 0; i < tabContainers['colors'].children.length; i++) { var textElement = tabContainers['colors'].children[i]; if (textElement.text === colorUpgrade.name) { nameTextFound = true; } // If we found the name and now found the LOCKED text after it if (nameTextFound && textElement === child) { child.setText(getUpgradeCost(colorUpgrade) + " BP"); child.fill = 0xFFFF00; // Change to standard cost color break; } } } }); } }); // Update to show SOLD OUT costText.setText("SOLD OUT"); costText.fill = 0x888888; // Gray color for sold out }
User prompt
Update as needed with: if (category === 'colors') { // Find all relevant UI elements in the tab const colorContainer = tabContainers.colors; // First update this upgrade's cost to "SOLD OUT" colorContainer.children.forEach(function(child) { if (child.text && child.text.includes("BP")) { // Find the cost text by looking at nearby elements const nameTexts = colorContainer.children.filter(e => e.text === upgrade.name && Math.abs(e.y - child.y) < 150 ); if (nameTexts.length > 0) { // This is the cost text for our upgrade child.setText("SOLD OUT"); child.fill = 0x888888; // Gray color } } }); // Then update any dependent upgrades from "LOCKED" to their cost Object.keys(UPGRADE_CONFIG.colors).forEach(function(colorKey) { const colorUpgrade = UPGRADE_CONFIG.colors[colorKey]; if (colorUpgrade.requires === key) { colorContainer.children.forEach(function(child) { if (child.text === "LOCKED") { // Find the name text near this LOCKED text const nameTexts = colorContainer.children.filter(e => e.text === colorUpgrade.name && Math.abs(e.y - child.y) < 150 ); if (nameTexts.length > 0) { // Update from LOCKED to cost child.setText(getUpgradeCost(colorUpgrade) + " BP"); child.fill = 0xFFFF00; // Yellow for costs } } }); } }); }
User prompt
Update as needed with: // Instead of completely rebuilding the UI, let's just update the text elements if (category === 'colors') { // Update the cost text directly if (upgrade.currentLevel >= upgrade.maxLevel) { costText.setText("SOLD OUT"); costText.fill = 0x888888; // Gray color } else { costText.setText(getUpgradeCost(upgrade) + " BP"); } // Find and update dependent upgrades Object.keys(UPGRADE_CONFIG.colors).forEach(function(colorKey) { const colorUpgrade = UPGRADE_CONFIG.colors[colorKey]; if (colorUpgrade.requires === key) { // Find the text element for this dependent upgrade const dependentTexts = []; tabContainers.colors.children.forEach(function(child) { // Find the name text if (child.text === colorUpgrade.name) { dependentTexts.push(child); } // Find its associated cost text else if (child.text === "LOCKED" && dependentTexts.length > 0 && Math.abs(child.y - dependentTexts[dependentTexts.length-1].y) < 120) { // Update from LOCKED to actual cost child.setText(getUpgradeCost(colorUpgrade) + " BP"); child.fill = 0xFFFF00; // Yellow for costs } }); } }); }
User prompt
Update with: // After: upgrade.currentLevel++; // Before updating cost text, check for max level if (upgrade.currentLevel >= upgrade.maxLevel) { costText.setText("SOLD OUT"); costText.fill = 0x888888; // Set to gray color } else { costText.setText(getUpgradeCost(upgrade) + " BP"); }
User prompt
Update with: // First, check if the upgrade is already at max level if (upgrade.currentLevel >= upgrade.maxLevel) { // Create "SOLD OUT" text for maxed upgrades costText = new Text2("SOLD OUT", { size: 96, fill: 0x888888, // Gray color stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } else if (category === 'colors' && upgrade.requires) { // Rest of the existing code for locked/unlocked states const required = UPGRADE_CONFIG.colors[upgrade.requires]; if (required && required.currentLevel === 0) { // Create "LOCKED" text for locked upgrades costText = new Text2("LOCKED", { size: 96, fill: 0x888888, // Gray color stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } else { // Normal cost text costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } } else { // Normal cost text for non-color upgrades costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); }
User prompt
Update as needed with: UPGRADE_CONFIG.colors.blueBubbles.currentLevel); // Then proceed with createUpgradeText calls // This will now use the updated check for max level tabColumns.colors.left.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, true, 'colors'); }); tabColumns.colors.right.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, false, 'colors'); });
User prompt
Update with: // Look for the upgrade purchase code in hitContainer.down // After successful purchase and upgrade.currentLevel++ for a color upgrade: // If this is a color upgrade, update the locked state of dependent upgrades if (category === 'colors') { // Force recreate all upgrade texts in the colors tab to refresh their state while (tabContainers.colors.children.length > 0) { tabContainers.colors.children[0].destroy(); } // Recreate with updated locked/unlocked status if (tabColumns.colors && tabColumns.colors.left) { tabColumns.colors.left.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, true, 'colors'); }); } if (tabColumns.colors && tabColumns.colors.right) { tabColumns.colors.right.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, false, 'colors'); }); } }
Code edit (1 edits merged)
Please save this source code
User prompt
Increase it some more.
User prompt
Increase it some more.
User prompt
Increase the base time between clam bubbles for all clam types.
User prompt
Update as needed with: // Modify getBP to use the color multiplier self.getBP = function() { let baseValue = Math.max(1, Math.floor(Math.pow(self.size, 1.4) * 0.02)); // Apply color multiplier baseValue = Math.floor(baseValue * self.colorMultiplier); return baseValue; };
User prompt
Update as needed with: // Update the Bubble.update method to handle prismatic color changes self.update = function() { // Begin of update // Update prismatic bubbles every frame if (UPGRADE_CONFIG.colors.prismaticBubbles.currentLevel > 0) { self.colorPhase += 0.02; const r = Math.sin(self.colorPhase) * 127 + 128; const g = Math.sin(self.colorPhase + 2) * 127 + 128; const b = Math.sin(self.colorPhase + 4) * 127 + 128; const 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 const colorValue = Math.sin(self.colorPhase) * 0.5 + 0.5; self.colorMultiplier = 0.8 + colorValue * 1.2; // 0.8x to 2.0x } // Rest of update code... };
User prompt
Update as needed with: // In the Bubble class's activate method, where bubbles are reset for reuse 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; // ... other resets ... // 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(); self.visible = true; // rest of activate code... }; // Add a dedicated method for applying color upgrades self.applyColorUpgrade = function() { // Only apply if any color upgrade is active if (UPGRADE_CONFIG.colors.blueBubbles.currentLevel > 0) { let color = 0xFFFFFF; let multiplier = 1.0; // Determine the highest active color upgrade if (UPGRADE_CONFIG.colors.prismaticBubbles.currentLevel > 0) { // Initial color for prismatic (will change over time) self.colorPhase = Math.random() * Math.PI * 2; const r = Math.sin(self.colorPhase) * 127 + 128; const g = Math.sin(self.colorPhase + 2) * 127 + 128; const b = Math.sin(self.colorPhase + 4) * 127 + 128; color = (Math.floor(r) << 16) + (Math.floor(g) << 8) + Math.floor(b); multiplier = 1.6; } else if (UPGRADE_CONFIG.colors.rainbowBubbles.currentLevel > 0) { // Rainbow - pick a random color from set const rainbowColors = [ 0xFF0000, // Red 0xFFAA00, // Orange 0xFFFF00, // Yellow 0x00FF00, // Green 0x0000FF, // Blue 0xFF00FF // Purple ]; const multipliers = [1.5, 1.4, 1.3, 1.2, 1.1, 1.6]; const index = Math.floor(Math.random() * rainbowColors.length); color = rainbowColors[index]; multiplier = multipliers[index]; } 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; } // Apply the color and multiplier sprite.tint = color; self.colorTint = color; self.colorMultiplier = multiplier; // Log to verify colors are being applied console.log('Applied bubble color:', color.toString(16), 'multiplier:', multiplier); } };
User prompt
Update as needed with: // In the Bubble class's activate method, where bubbles are reset for reuse 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; // ... other resets ... // 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(); self.visible = true; // rest of activate code... }; // Add a dedicated method for applying color upgrades self.applyColorUpgrade = function() { // Only apply if any color upgrade is active if (UPGRADE_CONFIG.colors.blueBubbles.currentLevel > 0) { let color = 0xFFFFFF; let multiplier = 1.0; // Determine the highest active color upgrade if (UPGRADE_CONFIG.colors.prismaticBubbles.currentLevel > 0) { // Initial color for prismatic (will change over time) self.colorPhase = Math.random() * Math.PI * 2; const r = Math.sin(self.colorPhase) * 127 + 128; const g = Math.sin(self.colorPhase + 2) * 127 + 128; const b = Math.sin(self.colorPhase + 4) * 127 + 128; color = (Math.floor(r) << 16) + (Math.floor(g) << 8) + Math.floor(b); multiplier = 1.6; } else if (UPGRADE_CONFIG.colors.rainbowBubbles.currentLevel > 0) { // Rainbow - pick a random color from set const rainbowColors = [ 0xFF0000, // Red 0xFFAA00, // Orange 0xFFFF00, // Yellow 0x00FF00, // Green 0x0000FF, // Blue 0xFF00FF // Purple ]; const multipliers = [1.5, 1.4, 1.3, 1.2, 1.1, 1.6]; const index = Math.floor(Math.random() * rainbowColors.length); color = rainbowColors[index]; multiplier = multipliers[index]; } 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; } // Apply the color and multiplier sprite.tint = color; self.colorTint = color; self.colorMultiplier = multiplier; // Log to verify colors are being applied console.log('Applied bubble color:', color.toString(16), 'multiplier:', multiplier); } };
User prompt
Update with: // Find where upgrade purchase happens and add the following code // Look for the block where upgrade.currentLevel++ happens // This should be inside the click handler (hitContainer.down function) // After: upgrade.currentLevel++; // Add: // If this is a color upgrade, update the UI to reflect dependency status if (category === 'colors') { // Find any upgrades that depend on this one Object.keys(UPGRADE_CONFIG.colors).forEach(function(colorKey) { const colorUpgrade = UPGRADE_CONFIG.colors[colorKey]; if (colorUpgrade.requires === key) { // This upgrade depends on the one we just purchased // We need to find and update its cost text Object.values(tabContainers).forEach(function(tabContainer) { tabContainer.children.forEach(function(child) { if (child.text === "LOCKED" && child.y > tabContainer.children.find(function(c) { return c.text === colorUpgrade.name; }).y) { // Update this text to show the cost instead of LOCKED child.setText(getUpgradeCost(colorUpgrade) + " BP"); child.fill = 0xFFFF00; // Change to standard cost color } }); }); } }); }
User prompt
Update as needed with: // Modify the Bubble constructor where sprite is created var sprite = self.attachAsset('bubble', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); // Problem: tint might not be working, let's use a more direct approach self.updateColor = function() { // Check if color upgrades are active if (UPGRADE_CONFIG.colors.blueBubbles.currentLevel > 0) { let color = 0xFFFFFF; let multiplier = 1.0; if (UPGRADE_CONFIG.colors.prismaticBubbles.currentLevel > 0) { // Prismatic calculations remain the same self.colorPhase += 0.02; const colorValue = Math.sin(self.colorPhase) * 0.5 + 0.5; multiplier = 0.8 + colorValue * 1.2; const r = Math.sin(self.colorPhase) * 127 + 128; const g = Math.sin(self.colorPhase + 2) * 127 + 128; const b = Math.sin(self.colorPhase + 4) * 127 + 128; color = (Math.floor(r) << 16) + (Math.floor(g) << 8) + Math.floor(b); } // Rest of color logic remains the same // Apply the color - make sure this works with the engine sprite.tint = color; // Try direct assignment // If that doesn't work, try: // sprite.color = color; self.colorTint = color; self.colorMultiplier = multiplier; } }
User prompt
Tint default bubbles blue.
User prompt
Tint the bubbles blue.
User prompt
Please fix the bug: 'self.updateColor is not a function. (In 'self.updateColor()', 'self.updateColor' is undefined)' in or related to this line: 'self.updateColor();' Line Number: 75
User prompt
Update as needed with: // Add to Bubble constructor self.colorTint = 0xFFFFFF; // Default white tint self.colorMultiplier = 1.0; // Default multiplier self.colorPhase = Math.random() * Math.PI * 2; // Random starting phase // Add color update method to Bubble class self.updateColor = function() { // Check if color upgrades are active if (UPGRADE_CONFIG.colors.blueBubbles.currentLevel > 0) { let color = 0xFFFFFF; let multiplier = 1.0; if (UPGRADE_CONFIG.colors.prismaticBubbles.currentLevel > 0) { // Prismatic - constantly shifting color with variable multiplier self.colorPhase += 0.02; const 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 const r = Math.sin(self.colorPhase) * 127 + 128; const g = Math.sin(self.colorPhase + 2) * 127 + 128; const 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 const 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; } } // Modify Bubble.activate to call updateColor self.activate = function (x, y, size, isPlayerBlown) { // Existing activate code... // Initialize color at activation self.updateColor(); } // Modify Bubble.getBP to include color multiplier self.getBP = function () { let baseValue = Math.max(1, Math.floor(Math.pow(self.size, 1.4) * 0.02)); // Apply Bubble Refinement upgrade const refinementLevel = UPGRADE_CONFIG.player.bubbleRefinement.currentLevel; if (refinementLevel > 0) { baseValue *= (1 + 0.25 * refinementLevel); } // Apply color multiplier baseValue *= self.colorMultiplier; return baseValue; }; // Add updateColor call to Bubble.update for prismatic bubbles // Add near the start of the update method if (UPGRADE_CONFIG.colors.prismaticBubbles.currentLevel > 0) { self.updateColor(); }
/**** * Plugins ****/ var tween = LK.import("@upit/tween.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.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 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 console.log('Activating bubble:', { size: self.size, maxLifetime: self.maxLifetime, isPlayerBlown: isPlayerBlown }); }; 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; // Determine the highest active color upgrade if (UPGRADE_CONFIG.colors.prismaticBubbles.currentLevel > 0) { // Initial color for prismatic (will change over time) self.colorPhase = Math.random() * Math.PI * 2; 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); multiplier = 1.6; } else if (UPGRADE_CONFIG.colors.rainbowBubbles.currentLevel > 0) { // Rainbow - pick a random color from set var rainbowColors = [0xFF0000, // Red 0xFFAA00, // 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 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; } // Apply the color and multiplier sprite.tint = color; self.colorTint = color; self.colorMultiplier = multiplier; // Log to verify colors are being applied console.log('Applied bubble color:', color.toString(16), 'multiplier:', multiplier); } }; self.deactivate = function () { self.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; self.down = function (e) { var currentTime = Date.now(); if (currentTime - self.lastPopTime < 100) { return true; } self.lastPopTime = currentTime; var points = self.getBP(); 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; // Reduce the direction multiplier to 0.5 to make movement more gentle spawnBubble(self.x, self.y, newSize, Math.cos(angle) * 0.5, false); } } // Check if this bubble is part of twin pair 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.5 + 0.25 * twinLevel; // 50/75/100% bonus // Pop the other bubble automatically and add bonus points if (otherBubble.visible) { var bonusPoints = Math.floor(otherBubble.getBP() * bonusMultiplier); game.addBP(bonusPoints, otherBubble.x, otherBubble.y, false); otherBubble.deactivate(); } } self.deactivate(); return true; }; self.getBP = function () { var baseValue = Math.max(1, Math.floor(Math.pow(self.size, 1.4) * 0.02)); // 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); 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 } // 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) { console.log('Bubble dying - lifetime:', self.lifetime, 'maxLifetime:', 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) { // Just deactivate, no points self.visible = false; var index = game.activeBubbles.indexOf(self); if (index > -1) { game.activeBubbles.splice(index, 1); } 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; }; 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); game.addBP(points, self.x, self.y, true); self.autoPopDisplayed = true; } 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 + 50) { // Increased from 30 to 50 // 30px collision radius var points = bubble.getBP(); game.addBP(points, bubble.x, bubble.y, false); // false = manual pop points bubble.deactivate(); } } }); // Remove when off screen if (self.fromLeft && self.x > game.width + 100 || !self.fromLeft && self.x < -100) { self.destroy(); } }; 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 () { // 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 ****/ game.twinBubbles = []; // Define column assignments for each tab var tabColumns = { bubbles: { left: [['player', 'lungCapacity'], ['player', 'quickBreath'], ['player', 'bubbleRefinement'], ['player', 'twinBubbles']], right: [['player', 'autoPop'], ['player', 'sizeVariance'], ['player', 'bubbleBreath']] }, clams: { left: [['machines', 'basicClam'], ['machines', 'advancedClam'], ['machines', 'premiumClam']], right: [['machine', 'bubbleDurability'], ['machine', 'autoBubbleSpeed'], ['machine', 'bubbleQuality']] }, colors: { left: [['colors', 'blueBubbles'], ['colors', 'greenBubbles'], ['colors', 'pinkBubbles']], right: [['colors', 'rainbowBubbles'], ['colors', 'prismaticBubbles']] }, decorations: { left: [['decorations', 'bubbleCoral']], right: [['decorations', 'sunkenTreasures']] } }; var UPGRADE_EFFECTS = { lungCapacity: { baseValue: 160, // Base max bubble size incrementPercent: 25 // +25% per level }, quickBreath: { baseValue: 1.6, // Base growth rate incrementPercent: 25 // +25% per level }, autoBubbleSpeed: { decrementPercent: 10 // -10% production time per level }, bubbleDurability: { extraSplits: 1 // +1 split per level }, autoPop: { timeReduction: 0.8 // Reduces lifetime by 20% per level } }; function _slicedToArray3(r, e) { return _arrayWithHoles3(r) || _iterableToArrayLimit3(r, e) || _unsupportedIterableToArray3(r, e) || _nonIterableRest3(); } function _nonIterableRest3() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray3(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray3(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray3(r, a) : void 0; } } function _arrayLikeToArray3(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } function _iterableToArrayLimit3(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) { return; } f = !1; } else { for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) { ; } } } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) { return; } } finally { if (o) { throw n; } } } return a; } } function _arrayWithHoles3(r) { if (Array.isArray(r)) { return r; } } function getUpgradeCost(upgrade) { if (upgrade.amount !== undefined) { // For clams 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 _slicedToArray2(r, e) { return _arrayWithHoles2(r) || _iterableToArrayLimit2(r, e) || _unsupportedIterableToArray2(r, e) || _nonIterableRest2(); } function _nonIterableRest2() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray2(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray2(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray2(r, a) : void 0; } } function _arrayLikeToArray2(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } function _iterableToArrayLimit2(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) { return; } f = !1; } else { for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) { ; } } } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) { return; } } finally { if (o) { throw n; } } } return a; } } function _arrayWithHoles2(r) { if (Array.isArray(r)) { return r; } } 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 speedMultiplier = Math.pow(1 - UPGRADE_EFFECTS.autoBubbleSpeed.decrementPercent / 100, UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel); 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 // not player blown ); 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 = 0.15 * variance; // +15% per level to max size // 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); } } }); } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) { return; } f = !1; } else { for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) { ; } } } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) { return; } } finally { if (o) { throw n; } } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) { return r; } } var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: game.width / 2, y: game.height / 2 }); game.addChild(background); var clamContainer = new Container(); game.addChild(clamContainer); var playerMask = new pufferMask(); game.addChild(playerMask); var UPGRADE_CONFIG = { player: { lungCapacity: { name: "Lung Capacity", baseCost: 100, // About 1-2 max bubbles costScale: 3.0, // Steeper scaling maxLevel: 10, currentLevel: 0 }, quickBreath: { name: "Quick Breath", baseCost: 100, // 2-3 max bubbles costScale: 3.0, // Steeper scaling maxLevel: 10, currentLevel: 0 }, autoPop: { // Moved here from machine section name: "Fish Friends", // Updated name baseCost: 400, // Changed from 1000 costScale: 3, maxLevel: 6, // Changed from 5 currentLevel: 0 }, bubbleRefinement: { // New upgrade name: "Bubble Refinement", baseCost: 2500, costScale: 2.5, maxLevel: 5, currentLevel: 0 }, twinBubbles: { // New upgrade name: "Twin Bubbles", baseCost: 5000, costScale: 2.3, maxLevel: 3, currentLevel: 0 }, sizeVariance: { // New upgrade name: "Size Variance", baseCost: 4000, costScale: 1.8, maxLevel: 5, currentLevel: 0 }, bubbleBreath: { // New upgrade name: "Bubble Breath", baseCost: 12000, costScale: 2.2, maxLevel: 4, currentLevel: 0 } }, machines: { basicClam: { name: "Basic Clam", baseCost: 300, // 6-7 max bubbles costScale: 2, // Slightly steeper scaling amount: 0, production: 3, bubbleSize: 80 }, advancedClam: { name: "Advanced Clam", baseCost: 3000, // Requires running basic clams for a while costScale: 3.0, amount: 0, production: 2, bubbleSize: 100, unlockCost: 3000 }, premiumClam: { name: "Premium Clam", baseCost: 20000, // True end-game content costScale: 3, amount: 0, production: 1, bubbleSize: 150, unlockCost: 20000 } }, machine: { bubbleDurability: { name: "Bubble Splitting", baseCost: 500, costScale: 5, // Significant scaling maxLevel: 3, currentLevel: 0 }, autoBubbleSpeed: { name: "Clam Speed", baseCost: 500, costScale: 3.5, maxLevel: 8, currentLevel: 0 }, bubbleQuality: { // New upgrade name: "Bubble Quality", baseCost: 3000, costScale: 2.2, maxLevel: 5, currentLevel: 0 } }, colors: { // New section blueBubbles: { name: "Blue Bubbles", baseCost: 1000, costScale: 1.0, // Only one level maxLevel: 1, currentLevel: 0 }, greenBubbles: { name: "Green Bubbles", baseCost: 3000, costScale: 1.0, // Only one level maxLevel: 1, currentLevel: 0, requires: "blueBubbles" }, pinkBubbles: { name: "Pink Bubbles", baseCost: 6000, costScale: 1.0, // Only one level maxLevel: 1, currentLevel: 0, requires: "greenBubbles" }, rainbowBubbles: { name: "Rainbow Bubbles", baseCost: 15000, costScale: 1.0, // Only one level maxLevel: 1, currentLevel: 0, requires: "pinkBubbles" }, prismaticBubbles: { name: "Prismatic Bubbles", baseCost: 30000, costScale: 1.0, // Only one level maxLevel: 1, currentLevel: 0, requires: "rainbowBubbles" } }, decorations: { // New section bubbleCoral: { name: "Bubble Coral", baseCost: 10000, costScale: 2.0, amount: 0, maxAmount: 5 }, sunkenTreasures: { name: "Sunken Treasures", baseCost: 25000, costScale: 2.5, amount: 0, maxAmount: 3 } } }; game.showError = function (message) { var errorText = new Text2(message, { size: 120, fill: 0xFF0000, stroke: 0x000000, strokeThickness: 5, font: "Impact" }); // Set the anchor point to center the text errorText.anchor = { x: 0.5, y: 0.5 }; // Position at the center of the screen errorText.x = game.width / 2; errorText.y = game.height / 2; game.addChild(errorText); // Animate the text tween(errorText, { alpha: 0, y: errorText.y - 50 }, { duration: 1200, onFinish: function onFinish() { errorText.destroy(); } }); }; var currentTab = 'bubbles'; // Default tab var menuTabs = ['bubbles', 'clams', 'colors', 'decorations']; var tabButtons = {}; // Will hold references to tab buttons // Create upgrade menu elements // Menu tab (handle) // First position the panel relative to container at y=0 // Menu panel should be below the tab in the container var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, // Change to bottom anchor y: 0, // Will be at container's position scaleX: 3, scaleY: 0.8, alpha: 0.9 }); var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, y: -570, alpha: 0.9, scaleX: 2048 / 200, // Use the width of the asset directly scaleY: game.height * 0.4 / 100.3 }); // Initialize menu structure // Initialize 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 setup - should be good as is 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); // Add this after the menuPanel is created but before upgradeTexts are created // Create text container AFTER panel scaling var menuTextContainer = new Container(); menuContainer.addChild(menuTextContainer); var tabContainers = {}; menuTabs.forEach(function (tab) { var contentContainer = new Container(); contentContainer.visible = tab === currentTab; tabContainers[tab] = contentContainer; menuTextContainer.addChild(contentContainer); }); // Create text container AFTER panel scaling var menuTextContainer = new Container(); menuContainer.addChild(menuTextContainer); // Create tab container (only visible when menu is open) var tabsContainer = new Container(); // Position tabsContainer at the bottom of the panel 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) { // Create tab button var tabButton = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, // Anchor to top // Position evenly across panel width x: -menuPanel.width * menuPanel.scaleX / 2 + (index + 0.5) * tabWidth, y: 0, // Start at the container's position scaleX: tabWidth / 200, scaleY: tabHeight / 299, // Updated for new height alpha: tab === currentTab ? 1.0 : 0.7 }); // Add hit detection to the tab button tabButton.down = function () { // Only do something if this isn't already the current tab if (tab !== currentTab) { // Update tab appearance Object.keys(tabButtons).forEach(function (t) { if (tabButtons[t]) { tabButtons[t].alpha = t === tab ? 1.0 : 0.7; } }); // 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 to indicate the event was handled 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), { // Capitalize first letter size: 80, // Increased from 50 fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, // Slightly thicker for larger text 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); }); // 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; } // Add upgrade texts to text container instead of panel var upgradeTexts = []; var startY = 150; var upgradeSpacing = 250; var columnWidth = 1024; // Create arrays to hold upgrade categories in desired order 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 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 create upgrade text function createUpgradeText(category, key, index, isLeftColumn, tab) { var upgrade = UPGRADE_CONFIG[category][key]; if (!upgrade) { return; } // Skip if upgrade doesn't exist var xOffset = isLeftColumn ? -750 : 200; // Changed from -550 and 100 var yPos = startY + index * upgradeSpacing + 120; // Added 120 to move everything down // Create hit container var hitContainer = new Container(); var hitArea = LK.getAsset('blower', { width: 400, height: 150, 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; if (category === 'colors' && upgrade.requires) { var required = UPGRADE_CONFIG.colors[upgrade.requires]; if (required && required.currentLevel === 0) { // Create "LOCKED" text for locked upgrades costText = new Text2("LOCKED", { size: 96, fill: 0x888888, // Gray color stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } else { // Normal cost text costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } } else { // Normal cost text for non-color upgrades costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } costText.x = xOffset; costText.y = yPos + 100; // Add click handler hitContainer.down = function () { var cost = getUpgradeCost(upgrade); // Check if this is a locked color upgrade 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 (upgrade.amount !== undefined) { // For clams and decorations with amount if (upgrade.amount < (upgrade.maxAmount || 999)) { upgrade.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); costText.setText(getUpgradeCost(upgrade) + " BP"); // Update clam visuals if needed if (category === 'machines') { updateClamVisuals(); } // Update decoration visuals if implemented if (category === 'decorations') { // TODO: Implement decoration visuals } } } else if (upgrade.currentLevel < upgrade.maxLevel) { // For regular upgrades with levels upgrade.currentLevel++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); // If this is a color upgrade, update the UI to reflect dependency status if (category === 'colors') { // Find any upgrades that depend on this one Object.keys(UPGRADE_CONFIG.colors).forEach(function (colorKey) { var colorUpgrade = UPGRADE_CONFIG.colors[colorKey]; if (colorUpgrade.requires === key) { // This upgrade depends on the one we just purchased // We need to find and update its cost text Object.values(tabContainers).forEach(function (tabContainer) { tabContainer.children.forEach(function (child) { if (child.text === "LOCKED" && child.y > tabContainer.children.find(function (c) { return c.text === colorUpgrade.name; }).y) { // Update this text to show the cost instead of LOCKED child.setText(getUpgradeCost(colorUpgrade) + " BP"); child.fill = 0xFFFF00; // Change to standard cost color } }); }); } }); } // Update cost text if (upgrade.currentLevel >= upgrade.maxLevel) { costText.setText("SOLD OUT"); } else { costText.setText(getUpgradeCost(upgrade) + " BP"); } // 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); } // Other upgrade effects will be implemented later } } } 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); } // Replace the existing code for creating upgrade texts with this: Object.keys(tabContainers).forEach(function (tab) { // Clear any existing content while (tabContainers[tab].children.length > 0) { tabContainers[tab].children[0].destroy(); } // Create left column for this tab if (tabColumns[tab] && tabColumns[tab].left) { tabColumns[tab].left.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, true, tab); }); } // Create right column for this tab if (tabColumns[tab] && tabColumns[tab].right) { tabColumns[tab].right.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, false, tab); }); } }); // Move the entire text container down by adjusting its Y position menuTextContainer.y = 0; // This should align it with the top of the panel instead of being above it menuTextContainer.x = 0; // Center in panel // Menu state and animation var menuOpen = false; var menuTargetY = game.height; function updateClamVisuals() { while (clamContainer.children.length) { clamContainer.children[0].destroy(); } var leftStart = game.width * 0.1; // Moved further left from 0.15 var rightStart = game.width * 0.9; // Moved further right from 0.85 var spacing = 250; // Increased from 150 var y = game.height - 100; // We'll store type of each clam position (0-3) var clamTypes = []; // Fill with basic clams first for (var i = 0; i < UPGRADE_CONFIG.machines.basicClam.amount; i++) { clamTypes.push('basicClam'); } // Replace some with advanced for (var i = 0; i < UPGRADE_CONFIG.machines.advancedClam.amount; i++) { if (clamTypes[i]) { clamTypes[i] = 'advancedClam'; } } // Replace some with premium for (var i = 0; i < UPGRADE_CONFIG.machines.premiumClam.amount; i++) { if (clamTypes[i]) { clamTypes[i] = 'premiumClam'; } } // Place clams game.clamSpawnPoints = []; clamTypes.forEach(function (type, i) { var isRight = i % 2 === 1; var baseX = isRight ? rightStart : leftStart; var direction = isRight ? -1 : 1; var position = Math.floor(i / 2); var x = baseX + direction * position * spacing; var sprite = LK.getAsset(type, { anchorX: 0.5, anchorY: 1, x: x, y: y, scaleX: isRight ? -0.5 : 0.5, // Flip right-side clams scaleY: 0.5 }); // Store spawn point for this clam game.clamSpawnPoints.push({ x: x + (isRight ? -75 : 75), // 25% from edge y: y - 50, // Slightly above clam type: type, isRight: isRight }); clamContainer.addChild(sprite); }); } updateClamVisuals(); // Add this to the end of the game initialization code, after updating the clam visuals function updateAllUpgradeTexts() { // Process left column upgrades leftColumnUpgrades.forEach(function (upgrade) { var category = upgrade[0]; var key = upgrade[1]; var upgradeConfig = UPGRADE_CONFIG[category][key]; // Find the cost text for this upgrade Object.values(tabContainers).forEach(function (tabContainer) { tabContainer.children.forEach(function (child) { if (child.text && child.text.includes("BP") && child.y > tabContainer.children.find(function (c) { return c.text === upgradeConfig.name; }).y) { // Check if max level reached if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) { child.setText("SOLD OUT"); } } }); }); }); // Process right column upgrades rightColumnUpgrades.forEach(function (upgrade) { var category = upgrade[0]; var key = upgrade[1]; if (category === 'machines') { var upgradeConfig = UPGRADE_CONFIG[category][key]; var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; // Find the cost text for this upgrade Object.values(tabContainers).forEach(function (tabContainer) { tabContainer.children.forEach(function (child) { if (child.text && child.text.includes("BP") && child.y > tabContainer.children.find(function (c) { return c.text === upgradeConfig.name; }).y) { // Check if sold out based on clam logic if (totalClams >= 4 && key === 'basicClam' || UPGRADE_CONFIG.machines.basicClam.amount <= UPGRADE_CONFIG.machines.advancedClam.amount && key === 'advancedClam' || UPGRADE_CONFIG.machines.advancedClam.amount <= UPGRADE_CONFIG.machines.premiumClam.amount && key === 'premiumClam') { child.setText("SOLD OUT"); } } }); }); } else if (category === 'machine') { var upgradeConfig = UPGRADE_CONFIG[category][key]; // Find the cost text for this upgrade Object.values(tabContainers).forEach(function (tabContainer) { tabContainer.children.forEach(function (child) { if (child.text && child.text.includes("BP") && child.y > tabContainer.children.find(function (c) { return c.text === upgradeConfig.name; }).y) { // Check if max level reached if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) { child.setText("SOLD OUT"); } } }); }); } }); } // Call the function after creating all upgrades updateAllUpgradeTexts(); // Initialize game variables 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 = 160; // Increased by 30% game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + UPGRADE_EFFECTS.quickBreath.incrementPercent / 100 * UPGRADE_CONFIG.player.quickBreath.currentLevel); game.bubblePool = Array(250).fill(null).map(function () { return new Bubble(); }); game.activeBubbles = []; game.MAX_BUBBLES = 200; // Active bubble limit game.bubblePool.forEach(function (bubble) { game.addChild(bubble); }); game.baseSpawnRate = 180; // Every 3 seconds 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; } // Initialize game variables //<Assets used in the game will automatically appear here> game.bp = 0; // Track total BP game.combo = 0; game.lastPopTime = 0; game.COMBO_WINDOW = 60; // 1 second in frames 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]; } // Create BP display text (add near game initialization) 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); game.addBP = function (points, x, y, isAutoPop) { var currentTime = LK.ticks; // Only update combo if it's not an auto-pop if (!isAutoPop) { if (currentTime - game.lastPopTime < game.COMBO_WINDOW) { game.combo++; points *= 1 + game.combo * 0.1; // 10% bonus per combo } else { game.combo = 0; } 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 regardless of auto or manual pop 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); 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(); } }); } }; game.update = function () { // 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) { var spawnX = playerMask.x; var spawnY = playerMask.y + playerMask.height * 0.15; 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) { game.growingBubble.x = playerMask.x; game.growingBubble.y = playerMask.y + playerMask.height * 0.15; game.growingBubble.size = Math.min(game.growingBubble.size + game.growthRate, game.maxBubbleSize); game.growingBubble.verticalVelocity = 0; game.growingBubble.driftX = 0; } } else { if (game.growingBubble) { // 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) { // 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) { // Link the bubbles game.twinBubbles.push({ bubble1: game.growingBubble, bubble2: twinBubble, popped: false, timestamp: LK.ticks }); } } game.growingBubble = null; var breathLevel = UPGRADE_CONFIG.player.bubbleBreath.currentLevel; if (breathLevel > 0) { // Calculate chances for extra bubbles var extraBubbles = 0; // Level 1: 20% for +1 // Level 2: 40% for +1 // Level 3: 60% for +1, 20% for +2 // Level 4: 80% for +1, 40% for +2 var rand = Math.random(); if (rand < breathLevel * 0.2) { extraBubbles++; // Check for 2nd extra bubble at level 3-4 if (breathLevel >= 3 && rand < (breathLevel - 2) * 0.2) { extraBubbles++; } } // Spawn extra bubbles for (var i = 0; i < extraBubbles; i++) { var angle = Math.random() * Math.PI * 2; var distance = 20 + Math.random() * 30; var extraX = playerMask.x + Math.cos(angle) * distance; var extraY = playerMask.y + playerMask.height * 0.15 + Math.sin(angle) * distance; var extraSize = Math.max(game.MIN_SPAWN_SIZE, game.growingBubble.size * (0.6 + Math.random() * 0.2)); var extraBubble = spawnBubble(extraX, extraY, extraSize, 0, false); if (extraBubble) { extraBubble.verticalVelocity = -8 - Math.random() * 4; extraBubble.driftX = Math.random() * 4 - 2; } } } game.mouthOpenDuration = 0; } } // Update cooldown timer if (game.blowCooldown > 0) { game.blowCooldown--; } game.lastMouthState = facekit.mouthOpen; updateClams(); // Inside game.update, after other updates if (UPGRADE_CONFIG.player.autoPop.currentLevel > 0) { // 8 seconds (480 frames) base, reduced by 2 seconds (120 frames) per level if (LK.ticks % Math.max(60, 660 - UPGRADE_CONFIG.player.autoPop.currentLevel * 120) === 0) { var fish = new Fish(); game.addChild(fish); } } 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 both bubbles are gone or time expired (60 frames = 1 second) if (!pair.bubble1.visible || !pair.bubble2.visible || pair.popped && LK.ticks - pair.timestamp > 60) { return false; } return true; }); game.activeBubbles.forEach(function (bubble) { if (bubble.update) { bubble.update(); } }); }; // Handle touch/mouse events for the game game.down = function (x, y, obj) { var localX = x - menuContainer.x; var localY = y - menuContainer.y; // Tab click handling 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) { menuOpen = !menuOpen; var targetY = menuOpen ? menuTab.height : game.height; if (menuOpen) { game.setChildIndex(menuContainer, game.children.length - 1); } tween(menuContainer, { y: targetY //{9O.1} }, { //{9O.2} duration: 300, //{9O.3} easing: tween.easeOutBack, //{9O.4} onFinish: function onFinish() { tabsContainer.visible = menuOpen; // Show/hide tabs based on menu state //{9Q.1} if (!menuOpen) { //{9Q.2} game.setChildIndex(menuContainer, 1); //{9Q.3} } //{9Q.4} } //{9Q.5} }); return true; } if (menuOpen) { return true; // Let containers handle their own clicks } // Bubble popping logic remains the same var popped = false; // NEW: 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 (!popped && distance <= bubble.size / 2 + 30 && bubble.down) { bubble.down(); popped = true; break; } } };
===================================================================
--- original.js
+++ change.js
@@ -709,9 +709,9 @@
}
game.clamSpawnPoints.forEach(function (spawnPoint) {
var config = UPGRADE_CONFIG.machines[spawnPoint.type];
// Calculate production time with speed upgrade
- var baseTime = config.production * 120; // Convert to frames and further increase base time
+ var baseTime = config.production * 150; // Convert to frames and further increase base time
var speedMultiplier = Math.pow(1 - UPGRADE_EFFECTS.autoBubbleSpeed.decrementPercent / 100, UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel);
var adjustedTime = Math.max(1, Math.floor(baseTime * speedMultiplier));
var qualityLevel = UPGRADE_CONFIG.machine.bubbleQuality.currentLevel;
if (qualityLevel > 0) {
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