fulfillment

This commit is contained in:
33333-33333 2026-06-23 11:27:55 +09:00
commit 2e90add2e1
67 changed files with 5427 additions and 690 deletions

View file

@ -5,7 +5,19 @@
if (!Tarinai) throw new Error("Tarinai is not available for mixin: tarinai_needs_items.js");
Object.defineProperties(Tarinai.prototype, Object.getOwnPropertyDescriptors({
canEatItemType(type) {
return this.isZunchiSlave ? type === "zunchi" : true;
const t = String(type || "");
if (!this.isZunchiSlave) return true;
if (!t || t === "sweet" || t === "grass" || t === "water" || t === "water_bowl") return false;
if (t === "zunchi" || t === "food" || t === "ant_corpse") return true;
const foodDef = global.TarinaiFoodRegistry?.get?.(t);
return Boolean(foodDef && foodDef.interest !== "water");
},
zunchiSlaveFoodTypes() {
const defs = global.TARINAI_FOOD_DEFINITIONS || {};
const types = Object.keys(defs).filter(type => this.canEatItemType(type));
if (!types.includes("zunchi")) types.push("zunchi");
return types;
},
hasLoveMochiEffect() {
@ -146,8 +158,8 @@
startSunbath() {
if (!this.canStartSunbath()) return false;
this.setActionState?.("sunbath", { target: null, reason: "日なたぼっこ", sleeping: false });
this.sunbathTimer = rand(6, 14);
this.sunbathCooldown = rand(16, 34);
this.sunbathTimer = rand(12, 24);
this.sunbathCooldown = rand(8, 18);
this.sunbathFrameTimer = 0;
this.sunbathSpriteVariant = 1 + Math.floor(Math.random() * 3);
this.sunbathRecoveryChecked = false;
@ -166,7 +178,7 @@
}
this.stress = clamp((this.stress || 0) - dt * 1.15, 0, 130);
this.energy = clamp((this.energy || 0) + dt * 0.08, 0, 100);
if (!this.isSunbathTime() || this.hunger > 74 || this.energy < 10 || this.state === "panic") this.sunbathTimer = 0;
if (!this.isSunbathTime() || this.hunger > 86 || this.energy < 8 || this.state === "panic") this.sunbathTimer = 0;
if ((this.sunbathTimer || 0) <= 0 && this.state === "sunbath") {
this.goIdle("");
}
@ -211,6 +223,7 @@
this.fearTimer = Math.max(0, this.fearTimer - dt);
this.intimidateTimer = Math.max(0, this.intimidateTimer - dt);
this.intimidatedTimer = Math.max(0, this.intimidatedTimer - dt);
this.panicTimer = Math.max(0, (this.panicTimer || 0) - dt);
this.blastSpinTimer = Math.max(0, (this.blastSpinTimer || 0) - dt);
this.postBirthPeaceTimer = Math.max(0, (this.postBirthPeaceTimer || 0) - dt);
this.hpBarTimer = Math.max(0, (this.hpBarTimer || 0) - dt);
@ -453,8 +466,13 @@
this.ensureFoodServings(it);
if (!this.isServingFoodItem(it) || (it.foodServingsRemaining || 0) <= 0) return 0;
const value = Number.isFinite(nutrition) ? nutrition : (window.TarinaiFoodRegistry?.nutrition?.(it.type, 0) ?? 0);
it.foodServingsRemaining = Math.max(0, (it.foodServingsRemaining || 0) - 1);
it.amount = it.foodServingsRemaining;
if (!it.infiniteServing) {
it.foodServingsRemaining = Math.max(0, (it.foodServingsRemaining || 0) - 1);
it.amount = it.foodServingsRemaining;
} else {
it.foodServingsRemaining = Math.max(it.foodServingsRemaining || 0, it.foodServingsMax || 1);
it.amount = it.foodServingsRemaining;
}
this.world?.emit?.("item:eaten", { tarinai: this, item: it, type: it.type, nutrition: value });
return value;
},
@ -578,6 +596,20 @@
}
this.stuckPushpinId = null;
}
if ((this.panicTimer || 0) > 0 && this.state === "panic") {
this.setActionState?.("panic", {
target: previousTarget || this.panicDestination?.(null, true) || null,
reason: this.intent?.reasonText || "\u304a\u3069\u308d\u3044\u3066\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b",
wake: true,
});
return;
}
if (window.TarinaiNeedSystem?.runNeedDecision) {
window.TarinaiNeedSystem.runNeedDecision(this, this.world, dt);
return;
}
const friendNearby = this.bestFriendLive ? this.bestFriendLive(FRIEND_AFFINITY_THRESHOLD, 170) : null;
const panicSource = (this.world?.nearbyTarinai?.(this.x, this.y, 128, true) || [])
@ -623,6 +655,16 @@
return;
}
if (this.canStartSunbath?.()) {
const moodBonus = (this.world?.colonyMood?.id === "sun_party") ? 0.72 : 0;
const sickBonus = (this.zunchiDisease || this.explosionDisease || this.fightDisease) ? 0.48 : 0;
const stressBonus = this.stress > 28 ? 0.22 : 0;
const idleBonus = this.state === "idle" ? 0.18 : 0;
if (Math.random() < dt * (0.34 + moodBonus + sickBonus + stressBonus + idleBonus)) {
if (this.startSunbath()) return;
}
}
const dayP = this.world.dayProgress();
const personalSleep = this.sleepStart < this.sleepEnd
? dayP >= this.sleepStart && dayP < this.sleepEnd
@ -793,14 +835,14 @@
}
}
if (friendNearby && this.hunger > 58 && friendNearby.target && (this.isZunchiSlave ? ["zunchi"] : ["sweet", "grass", "ant_corpse"]).includes(friendNearby.target.type) && !friendNearby.target.dead && !this.shouldAvoidTarget(friendNearby.target)) {
if (friendNearby && this.hunger > 58 && friendNearby.target && (this.isZunchiSlave ? this.canEatItemType(friendNearby.target.type) : ["sweet", "grass", "ant_corpse"].includes(friendNearby.target.type)) && !friendNearby.target.dead && !this.shouldAvoidTarget(friendNearby.target)) {
this.setActionState?.("seek_food", { target: friendNearby.target, reason: "友達と一緒に食べに行く" });
this.foodReactTimer = Math.max(this.foodReactTimer, 0.24);
return;
}
if (this.hunger > 62) {
const itemTypes = this.isZunchiSlave ? ["zunchi"] : ["sweet", ...((window.TarinaiFoodRegistry?.typesByInterest?.("grass_like", "corpse")) || ["grass", "ant_corpse"])];
const itemTypes = this.isZunchiSlave ? this.zunchiSlaveFoodTypes() : ["sweet", ...((window.TarinaiFoodRegistry?.typesByInterest?.("grass_like", "corpse")) || ["grass", "ant_corpse"])];
const item = this.world.nearest(this, itemTypes, 420);
if (item) {
this.setActionState?.("seek_food", { target: item, reason: "食べ物を探している" });
@ -856,12 +898,13 @@
interactWithItems(dt) {
const canBite = !this.isZunchiSlave && this.eatCooldown <= 0 && this.hunger > 18;
const canSweetBite = !this.isZunchiSlave && this.eatCooldown <= 0 && this.hunger > 7;
const slaveCanFoodBite = this.isZunchiSlave && this.eatCooldown <= 0 && this.hunger > 16;
const canZunchiBite = this.eatCooldown <= 0 && this.hunger > (this.isZunchiSlave ? 16 : 104);
for (const it of this.world.nearbyItems(this.x, this.y, this.radius + 42)) {
const d = dist(this, it);
if (d > this.radius + it.r + 12) continue;
if (((it.type === "sweet" && canSweetBite) || (it.type === "food" && canBite)) && this.foodItemHasServingLeft(it)) {
if (((it.type === "sweet" && canSweetBite) || (it.type === "food" && (canBite || (slaveCanFoodBite && this.canEatItemType(it.type))))) && this.foodItemHasServingLeft(it)) {
const eating = this.consumeFoodServing(it);
if (eating > 0) {
this.beginEating(it, `${toolLabel(it.type)}を食べている`);
@ -896,7 +939,7 @@
}
}
if (canSweetBite && (it.type === "love_mochi" || it.type === "fight_mochi" || it.type === "sleep_drug" || (typeof isParamEffectItemType === "function" && isParamEffectItemType(it.type))) && this.foodItemHasServingLeft(it)) {
if ((canSweetBite || (slaveCanFoodBite && this.canEatItemType(it.type))) && (it.type === "love_mochi" || it.type === "fight_mochi" || it.type === "sleep_drug" || (typeof isParamEffectItemType === "function" && isParamEffectItemType(it.type))) && this.foodItemHasServingLeft(it)) {
const eating = this.consumeFoodServing(it);
if (eating <= 0) continue;
this.beginEating(it, `${toolLabel(it.type)}を食べている`);
@ -946,9 +989,14 @@
this.grassEatTimer = Math.max(this.grassEatTimer, 0.22);
this.vx *= Math.pow(0.20, dt * 60);
this.vy *= Math.pow(0.20, dt * 60);
it.amount -= bite;
it.eatenAmount = (it.eatenAmount || 0) + bite;
it.growth = clamp((it.growth ?? it.amount / 120) - bite / 150, 0.04, 1.2);
if (!it.infiniteServing) {
it.amount -= bite;
it.eatenAmount = (it.eatenAmount || 0) + bite;
it.growth = clamp((it.growth ?? it.amount / 120) - bite / 150, 0.04, 1.2);
} else {
it.amount = Math.max(it.amount || 0, 120);
it.growth = Math.max(it.growth || 0, 0.82);
}
this.applyFoodHungerRelief ? this.applyFoodHungerRelief(bite * 0.46) : (this.hunger -= bite * 0.46);
this.recoverHealth(bite * 0.18);
this.stress -= dt * 0.5;
@ -965,13 +1013,14 @@
break;
}
if (canBite && it.type === "ant_corpse") {
if ((canBite || (slaveCanFoodBite && this.canEatItemType(it.type))) && it.type === "ant_corpse") {
const bite = Math.min(it.amount, rand(3.2, 5.4));
if (bite > 0) {
this.beginEating(it, "アリの死骸を食べている");
this.vx *= Math.pow(0.18, dt * 60);
this.vy *= Math.pow(0.18, dt * 60);
it.amount -= bite;
if (!it.infiniteServing) it.amount -= bite;
else it.amount = Math.max(it.amount || 0, 24);
this.applyFoodHungerRelief ? this.applyFoodHungerRelief(bite * 0.62) : (this.hunger -= bite * 0.62);
this.recoverHealth(bite * 0.12);
this.stress = clamp(this.stress + bite * 0.05 * this.personalityProfile().fear, 0, 130);
@ -1000,7 +1049,8 @@
this.beginEating(it, this.isZunchiSlave ? "ずんちどれいなのでずんちを食べている" : "ずんちを食べている");
this.vx *= Math.pow(0.18, dt * 60);
this.vy *= Math.pow(0.18, dt * 60);
it.amount -= bite;
if (!it.infiniteServing) it.amount -= bite;
else it.amount = Math.max(it.amount || 0, 240);
this.applyFoodHungerRelief ? this.applyFoodHungerRelief(bite * (this.isZunchiSlave ? 0.66 : 0.18)) : (this.hunger -= bite * (this.isZunchiSlave ? 0.66 : 0.18));
this.recoverHealth(bite * (this.isZunchiSlave ? 0.10 : 0.02));
this.stress = clamp(this.stress + bite * (this.isZunchiSlave ? 0.06 : 0.52) * this.personalityProfile().fear, 0, 130);
@ -1025,7 +1075,8 @@
audio.play?.("sfx_drink", { category: "ops", minGap: 0.20 });
this.nextDrinkSound = (this.world?.time || 0) + 0.72;
}
if (it.type === "water") it.amount -= dt * 2.4;
if (it.type === "water" && !it.infiniteServing) it.amount -= dt * 2.4;
if (it.type === "water" && it.infiniteServing) it.amount = Math.max(it.amount || 0, 120);
if (this.type === "teary" || this.type === "cry") this.affection += dt * 0.18;
}