209 lines
8.8 KiB
JavaScript
209 lines
8.8 KiB
JavaScript
"use strict";
|
|
|
|
(function (global) {
|
|
function markWorldAfterStructureChange(worldRef, reason = "structure-change", item = null, radius = 40) {
|
|
if (!worldRef) return;
|
|
worldRef.drawListDirty = true;
|
|
worldRef.markItemBucketsDirty?.(reason);
|
|
worldRef.markSpatialDirty?.(reason);
|
|
if (item && Number.isFinite(item.x) && Number.isFinite(item.y)) {
|
|
worldRef.markTerrainDirtyAt?.(item.x, item.y, Math.max(24, Number(radius || item.r || 40) || 40), reason);
|
|
} else {
|
|
worldRef.markTerrainDirty?.(reason);
|
|
}
|
|
}
|
|
|
|
function markLooseItemGone(item, reason = "item-removed") {
|
|
if (!item || item.dead) return false;
|
|
// Normal Item.dead is a getter, so all non-structure removals go through
|
|
// amount/hp zeroing instead of assigning to .dead.
|
|
item.amount = 0;
|
|
item.hp = 0;
|
|
item.foodServingsRemaining = 0;
|
|
if (item.type === "grass") {
|
|
item.growth = 0;
|
|
item.grassStage = -1;
|
|
item.seedTimer = Math.max(Number(item.seedTimer || 0), 2.5);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function markStructureGone(structure, reason = "structure-removed") {
|
|
if (!structure || structure.dead) return false;
|
|
structure.dead = true;
|
|
structure.hp = 0;
|
|
structure.amount = 0;
|
|
return true;
|
|
}
|
|
|
|
function ownedStructureDestroyedLabel(structure) {
|
|
if (structure?.type === "plushie") return "\u306C\u3044\u3050\u308B\u307F";
|
|
if (structure?.type === "grass_bed") return "\u304B\u3093\u305F\u3093\u30D9\u30C3\u30C9";
|
|
return structure?.label || "\u81EA\u5206\u306E\u3082\u306E";
|
|
}
|
|
|
|
function shouldPanicOwnerOnStructureGone(structure, reason = "") {
|
|
if (!structure || !structure.ownerId) return false;
|
|
if (!(structure.type === "plushie" || structure.type === "grass_bed")) return false;
|
|
if (String(reason || "") === "grass-bed-wake-used") return false;
|
|
return true;
|
|
}
|
|
|
|
function triggerOwnedStructureDestroyedPanic(worldRef, structure, breaker = null, reason = "structure-destroyed") {
|
|
if (!worldRef || !shouldPanicOwnerOnStructureGone(structure, reason)) return false;
|
|
const owner = (worldRef.tarinai || []).find(t => t && !t.dead && t.id === structure.ownerId);
|
|
if (!owner) return false;
|
|
const label = ownedStructureDestroyedLabel(structure);
|
|
const detail = `\u81EA\u5206\u306E${label}\u304C\u58CA\u3055\u308C\u305F`;
|
|
const panicReason = `\u81EA\u5206\u306E${label}\u3092\u58CA\u3055\u308C\u3066\u30D1\u30CB\u30C3\u30AF\u306B\u306A\u3063\u3066\u3044\u308B`;
|
|
const threat = breaker || {
|
|
id: `${structure.id || "structure"}:destroyed`,
|
|
x: Number(structure.x) || owner.x,
|
|
y: Number(structure.y) || owner.y,
|
|
type: "owned_structure_destroyed",
|
|
dead: false,
|
|
};
|
|
owner.lastPanicDetail = detail;
|
|
owner.fearTimer = Math.max(owner.fearTimer || 0, structure.type === "plushie" ? 3.2 : 2.6);
|
|
owner.surpriseTimer = Math.max(owner.surpriseTimer || 0, 0.85);
|
|
owner.awakeLockTimer = Math.max(owner.awakeLockTimer || 0, 2.4);
|
|
if (owner.enterPanic) {
|
|
owner.enterPanic({
|
|
threat,
|
|
target: owner.panicDestination?.(threat, true) || null,
|
|
forceDestination: true,
|
|
reason: panicReason,
|
|
fear: structure.type === "plushie" ? 1.65 : 1.35,
|
|
stress: structure.type === "plushie" ? 20 : 15,
|
|
wake: true,
|
|
cause: `${structure.type}_destroyed`,
|
|
surpriseTimer: 0.85,
|
|
awakeLockTimer: 2.4,
|
|
});
|
|
} else {
|
|
owner.setActionState?.("panic", { target: owner.panicDestination?.(threat, true) || null, reason: panicReason, wake: true, sleeping: false });
|
|
}
|
|
if (typeof global.setBehaviorText === "function") {
|
|
global.setBehaviorText(owner, {
|
|
need: "safety",
|
|
actionId: "panic_escape",
|
|
actionLabel: "\u30D1\u30CB\u30C3\u30AF\u306B\u306A\u3063\u3066\u3044\u308B",
|
|
reasonText: panicReason,
|
|
causeText: detail,
|
|
target: threat,
|
|
phase: "perform",
|
|
source: "behavior",
|
|
});
|
|
}
|
|
owner.thought = panicReason;
|
|
owner.behaviorLockTimer = Math.max(owner.behaviorLockTimer || 0, 1.2);
|
|
worldRef.spawnBubble?.(owner.x, owner.y - (owner.radius || 20) * 1.20, "\uFF01\uFF1F", "rgba(188,66,76,0.88)");
|
|
worldRef.log?.(`${owner.name || "\u305F\u308A\u306A\u3044"}\u306F\u81EA\u5206\u306E${label}\u3092\u58CA\u3055\u308C\u3066\u30D1\u30CB\u30C3\u30AF\u306B\u306A\u3063\u305F\u3002`, "danger");
|
|
worldRef.drawListDirty = true;
|
|
return true;
|
|
}
|
|
|
|
function releaseStructureUsers(worldRef, structure, opts = {}) {
|
|
if (!worldRef || !structure) return 0;
|
|
const reason = opts.reason || "\u306a\u304f\u306a\u3063\u305f";
|
|
const wake = opts.wake !== false;
|
|
let released = 0;
|
|
for (const t of worldRef.tarinai || []) {
|
|
if (!t || t.dead) continue;
|
|
const targetMatch = t.target === structure;
|
|
const sleepTargetMatch = t.sleepSession?.targetId && t.sleepSession.targetId === structure.id;
|
|
const nestMatch = structure.type === "nest_box" && t.insideNestBoxId === structure.id;
|
|
if (!targetMatch && !sleepTargetMatch && !nestMatch) continue;
|
|
|
|
if (nestMatch) {
|
|
t.insideNestBoxId = null;
|
|
}
|
|
if (sleepTargetMatch && t.sleepSession) {
|
|
t.sleepSession.targetId = null;
|
|
t.sleepSession.consumesGrassBedOnWake = false;
|
|
}
|
|
if (targetMatch) t.target = null;
|
|
if (t.state === "sleep" || t.sleeping || t.state === "seek_bed" || nestMatch) {
|
|
t.setActionState?.("idle", { clearTarget: true, reason, wake, sleeping: false });
|
|
}
|
|
released += 1;
|
|
}
|
|
return released;
|
|
}
|
|
|
|
|
|
function removeDependentLinks(worldRef, target, reason = "support-removed") {
|
|
if (!worldRef || !target || !target.id) return 0;
|
|
const pb = global.TarinaiPhysicsBodySystem || null;
|
|
if (!pb?.endpoint) return 0;
|
|
let removed = 0;
|
|
const removedIds = new Set([target.id]);
|
|
let changed = true;
|
|
while (changed) {
|
|
changed = false;
|
|
for (const link of worldRef.items || []) {
|
|
if (!link || link.dead || (link.type !== "rope" && link.type !== "rod")) continue;
|
|
const a = pb.endpoint(link, 0);
|
|
const b = pb.endpoint(link, 1);
|
|
const hit = (a && a.kind === "item" && removedIds.has(a.id)) || (b && b.kind === "item" && removedIds.has(b.id));
|
|
if (!hit) continue;
|
|
link.amount = 0;
|
|
link.hp = 0;
|
|
removedIds.add(link.id);
|
|
removed += 1;
|
|
changed = true;
|
|
}
|
|
}
|
|
if (removed) {
|
|
worldRef.drawListDirty = true;
|
|
worldRef.markItemBucketsDirty?.("dependent-links-removed");
|
|
worldRef.markSpatialDirty?.("dependent-links-removed");
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
function deleteStructure(worldRef, structure, opts = {}) {
|
|
if (!structure || structure.dead) return false;
|
|
const reason = opts.reason || "structure-deleted";
|
|
releaseStructureUsers(worldRef, structure, { reason: opts.userReason || "\u7f6e\u304d\u3082\u306e\u304c\u306a\u304f\u306a\u3063\u305f", wake: opts.wake !== false });
|
|
const ownerPanic = shouldPanicOwnerOnStructureGone(structure, reason);
|
|
const changed = markStructureGone(structure, reason);
|
|
if (!changed) return false;
|
|
removeDependentLinks(worldRef, structure, reason);
|
|
if (ownerPanic && opts.panicOwner !== false) triggerOwnedStructureDestroyedPanic(worldRef, structure, opts.breaker || null, reason);
|
|
markWorldAfterStructureChange(worldRef, reason);
|
|
worldRef?.emit?.("structure:deleted", { structure, type: structure.type, reason });
|
|
return true;
|
|
}
|
|
|
|
function consumeGrassBedOnWake(worldRef, bed, user = null, reason = "wake") {
|
|
if (!bed || bed.dead || bed.type !== "grass_bed") return false;
|
|
bed.singleUsePending = false;
|
|
bed.singleUseConsumedById = user?.id || bed.singleUseConsumedById || null;
|
|
if (user?.target === bed) user.target = null;
|
|
const changed = markStructureGone(bed, "grass-bed-wake-used");
|
|
if (!changed) return false;
|
|
markWorldAfterStructureChange(worldRef, "grass-bed-wake-used");
|
|
worldRef?.emit?.("structure:usedUp", { structure: bed, user, reason: "wake" });
|
|
return true;
|
|
}
|
|
|
|
function deleteItem(worldRef, item, opts = {}) {
|
|
if (!item || item.dead) return false;
|
|
const reason = opts.reason || "delete-tool";
|
|
if (item.isStructure) return deleteStructure(worldRef, item, { reason, userReason: opts.userReason, wake: opts.wake, breaker: opts.breaker || null, panicOwner: opts.panicOwner });
|
|
const changed = markLooseItemGone(item, reason);
|
|
if (!changed) return false;
|
|
removeDependentLinks(worldRef, item, reason);
|
|
markWorldAfterStructureChange(worldRef, reason, item, Math.max(36, Number(item.r || item.radius || 24) + 18));
|
|
worldRef?.emit?.("item:deleted", { item, type: item.type, reason });
|
|
return true;
|
|
}
|
|
|
|
const api = Object.freeze({
|
|
consumeGrassBedOnWake,
|
|
deleteItem,
|
|
triggerOwnedStructureDestroyedPanic,
|
|
});
|
|
global.TarinaiStructureLifecycle = api;
|
|
})(typeof window !== "undefined" ? window : globalThis);
|