User prompt
Make the fungi have a population control of 20 if there are any more than that they can no longer infect creatures with spores and all creatures that are still infected will not drop fungi at death
User prompt
Make population control for carnivores where the limit is 4 and after that they cant lay eggs and they dont hunt herbivores as constantly and all existent eggs will not hatch until there is an available slot
User prompt
The population control for ferns is 20 and when that happens pollinators start wandering around until there is a slot available and if a seed is there after the 20 plant limit there is 100% chance it will fail to germinate
User prompt
When the species population control is at max make the counter for that species red
User prompt
Make a counter above each species icon in the organism slots to show the population
User prompt
Make population control for herbivores where if there are 6 of them they will also stop laying eggs and all current herbivore eggs will not hatch until there is a available space
User prompt
Make it so pollinators will continue pollinating after laying an egg
User prompt
Add a nee thing called population control where if there are 10 pollnators they will no longer lay eggs until there is a available slot and all existing eggs for pollinators will not hatch until there are 9
User prompt
Make carnivores a bit larger
User prompt
Make a new carnivore ai that chases herbivores however the herbivore runs away and when the carnivore gets tired the herbivores runs away
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'obj = new Nummer();' Line Number: 949
User prompt
Fix the bug where when you drag certain organisims the game pauses
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'obj = new Plant();' Line Number: 905
User prompt
Make it so you can delete flytraps and extinction events kill them
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'obj = new Carnivore();' Line Number: 974
User prompt
Fix line 976
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'obj = new Nummer();' Line Number: 976
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'obj = new Nummer();' Line Number: 976
User prompt
Fix all glitches
User prompt
Fix the bug
User prompt
Make it so that pollinators have a small chance of trying to pollinate a flytrap and if they do they get eaten by the flytrap
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'obj = new Carnivore();' Line Number: 974
User prompt
Make it so you can delete flytraps
User prompt
Make it so extinctions can kill flytraps and parasites
User prompt
Make it so you can drag the flytrap
/****
* 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
});
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;
// --- 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") {
// 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;
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 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
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) {
// 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.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);
// 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 {
// 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 (nearestCarn._tired && nearestCarn.target === self) {
// If a tired carnivore was chasing this herbivore, run away even if not too close
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;
}
}
// --- 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('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 ---
if (typeof self._lastCycle === "undefined") self._lastCycle = -1;
if (typeof cycleCount !== "undefined" && self._lastCycle !== cycleCount) {
self._lastCycle = cycleCount;
// 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;
// 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) {
// 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;
}
// 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 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;
});
/****
* 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";
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");
// 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;
// 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
// --- 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, 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;
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);
randomizeFreeParasites(parasites);
// 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 = 'Parasite';
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;
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;
// 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);
// 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 {
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, parasites);
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 {
// 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();
}
// 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
@@ -5,57 +5,1540 @@
/****
* Classes
****/
-// --- Flytrap Organism Class ---
-// Minimal organism that does nothing (placeholder)
-var Flytrap = Container.expand(function () {
+// Carnivore class
+var Carnivore = Container.expand(function () {
var self = Container.call(this);
- // Attach the Flytrap image asset as the visual for the flytrap
- self.flytrapAsset = self.attachAsset('Flytrap', {
+ // 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,
- scaleY: 1
+ 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;
+ // --- 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") {
+ // 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;
+ 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 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
+ 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;
});
-// --- Nummer Organism Class ---
-// Minimal organism that does nothing (placeholder)
+// 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) {
+ // 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.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);
- // Attach the herbivore image asset as the visual for the Nummer
- self.herbivoreAsset = self.attachAsset('herbivore', {
+ // 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 {
+ // 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 (nearestCarn._tired && nearestCarn.target === self) {
+ // If a tired carnivore was chasing this herbivore, run away even if not too close
+ 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;
+ }
+ }
+ // --- 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('Parasite', {
+ anchorX: 0.5,
anchorY: 0.5,
- scaleX: 1,
- scaleY: 1
+ scaleX: 0.5,
+ scaleY: 0.5
});
- // Dummy die method for deletion compatibility
+ // 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 ---
+ if (typeof self._lastCycle === "undefined") self._lastCycle = -1;
+ if (typeof cycleCount !== "undefined" && self._lastCycle !== cycleCount) {
+ self._lastCycle = cycleCount;
+ // 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;
+ // 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) {
+ // 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;
+ }
+ // 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;
- self.destroy();
+ tween(self, {
+ scaleX: 0,
+ scaleY: 0,
+ alpha: 0
+ }, {
+ duration: 200,
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ self.destroy();
+ }
+ });
};
return self;
});
-// --- Plant Organism Class ---
-// Minimal organism that does nothing (placeholder)
-var Plant = Container.expand(function () {
+// Seed class
+var Seed = Container.expand(function () {
var self = Container.call(this);
- // Attach the Plant image asset as the visual for the plant
- self.plantAsset = self.attachAsset('plant', {
+ // Use dedicated brown seed asset
+ var seedAsset = self.attachAsset('seed', {
anchorX: 0.5,
anchorY: 0.5,
- scaleX: 1,
- scaleY: 1
+ scaleX: 0.4,
+ scaleY: 0.4
});
- // Dummy die method for deletion compatibility
+ 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 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;
- self.destroy();
+ 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
****/
@@ -66,16 +1549,8 @@
/****
* Game Code
****/
// --- Pollinator Hibernation State ---
-function _typeof(o) {
- "@babel/helpers - typeof";
- return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
- return typeof o;
- } : function (o) {
- return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
- }, _typeof(o);
-}
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;
@@ -389,14 +1864,12 @@
var pollinators = [];
var fungis = []; // Track all fungi globally
var parasites = []; // Track all parasites globally
var fungiSpores = []; // Track all spores shot by fungi
-// --- Palette UI ---
+// --- Palette UI ---
var paletteY = 180; // y position for palette
var paletteSpacing = 260;
var paletteItems = [];
-// --- Flytrap Tracking ---
-var flytraps = [];
// --- Delete Mode Button ---
var deleteMode = false;
var deleteBtn = new Container();
var deleteBtnBox = deleteBtn.attachAsset('box', {
@@ -522,20 +1995,8 @@
org.destroy();
}
}
}
- // Extinction: also kill half of all flytraps
- var flytrapSurvivors = Math.ceil(flytraps.length / 2);
- var flytrapsToKill = flytraps.length - flytrapSurvivors;
- for (var i = 0; i < flytrapsToKill; i++) {
- if (flytraps.length === 0) break;
- var idx = Math.floor(Math.random() * flytraps.length);
- var ft = flytraps[idx];
- flytraps.splice(idx, 1);
- if (ft && typeof ft.destroy === "function") {
- ft.destroy();
- }
- }
};
skipCycleBtn.down = function () {
// Increment cycle and update UI
cycleCount++;
@@ -693,12 +2154,8 @@
} else if (type === 'parasite') {
assetId = 'Parasite';
color = 0x8888ff;
label = 'Parasite';
- } else if (type === 'flytrap') {
- assetId = 'Flytrap';
- color = 0x228B22;
- label = 'Flytrap';
} else {
assetId = 'carnivore';
color = 0xe74c3c;
label = 'Hunter';
@@ -755,9 +2212,9 @@
return node;
}
// --- Palette Scroll Bar ---
// All available organism types in palette
-var paletteTypes = ['plant', 'herbivore', 'carnivore', 'pollinator', 'fungi', 'parasite', 'flytrap'];
+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
@@ -854,12 +2311,8 @@
}
};
// --- End Palette Scroll Bar ---
// --- Drag and Drop Logic ---
-// Prevent LK from pausing the game when dragging
-if (typeof LK.setPauseOnDrag === "function") {
- LK.setPauseOnDrag(false);
-}
var dragging = null; // {type, node, paletteRef}
var dragOffsetX = 0,
dragOffsetY = 0;
// Helper: create organism at x,y
@@ -917,12 +2370,8 @@
obj.destroy();
}
});
}
- } else if (type === 'flytrap') {
- obj = new Flytrap();
- flytraps.push(obj);
- game.addChild(obj);
} else {
obj = new Carnivore();
carnivores.push(obj);
}
@@ -952,20 +2401,15 @@
// 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);
- // Add all flytraps to deletable organisms
- for (var i = 0; i < flytraps.length; i++) {
- allOrganisms.push(flytraps[i]);
- }
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 isFlytrap = flytraps.indexOf(org) !== -1;
- var canDelete = isFungi || isFlytrap || !org.eaten;
+ var canDelete = isFungi || !org.eaten;
if (canDelete && dist < 60) {
// Remove from correct array
if (plants.indexOf(org) !== -1) {
var idx = plants.indexOf(org);
@@ -981,11 +2425,8 @@
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);
- } else if (flytraps.indexOf(org) !== -1) {
- var idx = flytraps.indexOf(org);
- if (idx !== -1) flytraps.splice(idx, 1);
}
if (typeof org.die === "function") {
org.die();
} else if (typeof org.destroy === "function") {
@@ -1022,18 +2463,8 @@
// 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);
- // Add all flytraps to draggable organisms
- if (_typeof(game.children) === "object" && game.children.length) {
- for (var i = 0; i < game.children.length; i++) {
- var child = game.children[i];
- if (child instanceof Flytrap) {
- allOrganisms.push(child);
- }
- }
- }
- ;
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;
@@ -1115,11 +2546,8 @@
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 === 'flytrap') {
- var idx = flytraps.indexOf(dragging.node);
- if (idx !== -1) flytraps.splice(idx, 1);
}
} else {
// Place organism: snap alpha to 1
dragging.node.alpha = 1;
@@ -1315,5 +2743,6 @@
});
borderBottom.alpha = borderAlpha;
game.addChild(borderBottom);
borderBottom.x = 124 - borderThickness;
-borderBottom.y = paletteY + 320 + 2000;
\ No newline at end of file
+borderBottom.y = paletteY + 320 + 2000;
+// --- End of Game Code ---;
\ No newline at end of file
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