stable
This commit is contained in:
parent
8bd4405fe8
commit
d5c661bc22
53 changed files with 4264 additions and 950 deletions
201
js/tarinai_action_spec.js
Normal file
201
js/tarinai_action_spec.js
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
"use strict";
|
||||
|
||||
(function (global) {
|
||||
const registry = new Map();
|
||||
|
||||
function isObject(value) {
|
||||
return Boolean(value && typeof value === "object");
|
||||
}
|
||||
|
||||
function finiteOr(value, fallback) {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : fallback;
|
||||
}
|
||||
|
||||
function toList(value) {
|
||||
if (Array.isArray(value)) return value.filter(Boolean).map(String);
|
||||
if (value == null || value === "") return [];
|
||||
return [String(value)];
|
||||
}
|
||||
|
||||
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.
|
||||
// Legacy hooks (`condition`, `run`, `update`) remain accepted so old code can be
|
||||
// migrated incrementally, but newly migrated actions should use the lifecycle names.
|
||||
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),
|
||||
tags: toList(input.tags),
|
||||
timerBacked: Boolean(input.timerBacked),
|
||||
interruptible: input.interruptible !== false,
|
||||
};
|
||||
|
||||
const originalCanStart = hasOwnFn(input, "canStart") ? input.canStart : null;
|
||||
const originalCondition = hasOwnFn(input, "condition") ? input.condition : null;
|
||||
const originalSelectTarget = hasOwnFn(input, "selectTarget") ? input.selectTarget : null;
|
||||
const originalStart = hasOwnFn(input, "start") ? input.start : null;
|
||||
const originalRun = hasOwnFn(input, "run") ? input.run : null;
|
||||
const originalTick = hasOwnFn(input, "tick") ? input.tick : null;
|
||||
const originalUpdate = hasOwnFn(input, "update") ? input.update : 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));
|
||||
if (originalCondition) return Boolean(originalCondition.call(spec, tarinai, world, ctx));
|
||||
return true;
|
||||
};
|
||||
|
||||
spec.start = function start(tarinai, world, ctx = {}) {
|
||||
if (originalStart) return originalStart.call(spec, tarinai, world, ctx);
|
||||
if (originalRun) return originalRun.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);
|
||||
if (originalUpdate) return originalUpdate.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;
|
||||
};
|
||||
|
||||
Object.defineProperty(spec, "__actionSpec", {
|
||||
value: true,
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
});
|
||||
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 registerTarinaiActionSpec(definition = {}) {
|
||||
const spec = normalizeTarinaiActionSpec(definition);
|
||||
registry.set(spec.id, spec);
|
||||
return spec;
|
||||
}
|
||||
|
||||
function getTarinaiActionSpec(id = "") {
|
||||
return registry.get(String(id || "")) || null;
|
||||
}
|
||||
|
||||
function listTarinaiActionSpecs() {
|
||||
return Array.from(registry.values());
|
||||
}
|
||||
|
||||
function selectTarinaiActionTarget(action, tarinai, world, ctx = {}) {
|
||||
const spec = resolveActionSpec(action);
|
||||
if (!spec) return null;
|
||||
return hasOwnFn(spec, "selectTarget") ? spec.selectTarget(tarinai, world, ctx) : (ctx.target ?? tarinai?.target ?? 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 hasOwnFn(spec, "start") ? spec.start(tarinai, world, nextCtx) : spec.run?.(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 hasOwnFn(spec, "tick") ? spec.tick(tarinai, world, dt, needs, nextCtx) : spec.update?.(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, {
|
||||
normalizeTarinaiActionSpec,
|
||||
createTarinaiActionSpec: normalizeTarinaiActionSpec,
|
||||
registerTarinaiActionSpec,
|
||||
registerTarinaiActionSpecs,
|
||||
getTarinaiActionSpec,
|
||||
listTarinaiActionSpecs,
|
||||
selectTarinaiActionTarget,
|
||||
canStartTarinaiAction,
|
||||
startTarinaiAction,
|
||||
tickTarinaiAction,
|
||||
finishTarinaiAction,
|
||||
failTarinaiAction,
|
||||
textTarinaiAction,
|
||||
TarinaiActionSpecRegistry: registry,
|
||||
});
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
Loading…
Add table
Add a link
Reference in a new issue