tarinai/js/structure_lifecycle.js

179 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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);
}
item.removedReason = String(reason || "item-removed");
return true;
}
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;
}
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;
}
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 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;
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.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;
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;
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);