tarinai/js/tarinai_behavior_state.js
2026-06-28 23:07:40 +09:00

250 lines
13 KiB
JavaScript

"use strict";
(function (global) {
const Tarinai = global.Tarinai;
if (!Tarinai) throw new Error("Tarinai is not available for mixin: tarinai_behavior_state.js");
const PHASES = new Set(["starting", "seeking", "approaching", "acting", "recovering", "finished", "failed", "idle", "start", "active", "perform"]);
function isObject(value) {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function finiteOr(value, fallback) {
const n = Number(value);
return Number.isFinite(n) ? n : fallback;
}
function nowFor(tarinai = null) {
return finiteOr(tarinai?.world?.time, 0);
}
function normalizePhase(value, fallback = "acting") {
const phase = String(value || fallback || "acting");
return PHASES.has(phase) ? phase : phase;
}
function targetIdFor(target = null) {
if (!target) return null;
if (target.id != null) return String(target.id);
if (Number.isFinite(target.x) && Number.isFinite(target.y)) return `pos:${Math.round(target.x)},${Math.round(target.y)}`;
return null;
}
function normalizeBehaviorTarget(input = undefined, fallback = undefined) {
const target = input !== undefined ? input : fallback;
if (!target) return null;
if (target.kind) {
const out = { kind: String(target.kind) };
if (target.id != null) out.id = String(target.id);
if (Number.isFinite(Number(target.x))) out.x = Number(target.x);
if (Number.isFinite(Number(target.y))) out.y = Number(target.y);
return out;
}
if (target.id != null) {
const type = String(target.type || "");
const kind = target.kind === "tarinai" || target.familyKey || type === "tarinai" ? "tarinai" : "item";
return { kind, id: String(target.id) };
}
if (Number.isFinite(Number(target.x)) && Number.isFinite(Number(target.y))) {
return { kind: "position", x: Number(target.x), y: Number(target.y) };
}
return null;
}
function normalizeForcedRequestValue(value = null, fallback = null) {
const src = isObject(value) ? value : {};
const prev = isObject(fallback) ? fallback : {};
const uid = String(src.uid || src.forcedId || prev.uid || prev.forcedId || "");
const id = String(src.id || src.actionId || prev.id || prev.actionId || "");
if (!uid && !id) return null;
return {
uid,
id,
source: String(src.source || prev.source || "external"),
priority: finiteOr(src.priority, finiteOr(prev.priority, 0)),
status: String(src.status || prev.status || "active"),
reasonText: String(src.reasonText || src.reason || prev.reasonText || prev.reason || ""),
causeText: String(src.causeText || prev.causeText || ""),
targetId: src.targetId !== undefined ? src.targetId : (prev.targetId !== undefined ? prev.targetId : null),
createdAt: finiteOr(src.createdAt, finiteOr(prev.createdAt, 0)),
startedAt: finiteOr(src.startedAt, finiteOr(src.activeSince, finiteOr(prev.startedAt, finiteOr(prev.activeSince, 0)))),
activeSince: finiteOr(src.activeSince, finiteOr(src.startedAt, finiteOr(prev.activeSince, finiteOr(prev.startedAt, 0)))),
expiresAt: Number.isFinite(Number(src.expiresAt)) ? Number(src.expiresAt) : (Number.isFinite(Number(prev.expiresAt)) ? Number(prev.expiresAt) : undefined),
minDuration: finiteOr(src.minDuration, finiteOr(prev.minDuration, 0)),
duration: finiteOr(src.duration, finiteOr(prev.duration, 0)),
attempt: finiteOr(src.attempt ?? src.attempts, finiteOr(prev.attempt ?? prev.attempts, 0)),
failReason: String(src.failReason || prev.failReason || ""),
};
}
function isBehaviorForcedValue(behavior = null) {
if (!isObject(behavior)) return false;
return Boolean(behavior.source === "forced" || behavior.forcedId || behavior.forcedRequest || behavior.data?.forcedSource || behavior.forced === true);
}
function forcedBehaviorSource(behavior = null) {
if (!isObject(behavior)) return "";
return String(behavior.forcedRequest?.source || behavior.data?.forcedSource || behavior.sourceDetail || "");
}
function isEffectCauseText(text = "") {
return /の効果$/.test(String(text || "").trim().replace(/[。!?]+$/, ""));
}
function normalizeBehaviorValue(input, tarinai = null, previous = null) {
if (!isObject(input)) return null;
const now = nowFor(tarinai);
const previousActionId = previous?.actionId || "";
const actionId = String(input.actionId || previousActionId || tarinai?.state || "idle");
const targetInput = input.target !== undefined ? input.target : (input.targetRef !== undefined ? input.targetRef : undefined);
const target = normalizeBehaviorTarget(targetInput, tarinai?.target);
const forcedRequest = normalizeForcedRequestValue(
input.forcedRequest || input.request || null,
(input.forced || input.forcedId || previous?.forcedRequest)
? { ...(previous?.forcedRequest || {}), uid: input.forcedId || previous?.forcedId || previous?.forcedRequest?.uid || "", id: actionId, source: input.sourceDetail || input.source || previous?.data?.forcedSource || previous?.forcedRequest?.source || "external", priority: input.priority ?? previous?.priority ?? 0, reasonText: input.sourceReasonText || input.forcedReasonText || input.reason || input.reasonText || previous?.sourceReasonText || previous?.forcedRequest?.reasonText || "", causeText: input.causeText || previous?.causeText || "", targetId: input.targetId ?? targetIdFor(target) }
: null
);
const forced = Boolean(forcedRequest || input.forced || input.forcedId || previous?.forcedId && input.source === "forced");
const source = forced ? "forced" : String(input.source || previous?.source || "need");
const data = isObject(previous?.data) ? { ...previous.data } : {};
if (isObject(input.data)) Object.assign(data, input.data);
if (forcedRequest) data.forcedSource = forcedRequest.source || data.forcedSource || "external";
else if (forced && input.source && input.source !== "forced") data.forcedSource = String(input.source);
const reason = String(input.reason ?? input.reasonText ?? input.presentText ?? previous?.reason ?? previous?.reasonText ?? previous?.presentText ?? input.label ?? previous?.label ?? "");
const label = String(input.label || input.actionLabel || previous?.label || actionId || "\u884c\u52d5\u3057\u3066\u3044\u308b");
const need = String(input.need || previous?.need || "fulfill");
const tiedNeeds = Array.isArray(input.tiedNeeds) && input.tiedNeeds.length
? [...input.tiedNeeds].filter(Boolean).map(String)
: (Array.isArray(previous?.tiedNeeds) && previous.tiedNeeds.length ? [...previous.tiedNeeds] : [need]);
const behavior = {
actionId,
phase: normalizePhase(input.phase || previous?.phase || (String(tarinai?.state || "") === "idle" ? "idle" : "acting")),
target,
source,
reason,
priority: finiteOr(input.priority, finiteOr(previous?.priority, 0)),
startedAt: finiteOr(input.startedAt, finiteOr(previous?.startedAt, now)),
elapsed: finiteOr(input.elapsed, Math.max(0, now - finiteOr(input.startedAt ?? previous?.startedAt, now))),
timeout: Number.isFinite(Number(input.timeout ?? input.deadlineAt)) ? Number(input.timeout ?? input.deadlineAt) : (previous?.timeout ?? null),
interruptible: input.interruptible !== undefined ? input.interruptible !== false : previous?.interruptible !== false,
data,
need,
subNeed: String(input.subNeed || previous?.subNeed || ""),
tiedNeeds,
label,
text: String(input.text ?? input.presentText ?? input.reasonText ?? previous?.text ?? previous?.presentText ?? reason ?? label ?? ""),
lockSeconds: finiteOr(input.lockSeconds, finiteOr(previous?.lockSeconds, 1.0)),
minDuration: finiteOr(input.minDuration, finiteOr(previous?.minDuration, 0.45)),
deadlineAt: Number.isFinite(Number(input.deadlineAt)) ? Number(input.deadlineAt) : previous?.deadlineAt,
forcedId: forcedRequest?.uid || String(input.forcedId || previous?.forcedId || ""),
forcedRequest,
sourceReasonText: String(input.sourceReasonText || input.forcedReasonText || forcedRequest?.reasonText || previous?.sourceReasonText || ""),
causeText: String(input.causeText || forcedRequest?.causeText || ((forced || !isEffectCauseText(previous?.causeText)) ? previous?.causeText : "") || ""),
specId: String(input.specId || input.actionSpecId || previous?.specId || actionId),
specKind: String(input.specKind || previous?.specKind || "need_action"),
};
if (Number.isFinite(Number(input.serial))) behavior.serial = Number(input.serial);
return behavior;
}
function normalizeBehaviorState(opts = {}, tarinai = null) {
const previous = isObject(tarinai?.behavior) ? tarinai.behavior : null;
return normalizeBehaviorValue(opts.behavior || null, tarinai, previous);
}
function ensureBehavior(tarinai) {
if (!tarinai || !isObject(tarinai.behavior)) return null;
const b = tarinai.behavior;
return b;
}
function assignBehavior(tarinai, value) {
if (!tarinai) return null;
const next = normalizeBehaviorValue(value, tarinai, tarinai.behavior || null);
tarinai.behavior = next;
return next;
}
function patchTarinaiBehavior(tarinai, fields = {}) {
if (!tarinai || !isObject(fields)) return ensureBehavior(tarinai);
const previous = ensureBehavior(tarinai) || {};
const next = normalizeBehaviorValue({ ...previous, ...fields }, tarinai, previous);
tarinai.behavior = next;
return next;
}
function clearTarinaiBehavior(tarinai, opts = {}) {
if (!tarinai) return null;
tarinai.behavior = null;
if (opts.clearTarget) tarinai.target = null;
return null;
}
function getTarinaiBehavior(tarinai) { return ensureBehavior(tarinai); }
function getTarinaiBehaviorId(tarinai, fallback = "") { return String(ensureBehavior(tarinai)?.actionId || fallback || ""); }
function getTarinaiBehaviorNeed(tarinai, fallback = "") { return String(ensureBehavior(tarinai)?.need || fallback || ""); }
function getTarinaiBehaviorTiedNeeds(tarinai, fallbackNeed = "fulfill") {
const b = ensureBehavior(tarinai);
if (Array.isArray(b?.tiedNeeds) && b.tiedNeeds.length) return [...b.tiedNeeds];
return [String(b?.need || fallbackNeed || "fulfill")];
}
function getTarinaiBehaviorText(tarinai, fallback = "") {
const b = ensureBehavior(tarinai);
return String(b?.text || b?.reason || b?.label || fallback || "").trim();
}
function setTarinaiForcedBehavior(tarinai, value = null) {
if (!tarinai) return null;
const b = ensureBehavior(tarinai);
if (!b) return null;
if (!value) {
const data = isObject(b.data) ? { ...b.data } : {};
delete data.forcedSource;
const next = normalizeBehaviorValue({ ...b, source: b.source === "forced" ? "need" : (b.source || "need"), forcedId: "", forcedRequest: null, sourceReasonText: "", causeText: "", data }, tarinai, null);
tarinai.behavior = next;
return next;
}
const request = normalizeForcedRequestValue(value, { uid: b.forcedId || "", id: b.actionId || "idle", source: b.data?.forcedSource || "external", priority: b.priority || 0, reasonText: b.sourceReasonText || b.reason || "", causeText: b.causeText || "", targetId: targetIdFor(b.target) });
return patchTarinaiBehavior(tarinai, {
...b,
actionId: request?.id || value.actionId || value.id || b.actionId || "idle",
source: "forced",
priority: finiteOr(request?.priority, finiteOr(value.priority, finiteOr(b.priority, 0))),
forcedId: String(request?.uid || value.uid || value.forcedId || b.forcedId || ""),
forcedRequest: request,
sourceReasonText: request?.reasonText || b.sourceReasonText || "",
causeText: request?.causeText || b.causeText || "",
data: { ...(b.data || {}), forcedSource: request?.source || value.source || "external" },
});
}
function getTarinaiForcedBehavior(tarinai) {
const b = ensureBehavior(tarinai);
if (!isBehaviorForcedValue(b)) return null;
return b.forcedRequest || { uid: b.forcedId || "", id: b.actionId || "", source: forcedBehaviorSource(b) || "external", priority: finiteOr(b.priority, 0), reasonText: b.sourceReasonText || b.reason || "", causeText: b.causeText || "" };
}
Object.assign(global, {
normalizeTarinaiBehaviorState: normalizeBehaviorState,
normalizeTarinaiBehaviorValue: normalizeBehaviorValue,
getTarinaiBehavior,
currentTarinaiBehavior: getTarinaiBehavior,
getTarinaiBehaviorId,
getTarinaiBehaviorNeed,
getTarinaiBehaviorTiedNeeds,
getTarinaiBehaviorText,
patchTarinaiBehavior,
setTarinaiBehavior: assignBehavior,
clearTarinaiBehavior,
normalizeTarinaiForcedRequest: normalizeForcedRequestValue,
getTarinaiForcedBehavior,
setTarinaiForcedBehavior,
clearTarinaiForcedBehavior: function clearTarinaiForcedBehavior(tarinai) { return setTarinaiForcedBehavior(tarinai, null); },
isTarinaiBehaviorForced: function isTarinaiBehaviorForced(tarinai) { return isBehaviorForcedValue(ensureBehavior(tarinai)); },
isTarinaiBehaviorValueForced: isBehaviorForcedValue,
tarinaiBehaviorForcedSource: forcedBehaviorSource,
});
})(typeof window !== "undefined" ? window : globalThis);