tarinai/js/item_lifecycle_support.js
2026-07-16 22:12:03 +09:00

122 lines
5.9 KiB
JavaScript

"use strict";
// Layer: entity-runtime/items/support
// Owns shared lifecycle support helpers used by dynamic item systems and
// placement/tool commands. The runtime imports these via
// globals instead of carrying helper implementations inline.
(function (global) {
const DUPLICATOR_STORABLE_TYPES = new Set([
"food", "sweet", "love_mochi", "fight_mochi", "sleep_drug", "first_aid", "sedative",
"laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug",
"zunda_juice", "water", "pushpin", "oshibyo"
]);
function duplicatorLoadTypeForItem(item) {
if (!item || item.dead || item.type === "duplicator") return "";
const type = String(item.type || "");
if (!type || type === "grass") return "";
if ((isPinType(type)) && item.pinState === "lodged") return "";
if (type === "slave_chain" || type === "golden_crown") return "";
// Explicit list first. \u3053\u308c\u3067\u300c\u305a\u3093\u3060\u9905\u300d\u300c\u3078\u3053\u9905\u300d\u300c\u3051\u3093\u304b\u9905\u300d\u304c
// serving-food \u5224\u5b9a\u3084 medicine role \u306e\u63fa\u308c\u306b\u5de6\u53f3\u3055\u308c\u305a\u8907\u88fd\u6a5f\u3078\u5165\u308b\u3002
if (DUPLICATOR_STORABLE_TYPES.has(type)) return type;
if (isServingFoodType(type)) return type;
if (isParamEffectItemType(type)) return type;
const foodDef = global.TarinaiItemRegistry.food?.get?.(type);
if (foodDef && (Number(foodDef.nutrition || 0) > 0 || foodDef.effectId)) return type;
if (typeof roleMatches === "function" && roleMatches(item, "medicine")) return type;
return "";
}
function duplicatorLoadPoint(duplicator) {
const r = duplicator?.r || 34;
return { x: (duplicator?.x || 0) + r * 0.62, y: (duplicator?.y || 0) + r * 0.24 };
}
function syncDuplicatorRoles(item) {
if (!item || item.type !== "duplicator") return;
const type = String(item.storedFoodType || "");
const pinLoaded = Boolean(type && isPinType(type));
item.roles.food = Boolean(type && !pinLoaded);
item.roles.medicine = Boolean(type && !pinLoaded && (type === "sweet" || type === "water" || type === "zunda_juice" || type === "sleep_drug" || (isParamEffectItemType(type))));
item.roles.drink = Boolean(!pinLoaded && (type === "water" || type === "zunda_juice"));
item.roles.danger = pinLoaded;
}
function duplicatorSetStoredType(duplicator, storedType = "", worldRef = null, options = {}) {
if (!duplicator || duplicator.type !== "duplicator") return false;
if (options.direct !== true && options.force !== true) return false;
const type = String(storedType || "");
if (!type) return false;
const probe = { type, amount: 1, foodServingsRemaining: 1, dead: false };
const loadType = duplicatorLoadTypeForItem(probe);
if (!loadType) return false;
const changed = duplicator.storedFoodType !== loadType;
if (!changed && duplicator.storedFoodType && !options.force) return true;
const def = itemDefinition(loadType);
const newLabel = def?.label || toolLabel(loadType);
duplicator.storedFoodType = loadType;
duplicator.storedFoodLabel = newLabel;
syncDuplicatorRoles(duplicator);
duplicator.duplicatorPinFlash = isPinType(loadType) ? 0.28 : (duplicator.duplicatorPinFlash || 0);
const consume = options.consumeItem;
if (consume && typeof consume === "object") {
consume.amount = 0;
consume.foodServingsRemaining = 0;
}
worldRef?.markItemBucketsDirty?.(changed ? "duplicator-direct-reloaded" : "duplicator-direct-loaded");
worldRef?.markSpatialDirty?.(changed ? "duplicator-direct-reloaded" : "duplicator-direct-loaded");
worldRef?.markTerrainDirty?.(changed ? "duplicator-direct-reloaded" : "duplicator-direct-loaded");
if (options.log !== false) worldRef?.log?.(`\u8907\u88fd\u6a5f\u306b${duplicator.storedFoodLabel}\u3092\u30bb\u30c3\u30c8\u3057\u305f\u3002`, isPinType(loadType) ? "observe" : "food");
return true;
}
function duplicatorApplyStoredPin(duplicator, worldRef) {
const storedType = String(duplicator?.storedFoodType || "");
if (!duplicator || duplicator.type !== "duplicator" || !isPinType(storedType)) return false;
if (globalThis.TarinaiSignalSystem?.powerOverride?.(duplicator) === false) return false;
const now = Number(worldRef?.time || 0);
if (now < Number(duplicator.nextDuplicatedPinAt || 0)) return false;
const dish = duplicatorLoadPoint(duplicator);
const reach = Math.max(24, (duplicator.r || 34) * 0.56);
const candidates = worldRef?.nearbyTarinai?.(dish.x, dish.y, reach + 72, true) || [];
for (const t of candidates) {
if (!t || t.dead || worldRef?.isTarinaiHiddenInNestBox?.(t)) continue;
if (t.stuckPushpinId || t.currentLodgedPin?.()) continue;
const hit = reach + Math.max(10, (t.radius || 18) * 0.78);
if (distXY(dish.x, dish.y, t.x, t.y) > hit) continue;
const pin = new Item(storedType, dish.x, dish.y);
pin.amount = 999;
pin.noStickUntil = 0;
pin.noStickTargetId = "";
pin.noStickTargetUntil = 0;
const added = worldRef?.addItem?.(pin, "duplicator-pin", { terrain: false }) || null;
if (!added) return false;
const ok = global.TarinaiItemDynamicPinSystem.attach(pin, t, worldRef, distXY(dish.x, dish.y, t.x, t.y));
if (!ok) {
pin.amount = 0;
continue;
}
duplicator.usedCount = (duplicator.usedCount || 0) + 1;
duplicator.nextDuplicatedPinAt = now + 0.82;
duplicator.duplicatorPinFlash = 0.24;
worldRef.effects?.push(new Effect("ring", dish.x, dish.y, { size: Math.max(15, (duplicator.r || 34) * 0.62), life: 0.18, color: storedType === "oshibyo" ? "rgba(73,119,205,0.40)" : "rgba(214,72,72,0.42)" }));
worldRef.drawListDirty = true;
worldRef.markSpatialDirty?.("duplicator-pin-lodged");
return true;
}
return false;
}
global.TarinaiItemLifecycleRuntimeSupport = Object.freeze({
duplicatorLoadTypeForItem,
duplicatorLoadPoint,
syncDuplicatorRoles,
duplicatorSetStoredType,
duplicatorApplyStoredPin,
});
})(typeof window !== "undefined" ? window : globalThis);