tarinai/js/structure_lifecycle.js

161 lines
6.8 KiB
JavaScript
Raw Normal View History

2026-06-25 14:51:58 +09:00
"use strict";
(function (global) {
function markWorldAfterStructureChange(worldRef, reason = "structure-change") {
if (!worldRef) return;
worldRef.drawListDirty = true;
worldRef.markItemBucketsDirty?.(reason);
worldRef.markSpatialDirty?.(reason);
worldRef.markTerrainDirty?.(reason);
}
function markStructureGone(structure, reason = "structure-removed") {
if (!structure || structure.dead) return false;
structure.dead = true;
structure.hp = 0;
structure.amount = 0;
structure.removedReason = String(reason || "structure-removed");
return true;
}
2026-06-28 16:59:53 +09:00
function ownedStructureDestroyedLabel(structure) {
if (structure?.type === "plushie") return "ぬいぐるみ";
if (structure?.type === "grass_bed") return "かんたんベッド";
return structure?.label || "自分のもの";
}
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 = `自分の${label}が壊された`;
const panicReason = `自分の${label}を壊されてパニックになっている`;
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: "パニックになっている",
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, "", "rgba(188,66,76,0.88)");
worldRef.log?.(`${owner.name || "たりない"}は自分の${label}を壊されてパニックになった。`, "danger");
worldRef.drawListDirty = true;
return true;
}
2026-06-25 14:51:58 +09:00
function releaseStructureUsers(worldRef, structure, opts = {}) {
if (!worldRef || !structure) return 0;
2026-06-28 13:40:41 +09:00
const reason = opts.reason || "\u306a\u304f\u306a\u3063\u305f";
2026-06-25 14:51:58 +09:00
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;
t.nestBoxSleepTimer = 0;
}
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 deleteStructure(worldRef, structure, opts = {}) {
if (!structure || structure.dead) return false;
const reason = opts.reason || "structure-deleted";
2026-06-28 13:40:41 +09:00
releaseStructureUsers(worldRef, structure, { reason: opts.userReason || "\u7f6e\u304d\u3082\u306e\u304c\u306a\u304f\u306a\u3063\u305f", wake: opts.wake !== false });
2026-06-28 16:59:53 +09:00
const ownerPanic = shouldPanicOwnerOnStructureGone(structure, reason);
2026-06-25 14:51:58 +09:00
const changed = markStructureGone(structure, reason);
if (!changed) return false;
2026-06-28 16:59:53 +09:00
if (ownerPanic && opts.panicOwner !== false) triggerOwnedStructureDestroyedPanic(worldRef, structure, opts.breaker || null, reason);
2026-06-25 14:51:58 +09:00
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.singleUseWakeReason = String(reason || "wake");
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;
2026-06-28 16:59:53 +09:00
if (item.isStructure) return deleteStructure(worldRef, item, { reason: opts.reason || "delete-tool", userReason: opts.userReason, wake: opts.wake, breaker: opts.breaker || null, panicOwner: opts.panicOwner });
2026-06-25 14:51:58 +09:00
item.amount = 0;
2026-06-26 19:04:32 +09:00
item.hp = 0;
item.removedReason = String(opts.reason || "delete-tool");
2026-06-25 14:51:58 +09:00
markWorldAfterStructureChange(worldRef, opts.reason || "delete-tool");
return true;
}
global.TarinaiStructureLifecycle = Object.freeze({
markWorldAfterStructureChange,
releaseStructureUsers,
deleteStructure,
consumeGrassBedOnWake,
deleteItem,
2026-06-28 16:59:53 +09:00
triggerOwnedStructureDestroyedPanic,
2026-06-25 14:51:58 +09:00
});
})(window);