User prompt
Please fix the bug: 'undefined is not an object (evaluating 'tabButtons[currentTab]')' in or related to this line: 'if (tabButtons[currentTab]) {' Line Number: 786
User prompt
Update as needed with: // Modify the tab button down function for all tabs menuTabs.forEach(function (tab, index) { var tabButton = tabButtons[tab]; if (tabButton) { tabButton.down = function () { if (tab !== currentTab) { // Update tab appearance Object.keys(tabButtons).forEach(function (t) { if (tabButtons[t]) { tabButtons[t].alpha = t === tab ? 1.0 : 0.7; } }); // Remove ALL indicators (by finding them in tabsContainer) for (var i = tabsContainer.children.length - 1; i >= 0; i--) { var child = tabsContainer.children[i]; // Check if this is an indicator (has 10px height and yellow color) if (child.height === 10 && child.color === 0xFFFF00) { tabsContainer.removeChild(child); child.destroy(); } } // Create new indicator for current tab 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 tabsContainer.addChild(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; }; } }); // Call the down function for the current tab to create the initial indicator if (tabButtons[currentTab]) { tabButtons[currentTab].down(); }
Code edit (1 edits merged)
Please save this source code
User prompt
Update with: // Modify the tab button down function for all tabs menuTabs.forEach(function (tab, index) { var tabButton = tabButtons[tab]; if (tabButton) { tabButton.down = function () { if (tab !== currentTab) { // Update tab appearance Object.keys(tabButtons).forEach(function (t) { if (tabButtons[t]) { tabButtons[t].alpha = t === tab ? 1.0 : 0.7; } }); // Remove ALL indicators (by finding them in tabsContainer) for (var i = tabsContainer.children.length - 1; i >= 0; i--) { var child = tabsContainer.children[i]; // Check if this is an indicator (has 10px height and yellow color) if (child.height === 10 && child.color === 0xFFFF00) { tabsContainer.removeChild(child); child.destroy(); } } // Create new indicator for current tab 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 tabsContainer.addChild(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; }; } }); // Call the down function for the current tab to create the initial indicator if (tabButtons[currentTab]) { tabButtons[currentTab].down(); }
Code edit (1 edits merged)
Please save this source code
User prompt
Update as needed with: tabButton.down = function () { if (tab !== currentTab) { // Update tab appearance Object.keys(tabButtons).forEach(function (t) { if (tabButtons[t]) { tabButtons[t].alpha = t === tab ? 1.0 : 0.7; } }); // Remove ALL existing indicators while (game.tabIndicators.length > 0) { var oldIndicator = game.tabIndicators.pop(); tabsContainer.removeChild(oldIndicator); oldIndicator.destroy(); } // Create new indicator for current tab 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 tracking array tabsContainer.addChild(newIndicator); game.tabIndicators.push(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; };
User prompt
Update just this: game.tabIndicators = []; game.tabIndicators.push(activeTabIndicator);
User prompt
Update with: // AFTER CREATING ACTIVE TAB INDICATOR, STORE IT IN GAME OBJECT FOR REFERENCE // Store indicator in game object to access it globally game.activeTabIndicator = activeTabIndicator; // THEN REPLACE ALL tabButton.down FUNCTIONS WITH THIS tabButton.down = function () { if (tab !== currentTab) { // Update tab appearance Object.keys(tabButtons).forEach(function (t) { if (tabButtons[t]) { tabButtons[t].alpha = t === tab ? 1.0 : 0.7; } }); // Remove old indicator from display if (game.activeTabIndicator) { tabsContainer.removeChild(game.activeTabIndicator); } // Create new indicator for current tab 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 store reference tabsContainer.addChild(newIndicator); game.activeTabIndicator = 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; };
User prompt
Update with: tabButton.down = function () { if (tab !== currentTab) { // Update tab appearance Object.keys(tabButtons).forEach(function (t) { if (tabButtons[t]) { tabButtons[t].alpha = t === tab ? 1.0 : 0.7; } }); // Move the indicator to the new tab instead of creating a new one activeTabIndicator.x = tabButton.x - tabWidth/2; // 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; };
User prompt
Update as needed with: // Add this code after the forEach closing bracket but before adding the tabsContainer to menuContainer // Create active tab indicator var activeTabIndicator = LK.getAsset('blower', { width: tabWidth, height: 10, // Small bar at bottom of tab color: 0xFFFF00, // Yellow indicator alpha: 1.0 }); // Position at the bottom of the current tab activeTabIndicator.x = tabButtons[currentTab].x - tabWidth/2; activeTabIndicator.y = tabHeight - 5; // Add to tab container tabsContainer.addChild(activeTabIndicator); // THEN FIND AND REPLACE ALL THE tabButton.down FUNCTIONS // Inside the menuTabs.forEach loop, find the tabButton.down function and replace it with this: tabButton.down = function () { if (tab !== currentTab) { // Update tab appearance Object.keys(tabButtons).forEach(function (t) { if (tabButtons[t]) { tabButtons[t].alpha = t === tab ? 1.0 : 0.7; } }); // Move the indicator to the new tab activeTabIndicator.x = tabButtons[tab].x - tabWidth/2; // 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; };
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'tabButtons[currentTab].x')' in or related to this line: 'activeTabIndicator.x = tabButtons[currentTab].x - tabWidth / 2;' Line Number: 1026
User prompt
Update with: var activeTabIndicator = LK.getAsset('blower', { width: tabWidth, height: 10, // Small bar at bottom of tab color: 0xFFFF00, // Yellow indicator alpha: 1.0 }); // Position at the bottom of the current tab activeTabIndicator.x = tabButtons[currentTab].x - tabWidth/2; activeTabIndicator.y = tabHeight - 5; // Add to tab container tabsContainer.addChild(activeTabIndicator);
User prompt
Update with: tabButton.down = function () { if (tab !== currentTab) { // Update tab appearance Object.keys(tabButtons).forEach(function (t) { if (tabButtons[t]) { tabButtons[t].alpha = t === tab ? 1.0 : 0.7; } }); // Update text colors Object.keys(tabTextRefs).forEach(function (t) { tabTextRefs[t].fill = t === tab ? 0xFFFF00 : 0xFFFFFF; }); // 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; };
User prompt
Update with: var tabText = new Text2(tab.charAt(0).toUpperCase() + tab.slice(1), { size: 80, fill: tab === currentTab ? 0xFFFF00 : 0xFFFFFF, // Set initial color based on current tab stroke: 0x000000, strokeThickness: 3, font: "Impact" }); tabText.anchor = { x: 0.5, y: 0.5 }; tabText.x = tabButton.x; tabText.y = tabHeight / 2; // Store reference to text tabTextRefs[tab] = tabText;
Code edit (2 edits merged)
Please save this source code
User prompt
Update with: // When creating tab text initially, set the color based on whether it's the current tab var tabText = new Text2(tab.charAt(0).toUpperCase() + tab.slice(1), { size: 80, fill: tab === currentTab ? 0xFFFF00 : 0xFFFFFF, // Yellow if active, white otherwise stroke: 0x000000, strokeThickness: 3, font: "Impact" }); // Store reference to the text object tabButtons[tab].textRef = tabText; // Then in the tab.down function, update to also change text color: tabButton.down = function () { if (tab !== currentTab) { // Update tab appearance Object.keys(tabButtons).forEach(function (t) { if (tabButtons[t]) { tabButtons[t].alpha = t === tab ? 1.0 : 0.7; // Change text color if (tabButtons[t].textRef) { tabButtons[t].textRef.fill = t === tab ? 0xFFFF00 : 0xFFFFFF; } } }); // 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; };
Code edit (1 edits merged)
Please save this source code
User prompt
update with: // Advanced clam - replace exactly one basic clam if (key === 'advancedClam') { if (UPGRADE_CONFIG.machines.basicClam.amount > 0) { // Replace one basic clam UPGRADE_CONFIG.machines.basicClam.amount--; upgrade.amount++; game.bp -= cost; // Update BP text bpText.setText(formatBP(game.bp) + " BP"); // Force a complete refresh of the clams tab refreshUpgradeTab('clams'); // Update visual representation of clams updateClamVisuals(); return true; } else { game.showError("No basic clams to upgrade!"); return true; } }
User prompt
update as needed with: function updateClamVisuals() { // Clear existing clams while (clamContainer.children.length) { clamContainer.children[0].destroy(); } // Define positioning variables (these were likely defined elsewhere) var leftStart = game.width * 0.1; var rightStart = game.width * 0.9; var spacing = 250; var y = game.height - 100; // Total number of clams to display var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; // Create array to hold exactly the right number of clams var clamTypes = new Array(totalClams); // Fill all positions with null initially for (var i = 0; i < clamTypes.length; i++) { clamTypes[i] = null; } // Fill with premium clams first (highest priority) var premiumCount = UPGRADE_CONFIG.machines.premiumClam.amount; for (var i = 0; i < premiumCount; i++) { if (i < clamTypes.length) { clamTypes[i] = 'premiumClam'; } } // Fill with advanced clams next var advancedCount = UPGRADE_CONFIG.machines.advancedClam.amount; for (var i = 0; i < advancedCount; i++) { if (i + premiumCount < clamTypes.length) { clamTypes[i + premiumCount] = 'advancedClam'; } } // Fill remaining slots with basic clams var basicCount = UPGRADE_CONFIG.machines.basicClam.amount; for (var i = 0; i < basicCount; i++) { if (i + premiumCount + advancedCount < clamTypes.length) { clamTypes[i + premiumCount + advancedCount] = 'basicClam'; } } // Place clams game.clamSpawnPoints = []; clamTypes.forEach(function (type, i) { if (!type) return; // Skip null entries 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); }); console.log("After visual update - Clam types: " + clamTypes.join(", ")); }
User prompt
Please fix the bug: 'ReferenceError: leftStart is not defined' in or related to this line: 'var baseX = isRight ? rightStart : leftStart;' Line Number: 1647
User prompt
update with: function updateClamVisuals() { // Clear existing clams while (clamContainer.children.length) { clamContainer.children[0].destroy(); } // Total number of clams to display var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; // Create array to hold exactly the right number of clams var clamTypes = new Array(totalClams); // Fill all positions with null initially for (var i = 0; i < clamTypes.length; i++) { clamTypes[i] = null; } // Fill with premium clams first (highest priority) var premiumCount = UPGRADE_CONFIG.machines.premiumClam.amount; for (var i = 0; i < premiumCount; i++) { if (i < clamTypes.length) { clamTypes[i] = 'premiumClam'; } } // Fill with advanced clams next var advancedCount = UPGRADE_CONFIG.machines.advancedClam.amount; for (var i = 0; i < advancedCount; i++) { if (i + premiumCount < clamTypes.length) { clamTypes[i + premiumCount] = 'advancedClam'; } } // Fill remaining slots with basic clams var basicCount = UPGRADE_CONFIG.machines.basicClam.amount; for (var i = 0; i < basicCount; i++) { if (i + premiumCount + advancedCount < clamTypes.length) { clamTypes[i + premiumCount + advancedCount] = 'basicClam'; } } // Place clams game.clamSpawnPoints = []; clamTypes.forEach(function (type, i) { if (!type) return; // Skip null entries 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); }); console.log("After visual update - Clam types: " + clamTypes.join(", ")); }
Code edit (1 edits merged)
Please save this source code
User prompt
update as needed with: 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') { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; // Check if already at max clams if (totalClams >= 4) { if (key === 'basicClam') { updateCostText(category, key, "SOLD OUT", 0x888888); return true; // Add return here } // If trying to upgrade (e.g. basic to advanced) if (key === 'advancedClam' && UPGRADE_CONFIG.machines.basicClam.amount > 0) { // Replace a basic clam with advanced UPGRADE_CONFIG.machines.basicClam.amount--; upgrade.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); updateClamVisuals(); return true; } else if (key === 'premiumClam' && (UPGRADE_CONFIG.machines.basicClam.amount > 0 || UPGRADE_CONFIG.machines.advancedClam.amount > 0)) { // Replace lower tier clam with premium if (UPGRADE_CONFIG.machines.basicClam.amount > 0) { UPGRADE_CONFIG.machines.basicClam.amount--; } else { UPGRADE_CONFIG.machines.advancedClam.amount--; } upgrade.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); updateClamVisuals(); return true; } else { game.showError("Maximum of 4 clams reached!"); return true; } } } 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"); updateCostText(category, key, getUpgradeCost(upgrade) + " BP", 0xFFFF00); // Update visuals if (category === 'machines') { updateClamVisuals(); } else if (category === 'decorations') { if (key === 'bubbleCoral') { updateCoralDecorations(); } else if (key === 'sunkenTreasures') { updateTreasureDecorations(); } } } } 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 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; };
Code edit (1 edits merged)
Please save this source code
User prompt
Update as needed: if (category === 'machines') { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; if (totalClams >= 4) { // Basic clam - just show sold out if (key === 'basicClam') { costText.setText("SOLD OUT"); costText.fill = 0x888888; return true; } // Advanced clam upgrade - can replace basic if (key === 'advancedClam' && UPGRADE_CONFIG.machines.basicClam.amount > 0) { if (game.bp >= cost) { UPGRADE_CONFIG.machines.basicClam.amount--; UPGRADE_CONFIG.machines.advancedClam.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); updateClamVisuals(); refreshUpgradeTab('clams'); } return true; } // Premium clam upgrade - can replace either basic or advanced if (key === 'premiumClam') { // First try to replace basic clam if (UPGRADE_CONFIG.machines.basicClam.amount > 0) { if (game.bp >= cost) { UPGRADE_CONFIG.machines.basicClam.amount--; UPGRADE_CONFIG.machines.premiumClam.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); updateClamVisuals(); refreshUpgradeTab('clams'); } return true; } // If no basic clams, try to replace advanced else if (UPGRADE_CONFIG.machines.advancedClam.amount > 0) { if (game.bp >= cost) { UPGRADE_CONFIG.machines.advancedClam.amount--; UPGRADE_CONFIG.machines.premiumClam.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); updateClamVisuals(); refreshUpgradeTab('clams'); } return true; } } game.showError("Maximum of 4 clams reached!"); return true; } // Normal purchase logic for when total clams < 4 if (game.bp >= cost) { upgrade.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); costText.setText(getUpgradeCost(upgrade) + " BP"); updateClamVisuals(); refreshUpgradeTab('clams'); } else { game.showError("Not enough BP!"); } return true; }
/**** * 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 }; 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; 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 * 100)); // 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 } // 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) { // 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 ****/ if (typeof UPGRADE_CONFIG === 'undefined') { var UPGRADE_CONFIG = {}; } if (!UPGRADE_CONFIG.gameSettings) { UPGRADE_CONFIG.gameSettings = { activeColor: "auto" // Default to automatic progression }; } // Add function to get current active color 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 a function to set active 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 50% bonus if (distance <= zone.radius) { totalBonus += 0.5; // +50% per overlapping zone as specified } }); // Return multiplier (1.0 = no bonus, 1.5 = one zone, 2.0 = two zones, etc.) return 1.0 + totalBonus; } // 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.15; var zoneIndicator = LK.getAsset('blower', { width: zoneRadius * 2, height: zoneRadius * 2, shape: 'circle', // Specify circle shape 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: 1.8, scaleY: 1.8, alpha: 0.9 }); // Add zone first (so it's behind treasure) treasureContainer.addChild(zoneIndicator); treasureContainer.addChild(treasure); } } function updateCoralBubbles() { if (UPGRADE_CONFIG.decorations.bubbleCoral.amount <= 0) { return; } coralContainer.children.forEach(function (coral) { // 5% chance per second to spawn bubble (0.083% per frame at 60fps) if (Math.random() < 0.00083 || LK.ticks - coral.lastBubbleTime > 600) { // Force spawn after 10 seconds coral.lastBubbleTime = LK.ticks; // Calculate bubble size based on player's max size var bubbleSize = game.maxBubbleSize * (0.5 + Math.random() * 0.3); // Spawn position slightly above coral var bubbleX = coral.x + (Math.random() * 30 - 15); var bubbleY = coral.y - coral.height * 0.3; var bubble = spawnBubble(bubbleX, bubbleY, bubbleSize, 0, false); if (bubble) { // Set moderate upward velocity bubble.verticalVelocity = -(2 + Math.random() * 3); bubble.driftX = Math.random() * 2 - 1; } } }); } function updateCoralDecorations() { // Clear existing corals while (coralContainer.children.length) { coralContainer.children[0].destroy(); } // Position corals along bottom of screen var coralCount = UPGRADE_CONFIG.decorations.bubbleCoral.amount; if (coralCount <= 0) { return; } // Available coral types var coralTypes = ['coral1', 'coral2', 'coral3', 'coral4']; // Space corals evenly along bottom var spacing = game.width / (coralCount + 1); for (var i = 0; i < coralCount; i++) { // Choose random coral type var coralType = coralTypes[Math.floor(Math.random() * coralTypes.length)]; // Create coral sprite var coral = LK.getAsset(coralType, { anchorX: 0.5, anchorY: 1, x: spacing * (i + 1), y: game.height - 20, // Slightly above bottom scaleX: 1.5 + Math.random() * 0.5, scaleY: 1.5 + Math.random() * 0.5 }); // Store last bubble spawn time coral.lastBubbleTime = 0; coralContainer.addChild(coral); } } 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); }); } // Add this at the end of the function: if (tabName === 'colors') { var activeColorKey = getActiveColorKey(); tabColumns[tabName].left.concat(tabColumns[tabName].right).forEach(function (upgrade) { var category = upgrade[0]; var key = upgrade[1]; var upgradeConfig = UPGRADE_CONFIG[category][key]; if (upgradeConfig.currentLevel > 0) { // Find the corresponding cost text for this upgrade tabContainers[tabName].children.forEach(function (child) { // Identify the cost text by its position below the name if (child.text && child.y > 0) { var nameText = tabContainers[tabName].children.find(function (c) { return c.text === upgradeConfig.name; }); if (nameText && child.y > nameText.y && child.y < nameText.y + 150) { // Check if this color is active if (activeColorKey === key || UPGRADE_CONFIG.gameSettings.activeColor === "auto" && key === activeColorKey) { child.setText("ACTIVE"); child.fill = 0x00FF00; // Green } else if (category === 'colors' && upgradeConfig.currentLevel === 1) { // If this is the first color purchased or auto is selected with no active colors if (UPGRADE_CONFIG.gameSettings.activeColor === "auto" || !UPGRADE_CONFIG.colors[UPGRADE_CONFIG.gameSettings.activeColor] || UPGRADE_CONFIG.colors[UPGRADE_CONFIG.gameSettings.activeColor].currentLevel === 0) { UPGRADE_CONFIG.gameSettings.activeColor = key; game.showMessage(upgradeConfig.name + " activated"); } } else if (category === 'colors' && upgradeConfig.currentLevel >= upgradeConfig.maxLevel) { // Create "SOLD OUT" text for maxed out color upgrades child.setText("SOLD OUT"); child.fill = 0x888888; // Gray } else { var cost = getUpgradeCost(upgradeConfig); child.setText(cost + " BP"); child.fill = 0xFFFF00; // Yellow } } } }); } }); } else if (tabName === 'clams') { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; // Check if we've reached max clams if (totalClams >= 4) { // Find all basic clam upgrade texts and update them tabContainers[tabName].children.forEach(function (child) { if (child.text && (child.text.includes("BP") || child.text === "SOLD OUT")) { // Find the name text for basic clam var nameText = tabContainers[tabName].children.find(function (c) { return c.text === UPGRADE_CONFIG.machines.basicClam.name; }); // If this cost text is below the basic clam name text if (nameText && child.y > nameText.y && child.y < nameText.y + 150) { child.setText("SOLD OUT"); child.fill = 0x888888; // Gray color } } }); } } } game.twinBubbles = []; game.treasureZones = []; // 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', 'purpleBubbles'], ['colors', 'greenBubbles'], ['colors', 'orangeBubbles'], ['colors', 'pinkBubbles']], right: [['colors', 'tealBubbles'], ['colors', 'goldBubbles'], ['colors', 'crimsonBubbles'], ['colors', 'silverBubbles'], ['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, consider total clams when calculating cost if (upgrade.name.includes('Clam')) { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; return Math.floor(upgrade.baseCost * Math.pow(upgrade.costScale, totalClams)); } 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); // Add coral container here var coralContainer = new Container(); game.addChild(coralContainer); // Reposition in display list game.setChildIndex(coralContainer, 1); var treasureContainer = new Container(); // Add after coralContainer but before clams for proper layering game.addChild(treasureContainer); game.setChildIndex(treasureContainer, 2); var clamContainer = new Container(); game.addChild(clamContainer); var playerMask = new pufferMask(); game.addChild(playerMask); var UPGRADE_CONFIG = { gameSettings: { activeColor: "auto" // Default to automatic progression (highest unlocked) }, 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, 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: 150000, costScale: 1.0, maxLevel: 1, currentLevel: 0, requires: "rainbowBubbles", multiplier: 1.8 } }, 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(); } }); }; game.showMessage = function (message) { var messageText = new Text2(message, { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); // Set the anchor point to center the text messageText.anchor = { x: 0.5, y: 0.5 }; // Position at the center of the screen messageText.x = game.width / 2; messageText.y = game.height / 2; game.addChild(messageText); // Animate the text tween(messageText, { alpha: 0, y: messageText.y - 30 }, { duration: 1000, 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 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 === 'machines' && key === 'basicClam') { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; if (totalClams >= 4) { costText = new Text2("SOLD OUT", { size: 96, fill: 0x888888, // Gray color stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } } // For colors, check active state first before any other states if (category === 'colors' && upgrade.currentLevel > 0) { var activeColorKey = getActiveColorKey(); if (key === activeColorKey || UPGRADE_CONFIG.gameSettings.activeColor === "auto" && key === activeColorKey) { // Active color - green "ACTIVE" text costText = new Text2("ACTIVE", { size: 96, fill: 0x00FF00, // Green color for active stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } else if (upgrade.currentLevel >= upgrade.maxLevel) { // Maxed out color - gray "SOLD OUT" text costText = new Text2("SOLD OUT", { size: 96, fill: 0x888888, // Gray color stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } else { // Purchased but not active or maxed - yellow cost text costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } } else 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, 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); // For machines, check clam limit first if (category === 'machines') { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; if (totalClams >= 4) { if (key === 'basicClam') { costText.setText("SOLD OUT"); costText.fill = 0x888888; return true; } // Advanced clam upgrade - can replace basic if (key === 'advancedClam' && UPGRADE_CONFIG.machines.basicClam.amount > 0) { if (game.bp >= cost) { UPGRADE_CONFIG.machines.basicClam.amount--; UPGRADE_CONFIG.machines.advancedClam.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); updateClamVisuals(); refreshUpgradeTab('clams'); } return true; } else if (key === 'premiumClam') { // First try to replace basic clam if (UPGRADE_CONFIG.machines.basicClam.amount > 0) { if (game.bp >= cost) { UPGRADE_CONFIG.machines.basicClam.amount--; UPGRADE_CONFIG.machines.premiumClam.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); updateClamVisuals(); refreshUpgradeTab('clams'); } return true; } // If no basic clams, try to replace advanced else if (UPGRADE_CONFIG.machines.advancedClam.amount > 0) { if (game.bp >= cost) { UPGRADE_CONFIG.machines.advancedClam.amount--; UPGRADE_CONFIG.machines.premiumClam.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); updateClamVisuals(); refreshUpgradeTab('clams'); } return true; } } else { game.showError("Maximum of 4 clams reached!"); return true; } } } var child = costText; // Define 'child' as 'costText' to fix ReferenceError // 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 follows... 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') { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; // Check if already at max clams if (totalClams >= 4) { if (key === 'basicClam') { child.setText("SOLD OUT"); child.fill = 0x888888; // Gray color } // If trying to upgrade (e.g. basic to advanced) if (key === 'advancedClam' && UPGRADE_CONFIG.machines.basicClam.amount > 0) { // Replace a basic clam with advanced UPGRADE_CONFIG.machines.basicClam.amount--; upgrade.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); updateClamVisuals(); return true; } else if (key === 'premiumClam' && (UPGRADE_CONFIG.machines.basicClam.amount > 0 || UPGRADE_CONFIG.machines.advancedClam.amount > 0)) { // Replace lower tier clam with premium if (UPGRADE_CONFIG.machines.basicClam.amount > 0) { UPGRADE_CONFIG.machines.basicClam.amount--; } else { UPGRADE_CONFIG.machines.advancedClam.amount--; } upgrade.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); updateClamVisuals(); return true; } else { game.showError("Maximum of 4 clams reached!"); return true; } } } if (upgrade.amount !== undefined) { // For clams and decorations with amount if (upgrade.amount < (upgrade.maxAmount || 999)) { upgrade.amount++; console.log("Clam purchased, new amount:", upgrade.amount); var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; console.log("Total clams now:", totalClams); if (totalClams >= 4 && key === 'basicClam') { console.log("Should set basic clam to SOLD OUT"); costText.setText("SOLD OUT"); costText.fill = 0x888888; // Gray color console.log("Forcing tab refresh for clams"); // Clear existing tab container while (tabContainers['clams'].children.length > 0) { tabContainers['clams'].children[0].destroy(); } // Recreate all upgrades for the tab if (tabColumns['clams'] && tabColumns['clams'].left) { tabColumns['clams'].left.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, true, 'clams'); }); } if (tabColumns['clams'] && tabColumns['clams'].right) { tabColumns['clams'].right.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, false, 'clams'); }); } } game.bp -= cost; if (category === 'machines') { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; // Check if we've reached max clams if (totalClams >= 4) { // Find all basic clam upgrade texts and update them tabContainers['clams'].children.forEach(function (child) { if (child.text && child.text.includes("BP")) { var nameText = tabContainers['clams'].children.find(function (c) { return c.text === UPGRADE_CONFIG.machines.basicClam.name; }); if (nameText && child.y > nameText.y && child.y < nameText.y + 150) { child.setText("SOLD OUT"); child.fill = 0x888888; // Gray color } } }); refreshUpgradeTab('clams'); // Refresh entire clams tab } } 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') { if (key === 'bubbleCoral') { updateCoralDecorations(); } else if (key === 'sunkenTreasures') { updateTreasureDecorations(); } // Future decoration handlers would go here } } } 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') { refreshUpgradeTab('colors'); return true; } // 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; // Update selection UI when colors change 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); // Add to the end of the updateClamVisuals() function: var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; if (totalClams >= 4) { refreshUpgradeTab('clams'); } }); } updateClamVisuals(); updateCoralDecorations(); updateTreasureDecorations(); // 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 = 120; // 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 () { updateCoralBubbles(); // 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 = game.growingBubble ? Math.max(game.MIN_SPAWN_SIZE, game.growingBubble.size * (0.6 + Math.random() * 0.2)) : game.MIN_SPAWN_SIZE; 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; } } };
/****
* 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
};
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;
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 * 100));
// 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
}
// 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) {
// 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
****/
if (typeof UPGRADE_CONFIG === 'undefined') {
var UPGRADE_CONFIG = {};
}
if (!UPGRADE_CONFIG.gameSettings) {
UPGRADE_CONFIG.gameSettings = {
activeColor: "auto" // Default to automatic progression
};
}
// Add function to get current active color
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 a function to set active 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 50% bonus
if (distance <= zone.radius) {
totalBonus += 0.5; // +50% per overlapping zone as specified
}
});
// Return multiplier (1.0 = no bonus, 1.5 = one zone, 2.0 = two zones, etc.)
return 1.0 + totalBonus;
}
// 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.15;
var zoneIndicator = LK.getAsset('blower', {
width: zoneRadius * 2,
height: zoneRadius * 2,
shape: 'circle',
// Specify circle shape
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: 1.8,
scaleY: 1.8,
alpha: 0.9
});
// Add zone first (so it's behind treasure)
treasureContainer.addChild(zoneIndicator);
treasureContainer.addChild(treasure);
}
}
function updateCoralBubbles() {
if (UPGRADE_CONFIG.decorations.bubbleCoral.amount <= 0) {
return;
}
coralContainer.children.forEach(function (coral) {
// 5% chance per second to spawn bubble (0.083% per frame at 60fps)
if (Math.random() < 0.00083 || LK.ticks - coral.lastBubbleTime > 600) {
// Force spawn after 10 seconds
coral.lastBubbleTime = LK.ticks;
// Calculate bubble size based on player's max size
var bubbleSize = game.maxBubbleSize * (0.5 + Math.random() * 0.3);
// Spawn position slightly above coral
var bubbleX = coral.x + (Math.random() * 30 - 15);
var bubbleY = coral.y - coral.height * 0.3;
var bubble = spawnBubble(bubbleX, bubbleY, bubbleSize, 0, false);
if (bubble) {
// Set moderate upward velocity
bubble.verticalVelocity = -(2 + Math.random() * 3);
bubble.driftX = Math.random() * 2 - 1;
}
}
});
}
function updateCoralDecorations() {
// Clear existing corals
while (coralContainer.children.length) {
coralContainer.children[0].destroy();
}
// Position corals along bottom of screen
var coralCount = UPGRADE_CONFIG.decorations.bubbleCoral.amount;
if (coralCount <= 0) {
return;
}
// Available coral types
var coralTypes = ['coral1', 'coral2', 'coral3', 'coral4'];
// Space corals evenly along bottom
var spacing = game.width / (coralCount + 1);
for (var i = 0; i < coralCount; i++) {
// Choose random coral type
var coralType = coralTypes[Math.floor(Math.random() * coralTypes.length)];
// Create coral sprite
var coral = LK.getAsset(coralType, {
anchorX: 0.5,
anchorY: 1,
x: spacing * (i + 1),
y: game.height - 20,
// Slightly above bottom
scaleX: 1.5 + Math.random() * 0.5,
scaleY: 1.5 + Math.random() * 0.5
});
// Store last bubble spawn time
coral.lastBubbleTime = 0;
coralContainer.addChild(coral);
}
}
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);
});
}
// Add this at the end of the function:
if (tabName === 'colors') {
var activeColorKey = getActiveColorKey();
tabColumns[tabName].left.concat(tabColumns[tabName].right).forEach(function (upgrade) {
var category = upgrade[0];
var key = upgrade[1];
var upgradeConfig = UPGRADE_CONFIG[category][key];
if (upgradeConfig.currentLevel > 0) {
// Find the corresponding cost text for this upgrade
tabContainers[tabName].children.forEach(function (child) {
// Identify the cost text by its position below the name
if (child.text && child.y > 0) {
var nameText = tabContainers[tabName].children.find(function (c) {
return c.text === upgradeConfig.name;
});
if (nameText && child.y > nameText.y && child.y < nameText.y + 150) {
// Check if this color is active
if (activeColorKey === key || UPGRADE_CONFIG.gameSettings.activeColor === "auto" && key === activeColorKey) {
child.setText("ACTIVE");
child.fill = 0x00FF00; // Green
} else if (category === 'colors' && upgradeConfig.currentLevel === 1) {
// If this is the first color purchased or auto is selected with no active colors
if (UPGRADE_CONFIG.gameSettings.activeColor === "auto" || !UPGRADE_CONFIG.colors[UPGRADE_CONFIG.gameSettings.activeColor] || UPGRADE_CONFIG.colors[UPGRADE_CONFIG.gameSettings.activeColor].currentLevel === 0) {
UPGRADE_CONFIG.gameSettings.activeColor = key;
game.showMessage(upgradeConfig.name + " activated");
}
} else if (category === 'colors' && upgradeConfig.currentLevel >= upgradeConfig.maxLevel) {
// Create "SOLD OUT" text for maxed out color upgrades
child.setText("SOLD OUT");
child.fill = 0x888888; // Gray
} else {
var cost = getUpgradeCost(upgradeConfig);
child.setText(cost + " BP");
child.fill = 0xFFFF00; // Yellow
}
}
}
});
}
});
} else if (tabName === 'clams') {
var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
// Check if we've reached max clams
if (totalClams >= 4) {
// Find all basic clam upgrade texts and update them
tabContainers[tabName].children.forEach(function (child) {
if (child.text && (child.text.includes("BP") || child.text === "SOLD OUT")) {
// Find the name text for basic clam
var nameText = tabContainers[tabName].children.find(function (c) {
return c.text === UPGRADE_CONFIG.machines.basicClam.name;
});
// If this cost text is below the basic clam name text
if (nameText && child.y > nameText.y && child.y < nameText.y + 150) {
child.setText("SOLD OUT");
child.fill = 0x888888; // Gray color
}
}
});
}
}
}
game.twinBubbles = [];
game.treasureZones = [];
// 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', 'purpleBubbles'], ['colors', 'greenBubbles'], ['colors', 'orangeBubbles'], ['colors', 'pinkBubbles']],
right: [['colors', 'tealBubbles'], ['colors', 'goldBubbles'], ['colors', 'crimsonBubbles'], ['colors', 'silverBubbles'], ['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, consider total clams when calculating cost
if (upgrade.name.includes('Clam')) {
var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
return Math.floor(upgrade.baseCost * Math.pow(upgrade.costScale, totalClams));
}
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);
// Add coral container here
var coralContainer = new Container();
game.addChild(coralContainer);
// Reposition in display list
game.setChildIndex(coralContainer, 1);
var treasureContainer = new Container();
// Add after coralContainer but before clams for proper layering
game.addChild(treasureContainer);
game.setChildIndex(treasureContainer, 2);
var clamContainer = new Container();
game.addChild(clamContainer);
var playerMask = new pufferMask();
game.addChild(playerMask);
var UPGRADE_CONFIG = {
gameSettings: {
activeColor: "auto" // Default to automatic progression (highest unlocked)
},
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,
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: 150000,
costScale: 1.0,
maxLevel: 1,
currentLevel: 0,
requires: "rainbowBubbles",
multiplier: 1.8
}
},
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();
}
});
};
game.showMessage = function (message) {
var messageText = new Text2(message, {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3,
font: "Impact"
});
// Set the anchor point to center the text
messageText.anchor = {
x: 0.5,
y: 0.5
};
// Position at the center of the screen
messageText.x = game.width / 2;
messageText.y = game.height / 2;
game.addChild(messageText);
// Animate the text
tween(messageText, {
alpha: 0,
y: messageText.y - 30
}, {
duration: 1000,
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 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 === 'machines' && key === 'basicClam') {
var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
if (totalClams >= 4) {
costText = new Text2("SOLD OUT", {
size: 96,
fill: 0x888888,
// Gray color
stroke: 0x000000,
strokeThickness: 2,
font: "Impact"
});
}
}
// For colors, check active state first before any other states
if (category === 'colors' && upgrade.currentLevel > 0) {
var activeColorKey = getActiveColorKey();
if (key === activeColorKey || UPGRADE_CONFIG.gameSettings.activeColor === "auto" && key === activeColorKey) {
// Active color - green "ACTIVE" text
costText = new Text2("ACTIVE", {
size: 96,
fill: 0x00FF00,
// Green color for active
stroke: 0x000000,
strokeThickness: 2,
font: "Impact"
});
} else if (upgrade.currentLevel >= upgrade.maxLevel) {
// Maxed out color - gray "SOLD OUT" text
costText = new Text2("SOLD OUT", {
size: 96,
fill: 0x888888,
// Gray color
stroke: 0x000000,
strokeThickness: 2,
font: "Impact"
});
} else {
// Purchased but not active or maxed - yellow cost text
costText = new Text2(cost + " BP", {
size: 96,
fill: 0xFFFF00,
stroke: 0x000000,
strokeThickness: 2,
font: "Impact"
});
}
} else 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,
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);
// For machines, check clam limit first
if (category === 'machines') {
var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
if (totalClams >= 4) {
if (key === 'basicClam') {
costText.setText("SOLD OUT");
costText.fill = 0x888888;
return true;
}
// Advanced clam upgrade - can replace basic
if (key === 'advancedClam' && UPGRADE_CONFIG.machines.basicClam.amount > 0) {
if (game.bp >= cost) {
UPGRADE_CONFIG.machines.basicClam.amount--;
UPGRADE_CONFIG.machines.advancedClam.amount++;
game.bp -= cost;
bpText.setText(formatBP(game.bp) + " BP");
updateClamVisuals();
refreshUpgradeTab('clams');
}
return true;
} else if (key === 'premiumClam') {
// First try to replace basic clam
if (UPGRADE_CONFIG.machines.basicClam.amount > 0) {
if (game.bp >= cost) {
UPGRADE_CONFIG.machines.basicClam.amount--;
UPGRADE_CONFIG.machines.premiumClam.amount++;
game.bp -= cost;
bpText.setText(formatBP(game.bp) + " BP");
updateClamVisuals();
refreshUpgradeTab('clams');
}
return true;
}
// If no basic clams, try to replace advanced
else if (UPGRADE_CONFIG.machines.advancedClam.amount > 0) {
if (game.bp >= cost) {
UPGRADE_CONFIG.machines.advancedClam.amount--;
UPGRADE_CONFIG.machines.premiumClam.amount++;
game.bp -= cost;
bpText.setText(formatBP(game.bp) + " BP");
updateClamVisuals();
refreshUpgradeTab('clams');
}
return true;
}
} else {
game.showError("Maximum of 4 clams reached!");
return true;
}
}
}
var child = costText; // Define 'child' as 'costText' to fix ReferenceError
// 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 follows...
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') {
var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
// Check if already at max clams
if (totalClams >= 4) {
if (key === 'basicClam') {
child.setText("SOLD OUT");
child.fill = 0x888888; // Gray color
}
// If trying to upgrade (e.g. basic to advanced)
if (key === 'advancedClam' && UPGRADE_CONFIG.machines.basicClam.amount > 0) {
// Replace a basic clam with advanced
UPGRADE_CONFIG.machines.basicClam.amount--;
upgrade.amount++;
game.bp -= cost;
bpText.setText(formatBP(game.bp) + " BP");
updateClamVisuals();
return true;
} else if (key === 'premiumClam' && (UPGRADE_CONFIG.machines.basicClam.amount > 0 || UPGRADE_CONFIG.machines.advancedClam.amount > 0)) {
// Replace lower tier clam with premium
if (UPGRADE_CONFIG.machines.basicClam.amount > 0) {
UPGRADE_CONFIG.machines.basicClam.amount--;
} else {
UPGRADE_CONFIG.machines.advancedClam.amount--;
}
upgrade.amount++;
game.bp -= cost;
bpText.setText(formatBP(game.bp) + " BP");
updateClamVisuals();
return true;
} else {
game.showError("Maximum of 4 clams reached!");
return true;
}
}
}
if (upgrade.amount !== undefined) {
// For clams and decorations with amount
if (upgrade.amount < (upgrade.maxAmount || 999)) {
upgrade.amount++;
console.log("Clam purchased, new amount:", upgrade.amount);
var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
console.log("Total clams now:", totalClams);
if (totalClams >= 4 && key === 'basicClam') {
console.log("Should set basic clam to SOLD OUT");
costText.setText("SOLD OUT");
costText.fill = 0x888888; // Gray color
console.log("Forcing tab refresh for clams");
// Clear existing tab container
while (tabContainers['clams'].children.length > 0) {
tabContainers['clams'].children[0].destroy();
}
// Recreate all upgrades for the tab
if (tabColumns['clams'] && tabColumns['clams'].left) {
tabColumns['clams'].left.forEach(function (upgrade, index) {
createUpgradeText(upgrade[0], upgrade[1], index, true, 'clams');
});
}
if (tabColumns['clams'] && tabColumns['clams'].right) {
tabColumns['clams'].right.forEach(function (upgrade, index) {
createUpgradeText(upgrade[0], upgrade[1], index, false, 'clams');
});
}
}
game.bp -= cost;
if (category === 'machines') {
var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
// Check if we've reached max clams
if (totalClams >= 4) {
// Find all basic clam upgrade texts and update them
tabContainers['clams'].children.forEach(function (child) {
if (child.text && child.text.includes("BP")) {
var nameText = tabContainers['clams'].children.find(function (c) {
return c.text === UPGRADE_CONFIG.machines.basicClam.name;
});
if (nameText && child.y > nameText.y && child.y < nameText.y + 150) {
child.setText("SOLD OUT");
child.fill = 0x888888; // Gray color
}
}
});
refreshUpgradeTab('clams'); // Refresh entire clams tab
}
}
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') {
if (key === 'bubbleCoral') {
updateCoralDecorations();
} else if (key === 'sunkenTreasures') {
updateTreasureDecorations();
}
// Future decoration handlers would go here
}
}
} 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') {
refreshUpgradeTab('colors');
return true;
}
// 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;
// Update selection UI when colors change
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);
// Add to the end of the updateClamVisuals() function:
var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
if (totalClams >= 4) {
refreshUpgradeTab('clams');
}
});
}
updateClamVisuals();
updateCoralDecorations();
updateTreasureDecorations();
// 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 = 120; // 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 () {
updateCoralBubbles();
// 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 = game.growingBubble ? Math.max(game.MIN_SPAWN_SIZE, game.growingBubble.size * (0.6 + Math.random() * 0.2)) : game.MIN_SPAWN_SIZE;
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;
}
}
};
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