465 lines
24 KiB
JavaScript
465 lines
24 KiB
JavaScript
"use strict";
|
|
|
|
function actionById(id = "") {
|
|
const key = String(id || "");
|
|
return (typeof getTarinaiActionSpec === "function" ? getTarinaiActionSpec(key) : null)
|
|
|| (Array.isArray(TARINAI_ACTIONS) ? TARINAI_ACTIONS.find(action => action && action.id === key) || null : null);
|
|
}
|
|
|
|
function behaviorTargetId(target = null) {
|
|
if (!target) return null;
|
|
if (target.id != null) return target.id;
|
|
if (Number.isFinite(target.x) && Number.isFinite(target.y)) return `pos:${Math.round(target.x)},${Math.round(target.y)}`;
|
|
return null;
|
|
}
|
|
|
|
function currentBehaviorTarget(tarinai, behavior = null) {
|
|
if (!tarinai) return null;
|
|
const b = behavior || (currentTarinaiBehavior(tarinai)) || null;
|
|
if (!b) return tarinai.target || null;
|
|
const ref = b.target || null;
|
|
const id = ref?.id || b.targetId || null;
|
|
if (tarinai.target && !tarinai.target.dead && (!id || behaviorTargetId(tarinai.target) === id)) return tarinai.target;
|
|
if (id) {
|
|
return tarinai.world?.liveTarinaiById?.(id)
|
|
|| tarinai.world?.itemById?.(id)
|
|
|| (tarinai.world?.items || []).find(item => item && !item.dead && item.id === id)
|
|
|| null;
|
|
}
|
|
if (Number.isFinite(ref?.x) && Number.isFinite(ref?.y)) return { x: ref.x, y: ref.y, dead: false };
|
|
return null;
|
|
}
|
|
|
|
function isLiveTarinaiEntity(value) {
|
|
return Boolean(value && !value.dead && typeof value.relationTo === "function" && Number.isFinite(value.x) && Number.isFinite(value.y));
|
|
}
|
|
|
|
|
|
function nearestForcedFightTarget(tarinai, world, maxDist = 720) {
|
|
if (!tarinai || !world) return null;
|
|
const current = currentBehaviorTarget(tarinai) || tarinai.target || null;
|
|
const ok = o => isLiveTarinaiEntity(o) && o !== tarinai && !!o.isZunchiSlave === !!tarinai.isZunchiSlave && !world.areParentChild?.(tarinai, o) && !world.areCoParents?.(tarinai, o) && !o.sleepDisease;
|
|
if (ok(current) && dist(tarinai, current) <= maxDist + 120) return current;
|
|
const conflict = conflictTargetFor(tarinai, world, 6);
|
|
if (ok(conflict?.target)) return conflict.target;
|
|
return world.nearestOther?.(tarinai, maxDist, ok) || null;
|
|
}
|
|
|
|
function resolveForcedBehaviorTarget(tarinai, world, entry = {}, action = null) {
|
|
if (!tarinai || !world) return null;
|
|
if (entry.targetId) {
|
|
const found = world.liveTarinaiById?.(entry.targetId)
|
|
|| world.itemById?.(entry.targetId)
|
|
|| (world.items || []).find(item => item && !item.dead && item.id === entry.targetId)
|
|
|| null;
|
|
if (found) return found;
|
|
}
|
|
const id = String(entry.id || action?.id || "");
|
|
if (id === "fight_rival" || id === "intimidate_enemy") return nearestForcedFightTarget(tarinai, world, Number(entry.searchRange || 760));
|
|
if (id === "approach_mate" || id === "birth_ritual") return mateTargetFor(tarinai, world, Number(entry.searchRange || 760));
|
|
if (id === "eat_food") return behaviorFoodTarget(tarinai, world, "food", Number(entry.searchRange || 900));
|
|
if (id === "drink_water") return behaviorFoodTarget(tarinai, world, "drink", Number(entry.searchRange || 680));
|
|
if (id === "use_medicine") return behaviorFoodTarget(tarinai, world, "medicine", Number(entry.searchRange || 760));
|
|
if (id === "sleep_in_bed") return findNearestSleepPlace(world, tarinai, Number(entry.searchRange || 760));
|
|
return null;
|
|
}
|
|
|
|
function forcedBehaviorSourcePriority(source = "external") {
|
|
return Number(TARINAI_FORCED_BEHAVIOR_SOURCE_PRIORITY[String(source || "external")] || TARINAI_FORCED_BEHAVIOR_SOURCE_PRIORITY.external || 130) || 130;
|
|
}
|
|
|
|
function forcedBehaviorQueueOf(tarinai) {
|
|
if (!tarinai) return [];
|
|
if (!Array.isArray(tarinai.forcedBehaviorQueue)) tarinai.forcedBehaviorQueue = [];
|
|
return tarinai.forcedBehaviorQueue;
|
|
}
|
|
|
|
function setForcedBehaviorQueue(tarinai, queue = []) {
|
|
if (!tarinai) return [];
|
|
tarinai.forcedBehaviorQueue = (Array.isArray(queue) ? queue : []).filter(Boolean);
|
|
return tarinai.forcedBehaviorQueue;
|
|
}
|
|
|
|
function normalizeForcedBehaviorEntry(tarinai, actionId, opts = {}) {
|
|
const now = tarinai?.world?.time || 0;
|
|
const source = opts.source || "external";
|
|
const basePriority = forcedBehaviorSourcePriority(source);
|
|
const raw = {
|
|
uid: opts.uid || `${String(actionId)}:${source}:${now.toFixed(3)}:${Math.random().toString(36).slice(2, 8)}`,
|
|
id: String(actionId),
|
|
targetId: opts.targetId ?? behaviorTargetId(opts.target),
|
|
reason: opts.reasonText || opts.reason || "",
|
|
causeText: opts.causeText || "",
|
|
source,
|
|
priority: Number(opts.priority ?? Math.max(basePriority, actionPriority(actionId) + 70)) || basePriority,
|
|
force: opts.force !== false,
|
|
replaceSameSource: opts.replaceSameSource !== false,
|
|
interrupt: opts.interrupt !== false,
|
|
createdAt: now,
|
|
status: opts.status || "queued",
|
|
attempts: Number(opts.attempts || 0) || 0,
|
|
lastTriedAt: Number(opts.lastTriedAt ?? -999) || -999,
|
|
retryDelay: Number(opts.retryDelay ?? 0.25) || 0.25,
|
|
expiresAt: Number.isFinite(opts.expiresAt) ? opts.expiresAt : now + Number(opts.ttl ?? 18),
|
|
duration: Number(opts.duration ?? 0) || 0,
|
|
minDuration: Number(opts.minDuration ?? opts.duration ?? 0) || 0,
|
|
searchRange: Number(opts.searchRange ?? 0) || 0,
|
|
};
|
|
return typeof normalizeTarinaiForcedRequest === "function" ? { ...raw, ...normalizeTarinaiForcedRequest(raw, raw), force: raw.force, replaceSameSource: raw.replaceSameSource, interrupt: raw.interrupt, retryDelay: raw.retryDelay, attempts: raw.attempts, lastTriedAt: raw.lastTriedAt, searchRange: raw.searchRange } : raw;
|
|
}
|
|
|
|
function markForcedBehaviorActive(tarinai, entry, action = null) {
|
|
if (!tarinai || !entry) return null;
|
|
entry.status = "active";
|
|
entry.activeSince = entry.activeSince || tarinai.world?.time || 0;
|
|
entry.startedAt = entry.startedAt || entry.activeSince;
|
|
const payload = { ...entry, id: action?.id || entry.id, status: "active" };
|
|
return typeof setTarinaiForcedBehavior === "function" ? setTarinaiForcedBehavior(tarinai, payload) : payload;
|
|
}
|
|
|
|
function behaviorIsForced(behavior = null) {
|
|
return typeof isTarinaiBehaviorValueForced === "function"
|
|
? isTarinaiBehaviorValueForced(behavior)
|
|
: Boolean(behavior?.source === "forced" || behavior?.forcedId || behavior?.forcedRequest);
|
|
}
|
|
|
|
function isCurrentBehaviorForced(tarinai) {
|
|
return behaviorIsForced(currentTarinaiBehavior(tarinai));
|
|
}
|
|
|
|
function currentForcedBehaviorRequest(tarinai) {
|
|
if (typeof getTarinaiForcedBehavior === "function") return getTarinaiForcedBehavior(tarinai);
|
|
const b = currentTarinaiBehavior(tarinai);
|
|
return behaviorIsForced(b) ? (b.forcedRequest || { uid: b.forcedId || "", id: b.actionId || "", source: b.data?.forcedSource || b.source || "external", priority: Number(b.priority || 0) || 0 }) : null;
|
|
}
|
|
|
|
function clearForcedBehaviorQueue(tarinai, predicate = null) {
|
|
if (!tarinai) return 0;
|
|
const queue = forcedBehaviorQueueOf(tarinai);
|
|
const before = queue.length;
|
|
const next = typeof predicate !== "function" ? [] : queue.filter(entry => !predicate(entry));
|
|
setForcedBehaviorQueue(tarinai, next);
|
|
const active = currentForcedBehaviorRequest(tarinai);
|
|
if (active?.uid && !next.some(entry => entry && entry.uid === active.uid) && typeof clearTarinaiForcedBehavior === "function") clearTarinaiForcedBehavior(tarinai);
|
|
return before - next.length;
|
|
}
|
|
|
|
function behaviorText(tarinai) {
|
|
const b = currentTarinaiBehavior(tarinai);
|
|
if (!b) return "";
|
|
const state = String(tarinai.state || "");
|
|
const timerActive = (tarinai.birthRitualTimer || 0) > 0.04 || (tarinai.fightTimer || 0) > 0.04 || (tarinai.eatTimer || 0) > 0.04 || (tarinai.intimidateTimer || 0) > 0.04 || (tarinai.fearTimer || 0) > 0.04;
|
|
const validByState = !b.state || b.state === state || timerActive || ["seek_food", "seek_water", "seek_friend", "seek_enemy", "follow_parent", "seek_bed", "wander", "build", "play_ball"].includes(state);
|
|
if (!validByState && !behaviorIsForced(b)) return "";
|
|
return composeTarinaiBehaviorText(tarinai, b) || String(b.text || b.reason || b.label || "").trim();
|
|
}
|
|
|
|
|
|
function setBehavior(tarinai, fields = {}) {
|
|
if (!tarinai || tarinai.dead) return null;
|
|
const now = tarinai.world?.time || 0;
|
|
const previous = (currentTarinaiBehavior(tarinai)) || {};
|
|
const previousActionId = previous.actionId || "";
|
|
const actionId = fields.actionId || fields.id || previousActionId || tarinai.state || "idle";
|
|
const action = fields.action || actionById(actionId) || null;
|
|
const need = fields.need || action?.need || previous.need || "fulfill";
|
|
const label = fields.label || fields.actionLabel || action?.label || previous.label || "\u884c\u52d5\u3057\u3066\u3044\u308b";
|
|
const reason = fields.reason || fields.text || label;
|
|
const startedAt = Number.isFinite(fields.startedAt) ? fields.startedAt : (Number.isFinite(previous.startedAt) && previousActionId === actionId ? previous.startedAt : now);
|
|
const lockSeconds = Number(fields.lockSeconds ?? previous.lockSeconds ?? (action ? actionLockSeconds(action, tarinai) : 1.0)) || 1.0;
|
|
const minDuration = Number(fields.minDuration ?? previous.minDuration ?? Math.max(0.45, lockSeconds * 0.72)) || 0.45;
|
|
const target = fields.target !== undefined ? fields.target : tarinai.target;
|
|
const forced = Boolean(fields.forced ?? (typeof isTarinaiBehaviorValueForced === "function" ? isTarinaiBehaviorValueForced(previous) : Boolean(previous.forcedId || previous.forcedRequest)));
|
|
const source = forced ? "forced" : (fields.source || previous.source || "need");
|
|
const forcedRequest = fields.forcedRequest || previous.forcedRequest || null;
|
|
const next = {
|
|
actionId,
|
|
need,
|
|
subNeed: fields.subNeed || action?.subNeed || previous.subNeed || "",
|
|
phase: fields.phase || previous.phase || (String(tarinai.state || "") === "idle" ? "idle" : "acting"),
|
|
label,
|
|
text: fields.text || reason,
|
|
reason,
|
|
target,
|
|
startedAt,
|
|
elapsed: Math.max(0, now - startedAt),
|
|
minDuration,
|
|
lockSeconds,
|
|
deadlineAt: Number.isFinite(fields.deadlineAt) ? fields.deadlineAt : previous.deadlineAt,
|
|
source,
|
|
sourceReasonText: fields.sourceReasonText || previous.sourceReasonText || "",
|
|
causeText: fields.causeText || previous.causeText || "",
|
|
forcedId: fields.forcedId || previous.forcedId || "",
|
|
forcedRequest,
|
|
priority: Number(fields.priority ?? previous.priority ?? (action ? actionPriority(action) : 0)) || 0,
|
|
interruptible: fields.interruptible !== false,
|
|
data: { ...(previous.data || {}), ...(fields.data || {}) },
|
|
tiedNeeds: fields.tiedNeeds || previous.tiedNeeds || [need],
|
|
specId: fields.specId || action?.id || previous.specId || actionId,
|
|
specKind: fields.specKind || action?.kind || previous.specKind || "need_action",
|
|
};
|
|
if (forcedRequest?.source) next.data.forcedSource = forcedRequest.source;
|
|
tarinai.behaviorSerial = (tarinai.behaviorSerial || 0) + 1;
|
|
next.serial = tarinai.behaviorSerial;
|
|
if (typeof setTarinaiBehavior === "function") tarinai.behavior = setTarinaiBehavior(tarinai, next);
|
|
else tarinai.behavior = next;
|
|
if (target !== undefined) tarinai.target = target;
|
|
if (reason && (!tarinai.thought || tarinai.thought === previous.reason || tarinai.thought === previous.text || tarinai.thought === previous.label)) tarinai.thought = reason;
|
|
return tarinai.behavior || next;
|
|
}
|
|
|
|
function setBehaviorFromAction(tarinai, action, choice = {}, reasonText = "", options = {}) {
|
|
if (!action) return null;
|
|
return setBehavior(tarinai, {
|
|
actionId: action.id,
|
|
action: action,
|
|
need: choice.need || action.need,
|
|
subNeed: options.subNeed || action.subNeed || "",
|
|
label: action.label || options.actionLabel || "\u884c\u52d5\u3057\u3066\u3044\u308b",
|
|
text: reasonText || action.label,
|
|
reason: reasonText || action.label,
|
|
target: options.target !== undefined ? options.target : tarinai?.target,
|
|
tiedNeeds: choice.tiedNeeds || [choice.need || action.need],
|
|
source: options.forced ? "forced" : (options.source || "need"),
|
|
forced: Boolean(options.forced),
|
|
forcedId: options.forcedId || "",
|
|
sourceReasonText: options.sourceReasonText || "",
|
|
causeText: options.causeText || "",
|
|
forcedRequest: options.forcedRequest || null,
|
|
priority: options.priority ?? actionPriority(action),
|
|
lockSeconds: options.lockSeconds,
|
|
minDuration: options.minDuration,
|
|
phase: options.phase || "starting",
|
|
specId: action.id,
|
|
specKind: action.kind || "need_action",
|
|
});
|
|
}
|
|
|
|
function clearBehavior(tarinai, reason = "") {
|
|
if (!tarinai) return;
|
|
clearTarinaiBehavior(tarinai, { reason });
|
|
if (reason) tarinai.thought = reason;
|
|
}
|
|
|
|
function queueForcedTarinaiBehavior(tarinai, actionId, opts = {}) {
|
|
if (!tarinai || tarinai.dead || !actionId) return false;
|
|
const entry = normalizeForcedBehaviorEntry(tarinai, actionId, opts);
|
|
let queue = forcedBehaviorQueueOf(tarinai);
|
|
if (entry.replaceSameSource) queue = queue.filter(e => !(e && e.id === entry.id && e.source === entry.source));
|
|
queue.push(entry);
|
|
queue.sort((a, b) => (Number(b.priority || 0) - Number(a.priority || 0)) || (Number(a.createdAt || 0) - Number(b.createdAt || 0)));
|
|
setForcedBehaviorQueue(tarinai, queue.slice(0, 12));
|
|
if (opts.target) tarinai.target = opts.target;
|
|
if (opts.applyShock && typeof applyNeedShock === "function") applyNeedShock(tarinai, opts.applyShock, opts.target || null);
|
|
return true;
|
|
}
|
|
|
|
|
|
function completeForcedBehavior(tarinai, behavior = null, status = "completed") {
|
|
if (!tarinai) return false;
|
|
const b = behavior || (currentTarinaiBehavior(tarinai)) || {};
|
|
const forcedId = b.forcedId || "";
|
|
if (!forcedId) return false;
|
|
const queue = forcedBehaviorQueueOf(tarinai);
|
|
const before = queue.length;
|
|
const next = queue.filter(entry => entry && entry.uid !== forcedId);
|
|
setForcedBehaviorQueue(tarinai, next);
|
|
if (typeof clearTarinaiForcedBehavior === "function") clearTarinaiForcedBehavior(tarinai);
|
|
return before !== next.length;
|
|
}
|
|
|
|
function retryForcedBehavior(tarinai, behavior = null, reason = "retry") {
|
|
if (!tarinai) return false;
|
|
const b = behavior || (currentTarinaiBehavior(tarinai)) || {};
|
|
const forcedId = b.forcedId || "";
|
|
if (!forcedId) return false;
|
|
const now = tarinai.world?.time || 0;
|
|
const entry = forcedBehaviorQueueOf(tarinai).find(e => e && e.uid === forcedId);
|
|
if (!entry) return false;
|
|
entry.status = "queued";
|
|
entry.failReason = reason;
|
|
entry.activeSince = 0;
|
|
if (typeof clearTarinaiForcedBehavior === "function") clearTarinaiForcedBehavior(tarinai);
|
|
return true;
|
|
}
|
|
|
|
function processForcedBehaviorQueue(tarinai, world, needs) {
|
|
if (!tarinai || tarinai.dead) return false;
|
|
const now = world?.time || 0;
|
|
let queue = forcedBehaviorQueueOf(tarinai).filter(entry => entry && (!Number.isFinite(entry.expiresAt) || entry.expiresAt >= now));
|
|
if (!queue.length) {
|
|
setForcedBehaviorQueue(tarinai, []);
|
|
if (typeof clearTarinaiForcedBehavior === "function") clearTarinaiForcedBehavior(tarinai);
|
|
return false;
|
|
}
|
|
|
|
// \u5b9f\u884c\u4e2d\u306e\u5f37\u5236\u884c\u52d5\u306f\u30ad\u30e5\u30fc\u306b\u6b8b\u3057\u3001\u5b8c\u4e86/\u5931\u6557/\u671f\u9650\u5207\u308c\u307e\u3067\u52b9\u679c\u3092\u4fdd\u6301\u3059\u308b\u3002
|
|
const activeQueueBehavior = currentTarinaiBehavior(tarinai);
|
|
const activeForcedId = activeQueueBehavior?.forcedId || "";
|
|
for (const entry of queue) {
|
|
if (!entry.uid) entry.uid = `${entry.id}:${entry.source || "external"}:${entry.createdAt || now}:${Math.random().toString(36).slice(2, 8)}`;
|
|
if (entry.uid === activeForcedId && behaviorIsForced(activeQueueBehavior) && activeQueueBehavior?.actionId === entry.id) {
|
|
markForcedBehaviorActive(tarinai, entry, actionById(entry.id));
|
|
} else if (entry.status === "active") {
|
|
// behavior \u304c\u5916\u90e8\u8981\u56e0\u3067\u6d88\u3048\u305f\u5834\u5408\u306f\u3001\u671f\u9650\u5185\u306a\u3089\u518d\u8a66\u884c\u3078\u623b\u3059\u3002
|
|
entry.status = "queued";
|
|
}
|
|
}
|
|
|
|
queue.sort((a, b) => (Number(b.priority || 0) - Number(a.priority || 0)) || (Number(a.createdAt || 0) - Number(b.createdAt || 0)));
|
|
setForcedBehaviorQueue(tarinai, queue);
|
|
|
|
const activePriority = Number(activeQueueBehavior?.priority ?? actionPriority(activeQueueBehavior?.actionId)) || 0;
|
|
const activeForced = behaviorIsForced(activeQueueBehavior);
|
|
|
|
for (let i = 0; i < queue.length; i++) {
|
|
const entry = queue[i];
|
|
const action = actionById(entry.id);
|
|
if (!action) { queue.splice(i, 1); i--; continue; }
|
|
if (entry.status === "active" && activeQueueBehavior?.forcedId === entry.uid) continue;
|
|
const entryPriority = Number(entry.priority ?? actionPriority(entry.id)) || 0;
|
|
if (activeQueueBehavior && (tarinai.behaviorLockTimer || 0) > 0.08) {
|
|
if (activeForced && entryPriority <= activePriority + 8) continue;
|
|
if (!entry.interrupt && entryPriority <= activePriority) continue;
|
|
if (entryPriority <= activePriority && !entry.force) continue;
|
|
}
|
|
if (now - Number(entry.lastTriedAt || -999) < Number(entry.retryDelay || 0.25)) continue;
|
|
entry.lastTriedAt = now;
|
|
entry.attempts = (Number(entry.attempts || 0) || 0) + 1;
|
|
|
|
const target = resolveForcedBehaviorTarget(tarinai, world, entry, action);
|
|
if (target) {
|
|
tarinai.target = target;
|
|
entry.targetId = behaviorTargetId(target);
|
|
}
|
|
if ((action.id === "fight_rival" || action.id === "intimidate_enemy" || action.id === "approach_mate" || action.id === "birth_ritual") && !target) {
|
|
continue;
|
|
}
|
|
|
|
const need = action.need || entry.need || "safety";
|
|
const choice = { need, tiedNeeds: [need], max: Math.max(needThreshold(need, "start"), Number(needs?.[need] || 0) || 0) };
|
|
const causeText = entry.causeText || forcedCausePhrase(entry.source || "", entry.reasonText || "");
|
|
const reasonText = buildReasonText(need, choice.tiedNeeds, action, tarinai, world, { causeText });
|
|
if (activeQueueBehavior && entryPriority > activePriority) {
|
|
retryForcedBehavior(tarinai, activeQueueBehavior, "interrupted_by_higher_forced_behavior");
|
|
clearBehavior(tarinai);
|
|
tarinai.behaviorLockTimer = 0;
|
|
if (tarinai.state === "sleep" || tarinai.sleeping) {
|
|
tarinai.sleeping = false;
|
|
tarinai.sleepSession = null;
|
|
}
|
|
}
|
|
const started = startNeedAction(tarinai, world, choice, action, reasonText, {
|
|
forced: true,
|
|
forcedId: entry.uid,
|
|
forcedReasonText: entry.reasonText || reasonText,
|
|
sourceReasonText: entry.reasonText || reasonText,
|
|
causeText,
|
|
priority: entryPriority,
|
|
source: entry.source,
|
|
target,
|
|
forcedRequest: { ...entry, status: "active", reason: entry.reasonText || reasonText, causeText },
|
|
lockSeconds: entry.duration || undefined,
|
|
minDuration: entry.minDuration || undefined,
|
|
});
|
|
if (started) {
|
|
markForcedBehaviorActive(tarinai, entry, action);
|
|
setForcedBehaviorQueue(tarinai, queue);
|
|
return true;
|
|
}
|
|
}
|
|
setForcedBehaviorQueue(tarinai, queue);
|
|
return false;
|
|
}
|
|
|
|
function effectCauseTextLooksStale(text = "") {
|
|
return /\u306E\u52B9\u679C$/.test(String(text || "").trim().replace(/[\u3002\uFF01\uFF1F]+$/, ""));
|
|
}
|
|
|
|
function setBehaviorText(tarinai, { need = null, actionId = null, actionLabel = null, reasonText = null, reason = null, target = undefined, subNeed = "", phase = "active", source = "state", forced = null, forcedId = "", sourceReasonText = "", forcedReasonText = "", causeText = "" } = {}) {
|
|
if (!tarinai) return;
|
|
const now = tarinai.world?.time || 0;
|
|
const previousBehavior = (currentTarinaiBehavior(tarinai)) || {};
|
|
const previousActionId = previousBehavior.actionId || "";
|
|
const nextNeed = need || previousBehavior.need || "social";
|
|
const nextActionId = actionId || previousActionId || tarinai.state || "idle";
|
|
const displayReason = reasonText ?? reason;
|
|
const nextActionLabel = actionLabel || displayReason || previousBehavior.label || tarinai.state || "\u884c\u52d5\u3057\u3066\u3044\u308b";
|
|
const action = actionById(nextActionId);
|
|
const previousForced = behaviorIsForced(previousBehavior);
|
|
const preserveForced = Boolean(previousForced && source === "state" && (!actionId || previousActionId === nextActionId));
|
|
let nextReasonText = displayReason || nextActionLabel;
|
|
const rawNeedsForText = tarinai.needRaw || tarinai.needs || {};
|
|
const existingTiedNeeds = (Array.isArray(previousBehavior.tiedNeeds) && previousBehavior.tiedNeeds.length ? [...previousBehavior.tiedNeeds] : [nextNeed])
|
|
.filter((key, idx) => idx === 0 || Number(rawNeedsForText?.[key] || 0) > 0.5);
|
|
if (existingTiedNeeds.some(key => key && key !== nextNeed) && !String(nextReasonText).includes("\u3051\u3069\u3001")) {
|
|
nextReasonText = buildReasonText(nextNeed, existingTiedNeeds, { ...(action || {}), id: nextActionId, label: nextActionLabel, subNeed: subNeed || action?.subNeed || "" }, tarinai, tarinai.world || null);
|
|
}
|
|
const nextForced = forced ?? preserveForced;
|
|
const nextSource = nextForced ? "forced" : (source || previousBehavior.source || "state");
|
|
const nextForcedId = nextForced ? (forcedId || previousBehavior.forcedId || "") : "";
|
|
const nextSourceReason = nextForced ? (sourceReasonText || forcedReasonText || previousBehavior.sourceReasonText || "") : (sourceReasonText || forcedReasonText || "");
|
|
const explicitCauseText = String(causeText || "");
|
|
const previousCauseText = String(previousBehavior.causeText || "");
|
|
const nextCauseText = explicitCauseText || ((nextForced || !effectCauseTextLooksStale(previousCauseText)) ? previousCauseText : "");
|
|
if (nextCauseText) {
|
|
nextReasonText = buildReasonText(nextNeed, existingTiedNeeds.length ? existingTiedNeeds : [nextNeed], { ...(action || {}), id: nextActionId, label: nextActionLabel, subNeed: subNeed || action?.subNeed || "" }, tarinai, tarinai.world || null, { causeText: nextCauseText });
|
|
}
|
|
if (target !== undefined) tarinai.target = target;
|
|
setBehavior(tarinai, {
|
|
actionId: nextActionId,
|
|
action,
|
|
need: nextNeed,
|
|
subNeed: subNeed || action?.subNeed || previousBehavior.subNeed || "",
|
|
label: nextActionLabel,
|
|
text: nextReasonText,
|
|
reason: nextReasonText,
|
|
target: target !== undefined ? target : tarinai.target,
|
|
tiedNeeds: existingTiedNeeds,
|
|
source: nextSource,
|
|
sourceReasonText: nextSourceReason,
|
|
causeText: nextCauseText,
|
|
forced: nextForced,
|
|
forcedId: nextForcedId,
|
|
forcedRequest: nextForced ? (previousBehavior.forcedRequest || null) : null,
|
|
priority: previousBehavior.priority ?? actionPriority(action),
|
|
phase,
|
|
startedAt: previousBehavior.startedAt || now,
|
|
minDuration: previousBehavior.minDuration || 0.8,
|
|
lockSeconds: previousBehavior.lockSeconds || 1.0,
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
function synchronizeActionTextFromState(tarinai) {
|
|
if (!tarinai || tarinai.dead) return;
|
|
const state = String(tarinai.state || "");
|
|
const catalogText = typeof window !== "undefined" && window.TEXT_CATALOG?.reasonLabel ? window.TEXT_CATALOG.reasonLabel(tarinai) : "";
|
|
if (state === "birth_ritual" || (tarinai.birthRitualTimer || 0) > 0.04) {
|
|
const partner = tarinai.world?.tarinai?.find?.(o => o && !o.dead && o.id === tarinai.birthPartnerId) || tarinai.target || null;
|
|
const label = partner?.name ? `${partner.name}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b` : "\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b";
|
|
setBehaviorText(tarinai, { need: "social", actionId: "birth_ritual", actionLabel: "\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b", reason: label, target: partner });
|
|
return;
|
|
}
|
|
if (state === "eat" || (tarinai.eatTimer || 0) > 0.04 || (tarinai.grassEatTimer || 0) > 0.04) {
|
|
setBehaviorText(tarinai, { need: "food", actionId: "eat_food", actionLabel: "\u98df\u3079\u3066\u3044\u308b", reason: catalogText || "\u98df\u3079\u3066\u3044\u308b" });
|
|
return;
|
|
}
|
|
if (state === "fight" || (tarinai.fightTimer || 0) > 0.04) {
|
|
setBehaviorText(tarinai, { need: "social", actionId: "fight_rival", actionLabel: "\u55a7\u5629\u3057\u3066\u3044\u308b", reason: catalogText || "\u55a7\u5629\u3057\u3066\u3044\u308b" });
|
|
return;
|
|
}
|
|
if (state === "panic" || (tarinai.defeatedTimer || 0) > 0.04) {
|
|
setBehaviorText(tarinai, { need: "safety", actionId: "panic_escape", actionLabel: "\u9003\u3052\u3066\u3044\u308b", reason: catalogText || "\u6016\u304f\u3066\u9003\u3052\u3066\u3044\u308b" });
|
|
return;
|
|
}
|
|
if (state === "intimidate" || (tarinai.intimidateTimer || 0) > 0.04) {
|
|
setBehaviorText(tarinai, { need: "social", actionId: "intimidate_enemy", actionLabel: "\u5a01\u5687\u3057\u3066\u3044\u308b", reason: catalogText || "\u5a01\u5687\u3057\u3066\u3044\u308b" });
|
|
return;
|
|
}
|
|
if (state === "sleep" || tarinai.sleeping) {
|
|
setBehaviorText(tarinai, { need: "sleep", actionId: (getTarinaiBehaviorId(tarinai)) || "sleep_anywhere", actionLabel: "\u7720\u3063\u3066\u3044\u308b", reason: catalogText || "\u7720\u3063\u3066\u3044\u308b" });
|
|
}
|
|
}
|