tarinai/js/tarinai_building_behavior.js

152 lines
7.1 KiB
JavaScript
Raw Normal View History

2026-06-25 14:51:58 +09:00
"use strict";
// Owned-structure building behavior runtime.
function structureBuildProbe(type, x, y) {
const def = globalThis.StructureRegistry?.get?.(type) || {};
const r = type === "plushie" ? 6.5 : (type === "grass_bed" ? 18 : 24);
return { type, x, y, r, dead: false, isStructure: true, roles: def.roles || {}, ownerId: "" };
}
function canBuildOwnedStructureByAge(tarinai) {
if (!tarinai || tarinai.dead) return false;
if (tarinai.generation <= 1) return true;
const growth = Number.isFinite(tarinai.growth)
? tarinai.growth
: clamp(((tarinai.world?.time || 0) - (tarinai.birthTime || 0)) / Math.max(1, CONFIG.childGrowTime || 58), 0, 1);
return growth >= 0.55;
}
function stopFailedBuildPlan(t, world, type = "", reason = "") {
if (!t) return false;
t.buildPlan = null;
t.actionCooldowns = t.actionCooldowns || {};
t.actionIdCooldowns = t.actionIdCooldowns || {};
const actionId = type === "grass_bed" ? "build_grass_bed" : (type === "plushie" ? "build_plushie" : "build");
t.actionCooldowns.fulfill = Math.max(t.actionCooldowns.fulfill || 0, 5.0);
t.actionCooldowns.sleep = Math.max(t.actionCooldowns.sleep || 0, type === "grass_bed" ? 3.8 : 0);
t.actionIdCooldowns[actionId] = Math.max(t.actionIdCooldowns[actionId] || 0, 9.0);
if (type === "grass_bed") {
t.actionIdCooldowns.sleep_build_grass_bed = Math.max(t.actionIdCooldowns.sleep_build_grass_bed || 0, 9.0);
t.lastFailedGrassBedBuildAt = world?.time || 0;
}
t.behaviorSwitchCooldownUntil = Math.max(t.behaviorSwitchCooldownUntil || 0, (world?.time || 0) + 1.2);
const text = reason || "\u4f5c\u308b\u5834\u6240\u304c\u898b\u3064\u304b\u3089\u306a\u3044";
if (typeof clearBehavior === "function") clearBehavior(t, text);
t.goIdle?.(text);
return false;
}
2026-06-27 19:22:38 +09:00
function forceBuiltStructureVisualRefresh(world, structure, reason = "structure-built") {
if (!world) return;
world.drawListDirty = true;
if (world._renderStack) world._renderStack.signature = "";
if (world._visibleRenderStack) {
world._visibleRenderStack.backItems = [];
world._visibleRenderStack.layered = [];
world._visibleRenderStack.carriedPlushies = [];
world._visibleRenderStack.lodgedPins = [];
}
world.markItemBucketsDirty?.(reason);
world.markSpatialDirty?.(reason);
world.ensureItemBuckets?.(reason);
world.ensureSpatial?.(reason);
if (structure) {
structure.world = world;
structure._builtVisibleAt = Number(world.time || 0) || 0;
}
if (typeof globalThis.render === "function") {
globalThis.render();
globalThis.requestAnimationFrame?.(() => globalThis.render?.());
}
}
2026-06-25 14:51:58 +09:00
function findStructureBuildSpot(t, world, type) {
const baseX = Number.isFinite(t?.x) ? t.x : 0;
const baseY = Number.isFinite(t?.y) ? t.y : 0;
const candidates = [];
for (let i = 0; i < 28; i++) {
const angle = (i / 28) * Math.PI * 2 + rand(-0.18, 0.18);
const ring = type === "grass_bed" ? rand(34, 72) : rand(18, 42);
candidates.push({ x: clamp(baseX + Math.cos(angle) * ring, CONFIG.worldPadding || 30, (world?.w || 1000) - (CONFIG.worldPadding || 30)), y: clamp(baseY + Math.sin(angle) * ring, CONFIG.worldPadding || 30, (world?.h || 720) - (CONFIG.worldPadding || 30)) });
}
candidates.unshift({ x: clamp(baseX + rand(-18, 18), CONFIG.worldPadding || 30, (world?.w || 1000) - (CONFIG.worldPadding || 30)), y: clamp(baseY + rand(16, 28), CONFIG.worldPadding || 30, (world?.h || 720) - (CONFIG.worldPadding || 30)) });
let best = null;
let bestScore = -Infinity;
for (const c of candidates) {
const probe = structureBuildProbe(type, c.x, c.y);
if (world?.placementBlocked?.(probe)) continue;
let nearestSame = Infinity;
if (type === "grass_bed") {
for (const it of world?.nearbyItems?.(c.x, c.y, 160) || world?.items || []) {
if (!it || it.dead || it.type !== "grass_bed") continue;
nearestSame = Math.min(nearestSame, distXY(c.x, c.y, it.x, it.y));
}
}
const score = (Number.isFinite(nearestSame) ? nearestSame : 160) - distXY(c.x, c.y, baseX, baseY) * 0.12;
if (score > bestScore) { best = c; bestScore = score; }
}
return best || candidates[0] || { x: baseX, y: baseY };
}
function buildOwnedStructureFromGrass(t, world, type, label) {
const material = findNearbyMaterial(world, t, "grassMaterial", 520);
if (!material || !globalThis.StructureRegistry) return false;
t.buildPlan = {
type,
materialId: material.id,
timer: Math.max(0.4, t.buildPlan?.type === type ? Number(t.buildPlan.timer || 0) : 4),
};
if (dist(t, material) > (t.radius || 20) + (material.r || 12) + 18) return moveToOrUse(t, material, "seek_material", label);
t.setActionState?.("build", { target: material, reason: label, sleeping: false });
return true;
}
function continueBuildPlan(t, world, dt) {
const plan = t.buildPlan;
if (!plan?.type || !globalThis.StructureRegistry) return false;
if (findOwnedStructure(world, t, plan.type)) {
t.buildPlan = null;
return false;
}
let material = (world?.itemById?.(plan.materialId) || null);
if (material && (material.dead || !roleMatches(material, "grassMaterial"))) material = null;
if (!material) material = findNearbyMaterial(world, t, "grassMaterial", 520);
if (!material) {
return stopFailedBuildPlan(t, world, plan.type, "\u6750\u6599\u304c\u898b\u3064\u304b\u3089\u306a\u3044");
}
plan.materialId = material.id;
const label = globalThis.StructureRegistry.get(plan.type)?.label || "\u69cb\u9020\u7269";
if (dist(t, material) > (t.radius || 20) + (material.r || 12) + 18) {
moveToOrUse(t, material, "seek_material", `${label}\u3092\u4f5c\u3063\u3066\u3044\u308b`);
return true;
}
plan.timer = Math.max(0, Number(plan.timer || 0) - Math.max(0.05, dt || 0.1));
t.setActionState?.("build", { target: material, reason: `${label}\u3092\u4f5c\u3063\u3066\u3044\u308b`, sleeping: false });
if (plan.timer > 0) return true;
consumeGrassMaterial(material, plan.type === "grass_bed" ? 4 : 1);
2026-06-26 19:04:32 +09:00
const spot = plan.type === "plushie"
? { x: t.x, y: t.y - Math.max(28, (t.radius || 24) * 1.28) }
: findStructureBuildSpot(t, world, plan.type);
2026-06-25 14:51:58 +09:00
const structure = globalThis.StructureRegistry.create(plan.type, t, spot.x, spot.y, world);
if (!structure) {
return stopFailedBuildPlan(t, world, plan.type, "\u4f5c\u308b\u5834\u6240\u304c\u898b\u3064\u304b\u3089\u306a\u3044");
}
2026-06-26 19:04:32 +09:00
if (plan.type === "plushie") {
structure.carriedById = t.id;
structure.onHead = true;
structure.x = t.x;
structure.y = t.y - Math.max(28, (t.radius || 24) * 1.28);
}
2026-06-27 19:22:38 +09:00
const addedStructure = world.addItem?.(structure, `build:${plan.type}`) || (world.items.push(structure), structure);
if (!addedStructure) {
2026-06-28 13:40:41 +09:00
return stopFailedBuildPlan(t, world, plan.type, "\u4f5c\u308b\u5834\u6240\u304c\u898b\u3064\u304b\u3089\u306a\u3044");
2026-06-27 19:22:38 +09:00
}
2026-06-25 14:51:58 +09:00
structure.use?.(t, world);
2026-06-27 19:22:38 +09:00
forceBuiltStructureVisualRefresh(world, structure, `build:${plan.type}:visual-refresh`);
2026-06-25 14:51:58 +09:00
t.setActionState?.("idle", { target: structure, reason: `${label}\u3092\u4f5c\u3063\u305f`, sleeping: false });
applyNeedSatisfaction(t, { fulfill: plan.type === "plushie" ? 50 : 36, safety: plan.type === "plushie" ? 4 : 8 }, plan.type);
t.buildPlan = null;
return true;
}