User prompt
When the randomize button is pressed then the cycle timer starts again
User prompt
Make it so when the randomize button is pressed the skip cycle button is available to press
User prompt
Make a new button under extinction called randomize that adds 3-5 ferns, herbivores, pollinators and 0-1 carnivores
User prompt
Move the widget slightly to the left
User prompt
When you press the widget the skip cycle, the extinction and the delete button are hidden
User prompt
Put the widget more to the left
User prompt
Make a widget that retracts the skip cycle, the delete and the extinction buttons offscreen and you can press the widget to bring them back
User prompt
Make it so parasites die to extinction button being pressed
User prompt
Make it so that resistant organisms from parasites have 100% chance they will inherit that trait in its offspring
User prompt
Make it so that placing a organism plays the plop sound
User prompt
Make it so special plants can germinate in the first cycle
User prompt
Make it a 100% chance for special plants to germinate from seeds
User prompt
Make it so special plants are red
User prompt
Make it so that special plants have a 40% chance of spawning during the first 3 cycles until summer
User prompt
Make special plants now red
User prompt
Make it a 40% chance that a seed will germinate into a special plant when its spring
User prompt
Add a rare chance that after a seed is placed when it germinates in spring it turns into a special plant that is just colored white
User prompt
When all organisms are gone then you can no longer skip cycle until a new organism is placed
User prompt
Make a white counter next to the skip cycle button that appears after clicking it that counts down 3 seconds and then disappears
User prompt
Make it so there is a 3 sec cooldown for the skip cycle button
User prompt
Make it so you cant skip a cycle until you place the first organism and clicking it beforehand will cause the skip cycle button to turn red for 2 seconds
User prompt
Make it so the herbivore targets the nearest plant when in a hungry state
User prompt
Remove the achievement button
User prompt
Make a box appear in the center of the screen when achievements is tapped and have the text achievements at the top
User prompt
Change the achievements menu texture into box and have the text achievements at the top
/**** * 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; 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 = {}; // --- 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; } // Handle tired state 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 herbivore if not already targeting if (!self.target || self.target.eaten) { var minDist = 999999, closest = null; // Check 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 6 seconds (360 frames), get tired if (self._chaseTime > 360) { self._tired = true; self._tiredTimer = 600; // 10 seconds at 60fps 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 herbivore 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; } self.target.die(); // Move away from the eaten herbivore 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 eaten for egg laying self._herbivoresEaten = (self._herbivoresEaten || 0) + 1; // Lay an egg if 3 herbivores 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 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); } 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; if (self.parent) self.parent.addChild(poll); if (typeof pollinators !== "undefined") pollinators.push(poll); } else if (self._isParasiteEgg && self._parasiteHatch) { var parasite = new Parasite(); parasite.x = self.x; parasite.y = self.y; parasite.scaleX = 1; parasite.scaleY = 1; parasite.alpha = 1; if (self.parent) self.parent.addChild(parasite); if (typeof parasites !== "undefined") parasites.push(parasite); } 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 (self.parent) self.parent.addChild(herb); herbivores.push(herb); } self.destroy(); } }); } }; return self; }); // Fungi class var Fungi = Container.expand(function () { var self = Container.call(this); // 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 () { // 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 } }; 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 self.update = function () { if (self.eaten) return; // --- 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 (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; // 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; // 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(); 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; 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; // 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 && !self._isBrown && !self.eaten) { // Only roll once per plant per summer if (typeof self._brownChecked === "undefined" || self._brownChecked !== cycleCount) { self._brownChecked = cycleCount; if (Math.random() < 0.12) { // ~12% chance per cycle 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 }); self.state = "seek"; // "seek", "scatter", "planting" 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; // --- Pollination counter for egg laying --- self._pollinations = 0; self._layingEgg = false; self._eggObj = null; self.update = function () { if (self.eaten) return; // --- 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 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; self.state = "scatter"; self._scatterTimer = 60 + Math.floor(Math.random() * 60); self._scatterAngle = Math.random() * Math.PI * 2; 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) { // --- 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() { // 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; } } 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 Hibernation State --- // Pollinator sound effects (buzz, chirp, etc) 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"); // 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); } } // 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 = []; 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 () { 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: "#fff" }); 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 --- // 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 () { 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) var allGroups = [plants, herbivores, carnivores, pollinators, fungis, seeds, eggs, worms]; 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) var allGroups = [plants, herbivores, carnivores, pollinators, fungis, seeds, eggs, worms]; 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 () { // --- 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) { // 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) { // 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); // 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; }; LK.gui.top.addChild(skipCycleBtn); LK.gui.top.addChild(extinctionBtn); // 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 () { if (paletteScrollOffset > paletteScrollMin) { paletteScrollOffset -= paletteSpacing; if (paletteScrollOffset < paletteScrollMin) paletteScrollOffset = paletteScrollMin; renderPaletteItems(); } }; window.paletteRightBtn.down = function () { 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(); 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(); 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 --- // 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) 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) 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) { 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" }); } } }; // --- 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'); // --- 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
@@ -1634,15 +1634,8 @@
// Set the plant's _bornCycle to the current cycleCount so its cycle restarts
if (typeof cycleCount !== "undefined") {
newPlant._bornCycle = cycleCount;
}
- // --- Special: 100% chance for red plant in spring, but only during the first 3 cycles until summer (including first cycle) ---
- if (window.springState && typeof cycleCount !== "undefined" && cycleCount < 3) {
- newPlant._isSpecialRed = true;
- if (newPlant.plantAsset && typeof newPlant.plantAsset.tint !== "undefined") {
- newPlant.plantAsset.tint = 0xff0000; // red
- }
- }
plants.push(newPlant);
if (self.parent) self.parent.addChild(newPlant);
self.eaten = true;
self.destroy();
@@ -1922,10 +1915,10 @@
/****
* Game Code
****/
-// Pollinator sound effects (buzz, chirp, etc)
// --- Pollinator Hibernation State ---
+// Pollinator sound effects (buzz, chirp, etc)
var pollinatorsHibernating = false;
// --- Year Counter ---
// Add a year counter above the cycle counter that increments after winter ends
var yearCount = 1;
@@ -3084,8 +3077,11 @@
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();
return obj;
}
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