2026-06-26 12:46:32 +09:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
// Layer: entity-runtime/tarinai/update-step
|
|
|
|
|
// Owns per-frame timers, passive metabolism, disease/effect ticks, colony mood
|
2026-06-26 19:04:32 +09:00
|
|
|
// pressure, and early freeze exits. This is extracted from the former
|
2026-06-26 12:46:32 +09:00
|
|
|
// Tarinai.prototype.update body without changing behavior.
|
|
|
|
|
(function (global) {
|
|
|
|
|
function update(t, dt, context = {}) {
|
|
|
|
|
if (!t || t.dead) return { done: true };
|
|
|
|
|
t.prevX = t.x;
|
|
|
|
|
t.prevY = t.y;
|
|
|
|
|
|
|
|
|
|
const minute = dt / 60;
|
|
|
|
|
t.age += dt;
|
|
|
|
|
t.reproductionTimer -= dt;
|
|
|
|
|
t.blink += dt;
|
|
|
|
|
t.eatTimer = Math.max(0, t.eatTimer - dt);
|
|
|
|
|
if (t.state === "eat" && t.eatTimer <= 0.02) {
|
|
|
|
|
t.setActionState?.("idle", { target: null, reason: "食べ終えた", sleeping: false, clearTarget: true });
|
|
|
|
|
}
|
|
|
|
|
t.eatCooldown = Math.max(0, t.eatCooldown - dt);
|
|
|
|
|
t.foodReactTimer = Math.max(0, t.foodReactTimer - dt);
|
|
|
|
|
t.surpriseTimer = Math.max(0, t.surpriseTimer - dt);
|
|
|
|
|
t.fearTimer = Math.max(0, t.fearTimer - dt);
|
|
|
|
|
t.intimidateTimer = Math.max(0, t.intimidateTimer - dt);
|
|
|
|
|
t.intimidatedTimer = Math.max(0, t.intimidatedTimer - dt);
|
|
|
|
|
t.blastSpinTimer = Math.max(0, (t.blastSpinTimer || 0) - dt);
|
|
|
|
|
t.postBirthPeaceTimer = Math.max(0, (t.postBirthPeaceTimer || 0) - dt);
|
|
|
|
|
t.hpBarTimer = Math.max(0, (t.hpBarTimer || 0) - dt);
|
|
|
|
|
t.stressBarTimer = Math.max(0, (t.stressBarTimer || 0) - dt);
|
|
|
|
|
t.updateZunchiDisease(dt);
|
|
|
|
|
t.updateSpecialDiseases(dt);
|
|
|
|
|
if (t.updateItemEffects) t.updateItemEffects(dt);
|
|
|
|
|
if (t.dead) return { done: true };
|
|
|
|
|
t.loveMochiTimer = Math.max(0, (t.loveMochiTimer || 0) - dt);
|
|
|
|
|
t.fightMochiTimer = Math.max(0, (t.fightMochiTimer || 0) - dt);
|
|
|
|
|
if (t.fightMochiTimer > 0.04) {
|
|
|
|
|
applyNeedShock(t, { safety: dt * 4 });
|
|
|
|
|
if (t.state !== "fight") t.fearTimer = Math.max(t.fearTimer || 0, 0.10);
|
|
|
|
|
}
|
|
|
|
|
if (t.intimidateTimer <= 0.04) t.intimidateTargetId = null;
|
|
|
|
|
if (t.birthRitualTimer > 0) {
|
|
|
|
|
const beforeBirthTimer = t.birthRitualTimer;
|
|
|
|
|
t.birthRitualTimer = Math.max(0, t.birthRitualTimer - dt);
|
|
|
|
|
if (beforeBirthTimer > 0 && t.birthRitualTimer <= 0.04 && t.birthRitualLeader) {
|
|
|
|
|
const partner = t.world.liveTarinaiById?.(t.birthPartnerId);
|
|
|
|
|
if (partner) t.world.finishBirthRitual(t, partner);
|
|
|
|
|
t.birthRitualLeader = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
t.pokeFlashTimer = Math.max(0, t.pokeFlashTimer - dt);
|
|
|
|
|
t.focusPulseTimer = Math.max(0, (t.focusPulseTimer || 0) - dt);
|
|
|
|
|
t.cursorPetting = Math.max(0, t.cursorPetting - dt * 1.2);
|
|
|
|
|
t.pinchThoughtTimer = Math.max(0, (t.pinchThoughtTimer || 0) - dt);
|
|
|
|
|
t.sunbathCooldown = Math.max(0, (t.sunbathCooldown || 0) - dt);
|
|
|
|
|
if (t.freezeSleepDiseaseMotion(dt)) {
|
|
|
|
|
t.checkLife(dt);
|
|
|
|
|
return { done: true };
|
|
|
|
|
}
|
|
|
|
|
if (global.TarinaiCursorCareSystem?.updateOne) global.TarinaiCursorCareSystem.updateOne(t, dt);
|
|
|
|
|
else t.applyCursorContactCare?.(dt);
|
|
|
|
|
const colonyMoodId = t.world?.colonyMood?.id || "relaxed";
|
|
|
|
|
if (colonyMoodId === "weaklings" && !t.isZunchiSlave) applyNeedRelief(t, { safety: -dt * 2, fulfill: -dt * 1 });
|
|
|
|
|
if (colonyMoodId === "zunda_party" || colonyMoodId === "ball_party") applyNeedRelief(t, { fulfill: -dt * 3 });
|
|
|
|
|
if (colonyMoodId === "ant_overrun" && t.state !== "panic") {
|
|
|
|
|
const antSeen = (t.world?.nearbyAnts?.(t.x, t.y, 110, true) || []).some(a => a && !a.dead);
|
|
|
|
|
if (antSeen) {
|
|
|
|
|
if (t.enterPanic) t.enterPanic({ target: null, reason: "アリだ", fear: 1.1, wake: true, cause: "colony_ant_overrun" });
|
|
|
|
|
else { t.setActionState?.("panic", { target: null, reason: "アリだ", wake: true }); t.fearTimer = Math.max(t.fearTimer || 0, 1.1 * t.personalityProfile().fear); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const wasFighting = t.fightTimer > 0.04;
|
|
|
|
|
t.fightTimer = Math.max(0, t.fightTimer - dt);
|
|
|
|
|
t.fightCooldown = Math.max(0, t.fightCooldown - dt);
|
|
|
|
|
t.groundStumbleCooldown = Math.max(0, (t.groundStumbleCooldown || 0) - dt);
|
|
|
|
|
t.defeatedTimer = Math.max(0, t.defeatedTimer - dt);
|
|
|
|
|
if (wasFighting && t.fightTimer <= 0.04 && t.defeatedById) {
|
|
|
|
|
const winner = t.world.liveTarinaiById?.(t.defeatedById) || null;
|
|
|
|
|
t.defeatedTimer = Math.max(t.defeatedTimer, rand(3.4, 5.2));
|
|
|
|
|
t.fearTimer = Math.max(t.fearTimer, 1.0 * t.personalityProfile().fear);
|
|
|
|
|
t.hurtTimer = Math.max(t.hurtTimer, 1.8);
|
|
|
|
|
t.fallTimer = Math.max(t.fallTimer, 1.15);
|
|
|
|
|
t.fallMax = Math.max(t.fallMax || 1.15, t.fallTimer);
|
|
|
|
|
t.fallDir = winner ? (t.x < winner.x ? -1 : 1) : t.fallDir;
|
|
|
|
|
t.world.spawnFallEffect(t.x, t.y + t.radius * 0.45);
|
|
|
|
|
if (winner) t.world.resolveFightOutcome(winner, t);
|
|
|
|
|
t.fightTargetIds = [];
|
|
|
|
|
t.fightTargetId = null;
|
|
|
|
|
}
|
|
|
|
|
t.stretchTimer = Math.max(0, t.stretchTimer - dt);
|
|
|
|
|
t.grassEatTimer = Math.max(0, t.grassEatTimer - dt);
|
|
|
|
|
t.hurtTimer = Math.max(0, t.hurtTimer - dt);
|
|
|
|
|
t.fallTimer = Math.max(0, t.fallTimer - dt);
|
|
|
|
|
if ((t.world?.groundType || "soil") === "foot_massage"
|
|
|
|
|
&& (t.groundStumbleCooldown || 0) <= 0.04
|
|
|
|
|
&& (t.fallTimer || 0) <= 0.04
|
|
|
|
|
&& !t.dead
|
|
|
|
|
&& !["sleep", "fight", "panic", "birth_ritual", "frozen"].includes(String(t.state || ""))
|
|
|
|
|
&& Math.random() < dt * 0.055) {
|
|
|
|
|
const dir = Math.random() < 0.5 ? -1 : 1;
|
|
|
|
|
t.groundStumbleCooldown = rand(8.5, 16.0);
|
|
|
|
|
t.fallTimer = Math.max(t.fallTimer || 0, rand(0.86, 1.18));
|
|
|
|
|
t.fallMax = Math.max(t.fallMax || 1.05, t.fallTimer);
|
|
|
|
|
t.fallDir = dir;
|
|
|
|
|
t.vx += dir * rand(26, 52);
|
|
|
|
|
t.vy += rand(-18, 18);
|
|
|
|
|
t.world?.spawnFallEffect?.(t.x, t.y + t.radius * 0.45);
|
|
|
|
|
t.setActionState?.("idle", { target: null, reason: "足つぼの突起につまづいた。", sleeping: false, clearTarget: false });
|
|
|
|
|
if ((t.world?.time || 0) >= (t.nextBubbleAt || 0)) t.bubble?.("うっ", 1.6, "rgba(86,70,96,0.78)");
|
|
|
|
|
}
|
|
|
|
|
if (t.stress > 92 && t.state !== "sleep" && t.state !== "fight" && t.state !== "eat" && Math.random() < dt * 0.050) {
|
|
|
|
|
t.bubble("はっ...はっ...", 3.4, "rgba(58,48,63,0.80)");
|
|
|
|
|
}
|
|
|
|
|
if (t.state === "idle" && t.stress < 78 && Math.random() < dt * 0.018) {
|
|
|
|
|
t.bubble(pick(["はう", "はうぅ", "はうっ", "はぅ"]), 4.2, "rgba(62,84,45,0.76)");
|
|
|
|
|
}
|
|
|
|
|
if (t.hurtTimer > 0 && Math.random() < dt * 5.2) {
|
|
|
|
|
const side = t.facingDir();
|
|
|
|
|
t.world.spawnBleedEffect(t.x - side * t.radius * rand(0.15, 0.42), t.y - t.radius * rand(0.08, 0.34));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.tempTimer += dt;
|
|
|
|
|
if (t.tempTimer >= 0.55) {
|
|
|
|
|
t.tempComfort = t.world.temperatureAt(t.x, t.y);
|
|
|
|
|
t.tempTimer = 0;
|
|
|
|
|
}
|
|
|
|
|
const tempComfort = t.tempComfort ?? 0.62;
|
|
|
|
|
const tooCold = Math.max(0, 0.48 - tempComfort);
|
|
|
|
|
const hungerScale = t.metabolicHungerMultiplier ? t.metabolicHungerMultiplier() : (typeof tarinaiMetabolicHungerScale === "function" ? tarinaiMetabolicHungerScale(t) : 1);
|
|
|
|
|
t.hunger += CONFIG.hungerPerMinute * minute * t.trait.hunger * hungerScale * (1 + tooCold * 0.8);
|
|
|
|
|
t.loneliness += CONFIG.lonelinessPerMinute * minute * (2 - t.trait.social);
|
|
|
|
|
t.energy -= CONFIG.energyPerMinute * minute * t.trait.sleep * (t.state === "sleep" ? 0.025 : 1);
|
|
|
|
|
// stress is derived from needs; no independent stress decay.
|
|
|
|
|
if (tooCold > 0) applyNeedShock(t, { health: tooCold * dt * 4, safety: tooCold * dt * 2 });
|
|
|
|
|
|
|
|
|
|
const phase = t.world.lightLevel();
|
|
|
|
|
if (phase < 0.25) t.energy += dt * 0.07;
|
|
|
|
|
if (phase > 0.75) applyNeedShock(t, { health: dt * 1.2 });
|
|
|
|
|
if (t.world.weather === "cloudy") {
|
|
|
|
|
applyNeedRelief(t, { safety: -dt * 1 });
|
|
|
|
|
t.vx *= Math.pow(0.996, dt * 60);
|
|
|
|
|
t.vy *= Math.pow(0.996, dt * 60);
|
|
|
|
|
} else if (t.world.weather === "light_rain") {
|
|
|
|
|
t.energy += dt * 0.018;
|
|
|
|
|
applyNeedRelief(t, { health: -dt * 1, safety: -dt * 1 });
|
|
|
|
|
} else if (t.world.weather === "sunny") {
|
|
|
|
|
t.vx *= Math.pow(1.001, dt * 60);
|
|
|
|
|
t.vy *= Math.pow(1.001, dt * 60);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (t.generation > 1 && t.syncGrowthScale) t.syncGrowthScale();
|
|
|
|
|
|
|
|
|
|
t.hunger = clamp(t.hunger, 0, 115);
|
|
|
|
|
t.loneliness = clamp(t.loneliness, 0, 110);
|
|
|
|
|
t.energy = clamp(t.energy, 0, typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(t) : (Number(t.maxEnergy) || 100));
|
|
|
|
|
t.stress = applyGroundStressModifier(t, calculateStressFromNeeds(t.needRaw || t.needs || createDefaultNeeds()));
|
|
|
|
|
|
|
|
|
|
context.wasSleepingAtFrameStart = t.state === "sleep" || t.sleeping;
|
|
|
|
|
t.awakeLockTimer = Math.max(0, (t.awakeLockTimer || 0) - dt);
|
|
|
|
|
|
|
|
|
|
if (t.freezeSleepDiseaseMotion(dt)) {
|
|
|
|
|
t.checkLife(dt);
|
|
|
|
|
return { done: true };
|
|
|
|
|
}
|
|
|
|
|
return { done: false };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
global.TarinaiFrameUpdateStep = Object.freeze({ update });
|
|
|
|
|
})(typeof window !== "undefined" ? window : globalThis);
|