tarinai/js/item_lifecycle_decay_system.js
2026-06-26 12:46:32 +09:00

66 lines
2.9 KiB
JavaScript

"use strict";
// Layer: entity-runtime/items/decay-system
// Owns passive amount decay and spoilage side effects for water, food,
// transient traces, splats, and ant corpses. The lifecycle step is now only a
// thin pipeline adapter.
(function (global) {
const MEDICINE_LIKE_FOOD_TYPES = Object.freeze([
"sweet", "love_mochi", "fight_mochi", "sleep_drug", "laxative", "protein",
"niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice",
]);
function decayServingFood(item, dt, worldRef) {
if (typeof isServingFoodType !== "function" || !isServingFoodType(item.type)) return false;
const interval = passiveFoodDecayInterval(worldRef);
item.passiveFoodDecayTimer = (item.passiveFoodDecayTimer || 0) + dt;
let changed = false;
let ticks = 0;
while (item.passiveFoodDecayTimer >= interval && ticks < 3) {
item.passiveFoodDecayTimer -= interval;
ticks++;
if (Number.isFinite(item.foodServingsRemaining)) {
const before = Math.max(0, item.foodServingsRemaining || 0);
const lost = Math.min(before, interval * passiveFoodDecayRate(item));
if (lost > 0) {
changed = true;
item.foodServingsRemaining = Math.max(0, before - lost);
item.amount = item.foodServingsRemaining;
const penalty = global.TarinaiItemRegistry?.food?.hygienePenalty?.(item.type) ?? PASSIVE_FOOD_HYGIENE_PENALTY_PER_SERVING;
worldRef?.registerFoodSpoilage?.(lost * penalty, item);
worldRef?.markTerrainDirty?.("food-passive-decay");
worldRef?.emit?.("item:decayed", { item, type: item.type, amount: lost, passive: true });
if (item.foodServingsRemaining <= 0.015) item.amount = 0;
}
} else if (MEDICINE_LIKE_FOOD_TYPES.includes(item.type)) {
const before = item.amount || 0;
const lost = Math.min(before, interval * 0.12);
item.amount -= lost;
if (lost > 0) {
changed = true;
const penalty = global.TarinaiItemRegistry?.food?.hygienePenalty?.(item.type) ?? 0.002;
worldRef?.registerFoodSpoilage?.(lost * penalty, item);
worldRef?.markTerrainDirty?.("food-passive-decay");
worldRef?.emit?.("item:decayed", { item, type: item.type, amount: lost, passive: true });
}
}
}
return changed;
}
function update(item, dt, worldRef) {
if (!item || item.dead) return { done: true };
if (item.type === "water") item.amount -= dt * 0.9;
decayServingFood(item, dt, worldRef);
if (item.type === "trace") item.amount -= dt * 1.35;
if (item.type === "splat") item.amount -= dt * 1.05;
if (item.type === "ant_corpse") item.amount -= dt * 0.018;
return { done: false };
}
global.TarinaiItemDecaySystem = Object.freeze({
MEDICINE_LIKE_FOOD_TYPES,
decayServingFood,
update,
});
})(typeof window !== "undefined" ? window : globalThis);