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