2026-06-26 12:46:32 +09:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
// Layer: entity-runtime/items/policy
|
|
|
|
|
// Owns the item update cadence and scheduled-item candidate set. Simulation
|
|
|
|
|
// helpers and the item scheduler both read this single policy instead of
|
|
|
|
|
// duplicating item-type timing rules.
|
|
|
|
|
(function (global) {
|
2026-06-26 19:04:32 +09:00
|
|
|
const REALTIME_ITEM_TYPES = new Set(["ball", "genkotsu", "firecracker", "rotator", "reciprocator", "rope", "rod"]);
|
2026-06-26 12:46:32 +09:00
|
|
|
const PIN_ITEM_TYPES = new Set(["pushpin", "oshibyo"]);
|
|
|
|
|
const SCHEDULED_ITEM_TYPES = Object.freeze([
|
|
|
|
|
"ball", "genkotsu", "firecracker", "pushpin", "oshibyo",
|
|
|
|
|
"plushie", "grass_bed", "zunchi", "duplicator", "water", "trace", "splat", "ant_corpse", "food",
|
|
|
|
|
"sweet", "love_mochi", "fight_mochi", "sleep_drug", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice",
|
2026-06-26 19:04:32 +09:00
|
|
|
"stone", "bed", "nest_box", "ant_nest", "signboard", "fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence", "rotator", "reciprocator", "rope", "rod", "water_bowl",
|
2026-06-26 12:46:32 +09:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
function itemUpdateInterval(item) {
|
|
|
|
|
if (!item || item.dead) return Infinity;
|
|
|
|
|
if (REALTIME_ITEM_TYPES.has(item.type)) return 0;
|
|
|
|
|
if (PIN_ITEM_TYPES.has(item.type)) return 0;
|
|
|
|
|
if (item.isStructure && item.type === "plushie") return 0;
|
|
|
|
|
if (item.isStructure && item.type === "grass_bed") return 3.0;
|
|
|
|
|
if ((item.dropTimer || 0) > 0 || Math.hypot(item.vx || 0, item.vy || 0) > 0.08) return 0;
|
|
|
|
|
if (item.type === "zunchi") return 2.4;
|
|
|
|
|
if (item.type === "grass") return Infinity;
|
|
|
|
|
if (item.type === "duplicator") return 0.5;
|
|
|
|
|
if (item.type === "water") return 1.8;
|
|
|
|
|
if (item.type === "trace" || item.type === "splat") return 1.2;
|
|
|
|
|
if (item.type === "ant_corpse") return 3.5;
|
|
|
|
|
if (typeof global.isServingFoodType === "function" && global.isServingFoodType(item.type)) return 5.0;
|
|
|
|
|
if (typeof isServingFoodType === "function" && isServingFoodType(item.type)) return 5.0;
|
|
|
|
|
return Infinity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ensureBuckets(worldRef) {
|
|
|
|
|
if (worldRef?.ensureItemBuckets) worldRef.ensureItemBuckets("item-update-policy");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function forScheduledItems(worldRef, visitor) {
|
|
|
|
|
if (!worldRef || typeof visitor !== "function") return 0;
|
|
|
|
|
let count = 0;
|
|
|
|
|
if (!worldRef.itemsOfType) {
|
|
|
|
|
for (const it of worldRef.items || []) {
|
|
|
|
|
if (!it || it.dead || it.type === "grass") continue;
|
|
|
|
|
visitor(it);
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
ensureBuckets(worldRef);
|
|
|
|
|
for (const type of SCHEDULED_ITEM_TYPES) {
|
|
|
|
|
const bucket = worldRef.itemsOfType(type);
|
|
|
|
|
for (const it of bucket || []) {
|
|
|
|
|
if (!it || it.dead) continue;
|
|
|
|
|
visitor(it);
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function candidateItems(worldRef) {
|
|
|
|
|
const out = [];
|
|
|
|
|
forScheduledItems(worldRef, (item) => out.push(item));
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isRealtime(item) {
|
|
|
|
|
return itemUpdateInterval(item) <= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
global.TarinaiItemUpdatePolicy = Object.freeze({
|
|
|
|
|
REALTIME_ITEM_TYPES,
|
|
|
|
|
PIN_ITEM_TYPES,
|
|
|
|
|
SCHEDULED_ITEM_TYPES,
|
|
|
|
|
itemUpdateInterval,
|
|
|
|
|
forScheduledItems,
|
|
|
|
|
ensureBuckets,
|
|
|
|
|
candidateItems,
|
|
|
|
|
isRealtime,
|
|
|
|
|
});
|
|
|
|
|
})(typeof window !== "undefined" ? window : globalThis);
|