250 lines
13 KiB
JavaScript
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);
|