"use strict"; const TARINAI_NEED_KEYS = ["food", "sleep", "health", "safety", "social", "fulfill"]; const TARINAI_NEED_PRIORITY = ["safety", "health", "food", "sleep", "social", "fulfill"]; const TARINAI_NEED_LABELS = { food: "\u98df\u6b32", sleep: "\u7761\u7720", health: "\u5065\u5eb7", safety: "\u5b89\u5168", social: "\u95a2\u4fc2", fulfill: "\u5145\u8db3", }; const TARINAI_NEED_PHRASES = { food: "\u304a\u306a\u304b\u304c\u3059\u3044\u3066\u3044\u308b", sleep: "\u306d\u3080\u3044", health: "\u8abf\u5b50\u304c\u60aa\u3044", safety: "\u3042\u3076\u306a\u3044", social: "\u3060\u308c\u304b\u304c\u6c17\u306b\u306a\u308b", fulfill: "\u306a\u306b\u304b\u6e80\u305f\u3055\u308c\u306a\u3044", }; const TARINAI_NEED_THRESHOLDS = { food: { start: 64, continue: 20 }, sleep: { start: 62, continue: 30 }, health: { start: 54, continue: 32 }, safety: { start: 42, continue: 20 }, social: { start: 52, continue: 30 }, fulfill: { start: 48, continue: 26 }, }; const TARINAI_NEED_SATISFACTION_DECAY = { food: 0.85, sleep: 0.75, health: 0.65, safety: 1.10, social: 1.05, fulfill: 1.15, }; const TARINAI_ACTION_LOCK_SECONDS = { eat_food: 0.85, drink_water: 1.8, sleep_in_bed: 18.0, sleep_build_grass_bed: 10.0, sleep_anywhere: 12.0, use_medicine: 2.4, rest_to_recover: 6.0, sunbath: 10.5, seek_comfort_temperature: 5.8, panic_escape: 2.1, hide_at_owned_structure: 6.4, approach_friend: 6.4, approach_parent_or_child: 7.0, approach_mate: 5.6, fight_rival: 3.6, intimidate_enemy: 1.15, play: 8.2, build_grass_bed: 10.0, build_plushie: 10.0, return_owned_structure: 6.4, use_plushie: 7.4, wander_lightly: 4.2, }; const TARINAI_ACTION_PRIORITY = { fight_rival: 72, intimidate_enemy: 0, panic_escape: 94, hide_at_owned_structure: 90, use_medicine: 86, rest_to_recover: 84, sunbath: 91, seek_comfort_temperature: 53, sleep_in_bed: 86, sleep_build_grass_bed: 85, sleep_anywhere: 84, birth_ritual: 72, approach_mate: 72, approach_parent_or_child: 66, approach_friend: 66, eat_food: 50, drink_water: 48, build_grass_bed: 30, build_plushie: 28, use_plushie: 24, return_owned_structure: 22, play_seesaw: 27, play: 20, wander_lightly: 10, }; function actionPriority(actionOrId) { const id = typeof actionOrId === "string" ? actionOrId : actionOrId?.id; if (!id) return 0; return Number(TARINAI_ACTION_PRIORITY[id] ?? actionOrId?.priority ?? 0) || 0; } const TARINAI_FORCED_BEHAVIOR_SOURCE_PRIORITY = { fight_mochi: 180, love_mochi: 150, sleep_drug: 145, drug: 140, user: 200, external: 130, }; function createDefaultNeeds() { return { food: 0, sleep: 0, health: 0, safety: 0, social: 0, fulfill: 0 }; } function quantizeNeed(value) { return Math.max(0, Math.min(100, Math.round((Number(value) || 0) / 10) * 10)); } function getNeedDisplayValue(value) { return Math.max(0, Math.min(10, Math.round((Number(value) || 0) / 10))); } function needPressure(value) { const x = Math.max(0, Math.min(100, Number(value) || 0)) / 100; return x * x * 100; } function groundStressMultiplierFor(tarinai) { const mult = Number(tarinai?.world?.groundStressMultiplier?.()); return Number.isFinite(mult) && mult > 0 ? mult : 1; } function applyGroundStressModifier(tarinai, nextStress) { const next = clamp(Number(nextStress) || 0, 0, 130); const before = Number(tarinai?.stress); if (!Number.isFinite(before) || next <= before) return next; return clamp(before + (next - before) * groundStressMultiplierFor(tarinai), 0, 130); } function calculateStressFromNeeds(needs) { const weightedLinear = (Number(needs.food) || 0) * 0.09 + (Number(needs.sleep) || 0) * 0.11 + (Number(needs.health) || 0) * 0.22 + (Number(needs.safety) || 0) * 0.26 + (Number(needs.social) || 0) * 0.07 + (Number(needs.fulfill) || 0) * 0.13; const peakNeed = Math.max(0, ...TARINAI_NEED_KEYS.map(key => Number(needs?.[key] || 0) || 0)); const peakPressure = Math.max(0, peakNeed - 62) * 0.10; const quadraticPressure = needPressure(needs.food) * 0.10 + needPressure(needs.sleep) * 0.12 + needPressure(needs.health) * 0.25 + needPressure(needs.safety) * 0.30 + needPressure(needs.social) * 0.08 + needPressure(needs.fulfill) * 0.15; return clamp( weightedLinear + peakPressure + quadraticPressure * 0.35, 0, 130 ); } function applyNeedShock(tarinai, deltas = {}, breaker = null) { if (!tarinai || tarinai.dead) return false; tarinai.needShock = tarinai.needShock || {}; for (const key of TARINAI_NEED_KEYS) { const delta = Number(deltas[key] || 0) || 0; if (delta > 0) tarinai.needShock[key] = Math.max(tarinai.needShock[key] || 0, delta); else if (delta < 0) tarinai.needShock[key] = Math.min(tarinai.needShock[key] || 0, delta); } if (breaker) tarinai.lastNeedShockBreaker = breaker; if (tarinai.needs && typeof calculateStressFromNeeds === "function") { const projected = { ...tarinai.needs }; for (const key of TARINAI_NEED_KEYS) projected[key] = quantizeNeed((projected[key] || 0) + (tarinai.needShock[key] || 0)); tarinai.stress = applyGroundStressModifier(tarinai, calculateStressFromNeeds(projected)); } return true; } function applyNeedSatisfaction(tarinai, deltas = {}) { if (!tarinai || tarinai.dead) return false; tarinai.needSatisfaction = tarinai.needSatisfaction || createDefaultNeeds(); let changed = false; for (const key of TARINAI_NEED_KEYS) { const amount = Math.max(0, Number(deltas[key] || 0) || 0); if (amount <= 0) continue; tarinai.needSatisfaction[key] = clamp((tarinai.needSatisfaction[key] || 0) + amount, 0, 88); changed = true; } return changed; } function applyNeedRelief(tarinai, deltas = {}) { if (!tarinai || tarinai.dead) return false; const shockDeltas = {}; const satisfactionDeltas = {}; let hasShock = false; let hasSatisfaction = false; for (const key of TARINAI_NEED_KEYS) { const delta = Number(deltas[key] || 0) || 0; if (delta < 0) { satisfactionDeltas[key] = -delta; hasSatisfaction = true; } else if (delta > 0) { shockDeltas[key] = delta; hasShock = true; } } if (hasSatisfaction) applyNeedSatisfaction(tarinai, satisfactionDeltas, "relief"); if (hasShock) applyNeedShock(tarinai, shockDeltas, null); return hasSatisfaction || hasShock; } function startNeedDrivenEmergencyReaction(tarinai, breaker = null, reason = "\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b") { let actions = null; try { actions = TARINAI_ACTIONS; } catch (err) { actions = null; } if (!tarinai || tarinai.dead || !Array.isArray(actions)) return false; const world = tarinai.world || globalThis.world || null; const aggression = Number(tarinai.currentPersonality?.aggression || tarinai.personalityProfile?.().fight || 0); const fightLikeTarget = breaker && breaker.type !== "firecracker" && breaker.kind !== "ant" && breaker.id && world?.canFightPair?.(tarinai, breaker); const fightAction = actions.find(a => a.id === "fight_rival"); if (fightLikeTarget && aggression > 0.62 && fightAction) { tarinai.conflictTargetId = breaker.id; tarinai.conflictUrge = Math.max(tarinai.conflictUrge || 0, 72); const choice = { need: "social", tiedNeeds: ["social", "safety"], max: Number(tarinai.needRaw?.social || tarinai.needs?.social || 0) || 72 }; const reasonText = buildReasonText("social", choice.tiedNeeds, fightAction, tarinai, world); if (globalThis.TarinaiNeedsRuntime?.startNeedAction?.(tarinai, world, choice, fightAction, reasonText)) { tarinai.lastNeedShockBreaker = null; return true; } } const panicAction = actions.find(a => a.id === "panic_escape"); if (!panicAction) return false; const choice = { need: "safety", tiedNeeds: ["safety"], max: Number(tarinai.needRaw?.safety || tarinai.needs?.safety || 0) || 80 }; const reasonText = reason && reason.endsWith("\u3002") ? reason : buildReasonText("safety", choice.tiedNeeds, panicAction, tarinai, world); if (globalThis.TarinaiNeedsRuntime?.startNeedAction?.(tarinai, world, choice, panicAction, reasonText)) { tarinai.lastNeedShockBreaker = null; return true; } return false; } function triggerNeedShockReaction(tarinai, breaker = null, reason = "\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b") { if (!tarinai || tarinai.dead) return false; const before = tarinai.previousNeeds || createDefaultNeeds(); const projected = { ...(tarinai.needs || createDefaultNeeds()) }; for (const key of TARINAI_NEED_KEYS) projected[key] = quantizeNeed((projected[key] || 0) + (tarinai.needShock?.[key] || 0)); const safetyDelta = (projected.safety || 0) - (before.safety || 0); const healthDelta = (projected.health || 0) - (before.health || 0); const fulfillDelta = (projected.fulfill || 0) - (before.fulfill || 0); const shockTriggered = safetyDelta >= 40 || healthDelta >= 45 || fulfillDelta >= 50; if (!shockTriggered) return false; if (["panic", "intimidate", "fight", "birth_ritual"].includes(tarinai.state)) return false; return startNeedDrivenEmergencyReaction(tarinai, breaker, reason); } function needThreshold(need, kind = "start") { return TARINAI_NEED_THRESHOLDS[need]?.[kind] ?? (kind === "continue" ? 30 : 50); } function actionLockSeconds(action, tarinai = null) { const base = Number(action?.lockSeconds ?? TARINAI_ACTION_LOCK_SECONDS[action?.id] ?? 2.0) || 2.0; const key = `${tarinai?.familyKey || tarinai?.id || "tarinai"}:${action?.id || "idle"}`; const jitter = (stableUnit(key, "action-lock") - 0.5) * 0.18; return Math.max(0.5, base * (1 + jitter)); } function scoreNeedForAction(needs, tarinai, world, need) { const value = Number(needs?.[need]) || 0; let score = value; if (need === "safety") score += 14; else if (need === "health") score += 8; else if (need === "food") { const hunger = Number(tarinai?.hunger || 0) || 0; score += 3 + Math.max(0, hunger - 70) * 0.55 + Math.max(0, hunger - 92) * 0.95; } if ((getTarinaiBehaviorNeed(tarinai)) === need) score += 10; const cooldown = Number(tarinai?.actionCooldowns?.[need] || 0) || 0; if (cooldown > 0) score -= Math.min(22, cooldown * 2.8); const nowBucket = Math.floor((world?.time || 0) / 8); const key = `${tarinai?.familyKey || tarinai?.id || "tarinai"}:${need}:${nowBucket}`; const noise = (stableUnit(key, "need-score") - 0.5) * 3.2; return score + noise; } function chooseTopNeedRandom(needs, tarinai = null, world = null) { const scores = {}; for (const key of TARINAI_NEED_KEYS) scores[key] = scoreNeedForAction(needs, tarinai, world, key); const max = Math.max(...TARINAI_NEED_KEYS.map(key => scores[key])); const tiedNeeds = TARINAI_NEED_KEYS.filter(key => max - scores[key] <= 1.5); const need = TARINAI_NEED_PRIORITY.find(key => tiedNeeds.includes(key)) || tiedNeeds[0] || "fulfill"; return { need, tiedNeeds, max: Number(needs?.[need]) || 0, score: max, scores }; }