User prompt
Make the score counter retract with the streak counter
User prompt
Make a score for the game over screen
User prompt
Make it so that the game over has a score and the score is the exact same as the score counter
User prompt
Make a button under the score button called end game where pressing it causes you to game over
User prompt
Change the extinction, randomized, And personality buttons text to a black color
User prompt
Make a score counter under the streak counter where every time the streak goes up the score is added by 50
User prompt
Make the streak counter retract with the buttons when the retract widget is presed
User prompt
Make the streak counter follow the personality button
User prompt
When your streak is reset put some text in the middle of the screen that says “streak lost!” In red text for 5 seconds
User prompt
Everytime you press the skip cycle button your streak resets to 0 and it plays the lost sound
User prompt
Every time you lose a streak it plays the lost sound
User prompt
Everytime the streak counts up the sound streak plays
User prompt
Make a streak counter under the personality button and the streak counts 1 number every cycle the ecosystem is alive! If all organisms die the streak resets
User prompt
Make a streak counter under the personality button and the streak counts 1 number every cycle an organism is alive! However if all organisms die the streak resets or if you skip a cycle
User prompt
Make it so you can place organisims during autumn and winter
User prompt
Make it so 2 bloodthirsty carnivores will not attack each other
User prompt
Make it move a bit more to the right when you press it
User prompt
Make the retract widget move a bit to the right when pressed, and return to original spot when pressed again
User prompt
Make the retract widget move a bit to the right when pressed, and return to original spot when pressed again
User prompt
Make it so the retract widget stops right before heading offscreen
User prompt
Make it so the 3 line button next to all the buttons follows the other buttons when retracting/expanding
User prompt
Make it so the personality button follows all the other buttons
User prompt
Make random traits for herbivores and carnivores more common
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Carnivore class var Carnivore = Container.expand(function () { var self = Container.call(this); // Uncommon: 15% chance to be resistant to parasites self.parasiteResistant = Math.random() < 0.15; var carnAsset = self.attachAsset('carnivore', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.25, scaleY: 1.25 }); self.target = null; // Assign base speed, will adjust for personality below self.speed = 1.5 + Math.random() * 1.2; self.eaten = false; self._chaseTime = 0; // How long we've been chasing a herbivore self._tired = false; // Are we tired? self._tiredTimer = 0; // How long left to rest // Track tired attempts per herbivore (herbivore.id -> count) self._tiredAttempts = {}; // --- Personality system --- // Personalities: "normal", "bloodthirsty", "weak" var carnPersonalities = ["normal", "bloodthirsty", "weak"]; var rand = Math.random(); // Make random traits more common: 30% normal, 35% bloodthirsty, 35% weak if (rand < 0.3) { self.personality = "normal"; } else if (rand < 0.65) { self.personality = "bloodthirsty"; } else { self.personality = "weak"; } // Adjust speed for bloodthirsty carnivores if (self.personality === "bloodthirsty") { self.speed += 0.7 + Math.random() * 0.5; // noticeably faster } // For weak carnivores, no speed change, but will tire faster in update logic // For weak: timer for running away from other carnivores self._weakRunTimer = 0; self._weakRunAngle = 0; // --- State management for wandering/hungry --- self.state = "hungry"; // "wandering" or "hungry" self._eatCooldown = 0; // frames left in wandering after eating self.update = function () { if (self.eaten) return; // --- CARNIVORES CANNOT STARVE: No starvation logic for carnivores --- // --- WINTER BEHAVIOR: Wanderlust, ignore herbivores, and cannot starve --- var isWinter = !!window.winterState; if (isWinter) { // Always wander, never chase or eat, never get tired, never starve self.state = "wandering"; self.target = null; self._tired = false; self._tiredTimer = 0; self._chaseTime = 0; // Wander constantly if (!self._wanderTimer || self._wanderTimer <= 0) { var angle = Math.random() * Math.PI * 2; self._wanderAngle = angle; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; // Keep inside map bounds (strictly) var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } return; } // --- Personality: weak carnivore runs from other carnivores, can be infected by fungi --- if (self.personality === "weak") { // If running away, keep running for a short time if (self._weakRunTimer > 0) { self.x += Math.cos(self._weakRunAngle) * self.speed * 1.5; self.y += Math.sin(self._weakRunAngle) * self.speed * 1.5; self._weakRunTimer--; // Clamp to map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; // While running, don't do anything else return; } // Check for nearby other carnivores (not self) var nearestCarn = null; var minCarnDist = 999999; for (var ci = 0; ci < carnivores.length; ci++) { var c = carnivores[ci]; if (c === self || c.eaten) continue; var dx = c.x - self.x; var dy = c.y - self.y; var dist = dx * dx + dy * dy; if (dist < minCarnDist) { minCarnDist = dist; nearestCarn = c; } } if (nearestCarn && Math.sqrt(minCarnDist) < 220) { // Run away from the other carnivore for 60-120 frames var dx = self.x - nearestCarn.x; var dy = self.y - nearestCarn.y; var angle = Math.atan2(dy, dx); self._weakRunAngle = angle; self._weakRunTimer = 60 + Math.floor(Math.random() * 60); // Move immediately this frame self.x += Math.cos(self._weakRunAngle) * self.speed * 1.5; self.y += Math.sin(self._weakRunAngle) * self.speed * 1.5; // Clamp to map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; return; } // Weak carnivores can be infected by fungi (handled in Parasite/Fungi logic) } // Handle tired state // Bloodthirsty carnivores never get tired if (self.personality === "bloodthirsty") { self._tired = false; self._tiredTimer = 0; } else { if (self._tired) { self._tiredTimer--; if (self._tiredTimer <= 0) { self._tired = false; self._chaseTime = 0; } // While tired, don't chase or wander, just stand still return; } } // Handle eat cooldown (wandering after eating) if (self._eatCooldown > 0) { self._eatCooldown--; if (self._eatCooldown === 0) { self.state = "hungry"; } } // State: wandering (ignore prey) if (self.state === "wandering") { self.target = null; // Don't chase // --- Random wandering when not chasing herbivores --- if (!self._wanderTimer || self._wanderTimer <= 0) { var angle = Math.random() * Math.PI * 2; self._wanderAngle = angle; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; // Keep inside map bounds (strictly) var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } self._chaseTime = 0; // Not chasing, reset chase timer return; } // State: hungry (look for prey) if (self.state === "hungry") { // --- CARNIVORE POPULATION CONTROL: If at max, hunt less frequently --- if (typeof carnivores !== "undefined" && carnivores.length >= 4) { // Only hunt every 120 frames (2 seconds) if (typeof self._huntDelay === "undefined") self._huntDelay = Math.floor(Math.random() * 120); self._huntDelay++; if (self._huntDelay < 120) { // Wander instead of hunting self.state = "wandering"; self._huntDelay = self._huntDelay % 120; return; } self._huntDelay = 0; } // Find nearest prey if not already targeting if (!self.target || self.target.eaten) { var minDist = 999999, closest = null; // --- Bloodthirsty: can hunt herbivores and other carnivores (except self) --- if (self.personality === "bloodthirsty") { // Hunt herbivores for (var i = 0; i < herbivores.length; i++) { var h = herbivores[i]; if (h.eaten) continue; var dx = h.x - self.x, dy = h.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = h; } } // Hunt other carnivores (not self, not eaten) for (var i = 0; i < carnivores.length; i++) { var c = carnivores[i]; if (c === self || c.eaten) continue; var dx = c.x - self.x, dy = c.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = c; } } } else if (self.personality === "weak") { // Weak carnivores do not hunt other carnivores, only herbivores for (var i = 0; i < herbivores.length; i++) { var h = herbivores[i]; if (h.eaten) continue; // Ignore this herbivore if we've gotten tired chasing it 2 or more times this cycle if (h._uniqueId === undefined) { // Assign a unique id to each herbivore if not present h._uniqueId = "herb_" + Math.floor(Math.random() * 1e9) + "_" + Math.floor(Math.random() * 1e9); } if (self._tiredAttempts[h._uniqueId] >= 2) continue; var dx = h.x - self.x, dy = h.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = h; } } } else { // Normal: only hunt herbivores for (var i = 0; i < herbivores.length; i++) { var h = herbivores[i]; if (h.eaten) continue; // Ignore this herbivore if we've gotten tired chasing it 2 or more times this cycle if (h._uniqueId === undefined) { // Assign a unique id to each herbivore if not present h._uniqueId = "herb_" + Math.floor(Math.random() * 1e9) + "_" + Math.floor(Math.random() * 1e9); } if (self._tiredAttempts[h._uniqueId] >= 2) continue; var dx = h.x - self.x, dy = h.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = h; } } } // (Carnivores ignore pollinators) self.target = closest; } // --- Random wandering when not chasing herbivores --- if ((!self.target || self.target.eaten) && (!self._wanderTimer || self._wanderTimer <= 0)) { var angle = Math.random() * Math.PI * 2; self._wanderAngle = angle; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (!self.target || self.target.eaten) { if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } self._chaseTime = 0; // Not chasing, reset chase timer } // Move toward target herbivore if (self.target && !self.target.eaten) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } // Track how long we've been chasing self._chaseTime++; // If we've been chasing for more than X seconds, get tired var chaseLimit = 360; // default 6 seconds var tiredDuration = 600; // default 10 seconds if (self.personality === "weak") { chaseLimit = 180; // 3 seconds for weak carnivores tiredDuration = 900; // weak carnivores rest longer (15 seconds) } if (self._chaseTime > chaseLimit) { self._tired = true; self._tiredTimer = tiredDuration; self._chaseTime = 0; // Track tired attempts for this herbivore if (self.target && self.target._uniqueId !== undefined) { if (!self._tiredAttempts[self.target._uniqueId]) self._tiredAttempts[self.target._uniqueId] = 0; self._tiredAttempts[self.target._uniqueId]++; // If tired 2 or more times, ignore this herbivore for the rest of the cycle if (self._tiredAttempts[self.target._uniqueId] >= 2) { self.target = null; self.state = "wandering"; } } return; } // Eat target if close enough // Prevent eating in autumn if (dist < 60 && !self.target.eaten) { if (window.autumnState) { // In autumn, do not eat, just wander self.target = null; self.state = "wandering"; self._eatCooldown = 3600; self._chaseTime = 0; return; } // --- Bloodthirsty: can eat other carnivores or herbivores --- // --- Normal/Weak: only eat herbivores --- var ateCarnivore = false; if (self.personality === "bloodthirsty" && carnivores.indexOf(self.target) !== -1 && self.target !== self) { // Eat other carnivore if (typeof self.target.die === "function") self.target.die(); ateCarnivore = true; } else if (herbivores.indexOf(self.target) !== -1) { // Eat herbivore if (typeof self.target.die === "function") self.target.die(); } else { // Not a valid target, do nothing self.target = null; return; } // Move away from the eaten target if (self.target) { var awayDX = self.x - self.target.x; var awayDY = self.y - self.target.y; var awayDist = Math.sqrt(awayDX * awayDX + awayDY * awayDY); if (awayDist > 0) { // Move 100px away in the opposite direction self.x += awayDX / awayDist * 100; self.y += awayDY / awayDist * 100; // Clamp to map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } } // Track herbivores/carnivores eaten for egg laying if (ateCarnivore) { self._herbivoresEaten = (self._herbivoresEaten || 0) + 1; } else { self._herbivoresEaten = (self._herbivoresEaten || 0) + 1; } // Lay an egg if 3 prey have been eaten, then reset counter // --- CARNIVORE POPULATION CONTROL: Only lay egg if < 4 carnivores --- if (self._herbivoresEaten >= 3) { if (typeof carnivores !== "undefined" && carnivores.length < 4) { var egg = new Egg(); egg.x = self.x; egg.y = self.y; egg.scaleX = 0.5; egg.scaleY = 0.5; egg.alpha = 1; egg._isCarnivoreEgg = true; egg._carnivoreHatch = true; if (self.parent) self.parent.addChild(egg); if (typeof eggs !== "undefined") eggs.push(egg); } self._herbivoresEaten = 0; } self.target = null; self._chaseTime = 0; // Reset chase timer after eating // Animate carnivore "eating" tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); // After eating, enter wandering state for 1 minute (3600 frames) self.state = "wandering"; self._eatCooldown = 3600; } } } // end hungry state }; // Carnivores are not eaten in this MVP return self; }); // Egg class var Egg = Container.expand(function () { var self = Container.call(this); // Always use carnivore_egg asset if this is a carnivore egg, pollinator_egg if pollinator, else normal egg var eggAsset; if (self._isCarnivoreEgg === true || self._carnivoreHatch === true) { eggAsset = self.attachAsset('carnivore_egg', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1 }); } else if (self._isPollinatorEgg === true || self._pollinatorHatch === true) { eggAsset = self.attachAsset('pollinator_egg', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1 }); } else { eggAsset = self.attachAsset('egg', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1 }); } self.hatched = false; self._hatchTimer = 420 + Math.floor(Math.random() * 180); // 7-10 seconds self.update = function () { if (self.hatched) return; // Pause hatching if it's summer or winter var isSummer = !!window.summerState; var isWinter = !!window.winterState; if (isSummer || isWinter) { // Do not decrement hatch timer, eggs are dormant return; } // --- POLLINATOR POPULATION CONTROL: Prevent pollinator eggs from hatching if there are already 10 pollinators --- if ((self._isPollinatorEgg || self._pollinatorHatch) && typeof pollinators !== "undefined" && pollinators.length >= 10) { // Do not decrement hatch timer, do not hatch, just wait return; } // --- CARNIVORE POPULATION CONTROL: Prevent carnivore eggs from hatching if there are already 4 carnivores --- if ((self._isCarnivoreEgg || self._carnivoreHatch) && typeof carnivores !== "undefined" && carnivores.length >= 4) { // Do not decrement hatch timer, do not hatch, just wait return; } self._hatchTimer--; // --- PARASITE POPULATION CONTROL: Prevent parasite eggs from hatching if there are already 4 parasites --- if ((self._isParasiteEgg || self._parasiteHatch) && typeof parasites !== "undefined" && parasites.length >= 4) { // Do not decrement hatch timer, do not hatch, just wait return; } // --- HERBIVORE POPULATION CONTROL: Prevent herbivore eggs from hatching if there are already 6 herbivores --- if (!self._isCarnivoreEgg && !self._carnivoreHatch && !self._isPollinatorEgg && !self._pollinatorHatch && !self._isParasiteEgg && !self._parasiteHatch && typeof herbivores !== "undefined" && herbivores.length >= 6) { // Do not decrement hatch timer, do not hatch, just wait return; } if (self._hatchTimer <= 0) { // In autumn, eggs have a reduced chance to hatch (e.g. 60% hatch, 40% fail) if (window.autumnState && Math.random() < 0.4) { // Egg fails to hatch, animate and destroy self.eaten = true; tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); return; } self.hatched = true; // Animate egg hatching tween(self, { scaleX: 1.2, scaleY: 1.2, alpha: 0.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Spawn a new carnivore if this is a carnivore egg, pollinator if pollinator egg, parasite if parasite egg, otherwise herbivore var popSnd = LK.getSound('Hatch'); if (self._isCarnivoreEgg && self._carnivoreHatch) { var carn = new Carnivore(); carn.x = self.x; carn.y = self.y; carn.scaleX = 1; carn.scaleY = 1; carn.alpha = 1; if (self.parent) self.parent.addChild(carn); if (typeof carnivores !== "undefined") carnivores.push(carn); if (popSnd && typeof popSnd.play === "function") popSnd.play(); } else if (self._isPollinatorEgg && self._pollinatorHatch) { var poll = new Pollinator(); poll.x = self.x; poll.y = self.y; poll.scaleX = 1; poll.scaleY = 1; poll.alpha = 1; // Reset pollinator starve timer on hatch poll._starveTimer = 3000 + Math.floor(Math.random() * 1200); // Set pollinator to hungry state after hatching poll.state = "hungry"; poll.target = null; if (self.parent) self.parent.addChild(poll); if (typeof pollinators !== "undefined") pollinators.push(poll); if (popSnd && typeof popSnd.play === "function") popSnd.play(); } else if (self._isParasiteEgg && self._parasiteHatch) { // If this egg was laid by a parasite attached to a resistant host, always inherit resistance var inheritResistance = false; if (typeof self._parentHost !== "undefined" && self._parentHost && self._parentHost.parasiteResistant === true) { inheritResistance = true; } var parasite = new Parasite(); parasite.x = self.x; parasite.y = self.y; parasite.scaleX = 1; parasite.scaleY = 1; parasite.alpha = 1; if (inheritResistance) { parasite.parasiteResistant = true; } if (self.parent) self.parent.addChild(parasite); if (typeof parasites !== "undefined") parasites.push(parasite); if (popSnd && typeof popSnd.play === "function") popSnd.play(); } else { // Spawn a new Nummer at this position var herb = new Nummer(); herb._uniqueId = "herb_" + Math.floor(Math.random() * 1e9) + "_" + Math.floor(Math.random() * 1e9); herb.x = self.x; herb.y = self.y; herb.scaleX = 1; herb.scaleY = 1; herb.alpha = 1; // If this egg was laid by a parasite attached to a resistant host, always inherit resistance if (typeof self._parentHost !== "undefined" && self._parentHost && self._parentHost.parasiteResistant === true) { herb.parasiteResistant = true; } if (self.parent) self.parent.addChild(herb); herbivores.push(herb); if (popSnd && typeof popSnd.play === "function") popSnd.play(); } self.destroy(); } }); } }; return self; }); // Fungi class var Fungi = Container.expand(function () { var self = Container.call(this); // Track the cycle the fungi was created in self._bornCycle = typeof cycleCount !== "undefined" ? cycleCount : 0; // Use fungi asset var fungiAsset = self.attachAsset('fungi', { anchorX: 0.5, anchorY: 0.5 }); // Fungi AI: shoot spores at nearby herbivores self._sporeCooldown = 0; self.update = function () { // Fungi lifespan removed: fungi no longer die after 1 cycle // Only shoot if cooldown is 0 if (self._sporeCooldown > 0) { self._sporeCooldown--; return; } // --- FUNGI POPULATION CONTROL: Prevent infecting with spores if 20 or more fungi --- if (typeof fungis !== "undefined" && fungis.length >= 20) { return; } // Find nearest herbivore within 400px var minDist = 999999, closest = null; for (var i = 0; i < herbivores.length; i++) { var h = herbivores[i]; if (h.eaten || h.spored) continue; var dx = h.x - self.x, dy = h.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist && Math.sqrt(dist) < 400) { minDist = dist; closest = h; } } if (closest) { // Shoot a spore at the herbivore var spore = new FungiSpore(); spore.x = self.x; spore.y = self.y; spore.target = closest; game.addChild(spore); if (!fungiSpores) fungiSpores = []; fungiSpores.push(spore); self._sporeCooldown = 120 + Math.floor(Math.random() * 60); // 2-3 seconds cooldown } }; // Track if this fungi is currently 'blocked' from respawning after being eaten self._blockedRespawn = false; // Add die method so fungi disappear when eaten self.die = function () { if (self.eaten) return; self.eaten = true; // Animate shrink and fade out, then destroy tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Nummer class (formerly Herbivore) var Nummer = Container.expand(function () { var self = Container.call(this); // Uncommon: 15% chance to be resistant to parasites self.parasiteResistant = Math.random() < 0.15; // Track if this Nummer was hit by a spore self.spored = false; var herbAsset = self.attachAsset('herbivore', { anchorX: 0.5, anchorY: 0.5 }); self.target = null; self.speed = 1.2 + Math.random() * 1.0; self.eaten = false; // Track number of plants eaten for egg laying self._plantsEaten = 0; // --- State management for hungry/sleepy --- self.state = "hungry"; // "hungry" or "sleepy" self._sleepTimer = 0; // frames left in sleepy state // --- Starvation timer: die if not eating for 2 cycles (80 seconds = 4800 frames) --- self._starveTimer = 4800; // 80 seconds at 60fps // --- Personality system --- // Personalities: "normal", "curious", "grumpy" var personalities = ["normal", "curious", "grumpy"]; // Assign a random personality at creation var rand = Math.random(); // Make random traits more common: 30% normal, 35% curious, 35% grumpy if (rand < 0.3) { self.personality = "normal"; } else if (rand < 0.65) { self.personality = "curious"; } else { self.personality = "grumpy"; } // For curious: timer for following pollinators self._curiousFollowTimer = 0; self._curiousPollinatorTarget = null; // For grumpy: scare cooldown self._grumpyScareCooldown = 0; self.update = function () { if (self.eaten) return; // --- HERBIVORE HUNGER QUEUE LOGIC --- // Only 2 herbivores can be in "hungry" state at a time. Others must wait in "waiting" state. if (!self._hungerQueueState) self._hungerQueueState = "unset"; // "unset", "waiting", "hungry" if (self.state === "hungry" && self._hungerQueueState !== "hungry") { // Try to enter the hunger queue if (herbivoreHungerQueue.indexOf(self) === -1) { herbivoreHungerQueue.push(self); } // If not in the first 2, set to waiting if (herbivoreHungerQueue[0] !== self && herbivoreHungerQueue[1] !== self) { self._hungerQueueState = "waiting"; self.state = "waiting"; // Don't do hungry logic this frame } else { self._hungerQueueState = "hungry"; // Continue as hungry } } else if (self.state === "waiting" && self._hungerQueueState !== "waiting") { // Enter waiting queue if not already if (herbivoreHungerQueue.indexOf(self) === -1) { herbivoreHungerQueue.push(self); } // If now in first 2, become hungry if (herbivoreHungerQueue[0] === self || herbivoreHungerQueue[1] === self) { self.state = "hungry"; self._hungerQueueState = "hungry"; } else { self._hungerQueueState = "waiting"; // Don't do hungry logic this frame } } else if (self.state === "hungry" && (herbivoreHungerQueue[0] === self || herbivoreHungerQueue[1] === self)) { self._hungerQueueState = "hungry"; } else if (self.state === "hungry" && herbivoreHungerQueue.indexOf(self) > 1) { // If somehow in hungry state but not allowed, set to waiting self.state = "waiting"; self._hungerQueueState = "waiting"; } // If in waiting state, just wander if (self.state === "waiting") { // --- Random wandering --- if (!self._wanderTimer || self._wanderTimer <= 0) { var angle = Math.random() * Math.PI * 2; self._wanderAngle = angle; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; // Keep inside map bounds (strictly) var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } // Don't do hungry logic return; } // --- HERBIVORE RANDOM SOUND LOGIC --- // Only make sound if not eaten and not in winter (optional: you can allow in winter if desired) if (typeof self._nextHerbSoundTimer === "undefined") { // Set initial timer to random 6-10 seconds (360-600 frames) self._nextHerbSoundTimer = 360 + Math.floor(Math.random() * 241); } if (self._nextHerbSoundTimer > 0) { self._nextHerbSoundTimer--; } else { // Play the 'Herbivore' sound var snd = LK.getSound('Herbivore'); if (snd && typeof snd.play === "function") snd.play(); // Reset timer to next random interval (6-10 seconds) self._nextHerbSoundTimer = 360 + Math.floor(Math.random() * 241); } // --- WINTER BEHAVIOR: Move slower, avoid eating, and halt starvation meter --- var isWinter = !!window.winterState; var originalSpeed = self.speed; if (isWinter) { // Move slower in winter self.speed = originalSpeed * 0.4; } else { self.speed = originalSpeed; } // Starvation logic: decrement timer if alive and hungry/wandering, but not in winter if ((self.state === "hungry" || self.state === "wandering") && !isWinter) { self._starveTimer--; if (self._starveTimer <= 0 && !self.eaten) { // Starve and die self.die(); return; } } // Handle infected state if (self.infected) { self.infectedTimer = (self.infectedTimer || 0) + 1; // Future: add infected behavior here (e.g. slow, animation, etc) } // If infection was pending from summer, and now it's not summer, apply infection if (self._pendingFungiInfection && !window.summerState) { self._pendingFungiInfection = false; self.infected = true; self.infectedTimer = 0; // Tint permanently purple tween(self, { tint: 0xbb88ff }, { duration: 80 }); } // Handle sleepy state // If just left hungry state (became wandering or sleepy), remove from hunger queue and allow next in queue to become hungry if ((self.state === "wandering" || self.state === "sleepy") && self._hungerQueueState === "hungry") { var idx = herbivoreHungerQueue.indexOf(self); if (idx !== -1) { herbivoreHungerQueue.splice(idx, 1); } self._hungerQueueState = "unset"; // Next in queue (if any) becomes hungry for (var i = 0; i < herbivoreHungerQueue.length; i++) { var h = herbivoreHungerQueue[i]; if (h && h.state === "waiting") { h.state = "hungry"; h._hungerQueueState = "hungry"; break; } } } if (self.state === "sleepy") { // Check for nearby carnivore and possibly wake up var nearestCarn = null; var minCarnDist = 999999; for (var ci = 0; ci < carnivores.length; ci++) { var carn = carnivores[ci]; if (carn.eaten) continue; var cdx = carn.x - self.x; var cdy = carn.y - self.y; var cdist = cdx * cdx + cdy * cdy; if (cdist < minCarnDist) { minCarnDist = cdist; nearestCarn = carn; } } // If carnivore is within 300px, 50% chance to wake up and run away if (nearestCarn && Math.sqrt(minCarnDist) < 300) { // Only try to wake up once per frame if still sleepy if (!self._triedWakeThisFrame) { self._triedWakeThisFrame = true; if (Math.random() < 0.5) { // Wake up and run away from carnivore self.state = "hungry"; // Move away from carnivore immediately var dx = self.x - nearestCarn.x; var dy = self.y - nearestCarn.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { // Move a burst away (3x speed for one frame) self.x += dx / dist * (self.speed * 3); self.y += dy / dist * (self.speed * 3); } // Clamp to map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; // Don't continue sleeping return; } } } else { self._triedWakeThisFrame = false; } self._sleepTimer--; if (self._sleepTimer <= 0) { self.state = "wandering"; self._wanderAfterSleepTimer = 600; // 10 seconds at 60fps } // While sleepy, don't move or seek food return; } // Wandering state after sleep: ignore plants, just wander for 10s, then become hungry if (self.state === "wandering") { if (typeof self._wanderAfterSleepTimer === "undefined") self._wanderAfterSleepTimer = 600; self._wanderAfterSleepTimer--; // --- Random wandering --- if (!self._wanderTimer || self._wanderTimer <= 0) { var angle = Math.random() * Math.PI * 2; self._wanderAngle = angle; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; // Keep inside map bounds (strictly) var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } if (self._wanderAfterSleepTimer <= 0) { self.state = "hungry"; } return; } // Only seek food if hungry if (self.state === "hungry") { var isWinter = !!window.winterState; // --- PERSONALITY: CURIOUS --- // If curious, sometimes follow pollinators for a few seconds if (self.personality === "curious") { // If not already following, 1% chance per frame to start following a pollinator if (self._curiousFollowTimer <= 0 && Math.random() < 0.01 && pollinators.length > 0) { // Pick a random pollinator to follow var idx = Math.floor(Math.random() * pollinators.length); var poll = pollinators[idx]; if (poll && !poll.eaten) { self._curiousPollinatorTarget = poll; self._curiousFollowTimer = 120 + Math.floor(Math.random() * 120); // 2-4 seconds } } // If following a pollinator if (self._curiousFollowTimer > 0 && self._curiousPollinatorTarget && !self._curiousPollinatorTarget.eaten) { var dx = self._curiousPollinatorTarget.x - self.x; var dy = self._curiousPollinatorTarget.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } self._curiousFollowTimer--; // Clamp to map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; // If timer runs out or pollinator is gone, stop following if (self._curiousFollowTimer <= 0 || self._curiousPollinatorTarget.eaten) { self._curiousFollowTimer = 0; self._curiousPollinatorTarget = null; } // While following, do not seek plants return; } // If not following, proceed as normal below } // --- PERSONALITY: GRUMPY --- // If grumpy, check for carnivore within 120px and scare it away if it is targeting this herbivore if (self.personality === "grumpy" && self._grumpyScareCooldown <= 0) { for (var ci = 0; ci < carnivores.length; ci++) { var carn = carnivores[ci]; if (carn.eaten) continue; var dx = carn.x - self.x; var dy = carn.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); // If carnivore is close and targeting this herbivore // Bloodthirsty carnivores are not scared of grumpy herbivores if (dist < 120 && carn.target === self && !carn._tired && carn.personality !== "bloodthirsty") { // Scare the carnivore away: set it to wandering, clear its target, and make it tired carn.state = "wandering"; carn.target = null; carn._tired = true; carn._tiredTimer = 300 + Math.floor(Math.random() * 200); // 5-8 seconds carn._chaseTime = 0; // Grumpy herbivore enters a short cooldown before it can scare again self._grumpyScareCooldown = 300; // 5 seconds // Optional: animate the herbivore (e.g. scale up briefly) tween(self, { scaleX: 1.3, scaleY: 1.3 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 80, easing: tween.easeIn }); } }); break; } } } if (self._grumpyScareCooldown > 0) self._grumpyScareCooldown--; // --- END PERSONALITY LOGIC --- // Normal hungry logic // In winter, do not seek or eat plants, but wander randomly if (isWinter) { // Wander only, do not target plants self.target = null; // --- Random wandering in winter --- if (!self._wanderTimer || self._wanderTimer <= 0) { var angle = Math.random() * Math.PI * 2; self._wanderAngle = angle; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; // Keep inside map bounds (strictly) var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } } else { // Always target the nearest plant when hungry var minDist = 999999, closest = null; for (var i = 0; i < plants.length; i++) { var p = plants[i]; if (p.eaten) continue; var dx = p.x - self.x, dy = p.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = p; } } self.target = closest; } // Nummer can walk over fungi as if it is not there: do not treat fungi as obstacles, so do not check for collision or avoid them at all in movement logic. // Check for nearest carnivore var nearestCarn = null; var minCarnDist = 999999; var tiredCarnivore = null; var tiredCarnDist = 999999; for (var ci = 0; ci < carnivores.length; ci++) { var carn = carnivores[ci]; if (carn.eaten) continue; var cdx = carn.x - self.x; var cdy = carn.y - self.y; var cdist = cdx * cdx + cdy * cdy; if (cdist < minCarnDist) { minCarnDist = cdist; nearestCarn = carn; } // Track tired carnivore chasing this herbivore if (carn._tired && carn.target === self && cdist < tiredCarnDist) { tiredCarnivore = carn; tiredCarnDist = cdist; } } var carnivoreTooClose = false; var carnDist = 0; var runAwayDX = 0, runAwayDY = 0; if (nearestCarn) { carnDist = Math.sqrt((nearestCarn.x - self.x) * (nearestCarn.x - self.x) + (nearestCarn.y - self.y) * (nearestCarn.y - self.y)); if (nearestCarn._tired && nearestCarn.target === self) { // If a tired carnivore was chasing this herbivore, teleport a tiny distance in front of it (away from carnivore) // Only teleport once per tired state if (!self._teleportedFromTiredCarnivore) { var dx = self.x - nearestCarn.x; var dy = self.y - nearestCarn.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 200) { // Only teleport if not already far away // Teleport 80px away in the direction away from carnivore var teleportDist = 80; var tx = nearestCarn.x + dx / (dist || 1) * teleportDist; var ty = nearestCarn.y + dy / (dist || 1) * teleportDist; // Clamp to map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (tx < minX) tx = minX; if (tx > maxX) tx = maxX; if (ty < minY) ty = minY; if (ty > maxY) ty = maxY; self.x = tx; self.y = ty; } self._teleportedFromTiredCarnivore = true; // After teleport, set a run away timer so it keeps running for a while self._runAwayFromTiredCarnivoreTimer = 60 + Math.floor(Math.random() * 60); // 1-2 seconds // Pick a run direction directly away from carnivore if (dist > 0) { self._runAwayAngle = Math.atan2(dy, dx); } else { self._runAwayAngle = Math.random() * Math.PI * 2; } } carnivoreTooClose = true; runAwayDX = self.x - nearestCarn.x; runAwayDY = self.y - nearestCarn.y; } else if (carnDist < 300) { // If carnivore is within 300px, run away carnivoreTooClose = true; runAwayDX = self.x - nearestCarn.x; runAwayDY = self.y - nearestCarn.y; // Reset teleport flag if not being chased by tired carnivore self._teleportedFromTiredCarnivore = false; } } // --- New: If a tired carnivore was chasing this herbivore, charge away until far enough --- // (Removed barrier logic: herbivores can always run away from tired carnivores) if (carnivoreTooClose && carnDist > 1) { // If running from a tired carnivore and we have a run timer, keep running in the set direction if (typeof self._runAwayFromTiredCarnivoreTimer !== "undefined" && self._runAwayFromTiredCarnivoreTimer > 0) { self.x += Math.cos(self._runAwayAngle) * self.speed * 2.2; self.y += Math.sin(self._runAwayAngle) * self.speed * 2.2; self._runAwayFromTiredCarnivoreTimer--; // Clamp to map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; // When timer runs out, clear teleport/run state so it can look for food again if (self._runAwayFromTiredCarnivoreTimer <= 0) { self._runAwayFromTiredCarnivoreTimer = 0; self._teleportedFromTiredCarnivore = false; } // While running, do not seek food return; } else { // Run away from carnivore (normal, not tired chase) self.x += runAwayDX / carnDist * (self.speed * 1.5); self.y += runAwayDY / carnDist * (self.speed * 1.5); // Reset teleport/run state if not running from tired carnivore self._teleportedFromTiredCarnivore = false; self._runAwayFromTiredCarnivoreTimer = 0; } } else { // --- Random wandering when not chasing plant --- if ((!self.target || self.target.eaten) && (!self._wanderTimer || self._wanderTimer <= 0)) { // Pick a new random direction every 60-120 frames var angle = Math.random() * Math.PI * 2; self._wanderAngle = angle; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (!self.target || self.target.eaten) { // Wander in the chosen direction if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; // Keep inside map bounds (strictly) var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } } // Move toward target plant if (self.target && !self.target.eaten) { var isWinter = !!window.winterState; var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } // Eat plant if close enough, but not in winter if (!isWinter && dist < 60 && !self.target.eaten) { var eatenPlant = self.target; // Play chomp sound when a herbivore eats a plant var chompSnd = LK.getSound('Chomp'); if (chompSnd && typeof chompSnd.play === "function") chompSnd.play(); self.target.die(); self.target = null; // Animate Nummer "eating" tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); // After eating, enter sleepy state for 10 seconds (600 frames) with a chance to skip sleep if (Math.random() < 0.7) { // 70% chance to sleep, 30% chance to skip self.state = "sleepy"; self._sleepTimer = 600; } else { // Skip sleep, go straight to wandering self.state = "wandering"; self._wanderAfterSleepTimer = 600; // 10 seconds wandering } // Reset starvation timer after eating only if plant is not brown if (!(eatenPlant && eatenPlant._isBrown)) { self._starveTimer = 4800; // 80 seconds at 60fps } // Increment plants eaten counter self._plantsEaten = (self._plantsEaten || 0) + 1; // Lay an egg if 4 plants have been eaten, then reset counter // --- HERBIVORE POPULATION CONTROL: Only lay egg if < 6 herbivores --- if (self._plantsEaten >= 4) { if (typeof herbivores !== "undefined" && herbivores.length < 6) { var egg = new Egg(); egg.x = self.x; egg.y = self.y; egg.scaleX = 0.5; egg.scaleY = 0.5; egg.alpha = 1; if (self.parent) self.parent.addChild(egg); if (typeof eggs !== "undefined") eggs.push(egg); } self._plantsEaten = 0; } } } } } }; // Called when eaten by carnivore self.die = function (_onFinish) { if (self.eaten) return; self.eaten = true; // Remove from hunger queue if present var idx = herbivoreHungerQueue.indexOf(self); if (idx !== -1) { herbivoreHungerQueue.splice(idx, 1); } // If infected, spawn a fungi at this position // --- FUNGI POPULATION CONTROL: Prevent dropping fungi at death if 20 or more fungi --- if (self.infected && (typeof fungis === "undefined" || fungis.length < 20)) { var newFungi = new Fungi(); // If fungi is spawned by Nummer death, set its _bornCycle to the next cycle so it lives a full cycle if (typeof cycleCount !== "undefined") { newFungi._bornCycle = cycleCount + 1; } newFungi.x = self.x; newFungi.y = self.y; newFungi.scaleX = 1; newFungi.scaleY = 1; newFungi.alpha = 1; if (self.parent) self.parent.addChild(newFungi); if (typeof fungis !== "undefined") fungis.push(newFungi); } tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); if (_onFinish) _onFinish(); } }); }; // Called when hit by a spore self.sporeHit = function () { if (self.spored) return; // Only allow once self.spored = true; // If it's summer, delay infection until next winter or spring if (window.summerState) { // Mark as pending infection, but do not infect yet self._pendingFungiInfection = true; // Optionally, you could tint a different color to show pending state, or do nothing return; } // Enter infected state immediately if not summer self.infected = true; self.infectedTimer = 0; // Tint permanently purple tween(self, { tint: 0xbb88ff }, { duration: 80 }); }; return self; }); // Parasite class (seeks and attaches to herbivores or carnivores) var Parasite = Container.expand(function () { var self = Container.call(this); var parasiteAsset = self.attachAsset('Parasite', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); // State: "seek", "attached" self.state = "seek"; self.target = null; self.attachedTo = null; self.offsetX = 0; self.offsetY = 0; self.speed = 1.5 + Math.random() * 0.7; // Track last intersecting for attach logic self._lastIntersecting = false; self.update = function () { // If attached, follow the host if (self.state === "attached" && self.attachedTo) { // AUTUMN: Detach immediately in autumn if (window.autumnState) { // Detach from host if (self.attachedTo._parasiteAttached === self) { self.attachedTo._parasiteAttached = null; } self.state = "seek"; self.attachedTo = null; self.target = null; self._wanderTimer = 0; // Allow laying an egg only once per parasite in autumn if (!self._laidAutumnEgg) { var egg = new Egg(); egg.x = self.x; egg.y = self.y; egg.scaleX = 0.5; egg.scaleY = 0.5; egg.alpha = 1; egg._isParasiteEgg = true; egg._parasiteHatch = true; if (self.parent) self.parent.addChild(egg); if (typeof eggs !== "undefined") eggs.push(egg); self._laidAutumnEgg = true; } return; } // If host is eaten, parasite dies with host if (self.attachedTo.eaten) { // Animate parasite dying and remove from array tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); // Remove from parasites array if present if (typeof parasites !== "undefined") { var idx = parasites.indexOf(self); if (idx !== -1) parasites.splice(idx, 1); } } }); return; } // If host is a herbivore and is infected by shrooms, let go immediately if (typeof self.attachedTo.spored !== "undefined" && self.attachedTo.spored === true) { // Detach from host if (self.attachedTo._parasiteAttached === self) { self.attachedTo._parasiteAttached = null; } self.state = "seek"; self.attachedTo = null; self.target = null; // Wander in a new random direction self._wanderTimer = 0; return; } // Stay at the same offset from host self.x = self.attachedTo.x + self.offsetX; self.y = self.attachedTo.y + self.offsetY; // --- Parasite lays an egg every cycle if attached to a host, but only if < 4 parasites --- if (typeof self._lastCycle === "undefined") self._lastCycle = -1; if (typeof cycleCount !== "undefined" && self._lastCycle !== cycleCount) { self._lastCycle = cycleCount; if (typeof parasites !== "undefined" && parasites.length < 4) { // Lay a parasite egg at current position var egg = new Egg(); egg.x = self.x; egg.y = self.y; egg.scaleX = 0.5; egg.scaleY = 0.5; egg.alpha = 1; egg._isParasiteEgg = true; egg._parasiteHatch = true; // Pass parent host resistance to egg for inheritance if (self.attachedTo && self.attachedTo.parasiteResistant === true) { egg._parentHost = self.attachedTo; } if (self.parent) self.parent.addChild(egg); if (typeof eggs !== "undefined") eggs.push(egg); } } return; } // If seeking, look for nearest herbivore or carnivore if (self.state === "seek") { // In autumn, cannot infect, so just wander if (window.autumnState) { // Wander randomly if no target if (!self._wanderTimer || self._wanderTimer <= 0) { self._wanderAngle = Math.random() * Math.PI * 2; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; // Keep inside map bounds var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } self._lastIntersecting = false; return; } // If no target or target is eaten or already has a parasite attached (not self), find new target if (!self.target || self.target.eaten || self.target._parasiteAttached && self.target._parasiteAttached !== self) { var minDist = 999999, closest = null; // Search herbivores first for (var i = 0; i < herbivores.length; i++) { var h = herbivores[i]; if (h.eaten) continue; // Only attach if not already parasitized if (h._parasiteAttached) continue; // Avoid targeting spore-infected herbivores if (h.spored === true) continue; // Avoid parasite-resistant herbivores if (h.parasiteResistant === true) continue; var dx = h.x - self.x, dy = h.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = h; } } // If no herbivore, search carnivores if (!closest) { for (var i = 0; i < carnivores.length; i++) { var c = carnivores[i]; if (c.eaten) continue; if (c._parasiteAttached) continue; // Only infect weak carnivores (personality === "weak") if (c.personality !== "weak") continue; // Avoid parasite-resistant carnivores if (c.parasiteResistant === true) continue; var dx = c.x - self.x, dy = c.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = c; } } } self.target = closest; } // If we have a target, move toward it if (self.target && !self.target.eaten && !self.target._parasiteAttached) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } // Attach if close enough and not already attached var isIntersecting = self.intersects(self.target); if (!self._lastIntersecting && isIntersecting && !self.target._parasiteAttached) { // Attach to host self.state = "attached"; self.attachedTo = self.target; // Store offset so parasite stays visually attached self.offsetX = self.x - self.attachedTo.x; self.offsetY = self.y - self.attachedTo.y; // Mark host as parasitized self.attachedTo._parasiteAttached = self; } self._lastIntersecting = isIntersecting; } else { // Wander randomly if no target if (!self._wanderTimer || self._wanderTimer <= 0) { self._wanderAngle = Math.random() * Math.PI * 2; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; // Keep inside map bounds var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } self._lastIntersecting = false; } } }; return self; }); // Plant class var Plant = Container.expand(function () { var self = Container.call(this); var plantAsset = self.attachAsset('plant', { anchorX: 0.5, anchorY: 0.5 }); self.plantAsset = plantAsset; // Expose for tinting self._canDie = true; // Track if plant can die // Plants are static, but we can animate them when eaten self.eaten = false; // Track the cycle the plant was created in self._bornCycle = typeof cycleCount !== "undefined" ? cycleCount : 0; self.die = function (_onFinish2) { if (self.eaten) return; if (self._canDie === false) return; // Prevent dying if not allowed self.eaten = true; tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); if (_onFinish2) _onFinish2(); } }); }; // Plants die after 1 cycle self.update = function () { if (self.eaten) return; // --- Summer: chance to turn brown and become unpollinatable --- if (self._invulnerable) { // While invulnerable, never turn brown or die self._isBrown = false; if (self.plantAsset && typeof self.plantAsset.tint !== "undefined") { self.plantAsset.tint = 0xffffff; } } else { if ((window.summerState || window.springState) && !self._isBrown && !self.eaten) { // Only roll once per plant per summer or spring if (typeof self._brownChecked === "undefined" || self._brownChecked !== cycleCount) { self._brownChecked = cycleCount; if (window.summerState && Math.random() < 0.12) { // ~12% chance per cycle in summer self._isBrown = true; if (self.plantAsset && typeof self.plantAsset.tint !== "undefined") { self.plantAsset.tint = 0x996633; // brown } } else if (window.springState && Math.random() < 0.002) { // ~0.2% chance per cycle in spring (VERY low) self._isBrown = true; if (self.plantAsset && typeof self.plantAsset.tint !== "undefined") { self.plantAsset.tint = 0x996633; // brown } } } } // If not summer, reset brown state if (!window.summerState && self._isBrown) { self._isBrown = false; if (self.plantAsset && typeof self.plantAsset.tint !== "undefined") { self.plantAsset.tint = 0xffffff; } } // Only die if at least one full cycle has passed since birth if (typeof cycleCount !== "undefined" && typeof self._bornCycle !== "undefined") { // Brown plants in summer: die after 1/2 cycle (shorter lifespan) if (self._isBrown && window.summerState && self._canDie !== false) { if (cycleCount > self._bornCycle + 0.5) { self.die(); } } else if (cycleCount > self._bornCycle + 1 && self._canDie !== false) { self.die(); } } } }; return self; }); // Pollinator class var Pollinator = Container.expand(function () { var self = Container.call(this); var pollAsset = self.attachAsset('pollinator', { anchorX: 0.5, anchorY: 0.5 }); // --- Pollinator Starvation Logic --- self.state = "seek"; // "seek", "scatter", "planting", "hungry" self.target = null; self.speed = 2 + Math.random(); self._scatterTimer = 0; self._scatterAngle = 0; self._plantSeedTimer = 0; self._hasSeed = true; self.eaten = false; self.lastWasTouchingPlant = false; // --- Pollinator Starve Timer --- self._starveTimer = 3000 + Math.floor(Math.random() * 1200); // 1 cycle = 50s = 3000 frames, randomize a bit self._lastCycle = typeof cycleCount !== "undefined" ? cycleCount : 0; // --- Pollination counter for egg laying --- self._pollinations = 0; self._layingEgg = false; self._eggObj = null; // --- After eating shroom, next pollination is a shroom --- self._nextPollinationIsShroom = false; self._didShroomPollination = false; self.update = function () { if (self.eaten) return; // --- POLLINATOR STARVATION LOGIC (handled in constructor patch above) --- // --- POLLINATOR HUNGRY STATE: Seek fungi and eat if hungry --- if (self.state === "hungry") { // Prevent pollinators from eating fungi during autumn, winter, and summer var isAutumn = !!window.autumnState; var isWinter = !!window.winterState; var isSummer = !!window.summerState; if (isAutumn || isWinter || isSummer) { // Just wander randomly, do not seek or eat fungi, and do not decrement starve timer if (!self._wanderTimer || self._wanderTimer <= 0) { self._wanderAngle = Math.random() * Math.PI * 2; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; } // Clamp to map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; return; } // Randomly (1 in 8 chance per frame) pick a new fungi to seek, or keep current if (!self.target || self.target.eaten || Math.random() < 0.125) { // Find nearest fungi var minDist = 999999, closest = null; for (var i = 0; i < fungis.length; i++) { var f = fungis[i]; if (f.eaten) continue; var dx = f.x - self.x, dy = f.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = f; } } self.target = closest; } // Move toward fungi if any if (self.target && !self.target.eaten) { var dx = self.target.x - self.x, dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } // Eat fungi if close enough if (dist < 60 && !self.target.eaten) { // Play chomp sound when pollinator eats fungi var chompSnd = LK.getSound('Chomp'); if (chompSnd && typeof chompSnd.play === "function") chompSnd.play(); // Track how many times this fungi has been eaten by pollinators if (typeof self.target._pollinatorEatCount === "undefined") { self.target._pollinatorEatCount = 0; } self.target._pollinatorEatCount++; // If eaten 3 times, do nothing (fungi does not disappear anymore) if (self.target._pollinatorEatCount >= 3) { // Fungi no longer disappears after being eaten 3 times // Optionally, you could add a visual effect or reset the counter, but do nothing here } // Reset starve timer self._starveTimer = 3000 + Math.floor(Math.random() * 1200); // After eating, return to normal pollinator state self.state = "seek"; self.target = null; // Set flag so next pollination is a shroom self._nextPollinationIsShroom = true; self._didShroomPollination = false; // Animate pollinator "eating" tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); return; } } else { // If no fungi, wander randomly if (!self._wanderTimer || self._wanderTimer <= 0) { self._wanderAngle = Math.random() * Math.PI * 2; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; } } // Clamp to map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; return; } // --- POLLINATOR STARVATION: decrement timer unless hibernating or in autumn, winter, or summer --- var isAutumn = !!window.autumnState; var isWinter = !!window.winterState; var isSummer = !!window.summerState; if (!(typeof pollinatorsHibernating !== "undefined" && pollinatorsHibernating) && !isAutumn && !isWinter && !isSummer) { // Only decrement if not hibernating and not in autumn, winter, or summer self._starveTimer--; if (self._starveTimer <= 0 && !self.eaten) { // Die of starvation! self.eaten = true; tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); return; } } // --- POLLINATOR HUNGRY STATE: If timer is low, become hungry and seek fungi --- if (self.state !== "hungry" && self._starveTimer < 900) { self.state = "hungry"; self.target = null; } // --- If pollinator eats fungi, reset timer --- self._tryEatFungi = function () { // Find nearest fungi var minDist = 999999, closest = null; for (var i = 0; i < fungis.length; i++) { var f = fungis[i]; if (f.eaten) continue; var dx = f.x - self.x, dy = f.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = f; } } if (closest) { self.target = closest; var dx = closest.x - self.x, dy = closest.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } // Eat fungi if close enough if (dist < 60 && !closest.eaten) { if (typeof closest.die === "function") closest.die(); // Reset starve timer self._starveTimer = 3000 + Math.floor(Math.random() * 1200); // After eating, return to normal pollinator state self.state = "seek"; self.target = null; // Animate pollinator "eating" tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); return true; } } return false; }; // --- POLLINATOR ACTIVE/NONACTIVE BEHAVIOR --- // If more than 5 pollinators, only 3 are 'active' and make sound, others are 'nonactive' if (typeof pollinators !== "undefined" && pollinators.length > 5) { // Assign active/nonactive roles once per frame for all pollinators if (typeof window._activePollinatorIds === "undefined") window._activePollinatorIds = []; // Only recalculate once per frame if (typeof window._activePollinatorFrame !== "number" || window._activePollinatorFrame !== LK.ticks) { // Pick 3 unique random pollinators to be active var ids = []; var pool = []; for (var i = 0; i < pollinators.length; i++) { if (!pollinators[i].eaten) pool.push(i); } // Shuffle pool for (var i = pool.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var tmp = pool[i]; pool[i] = pool[j]; pool[j] = tmp; } ids = pool.slice(0, 3); window._activePollinatorIds = ids; window._activePollinatorFrame = LK.ticks; } // Set self.activeBehavior based on selection var myIdx = -1; for (var i = 0; i < pollinators.length; i++) { if (pollinators[i] === self) { myIdx = i; break; } } self.behavior = window._activePollinatorIds.indexOf(myIdx) !== -1 ? "active" : "nonactive"; } else { // If 5 or fewer, all are active self.behavior = "active"; } // --- POLLINATOR NOISE: Play sound at random intervals, only if active --- // Nonactive pollinators are incapable of making sound unless population is reduced to 3 or fewer if (typeof self._nextNoiseTimer === "undefined") { // Set initial timer to random 1-4 seconds (60-240 frames) self._nextNoiseTimer = 60 + Math.floor(Math.random() * 180); } if (self._nextNoiseTimer > 0) { self._nextNoiseTimer--; } else { var canMakeSound = false; if (typeof pollinators !== "undefined" && pollinators.length <= 3) { // All pollinators can make sound if population is 3 or fewer canMakeSound = true; } else if (self.behavior === "active") { // Only active pollinators can make sound if more than 3 canMakeSound = true; } if (canMakeSound && !(typeof pollinatorsHibernating !== "undefined" && pollinatorsHibernating)) { // Play the 'Bug' sound var snd = LK.getSound('Bug'); if (snd && typeof snd.play === "function") snd.play(); } // Reset timer to next random interval (1-4 seconds) self._nextNoiseTimer = 60 + Math.floor(Math.random() * 180); } // --- HIBERNATION: If background is white, pollinators do nothing, don't move, don't die, don't act --- if (typeof pollinatorsHibernating !== "undefined" && pollinatorsHibernating) { // Do not decrement lifetime, do not move, do not die, do not lay eggs, do not plant seeds return; } // If currently laying an egg, wait for egg to hatch before continuing if (self._layingEgg && self._eggObj) { // If it's summer or winter, eggs will not hatch, so pollinator should move away and continue pollinating var isSummer = !!window.summerState; var isWinter = !!window.winterState; if (isSummer || isWinter) { // Move away from the egg and resume pollinating if (self._eggObj) { // Move pollinator a bit away from the egg (random direction) var angle = Math.random() * Math.PI * 2; self.x += Math.cos(angle) * 80; self.y += Math.sin(angle) * 80; // Clamp to map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } self._eggObj = null; self._layingEgg = false; self._pollinations = 0; // Reset counter // Resume pollinating self.state = "seek"; self.target = null; // Continue with rest of update } else { // Wait for egg to hatch, then continue pollinating if (self._eggObj.hatched) { // Spawn a new pollinator at egg position var newPoll = new Pollinator(); newPoll.x = self._eggObj.x; newPoll.y = self._eggObj.y; newPoll.scaleX = 1; newPoll.scaleY = 1; newPoll.alpha = 1; if (self.parent) self.parent.addChild(newPoll); if (typeof pollinators !== "undefined") pollinators.push(newPoll); // Remove egg from global eggs array var idx = eggs.indexOf(self._eggObj); if (idx !== -1) eggs.splice(idx, 1); self._eggObj.destroy(); self._eggObj = null; self._layingEgg = false; self._pollinations = 0; // Reset counter } else { // Wait for egg to hatch, do nothing else return; } } } // SEEK: Find nearest plant and move to it, but run from carnivores if close if (self.state === "seek") { // Check for nearest carnivore var nearestCarn = null; var minCarnDist = 999999; for (var ci = 0; ci < carnivores.length; ci++) { var carn = carnivores[ci]; if (carn.eaten) continue; var cdx = carn.x - self.x; var cdy = carn.y - self.y; var cdist = cdx * cdx + cdy * cdy; if (cdist < minCarnDist) { minCarnDist = cdist; nearestCarn = carn; } } var carnivoreTooClose = false; var carnDist = 0; var runAwayDX = 0, runAwayDY = 0; if (nearestCarn) { carnDist = Math.sqrt((nearestCarn.x - self.x) * (nearestCarn.x - self.x) + (nearestCarn.y - self.y) * (nearestCarn.y - self.y)); if (carnDist < 220) { // Run if carnivore is within 220px carnivoreTooClose = true; runAwayDX = self.x - nearestCarn.x; runAwayDY = self.y - nearestCarn.y; } } if (carnivoreTooClose && carnDist > 1) { // Run away from carnivore (burst speed) self.x += runAwayDX / carnDist * (self.speed * 2.2); self.y += runAwayDY / carnDist * (self.speed * 2.2); } else { // Find nearest plant if (!self.target || self.target.eaten) { var minDist = 999999, closest = null; for (var i = 0; i < plants.length; i++) { var p = plants[i]; if (p.eaten) continue; // Ignore brown plants in summer if (window.summerState && p._isBrown) continue; var dx = p.x - self.x, dy = p.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = p; } } self.target = closest; } // Move toward target plant if (self.target && !self.target.eaten) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } // Check for touching plant (within 60px) var isTouching = dist < 60; if (!self.lastWasTouchingPlant && isTouching) { // If plant is brown, skip and immediately seek a new healthy plant if (self.target && self.target._isBrown) { // Find next healthy plant (not brown, not eaten) var minDist2 = 999999, closest2 = null; for (var j = 0; j < plants.length; j++) { var p2 = plants[j]; if (p2.eaten) continue; if (window.summerState && p2._isBrown) continue; var dx2 = p2.x - self.x, dy2 = p2.y - self.y; var dist2 = dx2 * dx2 + dy2 * dy2; if (dist2 < minDist2) { minDist2 = dist2; closest2 = p2; } } self.target = closest2; // Do not pollinate, just move on self.lastWasTouchingPlant = isTouching; return; } // Just touched plant // --- Pollinate a shroom instead of a plant if flag is set --- // (Removed: pollinators no longer spawn fungi or delete shrooms) if (self._nextPollinationIsShroom && !self._didShroomPollination) { self._didShroomPollination = true; self._nextPollinationIsShroom = false; // After pollinating a shroom, next pollination is normal self.state = "scatter"; self._scatterTimer = 60 + Math.floor(Math.random() * 60); // 1-2 seconds self._scatterAngle = Math.random() * Math.PI * 2; self.lastWasTouchingPlant = isTouching; return; } self._pollinations = (self._pollinations || 0) + 1; // If pollinated 3 times, lay an egg and continue pollinating (do not wait for egg to hatch) if (self._pollinations >= 3 && !self._layingEgg) { // In autumn, pollinators do NOT lay eggs, just pollinate and continue if (window.autumnState) { self._pollinations = 0; // Reset pollination counter in autumn self.state = "scatter"; self._scatterTimer = 60 + Math.floor(Math.random() * 60); // 1-2 seconds self._scatterAngle = Math.random() * Math.PI * 2; self.lastWasTouchingPlant = isTouching; return; } // --- POLLINATOR POPULATION CONTROL: Only lay egg if < 10 pollinators --- // --- FERN POPULATION CONTROL: Only lay egg if < 20 ferns (plants) --- if (typeof pollinators !== "undefined" && pollinators.length >= 10 || typeof plants !== "undefined" && plants.length >= 20) { // Do not lay egg, just reset pollination counter and scatter self._pollinations = 0; self.state = "scatter"; self._scatterTimer = 60 + Math.floor(Math.random() * 60); self._scatterAngle = Math.random() * Math.PI * 2; self.lastWasTouchingPlant = isTouching; return; } // Lay a pollinator egg at current position var egg = new Egg(); egg.x = self.x; egg.y = self.y; egg.scaleX = 0.5; egg.scaleY = 0.5; egg.alpha = 1; egg._isPollinatorEgg = true; egg._pollinatorHatch = true; if (self.parent) self.parent.addChild(egg); if (typeof eggs !== "undefined") eggs.push(egg); // Reset pollination counter and continue pollinating self._pollinations = 0; // Set pollinator to hungry state after laying an egg self.state = "hungry"; self.target = null; self.lastWasTouchingPlant = isTouching; return; } self.state = "scatter"; self._scatterTimer = 60 + Math.floor(Math.random() * 60); // 1-2 seconds self._scatterAngle = Math.random() * Math.PI * 2; } self.lastWasTouchingPlant = isTouching; } else { // No plant to go to, wander randomly if (!self._wanderTimer || self._wanderTimer <= 0) { self._wanderAngle = Math.random() * Math.PI * 2; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; } } } } // SCATTER: Move in a random direction for a short time else if (self.state === "scatter") { self.x += Math.cos(self._scatterAngle) * self.speed * 2; self.y += Math.sin(self._scatterAngle) * self.speed * 2; self._scatterTimer--; if (self._scatterTimer <= 0) { self.state = "planting"; self._plantSeedTimer = 30 + Math.floor(Math.random() * 30); // short pause before planting } } // PLANTING: Pause, then plant a seed else if (self.state === "planting") { self._plantSeedTimer--; if (self._plantSeedTimer <= 0 && self._hasSeed) { // Place a seed at current position var seed = new Seed(); seed.x = self.x; seed.y = self.y; seed.scaleX = 0.4; seed.scaleY = 0.4; seed.alpha = 1; seeds.push(seed); if (self.parent) self.parent.addChild(seed); // Play plop sound when pollinator places a seed var plopSnd = LK.getSound('Plop'); if (plopSnd && typeof plopSnd.play === "function") plopSnd.play(); self._hasSeed = false; // After planting, look for another plant self.state = "seek"; self.target = null; self._hasSeed = true; } } // Keep inside map bounds var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; }; self.die = function () { if (self.eaten) return; self.eaten = true; tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Seed class var Seed = Container.expand(function () { var self = Container.call(this); // Use dedicated brown seed asset var seedAsset = self.attachAsset('seed', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4 }); self.growing = false; self.eaten = false; self._growTimer = 600; // 10 seconds at 60fps self.update = function () { if (self.eaten) return; if (!self.growing) { self._growTimer--; // --- FERN POPULATION CONTROL: If 20 or more ferns, seed always fails to germinate --- if (typeof plants !== "undefined" && plants.length >= 20) { // 100% fail to germinate self.eaten = true; tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); return; } if (self._growTimer <= 0) { // --- 30% chance for seed to not germinate and disappear --- if (Math.random() < 0.3) { self.eaten = true; tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); return; } // --- Summer or Autumn state: chance for seed to fail germination and disappear --- if (window.summerState && Math.random() < 0.4) { // 40% chance to fail in summer, just disappear self.eaten = true; tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); return; } else if (window.autumnState && Math.random() < 0.55) { // 55% chance to fail in autumn, just disappear self.eaten = true; tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); return; } self.growing = true; // Animate seed growing into plant tween(self, { scaleX: 1, scaleY: 1, alpha: 0.7 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Play grow sound when seed germinates var growSnd = LK.getSound('Grow'); if (growSnd && typeof growSnd.play === "function") growSnd.play(); // Replace seed with a new plant at same position var newPlant = new Plant(); newPlant.x = self.x; newPlant.y = self.y; newPlant.scaleX = 1; newPlant.scaleY = 1; newPlant.alpha = 1; // Set the plant's _bornCycle to the current cycleCount so its cycle restarts if (typeof cycleCount !== "undefined") { newPlant._bornCycle = cycleCount; } plants.push(newPlant); if (self.parent) self.parent.addChild(newPlant); self.eaten = true; self.destroy(); } }); } } }; // If something eats the seed (future-proof) self.die = function () { if (self.eaten) return; self.eaten = true; tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Spore class (projectile shot by fungi) var Spore = Container.expand(function () { var self = Container.call(this); // Use a simple green circle for the spore var sporeAsset = self.attachAsset('plant', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.25, scaleY: 0.25 }); self.target = null; self.speed = 2.5; self._alive = true; self.update = function () { if (!self.target || self.target.eaten || !self._alive) { self.destroy(); self._alive = false; return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } // Check collision with target herbivore if (!self._lastIntersecting && self.intersects(self.target)) { // "Hit" the herbivore: destroy spore, animate herbivore, but do not kill if (self.target && typeof self.target.sporeHit === "function") { self.target.sporeHit(); } self.destroy(); self._alive = false; } self._lastIntersecting = self.intersects(self.target); }; return self; }); // FungiSpore class for future extensibility (could add poison, etc) // Modified: FungiSpore now takes a straight path (direction set at spawn) var FungiSpore = Spore.expand(function () { var self = Spore.call(this); // Store initial direction at spawn self._vx = 0; self._vy = 0; // Set direction only once at spawn, toward target self.setDirection = function () { if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { self._vx = dx / dist * self.speed; self._vy = dy / dist * self.speed; } else { self._vx = 0; self._vy = 0; } } else { self._vx = 0; self._vy = 0; } }; // Call setDirection at spawn self.setDirection(); // Override update to move in a straight line self.update = function () { if (!self.target || self.target.eaten || !self._alive) { self.destroy(); self._alive = false; return; } self.x += self._vx; self.y += self._vy; // Check collision with target herbivore if (!self._lastIntersecting && self.intersects(self.target)) { if (self.target && typeof self.target.sporeHit === "function") { self.target.sporeHit(); } self.destroy(); self._alive = false; } self._lastIntersecting = self.intersects(self.target); }; return self; }); // Worm class (moves only in summer and winter, buried in autumn and spring) var Worm = Container.expand(function () { var self = Container.call(this); // Determine if worm is buried at spawn self.buried = false; var isAutumn = !!window.autumnState; var isSpring = !!window.springState; if (isAutumn || isSpring) { self.buried = true; } // Use the worm image asset for the worm, or buried asset if buried self._wormAsset = null; if (self.buried) { self._wormAsset = self.attachAsset('Buried_worm', { anchorX: 0.5, anchorY: 0.5 }); } else { self._wormAsset = self.attachAsset('Worm', { anchorX: 0.5, anchorY: 0.5 }); } // Movement speed for worms self.speed = 1 + Math.random() * 0.5; // Track wander direction and timer self._wanderAngle = Math.random() * Math.PI * 2; self._wanderTimer = 60 + Math.floor(Math.random() * 60); // Track brown plants eaten in summer self._brownPlantsEaten = 0; // Track last brown plant intersected (for collision detection) self._lastIntersectingBrownPlant = null; self.update = function () { var isSummer = !!window.summerState; var isWinter = !!window.winterState; var isAutumn = !!window.autumnState; var isSpring = !!window.springState; // If currently buried but season changed to summer/winter, unbury and swap asset if (self.buried && (isSummer || isWinter)) { self.buried = false; if (self._wormAsset) { self.removeChild(self._wormAsset); } self._wormAsset = self.attachAsset('Worm', { anchorX: 0.5, anchorY: 0.5 }); } // If currently not buried but season changed to autumn/spring, bury and swap asset if (!self.buried && (isAutumn || isSpring)) { self.buried = true; if (self._wormAsset) { self.removeChild(self._wormAsset); } self._wormAsset = self.attachAsset('Buried_worm', { anchorX: 0.5, anchorY: 0.5 }); } // --- Worm AI: In summer, seek and eat brown plants, then bury after 3 eaten --- if (isSummer && !self.buried) { // Find nearest brown plant var minDist = 999999, closest = null; for (var i = 0; i < plants.length; i++) { var p = plants[i]; if (p.eaten) continue; if (!p._isBrown) continue; var dx = p.x - self.x, dy = p.y - self.y; var dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closest = p; } } if (closest) { // Move toward brown plant var dx = closest.x - self.x; var dy = closest.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } // Eat brown plant if close enough and not already eaten var isIntersecting = self.intersects(closest); if ((!self._lastIntersectingBrownPlant || self._lastIntersectingBrownPlant !== closest) && isIntersecting && !closest.eaten) { if (typeof closest.die === "function") { closest.die(); } self._brownPlantsEaten = (self._brownPlantsEaten || 0) + 1; // After eating 3 brown plants, bury and swap asset if (self._brownPlantsEaten >= 3) { self.buried = true; if (self._wormAsset) { self.removeChild(self._wormAsset); } self._wormAsset = self.attachAsset('Buried_worm', { anchorX: 0.5, anchorY: 0.5 }); self._brownPlantsEaten = 0; // Immediately stop moving after burying self._wanderTimer = 0; self._wanderAngle = 0; } } self._lastIntersectingBrownPlant = isIntersecting ? closest : null; } else { // No brown plant: wander randomly if (!self._wanderTimer || self._wanderTimer <= 0) { self._wanderAngle = Math.random() * Math.PI * 2; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; // Keep inside map bounds (strictly) var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } self._lastIntersectingBrownPlant = null; } } else if (isWinter && !self.buried) { // Wander randomly in winter if (!self._wanderTimer || self._wanderTimer <= 0) { self._wanderAngle = Math.random() * Math.PI * 2; self._wanderTimer = 60 + Math.floor(Math.random() * 60); } if (typeof self._wanderAngle === "number") { self.x += Math.cos(self._wanderAngle) * self.speed * 0.5; self.y += Math.sin(self._wanderAngle) * self.speed * 0.5; self._wanderTimer--; // Keep inside map bounds (strictly) var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; if (self.x < minX) self.x = minX; if (self.x > maxX) self.x = maxX; if (self.y < minY) self.y = minY; if (self.y > maxY) self.y = maxY; } // Reset brown plant eating state in winter self._brownPlantsEaten = 0; self._lastIntersectingBrownPlant = null; } // In autumn and spring, do nothing (no movement, buried) }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2ecc40 //{7C} // Set to a green background }); /**** * Game Code ****/ // Pollinator sound effects (buzz, chirp, etc) // --- Pollinator Hibernation State --- var pollinatorsHibernating = false; // --- Year Counter --- // Add a year counter above the cycle counter that increments after winter ends var yearCount = 1; var yearTxt = new Text2("Year: 1", { size: 80, fill: "#000" }); yearTxt.anchor.set(0.5, 0); // Place above the cycle counter LK.gui.top.addChild(yearTxt); yearTxt.x = 500; yearTxt.y = 40; // --- Cycle Counter --- // Add a cycle counter at the bottom of the screen that increments every 50 seconds (3000 frames at 60fps) var cycleCount = 0; var cycleTxt = new Text2("Cycle: 0", { size: 80, fill: "#000" }); cycleTxt.anchor.set(0.5, 0); // Place at top left, but not in the top 100px reserved area LK.gui.top.addChild(cycleTxt); cycleTxt.x = 500; cycleTxt.y = 120; // --- Season Text at Bottom --- // Helper to get current season as string function getCurrentSeason() { if (window.summerState) return "Summer"; if (window.winterState) return "Winter"; if (window.springState) return "Spring"; if (window.autumnState) return "Autumn"; // Fallback: guess from background color if (game.backgroundColor === 0xffff00) return "Summer"; if (game.backgroundColor === 0xffffff) return "Winter"; if (game.backgroundColor === 0x8B5A2B) return "Autumn"; return "Spring"; } var seasonTxt = new Text2("Season: " + getCurrentSeason(), { size: 90, fill: "#000" }); seasonTxt.anchor.set(0, 1); LK.gui.bottom.addChild(seasonTxt); seasonTxt.x = 40; seasonTxt.y = 0; // Helper to update season text function updateSeasonText() { if (seasonTxt && typeof seasonTxt.setText === "function") { seasonTxt.setText("Season: " + getCurrentSeason()); if (typeof seasonTxt.setStyle === "function") seasonTxt.setStyle({ fill: "#000" }); } } // --- Cycle Countdown Timer Text --- var cycleDurationMs = 50000; // 50,000 ms = 50 seconds var cycleTimeLeftMs = cycleDurationMs; var cycleTimerTxt = new Text2("Next cycle: 50s", { size: 60, fill: "#000" }); cycleTimerTxt.anchor.set(0.5, 0); LK.gui.top.addChild(cycleTimerTxt); cycleTimerTxt.x = 500; cycleTimerTxt.y = cycleTxt.y + 80; // Place under the cycle counter // Timer to increment cycle every 50 seconds var cycleInterval = null; // Will be started after first organism is placed var cycleCountdownInterval = null; // For updating the countdown text // Helper to set all on-screen text color to black function updateTextColors(bgColor) { var textColor = "#000"; if (cycleTxt && typeof cycleTxt.setStyle === "function") cycleTxt.setStyle({ fill: textColor }); if (cycleTimerTxt && typeof cycleTimerTxt.setStyle === "function") cycleTimerTxt.setStyle({ fill: textColor }); if (deleteBtnTxt && typeof deleteBtnTxt.setStyle === "function") deleteBtnTxt.setStyle({ fill: textColor }); if (skipCycleBtnTxt && typeof skipCycleBtnTxt.setStyle === "function") skipCycleBtnTxt.setStyle({ fill: textColor }); // Palette item labels for (var i = 0; i < paletteItems.length; i++) { for (var j = 0; j < paletteItems[i].children.length; j++) { var child = paletteItems[i].children[j]; if (child instanceof Text2 && typeof child.setStyle === "function") { child.setStyle({ fill: textColor }); } } } // Palette scroll arrows if (window.paletteLeftBtn) { for (var i = 0; i < window.paletteLeftBtn.children.length; i++) { var child = window.paletteLeftBtn.children[i]; if (child instanceof Text2 && typeof child.setStyle === "function") child.setStyle({ fill: textColor }); } } if (window.paletteRightBtn) { for (var i = 0; i < window.paletteRightBtn.children.length; i++) { var child = window.paletteRightBtn.children[i]; if (child instanceof Text2 && typeof child.setStyle === "function") child.setStyle({ fill: textColor }); } } } function startCycleInterval() { if (cycleInterval !== null) return; cycleTimeLeftMs = cycleDurationMs; cycleTimerTxt.setText("Next cycle: " + Math.ceil(cycleTimeLeftMs / 1000) + "s"); cycleInterval = LK.setInterval(function () { cycleCount++; cycleTxt.setText("Cycle: " + cycleCount); cycleTimeLeftMs = cycleDurationMs; cycleTimerTxt.setText("Next cycle: " + Math.ceil(cycleTimeLeftMs / 1000) + "s"); // --- Streak Counter Logic --- // If skip was used this cycle, reset streak and clear flag if (streakSkipThisCycle) { resetStreak(); streakSkipThisCycle = false; } else { // If at least one organism is alive, increment streak, else reset var anyAlive = plants.length > 0 || herbivores.length > 0 || carnivores.length > 0 || pollinators.length > 0 || fungis.length > 0 || parasites.length > 0 || worms.length > 0; if (anyAlive) { incrementStreak(); } else { resetStreak(); } } // Reset tired attempts for all carnivores at the start of each cycle for (var i = 0; i < carnivores.length; i++) { if (carnivores[i] && carnivores[i]._tiredAttempts) { carnivores[i]._tiredAttempts = {}; } } // --- Extinction: If no plants, kill all herbivores, pollinators, and carnivores --- if (plants.length === 0) { // Herbivores for (var i = herbivores.length - 1; i >= 0; i--) { var h = herbivores[i]; if (typeof h.die === "function") h.die();else if (typeof h.destroy === "function") h.destroy(); herbivores.splice(i, 1); } // Pollinators for (var i = pollinators.length - 1; i >= 0; i--) { var p = pollinators[i]; if (typeof p.die === "function") p.die();else if (typeof p.destroy === "function") p.destroy(); pollinators.splice(i, 1); } // Carnivores for (var i = carnivores.length - 1; i >= 0; i--) { var c = carnivores[i]; if (typeof c.die === "function") c.die();else if (typeof c.destroy === "function") c.destroy(); carnivores.splice(i, 1); } // --- Make randomize button available on total extinction --- if (typeof randomizeBtn !== "undefined") { // Check if all organisms are dead (plants, herbivores, carnivores, pollinators, fungis, parasites, worms) var allGone = plants.length === 0 && herbivores.length === 0 && carnivores.length === 0 && pollinators.length === 0 && fungis.length === 0 && parasites.length === 0 && worms.length === 0; if (allGone) { randomizeBtn._disabled = false; randomizeBtnBox.color = 0x228B22; if (typeof randomizeBtnBox.setStyle === "function") { randomizeBtnBox.setStyle({ fill: "#fff" }); } randomizeBtnTxt.setText("Randomize"); } else { // If any organisms are present, keep randomize button disabled randomizeBtn._disabled = true; randomizeBtnBox.color = 0xcc2222; if (typeof randomizeBtnBox.setStyle === "function") { randomizeBtnBox.setStyle({ fill: "#fff" }); } randomizeBtnTxt.setText("Randomized"); } } } // Set background color every 3 cycles: green, yellow, brown, white, repeat (spring, summer, autumn, winter) var mod = cycleCount % 12; if (mod === 0) { // SPRING game.setBackgroundColor(0x2ecc40); // green updateTextColors(0x2ecc40); pollinatorsHibernating = false; window.summerState = false; window.springState = true; window.winterState = false; window.autumnState = false; // Play mellow music LK.playMusic('mellow'); // --- YEAR COUNTER: Increment year after winter ends (when entering spring) --- if (cycleCount > 0) { yearCount++; if (yearTxt && typeof yearTxt.setText === "function") { yearTxt.setText("Year: " + yearCount); } } // Restore plant color to normal (green) and allow dying for (var i = 0; i < plants.length; i++) { if (plants[i] && plants[i].plantAsset && typeof plants[i].plantAsset.tint !== "undefined") { plants[i].plantAsset.tint = 0xffffff; } plants[i]._canDie = true; } // --- SPRING: Hatch all parasite eggs (eggs with _isParasiteEgg and _parasiteHatch) --- for (var i = eggs.length - 1; i >= 0; i--) { var e = eggs[i]; if (e && e._isParasiteEgg && e._parasiteHatch && !e.hatched) { // Instantly hatch the egg into a parasite e.hatched = true; // Animate egg hatching tween(e, { scaleX: 1.2, scaleY: 1.2, alpha: 0.5 }, { duration: 200, easing: tween.easeOut, onFinish: function (egg) { return function () { var parasite = new Parasite(); parasite.x = egg.x; parasite.y = egg.y; parasite.scaleX = 1; parasite.scaleY = 1; parasite.alpha = 1; if (egg.parent) egg.parent.addChild(parasite); if (typeof parasites !== "undefined") parasites.push(parasite); egg.destroy(); }; }(e) }); eggs.splice(i, 1); } } // --- Resume herbivore movement after winter ends --- for (var i = 0; i < herbivores.length; i++) { if (herbivores[i] && typeof herbivores[i].speed === "number") { herbivores[i].speed = 1.2 + Math.random() * 1.0; } } // --- Resume carnivore hunting after winter ends --- (Ava) // Reset carnivore state to "hungry" so they resume hunting in spring for (var i = 0; i < carnivores.length; i++) { if (carnivores[i]) { carnivores[i].state = "hungry"; carnivores[i]._tired = false; carnivores[i]._tiredTimer = 0; carnivores[i]._chaseTime = 0; carnivores[i].target = null; } } updateSeasonText(); } else if (mod === 3) { // SUMMER game.setBackgroundColor(0xffff00); // yellow updateTextColors(0xffff00); pollinatorsHibernating = false; window.summerState = true; window.winterState = false; window.springState = false; window.autumnState = false; // Plants can die, but keep their color normal for (var i = 0; i < plants.length; i++) { if (plants[i] && plants[i].plantAsset && typeof plants[i].plantAsset.tint !== "undefined") { plants[i].plantAsset.tint = 0xffffff; } plants[i]._canDie = true; } updateSeasonText(); } else if (mod === 6) { // AUTUMN game.setBackgroundColor(0x8B5A2B); // brown updateTextColors(0x8B5A2B); pollinatorsHibernating = false; window.summerState = false; window.springState = false; window.winterState = false; window.autumnState = true; // Make all plants brown and allow dying for (var i = 0; i < plants.length; i++) { if (plants[i] && plants[i].plantAsset && typeof plants[i].plantAsset.tint !== "undefined") { plants[i].plantAsset.tint = 0x996633; // brown } plants[i]._canDie = true; plants[i]._isBrown = true; } updateSeasonText(); } else if (mod === 9) { // WINTER game.setBackgroundColor(0xffffff); // white updateTextColors(0xffffff); pollinatorsHibernating = true; window.summerState = false; window.springState = false; window.winterState = true; window.autumnState = false; // --- WINTER: Parasites die and lay eggs before death --- for (var i = parasites.length - 1; i >= 0; i--) { var parasite = parasites[i]; if (parasite && !parasite.eaten) { // Lay a parasite egg at current position before dying var egg = new Egg(); egg.x = parasite.x; egg.y = parasite.y; egg.scaleX = 0.5; egg.scaleY = 0.5; egg.alpha = 1; egg._isParasiteEgg = true; egg._parasiteHatch = true; if (parasite.parent) parasite.parent.addChild(egg); if (typeof eggs !== "undefined") eggs.push(egg); // Animate parasite dying and remove from array tween(parasite, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function (p) { return function () { if (typeof parasites !== "undefined") { var idx = parasites.indexOf(p); if (idx !== -1) parasites.splice(idx, 1); } p.destroy(); }; }(parasite) }); } } // Kill all brown plants when winter starts for (var i = plants.length - 1; i >= 0; i--) { var p = plants[i]; // Only kill brown plants; healthy plants do not die in winter if (p && p._isBrown && !p.eaten && typeof p.die === "function") { p.die(); } } // Make all plants blue and prevent dying for (var i = 0; i < plants.length; i++) { if (plants[i] && plants[i].plantAsset && typeof plants[i].plantAsset.tint !== "undefined") { plants[i].plantAsset.tint = 0x3399ff; } plants[i]._canDie = false; } // Cull a random portion of eggs during winter to reduce lag if (eggs && eggs.length > 0) { // Remove up to 40% of eggs at random, but always leave at least 3 eggs if possible var eggsToCull = Math.floor(eggs.length * 0.4); var minEggsLeft = 3; if (eggs.length - eggsToCull < minEggsLeft) { eggsToCull = Math.max(0, eggs.length - minEggsLeft); } for (var i = 0; i < eggsToCull; i++) { // Pick a random egg index var idx = Math.floor(Math.random() * eggs.length); var e = eggs[idx]; if (e && typeof e.destroy === "function") { e.destroy(); } eggs.splice(idx, 1); } } updateSeasonText(); } }, cycleDurationMs); // Start countdown updater (every 100ms for smoothness) if (cycleCountdownInterval === null) { cycleCountdownInterval = LK.setInterval(function () { if (cycleTimeLeftMs > 0) { cycleTimeLeftMs -= 100; if (cycleTimeLeftMs < 0) cycleTimeLeftMs = 0; cycleTimerTxt.setText("Next cycle: " + Math.ceil(cycleTimeLeftMs / 1000) + "s"); } }, 100); } } // --- End of Game Code ---;; // --- Asset Initialization --- // new carnivore egg texture var plants = []; var herbivores = []; // --- Herbivore Hunger Queue: Only 2 can be hungry at a time --- var herbivoreHungerQueue = []; // Array of herbivores waiting to be hungry var carnivores = []; var seeds = []; var eggs = []; // Track all eggs globally var pollinators = []; var fungis = []; // Track all fungi globally var parasites = []; // Track all parasites globally var fungiSpores = []; // Track all spores shot by fungi // --- Worms spawned at game start --- // If you want worms to be present at game start, add them here and set them buried until the next season var worms = []; // Worms are not spawned at the top of the screen at game start // --- Palette UI --- var paletteY = 180; // y position for palette var paletteSpacing = 260; var paletteItems = []; // --- Delete Mode Button --- var deleteMode = false; var deleteBtn = new Container(); var deleteBtnBox = deleteBtn.attachAsset('box', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 100, color: 0xcc3333, shape: 'box' }); var deleteBtnTxt = new Text2("Delete: OFF", { size: 54, fill: "#000" }); deleteBtnTxt.anchor.set(0.5, 0.5); deleteBtn.addChild(deleteBtnTxt); // Place delete button under the cycle counter and timer deleteBtn.x = 500; deleteBtn.y = cycleTimerTxt.y + 140; // Move further down (was 90px, now 140px below the timer text) deleteBtn.interactive = true; deleteBtn.visible = true; deleteBtn.down = function () { var clickSnd = LK.getSound('Click'); if (clickSnd && typeof clickSnd.play === "function") clickSnd.play(); deleteMode = !deleteMode; deleteBtnBox.color = deleteMode ? 0xff4444 : 0xcc3333; deleteBtnTxt.setText(deleteMode ? "Delete: ON" : "Delete: OFF"); }; LK.gui.top.addChild(deleteBtn); // --- Skip Cycle Button --- var skipCycleBtn = new Container(); var skipCycleBtnBox = skipCycleBtn.attachAsset('box', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 100, color: 0x3388cc, shape: 'box' }); var skipCycleBtnTxt = new Text2("Skip Cycle", { size: 54, fill: "#000" }); skipCycleBtnTxt.anchor.set(0.5, 0.5); skipCycleBtn.addChild(skipCycleBtnTxt); // Place skip button under the delete button skipCycleBtn.x = 500; skipCycleBtn.y = deleteBtn.y + 120; skipCycleBtn.interactive = true; skipCycleBtn.visible = true; // --- Skip Cycle Cooldown State --- var skipCycleCooldownActive = false; var skipCycleCooldownTimeout = null; // --- Skip Cycle Cooldown Counter Text --- var skipCycleCountdownTxt = new Text2("", { size: 54, fill: "#fff" }); skipCycleCountdownTxt.anchor.set(0, 0.5); // Position to the right of the skip button (visually next to it) skipCycleCountdownTxt.x = 130; skipCycleCountdownTxt.y = 0; skipCycleCountdownTxt.visible = false; skipCycleBtn.addChild(skipCycleCountdownTxt); var skipCycleCountdownInterval = null; var skipCycleCountdownValue = 0; // --- Extinction Button --- var extinctionBtn = new Container(); var extinctionBtnBox = extinctionBtn.attachAsset('box', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 100, color: 0x555555, shape: 'box' }); var extinctionBtnTxt = new Text2("Extinction", { size: 54, fill: "#000" }); extinctionBtnTxt.anchor.set(0.5, 0.5); extinctionBtn.addChild(extinctionBtnTxt); // Place extinction button under the skip cycle button extinctionBtn.x = 500; extinctionBtn.y = skipCycleBtn.y + 120; extinctionBtn.interactive = true; extinctionBtn.visible = true; LK.gui.top.addChild(extinctionBtn); // --- Achievements Button and Menu removed --- // --- Randomize Button --- var randomizeBtn = new Container(); var randomizeBtnBox = randomizeBtn.attachAsset('box', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 100, color: 0x228B22, shape: 'box' }); var randomizeBtnTxt = new Text2("Randomize", { size: 54, fill: "#000" }); randomizeBtnTxt.anchor.set(0.5, 0.5); randomizeBtn.addChild(randomizeBtnTxt); // Place randomize button under the extinction button randomizeBtn.x = 500; randomizeBtn.y = extinctionBtn.y + 120; randomizeBtn.interactive = true; randomizeBtn.visible = true; LK.gui.top.addChild(randomizeBtn); // --- Personality Reveal Button --- var personalityBtn = new Container(); var personalityBtnBox = personalityBtn.attachAsset('box', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 100, color: 0x8e44ad, shape: 'box' }); var personalityBtnTxt = new Text2("Personality", { size: 54, fill: "#000" }); personalityBtnTxt.anchor.set(0.5, 0.5); personalityBtn.addChild(personalityBtnTxt); // Place personality button under the randomize button personalityBtn.x = 500; personalityBtn.y = randomizeBtn.y + 120; personalityBtn.interactive = true; personalityBtn.visible = true; LK.gui.top.addChild(personalityBtn); // --- Personality Reveal Text Above Each Organism --- var personalityPopup = null; var personalityTextNodes = []; // Track all personality text objects // --- Streak Counter UI and Variables --- // Streak: counts how many cycles at least one organism is alive, resets if all die or if you skip a cycle var streakCount = 0; var streakTxt = new Text2("Streak: 0", { size: 60, fill: "#000" }); streakTxt.anchor.set(0.5, 0.5); // Place streak counter under the personality button (y + 120) streakTxt.x = personalityBtn.x; streakTxt.y = personalityBtn.y + 120; LK.gui.top.addChild(streakTxt); // --- Score Counter UI and Variables --- var scoreCount = 0; var scoreTxt = new Text2("Score: 0", { size: 60, fill: "#000" }); scoreTxt.anchor.set(0.5, 0.5); // Place score counter under the streak counter scoreTxt.x = streakTxt.x; scoreTxt.y = streakTxt.y + 80; LK.gui.top.addChild(scoreTxt); // Helper to update score text function updateScoreText() { if (scoreTxt && typeof scoreTxt.setText === "function") { scoreTxt.setText("Score: " + scoreCount); if (typeof scoreTxt.setStyle === "function") scoreTxt.setStyle({ fill: "#000" }); } } // --- Retract/Expand Streak and Score Counter with Top Buttons --- // Patch updateTopButtonsRetracted to also move/hide streakTxt and scoreTxt var _originalUpdateTopButtonsRetracted = updateTopButtonsRetracted; updateTopButtonsRetracted = function updateTopButtonsRetracted() { if (topButtonsRetracted) { // Move streak counter offscreen and hide var offX = 2048 + 300; tween(streakTxt, { x: offX }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { streakTxt.visible = false; } }); // Move score counter offscreen and hide tween(scoreTxt, { x: offX }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { scoreTxt.visible = false; } }); } else { // Move streak counter back and show streakTxt.visible = true; tween(streakTxt, { x: 500 }, { duration: 200, easing: tween.easeOut }); // Move score counter back and show scoreTxt.visible = true; tween(scoreTxt, { x: 500 }, { duration: 200, easing: tween.easeOut }); } // Call original for other buttons if (typeof _originalUpdateTopButtonsRetracted === "function") { _originalUpdateTopButtonsRetracted(); } }; // Track if a skip happened this cycle (reset streak if so) var streakSkipThisCycle = false; // Helper to update streak text function updateStreakText() { if (streakTxt && typeof streakTxt.setText === "function") { streakTxt.setText("Streak: " + streakCount); if (typeof streakTxt.setStyle === "function") streakTxt.setStyle({ fill: "#000" }); } } function resetStreak() { streakCount = 0; updateStreakText(); // Reset score to 0 when streak is reset scoreCount = 0; updateScoreText(); // Play the 'Lost' sound every time the streak is reset var lostSnd = LK.getSound('Lost'); if (lostSnd && typeof lostSnd.play === "function") lostSnd.play(); // Show "streak lost!" text in the middle of the screen in red for 5 seconds if (typeof window._streakLostTxt !== "undefined" && window._streakLostTxt && window._streakLostTxt.parent) { window._streakLostTxt.parent.removeChild(window._streakLostTxt); if (typeof window._streakLostTxt.destroy === "function") window._streakLostTxt.destroy(); window._streakLostTxt = null; } var streakLostTxt = new Text2("streak lost!", { size: 160, fill: "#c00", font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); streakLostTxt.anchor.set(0.5, 0.5); streakLostTxt.x = 2048 / 2; streakLostTxt.y = 2732 / 2; game.addChild(streakLostTxt); window._streakLostTxt = streakLostTxt; if (typeof window._streakLostTimeout !== "undefined" && window._streakLostTimeout) { LK.clearTimeout(window._streakLostTimeout); } window._streakLostTimeout = LK.setTimeout(function () { if (window._streakLostTxt && window._streakLostTxt.parent) { window._streakLostTxt.parent.removeChild(window._streakLostTxt); if (typeof window._streakLostTxt.destroy === "function") window._streakLostTxt.destroy(); } window._streakLostTxt = null; window._streakLostTimeout = null; }, 5000); } function incrementStreak() { streakCount++; updateStreakText(); // Increment score by 50 every time the streak increases scoreCount += 50; updateScoreText(); // Play the 'Streak' sound every time the streak counter increases var streakSnd = LK.getSound('Streak'); if (streakSnd && typeof streakSnd.play === "function") streakSnd.play(); } function showPersonalityPopup() { // Remove any previous personality text nodes if (personalityTextNodes && personalityTextNodes.length) { for (var i = 0; i < personalityTextNodes.length; i++) { var node = personalityTextNodes[i]; if (node && node.parent) node.parent.removeChild(node); if (node && typeof node.destroy === "function") node.destroy(); } personalityTextNodes = []; } // For each organism, show a floating text above it with its personality function addPersonalityTextFor(arr, getType, getPers) { for (var i = 0; i < arr.length; i++) { var org = arr[i]; if (!org || typeof org.x !== "number" || typeof org.y !== "number") continue; var type = getType(org); var pers = getPers(org); var txt = new Text2(pers, { size: 44, fill: pers === "N/A" ? "#888" : "#2c3e50" }); txt.anchor.set(0.5, 1); txt.x = org.x; txt.y = org.y - 70; txt._personalityTarget = org; txt._personalityType = type; game.addChild(txt); personalityTextNodes.push(txt); } } // Ferns (plants) addPersonalityTextFor(plants, function () { return "Fern"; }, function () { return "N/A"; }); // Nummer (herbivores) addPersonalityTextFor(herbivores, function () { return "Nummer"; }, function (org) { // Only show personality if actually placed (on map, not destroyed) if (typeof org.eaten !== "undefined" && org.eaten) return "N/A"; return org.personality || "N/A"; }); // Hunter (carnivores) addPersonalityTextFor(carnivores, function () { return "Hunter"; }, function (org) { // Only show personality if actually placed (on map, not destroyed) if (typeof org.eaten !== "undefined" && org.eaten) return "N/A"; return org.personality || "N/A"; }); // Pollinator addPersonalityTextFor(pollinators, function () { return "Pollinator"; }, function (org) { return org.personality || "N/A"; }); // Shroom (fungi) addPersonalityTextFor(fungis, function () { return "Shroom"; }, function () { return "N/A"; }); // Parasite addPersonalityTextFor(parasites, function () { return "Parasite"; }, function () { return "N/A"; }); // Worm addPersonalityTextFor(worms, function () { return "Worm"; }, function () { return "N/A"; }); // Patch game.update to keep personality text above each organism while visible if (!game._personalityTextPatched) { var _originalGameUpdate = game.update; game.update = function () { // Move each personality text to follow its organism if (personalityTextNodes && personalityTextNodes.length) { for (var i = personalityTextNodes.length - 1; i >= 0; i--) { var txt = personalityTextNodes[i]; var org = txt && txt._personalityTarget; if (!org || typeof org.x !== "number" || typeof org.y !== "number" || org.eaten) { // Remove text if organism is gone if (txt.parent) txt.parent.removeChild(txt); if (typeof txt.destroy === "function") txt.destroy(); personalityTextNodes.splice(i, 1); continue; } txt.x = org.x; txt.y = org.y - 70; } } if (_originalGameUpdate) _originalGameUpdate.apply(this, arguments); }; game._personalityTextPatched = true; } // Add a close button in the top right to remove all personality text if (!window._personalityTextCloseBtn) { var closeBtn = new Container(); var closeBtnBox = closeBtn.attachAsset('box', { anchorX: 0.5, anchorY: 0.5, width: 180, height: 80, color: 0x8e44ad, shape: 'box' }); var closeBtnTxt = new Text2("Hide", { size: 44, fill: "#fff" }); closeBtnTxt.anchor.set(0.5, 0.5); closeBtn.addChild(closeBtnTxt); closeBtn.x = 2048 - 200; closeBtn.y = 180; closeBtn.interactive = true; closeBtn.visible = true; closeBtn.down = function () { // Remove all personality text nodes if (personalityTextNodes && personalityTextNodes.length) { for (var i = 0; i < personalityTextNodes.length; i++) { var node = personalityTextNodes[i]; if (node && node.parent) node.parent.removeChild(node); if (node && typeof node.destroy === "function") node.destroy(); } personalityTextNodes = []; } if (closeBtn && closeBtn.parent) closeBtn.parent.removeChild(closeBtn); window._personalityTextCloseBtn = null; }; LK.gui.top.addChild(closeBtn); window._personalityTextCloseBtn = closeBtn; } } // Button handler // Track if personality text is currently shown var personalityTextVisible = false; personalityBtn.down = function () { var clickSnd = LK.getSound('Click'); if (clickSnd && typeof clickSnd.play === "function") clickSnd.play(); if (personalityTextVisible) { // Hide all personality text nodes if (personalityTextNodes && personalityTextNodes.length) { for (var i = 0; i < personalityTextNodes.length; i++) { var node = personalityTextNodes[i]; if (node && node.parent) node.parent.removeChild(node); if (node && typeof node.destroy === "function") node.destroy(); } personalityTextNodes = []; } // Remove close button if present if (window._personalityTextCloseBtn) { if (window._personalityTextCloseBtn.parent) window._personalityTextCloseBtn.parent.removeChild(window._personalityTextCloseBtn); window._personalityTextCloseBtn = null; } personalityTextVisible = false; } else { showPersonalityPopup(); personalityTextVisible = true; } }; // --- Randomize Button Handler --- randomizeBtn.down = function () { var clickSnd = LK.getSound('Click'); if (clickSnd && typeof clickSnd.play === "function") clickSnd.play(); // Only randomize if not retracted or already disabled if (topButtonsRetracted || randomizeBtn._disabled) return; // Helper to get a random position in the map area function getRandomPos() { var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; return { x: minX + Math.random() * (maxX - minX), y: minY + Math.random() * (maxY - minY) }; } // Add 3-5 ferns var fernCount = 3 + Math.floor(Math.random() * 3); // 3,4,5 for (var i = 0; i < fernCount; i++) { var pos = getRandomPos(); var fern = new Plant(); fern.x = pos.x; fern.y = pos.y; fern.scaleX = 1; fern.scaleY = 1; fern.alpha = 1; plants.push(fern); game.addChild(fern); } // Add 3-5 herbivores var herbCount = 3 + Math.floor(Math.random() * 3); for (var i = 0; i < herbCount; i++) { var pos = getRandomPos(); var herb = new Nummer(); herb._uniqueId = "herb_" + Math.floor(Math.random() * 1e9) + "_" + Math.floor(Math.random() * 1e9); herb.x = pos.x; herb.y = pos.y; herb.scaleX = 1; herb.scaleY = 1; herb.alpha = 1; // Start in wandering state when spawned by randomize button herb.state = "wandering"; herb._wanderAfterSleepTimer = 600; // 10 seconds wandering // --- Randomize herbivore personality --- var personalities = ["normal", "curious", "grumpy"]; var rand = Math.random(); // Make random traits more common: 30% normal, 35% curious, 35% grumpy if (rand < 0.3) { herb.personality = "normal"; } else if (rand < 0.65) { herb.personality = "curious"; } else { herb.personality = "grumpy"; } herbivores.push(herb); game.addChild(herb); } // Add 3-5 pollinators var pollCount = 3 + Math.floor(Math.random() * 3); for (var i = 0; i < pollCount; i++) { var pos = getRandomPos(); var poll = new Pollinator(); poll.x = pos.x; poll.y = pos.y; poll.scaleX = 1; poll.scaleY = 1; poll.alpha = 1; pollinators.push(poll); game.addChild(poll); } // Add 0-1 carnivores var carnCount = Math.floor(Math.random() * 2); // 0 or 1 for (var i = 0; i < carnCount; i++) { var pos = getRandomPos(); var carn = new Carnivore(); // --- Randomize carnivore personality --- var carnPersonalities = ["normal", "bloodthirsty", "weak"]; var rand = Math.random(); // Make random traits more common: 30% normal, 35% bloodthirsty, 35% weak if (rand < 0.3) { carn.personality = "normal"; } else if (rand < 0.65) { carn.personality = "bloodthirsty"; } else { carn.personality = "weak"; } // Adjust speed for bloodthirsty carnivores if (carn.personality === "bloodthirsty") { carn.speed = 1.5 + Math.random() * 1.2 + 0.7 + Math.random() * 0.5; } carn.x = pos.x; carn.y = pos.y; carn.scaleX = 1.25; carn.scaleY = 1.25; carn.alpha = 1; carnivores.push(carn); game.addChild(carn); } // Play plop sound for feedback var plopSnd = LK.getSound('Plop'); if (plopSnd && typeof plopSnd.play === "function") plopSnd.play(); // --- Unlock skip cycle button when randomize is pressed --- skipCycleBtn._skipLocked = false; firstOrganismPlaced = true; // --- Restart the cycle timer when randomize is pressed --- if (cycleInterval !== null) { LK.clearInterval(cycleInterval); cycleInterval = null; } if (cycleCountdownInterval !== null) { LK.clearInterval(cycleCountdownInterval); cycleCountdownInterval = null; } cycleTimeLeftMs = cycleDurationMs; cycleTimerTxt.setText("Next cycle: " + Math.ceil(cycleTimeLeftMs / 1000) + "s"); startCycleInterval(); // --- Disable and color the randomize button red --- randomizeBtn._disabled = true; randomizeBtnBox.color = 0xcc2222; if (typeof randomizeBtnBox.setStyle === "function") { randomizeBtnBox.setStyle({ fill: "#fff" }); } randomizeBtnTxt.setText("Randomized"); // --- Disable randomize button if any organisms are present --- if (plants.length > 0 || herbivores.length > 0 || carnivores.length > 0 || pollinators.length > 0 || fungis.length > 0 || parasites.length > 0 || worms.length > 0) { randomizeBtn._disabled = true; randomizeBtnBox.color = 0xcc2222; if (typeof randomizeBtnBox.setStyle === "function") { randomizeBtnBox.setStyle({ fill: "#fff" }); } randomizeBtnTxt.setText("Randomized"); } }; // Asteroid extinction event handler // --- Extinction Event Rate Limiting --- // Track timestamps (in ms) of recent extinction events if (typeof window._extinctionEventTimes === "undefined") window._extinctionEventTimes = []; extinctionBtn.down = function () { var clickSnd = LK.getSound('Click'); if (clickSnd && typeof clickSnd.play === "function") clickSnd.play(); LK.getSound('explosion').play(); // Asteroid visual: big circle in center of map var asteroid = LK.getAsset('box', { anchorX: 0.5, anchorY: 0.5, width: 400, height: 400, color: 0x888888, shape: 'ellipse' }); asteroid.x = 124 + 1800 / 2; asteroid.y = paletteY + 320 + 2000 / 2; asteroid.alpha = 0.0; game.addChild(asteroid); // Animate asteroid fade in, then out tween(asteroid, { alpha: 1 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { tween(asteroid, { alpha: 0 }, { duration: 700, easing: tween.easeOut, onFinish: function onFinish() { asteroid.destroy(); } }); } }); // Flash screen for dramatic effect LK.effects.flashScreen(0xff6600, 800); // --- Extinction Event Rate Limiting Logic --- // Add current timestamp to the event list var now = Date.now(); window._extinctionEventTimes.push(now); // Only keep events in the last 10 seconds (10000 ms) var cutoff = now - 10000; for (var i = window._extinctionEventTimes.length - 1; i >= 0; i--) { if (window._extinctionEventTimes[i] < cutoff) { window._extinctionEventTimes.splice(i, 1); } } // If more than 3 extinction events in last 10 seconds, kill all creatures if (window._extinctionEventTimes.length > 3) { // Gather all organisms (plants, herbivores, carnivores, pollinators, fungis, seeds, eggs, worms, parasites) var allGroups = [plants, herbivores, carnivores, pollinators, fungis, seeds, eggs, worms, parasites]; for (var g = 0; g < allGroups.length; g++) { var arr = allGroups[g]; for (var i = arr.length - 1; i >= 0; i--) { var org = arr[i]; arr.splice(i, 1); if (org && typeof org.die === "function") { org.die(); } else if (org && typeof org.destroy === "function") { org.destroy(); } } } // Optionally, flash the screen again for drama LK.effects.flashScreen(0x000000, 1200); // Clear the event times to prevent repeated wipes window._extinctionEventTimes = []; return; } // Gather all organisms (plants, herbivores, carnivores, pollinators, fungis, seeds, eggs, worms, parasites) var allGroups = [plants, herbivores, carnivores, pollinators, fungis, seeds, eggs, worms, parasites]; for (var g = 0; g < allGroups.length; g++) { var arr = allGroups[g]; if (arr.length === 0) continue; // If the group has 3 or fewer, kill 1 or 2 at random (never all) if (arr.length <= 3) { var toKill = Math.floor(Math.random() * arr.length); // 0, 1, or 2 for (var i = 0; i < toKill; i++) { if (arr.length === 0) break; var idx = Math.floor(Math.random() * arr.length); var org = arr[idx]; arr.splice(idx, 1); if (org && typeof org.die === "function") { org.die(); } else if (org && typeof org.destroy === "function") { org.destroy(); } } } else if (arr.length % 2 !== 0) { // If uneven, kill a random number between 1 and half the group (rounded down) var maxKill = Math.floor(arr.length / 2); var toKill = 1 + Math.floor(Math.random() * maxKill); for (var i = 0; i < toKill; i++) { if (arr.length === 0) break; var idx = Math.floor(Math.random() * arr.length); var org = arr[idx]; arr.splice(idx, 1); if (org && typeof org.die === "function") { org.die(); } else if (org && typeof org.destroy === "function") { org.destroy(); } } } else { // Even and more than 3: kill half (rounded down), random selection var survivors = Math.ceil(arr.length / 2); var toKill = arr.length - survivors; for (var i = 0; i < toKill; i++) { if (arr.length === 0) break; var idx = Math.floor(Math.random() * arr.length); var org = arr[idx]; arr.splice(idx, 1); if (org && typeof org.die === "function") { org.die(); } else if (org && typeof org.destroy === "function") { org.destroy(); } } } } }; // --- Prevent skipping cycle until first organism is placed --- var firstOrganismPlaced = false; skipCycleBtn._skipLocked = true; // Lock skipping at start skipCycleBtn.down = function () { var clickSnd = LK.getSound('Click'); if (clickSnd && typeof clickSnd.play === "function") clickSnd.play(); // --- Streak: Reset streak and play lost sound immediately when skip is pressed --- streakCount = 0; updateStreakText(); var lostSnd = LK.getSound('Lost'); if (lostSnd && typeof lostSnd.play === "function") lostSnd.play(); // --- Prevent skipping if all organisms are gone (plants, herbivores, carnivores, pollinators, fungis, parasites, worms) --- var allGone = plants.length === 0 && herbivores.length === 0 && carnivores.length === 0 && pollinators.length === 0 && fungis.length === 0 && parasites.length === 0 && worms.length === 0; if (allGone) { // --- Streak: Reset streak if all organisms die on skip --- resetStreak(); // Flash skip button red for 2 seconds var originalColor = skipCycleBtnBox.color; skipCycleBtnBox.color = 0xff4444; if (typeof skipCycleBtnBox.setStyle === "function") { skipCycleBtnBox.setStyle({ fill: "#fff" }); } LK.setTimeout(function () { skipCycleBtnBox.color = originalColor; if (typeof skipCycleBtnBox.setStyle === "function") { skipCycleBtnBox.setStyle({ fill: "#000" }); } }, 2000); return; } if (skipCycleBtn._skipLocked) { // Flash skip button red for 2 seconds var originalColor = skipCycleBtnBox.color; skipCycleBtnBox.color = 0xff4444; if (typeof skipCycleBtnBox.setStyle === "function") { skipCycleBtnBox.setStyle({ fill: "#fff" }); } // Restore color after 2 seconds LK.setTimeout(function () { skipCycleBtnBox.color = originalColor; if (typeof skipCycleBtnBox.setStyle === "function") { skipCycleBtnBox.setStyle({ fill: "#000" }); } }, 2000); return; } // --- 3 Second Cooldown Logic --- if (skipCycleCooldownActive) { // --- Streak: Reset streak if skip is attempted during cooldown (optional, but for safety) --- resetStreak(); // Flash skip button red for 1 second if on cooldown var originalColor = skipCycleBtnBox.color; skipCycleBtnBox.color = 0xff4444; if (typeof skipCycleBtnBox.setStyle === "function") { skipCycleBtnBox.setStyle({ fill: "#fff" }); } LK.setTimeout(function () { skipCycleBtnBox.color = originalColor; if (typeof skipCycleBtnBox.setStyle === "function") { skipCycleBtnBox.setStyle({ fill: "#000" }); } }, 1000); return; } skipCycleCooldownActive = true; skipCycleBtnBox.color = 0xaaaaaa; skipCycleBtnTxt.setText("Cooldown"); // Show and start the white countdown skipCycleCountdownValue = 3; skipCycleCountdownTxt.setText("" + skipCycleCountdownValue); skipCycleCountdownTxt.visible = true; // Clear any previous interval if (skipCycleCountdownInterval) { LK.clearInterval(skipCycleCountdownInterval); skipCycleCountdownInterval = null; } // Start interval to update countdown every 1s skipCycleCountdownInterval = LK.setInterval(function () { skipCycleCountdownValue--; if (skipCycleCountdownValue > 0) { skipCycleCountdownTxt.setText("" + skipCycleCountdownValue); } else { skipCycleCountdownTxt.visible = false; skipCycleCountdownTxt.setText(""); if (skipCycleCountdownInterval) { LK.clearInterval(skipCycleCountdownInterval); skipCycleCountdownInterval = null; } } }, 1000); // Restore after 3 seconds if (skipCycleCooldownTimeout) { LK.clearTimeout(skipCycleCooldownTimeout); } skipCycleCooldownTimeout = LK.setTimeout(function () { skipCycleCooldownActive = false; skipCycleBtnBox.color = 0x3388cc; skipCycleBtnTxt.setText("Skip Cycle"); if (typeof skipCycleBtnBox.setStyle === "function") { skipCycleBtnBox.setStyle({ fill: "#000" }); } // Hide the countdown if still visible skipCycleCountdownTxt.visible = false; skipCycleCountdownTxt.setText(""); if (skipCycleCountdownInterval) { LK.clearInterval(skipCycleCountdownInterval); skipCycleCountdownInterval = null; } }, 3000); // --- Streak: Mark that skip was used this cycle (will reset streak on next cycle) --- streakSkipThisCycle = true; // Increment cycle and update UI cycleCount++; cycleTxt.setText("Cycle: " + cycleCount); cycleTimeLeftMs = cycleDurationMs; cycleTimerTxt.setText("Next cycle: " + Math.ceil(cycleTimeLeftMs / 1000) + "s"); // --- Extinction: If no plants, kill all herbivores, pollinators, and carnivores --- if (plants.length === 0) { // Herbivores for (var i = herbivores.length - 1; i >= 0; i--) { var h = herbivores[i]; if (typeof h.die === "function") h.die();else if (typeof h.destroy === "function") h.destroy(); herbivores.splice(i, 1); } // Pollinators for (var i = pollinators.length - 1; i >= 0; i--) { var p = pollinators[i]; if (typeof p.die === "function") p.die();else if (typeof p.destroy === "function") p.destroy(); pollinators.splice(i, 1); } // Carnivores for (var i = carnivores.length - 1; i >= 0; i--) { var c = carnivores[i]; if (typeof c.die === "function") c.die();else if (typeof c.destroy === "function") c.destroy(); carnivores.splice(i, 1); } } // Set background color every 3 cycles: green, yellow, brown, white, repeat (spring, summer, autumn, winter) var mod = cycleCount % 12; if (mod === 0) { // SPRING game.setBackgroundColor(0x2ecc40); // green updateTextColors(0x2ecc40); pollinatorsHibernating = false; window.summerState = false; window.springState = true; window.winterState = false; window.autumnState = false; // Play mellow music LK.playMusic('mellow'); // --- YEAR COUNTER: Increment year after winter ends (when entering spring) --- if (cycleCount > 0) { yearCount++; if (yearTxt && typeof yearTxt.setText === "function") { yearTxt.setText("Year: " + yearCount); } } for (var i = 0; i < plants.length; i++) { if (plants[i] && plants[i].plantAsset && typeof plants[i].plantAsset.tint !== "undefined") { plants[i].plantAsset.tint = 0xffffff; } plants[i]._canDie = true; } for (var i = 0; i < herbivores.length; i++) { if (herbivores[i] && typeof herbivores[i].speed === "number") { herbivores[i].speed = 1.2 + Math.random() * 1.0; } } for (var i = 0; i < carnivores.length; i++) { if (carnivores[i]) { carnivores[i].state = "hungry"; carnivores[i]._tired = false; carnivores[i]._tiredTimer = 0; carnivores[i]._chaseTime = 0; carnivores[i].target = null; } } updateSeasonText(); } else if (mod === 3) { // SUMMER game.setBackgroundColor(0xffff00); // yellow updateTextColors(0xffff00); pollinatorsHibernating = false; window.summerState = true; window.winterState = false; window.springState = false; window.autumnState = false; for (var i = 0; i < plants.length; i++) { if (plants[i] && plants[i].plantAsset && typeof plants[i].plantAsset.tint !== "undefined") { plants[i].plantAsset.tint = 0xffffff; } plants[i]._canDie = true; } updateSeasonText(); } else if (mod === 6) { // AUTUMN game.setBackgroundColor(0x8B5A2B); // brown updateTextColors(0x8B5A2B); pollinatorsHibernating = false; window.summerState = false; window.springState = false; window.winterState = false; window.autumnState = true; for (var i = 0; i < plants.length; i++) { if (plants[i] && plants[i].plantAsset && typeof plants[i].plantAsset.tint !== "undefined") { plants[i].plantAsset.tint = 0x996633; // brown } plants[i]._canDie = true; plants[i]._isBrown = true; } updateSeasonText(); } else if (mod === 9) { // WINTER game.setBackgroundColor(0xffffff); // white updateTextColors(0xffffff); pollinatorsHibernating = true; window.summerState = false; window.springState = false; window.winterState = true; window.autumnState = false; for (var i = plants.length - 1; i >= 0; i--) { var p = plants[i]; if (p && p._isBrown && !p.eaten && typeof p.die === "function") { p.die(); } } for (var i = 0; i < plants.length; i++) { if (plants[i] && plants[i].plantAsset && typeof plants[i].plantAsset.tint !== "undefined") { plants[i].plantAsset.tint = 0x3399ff; } plants[i]._canDie = false; } updateSeasonText(); } // Randomize all organism positions except fungi, plants, and lichen var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; // Helper to randomize array of organisms function randomizeOrganisms(arr) { for (var i = 0; i < arr.length; i++) { var org = arr[i]; // If this is a host with a parasite attached, skip randomizing its position if (org && typeof org.x === "number" && typeof org.y === "number" && !org.eaten && !org._parasiteAttached // Only randomize if not hosting a parasite ) { org.x = minX + Math.random() * (maxX - minX); org.y = minY + Math.random() * (maxY - minY); } } } // When randomizing parasites, only randomize those not attached to a host function randomizeFreeParasites(arr) { for (var i = 0; i < arr.length; i++) { var parasite = arr[i]; if (parasite && typeof parasite.x === "number" && typeof parasite.y === "number" && !parasite.eaten && parasite.state !== "attached") { parasite.x = minX + Math.random() * (maxX - minX); parasite.y = minY + Math.random() * (maxY - minY); } } } randomizeOrganisms(herbivores); randomizeOrganisms(carnivores); randomizeOrganisms(pollinators); randomizeOrganisms(seeds); randomizeOrganisms(eggs); randomizeOrganisms(worms); randomizeFreeParasites(parasites); // Fungi, plants, and lichen are NOT moved // (fungis, plants, window.lichens) }; // --- Patch createOrganism to unlock skip after first organism placed --- var _originalCreateOrganism = createOrganism; createOrganism = function createOrganism(type, x, y) { var obj = _originalCreateOrganism(type, x, y); if (!firstOrganismPlaced) { firstOrganismPlaced = true; skipCycleBtn._skipLocked = false; } return obj; }; // --- Retract/Expand Widget for Top Buttons --- // State for retraction var topButtonsRetracted = false; // Widget button (a small round button at the right of the top buttons) var retractWidget = new Container(); var retractWidgetBox = retractWidget.attachAsset('box', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90, color: 0x888888, shape: 'ellipse' }); var retractWidgetTxt = new Text2("≡", { size: 60, fill: "#fff" }); retractWidgetTxt.anchor.set(0.5, 0.5); retractWidget.addChild(retractWidgetTxt); // Place widget to the right of the top buttons retractWidget.x = 300; retractWidget.y = deleteBtn.y + 50; retractWidget.interactive = true; retractWidget.visible = true; // Helper to update button visibility/position function updateTopButtonsRetracted() { if (topButtonsRetracted) { // Move buttons offscreen right, hide text, and set visible to false var offX = 2048 + 300; tween(skipCycleBtn, { x: offX }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { skipCycleBtn.visible = false; } }); tween(deleteBtn, { x: offX }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { deleteBtn.visible = false; } }); tween(extinctionBtn, { x: offX }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { extinctionBtn.visible = false; } }); // Hide randomize button as well if (typeof randomizeBtn !== "undefined") { tween(randomizeBtn, { x: offX }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { randomizeBtn.visible = false; } }); } // Hide personality button as well if (typeof personalityBtn !== "undefined") { tween(personalityBtn, { x: offX }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { personalityBtn.visible = false; } }); } retractWidgetTxt.setText("▶"); // Also move retractWidget to the right when retracted tween(retractWidget, { x: 700 //{Fe} // Move even further right }, { duration: 200, easing: tween.easeIn }); } else { // Move buttons back to original positions and set visible to true tween(retractWidget, { x: 300 }, { duration: 200, easing: tween.easeOut }); skipCycleBtn.visible = true; deleteBtn.visible = true; extinctionBtn.visible = true; if (typeof randomizeBtn !== "undefined") { randomizeBtn.visible = true; tween(randomizeBtn, { x: 500 }, { duration: 200, easing: tween.easeOut }); } // Show and move personality button back if (typeof personalityBtn !== "undefined") { personalityBtn.visible = true; tween(personalityBtn, { x: 500 }, { duration: 200, easing: tween.easeOut }); } tween(skipCycleBtn, { x: 500 }, { duration: 200, easing: tween.easeOut }); tween(deleteBtn, { x: 500 }, { duration: 200, easing: tween.easeOut }); tween(extinctionBtn, { x: 500 }, { duration: 200, easing: tween.easeOut }); retractWidgetTxt.setText("≡"); } } // Widget click toggles retraction retractWidget.down = function () { var clickSnd = LK.getSound('Click'); if (clickSnd && typeof clickSnd.play === "function") clickSnd.play(); topButtonsRetracted = !topButtonsRetracted; // Animate widget itself to the right when retracting, back to original when expanding if (topButtonsRetracted) { tween(retractWidget, { x: 500 }, { duration: 200, easing: tween.easeIn }); } else { tween(retractWidget, { x: 300 }, { duration: 200, easing: tween.easeOut }); } updateTopButtonsRetracted(); }; // Add widget to GUI LK.gui.top.addChild(skipCycleBtn); LK.gui.top.addChild(extinctionBtn); LK.gui.top.addChild(randomizeBtn); LK.gui.top.addChild(retractWidget); // On game start, ensure buttons are expanded updateTopButtonsRetracted(); // Shift the palette even further left so it is not offscreen var paletteTotalWidth = paletteSpacing * 2; var paletteXStart = 2048 / 2 - paletteTotalWidth / 2 - 1300; // Create palette items for drag-and-drop function createPaletteItem(type, x, y) { var assetId, color, label; if (type === 'plant') { assetId = 'plant'; color = 0x6adf60; label = 'Fern'; } else if (type === 'herbivore') { assetId = 'herbivore'; color = 0xf7e26b; label = 'Nummer'; } else if (type === 'pollinator') { assetId = 'pollinator'; color = 0xffc300; label = 'Pollinator'; } else if (type === 'fungi') { assetId = 'fungi'; color = 0xbb88ff; label = 'Shroom'; } else if (type === 'parasite') { assetId = 'Parasite'; color = 0x8888ff; label = 'Parasite'; } else if (type === 'worm') { assetId = 'Worm'; color = 0x888888; label = 'Worm'; } else { assetId = 'carnivore'; color = 0xe74c3c; label = 'Hunter'; } ; // Update all eggs for (var i = eggs.length - 1; i >= 0; i--) { var e = eggs[i]; if (!e.parent || e.hatched) { eggs.splice(i, 1); continue; } if (e.update) e.update(); } ; // Update all pollinators for (var i = pollinators.length - 1; i >= 0; i--) { var p = pollinators[i]; if (p.eaten) { pollinators.splice(i, 1); continue; } if (p.update) p.update(); } var node = new Container(); var icon; if (type === 'parasite') { icon = node.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); } else { icon = node.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); } node.x = x; node.y = y; node.type = type; // --- Population Counter Above Icon --- var popCount = 0; if (type === 'plant') popCount = plants.length;else if (type === 'herbivore') popCount = herbivores.length;else if (type === 'carnivore') popCount = carnivores.length;else if (type === 'pollinator') popCount = pollinators.length;else if (type === 'fungi') popCount = fungis.length;else if (type === 'parasite') popCount = parasites.length; var popTxt = new Text2("" + popCount, { size: 54, fill: "#000" }); popTxt.anchor.set(0.5, 1); popTxt.y = -70; node.addChild(popTxt); node._popTxt = popTxt; // Store for update // Add label var txt = new Text2(label, { size: 60, fill: "#000" }); txt.anchor.set(0.5, 0); txt.y = 60; node.addChild(txt); // Add to GUI overlay (so it stays on top) LK.gui.top.addChild(node); paletteItems.push(node); return node; } // --- Palette Scroll Bar --- // All available organism types in palette var paletteTypes = ['plant', 'herbivore', 'carnivore', 'pollinator', 'fungi', 'parasite', 'worm']; var paletteScrollOffset = 0; // How much the palette is scrolled (in px) var paletteScrollMin = 0; var paletteScrollMax = Math.max(0, paletteTypes.length * paletteSpacing - paletteSpacing * 3); // Show 3 at a time // Remove old palette items if any for (var i = 0; i < paletteItems.length; i++) { if (paletteItems[i].parent) paletteItems[i].parent.removeChild(paletteItems[i]); } paletteItems = []; // Helper to render palette items based on scroll offset function renderPaletteItems() { // Remove all current palette items from GUI for (var i = 0; i < paletteItems.length; i++) { if (paletteItems[i].parent) paletteItems[i].parent.removeChild(paletteItems[i]); } paletteItems = []; // Only show up to 3 at a time, centered var visibleCount = 3; var startIdx = Math.floor(paletteScrollOffset / paletteSpacing); var offsetPx = paletteScrollOffset % paletteSpacing; for (var i = 0; i < visibleCount; i++) { var idx = startIdx + i; if (idx >= 0 && idx < paletteTypes.length) { var px = paletteXStart + i * paletteSpacing - offsetPx; var node = createPaletteItem(paletteTypes[idx], px, paletteY); paletteItems.push(node); } } // Draw left/right scroll buttons if (!window.paletteLeftBtn) { window.paletteLeftBtn = new Container(); var leftIcon = window.paletteLeftBtn.attachAsset('box', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 120, color: 0x444444, shape: 'box' }); var leftArrow = new Text2("<", { size: 80, fill: "#000" }); leftArrow.anchor.set(0.5, 0.5); leftArrow.x = 0; leftArrow.y = 0; window.paletteLeftBtn.addChild(leftArrow); window.paletteLeftBtn.x = paletteXStart - 120; window.paletteLeftBtn.y = paletteY; window.paletteLeftBtn.interactive = true; LK.gui.top.addChild(window.paletteLeftBtn); window.paletteLeftBtn.visible = false; } if (!window.paletteRightBtn) { window.paletteRightBtn = new Container(); var rightIcon = window.paletteRightBtn.attachAsset('box', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 120, color: 0x444444, shape: 'box' }); var rightArrow = new Text2(">", { size: 80, fill: "#000" }); rightArrow.anchor.set(0.5, 0.5); rightArrow.x = 0; rightArrow.y = 0; window.paletteRightBtn.addChild(rightArrow); window.paletteRightBtn.x = paletteXStart + paletteSpacing * visibleCount + 40; window.paletteRightBtn.y = paletteY; window.paletteRightBtn.interactive = true; LK.gui.top.addChild(window.paletteRightBtn); window.paletteRightBtn.visible = false; } // Show/hide buttons window.paletteLeftBtn.visible = paletteScrollOffset > paletteScrollMin; window.paletteRightBtn.visible = paletteScrollOffset < paletteScrollMax; } renderPaletteItems(); // --- Palette Scroll Button Handlers --- window.paletteLeftBtn.down = function () { var clickSnd = LK.getSound('Click'); if (clickSnd && typeof clickSnd.play === "function") clickSnd.play(); if (paletteScrollOffset > paletteScrollMin) { paletteScrollOffset -= paletteSpacing; if (paletteScrollOffset < paletteScrollMin) paletteScrollOffset = paletteScrollMin; renderPaletteItems(); } }; window.paletteRightBtn.down = function () { var clickSnd = LK.getSound('Click'); if (clickSnd && typeof clickSnd.play === "function") clickSnd.play(); if (paletteScrollOffset < paletteScrollMax) { paletteScrollOffset += paletteSpacing; if (paletteScrollOffset > paletteScrollMax) paletteScrollOffset = paletteScrollMax; renderPaletteItems(); } }; // --- End Palette Scroll Bar --- // --- Drag and Drop Logic --- var dragging = null; // {type, node, paletteRef} var dragOffsetX = 0, dragOffsetY = 0; // Helper: create organism at x,y function createOrganism(type, x, y) { var obj; if (type === 'plant') { obj = new Plant(); plants.push(obj); // If background is white, make plant blue and undying if (game.backgroundColor === 0xffffff) { if (obj.plantAsset && typeof obj.plantAsset.tint !== "undefined") { obj.plantAsset.tint = 0x3399ff; } obj._canDie = false; } // If placed in summer, make plant invulnerable for 10 seconds if (game.backgroundColor === 0xffff00 || window.summerState) { obj._invulnerable = true; obj._canDie = false; // Remove invulnerability after 10 seconds (600 frames) LK.setTimeout(function () { if (plants.indexOf(obj) !== -1) { obj._invulnerable = false; obj._canDie = true; } }, 10000); } } else if (type === 'herbivore') { obj = new Nummer(); obj._uniqueId = "herb_" + Math.floor(Math.random() * 1e9) + "_" + Math.floor(Math.random() * 1e9); herbivores.push(obj); } else if (type === 'pollinator') { obj = new Pollinator(); pollinators.push(obj); } else if (type === 'fungi') { obj = new Fungi(); // If fungi is spawned after the cycle has started, set its _bornCycle to the next cycle so it lives a full cycle if (typeof cycleCount !== "undefined") { obj._bornCycle = cycleCount + 1; } fungis.push(obj); } else if (type === 'parasite') { obj = new Parasite(); parasites.push(obj); // If spawned in winter, kill parasite immediately if (window.winterState) { // Animate parasite dying and remove from array tween(obj, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { if (typeof parasites !== "undefined") { var idx = parasites.indexOf(obj); if (idx !== -1) parasites.splice(idx, 1); } obj.destroy(); } }); } } else if (type === 'worm') { obj = new Worm(); worms.push(obj); // If spawned in autumn or spring, set buried state (handled in Worm class, but ensure correct state at spawn) if ((window.autumnState || window.springState) && obj) { obj.buried = true; if (obj._wormAsset) { obj.removeChild(obj._wormAsset); } obj._wormAsset = obj.attachAsset('Buried_worm', { anchorX: 0.5, anchorY: 0.5 }); } // If spawned in the first 2 cycles, set buried state if (typeof cycleCount !== "undefined" && cycleCount < 2 && obj) { obj.buried = true; if (obj._wormAsset) { obj.removeChild(obj._wormAsset); } obj._wormAsset = obj.attachAsset('Buried_worm', { anchorX: 0.5, anchorY: 0.5 }); } } else { obj = new Carnivore(); carnivores.push(obj); } obj.x = x; obj.y = y; obj.scaleX = 1; obj.scaleY = 1; obj.alpha = 1; game.addChild(obj); // Play plop sound when placing an organism from the palette var plopSnd = LK.getSound('Plop'); if (plopSnd && typeof plopSnd.play === "function") plopSnd.play(); // Start the cycle interval if not already started if (typeof startCycleInterval === "function") startCycleInterval(); // If personality button is enabled (personality text nodes are present), show personality text for this organism if (personalityTextNodes && personalityTextNodes.length > 0) { // Helper to add personality text for a single organism var addPersonalityTextForSingle = function addPersonalityTextForSingle(org, type, pers) { var txt = new Text2(pers, { size: 44, fill: pers === "N/A" ? "#888" : "#2c3e50" }); txt.anchor.set(0.5, 1); txt.x = org.x; txt.y = org.y - 70; txt._personalityTarget = org; txt._personalityType = type; game.addChild(txt); personalityTextNodes.push(txt); }; // Determine type and personality string var type = null, pers = "N/A"; if (obj instanceof Plant) { type = "Fern"; pers = "N/A"; } else if (obj instanceof Nummer) { type = "Nummer"; pers = obj.personality || "N/A"; } else if (obj instanceof Carnivore) { type = "Hunter"; pers = obj.personality || "N/A"; } else if (obj instanceof Pollinator) { type = "Pollinator"; pers = obj.personality || "N/A"; } else if (obj instanceof Fungi) { type = "Shroom"; pers = "N/A"; } else if (obj instanceof Parasite) { type = "Parasite"; pers = "N/A"; } else if (obj instanceof Worm) { type = "Worm"; pers = "N/A"; } addPersonalityTextForSingle(obj, type, pers); } return obj; } // Helper: check if point is inside a palette item function isInPalette(x, y, node) { var bounds = { x: node.x - 60, y: node.y - 60, w: 120, h: 120 }; return x >= bounds.x && x <= bounds.x + bounds.w && y >= bounds.y && y <= bounds.y + bounds.h; } // --- Event Handlers --- // Global button click sound handler: play click sound for any button on screen if (typeof window._globalButtonClickHandlerAttached === "undefined") { window._globalButtonClickHandlerAttached = true; // Patch LK.gui.top's down event to play click sound for any button var _originalGuiTopDown = LK.gui.top.down; LK.gui.top.down = function (x, y, obj) { var clickSnd = LK.getSound('Click'); if (clickSnd && typeof clickSnd.play === "function") clickSnd.play(); if (typeof _originalGuiTopDown === "function") _originalGuiTopDown.call(this, x, y, obj); }; // Patch LK.gui.bottom's down event to play click sound for any button var _originalGuiBottomDown = LK.gui.bottom.down; LK.gui.bottom.down = function (x, y, obj) { var clickSnd = LK.getSound('Click'); if (clickSnd && typeof clickSnd.play === "function") clickSnd.play(); if (typeof _originalGuiBottomDown === "function") _originalGuiBottomDown.call(this, x, y, obj); }; } // Down: start drag if on palette game.down = function (x, y, obj) { // If delete mode is ON, check if click is on an organism and delete it if (deleteMode) { // Include lichens in allOrganisms if present var allOrganisms = plants.concat(herbivores, carnivores, pollinators, fungis, parasites, worms); for (var i = allOrganisms.length - 1; i >= 0; i--) { var org = allOrganisms[i]; var dx = x - org.x; var dy = y - org.y; var dist = Math.sqrt(dx * dx + dy * dy); var isFungi = fungis.indexOf(org) !== -1; var canDelete = isFungi || !org.eaten; if (canDelete && dist < 60) { // Remove from correct array if (plants.indexOf(org) !== -1) { var idx = plants.indexOf(org); if (idx !== -1) plants.splice(idx, 1); } else if (herbivores.indexOf(org) !== -1) { var idx = herbivores.indexOf(org); if (idx !== -1) { // Remove from hunger queue if present var hqidx = herbivoreHungerQueue.indexOf(org); if (hqidx !== -1) { herbivoreHungerQueue.splice(hqidx, 1); // Next in queue (if any) becomes hungry for (var j = 0; j < herbivoreHungerQueue.length; j++) { var nextH = herbivoreHungerQueue[j]; if (nextH && nextH.state === "waiting") { nextH.state = "hungry"; nextH._hungerQueueState = "hungry"; break; } } } herbivores.splice(idx, 1); } } else if (carnivores.indexOf(org) !== -1) { var idx = carnivores.indexOf(org); if (idx !== -1) carnivores.splice(idx, 1); } else if (pollinators.indexOf(org) !== -1) { var idx = pollinators.indexOf(org); if (idx !== -1) pollinators.splice(idx, 1); } else if (fungis.indexOf(org) !== -1) { var idx = fungis.indexOf(org); if (idx !== -1) fungis.splice(idx, 1); } if (typeof org.die === "function") { org.die(); } else if (typeof org.destroy === "function") { org.destroy(); } return; } } // If not on an organism, do nothing in delete mode return; } // Check if touch/click is on a palette item for (var i = 0; i < paletteItems.length; i++) { var p = paletteItems[i]; // Convert GUI coordinates to game coordinates var guiPos = LK.gui.top.toLocal({ x: x, y: y }, game); if (isInPalette(guiPos.x, guiPos.y, p)) { // Start dragging a new organism dragging = { type: p.type, node: createOrganism(p.type, x, y), paletteRef: p }; dragOffsetX = 0; dragOffsetY = 0; // Make it semi-transparent while dragging dragging.node.alpha = 0.7; return; } } // If not on palette, check if on an organism to move it // Check if touch/click is on an organism to move it var found = false; var allOrganisms = plants.concat(herbivores, carnivores, pollinators, fungis, parasites, worms); for (var i = allOrganisms.length - 1; i >= 0; i--) { var org = allOrganisms[i]; // Only allow moving if not eaten and touch is within 60px radius of center var dx = x - org.x; var dy = y - org.y; var dist = Math.sqrt(dx * dx + dy * dy); var isFungi = fungis.indexOf(org) !== -1; var isParasite = parasites.indexOf(org) !== -1; var canDrag = isFungi || isParasite || !org.eaten; if (canDrag && dist < 60) { var type; if (plants.indexOf(org) !== -1) type = 'plant';else if (herbivores.indexOf(org) !== -1) type = 'herbivore';else if (carnivores.indexOf(org) !== -1) type = 'carnivore';else if (pollinators.indexOf(org) !== -1) type = 'pollinator';else if (fungis.indexOf(org) !== -1) type = 'fungi';else if (parasites.indexOf(org) !== -1) type = 'parasite'; // (UI label is handled in createPaletteItem, so no change needed here for type) dragging = { type: type, node: org, paletteRef: null }; dragOffsetX = org.x - x; dragOffsetY = org.y - y; org.alpha = 0.7; found = true; break; } } if (!found) { dragging = null; } }; // Move: update drag position game.move = function (x, y, obj) { if (dragging && dragging.node) { var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; var newX = x + dragOffsetX; var newY = y + dragOffsetY; // Clamp to map area if (newX < minX) newX = minX; if (newX > maxX) newX = maxX; if (newY < minY) newY = minY; if (newY > maxY) newY = maxY; dragging.node.x = newX; dragging.node.y = newY; } }; // Up: drop organism if dragging game.up = function (x, y, obj) { if (dragging && dragging.node) { // If dropped inside palette area, destroy (cancel) var guiPos = LK.gui.top.toLocal({ x: x, y: y }, game); var cancel = false; for (var i = 0; i < paletteItems.length; i++) { if (isInPalette(guiPos.x, guiPos.y, paletteItems[i])) { cancel = true; break; } } if (cancel) { // Remove organism dragging.node.destroy(); if (dragging.type === 'plant') { var idx = plants.indexOf(dragging.node); if (idx !== -1) plants.splice(idx, 1); } else if (dragging.type === 'herbivore') { var idx = herbivores.indexOf(dragging.node); if (idx !== -1) { // Remove from hunger queue if present var hqidx = herbivoreHungerQueue.indexOf(dragging.node); if (hqidx !== -1) { herbivoreHungerQueue.splice(hqidx, 1); // Next in queue (if any) becomes hungry for (var j = 0; j < herbivoreHungerQueue.length; j++) { var nextH = herbivoreHungerQueue[j]; if (nextH && nextH.state === "waiting") { nextH.state = "hungry"; nextH._hungerQueueState = "hungry"; break; } } } herbivores.splice(idx, 1); } } else if (dragging.type === 'carnivore') { var idx = carnivores.indexOf(dragging.node); if (idx !== -1) carnivores.splice(idx, 1); } else if (dragging.type === 'pollinator') { var idx = pollinators.indexOf(dragging.node); if (idx !== -1) pollinators.splice(idx, 1); } else if (dragging.type === 'fungi') { var idx = fungis.indexOf(dragging.node); if (idx !== -1) fungis.splice(idx, 1); } else if (dragging.type === 'parasite') { var idx = parasites.indexOf(dragging.node); if (idx !== -1) parasites.splice(idx, 1); } else if (dragging.type === 'worm') { var idx = worms.indexOf(dragging.node); if (idx !== -1) worms.splice(idx, 1); } } else { // Place organism: snap alpha to 1 dragging.node.alpha = 1; // If dropping a parasite, set state to 'seek' and detach from host if needed if (dragging.type === 'parasite') { // If attached, detach if (dragging.node.state === "attached") { if (dragging.node.attachedTo && dragging.node.attachedTo._parasiteAttached === dragging.node) { dragging.node.attachedTo._parasiteAttached = null; } dragging.node.attachedTo = null; } dragging.node.state = "seek"; dragging.node.target = null; } } dragging = null; } }; // --- Main Update Loop --- game.update = function () { // Update all herbivores for (var i = herbivores.length - 1; i >= 0; i--) { var h = herbivores[i]; if (h.eaten) { // Remove from hunger queue if present var idx = herbivoreHungerQueue.indexOf(h); if (idx !== -1) { herbivoreHungerQueue.splice(idx, 1); // Next in queue (if any) becomes hungry for (var j = 0; j < herbivoreHungerQueue.length; j++) { var nextH = herbivoreHungerQueue[j]; if (nextH && nextH.state === "waiting") { nextH.state = "hungry"; nextH._hungerQueueState = "hungry"; break; } } } herbivores.splice(i, 1); continue; } if (h.update) h.update(); } // Update all carnivores for (var i = carnivores.length - 1; i >= 0; i--) { var c = carnivores[i]; if (c.eaten) { carnivores.splice(i, 1); continue; } if (c.update) c.update(); } // Update all worms for (var i = worms.length - 1; i >= 0; i--) { var w = worms[i]; if (w.update) w.update(); } // In summer, randomly turn some healthy plants brown if (window.summerState) { for (var i = 0; i < plants.length; i++) { var p = plants[i]; // Only affect healthy, non-brown, non-eaten plants if (!p.eaten && !p._isBrown) { // Smaller chance per frame (e.g. 0.0005 = ~0.05% per frame) if (Math.random() < 0.0005) { p._isBrown = true; if (p.plantAsset && typeof p.plantAsset.tint !== "undefined") { p.plantAsset.tint = 0x996633; // brown } } } } } // Clean up dead plants for (var i = plants.length - 1; i >= 0; i--) { var p = plants[i]; if (p.eaten) { plants.splice(i, 1); } } // Update all seeds for (var i = seeds.length - 1; i >= 0; i--) { var s = seeds[i]; if (s.eaten) { seeds.splice(i, 1); continue; } if (s.update) s.update(); } ; // Update all fungi for (var i = fungis.length - 1; i >= 0; i--) { var f = fungis[i]; if (f.update) f.update(); } // Update all fungi spores for (var i = fungiSpores.length - 1; i >= 0; i--) { var s = fungiSpores[i]; if (!s.parent || !s._alive) { fungiSpores.splice(i, 1); continue; } if (s.update) s.update(); } // Update all parasites for (var i = parasites.length - 1; i >= 0; i--) { var b = parasites[i]; if (b.update) b.update(); } // --- Update palette population counters --- for (var i = 0; i < paletteItems.length; i++) { var node = paletteItems[i]; if (!node._popTxt) continue; var type = node.type; var popCount = 0; var maxCount = null; if (type === 'plant') { popCount = plants.length; maxCount = 20; } else if (type === 'herbivore') { popCount = herbivores.length; maxCount = 6; } else if (type === 'carnivore') { popCount = carnivores.length; maxCount = 4; } else if (type === 'pollinator') { popCount = pollinators.length; maxCount = 10; } else if (type === 'fungi') { popCount = fungis.length; } else if (type === 'parasite') { popCount = parasites.length; maxCount = 4; } else if (type === 'worm') { popCount = worms.length; } node._popTxt.setText("" + popCount); // Set counter color to red if at population control max, else black if (type === 'herbivore' && popCount >= 6 || type === 'pollinator' && popCount >= 10 || type === 'plant' && popCount >= 20 || type === 'carnivore' && popCount >= 4 || type === 'parasite' && popCount >= 4) { if (typeof node._popTxt.setStyle === "function") node._popTxt.setStyle({ fill: "#c00" }); } else { if (typeof node._popTxt.setStyle === "function") node._popTxt.setStyle({ fill: "#000" }); } } // --- Continuously keep randomize button disabled if organisms are present --- if (plants.length > 0 || herbivores.length > 0 || carnivores.length > 0 || pollinators.length > 0 || fungis.length > 0 || parasites.length > 0 || worms.length > 0) { randomizeBtn._disabled = true; randomizeBtnBox.color = 0xcc2222; if (typeof randomizeBtnBox.setStyle === "function") { randomizeBtnBox.setStyle({ fill: "#fff" }); } randomizeBtnTxt.setText("Randomized"); } else { randomizeBtn._disabled = false; randomizeBtnBox.color = 0x228B22; if (typeof randomizeBtnBox.setStyle === "function") { randomizeBtnBox.setStyle({ fill: "#fff" }); } randomizeBtnTxt.setText("Randomize"); } }; // --- Instructions Text --- // --- Random seed spawning around the map if there are any plants --- var randomSeedSpawnTimer = null; function startRandomSeedSpawning() { if (randomSeedSpawnTimer !== null) return; randomSeedSpawnTimer = LK.setInterval(function () { // Only spawn if there are any plants if (plants.length > 0) { // 40% chance per interval to spawn a seed if (Math.random() < 0.4) { // Pick a random position inside the map area var minX = 124 + 60; var maxX = 124 + 1800 - 60; var minY = paletteY + 320 + 60; var maxY = paletteY + 320 + 2000 - 60; var sx = minX + Math.random() * (maxX - minX); var sy = minY + Math.random() * (maxY - minY); var seed = new Seed(); seed.x = sx; seed.y = sy; seed.scaleX = 0.4; seed.scaleY = 0.4; seed.alpha = 1; seeds.push(seed); game.addChild(seed); } } // If no plants, stop spawning if (plants.length === 0 && randomSeedSpawnTimer !== null) { LK.clearInterval(randomSeedSpawnTimer); randomSeedSpawnTimer = null; } }, 120); // Try every 2 seconds } // Start random seed spawning at game start startRandomSeedSpawning(); LK.playMusic('mellow', { loop: true }); // --- Map Area Border (for visual feedback) --- // Use a simple colored rectangle instead of a plant asset for the map border background var mapBorder = LK.getAsset('box', { anchorX: 0, anchorY: 0, width: 1800, height: 2000, color: 0x2e8b57, shape: 'box' }); mapBorder.alpha = 0.12; game.addChild(mapBorder); mapBorder.x = 124; mapBorder.y = paletteY + 320; // --- Add invisible border walls to prevent creatures from exiting the map area --- var borderThickness = 40; var borderAlpha = 0; // fully invisible // Left border var borderLeft = LK.getAsset('box', { anchorX: 0, anchorY: 0, width: borderThickness, height: 2000, color: 0x000000, shape: 'box' }); borderLeft.alpha = borderAlpha; game.addChild(borderLeft); borderLeft.x = 124 - borderThickness; borderLeft.y = paletteY + 320; // Right border var borderRight = LK.getAsset('box', { anchorX: 0, anchorY: 0, width: borderThickness, height: 2000, color: 0x000000, shape: 'box' }); borderRight.alpha = borderAlpha; game.addChild(borderRight); borderRight.x = 124 + 1800; borderRight.y = paletteY + 320; // Top border var borderTop = LK.getAsset('box', { anchorX: 0, anchorY: 0, width: 1800 + borderThickness * 2, height: borderThickness, color: 0x000000, shape: 'box' }); borderTop.alpha = borderAlpha; game.addChild(borderTop); borderTop.x = 124 - borderThickness; borderTop.y = paletteY + 320 - borderThickness; // Bottom border var borderBottom = LK.getAsset('box', { anchorX: 0, anchorY: 0, width: 1800 + borderThickness * 2, height: borderThickness, color: 0x000000, shape: 'box' }); borderBottom.alpha = borderAlpha; game.addChild(borderBottom); borderBottom.x = 124 - borderThickness; borderBottom.y = paletteY + 320 + 2000; // --- End of Game Code ---;
===================================================================
--- original.js
+++ change.js
@@ -2578,10 +2578,10 @@
/****
* Game Code
****/
-// --- Pollinator Hibernation State ---
// Pollinator sound effects (buzz, chirp, etc)
+// --- Pollinator Hibernation State ---
var pollinatorsHibernating = false;
// --- Year Counter ---
// Add a year counter above the cycle counter that increments after winter ends
var yearCount = 1;
@@ -3152,39 +3152,8 @@
// Place score counter under the streak counter
scoreTxt.x = streakTxt.x;
scoreTxt.y = streakTxt.y + 80;
LK.gui.top.addChild(scoreTxt);
-// --- End Game Button ---
-var endGameBtn = new Container();
-var endGameBtnBox = endGameBtn.attachAsset('box', {
- anchorX: 0.5,
- anchorY: 0.5,
- width: 220,
- height: 100,
- color: 0x222222,
- shape: 'box'
-});
-var endGameBtnTxt = new Text2("End Game", {
- size: 54,
- fill: "#fff"
-});
-endGameBtnTxt.anchor.set(0.5, 0.5);
-endGameBtn.addChild(endGameBtnTxt);
-// Place end game button under the score counter
-endGameBtn.x = scoreTxt.x;
-endGameBtn.y = scoreTxt.y + 100;
-endGameBtn.interactive = true;
-endGameBtn.visible = true;
-endGameBtn.down = function () {
- // Play click sound for feedback
- var clickSnd = LK.getSound('Click');
- if (clickSnd && typeof clickSnd.play === "function") clickSnd.play();
- // Set the game over score to the current score counter
- LK.setScore(scoreCount);
- // Trigger game over
- LK.showGameOver();
-};
-LK.gui.top.addChild(endGameBtn);
// Helper to update score text
function updateScoreText() {
if (scoreTxt && typeof scoreTxt.setText === "function") {
scoreTxt.setText("Score: " + scoreCount);
@@ -3192,10 +3161,10 @@
fill: "#000"
});
}
}
-// --- Retract/Expand Streak Counter with Top Buttons ---
-// Patch updateTopButtonsRetracted to also move/hide streakTxt
+// --- Retract/Expand Streak and Score Counter with Top Buttons ---
+// Patch updateTopButtonsRetracted to also move/hide streakTxt and scoreTxt
var _originalUpdateTopButtonsRetracted = updateTopButtonsRetracted;
updateTopButtonsRetracted = function updateTopButtonsRetracted() {
if (topButtonsRetracted) {
// Move streak counter offscreen and hide
@@ -3208,8 +3177,18 @@
onFinish: function onFinish() {
streakTxt.visible = false;
}
});
+ // Move score counter offscreen and hide
+ tween(scoreTxt, {
+ x: offX
+ }, {
+ duration: 200,
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ scoreTxt.visible = false;
+ }
+ });
} else {
// Move streak counter back and show
streakTxt.visible = true;
tween(streakTxt, {
@@ -3217,8 +3196,16 @@
}, {
duration: 200,
easing: tween.easeOut
});
+ // Move score counter back and show
+ scoreTxt.visible = true;
+ tween(scoreTxt, {
+ x: 500
+ }, {
+ duration: 200,
+ easing: tween.easeOut
+ });
}
// Call original for other buttons
if (typeof _originalUpdateTopButtonsRetracted === "function") {
_originalUpdateTopButtonsRetracted();
tentacled red circle with a mouth and no eyes. In-Game asset. 2d. High contrast. No shadows. Very simple
A plant. In-Game asset. 2d. High contrast. No shadows. Very simple
Brown Seed. In-Game asset. 2d. High contrast. No shadows. Very simple
A small orange circle with spider legs 1 cute eye and no mouth. In-Game asset. 2d. High contrast. No shadows. Very very simple
Make a red egg with dark red spots. In-Game asset. 2d. High contrast. No shadows
Purple mushroom. In-Game asset. 2d. High contrast. No shadows. Very simple
A green shiny orb with a black circle. In-Game asset. 2d. High contrast. No shadows. Very simple
make a cyan lichen. In-Game asset. 2d. High contrast. No shadows. very simple
Just the color blue filling the entire space. In-Game asset. 2d. High contrast. No shadows
A brown circle with bug like antennas and bug legs with bug jaws and no eyes. In-Game asset. 2d. High contrast. No shadows. Very simple
A venus flytrap with one mouth and is looking up side view. In-Game asset. 2d. High contrast. No shadows VERY SIMPLE
Short brown worm. In-Game asset. 2d. High contrast. No shadows. Very simple
Pile of dirt. In-Game asset. 2d. High contrast. No shadows. Very simple
A yellow circle with 3 eyes no mouth and the eyes are in a triangle orientation. In-Game asset. 2d. High contrast. No shadows
Shrub. In-Game asset. 2d. High contrast. No shadows. Very simple