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
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: 922
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
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
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: // 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
Update with: // Try this positioning for tabsContainer var tabsContainer = new Container(); // Set Y to match where the bottom of the panel is tabsContainer.y = -100; // Adjust this value to find the right position menuContainer.addChild(tabsContainer);
User prompt
Update with: // Position tabsContainer at the bottom of the panel var tabsContainer = new Container(); tabsContainer.y = -570 + (menuPanel.height * menuPanel.scaleY); // Bottom of panel menuContainer.addChild(tabsContainer);
Code edit (9 edits merged)
Please save this source code
User prompt
Update with: // Increase tab height var tabHeight = 120; // Doubled from 60 // When creating each tab button var tabButton = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, // Anchor to top x: -menuPanel.width * menuPanel.scaleX / 2 + (index + 0.5) * tabWidth, y: 0, scaleX: tabWidth / 200, scaleY: tabHeight / 299, // Updated for new height }); // Increase text size 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" });
Code edit (2 edits merged)
Please save this source code
User prompt
Update with: // When creating tab text, capitalize the entire tab name var tabText = new Text2(tab.toUpperCase(), { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" });
User prompt
Update with: // When creating each tab button, add or update the hit detection var tabButton = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, x: -menuPanel.width * menuPanel.scaleX / 2 + (index + 0.5) * tabWidth, y: 0, scaleX: tabWidth / 200, scaleY: tabHeight / 299, alpha: tab === currentTab ? 1.0 : 0.7 }); // Add hit detection 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;
User prompt
Update with: // Add these new upgrade definitions to your UPGRADE_CONFIG // For the Bubbles tab UPGRADE_CONFIG.player.bubbleRefinement = { name: "Bubble Refinement", baseCost: 2500, costScale: 2.5, maxLevel: 5, currentLevel: 0 }; UPGRADE_CONFIG.player.twinBubbles = { name: "Twin Bubbles", baseCost: 5000, costScale: 2.3, maxLevel: 3, currentLevel: 0 }; UPGRADE_CONFIG.player.sizeVariance = { name: "Size Variance", baseCost: 4000, costScale: 1.8, maxLevel: 5, currentLevel: 0 }; UPGRADE_CONFIG.player.bubbleBreath = { name: "Bubble Breath", baseCost: 12000, costScale: 2.2, maxLevel: 4, currentLevel: 0 }; // For the Clams tab UPGRADE_CONFIG.machine.bubbleQuality = { name: "Bubble Quality", baseCost: 3000, costScale: 2.2, maxLevel: 5, currentLevel: 0 }; // For the Colors tab - create a new section UPGRADE_CONFIG.colors = { blueBubbles: { name: "Blue Bubbles", baseCost: 1000, costScale: 1.0, // Only one level maxLevel: 1, currentLevel: 0 }, greenBubbles: { name: "Green Bubbles", baseCost: 3000, costScale: 1.0, // Only one level maxLevel: 1, currentLevel: 0, requires: "blueBubbles" }, pinkBubbles: { name: "Pink Bubbles", baseCost: 6000, costScale: 1.0, // Only one level maxLevel: 1, currentLevel: 0, requires: "greenBubbles" }, rainbowBubbles: { name: "Rainbow Bubbles", baseCost: 15000, costScale: 1.0, // Only one level maxLevel: 1, currentLevel: 0, requires: "pinkBubbles" }, prismaticBubbles: { name: "Prismatic Bubbles", baseCost: 30000, costScale: 1.0, // Only one level maxLevel: 1, currentLevel: 0, requires: "rainbowBubbles" } }; // For the Decorations tab - create a new section UPGRADE_CONFIG.decorations = { 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 } };
User prompt
Update with: // Update the getTabForUpgrade function 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 } // Define column assignments for each tab var tabColumns = { bubbles: { left: [ ['player', 'lungCapacity'], ['player', 'quickBreath'], ['player', 'bubbleRefinement'], ['player', 'twinBubbles'] ], right: [ ['player', 'autoPop'], ['player', 'sizeVariance'], ['player', 'bubbleBreath'] ] }, clams: { left: [ ['machines', 'basicClam'], ['machines', 'advancedClam'], ['machines', 'premiumClam'] ], right: [ ['machine', 'bubbleDurability'], ['machine', 'autoBubbleSpeed'], ['machine', 'bubbleQuality'] ] }, colors: { left: [ ['colors', 'blueBubbles'], ['colors', 'greenBubbles'], ['colors', 'pinkBubbles'] ], right: [ ['colors', 'rainbowBubbles'], ['colors', 'prismaticBubbles'] ] }, decorations: { left: [ ['decorations', 'bubbleCoral'] ], right: [ ['decorations', 'sunkenTreasures'] ] } };
Code edit (1 edits merged)
Please save this source code
User prompt
Update with: // Update createUpgradeText to handle tab-specific containers 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 ? -550 : 100; var yPos = startY + index * upgradeSpacing; // 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 = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); costText.x = xOffset; costText.y = yPos + 100; // Special handling for color upgrades with requirements if (category === 'colors' && upgrade.requires) { var required = UPGRADE_CONFIG.colors[upgrade.requires]; if (required && required.currentLevel === 0) { costText.setText("LOCKED"); costText.setFill(0x888888); } } // Add click handler hitContainer.down = function() { var cost = getUpgradeCost(upgrade); // Check if this is a locked color upgrade if (category === 'colors' && upgrade.requires) { var required = UPGRADE_CONFIG.colors[upgrade.requires]; if (required && required.currentLevel === 0) { game.showError("Unlock " + required.name + " first!"); return true; } } if (game.bp >= cost) { if (upgrade.amount !== undefined) { // For clams and decorations with amount if (upgrade.amount < (upgrade.maxAmount || 999)) { upgrade.amount++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); costText.setText(getUpgradeCost(upgrade) + " BP"); // Update clam visuals if needed if (category === 'machines') { updateClamVisuals(); } // Update decoration visuals if implemented if (category === 'decorations') { // TODO: Implement decoration visuals } } } else if (upgrade.currentLevel < upgrade.maxLevel) { // For regular upgrades with levels upgrade.currentLevel++; game.bp -= cost; bpText.setText(formatBP(game.bp) + " BP"); // 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); }
User prompt
Please fix the bug: 'costText.setFill is not a function. (In 'costText.setFill(0x888888)', 'costText.setFill' is undefined)' in or related to this line: 'costText.setFill(0x888888);' Line Number: 1032
User prompt
Update with: // Update the createUpgradeText function to properly handle locked upgrades: // Inside createUpgradeText function, modify the cost text creation: var costText; if (category === 'colors' && upgrade.requires) { var required = UPGRADE_CONFIG.colors[upgrade.requires]; if (required && required.currentLevel === 0) { // Create "LOCKED" text for locked upgrades costText = new Text2("LOCKED", { size: 96, fill: 0x888888, // Gray color stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } else { // Normal cost text costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } } else { // Normal cost text for non-color upgrades costText = new Text2(cost + " BP", { size: 96, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); } costText.x = xOffset; costText.y = yPos + 100;
Code edit (6 edits merged)
Please save this source code
User prompt
Anchor tabs container to center x
Code edit (6 edits merged)
Please save this source code
User prompt
Equalize the spacing between menu tabs
User prompt
Lower the height of the menu text container by 80 pixels.
/****
* 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
});
// First, let's get the exact dimensions of the menu panel
var panelWidth = menuPanel.width * menuPanel.scaleX;
var panelHeight = menuPanel.height * menuPanel.scaleY;
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 tabWidth = panelWidth / menuTabs.length;
var tabHeight = 60;
// 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();
// 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);
});
// 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;
};
var tabName = getTabForUpgrade(category, key);
if (!tabContainers[tabName]) {
tabContainers[tabName] = new Container();
tabsContainer.addChild(tabContainers[tabName]);
}
var targetContainer = tabContainers[tabName];
targetContainer.addChild(hitContainer);
targetContainer.addChild(nameText);
targetContainer.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
Object.values(tabContainers).forEach(function (tabContainer) {
tabContainer.children.forEach(function (child) {
if (child.text && child.text.includes("BP") && child.y > tabContainer.children.find(function (c) {
return c.text === upgradeConfig.name;
}).y) {
// Check if max level reached
if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) {
child.setText("SOLD OUT");
}
}
});
});
});
// Process right column upgrades
rightColumnUpgrades.forEach(function (upgrade) {
var category = upgrade[0];
var key = upgrade[1];
if (category === 'machines') {
var upgradeConfig = UPGRADE_CONFIG[category][key];
var totalClams = UPGRADE_CONFIG.machines.basicClam.amount + UPGRADE_CONFIG.machines.advancedClam.amount + UPGRADE_CONFIG.machines.premiumClam.amount;
// Find the cost text for this upgrade
Object.values(tabContainers).forEach(function (tabContainer) {
tabContainer.children.forEach(function (child) {
if (child.text && child.text.includes("BP") && child.y > tabContainer.children.find(function (c) {
return c.text === upgradeConfig.name;
}).y) {
// Check if sold out based on clam logic
if (totalClams >= 4 && key === 'basicClam' || UPGRADE_CONFIG.machines.basicClam.amount <= UPGRADE_CONFIG.machines.advancedClam.amount && key === 'advancedClam' || UPGRADE_CONFIG.machines.advancedClam.amount <= UPGRADE_CONFIG.machines.premiumClam.amount && key === 'premiumClam') {
child.setText("SOLD OUT");
}
}
});
});
} else if (category === 'machine') {
var upgradeConfig = UPGRADE_CONFIG[category][key];
// Find the cost text for this upgrade
Object.values(tabContainers).forEach(function (tabContainer) {
tabContainer.children.forEach(function (child) {
if (child.text && child.text.includes("BP") && child.y > tabContainer.children.find(function (c) {
return c.text === upgradeConfig.name;
}).y) {
// Check if max level reached
if (upgradeConfig.currentLevel >= upgradeConfig.maxLevel) {
child.setText("SOLD OUT");
}
}
});
});
}
});
}
// Call the function after creating all upgrades
updateAllUpgradeTexts();
// Initialize game variables
game.growingBubble = null;
game.lastMouthState = false; // Track previous mouth state
game.mouthOpenDuration = 0; // Track how long mouth has been open
game.MOUTH_OPEN_THRESHOLD = 10; // Frames required with mouth open to start bubble
game.MIN_SPAWN_SIZE = 25; // Minimum initial bubble size
game.blowCooldown = 0; // Cooldown timer between bubble starts
game.BLOW_COOLDOWN_TIME = 15; // Frames to wait between new bubbles
game.maxBubbleSize = 160; // Increased by 30%
game.growthRate = UPGRADE_EFFECTS.quickBreath.baseValue * (1 + UPGRADE_EFFECTS.quickBreath.incrementPercent / 100 * UPGRADE_CONFIG.player.quickBreath.currentLevel);
game.bubblePool = Array(250).fill(null).map(function () {
return new Bubble();
});
game.activeBubbles = [];
game.MAX_BUBBLES = 200; // Active bubble limit
game.bubblePool.forEach(function (bubble) {
game.addChild(bubble);
});
game.baseSpawnRate = 180; // Every 3 seconds
function spawnBubble(x, y, size) {
var direction = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
var isAutoPop = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
if (game.activeBubbles.length >= game.MAX_BUBBLES) {
return null;
}
// Find first available bubble in pool
var bubble = game.bubblePool.find(function (b) {
return !b.visible;
});
if (!bubble) {
return null;
}
bubble.activate(x, y, size);
if (isAutoPop) {
bubble.verticalVelocity = bubble.floatSpeed;
bubble.driftX = direction * (Math.random() * 0.8 + 0.5);
} else {
bubble.verticalVelocity = -(Math.random() * 2 + 4);
bubble.driftX = direction * (Math.random() * 1.5 + 2);
}
game.activeBubbles.push(bubble);
return bubble;
}
// Initialize game variables
//<Assets used in the game will automatically appear here>
game.bp = 0; // Track total BP
game.combo = 0;
game.lastPopTime = 0;
game.COMBO_WINDOW = 60; // 1 second in frames
function formatBP(value) {
var units = ['', 'K', 'M', 'B', 'T'];
var unitIndex = 0;
while (value >= 1000 && unitIndex < units.length - 1) {
value /= 1000;
unitIndex++;
}
return Math.floor(value * 10) / 10 + units[unitIndex];
}
// Create BP display text (add near game initialization)
var bpText = new Text2("0 BP", {
size: 120,
fill: 0xFFFFFF,
stroke: 0x33caf8,
strokeThickness: 4,
font: "Impact",
fontWeight: "bold"
});
bpText.anchor.set(1, 0);
bpText.x = game.width - 20;
bpText.y = 20;
game.addChild(bpText);
game.addBP = function (points, x, y, isAutoPop) {
var currentTime = LK.ticks;
// Only update combo if it's not an auto-pop
if (!isAutoPop) {
if (currentTime - game.lastPopTime < game.COMBO_WINDOW) {
game.combo++;
points *= 1 + game.combo * 0.1; // 10% bonus per combo
} else {
game.combo = 0;
}
game.lastPopTime = currentTime;
}
// Ensure points is at least 1
points = Math.max(1, Math.floor(points));
game.bp += points;
bpText.setText(formatBP(game.bp) + " BP");
// Set size and color based on point value
var textSize = 96;
var textColor = 0xFFFF00; // Default yellow
if (points > 100) {
textSize = 120;
textColor = 0xFF0000; // Red for >100
} else if (points > 50) {
textSize = 108;
textColor = 0xFFA500; // Orange for 51-100
}
// Always show point text regardless of auto or manual pop
var pointText = new Text2("+" + points, {
size: textSize,
fill: textColor,
font: "Impact",
fontWeight: 'bold'
});
pointText.anchorX = 0.5;
pointText.anchorY = 0.5;
pointText.x = x;
pointText.y = y;
game.addChild(pointText);
tween(pointText, {
y: pointText.y - 100,
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
pointText.destroy();
}
});
// Only show combo text if it's a manual pop and we have a combo
if (!isAutoPop && game.combo > 0) {
var comboText = new Text2("x" + (game.combo + 1), {
size: 96,
fill: 0xFFA500,
stroke: 0x000000,
strokeThickness: 4,
fontWeight: 'bold'
});
comboText.anchorX = 0.5;
comboText.anchorY = 0;
comboText.x = game.width / 2;
comboText.y = 20;
game.addChild(comboText);
tween(comboText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
comboText.destroy();
}
});
}
};
game.update = function () {
// Update mouth state and duration
if (!game.lastMouthState) {
game.mouthOpenDuration = 0;
}
if (facekit.mouthOpen) {
game.mouthOpenDuration++;
} else {
game.mouthOpenDuration = 0;
}
// Only allow bubble creation if menu is closed and mouth has been open long enough
if (!menuOpen && facekit.mouthOpen && game.mouthOpenDuration >= game.MOUTH_OPEN_THRESHOLD) {
// Only allow new bubbles after cooldown
if (!game.growingBubble && game.blowCooldown <= 0) {
var spawnX = playerMask.x;
var spawnY = playerMask.y + playerMask.height * 0.15;
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 //{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 treasure chest with gold coins. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A golden skull with diamonds for eyes. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A golden necklace with a ruby pendant. Cartoon.. 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
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