163 lines
10 KiB
JavaScript
163 lines
10 KiB
JavaScript
"use strict";
|
|
|
|
// Consumable/drink/medicine behavior runtime.
|
|
|
|
function finishBehaviorConsumption(t, item, foodType, eating, dt, label, color = "#f1dfb7") {
|
|
if (!t || !item || !(eating > 0)) return false;
|
|
t.beginEating?.(item, label || "\u98df\u3079\u3066\u3044\u308b");
|
|
t.vx *= Math.pow(0.25, Math.max(0.1, dt || 0.1) * 60);
|
|
t.vy *= Math.pow(0.25, Math.max(0.1, dt || 0.1) * 60);
|
|
t.eatTimer = Math.max(t.eatTimer || 0, 1.0);
|
|
t.eatCooldown = rand(0.96, 1.12);
|
|
t.foodReactTimer = Math.max(t.foodReactTimer || 0, 0.16);
|
|
if (typeof applyNeedSatisfaction === "function") applyNeedSatisfaction(t, { food: Math.max(18, Math.min(54, (Number(eating) || 0) * 1.1)) }, "meal");
|
|
t.lastMealColor = color;
|
|
if (Math.random() < Math.max(0.1, dt || 0.1) * 2.2) {
|
|
const mouth = t.mouthPosition?.(item) || { x: t.x, y: t.y };
|
|
t.world?.spawnEatEffect?.(mouth.x, mouth.y, color);
|
|
}
|
|
if ((t.world?.time || 0) > (t.nextEatSound || -999)) {
|
|
audio.eat?.();
|
|
t.nextEatSound = (t.world?.time || 0) + 0.42;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function consumeBehaviorTarget(t, world, target, role = "food", dt = 0.12) {
|
|
if (!t || !world || !target || target.dead) return false;
|
|
const foodType = effectiveFoodTypeForItem(target);
|
|
const labelFor = type => typeof toolLabel === "function" ? toolLabel(type || target.type) : "\u98df\u3079\u7269";
|
|
const effectiveDt = Math.max(0.10, Number(dt) || 0.12);
|
|
if (isDuplicatorFoodSource(target) && (t.eatCooldown || 0) > 0.04) {
|
|
t.setActionState?.(role === "medicine" ? "seek_food" : "eat", { target, reason: `${labelFor(foodType)}\u3092\u98df\u3079\u3066\u3044\u308b`, sleeping: false });
|
|
return true;
|
|
}
|
|
|
|
if ((role === "drink" || role === "medicine") && (target.type === "water" || target.type === "water_bowl")) {
|
|
const text = target.type === "water_bowl" ? "\u6c34\u306e\u76bf\u3067\u6c34\u3092\u98f2\u3093\u3067\u3044\u308b" : "\u6c34\u3092\u98f2\u3093\u3067\u3044\u308b";
|
|
t.setActionState?.("seek_water", { target, reason: text, sleeping: false });
|
|
t.thought = text;
|
|
t.applyWaterEffect?.(0.55, target.type);
|
|
if (target.type === "water") target.amount -= 1.2;
|
|
if ((world.time || 0) > (t.nextDrinkSound || -999)) {
|
|
audio.play?.("sfx_drink", { category: "ops", minGap: 0.20 });
|
|
t.nextDrinkSound = (world.time || 0) + 0.72;
|
|
}
|
|
applyNeedRelief(t, role === "medicine" ? { food: -2, health: -5, safety: -1 } : { food: -4, health: -1 });
|
|
return true;
|
|
}
|
|
|
|
if ((role === "drink" || role === "medicine") && isDuplicatorFoodSource(target) && foodType === "water") {
|
|
const text = "複製機の水滴を飲んでいる";
|
|
t.setActionState?.("seek_water", { target, reason: text, sleeping: false });
|
|
t.thought = text;
|
|
t.applyWaterEffect?.(0.55, "water");
|
|
if ((world.time || 0) > (t.nextDrinkSound || -999)) {
|
|
audio.play?.("sfx_drink", { category: "ops", minGap: 0.20 });
|
|
t.nextDrinkSound = (world.time || 0) + 0.72;
|
|
}
|
|
applyNeedRelief(t, role === "medicine" ? { food: -2, health: -5, safety: -1 } : { food: -4, health: -1 });
|
|
return true;
|
|
}
|
|
|
|
|
|
if (target.type === "ant_corpse" && (target.amount || 0) > 0) {
|
|
const bite = Math.min(target.amount, rand(3.2, 5.4));
|
|
if (!(bite > 0)) return false;
|
|
target.amount -= bite;
|
|
finishBehaviorConsumption(t, target, "ant_corpse", bite, effectiveDt, "\u30a2\u30ea\u306e\u6b7b\u9ab8\u3092\u98df\u3079\u3066\u3044\u308b", "#4a3f36");
|
|
t.applyFoodHungerRelief ? t.applyFoodHungerRelief(bite * 0.62) : (t.hunger -= bite * 0.62);
|
|
t.recoverHealth?.(bite * 0.12);
|
|
applyNeedShock(t, { safety: bite * 0.12 });
|
|
t.makePoop?.(normalPoopUnitsFor("ant_corpse", bite));
|
|
return true;
|
|
}
|
|
|
|
if ((foodType === "sweet" || foodType === "food" || (isDuplicatorFoodSource(target) && foodType === "grass") || (typeof isServingFoodType === "function" && isServingFoodType(foodType)) || (typeof isParamEffectItemType === "function" && isParamEffectItemType(foodType))) && t.foodItemHasServingLeft?.(target)) {
|
|
const eating = t.consumeFoodServing?.(target);
|
|
if (!(eating > 0)) return false;
|
|
const isParam = typeof isParamEffectItemType === "function" && isParamEffectItemType(foodType);
|
|
const isSpecial = foodType === "love_mochi" || foodType === "fight_mochi" || foodType === "sleep_drug" || isParam;
|
|
const color = ({ sweet: "#78b957", love_mochi: "#ff7bab", fight_mochi: "#e07c43", sleep_drug: "#b89cff", protein: "#f28d3d", niteropu: "#6255a8", ammo: "#d0942f", laxative: "#788c3b", mercury: "#6e95a8", giant_drug: "#e47b3a", dwarf_drug: "#7773c8", zunda_juice: "#61b94f" })[foodType] || "#f1dfb7";
|
|
finishBehaviorConsumption(t, target, foodType, eating, effectiveDt, `${labelFor(foodType)}\u3092\u98df\u3079\u3066\u3044\u308b`, color);
|
|
const hungerRelief = t.foodHungerReliefFor?.(foodType || target.type, eating, foodType === "sweet" ? 0.75 : (isSpecial ? 0.82 : 0.95)) ?? eating * 0.8;
|
|
t.applyFoodHungerRelief ? t.applyFoodHungerRelief(hungerRelief) : (t.hunger -= hungerRelief);
|
|
t.recoverHealth?.(eating * (foodType === "sweet" ? 0.28 : (isSpecial ? 0.24 : 0.46)));
|
|
t.affection += foodType === "sweet" ? eating * 0.03 : eating * 0.012;
|
|
if (isSpecial && t.applyParamItemEffect) t.applyParamItemEffect(foodType || target.type, target, { source: "eating", nutrition: eating, silentLog: foodType === "love_mochi" || foodType === "fight_mochi" || foodType === "sleep_drug" });
|
|
if (foodType === "sweet") {
|
|
applyNeedRelief(t, { food: -eating * 0.4, fulfill: -eating * 0.2 });
|
|
if (t.explosionDisease) t.recoverExplosionDisease?.("\u305a\u3093\u3060\u9905\u3067\u7206\u767a\u75c5\u304c\u6cbb\u3063\u305f");
|
|
if (t.fightDisease) t.recoverFightDisease?.("\u305a\u3093\u3060\u9905\u3067\u304d\u305a\u3064\u304d\u75c5\u304c\u6cbb\u3063\u305f");
|
|
}
|
|
const fromDuplicator = isDuplicatorFoodSource(target);
|
|
t.makePoop?.(normalPoopUnitsFor(foodType || target.type, eating));
|
|
if (fromDuplicator) {
|
|
t.mealCooldownUntil = Math.max(t.mealCooldownUntil || 0, (world.time || 0) + 1.2);
|
|
t.behaviorLockTimer = Math.max(0, Math.min(t.behaviorLockTimer || 0, 0.18));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (target.type === "grass" && (target.amount || 0) > 0) {
|
|
const bite = window.TarinaiGrass?.regress?.(target, 1) ?? 0;
|
|
if (!(bite > 0)) return false;
|
|
target.eatenAmount = (target.eatenAmount || 0) + bite;
|
|
world.markTerrainDirty?.("grass-eaten-stage");
|
|
world.markItemBucketsDirty?.("grass-eaten-stage");
|
|
finishBehaviorConsumption(t, target, "grass", bite, effectiveDt, "\u8349\u3092\u98df\u3079\u3066\u3044\u308b", "#5b9d39");
|
|
t.applyFoodHungerRelief ? t.applyFoodHungerRelief(bite * 0.46) : (t.hunger -= bite * 0.46);
|
|
t.recoverHealth?.(bite * 0.18);
|
|
applyNeedRelief(t, { food: -2, fulfill: -1 });
|
|
t.makePoop?.(normalPoopUnitsFor("grass", bite));
|
|
return true;
|
|
}
|
|
|
|
if (target.type === "zunchi" && (target.amount || 0) > 0) {
|
|
const foodNeed = Number(t.needRaw?.food ?? t.needs?.food ?? 0) || 0;
|
|
if (zunchiEatingBlocked(t, target, world)) return false;
|
|
if (!t.isZunchiSlave && hasBetterFoodThanZunchiNearby(t, world, 240)) return false;
|
|
const emergencyHunger = (t.hunger || 0) >= 104 || foodNeed >= 94;
|
|
if (!emergencyHunger && !t.isZunchiSlave && target.stage === "fresh" && (target.age || 0) < 150 && foodNeed < 94 && (t.hunger || 0) < 104) return false;
|
|
if (!t.isZunchiSlave && foodNeed < 90 && (t.hunger || 0) < 100) return false;
|
|
const servingScale = Number.isFinite(target.foodServingScale) ? target.foodServingScale : (target.toolSize === "large" ? 1.18 : (target.toolSize === "small" ? 0.82 : 1));
|
|
const bite = Math.min(target.amount, (t.isZunchiSlave ? rand(4.8, 7.4) : rand(3.4, 5.8)) * servingScale);
|
|
if (!(bite > 0)) return false;
|
|
target.amount -= bite;
|
|
finishBehaviorConsumption(t, target, "zunchi", bite, effectiveDt, t.isZunchiSlave ? "\u305a\u3093\u3061\u3069\u308c\u3044\u306a\u306e\u3067\u305a\u3093\u3061\u3092\u98df\u3079\u3066\u3044\u308b" : "\u305a\u3093\u3061\u3092\u98df\u3079\u3066\u3044\u308b", "#6b8d3f");
|
|
t.applyFoodHungerRelief ? t.applyFoodHungerRelief(bite * (t.isZunchiSlave ? 0.66 : 0.18)) : (t.hunger -= bite * (t.isZunchiSlave ? 0.66 : 0.18));
|
|
t.recoverHealth?.(bite * (t.isZunchiSlave ? 0.10 : 0.02));
|
|
applyNeedShock(t, { health: bite * (t.isZunchiSlave ? 0.2 : 1.2), safety: bite * (t.isZunchiSlave ? 0.1 : 0.6) });
|
|
t.makePoop?.(normalPoopUnitsFor("zunchi", bite));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function behaviorFoodTarget(t, world, role = "food", maxDist = 860) {
|
|
const best = findNearestItemWithRole(world, t, role, maxDist);
|
|
if (role === "food") return best;
|
|
const current = currentBehaviorTarget(t) || t.target;
|
|
if (current && !current.dead && (roleMatches(current, role) || (role === "food" && current.type === "duplicator" && current.storedFoodType))) return current;
|
|
return best;
|
|
}
|
|
|
|
function updateConsumableBehavior(t, world, dt, role = "food") {
|
|
if (!t || !world) return false;
|
|
const target = behaviorFoodTarget(t, world, role, role === "food" ? 900 : 700);
|
|
if (!target || target.dead) return false;
|
|
const state = role === "drink" ? "seek_water" : "seek_food";
|
|
const label = role === "drink" ? "\u6c34\u3092\u63a2\u3057\u3066\u3044\u308b" : (role === "medicine" ? "\u56de\u5fa9\u3067\u304d\u308b\u3082\u306e\u3092\u63a2\u3057\u3066\u3044\u308b" : "\u98df\u3079\u7269\u3092\u63a2\u3057\u3066\u3044\u308b");
|
|
const contactDistance = target.type === "duplicator" ? foodInteractionDistance(t, target) : dist(t, target);
|
|
const contactReach = foodInteractionReach(t, target, role);
|
|
if (contactDistance > contactReach) {
|
|
const approachTarget = target.type === "duplicator" ? { x: foodInteractionPoint(target).x, y: foodInteractionPoint(target).y, id: `${target.id || "duplicator"}:dish`, dead: false, hostItem: target } : target;
|
|
t.target = target;
|
|
moveToOrUse(t, approachTarget, state, label);
|
|
return true;
|
|
}
|
|
t.target = target;
|
|
if (consumeBehaviorTarget(t, world, target, role, dt)) return "finished";
|
|
t.setActionState?.(state, { target, reason: label, sleeping: false });
|
|
return t.foodItemHasServingLeft?.(target) ? true : false;
|
|
}
|