User prompt
Change the parasite asset into the parasite asset
User prompt
Make it so only one parasite can be on an organism and if another one attempts to attach onto an organism that already has a parasite they ignore them and look for another organism
User prompt
Make it so parasites every cycle if they have a host will lay a parasite egg that hatches into another parasite
User prompt
Make it so if the parasites host dies they die with the host
User prompt
Make herbivores slightly larger
User prompt
Make parasites their own asset and change their color into brown
User prompt
Make parasites look for herbivores or carnivores and then they attach on them
User prompt
Make parasites smaller
User prompt
Rename blob parasite
User prompt
make blob into its own pallet type
User prompt
Add a new organism that doesn nothing yet
User prompt
Make it so there is a button under the skip season called extinction that when clicked an asteroid hits the center of the map killing half of all organisms
User prompt
Make it so that during winter some eggs will die to reduce lag
User prompt
Make sure that due to the eggs not hatching in summer or winter pollinators will move away from the eggs and continue pollinating
User prompt
Make it so during summer and winter eggs will not hatch until the following spring
User prompt
Make it so carnivores will continue hunting when spring starts
User prompt
Make it so that hunters during winter are in a constant wanderlust state and will ignore herbivores and they cant starve
User prompt
Make it so after winter and when spring starts the herbivores will continue moving
User prompt
Make it so that herbivores still move around when its winter
User prompt
Make it so herbivores avoid eating and are slower in winter and the starvation meter has gone into a halt until the end of the season
User prompt
Make it so that healthy plants cannot die during winter!
User prompt
Make it so that all brown plants die when winter starts
User prompt
Make it so if herbivores eat brown plants the starvation meter does not reset
User prompt
Make it so its less common for plants to turn brown
User prompt
Remove lichen
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Carnivore class var Carnivore = Container.expand(function () { var self = Container.call(this); var carnAsset = self.attachAsset('carnivore', { anchorX: 0.5, anchorY: 0.5 }); 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 // --- 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; // --- 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") { // Find nearest herbivore or pollinator 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; 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; return; } // Eat herbivore or pollinator if close enough if (dist < 60 && !self.target.eaten) { // Remove from correct array if pollinator if (pollinators.indexOf(self.target) !== -1) { self.target.die(); // Remove from pollinators array in main update loop, not here } else { 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 if (self._herbivoresEaten >= 3) { 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; } self._hatchTimer--; if (self._hatchTimer <= 0) { 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, 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 { // Spawn a new Nummer at this position var herb = new Nummer(); 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; } // 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); self.spored = false; // Track if this Nummer was hit by a spore 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 { // Find nearest plant if not already targeting 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; 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 (carnDist < 300) { // If carnivore is within 300px, run away carnivoreTooClose = true; runAwayDX = self.x - nearestCarn.x; runAwayDY = self.y - nearestCarn.y; } } // --- 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) { // Run away from carnivore self.x += runAwayDX / carnDist * (self.speed * 1.5); self.y += runAwayDY / carnDist * (self.speed * 1.5); } 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; 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) self.state = "sleepy"; self._sleepTimer = 600; // 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 if (self._plantsEaten >= 4) { 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 if (self.infected) { 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('box', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60, color: 0x8888ff, shape: 'box' }); // 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 && !self.attachedTo.eaten) { // Stay at the same offset from host self.x = self.attachedTo.x + self.offsetX; self.y = self.attachedTo.y + self.offsetY; // If host is eaten, detach and seek again if (self.attachedTo.eaten) { self.state = "seek"; self.attachedTo = null; self.target = null; } return; } // If seeking, look for nearest herbivore or carnivore if (self.state === "seek") { // If no target or target is eaten or already has a parasite attached, 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; 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; 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) { 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) { // 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; // Add a lifetime timer (1 minute = 3600 frames) self._lifetime = 3600; self.update = function () { if (self.eaten) return; // --- 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; } // Pollinator dies after 1 minute self._lifetime--; if (self._lifetime <= 0) { // Animate and destroy pollinator tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); // Prevent further updates self.update = function () {}; 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 wait for it to hatch if (self._pollinations >= 3 && !self._layingEgg) { // 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); self._eggObj = egg; self._layingEgg = true; // Wait for egg to hatch before continuing 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); 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--; if (self._growTimer <= 0) { // --- Summer 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; } 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; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2ecc40 //{7C} // Set to a green background }); /**** * Game Code ****/ // --- Pollinator Hibernation State --- var pollinatorsHibernating = false; // --- 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"; // Fallback: guess from background color if (game.backgroundColor === 0xffff00) return "Summer"; if (game.backgroundColor === 0xffffff) return "Winter"; 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"); // Set background color every 3 cycles: green, yellow, white, repeat var mod = cycleCount % 9; if (mod === 0) { game.setBackgroundColor(0x2ecc40); // green updateTextColors(0x2ecc40); pollinatorsHibernating = false; // --- Not summer: reset summerState flag, set springState true --- window.summerState = false; window.springState = true; window.winterState = false; // 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; } // --- 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) { game.setBackgroundColor(0xffff00); // yellow updateTextColors(0xffff00); pollinatorsHibernating = false; // --- SUMMER STATE: Set summerState flag to true, others false --- window.summerState = true; window.winterState = false; window.springState = 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) { game.setBackgroundColor(0xffffff); // white updateTextColors(0xffffff); pollinatorsHibernating = true; // --- Not summer: reset summerState flag, set winterState true --- window.summerState = false; window.springState = false; window.winterState = true; // 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 // --- 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; // --- 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); // Asteroid extinction event handler extinctionBtn.down = function () { // 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); // Gather all organisms (plants, herbivores, carnivores, pollinators, fungis, seeds, eggs) var allGroups = [plants, herbivores, carnivores, pollinators, fungis, seeds, eggs]; for (var g = 0; g < allGroups.length; g++) { var arr = allGroups[g]; // Only 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]; // Remove from array arr.splice(idx, 1); // Destroy or die if (org && typeof org.die === "function") { org.die(); } else if (org && typeof org.destroy === "function") { org.destroy(); } } } }; skipCycleBtn.down = function () { // Increment cycle and update UI cycleCount++; cycleTxt.setText("Cycle: " + cycleCount); cycleTimeLeftMs = cycleDurationMs; cycleTimerTxt.setText("Next cycle: " + Math.ceil(cycleTimeLeftMs / 1000) + "s"); // Set background color every 3 cycles: green, yellow, white, repeat var mod = cycleCount % 9; if (mod === 0) { game.setBackgroundColor(0x2ecc40); // green updateTextColors(0x2ecc40); pollinatorsHibernating = false; // --- Not summer: reset summerState flag, set springState true --- window.summerState = false; window.springState = true; window.winterState = false; // 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; } // --- 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) { game.setBackgroundColor(0xffff00); // yellow updateTextColors(0xffff00); pollinatorsHibernating = false; // --- SUMMER STATE: Set summerState flag to true, others false --- window.summerState = true; window.winterState = false; window.springState = 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) { game.setBackgroundColor(0xffffff); // white updateTextColors(0xffffff); pollinatorsHibernating = true; // --- Not summer: reset summerState flag, set winterState true --- window.summerState = false; window.springState = false; window.winterState = true; // 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; } 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 (org && typeof org.x === "number" && typeof org.y === "number" && !org.eaten) { org.x = minX + Math.random() * (maxX - minX); org.y = minY + Math.random() * (maxY - minY); } } } randomizeOrganisms(herbivores); randomizeOrganisms(carnivores); randomizeOrganisms(pollinators); randomizeOrganisms(seeds); randomizeOrganisms(eggs); // Fungi, plants, and lichen are NOT moved // (fungis, plants, window.lichens) }; 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 = 'box'; color = 0x8888ff; label = 'Parasite'; } 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 = node.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); node.x = x; node.y = y; node.type = type; // 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']; 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(); 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); } 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); // 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); 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); 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 canDrag = isFungi || !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 { // Place organism: snap alpha to 1 dragging.node.alpha = 1; } 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(); } // 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(); } }; // --- 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(); // --- 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
@@ -725,9 +725,9 @@
});
};
return self;
});
-// Parasite class (does nothing yet)
+// Parasite class (seeks and attaches to herbivores or carnivores)
var Parasite = Container.expand(function () {
var self = Container.call(this);
var parasiteAsset = self.attachAsset('box', {
anchorX: 0.5,
@@ -736,10 +736,114 @@
height: 60,
color: 0x8888ff,
shape: 'box'
});
- // No behavior yet
- self.update = function () {};
+ // 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 && !self.attachedTo.eaten) {
+ // Stay at the same offset from host
+ self.x = self.attachedTo.x + self.offsetX;
+ self.y = self.attachedTo.y + self.offsetY;
+ // If host is eaten, detach and seek again
+ if (self.attachedTo.eaten) {
+ self.state = "seek";
+ self.attachedTo = null;
+ self.target = null;
+ }
+ return;
+ }
+ // If seeking, look for nearest herbivore or carnivore
+ if (self.state === "seek") {
+ // If no target or target is eaten or already has a parasite attached, 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;
+ 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;
+ 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) {
+ 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) {
+ // 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 () {
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