User prompt
Update with: // When creating tabsContainer (after creating menuPanel and menuTab) var tabsContainer = new Container(); // Position at the bottom of the menu panel tabsContainer.y = -menuPanel.height * menuPanel.scaleY; // This positions it at the bottom of the panel menuContainer.addChild(tabsContainer); // When creating each 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, });
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'menuTextContainer.addChild')' in or related to this line: 'menuTextContainer.addChild(contentContainer);' Line Number: 750
User prompt
Update with: // In createUpgradeText function, replace: var targetContainer = tabContainers[getTabForUpgrade(category, key)]; targetContainer.addChild(hitContainer); targetContainer.addChild(nameText); targetContainer.addChild(costText); // With: var tabName = getTabForUpgrade(category, key); var targetContainer = tabContainers[tabName]; // Add fallback in case the container doesn't exist if (!targetContainer) { console.log("Warning: Tab '" + tabName + "' not found for " + category + "." + key); targetContainer = tabContainers[menuTabs[0]]; // Default to first tab } targetContainer.addChild(hitContainer); targetContainer.addChild(nameText); targetContainer.addChild(costText);
User prompt
Update with: var tabContainers = {}; menuTabs.forEach(function(tab) { var contentContainer = new Container(); contentContainer.visible = tab === currentTab; tabContainers[tab] = contentContainer; menuTextContainer.addChild(contentContainer); });
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'targetContainer.addChild')' in or related to this line: 'targetContainer.addChild(hitContainer);' Line Number: 922
User prompt
Update with: var tabWidth = menuPanel.width * menuPanel.scaleX / menuTabs.length;
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'targetContainer.addChild')' in or related to this line: 'targetContainer.addChild(hitContainer);' Line Number: 925
User prompt
Please fix the bug: 'Can't find variable: panelWidth' in or related to this line: 'var tabWidth = panelWidth / menuTabs.length;' Line Number: 746
User prompt
Update with: // After creating the menuTab and menuPanel but before adding content var tabsContainer = new Container(); // Set position to match the menuPanel's bottom edge tabsContainer.y = 0; // The panel bottom is at the container's origin (0) // Add it to the menuContainer menuContainer.addChild(tabsContainer); // Define tab dimensions var tabWidth = menuPanel.width * menuPanel.scaleX / menuTabs.length; var tabHeight = 60; // 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, // Align with container origin scaleX: tabWidth / 200, scaleY: tabHeight / 299, }); // Add text to tab var tabText = new Text2(tab, { size: 50, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2 }); 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); });
User prompt
Update with: // Add debug text to ensure the tab is visible var debugText = new Text2(tab, { size: 70, fill: 0xFF0000, // Bright red for visibility stroke: 0x000000, strokeThickness: 2 }); debugText.anchor = {x: 0.5, y: 0.5}; debugText.x = tabButton.x; debugText.y = -tabHeight/2; tabsContainer.addChild(debugText); });
User prompt
Update as needed with: // Create the tabs with precise positioning menuTabs.forEach(function(tab, index) { // Create tab button with careful positioning var tabButton = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1.0, // Anchor to bottom edge // Position horizontally across the panel width x: (index * tabWidth) + (tabWidth/2) - (panelWidth/2), // Position at the bottom edge of the container y: 0, scaleX: (tabWidth * 0.95) / 200, scaleY: tabHeight / 299, alpha: 1.0 // Full visibility for debugging }); // Add to container tabsContainer.addChild(tabButton);
User prompt
Update as needed with: // First, let's get the exact dimensions of the menu panel // This should go right after you create the menuPanel var panelWidth = menuPanel.width * menuPanel.scaleX; var panelHeight = menuPanel.height * menuPanel.scaleY; // Now create the tab container with precise positioning var tabsContainer = new Container(); // Position it at the bottom of the panel, accounting for the panel's anchor tabsContainer.y = -panelHeight; menuContainer.addChild(tabsContainer); // Define tab dimensions based on panel size var tabWidth = panelWidth / menuTabs.length; var tabHeight = 60;
User prompt
Update with: // Define tab dimensions var tabWidth = (menuPanel.width * menuPanel.scaleX) / menuTabs.length; var tabHeight = 60; // When creating each tab button (inside the menuTabs.forEach loop): var tabButton = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, // Calculate position to space them evenly across the panel width x: (index * tabWidth) + (tabWidth/2) - ((menuPanel.width * menuPanel.scaleX) / 2), scaleX: (tabWidth * 0.9) / 200, // 90% of available width scaleY: tabHeight / 299, alpha: tab === currentTab ? 1.0 : 0.7 });
User prompt
Update with: // When creating tabsContainer: var tabsContainer = new Container(); // Position at the bottom of the menu panel tabsContainer.y = -menuPanel.height * menuPanel.scaleY; menuContainer.addChild(tabsContainer);
User prompt
Update with: // When initializing tabsContainer, add: tabsContainer.visible = false; // In the menu tween, update to: tween(menuContainer, { y: targetY }, { duration: 300, easing: tween.easeOutBack, onFinish: function() { // Show/hide tabs based on menu state tabsContainer.visible = menuOpen; if (!menuOpen) { game.setChildIndex(menuContainer, 1); } } }); ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Update with: // Update these values for the tab buttons var tabWidth = menuPanel.width * 0.22; // Make them wider var tabHeight = 60; // Make them shorter // When creating tab buttons: var tabButton = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, x: (index - 1.5) * tabWidth, // Space them out horizontally scaleX: tabWidth / 200, scaleY: tabHeight / 299, alpha: tab === currentTab ? 1.0 : 0.7 });
User prompt
Update with: // When creating tabsContainer, change to: var tabsContainer = new Container(); tabsContainer.y = -100; // Position above the bottom of the panel menuContainer.addChild(tabsContainer);
User prompt
Update with: tabsContainer.y = -300; // Fixed position that should be visible tabsContainer.visible = true; // Always visible for testing
User prompt
Update with: Change these to iterate through the tab containers: ```javascript Object.values(tabContainers).forEach(function(tabContainer) { tabContainer.children.forEach(function (child) { // ...existing code that finds and updates text elements }); });
Code edit (2 edits merged)
Please save this source code
User prompt
Update with: // Modify the existing menu animation to show/hide tabs // Update the tween function where menu is opened/closed tween(menuContainer, { y: targetY }, { duration: 300, easing: tween.easeOutBack, onUpdate: function(progress) { // Show tabs only when menu is fully open tabsContainer.visible = progress > 0.9 && menuOpen; }, onFinish: function() { tabsContainer.visible = menuOpen; if (!menuOpen) { game.setChildIndex(menuContainer, 1); } } }); ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'menuTextContainer.addChild')' in or related to this line: 'menuTextContainer.addChild(contentContainer);' Line Number: 741
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'menuTextContainer.addChild')' in or related to this line: 'menuTextContainer.addChild(contentContainer);' Line Number: 741
User prompt
Please fix the bug: 'Can't find variable: getTabForUpgrade' in or related to this line: 'var targetContainer = tabContainers[getTabForUpgrade(category, key)];' Line Number: 914
/**** * 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.size = 100; self.activate = function (x, y, size) { var isPlayerBlown = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; // Reset ALL state 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; // Add some debug logs console.log('Activating bubble:', { size: self.size, maxLifetime: self.maxLifetime, isPlayerBlown: isPlayerBlown }); }; 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); } } self.deactivate(); return true; }; self.getBP = function () { return Math.max(1, Math.floor(Math.pow(self.size, 1.4) * 0.02)); }; self.update = function () { // Only increment lifetime if not being actively blown if (game.growingBubble !== self) { self.lifetime++; if (self.lifetime % 60 === 0) { self.driftX += (Math.random() - 0.5) * 0.8; } } self.x += self.driftX * 1.2; if (self.lifetime > self.maxLifetime) { console.log('Bubble dying - lifetime:', self.lifetime, 'maxLifetime:', self.maxLifetime); if (self.size > 60 && !self.hasSplit) { self.hasSplit = true; var newSize = Math.max(self.MIN_SPLIT_SIZE, self.size * 0.6); for (var i = 0; i < 2; i++) { var split = spawnBubble(self.x, self.y, newSize, i === 0 ? -1 : 1, true); if (split) { split.maxLifetime *= 0.7; } } self.deactivate(); return; } self.autoPop(); return; } if (self.y < -self.size) { // Just deactivate, no points self.visible = false; var index = game.activeBubbles.indexOf(self); if (index > -1) { game.activeBubbles.splice(index, 1); } return; } self.justSplit = false; if (self.verticalVelocity < self.floatSpeed) { self.verticalVelocity += 0.08; } self.y -= self.verticalVelocity; if (Math.abs(self.driftX) > (Math.random() * 20 - 10) / 60) { self.driftX *= 0.98; } self.x += self.driftX; if (self.x < self.size) { self.x = self.size; self.driftX = Math.abs(self.driftX); } else if (self.x > game.width - self.size) { self.x = game.width - self.size; self.driftX = -Math.abs(self.driftX); } var scale = self.size / sprite.width; sprite.scaleX = scale; sprite.scaleY = scale; }; self.autoPop = function () { // Only award points if bubble is on screen if (!self.autoPopDisplayed && self.y > -self.size) { var points = Math.floor(self.getBP() * 0.5); game.addBP(points, self.x, self.y, true); self.autoPopDisplayed = true; } self.deactivate(); return; }; 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 ****/ var UPGRADE_EFFECTS = { lungCapacity: { baseValue: 160, // Base max bubble size incrementPercent: 25 // +25% per level }, quickBreath: { baseValue: 1.6, // Base growth rate incrementPercent: 25 // +25% per level }, autoBubbleSpeed: { decrementPercent: 10 // -10% production time per level }, bubbleDurability: { extraSplits: 1 // +1 split per level }, autoPop: { timeReduction: 0.8 // Reduces lifetime by 20% per level } }; function _slicedToArray3(r, e) { return _arrayWithHoles3(r) || _iterableToArrayLimit3(r, e) || _unsupportedIterableToArray3(r, e) || _nonIterableRest3(); } function _nonIterableRest3() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray3(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray3(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray3(r, a) : void 0; } } function _arrayLikeToArray3(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } function _iterableToArrayLimit3(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) { return; } f = !1; } else { for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) { ; } } } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) { return; } } finally { if (o) { throw n; } } } return a; } } function _arrayWithHoles3(r) { if (Array.isArray(r)) { return r; } } function getUpgradeCost(upgrade) { if (upgrade.amount !== undefined) { // For clams return Math.floor(upgrade.baseCost * Math.pow(upgrade.costScale, upgrade.amount)); } else { // For regular upgrades return Math.floor(upgrade.baseCost * Math.pow(upgrade.costScale, upgrade.currentLevel)); } } function _slicedToArray2(r, e) { return _arrayWithHoles2(r) || _iterableToArrayLimit2(r, e) || _unsupportedIterableToArray2(r, e) || _nonIterableRest2(); } function _nonIterableRest2() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray2(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray2(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray2(r, a) : void 0; } } function _arrayLikeToArray2(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } function _iterableToArrayLimit2(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) { return; } f = !1; } else { for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) { ; } } } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) { return; } } finally { if (o) { throw n; } } } return a; } } function _arrayWithHoles2(r) { if (Array.isArray(r)) { return r; } } function updateClams() { if (!game.clamSpawnPoints) { return; } game.clamSpawnPoints.forEach(function (spawnPoint) { var config = UPGRADE_CONFIG.machines[spawnPoint.type]; // Calculate production time with speed upgrade var baseTime = config.production * 60; // Convert to frames var speedMultiplier = Math.pow(1 - UPGRADE_EFFECTS.autoBubbleSpeed.decrementPercent / 100, UPGRADE_CONFIG.machine.autoBubbleSpeed.currentLevel); var adjustedTime = Math.max(1, Math.floor(baseTime * speedMultiplier)); 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 ); // Set initial velocities for clam bubbles bubble.verticalVelocity = 0; bubble.driftX = (spawnPoint.isRight ? -1 : 1) * (Math.random() * 1.5 + 2); game.activeBubbles.push(bubble); } } }); } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) { return; } f = !1; } else { for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) { ; } } } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) { return; } } finally { if (o) { throw n; } } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) { return r; } } var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: game.width / 2, y: game.height / 2 }); game.addChild(background); var clamContainer = new Container(); game.addChild(clamContainer); var playerMask = new pufferMask(); game.addChild(playerMask); var UPGRADE_CONFIG = { player: { lungCapacity: { name: "Lung Capacity", baseCost: 100, // About 1-2 max bubbles costScale: 3.0, // Steeper scaling maxLevel: 10, currentLevel: 0 }, quickBreath: { name: "Quick Breath", baseCost: 100, // 2-3 max bubbles costScale: 3.0, // Steeper scaling maxLevel: 10, currentLevel: 0 }, autoPop: { // Moved here from machine section name: "Fish Friends", // Updated name baseCost: 400, // Changed from 1000 costScale: 3, maxLevel: 6, // Changed from 5 currentLevel: 0 } }, 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 } } }; game.showError = function (message) { var errorText = new Text2(message, { size: 120, fill: 0xFF0000, stroke: 0x000000, strokeThickness: 5, font: "Impact" }); // Set the anchor point to center the text errorText.anchor = { x: 0.5, y: 0.5 }; // Position at the center of the screen errorText.x = game.width / 2; errorText.y = game.height / 2; game.addChild(errorText); // Animate the text tween(errorText, { alpha: 0, y: errorText.y - 50 }, { duration: 1200, onFinish: function onFinish() { errorText.destroy(); } }); }; var currentTab = 'bubbles'; // Default tab var menuTabs = ['bubbles', 'clams', 'colors', 'decorations']; var tabButtons = {}; // Will hold references to tab buttons // Create upgrade menu elements // Menu tab (handle) // First position the panel relative to container at y=0 // Menu panel should be below the tab in the container var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, // Change to bottom anchor y: 0, // Will be at container's position scaleX: 3, scaleY: 0.8, alpha: 0.9 }); var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, y: -570, alpha: 0.9, scaleX: 2048 / 200, // Use the width of the asset directly scaleY: game.height * 0.4 / 100.3 }); // Initialize menu structure // Initialize menu container at the right position var menuContainer = new Container(); menuContainer.x = game.width / 2; menuContainer.y = game.height; // Position at bottom var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, y: -570, alpha: 0.9, scaleX: 2048 / 200, scaleY: game.height * 0.4 / 100.3 }); var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, y: 0, scaleX: 3, scaleY: 0.8, alpha: 0.9 }); // Add panel first (so it's behind tab) menuContainer.addChild(menuPanel); menuContainer.addChild(menuTab); // Menu text setup - should be good as is var menuText = new Text2("Upgrades", { size: 90, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); menuText.anchor = { x: 0.5, y: 0.5 }; menuText.x = 0; // Relative to container menuText.y = -menuTab.height / 2; // Position relative to container bottom menuContainer.addChild(menuText); // Add to game game.addChild(menuContainer); // Add this after the menuPanel is created but before upgradeTexts are created var tabContainers = {}; var tabHeight = 80; var tabWidth = menuPanel.width / menuTabs.length; // 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(); tabsContainer.y = -menuPanel.height * menuPanel.scaleY; // Position at bottom of panel menuContainer.addChild(tabsContainer); // Create tab for each category menuTabs.forEach(function (tab, index) { // Create container for this tab's content var contentContainer = new Container(); contentContainer.visible = tab === currentTab; tabContainers[tab] = contentContainer; menuTextContainer.addChild(contentContainer); // Create tab button var tabButton = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, x: (index + 0.5) * tabWidth - menuPanel.width / 2, scaleX: tabWidth / 200, scaleY: tabHeight / 100, alpha: tab === currentTab ? 1.0 : 0.7 }); // Tab label var tabLabel = new Text2(tab.charAt(0).toUpperCase() + tab.slice(1), { size: 70, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); tabLabel.anchor = { x: 0.5, y: 0.5 }; tabLabel.x = tabButton.x; tabLabel.y = tabHeight / 2; // Add hit detection tabButton.down = function () { if (tab !== currentTab) { switchTab(tab); } return true; }; tabsContainer.addChild(tabButton); tabsContainer.addChild(tabLabel); tabButtons[tab] = tabButton; }); // 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') { return 'clams'; } if (key.includes('color') || key.includes('prismatic')) { return 'colors'; } if (key.includes('coral') || key.includes('treasure')) { return 'decorations'; } // Default case return 'bubbles'; } // Function to create upgrade text function createUpgradeText(category, key, index, isLeftColumn) { var upgrade = UPGRADE_CONFIG[category][key]; var xOffset = isLeftColumn ? -550 : 100; var yPos = startY + index * upgradeSpacing; // Create hit container first (so it's behind text) var hitContainer = new Container(); // Create a shape for the hit area (can make visible for debugging) var hitArea = LK.getAsset('blower', { width: 400, height: 150, color: 0xFFFFFF, alpha: 0.0 // Set to 0.2 to see hit areas }); hitContainer.addChild(hitArea); hitContainer.x = xOffset; // Center on the text position hitContainer.y = yPos; // Align with the text // Adjust the hitArea position within the container to center properly hitArea.x = 0; // Center horizontally hitArea.y = -40; // Slightly above text to cover both name and cost // Create name text var nameText = new Text2(upgrade.name, { size: 96, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); nameText.x = xOffset; nameText.y = yPos; // Create cost text var cost = getUpgradeCost(upgrade); var costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); costText.x = xOffset; costText.y = yPos + 100; // Add click handler to hit container hitContainer.down = function () { var cost = getUpgradeCost(upgrade); if (game.bp >= cost) { if (category === 'machines') { var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount; if (key === 'basicClam' && totalClams < 4) { upgrade.amount++; game.bp -= cost; } else if (key === 'advancedClam' && UPGRADE_CONFIG.machines.basicClam.amount > UPGRADE_CONFIG.machines.advancedClam.amount) { upgrade.amount++; game.bp -= cost; } else if (key === 'premiumClam' && UPGRADE_CONFIG.machines.advancedClam.amount > UPGRADE_CONFIG.machines.premiumClam.amount) { upgrade.amount++; game.bp -= cost; } else { game.showError("Not enough space for more clams!"); return true; } bpText.setText(formatBP(game.bp) + " BP"); // Update cost text based on whether more can be purchased 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') { costText.setText("SOLD OUT"); } else { costText.setText(getUpgradeCost(upgrade) + " BP"); } updateClamVisuals(); } else if (upgrade.currentLevel < upgrade.maxLevel) { upgrade.currentLevel++; game.bp -= cost; // Add this section to update maxBubbleSize when lung capacity changes if (category === 'player' && 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; } bpText.setText(formatBP(game.bp) + " BP"); // Update cost text based on whether more levels can be purchased if (upgrade.currentLevel >= upgrade.maxLevel) { costText.setText("SOLD OUT"); } else { costText.setText(getUpgradeCost(upgrade) + " BP"); } if (key === 'quickBreath') { game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + UPGRADE_EFFECTS.quickBreath.incrementPercent / 100 * upgrade.currentLevel); } } } else { // Show not enough BP error game.showError("Not enough BP!"); } return true; }; menuTextContainer.addChild(hitContainer); menuTextContainer.addChild(nameText); menuTextContainer.addChild(costText); } // Clear existing texts upgradeTexts.forEach(function (text) { return text.destroy(); }); upgradeTexts = []; // Create left column leftColumnUpgrades.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, true); }); // Create right column rightColumnUpgrades.forEach(function (upgrade, index) { createUpgradeText(upgrade[0], upgrade[1], index, false); }); // Move the entire text container down by adjusting its Y position menuTextContainer.y = 0; // This should align it with the top of the panel instead of being above it menuTextContainer.x = 0; // Center in panel // Menu state and animation var menuOpen = false; var menuTargetY = game.height; function updateClamVisuals() { while (clamContainer.children.length) { clamContainer.children[0].destroy(); } var leftStart = game.width * 0.1; // Moved further left from 0.15 var rightStart = game.width * 0.9; // Moved further right from 0.85 var spacing = 250; // Increased from 150 var y = game.height - 100; // We'll store type of each clam position (0-3) var clamTypes = []; // Fill with basic clams first for (var i = 0; i < UPGRADE_CONFIG.machines.basicClam.amount; i++) { clamTypes.push('basicClam'); } // Replace some with advanced for (var i = 0; i < UPGRADE_CONFIG.machines.advancedClam.amount; i++) { if (clamTypes[i]) { clamTypes[i] = 'advancedClam'; } } // Replace some with premium for (var i = 0; i < UPGRADE_CONFIG.machines.premiumClam.amount; i++) { if (clamTypes[i]) { clamTypes[i] = 'premiumClam'; } } // Place clams game.clamSpawnPoints = []; clamTypes.forEach(function (type, i) { var isRight = i % 2 === 1; var baseX = isRight ? rightStart : leftStart; var direction = isRight ? -1 : 1; var position = Math.floor(i / 2); var x = baseX + direction * position * spacing; var sprite = LK.getAsset(type, { anchorX: 0.5, anchorY: 1, x: x, y: y, scaleX: isRight ? -0.5 : 0.5, // Flip right-side clams scaleY: 0.5 }); // Store spawn point for this clam game.clamSpawnPoints.push({ x: x + (isRight ? -75 : 75), // 25% from edge y: y - 50, // Slightly above clam type: type, isRight: isRight }); clamContainer.addChild(sprite); }); } updateClamVisuals(); // Add this to the end of the game initialization code, after updating the clam visuals function updateAllUpgradeTexts() { // Process left column upgrades leftColumnUpgrades.forEach(function (upgrade) { var category = upgrade[0]; var key = upgrade[1]; var upgradeConfig = UPGRADE_CONFIG[category][key]; // Find the cost text for this upgrade menuTextContainer.children.forEach(function (child) { if (child.text && child.text.includes("BP") && child.y > menuTextContainer.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 menuTextContainer.children.forEach(function (child) { if (child.text && child.text.includes("BP") && child.y > menuTextContainer.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 menuTextContainer.children.forEach(function (child) { if (child.text && child.text.includes("BP") && child.y > menuTextContainer.children.find(function (c) { return c.text === upgradeConfig.name; }).y) { // Check if max level reached if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) { child.setText("SOLD OUT"); } } }); } }); } // Call the function after creating all upgrades updateAllUpgradeTexts(); // Initialize game variables game.growingBubble = null; game.lastMouthState = false; // Track previous mouth state game.mouthOpenDuration = 0; // Track how long mouth has been open game.MOUTH_OPEN_THRESHOLD = 10; // Frames required with mouth open to start bubble game.MIN_SPAWN_SIZE = 25; // Minimum initial bubble size game.blowCooldown = 0; // Cooldown timer between bubble starts game.BLOW_COOLDOWN_TIME = 15; // Frames to wait between new bubbles game.maxBubbleSize = 160; // Increased by 30% game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + UPGRADE_EFFECTS.quickBreath.incrementPercent / 100 * UPGRADE_CONFIG.player.quickBreath.currentLevel); game.bubblePool = Array(250).fill(null).map(function () { return new Bubble(); }); game.activeBubbles = []; game.MAX_BUBBLES = 200; // Active bubble limit game.bubblePool.forEach(function (bubble) { game.addChild(bubble); }); game.baseSpawnRate = 180; // Every 3 seconds function spawnBubble(x, y, size) { var direction = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; var isAutoPop = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; if (game.activeBubbles.length >= game.MAX_BUBBLES) { return null; } // Find first available bubble in pool var bubble = game.bubblePool.find(function (b) { return !b.visible; }); if (!bubble) { return null; } bubble.activate(x, y, size); if (isAutoPop) { bubble.verticalVelocity = bubble.floatSpeed; bubble.driftX = direction * (Math.random() * 0.8 + 0.5); } else { bubble.verticalVelocity = -(Math.random() * 2 + 4); bubble.driftX = direction * (Math.random() * 1.5 + 2); } game.activeBubbles.push(bubble); return bubble; } // Initialize game variables //<Assets used in the game will automatically appear here> game.bp = 0; // Track total BP game.combo = 0; game.lastPopTime = 0; game.COMBO_WINDOW = 60; // 1 second in frames function formatBP(value) { var units = ['', 'K', 'M', 'B', 'T']; var unitIndex = 0; while (value >= 1000 && unitIndex < units.length - 1) { value /= 1000; unitIndex++; } return Math.floor(value * 10) / 10 + units[unitIndex]; } // Create BP display text (add near game initialization) var bpText = new Text2("0 BP", { size: 120, fill: 0xFFFFFF, stroke: 0x33caf8, strokeThickness: 4, font: "Impact", fontWeight: "bold" }); bpText.anchor.set(1, 0); bpText.x = game.width - 20; bpText.y = 20; game.addChild(bpText); game.addBP = function (points, x, y, isAutoPop) { var currentTime = LK.ticks; // Only update combo if it's not an auto-pop if (!isAutoPop) { if (currentTime - game.lastPopTime < game.COMBO_WINDOW) { game.combo++; points *= 1 + game.combo * 0.1; // 10% bonus per combo } else { game.combo = 0; } game.lastPopTime = currentTime; } // Ensure points is at least 1 points = Math.max(1, Math.floor(points)); game.bp += points; bpText.setText(formatBP(game.bp) + " BP"); // Set size and color based on point value var textSize = 96; var textColor = 0xFFFF00; // Default yellow if (points > 100) { textSize = 120; textColor = 0xFF0000; // Red for >100 } else if (points > 50) { textSize = 108; textColor = 0xFFA500; // Orange for 51-100 } // Always show point text regardless of auto or manual pop var pointText = new Text2("+" + points, { size: textSize, fill: textColor, font: "Impact", fontWeight: 'bold' }); pointText.anchorX = 0.5; pointText.anchorY = 0.5; pointText.x = x; pointText.y = y; game.addChild(pointText); tween(pointText, { y: pointText.y - 100, alpha: 0 }, { duration: 1200, onFinish: function onFinish() { pointText.destroy(); } }); // Only show combo text if it's a manual pop and we have a combo if (!isAutoPop && game.combo > 0) { var comboText = new Text2("x" + (game.combo + 1), { size: 96, fill: 0xFFA500, stroke: 0x000000, strokeThickness: 4, fontWeight: 'bold' }); comboText.anchorX = 0.5; comboText.anchorY = 0; comboText.x = game.width / 2; comboText.y = 20; game.addChild(comboText); tween(comboText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { comboText.destroy(); } }); } }; game.update = function () { // Update mouth state and duration if (!game.lastMouthState) { game.mouthOpenDuration = 0; } if (facekit.mouthOpen) { game.mouthOpenDuration++; } else { game.mouthOpenDuration = 0; } // Only allow bubble creation if menu is closed and mouth has been open long enough if (!menuOpen && facekit.mouthOpen && game.mouthOpenDuration >= game.MOUTH_OPEN_THRESHOLD) { // Only allow new bubbles after cooldown if (!game.growingBubble && game.blowCooldown <= 0) { var spawnX = playerMask.x; var spawnY = playerMask.y + playerMask.height * 0.15; game.growingBubble = spawnBubble(spawnX, spawnY, game.MIN_SPAWN_SIZE, 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; game.growingBubble = null; 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); } } 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 }, { duration: 300, easing: tween.easeOutBack, onUpdate: function onUpdate(progress) { // Show tabs only when menu is fully open tabsContainer.visible = progress > 0.9 && menuOpen; }, onFinish: function onFinish() { tabsContainer.visible = menuOpen; if (!menuOpen) { game.setChildIndex(menuContainer, 1); } } }); return true; } if (menuOpen) { return true; // Let containers handle their own clicks } // Bubble popping logic remains the same var popped = false; // NEW: for (var i = game.activeBubbles.length - 1; i >= 0; i--) { var bubble = game.activeBubbles[i]; var dx = x - bubble.x; var dy = y - bubble.y; var distance = Math.sqrt(dx * dx + dy * dy); if (!popped && distance <= bubble.size / 2 + 30 && bubble.down) { bubble.down(); popped = true; break; } } };
===================================================================
--- original.js
+++ change.js
@@ -789,8 +789,25 @@
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') {
+ return 'clams';
+ }
+ if (key.includes('color') || key.includes('prismatic')) {
+ return 'colors';
+ }
+ if (key.includes('coral') || key.includes('treasure')) {
+ return 'decorations';
+ }
+ // Default case
+ return 'bubbles';
+}
// Function to create upgrade text
function createUpgradeText(category, key, index, isLeftColumn) {
var upgrade = UPGRADE_CONFIG[category][key];
var xOffset = isLeftColumn ? -550 : 100;
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