This commit is contained in:
33333-33333 2026-07-05 18:01:36 +09:00
commit 8f9f5b5100
108 changed files with 2139 additions and 1500 deletions

View file

@ -5,11 +5,37 @@
const Tarinai = global.Tarinai;
if (!Tarinai) throw new Error("Tarinai is not available for mixin: tarinai_disease_nest.js");
function isNestContainerItem(item) {
return Boolean(item && !item.dead && (typeof global.isNestContainerType === "function" ? global.isNestContainerType(item.type) : (item.type === "nest_box" || item.type === "pipe")));
return Boolean(global.TarinaiToolRuntime?.isNestContainerItem?.(item));
}
function containerLabel(item) { return item?.type === "pipe" ? "\u571f\u7ba1" : "\u5de3\u7bb1"; }
function containerInsideSleepText(item, verb = "\u7720\u3063\u3066\u3044\u308b") { return `${containerLabel(item)}\u306e\u4e2d\u3067${verb}`; }
function containerFullText(item) { return `${containerLabel(item)}\u304c\u3044\u3063\u3071\u3044\u3067\u5165\u308c\u306a\u3044`; }
function targetShelterItem(target = null) {
return isNestContainerItem(target?.shelterItem) ? target.shelterItem : (isNestContainerItem(target?.hostItem) ? target.hostItem : null);
}
function targetReferencesNestBox(target = null, box = null) {
if (!box || !isNestContainerItem(box)) return false;
return target === box || targetShelterItem(target) === box;
}
function zunchiDiseaseTemperatureInfectionMultiplier(actor = null) {
const worldRef = actor?.world || global.world || null;
if (!actor || !worldRef) return 1;
const felt = worldRef.feltTemperatureFor?.(actor) ?? worldRef.temperatureAt?.(actor.x, actor.y, actor) ?? actor.feltTemperature ?? actor.tempComfort ?? (CONFIG.standardTemperature ?? 15);
const status = worldRef.temperatureStatusFor?.(felt, actor) || null;
if (!status || status.comfortable) return 1;
const discomfort = Math.max(0, Number(status.discomfort || 0) || 0);
if (status.direction === "hot") return clamp(1 + discomfort * 0.070, 1, 2.85);
if (status.direction === "cold") return clamp(1 - discomfort * 0.030, 0.42, 1);
return 1;
}
if (typeof global !== "undefined") global.zunchiDiseaseTemperatureInfectionMultiplier = zunchiDiseaseTemperatureInfectionMultiplier;
function temperatureShelterStillUseful(t, box = null) {
if (!t || !box || !t.world) return false;
const now = Number(t.world.time || 0) || 0;
if (now < Number(t._nestBoxShelterUntil || 0)) return true;
const status = t.world.temperatureStatusFor?.(t.world.feltTemperatureFor?.(t) ?? t.tempComfort ?? t.feltTemperature ?? (CONFIG.standardTemperature ?? 15), t);
return Boolean(status && !status.comfortable && (status.discomfort || 0) >= 1.2 && targetReferencesNestBox(t.target, box));
}
Object.defineProperties(Tarinai.prototype, Object.getOwnPropertyDescriptors({
infectZunchiDisease(source = null, force = false) {
@ -23,7 +49,6 @@
this.thought = "\u305a\u3093\u3061\u75c5\u306b\u304b\u304b\u3063\u305f";
audio.disease?.();
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.20, "\u3046\u3045\u2026", "rgba(38,82,38,0.82)");
this.world?.emit?.("disease:infected", { tarinai: this, diseaseId: "disease_zunchi", source });
this.recordChangeCause?.("\u75c5\u6c17\u767a\u75c7", "\u305a\u3093\u3061\u75c5");
return true;
},
@ -38,19 +63,18 @@
this.thought = reason || "\u305a\u3093\u3061\u75c5\u304c\u6cbb\u307e\u3063\u305f";
audio.heal?.();
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.18, "\u306f\u3046", "rgba(62,104,62,0.78)");
this.world?.emit?.("disease:cured", { tarinai: this, diseaseId: "disease_zunchi", reason });
this.recordChangeCause?.("\u75c5\u6c17\u56de\u5fa9", "\u305a\u3093\u3061\u75c5");
return true;
},
emitZunchiDiseaseEffect(dt) {
if (!this.zunchiDisease || !this.world?.effects) return;
if (deterministicChance(this.world, "zunchi-disease-miasma", dt * (1.2 + (this.zunchiDiseaseSeverity || 0) * 1.4), this)) {
if (deterministicChance(this.world, "zunchi-disease-miasma", dt * (0.48 + (this.zunchiDiseaseSeverity || 0) * 0.62), this)) {
this.world.effects.push(new Effect("zunchi_miasma", this.x + deterministicRange(this.world, "zunchi-miasma-x", -this.radius * 0.45, this.radius * 0.45, this), this.y - this.radius * deterministicRange(this.world, "zunchi-miasma-y", 0.05, 0.72, this), {
vx: deterministicRange(this.world, "zunchi-miasma-vx", -5, 5, this),
vy: deterministicRange(this.world, "zunchi-miasma-vy", -10, -3, this),
life: deterministicRange(this.world, "zunchi-miasma-life", 1.4, 2.4, this),
size: deterministicRange(this.world, "zunchi-miasma-size", 8, 18, this) * (0.8 + (this.zunchiDiseaseSeverity || 0) * 0.45),
life: deterministicRange(this.world, "zunchi-miasma-life", 1.6, 2.8, this),
size: deterministicRange(this.world, "zunchi-miasma-size", 12, 26, this) * (1.0 + (this.zunchiDiseaseSeverity || 0) * 0.55),
color: "rgba(16,78,28,0.70)",
}));
}
@ -63,7 +87,9 @@
this.zunchiStain = clamp(this.zunchiStain - dt * 2.4, 0, 100);
if (this.zunchiDisease) this.zunchiDiseaseSeverity = Math.max(0, (this.zunchiDiseaseSeverity || 1) - dt * 0.006);
}
const zunchiSelfInfectChance = this.diseaseChance ? this.diseaseChance(dt * clamp((this.zunchiStain - 82) / 18, 0, 1) * 0.045) : dt * clamp((this.zunchiStain - 82) / 18, 0, 1) * 0.045;
const zunchiTempInfectMul = zunchiDiseaseTemperatureInfectionMultiplier(this);
const zunchiSelfInfectBase = dt * clamp((this.zunchiStain - 82) / 18, 0, 1) * 0.045 * zunchiTempInfectMul;
const zunchiSelfInfectChance = this.diseaseChance ? this.diseaseChance(zunchiSelfInfectBase) : zunchiSelfInfectBase;
if (!this.zunchiDisease && this.zunchiStain > 82 && deterministicChance(this.world, "zunchi-self-infect", zunchiSelfInfectChance, this)) {
this.infectZunchiDisease(null, true);
this.world?.log?.(`${this.name}\u306f\u305a\u3093\u3061\u75c5\u3092\u767a\u75c7\u3057\u305f\u3002`, "accident", { participants: [this] });
@ -93,7 +119,6 @@
audio.disease?.();
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.18, "Zzz...", "rgba(72,76,120,0.76)");
this.world?.log?.(`${this.name}\u306f\u306d\u3080\u308a\u75c5\u306b\u304b\u304b\u3063\u305f\u3002`, "accident", { participants: [this] });
this.world?.emit?.("disease:infected", { tarinai: this, diseaseId: "disease_sleep", source });
this.recordChangeCause?.("\u75c5\u6c17\u767a\u75c7", "\u306d\u3080\u308a\u75c5");
return true;
},
@ -111,7 +136,6 @@
this.surpriseTimer = Math.max(this.surpriseTimer || 0, 0.65);
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.18, "!", "rgba(72,76,120,0.76)");
this.world?.log?.(`${this.name}\u306e\u306d\u3080\u308a\u75c5\u304c\u6cbb\u3063\u305f\u3002`, "accident", { participants: [this] });
this.world?.emit?.("disease:cured", { tarinai: this, diseaseId: "disease_sleep", reason });
this.recordChangeCause?.("\u75c5\u6c17\u56de\u5fa9", "\u306d\u3080\u308a\u75c5");
return true;
},
@ -125,7 +149,6 @@
audio.disease?.();
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.2, "!", "rgba(178,78,42,0.82)");
this.world?.log?.(`${this.name}\u306f\u7206\u767a\u75c5\u3092\u767a\u75c7\u3057\u305f\u3002`, "accident", { participants: [this] });
this.world?.emit?.("disease:infected", { tarinai: this, diseaseId: "disease_explosion", source });
this.recordChangeCause?.("\u75c5\u6c17\u767a\u75c7", "\u7206\u767a\u75c5");
return true;
},
@ -139,7 +162,6 @@
if (typeof applyNeedRelief === "function") applyNeedRelief(this, { health: -22, safety: -8 });
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.18, "\u306f\u3046", "rgba(82,126,88,0.76)");
this.world?.log?.(`${this.name}\u306e\u7206\u767a\u75c5\u304c\u6cbb\u3063\u305f\u3002`, "accident", { participants: [this] });
this.world?.emit?.("disease:cured", { tarinai: this, diseaseId: "disease_explosion", reason });
this.recordChangeCause?.("\u75c5\u6c17\u56de\u5fa9", "\u7206\u767a\u75c5");
return true;
},
@ -157,7 +179,6 @@
audio.disease?.();
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.20, "?", "rgba(150,78,44,0.80)");
this.world?.log?.(`${this.name}\u306f\u304d\u305a\u3064\u304d\u75c5\u3092\u767a\u75c7\u3057\u305f\u3002`, "fight", { participants: [this] });
this.world?.emit?.("disease:infected", { tarinai: this, diseaseId: "disease_fight", source });
this.recordChangeCause?.("\u75c5\u6c17\u767a\u75c7", "\u304d\u305a\u3064\u304d\u75c5");
return true;
},
@ -171,7 +192,6 @@
if (typeof applyNeedRelief === "function") applyNeedRelief(this, { health: -24, safety: -10 });
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.18, "\u306f\u3046", "rgba(82,126,88,0.76)");
this.world?.log?.(`${this.name}\u306e\u304d\u305a\u3064\u304d\u75c5\u304c\u6cbb\u3063\u305f\u3002`, "fight", { participants: [this] });
this.world?.emit?.("disease:cured", { tarinai: this, diseaseId: "disease_fight", reason });
this.recordChangeCause?.("\u75c5\u6c17\u56de\u5fa9", "\u304d\u305a\u3064\u304d\u75c5");
return true;
},
@ -251,16 +271,22 @@
if (!isNestContainerItem(box)) return false;
const now = this.world?.time || 0;
if (this._nestBoxAvoidId === box.id && now < (this._nestBoxAvoidUntil || -Infinity)) return false;
if (this.insideNestBoxId === box.id && (this.world?.time || 0) < (this.nestBoxStayUntil || -Infinity) && (this.state === "sleep" || this.state === "seek_bed" || this.sleeping)) return true;
if (!(this.state === "sleep" || this.state === "seek_bed" || this.sleeping)) return false;
return this.target === box || this.insideNestBoxId === box.id;
const sleepIntent = this.state === "sleep" || this.state === "seek_bed" || this.sleeping;
const temperatureIntent = this.state === "seek_temperature" || targetReferencesNestBox(this.target, box) || temperatureShelterStillUseful(this, box);
if (this.insideNestBoxId === box.id && now < (this.nestBoxStayUntil || -Infinity) && (sleepIntent || temperatureIntent)) return true;
if (this.insideNestBoxId === box.id && temperatureShelterStillUseful(this, box)) return true;
if (!(sleepIntent || temperatureIntent)) return false;
return targetReferencesNestBox(this.target, box) || this.insideNestBoxId === box.id;
},
enterNestBox(box, dt = 0.016, options = {}) {
if (!isNestContainerItem(box) || this.dead) return false;
const alreadyInside = this.insideNestBoxId === box.id;
if (!alreadyInside && box?.type === "nest_box" && globalThis.TarinaiItemDynamicToolSystem?.nestBoxFireActive?.(box)) {
return this.abortNestBoxEntry?.(box, "巣箱が燃えていて入れない") || false;
}
const capacity = this.world?.nestBoxCapacity ? this.world.nestBoxCapacity(box) : 5;
let occupants = this.world?.nestBoxOccupants ? this.world.nestBoxOccupants(box, Infinity) : [];
const alreadyInside = this.insideNestBoxId === box.id;
const now = this.world?.time || 0;
if (!alreadyInside && occupants.length >= capacity) {
const kingSleepDisplace = Boolean(this.isTarinaiChampion && box.type === "nest_box" && (this.state === "seek_bed" || this.state === "sleep" || this.sleeping));
@ -279,11 +305,15 @@
}
if (occupants.length >= capacity) return this.abortNestBoxEntry?.(box, containerFullText(box)) || false;
}
const shelterPurpose = options?.purpose === "temperature" || this.state === "seek_temperature" || targetShelterItem(this.target) === box;
if (shelterPurpose) {
this._nestBoxShelterUntil = Math.max(Number(this._nestBoxShelterUntil || 0) || 0, now + 12);
}
if (!alreadyInside) {
this._nestBoxSeekStartedAt = NaN;
this._nestBoxSeekTargetId = "";
const entry = this.world?.nestBoxEntryPoint ? this.world.nestBoxEntryPoint(box) : { x: box.x, y: box.y };
this.nestBoxStayUntil = Math.max(this.nestBoxStayUntil || -Infinity, now + 14);
this.nestBoxStayUntil = Math.max(this.nestBoxStayUntil || -Infinity, now + (shelterPurpose ? 12 : 14));
this.nestBoxSlotIndex = Math.min(occupants.length, Math.max(0, capacity - 1));
this._nestBoxEntryStartX = Number.isFinite(this.x) ? this.x : entry.x;
this._nestBoxEntryStartY = Number.isFinite(this.y) ? this.y : entry.y;
@ -355,14 +385,40 @@
this._nestBoxExitEndY = Number.isFinite(exit.y) ? exit.y : this._nestBoxExitDoorY;
this._nestBoxExitDuration = 0.58;
this._nestBoxExitSmoothUntil = now + this._nestBoxExitDuration;
if (b?.type === "nest_box" && globalThis.TarinaiItemDynamicToolSystem?.nestBoxFireActive?.(b)) {
this._burningNestExitBoxId = b.id || "";
this._burningNestExitUntil = Math.max(Number(this._burningNestExitUntil || 0) || 0, now + this._nestBoxExitDuration + 1.2);
}
this.nestFade = Math.max(this.nestFade || 0, 0.96);
return true;
},
forceIgniteFromBurningNestExit(box = null) {
const now = Number(this.world?.time || 0) || 0;
const b = box || this.nestBoxById?.(this._burningNestExitBoxId);
const active = Boolean(b?.type === "nest_box" && globalThis.TarinaiItemDynamicToolSystem?.nestBoxFireActive?.(b));
const taggedExit = Boolean(this._burningNestExitBoxId && now < Number(this._burningNestExitUntil || 0));
if (!active && !taggedExit) return false;
if (!this.burning || !(this.burnTimer > 0.02)) {
if (typeof this.igniteFire === "function") this.igniteFire(b || box || null, true);
else {
const text = globalThis.TarinaiItemDynamicToolSystem?.fireBurningText || "\u71c3\u3048\u3066\u9003\u3052\u307e\u3069\u3063\u3066\u3044\u308b\u3002";
this.burning = true;
this.burnTimer = Math.max(Number(this.burnTimer || 0) || 0, 5.2);
this.thought = text;
this.sleeping = false;
this.setActionState?.("panic", { target: null, reason: text, wake: true, sleeping: false });
}
}
this._burningNestExitUntil = Math.max(Number(this._burningNestExitUntil || 0) || 0, now + 0.75);
return true;
},
updateNestBoxExitAnimation(dt = 0.016) {
const now = this.world?.time || 0;
const until = this._nestBoxExitSmoothUntil || -Infinity;
if (!(now < until)) return false;
if (this._burningNestExitBoxId && now < Number(this._burningNestExitUntil || 0)) this.forceIgniteFromBurningNestExit?.(this.nestBoxById?.(this._burningNestExitBoxId));
const duration = Math.max(0.20, Number(this._nestBoxExitDuration || 0.58) || 0.58);
const progress = clamp(1 - Math.max(0, until - now) / duration, 0, 1);
const eased = progress * progress * (3 - 2 * progress);
@ -416,6 +472,7 @@
this.sleepSession = null;
if (this.state === "sleep" || this.state === "seek_bed") this.goIdle?.(`${containerLabel(b)}\u304b\u3089\u51fa\u305f`);
this.nestBoxStayUntil = -Infinity;
this._nestBoxShelterUntil = 0;
this.nestBoxSlotIndex = -1;
this._nestBoxEntrySmoothUntil = -Infinity;
this._nestBoxEntryStartX = NaN;
@ -425,13 +482,20 @@
this._nestBoxEntryDuration = 0;
this._nestBoxSeekStartedAt = NaN;
this._nestBoxSeekTargetId = "";
if (b?.type === "nest_box" && globalThis.TarinaiItemDynamicToolSystem?.nestBoxFireActive?.(b)) {
const now = Number(this.world?.time || 0) || 0;
this._burningNestExitBoxId = b.id || this._burningNestExitBoxId || "";
this._burningNestExitUntil = Math.max(Number(this._burningNestExitUntil || 0) || 0, now + 1.4);
this.forceIgniteFromBurningNestExit?.(b);
}
return true;
},
updateNestBoxPresence(dt = 0) {
if (this.updateNestBoxExitAnimation?.(dt || 0.016)) return null;
const currentBox = this.nestBoxById(this.insideNestBoxId);
const targetBox = isNestContainerItem(this.target) ? this.target : null;
const shelterBox = targetShelterItem(this.target);
const targetBox = isNestContainerItem(this.target) ? this.target : shelterBox;
const box = currentBox || targetBox;
if (!box) {
this.insideNestBoxId = null;
@ -533,7 +597,6 @@
this.nextBurnVoiceAt = now + 2.6;
this.bubble?.("\u3042\u3061\u3085\u3044\uff01", 1.35, "rgba(214,82,38,0.90)", { force: true });
}
this.world?.emit?.("fire:ignite", { tarinai: this, source });
this.recordChangeCause?.("\u708e\u4e0a", "\u708e\u4e0a");
return true;
},
@ -546,7 +609,6 @@
this.hurtTimer = Math.max(this.hurtTimer || 0, 0.35);
if (reason) this.thought = reason;
this.world?.effects?.push(new Effect("ring", this.x, this.y, { size: Math.max(22, this.radius * 1.2), life: 0.22, color: "rgba(108,192,236,0.60)" }));
this.world?.emit?.("fire:extinguish", { tarinai: this, reason });
return true;
},
@ -562,8 +624,24 @@
this.fearTimer = Math.max(this.fearTimer || 0, 0.8);
this.sleeping = false;
this.eatTimer = 0;
const currentNest = this.nestBoxById?.(this.insideNestBoxId);
if (currentNest) {
const water = globalThis.TarinaiItemDynamicToolSystem?.contactWaterDrop?.(this.world, currentNest.x, currentNest.y, Math.max(this.radius || 18, (currentNest.r || 42) * 1.15));
if (water) {
globalThis.TarinaiItemDynamicToolSystem?.consumeWaterForFire?.(this.world, water, Math.max(10, (this.radius || 18) * 0.55));
this.extinguishFire("水で消火された");
return;
}
const mul = globalThis.TarinaiItemDynamicToolSystem?.fireTempMultiplier?.(this.world, currentNest.x, currentNest.y, this) || 1;
if (deterministicChance(this.world, "tarinai-nest-fire-extinguish", dt * 0.014 * mul, this, currentNest)) {
this.extinguishFire("自然に火が消えた");
return;
}
if (this.burnTimer <= 0.01) this.extinguishFire("自然に火が消えた");
return;
}
if (typeof applyNeedShock === "function") applyNeedShock(this, { health: dt * 6.2, safety: dt * 3.0 });
if (typeof this.damage === "function") this.damage(dt * 3.8, "\u708e\u4e0a");
if (typeof this.damage === "function") this.damage(dt * 3.8, "炎上");
else this.energy = clamp((this.energy || 0) - dt * 0.12, 0, typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(this) : (Number(this.maxEnergy) || 100));
this.hurtTimer = Math.max(this.hurtTimer || 0, 0.22);
const water = globalThis.TarinaiItemDynamicToolSystem?.contactWaterDrop?.(this.world, this.x, this.y, Math.max(this.radius || 18, 16));
@ -578,12 +656,12 @@
return;
}
for (const viewer of [...(this.world?.nearbyTarinai?.(this.x, this.y, 190, true) || [])]) {
if (!viewer || viewer === this || viewer.dead) continue;
if (!viewer || viewer === this || viewer.dead || viewer.insideNestBoxId || this.world?.isTarinaiHiddenInNestBox?.(viewer)) continue;
globalThis.TarinaiItemDynamicToolSystem?.panicTarinaiFromFire?.(viewer, this, this.world);
}
const contactRadius = Math.max(20, (this.radius || 18) * 1.1);
for (const other of [...(this.world?.nearbyTarinai?.(this.x, this.y, contactRadius + 28, true) || [])]) {
if (!other || other === this || other.dead) continue;
if (!other || other === this || other.dead || other.insideNestBoxId || this.world?.isTarinaiHiddenInNestBox?.(other)) continue;
if (distXY(this.x, this.y, other.x, other.y) <= contactRadius + Math.max(other.radius || 18, 12) * 0.72) other.igniteFire?.(this, false);
}
for (const ant of [...(this.world?.nearbyAnts?.(this.x, this.y, contactRadius + 18, true) || [])]) {