User prompt
Remember the players last menu recipe choice. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
After groundFish gets tapped by spices tool it should use the spicedGroundFish asset
User prompt
Use the spicedFish asset after a fishSteak has been processed by the spice tool.
User prompt
When the player taps the grinder, spices or pot tools in the cooking screen. Use the grindIndicator, spiceIndicator or potIndicator respectively. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
After the player buys their first cooking tool and they return to the beach screen. Once the animation intro has completed, display a message to tell them that they can switch cooking recipes by tapping on the menu board. ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
When a cooking tool is purchased from the shop, display a message about what cooking recipes are unlocked. ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
Code edit (2 edits merged)
Please save this source code
User prompt
Use the correct indicator assets for the grinder, spices and pot tools in grindIndicator, spiceIndicator and potIndicator respectively.
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'var availableRecipes = RECIPE_SYSTEM.getAvailableRecipes();' Line Number: 6838 ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
Update as needed with: /**** * Recipe System Configuration ****/ var RECIPE_SYSTEM = { recipes: { fishAndChips: { name: "Fish and Chips", stations: ['knife', 'bowl', 'fryer'], requiredTools: [], baseValue: 5, finalAsset: 'fishAndChips', processingSteps: [ { from: 'raw', station: 'knife', produces: 'steak' }, { from: 'steak', station: 'bowl', produces: 'battered' }, { from: 'battered', station: 'fryer', produces: 'final' } ] }, fishChowder: { name: "Fish Chowder", stations: ['knife', 'bowl', 'pot'], requiredTools: ['pot'], baseValue: 7, finalAsset: 'fishChowder', processingSteps: [ { from: 'raw', station: 'knife', produces: 'steak' }, { from: 'steak', station: 'bowl', produces: 'battered' }, { from: 'battered', station: 'pot', produces: 'final' } ] }, fishTacos: { name: "Fish Tacos", stations: ['knife', 'spices', 'fryer'], requiredTools: ['spices'], baseValue: 7, finalAsset: 'fishTaco', processingSteps: [ { from: 'raw', station: 'knife', produces: 'steak' }, { from: 'steak', station: 'spices', produces: 'spiced' }, { from: 'spiced', station: 'fryer', produces: 'final' } ] }, fishCurry: { name: "Fish Curry", stations: ['knife', 'spices', 'pot'], requiredTools: ['spices', 'pot'], baseValue: 7, finalAsset: 'fishCurry', processingSteps: [ { from: 'raw', station: 'knife', produces: 'steak' }, { from: 'steak', station: 'spices', produces: 'spiced' }, { from: 'spiced', station: 'pot', produces: 'final' } ] }, fishCakes: { name: "Fish Cakes", stations: ['grinder', 'bowl', 'fryer'], requiredTools: ['grinder'], baseValue: 7, finalAsset: 'fishCakes', processingSteps: [ { from: 'raw', station: 'grinder', produces: 'ground' }, { from: 'ground', station: 'bowl', produces: 'batteredGround' }, { from: 'batteredGround', station: 'fryer', produces: 'final' } ] }, fishDumplings: { name: "Fish Dumplings", stations: ['grinder', 'bowl', 'pot'], requiredTools: ['grinder', 'pot'], baseValue: 7, finalAsset: 'fishDumplings', processingSteps: [ { from: 'raw', station: 'grinder', produces: 'ground' }, { from: 'ground', station: 'bowl', produces: 'batteredGround' }, { from: 'batteredGround', station: 'pot', produces: 'final' } ] }, fishBurgers: { name: "Fish Burgers", stations: ['grinder', 'spices', 'fryer'], requiredTools: ['grinder', 'spices'], baseValue: 7, finalAsset: 'fishBurger', processingSteps: [ { from: 'raw', station: 'grinder', produces: 'ground' }, { from: 'ground', station: 'spices', produces: 'spicedGround' }, { from: 'spicedGround', station: 'fryer', produces: 'final' } ] }, fishMeatballs: { name: "Fish Meatballs", stations: ['grinder', 'spices', 'pot'], requiredTools: ['grinder', 'spices', 'pot'], baseValue: 7, finalAsset: 'fishMeatballs', processingSteps: [ { from: 'raw', station: 'grinder', produces: 'ground' }, { from: 'ground', station: 'spices', produces: 'spicedGround' }, { from: 'spicedGround', station: 'pot', produces: 'final' } ] } }, getAvailableRecipes: function() { var available = []; for (var recipeId in this.recipes) { var recipe = this.recipes[recipeId]; var canMake = true; // Check if all required tools are owned for (var i = 0; i < recipe.requiredTools.length; i++) { var tool = recipe.requiredTools[i]; var storageKey = tool + 'Owned'; if (!storage[storageKey]) { canMake = false; break; } } if (canMake) { available.push({ id: recipeId, recipe: recipe }); } } return available; }, getCurrentRecipe: function() { return this.recipes[cookingDish] || this.recipes.fishAndChips; } }; /**** * Update Cooking Screen Creation ****/ function createCookingScreen() { cookingScreen.removeChildren(); // Create lane guides for (var i = 0; i < COOKING_CONFIG.LANE_COUNT; i++) { var laneX = COOKING_CONFIG.LANE_START_X + i * COOKING_CONFIG.LANE_WIDTH + COOKING_CONFIG.LANE_WIDTH / 2; var lane = cookingScreen.addChild(LK.getAsset('counterLane', { anchorX: 0.5, anchorY: 0, x: laneX, y: COOKING_CONFIG.FALL_START_Y, height: COOKING_CONFIG.STATION_Y - COOKING_CONFIG.FALL_START_Y, alpha: 0.3 })); } // Add counter graphic var counterHitzone = cookingScreen.addChild(LK.getAsset('counterHitzone', { anchorX: 0.5, anchorY: 0.5, x: COOKING_CONFIG.SCREEN_WIDTH / 2, y: COOKING_CONFIG.STATION_Y })); // Get current recipe and create appropriate stations var currentRecipe = RECIPE_SYSTEM.getCurrentRecipe(); CookingState.stations = []; for (var i = 0; i < currentRecipe.stations.length; i++) { var stationType = currentRecipe.stations[i]; var stationX = COOKING_CONFIG.LANE_START_X + i * COOKING_CONFIG.LANE_WIDTH + COOKING_CONFIG.LANE_WIDTH / 2; // Use appropriate tool asset for each station var assetName = stationType + 'Tool'; var stationSprite = cookingScreen.addChild(LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, x: stationX, y: COOKING_CONFIG.STATION_Y, scaleX: 2.5, scaleY: 2.5 })); CookingState.stations.push({ sprite: stationSprite, type: stationType, lane: i, x: stationX, y: COOKING_CONFIG.STATION_Y }); } // Create UI elements var songNameText = new Text2('', { size: 60, fill: '#FFFFFF', stroke: '#000000', strokeThickness: 3, align: 'right' }); songNameText.anchor.set(1, 0); songNameText.x = 2048 - 50; songNameText.y = 80; cookingScreen.addChild(songNameText); var countdownText = new Text2('', { size: 60, fill: '#FFD700', stroke: '#000000', strokeThickness: 3, align: 'right' }); countdownText.anchor.set(1, 0); countdownText.x = 2048 - 50; countdownText.y = songNameText.y + 70; cookingScreen.addChild(countdownText); // Add recipe name display var recipeNameText = new Text2(currentRecipe.name, { size: 80, fill: '#FFD700', stroke: '#000000', strokeThickness: 4, align: 'center' }); recipeNameText.anchor.set(0.5, 0); recipeNameText.x = COOKING_CONFIG.SCREEN_WIDTH / 2; recipeNameText.y = 50; cookingScreen.addChild(recipeNameText); return { songNameText: songNameText, countdownText: countdownText, recipeNameText: recipeNameText }; } /**** * Update RhythmItem Processing ****/ function processRhythmStation(laneIndex, stationType) { // Find closest item in the lane var closestItem = null; var closestDistance = Infinity; for (var i = 0; i < CookingState.fallingItems.length; i++) { var item = CookingState.fallingItems[i]; if (item.lane === laneIndex) { var distance = Math.abs(item.y - COOKING_CONFIG.STATION_Y); if (distance < closestDistance) { closestDistance = distance; closestItem = item; } } } if (!closestItem || closestDistance > COOKING_CONFIG.MISS_WINDOW) { // Miss showCookingFeedback('miss', laneIndex); LK.getSound('miss').play(); return; } var timingMultiplier = 1.0; var catchType = ''; if (closestDistance < COOKING_CONFIG.PERFECT_WINDOW) { timingMultiplier = 1.0; catchType = stationType; } else if (closestDistance < COOKING_CONFIG.GOOD_WINDOW) { timingMultiplier = 1.0; catchType = stationType; } else { timingMultiplier = 0.5; catchType = stationType; } showCookingFeedback(catchType, laneIndex); var catchSounds = ['catch', 'catch2', 'catch3', 'catch4']; var randomCatchSound = catchSounds[Math.floor(Math.random() * catchSounds.length)]; LK.getSound(randomCatchSound).play(); var item = closestItem; removeRhythmItem(item); var cumulativeMultiplier = item.timingMultiplier * timingMultiplier; // Get current recipe and processing steps var currentRecipe = RECIPE_SYSTEM.getCurrentRecipe(); var currentStep = null; // Find the processing step for this station and current item type for (var i = 0; i < currentRecipe.processingSteps.length; i++) { var step = currentRecipe.processingSteps[i]; if (step.station === stationType && step.from === item.processedType) { currentStep = step; break; } } if (!currentStep) { console.error('No processing step found for station:', stationType, 'item type:', item.processedType); return; } if (currentStep.produces === 'final') { // Complete the dish! completeDish(item.fishType, cumulativeMultiplier, currentRecipe); } else { // Process and send to next lane var newProcessedType = currentStep.produces; var newLane = laneIndex + 1; if (newLane < COOKING_CONFIG.LANE_COUNT) { var processedItem = new RhythmItem(item.fishType, newLane, newProcessedType, cumulativeMultiplier); // Same slower timing for processed items var beatsToNextStation = 8; var distanceToStation = COOKING_CONFIG.STATION_Y - COOKING_CONFIG.FALL_START_Y; var timeToReach = CookingState.beatInterval * beatsToNextStation; processedItem.speed = distanceToStation / (timeToReach / (1000 / 60)); processedItem.targetBeat = CookingState.currentBeat + beatsToNextStation; // Adjust spawn position to sync with beat var timeErrorMs = (item.y - COOKING_CONFIG.STATION_Y) / item.speed * (1000 / 60); var adjustedTimeToReachMs = timeToReach - timeErrorMs; var speedInPxPerFrame = processedItem.speed; var adjustedTravelFrames = adjustedTimeToReachMs / (1000 / 60); var adjustedDistanceInPx = speedInPxPerFrame * adjustedTravelFrames; processedItem.y = COOKING_CONFIG.STATION_Y - adjustedDistanceInPx; cookingScreen.addChild(processedItem); CookingState.fallingItems.push(processedItem); } } } function completeDish(fishType, timingMultiplier, recipe) { timingMultiplier = timingMultiplier || 1.0; var multiplier = COOKING_CONFIG.FISH_MULTIPLIERS[fishType] || 1.0; var earnings = Math.floor(recipe.baseValue * multiplier * timingMultiplier); CookingState.money += earnings; GameState.money += earnings; storage.money = GameState.money; // Capitalize fish name var fishName = fishType.charAt(0).toUpperCase() + fishType.slice(1); var timingText = ''; if (timingMultiplier >= 2.0) { timingText = ' PERFECT'; } else if (timingMultiplier > 0.5) { timingText = ''; } if (typeof animateCustomerLine === 'function') { animateCustomerLine(); } // Show completion popup showCompletionPopup(fishName + " " + recipe.name + timingText + "! +$" + earnings, recipe.finalAsset); } function showCompletionPopup(text, dishAsset) { var popupContainer = cookingScreen.addChild(new Container()); popupContainer.x = COOKING_CONFIG.SCREEN_WIDTH / 2; popupContainer.y = COOKING_CONFIG.SCREEN_HEIGHT / 2; var popup = popupContainer.addChild(new Text2(text, { size: 65, fill: 0xFFD700, stroke: 0x000000, strokeThickness: 3, align: 'center' })); popup.anchor.set(0.5, 0.5); var dishIcon = popupContainer.addChild(LK.getAsset(dishAsset, { anchorX: 0.5, anchorY: 0, scaleX: 2.5, scaleY: 2.5 })); dishIcon.y = popup.y + popup.height / 2 + 20; tween(popupContainer, { y: popupContainer.y - 150, alpha: 0 }, { duration: 2500, delay: 500, easing: tween.easeOut, onFinish: function() { if (popupContainer && !popupContainer.destroyed) { popupContainer.destroy(); } } }); } /**** * Update RhythmItem Class to Handle New Processing Types ****/ var RhythmItem = Container.expand(function(fishType, lane, processedType, timingMultiplier) { var self = Container.call(this); self.fishType = fishType; self.lane = lane; self.processedType = processedType || 'raw'; self.timingMultiplier = timingMultiplier || 1.0; self.targetY = COOKING_CONFIG.STATION_Y; self.speed = 0; self.targetBeat = 0; self.isSpecial = fishType === 'rareFish'; if (self.isSpecial) { self.sparkles = []; self.lastSparkleSpawnTime = 0; self.sparkleSpawnInterval = 5; } // Visual representation based on processed type var assetToUse = ''; var scaleX = 1.6; var scaleY = 1.6; switch (self.processedType) { case 'raw': assetToUse = fishType; break; case 'steak': assetToUse = 'fishSteak'; scaleX = 1.4; scaleY = 1.4; break; case 'battered': assetToUse = 'batteredFish'; scaleX = 1.4; scaleY = 1.4; break; case 'ground': assetToUse = 'groundFish'; scaleX = 1.4; scaleY = 1.4; break; case 'batteredGround': assetToUse = 'batteredGroundFish'; scaleX = 1.4; scaleY = 1.4; break; case 'spiced': assetToUse = 'fishSteak'; // Spiced fish steak looks like regular steak scaleX = 1.4; scaleY = 1.4; break; case 'spicedGround': assetToUse = 'groundFish'; // Spiced ground fish looks like regular ground fish scaleX = 1.4; scaleY = 1.4; break; default: assetToUse = fishType; break; } if (assetToUse) { self.sprite = self.attachAsset(assetToUse, { anchorX: 0.5, anchorY: 0.5, scaleX: scaleX, scaleY: scaleY }); } // Position in lane self.x = COOKING_CONFIG.LANE_START_X + lane * COOKING_CONFIG.LANE_WIDTH + COOKING_CONFIG.LANE_WIDTH / 2; self.y = COOKING_CONFIG.FALL_START_Y; self.cleanupSparkles = function() { if (!self.isSpecial || !self.sparkles) { return; } for (var i = self.sparkles.length - 1; i >= 0; i--) { var sparkle = self.sparkles[i]; if (sparkle && !sparkle.destroyed) { sparkle.destroy(); } } self.sparkles = []; }; self.update = function() { self.y += self.speed; if (self.isSpecial) { self.lastSparkleSpawnTime++; if (self.lastSparkleSpawnTime > self.sparkleSpawnInterval) { self.lastSparkleSpawnTime = 0; var spawnOffsetX = (Math.random() - 0.5) * self.sprite.width * 0.9; var spawnOffsetY = (Math.random() - 0.5) * self.sprite.height * 0.9; var newSparkle = new SparkleParticle(self.x + spawnOffsetX, self.y + spawnOffsetY); if (globalEffectsLayer && !globalEffectsLayer.destroyed) { globalEffectsLayer.addChild(newSparkle); self.sparkles.push(newSparkle); } } for (var i = self.sparkles.length - 1; i >= 0; i--) { var sparkle = self.sparkles[i]; sparkle.update(); if (sparkle.isDone) { if (sparkle && !sparkle.destroyed) { sparkle.destroy(); } self.sparkles.splice(i, 1); } } } }; self.getDistance = function() { return Math.abs(self.y - self.targetY); }; self.fadeAway = function() { if (self.isSpecial) { self.cleanupSparkles(); } // Quick fade animation for missed items tween(self, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 300, easing: tween.easeOut, onFinish: function() { if (self && !self.destroyed) { self.destroy(); } } }); }; return self; }); /**** * Update Menu Overlay System ****/ function createMenuOverlay() { menuOverlayContainer = beachScreen.addChild(new Container()); menuOverlayContainer.visible = false; var overlayBg = menuOverlayContainer.addChild(LK.getAsset('songCard', { x: 1024, y: 1366, width: 1400, height: 1000, color: 0x3a3a3a, anchorX: 0.5, anchorY: 0.5, alpha: 0.95 })); var title = menuOverlayContainer.addChild(new Text2('Menu', { size: 110, fill: 0xFFFFFF, align: 'center' })); title.anchor.set(0.5, 0); title.x = 1024; title.y = 1366 - 1000 / 2 + 50; var closeButton = menuOverlayContainer.addChild(LK.getAsset('closeButton', { anchorX: 0.5, anchorY: 0.5, x: 1024 + 1400 / 2 - 75, y: 1366 - 1000 / 2 + 75, tint: 0xff4444 })); var closeButtonText = menuOverlayContainer.addChild(new Text2('X', { size: 60, fill: 0xFFFFFF })); closeButtonText.anchor.set(0.5, 0.5); closeButtonText.x = closeButton.x; closeButtonText.y = closeButton.y; closeButton.down = function() { hideMenuOverlay(); }; // Get available recipes and create buttons var availableRecipes = RECIPE_SYSTEM.getAvailableRecipes(); var buttonWidth = 550; var buttonHeight = 100; var buttonsPerRow = 2; var buttonSpacing = 50; var startY = title.y + title.height + 100; for (var i = 0; i < availableRecipes.length; i++) { var recipeData = availableRecipes[i]; var recipe = recipeData.recipe; var recipeId = recipeData.id; var row = Math.floor(i / buttonsPerRow); var col = i % buttonsPerRow; var buttonX = 1024 + (col - 0.5) * (buttonWidth + buttonSpacing); var buttonY = startY + row * (buttonHeight + buttonSpacing); var recipeButton = menuOverlayContainer.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: buttonX, y: buttonY, width: buttonWidth, height: buttonHeight, tint: cookingDish === recipeId ? 0x2e7d32 : 0x1976d2 // Green if selected, blue otherwise })); var recipeText = menuOverlayContainer.addChild(new Text2(recipe.name, { size: 50, fill: 0xFFFFFF, align: 'center' })); recipeText.anchor.set(0.5, 0.5); recipeText.x = buttonX; recipeText.y = buttonY; // Create closure to capture recipeId (function(id) { recipeButton.down = function() { cookingDish = id; hideMenuOverlay(); }; })(recipeId); } } /**** * Update Sandwich Board Icon System ****/ function updateSandwichBoardIcon() { if (!beachScreenElements || !beachScreenElements.sandwichBoard || beachScreenElements.sandwichBoard.destroyed) { return; } var dishContainer = beachScreenElements.sandwichBoard.dishContainer; if (!dishContainer || dishContainer.destroyed) { return; } // Clear any existing icon dishContainer.removeChildren(); // Get the current recipe's final asset var currentRecipe = RECIPE_SYSTEM.getCurrentRecipe(); var dishAsset = currentRecipe.finalAsset; // Add the new icon based on the current dish var dishIcon = LK.getAsset(dishAsset, { anchorX: 0.5, anchorY: 0.6, scaleX: 1.4, scaleY: 1.4 }); dishContainer.addChild(dishIcon); } /**** * Update Base Fish and Chips Value in Config ****/ // Update the base value to match the recipe system COOKING_CONFIG.BASE_FISH_CHIPS_VALUE = 5; // This matches the fishAndChips recipe baseValue ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
Update only as needed with: function endFishingSession() { GameState.gameActive = false; GameState.tutorialMode = false; // Check if there are any catchable fish still on screen var catchableFishRemaining = fishArray.filter(function (fish) { return !fish.caught && !fish.missed && !fish.destroyed; }); if (catchableFishRemaining.length > 0) { // Set a flag to indicate we're waiting for fish to be caught GameState.fishingEnding = true; // Continue game loop to allow catching remaining fish GameState.gameActive = true; // Don't stop music yet - let it continue playing return; } // No catchable fish remaining, proceed with normal cleanup performFinalCleanup(); } function performFinalCleanup() { // Stop music and animations stopTweens([fishingElements.boat, fishingElements.fishermanContainer, fishingElements.fisherman]); if (fishingElements && fishingElements.waterSurfaceSegments) { stopTweens(fishingElements.waterSurfaceSegments); } LK.stopMusic(); ImprovedRhythmSpawner.reset(); // Clean up lane brackets if (laneBrackets && laneBrackets.length > 0) { laneBrackets.forEach(function (bracketPair) { if (bracketPair.left && !bracketPair.left.destroyed) { bracketPair.left.destroy(); } if (bracketPair.right && !bracketPair.right.destroyed) { bracketPair.right.destroy(); } }); laneBrackets = []; } // Clean up fish fishArray.forEach(function (fish) { fish.destroy(); }); fishArray = []; // Clean up particles GameState.musicNotesActive = false; if (fishingElements && fishingElements.musicNotesContainer) { fishingElements.musicNotesContainer.removeChildren(); } musicNotesArray = []; cleanupParticleArray(globalOceanBubblesArray, globalOceanBubbleContainer); cleanupParticleArray(globalSeaweedArray, globalSeaweedContainer); cleanupParticleArray(globalCloudArray, globalCloudContainer); createResultsScreen(); _showScreen('results'); } ``` And update the game loop section that handles the end-of-song logic: ```javascript // In the main game.update() function, replace the song end check section with: // Check song end var songConfig = GameState.getCurrentSongConfig(); if (currentTime - GameState.songStartTime >= songConfig.duration && !GameState.fishingEnding) { endFishingSession(); return; } // If song has ended and we're waiting for fish to be caught, check if all are gone if (GameState.fishingEnding) { var catchableFishRemaining = fishArray.filter(function (fish) { return !fish.caught && !fish.missed && !fish.destroyed; }); if (catchableFishRemaining.length === 0) { // All fish are caught/cleared, now end the session for real GameState.fishingEnding = false; GameState.gameActive = false; performFinalCleanup(); return; } // Still have catchable fish, but don't spawn new ones // The ImprovedRhythmSpawner.update() call below will be skipped // since GameState.fishingEnding is true } ``` Also, update the `ImprovedRhythmSpawner.update()` method to respect the ending state: ```javascript var ImprovedRhythmSpawner = { update: function (currentTime) { if (!GameState.gameActive || GameState.songStartTime === 0 || GameState.fishingEnding) { return; } // Only spawn if no battle is active and no fish are on screen if (GameState.battleState !== BATTLE_STATES.NONE || fishArray.length > 0) { return; }
User prompt
Update with: function endFishingSession() { GameState.gameActive = false; GameState.tutorialMode = false; // Check if there are any catchable fish still on screen var catchableFishRemaining = fishArray.filter(function (fish) { return !fish.caught && !fish.missed && !fish.destroyed; }); if (catchableFishRemaining.length > 0) { // Set a flag to indicate we're waiting for fish to be caught GameState.fishingEnding = true; // Continue game loop to allow catching remaining fish GameState.gameActive = true; // Don't stop music yet - let it continue playing return; } // No catchable fish remaining, proceed with normal cleanup performFinalCleanup(); } function performFinalCleanup() { // Stop music and animations stopTweens([fishingElements.boat, fishingElements.fishermanContainer, fishingElements.fisherman]); if (fishingElements && fishingElements.waterSurfaceSegments) { stopTweens(fishingElements.waterSurfaceSegments); } LK.stopMusic(); ImprovedRhythmSpawner.reset(); // Clean up lane brackets if (laneBrackets && laneBrackets.length > 0) { laneBrackets.forEach(function (bracketPair) { if (bracketPair.left && !bracketPair.left.destroyed) { bracketPair.left.destroy(); } if (bracketPair.right && !bracketPair.right.destroyed) { bracketPair.right.destroy(); } }); laneBrackets = []; } // Clean up fish fishArray.forEach(function (fish) { fish.destroy(); }); fishArray = []; // Clean up particles GameState.musicNotesActive = false; if (fishingElements && fishingElements.musicNotesContainer) { fishingElements.musicNotesContainer.removeChildren(); } musicNotesArray = []; cleanupParticleArray(globalOceanBubblesArray, globalOceanBubbleContainer); cleanupParticleArray(globalSeaweedArray, globalSeaweedContainer); cleanupParticleArray(globalCloudArray, globalCloudContainer); createResultsScreen(); _showScreen('results'); } ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Make sure the sandwich board icon container is on a layer above the sandwich board.
User prompt
Show the asset for the selected dish on the menu overlay on the sandwich board.
User prompt
Tapping on the sandwich board brings up a “Menu” overlay where the player can choose what dish will be made in the cooking screen. “Fish and Chips” is the default and only choice to start.
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var BubbleParticle = Container.expand(function (startX, startY) { var self = Container.call(this); self.gfx = self.attachAsset('bubbles', { anchorX: 0.5, anchorY: 0.5, alpha: 1 + Math.random() * 0.2, scaleX: 1.2 + Math.random() * 0.25, scaleY: this.scaleX }); self.x = startX; self.y = startY; self.vx = (Math.random() - 0.5) * 0.4; self.vy = -(0.4 + Math.random() * 0.3); self.life = 120 + Math.random() * 60; self.age = 0; self.isDone = false; var initialAlpha = self.gfx.alpha; var initialScale = self.gfx.scaleX; self.update = function () { if (self.isDone) { return; } self.age++; self.x += self.vx; self.y += self.vy; self.gfx.alpha = Math.max(0, initialAlpha * (1 - self.age / self.life)); var scaleFactor = 1 - self.age / self.life; self.gfx.scaleX = initialScale * scaleFactor; self.gfx.scaleY = initialScale * scaleFactor; if (self.age >= self.life || self.gfx.alpha <= 0 || self.gfx.scaleX <= 0.01) { self.isDone = true; } }; return self; }); var CloudParticle = Container.expand(function () { var self = Container.call(this); self.gfx = self.attachAsset('cloud', { anchorX: 0.5, anchorY: 0.5 }); var spawnOnScreen = Math.random() < 0.3; if (spawnOnScreen) { self.x = 200 + Math.random() * 1648; self.vx = (Math.random() < 0.5 ? 1 : -1) * (0.1 + Math.random() * 0.2); } else { var spawnFromLeft = Math.random() < 0.5; if (spawnFromLeft) { self.x = -100; self.vx = 0.1 + Math.random() * 0.2; } else { self.x = 2048 + 100; self.vx = -(0.1 + Math.random() * 0.2); } } var skyTop = -500; var skyBottom = GAME_CONFIG.WATER_SURFACE_Y - 100; self.y = skyTop + Math.random() * (skyBottom - skyTop); var baseScale = 0.8 + Math.random() * 0.6; self.gfx.scale.set(baseScale); self.vy = (Math.random() - 0.5) * 0.02; var targetAlpha = 0.4 + Math.random() * 0.3; self.gfx.alpha = 0; self.isDone = false; self.fadingOut = false; self.hasFadedIn = false; var fadeInStartX = 400; var fadeInEndX = 800; var fadeOutStartX = 1248; var fadeOutEndX = 1648; self.update = function () { if (self.isDone) { return; } self.x += self.vx; self.y += self.vy; var currentAlpha = self.gfx.alpha; if (spawnFromLeft) { if (!self.hasFadedIn && self.x >= fadeInStartX && self.x <= fadeInEndX) { var fadeInProgress = (self.x - fadeInStartX) / (fadeInEndX - fadeInStartX); self.gfx.alpha = targetAlpha * fadeInProgress; if (fadeInProgress >= 1) { self.hasFadedIn = true; } } else if (self.hasFadedIn && self.x >= fadeOutStartX && self.x <= fadeOutEndX) { var fadeOutProgress = (self.x - fadeOutStartX) / (fadeOutEndX - fadeOutStartX); self.gfx.alpha = targetAlpha * (1 - fadeOutProgress); } else if (self.hasFadedIn && self.x > fadeInEndX && self.x < fadeOutStartX) { self.gfx.alpha = targetAlpha; } } else { if (!self.hasFadedIn && self.x <= fadeOutEndX && self.x >= fadeOutStartX) { var fadeInProgress = (fadeOutEndX - self.x) / (fadeOutEndX - fadeOutStartX); self.gfx.alpha = targetAlpha * fadeInProgress; if (fadeInProgress >= 1) { self.hasFadedIn = true; } } else if (self.hasFadedIn && self.x <= fadeInEndX && self.x >= fadeInStartX) { var fadeOutProgress = (fadeInEndX - self.x) / (fadeInEndX - fadeInStartX); self.gfx.alpha = targetAlpha * (1 - fadeOutProgress); } else if (self.hasFadedIn && self.x < fadeOutStartX && self.x > fadeInEndX) { self.gfx.alpha = targetAlpha; } } var currentWidth = self.gfx.width * self.gfx.scale.x; if (self.x < -currentWidth || self.x > 2048 + currentWidth) { self.isDone = true; } }; return self; }); // Courier class var Courier = Container.expand(function (fishType, value, speed, lane) { var self = Container.call(this); self.courierGraphics = self.attachAsset('courier', { anchorX: 0.5, anchorY: 0.5 }); if (speed > 0) { // Assuming positive speed means moving right, so flip if moving left-to-right from player's perspective. // The original code has this: if (speed > 0) { self.courierGraphics.scaleX = -1; // Flip for right-to-left movement } // This seems to imply courier asset faces right by default. // If courier moves right (positive speed), and it's coming from left, it should face right (no flip). // If courier moves left (negative speed), and it's coming from right, it should face left (flip). // Let's stick to the prompt's logic: flip if speed > 0 (meaning it's moving from left to right, and should be flipped if it's default facing left) // OR the prompt's comment is "Flip for right-to-left movement" which means if speed < 0 (moving left), it should be flipped. // The prompt code: if (speed > 0) { self.courierGraphics.scaleX = -1; // Flip for right-to-left movement } // This indicates if speed is positive (moving right), it's considered right-to-left *from courier's perspective* if asset faces left. // Let's assume the asset faces right by default. Then for right-to-left movement (speed < 0), we need to flip it. // Let's reinterpret the prompt: "if speed > 0 then flip for right-to-left movement" means "if courier travels rightwards on screen, flip it to appear as if it's coming towards center from right and then moving left" // This is confusing. Standard: asset faces right. If moving left (speed < 0), scaleX = -1. If moving right (speed > 0), scaleX = 1. // The prompt says: "if (speed > 0) { self.courierGraphics.scaleX = -1; // Flip for right-to-left movement }" // This means: if speed is positive (e.g., courier moving from x=0 to x=2048), flip it. // This implies the courier asset itself is facing left. So if it moves right, we flip it to face right. // Let's follow the prompt's code. self.courierGraphics.scaleX = -1; } self.wantedFish = fishType; self.value = value; self.speed = speed; self.lane = lane; self.caught = false; self.missed = false; self.served = false; // Add this new property self.lastX = 0; // Speech bubble showing wanted fish self.speechBubble = self.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -120, // Position above courier width: 170, height: 130, alpha: 0.8 })); self.fishIcon = self.attachAsset(fishType, { // Corrected: removed self.addChild anchorX: 0.5, anchorY: 0.5, x: 0, // Centered with speech bubble y: -120, // Centered with speech bubble scaleX: 0.8, scaleY: 0.8 }); self.update = function () { // Continue moving regardless of interaction status self.x += self.speed; }; return self; }); var CrabParticle = Container.expand(function () { var self = Container.call(this); self.isDone = false; var spawnFromLeft = Math.random() < 0.5; var baseScale = 0.8 + Math.random() * 0.3; self.gfx = self.attachAsset('crab', { anchorX: 0.5, anchorY: 1.0, scaleX: spawnFromLeft ? baseScale : -baseScale, scaleY: baseScale }); var offscreenBuffer = self.gfx.width * baseScale + 20; if (spawnFromLeft) { self.x = -offscreenBuffer; self.speed = 1.0 + Math.random() * 0.8; } else { self.x = 2048 + offscreenBuffer; self.speed = -(1.0 + Math.random() * 0.8); } self.y = 1366 + Math.random() * (2732 - 1366); self.baseY = self.y; self.skitterTime = Math.random() * Math.PI * 2; self.skitterSpeed = 0.15 + Math.random() * 0.1; self.skitterAmplitude = 3 + Math.random() * 3; self.isPaused = false; self.pauseTimer = 0; self.timeUntilNextPause = 300 + Math.random() * 420; // 5-12 seconds self.update = function () { if (self.isDone) { return; } self.timeUntilNextPause--; if (self.timeUntilNextPause <= 0) { if (!self.isPaused) { self.isPaused = true; self.pauseTimer = 45 + Math.random() * 90; // pause for 0.75 to 2.25 seconds } } if (self.isPaused) { self.pauseTimer--; if (self.pauseTimer <= 0) { self.isPaused = false; self.timeUntilNextPause = 300 + Math.random() * 420; // schedule next pause } } else { self.x += self.speed; self.skitterTime += self.skitterSpeed; self.y = self.baseY + Math.sin(self.skitterTime) * self.skitterAmplitude; self.gfx.rotation = Math.sin(self.skitterTime * 0.5) * 0.08; } if (self.speed > 0 && self.x > 2048 + offscreenBuffer || self.speed < 0 && self.x < -offscreenBuffer) { self.isDone = true; } }; return self; }); var FeedbackIndicator = Container.expand(function (type) { var self = Container.call(this); var indicator = self.attachAsset(type + 'Indicator', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); self.show = function () { indicator.alpha = 1; indicator.scaleX = 0.5; indicator.scaleY = 0.5; tween(indicator, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 1400, easing: tween.easeOut }); }; return self; }); var Fish = Container.expand(function (type, value, speed, lane, assetNameOverride) { var self = Container.call(this); self.isSardinePartner = !!assetNameOverride; self.determineFirstPlannedLane = function () { // Based on initial self.lane (the lane the fish spawns in) if (self.lane === 1) { return 0; } // From middle, prefer to go to top lane (0) if (self.lane === 0) { return 1; } // From top lane (0), must go to middle lane (1) if (self.lane === 2) { return 1; } // From bottom lane (2), must go to middle lane (1) return self.lane; // Fallback, should not be reached with 3 lanes }; self.determineSubsequentPlannedLane = function (currentLane, laneJustMovedFrom) { // currentLane is where the fish IS after the current pushback // laneJustMovedFrom is where it WAS before the current pushback if (currentLane === 0) { return 1; } // Is at top (0), next must be middle (1) if (currentLane === 2) { return 1; } // Is at bottom (2), next must be middle (1) if (currentLane === 1) { // Is in middle lane (1) if (laneJustMovedFrom === 0) { return 2; } // Came from top (0), next must be bottom (2) if (laneJustMovedFrom === 2) { return 0; } // Came from bottom (2), next must be top (0) } // This fallback should ideally not be reached if fish always changes lane // and the game has 3 lanes. return currentLane; }; // Existing asset selection logic var assetName = assetNameOverride || type + 'Fish'; if (!assetNameOverride && type === 'shallow') { var currentSongConfig = null; if (typeof GameState !== 'undefined' && GameState && typeof GameState.getCurrentSongConfig === 'function') { currentSongConfig = GameState.getCurrentSongConfig(); } if (currentSongConfig && currentSongConfig.name === "Gentle Waves") { var randGentle = Math.random(); if (randGentle < 0.60) { assetName = 'anchovy'; } else if (randGentle < 0.90) { assetName = 'sardine'; } else { assetName = 'mackerel'; } } else if (currentSongConfig && currentSongConfig.name === "Morning Tide") { var randMorning = Math.random(); if (randMorning < 0.40) { assetName = 'anchovy'; } else if (randMorning < 0.80) { assetName = 'sardine'; } else { assetName = 'mackerel'; } } else if (currentSongConfig && currentSongConfig.name === "Sunny Afternoon") { var randSunny = Math.random(); if (randSunny < 0.30) { assetName = 'anchovy'; } else if (randSunny < 0.60) { assetName = 'sardine'; } else { assetName = 'mackerel'; } } else { var shallowFishAssets = ['sardine', 'anchovy', 'mackerel']; assetName = shallowFishAssets[Math.floor(Math.random() * shallowFishAssets.length)]; } } else if (!assetNameOverride && type === 'medium') { var currentSongConfig = null; if (typeof GameState !== 'undefined' && GameState && typeof GameState.getCurrentSongConfig === 'function') { currentSongConfig = GameState.getCurrentSongConfig(); } if (currentSongConfig && currentSongConfig.name === "Ocean Current") { var randOcean = Math.random(); if (randOcean < 0.60) { assetName = 'cod'; } else if (randOcean < 0.90) { assetName = 'snapper'; } else { assetName = 'seaBass'; } } else if (currentSongConfig && currentSongConfig.name === "Deep Flow") { var randDeep = Math.random(); if (randDeep < 0.40) { assetName = 'cod'; } else if (randDeep < 0.80) { assetName = 'snapper'; } else { assetName = 'seaBass'; } } else if (currentSongConfig && currentSongConfig.name === "Coral Groove") { var randCoral = Math.random(); if (randCoral < 0.30) { assetName = 'cod'; } else if (randCoral < 0.60) { assetName = 'snapper'; } else { assetName = 'seaBass'; } } else { var midWaterFishAssets = ['cod', 'snapper', 'seaBass']; assetName = midWaterFishAssets[Math.floor(Math.random() * midWaterFishAssets.length)]; } } self.assetName = assetName; self.fishGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); if (speed > 0) { self.fishGraphics.scaleX = -1; } // Existing properties self.type = type; self.value = value; self.speed = speed; self.lane = lane; // This is the initial lane self.caught = false; self.missed = false; self.lastX = 0; self.isSpecial = type === 'rare'; if (self.isSpecial) { self.sparkles = []; self.lastSparkleSpawnTime = 0; self.sparkleSpawnInterval = 4; // Reduce sparkle spawn rate } self.lastBubbleSpawnTime = 0; self.bubbleSpawnInterval = 120 + Math.random() * 80; self.swimTime = Math.random() * Math.PI * 2; self.baseY = self.y; self.scaleTime = 0; self.baseScale = 1; // Tutorial interaction flags self.wasCaughtThisInteraction = false; self.wasMissedThisInteraction = false; self.tutorialFish = false; // Will be set true by spawnTutorialFishHelper if applicable // NEW: Multi-tap properties var fishTypeName = assetName; // Use the determined assetName for rhythm patterns self.rhythmPattern = FISH_RHYTHM_PATTERNS[fishTypeName] || ['beat']; // fallback to single tap self.maxTaps = self.rhythmPattern.length; self.currentTaps = 0; self.isMultiTapFish = self.maxTaps > 1; self.currentPatternIndex = 0; self.isPushedBack = false; self.originalSpeed = speed; self.isInBattle = false; self.nextPlannedMoveLane = -1; // Initialize planned move lane // Create tap counter display for multi-tap fish if (self.isMultiTapFish) { self.nextPlannedMoveLane = self.determineFirstPlannedLane(); // Determine the very first planned move self.counterBg = self.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -80, // Position above the fish width: 120, height: 80, alpha: 0.8 })); var initialCounterText = self.maxTaps.toString(); // Arrow for the FIRST planned move (if more than one tap total) if (self.maxTaps > 1 && self.nextPlannedMoveLane !== -1 && self.nextPlannedMoveLane !== self.lane) { if (self.nextPlannedMoveLane < self.lane) { initialCounterText += "↑"; // Arrow indicates upward movement } else if (self.nextPlannedMoveLane > self.lane) { initialCounterText += "↓"; // Arrow indicates downward movement } } self.tapCounter = self.addChild(new Text2(initialCounterText, { size: 70, fill: 0xFFFFFF, // White text stroke: 0x000000, // Black stroke for visibility strokeThickness: 3 })); self.tapCounter.anchor.set(0.5, 0.5); self.tapCounter.x = 0; self.tapCounter.y = -80; // Align with background // Arrow graphics removed, will use text emojis in tapCounter } self.cleanupSparkles = function () { if (!self.isSpecial || !self.sparkles) { return; } for (var i = self.sparkles.length - 1; i >= 0; i--) { var sparkle = self.sparkles[i]; if (sparkle && !sparkle.destroyed) { sparkle.destroy(); } } self.sparkles = []; }; self.update = function () { if (!self.caught && !self.isPushedBack) { // Check isPushedBack self.x += self.speed; self.swimTime += 0.08; var swimAmplitude = 15; self.y = self.baseY + Math.sin(self.swimTime) * swimAmplitude; if (GameState.gameActive && GameState.songStartTime > 0) { var currentTime = LK.ticks * (1000 / 60); var songConfig = GameState.getCurrentSongConfig(); if (songConfig && songConfig.bpm) { // Ensure songConfig and bpm are valid var beatInterval = 60000 / songConfig.bpm; var timeSinceLastBeat = (currentTime - GameState.songStartTime) % beatInterval; var beatProgress = timeSinceLastBeat / beatInterval; var scalePulse = 1 + Math.sin(beatProgress * Math.PI) * 0.15; var baseScaleXDirection = (self.speed > 0 ? -1 : 1) * self.baseScale; self.fishGraphics.scaleX = baseScaleXDirection * scalePulse; self.fishGraphics.scaleY = scalePulse * self.baseScale; } } if (self.isSpecial) { self.lastSparkleSpawnTime++; if (self.lastSparkleSpawnTime > self.sparkleSpawnInterval) { self.lastSparkleSpawnTime = 0; var numSparkles = 2 + Math.floor(Math.random() * 2); // Spawn 2-3 sparkles at a time for (var n = 0; n < numSparkles; n++) { // Spread sparkles over the entire body of the fish var spawnOffsetX = (Math.random() - 0.5) * self.fishGraphics.width * 0.9; var spawnOffsetY = (Math.random() - 0.5) * self.fishGraphics.height * 0.9; var spawnX = self.x + spawnOffsetX; var spawnY = self.y + spawnOffsetY; var newSparkle = new SparkleParticle(spawnX, spawnY); // Add to the dedicated effects layer so they appear on top if (globalEffectsLayer && !globalEffectsLayer.destroyed) { globalEffectsLayer.addChild(newSparkle); self.sparkles.push(newSparkle); } } } // The Fish object manages its own spawned sparkles for (var i = self.sparkles.length - 1; i >= 0; i--) { var sparkle = self.sparkles[i]; sparkle.update(); if (sparkle.isDone) { if (sparkle && !sparkle.destroyed) { sparkle.destroy(); } self.sparkles.splice(i, 1); } } } else { self.fishGraphics.alpha = 1.0; } } }; self.handleTap = function () { if (!self.isMultiTapFish) { // Regular single-tap fish return true; } self.currentTaps++; var remainingTaps = self.maxTaps - self.currentTaps; if (self.tapCounter) { self.tapCounter.setText(remainingTaps.toString()); } if (remainingTaps <= 0) { // Fish is fully caught if (self.counterBg && !self.counterBg.destroyed) { self.counterBg.destroy(); } if (self.tapCounter && !self.tapCounter.destroyed) { self.tapCounter.destroy(); } return true; } // Push fish back and continue battle self.pushBack(); return false; }; self.pushBack = function () { self.isPushedBack = true; // The fish will move to its currently planned lane. var laneToMoveToForThisPushback = self.nextPlannedMoveLane; var laneFishWasInBeforeThisMove = self.lane; // Store where the fish was. // Update fish's internal lane state FOR THIS MOVEMENT // If nextPlannedMoveLane is somehow invalid (-1), keep current lane. if (laneToMoveToForThisPushback !== -1) { self.lane = laneToMoveToForThisPushback; } // else: fish stays in its current lane if no valid planned move (shouldn't happen with current logic) var targetY = GAME_CONFIG.LANES[self.lane].y; // Target Y for the tween self.baseY = targetY; // Update base Y for swim animation once it settles // Determine the NEXT planned move for the SUBSEQUENT pushback (if any). // This will be used for updating the arrow on the counter. var tapsRemainingIncludingThisOne = self.maxTaps - self.currentTaps; if (tapsRemainingIncludingThisOne > 1) { // If there will be at least one more pushback *after* this current one completes self.nextPlannedMoveLane = self.determineSubsequentPlannedLane(self.lane, laneFishWasInBeforeThisMove); } else { self.nextPlannedMoveLane = -1; // No more moves planned after this current one } if (self.isMultiTapFish && self.tapCounter && !self.tapCounter.destroyed) { var tapsStillNeededForDisplay = self.maxTaps - self.currentTaps; // Taps needed for counter (current one is still active) var counterText = tapsStillNeededForDisplay.toString(); // Arrow logic: Show arrow if more taps are needed *after* this current pushback resolves // (i.e., tapsStillNeededForDisplay > 1) AND a next move is planned. if (tapsStillNeededForDisplay > 1 && self.nextPlannedMoveLane !== -1 && self.nextPlannedMoveLane !== self.lane) { // Arrow points from the fish's NEW current lane (self.lane) to the NEXT planned move (self.nextPlannedMoveLane) if (self.nextPlannedMoveLane < self.lane) { counterText += "↑"; } else if (self.nextPlannedMoveLane > self.lane) { counterText += "↓"; } } self.tapCounter.setText(counterText); } // End of tapCounter update // targetY is already set based on the new self.lane self.baseY = targetY; // Calculate timing for next approach self.currentPatternIndex++; var songConfig = GameState.getCurrentSongConfig(); var beatInterval = 60000 / songConfig.bpm; var timeToNextTap = beatInterval; // Always full beat // Use original fish speed (keep it steady) var fishSpeed = Math.abs(self.originalSpeed); var framesForNextApproach = timeToNextTap / (1000 / 60); // Calculate how far back we need to push based on selected difficulty. var pushbackBeatMultiplier = GAME_DIFFICULTY.pushbackMultiplier[GAME_DIFFICULTY.current] || 3; // Default to easy (3) if not found var distanceNeededToReachHook = fishSpeed * framesForNextApproach * pushbackBeatMultiplier; var pushBackX; if (self.originalSpeed > 0) { // Fish moves right, push it left of hook pushBackX = GAME_CONFIG.SCREEN_CENTER_X - distanceNeededToReachHook; } else { // Fish moves left, push it right of hook pushBackX = GAME_CONFIG.SCREEN_CENTER_X + distanceNeededToReachHook; } // One smooth motion: push back while moving to new lane tween(self, { x: pushBackX, y: targetY }, { duration: 200, // Quick pushback easing: tween.easeOut, onFinish: function onFinish() { // Resume original steady speed self.speed = self.originalSpeed; self.isPushedBack = false; } }); }; self.startBattle = function () { if (!self.isInBattle) { self.isInBattle = true; GameState.battleState = BATTLE_STATES.ACTIVE; GameState.currentBattleFish = self; } }; self.endBattle = function () { self.isInBattle = false; GameState.battleState = BATTLE_STATES.NONE; GameState.currentBattleFish = null; // Schedule next fish ImprovedRhythmSpawner.scheduleNextFish(); }; self.missedTap = function () { // Fish escapes due to miss self.missed = true; self.endBattle(false); // wasSuccessful is false // Remove fish from array var fishIndex = fishArray.indexOf(self); if (fishIndex > -1) { fishArray.splice(fishIndex, 1); } // Animate fish swimming away var escapeSpeed = Math.abs(self.originalSpeed) * 2; if (self.x > GAME_CONFIG.SCREEN_CENTER_X) { escapeSpeed = escapeSpeed; // swim right } else { escapeSpeed = -escapeSpeed; // swim left } self.speed = escapeSpeed; // Hide tap counter if it exists and fish is missed if (self.isMultiTapFish) { if (self.counterBg && !self.counterBg.destroyed) { self.counterBg.destroy(); } if (self.tapCounter && !self.tapCounter.destroyed) { self.tapCounter.destroy(); } // Arrow graphics destruction removed } LK.setTimeout(function () { if (!self.destroyed) { if (typeof self.cleanupSparkles === 'function') { self.cleanupSparkles(); } self.destroy(); } }, 2000); // Give 2 seconds for fish to swim off screen }; self.catchFish = function () { self.caught = true; if (self.isMultiTapFish && self.counterBg && !self.counterBg.destroyed) { self.counterBg.destroy(); } if (self.isMultiTapFish && self.tapCounter && !self.tapCounter.destroyed) { self.tapCounter.destroy(); } // Arrow graphics destruction removed if (self.isMultiTapFish) { // No graphics to destroy here anymore } if (self.isInBattle) { self.endBattle(true); // wasSuccessful is true } else { // Single-tap fish - still schedule next ImprovedRhythmSpawner.scheduleNextFish(); } var currentFishX = self.x; var currentFishY = self.y; var boatCenterX = GAME_CONFIG.SCREEN_CENTER_X; var boatLandingY = GAME_CONFIG.BOAT_Y; var peakArcY = boatLandingY - 150; var peakArcX = currentFishX + (boatCenterX - currentFishX) * 0.5; var durationPhase1 = 350; var durationPhase2 = 250; tween(self, { x: peakArcX, y: peakArcY, scaleX: 0.75, scaleY: 0.75, alpha: 0.8 }, { duration: durationPhase1, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { x: boatCenterX, y: boatLandingY, scaleX: 0.2, scaleY: 0.2, alpha: 0 }, { duration: durationPhase2, easing: tween.easeIn, onFinish: function onFinish() { if (self && !self.destroyed) { if (typeof self.cleanupSparkles === 'function') { self.cleanupSparkles(); } self.destroy(); } } }); } }); }; self.getInventoryFishType = function () { if (FishInventory.fishTypes.indexOf(self.assetName) > -1) { return self.assetName; } return null; }; return self; }); var MapBubbleParticle = Container.expand(function (startX, startY) { var self = Container.call(this); var initialScale = 0.1 + Math.random() * 0.1; var targetScale = 0.4 + Math.random() * 0.3; self.gfx = self.attachAsset('bubbles', { anchorX: 0.5, anchorY: 0.5, alpha: 0, scaleX: initialScale, scaleY: initialScale }); self.x = startX + (Math.random() - 0.5) * 100; self.y = startY; var targetAlpha = 0.3 + Math.random() * 0.3; var riseDurationMs = 3000 + Math.random() * 2000; var riseDistance = 300 + Math.random() * 200; var driftDistance = (Math.random() - 0.5) * 100; var fadeInDurationMs = 600 + Math.random() * 400; var totalDurationMs = riseDurationMs; var fadeOutStartTimeMs = totalDurationMs * 0.6; var fadeOutDurationMs = totalDurationMs - fadeOutStartTimeMs; self.isDone = false; tween(self.gfx, { alpha: targetAlpha, scaleX: targetScale, scaleY: targetScale }, { duration: fadeInDurationMs, easing: tween.easeOut }); tween(self, { y: self.y - riseDistance, x: self.x + driftDistance }, { duration: totalDurationMs, easing: tween.linear }); LK.setTimeout(function () { if (self.isDone || !self.gfx || self.gfx.destroyed) { return; } tween(self.gfx, { alpha: 0, scaleX: initialScale * 0.5, scaleY: initialScale * 0.5 }, { duration: fadeOutDurationMs, easing: tween.easeIn, onFinish: function onFinish() { self.isDone = true; } }); }, fadeOutStartTimeMs); return self; }); var MapScreenCloudParticle = Container.expand(function () { var self = Container.call(this); self.gfx = self.attachAsset('mapCloud', { anchorX: 0.5, anchorY: 0.5 }); self.gfx.alpha = 0.4 + Math.random() * 0.3; var baseScale = 0.9 + Math.random() * 0.4; self.gfx.scale.set(baseScale); var spawnFromLeft = Math.random() < 0.5; var offscreenBuffer = self.gfx.width * baseScale + 50; var speedMultiplier = 1.15; if (spawnFromLeft) { self.x = -offscreenBuffer; self.vx = (0.15 + Math.random() * 0.2) * speedMultiplier; } else { self.x = 2048 + offscreenBuffer; self.vx = -(0.15 + Math.random() * 0.2) * speedMultiplier; } var screenHeight = 2732; var topMargin = screenHeight * 0.1; var spawnableHeight = screenHeight * 0.8; self.y = topMargin + Math.random() * spawnableHeight; self.isDone = false; self.update = function () { if (self.isDone) { return; } self.x += self.vx; if (self.vx > 0 && self.x > 2048 + offscreenBuffer) { self.isDone = true; } else if (self.vx < 0 && self.x < -offscreenBuffer) { self.isDone = true; } }; return self; }); var MusicNoteParticle = Container.expand(function (startX, startY) { var self = Container.call(this); var FADE_IN_DURATION_MS = 600; var TARGET_ALPHA = 0.6 + Math.random() * 0.4; self.gfx = self.attachAsset('musicnote', { anchorX: 0.5, anchorY: 0.5, alpha: 0, scaleX: 0.4 + Math.random() * 0.4 }); self.gfx.scaleY = self.gfx.scaleX; self.x = startX; self.y = startY; self.vx = (Math.random() - 0.5) * 0.8; self.vy = -(0.8 + Math.random() * 0.7); self.rotationSpeed = (Math.random() - 0.5) * 0.008; self.life = 240 + Math.random() * 120; self.age = 0; self.isDone = false; tween(self.gfx, { alpha: TARGET_ALPHA }, { duration: FADE_IN_DURATION_MS, easing: tween.easeOut }); self.update = function () { if (self.isDone) { return; } self.age++; self.x += self.vx; self.y += self.vy; self.gfx.rotation += self.rotationSpeed; var FADE_IN_TICKS = FADE_IN_DURATION_MS / (1000 / 60); if (self.age > FADE_IN_TICKS) { var lifePortionForFadeOut = 0.6; var fadeOutStartTimeTicks = self.life * (1 - lifePortionForFadeOut); if (self.age >= fadeOutStartTimeTicks && self.life > fadeOutStartTimeTicks) { var progressInFadeOut = (self.age - fadeOutStartTimeTicks) / (self.life * lifePortionForFadeOut); self.gfx.alpha = TARGET_ALPHA * (1 - progressInFadeOut); self.gfx.alpha = Math.max(0, self.gfx.alpha); } } if (self.age >= self.life || self.gfx.alpha !== undefined && self.gfx.alpha <= 0.01 && self.age > FADE_IN_TICKS) { self.isDone = true; } }; return self; }); var OceanBubbleParticle = Container.expand(function () { var self = Container.call(this); self.gfx = self.attachAsset('oceanbubbles', { anchorX: 0.5, anchorY: 0.5 }); self.initialX = Math.random() * 2048; var waterTop = GAME_CONFIG.WATER_SURFACE_Y; var waterBottom = 2732; self.x = self.initialX; self.y = waterTop + Math.random() * (waterBottom - waterTop); var baseScale = 0.1 + Math.random() * 0.4; self.gfx.scale.set(baseScale); self.vy = -(0.25 + Math.random() * 0.5); self.naturalVy = self.vy; self.driftAmplitude = 20 + Math.random() * 40; self.naturalDriftAmplitude = self.driftAmplitude; self.driftFrequency = (0.005 + Math.random() * 0.015) * (Math.random() < 0.5 ? 1 : -1); self.driftPhase = Math.random() * Math.PI * 2; self.rotationSpeed = (Math.random() - 0.5) * 0.01; var targetAlpha = 0.2 + Math.random() * 0.3; self.gfx.alpha = 0; self.isDone = false; self.fadingOut = false; tween(self.gfx, { alpha: targetAlpha }, { duration: 1000 + Math.random() * 1000, easing: tween.easeIn }); self.update = function () { if (self.isDone) { return; } self.y += self.vy; self.age++; if (!self.fadingOut && self.age >= self.lifespan) { self.fadingOut = true; tween.stop(self.gfx); tween(self.gfx, { alpha: 0 }, { duration: 600 + Math.random() * 400, easing: tween.easeOut, onFinish: function onFinish() { self.isDone = true; } }); } self.driftPhase += self.driftFrequency; self.x = self.initialX + Math.sin(self.driftPhase) * self.driftAmplitude; self.gfx.rotation += self.rotationSpeed; var naturalVy = -(0.25 + Math.random() * 0.5); var recoveryRate = 0.02; if (self.vy > naturalVy) { self.vy = self.vy + (naturalVy - self.vy) * recoveryRate; } var normalDriftAmplitude = 20 + Math.random() * 40; if (self.driftAmplitude > normalDriftAmplitude) { self.driftAmplitude = self.driftAmplitude + (normalDriftAmplitude - self.driftAmplitude) * recoveryRate; } var currentHeight = self.gfx.height * self.gfx.scale.y; var currentWidth = self.gfx.width * self.gfx.scale.x; if (!self.fadingOut && self.y <= GAME_CONFIG.WATER_SURFACE_Y - currentHeight * 0.5) { self.fadingOut = true; tween.stop(self.gfx); tween(self.gfx, { alpha: 0 }, { duration: 300 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { self.isDone = true; } }); } else if (!self.fadingOut && (self.y < -currentHeight || self.x < -currentWidth || self.x > 2048 + currentWidth)) { self.isDone = true; self.gfx.alpha = 0; } }; return self; }); // Rhythm item class var RhythmItem = Container.expand(function (fishType, lane, processedType, timingMultiplier) { var self = Container.call(this); self.fishType = fishType; self.lane = lane; self.processedType = processedType || 'raw'; self.timingMultiplier = timingMultiplier || 1.0; self.targetY = COOKING_CONFIG.STATION_Y; self.speed = 0; self.targetBeat = 0; self.isSpecial = fishType === 'rareFish'; if (self.isSpecial) { self.sparkles = []; self.lastSparkleSpawnTime = 0; self.sparkleSpawnInterval = 5; } // Visual representation based on processed type var assetToUse = ''; var scaleX = 1.6; var scaleY = 1.6; switch (self.processedType) { case 'raw': assetToUse = fishType; break; case 'steak': assetToUse = 'fishSteak'; scaleX = 1.4; scaleY = 1.4; break; case 'battered': assetToUse = 'batteredFish'; scaleX = 1.4; scaleY = 1.4; break; case 'ground': assetToUse = 'groundFish'; scaleX = 1.4; scaleY = 1.4; break; case 'batteredGround': assetToUse = 'batteredGroundFish'; scaleX = 1.4; scaleY = 1.4; break; case 'spiced': assetToUse = 'spicedFish'; scaleX = 1.4; scaleY = 1.4; break; case 'spicedGround': assetToUse = 'spicedGroundFish'; scaleX = 1.4; scaleY = 1.4; break; default: assetToUse = fishType; break; } if (assetToUse) { self.sprite = self.attachAsset(assetToUse, { anchorX: 0.5, anchorY: 0.5, scaleX: scaleX, scaleY: scaleY }); } // Position in lane self.x = COOKING_CONFIG.LANE_START_X + lane * COOKING_CONFIG.LANE_WIDTH + COOKING_CONFIG.LANE_WIDTH / 2; self.y = COOKING_CONFIG.FALL_START_Y; self.cleanupSparkles = function () { if (!self.isSpecial || !self.sparkles) { return; } for (var i = self.sparkles.length - 1; i >= 0; i--) { var sparkle = self.sparkles[i]; if (sparkle && !sparkle.destroyed) { sparkle.destroy(); } } self.sparkles = []; }; self.update = function () { self.y += self.speed; if (self.isSpecial) { self.lastSparkleSpawnTime++; if (self.lastSparkleSpawnTime > self.sparkleSpawnInterval) { self.lastSparkleSpawnTime = 0; var spawnOffsetX = (Math.random() - 0.5) * self.sprite.width * 0.9; var spawnOffsetY = (Math.random() - 0.5) * self.sprite.height * 0.9; var newSparkle = new SparkleParticle(self.x + spawnOffsetX, self.y + spawnOffsetY); if (globalEffectsLayer && !globalEffectsLayer.destroyed) { globalEffectsLayer.addChild(newSparkle); self.sparkles.push(newSparkle); } } for (var i = self.sparkles.length - 1; i >= 0; i--) { var sparkle = self.sparkles[i]; sparkle.update(); if (sparkle.isDone) { if (sparkle && !sparkle.destroyed) { sparkle.destroy(); } self.sparkles.splice(i, 1); } } } }; self.getDistance = function () { return Math.abs(self.y - self.targetY); }; self.fadeAway = function () { if (self.isSpecial) { self.cleanupSparkles(); } // Quick fade animation for missed items tween(self, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (self && !self.destroyed) { self.destroy(); } } }); }; return self; }); var RippleParticle = Container.expand(function (spawnCenterX, spawnCenterY, spawnAngle, initialOffset, travelDistance, initialScale, finalScale, durationMs, speedFactor) { var self = Container.call(this); self.isDone = false; var effectiveDurationMs = durationMs; if (speedFactor !== undefined && speedFactor > 0 && speedFactor !== 1.0) { effectiveDurationMs = durationMs / speedFactor; } var rippleGfx = self.attachAsset('waveline', { anchorX: 0.5, anchorY: 0.5, scaleX: initialScale, scaleY: initialScale, alpha: 1.0, rotation: spawnAngle + Math.PI / 2 }); self.x = spawnCenterX + initialOffset * Math.cos(spawnAngle); self.y = spawnCenterY + initialOffset * Math.sin(spawnAngle); var targetX = spawnCenterX + (initialOffset + travelDistance) * Math.cos(spawnAngle); var targetY = spawnCenterY + (initialOffset + travelDistance) * Math.sin(spawnAngle); tween(self, { x: targetX, y: targetY, alpha: 0 }, { duration: effectiveDurationMs, easing: tween.linear, onFinish: function onFinish() { self.isDone = true; } }); tween(rippleGfx.scale, { x: finalScale, y: finalScale }, { duration: effectiveDurationMs, easing: tween.easeOut }); return self; }); var SeagullParticle = Container.expand(function () { var self = Container.call(this); self.isDone = false; var flyFromLeft = Math.random() < 0.5; var assetBaseScale = 0.6 + Math.random() * 0.4; self.gfx = self.attachAsset('seagull', { anchorX: 0.5, anchorY: 0.5, scaleX: flyFromLeft ? -assetBaseScale : assetBaseScale, scaleY: assetBaseScale }); var flightDuration = 7000 + Math.random() * 5000; var startX, endX; var startY = 250 + Math.random() * 750; var endY = 250 + Math.random() * 750; var offscreenBuffer = self.gfx.width * assetBaseScale + 50; if (flyFromLeft) { startX = -offscreenBuffer; endX = 2048 + offscreenBuffer; } else { startX = 2048 + offscreenBuffer; endX = -offscreenBuffer; } self.x = startX; self.y = startY; self.lastX = startX; self.lastY = startY; var arcHeight = 150 + Math.random() * 250; var arcUpwards = Math.random() < 0.5; var peakY; if (arcUpwards) { peakY = Math.min(startY, endY) - arcHeight; } else { peakY = Math.max(startY, endY) + arcHeight; } var targetScaleMagnitude = assetBaseScale + (Math.random() - 0.5) * 0.5; targetScaleMagnitude = Math.max(0.4, Math.min(1.2, targetScaleMagnitude)); var finalScaleX = flyFromLeft ? -targetScaleMagnitude : targetScaleMagnitude; var finalScaleY = targetScaleMagnitude; tween(self.gfx.scale, { x: finalScaleX, y: finalScaleY }, { duration: flightDuration, easing: tween.linear }); tween(self, { x: endX }, { duration: flightDuration, easing: tween.linear, onFinish: function onFinish() { self.isDone = true; } }); tween(self, { y: peakY }, { duration: flightDuration / 2, easing: tween.easeOut, onFinish: function onFinish() { if (self.isDone || !self.gfx || self.gfx.destroyed) { return; } tween(self, { y: endY }, { duration: flightDuration / 2, easing: tween.easeIn }); } }); self.update = function () { if (self.isDone || !self.gfx || self.gfx.destroyed) { return; } var dx = self.x - self.lastX; var dy = self.y - self.lastY; if (dx !== 0 || dy !== 0) { var angle = Math.atan2(dy, dx); if (self.gfx.scale.x > 0) { self.gfx.rotation = angle - Math.PI; } else { self.gfx.rotation = angle; } } self.lastX = self.x; self.lastY = self.y; }; return self; }); var SeaweedParticle = Container.expand(function () { var self = Container.call(this); self.gfx = self.attachAsset('kelp', { anchorX: 0.5, anchorY: 0.5 }); var spawnType = Math.random(); var waterTop = GAME_CONFIG.WATER_SURFACE_Y; var waterBottom = 2732; if (spawnType < 0.4) { self.x = Math.random() * 2048; self.y = waterBottom + 50; self.vx = (Math.random() - 0.5) * 0.3; self.vy = -(0.4 + Math.random() * 0.3); } else if (spawnType < 0.7) { self.x = -50; self.y = waterTop + Math.random() * (waterBottom - waterTop); self.vx = 0.4 + Math.random() * 0.3; self.vy = -(0.1 + Math.random() * 0.2); } else { self.x = 2048 + 50; self.y = waterTop + Math.random() * (waterBottom - waterTop); self.vx = -(0.4 + Math.random() * 0.3); self.vy = -(0.1 + Math.random() * 0.2); } self.initialX = self.x; self.naturalVx = self.vx; self.naturalVy = self.vy; var baseScale = 0.6 + Math.random() * 0.6; self.gfx.scale.set(baseScale); self.swayAmplitude = 15 + Math.random() * 25; self.swayFrequency = (0.003 + Math.random() * 0.007) * (Math.random() < 0.5 ? 1 : -1); self.swayPhase = Math.random() * Math.PI * 2; self.gfx.rotation = Math.random() * Math.PI * 2; self.continuousRotationSpeed = (Math.random() - 0.5) * 0.003; var targetAlpha = 0.3 + Math.random() * 0.3; self.gfx.alpha = 0; self.isDone = false; self.fadingOut = false; self.reachedSurface = false; self.lifespan = 600 + Math.random() * 1200; self.age = 0; tween(self.gfx, { alpha: targetAlpha }, { duration: 1500 + Math.random() * 1000, easing: tween.easeIn }); self.update = function () { if (self.isDone) { return; } self.x += self.vx; self.y += self.vy; self.swayPhase += self.swayFrequency; var swayOffset = Math.sin(self.swayPhase) * self.swayAmplitude; self.gfx.rotation += self.continuousRotationSpeed + swayOffset * 0.0001; var recoveryRate = 0.015; if (self.vx !== self.naturalVx) { self.vx = self.vx + (self.naturalVx - self.vx) * recoveryRate; } if (self.vy !== self.naturalVy) { self.vy = self.vy + (self.naturalVy - self.vy) * recoveryRate; } var currentHeight = self.gfx.height * self.gfx.scale.y; var currentWidth = self.gfx.width * self.gfx.scale.x; if (!self.reachedSurface && self.y <= GAME_CONFIG.WATER_SURFACE_Y + currentHeight * 0.3) { self.reachedSurface = true; self.vy = 0; self.vx = (Math.random() < 0.5 ? 1 : -1) * (0.5 + Math.random() * 0.5); self.naturalVx = self.vx; self.naturalVy = 0; } if (!self.fadingOut && (self.y < -currentHeight || self.x < -currentWidth || self.x > 2048 + currentWidth || self.y > waterBottom + currentHeight)) { self.fadingOut = true; tween.stop(self.gfx); tween(self.gfx, { alpha: 0 }, { duration: 400 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { self.isDone = true; } }); } }; return self; }); var ShadowFishParticle = Container.expand(function (nodeX, nodeY) { var self = Container.call(this); self.isDone = false; var fishAsset = self.attachAsset('shadowfish', { anchorX: 0.5, anchorY: 0.5, alpha: 0, scaleX: 0.8, scaleY: 0.8 }); var startSide = Math.random() < 0.5 ? -1 : 1; var swimDistance = 300 + Math.random() * 150; var verticalOffsetBase = (Math.random() - 0.5) * 50; var verticalSwimAmplitude = 20 + Math.random() * 30; self.x = nodeX + startSide * (swimDistance + 150); self.y = nodeY + verticalOffsetBase; self.lastX = self.x; self.lastY = self.y; var targetX = nodeX - startSide * (swimDistance + 150); if (startSide > 0) { fishAsset.scale.x *= -1; } var fadeInDuration = 2000 + Math.random() * 1000; var swimDuration = 7000 + Math.random() * 4000; var fadeOutDuration = 700 + Math.random() * 300; var visibleAlpha = 0.5 + Math.random() * 0.2; tween(fishAsset, { alpha: visibleAlpha }, { duration: fadeInDuration, easing: tween.easeOut }); tween(self, { x: targetX }, { duration: swimDuration, easing: tween.linear, delay: fadeInDuration * 0.3, onFinish: function onFinish() { tween(fishAsset, { alpha: 0 }, { duration: fadeOutDuration, easing: tween.easeIn, onFinish: function onFinish() { self.isDone = true; } }); } }); var swimStartTime = LK.ticks; self.update = function () { if (self.isDone || !fishAsset || fishAsset.destroyed) { return; } var currentTicks = LK.ticks; var timeSinceFadeInStart = (currentTicks - swimStartTime) * (1000 / 60); if (timeSinceFadeInStart > fadeInDuration * 0.5 && timeSinceFadeInStart < fadeInDuration * 0.3 + swimDuration - fadeOutDuration * 0.5) { var swimProgress = (timeSinceFadeInStart - fadeInDuration * 0.3) / (swimDuration - fadeInDuration * 0.3); self.y = nodeY + verticalOffsetBase + Math.sin(swimProgress * Math.PI * 2) * verticalSwimAmplitude; } // Simplified rotation based on movement direction, ignoring small vertical movements var dx = self.x - self.lastX; var dy = self.y - self.lastY; // Only update rotation if horizontal movement is significant if (Math.abs(dx) > 0.5) { var angle = Math.atan2(dy, dx); // Simplify the scale-based rotation logic if (fishAsset.scale.x < 0) { // Fish is flipped horizontally fishAsset.rotation = angle + Math.PI; } else { // Fish is not flipped fishAsset.rotation = angle; } } self.lastX = self.x; self.lastY = self.y; }; return self; }); var SparkleParticle = Container.expand(function (startX, startY) { var self = Container.call(this); self.gfx = self.attachAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.2 + Math.random() * 0.3, alpha: 0 }); self.gfx.scaleY = self.gfx.scaleX; self.x = startX; self.y = startY; self.isDone = false; self.life = 60 + Math.random() * 60; // Increased lifespan for slower particles self.age = 0; self.rotationSpeed = (Math.random() - 0.5) * 0.05; self.vy = -(0.7 + Math.random() * 0.8); // Slower upward movement var targetAlpha = 0.5 + Math.random() * 0.5; tween(self.gfx, { alpha: targetAlpha }, { duration: 200, easing: tween.easeOut }); self.update = function () { if (self.isDone) { return; } self.age++; self.y += self.vy; self.gfx.rotation += self.rotationSpeed; if (self.age >= self.life * 0.5) { var fadeOutProgress = (self.age - self.life * 0.5) / (self.life * 0.5); self.gfx.alpha = targetAlpha * (1 - fadeOutProgress); } if (self.age >= self.life) { self.isDone = true; } }; return self; }); var WaterfallParticle = Container.expand(function (spawnX, spawnY) { var self = Container.call(this); self.gfx = self.attachAsset('bubbles', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 + Math.random() * 0.3, scaleX: 0.4 + Math.random() * 0.3 }); self.gfx.scaleY = self.gfx.scaleX; self.x = spawnX + (Math.random() - 0.5) * 50; self.y = spawnY; self.isDone = false; var fallDistance = 200; var sprayHeight = 50 + Math.random() * 50; var spraySpreadX = (Math.random() - 0.5) * 150; var fallDuration = 800 + Math.random() * 400; var sprayDuration = 700 + Math.random() * 500; tween(self, { y: self.y + fallDistance }, { duration: fallDuration, easing: tween.easeInSine, onFinish: function onFinish() { tween(self, { y: self.y - sprayHeight, x: self.x + spraySpreadX }, { duration: sprayDuration, easing: tween.easeOutSine, onFinish: function onFinish() { self.isDone = true; } }); tween(self.gfx, { alpha: 0, scaleX: self.gfx.scaleX * 0.4, scaleY: self.gfx.scaleY * 0.4 }, { duration: sprayDuration, easing: tween.easeOutSine }); } }); return self; }); var WaveParticle = Container.expand(function (movesRight) { var self = Container.call(this); self.isDone = false; var FINAL_SCALE_TARGET = 1.2 + Math.random() * 0.6; var assetScaleX = movesRight ? -FINAL_SCALE_TARGET : FINAL_SCALE_TARGET; var waveGfx = self.attachAsset('wave', { anchorX: 0.5, anchorY: 0.5, scaleX: assetScaleX, scaleY: FINAL_SCALE_TARGET, alpha: 0, rotation: 0 }); self.x = 100 + Math.random() * (2048 - 200); self.y = 100 + Math.random() * (2732 - 200); self.startX = self.x; self.startY = self.y; self.waveAmplitude = 10; self.waveFrequency = Math.PI * 2 / 150; var MOVE_DISTANCE_X = 200; var targetX; if (movesRight) { targetX = self.startX + MOVE_DISTANCE_X; } else { targetX = self.startX - MOVE_DISTANCE_X; } var SCALE_FADE_IN_DURATION_MS = 600; var MOVE_DURATION_MS = 5000; var SHRINK_FADE_OUT_DURATION_MS = MOVE_DURATION_MS - SCALE_FADE_IN_DURATION_MS; var VISIBLE_ALPHA_TARGET = 0.3 + Math.random() * 0.3; tween(waveGfx, { alpha: VISIBLE_ALPHA_TARGET }, { duration: SCALE_FADE_IN_DURATION_MS, easing: tween.easeOut }); tween(self, { x: targetX }, { duration: MOVE_DURATION_MS, easing: tween.linear }); var shrinkStartTimeDelay = MOVE_DURATION_MS - SHRINK_FADE_OUT_DURATION_MS; if (shrinkStartTimeDelay < 0) { shrinkStartTimeDelay = 0; } LK.setTimeout(function () { if (self.isDone || !waveGfx || waveGfx.destroyed) { return; } tween(waveGfx, { alpha: 0 }, { duration: SHRINK_FADE_OUT_DURATION_MS, easing: tween.easeIn, onFinish: function onFinish() { self.isDone = true; } }); }, shrinkStartTimeDelay); self.update = function () { if (self.isDone) { return; } var horizontalProgress; if (movesRight) { horizontalProgress = self.x - self.startX; } else { horizontalProgress = self.startX - self.x; } self.y = self.startY + self.waveAmplitude * Math.sin(horizontalProgress * self.waveFrequency); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ /**** * Utility Functions ****/ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread 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 _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) { return Array.from(r); } } function _arrayWithoutHoles(r) { if (Array.isArray(r)) { return _arrayLikeToArray(r); } } 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 updateParticleArray(particleArray) { for (var i = particleArray.length - 1; i >= 0; i--) { var particle = particleArray[i]; if (particle) { if (typeof particle.update === 'function') { particle.update(); } if (particle.isDone) { if (typeof particle.destroy === 'function') { particle.destroy(); } particleArray.splice(i, 1); } } else { particleArray.splice(i, 1); } } } function handleParticleSpawning(config) { config.counter++; if (config.counter >= config.interval && (!config.maxCount || config.array.length < config.maxCount)) { config.counter = 0; var newParticle = new config.constructor(); if (config.container && !config.container.destroyed) { config.container.addChild(newParticle); config.array.push(newParticle); } } } function clearTimer(timerId, isInterval) { if (timerId !== null) { if (isInterval) { LK.clearInterval(timerId); } else { LK.clearTimeout(timerId); } return null; } return timerId; } function cleanupParticleArray(array, container) { if (array) { array.forEach(function (item) { if (item && typeof item.destroy === 'function' && !item.destroyed) { item.destroy(); } }); array.length = 0; } if (container && typeof container.removeChildren === 'function' && !container.destroyed) { container.removeChildren(); } } function stopTween(object) { if (object && !object.destroyed) { tween.stop(object); } } function stopTweens(objects) { for (var i = 0; i < objects.length; i++) { stopTween(objects[i]); } } function createWaveAnimation(segment, amplitude, halfPeriod) { var animUp, animDown; animUp = function animUp() { if (!segment || segment.destroyed) { return; } tween(segment, { y: segment.baseY - amplitude }, { duration: halfPeriod, easing: tween.easeInOut, onFinish: animDown }); }; animDown = function animDown() { if (!segment || segment.destroyed) { return; } tween(segment, { y: segment.baseY + amplitude }, { duration: halfPeriod, easing: tween.easeInOut, onFinish: animUp }); }; return { up: animUp, down: animDown }; } function createAmbientSoundScheduler(config) { var timer = null; function scheduleNext() { if (GameState.currentScreen !== config.screenName) { timer = clearTimer(timer, false); return; } var delay = config.baseDelay + Math.random() * config.variance; timer = LK.setTimeout(function () { if (GameState.currentScreen !== config.screenName) { return; } var soundId = Array.isArray(config.sounds) ? config.sounds[Math.floor(Math.random() * config.sounds.length)] : config.sounds; LK.getSound(soundId).play(); scheduleNext(); }, delay); } return { start: scheduleNext, stop: function stop() { timer = clearTimer(timer, false); } }; } function updateSandwichBoardIcon() { if (!beachScreenElements || !beachScreenElements.sandwichBoard || beachScreenElements.sandwichBoard.destroyed) { return; } var dishContainer = beachScreenElements.sandwichBoard.dishContainer; if (!dishContainer || dishContainer.destroyed) { return; } // Clear any existing icon dishContainer.removeChildren(); // Get the current recipe's final asset var currentRecipe = RECIPE_SYSTEM.getCurrentRecipe(); var dishAsset = currentRecipe.finalAsset; // Add the new icon based on the current dish var dishIcon = LK.getAsset(dishAsset, { anchorX: 0.5, anchorY: 0.6, scaleX: 1.4, scaleY: 1.4 }); dishContainer.addChild(dishIcon); } function spawnBeachCrab() { if (GameState.currentScreen !== 'beach' || !beachCrabContainer || beachCrabContainer.destroyed || activeBeachCrabs.length >= 5) { return; } var newCrab = new CrabParticle(); beachCrabContainer.addChild(newCrab); activeBeachCrabs.push(newCrab); } function startBeachCrabSpawner() { stopBeachCrabSpawner(); beachCrabSpawnTimerId = LK.setInterval(spawnBeachCrab, 4000 + Math.random() * 3000); } function stopBeachCrabSpawner() { beachCrabSpawnTimerId = clearTimer(beachCrabSpawnTimerId, true); } 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); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) { return t; } var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) { return i; } throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function clearPlayerStorage() { storage.money = 0; storage.restaurantUnlocked = false; storage.shopUnlocked = false; storage.fiberglassRodOwned = false; storage.grinderOwned = false; storage.spicesOwned = false; storage.potOwned = false; storage.ownedSongs = {}; storage.currentDepth = 0; storage.beachWelcomeShown = false; storage.cookingWelcomeShown = false; storage.hasBoughtFirstTool = false; storage.recipeSwitchHintShown = false; if (FishInventory && FishInventory.fishTypes) { FishInventory.fishTypes.forEach(function (fishType) { var storageKey = 'fishInventory_' + fishType; if (storage[storageKey] !== undefined) { storage[storageKey] = 0; } }); } GameState.money = 0; GameState.currentDepth = 0; GameState.initOwnedSongs(); FishInventory.init(); if (GameState.currentScreen === 'levelSelect' && levelSelectElements) { levelSelectElements.updateMapDisplay(); } } var TITLE_ANIM_CONSTANTS = { INITIAL_GROUP_ALPHA: 0, FINAL_GROUP_ALPHA: 1, INITIAL_UI_ALPHA: 0, FINAL_UI_ALPHA: 1, INITIAL_GROUP_SCALE: 3.5, FINAL_GROUP_SCALE: 2.8, GROUP_ANIM_DURATION: 4000, TEXT_FADE_DURATION: 1000, BUTTON_FADE_DURATION: 800, BOAT_ANCHOR_X: 0.5, BOAT_ANCHOR_Y: 0.5, FISHERMAN_ANCHOR_X: 0.5, FISHERMAN_ANCHOR_Y: 0.9, FISHERMAN_X_OFFSET: -20, FISHERMAN_Y_OFFSET: -100, LINE_ANCHOR_X: 0.5, LINE_ANCHOR_Y: 0, LINE_X_OFFSET_FROM_FISHERMAN: 70, LINE_Y_OFFSET_FROM_FISHERMAN: -130, HOOK_ANCHOR_X: 0.5, HOOK_ANCHOR_Y: 0.5, HOOK_Y_DEPTH_FROM_LINE_START: 700, GROUP_PIVOT_X: 0, GROUP_PIVOT_Y: 0, GROUP_INITIAL_Y_SCREEN_OFFSET: -450 }; var GAME_DIFFICULTY = { current: 'easy', // Default difficulty pushbackMultiplier: { easy: 3, medium: 2 } }; // Store original game.up if it exists and hasn't been stored yet if (typeof game.originalGameUp === 'undefined') { game.originalGameUp = game.up; } // Remove the cooking swipe handling from the original game.up function game.up = function (x, y, obj) { if (GameState.currentScreen === 'cooking') { // Handle tap detection for cooking game (no swipe needed) var deltaX = x - swipeState.startX; var deltaY = y - swipeState.startY; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); var isTap = distance < 50; // Increased from 30 to 50 if (isTap) { // This is a tap - handle station processing handleCookingInput(x, y); // Fixed function name from handleCookingGameInput } // Remove all swipe handling for cooking - not needed in rhythm game return; } // Call the original game.up logic for other screens (e.g., fishing) if (typeof game.originalGameUp === 'function' && game.originalGameUp !== game.up) { game.originalGameUp.call(this, x, y, obj); } else { // Fallback: This is the logic from the original game.up if not captured or if it's the first definition if (GameState.currentScreen === 'fishing') { handleFishingInput(x, y, false); // false for isDown (release/up) } } }; /**** * Pattern Generation System ****/ var PatternGenerator = { lastLane: -1, minDistanceBetweenFish: 300, lastActualSpawnTime: -100000, getNextLane: function getNextLane() { if (this.lastLane === -1) { this.lastLane = 1; return 1; } var possibleLanes = [this.lastLane]; if (this.lastLane > 0) { possibleLanes.push(this.lastLane - 1); } if (this.lastLane < 2) { possibleLanes.push(this.lastLane + 1); } if (Math.random() < 0.7) { this.lastLane = possibleLanes[Math.floor(Math.random() * possibleLanes.length)]; } else { this.lastLane = Math.floor(Math.random() * 3); } return this.lastLane; }, canSpawnFishOnBeat: function canSpawnFishOnBeat(currentTime, configuredSpawnInterval) { var timeSinceLast = currentTime - this.lastActualSpawnTime; var minRequiredGap = configuredSpawnInterval; return timeSinceLast >= minRequiredGap; }, registerFishSpawn: function registerFishSpawn(spawnTime) { this.lastActualSpawnTime = spawnTime; }, reset: function reset() { this.lastLane = -1; this.lastActualSpawnTime = -100000; } }; /**** * Game Configuration ****/ var RECIPE_SYSTEM = { recipes: { fishAndChips: { name: "Fish and Chips", stations: ['knife', 'bowl', 'fryer'], requiredTools: [], baseValue: 5, finalAsset: 'fishAndChips', processingSteps: [{ from: 'raw', station: 'knife', produces: 'steak' }, { from: 'steak', station: 'bowl', produces: 'battered' }, { from: 'battered', station: 'fryer', produces: 'final' }] }, fishChowder: { name: "Fish Chowder", stations: ['knife', 'bowl', 'pot'], requiredTools: ['pot'], baseValue: 7, finalAsset: 'fishChowder', processingSteps: [{ from: 'raw', station: 'knife', produces: 'steak' }, { from: 'steak', station: 'bowl', produces: 'battered' }, { from: 'battered', station: 'pot', produces: 'final' }] }, fishTacos: { name: "Fish Tacos", stations: ['knife', 'spices', 'fryer'], requiredTools: ['spices'], baseValue: 7, finalAsset: 'fishTaco', processingSteps: [{ from: 'raw', station: 'knife', produces: 'steak' }, { from: 'steak', station: 'spices', produces: 'spiced' }, { from: 'spiced', station: 'fryer', produces: 'final' }] }, fishCurry: { name: "Fish Curry", stations: ['knife', 'spices', 'pot'], requiredTools: ['spices', 'pot'], baseValue: 7, finalAsset: 'fishCurry', processingSteps: [{ from: 'raw', station: 'knife', produces: 'steak' }, { from: 'steak', station: 'spices', produces: 'spiced' }, { from: 'spiced', station: 'pot', produces: 'final' }] }, fishCakes: { name: "Fish Cakes", stations: ['grinder', 'bowl', 'fryer'], requiredTools: ['grinder'], baseValue: 7, finalAsset: 'fishCakes', processingSteps: [{ from: 'raw', station: 'grinder', produces: 'ground' }, { from: 'ground', station: 'bowl', produces: 'batteredGround' }, { from: 'batteredGround', station: 'fryer', produces: 'final' }] }, fishDumplings: { name: "Fish Dumplings", stations: ['grinder', 'bowl', 'pot'], requiredTools: ['grinder', 'pot'], baseValue: 7, finalAsset: 'fishDumplings', processingSteps: [{ from: 'raw', station: 'grinder', produces: 'ground' }, { from: 'ground', station: 'bowl', produces: 'batteredGround' }, { from: 'batteredGround', station: 'pot', produces: 'final' }] }, fishBurgers: { name: "Fish Burgers", stations: ['grinder', 'spices', 'fryer'], requiredTools: ['grinder', 'spices'], baseValue: 7, finalAsset: 'fishBurger', processingSteps: [{ from: 'raw', station: 'grinder', produces: 'ground' }, { from: 'ground', station: 'spices', produces: 'spicedGround' }, { from: 'spicedGround', station: 'fryer', produces: 'final' }] }, fishMeatballs: { name: "Fish Meatballs", stations: ['grinder', 'spices', 'pot'], requiredTools: ['grinder', 'spices', 'pot'], baseValue: 7, finalAsset: 'fishMeatballs', processingSteps: [{ from: 'raw', station: 'grinder', produces: 'ground' }, { from: 'ground', station: 'spices', produces: 'spicedGround' }, { from: 'spicedGround', station: 'pot', produces: 'final' }] } }, getAvailableRecipes: function getAvailableRecipes() { var available = []; for (var recipeId in this.recipes) { var recipe = this.recipes[recipeId]; var canMake = true; // Check if all required tools are owned for (var i = 0; i < recipe.requiredTools.length; i++) { var tool = recipe.requiredTools[i]; var storageKey = tool + 'Owned'; if (!storage[storageKey]) { canMake = false; break; } } if (canMake) { available.push({ id: recipeId, recipe: recipe }); } } return available; }, getCurrentRecipe: function getCurrentRecipe() { return this.recipes[cookingDish] || this.recipes.fishAndChips; } }; var FISH_NAMES = { sardine: "Sardine", anchovy: "Anchovy", mackerel: "Mackerel", mediumFish: "Mid-Size", deepFish: "Deep Lurker", rareFish: "Rare Catch", cod: "Cod", snapper: "Snapper", seaBass: "Sea Bass" }; // Simplified battle system - one fish at a time var BATTLE_STATES = { NONE: 'none', ACTIVE: 'active', WAITING_FOR_NEXT: 'waiting' }; var FISH_RHYTHM_PATTERNS = { sardine: ['beat'], // 1 tap anchovy: ['beat', 'beat'], // 2 taps, 1 lane change mackerel: ['beat', 'beat', 'beat'], // 3 taps, 2 lane changes rareFish: ['beat', 'beat', 'beat', 'beat'], // 4 taps, 3 lane changes cod: ['beat'], // 1 tap snapper: ['beat', 'beat'], // 2 taps seaBass: ['beat', 'beat', 'beat'] // 3 taps }; var BATTLE_STATES = { NONE: 'none', ACTIVE: 'active' }; var GAME_CONFIG = { SCREEN_CENTER_X: 1024, SCREEN_CENTER_Y: 900, BOAT_Y: 710, WATER_SURFACE_Y: 760, LANES: [{ y: 1133, name: "shallow" }, { y: 1776, name: "medium" }, { y: 2419, name: "deep" }], //[FN#9T] PERFECT_WINDOW: 40, GOOD_WINDOW: 80, MISS_WINDOW: 120, DEPTHS: [{ level: 1, name: "Shallow Waters", fishSpeed: 6, fishValue: 1, upgradeCost: 0, songs: [{ name: "Gentle Waves", bpm: 93, duration: 135250, cost: 0 }, { name: "Morning Tide", bpm: 90, duration: 156827, cost: 200, musicId: 'morningtide' }, { name: "Sunny Afternoon", bpm: 97, duration: 181800, cost: 500, musicId: 'sunnyafternoon' }] }, { level: 2, name: "Mid Waters", fishSpeed: 7, fishValue: 2, upgradeCost: 100, songs: [{ name: "Ocean Current", bpm: 100, duration: 163239, cost: 0, musicId: 'oceanCurrent' }, { name: "Deep Flow", bpm: 110, duration: 167210, cost: 1000, musicId: 'deepFlow' }, { name: "Coral Groove", bpm: 123, duration: 185051, cost: 1500, musicId: 'coralGroove' }] }, { level: 3, name: "Deep Waters", fishSpeed: 8, fishValue: 3, upgradeCost: 400, songs: [{ name: "Storm Surge", bpm: 140, duration: 120000, cost: 0 }, { name: "Whirlpool", bpm: 150, duration: 135000, cost: 300 }] }, { level: 4, name: "Abyss", fishSpeed: 9, fishValue: 6, upgradeCost: 1000, songs: [{ name: "Leviathan", bpm: 160, duration: 150000, cost: 0 }, { name: "Deep Trench", bpm: 170, duration: 180000, cost: 600 }] }] }; /**** * Game State Management ****/ // Modified ImprovedRhythmSpawner - completely rewrite the update function var ImprovedRhythmSpawner = { update: function update(currentTime) { if (!GameState.gameActive || GameState.songStartTime === 0 || GameState.fishingEnding) { return; } // Only spawn if no battle is active and no fish are on screen if (GameState.battleState !== BATTLE_STATES.NONE || fishArray.length > 0) { return; } // If we're waiting for next spawn time if (GameState.nextFishSpawnTime > 0 && currentTime < GameState.nextFishSpawnTime) { return; } var songConfig = GameState.getCurrentSongConfig(); if (!songConfig || !songConfig.bpm) { // Safety check return; } var beatInterval = 60000 / songConfig.bpm; // Spawn immediately with normal speed this.spawnSingleFish(currentTime, beatInterval); }, spawnSingleFish: function spawnSingleFish(currentTime, beatInterval) { var depthConfig = GameState.getCurrentDepthConfig(); if (!depthConfig) { // Safety check return; } // Use the original fish speed from config var fishSpeed = depthConfig.fishSpeed; // This is the normal speed var spawnSide = Math.random() < 0.5 ? -1 : 1; // -1 for left, 1 for right var actualSpeed = Math.abs(fishSpeed) * (spawnSide === -1 ? 1 : -1); // Positive for left to right, negative for right to left // Determine fish type var laneIndex = PatternGenerator.getNextLane(); var targetLane = GAME_CONFIG.LANES[laneIndex]; var fishType, fishValue; var rand = Math.random(); if (rand < 0.05) { fishType = 'rare'; fishValue = Math.floor(depthConfig.fishValue * 4); } else if (GameState.selectedDepth >= 2 && rand < 0.3) { fishType = 'deep'; fishValue = Math.floor(depthConfig.fishValue * 2); } else if (GameState.selectedDepth === 1) { fishType = 'medium'; fishValue = Math.floor(depthConfig.fishValue * 1.5); } else if (GameState.selectedDepth >= 2 && rand < 0.6) { fishType = 'medium'; fishValue = Math.floor(depthConfig.fishValue * 1.5); } else { fishType = 'shallow'; fishValue = Math.floor(depthConfig.fishValue); } var newFish = new Fish(fishType, fishValue, actualSpeed, laneIndex); // If actualSpeed is positive, fish moves L to R, starts from left. // If actualSpeed is negative, fish moves R to L, starts from right. newFish.x = actualSpeed > 0 ? -150 : 2048 + 150; newFish.y = targetLane.y; newFish.baseY = targetLane.y; newFish.lastX = newFish.x; // Initialize lastX fishArray.push(newFish); if (fishingScreen && !fishingScreen.destroyed) { fishingScreen.addChild(newFish); } GameState.sessionFishSpawned++; if ((newFish.assetName === 'sardine' || newFish.assetName === 'cod') && !newFish.isSardinePartner) { var songConfig = GameState.getCurrentSongConfig(); var beatInterval = songConfig && songConfig.bpm ? 60000 / songConfig.bpm : 500; var partnerValue = newFish.value; var partnerSpeed = newFish.speed; var partnerLane = newFish.lane; var partnerY = newFish.y; var partnerBaseY = newFish.baseY; var partnerAssetName = newFish.assetName; // Store the asset name for the partner LK.setTimeout(function () { if (GameState.currentScreen !== 'fishing' || !GameState.gameActive) { return; } var partner = new Fish(fishType, partnerValue, partnerSpeed, partnerLane, partnerAssetName); partner.x = partnerSpeed > 0 ? -150 : 2048 + 150; partner.y = partnerY; partner.baseY = partnerBaseY; partner.lastX = partner.x; fishArray.push(partner); if (fishingScreen && !fishingScreen.destroyed) { fishingScreen.addChild(partner); } GameState.sessionFishSpawned++; }, beatInterval); } }, scheduleNextFish: function scheduleNextFish() { var songConfig = GameState.getCurrentSongConfig(); var beatInterval = 60000 / songConfig.bpm; var currentTime = LK.ticks * (1000 / 60); // Schedule next fish spawn for just a half beat later var beatsDelay = 0.1; // Much shorter delay GameState.nextFishSpawnTime = currentTime + beatInterval * beatsDelay; GameState.battleState = BATTLE_STATES.WAITING_FOR_NEXT; LK.setTimeout(function () { if (GameState.battleState === BATTLE_STATES.WAITING_FOR_NEXT) { GameState.battleState = BATTLE_STATES.NONE; GameState.nextFishSpawnTime = 0; } }, beatInterval * beatsDelay); }, // Adding a reset method, though not in the prompt, is good practice if replacing the whole object. // However, the original prompt for ImprovedRhythmSpawner did not include this in the new code. // Sticking to the prompt, so no explicit reset here unless it was part of the provided block. // The new system doesn't require the complex reset of the old one (nextBeatToSchedule, scheduledBeats). // An empty reset or a reset focusing on its new simpler state might be added later if needed. // For now, the structure above replaces the old entirely. reset: function reset() { // This spawner's state is mostly in GameState (battleState, nextFishSpawnTime) // No internal scheduledBeats or nextBeatToSchedule to reset in this new version. // If GameState.nextFishSpawnTime needs reset on a full game reset, that should happen elsewhere. } }; /**** * Tutorial System - Global Scope ****/ function updateLaneBracketsVisuals() { if (laneBrackets && laneBrackets.length === GAME_CONFIG.LANES.length) { for (var i = 0; i < laneBrackets.length; i++) { var isActiveLane = i === GameState.hookTargetLaneIndex; var targetAlpha = isActiveLane ? 0.9 : 0.5; if (laneBrackets[i] && laneBrackets[i].left && !laneBrackets[i].left.destroyed) { if (laneBrackets[i].left.alpha !== targetAlpha) { laneBrackets[i].left.alpha = targetAlpha; } } if (laneBrackets[i] && laneBrackets[i].right && !laneBrackets[i].right.destroyed) { if (laneBrackets[i].right.alpha !== targetAlpha) { laneBrackets[i].right.alpha = targetAlpha; } } } } } function createTutorialElements() { tutorialOverlayContainer.removeChildren(); tutorialTextBackground = tutorialOverlayContainer.addChild(LK.getAsset('screenBackground', { x: GAME_CONFIG.SCREEN_CENTER_X, y: 2732 * 0.85, width: 1800, height: 450, color: 0x000000, anchorX: 0.5, anchorY: 0.5, alpha: 0.75 })); tutorialTextDisplay = tutorialOverlayContainer.addChild(new Text2('', { size: 55, fill: 0xFFFFFF, wordWrap: true, wordWrapWidth: 1700, align: 'center', lineHeight: 65 })); tutorialTextDisplay.anchor.set(0.5, 0.5); tutorialTextDisplay.x = tutorialTextBackground.x; tutorialTextDisplay.y = tutorialTextBackground.y - 50; tutorialContinueButton = tutorialOverlayContainer.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: tutorialTextBackground.x, y: tutorialTextBackground.y + tutorialTextBackground.height / 2 - 55, tint: 0x1976d2, width: 350, height: 70 })); tutorialContinueText = tutorialOverlayContainer.addChild(new Text2('CONTINUE', { size: 34, fill: 0xFFFFFF })); tutorialContinueText.anchor.set(0.5, 0.5); tutorialContinueText.x = tutorialContinueButton.x; tutorialContinueText.y = tutorialContinueButton.y; tutorialOverlayContainer.visible = false; } function setTutorialText(newText, showContinue) { if (showContinue === undefined) { showContinue = true; } if (!tutorialTextDisplay || !tutorialContinueButton || !tutorialContinueText) { createTutorialElements(); } tutorialTextDisplay.setText(newText); tutorialContinueButton.visible = showContinue; tutorialContinueText.visible = showContinue; tutorialOverlayContainer.visible = true; } function spawnTutorialFishHelper(config) { var fishType = config.type || 'shallow'; var depthConfig = GAME_CONFIG.DEPTHS[0]; // Tutorial always uses shallow depth config for simplicity var fishValue = Math.floor(depthConfig.fishValue / 2); var baseSpeed = depthConfig.fishSpeed; var speedMultiplier = config.speedMultiplier || 0.5; var laneIndex = config.lane !== undefined ? config.lane : 1; var spawnSide = config.spawnSide !== undefined ? config.spawnSide : Math.random() < 0.5 ? -1 : 1; var actualFishSpeed = Math.abs(baseSpeed) * speedMultiplier * spawnSide; var newFish = new Fish(fishType, fishValue, actualFishSpeed, laneIndex); // Override fish asset if specified in config if (config.fishAsset) { if (newFish.fishGraphics && !newFish.fishGraphics.destroyed) { newFish.fishGraphics.destroy(); } newFish.fishGraphics = newFish.attachAsset(config.fishAsset, { anchorX: 0.5, anchorY: 0.5 }); if (actualFishSpeed > 0) { // Moving right newFish.fishGraphics.scaleX = -1; // Flip if moving L to R } else { newFish.fishGraphics.scaleX = 1; // Normal if moving R to L } // Override rhythm pattern and multi-tap properties for the forced fish type newFish.rhythmPattern = FISH_RHYTHM_PATTERNS[config.fishAsset] || ['beat']; // Default to single beat newFish.maxTaps = newFish.rhythmPattern.length; newFish.currentTaps = 0; newFish.isMultiTapFish = newFish.maxTaps > 1; newFish.currentPatternIndex = 0; newFish.isPushedBack = false; newFish.isInBattle = false; newFish.nextPlannedMoveLane = -1; // Recreate multi-tap display if it's a multi-tap fish if (newFish.isMultiTapFish) { // Destroy existing counter if any (from original Fish constructor) if (newFish.counterBg && !newFish.counterBg.destroyed) { newFish.counterBg.destroy(); } if (newFish.tapCounter && !newFish.tapCounter.destroyed) { newFish.tapCounter.destroy(); } newFish.nextPlannedMoveLane = newFish.determineFirstPlannedLane(); newFish.counterBg = newFish.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -80, // Position above the fish width: 120, height: 80, alpha: 0.8 })); var initialCounterText = newFish.maxTaps.toString(); if (newFish.maxTaps > 1 && newFish.nextPlannedMoveLane !== -1 && newFish.nextPlannedMoveLane !== newFish.lane) { if (newFish.nextPlannedMoveLane < newFish.lane) { initialCounterText += "↑"; } else if (newFish.nextPlannedMoveLane > newFish.lane) { initialCounterText += "↓"; } } newFish.tapCounter = newFish.addChild(new Text2(initialCounterText, { size: 70, fill: 0xFFFFFF, // White text stroke: 0x000000, // Black stroke for visibility strokeThickness: 3 })); newFish.tapCounter.anchor.set(0.5, 0.5); newFish.tapCounter.x = 0; newFish.tapCounter.y = -80; // Align with background } } newFish.x = actualFishSpeed > 0 ? -150 : 2048 + 150; newFish.y = GAME_CONFIG.LANES[laneIndex].y + (config.yOffset || 0); newFish.baseY = newFish.y; newFish.lastX = newFish.x; newFish.tutorialFish = true; newFish.wasCaughtThisInteraction = false; // Initialize tutorial interaction flag newFish.wasMissedThisInteraction = false; // Initialize tutorial interaction flag fishArray.push(newFish); fishingScreen.addChild(newFish); return newFish; } function runTutorialStep() { // GameState.tutorialPaused is set by each case. // GameState.tutorialAwaitingTap is not used in this refactor. // Clear previous lane highlights if any if (tutorialLaneHighlights.length > 0) { tutorialLaneHighlights.forEach(function (overlay) { if (overlay && !overlay.destroyed) { overlay.destroy(); } }); tutorialLaneHighlights = []; } // Default to showing continue button, specific cases will hide it. if (tutorialContinueButton) { tutorialContinueButton.visible = true; } if (tutorialContinueText) { tutorialContinueText.visible = true; } // Clean up previous tutorial fish if it exists and we are not in a fish-centric step (3, 4, 5) // For steps 3, 4, 5, fish cleanup is handled by game.down or the step logic itself. if (GameState.tutorialFish && GameState.tutorialStep !== 3 && GameState.tutorialStep !== 4 && GameState.tutorialStep !== 5) { if (!GameState.tutorialFish.destroyed) { var idx = fishArray.indexOf(GameState.tutorialFish); if (idx > -1) { fishArray.splice(idx, 1); } GameState.tutorialFish.destroy(); } GameState.tutorialFish = null; } // Reset interaction flags on the current tutorial fish if it exists (for retries) if (GameState.tutorialFish) { GameState.tutorialFish.wasCaughtThisInteraction = false; GameState.tutorialFish.wasMissedThisInteraction = false; } if (fishingElements) { // Ensure fishing animations are running for tutorial steps before the end if (typeof fishingElements.startWaterSurfaceAnimation === 'function' && GameState.tutorialStep < 9) { fishingElements.startWaterSurfaceAnimation(); } if (typeof fishingElements.startBoatAndFishermanAnimation === 'function' && GameState.tutorialStep < 9) { fishingElements.startBoatAndFishermanAnimation(); } if (fishingElements.hook) { tween.stop(fishingElements.hook); // For early steps, or if explicitly needed, force hook to a position. if (GameState.tutorialStep <= 1) { // Steps 0, 1: Hook starts in middle swipeState.currentLane = 1; fishingElements.hook.y = GAME_CONFIG.LANES[1].y; fishingElements.hook.originalY = GAME_CONFIG.LANES[1].y; GameState.hookTargetLaneIndex = 1; updateLaneBracketsVisuals(); } // For step 2, hook is where player left it from step 1, or middle if step 1 was skipped. // For step 3, hook is where player left it from step 2. // For step 4, hook is where player left it from step 3. } } switch (GameState.tutorialStep) { case 0: setTutorialText("Welcome to Beat Fisher! Let's learn the basics. Tap 'CONTINUE'."); GameState.tutorialPaused = true; break; case 1: setTutorialText("This is your hook. SWIPE UP or DOWN to move it between lanes. Try it now, then tap 'CONTINUE'."); if (fishingElements.hook && !fishingElements.hook.destroyed) { tween(fishingElements.hook.scale, { x: 1.2, y: 1.2 }, { duration: 250, onFinish: function onFinish() { if (fishingElements.hook && !fishingElements.hook.destroyed) { tween(fishingElements.hook.scale, { x: 1, y: 1 }, { duration: 250 }); } } }); } GameState.tutorialPaused = true; // Paused for CONTINUE, but swipe is enabled by handleFishingInput break; case 2: setTutorialText("Great! Fish swim in three lanes. Try SWIPING your hook to different lanes. When ready, tap 'CONTINUE'."); for (var i = 0; i < GAME_CONFIG.LANES.length; i++) { var laneYPos = GAME_CONFIG.LANES[i].y; var highlight = LK.getAsset('laneHighlight', { anchorX: 0.5, anchorY: 0.5, x: GAME_CONFIG.SCREEN_CENTER_X, y: laneYPos, alpha: 0.25, width: 1800, height: 200 }); tutorialOverlayContainer.addChildAt(highlight, 0); tutorialLaneHighlights.push(highlight); } GameState.tutorialPaused = true; // Paused for CONTINUE, swipe enabled by handleFishingInput break; case 3: // Ensure hook is visually and logically in the lane the fish will spawn. // swipeState.currentLane should hold the player's chosen lane from step 2 (or default to 1 if step 2 was instant). // The hook's Y position is determined by the swipe tween in handleFishingInput. // The tween.stop(fishingElements.hook) at the start of runTutorialStep will have halted any ongoing motion. // The hook will be at the Y position where its tween was stopped. GameState.hookTargetLaneIndex = swipeState.currentLane; updateLaneBracketsVisuals(); setTutorialText("Nice! A sardine will approach in your current lane (" + (swipeState.currentLane === 0 ? "TOP" : swipeState.currentLane === 1 ? "MIDDLE" : "BOTTOM") + "). It needs TWO TAPS. Tap to catch it!"); tutorialContinueButton.visible = false; tutorialContinueText.visible = false; GameState.tutorialPaused = false; // Player needs to act (tap fish) if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) { GameState.tutorialFish.destroy(); } GameState.tutorialFish = spawnTutorialFishHelper({ type: 'shallow', speedMultiplier: 0.35, lane: swipeState.currentLane, // Fish in the hook's current lane fishAsset: 'sardine' // Force sardine for 2 taps }); if (GameState.tutorialFish) { GameState.tutorialFish.wasCaughtThisInteraction = false; GameState.tutorialFish.wasMissedThisInteraction = false; } break; case 4: // Player needs to swipe to the top lane. Hook is wherever they left it. // The fish will spawn in the top lane. setTutorialText("Great! Now, SWIPE to the TOP lane. A sardine will appear there. Catch it with TWO TAPS!"); tutorialContinueButton.visible = false; tutorialContinueText.visible = false; GameState.tutorialPaused = false; // Player needs to swipe and tap if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) { GameState.tutorialFish.destroy(); } GameState.tutorialFish = spawnTutorialFishHelper({ type: 'shallow', speedMultiplier: 0.45, lane: 0, // Fish in top lane (index 0) fishAsset: 'sardine' }); if (GameState.tutorialFish) { GameState.tutorialFish.wasCaughtThisInteraction = false; GameState.tutorialFish.wasMissedThisInteraction = false; } break; case 5: setTutorialText("Excellent! Notice how the fish moved to different lanes after each tap? The arrow showed you where it would go next. This is the multi-tap battle system! Tap 'CONTINUE'."); GameState.tutorialPaused = true; break; case 7: setTutorialText("Fish will approach the hook on the beat with the music's rhythm. Listen to the beat! Tap 'CONTINUE'."); GameState.tutorialPaused = true; break; case 8: setTutorialText("You're all set! Tap 'CONTINUE' to go to the fishing spots!"); GameState.tutorialPaused = true; break; default: // End of tutorial GameState.tutorialMode = false; tutorialOverlayContainer.visible = false; if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) { var idxDefault = fishArray.indexOf(GameState.tutorialFish); if (idxDefault > -1) { fishArray.splice(idxDefault, 1); } GameState.tutorialFish.destroy(); GameState.tutorialFish = null; } if (tutorialLaneHighlights.length > 0) { tutorialLaneHighlights.forEach(function (overlay) { if (overlay && !overlay.destroyed) { overlay.destroy(); } }); tutorialLaneHighlights = []; } // Restore normal game animations if they were stopped/altered if (fishingElements && fishingElements.boat && !fishingElements.boat.destroyed) { stopTween(fishingElements.boat); } if (fishingElements && fishingElements.fishermanContainer && !fishingElements.fishermanContainer.destroyed) { stopTween(fishingElements.fishermanContainer); } if (fishingElements && fishingElements.waterSurfaceSegments) { stopTweens(fishingElements.waterSurfaceSegments); } _showScreen('levelSelect'); break; } } function startTutorial() { GameState.tutorialMode = true; GameState.tutorialStep = 0; GameState.gameActive = false; _showScreen('fishing'); fishingScreen.alpha = 1; fishArray.forEach(function (f) { if (f && !f.destroyed) { f.destroy(); } }); fishArray = []; ImprovedRhythmSpawner.reset(); if (fishingElements.scoreText) { fishingElements.scoreText.setText(''); } if (fishingElements.fishText) { fishingElements.fishText.setText(''); } if (fishingElements.comboText) { fishingElements.comboText.setText(''); } if (fishingElements.progressText) { fishingElements.progressText.setText(''); } var bracketAssetHeight = 150; var bracketAssetWidth = 75; if (laneBrackets && laneBrackets.length > 0) { laneBrackets.forEach(function (bracketPair) { if (bracketPair.left && !bracketPair.left.destroyed) { bracketPair.left.destroy(); } if (bracketPair.right && !bracketPair.right.destroyed) { bracketPair.right.destroy(); } }); } laneBrackets = []; if (fishingScreen && !fishingScreen.destroyed) { for (var i = 0; i < GAME_CONFIG.LANES.length; i++) { var laneY = GAME_CONFIG.LANES[i].y; var leftBracket = fishingScreen.addChild(LK.getAsset('lanebracket', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5, x: bracketAssetWidth / 2, y: laneY, height: bracketAssetHeight })); var rightBracket = fishingScreen.addChild(LK.getAsset('lanebracket', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5, scaleX: -1, x: 2048 - bracketAssetWidth / 2, y: laneY, height: bracketAssetHeight })); laneBrackets.push({ left: leftBracket, right: rightBracket }); } } } function checkTutorialFishState() { var fish = GameState.tutorialFish; if (!fish || fish.destroyed || fish.caught || fish.missed) { return; } var hookX = fishingElements.hook.x; if (GameState.tutorialStep === 3 || GameState.tutorialStep === 4) { var passedHook = fish.speed > 0 && fish.x > hookX + GAME_CONFIG.MISS_WINDOW + fish.fishGraphics.width / 2 || fish.speed < 0 && fish.x < hookX - GAME_CONFIG.MISS_WINDOW - fish.fishGraphics.width / 2; if (passedHook) { fish.missed = true; GameState.tutorialPaused = true; setTutorialText("It got away! Tap 'CONTINUE' to try that part again."); } } if (fish.x < -250 || fish.x > 2048 + 250) { var wasCriticalStep = GameState.tutorialStep === 3 || GameState.tutorialStep === 4; var fishIndex = fishArray.indexOf(fish); if (fishIndex > -1) { fishArray.splice(fishIndex, 1); } fish.destroy(); GameState.tutorialFish = null; if (wasCriticalStep && !fish.caught && !fish.missed) { GameState.tutorialPaused = true; setTutorialText("The fish swam off. Tap 'CONTINUE' to try that part again."); } } } var GameState = { currentScreen: 'title', currentDepth: storage.currentDepth || 0, money: storage.money || 0, totalFishCaught: 0, ownedSongs: [], selectedDepth: 0, selectedSong: 0, sessionScore: 0, sessionFishCaught: 0, sessionFishCaughtByType: {}, sessionFishSpawned: 0, gameActive: false, songStartTime: 0, introPlaying: false, musicNotesActive: false, currentPlayingMusicId: 'rhythmTrack', currentPlayingMusicInitialVolume: 0.8, lastLevelSelectNodeKey: 'dock', hookTargetLaneIndex: 1, tutorialMode: false, tutorialStep: 0, tutorialPaused: false, tutorialAwaitingTap: false, tutorialFish: null, battleState: BATTLE_STATES.NONE, currentBattleFish: null, nextFishSpawnTime: 0, fishingEnding: false, justBoughtFiberglassRod: false, initOwnedSongs: function initOwnedSongs() { this.ownedSongs = []; var storedSongs = storage.ownedSongs; if (storedSongs && Object.keys(storedSongs).length > 0) { // Load from storage for (var key in storedSongs) { var parts = key.split('_'); this.ownedSongs.push({ depth: parseInt(parts[0], 10), songIndex: parseInt(parts[1], 10) }); } } else { // Initialize for a new game, giving the first song of each unlocked depth for free var defaultSongs = {}; for (var i = 0; i <= this.currentDepth; i++) { this.ownedSongs.push({ depth: i, songIndex: 0 }); defaultSongs[i + '_0'] = true; } storage.ownedSongs = defaultSongs; } }, hasSong: function hasSong(depth, songIndex) { return this.ownedSongs.some(function (song) { return song.depth === depth && song.songIndex === songIndex; }); }, buySong: function buySong(depth, songIndex) { var song = GAME_CONFIG.DEPTHS[depth].songs[songIndex]; if (this.money >= song.cost && !this.hasSong(depth, songIndex)) { this.money -= song.cost; storage.money = this.money; this.ownedSongs.push({ depth: depth, songIndex: songIndex }); var storedSongs = storage.ownedSongs || {}; storedSongs[depth + "_" + songIndex] = true; storage.ownedSongs = storedSongs; return true; } return false; }, getCurrentDepthConfig: function getCurrentDepthConfig() { return GAME_CONFIG.DEPTHS[this.selectedDepth]; }, getCurrentSongConfig: function getCurrentSongConfig() { return GAME_CONFIG.DEPTHS[this.selectedDepth].songs[this.selectedSong]; }, canUpgrade: function canUpgrade() { var nextDepth = GAME_CONFIG.DEPTHS[this.currentDepth + 1]; return nextDepth && this.money >= nextDepth.upgradeCost; }, upgrade: function upgrade() { if (this.canUpgrade()) { var nextDepth = GAME_CONFIG.DEPTHS[this.currentDepth + 1]; this.money -= nextDepth.upgradeCost; storage.money = this.money; this.currentDepth++; storage.currentDepth = this.currentDepth; this.ownedSongs.push({ depth: this.currentDepth, songIndex: 0 }); var storedSongs = storage.ownedSongs || {}; storedSongs[this.currentDepth + "_0"] = true; storage.ownedSongs = storedSongs; return true; } return false; } }; /**** * Inventory System ****/ var FishInventory = { fishTypes: ['anchovy', 'sardine', 'mackerel', 'cod', 'snapper', 'seaBass', 'mediumFish', 'deepFish', 'rareFish'], init: function init() { // Initialize fish inventory if not exists this.fishTypes.forEach(function (fishType) { var storageKey = 'fishInventory_' + fishType; if (storage[storageKey] === undefined) { storage[storageKey] = 0; } }); // Initialize restaurant unlock status if (storage.restaurantUnlocked === undefined) { storage.restaurantUnlocked = false; } if (storage.shopUnlocked === undefined) { storage.shopUnlocked = false; } }, addFish: function addFish(fishType, quantity) { quantity = quantity || 1; var storageKey = 'fishInventory_' + fishType; if (storage[storageKey] !== undefined) { storage[storageKey] += quantity; this.checkRestaurantUnlock(); return true; } return false; }, removeFish: function removeFish(fishType, quantity) { quantity = quantity || 1; var storageKey = 'fishInventory_' + fishType; if (storage[storageKey] !== undefined && storage[storageKey] >= quantity) { storage[storageKey] -= quantity; return true; } return false; }, getFishCount: function getFishCount(fishType) { var storageKey = 'fishInventory_' + fishType; return storage[storageKey] || 0; }, getTotalFish: function getTotalFish() { var total = 0; this.fishTypes.forEach(function (fishType) { total += FishInventory.getFishCount(fishType); }); return total; }, hasAnyFish: function hasAnyFish() { return this.getTotalFish() > 0; }, checkRestaurantUnlock: function checkRestaurantUnlock() { if (!storage.restaurantUnlocked && this.hasAnyFish()) { // Don't set storage.restaurantUnlocked to true immediately. // Instead, set a flag to trigger the unlock sequence on the map screen. GameState.showRestaurantUnlockSequence = true; // The unlock sequence will be responsible for setting storage.restaurantUnlocked = true. } }, getInventoryDisplay: function getInventoryDisplay() { var displayData = []; this.fishTypes.forEach(function (fishType) { var count = FishInventory.getFishCount(fishType); if (count > 0) { displayData.push({ type: fishType, name: FISH_NAMES[fishType] || fishType, count: count }); } }); return displayData; } }; // Initialize fish inventory system FishInventory.init(); var titleScreen = game.addChild(new Container()); var levelSelectScreen = game.addChild(new Container()); var fishingScreen = game.addChild(new Container()); var resultsScreen = game.addChild(new Container()); var beachScreen = game.addChild(new Container()); var cookingScreen = game.addChild(new Container()); // Now that cookingScreen is a valid Container object, we can set its visibility. // The original line '{jg}' was problematic because cookingScreen was not yet an object. cookingScreen.visible = false; beachScreen.visible = false; var cookingResultsScreen = game.addChild(new Container()); cookingResultsScreen.visible = false; var globalEffectsLayer = game.addChild(new Container()); var beachCrabContainer; var activeBeachCrabs = []; var beachCrabSpawnTimerId = null; var cookingElements = null; var beachScreenElements = null; var cookingSongSelectOverlay; var cookingSongElements = {}; var cookingSongSelectOpen = false; var cookingSelectedSongIndex = 0; var shopOverlayContainer; var shopOverlayOpen = false; var shopItems = []; var selectedShopItemIndex = -1; var shopItemHighlight = null; var menuOverlayContainer; var menuOverlayOpen = false; var cookingDish = storage.cookingDish || 'fishAndChips'; // Validate that the loaded recipe is available with current tools var availableRecipes = RECIPE_SYSTEM.getAvailableRecipes(); var cookingDishIsAvailable = false; for (var i = 0; i < availableRecipes.length; i++) { if (availableRecipes[i].id === cookingDish) { cookingDishIsAvailable = true; break; } } if (!cookingDishIsAvailable) { cookingDish = 'fishAndChips'; // Default to a recipe that is always available storage.cookingDish = cookingDish; // Update storage with a valid recipe } var globalFadeOverlay = game.addChild(LK.getAsset('screenBackground', { x: 0, y: 0, width: 2048, height: 2732, color: 0x000000, alpha: 0 })); globalFadeOverlay.visible = false; if (game.children.indexOf(globalFadeOverlay) !== -1) { game.setChildIndex(globalFadeOverlay, game.children.length - 1); } GameState.initOwnedSongs(); /**** * New Rhythm-Based Cooking Game ****/ var COOKING_CONFIG = { SCREEN_WIDTH: 2048, SCREEN_HEIGHT: 2732, LANE_COUNT: 3, LANE_START_X: 2048 * 0.125, LANE_WIDTH: 2048 * 0.75 / 3, STATION_Y: 2732 * 0.9, FALL_START_Y: 100 + 2732 * 0.1, STATIONS: [{ name: 'knife', lane: 0, label: 'KNIFE' }, { name: 'bowl', lane: 1, label: 'BOWL' }, { name: 'fryer', lane: 2, label: 'FRYER' }], FISH_MULTIPLIERS: { 'anchovy': 1.0, 'sardine': 1.2, 'mackerel': 1.5, 'cod': 1.8, 'snapper': 2.0, 'seaBass': 2.3, 'mediumFish': 2.0, 'deepFish': 3.0, 'rareFish': 5.0 }, BASE_FISH_CHIPS_VALUE: 5, // This matches the fishAndChips recipe baseValue // Rhythm patterns (1 = fish, 0 = space) PATTERNS: [[1, 0, 0, 1, 0, 0, 0, 0], [1, 0, 0, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0, 1, 0], [1, 0, 0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 1, 0, 0, 0]], PERFECT_WINDOW: 90, // Increased from 72 GOOD_WINDOW: 180, // Increased from 144 MISS_WINDOW: 270 // Increased from 216 }; var CookingState = { gameActive: false, money: 0, songStartTime: 0, bpm: 93, beatInterval: 0, currentBeat: 0, isFinishing: false, songDuration: 90000, // Default duration, will be overwritten by selected song // Pattern system currentPattern: 0, patternPosition: 0, nextSpawnBeat: 0, outOfFish: false, // Game objects fallingItems: [], availableFish: ['anchovy', 'sardine', 'mackerel', 'cod', 'snapper', 'seaBass', 'mediumFish', 'deepFish', 'rareFish'], stations: [] }; function createCookingScreen() { cookingScreen.removeChildren(); // Create lane guides for (var i = 0; i < COOKING_CONFIG.LANE_COUNT; i++) { var laneX = COOKING_CONFIG.LANE_START_X + i * COOKING_CONFIG.LANE_WIDTH + COOKING_CONFIG.LANE_WIDTH / 2; var lane = cookingScreen.addChild(LK.getAsset('counterLane', { anchorX: 0.5, anchorY: 0, x: laneX, y: COOKING_CONFIG.FALL_START_Y, height: COOKING_CONFIG.STATION_Y - COOKING_CONFIG.FALL_START_Y, alpha: 0.3 })); } // Add counter graphic var counterHitzone = cookingScreen.addChild(LK.getAsset('counterHitzone', { anchorX: 0.5, anchorY: 0.5, x: COOKING_CONFIG.SCREEN_WIDTH / 2, y: COOKING_CONFIG.STATION_Y })); // Get current recipe and create appropriate stations var currentRecipe = RECIPE_SYSTEM.getCurrentRecipe(); CookingState.stations = []; for (var i = 0; i < currentRecipe.stations.length; i++) { var stationType = currentRecipe.stations[i]; var stationX = COOKING_CONFIG.LANE_START_X + i * COOKING_CONFIG.LANE_WIDTH + COOKING_CONFIG.LANE_WIDTH / 2; // Use appropriate tool asset for each station var assetName = stationType + 'Tool'; var stationSprite = cookingScreen.addChild(LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, x: stationX, y: COOKING_CONFIG.STATION_Y, scaleX: 2.5, scaleY: 2.5 })); CookingState.stations.push({ sprite: stationSprite, type: stationType, lane: i, x: stationX, y: COOKING_CONFIG.STATION_Y }); } // Create UI elements var songNameText = new Text2('', { size: 60, fill: '#FFFFFF', stroke: '#000000', strokeThickness: 3, align: 'right' }); songNameText.anchor.set(1, 0); songNameText.x = 2048 - 50; songNameText.y = 80; cookingScreen.addChild(songNameText); var countdownText = new Text2('', { size: 60, fill: '#FFD700', stroke: '#000000', strokeThickness: 3, align: 'right' }); countdownText.anchor.set(1, 0); countdownText.x = 2048 - 50; countdownText.y = songNameText.y + 70; cookingScreen.addChild(countdownText); // Add recipe name display var recipeNameText = new Text2(currentRecipe.name, { size: 80, fill: '#FFD700', stroke: '#000000', strokeThickness: 4, align: 'center' }); recipeNameText.anchor.set(0.5, 0); recipeNameText.x = COOKING_CONFIG.SCREEN_WIDTH / 2; recipeNameText.y = 50; cookingScreen.addChild(recipeNameText); return { songNameText: songNameText, countdownText: countdownText, recipeNameText: recipeNameText }; } function startCookingGame(songConfig) { function showCookingWelcomeMessage(onCloseCallback) { var messageContainer = cookingScreen.addChild(new Container()); messageContainer.x = GAME_CONFIG.SCREEN_CENTER_X; messageContainer.y = 2732 / 2; var bgWidth = 1600; var bgHeight = 600; var messageBg = messageContainer.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, width: bgWidth, height: bgHeight, alpha: 0.95 })); var messageText = messageContainer.addChild(new Text2("Cooking is easy! Simply tap the fish when they reach the tools on the bottom. The first recipe you get is fish and chips. Time to chop, dip and fry those fish!", { size: 65, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: bgWidth - 100, lineHeight: 75 })); messageText.anchor.set(0.5, 0.5); messageText.y = -50; var continueButton = messageContainer.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: bgHeight / 2 - 100, tint: 0x1976d2, width: 400, height: 100 })); var continueText = messageContainer.addChild(new Text2('LET\'S COOK!', { size: 50, fill: 0xFFFFFF })); continueText.anchor.set(0.5, 0.5); continueText.x = continueButton.x; continueText.y = continueButton.y; continueButton.down = function () { tween(messageContainer, { alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (messageContainer && !messageContainer.destroyed) { messageContainer.removeChildren(); messageContainer.destroy(); } if (typeof onCloseCallback === 'function') { onCloseCallback(); } } }); }; messageContainer.alpha = 0; tween(messageContainer, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); cookingScreen.setChildIndex(messageContainer, cookingScreen.children.length - 1); } var startGameLogic = function startGameLogic(song) { CookingState.gameActive = true; CookingState.money = 0; CookingState.isFinishing = false; CookingState.songStartTime = LK.ticks * (1000 / 60); CookingState.bpm = song.bpm; CookingState.songDuration = song.duration; CookingState.beatInterval = 60000 / CookingState.bpm; CookingState.currentBeat = 0; CookingState.currentPattern = 0; CookingState.patternPosition = 0; CookingState.nextSpawnBeat = 2; // Start spawning after 2 beats // Clear existing items CookingState.fallingItems.forEach(function (item) { if (item && !item.destroyed) { item.destroy(); } }); CookingState.fallingItems = []; // Start music var musicIdToPlay = song.musicId || 'rhythmTrack'; LK.playMusic(musicIdToPlay); if (cookingElements && cookingElements.songNameText && !cookingElements.songNameText.destroyed) { cookingElements.songNameText.setText(song.name); } }; if (!storage.cookingWelcomeShown) { showCookingWelcomeMessage(function () { storage.cookingWelcomeShown = true; startGameLogic(songConfig); }); } else { startGameLogic(songConfig); } } function spawnRhythmItem() { var currentPattern = COOKING_CONFIG.PATTERNS[CookingState.currentPattern]; // Check if we should spawn on this beat if (currentPattern[CookingState.patternPosition] === 1) { var availableFishFromInventory = []; FishInventory.fishTypes.forEach(function (fishType) { if (FishInventory.getFishCount(fishType) > 0) { availableFishFromInventory.push(fishType); } }); if (availableFishFromInventory.length > 0) { // Weighted selection to prefer more expensive fish var weightedFishList = availableFishFromInventory.map(function (fishType) { return { type: fishType, weight: COOKING_CONFIG.FISH_MULTIPLIERS[fishType] || 1.0 }; }); var totalWeight = weightedFishList.reduce(function (sum, fish) { return sum + fish.weight; }, 0); var randomWeight = Math.random() * totalWeight; var fishType; for (var i = 0; i < weightedFishList.length; i++) { randomWeight -= weightedFishList[i].weight; if (randomWeight <= 0) { fishType = weightedFishList[i].type; break; } } // Fallback if no fish was chosen (should not happen with correct logic) if (!fishType) { fishType = weightedFishList[weightedFishList.length - 1].type; } FishInventory.removeFish(fishType, 1); var newItem = new RhythmItem(fishType, 0, 'raw', 1.0); // FIXED: Much longer travel time for normal speed var beatsToStation = 8; // 8 beats to reach station (much slower) var distanceToStation = COOKING_CONFIG.STATION_Y - COOKING_CONFIG.FALL_START_Y; var timeToReach = CookingState.beatInterval * beatsToStation; newItem.speed = distanceToStation / (timeToReach / (1000 / 60)); newItem.targetBeat = CookingState.currentBeat + beatsToStation; cookingScreen.addChild(newItem); CookingState.fallingItems.push(newItem); } else if (!CookingState.isFinishing) { CookingState.isFinishing = true; CookingState.outOfFish = true; LK.stopMusic(); } } // Advance pattern CookingState.patternPosition++; if (CookingState.patternPosition >= currentPattern.length) { CookingState.patternPosition = 0; CookingState.currentPattern = (CookingState.currentPattern + 1) % COOKING_CONFIG.PATTERNS.length; } } function handleCookingInput(x, y) { if (!CookingState.gameActive) { return; } // Find which item was tapped, prioritizing the one closest to the action line var tappedItem = null; var closestDistanceToStation = Infinity; for (var i = 0; i < CookingState.fallingItems.length; i++) { var item = CookingState.fallingItems[i]; if (!item || !item.sprite || item.sprite.destroyed) { continue; } // Calculate the item's tappable area, with a lenient buffer var touchLeniency = 1.5; // 50% larger tappable area var actualWidth = item.sprite.width * item.sprite.scaleX * touchLeniency; var actualHeight = item.sprite.height * item.sprite.scaleY * touchLeniency; var left = item.x - actualWidth / 2; var right = item.x + actualWidth / 2; var top = item.y - actualHeight / 2; var bottom = item.y + actualHeight / 2; // Check if the tap is inside the item's bounds if (x >= left && x <= right && y >= top && y <= bottom) { var distanceToStationLine = Math.abs(item.y - COOKING_CONFIG.STATION_Y); // If multiple items are tapped (e.g., stacked), prioritize the one nearest the hit zone. if (distanceToStationLine < closestDistanceToStation) { closestDistanceToStation = distanceToStationLine; tappedItem = item; } } } // If an item was tapped, process the action for its lane. if (tappedItem) { var stationInLane = CookingState.stations[tappedItem.lane]; if (stationInLane) { // processRhythmStation will find the closest item in the lane (our tappedItem) // and evaluate its position against the hit zone to determine score. processRhythmStation(stationInLane.lane, stationInLane.type); } } } function showCookingFeedback(type, laneIndex) { var station = CookingState.stations[laneIndex]; if (!station) { return; } var indicator = new FeedbackIndicator(type); indicator.x = station.x; indicator.y = station.y - 150; // Position feedback above the tool cookingScreen.addChild(indicator); indicator.show(); } function processRhythmStation(laneIndex, stationType) { // Find closest item in the lane var closestItem = null; var closestDistance = Infinity; for (var i = 0; i < CookingState.fallingItems.length; i++) { var item = CookingState.fallingItems[i]; if (item.lane === laneIndex) { var distance = Math.abs(item.y - COOKING_CONFIG.STATION_Y); if (distance < closestDistance) { closestDistance = distance; closestItem = item; } } } if (!closestItem || closestDistance > COOKING_CONFIG.MISS_WINDOW) { // Miss showCookingFeedback('miss', laneIndex); LK.getSound('miss').play(); return; } var timingMultiplier = 1.0; var catchType = ''; if (closestDistance < COOKING_CONFIG.PERFECT_WINDOW) { timingMultiplier = 1.0; catchType = stationType; } else if (closestDistance < COOKING_CONFIG.GOOD_WINDOW) { timingMultiplier = 1.0; catchType = stationType; } else { timingMultiplier = 0.5; catchType = stationType; } var indicatorType = catchType; if (indicatorType === 'grinder') { indicatorType = 'grind'; } else if (indicatorType === 'spices') { indicatorType = 'spice'; } showCookingFeedback(indicatorType, laneIndex); var catchSounds = ['catch', 'catch2', 'catch3', 'catch4']; var randomCatchSound = catchSounds[Math.floor(Math.random() * catchSounds.length)]; LK.getSound(randomCatchSound).play(); var item = closestItem; removeRhythmItem(item); var cumulativeMultiplier = item.timingMultiplier * timingMultiplier; // Get current recipe and processing steps var currentRecipe = RECIPE_SYSTEM.getCurrentRecipe(); var currentStep = null; // Find the processing step for this station and current item type for (var i = 0; i < currentRecipe.processingSteps.length; i++) { var step = currentRecipe.processingSteps[i]; if (step.station === stationType && step.from === item.processedType) { currentStep = step; break; } } if (!currentStep) { console.error('No processing step found for station:', stationType, 'item type:', item.processedType); return; } if (currentStep.produces === 'final') { // Complete the dish! completeDish(item.fishType, cumulativeMultiplier, currentRecipe); } else { // Process and send to next lane var newProcessedType = currentStep.produces; var newLane = laneIndex + 1; if (newLane < COOKING_CONFIG.LANE_COUNT) { var processedItem = new RhythmItem(item.fishType, newLane, newProcessedType, cumulativeMultiplier); // Same slower timing for processed items var beatsToNextStation = 8; var distanceToStation = COOKING_CONFIG.STATION_Y - COOKING_CONFIG.FALL_START_Y; var timeToReach = CookingState.beatInterval * beatsToNextStation; processedItem.speed = distanceToStation / (timeToReach / (1000 / 60)); processedItem.targetBeat = CookingState.currentBeat + beatsToNextStation; // Adjust spawn position to sync with beat var timeErrorMs = (item.y - COOKING_CONFIG.STATION_Y) / item.speed * (1000 / 60); var adjustedTimeToReachMs = timeToReach - timeErrorMs; var speedInPxPerFrame = processedItem.speed; var adjustedTravelFrames = adjustedTimeToReachMs / (1000 / 60); var adjustedDistanceInPx = speedInPxPerFrame * adjustedTravelFrames; processedItem.y = COOKING_CONFIG.STATION_Y - adjustedDistanceInPx; cookingScreen.addChild(processedItem); CookingState.fallingItems.push(processedItem); } } } function completeDish(fishType, timingMultiplier, recipe) { timingMultiplier = timingMultiplier || 1.0; var multiplier = COOKING_CONFIG.FISH_MULTIPLIERS[fishType] || 1.0; var earnings = Math.floor(recipe.baseValue * multiplier * timingMultiplier); CookingState.money += earnings; GameState.money += earnings; storage.money = GameState.money; // Capitalize fish name var fishName = fishType.charAt(0).toUpperCase() + fishType.slice(1); var timingText = ''; if (timingMultiplier >= 2.0) { timingText = ' PERFECT'; } else if (timingMultiplier > 0.5) { timingText = ''; } if (typeof animateCustomerLine === 'function') { animateCustomerLine(); } // Show completion popup showCompletionPopup(fishName + " " + recipe.name + timingText + "! +$" + earnings, recipe.finalAsset); } function showCompletionPopup(text, dishAsset) { var popupContainer = cookingScreen.addChild(new Container()); popupContainer.x = COOKING_CONFIG.SCREEN_WIDTH / 2; popupContainer.y = COOKING_CONFIG.SCREEN_HEIGHT / 2; var popup = popupContainer.addChild(new Text2(text, { size: 65, fill: 0xFFD700, stroke: 0x000000, strokeThickness: 3, align: 'center' })); popup.anchor.set(0.5, 0.5); var dishIcon = popupContainer.addChild(LK.getAsset(dishAsset, { anchorX: 0.5, anchorY: 0, scaleX: 2.5, scaleY: 2.5 })); dishIcon.y = popup.y + popup.height / 2 + 20; tween(popupContainer, { y: popupContainer.y - 150, alpha: 0 }, { duration: 2500, delay: 500, easing: tween.easeOut, onFinish: function onFinish() { if (popupContainer && !popupContainer.destroyed) { popupContainer.destroy(); } } }); } function removeRhythmItem(item) { var index = CookingState.fallingItems.indexOf(item); if (index > -1) { CookingState.fallingItems.splice(index, 1); } if (item && !item.destroyed) { if (typeof item.cleanupSparkles === 'function') { item.cleanupSparkles(); } item.destroy(); } } function updateCookingGame() { if (!CookingState.gameActive) { return; } var currentTime = LK.ticks * (1000 / 60); var songElapsed = currentTime - CookingState.songStartTime; if (cookingElements && cookingElements.countdownText && !cookingElements.countdownText.destroyed) { var timeRemaining = Math.max(0, CookingState.songDuration - songElapsed); cookingElements.countdownText.setText(formatTime(timeRemaining)); } // Check if song has finished, and if so, stop spawning. if (!CookingState.isFinishing && CookingState.songStartTime > 0 && songElapsed >= CookingState.songDuration) { CookingState.isFinishing = true; LK.stopMusic(); } // If the song is over and all items are cleared, end the game. if (CookingState.isFinishing && CookingState.fallingItems.length === 0) { endCookingGame(); return; } // Update beat counter and spawn items only if song is not finishing if (!CookingState.isFinishing) { var newBeat = Math.floor(songElapsed / CookingState.beatInterval); // Spawn items on beat changes if (newBeat > CookingState.currentBeat && newBeat >= CookingState.nextSpawnBeat) { CookingState.currentBeat = newBeat; spawnRhythmItem(); CookingState.nextSpawnBeat = newBeat + 1; // Next beat } } // Update all falling items for (var i = CookingState.fallingItems.length - 1; i >= 0; i--) { var item = CookingState.fallingItems[i]; item.update(); // Check if item missed (went past station) if (item.y > COOKING_CONFIG.STATION_Y + 150) { item.fadeAway(); CookingState.fallingItems.splice(i, 1); } } // Pulse tools based on occupied lanes and BPM var occupiedLanes = [false, false, false]; for (var j = 0; j < CookingState.fallingItems.length; j++) { var fallingItem = CookingState.fallingItems[j]; if (fallingItem && !fallingItem.destroyed) { occupiedLanes[fallingItem.lane] = true; } } var beatProgress = songElapsed % CookingState.beatInterval / CookingState.beatInterval; var scalePulse = 1 + Math.sin(beatProgress * Math.PI) * 0.15; for (var k = 0; k < CookingState.stations.length; k++) { var station = CookingState.stations[k]; if (station && station.sprite && !station.sprite.destroyed) { var baseScale = 2.5; if (station.isPulsing === undefined) { station.isPulsing = false; } if (occupiedLanes[k]) { // Lane is occupied, pulse it tween.stop(station.sprite.scale); // Stop any return-to-normal tween station.sprite.scale.set(baseScale * scalePulse); station.isPulsing = true; } else { // Lane is empty if (station.isPulsing) { // It was pulsing, now it's not. Smoothly return to base scale. station.isPulsing = false; tween.stop(station.sprite.scale); // Stop just in case tween(station.sprite.scale, { x: baseScale, y: baseScale }, { duration: 200, easing: tween.easeOut }); } } } } } function endCookingGame() { if (!CookingState.gameActive) { return; } CookingState.gameActive = false; LK.stopMusic(); CookingState.fallingItems.forEach(function (item) { if (item && !item.destroyed) { item.destroy(); } }); CookingState.fallingItems = []; if (CookingState.stations) { CookingState.stations.forEach(function (station) { if (station && station.sprite && !station.sprite.destroyed) { tween.stop(station.sprite.scale); } }); } // New animation sequence tween(cookingScreen, { alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { cookingScreen.visible = false; cookingScreen.alpha = 1; // Reset for next time. // "Day complete!" text animation var message = CookingState.outOfFish ? "Out of fish!" : "Day complete!"; CookingState.outOfFish = false; // Reset for next session var dayCompleteText = game.addChild(new Text2(message, { size: 150, fill: '#FFFFFF', stroke: '#000000', strokeThickness: 8, align: 'center' })); dayCompleteText.anchor.set(0.5, 0.5); dayCompleteText.x = GAME_CONFIG.SCREEN_CENTER_X; dayCompleteText.y = -200; // Start off-screen top // Bring text to front, but behind global fade overlay if (game.children.indexOf(globalFadeOverlay) !== -1) { game.setChildIndex(dayCompleteText, game.children.indexOf(globalFadeOverlay)); } else { game.setChildIndex(dayCompleteText, game.children.length - 1); } tween(dayCompleteText, { y: GAME_CONFIG.SCREEN_CENTER_Y }, { duration: 1500, easing: tween.easeOut }); // Reverse beach intro animation playBeachOutroAnimation(); // Fade the whole screen to black. globalFadeOverlay.visible = true; globalFadeOverlay.alpha = 0; game.setChildIndex(globalFadeOverlay, game.children.length - 1); tween(globalFadeOverlay, { alpha: 1 }, { duration: 2500, delay: 500, easing: tween.easeIn, onFinish: function onFinish() { if (dayCompleteText && !dayCompleteText.destroyed) { dayCompleteText.destroy(); } globalFadeOverlay.visible = false; globalFadeOverlay.alpha = 0; _showScreen('cookingResults'); } }); } }); } function createCookingResultsScreen() { cookingResultsScreen.removeChildren(); var resultsBg = cookingResultsScreen.addChild(LK.getAsset('screenBackground', { x: 0, y: 0, alpha: 0.9, height: 2732 })); var title = new Text2('Order Complete!', { size: 100, fill: 0xFFFFFF }); title.anchor.set(0.5, 0.5); title.x = COOKING_CONFIG.SCREEN_WIDTH / 2; title.y = 600; cookingResultsScreen.addChild(title); var moneyEarned = new Text2('Money Earned: $' + CookingState.money, { size: 80, fill: 0xFFD700 }); moneyEarned.anchor.set(0.5, 0.5); moneyEarned.x = COOKING_CONFIG.SCREEN_WIDTH / 2; moneyEarned.y = 800; cookingResultsScreen.addChild(moneyEarned); var continueButton = cookingResultsScreen.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: COOKING_CONFIG.SCREEN_WIDTH / 2, y: 1200 })); var continueText = new Text2('BACK TO MAP', { size: 50, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null }); continueText.anchor.set(0.5, 0.5); continueText.x = continueButton.x; continueText.y = continueButton.y; cookingResultsScreen.addChild(continueText); } /**** * Title Screen ****/ function createTitleScreen() { var titleBg = titleScreen.addChild(LK.getAsset('screenBackground', { x: 0, y: 0, alpha: 1, height: 2732, color: 0x87CEEB })); var titleAnimationGroup = titleScreen.addChild(new Container()); var titleSky = titleAnimationGroup.addChild(LK.getAsset('skybackground', { x: 0, y: -500 })); var titleWater = titleAnimationGroup.addChild(LK.getAsset('water', { x: 0, y: GAME_CONFIG.WATER_SURFACE_Y, width: 2048, height: 2732 - GAME_CONFIG.WATER_SURFACE_Y })); titleScreenOceanBubbleContainer = titleAnimationGroup.addChild(new Container()); titleScreenSeaweedContainer = titleAnimationGroup.addChild(new Container()); titleScreenCloudContainer = titleAnimationGroup.addChild(new Container()); var titleBoatGroup = titleAnimationGroup.addChild(new Container()); var titleBoat = titleBoatGroup.addChild(LK.getAsset('boat', { anchorX: 0.5, anchorY: 0.74, x: 0, y: 0 })); var titleFisherman = titleBoatGroup.addChild(LK.getAsset('fisherman', { anchorX: 0.5, anchorY: 1, x: -100, y: -70 })); var rodTipX = -100 + 85; var rodTipY = -70 - 200; var initialHookYInGroup = GAME_CONFIG.LANES[1].y - GAME_CONFIG.WATER_SURFACE_Y; var titleLine = titleBoatGroup.addChild(LK.getAsset('fishingLine', { anchorX: 0.5, anchorY: 0, x: rodTipX, y: rodTipY, width: 6, height: initialHookYInGroup - rodTipY })); var titleHook = titleBoatGroup.addChild(LK.getAsset('hook', { anchorX: 0.5, anchorY: 0.5, x: rodTipX, y: initialHookYInGroup })); titleBoatGroup.x = GAME_CONFIG.SCREEN_CENTER_X; titleBoatGroup.y = GAME_CONFIG.WATER_SURFACE_Y; var boatGroupBaseY = titleBoatGroup.y; var boatWaveAmplitude = 10; var boatWaveHalfCycleDuration = 2000; var boatRotationAmplitude = 0.03; var boatRotationDuration = 3000; var lineWaveAmplitude = 12; var lineWaveSpeed = 0.03; var linePhaseOffset = 0; var titleWaterSurfaceSegments = []; var NUM_WAVE_SEGMENTS_TITLE = 32; var SEGMENT_WIDTH_TITLE = 2048 / NUM_WAVE_SEGMENTS_TITLE; var SEGMENT_HEIGHT_TITLE = 24; var WAVE_AMPLITUDE_TITLE = 12; var WAVE_HALF_PERIOD_MS_TITLE = 2500; var PHASE_DELAY_MS_PER_SEGMENT_TITLE = WAVE_HALF_PERIOD_MS_TITLE * 2 / NUM_WAVE_SEGMENTS_TITLE; for (var i = 0; i < NUM_WAVE_SEGMENTS_TITLE; i++) { var segment = LK.getAsset('waterSurface', { x: i * SEGMENT_WIDTH_TITLE, y: GAME_CONFIG.WATER_SURFACE_Y, width: SEGMENT_WIDTH_TITLE + 1, height: SEGMENT_HEIGHT_TITLE, anchorX: 0, anchorY: 0.5, alpha: 0.8, tint: 0x4fc3f7 }); segment.baseY = GAME_CONFIG.WATER_SURFACE_Y; titleAnimationGroup.addChild(segment); titleWaterSurfaceSegments.push(segment); } for (var i = 0; i < NUM_WAVE_SEGMENTS_TITLE; i++) { var whiteSegment = LK.getAsset('waterSurface', { x: i * SEGMENT_WIDTH_TITLE, y: GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT_TITLE / 2, width: SEGMENT_WIDTH_TITLE + 1, height: SEGMENT_HEIGHT_TITLE / 2, anchorX: 0, anchorY: 0.5, alpha: 0.6, tint: 0xffffff }); whiteSegment.baseY = GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT_TITLE / 2; titleAnimationGroup.addChild(whiteSegment); titleWaterSurfaceSegments.push(whiteSegment); } var boatCenterX = GAME_CONFIG.SCREEN_CENTER_X; var targetBoatScreenY = GAME_CONFIG.SCREEN_CENTER_Y + 300; var boatWorldY = GAME_CONFIG.WATER_SURFACE_Y; var pivotY = boatWorldY - (targetBoatScreenY - boatWorldY); titleAnimationGroup.pivot.set(boatCenterX, pivotY); titleAnimationGroup.x = GAME_CONFIG.SCREEN_CENTER_X; titleAnimationGroup.y = GAME_CONFIG.SCREEN_CENTER_Y; var INITIAL_ZOOM_FACTOR = 3.0; var FINAL_ZOOM_FACTOR = 1.8; titleAnimationGroup.scale.set(INITIAL_ZOOM_FACTOR); titleAnimationGroup.alpha = 1; var targetUpY = boatGroupBaseY - boatWaveAmplitude; var targetDownY = boatGroupBaseY + boatWaveAmplitude; function moveTitleBoatGroupUp() { if (!titleBoatGroup || titleBoatGroup.destroyed) { return; } tween(titleBoatGroup, { y: targetUpY }, { duration: boatWaveHalfCycleDuration, easing: tween.easeInOut, onFinish: moveTitleBoatGroupDown }); } function moveTitleBoatGroupDown() { if (!titleBoatGroup || titleBoatGroup.destroyed) { return; } tween(titleBoatGroup, { y: targetDownY }, { duration: boatWaveHalfCycleDuration, easing: tween.easeInOut, onFinish: moveTitleBoatGroupUp }); } function rockTitleBoatGroupLeft() { if (!titleBoatGroup || titleBoatGroup.destroyed) { return; } tween(titleBoatGroup, { rotation: -boatRotationAmplitude }, { duration: boatRotationDuration, easing: tween.easeInOut, onFinish: rockTitleBoatGroupRight }); } function rockTitleBoatGroupRight() { if (!titleBoatGroup || titleBoatGroup.destroyed) { return; } tween(titleBoatGroup, { rotation: boatRotationAmplitude }, { duration: boatRotationDuration, easing: tween.easeInOut, onFinish: rockTitleBoatGroupLeft }); } function updateTitleFishingLineWave() { if (!titleLine || titleLine.destroyed || !titleHook || titleHook.destroyed) { return; } linePhaseOffset += lineWaveSpeed; var waveOffset = Math.sin(linePhaseOffset) * lineWaveAmplitude; titleLine.x = rodTipX + waveOffset * 0.3; titleHook.x = rodTipX + waveOffset; var deltaX = titleHook.x - titleLine.x; var deltaY = titleHook.y - titleLine.y; var actualLineLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY); titleLine.height = actualLineLength; if (actualLineLength > 0.001) { titleLine.rotation = Math.atan2(deltaY, deltaX) - Math.PI / 2; } else { titleLine.rotation = 0; } titleHook.rotation = titleLine.rotation; } function startTitleWaterSurfaceAnimationFunc() { for (var k = 0; k < titleWaterSurfaceSegments.length; k++) { var segment = titleWaterSurfaceSegments[k]; if (!segment || segment.destroyed) { continue; } var segmentIndexForDelay = k % NUM_WAVE_SEGMENTS_TITLE; (function (currentLocalSegment, currentLocalSegmentIndexForDelay) { var waveAnim = createWaveAnimation(currentLocalSegment, WAVE_AMPLITUDE_TITLE, WAVE_HALF_PERIOD_MS_TITLE); LK.setTimeout(function () { if (!currentLocalSegment || currentLocalSegment.destroyed) { return; } tween(currentLocalSegment, { y: currentLocalSegment.baseY + WAVE_AMPLITUDE_TITLE }, { duration: WAVE_HALF_PERIOD_MS_TITLE, easing: tween.easeInOut, onFinish: waveAnim.up }); }, currentLocalSegmentIndexForDelay * PHASE_DELAY_MS_PER_SEGMENT_TITLE); })(segment, segmentIndexForDelay); } } var blackOverlay = titleScreen.addChild(LK.getAsset('screenBackground', { x: 0, y: 0, width: 2048, height: 2732, color: 0x000000, alpha: 1 })); var titleImage = titleScreen.addChild(LK.getAsset('titleimage', { anchorX: 0.5, anchorY: 0.5, x: GAME_CONFIG.SCREEN_CENTER_X, y: 700, alpha: 0, scaleX: 0.8, scaleY: 0.8 })); var startButtonY = 2732 - 2732 / 3.5; var tutorialButtonY = startButtonY + 600; var startButton = titleScreen.addChild(LK.getAsset('startbutton', { anchorX: 0.5, anchorY: 0.5, x: GAME_CONFIG.SCREEN_CENTER_X, y: startButtonY, alpha: 0 })); var tutorialButton = titleScreen.addChild(LK.getAsset('tutorialbutton', { anchorX: 0.5, anchorY: 0.5, x: GAME_CONFIG.SCREEN_CENTER_X, y: tutorialButtonY, alpha: 0 })); return { startButton: startButton, tutorialButton: tutorialButton, titleImage: titleImage, titleAnimationGroup: titleAnimationGroup, blackOverlay: blackOverlay, titleBoatGroup: titleBoatGroup, moveTitleBoatGroupUp: moveTitleBoatGroupUp, rockTitleBoatGroupLeft: rockTitleBoatGroupLeft, titleSky: titleSky, titleWater: titleWater, titleWaterSurfaceSegments: titleWaterSurfaceSegments, titleLine: titleLine, titleHook: titleHook, startTitleWaterSurfaceAnimation: startTitleWaterSurfaceAnimationFunc, updateTitleFishingLineWave: updateTitleFishingLineWave }; } /**** * Level Select Screen ****/ var MAP_CONFIG = { NODES: { dock: { x: 1200, y: 860, unlocked: true, type: 'dock' }, shallows: { x: 524, y: 1400, unlocked: true, type: 'fishing', depthIndex: 0 }, medium: { x: 1324, y: 1700, unlocked: false, type: 'fishing', depthIndex: 1 }, deep: { x: 524, y: 2000, unlocked: false, type: 'fishing', depthIndex: 2 }, abyss: { x: 1524, y: 2300, unlocked: false, type: 'fishing', depthIndex: 3 }, shop: { x: 724, y: 500, unlocked: false, type: 'shop' }, restaurant: { x: 1324, y: 500, unlocked: false, // Unlocked as per prompt type: 'cooking' // Changed to 'cooking' as per prompt } }, CONNECTIONS: [['dock', 'shallows'], ['shallows', 'medium'], ['medium', 'deep'], ['deep', 'abyss'], ['dock', 'shop'], ['dock', 'restaurant'], ['dock', 'medium']], BOAT_BOB_AMPLITUDE: 8, BOAT_BOB_DURATION: 2000, BOAT_ROTATION_AMPLITUDE: 0.02, BOAT_ROTATION_DURATION: 3000, BOAT_TRAVEL_SPEED: 300 }; function createLevelSelectScreen() { levelSelectScreen.removeChildren(); var nodeNameTexts = {}; function getNodeDisplayName(nodeKey) { var nodeNames = { dock: 'Dock', shallows: 'Shallow Waters', medium: 'Mid Waters', deep: 'Deep Waters', abyss: 'The Abyss', shop: 'Fishing Shop', restaurant: 'The Fish Shack' // Updated name }; return nodeNames[nodeKey] || 'Unknown'; } var mapBg = levelSelectScreen.addChild(LK.getAsset('mapBackground', { x: 0, y: 0 })); var moneyDisplayBackground = levelSelectScreen.addChild(LK.getAsset('songCard', { anchorX: 1, anchorY: 0, x: 1900 + 20 + 15 + 20, y: 80 - 10 + 15 + 20, width: 450, height: 100, color: 0x000000, alpha: 0.5 })); var ripplesContainer = levelSelectScreen.addChild(new Container()); var activeRipples = []; var rippleSpawnTimerId = null; var RIPPLE_SPAWN_INTERVAL_MS = 350; var RIPPLE_INITIAL_SCALE = 0.1; var RIPPLE_FINAL_SCALE = 1.8; var RIPPLE_INITIAL_OFFSET = 40; var RIPPLE_TRAVEL_DISTANCE = 140; var RIPPLE_DURATION_MS = 1800; var homeIslandRipplesContainer = levelSelectScreen.addChild(new Container()); var activeHomeIslandRipples = []; var homeIslandRippleSpawnTimerId = null; var HOME_ISLAND_RIPPLE_SPAWN_INTERVAL_MS = 225; var HOME_ISLAND_RIPPLE_INITIAL_SCALE = 0.1; var HOME_ISLAND_RIPPLE_FINAL_SCALE = 1.875; var HOME_ISLAND_RIPPLE_INITIAL_OFFSET_FROM_EDGE = 10; var HOME_ISLAND_RIPPLE_TRAVEL_DISTANCE = 220; var HOME_ISLAND_RIPPLE_DURATION_MS = 2000; var homeIsland = levelSelectScreen.addChild(LK.getAsset('homeisland', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 500 })); var shallowWatersNodeBubblesContainer = levelSelectScreen.addChild(new Container()); var midWatersNodeBubblesContainer = levelSelectScreen.addChild(new Container()); shadowFishContainer = levelSelectScreen.addChild(new Container()); var activeShallowWatersNodeBubbles = []; var activeMidWatersNodeBubbles = []; var shallowWatersNodeBubbleSpawnTimerId = null; var SHALLOW_NODE_BUBBLE_SPAWN_INTERVAL_MS = 350 + Math.random() * 250; var levelSelectCloudContainer; var activeLevelSelectClouds = []; var levelSelectCloudSpawnTimerId = null; var LEVEL_SELECT_CLOUD_SPAWN_INTERVAL_MS = 4000 + Math.random() * 3000; var MAX_LEVEL_SELECT_CLOUDS = 6; var shadowFishContainer; var activeShadowFish = []; var shallowShadowFishSpawnTimerId = null; var midWatersShadowFishSpawnTimerId = null; var moneyDisplay = new Text2('$0', { size: 70, fill: 0xFFD700, stroke: 0x000000, strokeThickness: 3 }); moneyDisplay.anchor.set(1, 0); moneyDisplay.x = 1900 + 15 + 20; moneyDisplay.y = 80 + 15 + 20; levelSelectScreen.addChild(moneyDisplay); // Inventory Button Background var inventoryButtonBg = levelSelectScreen.addChild(LK.getAsset('songCard', { anchorX: 1, anchorY: 0, x: 1900 + 50, y: 250, width: 375, height: 100, color: 0x424242, alpha: 0.8 })); // Inventory Button (Interactive Element) var inventoryButton = levelSelectScreen.addChild(LK.getAsset('button', { anchorX: 1, anchorY: 0, x: 1900 + 50, y: 250, width: 375, height: 100, tint: 0x1976d2, alpha: 0 })); // Inventory Button Text var inventoryButtonText = new Text2('INVENTORY', { size: 57, fill: 0xFFFFFF }); inventoryButtonText.anchor.set(1, 0); inventoryButtonText.x = 1920; inventoryButtonText.y = 250 + (100 - 57) / 2; // Centered vertically levelSelectScreen.addChild(inventoryButtonText); // Inventory Dropdown Container var inventoryDropdownContainer = levelSelectScreen.addChild(new Container()); inventoryDropdownContainer.visible = false; var inventoryDropdownOpen = false; function createInventoryDropdown() { inventoryDropdownContainer.removeChildren(); var inventory = FishInventory.getInventoryDisplay(); if (inventory.length === 0) { // Show "No fish caught yet" message var dropdownBaseY = 250 + 100 + 20; // button top + button height + padding var emptyBg = inventoryDropdownContainer.addChild(LK.getAsset('songCard', { anchorX: 1, anchorY: 0, x: 1900 + 50, y: dropdownBaseY, width: 600, height: 150, color: 0x424242, alpha: 0.9 })); var emptyText = inventoryDropdownContainer.addChild(new Text2('No fish caught yet', { size: 68, fill: 0xCCCCCC, align: 'center' })); emptyText.anchor.set(0.5, 0.5); emptyText.x = 1900 + 50 - 600 / 2; // Centered in emptyBg emptyText.y = dropdownBaseY + 150 / 2; // Centered in emptyBg height return; } // Calculate dropdown size var itemHeight = 180; var dropdownBaseY = 250 + 100 + 20; var dropdownVerticalPadding = 30; var dropdownHeight = inventory.length * itemHeight + dropdownVerticalPadding; var dropdownWidth = 675; // Background var dropdownBg = inventoryDropdownContainer.addChild(LK.getAsset('songCard', { anchorX: 1, anchorY: 0, x: 1900 + 50, y: dropdownBaseY, width: dropdownWidth, height: dropdownHeight, color: 0x424242, alpha: 0.9 })); // Fish items var itemTopPadding = 15; var itemImageLeftPadding = 30; var itemImageYOffset = 15; var textYOffset = 23; for (var i = 0; i < inventory.length; i++) { var fishData = inventory[i]; var itemY = dropdownBaseY + itemTopPadding + i * itemHeight; // Fish image var fishImage = inventoryDropdownContainer.addChild(LK.getAsset(fishData.type, { anchorX: 0, anchorY: 0, x: 1900 + 50 - dropdownWidth + itemImageLeftPadding, y: itemY + itemImageYOffset, scaleX: 0.6, scaleY: 0.6 })); // Fish name var fishNameText = inventoryDropdownContainer.addChild(new Text2(fishData.name, { size: 63, fill: 0xFFFFFF })); fishNameText.anchor.set(0, 0); // Calculate fish image display width and position name text var fishImageDisplayWidth = LK.getAsset(fishData.type, {}).width * 0.6; fishNameText.x = fishImage.x + fishImageDisplayWidth + 20 * 1.5; fishNameText.y = itemY + textYOffset; // Fish count var fishCountText = inventoryDropdownContainer.addChild(new Text2('x' + fishData.count, { size: 63, fill: 0xFFD700 })); fishCountText.anchor.set(1, 0); fishCountText.x = 1900 + 50 - itemImageLeftPadding; // Align with right edge fishCountText.y = itemY + textYOffset; } } function toggleInventoryDropdown() { if (inventoryDropdownOpen) { // Close dropdown inventoryDropdownContainer.visible = false; inventoryDropdownOpen = false; } else { // Open dropdown createInventoryDropdown(); inventoryDropdownContainer.visible = true; if (levelSelectScreen && inventoryDropdownContainer.parent === levelSelectScreen) { levelSelectScreen.setChildIndex(inventoryDropdownContainer, levelSelectScreen.children.length - 1); } inventoryDropdownOpen = true; } } var backButtonBg = levelSelectScreen.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2600, width: 400, height: 130, alpha: 0.92 })); var backButton = levelSelectScreen.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2600, tint: 0x757575, width: 700, height: 170, alpha: 0 })); var backButtonText = new Text2('BACK TO TITLE', { size: 50, fill: 0xFFFFFF }); backButtonText.anchor.set(0.5, 0.5); backButtonText.x = backButton.x; backButtonText.y = backButton.y; levelSelectScreen.addChild(backButtonText); var dottedLinesContainer = levelSelectScreen.addChild(new Container()); var nodesContainer = levelSelectScreen.addChild(new Container()); var boatContainer = levelSelectScreen.addChild(new Container()); var songOverlayContainer = levelSelectScreen.addChild(new Container()); songOverlayContainer.visible = false; var overlayBg = songOverlayContainer.addChild(LK.getAsset('songCard', { x: 1024, y: 1366, width: 1700, height: 900, color: 0x424242, anchorX: 0.5, anchorY: 0.5, alpha: 0.95 })); var closeButton = songOverlayContainer.addChild(LK.getAsset('closeButton', { anchorX: 0.5, anchorY: 0.5, x: 1700, y: 1026, width: 100, height: 100, tint: 0xff4444 })); var closeButtonText = new Text2('X', { size: 60, fill: 0xFFFFFF }); closeButtonText.anchor.set(0.5, 0.5); closeButtonText.x = closeButton.x; closeButtonText.y = closeButton.y; songOverlayContainer.addChild(closeButtonText); var songElements = { leftArrow: null, rightArrow: null, songTitle: null, songInfo: null, songEarnings: null, playButton: null, playButtonText: null, leftArrowText: null, rightArrowText: null, difficultyEasyButton: null, difficultyEasyButtonText: null, difficultyMediumButton: null, difficultyMediumButtonText: null }; var currentNode = GameState.lastLevelSelectNodeKey; var initialBoatNodeConfig = MAP_CONFIG.NODES[currentNode]; var playerBoat = boatContainer.addChild(LK.getAsset('playerBoat', { anchorX: 0.5, anchorY: 0.5, x: initialBoatNodeConfig.x, y: initialBoatNodeConfig.y })); var boatMoving = false; var _songOverlayOpen = false; var selectedDepth = 0; var selectedSong = 0; var boatBaseY = playerBoat.y; var boatBobPhase = 0; var boatRotationPhase = 0; var levelSelectSeagullSoundTimer = null; var levelSelectBoatSoundTimer = null; var levelSelectScreenWavesArray = []; var levelSelectScreenWaveSpawnTimerId = null; var SCREEN_WAVE_SPAWN_INTERVAL_MS = (200 + Math.random() * 300) * 4 / 3; var seagullsContainer = levelSelectScreen.addChild(new Container()); var activeSeagulls = []; var seagullSpawnTimerId = null; var SEAGULL_SPAWN_INTERVAL_MS = 3500 + Math.random() * 2500; levelSelectCloudContainer = levelSelectScreen.addChild(new Container()); // Create ambient sound schedulers var seagullScheduler = createAmbientSoundScheduler({ screenName: 'levelSelect', sounds: ['seagull1', 'seagull2', 'seagull3'], baseDelay: 5000, variance: 10000 }); var boatScheduler = createAmbientSoundScheduler({ screenName: 'levelSelect', sounds: 'boatsounds', baseDelay: 6000, variance: 0 }); function startLevelSelectAmbientSounds() { var initialSeagullSounds = ['seagull1', 'seagull2', 'seagull3']; var initialRandomSoundId = initialSeagullSounds[Math.floor(Math.random() * initialSeagullSounds.length)]; LK.getSound(initialRandomSoundId).play(); LK.getSound('boatsounds').play(); seagullScheduler.start(); boatScheduler.start(); } function stopLevelSelectAmbientSounds() { seagullScheduler.stop(); boatScheduler.stop(); } // Initialize clouds at screen load for (var i = 0; i < 3; i++) { var initialCloud = new MapScreenCloudParticle(); initialCloud.x = 400 + Math.random() * 1248; initialCloud.y = 300 + Math.random() * 1700; initialCloud.vx = (Math.random() < 0.5 ? 1 : -1) * (0.15 + Math.random() * 0.2) * 1.15; if (initialCloud.gfx && typeof initialCloud.gfx.alpha === "number") { initialCloud.gfx.alpha = 0.4 + Math.random() * 0.3; } levelSelectCloudContainer.addChild(initialCloud); activeLevelSelectClouds.push(initialCloud); } // Spawn an initial seagull and shadow fish on screen load var seagull = new SeagullParticle(); seagullsContainer.addChild(seagull); activeSeagulls.push(seagull); var shallowNodeData = MAP_CONFIG.NODES.shallows; if (shallowNodeData && shallowNodeData.unlocked) { var fish = new ShadowFishParticle(shallowNodeData.x, shallowNodeData.y); shadowFishContainer.addChild(fish); activeShadowFish.push(fish); } function spawnRippleEffect() { if (!playerBoat || playerBoat.destroyed || GameState.currentScreen !== 'levelSelect') { return; } var boatX = playerBoat.x; var boatY = playerBoat.y; var spawnAngle = Math.random() * Math.PI * 2; var ripple = new RippleParticle(boatX, boatY, spawnAngle, RIPPLE_INITIAL_OFFSET, RIPPLE_TRAVEL_DISTANCE, RIPPLE_INITIAL_SCALE, RIPPLE_FINAL_SCALE, RIPPLE_DURATION_MS, 1.0); ripplesContainer.addChild(ripple); activeRipples.push(ripple); } function spawnHomeIslandRippleEffect() { if (!homeIsland || homeIsland.destroyed || GameState.currentScreen !== 'levelSelect') { return; } var islandCenterX = homeIsland.x; var islandCenterY = homeIsland.y; var islandRadius = homeIsland.width / 2 * 0.75; var spawnEdgeAngle = Math.random() * Math.PI; var edgeX = islandCenterX + islandRadius * Math.cos(spawnEdgeAngle); var edgeY = islandCenterY + islandRadius * Math.sin(spawnEdgeAngle); var ripple = new RippleParticle(edgeX, edgeY, spawnEdgeAngle, HOME_ISLAND_RIPPLE_INITIAL_OFFSET_FROM_EDGE, HOME_ISLAND_RIPPLE_TRAVEL_DISTANCE, HOME_ISLAND_RIPPLE_INITIAL_SCALE, HOME_ISLAND_RIPPLE_FINAL_SCALE, HOME_ISLAND_RIPPLE_DURATION_MS, 0.5); homeIslandRipplesContainer.addChild(ripple); activeHomeIslandRipples.push(ripple); } function spawnLevelSelectScreenWaveEffect() { if (GameState.currentScreen !== 'levelSelect' || !ripplesContainer || ripplesContainer.destroyed) { return; } var movesRight = Math.random() < 0.5; var wave = new WaveParticle(movesRight); ripplesContainer.addChild(wave); levelSelectScreenWavesArray.push(wave); } function spawnShallowWatersNodeBubbleEffect() { if (GameState.currentScreen !== 'levelSelect' || !shallowWatersNodeBubblesContainer || shallowWatersNodeBubblesContainer.destroyed) { return; } var shallowNodePos = MAP_CONFIG.NODES.shallows; if (!shallowNodePos || !shallowNodePos.unlocked) { return; } var spawnX = shallowNodePos.x; var spawnY = shallowNodePos.y + 250 + (Math.random() - 0.5) * 200; var bubble = new MapBubbleParticle(spawnX, spawnY); shallowWatersNodeBubblesContainer.addChild(bubble); activeShallowWatersNodeBubbles.push(bubble); } function spawnMidWatersNodeBubbleEffect() { if (GameState.currentScreen !== 'levelSelect' || !midWatersNodeBubblesContainer || midWatersNodeBubblesContainer.destroyed) { return; } var midNodePos = MAP_CONFIG.NODES.medium; if (!midNodePos || !midNodePos.unlocked) { return; } var spawnX = midNodePos.x; var spawnY = midNodePos.y + 250 + (Math.random() - 0.5) * 200; var bubble = new MapBubbleParticle(spawnX, spawnY); midWatersNodeBubblesContainer.addChild(bubble); activeMidWatersNodeBubbles.push(bubble); } function spawnSeagullEffect() { if (GameState.currentScreen !== 'levelSelect' || !seagullsContainer || seagullsContainer.destroyed) { return; } var seagull = new SeagullParticle(); seagullsContainer.addChild(seagull); activeSeagulls.push(seagull); } function spawnLevelSelectCloudEffect() { if (GameState.currentScreen !== 'levelSelect' || !levelSelectCloudContainer || levelSelectCloudContainer.destroyed || activeLevelSelectClouds.length >= MAX_LEVEL_SELECT_CLOUDS) { return; } var cloud = new MapScreenCloudParticle(); levelSelectCloudContainer.addChild(cloud); activeLevelSelectClouds.push(cloud); } function spawnShallowWatersShadowFishEffect() { if (GameState.currentScreen !== 'levelSelect' || !shadowFishContainer || shadowFishContainer.destroyed) { return; } var shallowNodePos = MAP_CONFIG.NODES.shallows; if (shallowNodePos && shallowNodePos.unlocked) { var fish = new ShadowFishParticle(shallowNodePos.x, shallowNodePos.y); shadowFishContainer.addChild(fish); activeShadowFish.push(fish); } } function spawnMidWatersShadowFishEffect() { if (GameState.currentScreen !== 'levelSelect' || !shadowFishContainer || shadowFishContainer.destroyed) { return; } var midWatersNodePos = MAP_CONFIG.NODES.medium; if (midWatersNodePos && midWatersNodePos.unlocked) { var fish = new ShadowFishParticle(midWatersNodePos.x, midWatersNodePos.y); shadowFishContainer.addChild(fish); activeShadowFish.push(fish); } } var waterfallParticlesContainer = levelSelectScreen.addChild(new Container()); var activeWaterfallParticles = []; var waterfallSpawnTimerId = null; var WATERFALL_SPAWN_INTERVAL_MS = 80; var waterfallSpawnX = homeIsland.x + 20; var waterfallSpawnY = homeIsland.y - homeIsland.height / 2 + 350; function spawnWaterfallParticleEffect() { if (GameState.currentScreen !== 'levelSelect' || !waterfallParticlesContainer || waterfallParticlesContainer.destroyed) { return; } var particle = new WaterfallParticle(waterfallSpawnX, waterfallSpawnY); waterfallParticlesContainer.addChild(particle); activeWaterfallParticles.push(particle); } // Start timers rippleSpawnTimerId = LK.setInterval(spawnRippleEffect, RIPPLE_SPAWN_INTERVAL_MS); homeIslandRippleSpawnTimerId = LK.setInterval(spawnHomeIslandRippleEffect, HOME_ISLAND_RIPPLE_SPAWN_INTERVAL_MS); levelSelectScreenWaveSpawnTimerId = LK.setInterval(spawnLevelSelectScreenWaveEffect, SCREEN_WAVE_SPAWN_INTERVAL_MS); waterfallSpawnTimerId = LK.setInterval(spawnWaterfallParticleEffect, WATERFALL_SPAWN_INTERVAL_MS); shallowWatersNodeBubbleSpawnTimerId = LK.setInterval(spawnShallowWatersNodeBubbleEffect, SHALLOW_NODE_BUBBLE_SPAWN_INTERVAL_MS); var midWatersNodeBubbleSpawnTimerId = LK.setInterval(spawnMidWatersNodeBubbleEffect, SHALLOW_NODE_BUBBLE_SPAWN_INTERVAL_MS); seagullSpawnTimerId = LK.setInterval(spawnSeagullEffect, SEAGULL_SPAWN_INTERVAL_MS); levelSelectCloudSpawnTimerId = LK.setInterval(spawnLevelSelectCloudEffect, LEVEL_SELECT_CLOUD_SPAWN_INTERVAL_MS); shallowShadowFishSpawnTimerId = LK.setInterval(spawnShallowWatersShadowFishEffect, 9000 + Math.random() * 5000); midWatersShadowFishSpawnTimerId = LK.setInterval(spawnMidWatersShadowFishEffect, 10000 + Math.random() * 5000); function cleanupAllParticles() { rippleSpawnTimerId = clearTimer(rippleSpawnTimerId, true); homeIslandRippleSpawnTimerId = clearTimer(homeIslandRippleSpawnTimerId, true); levelSelectScreenWaveSpawnTimerId = clearTimer(levelSelectScreenWaveSpawnTimerId, true); waterfallSpawnTimerId = clearTimer(waterfallSpawnTimerId, true); shallowWatersNodeBubbleSpawnTimerId = clearTimer(shallowWatersNodeBubbleSpawnTimerId, true); seagullSpawnTimerId = clearTimer(seagullSpawnTimerId, true); levelSelectCloudSpawnTimerId = clearTimer(levelSelectCloudSpawnTimerId, true); shallowShadowFishSpawnTimerId = clearTimer(shallowShadowFishSpawnTimerId, true); midWatersShadowFishSpawnTimerId = clearTimer(midWatersShadowFishSpawnTimerId, true); cleanupParticleArray(activeRipples, ripplesContainer); cleanupParticleArray(activeHomeIslandRipples, homeIslandRipplesContainer); cleanupParticleArray(levelSelectScreenWavesArray, ripplesContainer); cleanupParticleArray(activeWaterfallParticles, waterfallParticlesContainer); cleanupParticleArray(activeShallowWatersNodeBubbles, shallowWatersNodeBubblesContainer); cleanupParticleArray(activeMidWatersNodeBubbles, midWatersNodeBubblesContainer); cleanupParticleArray(activeSeagulls, seagullsContainer); cleanupParticleArray(activeLevelSelectClouds, levelSelectCloudContainer); cleanupParticleArray(activeShadowFish, shadowFishContainer); } function updateNodeUnlocks() { var nodes = MAP_CONFIG.NODES; nodes.medium.unlocked = GameState.currentDepth >= 1; nodes.deep.unlocked = GameState.currentDepth >= 2; nodes.abyss.unlocked = GameState.currentDepth >= 3; nodes.shop.unlocked = storage.shopUnlocked; nodes.restaurant.unlocked = storage.restaurantUnlocked; } function createDottedLines() { dottedLinesContainer.removeChildren(); MAP_CONFIG.CONNECTIONS.forEach(function (connection) { var node1 = MAP_CONFIG.NODES[connection[0]]; var node2 = MAP_CONFIG.NODES[connection[1]]; // Special case: draw dotted line for 'dock' to 'medium' if medium is unlocked if (connection[0] === 'dock' && connection[1] === 'medium' && MAP_CONFIG.NODES.medium.unlocked) { var dx = node2.x - node1.x; var dy = node2.y - node1.y; var distance = Math.sqrt(dx * dx + dy * dy); var dotCount = Math.floor(distance / 20); for (var i = 0; i < dotCount; i++) { if (i % 2 === 0) { var dotX = node1.x + dx * i / dotCount; var dotY = node1.y + dy * i / dotCount; dottedLinesContainer.addChild(LK.getAsset('dottedLine', { anchorX: 0.5, anchorY: 0.5, x: dotX, y: dotY, width: 8, height: 8, tint: 0xFFFFFF, alpha: 0.7 })); } } } else if (node1.unlocked && node2.unlocked) { var dx = node2.x - node1.x; var dy = node2.y - node1.y; var distance = Math.sqrt(dx * dx + dy * dy); var dotCount = Math.floor(distance / 20); for (var i = 0; i < dotCount; i++) { if (i % 2 === 0) { var dotX = node1.x + dx * i / dotCount; var dotY = node1.y + dy * i / dotCount; dottedLinesContainer.addChild(LK.getAsset('dottedLine', { anchorX: 0.5, anchorY: 0.5, x: dotX, y: dotY, width: 8, height: 8, tint: 0xFFFFFF, alpha: 0.7 })); } } } else { // This is the existing logic for drawing lines if both nodes are unlocked. } }); } function createNodes() { nodesContainer.removeChildren(); Object.keys(MAP_CONFIG.NODES).forEach(function (nodeKey) { var node = MAP_CONFIG.NODES[nodeKey]; var nodeContainer = nodesContainer.addChild(new Container()); var nodeAsset = 'nodeUnlocked'; if (!node.unlocked) { nodeAsset = 'nodeLocked'; } else if (nodeKey === 'shop') { nodeAsset = 'shopUnlock'; } else if (nodeKey === 'restaurant') { nodeAsset = 'restaurantUnlock'; } var nodeGfx = nodeContainer.addChild(LK.getAsset(nodeAsset, { anchorX: 0.5, anchorY: 0.5, x: node.x, y: node.y })); if (!node.unlocked) { nodeContainer.addChild(LK.getAsset('nodeLocked', { anchorX: 0.5, anchorY: 0.5, x: node.x, y: node.y - 5 })); } nodeGfx.nodeKey = nodeKey; var displayName = getNodeDisplayName(nodeKey); var nameTextFill = 0xFFFFFF; if (nodeKey === 'restaurant' || nodeKey === 'shop') { nameTextFill = 0xFFD700; } var labelY = node.y + nodeGfx.height / 2 + 15; var labelHeight = 80; var labelBg = null; var nameText = nodeContainer.addChild(new Text2(displayName, { size: 70, fill: nameTextFill, stroke: 0x000000, strokeThickness: 2, align: 'center' })); nameText.anchor.set(0.5, 0); nameText.x = node.x; nameText.y = labelY; nameText.visible = true; if (nodeKey === 'restaurant' || nodeKey === 'shop') { var labelWidth = nameText.width + 60; labelBg = nodeContainer.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0, x: node.x, y: labelY, width: labelWidth, height: labelHeight, color: 0x222222, alpha: 0.85 })); if (labelBg && nameText) { nodeContainer.setChildIndex(labelBg, nodeContainer.children.indexOf(nameText)); } } nodeNameTexts[nodeKey] = nameText; }); } function moveBoatToNode(targetNodeKey, onArrivalCallback) { if (boatMoving || !MAP_CONFIG.NODES[targetNodeKey].unlocked) { return; } var targetNode = MAP_CONFIG.NODES[targetNodeKey]; var currentPos = { x: playerBoat.x, y: playerBoat.y }; var distance = Math.sqrt(Math.pow(targetNode.x - currentPos.x, 2) + Math.pow(targetNode.y - currentPos.y, 2)); var travelTime = distance / MAP_CONFIG.BOAT_TRAVEL_SPEED * 1000; boatMoving = true; currentNode = targetNodeKey; tween(playerBoat, { x: targetNode.x, y: targetNode.y }, { duration: travelTime, easing: tween.easeInOut, onFinish: function onFinish() { boatMoving = false; boatBaseY = playerBoat.y; tween(playerBoat, { rotation: 0 }, { duration: 500, easing: tween.easeOut }); if (typeof onArrivalCallback === 'function') { onArrivalCallback(); } else { if (MAP_CONFIG.NODES[targetNodeKey].type === 'fishing') { showSongSelection(MAP_CONFIG.NODES[targetNodeKey].depthIndex); } else if (MAP_CONFIG.NODES[targetNodeKey].type === 'cooking') { _showScreen('beach'); } else if (MAP_CONFIG.NODES[targetNodeKey].type === 'shop') {} } } }); } function showSongSelection(depthIndex) { // Close cooking song selection if it's open if (cookingSongSelectOpen) { hideCookingSongSelection(); } if (shopOverlayOpen) { hideShopOverlay(); } selectedDepth = depthIndex; selectedSong = 0; // Load saved difficulty or default to easy GAME_DIFFICULTY.current = storage.preferredDifficulty || 'easy'; _songOverlayOpen = true; songOverlayContainer.visible = true; createSongSelectionElements(); updateSongDisplay(); songOverlayContainer.alpha = 0; tween(songOverlayContainer, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); } function hideSongSelection() { _songOverlayOpen = false; tween(songOverlayContainer, { alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { songOverlayContainer.visible = false; clearSongSelectionElements(); } }); } function createSongSelectionElements() { clearSongSelectionElements(); var overlayCenterX = 1024; var overlayCenterY = 1366; songElements.leftArrow = songOverlayContainer.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: overlayCenterX - 650, y: overlayCenterY, width: 120, height: 120, tint: 0x666666 })); songElements.leftArrowText = songOverlayContainer.addChild(new Text2('<', { size: 80, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); songElements.leftArrowText.anchor.set(0.5, 0.5); songElements.leftArrowText.x = songElements.leftArrow.x; songElements.leftArrowText.y = songElements.leftArrow.y; songElements.rightArrow = songOverlayContainer.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: overlayCenterX + 650, y: overlayCenterY, width: 120, height: 120, tint: 0x666666 })); songElements.rightArrowText = songOverlayContainer.addChild(new Text2('>', { size: 80, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); songElements.rightArrowText.anchor.set(0.5, 0.5); songElements.rightArrowText.x = songElements.rightArrow.x; songElements.rightArrowText.y = songElements.rightArrow.y; songElements.songTitle = songOverlayContainer.addChild(new Text2('Song Title', { size: 110, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); songElements.songTitle.anchor.set(0.5, 0.5); songElements.songTitle.x = overlayCenterX; songElements.songTitle.y = overlayCenterY - 300; songElements.songInfo = songOverlayContainer.addChild(new Text2('BPM: 120 | Duration: 2:00', { size: 70, fill: 0xCCCCCC, wordWrap: false, wordWrapWidth: null })); songElements.songInfo.anchor.set(0.5, 0.5); songElements.songInfo.x = overlayCenterX; songElements.songInfo.y = overlayCenterY - 200; songElements.fishDisplayContainer = songOverlayContainer.addChild(new Container()); songElements.fishDisplayContainer.x = overlayCenterX; songElements.fishDisplayContainer.y = songElements.songInfo.y + 35 + 30; var fishDisplayBottomY = songElements.fishDisplayContainer.y + 260; songElements.playButton = songOverlayContainer.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: overlayCenterX, y: fishDisplayBottomY + 25 + 50, width: 400, height: 100 })); songElements.playButtonText = songOverlayContainer.addChild(new Text2('PLAY', { size: 100, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); songElements.playButtonText.anchor.set(0.5, 0.5); songElements.playButtonText.x = songElements.playButton.x; songElements.playButtonText.y = songElements.playButton.y; // Add Difficulty Buttons var difficultyButtonY = songElements.playButton.y + songElements.playButton.height / 2 + 30 + 40; // playButton bottom + 30px spacing + half height of new button (80/2=40) var difficultyButtonWidth = 300; var difficultyButtonHeight = 80; var difficultyButtonSpacing = 60; // Easy Button songElements.difficultyEasyButton = songOverlayContainer.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: overlayCenterX - difficultyButtonWidth / 2 - difficultyButtonSpacing / 2, y: difficultyButtonY, width: difficultyButtonWidth, height: difficultyButtonHeight })); songElements.difficultyEasyButtonText = songOverlayContainer.addChild(new Text2('EASY', { size: 40, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); songElements.difficultyEasyButtonText.anchor.set(0.5, 0.5); songElements.difficultyEasyButtonText.x = songElements.difficultyEasyButton.x; songElements.difficultyEasyButtonText.y = songElements.difficultyEasyButton.y; // Normal Button (renamed from Medium) songElements.difficultyMediumButton = songOverlayContainer.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: overlayCenterX + difficultyButtonWidth / 2 + difficultyButtonSpacing / 2, y: difficultyButtonY, width: difficultyButtonWidth, height: difficultyButtonHeight })); songElements.difficultyMediumButtonText = songOverlayContainer.addChild(new Text2('NORMAL', { size: 40, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); songElements.difficultyMediumButtonText.anchor.set(0.5, 0.5); songElements.difficultyMediumButtonText.x = songElements.difficultyMediumButton.x; songElements.difficultyMediumButtonText.y = songElements.difficultyMediumButton.y; } function clearSongSelectionElements() { Object.keys(songElements).forEach(function (key) { if (songElements[key] && songElements[key].destroy && !songElements[key].destroyed) { songElements[key].destroy(); } songElements[key] = null; }); } function updateSongDisplay() { if (!songElements.songTitle || !songElements.fishDisplayContainer) { return; } songElements.fishDisplayContainer.removeChildren(); var depthConfig = GAME_CONFIG.DEPTHS[selectedDepth]; if (!depthConfig || !depthConfig.songs || !depthConfig.songs[selectedSong]) { return; } var songConfig = depthConfig.songs[selectedSong]; var owned = GameState.hasSong(selectedDepth, selectedSong); var overlayCenterX = 1024; var overlayCenterY = 1366; if (songElements.songTitle) { songElements.songTitle.destroy(); } songElements.songTitle = songOverlayContainer.addChild(new Text2(songConfig.name, { size: 110, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null, align: 'center' })); songElements.songTitle.anchor.set(0.5, 0.5); songElements.songTitle.x = overlayCenterX; songElements.songTitle.y = overlayCenterY - 300; if (songElements.songInfo) { songElements.songInfo.destroy(); } songElements.songInfo = songOverlayContainer.addChild(new Text2('BPM: ' + songConfig.bpm + ' | Duration: ' + formatTime(songConfig.duration), { size: 70, fill: 0xCCCCCC, wordWrap: false, wordWrapWidth: null, align: 'center' })); songElements.songInfo.anchor.set(0.5, 0.5); songElements.songInfo.x = overlayCenterX; songElements.songInfo.y = overlayCenterY - 200; function getFishDistributionForSong(songCfg, depthCfg, currentSelectedDepth) { var distribution = []; if (songCfg.name === "Gentle Waves") { distribution = [{ asset: 'anchovy', name: FISH_NAMES.anchovy, percentage: 60 }, { asset: 'sardine', name: FISH_NAMES.sardine, percentage: 30 }, { asset: 'mackerel', name: FISH_NAMES.mackerel, percentage: 10 }]; } else if (songCfg.name === "Morning Tide") { distribution = [{ asset: 'anchovy', name: FISH_NAMES.anchovy, percentage: 40 }, { asset: 'sardine', name: FISH_NAMES.sardine, percentage: 40 }, { asset: 'mackerel', name: FISH_NAMES.mackerel, percentage: 20 }]; } else if (songCfg.name === "Sunny Afternoon") { distribution = [{ asset: 'anchovy', name: FISH_NAMES.anchovy, percentage: 30 }, { asset: 'sardine', name: FISH_NAMES.sardine, percentage: 30 }, { asset: 'mackerel', name: FISH_NAMES.mackerel, percentage: 40 }]; } else if (songCfg.name === "Ocean Current") { distribution = [{ asset: 'cod', name: FISH_NAMES.cod, percentage: 60 }, { asset: 'snapper', name: FISH_NAMES.snapper, percentage: 30 }, { asset: 'seaBass', name: FISH_NAMES.seaBass, percentage: 10 }]; } else if (songCfg.name === "Deep Flow") { distribution = [{ asset: 'cod', name: FISH_NAMES.cod, percentage: 40 }, { asset: 'snapper', name: FISH_NAMES.snapper, percentage: 40 }, { asset: 'seaBass', name: FISH_NAMES.seaBass, percentage: 20 }]; } else if (songCfg.name === "Coral Groove") { distribution = [{ asset: 'cod', name: FISH_NAMES.cod, percentage: 30 }, { asset: 'snapper', name: FISH_NAMES.snapper, percentage: 30 }, { asset: 'seaBass', name: FISH_NAMES.seaBass, percentage: 40 }]; } else { // Use a fixed 5% rare spawn chance var p_rare = 0.05; distribution.push({ asset: 'rareFish', name: FISH_NAMES.rareFish, percentage: p_rare * 100 }); var p_deep = 0; if (currentSelectedDepth >= 2) { p_deep = Math.max(0, 0.3 - p_rare); } if (p_deep > 0) { distribution.push({ asset: 'deepFish', name: FISH_NAMES.deepFish, percentage: p_deep * 100 }); } var p_medium = 0; if (currentSelectedDepth >= 1) { var lower_bound_for_medium = p_rare + p_deep; p_medium = Math.max(0, 0.6 - lower_bound_for_medium); } if (p_medium > 0) { distribution.push({ asset: 'mediumFish', name: FISH_NAMES.mediumFish, percentage: p_medium * 100 }); } var p_shallow_total = Math.max(0, 1.0 - (p_rare + p_deep + p_medium)); if (p_shallow_total > 0) { var shallowFishAssets = [{ asset: 'anchovy', name: FISH_NAMES.anchovy }, { asset: 'sardine', name: FISH_NAMES.sardine }, { asset: 'mackerel', name: FISH_NAMES.mackerel }]; if (currentSelectedDepth === 0 || p_rare + p_deep + p_medium < 0.8) { var per_shallow_percentage = p_shallow_total / shallowFishAssets.length * 100; if (per_shallow_percentage > 0.1) { shallowFishAssets.forEach(function (sf) { distribution.push(_objectSpread(_objectSpread({}, sf), { percentage: per_shallow_percentage })); }); } } else if (distribution.length === 0 && p_shallow_total > 0) { var per_shallow_percentage2 = p_shallow_total / shallowFishAssets.length * 100; if (per_shallow_percentage2 > 0.1) { shallowFishAssets.forEach(function (sf) { distribution.push(_objectSpread(_objectSpread({}, sf), { percentage: per_shallow_percentage2 })); }); } } } } return distribution.filter(function (f) { return f.percentage >= 1; }).sort(function (a, b) { return b.percentage - a.percentage; }); } var fishToDisplay = getFishDistributionForSong(songConfig, depthConfig, selectedDepth); var itemWidth = 180; var itemHeight = 130; var horizontalSpacing = 40; var verticalSpacing = 50; var itemsPerRow = 3; var startX = -((itemsPerRow - 1) * (itemWidth + horizontalSpacing)) / 2; for (var i = 0; i < fishToDisplay.length; i++) { var fishData = fishToDisplay[i]; var fishItemContainer = songElements.fishDisplayContainer.addChild(new Container()); var rowIndex = Math.floor(i / itemsPerRow); var colIndex = i % itemsPerRow; fishItemContainer.x = startX + colIndex * (itemWidth + horizontalSpacing) - 60; fishItemContainer.y = rowIndex * (itemHeight + verticalSpacing) + 60; var icon = fishItemContainer.attachAsset(fishData.asset, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7 }); icon.x = itemWidth / 2; icon.y = 5; var nameText = fishItemContainer.addChild(new Text2(fishData.name, { size: 48, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: itemWidth - 10 })); nameText.anchor.set(0.5, 0); nameText.x = itemWidth / 2; nameText.y = icon.y + icon.height * 0.35 + 5; var percentText = fishItemContainer.addChild(new Text2(fishData.percentage.toFixed(1) + '%', { size: 40, fill: 0xCCCCCC, align: 'center' })); percentText.anchor.set(0.5, 0); percentText.x = itemWidth / 2; percentText.y = nameText.y + nameText.height + 3; } if (owned) { songElements.playButtonText.setText('PLAY'); songElements.playButton.tint = 0x1976d2; } else { songElements.playButtonText.setText('BUY ($' + songConfig.cost + ')'); songElements.playButton.tint = GameState.money >= songConfig.cost ? 0x2e7d32 : 0x666666; } songElements.leftArrow.tint = selectedSong > 0 ? 0x1976d2 : 0x666666; songElements.rightArrow.tint = selectedSong < depthConfig.songs.length - 1 ? 0x1976d2 : 0x666666; // Update difficulty button tints var easyColor = GAME_DIFFICULTY.current === 'easy' ? 0x2e7d32 : 0x666666; // Green if selected, grey otherwise var mediumColor = GAME_DIFFICULTY.current === 'medium' ? 0x2e7d32 : 0x666666; if (songElements.difficultyEasyButton && !songElements.difficultyEasyButton.destroyed) { songElements.difficultyEasyButton.tint = easyColor; } if (songElements.difficultyMediumButton && !songElements.difficultyMediumButton.destroyed) { songElements.difficultyMediumButton.tint = mediumColor; } } function showLevelSelectMessage(message) { var messageContainer = levelSelectScreen.addChild(new Container()); messageContainer.x = GAME_CONFIG.SCREEN_CENTER_X; messageContainer.y = 2732 * 0.85; var messageBg = messageContainer.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, width: 1200, height: 150, alpha: 0.9 })); var messageText = messageContainer.addChild(new Text2(message, { size: 60, fill: 0xFFFFFF, align: 'center' })); messageText.anchor.set(0.5, 0.5); messageContainer.alpha = 0; tween(messageContainer, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); tween(messageContainer, { alpha: 0 }, { duration: 1000, delay: 2000, easing: tween.easeOut, onFinish: function onFinish() { if (messageContainer && !messageContainer.destroyed) { messageContainer.removeChildren(); messageContainer.destroy(); } } }); } function updateBoatAnimation() { if (boatMoving) { return; } boatBobPhase += 0.03; var bobOffset = Math.sin(boatBobPhase) * MAP_CONFIG.BOAT_BOB_AMPLITUDE; playerBoat.y = boatBaseY + bobOffset; boatRotationPhase += 0.02; var rotationOffset = Math.sin(boatRotationPhase) * MAP_CONFIG.BOAT_ROTATION_AMPLITUDE; playerBoat.rotation = rotationOffset; } function handleMapInput(x, y) { // Check inventory button click first if (x >= inventoryButton.x - inventoryButton.width && x <= inventoryButton.x && y >= inventoryButton.y && y <= inventoryButton.y + inventoryButton.height) { toggleInventoryDropdown(); LK.getSound('buttonClick').play(); return; } // Close inventory dropdown if clicking elsewhere var dropdownBaseY = 250 + 100 + 20; // Same as in createInventoryDropdown var newDropdownWidth = 675; if (inventoryDropdownOpen) { var dropdownRightX = 1900 + 50; var dropdownLeftX = dropdownRightX - newDropdownWidth; var dropdownTopY = dropdownBaseY; var dropdownBottomY = dropdownTopY + inventoryDropdownContainer.height; if (!(x >= dropdownLeftX && x <= dropdownRightX && y >= dropdownTopY && y <= dropdownBottomY)) { // Also ensure the click was not on the inventory button itself var buttonTopY = inventoryButton.y; var buttonBottomY = inventoryButton.y + inventoryButton.height; var buttonRightX = inventoryButton.x; var buttonLeftX = inventoryButton.x - inventoryButton.width; if (!(x >= buttonLeftX && x <= buttonRightX && y >= buttonTopY && y <= buttonBottomY)) { toggleInventoryDropdown(); } } } if (_songOverlayOpen) { if (x >= closeButton.x - closeButton.width / 2 && x <= closeButton.x + closeButton.width / 2 && y >= closeButton.y - closeButton.height / 2 && y <= closeButton.y + closeButton.height / 2) { hideSongSelection(); return; } if (songElements.leftArrow && x >= songElements.leftArrow.x - songElements.leftArrow.width / 2 && x <= songElements.leftArrow.x + songElements.leftArrow.width / 2 && y >= songElements.leftArrow.y - songElements.leftArrow.height / 2 && y <= songElements.leftArrow.y + songElements.leftArrow.height / 2 && selectedSong > 0) { selectedSong--; updateSongDisplay(); return; } // Handle Difficulty Button Clicks if (songElements.difficultyEasyButton && x >= songElements.difficultyEasyButton.x - songElements.difficultyEasyButton.width / 2 && x <= songElements.difficultyEasyButton.x + songElements.difficultyEasyButton.width / 2 && y >= songElements.difficultyEasyButton.y - songElements.difficultyEasyButton.height / 2 && y <= songElements.difficultyEasyButton.y + songElements.difficultyEasyButton.height / 2) { GAME_DIFFICULTY.current = 'easy'; storage.preferredDifficulty = 'easy'; updateSongDisplay(); LK.getSound('buttonClick').play(); return; } if (songElements.difficultyMediumButton && x >= songElements.difficultyMediumButton.x - songElements.difficultyMediumButton.width / 2 && x <= songElements.difficultyMediumButton.x + songElements.difficultyMediumButton.width / 2 && y >= songElements.difficultyMediumButton.y - songElements.difficultyMediumButton.height / 2 && y <= songElements.difficultyMediumButton.y + songElements.difficultyMediumButton.height / 2) { GAME_DIFFICULTY.current = 'medium'; storage.preferredDifficulty = 'medium'; updateSongDisplay(); LK.getSound('buttonClick').play(); return; } if (songElements.rightArrow && x >= songElements.rightArrow.x - songElements.rightArrow.width / 2 && x <= songElements.rightArrow.x + songElements.rightArrow.width / 2 && y >= songElements.rightArrow.y - songElements.rightArrow.height / 2 && y <= songElements.rightArrow.y + songElements.rightArrow.height / 2) { var depth = GAME_CONFIG.DEPTHS[selectedDepth]; if (depth && depth.songs && selectedSong < depth.songs.length - 1) { selectedSong++; updateSongDisplay(); } return; } if (songElements.playButton && x >= songElements.playButton.x - songElements.playButton.width / 2 && x <= songElements.playButton.x + songElements.playButton.width / 2 && y >= songElements.playButton.y - songElements.playButton.height / 2 && y <= songElements.playButton.y + songElements.playButton.height / 2) { var owned = GameState.hasSong(selectedDepth, selectedSong); if (owned) { GameState.selectedDepth = selectedDepth; GameState.selectedSong = selectedSong; GameState.lastLevelSelectNodeKey = currentNode; _showScreen('fishing'); } else { if (GameState.buySong(selectedDepth, selectedSong)) { updateSongDisplay(); updateMapDisplay(); } } return; } return; } if (x >= backButton.x - backButton.width / 2 && x <= backButton.x + backButton.width / 2 && y >= backButton.y - backButton.height / 2 && y <= backButton.y + backButton.height / 2) { _showScreen('title'); return; } if (nodesContainer && nodesContainer.children) { nodesContainer.children.forEach(function (nodeContainerInstance) { if (nodeContainerInstance && nodeContainerInstance.children && nodeContainerInstance.children.length > 0) { var nodeGfx = nodeContainerInstance.children[0]; if (nodeGfx && nodeGfx.nodeKey) { var nodeData = MAP_CONFIG.NODES[nodeGfx.nodeKey]; if (nodeData.unlocked) { var distance = Math.sqrt(Math.pow(x - nodeData.x, 2) + Math.pow(y - nodeData.y, 2)); if (distance < 60) { // Direct transition for cooking (no boat movement needed) if (nodeGfx.nodeKey === 'restaurant') { if (currentNode === 'dock') { _showScreen('beach'); } else { moveBoatToNode('dock', function () { _showScreen('beach'); }); } } else if (nodeGfx.nodeKey === 'shop') { if (currentNode === 'dock') { showShopOverlay(); } else { moveBoatToNode('dock', function () { showShopOverlay(); }); } } else if (!boatMoving) { moveBoatToNode(nodeGfx.nodeKey); } } } } } }); } } function updateMapDisplay() { updateNodeUnlocks(); createDottedLines(); createNodes(); moneyDisplay.setText('$' + GameState.money); } updateMapDisplay(); if (songOverlayContainer && levelSelectScreen.children.indexOf(songOverlayContainer) !== -1) { levelSelectScreen.setChildIndex(songOverlayContainer, levelSelectScreen.children.length - 1); } function playRestaurantUnlockSequence() { if (_songOverlayOpen) { hideSongSelection(); } var messageContainer = levelSelectScreen.addChild(new Container()); messageContainer.x = GAME_CONFIG.SCREEN_CENTER_X; messageContainer.y = 2732 * 0.85; var bgWidth = 1800; var bgHeight = 350; var messageBg = messageContainer.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, width: bgWidth, height: bgHeight, alpha: 0.9, y: 0 })); var messageText = messageContainer.addChild(new Text2("Now that you’ve caught some fish, you should head over to the Fish Shack and try your hand at cooking.", { size: 60, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: bgWidth - 100 })); messageText.anchor.set(0.5, 0.5); messageText.y = -30; var continueButton = messageContainer.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: bgHeight / 2 - 70, tint: 0x1976d2, width: 400, height: 100 })); var continueText = messageContainer.addChild(new Text2('OK', { size: 50, fill: 0xFFFFFF })); continueText.anchor.set(0.5, 0.5); continueText.x = continueButton.x; continueText.y = continueButton.y; continueButton.down = function () { tween(messageContainer, { alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (messageContainer && !messageContainer.destroyed) { messageContainer.destroy(); } var restaurantNodeContainer; var restaurantNodeGfx; var lockIcon; nodesContainer.children.forEach(function (container) { if (container.children[0] && container.children[0].nodeKey === 'restaurant') { restaurantNodeContainer = container; restaurantNodeGfx = container.children[0]; lockIcon = container.children[1]; } }); if (!restaurantNodeContainer || !restaurantNodeGfx || !lockIcon) { return; } tween(lockIcon, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { if (lockIcon && !lockIcon.destroyed) { lockIcon.destroy(); } var nodeData = MAP_CONFIG.NODES['restaurant']; if (restaurantNodeGfx && !restaurantNodeGfx.destroyed) { restaurantNodeGfx.destroy(); } var unlockedNodeGfx = restaurantNodeContainer.addChild(LK.getAsset('restaurantUnlock', { anchorX: 0.5, anchorY: 0.5, x: nodeData.x, y: nodeData.y, alpha: 0 })); unlockedNodeGfx.nodeKey = 'restaurant'; var labels = []; restaurantNodeContainer.children.forEach(function (c) { if (c !== unlockedNodeGfx) { labels.push(c); } }); labels.forEach(function (l) { restaurantNodeContainer.removeChild(l); restaurantNodeContainer.addChild(l); }); tween(unlockedNodeGfx, { alpha: 1 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { storage.restaurantUnlocked = true; MAP_CONFIG.NODES.restaurant.unlocked = true; createDottedLines(); } }); } }); } }); }; messageContainer.alpha = 0; tween(messageContainer, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); levelSelectScreen.setChildIndex(messageContainer, levelSelectScreen.children.length - 1); } function playShopUnlockSequence() { if (_songOverlayOpen) { hideSongSelection(); } var messageContainer = levelSelectScreen.addChild(new Container()); messageContainer.x = GAME_CONFIG.SCREEN_CENTER_X; messageContainer.y = 2732 * 0.85; var bgWidth = 1800; var bgHeight = 350; var messageBg = messageContainer.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, width: bgWidth, height: bgHeight, alpha: 0.9, y: 0 })); var messageText = messageContainer.addChild(new Text2("Now that you have some money, you should head over to the fishing shop and see what they have to offer.", { size: 60, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: bgWidth - 100 })); messageText.anchor.set(0.5, 0.5); messageText.y = -30; var continueButton = messageContainer.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: bgHeight / 2 - 70, tint: 0x1976d2, width: 400, height: 100 })); var continueText = messageContainer.addChild(new Text2('OK', { size: 50, fill: 0xFFFFFF })); continueText.anchor.set(0.5, 0.5); continueText.x = continueButton.x; continueText.y = continueButton.y; continueButton.down = function () { tween(messageContainer, { alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (messageContainer && !messageContainer.destroyed) { messageContainer.destroy(); } var shopNodeContainer; var shopNodeGfx; var lockIcon; nodesContainer.children.forEach(function (container) { var firstChild = container.children[0]; if (firstChild && firstChild.nodeKey === 'shop') { shopNodeContainer = container; shopNodeGfx = firstChild; lockIcon = container.children[1]; } }); if (!shopNodeContainer || !shopNodeGfx || !lockIcon) { return; } tween(lockIcon, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { if (lockIcon && !lockIcon.destroyed) { lockIcon.destroy(); } var nodeData = MAP_CONFIG.NODES['shop']; if (shopNodeGfx && !shopNodeGfx.destroyed) { shopNodeGfx.destroy(); } var unlockedNodeGfx = shopNodeContainer.addChild(LK.getAsset('shopUnlock', { anchorX: 0.5, anchorY: 0.5, x: nodeData.x, y: nodeData.y, alpha: 0 })); unlockedNodeGfx.nodeKey = 'shop'; var labels = []; shopNodeContainer.children.forEach(function (c) { if (c !== unlockedNodeGfx) { labels.push(c); } }); labels.forEach(function (l) { shopNodeContainer.removeChild(l); shopNodeContainer.addChild(l); }); tween(unlockedNodeGfx, { alpha: 1 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { storage.shopUnlocked = true; MAP_CONFIG.NODES.shop.unlocked = true; createDottedLines(); } }); } }); } }); }; messageContainer.alpha = 0; tween(messageContainer, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); levelSelectScreen.setChildIndex(messageContainer, levelSelectScreen.children.length - 1); } function playMidWatersUnlockSequence() { if (_songOverlayOpen) { hideSongSelection(); } var messageContainer = levelSelectScreen.addChild(new Container()); messageContainer.x = GAME_CONFIG.SCREEN_CENTER_X; messageContainer.y = 2732 * 0.85; var bgWidth = 1800; var bgHeight = 350; var messageBg = messageContainer.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, width: bgWidth, height: bgHeight, alpha: 0.9, y: 0 })); var messageText = messageContainer.addChild(new Text2("Your new rod is strong enough to reach deeper waters. A new area is now available!", { size: 60, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: bgWidth - 100 })); messageText.anchor.set(0.5, 0.5); messageText.y = -30; var continueButton = messageContainer.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: bgHeight / 2 - 70, tint: 0x1976d2, width: 400, height: 100 })); var continueText = messageContainer.addChild(new Text2('OK', { size: 50, fill: 0xFFFFFF })); continueText.anchor.set(0.5, 0.5); continueText.x = continueButton.x; continueText.y = continueButton.y; continueButton.down = function () { tween(messageContainer, { alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (messageContainer && !messageContainer.destroyed) { messageContainer.destroy(); } var nodeContainer; var nodeGfx; var lockIcon; nodesContainer.children.forEach(function (container) { var firstChild = container.children[0]; if (firstChild && firstChild.nodeKey === 'medium') { nodeContainer = container; nodeGfx = firstChild; lockIcon = container.children[1]; } }); if (!nodeContainer || !nodeGfx || !lockIcon) { return; } tween(lockIcon, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { if (lockIcon && !lockIcon.destroyed) { lockIcon.destroy(); } var nodeData = MAP_CONFIG.NODES['medium']; if (nodeGfx && !nodeGfx.destroyed) { nodeGfx.destroy(); } var unlockedNodeGfx = nodeContainer.addChild(LK.getAsset('nodeUnlocked', { anchorX: 0.5, anchorY: 0.5, x: nodeData.x, y: nodeData.y, alpha: 0 })); unlockedNodeGfx.nodeKey = 'medium'; var labels = []; nodeContainer.children.forEach(function (c) { if (c !== unlockedNodeGfx) { labels.push(c); } }); labels.forEach(function (l) { nodeContainer.removeChild(l); nodeContainer.addChild(l); }); tween(unlockedNodeGfx, { alpha: 1 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { MAP_CONFIG.NODES.medium.unlocked = true; createDottedLines(); } }); } }); } }); }; messageContainer.alpha = 0; tween(messageContainer, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); levelSelectScreen.setChildIndex(messageContainer, levelSelectScreen.children.length - 1); } return { updateMapDisplay: updateMapDisplay, handleMapInput: handleMapInput, updateBoatAnimation: updateBoatAnimation, moneyDisplay: moneyDisplay, updateRipples: function updateRipples() { updateParticleArray(activeRipples); }, cleanupRipples: cleanupAllParticles, ripplesContainer: ripplesContainer, activeRipples: activeRipples, rippleSpawnTimerId: rippleSpawnTimerId, updateHomeIslandRipples: function updateHomeIslandRipples() { updateParticleArray(activeHomeIslandRipples); }, cleanupHomeIslandRipples: cleanupAllParticles, homeIslandRipplesContainer: homeIslandRipplesContainer, activeHomeIslandRipples: activeHomeIslandRipples, homeIslandRippleSpawnTimerId: homeIslandRippleSpawnTimerId, updateScreenWaves: function updateScreenWaves() { updateParticleArray(levelSelectScreenWavesArray); }, cleanupScreenWaves: cleanupAllParticles, screenWaveSpawnTimerId: levelSelectScreenWaveSpawnTimerId, updateShallowWatersNodeBubbles: function updateShallowWatersNodeBubbles() { updateParticleArray(activeShallowWatersNodeBubbles); }, cleanupShallowWatersNodeBubbles: cleanupAllParticles, shallowWatersNodeBubbleSpawnTimerId: shallowWatersNodeBubbleSpawnTimerId, updateMidWatersNodeBubbles: function updateMidWatersNodeBubbles() { updateParticleArray(activeMidWatersNodeBubbles); }, cleanupMidWatersNodeBubbles: cleanupAllParticles, midWatersNodeBubbleSpawnTimerId: midWatersNodeBubbleSpawnTimerId, updateSeagulls: function updateSeagulls() { updateParticleArray(activeSeagulls); }, cleanupSeagulls: cleanupAllParticles, seagullSpawnTimerId: seagullSpawnTimerId, levelSelectCloudContainer: levelSelectCloudContainer, updateLevelSelectClouds: function updateLevelSelectClouds() { updateParticleArray(activeLevelSelectClouds); }, cleanupLevelSelectClouds: cleanupAllParticles, levelSelectCloudSpawnTimerId: levelSelectCloudSpawnTimerId, updateWaterfallParticles: function updateWaterfallParticles() { updateParticleArray(activeWaterfallParticles); }, cleanupWaterfallParticles: cleanupAllParticles, waterfallSpawnTimerId: waterfallSpawnTimerId, updateShadowFish: function updateShadowFish() { updateParticleArray(activeShadowFish); }, cleanupLevelSelectShadowFish: cleanupAllParticles, shallowShadowFishSpawnTimerId: shallowShadowFishSpawnTimerId, midWatersShadowFishSpawnTimerId: midWatersShadowFishSpawnTimerId, startLevelSelectAmbientSounds: startLevelSelectAmbientSounds, stopLevelSelectAmbientSounds: stopLevelSelectAmbientSounds, playRestaurantUnlockSequence: playRestaurantUnlockSequence, playShopUnlockSequence: playShopUnlockSequence, playMidWatersUnlockSequence: playMidWatersUnlockSequence, showLevelSelectMessage: showLevelSelectMessage, restartParticleTimers: function restartParticleTimers() { cleanupAllParticles(); rippleSpawnTimerId = LK.setInterval(spawnRippleEffect, RIPPLE_SPAWN_INTERVAL_MS); homeIslandRippleSpawnTimerId = LK.setInterval(spawnHomeIslandRippleEffect, HOME_ISLAND_RIPPLE_SPAWN_INTERVAL_MS); levelSelectScreenWaveSpawnTimerId = LK.setInterval(spawnLevelSelectScreenWaveEffect, SCREEN_WAVE_SPAWN_INTERVAL_MS); waterfallSpawnTimerId = LK.setInterval(spawnWaterfallParticleEffect, WATERFALL_SPAWN_INTERVAL_MS); shallowWatersNodeBubbleSpawnTimerId = LK.setInterval(spawnShallowWatersNodeBubbleEffect, SHALLOW_NODE_BUBBLE_SPAWN_INTERVAL_MS); midWatersNodeBubbleSpawnTimerId = LK.setInterval(spawnMidWatersNodeBubbleEffect, SHALLOW_NODE_BUBBLE_SPAWN_INTERVAL_MS); seagullSpawnTimerId = LK.setInterval(spawnSeagullEffect, SEAGULL_SPAWN_INTERVAL_MS); levelSelectCloudSpawnTimerId = LK.setInterval(spawnLevelSelectCloudEffect, LEVEL_SELECT_CLOUD_SPAWN_INTERVAL_MS); shallowShadowFishSpawnTimerId = LK.setInterval(spawnShallowWatersShadowFishEffect, 9000 + Math.random() * 5000); midWatersShadowFishSpawnTimerId = LK.setInterval(spawnMidWatersShadowFishEffect, 10000 + Math.random() * 5000); }, hideSongSelection: hideSongSelection, songOverlayOpen: function songOverlayOpen() { return _songOverlayOpen; } }; } function createBeachScreen() { // Add these variables at the top of createBeachScreen or make them accessible var waveAnimationActive = true; var waveTimeoutId = null; beachScreen.removeChildren(); beachScreen.addChild(LK.getAsset('skybackground', { x: 0, y: -500 })); beachScreen.addChild(LK.getAsset('sand', { x: 0, y: GAME_CONFIG.WATER_SURFACE_Y, width: 2048, height: 2732 - GAME_CONFIG.WATER_SURFACE_Y })); beachCrabContainer = beachScreen.addChild(new Container()); var waveContainer = beachScreen.addChild(new Container()); var wetSand = waveContainer.addChild(LK.getAsset('beachWave', { anchorX: 0.5, anchorY: 1.0, x: 1024, width: 2048, height: 600, tint: 0x000000, alpha: 0 })); var wave = waveContainer.addChild(LK.getAsset('beachWave', { anchorX: 0.5, anchorY: 1.0, x: 1024, width: 2048, height: 600, alpha: 0.8 })); var START_Y = 2732 + 600; // Start completely offscreen (wave is 600 pixels tall) var PEAK_Y = 2732 + 200; // Move highest position down 600 pixels from previous position wave.y = START_Y; wetSand.y = PEAK_Y; function animateWaveCycle() { if (!wave || wave.destroyed || !waveAnimationActive) { return; } wave.y = START_Y; wetSand.alpha = 0; // Play boat sound when wave starts its upward animation // Add screen check to prevent playing after leaving beach if (GameState.currentScreen === 'beach') { LK.getSound('boatsounds').play(); } tween(wave, { y: PEAK_Y, scaleY: 1.3 }, { duration: 4000, // Slightly longer for the increased distance easing: tween.easeOut, onFinish: function onFinish() { if (!wave || wave.destroyed || !waveAnimationActive) { return; } tween(wetSand, { alpha: 0.4, scaleY: 1.3 }, { duration: 500, easing: tween.easeOut }); LK.setTimeout(function () { if (!wave || wave.destroyed || !waveAnimationActive) { return; } tween(wave, { y: START_Y, scaleY: 1.0 }, { duration: 4500, // Slightly longer for the increased distance easing: tween.easeIn, onFinish: function onFinish() { if (!wave || wave.destroyed || !waveAnimationActive) { return; } // Add screen check before scheduling next cycle if (waveAnimationActive && GameState.currentScreen === 'beach') { waveTimeoutId = LK.setTimeout(animateWaveCycle, 2500); } } }); tween(wetSand, { alpha: 0, scaleY: 1.0 }, { duration: 6000, // Slower fade to match wave retreat timing easing: tween.easeIn }); }, 500); // Reduced pause at peak from 1500ms to 500ms } }); } animateWaveCycle(); // Add repeating bush line along the horizon var bushWidth = 500; // From asset definition var numBushes = Math.ceil(2048 / bushWidth); for (var i = 0; i < numBushes; i++) { beachScreen.addChild(LK.getAsset('bushLine', { anchorX: 0, anchorY: 1.0, // Anchor to bottom-left to sit on horizon x: i * bushWidth, y: GAME_CONFIG.WATER_SURFACE_Y + 25 })); } // Add kitchenBack centered on where the fish shack will be, below fishermanChef var kitchenBack = beachScreen.addChild(LK.getAsset('kitchenBack', { anchorX: 0.5, anchorY: 0.5, x: GAME_CONFIG.SCREEN_CENTER_X, y: GAME_CONFIG.WATER_SURFACE_Y - 100 })); // Add fishermanChef before the shack so it appears behind it var fishermanChef = beachScreen.addChild(LK.getAsset('fishermanChef', { anchorX: 0.5, anchorY: 0.74, // Same anchor as shack for consistent positioning x: GAME_CONFIG.SCREEN_CENTER_X, y: GAME_CONFIG.WATER_SURFACE_Y - 50, scaleX: 1.2, scaleY: 1.2 })); var fishShack = beachScreen.addChild(LK.getAsset('fishShack', { anchorX: 0.5, anchorY: 0.74, x: GAME_CONFIG.SCREEN_CENTER_X, y: GAME_CONFIG.WATER_SURFACE_Y, scaleX: 1.2, scaleY: 1.2 })); var sandwichBoard = beachScreen.addChild(new Container()); sandwichBoard.x = GAME_CONFIG.SCREEN_CENTER_X - 450; sandwichBoard.y = GAME_CONFIG.WATER_SURFACE_Y + 180; var sandwichBoardGfx = sandwichBoard.addChild(LK.getAsset('sandwichBoard', { anchorX: 0.5, anchorY: 1.0, x: 0, y: 0, scaleX: 1.5, scaleY: 1.5 })); sandwichBoard.dishContainer = sandwichBoard.addChild(new Container()); sandwichBoard.dishContainer.y = -130; sandwichBoard.down = function () { showMenuOverlay(); }; // Add beach tables var centerY = 2732 / 2 + 400; // Center Y position var leftTableX = 400; // 300 pixels from left side var rightTableX = 2048 - 400; // 300 pixels from right side var leftBeachTable = beachScreen.addChild(LK.getAsset('beachTable', { anchorX: 0.5, anchorY: 0.5, x: leftTableX, y: centerY, scaleX: 1.5, scaleY: 1.5 })); var rightBeachTable = beachScreen.addChild(LK.getAsset('beachTable', { anchorX: 0.5, anchorY: 0.5, x: rightTableX, y: centerY, scaleX: -1.5, scaleY: 1.5 })); var customerContainer = beachScreen.addChild(new Container()); var backButton = beachScreen.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2600, width: 400, height: 120 })); var backButtonText = beachScreen.addChild(new Text2('BACK TO MAP', { size: 50, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); backButtonText.anchor.set(0.5, 0.5); backButtonText.x = backButton.x; backButtonText.y = backButton.y; backButton.down = function () { _showScreen('levelSelect'); }; var boombox = beachScreen.addChild(LK.getAsset('boomBox', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: 250, alpha: 0, scaleX: 1.5, scaleY: 1.5 })); boombox.down = function () { showCookingSongSelection(); }; // Add subtle pulse animation to the boombox function pulseBoombox() { if (!boombox || boombox.destroyed) { return; } tween(boombox, { scaleX: 1.6, scaleY: 1.6 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (!boombox || boombox.destroyed) { return; } tween(boombox, { scaleX: 1.5, scaleY: 1.5 }, { duration: 600, easing: tween.easeInOut, onFinish: pulseBoombox }); } }); } // Start the pulse animation when boombox becomes visible if (boombox) { pulseBoombox(); } return { shack: fishShack, backButton: backButton, fishermanChef: fishermanChef, customerContainer: customerContainer, boombox: boombox, crabContainer: beachCrabContainer, sandwichBoard: sandwichBoard, // Add cleanup function cleanup: function cleanup() { waveAnimationActive = false; if (waveTimeoutId) { LK.clearTimeout(waveTimeoutId); waveTimeoutId = null; } } }; } function createCookingSongSelectElements() { clearCookingSongSelectElements(); cookingSongSelectOverlay = beachScreen.addChild(new Container()); cookingSongSelectOverlay.visible = false; var overlayBg = cookingSongSelectOverlay.addChild(LK.getAsset('songCard', { x: 1024, y: 1366, width: 1700, height: 900, color: 0x424242, anchorX: 0.5, anchorY: 0.5, alpha: 0.95 })); var closeButton = cookingSongSelectOverlay.addChild(LK.getAsset('closeButton', { anchorX: 0.5, anchorY: 0.5, x: 1024 + 1700 / 2 - 75, y: 1366 - 900 / 2 + 75, width: 100, height: 100, tint: 0xff4444 })); var closeButtonText = new Text2('X', { size: 60, fill: 0xFFFFFF }); closeButtonText.anchor.set(0.5, 0.5); closeButtonText.x = closeButton.x; closeButtonText.y = closeButton.y; cookingSongSelectOverlay.addChild(closeButtonText); closeButton.down = function () { hideCookingSongSelection(); }; var overlayCenterX = 1024; var overlayCenterY = 1366; cookingSongElements.leftArrow = cookingSongSelectOverlay.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: overlayCenterX - 650, y: overlayCenterY, width: 120, height: 120, tint: 0x666666 })); cookingSongElements.leftArrowText = cookingSongSelectOverlay.addChild(new Text2('<', { size: 80, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); cookingSongElements.leftArrowText.anchor.set(0.5, 0.5); cookingSongElements.leftArrowText.x = cookingSongElements.leftArrow.x; cookingSongElements.leftArrowText.y = cookingSongElements.leftArrow.y; cookingSongElements.leftArrow.down = function () { if (cookingSelectedSongIndex > 0) { cookingSelectedSongIndex--; updateCookingSongDisplay(); } }; cookingSongElements.rightArrow = cookingSongSelectOverlay.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: overlayCenterX + 650, y: overlayCenterY, width: 120, height: 120, tint: 0x666666 })); cookingSongElements.rightArrowText = cookingSongSelectOverlay.addChild(new Text2('>', { size: 80, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); cookingSongElements.rightArrowText.anchor.set(0.5, 0.5); cookingSongElements.rightArrowText.x = cookingSongElements.rightArrow.x; cookingSongElements.rightArrowText.y = cookingSongElements.rightArrow.y; cookingSongElements.rightArrow.down = function () { if (GameState.ownedSongs && cookingSelectedSongIndex < GameState.ownedSongs.length - 1) { cookingSelectedSongIndex++; updateCookingSongDisplay(); } }; cookingSongElements.songTitle = cookingSongSelectOverlay.addChild(new Text2('Song Title', { size: 110, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); cookingSongElements.songTitle.anchor.set(0.5, 0.5); cookingSongElements.songTitle.x = overlayCenterX; cookingSongElements.songTitle.y = overlayCenterY - 300; cookingSongElements.songInfo = cookingSongSelectOverlay.addChild(new Text2('BPM: 120 | Duration: 2:00', { size: 70, fill: 0xCCCCCC, wordWrap: false, wordWrapWidth: null })); cookingSongElements.songInfo.anchor.set(0.5, 0.5); cookingSongElements.songInfo.x = overlayCenterX; cookingSongElements.songInfo.y = overlayCenterY - 200; cookingSongElements.playButton = cookingSongSelectOverlay.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: overlayCenterX, y: overlayCenterY + 300, width: 400, height: 100 })); cookingSongElements.playButtonText = cookingSongSelectOverlay.addChild(new Text2('Cook', { size: 100, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); cookingSongElements.playButtonText.anchor.set(0.5, 0.5); cookingSongElements.playButtonText.x = cookingSongElements.playButton.x; cookingSongElements.playButtonText.y = cookingSongElements.playButton.y; cookingSongElements.playButton.down = function () { if (!GameState.ownedSongs || GameState.ownedSongs.length === 0) { return; } if (this.isTransitioning) { return; } if (FishInventory.getTotalFish() < 20) { // Simple centered text popup var simpleMessage = beachScreen.addChild(new Text2("Not enough fish! Go catch more first.", { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 4, align: 'center' })); simpleMessage.anchor.set(0.5, 0.5); simpleMessage.x = GAME_CONFIG.SCREEN_CENTER_X; simpleMessage.y = GAME_CONFIG.SCREEN_CENTER_Y + 400; simpleMessage.alpha = 0; // Ensure it's on top beachScreen.setChildIndex(simpleMessage, beachScreen.children.length - 1); // Simple fade in and out tween(simpleMessage, { alpha: 1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(simpleMessage, { alpha: 0 }, { duration: 500, delay: 2000, easing: tween.easeIn, onFinish: function onFinish() { if (simpleMessage && !simpleMessage.destroyed) { simpleMessage.destroy(); } } }); } }); return; } this.isTransitioning = true; var ownedSong = GameState.ownedSongs[cookingSelectedSongIndex]; var songConfig = GAME_CONFIG.DEPTHS[ownedSong.depth].songs[ownedSong.songIndex]; var darkOverlay = beachScreen.addChild(LK.getAsset('screenBackground', { x: 0, y: 0, width: 2048, height: 2732, color: 0x000000, alpha: 0 })); darkOverlay.interactive = true; darkOverlay.down = function () {}; // Block input to underlying beach elements tween(darkOverlay, { alpha: 0.7 }, { duration: 400, easing: tween.easeOut }); cookingScreen.alpha = 0; cookingScreen.visible = true; cookingElements = createCookingScreen(); var self = this; tween(cookingScreen, { alpha: 1 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { GameState.currentScreen = 'cooking'; startCookingGame(songConfig); if (self && !self.destroyed) { self.isTransitioning = false; } // Keep the darkening overlay to maintain beach screen darkness // if (darkOverlay && !darkOverlay.destroyed) { // darkOverlay.destroy(); // } } }); hideCookingSongSelection(); }; } function clearCookingSongSelectElements() { if (cookingSongSelectOverlay && !cookingSongSelectOverlay.destroyed) { cookingSongSelectOverlay.destroy(); } cookingSongSelectOverlay = null; Object.keys(cookingSongElements).forEach(function (key) { if (cookingSongElements[key] && cookingSongElements[key].destroy && !cookingSongElements[key].destroyed) { cookingSongElements[key].destroy(); } cookingSongElements[key] = null; }); } function showCookingSongSelection() { // Close level select song overlay if it's open if (levelSelectElements && typeof levelSelectElements.hideSongSelection === 'function') { levelSelectElements.hideSongSelection(); } if (shopOverlayOpen) { hideShopOverlay(); } createCookingSongSelectElements(); cookingSelectedSongIndex = 0; cookingSongSelectOpen = true; cookingSongSelectOverlay.visible = true; updateCookingSongDisplay(); cookingSongSelectOverlay.alpha = 0; tween(cookingSongSelectOverlay, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); } function hideCookingSongSelection() { cookingSongSelectOpen = false; if (cookingSongSelectOverlay) { tween(cookingSongSelectOverlay, { alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { if (cookingSongSelectOverlay && !cookingSongSelectOverlay.destroyed) { cookingSongSelectOverlay.visible = false; } } }); } } function updateCookingSongDisplay() { if (!cookingSongElements.songTitle || GameState.ownedSongs.length === 0) { if (cookingSongElements.songTitle) { cookingSongElements.songTitle.setText("No Songs Owned"); cookingSongElements.songInfo.setText("Go fishing to unlock more songs!"); cookingSongElements.playButton.visible = false; cookingSongElements.playButtonText.visible = false; cookingSongElements.leftArrow.visible = false; cookingSongElements.leftArrowText.visible = false; cookingSongElements.rightArrow.visible = false; cookingSongElements.rightArrowText.visible = false; } return; } var ownedSong = GameState.ownedSongs[cookingSelectedSongIndex]; var songConfig = GAME_CONFIG.DEPTHS[ownedSong.depth].songs[ownedSong.songIndex]; cookingSongElements.songTitle.setText(songConfig.name); cookingSongElements.songInfo.setText('BPM: ' + songConfig.bpm + ' | Duration: ' + formatTime(songConfig.duration)); cookingSongElements.playButton.visible = true; cookingSongElements.playButtonText.visible = true; cookingSongElements.leftArrow.visible = true; cookingSongElements.leftArrowText.visible = true; cookingSongElements.rightArrow.visible = true; cookingSongElements.rightArrowText.visible = true; cookingSongElements.playButtonText.setText('Cook'); cookingSongElements.playButton.tint = 0x1976d2; cookingSongElements.leftArrow.tint = cookingSelectedSongIndex > 0 ? 0x1976d2 : 0x666666; cookingSongElements.rightArrow.tint = cookingSelectedSongIndex < GameState.ownedSongs.length - 1 ? 0x1976d2 : 0x666666; } function showShopMessage(text) { if (!shopOverlayContainer || shopOverlayContainer.destroyed) { return; } var message = new Text2(text, { size: 70, fill: 0xFFFFFF, stroke: '#000000', strokeThickness: 4, align: 'center', wordWrap: true, wordWrapWidth: 1200 }); message.anchor.set(0.5, 0.5); message.x = 1024; message.y = 1366 + 900 / 2 - 200; // Above buy button shopOverlayContainer.addChild(message); message.alpha = 0; tween(message, { alpha: 1 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(message, { alpha: 0 }, { duration: 300, delay: 2000, easing: tween.easeIn, onFinish: function onFinish() { if (message && !message.destroyed) { message.destroy(); } } }); } }); } function createShopOverlayElements() { shopOverlayContainer = levelSelectScreen.addChild(new Container()); shopOverlayContainer.visible = false; shopItems = []; selectedShopItemIndex = -1; var overlayBg = shopOverlayContainer.addChild(LK.getAsset('songCard', { x: 1024, y: 1366, width: 1700, height: 1200, color: 0x3a3a3a, anchorX: 0.5, anchorY: 0.5, alpha: 0.95 })); var closeButton = shopOverlayContainer.addChild(LK.getAsset('closeButton', { anchorX: 0.5, anchorY: 0.5, x: 1024 + 1700 / 2 - 120, y: 1366 - 1200 / 2 + 120, tint: 0xff4444 })); var closeButtonText = shopOverlayContainer.addChild(new Text2('X', { size: 60, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); closeButtonText.anchor.set(0.5, 0.5); closeButtonText.x = closeButton.x; closeButtonText.y = closeButton.y; closeButton.down = function () { hideShopOverlay(); }; var title = shopOverlayContainer.addChild(new Text2('Fishing Shop', { size: 110, fill: 0xFFFFFF, align: 'center', wordWrap: false, wordWrapWidth: null })); title.anchor.set(0.5, 0); title.x = 1024; title.y = 1366 - 1200 / 2 + 50; shopItemHighlight = shopOverlayContainer.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: 1024, width: 1500, height: 100, tint: 0x1976d2, alpha: 0.5, visible: false })); var buyButton = shopOverlayContainer.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 + 1200 / 2 - 100, width: 400, height: 100 })); var buyButtonText = shopOverlayContainer.addChild(new Text2('BUY', { size: 70, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); buyButtonText.anchor.set(0.5, 0.5); buyButtonText.x = buyButton.x; buyButtonText.y = buyButton.y; var SHOP_ITEMS_CONFIG = [{ type: 'subheading', name: 'Fishing Rods' }, { id: 'wooden_rod', name: 'Wooden Rod', price: 0, owned: true, type: 'item' }, { id: 'fiberglass_rod', name: 'Fiberglass Rod', price: 800, owned: storage.fiberglassRodOwned || false, type: 'item' }, { type: 'subheading', name: 'Cooking Tools' }, { id: 'grinder', name: 'Grinder', price: 500, owned: storage.grinderOwned || false, type: 'item' }, { id: 'spices', name: 'Spices', price: 500, owned: storage.spicesOwned || false, type: 'item' }, { id: 'pot', name: 'Pot', price: 500, owned: storage.potOwned || false, type: 'item' }]; var itemY = title.y + title.height + 80; var itemSpacing = 110; var subheadingSpacing = 60; var itemLeftX = 1024 - 1700 / 2 + 100; var itemRightX = 1024 + 1700 / 2 - 100; function updateBuyButtonState() { if (selectedShopItemIndex === -1 || !shopItems[selectedShopItemIndex]) { buyButton.tint = 0x666666; // Disabled return; } var item = shopItems[selectedShopItemIndex].config; if (item.owned) { buyButton.tint = 0x666666; // Disabled } else if (GameState.money >= item.price) { buyButton.tint = 0x2e7d32; // Green for purchasable } else { buyButton.tint = 0xab544d; // Red for not enough money } } var shopItemIndex = 0; // Separate index for interactable items SHOP_ITEMS_CONFIG.forEach(function (itemConfig) { if (itemConfig.type === 'subheading') { itemY += subheadingSpacing; var subheadingText = shopOverlayContainer.addChild(new Text2(itemConfig.name, { size: 70, fill: 0x4fc3f7, wordWrap: false, wordWrapWidth: null })); subheadingText.anchor.set(0, 0.5); subheadingText.x = itemLeftX; subheadingText.y = itemY; itemY += 80; // Space after subheading } else if (itemConfig.type === 'item') { var hitArea = shopOverlayContainer.addChild(new Container()); hitArea.x = itemLeftX; hitArea.y = itemY; hitArea.interactive = true; var nameText = hitArea.addChild(new Text2(itemConfig.name, { size: 80, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null })); nameText.anchor.set(0, 0.5); var statusText; if (itemConfig.owned) { statusText = hitArea.addChild(new Text2('Sold Out', { size: 80, fill: '#AAAAAA', wordWrap: false, wordWrapWidth: null })); } else { statusText = hitArea.addChild(new Text2('$' + itemConfig.price, { size: 80, fill: 0xFFD700, wordWrap: false, wordWrapWidth: null })); } statusText.anchor.set(1, 0.5); statusText.x = itemRightX - itemLeftX; (function (localIndex) { hitArea.down = function () { selectedShopItemIndex = localIndex; shopItemHighlight.y = hitArea.y; shopItemHighlight.visible = true; updateBuyButtonState(); }; })(shopItemIndex); shopItems.push({ config: itemConfig, nameText: nameText, statusText: statusText, hitArea: hitArea }); shopItemIndex++; itemY += itemSpacing; } }); shopOverlayContainer.setChildIndex(shopItemHighlight, 1); buyButton.down = function () { if (selectedShopItemIndex === -1) { showShopMessage("Please select an item to buy."); return; } var item = shopItems[selectedShopItemIndex]; var itemConfig = item.config; if (itemConfig.owned) { showShopMessage("Item is already sold out."); return; } if (GameState.money < itemConfig.price) { showShopMessage("Not enough money!"); return; } var isCookingTool = ['grinder', 'spices', 'pot'].indexOf(itemConfig.id) > -1; if (isCookingTool && !storage.hasBoughtFirstTool) { storage.hasBoughtFirstTool = true; } var beforeRecipes = []; if (isCookingTool) { beforeRecipes = RECIPE_SYSTEM.getAvailableRecipes().map(function (r) { return r.id; }); } GameState.money -= itemConfig.price; storage.money = GameState.money; itemConfig.owned = true; if (itemConfig.id === 'fiberglass_rod') { storage.fiberglassRodOwned = true; GameState.justBoughtFiberglassRod = true; } else if (itemConfig.id === 'grinder') { storage.grinderOwned = true; } else if (itemConfig.id === 'spices') { storage.spicesOwned = true; } else if (itemConfig.id === 'pot') { storage.potOwned = true; } showShopMessage("Purchase successful!"); if (isCookingTool) { var afterRecipes = RECIPE_SYSTEM.getAvailableRecipes(); var newlyUnlocked = afterRecipes.filter(function (r) { return beforeRecipes.indexOf(r.id) === -1; }); if (newlyUnlocked.length > 0) { LK.setTimeout(function () { var unlockedNames = newlyUnlocked.map(function (r) { return r.recipe.name; }).join(', '); var message = "New recipes unlocked: " + unlockedNames; showShopMessage(message); }, 2500); } } item.statusText.destroy(); item.statusText = item.hitArea.addChild(new Text2('Sold Out', { size: 80, fill: '#AAAAAA', wordWrap: false, wordWrapWidth: null })); item.statusText.anchor.set(1, 0.5); item.statusText.x = itemRightX - itemLeftX; if (levelSelectElements && levelSelectElements.moneyDisplay) { levelSelectElements.moneyDisplay.setText('$' + GameState.money); } updateBuyButtonState(); }; updateBuyButtonState(); } function showShopOverlay() { if (shopOverlayOpen) { return; } if (levelSelectElements && typeof levelSelectElements.songOverlayOpen === 'function' && levelSelectElements.songOverlayOpen()) { levelSelectElements.hideSongSelection(); } if (cookingSongSelectOpen) { hideCookingSongSelection(); } createShopOverlayElements(); shopOverlayOpen = true; shopOverlayContainer.visible = true; shopOverlayContainer.alpha = 0; tween(shopOverlayContainer, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); } function hideShopOverlay() { if (!shopOverlayOpen) { return; } shopOverlayOpen = false; tween(shopOverlayContainer, { alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { if (shopOverlayContainer && !shopOverlayContainer.destroyed) { shopOverlayContainer.destroy(); shopOverlayContainer = null; } if (GameState.justBoughtFiberglassRod) { GameState.justBoughtFiberglassRod = false; var wasAlreadyUnlocked = GameState.currentDepth >= 1; if (!wasAlreadyUnlocked) { GameState.currentDepth = 1; storage.currentDepth = 1; // Give the first song of the new depth for free GameState.ownedSongs.push({ depth: 1, songIndex: 0 }); var storedSongs = storage.ownedSongs || {}; storedSongs['1_0'] = true; storage.ownedSongs = storedSongs; if (levelSelectElements && typeof levelSelectElements.playMidWatersUnlockSequence === 'function') { levelSelectElements.playMidWatersUnlockSequence(); } else { levelSelectElements.updateMapDisplay(); levelSelectElements.showLevelSelectMessage("New Area Unlocked: Mid Waters!"); } } } } }); } function createMenuOverlay() { menuOverlayContainer = beachScreen.addChild(new Container()); menuOverlayContainer.visible = false; var overlayBg = menuOverlayContainer.addChild(LK.getAsset('songCard', { x: 1024, y: 1366, width: 1400, height: 1000, color: 0x3a3a3a, anchorX: 0.5, anchorY: 0.5, alpha: 0.95 })); var title = menuOverlayContainer.addChild(new Text2('Menu', { size: 110, fill: 0xFFFFFF, align: 'center' })); title.anchor.set(0.5, 0); title.x = 1024; title.y = 1366 - 1000 / 2 + 50; var closeButton = menuOverlayContainer.addChild(LK.getAsset('closeButton', { anchorX: 0.5, anchorY: 0.5, x: 1024 + 1400 / 2 - 75, y: 1366 - 1000 / 2 + 75, tint: 0xff4444 })); var closeButtonText = menuOverlayContainer.addChild(new Text2('X', { size: 60, fill: 0xFFFFFF })); closeButtonText.anchor.set(0.5, 0.5); closeButtonText.x = closeButton.x; closeButtonText.y = closeButton.y; closeButton.down = function () { hideMenuOverlay(); }; // Get available recipes and create buttons var availableRecipes = RECIPE_SYSTEM.getAvailableRecipes(); var buttonWidth = 550; var buttonHeight = 100; var buttonsPerRow = 2; var buttonSpacing = 50; var startY = title.y + title.height + 100; for (var i = 0; i < availableRecipes.length; i++) { var recipeData = availableRecipes[i]; var recipe = recipeData.recipe; var recipeId = recipeData.id; var row = Math.floor(i / buttonsPerRow); var col = i % buttonsPerRow; var buttonX = 1024 + (col - 0.5) * (buttonWidth + buttonSpacing); var buttonY = startY + row * (buttonHeight + buttonSpacing); var recipeButton = menuOverlayContainer.addChild(LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, x: buttonX, y: buttonY, width: buttonWidth, height: buttonHeight, tint: cookingDish === recipeId ? 0x2e7d32 : 0x1976d2 // Green if selected, blue otherwise })); var recipeText = menuOverlayContainer.addChild(new Text2(recipe.name, { size: 50, fill: 0xFFFFFF, align: 'center' })); recipeText.anchor.set(0.5, 0.5); recipeText.x = buttonX; recipeText.y = buttonY; // Create closure to capture recipeId (function (id) { recipeButton.down = function () { cookingDish = id; storage.cookingDish = id; hideMenuOverlay(); }; })(recipeId); } } function showMenuOverlay() { if (menuOverlayOpen) { return; } if (cookingSongSelectOpen) { hideCookingSongSelection(); } createMenuOverlay(); menuOverlayOpen = true; menuOverlayContainer.visible = true; menuOverlayContainer.alpha = 0; tween(menuOverlayContainer, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); } function hideMenuOverlay() { if (!menuOverlayOpen) { return; } menuOverlayOpen = false; tween(menuOverlayContainer, { alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { if (menuOverlayContainer && !menuOverlayContainer.destroyed) { menuOverlayContainer.destroy(); menuOverlayContainer = null; } updateSandwichBoardIcon(); } }); } /**** * Fishing Screen ****/ function createFishingScreen() { var sky = fishingScreen.addChild(LK.getAsset('skybackground', { x: 0, y: -500 })); var water = fishingScreen.addChild(LK.getAsset('water', { x: 0, y: GAME_CONFIG.WATER_SURFACE_Y, width: 2048, height: 2732 - GAME_CONFIG.WATER_SURFACE_Y })); globalOceanBubbleContainer = fishingScreen.addChild(new Container()); globalSeaweedContainer = fishingScreen.addChild(new Container()); globalCloudContainer = fishingScreen.addChild(new Container()); bubbleContainer = fishingScreen.addChild(new Container()); musicNotesContainer = fishingScreen.addChild(new Container()); var waterSurfaceSegments = []; var waterSurfaceSegmentsBlueTemp = []; var waterSurfaceSegmentsWhiteTemp = []; var NUM_WAVE_SEGMENTS = 32; var SEGMENT_WIDTH = 2048 / NUM_WAVE_SEGMENTS; var SEGMENT_HEIGHT = 24; var WAVE_AMPLITUDE = 12; var WAVE_HALF_PERIOD_MS = 2500; var PHASE_DELAY_MS_PER_SEGMENT = WAVE_HALF_PERIOD_MS * 2 / NUM_WAVE_SEGMENTS; for (var i = 0; i < NUM_WAVE_SEGMENTS; i++) { var segment = LK.getAsset('waterSurface', { x: i * SEGMENT_WIDTH, y: GAME_CONFIG.WATER_SURFACE_Y, width: SEGMENT_WIDTH + 1, height: SEGMENT_HEIGHT, anchorX: 0, anchorY: 0.5, alpha: 0.8, tint: 0x4fc3f7 }); segment.baseY = GAME_CONFIG.WATER_SURFACE_Y; waterSurfaceSegmentsBlueTemp.push(segment); } for (var i = 0; i < NUM_WAVE_SEGMENTS; i++) { var whiteSegment = LK.getAsset('waterSurface', { x: i * SEGMENT_WIDTH, y: GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT / 2, width: SEGMENT_WIDTH + 1, height: SEGMENT_HEIGHT / 2, anchorX: 0, anchorY: 0.5, alpha: 0.6, tint: 0xffffff }); whiteSegment.baseY = GAME_CONFIG.WATER_SURFACE_Y - SEGMENT_HEIGHT / 2; waterSurfaceSegmentsWhiteTemp.push(whiteSegment); } var boat = fishingScreen.addChild(LK.getAsset('boat', { anchorX: 0.5, anchorY: 0.74, x: GAME_CONFIG.SCREEN_CENTER_X, y: GAME_CONFIG.WATER_SURFACE_Y })); for (var i = 0; i < waterSurfaceSegmentsBlueTemp.length; i++) { fishingScreen.addChild(waterSurfaceSegmentsBlueTemp[i]); waterSurfaceSegments.push(waterSurfaceSegmentsBlueTemp[i]); } for (var i = 0; i < waterSurfaceSegmentsWhiteTemp.length; i++) { fishingScreen.addChild(waterSurfaceSegmentsWhiteTemp[i]); waterSurfaceSegments.push(waterSurfaceSegmentsWhiteTemp[i]); } var fishermanContainer = fishingScreen.addChild(new Container()); var fisherman = fishermanContainer.addChild(LK.getAsset('fisherman', { anchorX: 0.5, anchorY: 1, x: GAME_CONFIG.SCREEN_CENTER_X - 100, y: GAME_CONFIG.WATER_SURFACE_Y - 70 })); var boatBaseY = boat.y; var fishermanBaseY = fishermanContainer.y; var boatWaveAmplitude = 10; var boatWaveHalfCycleDuration = 2000; var initialHookY = GAME_CONFIG.LANES[1].y; var fishingLineStartY = -100; var line = fishingScreen.addChild(LK.getAsset('fishingLine', { anchorX: 0.5, anchorY: 0, x: GAME_CONFIG.SCREEN_CENTER_X, y: GAME_CONFIG.WATER_SURFACE_Y + fishingLineStartY, width: 6, height: initialHookY - (GAME_CONFIG.WATER_SURFACE_Y + fishingLineStartY) })); var hook = fishingScreen.addChild(LK.getAsset('hook', { anchorX: 0.5, anchorY: 0.5, x: GAME_CONFIG.SCREEN_CENTER_X, y: initialHookY })); hook.originalY = initialHookY; var lineWaveAmplitude = 12; var lineWaveSpeed = 0.03; var linePhaseOffset = 0; function updateFishingLineWave() { linePhaseOffset += lineWaveSpeed; var rodTipX = fishermanContainer.x + fisherman.x + 85; var rodTipY = fishermanContainer.y + fisherman.y - fisherman.height; var waveOffset = Math.sin(linePhaseOffset) * lineWaveAmplitude; line.x = rodTipX + waveOffset * 0.3; line.y = rodTipY; hook.x = rodTipX + waveOffset; var hookAttachX = hook.x; var hookAttachY = hook.y - hook.height / 2; var deltaX = hookAttachX - line.x; var deltaY = hookAttachY - line.y; var actualLineLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY); line.height = actualLineLength; if (actualLineLength > 0.001) { line.rotation = Math.atan2(deltaY, deltaX) - Math.PI / 2; } else { line.rotation = 0; } hook.rotation = line.rotation; } var targetUpY = boatBaseY - boatWaveAmplitude; var targetDownY = boatBaseY + boatWaveAmplitude; var fishermanTargetUpY = fishermanBaseY - boatWaveAmplitude; var fishermanTargetDownY = fishermanBaseY + boatWaveAmplitude; function moveBoatAndFishermanUp() { if (!boat || boat.destroyed || !fishermanContainer || fishermanContainer.destroyed) { return; } tween(boat, { y: targetUpY }, { duration: boatWaveHalfCycleDuration, easing: tween.easeInOut, onFinish: moveBoatAndFishermanDown }); tween(fishermanContainer, { y: fishermanTargetUpY }, { duration: boatWaveHalfCycleDuration, easing: tween.easeInOut }); } function moveBoatAndFishermanDown() { if (!boat || boat.destroyed || !fishermanContainer || fishermanContainer.destroyed) { return; } tween(boat, { y: targetDownY }, { duration: boatWaveHalfCycleDuration, easing: tween.easeInOut, onFinish: moveBoatAndFishermanUp }); tween(fishermanContainer, { y: fishermanTargetDownY }, { duration: boatWaveHalfCycleDuration, easing: tween.easeInOut }); } var boatRotationAmplitude = 0.03; var boatRotationDuration = 3000; function rockBoatLeft() { if (!boat || boat.destroyed || !fisherman || fisherman.destroyed) { return; } tween(boat, { rotation: -boatRotationAmplitude }, { duration: boatRotationDuration, easing: tween.easeInOut, onFinish: rockBoatRight }); tween(fisherman, { rotation: boatRotationAmplitude }, { duration: boatRotationDuration, easing: tween.easeInOut }); } function rockBoatRight() { if (!boat || boat.destroyed || !fisherman || fisherman.destroyed) { return; } tween(boat, { rotation: boatRotationAmplitude }, { duration: boatRotationDuration, easing: tween.easeInOut, onFinish: rockBoatLeft }); tween(fisherman, { rotation: -boatRotationAmplitude }, { duration: boatRotationDuration, easing: tween.easeInOut }); } function startWaterSurfaceAnimationFunc() { var allSegments = waterSurfaceSegments; for (var k = 0; k < allSegments.length; k++) { var segment = allSegments[k]; if (!segment || segment.destroyed) { continue; } var segmentIndexForDelay = k % NUM_WAVE_SEGMENTS; (function (currentLocalSegment, currentLocalSegmentIndexForDelay) { var waveAnim = createWaveAnimation(currentLocalSegment, WAVE_AMPLITUDE, WAVE_HALF_PERIOD_MS); LK.setTimeout(function () { if (!currentLocalSegment || currentLocalSegment.destroyed) { return; } tween(currentLocalSegment, { y: currentLocalSegment.baseY - WAVE_AMPLITUDE }, { duration: WAVE_HALF_PERIOD_MS, easing: tween.easeInOut, onFinish: waveAnim.down }); }, currentLocalSegmentIndexForDelay * PHASE_DELAY_MS_PER_SEGMENT); })(segment, segmentIndexForDelay); } } function startBoatAndFishermanAnimationFunc() { if (boat && !boat.destroyed && fishermanContainer && !fishermanContainer.destroyed) { tween(boat, { y: targetUpY }, { duration: boatWaveHalfCycleDuration / 2, easing: tween.easeOut, onFinish: moveBoatAndFishermanDown }); tween(fishermanContainer, { y: fishermanTargetUpY }, { duration: boatWaveHalfCycleDuration / 2, easing: tween.easeOut }); rockBoatLeft(); } } var fishText = new Text2('Fish: 0/0', { size: 55, fill: 0xFFFFFF }); fishText.anchor.set(1, 0); fishText.x = 2048 - 50; fishText.y = 140; fishingScreen.addChild(fishText); var progressText = new Text2('0:00 / 0:00', { size: 50, fill: 0x4FC3F7 }); progressText.anchor.set(1, 0); progressText.x = 2048 - 50; progressText.y = 280; fishingScreen.addChild(progressText); return { boat: boat, fishermanContainer: fishermanContainer, fisherman: fisherman, hook: hook, line: line, updateFishingLineWave: updateFishingLineWave, fishText: fishText, progressText: progressText, waterSurfaceSegments: waterSurfaceSegments, bubbleContainer: bubbleContainer, musicNotesContainer: musicNotesContainer, startWaterSurfaceAnimation: startWaterSurfaceAnimationFunc, startBoatAndFishermanAnimation: startBoatAndFishermanAnimationFunc }; } /**** * Initialize Screen Elements ****/ var titleElements = createTitleScreen(); titleElements.tutorialButtonGfx = titleElements.tutorialButton; var levelSelectElements = createLevelSelectScreen(); var fishingElements = createFishingScreen(); var tutorialOverlayContainer = game.addChild(new Container()); tutorialOverlayContainer.visible = false; var tutorialTextBackground; var tutorialTextDisplay; var tutorialContinueButton; var tutorialContinueText; var tutorialLaneHighlights = []; var fishArray = []; var bubblesArray = []; var bubbleContainer; var musicNotesArray = []; var musicNotesContainer; var laneBrackets = []; var musicNoteSpawnCounter = 0; var MUSIC_NOTE_SPAWN_INTERVAL_TICKS = 45; var globalOceanBubblesArray = []; var globalOceanBubbleContainer; var globalOceanBubbleSpawnCounter = 0; var OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS = 40; var globalSeaweedArray = []; var globalSeaweedContainer; var globalSeaweedSpawnCounter = 0; var SEAWEED_SPAWN_INTERVAL_TICKS = 120; var MAX_SEAWEED_COUNT = 8; var globalCloudArray = []; var globalCloudContainer; var globalCloudSpawnCounter = 0; var CLOUD_SPAWN_INTERVAL_TICKS = 180; var MAX_CLOUD_COUNT = 5; var titleScreenOceanBubblesArray = []; var titleScreenOceanBubbleContainer; var titleScreenOceanBubbleSpawnCounter = 0; var titleScreenSeaweedArray = []; var titleScreenSeaweedContainer; var titleScreenSeaweedSpawnCounter = 0; var titleScreenCloudArray = []; var titleScreenCloudContainer; var titleScreenCloudSpawnCounter = 0; var titleSeagullSoundTimer = null; var titleBoatSoundTimer = null; /**** * Input State and Helpers for Fishing ****/ var inputState = { touching: false, touchLane: -1, touchStartTime: 0 }; function getTouchLane(y) { var boundary_lane0_lane1 = (GAME_CONFIG.LANES[0].y + GAME_CONFIG.LANES[1].y) / 2; var boundary_lane1_lane2 = (GAME_CONFIG.LANES[1].y + GAME_CONFIG.LANES[2].y) / 2; if (y < boundary_lane0_lane1) { return 0; } else if (y < boundary_lane1_lane2) { return 1; } else { return 2; } } function showFeedback(type, laneIndex) { var feedbackY = GAME_CONFIG.LANES[laneIndex].y; var indicator = new FeedbackIndicator(type); indicator.x = fishingElements.hook.x; indicator.y = feedbackY; fishingScreen.addChild(indicator); indicator.show(); } function animateHookCatch() { var hook = fishingElements.hook; var restingY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y; tween(hook, { y: restingY - 30 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(hook, { y: restingY }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { hook.originalY = restingY; } }); } }); } function handleFishingInput(x, y, isDown) { if (isDown) { swipeState.startX = x; swipeState.startY = y; // No action on 'down' other than recording start position for swipe/tap detection. } else { // This is a release (tap or swipe end) var deltaX = x - swipeState.startX; var deltaY = y - swipeState.startY; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); var isTap = distance < swipeState.tapThreshold; var isVerticalSwipe = !isTap && Math.abs(deltaY) > swipeState.swipeThreshold && Math.abs(deltaY) > Math.abs(deltaX); if (GameState.tutorialMode) { // --- TUTORIAL MODE INPUT HANDLING --- if (isTap) { // Process taps for catching fish ONLY if tutorial is NOT paused AND it's a fishing-action step. if (!GameState.tutorialPaused && (GameState.tutorialStep === 3 || GameState.tutorialStep === 4 || GameState.tutorialStep === 5)) { if (GameState.tutorialFish && !GameState.tutorialFish.caught && !GameState.tutorialFish.missed) { checkCatch(swipeState.currentLane); // Check against the hook's current lane } } // Other tutorial taps (like "CONTINUE" button) are handled by game.down. } else if (isVerticalSwipe) { // Determine if swiping is allowed in the current tutorial context. var canSwipeInTutorial = GameState.tutorialStep === 1 || GameState.tutorialStep === 2 || // Steps 1 & 2: Practice swiping (can be paused for "CONTINUE"). !GameState.tutorialPaused && (GameState.tutorialStep === 3 || GameState.tutorialStep === 4 || GameState.tutorialStep === 5); // Steps 3, 4 & 5: Active fishing/battle (must be unpaused). if (canSwipeInTutorial) { if (deltaY < 0) { // Swipe up swipeState.currentLane = Math.max(0, swipeState.currentLane - 1); } else { // Swipe down swipeState.currentLane = Math.min(GAME_CONFIG.LANES.length - 1, swipeState.currentLane + 1); } var targetY = GAME_CONFIG.LANES[swipeState.currentLane].y; tween(fishingElements.hook, { y: targetY }, { duration: 200, easing: tween.easeOut }); fishingElements.hook.originalY = targetY; GameState.hookTargetLaneIndex = swipeState.currentLane; updateLaneBracketsVisuals(); } } } else if (GameState.gameActive) { // --- REGULAR GAMEPLAY INPUT HANDLING --- if (isTap) { checkCatch(swipeState.currentLane); } else if (isVerticalSwipe) { if (deltaY < 0) { // Swipe up swipeState.currentLane = Math.max(0, swipeState.currentLane - 1); } else { // Swipe down swipeState.currentLane = Math.min(GAME_CONFIG.LANES.length - 1, swipeState.currentLane + 1); } var targetY = GAME_CONFIG.LANES[swipeState.currentLane].y; tween(fishingElements.hook, { y: targetY }, { duration: 200, easing: tween.easeOut }); fishingElements.hook.originalY = targetY; GameState.hookTargetLaneIndex = swipeState.currentLane; updateLaneBracketsVisuals(); } } } } /**** * Screen Management ****/ var originalShowScreen = _showScreen; _showScreen = function _showScreen(screenName) { var previousScreen = GameState.currentScreen; // Handle cooking screen specific cleanup or setup if coming from it if (previousScreen === 'cooking') { CookingState.gameActive = false; LK.stopMusic(); if (CookingState.fallingItems) { CookingState.fallingItems.forEach(function (item) { if (item && !item.destroyed) { item.destroy(); } }); CookingState.fallingItems = []; } } if (screenName === 'cooking') { // Stop sounds from previous screen if (previousScreen === 'levelSelect' && levelSelectElements && typeof levelSelectElements.stopLevelSelectAmbientSounds === 'function') { levelSelectElements.stopLevelSelectAmbientSounds(); } if (previousScreen === 'levelSelect' && levelSelectElements && typeof levelSelectElements.cleanupRipples === 'function') { levelSelectElements.cleanupRipples(); } titleScreen.visible = false; levelSelectScreen.visible = false; fishingScreen.visible = false; resultsScreen.visible = false; cookingScreen.visible = true; GameState.currentScreen = 'cooking'; cookingElements = createCookingScreen(); startCookingGame(); return; } // If not showing cooking screen, make sure it's hidden if (cookingScreen) { cookingScreen.visible = false; } // Call original showScreen for other screens originalShowScreen.call(this, screenName); }; // Capture original showScreen if it exists, otherwise this is the first definition. // This is tricky. Let's assume the provided code means `showScreen` is being defined for the first time // but needs to be extensible. The prompt's "var originalShowScreen = showScreen;" implies `showScreen` // already exists. If it's the first time, `originalShowScreen` would be undefined. // The safest way is to define the full function once, ensuring it handles all cases. // For this iterative process, I'm following the prompt's implied structure. if (typeof originalShowScreen === 'undefined') { // If showScreen wasn't defined before this block originalShowScreen = function originalShowScreen(screenNameInternal) { // This would be the fallback to the game's original showScreen logic before this modification. // For this exercise, we assume the wrapped version contains all necessary logic. // If this game were real, originalShowScreen would point to the previous state of showScreen. // Since we are replacing it entirely with the new comprehensive version, // the "originalShowScreen.call" part inside the new showScreen might need to be // the "else" block's content if we were truly "wrapping" an existing function piece by piece. // However, the prompt gives a full new showScreen. // Minimal original logic based on the provided gamecode.js: var previousScreenOriginal = GameState.currentScreen; // ... (rest of original visibility toggles and specific screen setups) // This part is now integrated into the main `showScreen` above. }; } function _showScreen(screenName) { if (globalEffectsLayer) { globalEffectsLayer.removeChildren(); } var previousScreen = GameState.currentScreen; // Clean up song overlays when leaving screens if (previousScreen === 'levelSelect') { if (levelSelectElements && typeof levelSelectElements.songOverlayOpen === 'function' && levelSelectElements.songOverlayOpen()) { levelSelectElements.hideSongSelection(); } if (shopOverlayOpen) { hideShopOverlay(); } } if (previousScreen === 'beach') { if (cookingSongSelectOpen) { hideCookingSongSelection(); } if (menuOverlayOpen) { hideMenuOverlay(); } } if (previousScreen === 'title' && titleElements) { stopTweens([titleElements.titleAnimationGroup, titleElements.blackOverlay, titleElements.titleImage, titleElements.startButton, titleElements.tutorialButton]); if (titleElements.titleWaterSurfaceSegments) { stopTweens(titleElements.titleWaterSurfaceSegments); } titleSeagullSoundTimer = clearTimer(titleSeagullSoundTimer, false); titleBoatSoundTimer = clearTimer(titleBoatSoundTimer, false); cleanupParticleArray(titleScreenOceanBubblesArray, titleScreenOceanBubbleContainer); cleanupParticleArray(titleScreenSeaweedArray, titleScreenSeaweedContainer); cleanupParticleArray(titleScreenCloudArray, titleScreenCloudContainer); } else if (previousScreen === 'levelSelect' && screenName !== 'levelSelect') { if (levelSelectElements && typeof levelSelectElements.cleanupRipples === 'function') { levelSelectElements.cleanupRipples(); } if (levelSelectElements && typeof levelSelectElements.stopLevelSelectAmbientSounds === 'function') { levelSelectElements.stopLevelSelectAmbientSounds(); } } if (previousScreen === 'beach') { stopBeachCrabSpawner(); if (beachCrabContainer) { cleanupParticleArray(activeBeachCrabs, beachCrabContainer); } // Stop wave animation and clear scheduled boat sounds if (typeof waveAnimationActive !== 'undefined') { waveAnimationActive = false; } if (typeof waveTimeoutId !== 'undefined' && waveTimeoutId !== null) { LK.clearTimeout(waveTimeoutId); waveTimeoutId = null; } } if (previousScreen === 'fishing' && GameState.tutorialMode && screenName !== 'fishing') { tutorialOverlayContainer.visible = false; if (GameState.tutorialFish && !GameState.tutorialFish.destroyed) { GameState.tutorialFish.destroy(); var idx = fishArray.indexOf(GameState.tutorialFish); if (idx > -1) { fishArray.splice(idx, 1); } GameState.tutorialFish = null; } if (tutorialLaneHighlights.length > 0) { tutorialLaneHighlights.forEach(function (overlay) { if (overlay && !overlay.destroyed) { overlay.destroy(); } }); tutorialLaneHighlights = []; } GameState.tutorialMode = false; } if (previousScreen === 'title' && screenName === 'levelSelect') { globalFadeOverlay.alpha = 0; globalFadeOverlay.visible = true; if (game.children.indexOf(globalFadeOverlay) !== -1) { game.setChildIndex(globalFadeOverlay, game.children.length - 1); } tween(globalFadeOverlay, { alpha: 1 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { titleScreen.visible = false; if (levelSelectElements) { if (typeof levelSelectElements.cleanupRipples === 'function') { levelSelectElements.cleanupRipples(); } if (typeof levelSelectElements.stopLevelSelectAmbientSounds === 'function') { levelSelectElements.stopLevelSelectAmbientSounds(); } } levelSelectElements = createLevelSelectScreen(); levelSelectScreen.visible = true; GameState.currentScreen = 'levelSelect'; if (levelSelectElements && typeof levelSelectElements.updateMapDisplay === 'function') { levelSelectElements.updateMapDisplay(); } if (levelSelectElements && typeof levelSelectElements.startLevelSelectAmbientSounds === 'function') { levelSelectElements.startLevelSelectAmbientSounds(); } tween(globalFadeOverlay, { alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { globalFadeOverlay.visible = false; } }); } }); return; } titleScreen.visible = false; levelSelectScreen.visible = false; fishingScreen.visible = false; resultsScreen.visible = false; beachScreen.visible = false; cookingResultsScreen.visible = false; GameState.currentScreen = screenName; switch (screenName) { case 'cookingResults': cookingResultsScreen.visible = true; createCookingResultsScreen(); cookingResultsScreen.alpha = 0; tween(cookingResultsScreen, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); break; case 'beach': beachScreen.visible = true; beachScreenElements = createBeachScreen(); updateSandwichBoardIcon(); waveAnimationActive = true; // Reset animation state playBeachIntroAnimation(); break; case 'title': titleScreen.visible = true; var seagullScheduler = createAmbientSoundScheduler({ screenName: 'title', sounds: ['seagull1', 'seagull2', 'seagull3'], baseDelay: 5000, variance: 10000 }); var boatScheduler = createAmbientSoundScheduler({ screenName: 'title', sounds: 'boatsounds', baseDelay: 6000, variance: 0 }); if (titleElements.startTitleWaterSurfaceAnimation) { titleElements.startTitleWaterSurfaceAnimation(); } if (titleElements.moveTitleBoatGroupUp) { titleElements.moveTitleBoatGroupUp(); } if (titleElements.rockTitleBoatGroupLeft) { titleElements.rockTitleBoatGroupLeft(); } var initialSeagullSounds = ['seagull1', 'seagull2', 'seagull3']; var initialRandomSoundId = initialSeagullSounds[Math.floor(Math.random() * initialSeagullSounds.length)]; LK.getSound(initialRandomSoundId).play(); LK.getSound('boatsounds').play(); seagullScheduler.start(); boatScheduler.start(); titleScreenOceanBubbleSpawnCounter = 0; titleScreenSeaweedSpawnCounter = 0; titleScreenCloudSpawnCounter = 0; var ZOOM_DURATION = 8000; var OVERLAY_FADE_DELAY = 1000; var OVERLAY_FADE_DURATION = 3000; var TEXT_DELAY = 4000; var BUTTON_DELAY = 5500; titleElements.titleAnimationGroup.x = GAME_CONFIG.SCREEN_CENTER_X; titleElements.titleAnimationGroup.y = GAME_CONFIG.SCREEN_CENTER_Y; titleElements.titleAnimationGroup.alpha = 1; titleElements.titleAnimationGroup.scale.set(3.0); titleElements.blackOverlay.alpha = 1; if (titleElements.titleImage) { titleElements.titleImage.alpha = 0; } titleElements.startButton.alpha = 0; titleElements.tutorialButton.alpha = 0; tween(titleElements.titleAnimationGroup, { scaleX: 1.8, scaleY: 1.8 }, { duration: ZOOM_DURATION, easing: tween.easeInOut }); LK.setTimeout(function () { tween(titleElements.blackOverlay, { alpha: 0 }, { duration: OVERLAY_FADE_DURATION, easing: tween.easeInOut }); }, OVERLAY_FADE_DELAY); LK.setTimeout(function () { if (titleElements.titleImage) { tween(titleElements.titleImage, { alpha: 1 }, { duration: 1200, easing: tween.easeOut }); } }, TEXT_DELAY); LK.setTimeout(function () { tween(titleElements.startButton, { alpha: 1 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { tween(titleElements.tutorialButton, { alpha: 1 }, { duration: 1000, easing: tween.easeOut }); } }); }, BUTTON_DELAY); break; case 'levelSelect': if (previousScreen === 'cookingResults' && GameState.money > 0 && !storage.shopUnlocked) { LK.setTimeout(function () { if (levelSelectElements && typeof levelSelectElements.playShopUnlockSequence === 'function') { levelSelectElements.playShopUnlockSequence(); } }, 500); } if (previousScreen === 'results') { if (GameState.showRestaurantUnlockSequence) { GameState.showRestaurantUnlockSequence = false; LK.setTimeout(function () { if (levelSelectElements && typeof levelSelectElements.playRestaurantUnlockSequence === 'function') { levelSelectElements.playRestaurantUnlockSequence(); } }, 500); } if (levelSelectElements) { if (typeof levelSelectElements.cleanupRipples === 'function') { levelSelectElements.cleanupRipples(); } if (typeof levelSelectElements.stopLevelSelectAmbientSounds === 'function') { levelSelectElements.stopLevelSelectAmbientSounds(); } if (typeof levelSelectElements.restartParticleTimers === 'function') { levelSelectElements.restartParticleTimers(); } if (typeof levelSelectElements.updateMapDisplay === 'function') { levelSelectElements.updateMapDisplay(); } if (typeof levelSelectElements.startLevelSelectAmbientSounds === 'function') { levelSelectElements.startLevelSelectAmbientSounds(); } } } else if (previousScreen !== 'title') { // This handles other screens like fishing AND beach if (levelSelectElements) { // ADD: Clean up and restart for beach screen too if (previousScreen === 'beach') { if (typeof levelSelectElements.cleanupRipples === 'function') { levelSelectElements.cleanupRipples(); } if (typeof levelSelectElements.stopLevelSelectAmbientSounds === 'function') { levelSelectElements.stopLevelSelectAmbientSounds(); } if (typeof levelSelectElements.restartParticleTimers === 'function') { levelSelectElements.restartParticleTimers(); } } if (typeof levelSelectElements.updateMapDisplay === 'function') { levelSelectElements.updateMapDisplay(); } if (typeof levelSelectElements.startLevelSelectAmbientSounds === 'function') { levelSelectElements.startLevelSelectAmbientSounds(); } } } levelSelectScreen.visible = true; break; case 'fishing': fishingScreen.visible = true; playIntroAnimation(); break; case 'results': resultsScreen.visible = true; createResultsScreen(); resultsScreen.alpha = 0; tween(resultsScreen, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); break; } if (screenName !== 'fishing' && GameState.tutorialMode) { tutorialOverlayContainer.visible = false; } } /**** * Intro Animation ****/ function playIntroAnimation() { GameState.introPlaying = true; GameState.gameActive = false; if (fishingElements) { if (typeof fishingElements.startWaterSurfaceAnimation === 'function') { fishingElements.startWaterSurfaceAnimation(); } if (typeof fishingElements.startBoatAndFishermanAnimation === 'function') { fishingElements.startBoatAndFishermanAnimation(); } } var fc = fishingElements.fishermanContainer; var f = fishingElements.fisherman; var rodTipCalculatedX = fc.x + f.x + 85; var rodTipCalculatedY = fc.y + f.y - f.height; var initialHookDangleY = rodTipCalculatedY + 50; fishingElements.hook.y = initialHookDangleY; var INITIAL_ZOOM_FACTOR = 1.5; var pivotX = fishingElements.boat.x; var pivotY = fishingElements.boat.y - fishingElements.boat.height * (fishingElements.boat.anchor.y - 0.5); fishingScreen.pivot.set(pivotX, pivotY); var screenCenterX = 2048 / 2; var screenCenterY = 2732 / 2; fishingScreen.x = screenCenterX; fishingScreen.y = screenCenterY; fishingScreen.scale.set(INITIAL_ZOOM_FACTOR, INITIAL_ZOOM_FACTOR); var introDuration = 2000; tween(fishingScreen.scale, { x: 1, y: 1 }, { duration: introDuration, easing: tween.easeInOut }); tween(fishingScreen, { x: pivotX, y: pivotY }, { duration: introDuration, easing: tween.easeInOut }); // CRITICAL FIX: Set both visual and logical positions swipeState.currentLane = 1; // Ensure logical state is center GameState.hookTargetLaneIndex = 1; // Ensure game state is center var targetHookY = GAME_CONFIG.LANES[GameState.hookTargetLaneIndex].y; LK.setTimeout(function () { LK.getSound('reel').play(); }, 600); tween(fishingElements.hook, { y: targetHookY }, { duration: introDuration * 0.8, delay: introDuration * 0.2, easing: tween.easeOut, onFinish: function onFinish() { GameState.introPlaying = false; fishingScreen.pivot.set(0, 0); fishingScreen.x = 0; fishingScreen.y = 0; fishingElements.hook.originalY = targetHookY; if (GameState.tutorialMode) { // CRITICAL FIX: Initialize tutorial with same state as regular gameplay swipeState.currentLane = 1; GameState.hookTargetLaneIndex = 1; fishingElements.hook.y = GAME_CONFIG.LANES[1].y; fishingElements.hook.originalY = GAME_CONFIG.LANES[1].y; GameState.gameActive = false; createTutorialElements(); runTutorialStep(); } else { startFishingSession(); } } }); } function showRecipeSwitchHint() { var messageContainer = beachScreen.addChild(new Container()); messageContainer.x = GAME_CONFIG.SCREEN_CENTER_X; messageContainer.y = 2732 / 2; var bgWidth = 1600; var bgHeight = 500; var messageBg = messageContainer.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, width: bgWidth, height: bgHeight, alpha: 0.95 })); var messageText = messageContainer.addChild(new Text2("You've unlocked new cooking tools! Tap the menu board to switch to new recipes.", { size: 65, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: bgWidth - 100, lineHeight: 75 })); messageText.anchor.set(0.5, 0.5); messageText.y = -50; var continueButton = messageContainer.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: bgHeight / 2 - 100, tint: 0x1976d2, width: 400, height: 100 })); var continueText = messageContainer.addChild(new Text2('GOT IT!', { size: 50, fill: 0xFFFFFF })); continueText.anchor.set(0.5, 0.5); continueText.x = continueButton.x; continueText.y = continueButton.y; continueButton.down = function () { tween(messageContainer, { alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (messageContainer && !messageContainer.destroyed) { messageContainer.destroy(); } } }); }; messageContainer.alpha = 0; tween(messageContainer, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); beachScreen.setChildIndex(messageContainer, beachScreen.children.length - 1); } function showBeachWelcomeMessage() { var messageContainer = beachScreen.addChild(new Container()); messageContainer.x = GAME_CONFIG.SCREEN_CENTER_X; messageContainer.y = 2732 / 2; var bgWidth = 1600; var bgHeight = 600; var messageBg = messageContainer.addChild(LK.getAsset('songCard', { anchorX: 0.5, anchorY: 0.5, width: bgWidth, height: bgHeight, alpha: 0.95 })); var messageText = messageContainer.addChild(new Text2("Welcome to the Fish Shack! Customers are lining up already. Time to cook the fish you caught and make some money! Tap the boombox in the corner to pick a song and get started.", { size: 65, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: bgWidth - 100, lineHeight: 75 })); messageText.anchor.set(0.5, 0.5); messageText.y = -50; var continueButton = messageContainer.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: bgHeight / 2 - 100, tint: 0x1976d2, width: 400, height: 100 })); var continueText = messageContainer.addChild(new Text2('GOT IT!', { size: 50, fill: 0xFFFFFF })); continueText.anchor.set(0.5, 0.5); continueText.x = continueButton.x; continueText.y = continueButton.y; continueButton.down = function () { tween(messageContainer, { alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (messageContainer && !messageContainer.destroyed) { messageContainer.removeChildren(); messageContainer.destroy(); } } }); }; messageContainer.alpha = 0; tween(messageContainer, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); beachScreen.setChildIndex(messageContainer, beachScreen.children.length - 1); } function playBeachIntroAnimation() { GameState.introPlaying = true; if (!beachScreenElements) { beachScreenElements = createBeachScreen(); } // Spawn a crab or two at the start of the beach scene spawnBeachCrab(); LK.setTimeout(spawnBeachCrab, 750 + Math.random() * 500); var fishermanChef = beachScreenElements.fishermanChef; var customerContainer = beachScreenElements.customerContainer; if (!fishermanChef || !customerContainer) { GameState.introPlaying = false; return; } var INITIAL_ZOOM_FACTOR = 2.0; var pivotX = fishermanChef.x; var pivotY = fishermanChef.y; beachScreen.pivot.set(pivotX, pivotY); beachScreen.x = 2048 / 2; beachScreen.y = 2732 / 2; beachScreen.scale.set(INITIAL_ZOOM_FACTOR, INITIAL_ZOOM_FACTOR); var introDuration = 2500; tween(beachScreen.scale, { x: 1, y: 1 }, { duration: introDuration, easing: tween.easeInOut }); tween(beachScreen, { x: pivotX, y: pivotY }, { duration: introDuration, easing: tween.easeInOut, onFinish: function onFinish() { GameState.introPlaying = false; beachScreen.pivot.set(0, 0); beachScreen.x = 0; beachScreen.y = 0; if (beachScreenElements && beachScreenElements.boombox && !beachScreenElements.boombox.destroyed) { tween(beachScreenElements.boombox, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); } startBeachCrabSpawner(); if (!storage.beachWelcomeShown) { storage.beachWelcomeShown = true; showBeachWelcomeMessage(); } else if (storage.hasBoughtFirstTool && !storage.recipeSwitchHintShown) { storage.recipeSwitchHintShown = true; showRecipeSwitchHint(); } } }); customerContainer.removeChildren(); var NUM_CUSTOMERS = 7; var lineupBaseX = GAME_CONFIG.SCREEN_CENTER_X; // Center the customer line on the fish shack var lineupStartY = GAME_CONFIG.WATER_SURFACE_Y + 280; // Start line lower var verticalSpacing = 80; // Increased vertical spread var horizontalJitter = 40; // Max horizontal random offset for (var i = 0; i < NUM_CUSTOMERS; i++) { var fromLeft = Math.random() < 0.5; // Randomly select from the four customer assets var customerAssets = ['customer', 'customer2', 'customer3', 'customer4']; var selectedCustomerAsset = customerAssets[Math.floor(Math.random() * customerAssets.length)]; var customer = customerContainer.addChild(LK.getAsset(selectedCustomerAsset, { anchorX: 0.5, anchorY: 1.0, x: fromLeft ? -200 : 2048 + 200, y: lineupStartY + i * verticalSpacing, alpha: 0 })); var targetX = lineupBaseX + (Math.random() - 0.5) * horizontalJitter * 2; var delay = i * 300; var animDuration = 1500; tween(customer, { alpha: 1 }, { duration: 500, delay: delay, easing: tween.easeIn }); tween(customer, { x: targetX }, { duration: animDuration, delay: delay, easing: tween.easeOut }); } } function animateCustomerLine() { if (!beachScreenElements || !beachScreenElements.customerContainer) { return; } var customerContainer = beachScreenElements.customerContainer; var customers = []; for (var i = 0; i < customerContainer.children.length; i++) { customers.push(customerContainer.children[i]); } if (customers.length === 0) { return; } var firstCustomer = customers[0]; tween(firstCustomer, { alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (firstCustomer && !firstCustomer.destroyed) { firstCustomer.destroy(); } } }); var lineupStartY = GAME_CONFIG.WATER_SURFACE_Y + 280; var verticalSpacing = 80; for (var i = 1; i < customers.length; i++) { var customerToMove = customers[i]; var newY = lineupStartY + (i - 1) * verticalSpacing; tween(customerToMove, { y: newY }, { duration: 800, easing: tween.easeInOut, delay: 100 }); } var newCustomerIndex = customers.length - 1; var fromLeft = Math.random() < 0.5; var customerAssets = ['customer', 'customer2', 'customer3', 'customer4']; var selectedCustomerAsset = customerAssets[Math.floor(Math.random() * customerAssets.length)]; var newCustomerTargetY = lineupStartY + newCustomerIndex * verticalSpacing; var horizontalJitter = 40; var lineupBaseX = GAME_CONFIG.SCREEN_CENTER_X; var newCustomerTargetX = lineupBaseX + (Math.random() - 0.5) * horizontalJitter * 2; var newCustomer = customerContainer.addChild(LK.getAsset(selectedCustomerAsset, { anchorX: 0.5, anchorY: 1.0, x: fromLeft ? -200 : 2048 + 200, y: newCustomerTargetY, alpha: 0 })); tween(newCustomer, { alpha: 1 }, { duration: 500, delay: 800, easing: tween.easeIn }); tween(newCustomer, { x: newCustomerTargetX }, { duration: 1500, delay: 800, easing: tween.easeOut }); } function playBeachOutroAnimation() { if (!beachScreenElements) { return; } var fishermanChef = beachScreenElements.fishermanChef; var customerContainer = beachScreenElements.customerContainer; if (!fishermanChef || !customerContainer) { return; } var INITIAL_ZOOM_FACTOR = 2.0; var pivotX = fishermanChef.x; var pivotY = fishermanChef.y; beachScreen.pivot.set(pivotX, pivotY); beachScreen.x = pivotX; beachScreen.y = pivotY; var outroDuration = 2500; tween(beachScreen.scale, { x: INITIAL_ZOOM_FACTOR, y: INITIAL_ZOOM_FACTOR }, { duration: outroDuration, easing: tween.easeInOut }); tween(beachScreen, { x: 2048 / 2, y: 2732 / 2 }, { duration: outroDuration, easing: tween.easeInOut }); if (beachScreenElements.boombox && !beachScreenElements.boombox.destroyed) { tween.stop(beachScreenElements.boombox); tween.stop(beachScreenElements.boombox.scale); tween(beachScreenElements.boombox, { alpha: 0 }, { duration: 1000, easing: tween.easeOut }); } var customers = customerContainer.children; for (var i = 0; i < customers.length; i++) { var customer = customers[i]; var toLeft = Math.random() < 0.5; var targetX = toLeft ? -200 : 2048 + 200; tween(customer, { x: targetX, alpha: 0 }, { duration: 1500, delay: Math.random() * 500, easing: tween.easeIn }); } } /**** * Level Select Logic ****/ function updateLevelSelectScreen() { var elements = levelSelectElements; elements.moneyDisplay.setText('$' + GameState.money); createDepthTabs(); updateSongDisplay(); updateShopButton(); } function createDepthTabs() { levelSelectElements.depthTabs.forEach(function (tab) { if (tab.container) { tab.container.destroy(); } }); levelSelectElements.depthTabs = []; var tabStartY = 600; var tabSpacing = 250; for (var i = 0; i <= GameState.currentDepth; i++) { var depth = GAME_CONFIG.DEPTHS[i]; var isSelected = i === GameState.selectedDepth; var tabContainer = levelSelectScreen.addChild(new Container()); var tab = tabContainer.addChild(LK.getAsset('depthTab', { anchorX: 0.5, anchorY: 0.5, x: 200 + i * tabSpacing, y: tabStartY, tint: isSelected ? 0x1976d2 : 0x455a64, width: 400, height: 160 })); var tabText = new Text2(depth.name.split(' ')[0], { size: 40, fill: 0xFFFFFF }); tabText.anchor.set(0.5, 0.5); tabText.x = 200 + i * tabSpacing; tabText.y = tabStartY; tabContainer.addChild(tabText); levelSelectElements.depthTabs.push({ container: tabContainer, tab: tab, depthIndex: i }); } } function updateSongDisplay() { var elements = levelSelectElements; var depth = GAME_CONFIG.DEPTHS[GameState.selectedDepth]; var song = depth.songs[GameState.selectedSong]; var owned = GameState.hasSong(GameState.selectedDepth, GameState.selectedSong); elements.songTitle.setText(song.name); elements.songInfo.setText('BPM: ' + song.bpm + ' | Duration: ' + formatTime(song.duration)); var minEarnings = Math.floor(depth.fishValue * 20); var maxEarnings = Math.floor(depth.fishValue * 60); elements.songEarnings.setText('Potential Earnings: $' + minEarnings + '-$' + maxEarnings); if (owned) { elements.playButtonText.setText('PLAY'); elements.playButton.tint = 0x1976d2; } else { elements.playButtonText.setText('BUY ($' + song.cost + ')'); elements.playButton.tint = GameState.money >= song.cost ? 0x2e7d32 : 0x666666; } elements.leftArrow.tint = GameState.selectedSong > 0 ? 0x1976d2 : 0x666666; elements.rightArrow.tint = GameState.selectedSong < depth.songs.length - 1 ? 0x1976d2 : 0x666666; } function updateShopButton() { var elements = levelSelectElements; elements.shopButtonText.setText('UPGRADE ROD'); elements.shopButton.tint = 0x666666; } function formatTime(ms) { var seconds = Math.floor(ms / 1000); var minutes = Math.floor(seconds / 60); seconds = seconds % 60; return minutes + ':' + (seconds < 10 ? '0' : '') + seconds; } /**** * Fishing Game Logic ****/ function startFishingSession() { GameState.tutorialMode = false; GameState.sessionFishCaught = 0; GameState.sessionFishCaughtByType = {}; GameState.sessionFishSpawned = 0; GameState.gameActive = true; GameState.songStartTime = 0; GameState.musicNotesActive = true; GameState.fishingEnding = false; // Reset battle state GameState.battleState = BATTLE_STATES.NONE; GameState.currentBattleFish = null; GameState.nextFishSpawnTime = 0; ImprovedRhythmSpawner.reset(); musicNotesArray = []; if (fishingElements && fishingElements.musicNotesContainer) { fishingElements.musicNotesContainer.removeChildren(); } musicNoteSpawnCounter = 0; globalOceanBubblesArray = []; if (globalOceanBubbleContainer) { globalOceanBubbleContainer.removeChildren(); } globalOceanBubbleSpawnCounter = 0; globalSeaweedArray = []; if (globalSeaweedContainer) { globalSeaweedContainer.removeChildren(); } globalSeaweedSpawnCounter = 0; globalCloudArray = []; if (globalCloudContainer) { globalCloudContainer.removeChildren(); } globalCloudSpawnCounter = 0; fishArray.forEach(function (fish) { fish.destroy(); }); fishArray = []; PatternGenerator.reset(); if (laneBrackets && laneBrackets.length > 0) { laneBrackets.forEach(function (bracketPair) { if (bracketPair.left && !bracketPair.left.destroyed) { bracketPair.left.destroy(); } if (bracketPair.right && !bracketPair.right.destroyed) { bracketPair.right.destroy(); } }); } laneBrackets = []; var bracketAssetHeight = 150; var bracketAssetWidth = 75; if (fishingScreen && !fishingScreen.destroyed) { for (var i = 0; i < GAME_CONFIG.LANES.length; i++) { var laneY = GAME_CONFIG.LANES[i].y; var leftBracket = fishingScreen.addChild(LK.getAsset('lanebracket', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5, x: bracketAssetWidth / 2, y: laneY, height: bracketAssetHeight })); var rightBracket = fishingScreen.addChild(LK.getAsset('lanebracket', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5, scaleX: -1, x: 2048 - bracketAssetWidth / 2, y: laneY, height: bracketAssetHeight })); laneBrackets.push({ left: leftBracket, right: rightBracket }); } } var songConfig = GameState.getCurrentSongConfig(); var musicIdToPlay = songConfig.musicId || 'rhythmTrack'; GameState.currentPlayingMusicId = musicIdToPlay; if (musicIdToPlay === 'morningtide') { GameState.currentPlayingMusicInitialVolume = 1.0; } else { GameState.currentPlayingMusicInitialVolume = 0.8; } LK.playMusic(GameState.currentPlayingMusicId); } // Modify the checkCatch function to use the current player lane instead of detecting from touch position function checkCatch(playerLane) { var hookX = fishingElements.hook.x; if (GameState.tutorialMode) { var tutorialFish = GameState.tutorialFish; if (!tutorialFish || tutorialFish.caught || tutorialFish.missed) { // No active tutorial fish to interact with, or it's already been handled. return; } // For tutorial steps involving active fishing, ensure the hook is in the fish's lane for a valid attempt. // This is a soft check; the main check is distance. if ((GameState.tutorialStep === 3 || GameState.tutorialStep === 4 || GameState.tutorialStep === 5) && tutorialFish.lane !== playerLane) { // If player taps while hook is in the wrong lane, it's a miss conceptually. // However, a player might tap just as they finish a swipe. Distance check is more robust. // For tutorial clarity, if the tap is clearly in the wrong lane and far from the fish, it's a miss. } var distance = Math.abs(tutorialFish.x - hookX); var caughtType = null; if (distance < GAME_CONFIG.PERFECT_WINDOW) { caughtType = 'perfect'; } else if (distance < GAME_CONFIG.GOOD_WINDOW) { caughtType = 'good'; } else if (distance < GAME_CONFIG.MISS_WINDOW) { // Looser window for tutorial's "good" caughtType = 'good'; } else { caughtType = 'miss'; } showFeedback(caughtType, playerLane); // Show visual feedback (Perfect, Good, Miss) if (caughtType === 'perfect' || caughtType === 'good') { LK.getSound('catch').play(); animateHookCatch(); var isFullyCaughtByThisTap = tutorialFish.handleTap(); // This updates tap count and pushes back if multi-tap if (isFullyCaughtByThisTap) { tutorialFish.catchFish(); // Handles animation, sets internal 'caught' flag. // Actual removal from fishArray and destruction will happen in game.down // after "CONTINUE" is pressed, to prevent issues if player exits early. GameState.tutorialPaused = true; tutorialFish.wasCaughtThisInteraction = true; // Signal to game.down if (GameState.tutorialStep === 3) { setTutorialText("Great catch! You tapped correctly. Tap 'CONTINUE'."); } else if (GameState.tutorialStep === 4) { setTutorialText("Perfect! You're mastering the swipe-and-tap. Tap 'CONTINUE'."); } // Step 5 is usually an informational message after step 4's catch. } else { // Fish was tapped (multi-tap) but not fully caught. It's pushed back. // Tutorial remains unpaused. Player needs to tap again. No text change yet. } } else { // Miss LK.getSound('miss').play(); // Don't set tutorialFish.missed = true permanently, as the player will retry the step. GameState.tutorialPaused = true; tutorialFish.wasMissedThisInteraction = true; // Signal to game.down if (GameState.tutorialStep === 3) { setTutorialText("Almost! Make sure you're in the right lane and tap closer to the fish. Tap 'CONTINUE' to try again."); } else if (GameState.tutorialStep === 4) { setTutorialText("Oops! Try SWIPING to the TOP lane first, then TAP the fish when it's close. Tap 'CONTINUE' to try again."); } } return; } // Rest of the function for non-tutorial mode var closestFishInLane = null; var closestDistance = Infinity; for (var i = 0; i < fishArray.length; i++) { var fish = fishArray[i]; if (!fish.caught && !fish.missed && fish.lane === playerLane && !fish.isPushedBack) { var distance = Math.abs(fish.x - hookX); var maxCatchDistance = GAME_CONFIG.MISS_WINDOW * 2; if (distance < maxCatchDistance && distance < closestDistance) { closestDistance = distance; closestFishInLane = fish; } } } if (!closestFishInLane) { // Miss - play sound LK.getSound('miss').play(); // Check if we missed during a battle if (GameState.battleState === BATTLE_STATES.ACTIVE && GameState.currentBattleFish) { GameState.currentBattleFish.missedTap(); } // Flash lane brackets red if (laneBrackets && laneBrackets[playerLane]) { var leftBracket = laneBrackets[playerLane].left; var rightBracket = laneBrackets[playerLane].right; var tintToRedDuration = 50; var holdRedDuration = 100; var tintToWhiteDuration = 150; if (leftBracket && !leftBracket.destroyed) { tween(leftBracket, { tint: 0xFF0000 }, { duration: tintToRedDuration, easing: tween.linear, onFinish: function onFinish() { LK.setTimeout(function () { if (leftBracket && !leftBracket.destroyed) { tween(leftBracket, { tint: 0xFFFFFF }, { duration: tintToWhiteDuration, easing: tween.linear }); } }, holdRedDuration); } }); } if (rightBracket && !rightBracket.destroyed) { tween(rightBracket, { tint: 0xFF0000 }, { duration: tintToRedDuration, easing: tween.linear, onFinish: function onFinish() { LK.setTimeout(function () { if (rightBracket && !rightBracket.destroyed) { tween(rightBracket, { tint: 0xFFFFFF }, { duration: tintToWhiteDuration, easing: tween.linear }); } }, holdRedDuration); } }); } } return; } // Rest of catch logic remains the same... var catchType = ''; if (closestDistance < GAME_CONFIG.PERFECT_WINDOW) { catchType = 'perfect'; } else if (closestDistance < GAME_CONFIG.GOOD_WINDOW) { catchType = 'good'; } else if (closestDistance < GAME_CONFIG.MISS_WINDOW) { catchType = 'good'; } else { showFeedback('miss', playerLane); LK.getSound('miss').play(); if (closestFishInLane) { closestFishInLane.missed = true; } return; } showFeedback(catchType, playerLane); var isFullyCaught = closestFishInLane.handleTap(); if (isFullyCaught) { closestFishInLane.catchFish(); var fishIndex = fishArray.indexOf(closestFishInLane); if (fishIndex > -1) { fishArray.splice(fishIndex, 1); } GameState.sessionFishCaught++; GameState.totalFishCaught++; var fishAsset = closestFishInLane.assetName; if (!GameState.sessionFishCaughtByType) { GameState.sessionFishCaughtByType = {}; } GameState.sessionFishCaughtByType[fishAsset] = (GameState.sessionFishCaughtByType[fishAsset] || 0) + 1; var inventoryFishType = closestFishInLane.getInventoryFishType(); if (inventoryFishType) { FishInventory.addFish(inventoryFishType, 1); } // Show fish name popup var fishName = FISH_NAMES[closestFishInLane.assetName] || "Fish"; var fishNamePopup = new Text2("+" + fishName, { size: 120, fill: 0xFFFFFF, align: 'center', stroke: 0x000000, strokeThickness: 6 }); fishNamePopup.anchor.set(0.5, 0.5); fishNamePopup.x = GAME_CONFIG.SCREEN_CENTER_X; fishNamePopup.y = GAME_CONFIG.BOAT_Y - 70; if (fishingScreen && !fishingScreen.destroyed) { fishingScreen.addChild(fishNamePopup); } tween(fishNamePopup, { y: fishNamePopup.y - 200, alpha: 0 }, { duration: 1800, easing: tween.easeOut, onFinish: function onFinish() { if (fishNamePopup && !fishNamePopup.destroyed) { fishNamePopup.destroy(); } } }); } else { if (!closestFishInLane.isInBattle) { closestFishInLane.startBattle(); } } var catchSounds = ['catch', 'catch2', 'catch3', 'catch4']; var randomCatchSound = catchSounds[Math.floor(Math.random() * catchSounds.length)]; LK.getSound(randomCatchSound).play(); animateHookCatch(); } function updateFishingUI() { var elements = fishingElements; elements.fishText.setText('Fish: ' + GameState.sessionFishCaught + '/' + GameState.sessionFishSpawned); if (GameState.songStartTime > 0) { var currentTime = LK.ticks * (1000 / 60); var elapsed = currentTime - GameState.songStartTime; var songConfig = GameState.getCurrentSongConfig(); elements.progressText.setText(formatTime(elapsed) + ' / ' + formatTime(songConfig.duration)); } } function endFishingSession() { GameState.gameActive = false; GameState.tutorialMode = false; // Check if there are any catchable fish still on screen var catchableFishRemaining = fishArray.filter(function (fish) { return !fish.caught && !fish.missed && !fish.destroyed; }); if (catchableFishRemaining.length > 0) { // Set a flag to indicate we're waiting for fish to be caught GameState.fishingEnding = true; // Continue game loop to allow catching remaining fish GameState.gameActive = true; // Don't stop music yet - let it continue playing return; } // No catchable fish remaining, proceed with normal cleanup performFinalCleanup(); } function performFinalCleanup() { // Stop music and animations stopTweens([fishingElements.boat, fishingElements.fishermanContainer, fishingElements.fisherman]); if (fishingElements && fishingElements.waterSurfaceSegments) { stopTweens(fishingElements.waterSurfaceSegments); } LK.stopMusic(); ImprovedRhythmSpawner.reset(); // Clean up lane brackets if (laneBrackets && laneBrackets.length > 0) { laneBrackets.forEach(function (bracketPair) { if (bracketPair.left && !bracketPair.left.destroyed) { bracketPair.left.destroy(); } if (bracketPair.right && !bracketPair.right.destroyed) { bracketPair.right.destroy(); } }); laneBrackets = []; } // Clean up fish fishArray.forEach(function (fish) { fish.destroy(); }); fishArray = []; // Clean up particles GameState.musicNotesActive = false; if (fishingElements && fishingElements.musicNotesContainer) { fishingElements.musicNotesContainer.removeChildren(); } musicNotesArray = []; cleanupParticleArray(globalOceanBubblesArray, globalOceanBubbleContainer); cleanupParticleArray(globalSeaweedArray, globalSeaweedContainer); cleanupParticleArray(globalCloudArray, globalCloudContainer); createResultsScreen(); _showScreen('results'); } function createResultsScreen() { resultsScreen.removeChildren(); var resultsBg = resultsScreen.addChild(LK.getAsset('screenBackground', { x: 0, y: 0, alpha: 0.9, height: 2732 })); var title = new Text2('Fishing Complete!', { size: 100, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null }); title.anchor.set(0.5, 0.5); title.x = GAME_CONFIG.SCREEN_CENTER_X; title.y = 400; resultsScreen.addChild(title); var yPos = 550; var caughtFishTypes = Object.keys(GameState.sessionFishCaughtByType || {}); if (caughtFishTypes.length > 0) { for (var i = 0; i < caughtFishTypes.length; i++) { var fishAsset = caughtFishTypes[i]; var count = GameState.sessionFishCaughtByType[fishAsset]; var fishName = FISH_NAMES[fishAsset] || fishAsset; var lineText = fishName + ': ' + count; var textLine = new Text2(lineText, { size: 50, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null }); textLine.anchor.set(0.5, 0); textLine.x = GAME_CONFIG.SCREEN_CENTER_X; textLine.y = yPos; resultsScreen.addChild(textLine); yPos += textLine.height + 10; } yPos += 40; } var fishResult = new Text2('Total Caught: ' + GameState.sessionFishCaught + '/' + GameState.sessionFishSpawned, { size: 50, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null }); fishResult.anchor.set(0.5, 0); fishResult.x = GAME_CONFIG.SCREEN_CENTER_X; fishResult.y = yPos; resultsScreen.addChild(fishResult); yPos += fishResult.height + 20; var accuracy = GameState.sessionFishSpawned > 0 ? Math.round(GameState.sessionFishCaught / GameState.sessionFishSpawned * 100) : 0; var accuracyResult = new Text2('Accuracy: ' + accuracy + '%', { size: 50, fill: 0x2196F3, wordWrap: false, wordWrapWidth: null }); accuracyResult.anchor.set(0.5, 0); accuracyResult.x = GAME_CONFIG.SCREEN_CENTER_X; accuracyResult.y = yPos; resultsScreen.addChild(accuracyResult); yPos += accuracyResult.height + 80; var continueButton = resultsScreen.addChild(LK.getAsset('bigButton', { anchorX: 0.5, anchorY: 0.5, x: GAME_CONFIG.SCREEN_CENTER_X, y: yPos + 50 })); var continueText = new Text2('CONTINUE', { size: 50, fill: 0xFFFFFF, wordWrap: false, wordWrapWidth: null }); continueText.anchor.set(0.5, 0.5); continueText.x = continueButton.x; continueText.y = continueButton.y; resultsScreen.addChild(continueText); resultsScreen.alpha = 0; tween(resultsScreen, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); } /**** * Input Handling ****/ // This new masterOriginalGameDown encapsulates all non-cooking input logic. var masterOriginalGameDown = function masterOriginalGameDown(x_orig, y_orig, obj_orig) { // General sound for non-cooking interactions. Cooking sounds are handled in its specific functions. LK.getSound('buttonClick').play(); //{Og} (retained from original context) var currentScreen_orig = GameState.currentScreen; //{Oh} (retained) if (GameState.tutorialMode && currentScreen_orig === 'fishing') { //{Oi} (retained) if (tutorialOverlayContainer.visible && tutorialContinueButton && tutorialContinueButton.visible && x_orig >= tutorialContinueButton.x - tutorialContinueButton.width / 2 && x_orig <= tutorialContinueButton.x + tutorialContinueButton.width / 2 && y_orig >= tutorialContinueButton.y - tutorialContinueButton.height / 2 && y_orig <= tutorialContinueButton.y + tutorialContinueButton.height / 2) { //{Ok} (retained) LK.getSound('buttonClick').play(); //{Ol} (retained) if ((GameState.tutorialStep === 3 || GameState.tutorialStep === 4) && GameState.tutorialPaused) { //{Om} (retained) var advanceToNextLogicalStep = false; if (GameState.tutorialFish && GameState.tutorialFish.wasCaughtThisInteraction) { //{On} (retained) advanceToNextLogicalStep = true; //{Oo} (retained) } //{Op} (retained) if (GameState.tutorialFish && (GameState.tutorialFish.wasCaughtThisInteraction || GameState.tutorialFish.wasMissedThisInteraction)) { //{Oq} (retained) if (!GameState.tutorialFish.destroyed) { //{Or} (retained) var idx = fishArray.indexOf(GameState.tutorialFish); //{Os} (retained) if (idx > -1) { //{Ot} (retained) fishArray.splice(idx, 1); //{Ou} (retained) } //{Ov} (retained) GameState.tutorialFish.destroy(); //{Ow} (retained) } //{Ox} (retained) GameState.tutorialFish = null; } //{Oy} (retained) if (advanceToNextLogicalStep) { //{Oz} (retained) GameState.tutorialStep++; } //{OA} (retained) runTutorialStep(); //{OB} (retained) } else { //{OC} (retained) GameState.tutorialStep++; //{OD} (retained) runTutorialStep(); //{OE} (retained) } //{OF} (retained) return; } //{OG} (retained) // For tutorial fishing, down event primarily records swipe start; actual action on up. // So, pass to handleFishingInput with isDown = true. handleFishingInput(x_orig, y_orig, true); return; } //{OH} (retained) // Non-tutorial input handling switch (currentScreen_orig) { //{OI} (retained) case 'title': //{OJ} (retained) var startButton = titleElements.startButton; //{OK} (retained) if (x_orig >= startButton.x - startButton.width / 2 && x_orig <= startButton.x + startButton.width / 2 && y_orig >= startButton.y - startButton.height / 2 && y_orig <= startButton.y + startButton.height / 2) { //{OL} (retained) _showScreen('levelSelect'); //{OM} (retained) } //{ON} (retained) var tutorialButtonGfx = titleElements.tutorialButtonGfx || titleElements.tutorialButton; //{OO} (retained) if (x_orig >= tutorialButtonGfx.x - tutorialButtonGfx.width / 2 && x_orig <= tutorialButtonGfx.x + tutorialButtonGfx.width / 2 && y_orig >= tutorialButtonGfx.y - tutorialButtonGfx.height / 2 && y_orig <= tutorialButtonGfx.y + tutorialButtonGfx.height / 2) { //{OP} (retained) if (typeof startTutorial === "function") { //{OQ} (retained) startTutorial(); //{OR} (retained) } //{OS} (retained) } //{OT} (retained) break; //{OU} (retained) case 'levelSelect': //{OV} (retained) handleLevelSelectInput(x_orig, y_orig); //{OW} (retained) break; //{OX} (retained) case 'fishing': //{OY} (retained) // Non-tutorial fishing presses also record swipe start. handleFishingInput(x_orig, y_orig, true); //{OZ} (retained) break; //{P0} (retained) case 'cookingResults': case 'results': //{P1} (retained) _showScreen('levelSelect'); //{P2} (retained) break; //{P3} (retained) } //{P4} (retained) }; game.down = function (x, y, obj) { if (GameState.currentScreen === 'cooking') { handleCookingInput(x, y); return; } masterOriginalGameDown.call(this, x, y, obj); }; function handleLevelSelectInput(x, y) { if (levelSelectElements && levelSelectElements.handleMapInput) { levelSelectElements.handleMapInput(x, y); } } /**** * Main Game Loop ****/ var originalGameUpdate = game.update; game.update = function () { // Call original update first, if it exists and is not this function itself if (typeof originalGameUpdate === 'function' && originalGameUpdate !== game.update) { originalGameUpdate.call(this); } // Cooking game updates if (GameState.currentScreen === 'cooking' && CookingState.gameActive) { // {Ou} var currentTime = LK.ticks * (1000 / 60); // Initialize cooking timer if (CookingState.songStartTime === 0) { // {Ov} CookingState.songStartTime = currentTime; // If this is the very first tick of the cooking session, // and if the first courier spawn relies on songStartTime for its schedule, // we might need to schedule it here *after* songStartTime is set. // However, the current CourierSpawner.scheduleNextCourier uses LK.ticks directly. // And startCookingSession already calls CourierSpawner.scheduleNextCourier(). } // Use the spawner system CourierSpawner.update(currentTime); // Update couriers for (var i = CookingState.couriers.length - 1; i >= 0; i--) { // {Ow} var courier = CookingState.couriers[i]; courier.update(); // Courier always updates // Remove off-screen couriers if (courier.x < -300 || courier.x > 2048 + 300) { // {Oy} courier.destroy(); CookingState.couriers.splice(i, 1); } else if (courier && courier.destroyed) { // {Oz} // Cleanup for already destroyed couriers if missed by other checks CookingState.couriers.splice(i, 1); } // {OA} } // {OB} // Update money display if (cookingElements && cookingElements.moneyDisplay && !cookingElements.moneyDisplay.destroyed) { // {OH} cookingElements.moneyDisplay.setText('$' + CookingState.money); } } // {OI} // --- End of Cooking Game Updates --- // The rest of this function will be the original game.update logic // if originalGameUpdate was not captured or if we're not truly "wrapping". // For this iterative process, the original logic is now below: if (typeof originalGameUpdate !== 'function' || originalGameUpdate === game.update) { // This is the original game.update content from the provided gamecode.js if (GameState.currentScreen === 'fishing' && fishingElements && fishingElements.updateFishingLineWave) { fishingElements.updateFishingLineWave(); } if (GameState.currentScreen === 'title') { if (titleElements && titleElements.updateTitleFishingLineWave) { titleElements.updateTitleFishingLineWave(); } // Title Screen Ocean Bubbles if (titleScreenOceanBubbleContainer) { titleScreenOceanBubbleSpawnCounter++; if (titleScreenOceanBubbleSpawnCounter >= OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS) { titleScreenOceanBubbleSpawnCounter = 0; var newOceanBubble = new OceanBubbleParticle(); titleScreenOceanBubbleContainer.addChild(newOceanBubble); titleScreenOceanBubblesArray.push(newOceanBubble); } updateParticleArray(titleScreenOceanBubblesArray); } // Title Screen Seaweed if (titleScreenSeaweedContainer) { titleScreenSeaweedSpawnCounter++; if (titleScreenSeaweedSpawnCounter >= SEAWEED_SPAWN_INTERVAL_TICKS && titleScreenSeaweedArray.length < MAX_SEAWEED_COUNT) { titleScreenSeaweedSpawnCounter = 0; var newSeaweed = new SeaweedParticle(); titleScreenSeaweedContainer.addChild(newSeaweed); titleScreenSeaweedArray.push(newSeaweed); } updateParticleArray(titleScreenSeaweedArray); } // Title Screen Clouds if (titleScreenCloudContainer) { titleScreenCloudSpawnCounter++; if (titleScreenCloudSpawnCounter >= CLOUD_SPAWN_INTERVAL_TICKS && titleScreenCloudArray.length < MAX_CLOUD_COUNT) { titleScreenCloudSpawnCounter = 0; var newCloud = new CloudParticle(); titleScreenCloudContainer.addChild(newCloud); titleScreenCloudArray.push(newCloud); } updateParticleArray(titleScreenCloudArray); } } if (GameState.currentScreen === 'levelSelect' && levelSelectElements) { if (levelSelectElements.updateBoatAnimation && typeof levelSelectElements.updateBoatAnimation === 'function') { levelSelectElements.updateBoatAnimation(); } if (levelSelectElements.updateRipples && typeof levelSelectElements.updateRipples === 'function') { levelSelectElements.updateRipples(); } if (levelSelectElements.updateHomeIslandRipples && typeof levelSelectElements.updateHomeIslandRipples === 'function') { levelSelectElements.updateHomeIslandRipples(); } if (levelSelectElements.updateScreenWaves && typeof levelSelectElements.updateScreenWaves === 'function') { levelSelectElements.updateScreenWaves(); } if (levelSelectElements.updateShallowWatersNodeBubbles && typeof levelSelectElements.updateShallowWatersNodeBubbles === 'function') { levelSelectElements.updateShallowWatersNodeBubbles(); } if (levelSelectElements.updateSeagulls && typeof levelSelectElements.updateSeagulls === 'function') { levelSelectElements.updateSeagulls(); } if (levelSelectElements.updateLevelSelectClouds && typeof levelSelectElements.updateLevelSelectClouds === 'function') { levelSelectElements.updateLevelSelectClouds(); } if (levelSelectElements.updateWaterfallParticles && typeof levelSelectElements.updateWaterfallParticles === 'function') { levelSelectElements.updateWaterfallParticles(); } if (levelSelectElements.updateShadowFish && typeof levelSelectElements.updateShadowFish === 'function') { levelSelectElements.updateShadowFish(); } } // Spawn and update ambient particles during fishing screen if (GameState.currentScreen === 'fishing' && globalOceanBubbleContainer) { globalOceanBubbleSpawnCounter++; if (globalOceanBubbleSpawnCounter >= OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS) { globalOceanBubbleSpawnCounter = 0; var numToSpawn = 1; for (var i = 0; i < numToSpawn; i++) { var newOceanBubble = new OceanBubbleParticle(); globalOceanBubbleContainer.addChild(newOceanBubble); globalOceanBubblesArray.push(newOceanBubble); } } // Cooking game updates if (GameState.currentScreen === 'cooking' && CookingState.gameActive) { // {Ou} var currentTime_cooking = LK.ticks * (1000 / 60); //{ON} - Renamed to avoid conflict with fishing's currentTime // Initialize cooking timer if not already set if (CookingState.songStartTime === 0) { // {Ov} CookingState.songStartTime = currentTime_cooking; } // Use the spawner system CourierSpawner.update(currentTime_cooking); // Update couriers for (var i = CookingState.couriers.length - 1; i >= 0; i--) { // {Ow} var courier = CookingState.couriers[i]; if (courier && !courier.destroyed) { courier.update(); // Remove off-screen couriers if (courier.x < -300 || courier.x > 2048 + 300) { // {Oy} courier.destroy(); CookingState.couriers.splice(i, 1); } } else { // Courier is null or already destroyed, remove from array CookingState.couriers.splice(i, 1); //{OR} -- Adapted from original else if } } // Update money display if (cookingElements && cookingElements.moneyDisplay && !cookingElements.moneyDisplay.destroyed) { // {OH} cookingElements.moneyDisplay.setText('$' + CookingState.money); } } // {OI} } if (GameState.currentScreen === 'fishing' && globalSeaweedContainer) { globalSeaweedSpawnCounter++; if (globalSeaweedSpawnCounter >= SEAWEED_SPAWN_INTERVAL_TICKS && globalSeaweedArray.length < MAX_SEAWEED_COUNT) { globalSeaweedSpawnCounter = 0; var newSeaweed = new SeaweedParticle(); globalSeaweedContainer.addChild(newSeaweed); globalSeaweedArray.push(newSeaweed); } } if (GameState.currentScreen === 'fishing' && globalCloudContainer) { handleParticleSpawning({ counter: globalCloudSpawnCounter, interval: CLOUD_SPAWN_INTERVAL_TICKS, maxCount: MAX_CLOUD_COUNT, array: globalCloudArray, container: globalCloudContainer, constructor: CloudParticle }); globalCloudSpawnCounter++; // This was missing in the prompt's provided game.update, might be an oversight there or handled by handleParticleSpawning updateParticleArray(globalCloudArray); } if (GameState.currentScreen === 'fishing' && GameState.tutorialMode) { if (fishingElements && fishingElements.updateFishingLineWave) { fishingElements.updateFishingLineWave(); } if (!GameState.tutorialPaused) { if (GameState.tutorialFish && !GameState.tutorialFish.destroyed && !GameState.tutorialFish.caught) { GameState.tutorialFish.update(); checkTutorialFishState(); } } updateLaneBracketsVisuals(); return; } if (GameState.currentScreen !== 'fishing' || !GameState.gameActive) { return; } var currentTimeIngameUpdate = LK.ticks * (1000 / 60); if (GameState.songStartTime === 0) { GameState.songStartTime = currentTimeIngameUpdate; } var songConfigIngameUpdate = GameState.getCurrentSongConfig(); if (currentTimeIngameUpdate - GameState.songStartTime >= songConfigIngameUpdate.duration) { endFishingSession(); return; } ImprovedRhythmSpawner.update(currentTimeIngameUpdate); for (var i = fishArray.length - 1; i >= 0; i--) { var fish = fishArray[i]; var previousFrameX = fish.lastX; fish.update(); var currentFrameX = fish.x; if (!fish.caught && !fish.missed) { var hookCenterX = fishingElements.hook.x; var missCheckBoundary = GAME_CONFIG.MISS_WINDOW; if (fish.speed > 0) { if (previousFrameX <= hookCenterX + missCheckBoundary && currentFrameX > hookCenterX + missCheckBoundary) { showFeedback('miss', fish.lane); LK.getSound('miss').play(); fish.missed = true; } } else if (fish.speed < 0) { if (previousFrameX >= hookCenterX - missCheckBoundary && currentFrameX < hookCenterX - missCheckBoundary) { showFeedback('miss', fish.lane); LK.getSound('miss').play(); fish.missed = true; } } } fish.lastX = currentFrameX; if (!fish.caught && (fish.x < -250 || fish.x > 2048 + 250)) { if (GameState.currentBattleFish === fish) { GameState.battleState = BATTLE_STATES.NONE; GameState.currentBattleFish = null; GameState.nextFishSpawnTime = 0; } fish.destroy(); fishArray.splice(i, 1); } } updateFishingUI(); if (GameState.musicNotesActive && fishingElements && fishingElements.hook && !fishingElements.hook.destroyed && musicNotesContainer) { musicNoteSpawnCounter++; if (musicNoteSpawnCounter >= MUSIC_NOTE_SPAWN_INTERVAL_TICKS) { musicNoteSpawnCounter = 0; var spawnX = fishingElements.hook.x; var spawnY = fishingElements.hook.y - 30; var newNote = new MusicNoteParticle(spawnX, spawnY); musicNotesContainer.addChild(newNote); musicNotesArray.push(newNote); if (fishingElements.hook && !fishingElements.hook.destroyed && fishingElements.hook.scale) { var currentSongConfig = GameState.getCurrentSongConfig(); var bpm = currentSongConfig && currentSongConfig.bpm ? currentSongConfig.bpm : 90; var beatDurationMs = 60000 / bpm; var pulsePhaseDuration = Math.max(50, beatDurationMs / 2); var pulseScaleFactor = 1.2; var originalScaleX = fishingElements.hook.scale.x !== undefined ? fishingElements.hook.scale.x : 1; var originalScaleY = fishingElements.hook.scale.y !== undefined ? fishingElements.hook.scale.y : 1; stopTween(fishingElements.hook.scale); tween(fishingElements.hook.scale, { x: originalScaleX * pulseScaleFactor, y: originalScaleY * pulseScaleFactor }, { duration: pulsePhaseDuration, easing: tween.easeOut, onFinish: function onFinish() { if (fishingElements.hook && !fishingElements.hook.destroyed && fishingElements.hook.scale) { tween(fishingElements.hook.scale, { x: originalScaleX, y: originalScaleY }, { duration: pulsePhaseDuration, easing: tween.easeIn }); } } }); } } } if (GameState.battleState === BATTLE_STATES.ACTIVE && !GameState.currentBattleFish) { GameState.battleState = BATTLE_STATES.NONE; GameState.nextFishSpawnTime = 0; } updateParticleArray(musicNotesArray); if (bubbleContainer) { for (var f = 0; f < fishArray.length; f++) { var fish_bubble = fishArray[f]; // Renamed to avoid conflict if (fish_bubble && !fish_bubble.caught && !fish_bubble.isHeld && fish_bubble.fishGraphics) { if (currentTimeIngameUpdate - fish_bubble.lastBubbleSpawnTime > fish_bubble.bubbleSpawnInterval) { fish_bubble.lastBubbleSpawnTime = currentTimeIngameUpdate; var tailOffsetDirection = Math.sign(fish_bubble.speed) * -1; var bubbleX = fish_bubble.x + tailOffsetDirection * (fish_bubble.fishGraphics.width * Math.abs(fish_bubble.fishGraphics.scaleX) / 2) * 0.8; var bubbleY = fish_bubble.y + (Math.random() - 0.5) * (fish_bubble.fishGraphics.height * Math.abs(fish_bubble.fishGraphics.scaleY) / 4); var newBubble = new BubbleParticle(bubbleX, bubbleY); bubbleContainer.addChild(newBubble); bubblesArray.push(newBubble); } } } } updateParticleArray(bubblesArray); } // End of the "originalGameUpdate" block }; // Similar capture logic for originalGameUpdate if (typeof originalGameUpdate === 'undefined') { originalGameUpdate = function originalGameUpdate() { // Minimal or non-existent game.update before this block. }; } game.update = function () { if (GameState.currentScreen === 'fishing' && fishingElements && fishingElements.updateFishingLineWave) { fishingElements.updateFishingLineWave(); } if (GameState.currentScreen === 'title') { if (titleElements && titleElements.updateTitleFishingLineWave) { titleElements.updateTitleFishingLineWave(); } if (titleScreenOceanBubbleContainer) { titleScreenOceanBubbleSpawnCounter++; if (titleScreenOceanBubbleSpawnCounter >= OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS) { titleScreenOceanBubbleSpawnCounter = 0; var newOceanBubble = new OceanBubbleParticle(); titleScreenOceanBubbleContainer.addChild(newOceanBubble); titleScreenOceanBubblesArray.push(newOceanBubble); } updateParticleArray(titleScreenOceanBubblesArray); } // Title Screen Seaweed if (titleScreenSeaweedContainer) { titleScreenSeaweedSpawnCounter++; if (titleScreenSeaweedSpawnCounter >= SEAWEED_SPAWN_INTERVAL_TICKS && titleScreenSeaweedArray.length < MAX_SEAWEED_COUNT) { titleScreenSeaweedSpawnCounter = 0; var newSeaweed = new SeaweedParticle(); titleScreenSeaweedContainer.addChild(newSeaweed); titleScreenSeaweedArray.push(newSeaweed); } updateParticleArray(titleScreenSeaweedArray); } // Title Screen Clouds if (titleScreenCloudContainer) { titleScreenCloudSpawnCounter++; if (titleScreenCloudSpawnCounter >= CLOUD_SPAWN_INTERVAL_TICKS && titleScreenCloudArray.length < MAX_CLOUD_COUNT) { titleScreenCloudSpawnCounter = 0; var newCloud = new CloudParticle(); titleScreenCloudContainer.addChild(newCloud); titleScreenCloudArray.push(newCloud); } updateParticleArray(titleScreenCloudArray); } } if (GameState.currentScreen === 'levelSelect' && levelSelectElements) { if (levelSelectElements.updateBoatAnimation && typeof levelSelectElements.updateBoatAnimation === 'function') { levelSelectElements.updateBoatAnimation(); } if (levelSelectElements.updateRipples && typeof levelSelectElements.updateRipples === 'function') { levelSelectElements.updateRipples(); } if (levelSelectElements.updateHomeIslandRipples && typeof levelSelectElements.updateHomeIslandRipples === 'function') { levelSelectElements.updateHomeIslandRipples(); } if (levelSelectElements.updateScreenWaves && typeof levelSelectElements.updateScreenWaves === 'function') { levelSelectElements.updateScreenWaves(); } if (levelSelectElements.updateShallowWatersNodeBubbles && typeof levelSelectElements.updateShallowWatersNodeBubbles === 'function') { levelSelectElements.updateShallowWatersNodeBubbles(); } if (levelSelectElements.updateMidWatersNodeBubbles && typeof levelSelectElements.updateMidWatersNodeBubbles === 'function') { levelSelectElements.updateMidWatersNodeBubbles(); } if (levelSelectElements.updateSeagulls && typeof levelSelectElements.updateSeagulls === 'function') { levelSelectElements.updateSeagulls(); } if (levelSelectElements.updateLevelSelectClouds && typeof levelSelectElements.updateLevelSelectClouds === 'function') { levelSelectElements.updateLevelSelectClouds(); } if (levelSelectElements.updateWaterfallParticles && typeof levelSelectElements.updateWaterfallParticles === 'function') { levelSelectElements.updateWaterfallParticles(); } if (levelSelectElements.updateShadowFish && typeof levelSelectElements.updateShadowFish === 'function') { levelSelectElements.updateShadowFish(); } } if (GameState.currentScreen === 'beach') { if (activeBeachCrabs) { updateParticleArray(activeBeachCrabs); } } // Cooking game updates if (GameState.currentScreen === 'cooking') { updateCookingGame(); // Update money display if (cookingElements && cookingElements.moneyDisplay && !cookingElements.moneyDisplay.destroyed) { cookingElements.moneyDisplay.setText('$' + CookingState.money); } } // Spawn and update ambient particles during fishing screen if (GameState.currentScreen === 'fishing' && globalOceanBubbleContainer) { globalOceanBubbleSpawnCounter++; if (globalOceanBubbleSpawnCounter >= OCEAN_BUBBLE_SPAWN_INTERVAL_TICKS) { globalOceanBubbleSpawnCounter = 0; var numToSpawn = 1; for (var i = 0; i < numToSpawn; i++) { var newOceanBubble = new OceanBubbleParticle(); globalOceanBubbleContainer.addChild(newOceanBubble); globalOceanBubblesArray.push(newOceanBubble); } } // Keep the existing fish physics update logic here... } // Spawn and update seaweed particles during fishing if (GameState.currentScreen === 'fishing' && globalSeaweedContainer) { globalSeaweedSpawnCounter++; if (globalSeaweedSpawnCounter >= SEAWEED_SPAWN_INTERVAL_TICKS && globalSeaweedArray.length < MAX_SEAWEED_COUNT) { globalSeaweedSpawnCounter = 0; var newSeaweed = new SeaweedParticle(); globalSeaweedContainer.addChild(newSeaweed); globalSeaweedArray.push(newSeaweed); } // Keep the existing fish physics update logic here... } // Spawn and update cloud particles during fishing if (GameState.currentScreen === 'fishing' && globalCloudContainer) { handleParticleSpawning({ counter: globalCloudSpawnCounter, interval: CLOUD_SPAWN_INTERVAL_TICKS, maxCount: MAX_CLOUD_COUNT, array: globalCloudArray, container: globalCloudContainer, constructor: CloudParticle }); globalCloudSpawnCounter++; updateParticleArray(globalCloudArray); } // Tutorial mode logic if (GameState.currentScreen === 'fishing' && GameState.tutorialMode) { if (fishingElements && fishingElements.updateFishingLineWave) { fishingElements.updateFishingLineWave(); } if (!GameState.tutorialPaused) { if (GameState.tutorialFish && !GameState.tutorialFish.destroyed && !GameState.tutorialFish.caught) { GameState.tutorialFish.update(); checkTutorialFishState(); } // Automatic hook movement removed; player swipe input (via handleFishingInput) // and initial setup in runTutorialStep now control the hook position. } updateLaneBracketsVisuals(); return; } if (GameState.currentScreen !== 'fishing' || !GameState.gameActive) { return; } var currentTime = LK.ticks * (1000 / 60); // Initialize game timer if (GameState.songStartTime === 0) { GameState.songStartTime = currentTime; } // Check song end var songConfig = GameState.getCurrentSongConfig(); if (currentTime - GameState.songStartTime >= songConfig.duration && !GameState.fishingEnding) { endFishingSession(); return; } // If song has ended and we're waiting for fish to be caught, check if all are gone if (GameState.fishingEnding) { var catchableFishRemaining = fishArray.filter(function (fish) { return !fish.caught && !fish.missed && !fish.destroyed; }); if (catchableFishRemaining.length === 0) { // All fish are caught/cleared, now end the session for real GameState.fishingEnding = false; GameState.gameActive = false; performFinalCleanup(); return; } // Still have catchable fish, but don't spawn new ones // The ImprovedRhythmSpawner.update() call below will be skipped // since GameState.fishingEnding is true } // Use RhythmSpawner to handle fish spawning ImprovedRhythmSpawner.update(currentTime); // (Automatic hook following logic removed as player now controls lane position via swipe) // updateLaneBracketsVisuals() is now called within handleFishingInput after a swipe. // Update fish for (var i = fishArray.length - 1; i >= 0; i--) { var fish = fishArray[i]; var previousFrameX = fish.lastX; fish.update(); var currentFrameX = fish.x; // Check for miss only if fish is active if (!fish.caught && !fish.missed) { var hookCenterX = fishingElements.hook.x; var missCheckBoundary = GAME_CONFIG.MISS_WINDOW; if (fish.speed > 0) { if (previousFrameX <= hookCenterX + missCheckBoundary && currentFrameX > hookCenterX + missCheckBoundary) { showFeedback('miss', fish.lane); LK.getSound('miss').play(); fish.missed = true; } } else if (fish.speed < 0) { if (previousFrameX >= hookCenterX - missCheckBoundary && currentFrameX < hookCenterX - missCheckBoundary) { showFeedback('miss', fish.lane); LK.getSound('miss').play(); fish.missed = true; } } } fish.lastX = currentFrameX; // Remove off-screen fish if (!fish.caught && (fish.x < -250 || fish.x > 2048 + 250)) { // If this was the battle fish, reset battle state if (GameState.currentBattleFish === fish) { GameState.battleState = BATTLE_STATES.NONE; GameState.currentBattleFish = null; GameState.nextFishSpawnTime = 0; } fish.destroy(); fishArray.splice(i, 1); } } // Update UI updateFishingUI(); // Spawn and update music notes if active if (GameState.musicNotesActive && fishingElements && fishingElements.hook && !fishingElements.hook.destroyed && musicNotesContainer) { musicNoteSpawnCounter++; if (musicNoteSpawnCounter >= MUSIC_NOTE_SPAWN_INTERVAL_TICKS) { musicNoteSpawnCounter = 0; var spawnX = fishingElements.hook.x; var spawnY = fishingElements.hook.y - 30; var newNote = new MusicNoteParticle(spawnX, spawnY); musicNotesContainer.addChild(newNote); musicNotesArray.push(newNote); // Add scale pulse to the hook if (fishingElements.hook && !fishingElements.hook.destroyed && fishingElements.hook.scale) { var currentSongConfig = GameState.getCurrentSongConfig(); var bpm = currentSongConfig && currentSongConfig.bpm ? currentSongConfig.bpm : 90; var beatDurationMs = 60000 / bpm; var pulsePhaseDuration = Math.max(50, beatDurationMs / 2); var pulseScaleFactor = 1.2; var originalScaleX = fishingElements.hook.scale.x !== undefined ? fishingElements.hook.scale.x : 1; var originalScaleY = fishingElements.hook.scale.y !== undefined ? fishingElements.hook.scale.y : 1; stopTween(fishingElements.hook.scale); tween(fishingElements.hook.scale, { x: originalScaleX * pulseScaleFactor, y: originalScaleY * pulseScaleFactor }, { duration: pulsePhaseDuration, easing: tween.easeOut, onFinish: function onFinish() { if (fishingElements.hook && !fishingElements.hook.destroyed && fishingElements.hook.scale) { tween(fishingElements.hook.scale, { x: originalScaleX, y: originalScaleY }, { duration: pulsePhaseDuration, easing: tween.easeIn }); } } }); } } } // In game.update(), add this after updating fish: if (GameState.battleState === BATTLE_STATES.ACTIVE && !GameState.currentBattleFish) { // Reset battle state if no active battle fish exists GameState.battleState = BATTLE_STATES.NONE; GameState.nextFishSpawnTime = 0; } // Update existing music notes updateParticleArray(musicNotesArray); // Spawn bubbles for active fish if (bubbleContainer) { for (var f = 0; f < fishArray.length; f++) { var fish = fishArray[f]; if (fish && !fish.caught && !fish.isHeld && fish.fishGraphics) { if (currentTime - fish.lastBubbleSpawnTime > fish.bubbleSpawnInterval) { fish.lastBubbleSpawnTime = currentTime; var tailOffsetDirection = Math.sign(fish.speed) * -1; var bubbleX = fish.x + tailOffsetDirection * (fish.fishGraphics.width * Math.abs(fish.fishGraphics.scaleX) / 2) * 0.8; var bubbleY = fish.y + (Math.random() - 0.5) * (fish.fishGraphics.height * Math.abs(fish.fishGraphics.scaleY) / 4); var newBubble = new BubbleParticle(bubbleX, bubbleY); bubbleContainer.addChild(newBubble); bubblesArray.push(newBubble); } } } } // Update and remove bubbles updateParticleArray(bubblesArray); }; /**** * Initialize game ****/ _showScreen('title'); // Add to the top of the game code, after the existing variables var swipeState = { startX: 0, startY: 0, currentLane: 1, // Start in middle lane isSwipe: false, swipeThreshold: 50, // Minimum distance for swipe tapThreshold: 30 // Maximum distance for tap };
===================================================================
--- original.js
+++ change.js
@@ -3051,9 +3051,22 @@
var selectedShopItemIndex = -1;
var shopItemHighlight = null;
var menuOverlayContainer;
var menuOverlayOpen = false;
-var cookingDish = 'fishAndChips';
+var cookingDish = storage.cookingDish || 'fishAndChips';
+// Validate that the loaded recipe is available with current tools
+var availableRecipes = RECIPE_SYSTEM.getAvailableRecipes();
+var cookingDishIsAvailable = false;
+for (var i = 0; i < availableRecipes.length; i++) {
+ if (availableRecipes[i].id === cookingDish) {
+ cookingDishIsAvailable = true;
+ break;
+ }
+}
+if (!cookingDishIsAvailable) {
+ cookingDish = 'fishAndChips'; // Default to a recipe that is always available
+ storage.cookingDish = cookingDish; // Update storage with a valid recipe
+}
var globalFadeOverlay = game.addChild(LK.getAsset('screenBackground', {
x: 0,
y: 0,
width: 2048,
@@ -6781,8 +6794,9 @@
// Create closure to capture recipeId
(function (id) {
recipeButton.down = function () {
cookingDish = id;
+ storage.cookingDish = id;
hideMenuOverlay();
};
})(recipeId);
}
No background.
A music note. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
A white bubble. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
Blue gradient background starting lighter blue at the top of the image and going to a darker blue at the bottom.. In-Game asset. 2d. High contrast. No shadows
A small single strand of loose floating seaweed. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
A thin wispy white cloud. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
Game logo for the game ‘Beat Fisher’. High def 80’s color themed SVG of the word.. In-Game asset. 2d. High contrast. No shadows
A yellow star burst that says 'Perfect!' in the center. 80s arcade machine graphics. In-Game asset. 2d. High contrast. No shadows
A red starburst with the word ‘Miss!’ In it. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
A green starburst with the word ‘Good!’ In it. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
White stylized square bracket. Pixelated. In-Game asset. 2d. High contrast. No shadows
A double sided fishing hook with a small speaker in the center. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
An SVG that says ‘Start’
Blue to green gradient on the word instead of pink.
Above water background image showing gradient of light to dark blue starting at the top. Looking straight down from high above.. In-Game asset. 2d. High contrast. No shadows
This boat, same perspective, boat facing down.
A small island centered with a large mountain taking up most of it with a waterfall on the south side and a fishing village just below it. Under the fishing village is a harbor with a single empty dock. The dock extends into a half open bay. 80s arcade machine inspire high definition graphics with 80s colored highlights. White background. Top down 3/4 view. In-Game asset. 2d. High contrast. No shadows
A small lock icon. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
A seagull with wings spread straight out as if soaring. Top down view, looking down from above. 80s arcade machine graphics. In-Game asset. 2d. High contrast. No shadows
A round button with an embossed edge with an anchor in the center. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
A long fluffy white cloud seen from overhead. 80s arcade machine graphics. In-Game asset. 2d. High contrast. No shadows
Much thinner border
The dark shadow silhouette of a fish. Top down view. In-Game asset. 2d. High contrast. No shadows
Change the anchor to a knife and fork icon.
Top down beach sand background image with a light to dark gradient starting at the top.. In-Game asset. 2d. High contrast. No shadows
A moped courier riding a moped with a food carrying basket with the top open and no lid on the back of the moped. Top down view with the moped pointing fully sideways.
A kitchen knife. Side view. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
A large kitchen mixing bowl. Side view.
A fryer basket from a deep fryer. Side view.
An anchovy. 80s arcade machine graphics. Swimming Side profile. White background. In-Game asset. 2d. High contrast. No shadows
A raw fish steak. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
This fish steak with covered with raw batter on it.
Cooked fish and chips in a newspaper lined basket. Side view. 80s arcade machine graphics.. In-Game asset. 2d. High contrast
A long rectangular wooden cutting board counter with different colored laminated wooden strips. Top down view. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
a textured white rectangular kitchen counter top. overhead view. In-Game asset. 2d. High contrast. No shadows
A light blue starburst with “Chop!” In the center. 80s arcade machine graphics
Change the text to “Dip!”
Change the text to “Fry!”
Inside the kitchen of a small wooden shack restaurant, looking out over the counter onto a beach view with long gradual gradients. No people inside or on the beach.
This man with no fishing rod and chefs clothes on, facing forward.
Change the sign to say “The Fish Shack” and make the window larger.
A background image view looking into a fry kitchen.
A young man standing, facing away with his back facing down. Top down view.
A boombox stereo. Top down view. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
A young woman with long hair and shorts. No hat. Pink shirt.
An old man with a cane and hat. Still facing straight forward with back facing straight down.
A young boy with a yellow Hawaiian style shirt and sandals. No hat. Blonde hair.
A small red crab. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
Top down view of a shallow wave water on a beach shore..80s arcade machine graphics. In-Game asset. 2d. High contrast. No shadows
A long horizontal line of low bushes. Top down view. 80s arcade machine graphics.. In-Game asset. 2d. High contrast. No shadows
A outdoor wooden picnic bench. Top down view. Squared up.
A sea bass. 80s arcade machine graphics. Swimming Side profile. White background. In-Game asset. 2d. High contrast. No shadows
A shining golden mythical fish. Side profile, swimming. 80s arcade machine graphics. White background. In-Game asset. 2d. High contrast. No shadows
A cod. 80s arcade machine graphics. Swimming Side profile. White background. In-Game asset. 2d. High contrast. No shadows
A snapper. 80s arcade machine graphics. Swimming Side profile. White background. In-Game asset. 2d. High contrast. No shadows
Replace the knife and fork with a dollar symbol.
A meat grinder.
A set of spices shakers.
A large soup pot.
A blank sandwhich board. Top down view.
Fish cakes in a to go box.
Two fish tacos in a to go box.
A fish burger and fries in a basket lined with paper.
A bowl of fish curry.
A bowl of fish dumplings.
A bowl of fish meatballs in broth.
A ball of ground fish in a bowl.
Two uncooked fish cakes.
A raw ground fish patty.
This fish steak with spices added to.
Change the word to say “Grind!”
Change the word to say “Spice!”
Change the word to say “Boil!”
rhythmTrack
Music
morningtide
Music
miss
Sound effect
catch
Sound effect
catch2
Sound effect
catch3
Sound effect
catch4
Sound effect
seagull1
Sound effect
seagull2
Sound effect
seagull3
Sound effect
boatsounds
Sound effect
sunnyafternoon
Music
reel
Sound effect
sardineRiff
Sound effect
anchovyRiff
Sound effect
oceanCurrent
Music
deepFlow
Music
coralGroove
Music