/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var AuraParticle = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'green'; self.collected = false; var assetName = 'aura' + self.type.charAt(0).toUpperCase() + self.type.slice(1); var auraGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); auraGraphics.alpha = 0.8; self.floatOffset = Math.random() * Math.PI * 2; self.floatSpeed = 0.02 + Math.random() * 0.03; self.pulseOffset = Math.random() * Math.PI * 2; self.initialY = 0; self.initialScale = 1.0; // Start gentle pulsing animation tween(auraGraphics, { scaleX: 1.2, scaleY: 1.2, alpha: 1.0 }, { duration: 1500 + Math.random() * 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.collected) { tween(auraGraphics, { scaleX: 0.8, scaleY: 0.8, alpha: 0.6 }, { duration: 1500 + Math.random() * 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.collected) { tween(auraGraphics, { scaleX: 1.0, scaleY: 1.0, alpha: 0.8 }, { duration: 1000, easing: tween.easeInOut }); } } }); } } }); self.update = function () { if (!self.collected) { self.y = self.initialY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 8; auraGraphics.rotation += 0.015; } }; return self; }); var AuraWisp = Container.expand(function (wispType) { var self = Container.call(this); self.wispType = wispType || 'green'; self.collected = false; self.isMovingToPlayer = false; self.moveSpeed = 3.5; // Set wisp color based on type var wispColors = { green: 0xc8e6c9, blue: 0xb3d9ff, yellow: 0xfff8d1 }; var wispGraphics = self.attachAsset('auraWisp', { anchorX: 0.5, anchorY: 0.5 }); // Apply wisp type color wispGraphics.tint = wispColors[self.wispType]; wispGraphics.alpha = 0.7; self.floatOffset = Math.random() * Math.PI * 2; self.floatSpeed = 0.025 + Math.random() * 0.015; self.pulseOffset = Math.random() * Math.PI * 2; self.initialY = 0; self.emergeDuration = 120; // 2 seconds to emerge self.emergeTimer = 0; // Start gentle emergence animation tween(wispGraphics, { alpha: 0.9, scaleX: 1.2, scaleY: 1.2 }, { duration: 1000 + Math.random() * 500, easing: tween.easeOut, onFinish: function onFinish() { if (!self.collected) { // Start floating towards player self.isMovingToPlayer = true; } } }); self.moveTowardsPlayer = function () { if (!player || self.collected) return; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 10) { // Gentle movement towards player self.x += dx / distance * self.moveSpeed; self.y += dy / distance * self.moveSpeed; } else { // Close enough to be collected self.collected = true; // Award small aura auraCount[self.wispType]++; updateUI(); // Gentle collection animation tween(wispGraphics, { scaleX: 1.8, scaleY: 1.8, alpha: 1.0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(wispGraphics, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); LK.getSound('collectAura').play(); } }; self.update = function () { if (self.collected) return; if (!self.isMovingToPlayer) { // Gentle floating motion while emerging self.y = self.initialY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 6; self.emergeTimer++; // Start moving towards player after emergence period if (self.emergeTimer >= self.emergeDuration) { self.isMovingToPlayer = true; } } else { // Move towards player with gentle floating motion self.moveTowardsPlayer(); // Add gentle floating while moving var floatMotion = Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 4; self.y += floatMotion * 0.1; } // Gentle pulsing glow wispGraphics.alpha = 0.6 + Math.sin(LK.ticks * 0.04 + self.pulseOffset) * 0.3; wispGraphics.rotation += 0.01; }; return self; }); var DistantCloud = Container.expand(function () { var self = Container.call(this); var cloudGraphics = self.attachAsset('distantCloud', { anchorX: 0.5, anchorY: 0.5 }); cloudGraphics.alpha = 0.3 + Math.random() * 0.2; self.driftSpeed = 0.1 + Math.random() * 0.15; // Extremely slow drift self.initialX = 0; self.floatOffset = Math.random() * Math.PI * 2; self.floatSpeed = 0.003 + Math.random() * 0.005; // Very slow vertical float self.initialY = 0; // Start gentle size pulsing for natural cloud variation tween(cloudGraphics, { scaleX: 1.1 + Math.random() * 0.2, scaleY: 1.1 + Math.random() * 0.2 }, { duration: 8000 + Math.random() * 4000, easing: tween.easeInOut, onFinish: function onFinish() { tween(cloudGraphics, { scaleX: 0.9, scaleY: 0.9 }, { duration: 8000 + Math.random() * 4000, easing: tween.easeInOut }); } }); self.update = function () { // Imperceptibly slow horizontal drift - consistent with typical cloud movement // Enhanced with tween-based smooth transitions for more natural movement self.x += self.driftSpeed * 0.3; // Reduced to imperceptibly slow speed // Very subtle vertical floating motion self.y = self.initialY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 8; // Reset cloud when it goes off screen if (self.x > 2100) { self.x = -200; self.initialX = self.x; // Add subtle tween variation when cloud resets for natural repositioning tween(self, { x: self.x + (Math.random() - 0.5) * 100, y: self.initialY + (Math.random() - 0.5) * 80 }, { duration: 5000 + Math.random() * 3000, easing: tween.easeInOut }); } }; return self; }); var EphemeralCreature = Container.expand(function (creatureType) { var self = Container.call(this); self.creatureType = creatureType || 'fox'; self.hasAppeared = false; self.isObserving = false; self.observeTimer = 0; self.observeDuration = 180; // 3 seconds at 60fps var assetName = 'ephemeral' + self.creatureType.charAt(0).toUpperCase() + self.creatureType.slice(1); var creatureGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Start invisible creatureGraphics.alpha = 0; self.floatOffset = Math.random() * Math.PI * 2; self.floatSpeed = 0.015; self.initialY = 0; self.appear = function () { if (self.hasAppeared) return; self.hasAppeared = true; // Create sparkling shimmer effect during appearance self.createShimmerBurst(12); // Gentle fade in appearance tween(creatureGraphics, { alpha: 0.8, scaleX: 1.1, scaleY: 1.1 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { self.isObserving = true; // Gentle glow pulsing while observing tween(creatureGraphics, { alpha: 1.0, scaleX: 1.0, scaleY: 1.0 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(creatureGraphics, { alpha: 0.7, scaleX: 1.1, scaleY: 1.1 }, { duration: 800, easing: tween.easeInOut }); } }); } }); }; self.fadeAway = function () { self.isObserving = false; // Create gentle dissipation shimmer effect self.createShimmerBurst(8); // Gentle fade out tween(creatureGraphics, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 2000, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); }; self.update = function () { if (self.hasAppeared && !self.isObserving) return; // Gentle floating motion self.y = self.initialY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 12; // Subtle rotation for hummingbird if (self.creatureType === 'hummingbird') { creatureGraphics.rotation = Math.sin(LK.ticks * 0.08) * 0.1; } // Count observe timer if (self.isObserving) { self.observeTimer++; if (self.observeTimer >= self.observeDuration) { self.fadeAway(); } } }; self.createShimmerBurst = function (count) { for (var i = 0; i < count; i++) { var shimmer = new EphemeralShimmer(); // Position around creature with some spread var angle = Math.random() * Math.PI * 2; var distance = 30 + Math.random() * 60; shimmer.x = self.x + Math.cos(angle) * distance; shimmer.y = self.y + Math.sin(angle) * distance; // Add to global shimmer tracking ephemeralShimmers.push(shimmer); game.addChild(shimmer); // Gentle sparkle appearance tween(shimmer, { alpha: 0.7 + Math.random() * 0.3, scaleX: 1.2, scaleY: 1.2 }, { duration: 300 + Math.random() * 200, easing: tween.easeOut }); } }; return self; }); var EphemeralShimmer = Container.expand(function () { var self = Container.call(this); var shimmerGraphics = self.attachAsset('ephemeralShimmer', { anchorX: 0.5, anchorY: 0.5 }); shimmerGraphics.alpha = 0; self.speed = 1.5 + Math.random() * 2.0; self.driftX = (Math.random() - 0.5) * 4; self.driftY = -1 - Math.random() * 2; self.floatOffset = Math.random() * Math.PI * 2; self.floatSpeed = 0.05 + Math.random() * 0.03; self.lifetime = 90 + Math.random() * 60; // 1.5-2.5 seconds self.age = 0; self.update = function () { self.age++; self.x += self.driftX; self.y += self.driftY; // Gentle floating motion self.x += Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 0.5; // Fade out over lifetime var lifeProgress = self.age / self.lifetime; shimmerGraphics.alpha = (1 - lifeProgress) * 0.8; shimmerGraphics.scaleX = 1 - lifeProgress * 0.3; shimmerGraphics.scaleY = 1 - lifeProgress * 0.3; // Remove when lifetime exceeded if (self.age >= self.lifetime) { self.destroy(); } }; return self; }); var FloatingParticle = Container.expand(function () { var self = Container.call(this); var particleGraphics = self.attachAsset('floatingParticle', { anchorX: 0.5, anchorY: 0.5 }); particleGraphics.alpha = 0.3 + Math.random() * 0.4; self.driftSpeed = 0.5 + Math.random() * 1.0; self.floatOffset = Math.random() * Math.PI * 2; self.floatSpeed = 0.01 + Math.random() * 0.02; self.initialX = 0; self.update = function () { self.y -= self.driftSpeed; self.x = self.initialX + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 20; particleGraphics.rotation += 0.01; // Reset particle when it goes off screen if (self.y < -50) { self.y = 2800; self.x = Math.random() * 2048; self.initialX = self.x; } }; return self; }); var HavenDecoration = Container.expand(function (decorationType) { var self = Container.call(this); self.decorationType = decorationType || 'plant'; var assetName = self.decorationType + 'Decoration'; var decorationGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 1.0 }); decorationGraphics.alpha = 0.9; self.swayOffset = Math.random() * Math.PI * 2; self.swaySpeed = 0.01 + Math.random() * 0.02; self.update = function () { // Gentle swaying animation for plants and light decorations if (self.decorationType === 'plant' || self.decorationType === 'light') { decorationGraphics.rotation = Math.sin(LK.ticks * self.swaySpeed + self.swayOffset) * 0.05; } // Subtle pulsing for light decorations if (self.decorationType === 'light') { decorationGraphics.alpha = 0.7 + Math.sin(LK.ticks * 0.03 + self.swayOffset) * 0.2; } }; return self; }); var HiddenVignette = Container.expand(function (vignetteType) { var self = Container.call(this); self.vignetteType = vignetteType || 'mushroom'; self.discovered = false; self.isActive = false; self.fireflies = []; // Create main vignette element var mainAsset = ''; if (self.vignetteType === 'mushroom') mainAsset = 'mushroomRing';else if (self.vignetteType === 'waterfall') mainAsset = 'hiddenWaterfall';else if (self.vignetteType === 'bear') mainAsset = 'sleepingBear'; var vignetteGraphics = self.attachAsset(mainAsset, { anchorX: 0.5, anchorY: 0.5 }); vignetteGraphics.alpha = 0; // Start invisible self.pulseOffset = Math.random() * Math.PI * 2; self.breathOffset = Math.random() * Math.PI * 2; // Discover animation self.discover = function () { if (self.discovered) return; self.discovered = true; self.isActive = true; // Gentle fade in discovery tween(vignetteGraphics, { alpha: 0.8, scaleX: 1.0, scaleY: 1.0 }, { duration: 2000, easing: tween.easeOut }); // Play vignette-specific sound var soundName = 'vignette' + self.vignetteType.charAt(0).toUpperCase() + self.vignetteType.slice(1); LK.getSound(soundName).play(); // Create special effects based on type if (self.vignetteType === 'mushroom') { self.createFireflies(); } else if (self.vignetteType === 'waterfall') { self.createRainbow(); } }; self.createFireflies = function () { // Create dancing fireflies around mushroom ring for (var i = 0; i < 8; i++) { var firefly = self.addChild(LK.getAsset('firefly', { anchorX: 0.5, anchorY: 0.5 })); firefly.alpha = 0; firefly.danceAngle = Math.PI * 2 / 8 * i; firefly.danceRadius = 80 + Math.random() * 40; firefly.danceSpeed = 0.02 + Math.random() * 0.02; firefly.pulseOffset = Math.random() * Math.PI * 2; self.fireflies.push(firefly); // Fade in firefly tween(firefly, { alpha: 0.7 + Math.random() * 0.3 }, { duration: 1000 + Math.random() * 1000, easing: tween.easeOut }); } }; self.createRainbow = function () { // Create subtle rainbow mist effect LK.effects.flashObject(vignetteGraphics, 0xff9800, 3000); // Add gentle shimmer tween(vignetteGraphics, { alpha: 1.0 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { tween(vignetteGraphics, { alpha: 0.6 }, { duration: 1500, easing: tween.easeInOut }); } }); }; self.update = function () { if (!self.isActive) return; // Gentle pulsing for mushroom ring if (self.vignetteType === 'mushroom') { vignetteGraphics.alpha = 0.6 + Math.sin(LK.ticks * 0.02 + self.pulseOffset) * 0.2; // Animate fireflies dancing for (var i = 0; i < self.fireflies.length; i++) { var firefly = self.fireflies[i]; firefly.danceAngle += firefly.danceSpeed; firefly.x = Math.cos(firefly.danceAngle) * firefly.danceRadius; firefly.y = Math.sin(firefly.danceAngle) * firefly.danceRadius; firefly.alpha = 0.4 + Math.sin(LK.ticks * 0.05 + firefly.pulseOffset) * 0.4; } } // Gentle breathing for sleeping bear else if (self.vignetteType === 'bear') { var breathScale = 1.0 + Math.sin(LK.ticks * 0.015 + self.breathOffset) * 0.05; vignetteGraphics.scaleX = breathScale; vignetteGraphics.scaleY = breathScale; } // Subtle shimmer for waterfall else if (self.vignetteType === 'waterfall') { vignetteGraphics.alpha = 0.7 + Math.sin(LK.ticks * 0.03 + self.pulseOffset) * 0.1; } }; return self; }); var LightShimmer = Container.expand(function () { var self = Container.call(this); var shimmerGraphics = self.attachAsset('lightShimmer', { anchorX: 0.5, anchorY: 0.5 }); shimmerGraphics.alpha = 0.1; self.pulseOffset = Math.random() * Math.PI * 2; self.pulseSpeed = 0.005 + Math.random() * 0.003; // Very slow, breathing-like pulse self.breathAmplitude = 0.05 + Math.random() * 0.03; // Very subtle intensity variation // Start gentle breathing animation using tween for smooth transitions self.startShimmer = function () { var breatheIn = function breatheIn() { tween(shimmerGraphics, { alpha: 0.15 + self.breathAmplitude, scaleX: 1.05, scaleY: 1.05 }, { duration: 8000 + Math.random() * 4000, // 8-12 seconds per breath easing: tween.easeInOut, onFinish: breatheOut }); }; var breatheOut = function breatheOut() { tween(shimmerGraphics, { alpha: 0.05, scaleX: 0.95, scaleY: 0.95 }, { duration: 8000 + Math.random() * 4000, // 8-12 seconds per breath easing: tween.easeInOut, onFinish: breatheIn }); }; // Start with random direction if (Math.random() < 0.5) { breatheIn(); } else { breatheOut(); } }; self.update = function () { // Additional very subtle pulsing using sine wave for natural variation var subtlePulse = Math.sin(LK.ticks * self.pulseSpeed + self.pulseOffset) * 0.02; shimmerGraphics.alpha += subtlePulse; // Ensure alpha stays within reasonable bounds shimmerGraphics.alpha = Math.max(0.02, Math.min(0.25, shimmerGraphics.alpha)); }; return self; }); var LuminescentParticle = Container.expand(function () { var self = Container.call(this); // Use alternating particle types for variety var assetName = Math.random() < 0.7 ? 'luminescentParticle' : 'luminescentParticleAlt'; var particleGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Soft intensity with pastel colors var pastelColors = [0xe0ffff, 0xf0f8ff, 0xfffacd, 0xf0fff0, 0xffe4e1]; particleGraphics.tint = pastelColors[Math.floor(Math.random() * pastelColors.length)]; particleGraphics.alpha = 0.3 + Math.random() * 0.2; // Reduced for subtle effect // Enhanced movement properties for gentle floating self.driftSpeed = 0.08 + Math.random() * 0.12; // Very slow upward drift self.lateralAmplitude = 20 + Math.random() * 30; // Gentle S-curve amplitude self.lateralSpeed = 0.003 + Math.random() * 0.005; // Very slow lateral sway frequency self.initialX = 0; self.lateralOffset = Math.random() * Math.PI * 2; // Random phase for S-curve self.lateralDirection = Math.random() < 0.5 ? 1 : -1; // Random sway direction // Long lifetime properties self.age = 0; self.maxAge = 3600 + Math.random() * 1800; // 60-90 seconds lifetime for long traversal self.spawned = false; // Soft pulsing properties self.pulseOffset = Math.random() * Math.PI * 2; self.pulseSpeed = 0.008 + Math.random() * 0.007; // Very gentle pulse frequency self.baseAlpha = 0.25 + Math.random() * 0.15; // Soft base opacity // Start gentle emergence animation self.emerge = function () { if (self.spawned) return; self.spawned = true; // Gentle fade in and scale up tween(particleGraphics, { alpha: self.baseAlpha, scaleX: 1.0, scaleY: 1.0 }, { duration: 2000 + Math.random() * 1000, easing: tween.easeOut }); }; // Start gentle pulsing animation self.startPulse = function () { var pulseIn = function pulseIn() { if (self.age >= self.maxAge) return; tween(particleGraphics, { alpha: self.baseAlpha + 0.2, scaleX: 1.1, scaleY: 1.1 }, { duration: 3000 + Math.random() * 2000, easing: tween.easeInOut, onFinish: pulseOut }); }; var pulseOut = function pulseOut() { if (self.age >= self.maxAge) return; tween(particleGraphics, { alpha: self.baseAlpha - 0.1, scaleX: 0.9, scaleY: 0.9 }, { duration: 3000 + Math.random() * 2000, easing: tween.easeInOut, onFinish: pulseIn }); }; // Start with random direction if (Math.random() < 0.5) { pulseIn(); } else { pulseOut(); } }; // Enhanced floating movement system self.update = function () { if (!self.spawned) { self.emerge(); self.startPulse(); } self.age++; // Very slow upward drift - effortless floating self.y -= self.driftSpeed; // Gentle lateral sway in S-curve pattern var lateralSway = Math.sin(LK.ticks * self.lateralSpeed + self.lateralOffset) * self.lateralAmplitude * self.lateralDirection; var secondarySway = Math.cos(LK.ticks * self.lateralSpeed * 0.7 + self.lateralOffset + Math.PI) * self.lateralAmplitude * 0.3; self.x = self.initialX + lateralSway + secondarySway; // Additional very subtle sine wave variation for natural movement var microDrift = Math.sin(LK.ticks * 0.002 + self.lateralOffset) * 3; self.x += microDrift; // Subtle additional alpha variation for ethereal effect var additionalGlow = Math.sin(LK.ticks * self.pulseSpeed + self.pulseOffset) * 0.08; particleGraphics.alpha += additionalGlow; // Gentle rotation for natural floating particleGraphics.rotation += 0.003 * self.lateralDirection; // Reset particle when lifetime exceeded or goes off screen if (self.age >= self.maxAge || self.y < -100) { // Reset for reuse with long lifetime self.y = 2800 + Math.random() * 200; self.x = Math.random() * 2048; self.initialX = self.x; self.age = 0; self.spawned = false; // Randomize movement parameters for variety self.driftSpeed = 0.08 + Math.random() * 0.12; self.lateralAmplitude = 20 + Math.random() * 30; self.lateralSpeed = 0.003 + Math.random() * 0.005; self.lateralDirection = Math.random() < 0.5 ? 1 : -1; self.lateralOffset = Math.random() * Math.PI * 2; self.maxAge = 3600 + Math.random() * 1800; self.baseAlpha = 0.25 + Math.random() * 0.15; // Reset graphics properties particleGraphics.alpha = 0; particleGraphics.scaleX = 0.8; particleGraphics.scaleY = 0.8; } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.targetX = 0; self.targetY = 0; self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Play footstep sounds based on biome if (LK.ticks - lastFootstepTime > footstepInterval * 60 / 1000) { var footstepSound = ''; if (currentBiome === 0) footstepSound = 'footstepsGrass'; // Woods else if (currentBiome === 1) footstepSound = 'footstepsPebbles'; // Caverns else if (currentBiome === 2) footstepSound = 'footstepsWater'; // Shore else footstepSound = 'footstepsGrass'; // Sanctuary LK.getSound(footstepSound).play(); lastFootstepTime = LK.ticks; } } }; return self; }); var RareAuraBloom = Container.expand(function () { var self = Container.call(this); self.collected = false; var bloomGraphics = self.attachAsset('rareAuraBloom', { anchorX: 0.5, anchorY: 0.5 }); bloomGraphics.alpha = 0.9; self.pulseOffset = Math.random() * Math.PI * 2; self.floatOffset = Math.random() * Math.PI * 2; self.floatSpeed = 0.03; self.initialY = 0; // Start dramatic pulsating animation tween(bloomGraphics, { scaleX: 1.5, scaleY: 1.5, alpha: 1.0 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.collected) { tween(bloomGraphics, { scaleX: 1.0, scaleY: 1.0, alpha: 0.7 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.collected) { tween(bloomGraphics, { scaleX: 1.5, scaleY: 1.5, alpha: 1.0 }, { duration: 1000, easing: tween.easeInOut }); } } }); } } }); self.update = function () { if (!self.collected) { self.y = self.initialY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 15; bloomGraphics.rotation += 0.02; } }; return self; }); var RareBloomBurst = Container.expand(function () { var self = Container.call(this); var burstGraphics = self.attachAsset('rareBloomBurstParticle', { anchorX: 0.5, anchorY: 0.5 }); // Vibrant pastel mix colors var vibrantColors = [0xffd700, 0xff69b4, 0x98fb98, 0x87ceeb, 0xdda0dd, 0xf0e68c]; burstGraphics.tint = vibrantColors[Math.floor(Math.random() * vibrantColors.length)]; burstGraphics.alpha = 0; self.velocity = { x: (Math.random() - 0.5) * 8, y: -2 - Math.random() * 6 }; self.lifetime = 60; // 1 second burst duration self.age = 0; self.gravity = 0.2; // Start burst animation tween(burstGraphics, { alpha: 0.8, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(burstGraphics, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 800, easing: tween.easeIn }); } }); self.update = function () { self.age++; self.x += self.velocity.x; self.y += self.velocity.y; self.velocity.y += self.gravity; burstGraphics.rotation += 0.1; // Remove when lifetime exceeded if (self.age >= self.lifetime) { self.destroy(); } }; return self; }); var TreeSwayBranch = Container.expand(function () { var self = Container.call(this); var branchGraphics = self.attachAsset('treeSwayBranch', { anchorX: 0.5, anchorY: 1.0 }); branchGraphics.alpha = 0.7; // Enhanced tree sway parameters for natural movement self.swayOffset = Math.random() * Math.PI * 2; // Random phase for variation between trees self.swaySpeed = 0.006 + Math.random() * 0.004; // Very low frequency (0.1-0.2 Hz equivalent) self.swayAmplitude = 0.02 + Math.random() * 0.015; // Small amplitude (1-3 degrees) self.secondarySwayOffset = Math.random() * Math.PI * 2; // Secondary sway for complexity self.secondarySwaySpeed = 0.004 + Math.random() * 0.003; // Even slower secondary movement self.windVariation = Math.random() * 0.5 + 0.5; // Random wind strength per tree // Enhanced continuous sway animation with multiple layers self.startSway = function () { // Primary sway motion - main side-to-side movement var primarySwayLeft = function primarySwayLeft() { tween(branchGraphics, { rotation: -self.swayAmplitude * self.windVariation }, { duration: 4000 + Math.random() * 3000, // 4-7 seconds per sway for calming rhythm easing: tween.easeInOut, onFinish: primarySwayRight }); }; var primarySwayRight = function primarySwayRight() { tween(branchGraphics, { rotation: self.swayAmplitude * self.windVariation }, { duration: 4000 + Math.random() * 3000, // 4-7 seconds per sway for calming rhythm easing: tween.easeInOut, onFinish: primarySwayLeft }); }; // Start with random direction for phase variation if (Math.random() < 0.5) { primarySwayLeft(); } else { primarySwayRight(); } }; self.update = function () { // Multi-layered natural sway simulation mimicking light breeze // Primary sine wave for main movement var primarySway = Math.sin(LK.ticks * self.swaySpeed + self.swayOffset) * 0.008 * self.windVariation; // Secondary sine wave for complexity and natural variation var secondarySway = Math.sin(LK.ticks * self.secondarySwaySpeed + self.secondarySwayOffset) * 0.004 * self.windVariation; // Tertiary micro-movement for realistic rustling var microSway = Math.sin(LK.ticks * 0.01 + self.swayOffset * 2) * 0.002; // Combine all movement layers for natural breeze effect var totalSway = primarySway + secondarySway + microSway; branchGraphics.rotation += totalSway; // Subtle scale variation to simulate depth movement in breeze var scaleVariation = 1.0 + Math.sin(LK.ticks * self.swaySpeed * 0.7 + self.swayOffset) * 0.01; branchGraphics.scaleX = scaleVariation; }; return self; }); var WaterRipple = Container.expand(function () { var self = Container.call(this); var rippleGraphics = self.attachAsset('waterRipple', { anchorX: 0.5, anchorY: 0.5 }); // Start invisible and small rippleGraphics.alpha = 0; rippleGraphics.scaleX = 0.05; rippleGraphics.scaleY = 0.05; self.isActive = false; self.maxScale = 0.8 + Math.random() * 0.4; // Very low amplitude for barely stirring water self.expandDuration = 15000 + Math.random() * 10000; // Extremely slow 15-25 seconds expansion // Enhanced shader-based animation properties self.noiseOffset = Math.random() * Math.PI * 2; self.noiseOffset2 = Math.random() * Math.PI * 2; self.noiseSpeed = 0.0003 + Math.random() * 0.0002; // Extremely slow noise scrolling self.distortionAmplitude = 0.008 + Math.random() * 0.005; // Minimal distortion for subtle undulations self.scrollDirection = Math.random() * Math.PI * 2; // Random scroll direction self.rippleFrequency = 0.1 + Math.random() * 0.05; // Infrequent pulse frequency self.noiseScrollX = 0; self.noiseScrollY = 0; self.radialOffset = Math.random() * Math.PI * 2; // For radial outward movement self.reflectionShift = 0; // For elongated reflection simulation self.startRipple = function () { if (self.isActive) return; self.isActive = true; // Reset noise pattern position self.noiseScrollX = 0; self.noiseScrollY = 0; self.reflectionShift = 0; // Extremely gentle fade in with very minimal alpha tween(rippleGraphics, { alpha: 0.08, scaleX: 0.1, scaleY: 0.1 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // Extremely slow main expansion phase with gradual fade tween(rippleGraphics, { alpha: 0, scaleX: self.maxScale, scaleY: self.maxScale }, { duration: self.expandDuration, easing: tween.easeOut, onFinish: function onFinish() { // Reset for reuse self.isActive = false; rippleGraphics.alpha = 0; rippleGraphics.scaleX = 0.05; rippleGraphics.scaleY = 0.05; } }); } }); }; self.update = function () { if (!self.isActive) return; // Enhanced shader-based noise pattern simulation for natural water movement self.noiseScrollX += Math.cos(self.scrollDirection) * self.noiseSpeed; self.noiseScrollY += Math.sin(self.scrollDirection) * self.noiseSpeed; // Multi-layered noise for complex water surface simulation var primaryNoise = Math.sin(LK.ticks * self.noiseSpeed + self.noiseOffset) * self.distortionAmplitude; var secondaryNoise = Math.cos(LK.ticks * self.noiseSpeed * 1.7 + self.noiseOffset2 + Math.PI) * self.distortionAmplitude * 0.6; var tertiaryNoise = Math.sin(LK.ticks * self.noiseSpeed * 0.5 + self.noiseOffset + Math.PI * 0.5) * self.distortionAmplitude * 0.3; // Radial outward movement simulation var radialPulse = Math.sin(LK.ticks * self.rippleFrequency + self.radialOffset) * 0.002; var radialExpansion = Math.cos(LK.ticks * self.rippleFrequency * 0.7 + self.radialOffset) * 0.001; // Apply extremely subtle rotation distortion for natural water movement rippleGraphics.rotation = (primaryNoise + secondaryNoise * 0.5) * 0.3; // Apply minimal position distortion for gentle drift rippleGraphics.x = (secondaryNoise + tertiaryNoise) * 1.5 + radialPulse * 50; rippleGraphics.y = (primaryNoise + tertiaryNoise * 0.8) * 1.2 + radialPulse * 30; // Simulate scrolling texture pattern with very subtle alpha variation var scrollPattern = Math.sin(self.noiseScrollX * 8) * Math.cos(self.noiseScrollY * 6) * 0.015; var reflectionPattern = Math.sin(self.reflectionShift * 0.1) * 0.01; self.reflectionShift += self.noiseSpeed * 100; // Apply elongated reflection shifts for sky/land reflection simulation rippleGraphics.skewX = (primaryNoise + reflectionPattern) * 0.02; rippleGraphics.skewY = (secondaryNoise + reflectionPattern * 0.7) * 0.015; // Combine all patterns for final alpha with natural pulsing var combinedAlpha = scrollPattern + reflectionPattern + radialExpansion; rippleGraphics.alpha = Math.max(0, Math.min(0.12, rippleGraphics.alpha + combinedAlpha)); }; return self; }); var WeatherParticle = Container.expand(function (weatherType) { var self = Container.call(this); self.weatherType = weatherType || 'rain'; self.speed = 0; self.drift = 0; self.initialX = 0; var assetName = weatherType === 'rain' ? 'rainDrop' : 'snowFlake'; var particleGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); if (weatherType === 'rain') { particleGraphics.alpha = 0.6 + Math.random() * 0.4; self.speed = 8 + Math.random() * 4; self.drift = (Math.random() - 0.5) * 2; } else if (weatherType === 'snow') { particleGraphics.alpha = 0.7 + Math.random() * 0.3; self.speed = 2 + Math.random() * 2; self.drift = (Math.random() - 0.5) * 4; self.floatOffset = Math.random() * Math.PI * 2; self.floatSpeed = 0.02 + Math.random() * 0.02; } self.update = function () { if (self.weatherType === 'rain') { self.y += self.speed; self.x += self.drift; } else if (self.weatherType === 'snow') { self.y += self.speed; self.x = self.initialX + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 30; particleGraphics.rotation += 0.01; } // Reset particle when it goes off screen if (self.y > 2780) { self.y = -50; self.x = Math.random() * 2048; self.initialX = self.x; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1B5E20 }); /**** * Game Code ****/ // Game state var gameState = 'exploration'; // 'exploration' or 'haven' var currentBiome = 0; var biomes = ['Whispering Woods', 'Crystal Caverns', 'Serene Shoreline', 'Starfall Sanctuary']; var biomeColors = [0xd7e4bd, 0xe1bee7, 0xb2dfdb, 0xc5cae9]; var biomeMusicTracks = ['whisperingWoods', 'crystalCaverns', 'sereneShore', 'starfallSanctuary']; var currentMusicTrack = null; var musicFadeTime = 2000; // ASMR ambient sound tracking var currentAmbientSounds = []; var lastFootstepTime = 0; var footstepInterval = 800; // milliseconds between footsteps var biomeAmbientSounds = [['birdChirps', 'lightRain'], // Whispering Woods ['caveDrops', 'fireflies'], // Crystal Caverns ['streamMurmur', 'windChimes'], // Serene Shoreline ['fireflies', 'gentleFire'] // Starfall Sanctuary ]; // Player and game objects var player = new Player(); var auraParticles = []; var havenDecorations = []; var floatingParticles = []; var luminescentParticles = []; var rareBloom = null; var rareBloomTimer = 0; var rareBloomSpawnInterval = 1800; // 30 seconds at 60fps var ephemeralCreature = null; var ephemeralTimer = 0; var ephemeralSpawnInterval = 3600; // 60 seconds at 60fps var ephemeralCreatureTypes = ['fox', 'hummingbird', 'spirit']; var hiddenVignettes = []; var vignetteTypes = ['mushroom', 'waterfall', 'bear']; var vignetteDiscoveryRadius = 120; // Weather system var currentWeather = 'clear'; // 'clear', 'rain', 'snow' var weatherParticles = []; var weatherTimer = 0; var weatherCheckInterval = 1800; // 30 seconds at 60fps var weatherDuration = 3600; // 60 seconds at 60fps var weatherEndTimer = 0; var isWeatherActive = false; // Water ripple system var waterRipples = []; var rippleTimer = 0; var rippleSpawnInterval = 900 + Math.random() * 1200; // 15-35 seconds at 60fps for extremely slow, infrequent ripples var maxActiveRipples = 3; // Zen moment system var zenMomentTimer = 0; var zenMomentInterval = 4500; // 75 seconds at 60fps of continuous activity var isZenMomentActive = false; var zenMomentDuration = 480; // 8 seconds at 60fps var zenMomentEndTimer = 0; var originalBackgroundTint = 0x000000; var zenAffirmations = ['Find your calm', 'Embrace tranquility', 'You are here', 'Breathe deeply', 'Peace flows through you', 'This moment is yours', 'Feel the serenity']; var zenText = null; // Tree sway system var treeSwayBranches = []; // Light shimmer system var lightShimmers = []; // Aura wisp system var auraWisps = []; var wispSpawnTimer = 0; var wispSpawnInterval = 120 + Math.random() * 180; // 2-5 seconds at 60fps var maxActiveWisps = 6; // Ephemeral shimmer particles var ephemeralShimmers = []; // Distant cloud system var distantClouds = []; // Rare bloom burst particles var rareBloomBursts = []; var biomeBackground = null; var havenBackground = null; // Aura collection tracking var auraCount = storage.auraCount || { green: 0, blue: 0, yellow: 0 }; var totalAuras = auraCount.green + auraCount.blue + auraCount.yellow; // UI elements var auraCountText = new Text2('Auras: ' + totalAuras, { size: 80, fill: 0xFFFFFF }); auraCountText.anchor.set(0.5, 0); var biomeText = new Text2(biomes[currentBiome], { size: 60, fill: 0xFFFFFF }); biomeText.anchor.set(0.5, 0); var havenButton = new Text2('Haven', { size: 70, fill: 0xFFEB3B }); havenButton.anchor.set(1, 0); var exploreButton = new Text2('Explore', { size: 70, fill: 0x4CAF50 }); exploreButton.anchor.set(0, 0); // Add UI to GUI LK.gui.top.addChild(auraCountText); LK.gui.top.addChild(biomeText); LK.gui.topRight.addChild(havenButton); LK.gui.topLeft.addChild(exploreButton); // Position UI elements auraCountText.y = 20; biomeText.y = 120; havenButton.x = -20; havenButton.y = 20; exploreButton.x = 120; exploreButton.y = 20; // Initialize game function initializeExploration() { gameState = 'exploration'; // Clear existing objects if (biomeBackground) biomeBackground.destroy(); if (havenBackground) havenBackground.destroy(); // Create biome background biomeBackground = game.addChild(LK.getAsset('biomeBackground', { anchorX: 0, anchorY: 0, x: 0, y: 0, tint: biomeColors[currentBiome] })); // Add player player.x = 1024; player.y = 1366; player.targetX = player.x; player.targetY = player.y; game.addChild(player); // Generate aura particles generateAuraParticles(); // Generate floating ambient particles generateFloatingParticles(); // Generate luminescent particles for magical atmosphere generateLuminescentParticles(); // Generate gentle tree sway for natural ambiance generateTreeSway(); // Generate subtle light shimmer for sky ambiance generateLightShimmer(); // Generate distant clouds for background movement generateDistantClouds(); // Spawn hidden vignettes for discovery spawnHiddenVignettes(); // Reset rare bloom timer rareBloomTimer = Math.floor(Math.random() * rareBloomSpawnInterval); // Clear ephemeral creature if (ephemeralCreature) { ephemeralCreature.destroy(); ephemeralCreature = null; } // Reset ephemeral timer ephemeralTimer = Math.floor(Math.random() * ephemeralSpawnInterval); // Reset weather timer weatherTimer = Math.floor(Math.random() * weatherCheckInterval); // Reset zen moment timer zenMomentTimer = Math.floor(Math.random() * zenMomentInterval); // Reset water ripple timer rippleTimer = Math.floor(Math.random() * rippleSpawnInterval); // Reset aura wisp timer wispSpawnTimer = Math.floor(Math.random() * wispSpawnInterval); // Update UI biomeText.setText(biomes[currentBiome]); biomeText.visible = true; exploreButton.visible = false; havenButton.visible = true; // Play biome-specific music and ambient sounds playBiomeMusic(currentBiome); playAmbientSounds(currentBiome); } function initializeHaven() { gameState = 'haven'; // Clear existing objects if (biomeBackground) biomeBackground.destroy(); clearAuraParticles(); clearFloatingParticles(); clearLuminescentParticles(); clearHiddenVignettes(); // Clear rare bloom if (rareBloom) { rareBloom.destroy(); rareBloom = null; } // Clear ephemeral creature if (ephemeralCreature) { ephemeralCreature.destroy(); ephemeralCreature = null; } // Clear weather effects if (isWeatherActive) { endWeatherTransformation(); } // Clear zen moment if (isZenMomentActive) { endZenMoment(); } // Clear water ripples clearWaterRipples(); // Clear tree sway clearTreeSway(); // Clear light shimmer clearLightShimmer(); // Clear aura wisps clearAuraWisps(); // Clear ephemeral shimmers clearEphemeralShimmers(); // Clear distant clouds clearDistantClouds(); // Clear rare bloom bursts clearRareBloomBursts(); // Create haven background havenBackground = game.addChild(LK.getAsset('havenBackground', { anchorX: 0, anchorY: 0, x: 0, y: 0 })); // Remove player from haven if (player.parent) player.parent.removeChild(player); // Load existing decorations loadHavenDecorations(); // Update UI biomeText.visible = false; exploreButton.visible = true; havenButton.visible = false; // Stop exploration ambient sounds for (var i = 0; i < currentAmbientSounds.length; i++) { var sound = LK.getSound(currentAmbientSounds[i]); sound.loop = false; sound.stop(); } currentAmbientSounds = []; // Play haven ambient music playHavenMusic(); } function generateAuraParticles() { clearAuraParticles(); var particleCount = 15 + Math.floor(Math.random() * 10); var auraTypes = ['green', 'blue', 'yellow']; for (var i = 0; i < particleCount; i++) { var auraType = auraTypes[Math.floor(Math.random() * auraTypes.length)]; var aura = new AuraParticle(auraType); aura.x = 100 + Math.random() * 1848; aura.y = 200 + Math.random() * 2332; aura.initialY = aura.y; auraParticles.push(aura); game.addChild(aura); } } function clearAuraParticles() { for (var i = auraParticles.length - 1; i >= 0; i--) { auraParticles[i].destroy(); auraParticles.splice(i, 1); } } function generateFloatingParticles() { clearFloatingParticles(); var particleCount = 20 + Math.floor(Math.random() * 15); for (var i = 0; i < particleCount; i++) { var particle = new FloatingParticle(); particle.x = Math.random() * 2048; particle.y = Math.random() * 2732; particle.initialX = particle.x; floatingParticles.push(particle); game.addChild(particle); } } function clearFloatingParticles() { for (var i = floatingParticles.length - 1; i >= 0; i--) { floatingParticles[i].destroy(); floatingParticles.splice(i, 1); } } function generateLuminescentParticles() { clearLuminescentParticles(); // Low spawn rate for sparse, ethereal feel var particleCount = 8 + Math.floor(Math.random() * 6); for (var i = 0; i < particleCount; i++) { var particle = new LuminescentParticle(); // Focus particles over magical areas with sparse distribution var spawnArea = Math.random(); if (spawnArea < 0.3) { // Over water area (magical reflections) particle.x = 300 + Math.random() * 1448; particle.y = 1600 + Math.random() * 1000; } else if (spawnArea < 0.6) { // Near mystical trees/edges particle.x = Math.random() < 0.5 ? Math.random() * 250 : 1798 + Math.random() * 250; particle.y = 200 + Math.random() * 2200; } else { // Scattered throughout for ambient magic particle.x = Math.random() * 2048; particle.y = 200 + Math.random() * 2200; } particle.initialX = particle.x; luminescentParticles.push(particle); game.addChild(particle); } } function clearLuminescentParticles() { for (var i = luminescentParticles.length - 1; i >= 0; i--) { luminescentParticles[i].destroy(); luminescentParticles.splice(i, 1); } } function clearEphemeralShimmers() { for (var i = ephemeralShimmers.length - 1; i >= 0; i--) { ephemeralShimmers[i].destroy(); ephemeralShimmers.splice(i, 1); } } function generateDistantClouds() { clearDistantClouds(); // Sparse cloud coverage for background ambiance var cloudCount = 3 + Math.floor(Math.random() * 4); for (var i = 0; i < cloudCount; i++) { var cloud = new DistantCloud(); // Position clouds in upper portion of sky cloud.x = Math.random() * 2400; // Start some off-screen cloud.y = 100 + Math.random() * 400; // Upper sky area cloud.initialX = cloud.x; cloud.initialY = cloud.y; // Vary cloud sizes for depth var depthScale = 0.4 + Math.random() * 0.8; cloud.scaleX = depthScale; cloud.scaleY = depthScale; distantClouds.push(cloud); game.addChild(cloud); } } function clearDistantClouds() { for (var i = distantClouds.length - 1; i >= 0; i--) { distantClouds[i].destroy(); distantClouds.splice(i, 1); } } function clearRareBloomBursts() { for (var i = rareBloomBursts.length - 1; i >= 0; i--) { rareBloomBursts[i].destroy(); rareBloomBursts.splice(i, 1); } } function spawnRareBloom() { if (rareBloom) return; // Only one rare bloom at a time rareBloom = new RareAuraBloom(); rareBloom.x = 200 + Math.random() * 1648; rareBloom.y = 300 + Math.random() * 2132; rareBloom.initialY = rareBloom.y; game.addChild(rareBloom); // Play appearance sound LK.getSound('rareBloomAppear').play(); // Create burst of light effect LK.effects.flashScreen(0xffd700, 800); // Reset timer for next spawn rareBloomTimer = 0; } function spawnEphemeralCreature() { if (ephemeralCreature) return; // Only one ephemeral creature at a time var creatureType = ephemeralCreatureTypes[Math.floor(Math.random() * ephemeralCreatureTypes.length)]; ephemeralCreature = new EphemeralCreature(creatureType); // Position away from player for mystical discovery var playerDistance = 300 + Math.random() * 400; var angle = Math.random() * Math.PI * 2; ephemeralCreature.x = Math.max(100, Math.min(1948, player.x + Math.cos(angle) * playerDistance)); ephemeralCreature.y = Math.max(200, Math.min(2532, player.y + Math.sin(angle) * playerDistance)); ephemeralCreature.initialY = ephemeralCreature.y; game.addChild(ephemeralCreature); // Play gentle appearance sound LK.getSound('ephemeralAppear').play(); // Trigger appearance animation ephemeralCreature.appear(); // Reset timer for next spawn ephemeralTimer = Math.floor(Math.random() * ephemeralSpawnInterval) + ephemeralSpawnInterval; } function spawnHiddenVignettes() { clearHiddenVignettes(); // Spawn 2-4 vignettes per biome in obscure corners var vignetteCount = 2 + Math.floor(Math.random() * 3); for (var i = 0; i < vignetteCount; i++) { var vignetteType = vignetteTypes[Math.floor(Math.random() * vignetteTypes.length)]; var vignette = new HiddenVignette(vignetteType); // Position in obscure corners and edges of map var cornerPositions = [{ x: 150, y: 250 }, // Top-left corner { x: 1900, y: 250 }, // Top-right corner { x: 150, y: 2500 }, // Bottom-left corner { x: 1900, y: 2500 }, // Bottom-right corner { x: 1024, y: 150 }, // Top center hidden { x: 100, y: 1366 }, // Left edge hidden { x: 1948, y: 1366 }, // Right edge hidden { x: 1024, y: 2600 } // Bottom center hidden ]; var position = cornerPositions[Math.floor(Math.random() * cornerPositions.length)]; // Add some randomness to exact position vignette.x = position.x + (Math.random() - 0.5) * 200; vignette.y = position.y + (Math.random() - 0.5) * 200; // Ensure vignette stays within bounds vignette.x = Math.max(100, Math.min(1948, vignette.x)); vignette.y = Math.max(200, Math.min(2532, vignette.y)); hiddenVignettes.push(vignette); game.addChild(vignette); } } function clearHiddenVignettes() { for (var i = hiddenVignettes.length - 1; i >= 0; i--) { hiddenVignettes[i].destroy(); hiddenVignettes.splice(i, 1); } } function loadHavenDecorations() { var savedDecorations = storage.havenDecorations || []; for (var i = 0; i < savedDecorations.length; i++) { var decorationData = savedDecorations[i]; var decoration = new HavenDecoration(decorationData.type); decoration.x = decorationData.x; decoration.y = decorationData.y; havenDecorations.push(decoration); game.addChild(decoration); } } function saveHavenDecorations() { var decorationData = []; for (var i = 0; i < havenDecorations.length; i++) { var decoration = havenDecorations[i]; decorationData.push({ type: decoration.decorationType, x: decoration.x, y: decoration.y }); } storage.havenDecorations = decorationData; } function updateUI() { totalAuras = auraCount.green + auraCount.blue + auraCount.yellow; auraCountText.setText('Auras: ' + totalAuras); storage.auraCount = auraCount; } function playAmbientSounds(biomeIndex) { // Stop current ambient sounds for (var i = 0; i < currentAmbientSounds.length; i++) { var sound = LK.getSound(currentAmbientSounds[i]); sound.loop = false; sound.stop(); } currentAmbientSounds = []; // Start new ambient sounds for this biome var sounds = biomeAmbientSounds[biomeIndex]; for (var j = 0; j < sounds.length; j++) { var sound = LK.getSound(sounds[j]); sound.loop = true; sound.play(); currentAmbientSounds.push(sounds[j]); } } function playBiomeMusic(biomeIndex) { var targetTrack = biomeMusicTracks[biomeIndex]; if (currentMusicTrack === targetTrack) return; if (currentMusicTrack) { // Fade out current music LK.playMusic(currentMusicTrack, { fade: { start: 0.7, end: 0, duration: musicFadeTime } }); LK.setTimeout(function () { // Fade in new music LK.playMusic(targetTrack, { fade: { start: 0, end: 0.7, duration: musicFadeTime } }); currentMusicTrack = targetTrack; }, musicFadeTime); } else { // First time playing music LK.playMusic(targetTrack, { fade: { start: 0, end: 0.7, duration: musicFadeTime } }); currentMusicTrack = targetTrack; } } function startWeatherTransformation() { if (isWeatherActive || gameState !== 'exploration') return; // Small chance for weather (10% chance each check) if (Math.random() > 0.1) return; // Choose weather type (equal chance for rain or snow) var weatherTypes = ['rain', 'snow']; currentWeather = weatherTypes[Math.floor(Math.random() * weatherTypes.length)]; isWeatherActive = true; weatherEndTimer = 0; // Create weather particles generateWeatherParticles(); // Play weather ambient sound var soundName = currentWeather === 'rain' ? 'gentleRain' : 'softSnow'; var weatherSound = LK.getSound(soundName); weatherSound.loop = true; weatherSound.play(); currentAmbientSounds.push(soundName); // Gentle screen tint for weather atmosphere if (currentWeather === 'rain') { tween(biomeBackground, { tint: 0x8fa5c7 }, { duration: 3000, easing: tween.easeInOut }); } else if (currentWeather === 'snow') { tween(biomeBackground, { tint: 0xc8d6e5 }, { duration: 3000, easing: tween.easeInOut }); } } function endWeatherTransformation() { if (!isWeatherActive) return; isWeatherActive = false; clearWeatherParticles(); // Stop weather ambient sound var soundName = currentWeather === 'rain' ? 'gentleRain' : 'softSnow'; var weatherSound = LK.getSound(soundName); weatherSound.loop = false; weatherSound.stop(); var soundIndex = currentAmbientSounds.indexOf(soundName); if (soundIndex > -1) { currentAmbientSounds.splice(soundIndex, 1); } // Restore original biome tint if (biomeBackground) { tween(biomeBackground, { tint: biomeColors[currentBiome] }, { duration: 3000, easing: tween.easeInOut }); } currentWeather = 'clear'; weatherTimer = Math.floor(Math.random() * weatherCheckInterval); } function generateWeatherParticles() { clearWeatherParticles(); var particleCount = currentWeather === 'rain' ? 25 : 20; for (var i = 0; i < particleCount; i++) { var particle = new WeatherParticle(currentWeather); particle.x = Math.random() * 2200; // Slightly wider than screen particle.y = Math.random() * 2800; particle.initialX = particle.x; weatherParticles.push(particle); game.addChild(particle); } } function clearWeatherParticles() { for (var i = weatherParticles.length - 1; i >= 0; i--) { weatherParticles[i].destroy(); weatherParticles.splice(i, 1); } } function generateWaterRipple() { // Only spawn ripples in Serene Shoreline biome (index 2) if (currentBiome !== 2 || gameState !== 'exploration') return; // Limit number of active ripples var activeRipples = 0; for (var i = 0; i < waterRipples.length; i++) { if (waterRipples[i].isActive) activeRipples++; } if (activeRipples >= maxActiveRipples) return; // Find an inactive ripple to reuse, or create new one var ripple = null; for (var j = 0; j < waterRipples.length; j++) { if (!waterRipples[j].isActive) { ripple = waterRipples[j]; break; } } if (!ripple) { ripple = new WaterRipple(); waterRipples.push(ripple); game.addChild(ripple); } // Position ripple in lower half of screen (lake area) ripple.x = 300 + Math.random() * 1448; // Avoid edges ripple.y = 1500 + Math.random() * 800; // Lower half for lake // Start the ripple animation ripple.startRipple(); // Reset timer with some randomness rippleTimer = 0; rippleSpawnInterval = 900 + Math.random() * 1200; // Extremely slow ripple spawn timing } function clearWaterRipples() { for (var i = waterRipples.length - 1; i >= 0; i--) { waterRipples[i].destroy(); waterRipples.splice(i, 1); } } function generateTreeSway() { clearTreeSway(); var branchCount = 8 + Math.floor(Math.random() * 6); // 8-14 swaying branches for (var i = 0; i < branchCount; i++) { var branch = new TreeSwayBranch(); // Position trees in foreground (edges) and mid-ground areas var positionArea = Math.random(); if (positionArea < 0.3) { // Left foreground trees branch.x = 50 + Math.random() * 300; branch.y = 400 + Math.random() * 1800; } else if (positionArea < 0.6) { // Right foreground trees branch.x = 1700 + Math.random() * 300; branch.y = 400 + Math.random() * 1800; } else { // Mid-ground scattered trees branch.x = 300 + Math.random() * 1400; branch.y = 200 + Math.random() * 1200; } // Vary branch sizes for depth var depthScale = 0.6 + Math.random() * 0.8; branch.scaleX = depthScale; branch.scaleY = depthScale; // Start the gentle swaying animation branch.startSway(); treeSwayBranches.push(branch); game.addChild(branch); } } function clearTreeSway() { for (var i = treeSwayBranches.length - 1; i >= 0; i--) { treeSwayBranches[i].destroy(); treeSwayBranches.splice(i, 1); } } function generateLightShimmer() { clearLightShimmer(); // Create 1-3 light sources depending on biome (sun/moon/stars) var shimmerCount = 1 + Math.floor(Math.random() * 3); for (var i = 0; i < shimmerCount; i++) { var shimmer = new LightShimmer(); // Position light sources in upper portion of sky if (shimmerCount === 1) { // Single main light source (sun/moon) positioned in upper third shimmer.x = 800 + Math.random() * 400; // Center-ish area shimmer.y = 200 + Math.random() * 400; // Upper third } else { // Multiple light sources (stars, etc.) scattered in upper half shimmer.x = 200 + Math.random() * 1648; shimmer.y = 100 + Math.random() * 600; } // Vary shimmer sizes for depth and variety var sizeScale = 0.5 + Math.random() * 1.0; shimmer.scaleX = sizeScale; shimmer.scaleY = sizeScale; // Start the gentle shimmer breathing animation shimmer.startShimmer(); lightShimmers.push(shimmer); game.addChild(shimmer); } } function clearLightShimmer() { for (var i = lightShimmers.length - 1; i >= 0; i--) { lightShimmers[i].destroy(); lightShimmers.splice(i, 1); } } function generateAuraWisp() { if (gameState !== 'exploration') return; // Limit number of active wisps var activeWisps = 0; for (var i = 0; i < auraWisps.length; i++) { if (!auraWisps[i].collected) activeWisps++; } if (activeWisps >= maxActiveWisps) return; // Choose wisp type based on current biome preference var wispTypes = ['green', 'blue', 'yellow']; var wispType = wispTypes[Math.floor(Math.random() * wispTypes.length)]; var wisp = new AuraWisp(wispType); // Position wisp to emerge from environmental elements var emergenceLocations = [ // From under rocks (bottom edges) { x: 100 + Math.random() * 300, y: 2400 + Math.random() * 200 }, { x: 1600 + Math.random() * 300, y: 2400 + Math.random() * 200 }, // Near glowing mushrooms (mid areas) { x: 400 + Math.random() * 1200, y: 1200 + Math.random() * 800 }, // From within tree trunks (forest edges) { x: 50 + Math.random() * 200, y: 600 + Math.random() * 1200 }, { x: 1800 + Math.random() * 200, y: 600 + Math.random() * 1200 }, // From crystal formations (upper areas for crystal caverns) { x: 300 + Math.random() * 1400, y: 300 + Math.random() * 600 }]; var location = emergenceLocations[Math.floor(Math.random() * emergenceLocations.length)]; // Add some randomness to exact position wisp.x = location.x + (Math.random() - 0.5) * 100; wisp.y = location.y + (Math.random() - 0.5) * 100; wisp.initialY = wisp.y; // Ensure wisp stays within bounds wisp.x = Math.max(50, Math.min(1998, wisp.x)); wisp.y = Math.max(200, Math.min(2532, wisp.y)); auraWisps.push(wisp); game.addChild(wisp); // Reset timer with some randomness wispSpawnTimer = 0; wispSpawnInterval = 120 + Math.random() * 180; } function clearAuraWisps() { for (var i = auraWisps.length - 1; i >= 0; i--) { auraWisps[i].destroy(); auraWisps.splice(i, 1); } } function triggerZenMoment() { if (isZenMomentActive || gameState !== 'exploration') return; isZenMomentActive = true; zenMomentEndTimer = 0; // Store original background tint if (biomeBackground) { originalBackgroundTint = biomeBackground.tint; // Subtle desaturation effect tween(biomeBackground, { tint: 0xb0b0b0 }, { duration: 2000, easing: tween.easeInOut }); } // Create zen affirmation text var affirmation = zenAffirmations[Math.floor(Math.random() * zenAffirmations.length)]; zenText = new Text2(affirmation, { size: 120, fill: 0xffffff }); zenText.anchor.set(0.5, 0.5); zenText.alpha = 0; // Position zen text in center LK.gui.center.addChild(zenText); // Gentle fade in for zen text - ensure zenText exists before tweening if (zenText) { tween(zenText, { alpha: 0.9, scaleX: 1.1, scaleY: 1.1 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { // Gentle pulsing while displayed - check zenText still exists if (zenText) { tween(zenText, { alpha: 0.7, scaleX: 1.0, scaleY: 1.0 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (zenText) { tween(zenText, { alpha: 0.9, scaleX: 1.1, scaleY: 1.1 }, { duration: 1000, easing: tween.easeInOut }); } } }); } } }); } // Play gentle zen bell sound LK.getSound('zenMomentBell').play(); // Play zen moment music (very minimalistic) var currentVolume = 0.7; if (currentMusicTrack) { // Fade current music to lower volume LK.playMusic(currentMusicTrack, { fade: { start: currentVolume, end: 0.2, duration: 1500 } }); } // Start zen music softly LK.playMusic('zenMomentMusic', { fade: { start: 0, end: 0.3, duration: 1500 } }); } function endZenMoment() { if (!isZenMomentActive) return; isZenMomentActive = false; // Fade out zen text if (zenText && zenText.parent) { tween(zenText, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 2000, easing: tween.easeIn, onFinish: function onFinish() { if (zenText && zenText.parent) { zenText.parent.removeChild(zenText); } zenText = null; } }); } else if (zenText) { // If zenText exists but has no parent, just set to null zenText = null; } // Restore original background tint if (biomeBackground) { tween(biomeBackground, { tint: originalBackgroundTint }, { duration: 2000, easing: tween.easeInOut }); } // Restore original music LK.playMusic('zenMomentMusic', { fade: { start: 0.3, end: 0, duration: 1500 } }); LK.setTimeout(function () { if (currentMusicTrack && currentMusicTrack !== 'zenMomentMusic') { LK.playMusic(currentMusicTrack, { fade: { start: 0.2, end: 0.7, duration: 1500 } }); } }, 1500); // Reset timer for next zen moment zenMomentTimer = Math.floor(Math.random() * zenMomentInterval) + zenMomentInterval; } function playHavenMusic() { if (currentMusicTrack === 'havenAmbient') return; if (currentMusicTrack) { // Fade out current music var prevTrack = currentMusicTrack; LK.playMusic(prevTrack, { fade: { start: 0.7, end: 0, duration: musicFadeTime } }); LK.setTimeout(function () { // Fade in haven music LK.playMusic('havenAmbient', { fade: { start: 0, end: 0.6, duration: musicFadeTime } }); currentMusicTrack = 'havenAmbient'; }, musicFadeTime); } else { // First time playing music LK.playMusic('havenAmbient', { fade: { start: 0, end: 0.6, duration: musicFadeTime } }); currentMusicTrack = 'havenAmbient'; } } // Touch/Mouse handlers var draggedDecoration = null; var isPlacingDecoration = false; game.move = function (x, y, obj) { if (gameState === 'exploration') { player.targetX = x; player.targetY = y; } else if (gameState === 'haven' && draggedDecoration) { draggedDecoration.x = x; draggedDecoration.y = y; } }; game.down = function (x, y, obj) { if (gameState === 'haven') { // Check if touching a decoration for (var i = 0; i < havenDecorations.length; i++) { var decoration = havenDecorations[i]; var dx = x - decoration.x; var dy = y - decoration.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 60) { draggedDecoration = decoration; // Play rustle sound for plant interactions if (decoration.decorationType === 'plant') { LK.getSound('plantRustle').play(); } break; } } // Try to place new decoration if not dragging if (!draggedDecoration && totalAuras >= 10) { var decorationTypes = ['plant', 'light', 'water']; var randomType = decorationTypes[Math.floor(Math.random() * decorationTypes.length)]; var newDecoration = new HavenDecoration(randomType); newDecoration.x = x; newDecoration.y = y; havenDecorations.push(newDecoration); game.addChild(newDecoration); auraCount.green = Math.max(0, auraCount.green - 4); auraCount.blue = Math.max(0, auraCount.blue - 3); auraCount.yellow = Math.max(0, auraCount.yellow - 3); updateUI(); saveHavenDecorations(); LK.getSound('placeItem').play(); } } }; game.up = function (x, y, obj) { if (draggedDecoration) { saveHavenDecorations(); draggedDecoration = null; } }; // Button handlers havenButton.down = function (x, y, obj) { if (gameState === 'exploration') { initializeHaven(); } }; exploreButton.down = function (x, y, obj) { if (gameState === 'haven') { initializeExploration(); } }; // Main game update loop game.update = function () { if (gameState === 'exploration') { // Check rare bloom spawning rareBloomTimer++; if (rareBloomTimer >= rareBloomSpawnInterval && !rareBloom) { spawnRareBloom(); } // Check ephemeral creature spawning ephemeralTimer++; if (ephemeralTimer >= ephemeralSpawnInterval && !ephemeralCreature) { spawnEphemeralCreature(); } // Check weather transformations weatherTimer++; if (weatherTimer >= weatherCheckInterval && !isWeatherActive) { startWeatherTransformation(); } // Check zen moment triggering zenMomentTimer++; if (zenMomentTimer >= zenMomentInterval && !isZenMomentActive) { triggerZenMoment(); } // Check water ripple spawning (only in Serene Shoreline) if (currentBiome === 2) { rippleTimer++; if (rippleTimer >= rippleSpawnInterval) { generateWaterRipple(); } } // Check aura wisp spawning wispSpawnTimer++; if (wispSpawnTimer >= wispSpawnInterval) { generateAuraWisp(); } // Clean up collected wisps for (var w = auraWisps.length - 1; w >= 0; w--) { if (auraWisps[w].collected) { auraWisps.splice(w, 1); } } // Clean up expired ephemeral shimmers for (var s = ephemeralShimmers.length - 1; s >= 0; s--) { if (!ephemeralShimmers[s].parent) { ephemeralShimmers.splice(s, 1); } } // Clean up expired rare bloom burst particles for (var b = rareBloomBursts.length - 1; b >= 0; b--) { if (!rareBloomBursts[b].parent) { rareBloomBursts.splice(b, 1); } } // Check zen moment duration if (isZenMomentActive) { zenMomentEndTimer++; if (zenMomentEndTimer >= zenMomentDuration) { endZenMoment(); } } // Check weather duration if (isWeatherActive) { weatherEndTimer++; if (weatherEndTimer >= weatherDuration) { endWeatherTransformation(); } } // Check hidden vignette discovery for (var v = 0; v < hiddenVignettes.length; v++) { var vignette = hiddenVignettes[v]; if (!vignette.discovered) { var dx = player.x - vignette.x; var dy = player.y - vignette.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < vignetteDiscoveryRadius) { vignette.discover(); } } } // Check rare bloom collection if (rareBloom && !rareBloom.collected && player.intersects(rareBloom)) { rareBloom.collected = true; // Award significant auras (5 of each type) auraCount.green += 5; auraCount.blue += 5; auraCount.yellow += 5; updateUI(); // Play special collection sound LK.getSound('rareBloomCollect').play(); // Create spectacular visual effect LK.effects.flashScreen(0xffd700, 1500); // Create burst of glowing particles (medium count for spectacular effect) for (var b = 0; b < 12; b++) { var burstParticle = new RareBloomBurst(); burstParticle.x = rareBloom.x; burstParticle.y = rareBloom.y; rareBloomBursts.push(burstParticle); game.addChild(burstParticle); } // Burst animation with swirl of particles tween(rareBloom, { scaleX: 3.0, scaleY: 3.0, alpha: 1.0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(rareBloom, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { rareBloom.destroy(); rareBloom = null; // Reset timer for next rare bloom rareBloomTimer = Math.floor(Math.random() * rareBloomSpawnInterval); } }); } }); } // Check aura collection for (var i = auraParticles.length - 1; i >= 0; i--) { var aura = auraParticles[i]; if (!aura.collected && player.intersects(aura)) { aura.collected = true; auraCount[aura.type]++; updateUI(); // Collect animation with gentle glow effect tween(aura, { scaleX: 2.0, scaleY: 2.0, alpha: 1.0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(aura, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { aura.destroy(); } }); } }); auraParticles.splice(i, 1); LK.getSound('collectAura').play(); } } // Check if all auras collected if (auraParticles.length === 0) { LK.setTimeout(function () { currentBiome = (currentBiome + 1) % biomes.length; generateAuraParticles(); // Update biome display and music biomeText.setText(biomes[currentBiome]); if (biomeBackground) { biomeBackground.tint = biomeColors[currentBiome]; } playBiomeMusic(currentBiome); playAmbientSounds(currentBiome); }, 1000); } } }; // Start the game initializeExploration();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AuraParticle = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'green';
self.collected = false;
var assetName = 'aura' + self.type.charAt(0).toUpperCase() + self.type.slice(1);
var auraGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
auraGraphics.alpha = 0.8;
self.floatOffset = Math.random() * Math.PI * 2;
self.floatSpeed = 0.02 + Math.random() * 0.03;
self.pulseOffset = Math.random() * Math.PI * 2;
self.initialY = 0;
self.initialScale = 1.0;
// Start gentle pulsing animation
tween(auraGraphics, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1.0
}, {
duration: 1500 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.collected) {
tween(auraGraphics, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.6
}, {
duration: 1500 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.collected) {
tween(auraGraphics, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
});
}
}
});
self.update = function () {
if (!self.collected) {
self.y = self.initialY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 8;
auraGraphics.rotation += 0.015;
}
};
return self;
});
var AuraWisp = Container.expand(function (wispType) {
var self = Container.call(this);
self.wispType = wispType || 'green';
self.collected = false;
self.isMovingToPlayer = false;
self.moveSpeed = 3.5;
// Set wisp color based on type
var wispColors = {
green: 0xc8e6c9,
blue: 0xb3d9ff,
yellow: 0xfff8d1
};
var wispGraphics = self.attachAsset('auraWisp', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply wisp type color
wispGraphics.tint = wispColors[self.wispType];
wispGraphics.alpha = 0.7;
self.floatOffset = Math.random() * Math.PI * 2;
self.floatSpeed = 0.025 + Math.random() * 0.015;
self.pulseOffset = Math.random() * Math.PI * 2;
self.initialY = 0;
self.emergeDuration = 120; // 2 seconds to emerge
self.emergeTimer = 0;
// Start gentle emergence animation
tween(wispGraphics, {
alpha: 0.9,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000 + Math.random() * 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (!self.collected) {
// Start floating towards player
self.isMovingToPlayer = true;
}
}
});
self.moveTowardsPlayer = function () {
if (!player || self.collected) return;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
// Gentle movement towards player
self.x += dx / distance * self.moveSpeed;
self.y += dy / distance * self.moveSpeed;
} else {
// Close enough to be collected
self.collected = true;
// Award small aura
auraCount[self.wispType]++;
updateUI();
// Gentle collection animation
tween(wispGraphics, {
scaleX: 1.8,
scaleY: 1.8,
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(wispGraphics, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
LK.getSound('collectAura').play();
}
};
self.update = function () {
if (self.collected) return;
if (!self.isMovingToPlayer) {
// Gentle floating motion while emerging
self.y = self.initialY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 6;
self.emergeTimer++;
// Start moving towards player after emergence period
if (self.emergeTimer >= self.emergeDuration) {
self.isMovingToPlayer = true;
}
} else {
// Move towards player with gentle floating motion
self.moveTowardsPlayer();
// Add gentle floating while moving
var floatMotion = Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 4;
self.y += floatMotion * 0.1;
}
// Gentle pulsing glow
wispGraphics.alpha = 0.6 + Math.sin(LK.ticks * 0.04 + self.pulseOffset) * 0.3;
wispGraphics.rotation += 0.01;
};
return self;
});
var DistantCloud = Container.expand(function () {
var self = Container.call(this);
var cloudGraphics = self.attachAsset('distantCloud', {
anchorX: 0.5,
anchorY: 0.5
});
cloudGraphics.alpha = 0.3 + Math.random() * 0.2;
self.driftSpeed = 0.1 + Math.random() * 0.15; // Extremely slow drift
self.initialX = 0;
self.floatOffset = Math.random() * Math.PI * 2;
self.floatSpeed = 0.003 + Math.random() * 0.005; // Very slow vertical float
self.initialY = 0;
// Start gentle size pulsing for natural cloud variation
tween(cloudGraphics, {
scaleX: 1.1 + Math.random() * 0.2,
scaleY: 1.1 + Math.random() * 0.2
}, {
duration: 8000 + Math.random() * 4000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(cloudGraphics, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 8000 + Math.random() * 4000,
easing: tween.easeInOut
});
}
});
self.update = function () {
// Imperceptibly slow horizontal drift - consistent with typical cloud movement
// Enhanced with tween-based smooth transitions for more natural movement
self.x += self.driftSpeed * 0.3; // Reduced to imperceptibly slow speed
// Very subtle vertical floating motion
self.y = self.initialY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 8;
// Reset cloud when it goes off screen
if (self.x > 2100) {
self.x = -200;
self.initialX = self.x;
// Add subtle tween variation when cloud resets for natural repositioning
tween(self, {
x: self.x + (Math.random() - 0.5) * 100,
y: self.initialY + (Math.random() - 0.5) * 80
}, {
duration: 5000 + Math.random() * 3000,
easing: tween.easeInOut
});
}
};
return self;
});
var EphemeralCreature = Container.expand(function (creatureType) {
var self = Container.call(this);
self.creatureType = creatureType || 'fox';
self.hasAppeared = false;
self.isObserving = false;
self.observeTimer = 0;
self.observeDuration = 180; // 3 seconds at 60fps
var assetName = 'ephemeral' + self.creatureType.charAt(0).toUpperCase() + self.creatureType.slice(1);
var creatureGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Start invisible
creatureGraphics.alpha = 0;
self.floatOffset = Math.random() * Math.PI * 2;
self.floatSpeed = 0.015;
self.initialY = 0;
self.appear = function () {
if (self.hasAppeared) return;
self.hasAppeared = true;
// Create sparkling shimmer effect during appearance
self.createShimmerBurst(12);
// Gentle fade in appearance
tween(creatureGraphics, {
alpha: 0.8,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isObserving = true;
// Gentle glow pulsing while observing
tween(creatureGraphics, {
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(creatureGraphics, {
alpha: 0.7,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
});
};
self.fadeAway = function () {
self.isObserving = false;
// Create gentle dissipation shimmer effect
self.createShimmerBurst(8);
// Gentle fade out
tween(creatureGraphics, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 2000,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
};
self.update = function () {
if (self.hasAppeared && !self.isObserving) return;
// Gentle floating motion
self.y = self.initialY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 12;
// Subtle rotation for hummingbird
if (self.creatureType === 'hummingbird') {
creatureGraphics.rotation = Math.sin(LK.ticks * 0.08) * 0.1;
}
// Count observe timer
if (self.isObserving) {
self.observeTimer++;
if (self.observeTimer >= self.observeDuration) {
self.fadeAway();
}
}
};
self.createShimmerBurst = function (count) {
for (var i = 0; i < count; i++) {
var shimmer = new EphemeralShimmer();
// Position around creature with some spread
var angle = Math.random() * Math.PI * 2;
var distance = 30 + Math.random() * 60;
shimmer.x = self.x + Math.cos(angle) * distance;
shimmer.y = self.y + Math.sin(angle) * distance;
// Add to global shimmer tracking
ephemeralShimmers.push(shimmer);
game.addChild(shimmer);
// Gentle sparkle appearance
tween(shimmer, {
alpha: 0.7 + Math.random() * 0.3,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300 + Math.random() * 200,
easing: tween.easeOut
});
}
};
return self;
});
var EphemeralShimmer = Container.expand(function () {
var self = Container.call(this);
var shimmerGraphics = self.attachAsset('ephemeralShimmer', {
anchorX: 0.5,
anchorY: 0.5
});
shimmerGraphics.alpha = 0;
self.speed = 1.5 + Math.random() * 2.0;
self.driftX = (Math.random() - 0.5) * 4;
self.driftY = -1 - Math.random() * 2;
self.floatOffset = Math.random() * Math.PI * 2;
self.floatSpeed = 0.05 + Math.random() * 0.03;
self.lifetime = 90 + Math.random() * 60; // 1.5-2.5 seconds
self.age = 0;
self.update = function () {
self.age++;
self.x += self.driftX;
self.y += self.driftY;
// Gentle floating motion
self.x += Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 0.5;
// Fade out over lifetime
var lifeProgress = self.age / self.lifetime;
shimmerGraphics.alpha = (1 - lifeProgress) * 0.8;
shimmerGraphics.scaleX = 1 - lifeProgress * 0.3;
shimmerGraphics.scaleY = 1 - lifeProgress * 0.3;
// Remove when lifetime exceeded
if (self.age >= self.lifetime) {
self.destroy();
}
};
return self;
});
var FloatingParticle = Container.expand(function () {
var self = Container.call(this);
var particleGraphics = self.attachAsset('floatingParticle', {
anchorX: 0.5,
anchorY: 0.5
});
particleGraphics.alpha = 0.3 + Math.random() * 0.4;
self.driftSpeed = 0.5 + Math.random() * 1.0;
self.floatOffset = Math.random() * Math.PI * 2;
self.floatSpeed = 0.01 + Math.random() * 0.02;
self.initialX = 0;
self.update = function () {
self.y -= self.driftSpeed;
self.x = self.initialX + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 20;
particleGraphics.rotation += 0.01;
// Reset particle when it goes off screen
if (self.y < -50) {
self.y = 2800;
self.x = Math.random() * 2048;
self.initialX = self.x;
}
};
return self;
});
var HavenDecoration = Container.expand(function (decorationType) {
var self = Container.call(this);
self.decorationType = decorationType || 'plant';
var assetName = self.decorationType + 'Decoration';
var decorationGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 1.0
});
decorationGraphics.alpha = 0.9;
self.swayOffset = Math.random() * Math.PI * 2;
self.swaySpeed = 0.01 + Math.random() * 0.02;
self.update = function () {
// Gentle swaying animation for plants and light decorations
if (self.decorationType === 'plant' || self.decorationType === 'light') {
decorationGraphics.rotation = Math.sin(LK.ticks * self.swaySpeed + self.swayOffset) * 0.05;
}
// Subtle pulsing for light decorations
if (self.decorationType === 'light') {
decorationGraphics.alpha = 0.7 + Math.sin(LK.ticks * 0.03 + self.swayOffset) * 0.2;
}
};
return self;
});
var HiddenVignette = Container.expand(function (vignetteType) {
var self = Container.call(this);
self.vignetteType = vignetteType || 'mushroom';
self.discovered = false;
self.isActive = false;
self.fireflies = [];
// Create main vignette element
var mainAsset = '';
if (self.vignetteType === 'mushroom') mainAsset = 'mushroomRing';else if (self.vignetteType === 'waterfall') mainAsset = 'hiddenWaterfall';else if (self.vignetteType === 'bear') mainAsset = 'sleepingBear';
var vignetteGraphics = self.attachAsset(mainAsset, {
anchorX: 0.5,
anchorY: 0.5
});
vignetteGraphics.alpha = 0; // Start invisible
self.pulseOffset = Math.random() * Math.PI * 2;
self.breathOffset = Math.random() * Math.PI * 2;
// Discover animation
self.discover = function () {
if (self.discovered) return;
self.discovered = true;
self.isActive = true;
// Gentle fade in discovery
tween(vignetteGraphics, {
alpha: 0.8,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 2000,
easing: tween.easeOut
});
// Play vignette-specific sound
var soundName = 'vignette' + self.vignetteType.charAt(0).toUpperCase() + self.vignetteType.slice(1);
LK.getSound(soundName).play();
// Create special effects based on type
if (self.vignetteType === 'mushroom') {
self.createFireflies();
} else if (self.vignetteType === 'waterfall') {
self.createRainbow();
}
};
self.createFireflies = function () {
// Create dancing fireflies around mushroom ring
for (var i = 0; i < 8; i++) {
var firefly = self.addChild(LK.getAsset('firefly', {
anchorX: 0.5,
anchorY: 0.5
}));
firefly.alpha = 0;
firefly.danceAngle = Math.PI * 2 / 8 * i;
firefly.danceRadius = 80 + Math.random() * 40;
firefly.danceSpeed = 0.02 + Math.random() * 0.02;
firefly.pulseOffset = Math.random() * Math.PI * 2;
self.fireflies.push(firefly);
// Fade in firefly
tween(firefly, {
alpha: 0.7 + Math.random() * 0.3
}, {
duration: 1000 + Math.random() * 1000,
easing: tween.easeOut
});
}
};
self.createRainbow = function () {
// Create subtle rainbow mist effect
LK.effects.flashObject(vignetteGraphics, 0xff9800, 3000);
// Add gentle shimmer
tween(vignetteGraphics, {
alpha: 1.0
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(vignetteGraphics, {
alpha: 0.6
}, {
duration: 1500,
easing: tween.easeInOut
});
}
});
};
self.update = function () {
if (!self.isActive) return;
// Gentle pulsing for mushroom ring
if (self.vignetteType === 'mushroom') {
vignetteGraphics.alpha = 0.6 + Math.sin(LK.ticks * 0.02 + self.pulseOffset) * 0.2;
// Animate fireflies dancing
for (var i = 0; i < self.fireflies.length; i++) {
var firefly = self.fireflies[i];
firefly.danceAngle += firefly.danceSpeed;
firefly.x = Math.cos(firefly.danceAngle) * firefly.danceRadius;
firefly.y = Math.sin(firefly.danceAngle) * firefly.danceRadius;
firefly.alpha = 0.4 + Math.sin(LK.ticks * 0.05 + firefly.pulseOffset) * 0.4;
}
}
// Gentle breathing for sleeping bear
else if (self.vignetteType === 'bear') {
var breathScale = 1.0 + Math.sin(LK.ticks * 0.015 + self.breathOffset) * 0.05;
vignetteGraphics.scaleX = breathScale;
vignetteGraphics.scaleY = breathScale;
}
// Subtle shimmer for waterfall
else if (self.vignetteType === 'waterfall') {
vignetteGraphics.alpha = 0.7 + Math.sin(LK.ticks * 0.03 + self.pulseOffset) * 0.1;
}
};
return self;
});
var LightShimmer = Container.expand(function () {
var self = Container.call(this);
var shimmerGraphics = self.attachAsset('lightShimmer', {
anchorX: 0.5,
anchorY: 0.5
});
shimmerGraphics.alpha = 0.1;
self.pulseOffset = Math.random() * Math.PI * 2;
self.pulseSpeed = 0.005 + Math.random() * 0.003; // Very slow, breathing-like pulse
self.breathAmplitude = 0.05 + Math.random() * 0.03; // Very subtle intensity variation
// Start gentle breathing animation using tween for smooth transitions
self.startShimmer = function () {
var breatheIn = function breatheIn() {
tween(shimmerGraphics, {
alpha: 0.15 + self.breathAmplitude,
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 8000 + Math.random() * 4000,
// 8-12 seconds per breath
easing: tween.easeInOut,
onFinish: breatheOut
});
};
var breatheOut = function breatheOut() {
tween(shimmerGraphics, {
alpha: 0.05,
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 8000 + Math.random() * 4000,
// 8-12 seconds per breath
easing: tween.easeInOut,
onFinish: breatheIn
});
};
// Start with random direction
if (Math.random() < 0.5) {
breatheIn();
} else {
breatheOut();
}
};
self.update = function () {
// Additional very subtle pulsing using sine wave for natural variation
var subtlePulse = Math.sin(LK.ticks * self.pulseSpeed + self.pulseOffset) * 0.02;
shimmerGraphics.alpha += subtlePulse;
// Ensure alpha stays within reasonable bounds
shimmerGraphics.alpha = Math.max(0.02, Math.min(0.25, shimmerGraphics.alpha));
};
return self;
});
var LuminescentParticle = Container.expand(function () {
var self = Container.call(this);
// Use alternating particle types for variety
var assetName = Math.random() < 0.7 ? 'luminescentParticle' : 'luminescentParticleAlt';
var particleGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Soft intensity with pastel colors
var pastelColors = [0xe0ffff, 0xf0f8ff, 0xfffacd, 0xf0fff0, 0xffe4e1];
particleGraphics.tint = pastelColors[Math.floor(Math.random() * pastelColors.length)];
particleGraphics.alpha = 0.3 + Math.random() * 0.2; // Reduced for subtle effect
// Enhanced movement properties for gentle floating
self.driftSpeed = 0.08 + Math.random() * 0.12; // Very slow upward drift
self.lateralAmplitude = 20 + Math.random() * 30; // Gentle S-curve amplitude
self.lateralSpeed = 0.003 + Math.random() * 0.005; // Very slow lateral sway frequency
self.initialX = 0;
self.lateralOffset = Math.random() * Math.PI * 2; // Random phase for S-curve
self.lateralDirection = Math.random() < 0.5 ? 1 : -1; // Random sway direction
// Long lifetime properties
self.age = 0;
self.maxAge = 3600 + Math.random() * 1800; // 60-90 seconds lifetime for long traversal
self.spawned = false;
// Soft pulsing properties
self.pulseOffset = Math.random() * Math.PI * 2;
self.pulseSpeed = 0.008 + Math.random() * 0.007; // Very gentle pulse frequency
self.baseAlpha = 0.25 + Math.random() * 0.15; // Soft base opacity
// Start gentle emergence animation
self.emerge = function () {
if (self.spawned) return;
self.spawned = true;
// Gentle fade in and scale up
tween(particleGraphics, {
alpha: self.baseAlpha,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 2000 + Math.random() * 1000,
easing: tween.easeOut
});
};
// Start gentle pulsing animation
self.startPulse = function () {
var pulseIn = function pulseIn() {
if (self.age >= self.maxAge) return;
tween(particleGraphics, {
alpha: self.baseAlpha + 0.2,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 3000 + Math.random() * 2000,
easing: tween.easeInOut,
onFinish: pulseOut
});
};
var pulseOut = function pulseOut() {
if (self.age >= self.maxAge) return;
tween(particleGraphics, {
alpha: self.baseAlpha - 0.1,
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 3000 + Math.random() * 2000,
easing: tween.easeInOut,
onFinish: pulseIn
});
};
// Start with random direction
if (Math.random() < 0.5) {
pulseIn();
} else {
pulseOut();
}
};
// Enhanced floating movement system
self.update = function () {
if (!self.spawned) {
self.emerge();
self.startPulse();
}
self.age++;
// Very slow upward drift - effortless floating
self.y -= self.driftSpeed;
// Gentle lateral sway in S-curve pattern
var lateralSway = Math.sin(LK.ticks * self.lateralSpeed + self.lateralOffset) * self.lateralAmplitude * self.lateralDirection;
var secondarySway = Math.cos(LK.ticks * self.lateralSpeed * 0.7 + self.lateralOffset + Math.PI) * self.lateralAmplitude * 0.3;
self.x = self.initialX + lateralSway + secondarySway;
// Additional very subtle sine wave variation for natural movement
var microDrift = Math.sin(LK.ticks * 0.002 + self.lateralOffset) * 3;
self.x += microDrift;
// Subtle additional alpha variation for ethereal effect
var additionalGlow = Math.sin(LK.ticks * self.pulseSpeed + self.pulseOffset) * 0.08;
particleGraphics.alpha += additionalGlow;
// Gentle rotation for natural floating
particleGraphics.rotation += 0.003 * self.lateralDirection;
// Reset particle when lifetime exceeded or goes off screen
if (self.age >= self.maxAge || self.y < -100) {
// Reset for reuse with long lifetime
self.y = 2800 + Math.random() * 200;
self.x = Math.random() * 2048;
self.initialX = self.x;
self.age = 0;
self.spawned = false;
// Randomize movement parameters for variety
self.driftSpeed = 0.08 + Math.random() * 0.12;
self.lateralAmplitude = 20 + Math.random() * 30;
self.lateralSpeed = 0.003 + Math.random() * 0.005;
self.lateralDirection = Math.random() < 0.5 ? 1 : -1;
self.lateralOffset = Math.random() * Math.PI * 2;
self.maxAge = 3600 + Math.random() * 1800;
self.baseAlpha = 0.25 + Math.random() * 0.15;
// Reset graphics properties
particleGraphics.alpha = 0;
particleGraphics.scaleX = 0.8;
particleGraphics.scaleY = 0.8;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.targetX = 0;
self.targetY = 0;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Play footstep sounds based on biome
if (LK.ticks - lastFootstepTime > footstepInterval * 60 / 1000) {
var footstepSound = '';
if (currentBiome === 0) footstepSound = 'footstepsGrass'; // Woods
else if (currentBiome === 1) footstepSound = 'footstepsPebbles'; // Caverns
else if (currentBiome === 2) footstepSound = 'footstepsWater'; // Shore
else footstepSound = 'footstepsGrass'; // Sanctuary
LK.getSound(footstepSound).play();
lastFootstepTime = LK.ticks;
}
}
};
return self;
});
var RareAuraBloom = Container.expand(function () {
var self = Container.call(this);
self.collected = false;
var bloomGraphics = self.attachAsset('rareAuraBloom', {
anchorX: 0.5,
anchorY: 0.5
});
bloomGraphics.alpha = 0.9;
self.pulseOffset = Math.random() * Math.PI * 2;
self.floatOffset = Math.random() * Math.PI * 2;
self.floatSpeed = 0.03;
self.initialY = 0;
// Start dramatic pulsating animation
tween(bloomGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1.0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.collected) {
tween(bloomGraphics, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.7
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.collected) {
tween(bloomGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1.0
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
});
}
}
});
self.update = function () {
if (!self.collected) {
self.y = self.initialY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 15;
bloomGraphics.rotation += 0.02;
}
};
return self;
});
var RareBloomBurst = Container.expand(function () {
var self = Container.call(this);
var burstGraphics = self.attachAsset('rareBloomBurstParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Vibrant pastel mix colors
var vibrantColors = [0xffd700, 0xff69b4, 0x98fb98, 0x87ceeb, 0xdda0dd, 0xf0e68c];
burstGraphics.tint = vibrantColors[Math.floor(Math.random() * vibrantColors.length)];
burstGraphics.alpha = 0;
self.velocity = {
x: (Math.random() - 0.5) * 8,
y: -2 - Math.random() * 6
};
self.lifetime = 60; // 1 second burst duration
self.age = 0;
self.gravity = 0.2;
// Start burst animation
tween(burstGraphics, {
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(burstGraphics, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 800,
easing: tween.easeIn
});
}
});
self.update = function () {
self.age++;
self.x += self.velocity.x;
self.y += self.velocity.y;
self.velocity.y += self.gravity;
burstGraphics.rotation += 0.1;
// Remove when lifetime exceeded
if (self.age >= self.lifetime) {
self.destroy();
}
};
return self;
});
var TreeSwayBranch = Container.expand(function () {
var self = Container.call(this);
var branchGraphics = self.attachAsset('treeSwayBranch', {
anchorX: 0.5,
anchorY: 1.0
});
branchGraphics.alpha = 0.7;
// Enhanced tree sway parameters for natural movement
self.swayOffset = Math.random() * Math.PI * 2; // Random phase for variation between trees
self.swaySpeed = 0.006 + Math.random() * 0.004; // Very low frequency (0.1-0.2 Hz equivalent)
self.swayAmplitude = 0.02 + Math.random() * 0.015; // Small amplitude (1-3 degrees)
self.secondarySwayOffset = Math.random() * Math.PI * 2; // Secondary sway for complexity
self.secondarySwaySpeed = 0.004 + Math.random() * 0.003; // Even slower secondary movement
self.windVariation = Math.random() * 0.5 + 0.5; // Random wind strength per tree
// Enhanced continuous sway animation with multiple layers
self.startSway = function () {
// Primary sway motion - main side-to-side movement
var primarySwayLeft = function primarySwayLeft() {
tween(branchGraphics, {
rotation: -self.swayAmplitude * self.windVariation
}, {
duration: 4000 + Math.random() * 3000,
// 4-7 seconds per sway for calming rhythm
easing: tween.easeInOut,
onFinish: primarySwayRight
});
};
var primarySwayRight = function primarySwayRight() {
tween(branchGraphics, {
rotation: self.swayAmplitude * self.windVariation
}, {
duration: 4000 + Math.random() * 3000,
// 4-7 seconds per sway for calming rhythm
easing: tween.easeInOut,
onFinish: primarySwayLeft
});
};
// Start with random direction for phase variation
if (Math.random() < 0.5) {
primarySwayLeft();
} else {
primarySwayRight();
}
};
self.update = function () {
// Multi-layered natural sway simulation mimicking light breeze
// Primary sine wave for main movement
var primarySway = Math.sin(LK.ticks * self.swaySpeed + self.swayOffset) * 0.008 * self.windVariation;
// Secondary sine wave for complexity and natural variation
var secondarySway = Math.sin(LK.ticks * self.secondarySwaySpeed + self.secondarySwayOffset) * 0.004 * self.windVariation;
// Tertiary micro-movement for realistic rustling
var microSway = Math.sin(LK.ticks * 0.01 + self.swayOffset * 2) * 0.002;
// Combine all movement layers for natural breeze effect
var totalSway = primarySway + secondarySway + microSway;
branchGraphics.rotation += totalSway;
// Subtle scale variation to simulate depth movement in breeze
var scaleVariation = 1.0 + Math.sin(LK.ticks * self.swaySpeed * 0.7 + self.swayOffset) * 0.01;
branchGraphics.scaleX = scaleVariation;
};
return self;
});
var WaterRipple = Container.expand(function () {
var self = Container.call(this);
var rippleGraphics = self.attachAsset('waterRipple', {
anchorX: 0.5,
anchorY: 0.5
});
// Start invisible and small
rippleGraphics.alpha = 0;
rippleGraphics.scaleX = 0.05;
rippleGraphics.scaleY = 0.05;
self.isActive = false;
self.maxScale = 0.8 + Math.random() * 0.4; // Very low amplitude for barely stirring water
self.expandDuration = 15000 + Math.random() * 10000; // Extremely slow 15-25 seconds expansion
// Enhanced shader-based animation properties
self.noiseOffset = Math.random() * Math.PI * 2;
self.noiseOffset2 = Math.random() * Math.PI * 2;
self.noiseSpeed = 0.0003 + Math.random() * 0.0002; // Extremely slow noise scrolling
self.distortionAmplitude = 0.008 + Math.random() * 0.005; // Minimal distortion for subtle undulations
self.scrollDirection = Math.random() * Math.PI * 2; // Random scroll direction
self.rippleFrequency = 0.1 + Math.random() * 0.05; // Infrequent pulse frequency
self.noiseScrollX = 0;
self.noiseScrollY = 0;
self.radialOffset = Math.random() * Math.PI * 2; // For radial outward movement
self.reflectionShift = 0; // For elongated reflection simulation
self.startRipple = function () {
if (self.isActive) return;
self.isActive = true;
// Reset noise pattern position
self.noiseScrollX = 0;
self.noiseScrollY = 0;
self.reflectionShift = 0;
// Extremely gentle fade in with very minimal alpha
tween(rippleGraphics, {
alpha: 0.08,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Extremely slow main expansion phase with gradual fade
tween(rippleGraphics, {
alpha: 0,
scaleX: self.maxScale,
scaleY: self.maxScale
}, {
duration: self.expandDuration,
easing: tween.easeOut,
onFinish: function onFinish() {
// Reset for reuse
self.isActive = false;
rippleGraphics.alpha = 0;
rippleGraphics.scaleX = 0.05;
rippleGraphics.scaleY = 0.05;
}
});
}
});
};
self.update = function () {
if (!self.isActive) return;
// Enhanced shader-based noise pattern simulation for natural water movement
self.noiseScrollX += Math.cos(self.scrollDirection) * self.noiseSpeed;
self.noiseScrollY += Math.sin(self.scrollDirection) * self.noiseSpeed;
// Multi-layered noise for complex water surface simulation
var primaryNoise = Math.sin(LK.ticks * self.noiseSpeed + self.noiseOffset) * self.distortionAmplitude;
var secondaryNoise = Math.cos(LK.ticks * self.noiseSpeed * 1.7 + self.noiseOffset2 + Math.PI) * self.distortionAmplitude * 0.6;
var tertiaryNoise = Math.sin(LK.ticks * self.noiseSpeed * 0.5 + self.noiseOffset + Math.PI * 0.5) * self.distortionAmplitude * 0.3;
// Radial outward movement simulation
var radialPulse = Math.sin(LK.ticks * self.rippleFrequency + self.radialOffset) * 0.002;
var radialExpansion = Math.cos(LK.ticks * self.rippleFrequency * 0.7 + self.radialOffset) * 0.001;
// Apply extremely subtle rotation distortion for natural water movement
rippleGraphics.rotation = (primaryNoise + secondaryNoise * 0.5) * 0.3;
// Apply minimal position distortion for gentle drift
rippleGraphics.x = (secondaryNoise + tertiaryNoise) * 1.5 + radialPulse * 50;
rippleGraphics.y = (primaryNoise + tertiaryNoise * 0.8) * 1.2 + radialPulse * 30;
// Simulate scrolling texture pattern with very subtle alpha variation
var scrollPattern = Math.sin(self.noiseScrollX * 8) * Math.cos(self.noiseScrollY * 6) * 0.015;
var reflectionPattern = Math.sin(self.reflectionShift * 0.1) * 0.01;
self.reflectionShift += self.noiseSpeed * 100;
// Apply elongated reflection shifts for sky/land reflection simulation
rippleGraphics.skewX = (primaryNoise + reflectionPattern) * 0.02;
rippleGraphics.skewY = (secondaryNoise + reflectionPattern * 0.7) * 0.015;
// Combine all patterns for final alpha with natural pulsing
var combinedAlpha = scrollPattern + reflectionPattern + radialExpansion;
rippleGraphics.alpha = Math.max(0, Math.min(0.12, rippleGraphics.alpha + combinedAlpha));
};
return self;
});
var WeatherParticle = Container.expand(function (weatherType) {
var self = Container.call(this);
self.weatherType = weatherType || 'rain';
self.speed = 0;
self.drift = 0;
self.initialX = 0;
var assetName = weatherType === 'rain' ? 'rainDrop' : 'snowFlake';
var particleGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
if (weatherType === 'rain') {
particleGraphics.alpha = 0.6 + Math.random() * 0.4;
self.speed = 8 + Math.random() * 4;
self.drift = (Math.random() - 0.5) * 2;
} else if (weatherType === 'snow') {
particleGraphics.alpha = 0.7 + Math.random() * 0.3;
self.speed = 2 + Math.random() * 2;
self.drift = (Math.random() - 0.5) * 4;
self.floatOffset = Math.random() * Math.PI * 2;
self.floatSpeed = 0.02 + Math.random() * 0.02;
}
self.update = function () {
if (self.weatherType === 'rain') {
self.y += self.speed;
self.x += self.drift;
} else if (self.weatherType === 'snow') {
self.y += self.speed;
self.x = self.initialX + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 30;
particleGraphics.rotation += 0.01;
}
// Reset particle when it goes off screen
if (self.y > 2780) {
self.y = -50;
self.x = Math.random() * 2048;
self.initialX = self.x;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1B5E20
});
/****
* Game Code
****/
// Game state
var gameState = 'exploration'; // 'exploration' or 'haven'
var currentBiome = 0;
var biomes = ['Whispering Woods', 'Crystal Caverns', 'Serene Shoreline', 'Starfall Sanctuary'];
var biomeColors = [0xd7e4bd, 0xe1bee7, 0xb2dfdb, 0xc5cae9];
var biomeMusicTracks = ['whisperingWoods', 'crystalCaverns', 'sereneShore', 'starfallSanctuary'];
var currentMusicTrack = null;
var musicFadeTime = 2000;
// ASMR ambient sound tracking
var currentAmbientSounds = [];
var lastFootstepTime = 0;
var footstepInterval = 800; // milliseconds between footsteps
var biomeAmbientSounds = [['birdChirps', 'lightRain'],
// Whispering Woods
['caveDrops', 'fireflies'],
// Crystal Caverns
['streamMurmur', 'windChimes'],
// Serene Shoreline
['fireflies', 'gentleFire'] // Starfall Sanctuary
];
// Player and game objects
var player = new Player();
var auraParticles = [];
var havenDecorations = [];
var floatingParticles = [];
var luminescentParticles = [];
var rareBloom = null;
var rareBloomTimer = 0;
var rareBloomSpawnInterval = 1800; // 30 seconds at 60fps
var ephemeralCreature = null;
var ephemeralTimer = 0;
var ephemeralSpawnInterval = 3600; // 60 seconds at 60fps
var ephemeralCreatureTypes = ['fox', 'hummingbird', 'spirit'];
var hiddenVignettes = [];
var vignetteTypes = ['mushroom', 'waterfall', 'bear'];
var vignetteDiscoveryRadius = 120;
// Weather system
var currentWeather = 'clear'; // 'clear', 'rain', 'snow'
var weatherParticles = [];
var weatherTimer = 0;
var weatherCheckInterval = 1800; // 30 seconds at 60fps
var weatherDuration = 3600; // 60 seconds at 60fps
var weatherEndTimer = 0;
var isWeatherActive = false;
// Water ripple system
var waterRipples = [];
var rippleTimer = 0;
var rippleSpawnInterval = 900 + Math.random() * 1200; // 15-35 seconds at 60fps for extremely slow, infrequent ripples
var maxActiveRipples = 3;
// Zen moment system
var zenMomentTimer = 0;
var zenMomentInterval = 4500; // 75 seconds at 60fps of continuous activity
var isZenMomentActive = false;
var zenMomentDuration = 480; // 8 seconds at 60fps
var zenMomentEndTimer = 0;
var originalBackgroundTint = 0x000000;
var zenAffirmations = ['Find your calm', 'Embrace tranquility', 'You are here', 'Breathe deeply', 'Peace flows through you', 'This moment is yours', 'Feel the serenity'];
var zenText = null;
// Tree sway system
var treeSwayBranches = [];
// Light shimmer system
var lightShimmers = [];
// Aura wisp system
var auraWisps = [];
var wispSpawnTimer = 0;
var wispSpawnInterval = 120 + Math.random() * 180; // 2-5 seconds at 60fps
var maxActiveWisps = 6;
// Ephemeral shimmer particles
var ephemeralShimmers = [];
// Distant cloud system
var distantClouds = [];
// Rare bloom burst particles
var rareBloomBursts = [];
var biomeBackground = null;
var havenBackground = null;
// Aura collection tracking
var auraCount = storage.auraCount || {
green: 0,
blue: 0,
yellow: 0
};
var totalAuras = auraCount.green + auraCount.blue + auraCount.yellow;
// UI elements
var auraCountText = new Text2('Auras: ' + totalAuras, {
size: 80,
fill: 0xFFFFFF
});
auraCountText.anchor.set(0.5, 0);
var biomeText = new Text2(biomes[currentBiome], {
size: 60,
fill: 0xFFFFFF
});
biomeText.anchor.set(0.5, 0);
var havenButton = new Text2('Haven', {
size: 70,
fill: 0xFFEB3B
});
havenButton.anchor.set(1, 0);
var exploreButton = new Text2('Explore', {
size: 70,
fill: 0x4CAF50
});
exploreButton.anchor.set(0, 0);
// Add UI to GUI
LK.gui.top.addChild(auraCountText);
LK.gui.top.addChild(biomeText);
LK.gui.topRight.addChild(havenButton);
LK.gui.topLeft.addChild(exploreButton);
// Position UI elements
auraCountText.y = 20;
biomeText.y = 120;
havenButton.x = -20;
havenButton.y = 20;
exploreButton.x = 120;
exploreButton.y = 20;
// Initialize game
function initializeExploration() {
gameState = 'exploration';
// Clear existing objects
if (biomeBackground) biomeBackground.destroy();
if (havenBackground) havenBackground.destroy();
// Create biome background
biomeBackground = game.addChild(LK.getAsset('biomeBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
tint: biomeColors[currentBiome]
}));
// Add player
player.x = 1024;
player.y = 1366;
player.targetX = player.x;
player.targetY = player.y;
game.addChild(player);
// Generate aura particles
generateAuraParticles();
// Generate floating ambient particles
generateFloatingParticles();
// Generate luminescent particles for magical atmosphere
generateLuminescentParticles();
// Generate gentle tree sway for natural ambiance
generateTreeSway();
// Generate subtle light shimmer for sky ambiance
generateLightShimmer();
// Generate distant clouds for background movement
generateDistantClouds();
// Spawn hidden vignettes for discovery
spawnHiddenVignettes();
// Reset rare bloom timer
rareBloomTimer = Math.floor(Math.random() * rareBloomSpawnInterval);
// Clear ephemeral creature
if (ephemeralCreature) {
ephemeralCreature.destroy();
ephemeralCreature = null;
}
// Reset ephemeral timer
ephemeralTimer = Math.floor(Math.random() * ephemeralSpawnInterval);
// Reset weather timer
weatherTimer = Math.floor(Math.random() * weatherCheckInterval);
// Reset zen moment timer
zenMomentTimer = Math.floor(Math.random() * zenMomentInterval);
// Reset water ripple timer
rippleTimer = Math.floor(Math.random() * rippleSpawnInterval);
// Reset aura wisp timer
wispSpawnTimer = Math.floor(Math.random() * wispSpawnInterval);
// Update UI
biomeText.setText(biomes[currentBiome]);
biomeText.visible = true;
exploreButton.visible = false;
havenButton.visible = true;
// Play biome-specific music and ambient sounds
playBiomeMusic(currentBiome);
playAmbientSounds(currentBiome);
}
function initializeHaven() {
gameState = 'haven';
// Clear existing objects
if (biomeBackground) biomeBackground.destroy();
clearAuraParticles();
clearFloatingParticles();
clearLuminescentParticles();
clearHiddenVignettes();
// Clear rare bloom
if (rareBloom) {
rareBloom.destroy();
rareBloom = null;
}
// Clear ephemeral creature
if (ephemeralCreature) {
ephemeralCreature.destroy();
ephemeralCreature = null;
}
// Clear weather effects
if (isWeatherActive) {
endWeatherTransformation();
}
// Clear zen moment
if (isZenMomentActive) {
endZenMoment();
}
// Clear water ripples
clearWaterRipples();
// Clear tree sway
clearTreeSway();
// Clear light shimmer
clearLightShimmer();
// Clear aura wisps
clearAuraWisps();
// Clear ephemeral shimmers
clearEphemeralShimmers();
// Clear distant clouds
clearDistantClouds();
// Clear rare bloom bursts
clearRareBloomBursts();
// Create haven background
havenBackground = game.addChild(LK.getAsset('havenBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
// Remove player from haven
if (player.parent) player.parent.removeChild(player);
// Load existing decorations
loadHavenDecorations();
// Update UI
biomeText.visible = false;
exploreButton.visible = true;
havenButton.visible = false;
// Stop exploration ambient sounds
for (var i = 0; i < currentAmbientSounds.length; i++) {
var sound = LK.getSound(currentAmbientSounds[i]);
sound.loop = false;
sound.stop();
}
currentAmbientSounds = [];
// Play haven ambient music
playHavenMusic();
}
function generateAuraParticles() {
clearAuraParticles();
var particleCount = 15 + Math.floor(Math.random() * 10);
var auraTypes = ['green', 'blue', 'yellow'];
for (var i = 0; i < particleCount; i++) {
var auraType = auraTypes[Math.floor(Math.random() * auraTypes.length)];
var aura = new AuraParticle(auraType);
aura.x = 100 + Math.random() * 1848;
aura.y = 200 + Math.random() * 2332;
aura.initialY = aura.y;
auraParticles.push(aura);
game.addChild(aura);
}
}
function clearAuraParticles() {
for (var i = auraParticles.length - 1; i >= 0; i--) {
auraParticles[i].destroy();
auraParticles.splice(i, 1);
}
}
function generateFloatingParticles() {
clearFloatingParticles();
var particleCount = 20 + Math.floor(Math.random() * 15);
for (var i = 0; i < particleCount; i++) {
var particle = new FloatingParticle();
particle.x = Math.random() * 2048;
particle.y = Math.random() * 2732;
particle.initialX = particle.x;
floatingParticles.push(particle);
game.addChild(particle);
}
}
function clearFloatingParticles() {
for (var i = floatingParticles.length - 1; i >= 0; i--) {
floatingParticles[i].destroy();
floatingParticles.splice(i, 1);
}
}
function generateLuminescentParticles() {
clearLuminescentParticles();
// Low spawn rate for sparse, ethereal feel
var particleCount = 8 + Math.floor(Math.random() * 6);
for (var i = 0; i < particleCount; i++) {
var particle = new LuminescentParticle();
// Focus particles over magical areas with sparse distribution
var spawnArea = Math.random();
if (spawnArea < 0.3) {
// Over water area (magical reflections)
particle.x = 300 + Math.random() * 1448;
particle.y = 1600 + Math.random() * 1000;
} else if (spawnArea < 0.6) {
// Near mystical trees/edges
particle.x = Math.random() < 0.5 ? Math.random() * 250 : 1798 + Math.random() * 250;
particle.y = 200 + Math.random() * 2200;
} else {
// Scattered throughout for ambient magic
particle.x = Math.random() * 2048;
particle.y = 200 + Math.random() * 2200;
}
particle.initialX = particle.x;
luminescentParticles.push(particle);
game.addChild(particle);
}
}
function clearLuminescentParticles() {
for (var i = luminescentParticles.length - 1; i >= 0; i--) {
luminescentParticles[i].destroy();
luminescentParticles.splice(i, 1);
}
}
function clearEphemeralShimmers() {
for (var i = ephemeralShimmers.length - 1; i >= 0; i--) {
ephemeralShimmers[i].destroy();
ephemeralShimmers.splice(i, 1);
}
}
function generateDistantClouds() {
clearDistantClouds();
// Sparse cloud coverage for background ambiance
var cloudCount = 3 + Math.floor(Math.random() * 4);
for (var i = 0; i < cloudCount; i++) {
var cloud = new DistantCloud();
// Position clouds in upper portion of sky
cloud.x = Math.random() * 2400; // Start some off-screen
cloud.y = 100 + Math.random() * 400; // Upper sky area
cloud.initialX = cloud.x;
cloud.initialY = cloud.y;
// Vary cloud sizes for depth
var depthScale = 0.4 + Math.random() * 0.8;
cloud.scaleX = depthScale;
cloud.scaleY = depthScale;
distantClouds.push(cloud);
game.addChild(cloud);
}
}
function clearDistantClouds() {
for (var i = distantClouds.length - 1; i >= 0; i--) {
distantClouds[i].destroy();
distantClouds.splice(i, 1);
}
}
function clearRareBloomBursts() {
for (var i = rareBloomBursts.length - 1; i >= 0; i--) {
rareBloomBursts[i].destroy();
rareBloomBursts.splice(i, 1);
}
}
function spawnRareBloom() {
if (rareBloom) return; // Only one rare bloom at a time
rareBloom = new RareAuraBloom();
rareBloom.x = 200 + Math.random() * 1648;
rareBloom.y = 300 + Math.random() * 2132;
rareBloom.initialY = rareBloom.y;
game.addChild(rareBloom);
// Play appearance sound
LK.getSound('rareBloomAppear').play();
// Create burst of light effect
LK.effects.flashScreen(0xffd700, 800);
// Reset timer for next spawn
rareBloomTimer = 0;
}
function spawnEphemeralCreature() {
if (ephemeralCreature) return; // Only one ephemeral creature at a time
var creatureType = ephemeralCreatureTypes[Math.floor(Math.random() * ephemeralCreatureTypes.length)];
ephemeralCreature = new EphemeralCreature(creatureType);
// Position away from player for mystical discovery
var playerDistance = 300 + Math.random() * 400;
var angle = Math.random() * Math.PI * 2;
ephemeralCreature.x = Math.max(100, Math.min(1948, player.x + Math.cos(angle) * playerDistance));
ephemeralCreature.y = Math.max(200, Math.min(2532, player.y + Math.sin(angle) * playerDistance));
ephemeralCreature.initialY = ephemeralCreature.y;
game.addChild(ephemeralCreature);
// Play gentle appearance sound
LK.getSound('ephemeralAppear').play();
// Trigger appearance animation
ephemeralCreature.appear();
// Reset timer for next spawn
ephemeralTimer = Math.floor(Math.random() * ephemeralSpawnInterval) + ephemeralSpawnInterval;
}
function spawnHiddenVignettes() {
clearHiddenVignettes();
// Spawn 2-4 vignettes per biome in obscure corners
var vignetteCount = 2 + Math.floor(Math.random() * 3);
for (var i = 0; i < vignetteCount; i++) {
var vignetteType = vignetteTypes[Math.floor(Math.random() * vignetteTypes.length)];
var vignette = new HiddenVignette(vignetteType);
// Position in obscure corners and edges of map
var cornerPositions = [{
x: 150,
y: 250
},
// Top-left corner
{
x: 1900,
y: 250
},
// Top-right corner
{
x: 150,
y: 2500
},
// Bottom-left corner
{
x: 1900,
y: 2500
},
// Bottom-right corner
{
x: 1024,
y: 150
},
// Top center hidden
{
x: 100,
y: 1366
},
// Left edge hidden
{
x: 1948,
y: 1366
},
// Right edge hidden
{
x: 1024,
y: 2600
} // Bottom center hidden
];
var position = cornerPositions[Math.floor(Math.random() * cornerPositions.length)];
// Add some randomness to exact position
vignette.x = position.x + (Math.random() - 0.5) * 200;
vignette.y = position.y + (Math.random() - 0.5) * 200;
// Ensure vignette stays within bounds
vignette.x = Math.max(100, Math.min(1948, vignette.x));
vignette.y = Math.max(200, Math.min(2532, vignette.y));
hiddenVignettes.push(vignette);
game.addChild(vignette);
}
}
function clearHiddenVignettes() {
for (var i = hiddenVignettes.length - 1; i >= 0; i--) {
hiddenVignettes[i].destroy();
hiddenVignettes.splice(i, 1);
}
}
function loadHavenDecorations() {
var savedDecorations = storage.havenDecorations || [];
for (var i = 0; i < savedDecorations.length; i++) {
var decorationData = savedDecorations[i];
var decoration = new HavenDecoration(decorationData.type);
decoration.x = decorationData.x;
decoration.y = decorationData.y;
havenDecorations.push(decoration);
game.addChild(decoration);
}
}
function saveHavenDecorations() {
var decorationData = [];
for (var i = 0; i < havenDecorations.length; i++) {
var decoration = havenDecorations[i];
decorationData.push({
type: decoration.decorationType,
x: decoration.x,
y: decoration.y
});
}
storage.havenDecorations = decorationData;
}
function updateUI() {
totalAuras = auraCount.green + auraCount.blue + auraCount.yellow;
auraCountText.setText('Auras: ' + totalAuras);
storage.auraCount = auraCount;
}
function playAmbientSounds(biomeIndex) {
// Stop current ambient sounds
for (var i = 0; i < currentAmbientSounds.length; i++) {
var sound = LK.getSound(currentAmbientSounds[i]);
sound.loop = false;
sound.stop();
}
currentAmbientSounds = [];
// Start new ambient sounds for this biome
var sounds = biomeAmbientSounds[biomeIndex];
for (var j = 0; j < sounds.length; j++) {
var sound = LK.getSound(sounds[j]);
sound.loop = true;
sound.play();
currentAmbientSounds.push(sounds[j]);
}
}
function playBiomeMusic(biomeIndex) {
var targetTrack = biomeMusicTracks[biomeIndex];
if (currentMusicTrack === targetTrack) return;
if (currentMusicTrack) {
// Fade out current music
LK.playMusic(currentMusicTrack, {
fade: {
start: 0.7,
end: 0,
duration: musicFadeTime
}
});
LK.setTimeout(function () {
// Fade in new music
LK.playMusic(targetTrack, {
fade: {
start: 0,
end: 0.7,
duration: musicFadeTime
}
});
currentMusicTrack = targetTrack;
}, musicFadeTime);
} else {
// First time playing music
LK.playMusic(targetTrack, {
fade: {
start: 0,
end: 0.7,
duration: musicFadeTime
}
});
currentMusicTrack = targetTrack;
}
}
function startWeatherTransformation() {
if (isWeatherActive || gameState !== 'exploration') return;
// Small chance for weather (10% chance each check)
if (Math.random() > 0.1) return;
// Choose weather type (equal chance for rain or snow)
var weatherTypes = ['rain', 'snow'];
currentWeather = weatherTypes[Math.floor(Math.random() * weatherTypes.length)];
isWeatherActive = true;
weatherEndTimer = 0;
// Create weather particles
generateWeatherParticles();
// Play weather ambient sound
var soundName = currentWeather === 'rain' ? 'gentleRain' : 'softSnow';
var weatherSound = LK.getSound(soundName);
weatherSound.loop = true;
weatherSound.play();
currentAmbientSounds.push(soundName);
// Gentle screen tint for weather atmosphere
if (currentWeather === 'rain') {
tween(biomeBackground, {
tint: 0x8fa5c7
}, {
duration: 3000,
easing: tween.easeInOut
});
} else if (currentWeather === 'snow') {
tween(biomeBackground, {
tint: 0xc8d6e5
}, {
duration: 3000,
easing: tween.easeInOut
});
}
}
function endWeatherTransformation() {
if (!isWeatherActive) return;
isWeatherActive = false;
clearWeatherParticles();
// Stop weather ambient sound
var soundName = currentWeather === 'rain' ? 'gentleRain' : 'softSnow';
var weatherSound = LK.getSound(soundName);
weatherSound.loop = false;
weatherSound.stop();
var soundIndex = currentAmbientSounds.indexOf(soundName);
if (soundIndex > -1) {
currentAmbientSounds.splice(soundIndex, 1);
}
// Restore original biome tint
if (biomeBackground) {
tween(biomeBackground, {
tint: biomeColors[currentBiome]
}, {
duration: 3000,
easing: tween.easeInOut
});
}
currentWeather = 'clear';
weatherTimer = Math.floor(Math.random() * weatherCheckInterval);
}
function generateWeatherParticles() {
clearWeatherParticles();
var particleCount = currentWeather === 'rain' ? 25 : 20;
for (var i = 0; i < particleCount; i++) {
var particle = new WeatherParticle(currentWeather);
particle.x = Math.random() * 2200; // Slightly wider than screen
particle.y = Math.random() * 2800;
particle.initialX = particle.x;
weatherParticles.push(particle);
game.addChild(particle);
}
}
function clearWeatherParticles() {
for (var i = weatherParticles.length - 1; i >= 0; i--) {
weatherParticles[i].destroy();
weatherParticles.splice(i, 1);
}
}
function generateWaterRipple() {
// Only spawn ripples in Serene Shoreline biome (index 2)
if (currentBiome !== 2 || gameState !== 'exploration') return;
// Limit number of active ripples
var activeRipples = 0;
for (var i = 0; i < waterRipples.length; i++) {
if (waterRipples[i].isActive) activeRipples++;
}
if (activeRipples >= maxActiveRipples) return;
// Find an inactive ripple to reuse, or create new one
var ripple = null;
for (var j = 0; j < waterRipples.length; j++) {
if (!waterRipples[j].isActive) {
ripple = waterRipples[j];
break;
}
}
if (!ripple) {
ripple = new WaterRipple();
waterRipples.push(ripple);
game.addChild(ripple);
}
// Position ripple in lower half of screen (lake area)
ripple.x = 300 + Math.random() * 1448; // Avoid edges
ripple.y = 1500 + Math.random() * 800; // Lower half for lake
// Start the ripple animation
ripple.startRipple();
// Reset timer with some randomness
rippleTimer = 0;
rippleSpawnInterval = 900 + Math.random() * 1200; // Extremely slow ripple spawn timing
}
function clearWaterRipples() {
for (var i = waterRipples.length - 1; i >= 0; i--) {
waterRipples[i].destroy();
waterRipples.splice(i, 1);
}
}
function generateTreeSway() {
clearTreeSway();
var branchCount = 8 + Math.floor(Math.random() * 6); // 8-14 swaying branches
for (var i = 0; i < branchCount; i++) {
var branch = new TreeSwayBranch();
// Position trees in foreground (edges) and mid-ground areas
var positionArea = Math.random();
if (positionArea < 0.3) {
// Left foreground trees
branch.x = 50 + Math.random() * 300;
branch.y = 400 + Math.random() * 1800;
} else if (positionArea < 0.6) {
// Right foreground trees
branch.x = 1700 + Math.random() * 300;
branch.y = 400 + Math.random() * 1800;
} else {
// Mid-ground scattered trees
branch.x = 300 + Math.random() * 1400;
branch.y = 200 + Math.random() * 1200;
}
// Vary branch sizes for depth
var depthScale = 0.6 + Math.random() * 0.8;
branch.scaleX = depthScale;
branch.scaleY = depthScale;
// Start the gentle swaying animation
branch.startSway();
treeSwayBranches.push(branch);
game.addChild(branch);
}
}
function clearTreeSway() {
for (var i = treeSwayBranches.length - 1; i >= 0; i--) {
treeSwayBranches[i].destroy();
treeSwayBranches.splice(i, 1);
}
}
function generateLightShimmer() {
clearLightShimmer();
// Create 1-3 light sources depending on biome (sun/moon/stars)
var shimmerCount = 1 + Math.floor(Math.random() * 3);
for (var i = 0; i < shimmerCount; i++) {
var shimmer = new LightShimmer();
// Position light sources in upper portion of sky
if (shimmerCount === 1) {
// Single main light source (sun/moon) positioned in upper third
shimmer.x = 800 + Math.random() * 400; // Center-ish area
shimmer.y = 200 + Math.random() * 400; // Upper third
} else {
// Multiple light sources (stars, etc.) scattered in upper half
shimmer.x = 200 + Math.random() * 1648;
shimmer.y = 100 + Math.random() * 600;
}
// Vary shimmer sizes for depth and variety
var sizeScale = 0.5 + Math.random() * 1.0;
shimmer.scaleX = sizeScale;
shimmer.scaleY = sizeScale;
// Start the gentle shimmer breathing animation
shimmer.startShimmer();
lightShimmers.push(shimmer);
game.addChild(shimmer);
}
}
function clearLightShimmer() {
for (var i = lightShimmers.length - 1; i >= 0; i--) {
lightShimmers[i].destroy();
lightShimmers.splice(i, 1);
}
}
function generateAuraWisp() {
if (gameState !== 'exploration') return;
// Limit number of active wisps
var activeWisps = 0;
for (var i = 0; i < auraWisps.length; i++) {
if (!auraWisps[i].collected) activeWisps++;
}
if (activeWisps >= maxActiveWisps) return;
// Choose wisp type based on current biome preference
var wispTypes = ['green', 'blue', 'yellow'];
var wispType = wispTypes[Math.floor(Math.random() * wispTypes.length)];
var wisp = new AuraWisp(wispType);
// Position wisp to emerge from environmental elements
var emergenceLocations = [
// From under rocks (bottom edges)
{
x: 100 + Math.random() * 300,
y: 2400 + Math.random() * 200
}, {
x: 1600 + Math.random() * 300,
y: 2400 + Math.random() * 200
},
// Near glowing mushrooms (mid areas)
{
x: 400 + Math.random() * 1200,
y: 1200 + Math.random() * 800
},
// From within tree trunks (forest edges)
{
x: 50 + Math.random() * 200,
y: 600 + Math.random() * 1200
}, {
x: 1800 + Math.random() * 200,
y: 600 + Math.random() * 1200
},
// From crystal formations (upper areas for crystal caverns)
{
x: 300 + Math.random() * 1400,
y: 300 + Math.random() * 600
}];
var location = emergenceLocations[Math.floor(Math.random() * emergenceLocations.length)];
// Add some randomness to exact position
wisp.x = location.x + (Math.random() - 0.5) * 100;
wisp.y = location.y + (Math.random() - 0.5) * 100;
wisp.initialY = wisp.y;
// Ensure wisp stays within bounds
wisp.x = Math.max(50, Math.min(1998, wisp.x));
wisp.y = Math.max(200, Math.min(2532, wisp.y));
auraWisps.push(wisp);
game.addChild(wisp);
// Reset timer with some randomness
wispSpawnTimer = 0;
wispSpawnInterval = 120 + Math.random() * 180;
}
function clearAuraWisps() {
for (var i = auraWisps.length - 1; i >= 0; i--) {
auraWisps[i].destroy();
auraWisps.splice(i, 1);
}
}
function triggerZenMoment() {
if (isZenMomentActive || gameState !== 'exploration') return;
isZenMomentActive = true;
zenMomentEndTimer = 0;
// Store original background tint
if (biomeBackground) {
originalBackgroundTint = biomeBackground.tint;
// Subtle desaturation effect
tween(biomeBackground, {
tint: 0xb0b0b0
}, {
duration: 2000,
easing: tween.easeInOut
});
}
// Create zen affirmation text
var affirmation = zenAffirmations[Math.floor(Math.random() * zenAffirmations.length)];
zenText = new Text2(affirmation, {
size: 120,
fill: 0xffffff
});
zenText.anchor.set(0.5, 0.5);
zenText.alpha = 0;
// Position zen text in center
LK.gui.center.addChild(zenText);
// Gentle fade in for zen text - ensure zenText exists before tweening
if (zenText) {
tween(zenText, {
alpha: 0.9,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Gentle pulsing while displayed - check zenText still exists
if (zenText) {
tween(zenText, {
alpha: 0.7,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (zenText) {
tween(zenText, {
alpha: 0.9,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
});
}
}
});
}
// Play gentle zen bell sound
LK.getSound('zenMomentBell').play();
// Play zen moment music (very minimalistic)
var currentVolume = 0.7;
if (currentMusicTrack) {
// Fade current music to lower volume
LK.playMusic(currentMusicTrack, {
fade: {
start: currentVolume,
end: 0.2,
duration: 1500
}
});
}
// Start zen music softly
LK.playMusic('zenMomentMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1500
}
});
}
function endZenMoment() {
if (!isZenMomentActive) return;
isZenMomentActive = false;
// Fade out zen text
if (zenText && zenText.parent) {
tween(zenText, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 2000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (zenText && zenText.parent) {
zenText.parent.removeChild(zenText);
}
zenText = null;
}
});
} else if (zenText) {
// If zenText exists but has no parent, just set to null
zenText = null;
}
// Restore original background tint
if (biomeBackground) {
tween(biomeBackground, {
tint: originalBackgroundTint
}, {
duration: 2000,
easing: tween.easeInOut
});
}
// Restore original music
LK.playMusic('zenMomentMusic', {
fade: {
start: 0.3,
end: 0,
duration: 1500
}
});
LK.setTimeout(function () {
if (currentMusicTrack && currentMusicTrack !== 'zenMomentMusic') {
LK.playMusic(currentMusicTrack, {
fade: {
start: 0.2,
end: 0.7,
duration: 1500
}
});
}
}, 1500);
// Reset timer for next zen moment
zenMomentTimer = Math.floor(Math.random() * zenMomentInterval) + zenMomentInterval;
}
function playHavenMusic() {
if (currentMusicTrack === 'havenAmbient') return;
if (currentMusicTrack) {
// Fade out current music
var prevTrack = currentMusicTrack;
LK.playMusic(prevTrack, {
fade: {
start: 0.7,
end: 0,
duration: musicFadeTime
}
});
LK.setTimeout(function () {
// Fade in haven music
LK.playMusic('havenAmbient', {
fade: {
start: 0,
end: 0.6,
duration: musicFadeTime
}
});
currentMusicTrack = 'havenAmbient';
}, musicFadeTime);
} else {
// First time playing music
LK.playMusic('havenAmbient', {
fade: {
start: 0,
end: 0.6,
duration: musicFadeTime
}
});
currentMusicTrack = 'havenAmbient';
}
}
// Touch/Mouse handlers
var draggedDecoration = null;
var isPlacingDecoration = false;
game.move = function (x, y, obj) {
if (gameState === 'exploration') {
player.targetX = x;
player.targetY = y;
} else if (gameState === 'haven' && draggedDecoration) {
draggedDecoration.x = x;
draggedDecoration.y = y;
}
};
game.down = function (x, y, obj) {
if (gameState === 'haven') {
// Check if touching a decoration
for (var i = 0; i < havenDecorations.length; i++) {
var decoration = havenDecorations[i];
var dx = x - decoration.x;
var dy = y - decoration.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60) {
draggedDecoration = decoration;
// Play rustle sound for plant interactions
if (decoration.decorationType === 'plant') {
LK.getSound('plantRustle').play();
}
break;
}
}
// Try to place new decoration if not dragging
if (!draggedDecoration && totalAuras >= 10) {
var decorationTypes = ['plant', 'light', 'water'];
var randomType = decorationTypes[Math.floor(Math.random() * decorationTypes.length)];
var newDecoration = new HavenDecoration(randomType);
newDecoration.x = x;
newDecoration.y = y;
havenDecorations.push(newDecoration);
game.addChild(newDecoration);
auraCount.green = Math.max(0, auraCount.green - 4);
auraCount.blue = Math.max(0, auraCount.blue - 3);
auraCount.yellow = Math.max(0, auraCount.yellow - 3);
updateUI();
saveHavenDecorations();
LK.getSound('placeItem').play();
}
}
};
game.up = function (x, y, obj) {
if (draggedDecoration) {
saveHavenDecorations();
draggedDecoration = null;
}
};
// Button handlers
havenButton.down = function (x, y, obj) {
if (gameState === 'exploration') {
initializeHaven();
}
};
exploreButton.down = function (x, y, obj) {
if (gameState === 'haven') {
initializeExploration();
}
};
// Main game update loop
game.update = function () {
if (gameState === 'exploration') {
// Check rare bloom spawning
rareBloomTimer++;
if (rareBloomTimer >= rareBloomSpawnInterval && !rareBloom) {
spawnRareBloom();
}
// Check ephemeral creature spawning
ephemeralTimer++;
if (ephemeralTimer >= ephemeralSpawnInterval && !ephemeralCreature) {
spawnEphemeralCreature();
}
// Check weather transformations
weatherTimer++;
if (weatherTimer >= weatherCheckInterval && !isWeatherActive) {
startWeatherTransformation();
}
// Check zen moment triggering
zenMomentTimer++;
if (zenMomentTimer >= zenMomentInterval && !isZenMomentActive) {
triggerZenMoment();
}
// Check water ripple spawning (only in Serene Shoreline)
if (currentBiome === 2) {
rippleTimer++;
if (rippleTimer >= rippleSpawnInterval) {
generateWaterRipple();
}
}
// Check aura wisp spawning
wispSpawnTimer++;
if (wispSpawnTimer >= wispSpawnInterval) {
generateAuraWisp();
}
// Clean up collected wisps
for (var w = auraWisps.length - 1; w >= 0; w--) {
if (auraWisps[w].collected) {
auraWisps.splice(w, 1);
}
}
// Clean up expired ephemeral shimmers
for (var s = ephemeralShimmers.length - 1; s >= 0; s--) {
if (!ephemeralShimmers[s].parent) {
ephemeralShimmers.splice(s, 1);
}
}
// Clean up expired rare bloom burst particles
for (var b = rareBloomBursts.length - 1; b >= 0; b--) {
if (!rareBloomBursts[b].parent) {
rareBloomBursts.splice(b, 1);
}
}
// Check zen moment duration
if (isZenMomentActive) {
zenMomentEndTimer++;
if (zenMomentEndTimer >= zenMomentDuration) {
endZenMoment();
}
}
// Check weather duration
if (isWeatherActive) {
weatherEndTimer++;
if (weatherEndTimer >= weatherDuration) {
endWeatherTransformation();
}
}
// Check hidden vignette discovery
for (var v = 0; v < hiddenVignettes.length; v++) {
var vignette = hiddenVignettes[v];
if (!vignette.discovered) {
var dx = player.x - vignette.x;
var dy = player.y - vignette.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < vignetteDiscoveryRadius) {
vignette.discover();
}
}
}
// Check rare bloom collection
if (rareBloom && !rareBloom.collected && player.intersects(rareBloom)) {
rareBloom.collected = true;
// Award significant auras (5 of each type)
auraCount.green += 5;
auraCount.blue += 5;
auraCount.yellow += 5;
updateUI();
// Play special collection sound
LK.getSound('rareBloomCollect').play();
// Create spectacular visual effect
LK.effects.flashScreen(0xffd700, 1500);
// Create burst of glowing particles (medium count for spectacular effect)
for (var b = 0; b < 12; b++) {
var burstParticle = new RareBloomBurst();
burstParticle.x = rareBloom.x;
burstParticle.y = rareBloom.y;
rareBloomBursts.push(burstParticle);
game.addChild(burstParticle);
}
// Burst animation with swirl of particles
tween(rareBloom, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(rareBloom, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
rareBloom.destroy();
rareBloom = null;
// Reset timer for next rare bloom
rareBloomTimer = Math.floor(Math.random() * rareBloomSpawnInterval);
}
});
}
});
}
// Check aura collection
for (var i = auraParticles.length - 1; i >= 0; i--) {
var aura = auraParticles[i];
if (!aura.collected && player.intersects(aura)) {
aura.collected = true;
auraCount[aura.type]++;
updateUI();
// Collect animation with gentle glow effect
tween(aura, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(aura, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
aura.destroy();
}
});
}
});
auraParticles.splice(i, 1);
LK.getSound('collectAura').play();
}
}
// Check if all auras collected
if (auraParticles.length === 0) {
LK.setTimeout(function () {
currentBiome = (currentBiome + 1) % biomes.length;
generateAuraParticles();
// Update biome display and music
biomeText.setText(biomes[currentBiome]);
if (biomeBackground) {
biomeBackground.tint = biomeColors[currentBiome];
}
playBiomeMusic(currentBiome);
playAmbientSounds(currentBiome);
}, 1000);
}
}
};
// Start the game
initializeExploration();
auraBlue top view. In-Game asset. 2d. High contrast. No shadows
auraGreen top view. In-Game asset. 2d. High contrast. No shadows
auraWisp top view. In-Game asset. 2d. High contrast. No shadows
auraYellow top view. In-Game asset. 2d. High contrast. No shadows
biomeBackground top view. In-Game asset. 2d. High contrast. No shadows
distantCloud top view. In-Game asset. 2d. High contrast. No shadows
waterRipple top view. In-Game asset. 2d. High contrast. No shadows
waterDecoration top view. In-Game asset. 2d. High contrast. No shadows
tree Sway Branch top view. In-Game asset. 2d. High contrast. No shadows
rare Bloom Pulse top view. In-Game asset. 2d. High contrast. No shadows
snow Flake top view. In-Game asset. 2d. High contrast. No shadows
rare Bloom Burst Particle top view. In-Game asset. 2d. High contrast. No shadows
rare Bloom Burst top view. In-Game asset. 2d. High contrast. No shadows
sleeping Bear top view. In-Game asset. 2d. High contrast. No shadows
rare Aura Bloom top view. In-Game asset. 2d. High contrast. No shadows
raindrops. In-Game asset. 2d. High contrast. No shadows
light Shimmer top view. In-Game asset. 2d. High contrast. No shadows
light Decoration top view. In-Game asset. 2d. High contrast. No shadows
hiddenWaterfall. In-Game asset. 2d. High contrast. No shadows
havenBackground. In-Game asset. 2d. High contrast. No shadows
floatingParticle. In-Game asset. 2d. High contrast. No shadows
ephemeralHummingbird. In-Game asset. 2d. High contrast. No shadows
ephemeralFox. In-Game asset. 2d. High contrast. No shadows
ephemeralSpirit. In-Game asset. 2d. High contrast. No shadows
ephemeralShimmer. In-Game asset. 2d. High contrast. No shadows
plantDecoration. In-Game asset. 2d. High contrast. No shadows
mushroomRing. In-Game asset. 2d. High contrast. No shadows
luminescentParticleAlt. In-Game asset. 2d. High contrast. No shadows
luminescentParticle. In-Game asset. 2d. High contrast. No shadows
player aura image top view in space game.