158 lines
5.7 KiB
JavaScript
158 lines
5.7 KiB
JavaScript
"use strict";
|
|
|
|
(function (global) {
|
|
const registry = new Map();
|
|
const isObject = global.TarinaiCoreHelpers?.isPlainObject || (value => Boolean(value && typeof value === "object"));
|
|
const finiteOr = global.TarinaiCoreHelpers?.finiteOr || ((value, fallback) => {
|
|
const n = Number(value);
|
|
return Number.isFinite(n) ? n : fallback;
|
|
});
|
|
|
|
|
|
function hasOwnFn(obj, key) {
|
|
return typeof obj?.[key] === "function";
|
|
}
|
|
|
|
function resolveActionSpec(action) {
|
|
if (!action) return null;
|
|
return typeof action === "string" ? getTarinaiActionSpec(action) : action;
|
|
}
|
|
|
|
function buildActionContext(tarinai, world, ctx = {}, spec = null) {
|
|
const base = isObject(ctx) ? { ...ctx } : {};
|
|
if (base.target === undefined && spec && hasOwnFn(spec, "selectTarget")) {
|
|
base.target = spec.selectTarget(tarinai, world, base);
|
|
}
|
|
return base;
|
|
}
|
|
|
|
function normalizeTarinaiActionSpec(input = {}) {
|
|
if (!isObject(input)) throw new TypeError("ActionSpec must be an object");
|
|
const id = String(input.id || input.actionId || "").trim();
|
|
if (!id) throw new TypeError("ActionSpec.id is required");
|
|
|
|
// Public lifecycle: canStart -> selectTarget -> start -> tick -> finish/fail -> text.
|
|
const spec = {
|
|
...input,
|
|
id,
|
|
kind: String(input.kind || "need_action"),
|
|
need: String(input.need || "fulfill"),
|
|
subNeed: String(input.subNeed || ""),
|
|
label: String(input.label || input.phrase || id),
|
|
phrase: String(input.phrase || input.label || id),
|
|
weight: finiteOr(input.weight, 1),
|
|
priority: finiteOr(input.priority, 0),
|
|
timerBacked: Boolean(input.timerBacked),
|
|
};
|
|
|
|
const originalCanStart = hasOwnFn(input, "canStart") ? input.canStart : null;
|
|
const originalSelectTarget = hasOwnFn(input, "selectTarget") ? input.selectTarget : null;
|
|
const originalStart = hasOwnFn(input, "start") ? input.start : null;
|
|
const originalTick = hasOwnFn(input, "tick") ? input.tick : null;
|
|
const originalFinish = hasOwnFn(input, "finish") ? input.finish : null;
|
|
const originalFail = hasOwnFn(input, "fail") ? input.fail : null;
|
|
const originalText = hasOwnFn(input, "text") ? input.text : null;
|
|
|
|
spec.selectTarget = function selectTarget(tarinai, world, ctx = {}) {
|
|
if (originalSelectTarget) return originalSelectTarget.call(spec, tarinai, world, ctx);
|
|
return ctx.target ?? tarinai?.target ?? null;
|
|
};
|
|
|
|
spec.canStart = function canStart(tarinai, world, ctx = {}) {
|
|
if (originalCanStart) return Boolean(originalCanStart.call(spec, tarinai, world, ctx));
|
|
return true;
|
|
};
|
|
|
|
spec.start = function start(tarinai, world, ctx = {}) {
|
|
if (originalStart) return originalStart.call(spec, tarinai, world, ctx);
|
|
return true;
|
|
};
|
|
|
|
spec.tick = function tick(tarinai, world, dt = 0, needs = null, ctx = {}) {
|
|
if (originalTick) return originalTick.call(spec, tarinai, world, dt, needs, ctx);
|
|
return undefined;
|
|
};
|
|
|
|
spec.finish = function finish(tarinai, world, ctx = {}) {
|
|
return originalFinish ? originalFinish.call(spec, tarinai, world, ctx) : true;
|
|
};
|
|
|
|
spec.fail = function fail(tarinai, world, ctx = {}) {
|
|
return originalFail ? originalFail.call(spec, tarinai, world, ctx) : false;
|
|
};
|
|
|
|
spec.text = function text(tarinai, world, ctx = {}) {
|
|
if (originalText) return String(originalText.call(spec, tarinai, world, ctx) || "");
|
|
if (ctx.phase === "finished") return spec.phrase;
|
|
return spec.label;
|
|
};
|
|
|
|
return spec;
|
|
}
|
|
|
|
function registerTarinaiActionSpecs(definitions = []) {
|
|
const specs = [];
|
|
for (const definition of definitions || []) {
|
|
const spec = normalizeTarinaiActionSpec(definition);
|
|
registry.set(spec.id, spec);
|
|
specs.push(spec);
|
|
}
|
|
return specs;
|
|
}
|
|
|
|
|
|
function getTarinaiActionSpec(id = "") {
|
|
return registry.get(String(id || "")) || null;
|
|
}
|
|
|
|
|
|
|
|
function canStartTarinaiAction(action, tarinai, world, ctx = {}) {
|
|
const spec = resolveActionSpec(action);
|
|
if (!spec) return false;
|
|
return hasOwnFn(spec, "canStart") ? Boolean(spec.canStart(tarinai, world, isObject(ctx) ? ctx : {})) : true;
|
|
}
|
|
|
|
function startTarinaiAction(action, tarinai, world, ctx = {}) {
|
|
const spec = resolveActionSpec(action);
|
|
if (!spec) return false;
|
|
const canCtx = isObject(ctx) ? ctx : {};
|
|
if (hasOwnFn(spec, "canStart") && !spec.canStart(tarinai, world, canCtx)) return false;
|
|
const nextCtx = buildActionContext(tarinai, world, canCtx, spec);
|
|
return spec.start(tarinai, world, nextCtx);
|
|
}
|
|
|
|
function tickTarinaiAction(action, tarinai, world, dt = 0, needs = null, ctx = {}) {
|
|
const spec = resolveActionSpec(action);
|
|
if (!spec) return undefined;
|
|
const nextCtx = buildActionContext(tarinai, world, ctx, spec);
|
|
return spec.tick(tarinai, world, dt, needs, nextCtx);
|
|
}
|
|
|
|
function finishTarinaiAction(action, tarinai, world, ctx = {}) {
|
|
const spec = resolveActionSpec(action);
|
|
return hasOwnFn(spec, "finish") ? spec.finish(tarinai, world, ctx) : true;
|
|
}
|
|
|
|
function failTarinaiAction(action, tarinai, world, ctx = {}) {
|
|
const spec = resolveActionSpec(action);
|
|
return hasOwnFn(spec, "fail") ? spec.fail(tarinai, world, ctx) : false;
|
|
}
|
|
|
|
function textTarinaiAction(action, tarinai, world, ctx = {}) {
|
|
const spec = resolveActionSpec(action);
|
|
if (!spec) return "";
|
|
return hasOwnFn(spec, "text") ? String(spec.text(tarinai, world, isObject(ctx) ? ctx : {}) || "") : String(spec.label || spec.phrase || spec.id || "");
|
|
}
|
|
|
|
Object.assign(global, {
|
|
registerTarinaiActionSpecs,
|
|
getTarinaiActionSpec,
|
|
canStartTarinaiAction,
|
|
startTarinaiAction,
|
|
tickTarinaiAction,
|
|
finishTarinaiAction,
|
|
failTarinaiAction,
|
|
textTarinaiAction,
|
|
});
|
|
})(typeof window !== "undefined" ? window : globalThis);
|