2026-06-25 14:51:58 +09:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
// ActionSpec factories and TARINAI_ACTIONS registry.
|
|
|
|
|
|
|
|
|
|
function selectRoleTarget(role, range) {
|
|
|
|
|
return function selectTarget(tarinai, world) {
|
|
|
|
|
return findNearestItemWithRole(world, tarinai, role, range);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 13:40:41 +09:00
|
|
|
function startMoveToTarget(state, fallbackLabel = "\u884c\u52d5\u3057\u3066\u3044\u308b") {
|
2026-06-25 14:51:58 +09:00
|
|
|
return function start(tarinai, world, ctx = {}) {
|
|
|
|
|
const target = ctx.target ?? this.selectTarget?.(tarinai, world, ctx) ?? null;
|
|
|
|
|
return moveToOrUse(tarinai, target, state, this.label || fallbackLabel);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectOwnedStructure(type, range = Infinity) {
|
|
|
|
|
return function selectTarget(tarinai, world) {
|
|
|
|
|
return findOwnedStructure(world, tarinai, type, range);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectMateTarget(tarinai, world, range = 440) {
|
|
|
|
|
return world?.nearestOther?.(tarinai, range, other => !world.areParentChild?.(tarinai, other) && !!tarinai.isZunchiSlave === !!other.isZunchiSlave && !other.sleepDisease && !other.fightDisease) || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function basicCanStartWithTarget(t, world, ctx = {}) {
|
|
|
|
|
return !!(ctx.target ?? this.selectTarget?.(t, world, ctx));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createConsumableActionSpec({ id, subNeed = "", state, label, phrase, weight, role, range }) {
|
|
|
|
|
return {
|
|
|
|
|
id,
|
|
|
|
|
need: role === "medicine" ? "health" : "food",
|
|
|
|
|
subNeed,
|
|
|
|
|
state,
|
|
|
|
|
label,
|
|
|
|
|
phrase,
|
|
|
|
|
weight,
|
|
|
|
|
timerBacked: true,
|
|
|
|
|
selectTarget: selectRoleTarget(role, range),
|
|
|
|
|
canStart: basicCanStartWithTarget,
|
|
|
|
|
start: startMoveToTarget(state, label),
|
|
|
|
|
tick(t, world, dt) { return updateConsumableBehavior(t, world, dt, role); },
|
|
|
|
|
finish(t) {
|
|
|
|
|
if (role === "drink" && typeof applyNeedSatisfaction === "function") applyNeedSatisfaction(t, { food: 8, health: 2 }, "drink");
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
fail(t) {
|
|
|
|
|
if (t) t.target = null;
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSleepInBedActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "sleep_in_bed",
|
|
|
|
|
need: "sleep",
|
|
|
|
|
state: "seek_bed",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u5bdd\u308b\u5834\u6240\u3092\u63a2\u3057\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u5bdd\u308b\u5834\u6240\u3078\u623b\u3063\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 42,
|
|
|
|
|
timerBacked: true,
|
|
|
|
|
selectTarget(t, world) { return findNearestSleepPlace(world, t, 760); },
|
|
|
|
|
canStart: basicCanStartWithTarget,
|
|
|
|
|
start(t, world, ctx = {}) {
|
|
|
|
|
const bed = ctx.target ?? this.selectTarget(t, world, ctx);
|
|
|
|
|
if (!bed) return false;
|
|
|
|
|
if (dist(t, bed) > (t.radius || 20) + 18) return moveToOrUse(t, bed, "seek_bed", this.label);
|
|
|
|
|
const isEasyBed = bed.type === "grass_bed";
|
|
|
|
|
bed.use?.(t, world, isEasyBed ? { purpose: "sleep" } : undefined);
|
2026-06-28 13:40:41 +09:00
|
|
|
t.startSleeping?.(bed, bed.type === "nest_box" ? "\u5de3\u7bb1\u306e\u4e2d\u3067\u5bdd\u3066\u3044\u308b" : (isEasyBed ? "\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9\u3067\u5bdd\u3066\u3044\u308b" : "\u30d9\u30c3\u30c9\u3067\u5bdd\u3066\u3044\u308b"));
|
2026-06-25 14:51:58 +09:00
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
tick(t) {
|
|
|
|
|
if (t?.state === "sleep" || t?.sleeping) return true;
|
|
|
|
|
return basicCanStartWithTarget.call(this, t, t?.world || null, {}) ? true : false;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSleepBuildGrassBedActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "sleep_build_grass_bed",
|
|
|
|
|
need: "sleep",
|
|
|
|
|
subNeed: "bed",
|
|
|
|
|
state: "build",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u5bdd\u308b\u5834\u6240\u304c\u306a\u3044\u306e\u3067\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9\u3092\u4f5c\u3063\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9\u3092\u4f5c\u3063\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 18,
|
|
|
|
|
timerBacked: true,
|
|
|
|
|
selectTarget: selectRoleTarget("grassMaterial", 520),
|
|
|
|
|
canStart(t, world, ctx = {}) {
|
|
|
|
|
return canBuildOwnedStructureByAge(t)
|
|
|
|
|
&& !findNearestSleepPlace(world, t, 760)
|
|
|
|
|
&& !findOwnedStructure(world, t, "grass_bed")
|
|
|
|
|
&& !!(ctx.target ?? this.selectTarget(t, world, ctx))
|
|
|
|
|
&& !["fight", "panic", "intimidate", "birth_ritual"].includes(t.state);
|
|
|
|
|
},
|
|
|
|
|
start(t, world) { return buildOwnedStructureFromGrass(t, world, "grass_bed", this.label); },
|
|
|
|
|
tick(t, world, dt) {
|
|
|
|
|
if (findOwnedStructure(world, t, "grass_bed")) return "finished";
|
|
|
|
|
return continueBuildPlan(t, world, dt) ? true : false;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSleepAnywhereActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "sleep_anywhere",
|
|
|
|
|
need: "sleep",
|
|
|
|
|
state: "sleep",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u306a\u306b\u3082\u306a\u3044\u6240\u3067\u5bdd\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u305d\u306e\u307e\u307e\u5bdd\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 8,
|
|
|
|
|
timerBacked: true,
|
|
|
|
|
canStart(t, world) {
|
|
|
|
|
if (findNearestSleepPlace(world, t, 760)) {
|
|
|
|
|
if (t) t.noBedNoBuildSince = NaN;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const now = world?.time || 0;
|
|
|
|
|
const failedRecently = (t?.lastFailedGrassBedBuildAt || -999) + 14 > now;
|
|
|
|
|
const buildTarget = this.selectBuildTarget ? this.selectBuildTarget(t, world, {}) : findNearbyMaterial(world, t, "grassMaterial", 520);
|
|
|
|
|
const canTryBuild = canBuildOwnedStructureByAge(t) && !!buildTarget && !(Number(t?.actionIdCooldowns?.sleep_build_grass_bed || 0) > 0.01);
|
|
|
|
|
if (canTryBuild && !failedRecently) {
|
|
|
|
|
if (t) t.noBedNoBuildSince = NaN;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (t && !Number.isFinite(t.noBedNoBuildSince)) t.noBedNoBuildSince = now;
|
|
|
|
|
const since = Number.isFinite(t?.noBedNoBuildSince) ? t.noBedNoBuildSince : now;
|
|
|
|
|
const waited = now - since;
|
|
|
|
|
const urgent = (t?.energy || 100) < 18 || (t?.needRaw?.sleep || t?.needs?.sleep || 0) >= 92;
|
|
|
|
|
return failedRecently || urgent || waited >= 6.0;
|
|
|
|
|
},
|
|
|
|
|
selectBuildTarget: selectRoleTarget("grassMaterial", 520),
|
|
|
|
|
start(t) { t.startSleeping?.(null, this.label); return true; },
|
|
|
|
|
tick(t) { return (t?.state === "sleep" || t?.sleeping) ? true : false; },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createRestActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "rest_to_recover",
|
|
|
|
|
need: "health",
|
|
|
|
|
state: "idle",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u5143\u6c17\u306b\u306a\u308b\u307e\u3067\u4f11\u3093\u3067\u3044\u308b",
|
|
|
|
|
phrase: "\u4f11\u3093\u3060",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 24,
|
|
|
|
|
canStart() { return true; },
|
|
|
|
|
start(t) {
|
|
|
|
|
t.setActionState?.("idle", { target: null, reason: this.label, sleeping: false });
|
2026-06-26 12:46:32 +09:00
|
|
|
t.energy = clamp((t.energy || 0) + 1.6, 0, typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(t) : (Number(t.maxEnergy) || 100));
|
2026-06-25 14:51:58 +09:00
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
tick(t) {
|
2026-06-26 12:46:32 +09:00
|
|
|
t.energy = clamp((t.energy || 0) + 0.8, 0, typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(t) : (Number(t.maxEnergy) || 100));
|
2026-06-25 14:51:58 +09:00
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSunbathActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "sunbath",
|
|
|
|
|
need: "health",
|
|
|
|
|
subNeed: "sunbath",
|
|
|
|
|
state: "sunbath",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u65e5\u5149\u6d74\u3057\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u65e5\u5149\u6d74\u3057\u3066\u3044\u308b",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 32,
|
|
|
|
|
timerBacked: true,
|
|
|
|
|
canStart(t, world, ctx = {}) {
|
|
|
|
|
const healthNeed = Number(t?.needRaw?.health ?? t?.needs?.health ?? 0) || 0;
|
|
|
|
|
return (ctx.leisure || healthNeed >= needThreshold("health", "start"))
|
|
|
|
|
&& !!t?.canStartSunbath?.({ leisure: !!ctx.leisure });
|
|
|
|
|
},
|
|
|
|
|
start(t, world, ctx = {}) {
|
|
|
|
|
return !!t?.startSunbath?.({ leisure: !!ctx.leisure });
|
|
|
|
|
},
|
|
|
|
|
tick(t) {
|
|
|
|
|
return (t?.sunbathTimer || 0) > 0.04 ? true : "finished";
|
|
|
|
|
},
|
|
|
|
|
finish(t) {
|
|
|
|
|
if ((t?.sunbathTimer || 0) <= 0 && t?.state === "sunbath") t.finishSunbath?.();
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
fail(t) {
|
|
|
|
|
if (t && t.state === "sunbath") {
|
|
|
|
|
t.sunbathTimer = 0;
|
2026-06-28 13:40:41 +09:00
|
|
|
t.goIdle?.("\u65e5\u5149\u6d74\u3092\u3084\u3081\u305f");
|
2026-06-25 14:51:58 +09:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createWanderActionSpec() {
|
|
|
|
|
function targetFor(t, world) {
|
|
|
|
|
const radius = rand(80, 180);
|
|
|
|
|
return {
|
|
|
|
|
x: clamp(t.x + Math.cos(t.wanderAngle) * radius, t.radius || 20, (world?.w || 1600) - (t.radius || 20)),
|
|
|
|
|
y: clamp(t.y + Math.sin(t.wanderAngle) * radius, t.radius || 20, (world?.h || 900) - (t.radius || 20)),
|
|
|
|
|
dead: false,
|
|
|
|
|
detour: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
id: "wander_lightly",
|
|
|
|
|
need: "fulfill",
|
|
|
|
|
state: "wander",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u5c11\u3057\u6b69\u3044\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u5c11\u3057\u6b69\u3044\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 14,
|
|
|
|
|
canStart() { return true; },
|
|
|
|
|
selectTarget: targetFor,
|
|
|
|
|
start(t, world, ctx = {}) {
|
|
|
|
|
t.wanderAngle += rand(-1.2, 1.2);
|
|
|
|
|
const target = ctx.target && Number.isFinite(ctx.target.x) ? ctx.target : targetFor(t, world);
|
|
|
|
|
t.wanderTarget = target;
|
|
|
|
|
t.setActionState?.("wander", { target, reason: this.label, sleeping: false });
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
tick(t, world) {
|
|
|
|
|
const target = t?.wanderTarget || currentBehaviorTarget(t) || null;
|
|
|
|
|
if (!target || !Number.isFinite(target.x) || dist(t, target) <= Math.max(28, (t.radius || 20) + 10)) return "finished";
|
|
|
|
|
moveToOrUse(t, target, "wander", this.label);
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectFightRivalTarget(tarinai, world, range = 360) {
|
|
|
|
|
const candidate = conflictTargetFor(tarinai, world, (tarinai?.fightMochiTimer || 0) > 0.04 || isCurrentBehaviorForced(tarinai) ? 18 : 34);
|
|
|
|
|
const current = currentBehaviorTarget(tarinai);
|
|
|
|
|
return isLiveTarinaiEntity(candidate?.target)
|
|
|
|
|
? candidate.target
|
|
|
|
|
: (isLiveTarinaiEntity(current) ? current : nearestForcedFightTarget(tarinai, world, isCurrentBehaviorForced(tarinai) ? 760 : range));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createPanicEscapeActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "panic_escape",
|
|
|
|
|
need: "safety",
|
|
|
|
|
subNeed: "panic",
|
|
|
|
|
state: "panic",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u6016\u304f\u3066\u9003\u3052\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u9003\u3052\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 62,
|
|
|
|
|
timerBacked: true,
|
|
|
|
|
selectTarget(t, world) {
|
|
|
|
|
return t.lastNeedShockBreaker || findNearbyDanger(world, t, 240) || ((t.defeatedById && world?.tarinai) ? world.liveTarinaiById?.(t.defeatedById) : null);
|
|
|
|
|
},
|
|
|
|
|
canStart(t, world, ctx = {}) {
|
|
|
|
|
const threat = ctx.target ?? this.selectTarget(t, world, ctx);
|
|
|
|
|
const safety = Number(t?.needRaw?.safety ?? t?.needs?.safety ?? 0) || 0;
|
|
|
|
|
// Do not start panic from ordinary safety pressure alone. Panic needs a
|
|
|
|
|
// concrete trigger, lingering fear, defeat, or an extreme safety spike.
|
|
|
|
|
return !!threat
|
|
|
|
|
|| (t?.defeatedTimer || 0) > 0.04
|
|
|
|
|
|| (safety >= 92 && !!activePanicBreaker(t, world, 260));
|
|
|
|
|
},
|
|
|
|
|
start(t, world, ctx = {}) {
|
|
|
|
|
const threat = ctx.target ?? this.selectTarget(t, world, ctx);
|
|
|
|
|
t.setActionState?.("panic", { target: t.panicDestination?.(threat, true), reason: this.label, wake: true, sleeping: false });
|
|
|
|
|
t.fearTimer = Math.max(t.fearTimer || 0, 1.6);
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
tick: updatePanicBehavior,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createFleeActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "flee",
|
|
|
|
|
need: "safety",
|
|
|
|
|
subNeed: "avoid",
|
|
|
|
|
state: "flee",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u5371\u306a\u3044\u3082\u306e\u304b\u3089\u96e2\u308c\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u96e2\u308c\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 40,
|
|
|
|
|
selectTarget(t, world) { return findNearbyDanger(world, t, 220); },
|
|
|
|
|
canStart: basicCanStartWithTarget,
|
|
|
|
|
start(t, world, ctx = {}) {
|
|
|
|
|
const danger = ctx.target ?? this.selectTarget(t, world, ctx);
|
|
|
|
|
t.setActionState?.("flee", { target: t.panicDestination?.(danger), reason: this.label, wake: true, sleeping: false });
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
tick: updatePanicBehavior,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createApproachFriendActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "approach_friend",
|
|
|
|
|
need: "social",
|
|
|
|
|
subNeed: "bond",
|
|
|
|
|
state: "seek_friend",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u4ef2\u9593\u306b\u8fd1\u3065\u3044\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u4ef2\u9593\u306b\u8fd1\u3065\u3044\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 46,
|
|
|
|
|
selectTarget(t, world) { return t.bestFriendLive?.(FRIEND_AFFINITY_THRESHOLD, 520) || world.nearestOther?.(t, 420) || null; },
|
|
|
|
|
canStart: basicCanStartWithTarget,
|
2026-06-28 13:40:41 +09:00
|
|
|
start: startMoveToTarget("seek_friend", "\u4ef2\u9593\u306b\u8fd1\u3065\u3044\u3066\u3044\u308b"),
|
2026-06-25 14:51:58 +09:00
|
|
|
tick(t, world, dt) { return updateSocialContactBehavior(t, world, dt, "bond"); },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createApproachFamilyActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "approach_parent_or_child",
|
|
|
|
|
need: "social",
|
|
|
|
|
subNeed: "family",
|
|
|
|
|
state: "follow_parent",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u5bb6\u65cf\u306b\u8fd1\u3065\u3044\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u5bb6\u65cf\u306b\u8fd1\u3065\u3044\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 28,
|
|
|
|
|
selectTarget(t) { return t.parentToFollow?.() || null; },
|
|
|
|
|
canStart: basicCanStartWithTarget,
|
2026-06-28 13:40:41 +09:00
|
|
|
start: startMoveToTarget("follow_parent", "\u5bb6\u65cf\u306b\u8fd1\u3065\u3044\u3066\u3044\u308b"),
|
2026-06-25 14:51:58 +09:00
|
|
|
tick(t, world, dt) { return updateSocialContactBehavior(t, world, dt, "family"); },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createApproachMateActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "approach_mate",
|
|
|
|
|
need: "social",
|
|
|
|
|
subNeed: "mate",
|
|
|
|
|
state: "seek_friend",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u7e41\u6b96\u3067\u304d\u308b\u76f8\u624b\u306b\u8fd1\u3065\u3044\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u7e41\u6b96\u3067\u304d\u308b\u76f8\u624b\u306b\u8fd1\u3065\u3044\u3066\u3044\u308b",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 48,
|
|
|
|
|
selectTarget(t, world) { return selectMateTarget(t, world, 520); },
|
|
|
|
|
canStart(t, world, ctx = {}) { return (t.reproductionTimer || 0) <= 12 && !!(ctx.target ?? this.selectTarget(t, world, ctx)); },
|
|
|
|
|
start(t, world, ctx = {}) {
|
|
|
|
|
const other = ctx.target ?? this.selectTarget(t, world, ctx);
|
|
|
|
|
if (!other) return false;
|
|
|
|
|
if (dist(t, other) <= Math.max(48, (t.radius || 20) + (other.radius || 20) + 12) && world.startBirthRitual?.(t, other)) return true;
|
|
|
|
|
return moveToOrUse(t, other, "seek_friend", this.label);
|
|
|
|
|
},
|
|
|
|
|
tick: updateMateBehavior,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createFightRivalActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "fight_rival",
|
|
|
|
|
need: "social",
|
|
|
|
|
subNeed: "conflict",
|
|
|
|
|
state: "seek_enemy",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u6c17\u306b\u5165\u3089\u306a\u3044\u76f8\u624b\u306b\u5411\u304b\u3063\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u55a7\u5629\u3092\u59cb\u3081\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 18,
|
|
|
|
|
timerBacked: true,
|
|
|
|
|
selectTarget(t, world) { return selectFightRivalTarget(t, world); },
|
|
|
|
|
canStart(t, world, ctx = {}) {
|
|
|
|
|
if ((t.fightCooldown || 0) > 0.04 || (t.postConflictPeaceTimer || 0) > 0.04) return false;
|
|
|
|
|
const forced = (t?.fightMochiTimer || 0) > 0.04 || isCurrentBehaviorForced(t);
|
|
|
|
|
const candidate = conflictTargetFor(t, world, forced ? 24 : 42);
|
|
|
|
|
if (!forced) {
|
|
|
|
|
const parts = t?.socialReasonParts || {};
|
|
|
|
|
const conflict = Number(parts.conflict || 0) || 0;
|
|
|
|
|
const socialPull = Math.max(Number(parts.bond || 0) || 0, Number(parts.mate || 0) || 0, Number(parts.family || 0) || 0);
|
|
|
|
|
if (!candidate?.target || conflict < 30 || conflict < socialPull * 0.82) return false;
|
|
|
|
|
}
|
|
|
|
|
return !!(ctx.target ?? candidate?.target ?? this.selectTarget(t, world, ctx));
|
|
|
|
|
},
|
|
|
|
|
start(t, world, ctx = {}) {
|
|
|
|
|
const candidate = conflictTargetFor(t, world, (t?.fightMochiTimer || 0) > 0.04 || isCurrentBehaviorForced(t) ? 18 : 34);
|
|
|
|
|
const rival = ctx.target ?? this.selectTarget(t, world, ctx);
|
|
|
|
|
if (!isLiveTarinaiEntity(rival)) return false;
|
|
|
|
|
t.conflictTargetId = rival.id;
|
|
|
|
|
t.conflictUrge = Math.max(t.conflictUrge || 0, candidate?.score || 48);
|
|
|
|
|
if (dist(t, rival) > Math.max(42, (t.radius || 20) + (rival.radius || 20) + 12)) return moveToOrUse(t, rival, "seek_enemy", this.label);
|
|
|
|
|
if (candidate?.forced || isCurrentBehaviorForced(t) || (t.fightMochiTimer || 0) > 0.04 || (rival.fightMochiTimer || 0) > 0.04) return !!world.startForcedFight?.(t, rival);
|
|
|
|
|
world.startConflict?.(t, rival);
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
tick: updateFightBehavior,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createIntimidateEnemyActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "intimidate_enemy",
|
|
|
|
|
need: "social",
|
|
|
|
|
subNeed: "conflict",
|
|
|
|
|
state: "intimidate",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u6c17\u306b\u306a\u308b\u76f8\u624b\u3092\u5a01\u5687\u3057\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u5a01\u5687\u3057\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 8,
|
|
|
|
|
timerBacked: true,
|
|
|
|
|
selectTarget(t) { return t.recentFightRival?.(80, 180) || null; },
|
|
|
|
|
canStart(t, world, ctx = {}) {
|
|
|
|
|
if ((t.intimidateTimer || 0) > 0.04 || (t.postConflictPeaceTimer || 0) > 0.04) return false;
|
|
|
|
|
const parts = t?.socialReasonParts || {};
|
|
|
|
|
if ((t.fightMochiTimer || 0) <= 0.04 && Number(parts.conflict || 0) < 24) return false;
|
|
|
|
|
return basicCanStartWithTarget.call(this, t, world, ctx);
|
|
|
|
|
},
|
|
|
|
|
start(t, world, ctx = {}) {
|
|
|
|
|
const rival = ctx.target ?? this.selectTarget(t, world, ctx);
|
|
|
|
|
t.setActionState?.("intimidate", { target: rival, reason: this.label, sleeping: false });
|
|
|
|
|
t.intimidateTimer = Math.max(t.intimidateTimer || 0, 0.85);
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
tick(t, world) {
|
|
|
|
|
if ((t.intimidateTimer || 0) > 0.04) return true;
|
|
|
|
|
const rival = currentBehaviorTarget(t) || t.recentFightRival?.(80, 180);
|
|
|
|
|
if (!rival) return false;
|
|
|
|
|
return world?.startIntimidation?.(t, rival) || true;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createBirthRitualActionSpec() {
|
|
|
|
|
return {
|
|
|
|
|
id: "birth_ritual",
|
|
|
|
|
need: "social",
|
|
|
|
|
subNeed: "mate",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 0,
|
|
|
|
|
state: "birth_ritual",
|
|
|
|
|
timerBacked: true,
|
|
|
|
|
canStart(t) { return (t.birthRitualTimer || 0) > 0.04; },
|
|
|
|
|
start() { return true; },
|
|
|
|
|
tick(t) { return (t.birthRitualTimer || 0) > 0.04 ? true : "finished"; },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TARINAI_ACTION_DEFINITIONS = [
|
|
|
|
|
createConsumableActionSpec({
|
|
|
|
|
id: "eat_food",
|
|
|
|
|
subNeed: "meal",
|
|
|
|
|
state: "seek_food",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u98df\u3079\u7269\u3092\u63a2\u3057\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u98df\u3079\u7269\u3092\u98df\u3079\u3066\u3044\u308b",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 62,
|
|
|
|
|
role: "food",
|
|
|
|
|
range: 760,
|
|
|
|
|
}),
|
|
|
|
|
createConsumableActionSpec({
|
|
|
|
|
id: "drink_water",
|
|
|
|
|
subNeed: "water",
|
|
|
|
|
state: "seek_water",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u6c34\u3092\u63a2\u3057\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u6c34\u3092\u98f2\u3093\u3060",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 24,
|
|
|
|
|
role: "drink",
|
|
|
|
|
range: 560,
|
|
|
|
|
}),
|
|
|
|
|
createSleepInBedActionSpec(),
|
|
|
|
|
createSleepBuildGrassBedActionSpec(),
|
|
|
|
|
createSleepAnywhereActionSpec(),
|
|
|
|
|
createConsumableActionSpec({
|
|
|
|
|
id: "use_medicine",
|
|
|
|
|
subNeed: "medicine",
|
|
|
|
|
state: "seek_food",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u5143\u6c17\u306b\u306a\u308b\u3082\u306e\u3092\u63a2\u3057\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u5143\u6c17\u306b\u306a\u3063\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 34,
|
|
|
|
|
role: "medicine",
|
|
|
|
|
range: 620,
|
|
|
|
|
}),
|
|
|
|
|
createRestActionSpec(),
|
|
|
|
|
createSunbathActionSpec(),
|
|
|
|
|
createPanicEscapeActionSpec(),
|
|
|
|
|
createFleeActionSpec(),
|
|
|
|
|
{
|
|
|
|
|
id: "hide_at_owned_structure",
|
|
|
|
|
need: "safety",
|
|
|
|
|
state: "seek_bed",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u81ea\u5206\u306e\u5834\u6240\u306b\u96a0\u308c\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u81ea\u5206\u306e\u5834\u6240\u3078\u623b\u3063\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 30,
|
|
|
|
|
selectTarget: selectOwnedStructure("grass_bed", 620),
|
|
|
|
|
canStart(t, world, ctx = {}) { return !!(ctx.target ?? this.selectTarget(t, world, ctx)); },
|
2026-06-28 13:40:41 +09:00
|
|
|
start: startMoveToTarget("seek_bed", "\u81ea\u5206\u306e\u5834\u6240\u306b\u96a0\u308c\u3066\u3044\u308b"),
|
2026-06-25 14:51:58 +09:00
|
|
|
},
|
|
|
|
|
createApproachFriendActionSpec(),
|
|
|
|
|
createApproachFamilyActionSpec(),
|
|
|
|
|
createApproachMateActionSpec(),
|
|
|
|
|
createFightRivalActionSpec(),
|
|
|
|
|
createIntimidateEnemyActionSpec(),
|
|
|
|
|
{
|
|
|
|
|
id: "play",
|
|
|
|
|
need: "fulfill",
|
|
|
|
|
state: "play_ball",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u904a\u3093\u3067\u3044\u308b",
|
|
|
|
|
phrase: "\u904a\u3093\u3060",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 24,
|
|
|
|
|
selectTarget(t, world) { return world.nearest?.(t, ["ball"], 520) || null; },
|
|
|
|
|
canStart(t, world, ctx = {}) { return !!(ctx.target ?? this.selectTarget(t, world, ctx)); },
|
2026-06-28 13:40:41 +09:00
|
|
|
start: startMoveToTarget("play_ball", "\u904a\u3093\u3067\u3044\u308b"),
|
2026-06-25 14:51:58 +09:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "build_grass_bed",
|
|
|
|
|
need: "fulfill",
|
|
|
|
|
state: "build",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9\u3092\u4f5c\u3063\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9\u3092\u4f5c\u3063\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 34,
|
|
|
|
|
selectTarget: selectRoleTarget("grassMaterial", 520),
|
|
|
|
|
canStart(t, world, ctx = {}) { return canBuildOwnedStructureByAge(t) && !findOwnedStructure(world, t, "grass_bed") && !!(ctx.target ?? this.selectTarget(t, world, ctx)) && !["sleep", "fight", "panic", "intimidate"].includes(t.state); },
|
|
|
|
|
start(t, world) { return buildOwnedStructureFromGrass(t, world, "grass_bed", this.label); },
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "build_plushie",
|
|
|
|
|
need: "fulfill",
|
|
|
|
|
state: "build",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u306c\u3044\u3050\u308b\u307f\u3092\u4f5c\u3063\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u306c\u3044\u3050\u308b\u307f\u3092\u4f5c\u3063\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 28,
|
|
|
|
|
selectTarget: selectRoleTarget("grassMaterial", 520),
|
|
|
|
|
canStart(t, world, ctx = {}) { return canBuildOwnedStructureByAge(t) && !findOwnedStructure(world, t, "plushie") && !!(ctx.target ?? this.selectTarget(t, world, ctx)) && !["sleep", "fight", "panic", "intimidate"].includes(t.state); },
|
|
|
|
|
start(t, world) { return buildOwnedStructureFromGrass(t, world, "plushie", this.label); },
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "return_owned_structure",
|
|
|
|
|
need: "fulfill",
|
|
|
|
|
state: "seek_bed",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u81ea\u5206\u306e\u5834\u6240\u3078\u623b\u3063\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u81ea\u5206\u306e\u5834\u6240\u3078\u623b\u3063\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 22,
|
|
|
|
|
selectTarget: selectOwnedStructure("grass_bed", 620),
|
|
|
|
|
canStart(t, world, ctx = {}) { return !!(ctx.target ?? this.selectTarget(t, world, ctx)); },
|
2026-06-28 13:40:41 +09:00
|
|
|
start: startMoveToTarget("seek_bed", "\u81ea\u5206\u306e\u5834\u6240\u3078\u623b\u3063\u3066\u3044\u308b"),
|
2026-06-25 14:51:58 +09:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "use_plushie",
|
|
|
|
|
need: "fulfill",
|
|
|
|
|
state: "idle",
|
2026-06-28 13:40:41 +09:00
|
|
|
label: "\u306c\u3044\u3050\u308b\u307f\u3092\u62b1\u3048\u3066\u3044\u308b",
|
|
|
|
|
phrase: "\u306c\u3044\u3050\u308b\u307f\u3092\u62b1\u3048\u305f",
|
2026-06-25 14:51:58 +09:00
|
|
|
weight: 18,
|
|
|
|
|
selectTarget: selectOwnedStructure("plushie", Infinity),
|
|
|
|
|
canStart(t, world, ctx = {}) { return !!(ctx.target ?? this.selectTarget(t, world, ctx)); },
|
|
|
|
|
start(t, world, ctx = {}) {
|
|
|
|
|
const plushie = ctx.target ?? this.selectTarget(t, world, ctx);
|
|
|
|
|
plushie?.use?.(t, world);
|
|
|
|
|
t.setActionState?.("idle", { target: plushie, reason: this.label, sleeping: false });
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
createBirthRitualActionSpec(),
|
|
|
|
|
createWanderActionSpec(),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const TARINAI_ACTIONS = typeof registerTarinaiActionSpecs === "function"
|
|
|
|
|
? registerTarinaiActionSpecs(TARINAI_ACTION_DEFINITIONS)
|
|
|
|
|
: TARINAI_ACTION_DEFINITIONS;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof window !== "undefined") Object.assign(window, { TARINAI_ACTION_DEFINITIONS, TARINAI_ACTIONS });
|