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

69 lines
3.9 KiB
JavaScript

"use strict";
// Builds and finalizes Tarinai item-interaction scan context.
// This keeps scan radius, candidate collection, and post-interaction clamping in one place.
(function (global) {
function create(tarinai, dt = 0) {
if (!tarinai || !tarinai.world) return null;
const foodNeed = Number(tarinai.needRaw?.food ?? tarinai.needs?.food ?? 0) || 0;
const healthNeed = Number(tarinai.needRaw?.health ?? tarinai.needs?.health ?? 0) || 0;
const currentNeed = typeof getTarinaiBehaviorNeed === "function" ? getTarinaiBehaviorNeed(tarinai) : tarinai.behavior?.need;
const foodActionActive = currentNeed === "food" || ["seek_food", "eat", "seek_water"].includes(tarinai.state);
const healthActionActive = currentNeed === "health" || tarinai.state === "seek_food";
const mealReady = (tarinai.mealCooldownUntil || 0) <= (tarinai.world?.time || 0) || tarinai.hunger > 92 || foodNeed > 84;
const wantsFood = foodActionActive || foodNeed >= needThreshold("food", "start") || tarinai.hunger > 70;
const wantsMedicine = healthActionActive || healthNeed >= needThreshold("health", "start");
const canBite = !tarinai.isZunchiSlave && tarinai.eatCooldown <= 0 && mealReady && wantsFood && tarinai.hunger > 18;
const canSweetBite = !tarinai.isZunchiSlave && tarinai.eatCooldown <= 0 && mealReady && (wantsFood || wantsMedicine) && tarinai.hunger > 7;
const canZunchiBite = tarinai.eatCooldown <= 0 && (tarinai.isZunchiSlave ? tarinai.hunger > 16 : (tarinai.hunger >= 102 || foodNeed >= 92)) && (tarinai.isZunchiSlave || !hasBetterFoodThanZunchiNearby(tarinai, tarinai.world, 260));
const scanLimit = (tarinai.hunger >= 96 || foodNeed >= 84) ? 32 : 18;
const foodScanRadius = tarinai.radius + ((tarinai.hunger >= 96 || foodNeed >= 84) ? 104 : 56);
const nearbyFood = tarinai.world.nearbyFood?.(tarinai.x, tarinai.y, foodScanRadius, true) || [];
const nearbyItems = nearbyFood.length ? nearbyFood : tarinai.world.nearbyItems(tarinai.x, tarinai.y, foodScanRadius);
const foodCandidates = [];
if (tarinai.target && !tarinai.target.dead && (tarinai.target.type === "duplicator" || roleMatches(tarinai.target, "food") || roleMatches(tarinai.target, "medicine"))) foodCandidates.push(tarinai.target);
for (const it of nearbyItems) {
if (!it || it.dead || foodCandidates.includes(it)) continue;
if (!(it.type === "duplicator" || roleMatches(it, "food") || roleMatches(it, "medicine") || it.type === "water" || it.type === "water_bowl")) continue;
foodCandidates.push(it);
if (foodCandidates.length >= scanLimit) break;
}
if (foodCandidates.length > 1 && foodCandidates.length <= scanLimit) {
foodCandidates.sort((a, b) => foodPriorityScore(tarinai, b, b?.type === "duplicator" ? foodInteractionDistance(tarinai, b) : dist(tarinai, b)) - foodPriorityScore(tarinai, a, a?.type === "duplicator" ? foodInteractionDistance(tarinai, a) : dist(tarinai, a)));
}
return {
dt,
foodNeed,
healthNeed,
currentNeed,
foodActionActive,
healthActionActive,
mealReady,
wantsFood,
wantsMedicine,
canBite,
canSweetBite,
canZunchiBite,
scanLimit,
foodScanRadius,
nearbyFood,
nearbyItems,
foodCandidates,
};
}
function finalize(tarinai) {
if (!tarinai) return false;
tarinai.hunger = clamp(tarinai.hunger, 0, 115);
tarinai.energy = clamp(tarinai.energy, 0, typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(tarinai) : (Number(tarinai.maxEnergy) || 100));
tarinai.stress = applyGroundStressModifier(tarinai, calculateStressFromNeeds(tarinai.needRaw || tarinai.needs || createDefaultNeeds()));
tarinai.loneliness = clamp(tarinai.loneliness, 0, 110);
return true;
}
global.TarinaiItemInteractionContext = Object.freeze({
create,
finalize,
});
})(typeof window !== "undefined" ? window : globalThis);